Skip to content

Commit

Permalink
Merge pull request #45 from Snugug/feature/js-gen
Browse files Browse the repository at this point in the history
Combine Validation and UX script generation
  • Loading branch information
scottnath authored Jul 5, 2016
2 parents b51119e + 137d417 commit b3af802
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 306 deletions.
13 changes: 1 addition & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,30 +171,19 @@ content.only('my-awesome-content-type').then(type => {
{{form.html | safe}}
<fieldset id="sunrise-sunset">
<legend>Select Sunrise and Sunset</legend>
<label for="sunrise-date"><input type="date" name="sunrise-date" id="sunrise-date" value="{{data['sunrise-date'].value}}"></label>
<label for="sunrise-time"><input type="time" name="sunrise-time" id="sunrise-time" value="{{data['sunrise-time'].value}}"></label>
<label for="sunset-date"><input type="date" name="sunset-date" id="sunset-date" value="{{data['sunset-date'].value}}"></label>
<label for="sunset-time"><input type="time" name="sunset-time" id="sunset-time" value="{{data['sunset-time'].value}}"></label>
</fieldset>
<button type="submit">Submit</button>
<button type="cancel">Cancel</button>
</form>
<script>
{{form.validation | safe}}
{{form.scripts | safe}}
</script>
<script type="text/javascript" src="/js/sunrise-sunset.js"></script>
```

#### Form response object

```javascript
form.html // string of wrapped form elements; should be placed inside a <form> tag
form.validation // validation, wrapped for browser via browserify
form.script // UX scripts, wrapped for browser via browserify
form.script // Validation and UX scripts, wrapped for browser via browserify
```
10 changes: 1 addition & 9 deletions lib/form.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
'use strict';

const html = require('./form/html');
const validation = require('./form/validation');
const scripts = require('./form/scripts');

const validate = require('./form/validate');

// const scripts = require('./form/scripts');
// const css = require('./form/css');


Expand All @@ -16,10 +12,6 @@ const form = (type, errors) => {
return html(type, errors).then(result => {
rendered.html = result;

return validation(type);
}).then(result => {
rendered.validation = result;

return scripts(type);
}).then(result => {
rendered.scripts = result;
Expand All @@ -30,4 +22,4 @@ const form = (type, errors) => {


module.exports = form;
module.exports.validate = validate;
module.exports.scripts = scripts;
39 changes: 39 additions & 0 deletions lib/form/js/ux.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/* eslint-env browser */
/* global allPlugins, allInputs, allSettings, allIDs */

// TODO: Add In-Browser Tests
(function formUX() {
var script = function (e) {
var target = e.target;
var inp = allInputs[target.id];
var input = {};
var settings = {};

// Set up script input
input.target = target;
input.all = {};
input.all[inp.name] = target;

// Set up script settings
settings.target = inp.settings;
settings.all = {};
settings.all[inp.name] = inp.settings;

// Set all sibling input and settings
Object.keys(inp.siblings).forEach(function (sibling) {
input.all[sibling] = allIDs[inp.siblings[sibling]];
settings.all[sibling] = allSettings[inp.parent.id][sibling];
});

// Run the script
script = allPlugins[inp.parent.type].scripts[inp.script.function](input, settings);

};

// Add all event listeners
Object.keys(allInputs).forEach(function (id) {
if (allInputs[id].hasOwnProperty('script')) {
allIDs[id].addEventListener(allInputs[id].script.on, script);
}
});
}());
92 changes: 92 additions & 0 deletions lib/form/js/validation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/* eslint-env browser */
/* global allPlugins, allInputs, allSettings, allIDs */

// TODO: Add In-Browser Tests
(function formValidation() {
var valueCheck = function (input, settings, validation) {
if (input.target.required && (input.target.required === 'save' || input.target.required === 'publish') && input.target.value === '') {
return 'Field cannot be left blank!';
}

return validation(input, settings);
};

// Validation Function
var validate = function (e) {
var target = e.target;
var value = target.value;
var inp = allInputs[target.id];
var parent = allIDs[inp.parent.id];
var input = {};
var settings = {};
var validation = false;
var message;
var currentMessage = parent.querySelector('[role="alert"][for="' + target.id + '"]');
var insertRef;

// Set up validation input
input.target = {
value: value,
name: inp.name,
required: inp.required,
};
input.all = {};
input.all[inp.name] = value;

// Set up validation settings
settings.target = inp.settings;
settings.all = {};
settings.all[inp.name] = inp.settings;

// Set all sibling input and settings
Object.keys(inp.siblings).forEach(function (sibling) {
input.all[sibling] = allIDs[inp.siblings[sibling]].value;
settings.all[sibling] = allSettings[inp.parent.id][sibling];
});

// Run the validation
validation = valueCheck(input, settings, allPlugins[inp.parent.type].validation[inp.validation.function]);

// Add/Remove validation
if (validation === true) {
// Check to see if there is a current alert message for this input
if (currentMessage) {
// Remove invalid
target.removeAttribute('aria-invalid');

// Delete the current message if it exists
parent.removeChild(currentMessage);
}
}
else {
// Create error message
message = document.createElement('p');
message.className = 'form--alert';
message.setAttribute('role', 'alert');
message.setAttribute('for', target.id);
message.textContent = validation;

// Check to see if there is a current alert message for this input
if (!message.isEqualNode(currentMessage)) {
// Set element to invalid
target.setAttribute('aria-invalid', 'true');

if (parent.nodeName.toLowerCase() !== 'fieldset') {
// If parent isn't a fieldset, add message before first child
insertRef = parent.firstChild;
parent.insertBefore(message, insertRef);
}
else {
// If parent is a fieldset, add message after either the description, if it exists, or the legend
insertRef = parent.querySelector('.form--description') || parent.querySelector('legend');
parent.insertBefore(message, insertRef.nextSibling);
}
}
}
};

// Add all event listeners
Object.keys(allInputs).forEach(function (id) {
allIDs[id].addEventListener(allInputs[id].validation.on, validate);
});
}());
84 changes: 32 additions & 52 deletions lib/form/scripts.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,22 @@

const browserify = require('browserify');
const stream = require('stream');
const fs = require('fs');
const path = require('path');

const formJS = fs.readdirSync(path.join(__dirname, 'js')).map(script => {
return fs.readFileSync(path.join(__dirname, 'js', script));
}).join('\n\n');

// const util = require('../util');

const rendered = (type) => {
return new Promise((res, rej) => {
const b = browserify();
const s = new stream.Readable();

const scriptFunctions = {};
const allScriptSettings = {};
const allPlugins = {};
const allSettings = {};
const inputs = {};
const ids = {};
let plugins = type.attributes.map(plugin => {
Expand All @@ -23,14 +31,14 @@ const rendered = (type) => {
plugins.forEach(plugin => {
// Require Plugins for Browserify
b.require(`input-plugin-${plugin}`);
scriptFunctions[plugin] = `input-plugin-${plugin}`;
allPlugins[plugin] = `input-plugin-${plugin}`;
});

type.attributes.map(plugin => {
const children = Object.keys(plugin.inputs);

// All settings for this plugin
allScriptSettings[plugin.id] = {};
allSettings[plugin.id] = {};

// Add ID to all IDs
ids[plugin.id] = plugin.id;
Expand All @@ -49,23 +57,25 @@ const rendered = (type) => {
});

// Set settings for this input blank as default
allScriptSettings[plugin.id][input] = {};
allSettings[plugin.id][input] = {};

// Set to actual settings if it exists
if (inp.hasOwnProperty('settings')) {
allScriptSettings[plugin.id][input] = inp.settings;
allSettings[plugin.id][input] = inp.settings;
}

// Build input object for this specific input
inputs[inp.id] = {};
inputs[inp.id].name = input;
inputs[inp.id].validation = inp.validation;
inputs[inp.id].script = inp.script;
inputs[inp.id].required = inp.required;
inputs[inp.id].settings = {};
inputs[inp.id].siblings = {};
inputs[inp.id].parent = {
id: plugin.id,
type: plugin.type,
};
inputs[inp.id].siblings = {};

if (inp.hasOwnProperty('settings')) {
inputs[inp.id].settings = inp.settings;
Expand All @@ -81,65 +91,35 @@ const rendered = (type) => {
});
});

const scripts = `
var allScripts = ${JSON.stringify(scriptFunctions)};
var allScriptInputs = ${JSON.stringify(inputs)};
var allScriptSettings = ${JSON.stringify(allScriptSettings)};
var allScriptIDs = ${JSON.stringify(ids)};
const formScript = `
var allPlugins = ${JSON.stringify(allPlugins)};
var allInputs = ${JSON.stringify(inputs)};
var allSettings = ${JSON.stringify(allSettings)};
var allIDs = ${JSON.stringify(ids)};
// Work those requires
Object.keys(allScripts).forEach(function(plugin) {
var plug = require(allScripts[plugin]);
Object.keys(allPlugins).forEach(function(plugin) {
var plug = require(allPlugins[plugin]);
// Require the script from the inputs
allScripts[plugin] = plug.scripts;
// Require the plugin
allPlugins[plugin] = plug;
});
// Script Function
var script = function (e) {
var target = e.target;
var inp = allScriptInputs[target.id];
var input = {};
var settings = {};
// Set up script input
input.target = target;
input.all = {};
input.all[inp.name] = target;
// Set up script settings
settings.target = inp.settings;
settings.all = {};
settings.all[inp.name] = inp.settings;
// Set all sibling input and settings
Object.keys(inp.siblings).forEach(function (sibling) {
input.all[sibling] = allScriptIDs[inp.siblings[sibling]];
settings.all[sibling] = allScriptSettings[inp.parent.id][sibling];
});
// Run the script
script = allScripts[inp.parent.type][inp.script.function](input, settings);
};
// Add Document Event Listener
document.addEventListener('DOMContentLoaded', function () {
// Get all elements so we don't need to constantly read from the DOM
Object.keys(allScriptIDs).forEach(function (id) {
allScriptIDs[id] = document.getElementById(id);
Object.keys(allIDs).forEach(function (id) {
allIDs[id] = document.getElementById(id);
});
// Add all event listeners
Object.keys(allScriptInputs).forEach(function (id) {
if (allScriptInputs[id].hasOwnProperty('script')) {
allScriptIDs[id].addEventListener(allScriptInputs[id].script.on, script);
}
});
// Inlcude all form scripts
${formJS}
});`;

s._read = function read() {};

s.push(scripts);
s.push(formScript);
s.push(null);

b.add(s);
Expand Down
Loading

0 comments on commit b3af802

Please sign in to comment.