/*!
SerializeJSON jQuery plugin.
https://github.com/marioizquierdo/jquery.serializeJSON
version 3.2.1 (Feb, 2021)
Copyright (c) 2012-2021 Mario Izquierdo
Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
*/
(function( factory ) {
var jQuery;
/* global define, require, module */
if ( 'function' === typeof define && define.amd ) { // AMD. Register as an anonymous module.
define( ['jquery'], factory );
} else if ( 'object' === typeof exports ) { // Node/CommonJS.
jQuery = require( 'jquery' );
module.exports = factory( jQuery );
} else { // Browser globals (zepto supported).
factory( window.jQuery || window.Zepto || window.$ ); // Zepto supported on browsers as well.
}
}( function( $ ) {
'use strict';
var rCRLF = /\r?\n/g;
var rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i;
var rsubmittable = /^(?:input|select|textarea|keygen)/i;
var rcheckableType = /^(?:checkbox|radio)$/i;
$.fn.serializeJSON = function( options ) {
var f = $.serializeJSON;
var _this = this; // NOTE: the set of matched elements is most likely a form, but it could also be a group of inputs.
var opts = f.setupOpts( options ); // Validate options and apply defaults.
var typeFunctions = $.extend( {}, opts.defaultTypes, opts.customTypes );
// Make a list with {name, value, el} for each input element.
var serializedArray = f.serializeArray( _this, opts );
// Convert the serializedArray into a serializedObject with nested keys.
var serializedObject = {};
$.each(
serializedArray,
function( _i, obj ) {
var nameSansType = obj.name;
var type = $( obj.el ).attr( 'data-value-type' );
var p;
var typedValue;
var keys;
if ( ! type && ! opts.disableColonTypes ) { // Try getting the type from the input name.
p = f.splitType( obj.name ); // "foo:string" => ["foo", "string"].
nameSansType = p[0];
type = p[1];
}
if ( 'skip' === type ) {
return; // Ignore fields with type skip.
}
if ( ! type ) {
type = opts.defaultType; // "string" by default
}
typedValue = f.applyTypeFunc( obj.name, obj.value, type, obj.el, typeFunctions ); // Parse type as string, number, etc.
if ( ! typedValue && f.shouldSkipFalsy( obj.name, nameSansType, type, obj.el, opts ) ) {
return; // Ignore falsy inputs if specified in the options.
}
keys = f.splitInputNameIntoKeysArray( nameSansType );
f.deepSet( serializedObject, keys, typedValue, opts );
}
);
return serializedObject;
};
// Use $.serializeJSON as namespace for the auxiliary functions
// and to define defaults.
$.serializeJSON = {
defaultOptions: {}, // Reassign to override option defaults for all serializeJSON calls.
defaultBaseOptions: { // Do not modify, use defaultOptions instead.
checkboxUncheckedValue: undefined, // To include that value for unchecked checkboxes (instead of ignoring them).
useIntKeysAsArrayIndex: false, // Tip: name="foo[2]" value="v" => {foo: [null, null, "v"]}, instead of {foo: ["2": "v"]}.
skipFalsyValuesForTypes: [], // Skip serialization of falsy values for listed value types.
skipFalsyValuesForFields: [], // Skip serialization of falsy values for listed field names.
disableColonTypes: false, // Do not interpret ":type" suffix as a type.
customTypes: {}, // Extends defaultTypes.
defaultTypes: {
'string': function( str ) {
return String( str );
},
'number': function( str ) {
return Number( str );
},
'boolean': function( str ) {
var falses = ['false', 'null', 'undefined', '', '0'];
return -1 === falses.indexOf( str );
},
'null': function( str ) {
var falses = ['false', 'null', 'undefined', '', '0'];
return -1 === falses.indexOf( str ) ? str : null;
},
'array': function( str ) {
return JSON.parse( str );
},
'object': function( str ) {
return JSON.parse( str );
},
'skip': null // Skip is a special type used to ignore fields.
},
defaultType: 'string'
},
// Validate and set defaults.
setupOpts: function( options ) {
var f;
var validOpts;
var opt;
if ( null == options ) {
options = {};
}
f = $.serializeJSON;
// Validate.
validOpts = [
'checkboxUncheckedValue',
'useIntKeysAsArrayIndex',
'skipFalsyValuesForTypes',
'skipFalsyValuesForFields',
'disableColonTypes',
'customTypes',
'defaultTypes',
'defaultType'
];
for ( opt in options ) {
if ( validOpts.indexOf( opt ) === -1 ) {
throw new Error( 'serializeJSON ERROR: invalid option \'' + opt + '\'. Please use one of ' + validOpts.join( ', ' ) );
}
}
// Helper to get options or defaults.
return $.extend( {}, f.defaultBaseOptions, f.defaultOptions, options );
},
// Just like jQuery's serializeArray method, returns an array of objects with name and value.
// but also includes the dom element (el) and is handles unchecked checkboxes if the option or data attribute are provided.
serializeArray: function( _this, opts ) {
var f;
var elements;
if ( null == opts ) {
opts = {};
}
f = $.serializeJSON;
return _this.map(
function() {
elements = $.prop( this, 'elements' ); // Handle propHook "elements" to filter or add form elements.
return elements ? $.makeArray( elements ) : this;
}
).filter(
function() {
var $el = $( this );
var type = this.type;
// Filter with the standard W3C rules for successful controls: http://www.w3.org/TR/html401/interact/forms.html#h-17.13.2.
return this.name && // Must contain a name attribute.
! $el.is( ':disabled' ) && // Must not be disable (use .is(":disabled") so that fieldset[disabled] works).
rsubmittable.test( this.nodeName ) && ! rsubmitterTypes.test( type ) && // only serialize submittable fields (and not buttons).
(this.checked || ! rcheckableType.test( type ) || f.getCheckboxUncheckedValue( $el, opts ) != null); // skip unchecked checkboxes (unless using opts).
}
).map(
function( _i, el ) {
var $el = $( this );
var val = $el.val();
var type = this.type; // "input", "select", "textarea", "checkbox", etc.
if ( null == val ) {
return null;
}
if ( rcheckableType.test( type ) && ! this.checked ) {
val = f.getCheckboxUncheckedValue( $el, opts );
}
if ( isArray( val ) ) {
return $.map(
val,
function( val ) {
return {name: el.name, value: val.replace( rCRLF, '\r\n' ), el: el};
}
);
}
return {name: el.name, value: val.replace( rCRLF, '\r\n' ), el: el};
}
).get();
},
getCheckboxUncheckedValue: function( $el, opts ) {
var val = $el.attr( 'data-unchecked-value' );
if ( null == val ) {
val = opts.checkboxUncheckedValue;
}
return val;
},
// Parse value with type function.
applyTypeFunc: function( name, strVal, type, el, typeFunctions ) {
var typeFunc = typeFunctions[type];
if ( ! typeFunc ) { // quick feedback to user if there is a typo or missconfiguration.
throw new Error(
'serializeJSON ERROR: Invalid type ' + type + ' found in input name \'' + name + '\', please use one of ' + objectKeys( typeFunctions )
.join( ', ' )
);
}
return typeFunc( strVal, el );
},
// Splits a field name into the name and the type. Examples:
// "foo" => ["foo", ""].
// "foo:boolean" => ["foo", "boolean"].
// "foo[bar]:null" => ["foo[bar]", "null"].
splitType: function( name ) {
var parts = name.split( ':' );
var t;
if ( parts.length > 1 ) {
t = parts.pop();
return [parts.join( ':' ), t];
} else {
return [name, ''];
}
},
// Check if this input should be skipped when it has a falsy value,
// depending on the options to skip values by name or type, and the data-skip-falsy attribute.
shouldSkipFalsy: function( name, nameSansType, type, el, opts ) {
var skipFromDataAttr = $( el ).attr( 'data-skip-falsy' );
var optForFields;
var optForTypes;
if ( skipFromDataAttr != null ) {
return skipFromDataAttr !== 'false'; // any value is true, except the string "false".
}
optForFields = opts.skipFalsyValuesForFields;
if ( optForFields && (optForFields.indexOf( nameSansType ) !== -1 || optForFields.indexOf( name ) !== -1) ) {
return true;
}
optForTypes = opts.skipFalsyValuesForTypes;
return ! ! ( optForTypes && optForTypes.indexOf( type ) !== -1 );
},
// Split the input name in programmatically readable keys.
// Examples:
// "foo" => ["foo"]
// "[foo]" => ["foo"]
// "foo[inn][bar]" => ["foo", "inn", "bar"]
// "foo[inn[bar]]" => ["foo", "inn", "bar"]
// "foo[inn][arr][0]" => ["foo", "inn", "arr", "0"]
// "arr[][val]" => ["arr", "", "val"].
splitInputNameIntoKeysArray: function( nameWithNoType ) {
var keys = nameWithNoType.split( '[' ); // split string into array.
keys = $.map(
keys,
function( key ) {
return key.replace( /\]/g, '' );
}
); // Remove closing brackets.
if ( keys[0] === '' ) {
keys.shift();
} // Ensure no opening bracket ("[foo][inn]" should be same as "foo[inn]")
return keys;
},
// Set a value in an object or array, using multiple keys to set in a nested object or array.
// This is the main function of the script, that allows serializeJSON to use nested keys.
// Examples:
//
// deepSet(obj, ["foo"], v) // obj["foo"] = v
// deepSet(obj, ["foo", "inn"], v) // obj["foo"]["inn"] = v // Create the inner obj["foo"] object, if needed
// deepSet(obj, ["foo", "inn", "123"], v) // obj["foo"]["arr"]["123"] = v //
//
// deepSet(obj, ["0"], v) // obj["0"] = v
// deepSet(arr, ["0"], v, {useIntKeysAsArrayIndex: true}) // arr[0] = v
// deepSet(arr, [""], v) // arr.push(v)
// deepSet(obj, ["arr", ""], v) // obj["arr"].push(v)
//
// arr = [];
// deepSet(arr, ["", v] // arr => [v]
// deepSet(arr, ["", "foo"], v) // arr => [v, {foo: v}]
// deepSet(arr, ["", "bar"], v) // arr => [v, {foo: v, bar: v}]
// deepSet(arr, ["", "bar"], v) // arr => [v, {foo: v, bar: v}, {bar: v}].
deepSet: function( o, keys, value, opts ) {
if ( null == opts ) {
opts = {};
}
var f = $.serializeJSON;
if ( isUndefined( o ) ) {
throw new Error( 'ArgumentError: param \'o\' expected to be an object or array, found undefined' );
}
if ( ! keys || 0 === keys.length ) {
throw new Error( 'ArgumentError: param \'keys\' expected to be an array with least one element' );
}
var key = keys[0];
// Only one key, then it's not a deepSet, just assign the value in the object or add it to the array.
if ( 1 === keys.length ) {
if ( key === '' ) { // Push values into an array (o must be an array).
o.push( value );
} else {
o[key] = value; // Keys can be object keys (strings) or array indexes (numbers).
}
return;
}
var nextKey = keys[1]; // Nested key.
var tailKeys = keys.slice( 1 ); // List of all other nested keys (nextKey is first).
if ( key === '' ) { // Push nested objects into an array (o must be an array).
var lastIdx = o.length - 1;
var lastVal = o[lastIdx];
// if the last value is an object or array, and the new key is not set yet.
if ( isObject( lastVal ) && isUndefined( f.deepGet( lastVal, tailKeys ) ) ) {
key = lastIdx; // then set the new value as a new attribute of the same object.
} else {
key = lastIdx + 1; // otherwise, add a new element in the array.
}
}
if ( nextKey === '' ) { // "" is used to push values into the nested array "array[]".
if ( isUndefined( o[key] ) || ! isArray( o[key] ) ) {
o[key] = []; // define (or override) as array to push values.
}
} else {
if ( opts.useIntKeysAsArrayIndex && isValidArrayIndex( nextKey ) ) { // if 1, 2, 3 ... then use an array, where nextKey is the index.
if ( isUndefined( o[key] ) || ! isArray( o[key] ) ) {
o[key] = []; // Define (or override) as array, to insert values using int keys as array indexes.
}
} else { // nextKey is going to be the nested object's attribute.
if ( isUndefined( o[key] ) || ! isObject( o[key] ) ) {
o[key] = {}; // Define (or override) as object, to set nested properties.
}
}
}
// Recursively set the inner object.
f.deepSet( o[key], tailKeys, value, opts );
},
deepGet: function( o, keys ) {
var f = $.serializeJSON;
var tailKeys;
if ( isUndefined( o ) || isUndefined( keys ) || keys.length === 0 || ( ! isObject( o ) && ! isArray( o ) ) ) {
return o;
}
var key = keys[0];
if ( '' === key ) { // "" means next array index (used by deepSet)
return undefined;
}
if ( 1 === keys.length ) {
return o[key];
}
tailKeys = keys.slice( 1 );
return f.deepGet( o[key], tailKeys );
}
};
// Polyfill Object.keys to get option keys in IE<9.
var objectKeys = function( obj ) {
if ( Object.keys ) {
return Object.keys( obj );
} else {
var key, keys = [];
for ( key in obj ) {
keys.push( key );
}
return keys;
}
};
var isObject = function( obj ) {
return obj === Object( obj );
}; // true for Objects and Arrays.
var isUndefined = function( obj ) {
return obj === void 0;
}; // safe check for undefined values.
var isValidArrayIndex = function( val ) {
return /^[0-9]+$/.test( String( val ) );
}; // 1,2,3,4 ... are valid array indexes.
var isArray = Array.isArray || function( obj ) {
return '[object Array]' === Object.prototype.toString.call( obj );
};
} ) );