• Jump To … +
    <<< back to documentation abstract.js defaults.js factory.js field.js form.js main.js multiple.js pubsub.js remote.js ui.js utils.js validator.js validator_registry.js
  • field.js

  • ¶
    import $ from 'jquery';
    import ConstraintFactory from './factory/constraint';
    import ParsleyUI from './ui';
    import ParsleyUtils from './utils';
    
    var ParsleyField = function (field, domOptions, options, parsleyFormInstance) {
      this.__class__ = 'ParsleyField';
      this.__id__ = ParsleyUtils.generateID();
    
      this.$element = $(field);
  • ¶

    Set parent if we have one

      if ('undefined' !== typeof parsleyFormInstance) {
        this.parent = parsleyFormInstance;
      }
    
      this.options = options;
      this.domOptions = domOptions;
  • ¶

    Initialize some properties

      this.constraints = [];
      this.constraintsByName = {};
      this.validationResult = [];
  • ¶

    Bind constraints

      this._bindConstraints();
    };
    
    var statusMapping = {pending: null, resolved: true, rejected: false};
    
    ParsleyField.prototype = {
  • ¶

    Public API

    Validate field and trigger some events for mainly ParsleyUI @returns true, an array of the validators that failed, or null if validation is not finished. Prefer using whenValidate

      validate: function (force) {
        var promise = this.whenValidate(force);
        switch (promise.state()) {
          case 'pending': return null;
          case 'resolved': return true;
          case 'rejected': return this.validationResult;
        }
      },
  • ¶

    Validate field and trigger some events for mainly ParsleyUI @returns a promise that succeeds only when all validations do.

      whenValidate: function (force) {
        var that = this;
    
        this.value = this.getValue();
  • ¶

    Field Validate event. this.value could be altered for custom needs

        this._trigger('validate');
    
        return this.whenValid(force, this.value)
          .done(function ()   { that._trigger('success'); })
          .fail(function ()   { that._trigger('error'); })
          .always(function () { that._trigger('validated'); });
      },
    
      hasConstraints: function () {
        return 0 !== this.constraints.length;
      },
  • ¶

    An empty optional field does not need validation

      needsValidation: function (value) {
        if ('undefined' === typeof value)
          value = this.getValue();
  • ¶

    If a field is empty and not required, it is valid Except if data-parsley-validate-if-empty explicitely added, useful for some custom validators

        if (!value.length && !this._isRequired() && 'undefined' === typeof this.options.validateIfEmpty)
          return false;
    
        return true;
      },
  • ¶

    Just validate field. Do not trigger any event. Returns true iff all constraints pass, false if there are failures, or null if the result can not be determined yet (depends on a promise) See also whenValid.

      isValid: function (force, value) {
        return statusMapping[this.whenValid(force, value).state()];
      },
  • ¶

    Just validate field. Do not trigger any event. @returns a promise that succeeds only when all validations do. The argument force is optional, defaults to false. The argument value is optional. If given, it will be validated instead of the value of the input.

      whenValid: function (force, value) {
  • ¶

    Recompute options and rebind constraints to have latest changes

        this.refreshConstraints();
        this.validationResult = true;
  • ¶

    A field without constraint is valid

        if (!this.hasConstraints())
          return $.when();
  • ¶

    Make force argument optional

        if ('boolean' !== typeof force && 'undefined' === typeof value) {
          value = force;
          force = false;
        }
  • ¶

    Value could be passed as argument, needed to add more power to ‘parsley:field:validate’

        if ('undefined' === typeof value || null === value)
          value = this.getValue();
    
        if (!this.needsValidation(value) && true !== force)
          return $.when();
    
        var groupedConstraints = this._getGroupedConstraints();
        var promises = [];
        var that = this;
        $.each(groupedConstraints, function(_, constraints) {
  • ¶

    Process one group of constraints at a time, we validate the constraints and combine the promises together.

          var promise = $.when.apply($,
            $.map(constraints, $.proxy(that, '_validateConstraint', value))
          );
          promises.push(promise);
          if (promise.state() === 'rejected')
            return false; // Interrupt processing if a group has already failed
        });
        return $.when.apply($, promises);
      },
  • ¶

    @returns a promise

      _validateConstraint: function(value, constraint) {
        var that = this;
        var result = constraint.validate(value, this);
  • ¶

    Map false to a failed promise

        if (false === result)
          result = $.Deferred().reject();
  • ¶

    Make sure we return a promise and that we record failures

        return $.when(result).fail(function(errorMessage) {
          if (true === that.validationResult)
            that.validationResult = [];
          that.validationResult.push({
            assert: constraint,
            errorMessage: 'string' === typeof errorMessage && errorMessage
          });
        });
      },
  • ¶

    @returns Parsley field computed value that could be overrided or configured in DOM

      getValue: function () {
        var value;
  • ¶

    Value could be overriden in DOM or with explicit options

        if ('function' === typeof this.options.value)
          value = this.options.value(this);
        else if ('undefined' !== typeof this.options.value)
          value = this.options.value;
        else
          value = this.$element.val();
  • ¶

    Handle wrong DOM or configurations

        if ('undefined' === typeof value || null === value)
          return '';
    
        return this._handleWhitespace(value);
      },
  • ¶

    Actualize options that could have change since previous validation Re-bind accordingly constraints (could be some new, removed or updated)

      refreshConstraints: function () {
        return this.actualizeOptions()._bindConstraints();
      },
    
      /**
      * Add a new constraint to a field
      *
      * @param {String}   name
      * @param {Mixed}    requirements      optional
      * @param {Number}   priority          optional
      * @param {Boolean}  isDomConstraint   optional
      */
      addConstraint: function (name, requirements, priority, isDomConstraint) {
    
        if (window.Parsley._validatorRegistry.validators[name]) {
          var constraint = new ConstraintFactory(this, name, requirements, priority, isDomConstraint);
  • ¶

    if constraint already exist, delete it and push new version

          if ('undefined' !== this.constraintsByName[constraint.name])
            this.removeConstraint(constraint.name);
    
          this.constraints.push(constraint);
          this.constraintsByName[constraint.name] = constraint;
        }
    
        return this;
      },
  • ¶

    Remove a constraint

      removeConstraint: function (name) {
        for (var i = 0; i < this.constraints.length; i++)
          if (name === this.constraints[i].name) {
            this.constraints.splice(i, 1);
            break;
          }
        delete this.constraintsByName[name];
        return this;
      },
  • ¶

    Update a constraint (Remove + re-add)

      updateConstraint: function (name, parameters, priority) {
        return this.removeConstraint(name)
          .addConstraint(name, parameters, priority);
      },
  • ¶

    Internals

  • ¶

    Internal only. Bind constraints from config + options + DOM

      _bindConstraints: function () {
        var constraints = [];
        var constraintsByName = {};
  • ¶

    clean all existing DOM constraints to only keep javascript user constraints

        for (var i = 0; i < this.constraints.length; i++)
          if (false === this.constraints[i].isDomConstraint) {
            constraints.push(this.constraints[i]);
            constraintsByName[this.constraints[i].name] = this.constraints[i];
          }
    
        this.constraints = constraints;
        this.constraintsByName = constraintsByName;
  • ¶

    then re-add Parsley DOM-API constraints

        for (var name in this.options)
          this.addConstraint(name, this.options[name], undefined, true);
  • ¶

    finally, bind special HTML5 constraints

        return this._bindHtml5Constraints();
      },
  • ¶

    Internal only. Bind specific HTML5 constraints to be HTML5 compliant

      _bindHtml5Constraints: function () {
  • ¶

    html5 required

        if (this.$element.hasClass('required') || this.$element.attr('required'))
          this.addConstraint('required', true, undefined, true);
  • ¶

    html5 pattern

        if ('string' === typeof this.$element.attr('pattern'))
          this.addConstraint('pattern', this.$element.attr('pattern'), undefined, true);
  • ¶

    range

        if ('undefined' !== typeof this.$element.attr('min') && 'undefined' !== typeof this.$element.attr('max'))
          this.addConstraint('range', [this.$element.attr('min'), this.$element.attr('max')], undefined, true);
  • ¶

    HTML5 min

        else if ('undefined' !== typeof this.$element.attr('min'))
          this.addConstraint('min', this.$element.attr('min'), undefined, true);
  • ¶

    HTML5 max

        else if ('undefined' !== typeof this.$element.attr('max'))
          this.addConstraint('max', this.$element.attr('max'), undefined, true);
  • ¶

    length

        if ('undefined' !== typeof this.$element.attr('minlength') && 'undefined' !== typeof this.$element.attr('maxlength'))
          this.addConstraint('length', [this.$element.attr('minlength'), this.$element.attr('maxlength')], undefined, true);
  • ¶

    HTML5 minlength

        else if ('undefined' !== typeof this.$element.attr('minlength'))
          this.addConstraint('minlength', this.$element.attr('minlength'), undefined, true);
  • ¶

    HTML5 maxlength

        else if ('undefined' !== typeof this.$element.attr('maxlength'))
          this.addConstraint('maxlength', this.$element.attr('maxlength'), undefined, true);
  • ¶

    html5 types

        var type = this.$element.attr('type');
    
        if ('undefined' === typeof type)
          return this;
  • ¶

    Small special case here for HTML5 number: integer validator if step attribute is undefined or an integer value, number otherwise

        if ('number' === type) {
          if (('undefined' === typeof this.$element.attr('step')) || (0 === parseFloat(this.$element.attr('step')) % 1)) {
            return this.addConstraint('type', 'integer', undefined, true);
          } else {
            return this.addConstraint('type', 'number', undefined, true);
          }
  • ¶

    Regular other HTML5 supported types

        } else if (/^(email|url|range)$/i.test(type)) {
          return this.addConstraint('type', type, undefined, true);
        }
        return this;
      },
  • ¶

    Internal only. Field is required if have required constraint without false value

      _isRequired: function () {
        if ('undefined' === typeof this.constraintsByName.required)
          return false;
    
        return false !== this.constraintsByName.required.requirements;
      },
  • ¶

    Internal only. Shortcut to trigger an event

      _trigger: function (eventName) {
        return this.trigger('field:' + eventName);
      },
  • ¶

    Internal only Handles whitespace in a value Use data-parsley-whitespace="squish" to auto squish input value Use data-parsley-whitespace="trim" to auto trim input value

      _handleWhitespace: function (value) {
        if (true === this.options.trimValue)
          ParsleyUtils.warnOnce('data-parsley-trim-value="true" is deprecated, please use data-parsley-whitespace="trim"');
    
        if ('squish' === this.options.whitespace)
          value = value.replace(/\s{2,}/g, ' ');
    
        if (('trim' === this.options.whitespace) || ('squish' === this.options.whitespace) || (true === this.options.trimValue))
          value = ParsleyUtils.trimString(value);
    
        return value;
      },
  • ¶

    Internal only. Returns the constraints, grouped by descending priority. The result is thus an array of arrays of constraints.

      _getGroupedConstraints: function () {
        if (false === this.options.priorityEnabled)
          return [this.constraints];
    
        var groupedConstraints = [];
        var index = {};
  • ¶

    Create array unique of priorities

        for (var i = 0; i < this.constraints.length; i++) {
          var p = this.constraints[i].priority;
          if (!index[p])
            groupedConstraints.push(index[p] = []);
          index[p].push(this.constraints[i]);
        }
  • ¶

    Sort them by priority DESC

        groupedConstraints.sort(function (a, b) { return b[0].priority - a[0].priority; });
    
        return groupedConstraints;
      }
    
    };
    
    export default ParsleyField;