compose-form-up
Advanced tools
Comparing version 1.0.0 to 1.1.0
190
index.js
@@ -1,189 +0,9 @@ | ||
// Dependencies | ||
var toolbox = require( 'compose-toolbox' ), | ||
Event = toolbox.event, | ||
matches = toolbox.matches, | ||
getClosest = toolbox.getClosest, | ||
wordCount = toolbox.wordCount, | ||
textSelectors = '[required]'; | ||
var validation = require( './lib/validation' ) | ||
var progressive = require( './lib/progressive' ) | ||
// Does this browser support HTML5 validation? | ||
function supported() { | ||
module.exports = { | ||
return typeof document.createElement( 'input' ).checkValidity === 'function' | ||
validate: validation.validate, | ||
next: progressive.next, | ||
} | ||
// Watch for events ( if validation is suported ) | ||
function watch() { | ||
if ( !supported() ) { return false } | ||
Event.bubbleFormEvents() | ||
Event.on( document.body, { | ||
invalid: function( event ) { event.preventDefault() }, // Suppress default message bubbles | ||
submit: submit | ||
}) | ||
// Watch input events | ||
Event.on( document, 'blur', '[required]', checkValidation ) | ||
Event.on( document, 'keydown', '[required]', Event.debounce( checkValidation, 200 ) ) | ||
} | ||
function validateForm( form ) { | ||
// Scoped variables | ||
var invalidInput = form.querySelector( 'input:invalid, textarea:invalid' ) | ||
// The form is valid, skip it | ||
if ( !invalidInput ) { | ||
return true | ||
} | ||
else { | ||
// Set validity state on element | ||
checkInput( invalidInput ) | ||
// Show validation message | ||
showMessage( invalidInput ) | ||
focus( invalidInput ) | ||
return false | ||
} | ||
} | ||
// Handler for validation events | ||
var checkValidation = Event.callback.new( function( event ) { | ||
// Remove any pre-existing validation message | ||
hideMessage( event.target ) | ||
checkInput( event.target, event.type ) | ||
}) | ||
function checkInput( input, event ) { | ||
var el = statusEl( input ), | ||
valid = isValid( input ), | ||
neutral = !valid && event == 'keydown'; | ||
// Don't trigger invalid style while typing | ||
if ( neutral && input == document.activeElement ) { | ||
el.classList.remove( 'invalid', 'valid' ) | ||
} else { | ||
el.classList.toggle( 'invalid', !valid ) | ||
el.classList.toggle( 'valid', valid ) | ||
} | ||
} | ||
// Is an input valid? | ||
function isValid( input ) { | ||
// If element only contains whitespace, strip value | ||
if ( !input.value.replace( /\s/g, '' ).length ) | ||
input.value = '' | ||
// Set a custom validation message for word count | ||
if ( input.dataset.minWords ) checkCount( input, 'min' ) | ||
if ( input.dataset.maxWords ) checkCount( input, 'max' ) | ||
return input.checkValidity() | ||
} | ||
// Test custom validation for maximum or minimum words present | ||
function checkCount( input, limit ) { | ||
var goal = input.dataset[ limit + 'Words' ], | ||
lessThanGoal = wordCount( input.value ) < goal | ||
var phrasing = ( limit == 'min' ) ? 'at least ' : 'no more than ', | ||
valid = ( limit == 'min' ) ? !lessThanGoal : lessThanGoal, | ||
message = ''; | ||
// Set a custom error message | ||
if ( input.value && !valid ) | ||
message = 'Please write ' + phrasing + goal + ' words.' | ||
input.setCustomValidity( message ) | ||
} | ||
// If input is nested in a label, treat the label as the | ||
// target for assigning status (class names and messages). | ||
function statusEl( input ) { | ||
return getClosest( input, 'label' ) || input | ||
} | ||
// Focus() if invalid element is not hidden | ||
// or Focus its immediate sibling (mostly used for upload buttons) | ||
function focus( el ) { | ||
el = ( el.style.display !== 'none' ) ? el : el.nextSibling | ||
el.focus() | ||
} | ||
// Submission validation handler function | ||
function submit( event ) { | ||
// Skip validation if no invalid inputs found | ||
if ( !validateForm( event.target ) ) { | ||
// Pause keydown/blur triggers for the next second to avoid neutral empty style | ||
checkValidation.stop() | ||
Event.delay( checkValidation.start, 1000 ) | ||
// Stop the submission event | ||
event.preventDefault() | ||
} | ||
} | ||
function hideMessage( el ) { | ||
var form = getClosest( el, 'form' ), | ||
msg = form.querySelector( '.validation-message' ) | ||
if (msg ) msg.parentNode.removeChild( msg ) | ||
} | ||
// Validation message handler function | ||
function showMessage( el ) { | ||
hideMessage( el ) | ||
var label = getClosest( el, 'label' ), | ||
message = el.dataset.message || el.validationMessage | ||
if ( label ) | ||
label.insertAdjacentHTML( 'beforeend', '<aside class="validation-message"><p>' + message + '</p></aside>' ) | ||
} | ||
// Public API | ||
module.exports = { | ||
watch: watch, | ||
validate: validateForm | ||
} | ||
{ | ||
"name": "compose-form-up", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"description": "A lightweight HTML5 form validation utility", | ||
@@ -33,4 +33,4 @@ "main": "index.js", | ||
"chai": "^3.5.0", | ||
"mochify": "^2.17.0" | ||
"mochify": "^2.18.1" | ||
} | ||
} |
# FormUp | ||
A lightwight HTML5 form validation module. | ||
A lightwight form utlitiy module. | ||
### `watch()` - Automatic | ||
## Validation | ||
FormUp enhances HTML5's form validation, adding `valid` and `invalid` classes as the form is completed, and handling custom messages with a bit more finesse. | ||
### Automatic validation | ||
FormUp can watch for form submission events and stop them and style invalid | ||
@@ -15,7 +19,9 @@ inputs, and add messages if the form is invalid. This will also watch for | ||
var formUp = require( 'compose-form-up' ) | ||
formUp.watch() | ||
``` | ||
### `validate( form )` - Manual | ||
Whenever `submit` is triggered on a form, validation will | ||
automatically run, handling messages and styles. | ||
### `validate( form )` - Manual Validation | ||
This component works best automatically, but if for some reason you have to | ||
@@ -38,1 +44,57 @@ manually trigger validation, you'd do this. | ||
invalid styles you'll need to run this any time a form element is updated. | ||
## Progressive Forms | ||
A progressive form will show one fieldset at a time. Each time a submit event is triggered, the active fieldset will transition away (assuming there are no | ||
validation errors), and the next fieldset will appear. Once all fieldsets are complete, the form will submit as usual. You can also register a callback to be | ||
triggered at the point of each transition, to call some scrip before continuing (like an ajax call or whatever). | ||
Here's what a simple progressive form may look like. | ||
```html | ||
<form id="some-form" class="progressive"> | ||
<fieldset class="form-step"> | ||
<!-- Some inputs --> | ||
<button type='submit'>Submit</button> | ||
</fieldset> | ||
<fieldset class="form-step"> | ||
<!-- Some more inputs --> | ||
<button type='submit'>Submit</button> | ||
</fieldset> | ||
</form> | ||
``` | ||
### Handling the next event | ||
```js | ||
var formUp = require( 'compose-form-up' ) | ||
var form = document.querySelector( '#some-form' ) | ||
formUp.next( form, function( event, step ) { | ||
// event is the submission event | ||
// step.forward() - go to the next step | ||
// step.back() - return to this step | ||
// step.fieldset - reference the current fieldset element | ||
// step.form - reference to the current form element | ||
// step.complete - true/false - is this the last step? | ||
// step.formData - formData object for the current fieldset ( handy for ajax ) | ||
// Example ajax | ||
ajax.post( '/users' ) | ||
.send( step.formData ) | ||
.end( function( err, response ) { | ||
if (!err) step.forward() // go to the next fielset | ||
else step.back() // return to current fieldset | ||
}) | ||
}) | ||
``` |
@@ -15,4 +15,17 @@ var assert = require('chai').assert | ||
container: function() { | ||
var div = document.querySelector('.container') | ||
if ( !div ){ | ||
div = Utils.injectHTML( document.body, '<div class="container"></div>' ) | ||
} else { | ||
div.innerHTML = '' | ||
} | ||
return div | ||
}, | ||
addInput: function( form, options ) { | ||
options = options || {} | ||
defaults = { | ||
@@ -24,3 +37,3 @@ required: true, | ||
var label = Utils.injectHTML( form, '<label></label>' ) | ||
var input = Utils.injectHTML( label, '<input />' ) | ||
var input = Utils.injectHTML( label, '<input>' ) | ||
@@ -38,2 +51,3 @@ for ( var attr in defaults ) { input.setAttribute( attr, defaults[attr] ) } | ||
setValue: function( input, value ) { | ||
input.setAttribute( 'value', value ) | ||
input.value = value | ||
@@ -40,0 +54,0 @@ Event.fire( input, 'blur' ) |
171
test/test.js
@@ -1,74 +0,159 @@ | ||
var assert = require( 'chai' ).assert | ||
var formUp = require( '../' ) | ||
var utils = require( './_utils' ) | ||
var isValid = utils.isValid | ||
var isInvalid = utils.isInvalid | ||
var setValue = utils.setValue | ||
formUp.watch() | ||
var assert = require( 'chai' ).assert, | ||
formUp = require( '../' ), | ||
utils = require( './_utils' ), | ||
isValid = utils.isValid, | ||
isInvalid = utils.isInvalid, | ||
setValue = utils.setValue, | ||
Event = require( 'compose-event' ) | ||
describe( 'validate', function() { | ||
describe( 'formup', function() { | ||
var form = utils.injectHTML( document.body, '<form></form>' ) | ||
var form = utils.injectHTML( utils.container(), '<form class="progressive"></form>' ) | ||
form.innerHTML = '<fieldset id="fieldsetOne" class="form-step"></fieldset>\ | ||
<fieldset id="fieldsetTwo" class="form-step"></fieldset>\ | ||
<fieldset class="form-step"></fieldset>' | ||
beforeEach( function() { | ||
form.innerHTML = "" | ||
}) | ||
var fieldsetOne = form.querySelector('#fieldsetOne') | ||
var fieldsetTwo = form.querySelector('#fieldsetTwo') | ||
it( 'required inputs', function() { | ||
Event.fire( document, 'DOMContentLoaded' ) | ||
Event.fire( document, 'page:change' ) | ||
var input = utils.addInput( form, {}) | ||
describe( 'validate', function() { | ||
setValue( input, '' ) | ||
isInvalid( input ) | ||
it( 'styles valid and invalid inputs', function() { | ||
setValue( input, 'test' ) | ||
isValid( input ) | ||
var input = utils.addInput( fieldsetOne ) | ||
}) | ||
setValue( input, '' ) | ||
isInvalid( input ) | ||
it( 'maximum words', function() { | ||
setValue( input, 'test' ) | ||
isValid( input ) | ||
var input = utils.addInput( form, { 'data-max-words': '3' }) | ||
fieldsetOne.removeChild( fieldsetOne.lastChild ) | ||
}) | ||
setValue( input, 'two words' ) | ||
isValid( input ) | ||
it( 'tests maximum words', function() { | ||
}) | ||
var input = utils.addInput( fieldsetOne, { 'data-max-words': '3' }) | ||
it( 'minimum words', function() { | ||
setValue( input, 'two words' ) | ||
isValid( input ) | ||
var input = utils.addInput( form, { 'data-min-words': '3' }) | ||
setValue( input, 'too many words here' ) | ||
isInvalid( input ) | ||
setValue( input, 'two words' ) | ||
isInvalid( input ) | ||
fieldsetOne.removeChild( fieldsetOne.lastChild ) | ||
}) | ||
}) | ||
it( 'sets custom validation messages', function() { | ||
it( 'tests minimum words', function() { | ||
var input = utils.addInput( form, { 'data-min-words': '3' }) | ||
var input = utils.addInput( fieldsetOne, { 'data-min-words': '3' }) | ||
setValue( input, 'two words' ) | ||
formUp.validate( form ) | ||
setValue( input, 'two words' ) | ||
isInvalid( input ) | ||
// Check custom validation message | ||
assert.equal( input.parentNode.textContent, 'Please write at least 3 words.' ) | ||
setValue( input, 'three words here' ) | ||
isValid( input ) | ||
setValue( input, 'here are three' ) | ||
fieldsetOne.removeChild( fieldsetOne.lastChild ) | ||
// Check that messages are hidden when input is valid | ||
assert.equal( input.parentNode.textContent, '' ) | ||
}) | ||
it( 'sets custom validation messages', function() { | ||
var input = utils.addInput( fieldsetOne, { 'data-min-words': '3' }) | ||
setValue( input, 'two words' ) | ||
formUp.validate( form ) | ||
// Check custom validation message | ||
assert.equal( input.parentNode.textContent, 'Please write at least 3 words.' ) | ||
setValue( input, 'here are three' ) | ||
// Check that messages are hidden when input is valid | ||
assert.equal( input.parentNode.textContent, '' ) | ||
fieldsetOne.removeChild( fieldsetOne.lastChild ) | ||
}) | ||
it( 'traps form submissions', function() { | ||
var input = utils.addInput( fieldsetOne, { 'data-min-words': '3' }) | ||
setValue( input, 'two words' ) | ||
utils.submit( form ) | ||
assert.equal( input.parentNode.textContent, 'Please write at least 3 words.' ) | ||
fieldsetOne.removeChild( fieldsetOne.lastChild ) | ||
}) | ||
}) | ||
it( 'traps form submissions', function() { | ||
describe( 'progressive form', function() { | ||
var input = utils.addInput( form, { 'data-min-words': '3' }) | ||
formUp.next( form, function( event, step ) { | ||
setValue( input, 'two words' ) | ||
utils.submit( form ) | ||
// Manufacture a reason to go back | ||
if ( step.fieldset.classList.contains('back') ) | ||
step.back() | ||
else | ||
step.forward() | ||
assert.equal( input.parentNode.textContent, 'Please write at least 3 words.' ) | ||
}) | ||
it( 'hides all but the first fieldset', function() { | ||
assert.isTrue( !fieldsetOne.disabled ) | ||
assert.isTrue( fieldsetTwo.disabled ) | ||
}) | ||
it( 'does not progress if an input is invalid', function() { | ||
// Add an invalid input (no value) | ||
var input = utils.addInput( fieldsetOne ) | ||
utils.submit( form ) | ||
assert.isTrue( !fieldsetOne.disabled ) | ||
assert.isTrue( fieldsetTwo.disabled ) | ||
fieldsetOne.removeChild( fieldsetOne.lastChild ) | ||
}) | ||
it( 'progresses if an input is valid', function( ) { | ||
// Add a valid input | ||
var input = utils.addInput( fieldsetOne, { value: 'true' } ) | ||
utils.submit( form ) | ||
assert.isTrue( fieldsetOne.disabled ) | ||
assert.isTrue( !fieldsetTwo.disabled ) | ||
fieldsetOne.removeChild( fieldsetOne.lastChild ) | ||
}) | ||
it( 'can step back', function( ) { | ||
// Add a valid input | ||
var input = utils.addInput( fieldsetTwo, { value: 'true' } ) | ||
fieldsetTwo.classList.add( 'back' ) | ||
utils.submit( form ) | ||
assert.isTrue( !fieldsetTwo.disabled ) | ||
}) | ||
}) | ||
}) |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
19135
9
414
99
1