typeof3

Benchmark created on


Test runner

Ready to run.

Testing in
TestOps/sec
1
/*!
 * Forms5 JavaScript Library
 * HTML5 Web Forms 2.0 for everyone!
 * 
 * Copyright (c) 2011 David Svetlik (davidsvetlik@hotmail.com)
 * Licensed under BSD or MIT license
 * http://davidsvetlik.cz/forms5/license.txt
 * http://creativecommons.org/licenses/BSD/
 * http://creativecommons.org/licenses/MIT/
 * 
 * Version 2
 * 
 * Supported attributes:
 * - autofocus (all)
 * - pattern (text input)
 * - placeholder (text-based input, textarea)
 * - required (all)
 * 
 * Input types:
 * - email
 * - number
 * 
 * JavaScript API:
 * - validity JS object (input, textarea, select) - only field.validity.valid [boolean]
 * - checkValidity() method for both form and field elements
 * 
 * Other:
 * - custom initialization with specific options (parents, events)
 */
(function(window, document) {

 var F5 = {
  version: 2,
  isInitialized: false,
  isUsed: false,
  used: function() {
   this.isUsed = true;
  },

  // attributes with native browser support
  // taken from Modernizr, many thanks!
  available: (function(a) {
   for (var i = 0, l = a.length, e = document.createElement('input'), n = [];
   i < l;
   i++) {
    n[a[i]] = !! (a[i] in e);
   };
   return n;
  })(['autofocus', 'max', 'min', 'pattern', 'placeholder', 'required', 'validity']),

  formFieldsTags: ['input', 'textarea', 'select'],

  classNames: {
   valid: 'valid',
   invalid: 'invalid',
   placeholder: 'placeholder'
  },

  // unleash the beast!
  // F5.init(), F5.init(parentId), F5.init(options), F5.init(parentId, options)
  init: function() {
   var parentId = typeof arguments[0] == 'string' ? arguments[0] : null,
       options = (parentId && typeof arguments[1] == 'object') ? arguments[1] : typeof arguments[0] == 'object' ? arguments[0] : {},
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       // get parent element
       parentElement = options.parent ? options.parent : parentId ? document.getElementById(parentId) : document.body,
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       // custom action to do after all invalid values were found when submitting form
       onInvalidForm = typeof options.onInvalidForm == 'function' ? options.onInvalidForm : null,
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       // custom action to do if are all values valid and required fields filled when submitting form; like some ajax stuff etc.
       onValidForm = typeof options.onValidForm == 'function' ? options.onValidForm : null,
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       // get all matched forms
       forms = (parentElement.tagName === 'FORM') ? [parentElement] : parentElement.getElementsByTagName('FORM'),
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       // get all form fields  (inputs, textareas and selects)
       fields = (parentElement.querySelectorAll) ? parentElement.querySelectorAll(F5.formFieldsTags.join(',')) : (function(elements) {
     for (var i = 0, l = elements.length, f = []; i < l; i++) {
      if (F5.isFormField(elements[i])) {
       f.push(elements[i]);
      }
     }
     return f;
    })(parentElement.getElementsByTagName('*'));

   // provide some HTML5 forms functionality if there is no native support
   for (var i = 0, l = fields.length; i < l; i++) {
    F5.initField(fields[i]);
   }

   for (var j = 0, lj = forms.length; j < lj; j++) {
    if (!F5.available.validity && typeof forms[j].checkValidity != 'function') {
     forms[j].checkValidity = function() {
      var fields = F5.getAllFormFields(this);

      for (var i = 0, l = fields.length; i < l; i++) {
       if (!fields[i].checkValidity()) {
        return false;
       }
      }

      return true;
     }
    }

    F5.used();
   }

   // set forms submit check
   if (F5.isUsed || onInvalidForm || onValidForm) {
    F5.on(forms, 'submit', function(event) {
     var i, l, form = event.target || event.srcElement || null,
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         // some browsers adds fieldsets to forms' "elements" collection, so let's just make our selection clear 
         fields = F5.getAllFormFields(form),
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         invalidFields = [],
         placeholderFields = [];

     // collect all invalid fields...
     for (i = 0, l = fields.length; i < l; i++) {
      if (typeof fields[i].validity == 'undefined') {
       F5.initField(fields[i]);
      }

      if (!fields[i].validity.valid && (!F5.available.validity || onInvalidForm)) {
       invalidFields.push(fields[i]);
      }

      // collect remaining placeholders to get rid of them later
      if (!F5.available.placeholder && fields[i].value === fields[i].placeholder && fields[i].className.split(' ').indexOf(F5.classNames.placeholder) !== -1) {
       placeholderFields.push(fields[i]);
      }
     }

     // ...and more important, choose what to do
     if (invalidFields.length > 0) {
      if (onInvalidForm) {
       onInvalidForm(event, invalidFields);
      }
      else {
       F5.preventDefault(event);
       invalidFields[0].focus();
      }
     }
     else {
      for (i = 0, l = placeholderFields.length; i < l; i++) {
       placeholderFields[i].value = '';
      }

      if (onValidForm) {
       onValidForm(event);
      }
     }
    });

    F5.used();
   }

   F5.isInitialized = true;
  },

  // methods
  // init single field
  initField: function(element) {
   var field = new F5.field(element);

   field.initPattern();
   field.initPlaceholder();
   field.initRequired();
   field.initValidity();
   field.initAutofocus();
  },

  // validate element
  validate: function(eventOrElement) {
   var element = eventOrElement.target || eventOrElement.srcElement || eventOrElement || false;
   if (!element) {
    return false;
   }

   var isDisabled = !! element.disabled,
       isPlaceholder = (element.value === element.placeholder && F5.classNames.placeholder && element.className.split(' ').indexOf(F5.classNames.placeholder) !== -1),
       isRequired = !! element.required,
       isValid = (isDisabled) ? true : (element.tagName === 'SELECT') ? (function(e, i) {
     return (!isRequired || (isRequired && !! e.options[i].value));
    })(element, element.selectedIndex) : !! (function(e, t) {
     switch (t) {
     case 'checkbox':
      return ((isRequired && e.checked) || !isRequired);

     case 'radio':
      var i, l, group = e.form[e.name],
          groupInvalid = [],
          isChecked = false;

      for (i = 0, l = group.length; i < l; i++) {
       isChecked = (group[i].checked || isChecked);

       if (group[i] !== e && !group[i].disabled && group[i].validity && !group[i].validity.valid) {
        groupInvalid.push(group[i]);
       }
      }

      if (isChecked) {
       for (i = 0, l = groupInvalid.length; i < l; i++) {
        groupInvalid[i].validity.valid = true;
        F5.removeClassName(groupInvalid[i], F5.classNames.invalid);
        F5.addClassName(groupInvalid[i], F5.classNames.valid);
       }
      }

      return ((isRequired && isChecked) || !isRequired);

     case 'number':
      var value = parseInt(e.value) || null,
          min = parseInt(e.min) || null,
          max = parseInt(e.max) || null;

      return (value || (!isRequired && !e.value)) && (!min || min <= value) && (!max || max >= value);

     default:
      if (isPlaceholder) {
       return !isRequired;
      }

      // it seems that HTML5 pattern behaves slightly different than JS regular expression test
      // - in HTML5 (cross-browser) doesn't matter if pattern has start and end characters (^ and $)
      var pattern = e.pattern,
          patternRegExp = (typeof pattern == 'string') ? new RegExp((pattern[0] !== '^' && pattern[pattern.length - 1] !== '$') ? '^' + pattern + '$' : pattern) : (t === 'email') ? /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/ : false;

      return (patternRegExp && e.value) ? patternRegExp.test(e.value) : ((isRequired && e.value) || !isRequired);
     }
    })(element, element.getAttribute('type')),
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       classNameArray = F5.removeClassName(element.className, [F5.classNames.valid, F5.classNames.invalid]);

   if (!isDisabled && !isPlaceholder) {
    classNameArray.push(isValid ? F5.classNames.valid : F5.classNames.invalid);
   }

   element.className = classNameArray.join(' ');
   element.validity.valid = isValid;

   return isValid;
  },

  // attach event listener && on function cross-browser
  attachEventListener: (function() {
   if (window.addEventListener) {
    return function(element, eventType, listener) {
     element.addEventListener(eventType, listener, false);
    };
   }
   else if (window.attachEvent) { // IE
    return function(element, eventType, listener) {
     element.attachEvent('on' + eventType, listener);
    };
   }
   else return function() {};
  })(),

  // detach event listener && off function cross-browser
  detachEventListener: (function() {
   if (window.removeEventListener) {
    return function(element, eventType, listener) {
     element.removeEventListener(eventType, listener, false);
    };
   }
   else if (window.detachEvent) { // IE
    return function(element, eventType, listener) {
     element.detachEvent('on' + eventType, listener);
    };
   }
   else return function() {};
  })(),

  // on and off functions for attaching or detaching multiple events on multiple elements
  on: function(elements, eventTypes, listener, action) {
   var elements = (elements === window || F5.isFormField(elements)) ? [elements] : F5.toArray(elements),
       eventTypes = eventTypes.split(' '),
       action = action || F5.attachEventListener;

   for (var i = 0, l = elements.length; i < l; i++) {
    for (var j = 0, lj = eventTypes.length; j < lj; j++) {
     action(elements[i], eventTypes[j], listener);
    }
   }

   return true;
  },

  off: function(elements, eventTypes, listener) {
   return F5.on(elements, eventTypes, listener, F5.detachEventListener);
  },

  // prevent default action
  preventDefault: function(event) {
   if (event.preventDefault) {
    event.preventDefault();
   }
   event.returnValue = false; // IE
  },

  addClassName: function(element, className) {
   if (element.className !== '') {
    element.className += ' ';
   }
   element.className += className;
  },

  // remove class name(s)
  // "element" can be either real element or string (class name) which results in returning array
  // "classNames" are expected to be a string or an array of strings
  removeClassName: function(element, classNames) {
   var classNames = (typeof classNames == 'string') ? classNames.split(' ') : classNames,
       classNameArray = [],
       returnArray = false;

   if (typeof element == 'string') {
    returnArray = true;
    if (element !== classNames.join(' ') && element !== '') {
     classNameArray = element.split(' ');
    }
   }
   else {
    if (element.className !== classNames.join(' ')) {
     classNameArray = element.className.split(' ');
    }
   }

   // search & destroy
   for (var i = 0, l = classNameArray.length; i < l; i++) {
    for (var j = 0, lj = classNames.length; j < lj; j++) {
     if (classNameArray[i] === classNames[j]) {
      classNameArray.splice(i, 1);
      break;
     }
    }
   }

   if (returnArray) {
    return classNameArray;
   }
   else {
    element.className = classNameArray.join(' ');
    return true;
   }
  },

  getAllFormFields: function(form) {
   return (form.querySelectorAll) ? form.querySelectorAll(F5.formFieldsTags.join(',')) : (function(elements) {
    for (var i = 0, l = elements.length, f = []; i < l; i++) {
     if (F5.isFormField(elements[i])) {
      f.push(elements[i]);
     }
    }
    return f;
   })(form.elements);
  },

  toArray: function(someObject) {
   return (typeof someObject == 'object' && someObject.length > 0) ? someObject : [someObject];
  },

  isFormField: function(element) {
   return (element.tagName && F5.formFieldsTags.indexOf(element.tagName.toLowerCase()) >= 0);
  }
 };


 // field class
 F5.field = function(element) {
  this.element = element;
 };

 // field class methods
 F5.field.prototype = {
  // pattern attribute
  initPattern: function() {
   if (!F5.available.pattern) {
    var pattern = this.element.getAttribute('pattern');
   }

   if (pattern) {
    this.element.pattern = pattern;

    F5.used();
   }
  },

  // placeholder attribute
  initPlaceholder: function() {
   if (!F5.available.placeholder) {
    var placeholder = this.element.getAttribute('placeholder');
   }

   if (placeholder) {
    this.element.placeholder = placeholder;

    // separated into two conditions on purpose because of form values browsers' storage
    if (this.element.value === '') {
     this.element.value = placeholder;
    }
    if (this.element.value === placeholder) {
     F5.addClassName(this.element, F5.classNames.placeholder);
    }

    F5.on(this.element, 'focus', function(event) {
     var element = event.target || event.srcElement || false;
     if (!element) {
      return false;
     }
     if (element.value === element.placeholder && element.className.split(' ').indexOf(F5.classNames.placeholder) !== -1) {
      element.value = '';
      F5.removeClassName(element, F5.classNames.placeholder);
     }
    });

    F5.on(this.element, 'blur', function(event) {
     var element = event.target || event.srcElement || false;
     if (!element) {
      return false;
     }
     if (element.value === '') {
      element.value = element.placeholder;
      F5.addClassName(element, F5.classNames.placeholder);
     }
    });

    F5.used();
   }
  },

  // required attribute
  initRequired: function() {
   if (!F5.available.required) {
    this.element.required = (typeof this.element.getAttribute('required') == 'string');

    F5.used();
   }
  },

  // set validation and create ValidityState object... sort of
  initValidity: function() {
   if (!F5.available.validity) {
    var realType = this.element.getAttribute('type');

    this.element.validity = {
     valid: true
    };

    if (typeof this.element.checkValidity != 'function') {
     this.element.checkValidity = function() {
      return F5.validate(this);
     };
    }

    F5.validate(this.element);

    if (realType === 'checkbox' || realType === 'radio') {
     // not using 'change' event because of IEs' bug
     F5.on(this.element, 'click keyup', F5.validate);
    }
    else if (realType !== this.element.type && realType === 'number') {
     if (!F5.available.min) {
      this.element.min = this.element.getAttribute('min');
     }
     if (!F5.available.max) {
      this.element.max = this.element.getAttribute('max');
     }

     this.element.setAttribute('autocomplete', 'off');

     F5.on(this.element, 'keydown', function(event) {
      var element = event.target || event.srcElement || false,
          keyCode = event.keyCode;

      if (!element) {
       return false;
      }

      if (keyCode === 38 || keyCode === 40) {
       var value = parseInt(element.value) || 0,
           min = parseInt(element.min) || null,
           max = parseInt(element.max) || null;
      }

      if (keyCode === 38) {
       var number = (min && min > value) ? min : value + 1;

       if (!max || (max && max >= number)) {
        element.value = number;
       }
      }
      else if (keyCode === 40) {
       var number = (max && max < value) ? max : value - 1;

       if (!min || (min && min <= number)) {
        element.value = number;
       }
      }
      else {
       F5.validate(element);
      }
     });

     F5.on(this.element, 'blur', function(event) {
      var element = event.target || event.srcElement || false,
          value = parseInt(element.value);

      element.value = value || '';
     });

     F5.on(this.element, 'keyup blur', F5.validate);
    }
    else {
     F5.on(this.element, 'keyup blur', F5.validate);
    }

    F5.used();
   }
  },

  // autofocus attribute, following Chrome's & Opera's implementation - last element with autofocus will be focused (...and there shouldn't be more than one)
  initAutofocus: function() {
   if (!F5.available.autofocus && typeof this.element.getAttribute('autofocus') == 'string') {
    this.element.focus();

    F5.used();
   }
  }
 };


 // lets make F5 with it's functions globally accessible object, hurray!
 window.F5 = F5;


 // extend Array objects by indexOf
 // http://erik.eae.net/playground/arrayextras/arrayextras.js
 if (!Array.prototype.indexOf) {
  Array.prototype.indexOf = function(obj, fromIndex) {
   if (fromIndex == null) {
    fromIndex = 0;
   } else if (fromIndex < 0) {
    fromIndex = Math.max(0, this.length + fromIndex);
   }
   for (var i = fromIndex, l = this.length; i < l; i++) {
    if (this[i] === obj) {
     return i;
    }
   }
   return -1;
  };
 }

})(window, window.document);
ready
2
/*!
 * Forms5 JavaScript Library
 * HTML5 Web Forms 2.0 for everyone!
 * 
 * Copyright (c) 2011 David Svetlik (davidsvetlik@hotmail.com)
 * Licensed under BSD or MIT license
 * http://davidsvetlik.cz/forms5/license.txt
 * http://creativecommons.org/licenses/BSD/
 * http://creativecommons.org/licenses/MIT/
 * 
 * Version 2
 * 
 * Supported attributes:
 * - autofocus (all)
 * - pattern (text input)
 * - placeholder (text-based input, textarea)
 * - required (all)
 * 
 * Input types:
 * - email
 * - number
 * 
 * JavaScript API:
 * - validity JS object (input, textarea, select) - only field.validity.valid [boolean]
 * - checkValidity() method for both form and field elements
 * 
 * Other:
 * - custom initialization with specific options (parents, events)
 */
