Last active
May 5, 2017 14:50
-
-
Save rodyhaddad/2e7464d1b9bc79d61a2345cba7020ebb to your computer and use it in GitHub Desktop.
Library to add CSS classes to form inputs, describing their different states
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
window.formify = function formify(form, options) { | |
if (!(form instanceof HTMLFormElement)) { | |
throw new Error('Tried to Formify a non-form element: ' + form); | |
} | |
var opt = { | |
classPrefix: options.classPrefix || 'fm' | |
}; | |
var validityKeys = [ | |
'badInput', | |
'customError', | |
'patternMismatch', | |
'rangeOverflow', | |
'rangeUnderflow', | |
'stepMismatch', | |
'tooLong', | |
'tooShort', | |
'typeMismatch', | |
'valueMissing' | |
]; | |
var inputChangeHandlers = []; | |
Array.from(form.elements).forEach(function(el) { | |
inputChangeHandlers.push({ | |
el: el, | |
listener: setupFormElement(el) | |
}); | |
}); | |
listenInputChange(form, handleInputChange, true) | |
function handleInputChange(evt) { | |
inputChangeHandlers.forEach(function(handler) { | |
handler.listener(evt); | |
}); | |
} | |
function setupFormElement(el) { | |
if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement || el instanceof HTMLButtonElement)) { | |
throw new Error('Tried to setup a non-input-ish element: ' + el); | |
} | |
var state = { | |
dirty: false, | |
touched: false, | |
valid: true, | |
empty: true, | |
invalidKeys: [], | |
}; | |
el.addEventListener('blur', changeStateFn('touched', true)); | |
listenInputChange(el, changeStateFn('dirty', true)); | |
var pTarget = el.getAttribute('data-formify-ptarget'); | |
var targets = [el]; | |
if (pTarget) { | |
var pIndex = parseInt(pTarget, 10); | |
var pEl = el; | |
for (var i = 0; i < pIndex; i++) { | |
pEl = el.parentNode; | |
} | |
targets.push(pEl); | |
} | |
updateValidity(); | |
return updateValidity; | |
function changeStateFn(stateName, val) { | |
return function() { | |
if (state[stateName] !== val) { | |
state[stateName] = val; | |
updateClasses(); | |
} | |
} | |
} | |
function updateValidity() { | |
var validity = el.validity; | |
state.empty = !el.value; | |
if (['checkbox', 'radio'].indexOf(el.type) !== -1) { | |
state.empty = !el.checked; | |
} | |
if (validity) { | |
state.valid = validity.valid; | |
state.invalidKeys = []; | |
validityKeys.forEach(function(key) { | |
if (validity[key]) { | |
state.invalidKeys.push(key); | |
} | |
}); | |
} | |
updateClasses(); | |
} | |
function updateClasses() { | |
targets.forEach(function(el) { | |
updateClass(state.dirty, '-dirty', '-pristine'); | |
updateClass(state.touched, '-touched', '-untouched'); | |
updateClass(state.valid, '-valid', '-invalid'); | |
updateClass(state.empty, '-empty', '-not-empty'); | |
function updateClass(val, trueClass, falseClass) { | |
if (val) { | |
el.classList.add(opt.classPrefix + trueClass); | |
el.classList.remove(opt.classPrefix + falseClass); | |
} else { | |
el.classList.add(opt.classPrefix + falseClass); | |
el.classList.remove(opt.classPrefix + trueClass); | |
} | |
} | |
validityKeys.forEach(function(key) { | |
if (state.invalidKeys.indexOf(key) === -1) { | |
el.classList.remove(opt.classPrefix + '-invalid-' + toKebabCase(key)); | |
} else { | |
el.classList.add(opt.classPrefix + '-invalid-' + toKebabCase(key)); | |
} | |
}); | |
}); | |
} | |
} | |
function listenInputChange(el, listener, useTarget) { | |
var handled = false; | |
var handleInputChange = function(evt) { | |
if (!handled) { | |
handled = true; | |
listener(evt); | |
setTimeout(function() { | |
handled = false; | |
}); | |
} | |
}; | |
if (useTarget) { | |
handled = []; | |
handleInputChange = function(evt) { | |
var target = evt.target; | |
if (handled.indexOf(target) === -1) { | |
handled.push(target); | |
listener(evt); | |
setTimeout(function() { | |
var index; | |
if ((index = handled.indexOf(target)) !== -1) { | |
handled.splice(index, 1); | |
} | |
}); | |
} | |
}; | |
} | |
el.addEventListener('input', handleInputChange); | |
el.addEventListener('change', handleInputChange); | |
} | |
function toKebabCase(str) { | |
return str.replace(/([A-Z])/g, function($1) { | |
return "-" + $1.toLowerCase(); | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
If you're interested in a demo & explanation for this: http://bit.ly/formify-demo2