You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
273 lines
10 KiB
273 lines
10 KiB
( function ( $, rwmb, i18n ) {
|
|
'use strict';
|
|
|
|
/**
|
|
* Extract the validation key from an input's name attribute. Usually it's the field ID, but sometimes (like for `file`), it's the field's input name.
|
|
*
|
|
* field[] => field // Fields with multiple values: file, checkbox list, etc.
|
|
* field[1] => field // Cloneable fields
|
|
* field[1][] => field // Cloneable fields with multiple values: file, checkbox list, etc.
|
|
*
|
|
* group[field][] => field // Group with fields with multiple values: file, checkbox list, etc.
|
|
* group[field][1] => field // Group with cloneable fields
|
|
* group[field][1][] => field // Group with cloneable fields with multiple values: file, checkbox list, etc.
|
|
*
|
|
* group[1][field][] => field // Cloneable group with fields with multiple values: file, checkbox list, etc.
|
|
* group[1][field][1] => field // Cloneable group with cloneable fields
|
|
* group[1][field][1][] => field // Cloneable group with cloneable fields with multiple values: file, checkbox list, etc.
|
|
*
|
|
* group[subgroup][field][] => field // Subgroup with fields with multiple values: file, checkbox list, etc.
|
|
* group[subgroup][field][1] => field // Subgroup with cloneable fields
|
|
* group[subgroup][field][1][] => field // Subgroup with cloneable fields with multiple values: file, checkbox list, etc.
|
|
*
|
|
* group[subgroup][1][field][] => field // Cloneable subgroup with fields with multiple values: file, checkbox list, etc.
|
|
* group[subgroup][1][field][1] => field // Cloneable subgroup with cloneable fields
|
|
* group[subgroup][1][field][1][] => field // Cloneable subgroup with cloneable fields with multiple values: file, checkbox list, etc.
|
|
*
|
|
* group[1][subgroup][field][] => field // Cloneable group with subgroup with fields with multiple values: file, checkbox list, etc.
|
|
* group[1][subgroup][field][1] => field // Cloneable group with subgroup with cloneable fields
|
|
* group[1][subgroup][field][1][] => field // Cloneable group with subgroup with cloneable fields with multiple values: file, checkbox list, etc.
|
|
*
|
|
* group[1][subgroup][1][field][] => field // Cloneable group with cloneable subgroup with fields with multiple values: file, checkbox list, etc.
|
|
* group[1][subgroup][1][field][1] => field // Cloneable group with cloneable subgroup with cloneable fields
|
|
* group[1][subgroup][1][field][1][] => field // Cloneable group with cloneable subgroup with cloneable fields with multiple values: file, checkbox list, etc.
|
|
*/
|
|
const getValidationKey = name => {
|
|
// Detect name parts in format of anything[] or anything[1].
|
|
let parts = name.match( /^(.+?)(?:\[\d+\]|(?:\[\]))?$/ );
|
|
|
|
if ( parts[ 1 ] && isNaN( parts[ 1 ] ) ) {
|
|
// Remove []
|
|
let words = name.match( /(\w+)|(\[\w+\])/g );
|
|
let resultArray = [ words.join( "" ) ];
|
|
|
|
// Remove characters "[" and "]".
|
|
words.forEach( matchedValue => {
|
|
if ( matchedValue.startsWith( "[" ) ) {
|
|
resultArray.push( matchedValue.substring( 1, matchedValue.length - 1 ) );
|
|
} else {
|
|
resultArray.push( matchedValue );
|
|
}
|
|
} );
|
|
|
|
parts[ 0 ] = resultArray[ 0 ];
|
|
parts[ 1 ] = isNaN( resultArray[ resultArray.length - 1 ] ) ? resultArray[ resultArray.length - 1 ] : resultArray[ resultArray.length - 2 ];
|
|
}
|
|
|
|
return parts.pop();
|
|
};
|
|
|
|
/**
|
|
* Fix validation not working for cloneable files or fields in groups.
|
|
*/
|
|
$.validator.staticRules = function ( element ) {
|
|
let rules = {},
|
|
validator = $.data( element.form, "validator" );
|
|
|
|
// No rules.
|
|
if ( validator.settings.rules === null || Object.keys( validator.settings.rules ).length === 0 ) {
|
|
return rules;
|
|
}
|
|
|
|
// Do not validate hidden fields.
|
|
if ( element.type === 'hidden' ) {
|
|
return rules;
|
|
}
|
|
|
|
let key = getValidationKey( element.name );
|
|
|
|
/**
|
|
* Cloneable files or files in groups.
|
|
* Input name is transformed into format `_file_{unique_id}`
|
|
* There is also a hidden input with name `_index_{field_id}` with value `_file_{unique_id}`
|
|
*
|
|
* In this case, `key` is always `_file_{unique_id}`
|
|
*
|
|
* Note that for cloneable files, validation rule is set for `_index_{field_id}`. For files in groups, validation rule is still `{field_id}`.
|
|
*/
|
|
if ( element.type === 'file' && ( $( element ).closest( '.rwmb-clone' ).length > 0 || $( element ).closest( '.rwmb-group-wrapper' ).length > 0 ) ) {
|
|
const $input = $( element ).closest( '.rwmb-input' );
|
|
const $indexInput = $input.find( '*[value="' + key + '"]' );
|
|
|
|
key = getValidationKey( $indexInput.attr( 'name' ) );
|
|
|
|
// Remove prefix `_index_` from input name when in groups.
|
|
if ( !validator.settings.rules[ key ] && key.includes( '_index_' ) ) {
|
|
key = key.slice( 7 );
|
|
}
|
|
|
|
if ( validator.settings.rules[ key ] ) {
|
|
// Set message for element.
|
|
validator.settings.messages[ element.name ] = validator.settings.messages[ key ];
|
|
// Set rule for element.
|
|
return $.validator.normalizeRule( validator.settings.rules[ key ] ) || {};
|
|
}
|
|
|
|
return rules;
|
|
}
|
|
|
|
// For normal fields and fields in groups: set rules by their field IDs (validation keys).
|
|
|
|
// Set message for element.
|
|
validator.settings.messages[ element.name ] = validator.settings.messages[ key ];
|
|
// Set rule for element.
|
|
return $.validator.normalizeRule( validator.settings.rules[ key ] ) || {};
|
|
};
|
|
|
|
/**
|
|
* Make jQuery Validation works with multiple inputs with same names.
|
|
* Need for file, image fields where users can upload multiple files with same input names.
|
|
*
|
|
* @link https://stackoverflow.com/q/931687/371240
|
|
*/
|
|
$.validator.prototype.checkForm = function () {
|
|
this.prepareForm();
|
|
for ( var i = 0, elements = ( this.currentElements = this.elements() ); elements[ i ]; i++ ) {
|
|
if ( this.findByName( elements[ i ].name ).length !== undefined && this.findByName( elements[ i ].name ).length > 1 ) {
|
|
for ( var cnt = 0; cnt < this.findByName( elements[ i ].name ).length; cnt++ ) {
|
|
this.check( this.findByName( elements[ i ].name )[ cnt ] );
|
|
}
|
|
} else {
|
|
this.check( elements[ i ] );
|
|
}
|
|
}
|
|
return this.valid();
|
|
};
|
|
|
|
class Validation {
|
|
constructor( formSelector ) {
|
|
this.$form = $( formSelector );
|
|
this.validationElements = this.$form.find( '.rwmb-validation' );
|
|
this.showAsterisks();
|
|
this.getSettings();
|
|
}
|
|
|
|
init() {
|
|
this.$form
|
|
// Update underlying textarea before submit.
|
|
// Don't use submitHandler() because form can be submitted via Ajax on the front end.
|
|
.on( 'submit', function () {
|
|
if ( typeof tinyMCE !== 'undefined' ) {
|
|
tinyMCE.triggerSave();
|
|
}
|
|
} )
|
|
.validate( this.settings );
|
|
}
|
|
|
|
showAsterisks() {
|
|
this.validationElements.each( function () {
|
|
const data = $( this ).data( 'validation' );
|
|
|
|
$.each( data.rules, function ( k, v ) {
|
|
if ( !v[ 'required' ] ) {
|
|
return;
|
|
}
|
|
let $el = $( '[name="' + k + '"]' );
|
|
if ( !$el.length ) {
|
|
$el = $( '[name*="[' + k + ']"]' ); // Subfields in groups.
|
|
}
|
|
if ( $el.length ) {
|
|
$el.closest( '.rwmb-input' ).siblings( '.rwmb-label' ).find( 'label' ).append( '<span class="rwmb-required">*</span>' );
|
|
}
|
|
} );
|
|
} );
|
|
}
|
|
|
|
getSettings() {
|
|
this.settings = {
|
|
ignore: ':not(.rwmb-media,.rwmb-image_select,.rwmb-wysiwyg,.rwmb-color,.rwmb-map,.rwmb-osm,.rwmb-switch,[class|="rwmb"])',
|
|
errorPlacement: function ( error, element ) {
|
|
error.appendTo( element.closest( '.rwmb-input' ) );
|
|
},
|
|
errorClass: 'rwmb-error',
|
|
errorElement: 'p',
|
|
invalidHandler: this.invalidHandler.bind( this )
|
|
};
|
|
|
|
// Gather all validation rules.
|
|
var that = this;
|
|
this.validationElements.each( function () {
|
|
$.extend( true, that.settings, $( this ).data( 'validation' ) );
|
|
} );
|
|
}
|
|
|
|
invalidHandler() {
|
|
this.showMessage();
|
|
// Group field will automatically expand and show an error warning when collapsing
|
|
for ( var i = 0; i < this.$form.data( 'validator' ).errorList.length; i++ ) {
|
|
$( '#' + this.$form.data( 'validator' ).errorList[ i ].element.id ).closest( '.rwmb-group-collapsed' ).removeClass( 'rwmb-group-collapsed' );
|
|
}
|
|
// Custom event for showing error fields inside tabs/hidden divs. Use setTimeout() to run after error class is added to inputs.
|
|
var that = this;
|
|
setTimeout( function () {
|
|
that.$form.trigger( 'after_validate' );
|
|
}, 200 );
|
|
}
|
|
|
|
showMessage() {
|
|
// Re-enable the submit ( publish/update ) button and hide the ajax indicator
|
|
$( '#publish' ).removeClass( 'button-primary-disabled' );
|
|
$( '#ajax-loading' ).attr( 'style', '' );
|
|
$( '#rwmb-validation-message' ).remove();
|
|
this.$form.before( '<div id="rwmb-validation-message" class="notice notice-error is-dismissible"><p>' + i18n.message + '</p></div>' );
|
|
}
|
|
};
|
|
|
|
class GutenbergValidation extends Validation {
|
|
init() {
|
|
var that = this,
|
|
editor = wp.data.dispatch( 'core/editor' ),
|
|
savePost = editor.savePost; // Reference original method.
|
|
|
|
if ( that.settings ) {
|
|
that.$form.validate( that.settings );
|
|
}
|
|
|
|
// Change the editor method.
|
|
editor.savePost = function ( options = {} ) {
|
|
// Bypass the validation when previewing in Gutenberg.
|
|
if ( typeof options === 'object' && options.isPreview ) {
|
|
return savePost( options );
|
|
}
|
|
|
|
// Must call savePost() here instead of in submitHandler() because the form has inline onsubmit callback.
|
|
if ( that.$form.valid() ) {
|
|
return savePost( options );
|
|
}
|
|
};
|
|
}
|
|
|
|
showMessage() {
|
|
wp.data.dispatch( 'core/notices' ).createErrorNotice( i18n.message, {
|
|
id: 'meta-box-validation',
|
|
isDismissible: true
|
|
} );
|
|
}
|
|
};
|
|
|
|
// Run on document ready.
|
|
function init() {
|
|
if ( rwmb.isGutenberg ) {
|
|
var advanced = new GutenbergValidation( '.metabox-location-advanced' ),
|
|
normal = new GutenbergValidation( '.metabox-location-normal' ),
|
|
side = new GutenbergValidation( '.metabox-location-side' );
|
|
|
|
side.init();
|
|
normal.init();
|
|
advanced.init();
|
|
return;
|
|
}
|
|
|
|
// Edit post, edit term, edit user, front-end form.
|
|
var $forms = $( '#post, #edittag, #your-profile, .rwmb-form' );
|
|
$forms.each( function () {
|
|
var form = new Validation( this );
|
|
form.init();
|
|
} );
|
|
};
|
|
|
|
rwmb.$document
|
|
.on( 'mb_ready', init );
|
|
|
|
} )( jQuery, rwmb, rwmbValidation );
|