(function(window, document) {

 var F5 = {
  version: 2,
  isInitialized: false,
  isUsed: false,
  used: function() {
   this.isUsed = true;
  },

  // attributes with native browser support
  // taken from Modernizr, many thanks!
  available: (function(a) {
   for (var i = 0, l = a.length, e = document.createElement('input'), n = [];
   i < l;
   i++) {
    n[a[i]] = !! (a[i] in e);
   };
   return n;
  })(['autofocus', 'max', 'min', 'pattern', 'placeholder', 'required', 'validity']),

  formFieldsTags: ['input', 'textarea', 'select'],

  classNames: {
   valid: 'valid',
   invalid: 'invalid',
   placeholder: 'placeholder'
  },

  // unleash the beast!
  // F5.init(), F5.init(parentId), F5.init(options), F5.init(parentId, options)
  init: function() {
   var parentId = typeof(arguments[0]) == 'string' ? arguments[0] : null,
       options = (parentId && typeof(arguments[1]) == 'object') ? arguments[1] : typeof(arguments[0]) == 'object' ? arguments[0] : {},
       
       
       
       
       
       
       
       
       
       
       
       
       
       // get parent element
       parentElement = options.parent ? options.parent : parentId ? document.getElementById(parentId) : document.body,
       
       
       
       
       
       
       
       
       
       
       
       
       
       // custom action to do after all invalid values were found when submitting form
       onInvalidForm = typeof(options.onInvalidForm) == 'function' ? options.onInvalidForm : null,
       
       
       
       
       
       
       
       
       
       
       
       
       
       // custom action to do if are all values valid and required fields filled when submitting form; like some ajax stuff etc.
       onValidForm = typeof(options.onValidForm) == 'function' ? options.onValidForm : null,
       
       
       
       
       
       
       
       
       
       
       
       
       
       // get all matched forms
       forms = (parentElement.tagName === 'FORM') ? [parentElement] : parentElement.getElementsByTagName('FORM'),
       
       
       
       
       
       
       
       
       
       
       
       
       
       // get all form fields  (inputs, textareas and selects)
       fields = (parentElement.querySelectorAll) ? parentElement.querySelectorAll(F5.formFieldsTags.join(',')) : (function(elements) {
     for (var i = 0, l = elements.length, f = []; i < l; i++) {
      if (F5.isFormField(elements[i])) {
       f.push(elements[i]);
      }
     }
     return f;
    })(parentElement.getElementsByTagName('*'));

   // provide some HTML5 forms functionality if there is no native support
   for (var i = 0, l = fields.length; i < l; i++) {
    F5.initField(fields[i]);
   }

   for (var j = 0, lj = forms.length; j < lj; j++) {
    if (!F5.available.validity && typeof(forms[j].checkValidity) != 'function') {
     forms[j].checkValidity = function() {
      var fields = F5.getAllFormFields(this);

      for (var i = 0, l = fields.length; i < l; i++) {
       if (!fields[i].checkValidity()) {
        return false;
       }
      }

      return true;
     }
    }

    F5.used();
   }

   // set forms submit check
   if (F5.isUsed || onInvalidForm || onValidForm) {
    F5.on(forms, 'submit', function(event) {
     var i, l, form = event.target || event.srcElement || null,
         
         
         
         
         
         
         
         
         
         
         
         
         
         // some browsers adds fieldsets to forms' "elements" collection, so let's just make our selection clear 
         fields = F5.getAllFormFields(form),
         
         
         
         
         
         invalidFields = [],
         placeholderFields = [];

     // collect all invalid fields...
     for (i = 0, l = fields.length; i < l; i++) {
      if (typeof(fields[i].validity) == 'undefined') {
       F5.initField(fields[i]);
      }

      if (!fields[i].validity.valid && (!F5.available.validity || onInvalidForm)) {
       invalidFields.push(fields[i]);
      }

      // collect remaining placeholders to get rid of them later
      if (!F5.available.placeholder && fields[i].value === fields[i].placeholder && fields[i].className.split(' ').indexOf(F5.classNames.placeholder) !== -1) {
       placeholderFields.push(fields[i]);
      }
     }

     // ...and more important, choose what to do
     if (invalidFields.length > 0) {
      if (onInvalidForm) {
       onInvalidForm(event, invalidFields);
      }
      else {
       F5.preventDefault(event);
       invalidFields[0].focus();
      }
     }
     else {
      for (i = 0, l = placeholderFields.length; i < l; i++) {
       placeholderFields[i].value = '';
      }

      if (onValidForm) {
       onValidForm(event);
      }
     }
    });

    F5.used();
   }

   F5.isInitialized = true;
  },

  // methods
  // init single field
  initField: function(element) {
   var field = new F5.field(element);

   field.initPattern();
   field.initPlaceholder();
   field.initRequired();
   field.initValidity();
   field.initAutofocus();
  },

  // validate element
  validate: function(eventOrElement) {
   var element = eventOrElement.target || eventOrElement.srcElement || eventOrElement || false;
   if (!element) {
    return false;
   }

   var isDisabled = !! element.disabled,
       isPlaceholder = (element.value === element.placeholder && F5.classNames.placeholder && element.className.split(' ').indexOf(F5.classNames.placeholder) !== -1),
       isRequired = !! element.required,
       isValid = (isDisabled) ? true : (element.tagName === 'SELECT') ? (function(e, i) {
     return (!isRequired || (isRequired && !! e.options[i].value));
    })(element, element.selectedIndex) : !! (function(e, t) {
     switch (t) {
     case 'checkbox':
      return ((isRequired && e.checked) || !isRequired);

     case 'radio':
      var i, l, group = e.form[e.name],
          groupInvalid = [],
          isChecked = false;

      for (i = 0, l = group.length; i < l; i++) {
       isChecked = (group[i].checked || isChecked);

       if (group[i] !== e && !group[i].disabled && group[i].validity && !group[i].validity.valid) {
        groupInvalid.push(group[i]);
       }
      }

      if (isChecked) {
       for (i = 0, l = groupInvalid.length; i < l; i++) {
        groupInvalid[i].validity.valid = true;
        F5.removeClassName(groupInvalid[i], F5.classNames.invalid);
        F5.addClassName(groupInvalid[i], F5.classNames.valid);
       }
      }

      return ((isRequired && isChecked) || !isRequired);

     case 'number':
      var value = parseInt(e.value) || null,
          min = parseInt(e.min) || null,
          max = parseInt(e.max) || null;

      return (value || (!isRequired && !e.value)) && (!min || min <= value) && (!max || max >= value);

     default:
      if (isPlaceholder) {
       return !isRequired;
      }

      // it seems that HTML5 pattern behaves slightly different than JS regular expression test
      // - in HTML5 (cross-browser) doesn't matter if pattern has start and end characters (^ and $)
      var pattern = e.pattern,
          patternRegExp = (typeof(pattern) == 'string') ? new RegExp((pattern[0] !== '^' && pattern[pattern.length - 1] !== '$') ? '^' + pattern + '$' : pattern) : (t === 'email') ? /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/ : false;

      return (patternRegExp && e.value) ? patternRegExp.test(e.value) : ((isRequired && e.value) || !isRequired);
     }
    })(element, element.getAttribute('type')),
       
       
       
       
       
       classNameArray = F5.removeClassName(element.className, [F5.classNames.valid, F5.classNames.invalid]);

   if (!isDisabled && !isPlaceholder) {
    classNameArray.push(isValid ? F5.classNames.valid : F5.classNames.invalid);
   }

   element.className = classNameArray.join(' ');
   element.validity.valid = isValid;

   return isValid;
  },

  // attach event listener && on function cross-browser
  attachEventListener: (function() {
   if (window.addEventListener) {
    return function(element, eventType, listener) {
     element.addEventListener(eventType, listener, false);
    };
   }
   else if (window.attachEvent) { // IE
    return function(element, eventType, listener) {
     element.attachEvent('on' + eventType, listener);
    };
   }
   else return function() {};
  })(),

  // detach event listener && off function cross-browser
  detachEventListener: (function() {
   if (window.removeEventListener) {
    return function(element, eventType, listener) {
     element.removeEventListener(eventType, listener, false);
    };
   }
   else if (window.detachEvent) { // IE
    return function(element, eventType, listener) {
     element.detachEvent('on' + eventType, listener);
    };
   }
   else return function() {};
  })(),

  // on and off functions for attaching or detaching multiple events on multiple elements
  on: function(elements, eventTypes, listener, action) {
   var elements = (elements === window || F5.isFormField(elements)) ? [elements] : F5.toArray(elements),
       eventTypes = eventTypes.split(' '),
       action = action || F5.attachEventListener;

   for (var i = 0, l = elements.length; i < l; i++) {
    for (var j = 0, lj = eventTypes.length; j < lj; j++) {
     action(elements[i], eventTypes[j], listener);
    }
   }

   return true;
  },

  off: function(elements, eventTypes, listener) {
   return F5.on(elements, eventTypes, listener, F5.detachEventListener);
  },

  // prevent default action
  preventDefault: function(event) {
   if (event.preventDefault) {
    event.preventDefault();
   }
   event.returnValue = false; // IE
  },

  addClassName: function(element, className) {
   if (element.className !== '') {
    element.className += ' ';
   }
   element.className += className;
  },

  // remove class name(s)
  // "element" can be either real element or string (class name) which results in returning array
  // "classNames" are expected to be a string or an array of strings
  removeClassName: function(element, classNames) {
   var classNames = (typeof(classNames) == 'string') ? classNames.split(' ') : classNames,
       classNameArray = [],
       returnArray = false;

   if (typeof(element) == 'string') {
    returnArray = true;
    if (element !== classNames.join(' ') && element !== '') {
     classNameArray = element.split(' ');
    }
   }
   else {
    if (element.className !== classNames.join(' ')) {
     classNameArray = element.className.split(' ');
    }
   }

   // search & destroy
   for (var i = 0, l = classNameArray.length; i < l; i++) {
    for (var j = 0, lj = classNames.length; j < lj; j++) {
     if (classNameArray[i] === classNames[j]) {
      classNameArray.splice(i, 1);
      break;
     }
    }
   }

   if (returnArray) {
    return classNameArray;
   }
   else {
    element.className = classNameArray.join(' ');
    return true;
   }
  },

  getAllFormFields: function(form) {
   return (form.querySelectorAll) ? form.querySelectorAll(F5.formFieldsTags.join(',')) : (function(elements) {
    for (var i = 0, l = elements.length, f = []; i < l; i++) {
     if (F5.isFormField(elements[i])) {
      f.push(elements[i]);
     }
    }
    return f;
   })(form.elements);
  },

  toArray: function(someObject) {
   return (typeof(someObject) == 'object' && someObject.length > 0) ? someObject : [someObject];
  },

  isFormField: function(element) {
   return (element.tagName && F5.formFieldsTags.indexOf(element.tagName.toLowerCase()) >= 0);
  }
 };


 // field class
 F5.field = function(element) {
  this.element = element;
 };

 // field class methods
 F5.field.prototype = {
  // pattern attribute
  initPattern: function() {
   if (!F5.available.pattern) {
    var pattern = this.element.getAttribute('pattern');
   }

   if (pattern) {
    this.element.pattern = pattern;

    F5.used();
   }
  },

  // placeholder attribute
  initPlaceholder: function() {
   if (!F5.available.placeholder) {
    var placeholder = this.element.getAttribute('placeholder');
   }

   if (placeholder) {
    this.element.placeholder = placeholder;

    // separated into two conditions on purpose because of form values browsers' storage
    if (this.element.value === '') {
     this.element.value = placeholder;
    }
    if (this.element.value === placeholder) {
     F5.addClassName(this.element, F5.classNames.placeholder);
    }

    F5.on(this.element, 'focus', function(event) {
     var element = event.target || event.srcElement || false;
     if (!element) {
      return false;
     }
     if (element.value === element.placeholder && element.className.split(' ').indexOf(F5.classNames.placeholder) !== -1) {
      element.value = '';
      F5.removeClassName(element, F5.classNames.placeholder);
     }
    });

    F5.on(this.element, 'blur', function(event) {
     var element = event.target || event.srcElement || false;
     if (!element) {
      return false;
     }
     if (element.value === '') {
      element.value = element.placeholder;
      F5.addClassName(element, F5.classNames.placeholder);
     }
    });

    F5.used();
   }
  },

  // required attribute
  initRequired: function() {
   if (!F5.available.required) {
    this.element.required = (typeof(this.element.getAttribute('required')) == 'string');

    F5.used();
   }
  },

  // set validation and create ValidityState object... sort of
  initValidity: function() {
   if (!F5.available.validity) {
    var realType = this.element.getAttribute('type');

    this.element.validity = {
     valid: true
    };

    if (typeof(this.element.checkValidity) != 'function') {
     this.element.checkValidity = function() {
      return F5.validate(this);
     };
    }

    F5.validate(this.element);

    if (realType === 'checkbox' || realType === 'radio') {
     // not using 'change' event because of IEs' bug
     F5.on(this.element, 'click keyup', F5.validate);
    }
    else if (realType !== this.element.type && realType === 'number') {
     if (!F5.available.min) {
      this.element.min = this.element.getAttribute('min');
     }
     if (!F5.available.max) {
      this.element.max = this.element.getAttribute('max');
     }

     this.element.setAttribute('autocomplete', 'off');

     F5.on(this.element, 'keydown', function(event) {
      var element = event.target || event.srcElement || false,
          keyCode = event.keyCode;

      if (!element) {
       return false;
      }

      if (keyCode === 38 || keyCode === 40) {
       var value = parseInt(element.value) || 0,
           min = parseInt(element.min) || null,
           max = parseInt(element.max) || null;
      }

      if (keyCode === 38) {
       var number = (min && min > value) ? min : value + 1;

       if (!max || (max && max >= number)) {
        element.value = number;
       }
      }
      else if (keyCode === 40) {
       var number = (max && max < value) ? max : value - 1;

       if (!min || (min && min <= number)) {
        element.value = number;
       }
      }
      else {
       F5.validate(element);
      }
     });

     F5.on(this.element, 'blur', function(event) {
      var element = event.target || event.srcElement || false,
          value = parseInt(element.value);

      element.value = value || '';
     });

     F5.on(this.element, 'keyup blur', F5.validate);
    }
    else {
     F5.on(this.element, 'keyup blur', F5.validate);
    }

    F5.used();
   }
  },

  // autofocus attribute, following Chrome's & Opera's implementation - last element with autofocus will be focused (...and there shouldn't be more than one)
  initAutofocus: function() {
   if (!F5.available.autofocus && typeof(this.element.getAttribute('autofocus')) == 'string') {
    this.element.focus();

    F5.used();
   }
  }
 };


 // lets make F5 with it's functions globally accessible object, hurray!
 window.F5 = F5;


 // extend Array objects by indexOf
 // http://erik.eae.net/playground/arrayextras/arrayextras.js
 if (!Array.prototype.indexOf) {
  Array.prototype.indexOf = function(obj, fromIndex) {
   if (fromIndex == null) {
    fromIndex = 0;
   } else if (fromIndex < 0) {
    fromIndex = Math.max(0, this.length + fromIndex);
   }
   for (var i = fromIndex, l = this.length; i < l; i++) {
    if (this[i] === obj) {
     return i;
    }
   }
   return -1;
  };
 }

})(window, window.document);
ready

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.