- 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.$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 = true;

- ¶

  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 (options) {
          if (arguments.length >= 1 && !$.isPlainObject(options)) {
            ParsleyUtils.warnOnce('Calling validate on a parsley field without passing arguments as an object is deprecated.');
            options = {options};
          }
          var promise = this.whenValidate(options);
          if (!promise)  // If excluded with `group` option
            return true;
          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 or `undefined` if field is not in the given `group`.

        whenValidate: function ({force, group} =  {}) {

- ¶

  do not validate a field if not the same as given validation group

          this.refreshConstraints();
          if (group && !this._isInGroup(group))
            return;

          this.value = this.getValue();

- ¶

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

          this._trigger('validate');

          return this.whenValid({force, value: this.value, _refreshed: true})
            .always(() => { this._reflowUI(); })
            .done(() =>   { this._trigger('success'); })
            .fail(() =>   { this._trigger('error'); })
            .always(() => { this._trigger('validated'); })
            .pipe(...this._pipeAccordingToValidationResult());
        },

        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;
        },

        _isInGroup: function (group) {
          if ($.isArray(this.options.group))
            return -1 !== $.inArray(group, this.options.group);
          return this.options.group === group;
        },

- ¶

  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 (options) {
          if (arguments.length >= 1 && !$.isPlainObject(options)) {
            ParsleyUtils.warnOnce('Calling isValid on a parsley field without passing arguments as an object is deprecated.');
            var [force, value] = arguments;
            options = {force, value};
          }
          var promise = this.whenValid(options);
          if (!promise) // Excluded via `group`
            return true;
          return statusMapping[promise.state()];
        },

- ¶

  Just validate field. Do not trigger any event. @returns a promise that succeeds only when all validations do or `undefined` if the field is not in the given `group`. The argument `force` will force validation of empty fields. If a `value` is given, it will be validated instead of the value of the input.

        whenValid: function ({force = false, value, group, _refreshed} = {}) {

- ¶

  Recompute options and rebind constraints to have latest changes

          if (!_refreshed)
            this.refreshConstraints();

- ¶

  do not validate a field if not the same as given validation group

          if (group && !this._isInGroup(group))
            return;

          this.validationResult = true;

- ¶

  A field without constraint is valid

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

- ¶

  Value could be passed as argument, needed to add more power to ‘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 = [];
          $.each(groupedConstraints, (_, constraints) => {

- ¶

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

            var promise = ParsleyUtils.all(
              $.map(constraints, constraint => this._validateConstraint(value, constraint))
            );
            promises.push(promise);
            if (promise.state() === 'rejected')
              return false; // Interrupt processing if a group has already failed
          });
          return ParsleyUtils.all(promises);
        },

- ¶

  @returns a promise

        _validateConstraint: function(value, constraint) {
          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 ParsleyUtils.all([result]).fail(errorMessage => {
            if (!(this.validationResult instanceof Array))
              this.validationResult = [];
            this.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) {
            return this.addConstraint('type', ['number', {
              step: this.$element.attr('step'),
              base: this.$element.attr('min') || this.$element.attr('value')
            }], 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;