• Jump To … +
    <<< back to documentation base.js constraint.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
  • ui.js

  • ¶
    import $ from 'jquery';
    import Utils from './utils';
    
    var UI = {};
    
    var diffResults = function (newResult, oldResult, deep) {
      var added = [];
      var kept = [];
    
      for (var i = 0; i < newResult.length; i++) {
        var found = false;
    
        for (var j = 0; j < oldResult.length; j++)
          if (newResult[i].assert.name === oldResult[j].assert.name) {
            found = true;
            break;
          }
    
        if (found)
          kept.push(newResult[i]);
        else
          added.push(newResult[i]);
      }
    
      return {
        kept: kept,
        added: added,
        removed: !deep ? diffResults(oldResult, newResult, true).added : []
      };
    };
    
    UI.Form = {
    
      _actualizeTriggers: function () {
        this.$element.on('submit.Parsley', evt => { this.onSubmitValidate(evt); });
        this.$element.on('click.Parsley', Utils._SubmitSelector, evt => { this.onSubmitButton(evt); });
  • ¶

    UI could be disabled

        if (false === this.options.uiEnabled)
          return;
    
        this.element.setAttribute('novalidate', '');
      },
    
      focus: function () {
        this._focusedField = null;
    
        if (true === this.validationResult || 'none' === this.options.focus)
          return null;
    
        for (var i = 0; i < this.fields.length; i++) {
          var field = this.fields[i];
          if (true !== field.validationResult && field.validationResult.length > 0 && 'undefined' === typeof field.options.noFocus) {
            this._focusedField = field.$element;
            if ('first' === this.options.focus)
              break;
          }
        }
    
        if (null === this._focusedField)
          return null;
    
        return this._focusedField.focus();
      },
    
      _destroyUI: function () {
  • ¶

    Reset all event listeners

        this.$element.off('.Parsley');
      }
    
    };
    
    UI.Field = {
    
      _reflowUI: function () {
        this._buildUI();
  • ¶

    If this field doesn’t have an active UI don’t bother doing something

        if (!this._ui)
          return;
  • ¶

    Diff between two validation results

        var diff = diffResults(this.validationResult, this._ui.lastValidationResult);
  • ¶

    Then store current validation result for next reflow

        this._ui.lastValidationResult = this.validationResult;
  • ¶

    Handle valid / invalid / none field class

        this._manageStatusClass();
  • ¶

    Add, remove, updated errors messages

        this._manageErrorsMessages(diff);
  • ¶

    Triggers impl

        this._actualizeTriggers();
  • ¶

    If field is not valid for the first time, bind keyup trigger to ease UX and quickly inform user

        if ((diff.kept.length || diff.added.length) && !this._failedOnce) {
          this._failedOnce = true;
          this._actualizeTriggers();
        }
      },
  • ¶

    Returns an array of field’s error message(s)

      getErrorsMessages: function () {
  • ¶

    No error message, field is valid

        if (true === this.validationResult)
          return [];
    
        var messages = [];
    
        for (var i = 0; i < this.validationResult.length; i++)
          messages.push(this.validationResult[i].errorMessage ||
           this._getErrorMessage(this.validationResult[i].assert));
    
        return messages;
      },
  • ¶

    It’s a goal of Parsley that this method is no longer required [#1073]

      addError: function (name, {message, assert, updateClass = true} = {}) {
        this._buildUI();
        this._addError(name, {message, assert});
    
        if (updateClass)
          this._errorClass();
      },
  • ¶

    It’s a goal of Parsley that this method is no longer required [#1073]

      updateError: function (name, {message, assert, updateClass = true} = {}) {
        this._buildUI();
        this._updateError(name, {message, assert});
    
        if (updateClass)
          this._errorClass();
      },
  • ¶

    It’s a goal of Parsley that this method is no longer required [#1073]

      removeError: function (name, {updateClass = true} = {}) {
        this._buildUI();
        this._removeError(name);
  • ¶

    edge case possible here: remove a standard Parsley error that is still failing in this.validationResult but highly improbable cuz’ manually removing a well Parsley handled error makes no sense.

        if (updateClass)
          this._manageStatusClass();
      },
    
      _manageStatusClass: function () {
        if (this.hasConstraints() && this.needsValidation() && true === this.validationResult)
          this._successClass();
        else if (this.validationResult.length > 0)
          this._errorClass();
        else
          this._resetClass();
      },
    
      _manageErrorsMessages: function (diff) {
        if ('undefined' !== typeof this.options.errorsMessagesDisabled)
          return;
  • ¶

    Case where we have errorMessage option that configure an unique field error message, regardless failing validators

        if ('undefined' !== typeof this.options.errorMessage) {
          if ((diff.added.length || diff.kept.length)) {
            this._insertErrorWrapper();
    
            if (0 === this._ui.$errorsWrapper.find('.parsley-custom-error-message').length)
              this._ui.$errorsWrapper
                .append(
                  $(this.options.errorTemplate)
                  .addClass('parsley-custom-error-message')
                );
    
            this._ui.$errorClassHandler.attr('aria-describedby', this._ui.errorsWrapperId);
    
            return this._ui.$errorsWrapper
              .addClass('filled')
              .attr('aria-hidden', 'false')
              .find('.parsley-custom-error-message')
              .html(this.options.errorMessage);
          }
    
          this._ui.$errorClassHandler.removeAttr('aria-describedby');
    
          return this._ui.$errorsWrapper
            .removeClass('filled')
            .attr('aria-hidden', 'true')
            .find('.parsley-custom-error-message')
            .remove();
        }
  • ¶

    Show, hide, update failing constraints messages

        for (var i = 0; i < diff.removed.length; i++)
          this._removeError(diff.removed[i].assert.name);
    
        for (i = 0; i < diff.added.length; i++)
          this._addError(diff.added[i].assert.name, {message: diff.added[i].errorMessage, assert: diff.added[i].assert});
    
        for (i = 0; i < diff.kept.length; i++)
          this._updateError(diff.kept[i].assert.name, {message: diff.kept[i].errorMessage, assert: diff.kept[i].assert});
      },
    
    
      _addError: function (name, {message, assert}) {
        this._insertErrorWrapper();
        this._ui.$errorClassHandler
          .attr('aria-describedby', this._ui.errorsWrapperId);
        this._ui.$errorsWrapper
          .addClass('filled')
          .attr('aria-hidden', 'false')
          .append(
            $(this.options.errorTemplate)
            .addClass('parsley-' + name)
            .html(message || this._getErrorMessage(assert))
          );
      },
    
      _updateError: function (name, {message, assert}) {
        this._ui.$errorsWrapper
          .addClass('filled')
          .find('.parsley-' + name)
          .html(message || this._getErrorMessage(assert));
      },
    
      _removeError: function (name) {
        this._ui.$errorClassHandler
          .removeAttr('aria-describedby');
        this._ui.$errorsWrapper
          .removeClass('filled')
          .attr('aria-hidden', 'true')
          .find('.parsley-' + name)
          .remove();
      },
    
      _getErrorMessage: function (constraint) {
        var customConstraintErrorMessage = constraint.name + 'Message';
    
        if ('undefined' !== typeof this.options[customConstraintErrorMessage])
          return window.Parsley.formatMessage(this.options[customConstraintErrorMessage], constraint.requirements);
    
        return window.Parsley.getErrorMessage(constraint);
      },
    
      _buildUI: function () {
  • ¶

    UI could be already built or disabled

        if (this._ui || false === this.options.uiEnabled)
          return;
    
        var _ui = {};
  • ¶

    Give field its Parsley id in DOM

        this.element.setAttribute(this.options.namespace + 'id', this.__id__);
    
        /** Generate important UI elements and store them in this **/
  • ¶

    $errorClassHandler is the $element that woul have parsley-error and parsley-success classes

        _ui.$errorClassHandler = this._manageClassHandler();
  • ¶

    $errorsWrapper is a div that would contain the various field errors, it will be appended into $errorsContainer

        _ui.errorsWrapperId = 'parsley-id-' + (this.options.multiple ? 'multiple-' + this.options.multiple : this.__id__);
        _ui.$errorsWrapper = $(this.options.errorsWrapper).attr('id', _ui.errorsWrapperId);
  • ¶

    ValidationResult UI storage to detect what have changed bwt two validations, and update DOM accordingly

        _ui.lastValidationResult = [];
        _ui.validationInformationVisible = false;
  • ¶

    Store it in this for later

        this._ui = _ui;
      },
  • ¶

    Determine which element will have parsley-error and parsley-success classes

      _manageClassHandler: function () {
  • ¶

    Class handled could also be determined by function given in Parsley options

        if ('string' === typeof this.options.classHandler && $(this.options.classHandler).length)
          return $(this.options.classHandler);
  • ¶

    Class handled could also be determined by function given in Parsley options

        var $handlerFunction = this.options.classHandler;
  • ¶

    It might also be the function name of a global function

        if ('string' === typeof this.options.classHandler && 'function' === typeof window[this.options.classHandler])
          $handlerFunction = window[this.options.classHandler];
    
        if ('function' === typeof $handlerFunction) {
          var $handler = $handlerFunction.call(this, this);
  • ¶

    If this function returned a valid existing DOM element, go for it

          if ('undefined' !== typeof $handler && $handler.length)
            return $handler;
        } else if ('object' === typeof $handlerFunction && $handlerFunction instanceof jQuery && $handlerFunction.length) {
          return $handlerFunction;
        } else if ($handlerFunction) {
          Utils.warn('The class handler `' + $handlerFunction + '` does not exist in DOM nor as a global JS function');
        }
    
        return this._inputHolder();
      },
    
      _inputHolder: function() {
  • ¶

    if simple element (input, texatrea, select…) it will perfectly host the classes and precede the error container

        if (!this.options.multiple || this.element.nodeName === 'SELECT')
          return this.$element;
  • ¶

    But if multiple element (radio, checkbox), that would be their parent

        return this.$element.parent();
      },
    
      _insertErrorWrapper: function () {
        var $errorsContainer = this.options.errorsContainer;
  • ¶

    Nothing to do if already inserted

        if (0 !== this._ui.$errorsWrapper.parent().length)
          return this._ui.$errorsWrapper.parent();
    
        if ('string' === typeof $errorsContainer) {
          if ($($errorsContainer).length)
            return $($errorsContainer).append(this._ui.$errorsWrapper);
          else if ('function' === typeof window[$errorsContainer])
            $errorsContainer = window[$errorsContainer];
          else
            Utils.warn('The errors container `' + $errorsContainer + '` does not exist in DOM nor as a global JS function');
        }
    
        if ('function' === typeof $errorsContainer)
          $errorsContainer = $errorsContainer.call(this, this);
    
        if ('object' === typeof $errorsContainer && $errorsContainer.length)
          return $errorsContainer.append(this._ui.$errorsWrapper);
    
        return this._inputHolder().after(this._ui.$errorsWrapper);
      },
    
      _actualizeTriggers: function () {
        var $toBind = this._findRelated();
        var trigger;
  • ¶

    Remove Parsley events already bound on this field

        $toBind.off('.Parsley');
        if (this._failedOnce)
          $toBind.on(Utils.namespaceEvents(this.options.triggerAfterFailure, 'Parsley'), () => {
            this._validateIfNeeded();
          });
        else if (trigger = Utils.namespaceEvents(this.options.trigger, 'Parsley')) {
          $toBind.on(trigger, event => {
            this._validateIfNeeded(event);
          });
        }
      },
    
      _validateIfNeeded: function (event) {
  • ¶

    For keyup, keypress, keydown, input… events that could be a little bit obstrusive do not validate if val length < min threshold on first validation. Once field have been validated once and info about success or failure have been displayed, always validate with this trigger to reflect every yalidation change.

        if (event && /key|input/.test(event.type))
          if (!(this._ui && this._ui.validationInformationVisible) && this.getValue().length <= this.options.validationThreshold)
            return;
    
        if (this.options.debounce) {
          window.clearTimeout(this._debounced);
          this._debounced = window.setTimeout(() => this.validate(), this.options.debounce);
        } else
          this.validate();
      },
    
      _resetUI: function () {
  • ¶

    Reset all event listeners

        this._failedOnce = false;
        this._actualizeTriggers();
  • ¶

    Nothing to do if UI never initialized for this field

        if ('undefined' === typeof this._ui)
          return;
  • ¶

    Reset all errors’ li

        this._ui.$errorsWrapper
          .removeClass('filled')
          .children()
          .remove();
  • ¶

    Reset validation class

        this._resetClass();
  • ¶

    Reset validation flags and last validation result

        this._ui.lastValidationResult = [];
        this._ui.validationInformationVisible = false;
      },
    
      _destroyUI: function () {
        this._resetUI();
    
        if ('undefined' !== typeof this._ui)
          this._ui.$errorsWrapper.remove();
    
        delete this._ui;
      },
    
      _successClass: function () {
        this._ui.validationInformationVisible = true;
        this._ui.$errorClassHandler.removeClass(this.options.errorClass).addClass(this.options.successClass);
      },
      _errorClass: function () {
        this._ui.validationInformationVisible = true;
        this._ui.$errorClassHandler.removeClass(this.options.successClass).addClass(this.options.errorClass);
      },
      _resetClass: function () {
        this._ui.$errorClassHandler.removeClass(this.options.successClass).removeClass(this.options.errorClass);
      }
    };
    
    export default UI;