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 hidden or 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