254 lines
7.8 KiB
JavaScript
254 lines
7.8 KiB
JavaScript
// `window.ParsleyExtend`, like `ParsleyAbstract`, is inherited by `ParsleyField` and `ParsleyForm`
|
||
// That way, we could add new methods or redefine some for these both classes. In particular case
|
||
// We are adding async validation methods that returns promises, bind them properly to triggered
|
||
// Events like onkeyup when field is invalid or on form submit. These validation methods adds an
|
||
// Extra `remote` validator which could not be simply added like other `ParsleyExtra` validators
|
||
// Because returns promises instead of booleans.
|
||
window.ParsleyExtend = window.ParsleyExtend || {};
|
||
window.ParsleyExtend = $.extend(window.ParsleyExtend, {
|
||
asyncSupport: true,
|
||
|
||
asyncValidators: $.extend({
|
||
default: {
|
||
fn: function (xhr) {
|
||
return 'resolved' === xhr.state();
|
||
},
|
||
url: false
|
||
},
|
||
reverse: {
|
||
fn: function (xhr) {
|
||
// If reverse option is set, a failing ajax request is considered successful
|
||
return 'rejected' === xhr.state();
|
||
},
|
||
url: false
|
||
}
|
||
}, window.ParsleyExtend.asyncValidators),
|
||
|
||
addAsyncValidator: function (name, fn, url) {
|
||
this.asyncValidators[name.toLowerCase()] = {
|
||
fn: fn,
|
||
url: url || false
|
||
};
|
||
|
||
return this;
|
||
},
|
||
|
||
asyncValidate: function () {
|
||
if ('ParsleyForm' === this.__class__)
|
||
return this._asyncValidateForm.apply(this, arguments);
|
||
|
||
return this._asyncValidateField.apply(this, arguments);
|
||
},
|
||
|
||
asyncIsValid: function () {
|
||
if ('ParsleyField' === this.__class__)
|
||
return this._asyncIsValidField.apply(this, arguments);
|
||
|
||
return this._asyncIsValidForm.apply(this, arguments);
|
||
},
|
||
|
||
onSubmitValidate: function (event) {
|
||
var that = this;
|
||
|
||
// This is a Parsley generated submit event, do not validate, do not prevent, simply exit and keep normal behavior
|
||
if (true === event.parsley)
|
||
return;
|
||
|
||
// Clone the event object
|
||
this.submitEvent = $.extend(true, {}, event);
|
||
|
||
// Prevent form submit and immediately stop its event propagation
|
||
if (event instanceof $.Event) {
|
||
event.stopImmediatePropagation();
|
||
event.preventDefault();
|
||
}
|
||
|
||
return this._asyncValidateForm(undefined, event)
|
||
.done(function () {
|
||
// If user do not have prevented the event, re-submit form
|
||
if (!that.submitEvent.isDefaultPrevented())
|
||
that.$element.trigger($.extend($.Event('submit'), { parsley: true }));
|
||
});
|
||
},
|
||
|
||
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.asyncValidate();
|
||
},
|
||
|
||
// Returns Promise
|
||
_asyncValidateForm: function (group, event) {
|
||
var
|
||
that = this,
|
||
promises = [];
|
||
|
||
this._refreshFields();
|
||
|
||
$.emit('parsley:form:validate', this);
|
||
|
||
for (var i = 0; i < this.fields.length; i++) {
|
||
|
||
// do not validate a field if not the same as given validation group
|
||
if (group && group !== this.fields[i].options.group)
|
||
continue;
|
||
|
||
promises.push(this.fields[i]._asyncValidateField());
|
||
}
|
||
|
||
return $.when.apply($, promises)
|
||
.always(function () {
|
||
$.emit('parsley:form:validated', that);
|
||
});
|
||
},
|
||
|
||
_asyncIsValidForm: function (group, force) {
|
||
var promises = [];
|
||
this._refreshFields();
|
||
|
||
for (var i = 0; i < this.fields.length; i++) {
|
||
|
||
// do not validate a field if not the same as given validation group
|
||
if (group && group !== this.fields[i].options.group)
|
||
continue;
|
||
|
||
promises.push(this.fields[i]._asyncIsValidField(force));
|
||
}
|
||
|
||
return $.when.apply($, promises);
|
||
},
|
||
|
||
_asyncValidateField: function (force) {
|
||
var that = this;
|
||
|
||
$.emit('parsley:field:validate', this);
|
||
|
||
return this._asyncIsValidField(force)
|
||
.done(function () {
|
||
$.emit('parsley:field:success', that);
|
||
})
|
||
.fail(function () {
|
||
$.emit('parsley:field:error', that);
|
||
})
|
||
.always(function () {
|
||
$.emit('parsley:field:validated', that);
|
||
});
|
||
},
|
||
|
||
_asyncIsValidField: function (force, value) {
|
||
var
|
||
deferred = $.Deferred(),
|
||
remoteConstraintIndex;
|
||
|
||
// If regular isValid (matching regular constraints) returns `false`, no need to go further
|
||
// Directly reject promise, do not run remote validator and save server load
|
||
if (false === this.isValid(force, value))
|
||
deferred.rejectWith(this);
|
||
|
||
// If regular constraints are valid, and there is a remote validator registered, run it
|
||
else if ('undefined' !== typeof this.constraintsByName.remote)
|
||
this._remote(deferred);
|
||
|
||
// Otherwise all is good, resolve promise
|
||
else
|
||
deferred.resolveWith(this);
|
||
|
||
// Return promise
|
||
return deferred.promise();
|
||
},
|
||
|
||
_remote: function (deferred) {
|
||
var
|
||
that = this,
|
||
data = {},
|
||
ajaxOptions,
|
||
csr,
|
||
validator = this.options.remoteValidator || (true === this.options.remoteReverse ? 'reverse' : 'default');
|
||
|
||
validator = validator.toLowerCase();
|
||
|
||
if ('undefined' === typeof this.asyncValidators[validator])
|
||
throw new Error('Calling an undefined async validator: `' + validator + '`');
|
||
|
||
// Fill data with current value
|
||
data[this.$element.attr('name') || this.$element.attr('id')] = this.getValue();
|
||
|
||
// All `$.ajax(options)` could be overridden or extended directly from DOM in `data-parsley-remote-options`
|
||
ajaxOptions = $.extend(true, {}, {
|
||
url: this.asyncValidators[validator].url || this.options.remote,
|
||
data: data,
|
||
type: 'GET'
|
||
}, this.options.remoteOptions || {});
|
||
|
||
// Generate store key based on ajax options
|
||
csr = $.param(ajaxOptions);
|
||
|
||
// Initialise querry cache
|
||
if ('undefined' === typeof this._remoteCache)
|
||
this._remoteCache = {};
|
||
|
||
// Try to retrieve stored xhr
|
||
if (!this._remoteCache[csr]) {
|
||
// Prevent multi burst xhr queries
|
||
if (this._xhr && 'pending' === this._xhr.state())
|
||
this._xhr.abort();
|
||
|
||
// Make ajax call
|
||
this._xhr = $.ajax(ajaxOptions)
|
||
|
||
// Store remote call result to avoid next calls with exact same parameters
|
||
this._remoteCache[csr] = this._xhr;
|
||
}
|
||
|
||
this._remoteCache[csr]
|
||
.done(function (data, textStatus, xhr) {
|
||
that._handleRemoteResult(validator, xhr, deferred);
|
||
})
|
||
.fail(function (xhr, status, message) {
|
||
// If we aborted the query, do not handle nothing for this value
|
||
if ('abort' === status)
|
||
return;
|
||
|
||
that._handleRemoteResult(validator, xhr, deferred);
|
||
});
|
||
},
|
||
|
||
_handleRemoteResult: function (validator, xhr, deferred) {
|
||
// If true, simply resolve and exit
|
||
if ('function' === typeof this.asyncValidators[validator].fn && this.asyncValidators[validator].fn(xhr)) {
|
||
deferred.resolveWith(this);
|
||
|
||
return;
|
||
}
|
||
|
||
// Else, create a proper remote validation Violation to trigger right UI
|
||
this.validationResult = [
|
||
new window.ParsleyValidator.Validator.Violation(
|
||
this.constraintsByName.remote,
|
||
this.getValue(),
|
||
null
|
||
)
|
||
];
|
||
|
||
deferred.rejectWith(this);
|
||
}
|
||
});
|
||
|
||
// Remote validator is just an always true sync validator with lowest (-1) priority possible
|
||
// It will be overloaded in `validateThroughValidator()` that will do the heavy async work
|
||
// This 'hack' is needed not to mess up too much with error messages and stuff in `ParsleyUI`
|
||
window.ParsleyConfig = window.ParsleyConfig || {};
|
||
window.ParsleyConfig.validators = window.ParsleyConfig.validators || {};
|
||
window.ParsleyConfig.validators.remote = {
|
||
fn: function () {
|
||
return true;
|
||
},
|
||
priority: -1
|
||
};
|