]> git.ipfire.org Git - thirdparty/foundation/foundation-sites.git/commitdiff
new abide plugin. Unifies and simplifies events, using the common .zf.abide namespace...
authorChris Oyler <chris@zurb.com>
Thu, 17 Dec 2015 00:58:08 +0000 (16:58 -0800)
committerChris Oyler <chris@zurb.com>
Thu, 17 Dec 2015 00:58:08 +0000 (16:58 -0800)
js/foundation.new-abide.js

index 13b3610e67351c6db80ecdd1b870d54c02be73e3..b29d62369a7af5db96d4eb4094f427a9a757340c 100644 (file)
    * Default settings for plugin
    */
   Abide.defaults = {
-    validateOn: 'fieldChange', // options: fieldChange, manual, submit
+    /**
+     * The default event to validate inputs. Checkboxes and radios validate immediately.
+     * Remove or change this value for manual validation.
+     * @option
+     * @example 'fieldChange'
+     */
+    validateOn: 'fieldChange',
+    /**
+     * Class to be applied to input labels on failed validation.
+     * @option
+     * @example 'is-invalid-label'
+     */
     labelErrorClass: 'is-invalid-label',
+    /**
+     * Class to be applied to inputs on failed validation.
+     * @option
+     * @example 'is-invalid-input'
+     */
     inputErrorClass: 'is-invalid-input',
+    /**
+     * Class selector to use to target Form Errors for show/hide.
+     * @option
+     * @example '.form-error'
+     */
     formErrorSelector: '.form-error',
+    /**
+     * Class added to Form Errors on failed validation.
+     * @option
+     * @example 'is-visible'
+     */
     formErrorClass: 'is-visible',
+    /**
+     * Set to true to validate text inputs on any value change.
+     * @option
+     * @example false
+     */
     liveValidate: false,
 
     patterns: {
       // #FFF or #FFFFFF
       color : /^#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/
     },
-
+    /**
+     * Optional validation functions to be used. `equalTo` being the only default included function.
+     * Functions should return only a boolean if the input is valid or not. Functions are given the following arguments:
+     * el : The jQuery element to validate.
+     * required : Boolean value of the required attribute be present or not.
+     * parent : The direct parent of the input.
+     * @option
+     */
     validators: {
       equalTo: function (el, required, parent) {
         return $('#' + el.attr('data-equalto')).val() === el.val();
    * @private
    */
   Abide.prototype._init = function(){
-    this.$inputs = this.$element.find('input, textarea, select');
+    this.$inputs = this.$element.find('input, textarea, select').not('[data-abide-ignore]');
+
     this._events();
   };
 
     var _this = this;
 
     this.$element.off('.abide')
-        .on('reset.zf.abide reset.fndtn.abide', function(e){
-          _this.resetForm($(this));
+        .on('reset.zf.abide', function(e){
+          _this.resetForm();
         })
-        .on('submit.zf.abide submit.fndtn.abide', function(e){
-          _this.validateForm();
+        .on('submit.zf.abide', function(e){
+          return _this.validateForm();
         });
 
-    this.$inputs.off('.abide')
-        .on('change.zf.abide', function(e){
-          if(_this.options.validateOn === 'fieldChange') _this.validateInput($(this));
-        });
+    if(this.options.validateOn === 'fieldChange'){
+        this.$inputs.off('change.zf.abide')
+            .on('change.zf.abide', function(e){
+              _this.validateInput($(this));
+            });
+    }
 
     if(this.options.liveValidate){
-      this.$inputs.on('input.zf.abide', function(e){
-        _this.validateInput($(this));
-      });
+      this.$inputs.off('input.zf.abide')
+          .on('input.zf.abide', function(e){
+            _this.validateInput($(this));
+          });
     }
   },
   /**
    * @private
    */
   Abide.prototype._reflow = function() {
-    var self = this;
+    this._init();
   };
   /**
    * Checks whether or not a form element has the required attribute and if it's checked or not
    * @param {Object} element - jQuery object to check for required attribute
    * @returns {Boolean} Boolean value depends on whether or not attribute is checked or empty
    */
-   var counter = 0;
   Abide.prototype.requiredCheck = function($el) {
     if(!$el.attr('required')) return true;
     var isGood = true;
 
       case 'select':
       case 'select-one':
+      case 'select-multiple':
         var opt = $el.find('option:selected');
         if(!opt.length || !opt.val()) isGood = false;
         break;
   };
   /**
    * Based on $el, get the first element with selector in this order:
-   * 1. The element next to it.
-   * 2. The element inside (is a child of) it.
-   * 3. As its sibling.
+   * 1. The element's direct sibling('s).
+   * 3. The element's parent's children.
+   *
+   * This allows for multiple form errors per input, though if none are found, no form errors will be shown.
    *
-   * @param {Object} element - jQuery object to use as reference to find the form error selector.
-   * @param {String} selector - jQuery selector to show error message when field doesn't pass validation.
+   * @param {Object} $el - jQuery object to use as reference to find the form error selector.
    * @returns {Object} jQuery object with the selector.
    */
-  Abide.prototype.findFormError = function($el, selector) {
+  Abide.prototype.findFormError = function($el){
+    var $error = $el.siblings(this.options.formErrorSelector)
+    if(!$error.length){
+      $error = $el.parent().find(this.options.formErrorSelector);
+    }
+    return $error;
   };
   /**
    * Get the first element in this order:
-   * 1. The closest parent ".input-group" class.
-   * 2. The <label> next to the element.
-   * 3. The closest parent <label>
+   * 2. The <label> with the attribute `[for="someInputId"]`
+   * 3. The `.closest()` <label>
    *
-   * @param {Object} element - jQuery object to check for required attribute
+   * @param {Object} $el - jQuery object to check for required attribute
    * @returns {Boolean} Boolean value depends on whether or not attribute is checked or empty
    */
   Abide.prototype.findLabel = function($el) {
+    var $label = this.$element.find('label[for="' + $el[0].id + '"]');
+    if(!$label.length){
+      return $el.closest('label');
+    }
+    return $label;
   };
   /**
    * Adds the CSS error class as specified by the Abide settings to the label, input, and the form
-   * @param {Object} element - jQuery object to add the class to
+   * @param {Object} $el - jQuery object to add the class to
    */
   Abide.prototype.addErrorClasses = function($el){
+    var $label = this.findLabel($el),
+        $formError = this.findFormError($el);
 
+    if($label.length){
+      $label.addClass(this.options.labelErrorClass);
+    }
+    if($formError.length){
+      $formError.addClass(this.options.formErrorClass);
+    }
+    $el.addClass(this.options.inputErrorClass).attr('data-invalid', '');
   };
   /**
    * Removes CSS error class as specified by the Abide settings from the label, input, and the form
-   * @param {Object} element - jQuery object to remove the class from
+   * @param {Object} $el - jQuery object to remove the class from
    */
-  Abide.prototype.removeErrorClasses = function($el) {
+  Abide.prototype.removeErrorClasses = function($el){
+    var $label = this.findLabel($el),
+        $formError = this.findFormError($el);
+
+    if($label.length){
+      $label.removeClass(this.options.labelErrorClass);
+    }
+    if($formError.length){
+      $formError.removeClass(this.options.formErrorClass);
+    }
+    $el.removeClass(this.options.inputErrorClass).removeAttr('data-invalid');
   };
   /**
    * Goes through a form to find inputs and proceeds to validate them in ways specific to their type
    * @fires Abide#invalid
    * @fires Abide#valid
    * @param {Object} element - jQuery object to validate, should be an HTML input
-   * @param {Object} form - jQuery object of the entire form to find the various input elements
+   * @returns {Boolean} goodToGo - If the input is valid or not.
    */
-  Abide.prototype.validateInput = function($el, $form){
+  Abide.prototype.validateInput = function($el){
     var clearRequire = this.requiredCheck($el),
-        validated = true;
+        validated = false,
+        customValidator = true,
+        validator = $el.attr('data-validator'),
+        equalTo = true;
 
     switch ($el[0].type) {
 
 
       case 'select':
       case 'select-one':
-        console.log('validating selects');
+      case 'select-multiple':
         validated = clearRequire;
         break;
 
       default:
         validated = this.validateText($el);
     }
-    var goodToGo = (clearRequire === validated === true),
+
+    if(validator){ customValidator = this.matchValidation($el, validator, $el.attr('required')); }
+    if($el.attr('data-equalto')){ equalTo = this.options.validators.equalTo($el); }
+
+    var goodToGo = [clearRequire, validated, customValidator, equalTo].indexOf(false) === -1,
         message = (goodToGo ? 'valid' : 'invalid') + '.zf.abide';
-    console.log(goodToGo);
+
     this[goodToGo ? 'removeErrorClasses' : 'addErrorClasses']($el);
+
+    /**
+     * Fires when the input is done checking for validation. Event trigger is either `valid.zf.abide` or `invalid.zf.abide`
+     * Trigger includes the DOM element of the input.
+     * @event Abide#valid
+     * @event Abide#invalid
+     */
     $el.trigger(message, $el[0]);
+
     return goodToGo;
   };
   /**
    * Goes through a form and if there are any invalid inputs, it will display the form error element
-   * @param {Object} element - jQuery object to validate, should be a form HTML element
+   * @returns {Boolean} noError - true if no errors were detected...
+   * @fires Abide#formvalid
+   * @fires Abide#forminvalid
    */
   Abide.prototype.validateForm = function(){
     var acc = [],
       acc.push(_this.validateInput($(this)));
     });
 
-    hasError = acc.indexOf(false) > -1;
+    var noError = acc.indexOf(false) === -1;
 
-    this.$element.find('[data-abide-error]').css('display', (hasError ? 'block' : 'none'));
+    this.$element.find('[data-abide-error]').css('display', (noError ? 'none' : 'block'))
+        /**
+         * Fires when the form is finished validating. Event trigger is either `formvalid.zf.abide` or `forminvalid.zf.abide`.
+         * Trigger includes the element of the form.
+         * @event Abide#formvalid
+         * @event Abide#forminvalid
+         */
+        .trigger((noError ? 'valid' : 'invalid') + '.zf.abide', [this.$element]);
 
-    return !hasError;
+    return noError;
   };
   /**
-   * Determines whether or a not a text input is valid based on the patterns specified in the attribute
-   * @param {Object} element - jQuery object to validate, should be a text input HTML element
+   * Determines whether or a not a text input is valid based on the pattern specified in the attribute. If no matching pattern is found, returns true.
+   * @param {Object} $el - jQuery object to validate, should be a text input HTML element
+   * @param {String} pattern - string value of one of the RegEx patterns in Abide.options.patterns
    * @returns {Boolean} Boolean value depends on whether or not the input value matches the pattern specified
    */
   Abide.prototype.validateText = function($el, pattern){
-    console.log('validating text', $el);
     pattern = pattern ? pattern : $el.attr('pattern');
     var inputText = $el.val();
 
   };
   /**
    * Determines whether or a not a radio input is valid based on whether or not it is required and selected
-   * @param {String} group - A string that specifies the name of a radio button group
+   * @param {String} groupName - A string that specifies the name of a radio button group
    * @returns {Boolean} Boolean value depends on whether or not at least one radio input has been selected (if it's required)
    */
   Abide.prototype.validateRadio = function(groupName){
         _this = this;
 
     $group.each(function(){
-      counter.push(_this.requiredCheck($(this)));
+      var rdio = $(this),
+          clear = _this.requiredCheck(rdio);
+      counter.push(clear);
+      if(clear) _this.removeErrorClasses(rdio);
     });
+
     return counter.indexOf(false) === -1;
   };
-  Abide.prototype.matchValidation = function(val, validation){
-
+  /**
+   * Determines if a selected input passes a custom validation function. Multiple validations can be used, if passed to the element with `data-validator="foo bar baz"` in a space separated listed.
+   * @param {Object} $el - jQuery input element.
+   * @param {String} validators - a string of function names matching functions in the Abide.options.validators object.
+   * @param {Boolean} required - self explanatory?
+   * @returns {Boolean} - true if validations passed.
+   */
+  Abide.prototype.matchValidation = function($el, validators, required){
+    var _this = this;
+    required = required ? true : false;
+    var clear = validators.split(' ').map(function(v){
+      return _this.options.validators[v]($el, required, $el.parent());
+    });
+    return clear.indexOf(false) === -1;
   };
   /**
    * Resets form inputs and styles
-   * @param {Object} $form - A jQuery object that should be an HTML form element
+   * @fires Abide#formreset
    */
-  Abide.prototype.resetForm = function($form) {
-    var self = this;
-    var invalidAttr = 'data-invalid';
-    // remove data attributes
-    $('[' + self.invalidAttr + ']', $form).removeAttr(invalidAttr);
-    // remove styles
-    $('.' + self.options.labelErrorClass, $form).not('small').removeClass(self.options.labelErrorClass);
-    $('.' + self.options.inputErrorClass, $form).not('small').removeClass(self.options.inputErrorClass);
-    $('.form-error.is-visible').removeClass('is-visible');
+  Abide.prototype.resetForm = function() {
+    var $form = this.$element,
+        opts = this.options;
+
+    $('.' + opts.labelErrorClass, $form).not('small').removeClass(opts.labelErrorClass);
+    $('.' + opts.inputErrorClass, $form).not('small').removeClass(opts.inputErrorClass);
+    $(opts.formErrorSelector + '.' + opts.formErrorClass).removeClass(opts.formErrorClass);
     $form.find('[data-abide-error]').css('display', 'none');
-    $(':input', $form).not(':button, :submit, :reset, :hidden, [data-abide-ignore]').val('').removeAttr(invalidAttr);
+    $(':input', $form).not(':button, :submit, :reset, :hidden, [data-abide-ignore]').val('').removeAttr('data-invalid');
+    /**
+     * Fires when the form has been reset.
+     * @event Abide#formreset
+     */
+    $form.trigger('formreset.zf.abide', [$form]);
   };
+  /**
+   * Destroys an instance of Abide.
+   * Removes error styles and classes from elements, without resetting their values.
+   */
   Abide.prototype.destroy = function(){
-    //TODO this...
+    var _this = this;
+    this.$element.off('.abide')
+        .find('[data-abide-error]').css('display', 'none');
+    this.$inputs.off('.abide')
+        .each(function(){
+          _this.removeErrorClasses($(this));
+        });
+
+    Foundation.unregisterPlugin(this);
   };
 
   Foundation.plugin(Abide, 'Abide');