Files
wordpress-preseed/wp-content/plugins/advanced-custom-fields/assets/build/js/_acf-validation.js
2019-09-11 19:08:46 +02:00

1006 lines
19 KiB
JavaScript

(function($, undefined){
/**
* Validator
*
* The model for validating forms
*
* @date 4/9/18
* @since 5.7.5
*
* @param void
* @return void
*/
var Validator = acf.Model.extend({
/** @var string The model identifier. */
id: 'Validator',
/** @var object The model data. */
data: {
/** @var array The form errors. */
errors: [],
/** @var object The form notice. */
notice: null,
/** @var string The form status. loading, invalid, valid */
status: ''
},
/** @var object The model events. */
events: {
'changed:status': 'onChangeStatus'
},
/**
* addErrors
*
* Adds errors to the form.
*
* @date 4/9/18
* @since 5.7.5
*
* @param array errors An array of errors.
* @return void
*/
addErrors: function( errors ){
errors.map( this.addError, this );
},
/**
* addError
*
* Adds and error to the form.
*
* @date 4/9/18
* @since 5.7.5
*
* @param object error An error object containing input and message.
* @return void
*/
addError: function( error ){
this.data.errors.push( error );
},
/**
* hasErrors
*
* Returns true if the form has errors.
*
* @date 4/9/18
* @since 5.7.5
*
* @param void
* @return bool
*/
hasErrors: function(){
return this.data.errors.length;
},
/**
* clearErrors
*
* Removes any errors.
*
* @date 4/9/18
* @since 5.7.5
*
* @param void
* @return void
*/
clearErrors: function(){
return this.data.errors = [];
},
/**
* getErrors
*
* Returns the forms errors.
*
* @date 4/9/18
* @since 5.7.5
*
* @param void
* @return array
*/
getErrors: function(){
return this.data.errors;
},
/**
* getFieldErrors
*
* Returns the forms field errors.
*
* @date 4/9/18
* @since 5.7.5
*
* @param void
* @return array
*/
getFieldErrors: function(){
// vars
var errors = [];
var inputs = [];
// loop
this.getErrors().map(function(error){
// bail early if global
if( !error.input ) return;
// update if exists
var i = inputs.indexOf(error.input);
if( i > -1 ) {
errors[ i ] = error;
// update
} else {
errors.push( error );
inputs.push( error.input );
}
});
// return
return errors;
},
/**
* getGlobalErrors
*
* Returns the forms global errors (errors without a specific input).
*
* @date 4/9/18
* @since 5.7.5
*
* @param void
* @return array
*/
getGlobalErrors: function(){
// return array of errors that contain no input
return this.getErrors().filter(function(error){
return !error.input;
});
},
/**
* showErrors
*
* Displays all errors for this form.
*
* @date 4/9/18
* @since 5.7.5
*
* @param void
* @return void
*/
showErrors: function(){
// bail early if no errors
if( !this.hasErrors() ) {
return;
}
// vars
var fieldErrors = this.getFieldErrors();
var globalErrors = this.getGlobalErrors();
// vars
var errorCount = 0;
var $scrollTo = false;
// loop
fieldErrors.map(function( error ){
// get input
var $input = this.$('[name="' + error.input + '"]').first();
// if $_POST value was an array, this $input may not exist
if( !$input.length ) {
$input = this.$('[name^="' + error.input + '"]').first();
}
// bail early if input doesn't exist
if( !$input.length ) {
return;
}
// increase
errorCount++;
// get field
var field = acf.getClosestField( $input );
// show error
field.showError( error.message );
// set $scrollTo
if( !$scrollTo ) {
$scrollTo = field.$el;
}
}, this);
// errorMessage
var errorMessage = acf.__('Validation failed');
globalErrors.map(function( error ){
errorMessage += '. ' + error.message;
});
if( errorCount == 1 ) {
errorMessage += '. ' + acf.__('1 field requires attention');
} else if( errorCount > 1 ) {
errorMessage += '. ' + acf.__('%d fields require attention').replace('%d', errorCount);
}
// notice
if( this.has('notice') ) {
this.get('notice').update({
type: 'error',
text: errorMessage
});
} else {
var notice = acf.newNotice({
type: 'error',
text: errorMessage,
target: this.$el
});
this.set('notice', notice);
}
// if no $scrollTo, set to message
if( !$scrollTo ) {
$scrollTo = this.get('notice').$el;
}
// timeout
setTimeout(function(){
$("html, body").animate({ scrollTop: $scrollTo.offset().top - ( $(window).height() / 2 ) }, 500);
}, 10);
},
/**
* onChangeStatus
*
* Update the form class when changing the 'status' data
*
* @date 4/9/18
* @since 5.7.5
*
* @param object e The event object.
* @param jQuery $el The form element.
* @param string value The new status.
* @param string prevValue The old status.
* @return void
*/
onChangeStatus: function( e, $el, value, prevValue ){
this.$el.removeClass('is-'+prevValue).addClass('is-'+value);
},
/**
* validate
*
* Vaildates the form via AJAX.
*
* @date 4/9/18
* @since 5.7.5
*
* @param object args A list of settings to customize the validation process.
* @return bool True if the form is valid.
*/
validate: function( args ){
// default args
args = acf.parseArgs(args, {
// trigger event
event: false,
// reset the form after submit
reset: false,
// loading callback
loading: function(){},
// complete callback
complete: function(){},
// failure callback
failure: function(){},
// success callback
success: function( $form ){
$form.submit();
}
});
// return true if is valid - allows form submit
if( this.get('status') == 'valid' ) {
return true;
}
// return false if is currently validating - prevents form submit
if( this.get('status') == 'validating' ) {
return false;
}
// return true if no ACF fields exist (no need to validate)
if( !this.$('.acf-field').length ) {
return true;
}
// if event is provided, create a new success callback.
if( args.event ) {
var event = $.Event(null, args.event);
args.success = function(){
acf.enableSubmit( $(event.target) ).trigger( event );
}
}
// action for 3rd party
acf.doAction('validation_begin', this.$el);
// lock form
acf.lockForm( this.$el );
// loading callback
args.loading( this.$el, this );
// update status
this.set('status', 'validating');
// success callback
var onSuccess = function( json ){
// validate
if( !acf.isAjaxSuccess(json) ) {
return;
}
// filter
var data = acf.applyFilters('validation_complete', json.data, this.$el, this);
// add errors
if( !data.valid ) {
this.addErrors( data.errors );
}
};
// complete
var onComplete = function(){
// unlock form
acf.unlockForm( this.$el );
// failure
if( this.hasErrors() ) {
// update status
this.set('status', 'invalid');
// action
acf.doAction('validation_failure', this.$el, this);
// display errors
this.showErrors();
// failure callback
args.failure( this.$el, this );
// success
} else {
// update status
this.set('status', 'valid');
// remove previous error message
if( this.has('notice') ) {
this.get('notice').update({
type: 'success',
text: acf.__('Validation successful'),
timeout: 1000
});
}
// action
acf.doAction('validation_success', this.$el, this);
acf.doAction('submit', this.$el);
// success callback (submit form)
args.success( this.$el, this );
// lock form
acf.lockForm( this.$el );
// reset
if( args.reset ) {
this.reset();
}
}
// complete callback
args.complete( this.$el, this );
// clear errors
this.clearErrors();
};
// serialize form data
var data = acf.serialize( this.$el );
data.action = 'acf/validate_save_post';
// ajax
$.ajax({
url: acf.get('ajaxurl'),
data: acf.prepareForAjax(data),
type: 'post',
dataType: 'json',
context: this,
success: onSuccess,
complete: onComplete
});
// return false to fail validation and allow AJAX
return false
},
/**
* setup
*
* Called during the constructor function to setup this instance
*
* @date 4/9/18
* @since 5.7.5
*
* @param jQuery $form The form element.
* @return void
*/
setup: function( $form ){
// set $el
this.$el = $form;
},
/**
* reset
*
* Rests the validation to be used again.
*
* @date 6/9/18
* @since 5.7.5
*
* @param void
* @return void
*/
reset: function(){
// reset data
this.set('errors', []);
this.set('notice', null);
this.set('status', '');
// unlock form
acf.unlockForm( this.$el );
}
});
/**
* getValidator
*
* Returns the instance for a given form element.
*
* @date 4/9/18
* @since 5.7.5
*
* @param jQuery $el The form element.
* @return object
*/
var getValidator = function( $el ){
// instantiate
var validator = $el.data('acf');
if( !validator ) {
validator = new Validator( $el );
}
// return
return validator;
};
/**
* acf.validateForm
*
* A helper function for the Validator.validate() function.
* Returns true if form is valid, or fetches a validation request and returns false.
*
* @date 4/4/18
* @since 5.6.9
*
* @param object args A list of settings to customize the validation process.
* @return bool
*/
acf.validateForm = function( args ){
return getValidator( args.form ).validate( args );
};
/**
* acf.enableSubmit
*
* Enables a submit button and returns the element.
*
* @date 30/8/18
* @since 5.7.4
*
* @param jQuery $submit The submit button.
* @return jQuery
*/
acf.enableSubmit = function( $submit ){
return $submit.removeClass('disabled');
};
/**
* acf.disableSubmit
*
* Disables a submit button and returns the element.
*
* @date 30/8/18
* @since 5.7.4
*
* @param jQuery $submit The submit button.
* @return jQuery
*/
acf.disableSubmit = function( $submit ){
return $submit.addClass('disabled');
};
/**
* acf.showSpinner
*
* Shows the spinner element.
*
* @date 4/9/18
* @since 5.7.5
*
* @param jQuery $spinner The spinner element.
* @return jQuery
*/
acf.showSpinner = function( $spinner ){
$spinner.addClass('is-active'); // add class (WP > 4.2)
$spinner.css('display', 'inline-block'); // css (WP < 4.2)
return $spinner;
};
/**
* acf.hideSpinner
*
* Hides the spinner element.
*
* @date 4/9/18
* @since 5.7.5
*
* @param jQuery $spinner The spinner element.
* @return jQuery
*/
acf.hideSpinner = function( $spinner ){
$spinner.removeClass('is-active'); // add class (WP > 4.2)
$spinner.css('display', 'none'); // css (WP < 4.2)
return $spinner;
};
/**
* acf.lockForm
*
* Locks a form by disabeling its primary inputs and showing a spinner.
*
* @date 4/9/18
* @since 5.7.5
*
* @param jQuery $form The form element.
* @return jQuery
*/
acf.lockForm = function( $form ){
// vars
var $wrap = findSubmitWrap( $form );
var $submit = $wrap.find('.button, [type="submit"]');
var $spinner = $wrap.find('.spinner, .acf-spinner');
// hide all spinners (hides the preview spinner)
acf.hideSpinner( $spinner );
// lock
acf.disableSubmit( $submit );
acf.showSpinner( $spinner.last() );
return $form;
};
/**
* acf.unlockForm
*
* Unlocks a form by enabeling its primary inputs and hiding all spinners.
*
* @date 4/9/18
* @since 5.7.5
*
* @param jQuery $form The form element.
* @return jQuery
*/
acf.unlockForm = function( $form ){
// vars
var $wrap = findSubmitWrap( $form );
var $submit = $wrap.find('.button, [type="submit"]');
var $spinner = $wrap.find('.spinner, .acf-spinner');
// unlock
acf.enableSubmit( $submit );
acf.hideSpinner( $spinner );
return $form;
};
/**
* findSubmitWrap
*
* An internal function to find the 'primary' form submit wrapping element.
*
* @date 4/9/18
* @since 5.7.5
*
* @param jQuery $form The form element.
* @return jQuery
*/
var findSubmitWrap = function( $form ){
// default post submit div
var $wrap = $form.find('#submitdiv');
if( $wrap.length ) {
return $wrap;
}
// 3rd party publish box
var $wrap = $form.find('#submitpost');
if( $wrap.length ) {
return $wrap;
}
// term, user
var $wrap = $form.find('p.submit').last();
if( $wrap.length ) {
return $wrap;
}
// front end form
var $wrap = $form.find('.acf-form-submit');
if( $wrap.length ) {
return $wrap;
}
// default
return $form;
};
/**
* acf.validation
*
* Global validation logic
*
* @date 4/4/18
* @since 5.6.9
*
* @param void
* @return void
*/
acf.validation = new acf.Model({
/** @var string The model identifier. */
id: 'validation',
/** @var bool The active state. Set to false before 'prepare' to prevent validation. */
active: true,
/** @var string The model initialize time. */
wait: 'prepare',
/** @var object The model actions. */
actions: {
'ready': 'addInputEvents',
'append': 'addInputEvents'
},
/** @var object The model events. */
events: {
'click input[type="submit"]': 'onClickSubmit',
'click button[type="submit"]': 'onClickSubmit',
//'click #editor .editor-post-publish-button': 'onClickSubmitGutenberg',
'click #save-post': 'onClickSave',
'submit form#post': 'onSubmitPost',
'submit form': 'onSubmit',
},
/**
* initialize
*
* Called when initializing the model.
*
* @date 4/9/18
* @since 5.7.5
*
* @param void
* @return void
*/
initialize: function(){
// check 'validation' setting
if( !acf.get('validation') ) {
this.active = false;
this.actions = {};
this.events = {};
}
},
/**
* enable
*
* Enables validation.
*
* @date 4/9/18
* @since 5.7.5
*
* @param void
* @return void
*/
enable: function(){
this.active = true;
},
/**
* disable
*
* Disables validation.
*
* @date 4/9/18
* @since 5.7.5
*
* @param void
* @return void
*/
disable: function(){
this.active = false;
},
/**
* reset
*
* Rests the form validation to be used again
*
* @date 6/9/18
* @since 5.7.5
*
* @param jQuery $form The form element.
* @return void
*/
reset: function( $form ){
getValidator( $form ).reset();
},
/**
* addInputEvents
*
* Adds 'invalid' event listeners to HTML inputs.
*
* @date 4/9/18
* @since 5.7.5
*
* @param jQuery $el The element being added / readied.
* @return void
*/
addInputEvents: function( $el ){
// vars
var $inputs = $('.acf-field [name]', $el);
// check
if( $inputs.length ) {
this.on( $inputs, 'invalid', 'onInvalid' );
}
},
/**
* onInvalid
*
* Callback for the 'invalid' event.
*
* @date 4/9/18
* @since 5.7.5
*
* @param object e The event object.
* @param jQuery $el The input element.
* @return void
*/
onInvalid: function( e, $el ){
// prevent default
// - prevents browser error message
// - also fixes chrome bug where 'hidden-by-tab' field throws focus error
e.preventDefault();
// vars
var $form = $el.closest('form');
// check form exists
if( $form.length ) {
// add error to validator
getValidator( $form ).addError({
input: $el.attr('name'),
message: e.target.validationMessage
});
// trigger submit on $form
// - allows for "save", "preview" and "publish" to work
$form.submit();
}
},
/**
* onClickSubmit
*
* Callback when clicking submit.
*
* @date 4/9/18
* @since 5.7.5
*
* @param object e The event object.
* @param jQuery $el The input element.
* @return void
*/
onClickSubmit: function( e, $el ){
// store the "click event" for later use in this.onSubmit()
this.set('originalEvent', e);
},
/**
* onClickSave
*
* Set ignore to true when saving a draft.
*
* @date 4/9/18
* @since 5.7.5
*
* @param object e The event object.
* @param jQuery $el The input element.
* @return void
*/
onClickSave: function( e, $el ) {
this.set('ignore', true);
},
/**
* onClickSubmitGutenberg
*
* Custom validation event for the gutenberg editor.
*
* @date 29/10/18
* @since 5.8.0
*
* @param object e The event object.
* @param jQuery $el The input element.
* @return void
*/
onClickSubmitGutenberg: function( e, $el ){
// validate
var valid = acf.validateForm({
form: $('#editor'),
event: e,
reset: true,
failure: function( $form, validator ){
var $notice = validator.get('notice').$el;
$notice.appendTo('.components-notice-list');
$notice.find('.acf-notice-dismiss').removeClass('small');
}
});
// if not valid, stop event and allow validation to continue
if( !valid ) {
e.preventDefault();
e.stopImmediatePropagation();
}
},
/**
* onSubmitPost
*
* Callback when the 'post' form is submit.
*
* @date 5/3/19
* @since 5.7.13
*
* @param object e The event object.
* @param jQuery $el The input element.
* @return void
*/
onSubmitPost: function( e, $el ) {
// Check if is preview.
if( $('input#wp-preview').val() === 'dopreview' ) {
// Ignore validation.
this.set('ignore', true);
// Unlock form to fix conflict with core "submit.edit-post" event causing all submit buttons to be disabled.
acf.unlockForm( $el )
}
},
/**
* onSubmit
*
* Callback when the form is submit.
*
* @date 4/9/18
* @since 5.7.5
*
* @param object e The event object.
* @param jQuery $el The input element.
* @return void
*/
onSubmit: function( e, $el ){
// Allow form to submit if...
if(
// Validation has been disabled.
!this.active
// Or this event is to be ignored.
|| this.get('ignore')
// Or this event has already been prevented.
|| e.isDefaultPrevented()
) {
// Return early and call reset function.
return this.allowSubmit();
}
// Validate form.
var valid = acf.validateForm({
form: $el,
event: this.get('originalEvent')
});
// If not valid, stop event to prevent form submit.
if( !valid ) {
e.preventDefault();
}
},
/**
* allowSubmit
*
* Resets data during onSubmit when the form is allowed to submit.
*
* @date 5/3/19
* @since 5.7.13
*
* @param void
* @return void
*/
allowSubmit: function(){
// Reset "ignore" state.
this.set('ignore', false);
// Reset "originalEvent" object.
this.set('originalEvent', false);
// Return true
return true;
}
});
})(jQuery);