410 lines
16 KiB
JavaScript
410 lines
16 KiB
JavaScript
define('parsley/ui', [
|
|
'parsley/utils'
|
|
], function (ParsleyUtils) {
|
|
var ParsleyUI = function (options) {
|
|
this.__class__ = 'ParsleyUI';
|
|
};
|
|
|
|
ParsleyUI.prototype = {
|
|
listen: function () {
|
|
$.listen('parsley:form:init', this, this.setupForm);
|
|
$.listen('parsley:field:init', this, this.setupField);
|
|
|
|
$.listen('parsley:field:validated', this, this.reflow);
|
|
$.listen('parsley:form:validated', this, this.focus);
|
|
|
|
$.listen('parsley:field:reset', this, this.reset);
|
|
|
|
$.listen('parsley:form:destroy', this, this.destroy);
|
|
$.listen('parsley:field:destroy', this, this.destroy);
|
|
|
|
return this;
|
|
},
|
|
|
|
reflow: function (fieldInstance) {
|
|
// If this field has not an active UI (case for multiples) don't bother doing something
|
|
if ('undefined' === typeof fieldInstance._ui || false === fieldInstance._ui.active)
|
|
return;
|
|
|
|
// Diff between two validation results
|
|
var diff = this._diff(fieldInstance.validationResult, fieldInstance._ui.lastValidationResult);
|
|
|
|
// Then store current validation result for next reflow
|
|
fieldInstance._ui.lastValidationResult = fieldInstance.validationResult;
|
|
|
|
// Field have been validated at least once if here. Useful for binded key events..
|
|
fieldInstance._ui.validatedOnce = true;
|
|
|
|
// Handle valid / invalid / none field class
|
|
this.manageStatusClass(fieldInstance);
|
|
|
|
// Add, remove, updated errors messages
|
|
this.manageErrorsMessages(fieldInstance, diff);
|
|
|
|
// Triggers impl
|
|
this.actualizeTriggers(fieldInstance);
|
|
|
|
// 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) && 'undefined' === typeof fieldInstance._ui.failedOnce)
|
|
this.manageFailingFieldTrigger(fieldInstance);
|
|
},
|
|
|
|
// Returns an array of field's error message(s)
|
|
getErrorsMessages: function (fieldInstance) {
|
|
// No error message, field is valid
|
|
if (true === fieldInstance.validationResult)
|
|
return [];
|
|
|
|
var messages = [];
|
|
|
|
for (var i = 0; i < fieldInstance.validationResult.length; i++)
|
|
messages.push(this._getErrorMessage(fieldInstance, fieldInstance.validationResult[i].assert));
|
|
|
|
return messages;
|
|
},
|
|
|
|
manageStatusClass: function (fieldInstance) {
|
|
if (true === fieldInstance.validationResult)
|
|
this._successClass(fieldInstance);
|
|
else if (fieldInstance.validationResult.length > 0)
|
|
this._errorClass(fieldInstance);
|
|
else
|
|
this._resetClass(fieldInstance);
|
|
},
|
|
|
|
manageErrorsMessages: function (fieldInstance, diff) {
|
|
if ('undefined' !== typeof fieldInstance.options.errorsMessagesDisabled)
|
|
return;
|
|
|
|
// Case where we have errorMessage option that configure an unique field error message, regardless failing validators
|
|
if ('undefined' !== typeof fieldInstance.options.errorMessage) {
|
|
if ((diff.added.length || diff.kept.length)) {
|
|
if (0 === fieldInstance._ui.$errorsWrapper.find('.parsley-custom-error-message').length)
|
|
fieldInstance._ui.$errorsWrapper
|
|
.append($(fieldInstance.options.errorTemplate)
|
|
.addClass('parsley-custom-error-message'));
|
|
|
|
return fieldInstance._ui.$errorsWrapper
|
|
.addClass('filled')
|
|
.find('.parsley-custom-error-message')
|
|
.html(fieldInstance.options.errorMessage);
|
|
}
|
|
|
|
return fieldInstance._ui.$errorsWrapper
|
|
.removeClass('filled')
|
|
.find('.parsley-custom-error-message')
|
|
.remove();
|
|
}
|
|
|
|
// Show, hide, update failing constraints messages
|
|
for (var i = 0; i < diff.removed.length; i++)
|
|
this.removeError(fieldInstance, diff.removed[i].assert.name, true);
|
|
|
|
for (i = 0; i < diff.added.length; i++)
|
|
this.addError(fieldInstance, diff.added[i].assert.name, undefined, diff.added[i].assert, true);
|
|
|
|
for (i = 0; i < diff.kept.length; i++)
|
|
this.updateError(fieldInstance, diff.kept[i].assert.name, undefined, diff.kept[i].assert, true);
|
|
},
|
|
|
|
// TODO: strange API here, intuitive for manual usage with addError(pslyInstance, 'foo', 'bar')
|
|
// but a little bit complex for above internal usage, with forced undefined parametter..
|
|
addError: function (fieldInstance, name, message, assert, doNotUpdateClass) {
|
|
fieldInstance._ui.$errorsWrapper
|
|
.addClass('filled')
|
|
.append($(fieldInstance.options.errorTemplate)
|
|
.addClass('parsley-' + name)
|
|
.html(message || this._getErrorMessage(fieldInstance, assert)));
|
|
|
|
if (true !== doNotUpdateClass)
|
|
this._errorClass(fieldInstance);
|
|
},
|
|
|
|
// Same as above
|
|
updateError: function (fieldInstance, name, message, assert, doNotUpdateClass) {
|
|
fieldInstance._ui.$errorsWrapper
|
|
.addClass('filled')
|
|
.find('.parsley-' + name)
|
|
.html(message || this._getErrorMessage(fieldInstance, assert));
|
|
|
|
if (true !== doNotUpdateClass)
|
|
this._errorClass(fieldInstance);
|
|
},
|
|
|
|
// Same as above twice
|
|
removeError: function (fieldInstance, name, doNotUpdateClass) {
|
|
fieldInstance._ui.$errorsWrapper
|
|
.removeClass('filled')
|
|
.find('.parsley-' + name)
|
|
.remove();
|
|
|
|
// edge case possible here: remove a standard Parsley error that is still failing in fieldInstance.validationResult
|
|
// but highly improbable cuz' manually removing a well Parsley handled error makes no sense.
|
|
if (true !== doNotUpdateClass)
|
|
this.manageStatusClass(fieldInstance);
|
|
},
|
|
|
|
focus: function (formInstance) {
|
|
if (true === formInstance.validationResult || 'none' === formInstance.options.focus)
|
|
return formInstance._focusedField = null;
|
|
|
|
formInstance._focusedField = null;
|
|
|
|
for (var i = 0; i < formInstance.fields.length; i++)
|
|
if (true !== formInstance.fields[i].validationResult && formInstance.fields[i].validationResult.length > 0 && 'undefined' === typeof formInstance.fields[i].options.noFocus) {
|
|
if ('first' === formInstance.options.focus) {
|
|
formInstance._focusedField = formInstance.fields[i].$element;
|
|
return formInstance._focusedField.focus();
|
|
}
|
|
|
|
formInstance._focusedField = formInstance.fields[i].$element;
|
|
}
|
|
|
|
if (null === formInstance._focusedField)
|
|
return null;
|
|
|
|
return formInstance._focusedField.focus();
|
|
},
|
|
|
|
_getErrorMessage: function (fieldInstance, constraint) {
|
|
var customConstraintErrorMessage = constraint.name + 'Message';
|
|
|
|
if ('undefined' !== typeof fieldInstance.options[customConstraintErrorMessage])
|
|
return window.ParsleyValidator.formatMessage(fieldInstance.options[customConstraintErrorMessage], constraint.requirements);
|
|
|
|
return window.ParsleyValidator.getErrorMessage(constraint);
|
|
},
|
|
|
|
_diff: function (newResult, oldResult, deep) {
|
|
var
|
|
added = [],
|
|
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 ? this._diff(oldResult, newResult, true).added : []
|
|
};
|
|
},
|
|
|
|
setupForm: function (formInstance) {
|
|
formInstance.$element.on('submit.Parsley', false, $.proxy(formInstance.onSubmitValidate, formInstance));
|
|
|
|
// UI could be disabled
|
|
if (false === formInstance.options.uiEnabled)
|
|
return;
|
|
|
|
formInstance.$element.attr('novalidate', '');
|
|
},
|
|
|
|
setupField: function (fieldInstance) {
|
|
var _ui = { active: false };
|
|
|
|
// UI could be disabled
|
|
if (false === fieldInstance.options.uiEnabled)
|
|
return;
|
|
|
|
_ui.active = true;
|
|
|
|
// Give field its Parsley id in DOM
|
|
fieldInstance.$element.attr(fieldInstance.options.namespace + 'id', fieldInstance.__id__);
|
|
|
|
/** Generate important UI elements and store them in fieldInstance **/
|
|
// $errorClassHandler is the $element that woul have parsley-error and parsley-success classes
|
|
_ui.$errorClassHandler = this._manageClassHandler(fieldInstance);
|
|
|
|
// $errorsWrapper is a div that would contain the various field errors, it will be appended into $errorsContainer
|
|
_ui.errorsWrapperId = 'parsley-id-' + ('undefined' !== typeof fieldInstance.options.multiple ? 'multiple-' + fieldInstance.options.multiple : fieldInstance.__id__);
|
|
_ui.$errorsWrapper = $(fieldInstance.options.errorsWrapper).attr('id', _ui.errorsWrapperId);
|
|
|
|
// ValidationResult UI storage to detect what have changed bwt two validations, and update DOM accordingly
|
|
_ui.lastValidationResult = [];
|
|
_ui.validatedOnce = false;
|
|
_ui.validationInformationVisible = false;
|
|
|
|
// Store it in fieldInstance for later
|
|
fieldInstance._ui = _ui;
|
|
|
|
/** Mess with DOM now **/
|
|
this._insertErrorWrapper(fieldInstance);
|
|
|
|
// Bind triggers first time
|
|
this.actualizeTriggers(fieldInstance);
|
|
},
|
|
|
|
// Determine which element will have `parsley-error` and `parsley-success` classes
|
|
_manageClassHandler: function (fieldInstance) {
|
|
// An element selector could be passed through DOM with `data-parsley-class-handler=#foo`
|
|
if ('string' === typeof fieldInstance.options.classHandler && $(fieldInstance.options.classHandler).length)
|
|
return $(fieldInstance.options.classHandler);
|
|
|
|
// Class handled could also be determined by function given in Parsley options
|
|
var $handler = fieldInstance.options.classHandler(fieldInstance);
|
|
|
|
// If this function returned a valid existing DOM element, go for it
|
|
if ('undefined' !== typeof $handler && $handler.length)
|
|
return $handler;
|
|
|
|
// Otherwise, if simple element (input, texatrea, select..) it will perfectly host the classes
|
|
if ('undefined' === typeof fieldInstance.options.multiple || fieldInstance.$element.is('select'))
|
|
return fieldInstance.$element;
|
|
|
|
// But if multiple element (radio, checkbox), that would be their parent
|
|
return fieldInstance.$element.parent();
|
|
},
|
|
|
|
_insertErrorWrapper: function (fieldInstance) {
|
|
var $errorsContainer;
|
|
|
|
if ('string' === typeof fieldInstance.options.errorsContainer )
|
|
if ($(fieldInstance.options.errorsContainer + '').length)
|
|
return $(fieldInstance.options.errorsContainer).append(fieldInstance._ui.$errorsWrapper);
|
|
else if (window.console && window.console.warn)
|
|
window.console.warn('The errors container `' + fieldInstance.options.errorsContainer + '` does not exist in DOM');
|
|
|
|
|
|
if ('function' === typeof fieldInstance.options.errorsContainer)
|
|
$errorsContainer = fieldInstance.options.errorsContainer(fieldInstance);
|
|
|
|
if ('undefined' !== typeof $errorsContainer && $errorsContainer.length)
|
|
return $errorsContainer.append(fieldInstance._ui.$errorsWrapper);
|
|
|
|
return 'undefined' === typeof fieldInstance.options.multiple ? fieldInstance.$element.after(fieldInstance._ui.$errorsWrapper) : fieldInstance.$element.parent().after(fieldInstance._ui.$errorsWrapper);
|
|
},
|
|
|
|
actualizeTriggers: function (fieldInstance) {
|
|
var that = this;
|
|
|
|
// Remove Parsley events already binded on this field
|
|
if (fieldInstance.options.multiple)
|
|
$('[' + fieldInstance.options.namespace + 'multiple="' + fieldInstance.options.multiple + '"]').each(function () {
|
|
$(this).off('.Parsley');
|
|
});
|
|
else
|
|
fieldInstance.$element.off('.Parsley');
|
|
|
|
// If no trigger is set, all good
|
|
if (false === fieldInstance.options.trigger)
|
|
return;
|
|
|
|
var triggers = fieldInstance.options.trigger.replace(/^\s+/g , '').replace(/\s+$/g , '');
|
|
|
|
if ('' === triggers)
|
|
return;
|
|
|
|
// Bind fieldInstance.eventValidate if exists (for parsley.ajax for example), ParsleyUI.eventValidate otherwise
|
|
if (fieldInstance.options.multiple)
|
|
$('[' + fieldInstance.options.namespace + 'multiple="' + fieldInstance.options.multiple + '"]').each(function () {
|
|
$(this).on(
|
|
triggers.split(' ').join('.Parsley ') + '.Parsley',
|
|
false,
|
|
$.proxy('function' === typeof fieldInstance.eventValidate ? fieldInstance.eventValidate : that.eventValidate, fieldInstance));
|
|
});
|
|
else
|
|
fieldInstance.$element
|
|
.on(
|
|
triggers.split(' ').join('.Parsley ') + '.Parsley',
|
|
false,
|
|
$.proxy('function' === typeof fieldInstance.eventValidate ? fieldInstance.eventValidate : this.eventValidate, fieldInstance));
|
|
},
|
|
|
|
// Called through $.proxy with fieldInstance. `this` context is ParsleyField
|
|
eventValidate: function(event) {
|
|
// For keyup, keypress, keydown.. 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 (new RegExp('key').test(event.type))
|
|
if (!this._ui.validationInformationVisible && this.getValue().length <= this.options.validationThreshold)
|
|
return;
|
|
|
|
this._ui.validatedOnce = true;
|
|
this.validate();
|
|
},
|
|
|
|
manageFailingFieldTrigger: function (fieldInstance) {
|
|
fieldInstance._ui.failedOnce = true;
|
|
|
|
// Radio and checkboxes fields must bind every field multiple
|
|
if (fieldInstance.options.multiple)
|
|
$('[' + fieldInstance.options.namespace + 'multiple="' + fieldInstance.options.multiple + '"]').each(function () {
|
|
if (!new RegExp('change', 'i').test($(this).parsley().options.trigger || ''))
|
|
return $(this).on('change.ParsleyFailedOnce', false, $.proxy(fieldInstance.validate, fieldInstance));
|
|
});
|
|
|
|
// Select case
|
|
if (fieldInstance.$element.is('select'))
|
|
if (!new RegExp('change', 'i').test(fieldInstance.options.trigger || ''))
|
|
return fieldInstance.$element.on('change.ParsleyFailedOnce', false, $.proxy(fieldInstance.validate, fieldInstance));
|
|
|
|
// All other inputs fields
|
|
if (!new RegExp('keyup', 'i').test(fieldInstance.options.trigger || ''))
|
|
return fieldInstance.$element.on('keyup.ParsleyFailedOnce', false, $.proxy(fieldInstance.validate, fieldInstance));
|
|
},
|
|
|
|
reset: function (parsleyInstance) {
|
|
// Reset all event listeners
|
|
parsleyInstance.$element.off('.Parsley');
|
|
parsleyInstance.$element.off('.ParsleyFailedOnce');
|
|
|
|
// Nothing to do if UI never initialized for this field
|
|
if ('undefined' === typeof parsleyInstance._ui)
|
|
return;
|
|
|
|
if ('ParsleyForm' === parsleyInstance.__class__)
|
|
return;
|
|
|
|
// Reset all errors' li
|
|
parsleyInstance._ui.$errorsWrapper.children().each(function () {
|
|
$(this).remove();
|
|
});
|
|
|
|
// Reset validation class
|
|
this._resetClass(parsleyInstance);
|
|
|
|
// Reset validation flags and last validation result
|
|
parsleyInstance._ui.validatedOnce = false;
|
|
parsleyInstance._ui.lastValidationResult = [];
|
|
parsleyInstance._ui.validationInformationVisible = false;
|
|
},
|
|
|
|
destroy: function (parsleyInstance) {
|
|
this.reset(parsleyInstance);
|
|
|
|
if ('ParsleyForm' === parsleyInstance.__class__)
|
|
return;
|
|
|
|
parsleyInstance._ui.$errorsWrapper.remove();
|
|
delete parsleyInstance._ui;
|
|
},
|
|
|
|
_successClass: function (fieldInstance) {
|
|
fieldInstance._ui.validationInformationVisible = true;
|
|
fieldInstance._ui.$errorClassHandler.removeClass(fieldInstance.options.errorClass).addClass(fieldInstance.options.successClass);
|
|
},
|
|
_errorClass: function (fieldInstance) {
|
|
fieldInstance._ui.validationInformationVisible = true;
|
|
fieldInstance._ui.$errorClassHandler.removeClass(fieldInstance.options.successClass).addClass(fieldInstance.options.errorClass);
|
|
},
|
|
_resetClass: function (fieldInstance) {
|
|
fieldInstance._ui.$errorClassHandler.removeClass(fieldInstance.options.successClass).removeClass(fieldInstance.options.errorClass);
|
|
}
|
|
};
|
|
|
|
return ParsleyUI;
|
|
});
|