UNPKG

gentelella-rtl

Version:

Gentelella RTL Admin is a free to use Bootstrap admin template

404 lines (330 loc) 13.4 kB
import $ from 'jquery'; import Constraint from './constraint'; import UI from './ui'; import Utils from './utils'; var Field = function (field, domOptions, options, parsleyFormInstance) { this.__class__ = 'Field'; this.element = field; 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}; Field.prototype = { // # Public API // Validate field and trigger some events for mainly `UI` // @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)) { Utils.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 `UI` // @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.refresh(); 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 (Array.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)) { Utils.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.refresh(); // 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 = Utils.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 Utils.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 Utils.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); }, // Reset UI reset: function () { this._resetUI(); return this._trigger('reset'); }, // Destroy Parsley instance (+ UI) destroy: function () { // Field case: emit destroy event to clean UI and then destroy stored instance this._destroyUI(); this.$element.removeData('Parsley'); this.$element.removeData('FieldMultiple'); this._trigger('destroy'); }, // Actualize options and rebind constraints refresh: function () { this._refreshConstraints(); return this; }, _refreshConstraints: function () { return this.actualizeOptions()._bindConstraints(); }, refreshConstraints: function() { Utils.warnOnce("Parsley's refreshConstraints is deprecated. Please use refresh"); return this.refresh(); }, /** * 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 Constraint(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 (null !== this.element.getAttribute('required')) this.addConstraint('required', true, undefined, true); // html5 pattern if (null !== this.element.getAttribute('pattern')) this.addConstraint('pattern', this.element.getAttribute('pattern'), undefined, true); // range let min = this.element.getAttribute('min'); let max = this.element.getAttribute('max'); if (null !== min && null !== max) this.addConstraint('range', [min, max], undefined, true); // HTML5 min else if (null !== min) this.addConstraint('min', min, undefined, true); // HTML5 max else if (null !== max) this.addConstraint('max', max, undefined, true); // length if (null !== this.element.getAttribute('minlength') && null !== this.element.getAttribute('maxlength')) this.addConstraint('length', [this.element.getAttribute('minlength'), this.element.getAttribute('maxlength')], undefined, true); // HTML5 minlength else if (null !== this.element.getAttribute('minlength')) this.addConstraint('minlength', this.element.getAttribute('minlength'), undefined, true); // HTML5 maxlength else if (null !== this.element.getAttribute('maxlength')) this.addConstraint('maxlength', this.element.getAttribute('maxlength'), undefined, true); // html5 types var type = Utils.getType(this.element); // 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.getAttribute('step') || '1', base: min || this.element.getAttribute('value') }], undefined, true); // Regular other HTML5 supported types } else if (/^(email|url|range|date)$/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) Utils.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 = Utils.trimString(value); return value; }, _isDateInput: function() { var c = this.constraintsByName.type; return c && c.requirements === 'date'; }, // 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 Field;