PluginProbe
UpdraftCentral Dashboard / trunk
UpdraftCentral Dashboard vtrunk
0.8.33 0.7.2 0.7.3 0.7.4 0.8.0 0.8.1 0.8.10 0.8.11 0.8.12 0.8.13 0.8.14 0.8.15 0.8.16 0.8.17 0.8.18 0.8.19 0.8.2 0.8.20 0.8.21 0.8.22 0.8.23 0.8.24 0.8.25 0.8.26 0.8.27 All 51 releases
updraftcentral / js / serialize-json / jquery.serializejson.js

jquery.serializejson.js in UpdraftCentral Dashboard trunk, at js/serialize-json/jquery.serializejson.js

345 lines 16.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /*!
2 SerializeJSON jQuery plugin.
3 https://github.com/marioizquierdo/jquery.serializeJSON
4 version 2.9.0 (Jan, 2018)
5
6 Copyright (c) 2012-2018 Mario Izquierdo
7 Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
8 and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
9 */
10 (function (factory) {
11 if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module.
12 define(['jquery'], factory);
13 } else if (typeof exports === 'object') { // Node/CommonJS
14 var jQuery = require('jquery');
15 module.exports = factory(jQuery);
16 } else { // Browser globals (zepto supported)
17 factory(window.jQuery || window.Zepto || window.$); // Zepto supported on browsers as well
18 }
19
20 }(function ($) {
21 "use strict";
22
23 // jQuery('form').serializeJSON()
24 $.fn.serializeJSON = function (options) {
25 var f, $form, opts, formAsArray, serializedObject, name, value, parsedValue, _obj, nameWithNoType, type, keys, skipFalsy;
26 f = $.serializeJSON;
27 $form = this; // NOTE: the set of matched elements is most likely a form, but it could also be a group of inputs
28 opts = f.setupOpts(options); // calculate values for options {parseNumbers, parseBoolens, parseNulls, ...} with defaults
29
30 // Use native `serializeArray` function to get an array of {name, value} objects.
31 formAsArray = $form.serializeArray();
32 f.readCheckboxUncheckedValues(formAsArray, opts, $form); // add objects to the array from unchecked checkboxes if needed
33
34 // Convert the formAsArray into a serializedObject with nested keys
35 serializedObject = {};
36 $.each(formAsArray, function (i, obj) {
37 name = obj.name; // original input name
38 value = obj.value; // input value
39 _obj = f.extractTypeAndNameWithNoType(name);
40 nameWithNoType = _obj.nameWithNoType; // input name with no type (i.e. "foo:string" => "foo")
41 type = _obj.type; // type defined from the input name in :type colon notation
42 if (!type) type = f.attrFromInputWithName($form, name, 'data-value-type');
43 f.validateType(name, type, opts); // make sure that the type is one of the valid types if defined
44
45 if (type !== 'skip') { // ignore inputs with type 'skip'
46 keys = f.splitInputNameIntoKeysArray(nameWithNoType);
47 parsedValue = f.parseValue(value, name, type, opts); // convert to string, number, boolean, null or customType
48
49 skipFalsy = !parsedValue && f.shouldSkipFalsy($form, name, nameWithNoType, type, opts); // ignore falsy inputs if specified
50 if (!skipFalsy) {
51 f.deepSet(serializedObject, keys, parsedValue, opts);
52 }
53 }
54 });
55 return serializedObject;
56 };
57
58 // Use $.serializeJSON as namespace for the auxiliar functions
59 // and to define defaults
60 $.serializeJSON = {
61
62 defaultOptions: {
63 checkboxUncheckedValue: undefined, // to include that value for unchecked checkboxes (instead of ignoring them)
64
65 parseNumbers: false, // convert values like "1", "-2.33" to 1, -2.33
66 parseBooleans: false, // convert "true", "false" to true, false
67 parseNulls: false, // convert "null" to null
68 parseAll: false, // all of the above
69 parseWithFunction: null, // to use custom parser, a function like: function(val){ return parsed_val; }
70
71 skipFalsyValuesForTypes: [], // skip serialization of falsy values for listed value types
72 skipFalsyValuesForFields: [], // skip serialization of falsy values for listed field names
73
74 customTypes: {}, // override defaultTypes
75 defaultTypes: {
76 "string": function(str) { return String(str); },
77 "number": function(str) { return Number(str); },
78 "boolean": function(str) { var falses = ["false", "null", "undefined", "", "0"]; return falses.indexOf(str) === -1; },
79 "null": function(str) { var falses = ["false", "null", "undefined", "", "0"]; return falses.indexOf(str) === -1 ? str : null; },
80 "array": function(str) { return JSON.parse(str); },
81 "object": function(str) { return JSON.parse(str); },
82 "auto": function(str) { return $.serializeJSON.parseValue(str, null, null, {parseNumbers: true, parseBooleans: true, parseNulls: true}); }, // try again with something like "parseAll"
83 "skip": null // skip is a special type that makes it easy to ignore elements
84 },
85
86 useIntKeysAsArrayIndex: false // name="foo[2]" value="v" => {foo: [null, null, "v"]}, instead of {foo: ["2": "v"]}
87 },
88
89 // Merge option defaults into the options
90 setupOpts: function(options) {
91 var opt, validOpts, defaultOptions, optWithDefault, parseAll, f;
92 f = $.serializeJSON;
93
94 if (options == null) { options = {}; } // options ||= {}
95 defaultOptions = f.defaultOptions || {}; // defaultOptions
96
97 // Make sure that the user didn't misspell an option
98 validOpts = ['checkboxUncheckedValue', 'parseNumbers', 'parseBooleans', 'parseNulls', 'parseAll', 'parseWithFunction', 'skipFalsyValuesForTypes', 'skipFalsyValuesForFields', 'customTypes', 'defaultTypes', 'useIntKeysAsArrayIndex']; // re-define because the user may override the defaultOptions
99 for (opt in options) {
100 if (validOpts.indexOf(opt) === -1) {
101 throw new Error("serializeJSON ERROR: invalid option '" + opt + "'. Please use one of " + validOpts.join(', '));
102 }
103 }
104
105 // Helper to get the default value for this option if none is specified by the user
106 optWithDefault = function(key) { return (options[key] !== false) && (options[key] !== '') && (options[key] || defaultOptions[key]); };
107
108 // Return computed options (opts to be used in the rest of the script)
109 parseAll = optWithDefault('parseAll');
110 return {
111 checkboxUncheckedValue: optWithDefault('checkboxUncheckedValue'),
112
113 parseNumbers: parseAll || optWithDefault('parseNumbers'),
114 parseBooleans: parseAll || optWithDefault('parseBooleans'),
115 parseNulls: parseAll || optWithDefault('parseNulls'),
116 parseWithFunction: optWithDefault('parseWithFunction'),
117
118 skipFalsyValuesForTypes: optWithDefault('skipFalsyValuesForTypes'),
119 skipFalsyValuesForFields: optWithDefault('skipFalsyValuesForFields'),
120 typeFunctions: $.extend({}, optWithDefault('defaultTypes'), optWithDefault('customTypes')),
121
122 useIntKeysAsArrayIndex: optWithDefault('useIntKeysAsArrayIndex')
123 };
124 },
125
126 // Given a string, apply the type or the relevant "parse" options, to return the parsed value
127 parseValue: function(valStr, inputName, type, opts) {
128 var f, parsedVal;
129 f = $.serializeJSON;
130 parsedVal = valStr; // if no parsing is needed, the returned value will be the same
131
132 if (opts.typeFunctions && type && opts.typeFunctions[type]) { // use a type if available
133 parsedVal = opts.typeFunctions[type](valStr);
134 } else if (opts.parseNumbers && f.isNumeric(valStr)) { // auto: number
135 parsedVal = Number(valStr);
136 } else if (opts.parseBooleans && (valStr === "true" || valStr === "false")) { // auto: boolean
137 parsedVal = (valStr === "true");
138 } else if (opts.parseNulls && valStr == "null") { // auto: null
139 parsedVal = null;
140 } else if (opts.typeFunctions && opts.typeFunctions["string"]) { // make sure to apply :string type if it was re-defined
141 parsedVal = opts.typeFunctions["string"](valStr);
142 }
143
144 // Custom parse function: apply after parsing options, unless there's an explicit type.
145 if (opts.parseWithFunction && !type) {
146 parsedVal = opts.parseWithFunction(parsedVal, inputName);
147 }
148
149 return parsedVal;
150 },
151
152 isObject: function(obj) { return obj === Object(obj); }, // is it an Object?
153 isUndefined: function(obj) { return obj === void 0; }, // safe check for undefined values
154 isValidArrayIndex: function(val) { return /^[0-9]+$/.test(String(val)); }, // 1,2,3,4 ... are valid array indexes
155 isNumeric: function(obj) { return obj - parseFloat(obj) >= 0; }, // taken from jQuery.isNumeric implementation. Not using jQuery.isNumeric to support old jQuery and Zepto versions
156
157 optionKeys: function(obj) { if (Object.keys) { return Object.keys(obj); } else { var key, keys = []; for(key in obj){ keys.push(key); } return keys;} }, // polyfill Object.keys to get option keys in IE<9
158
159
160 // Fill the formAsArray object with values for the unchecked checkbox inputs,
161 // using the same format as the jquery.serializeArray function.
162 // The value of the unchecked values is determined from the opts.checkboxUncheckedValue
163 // and/or the data-unchecked-value attribute of the inputs.
164 readCheckboxUncheckedValues: function (formAsArray, opts, $form) {
165 var selector, $uncheckedCheckboxes, $el, uncheckedValue, f, name;
166 if (opts == null) { opts = {}; }
167 f = $.serializeJSON;
168
169 selector = 'input[type=checkbox][name]:not(:checked):not([disabled])';
170 $uncheckedCheckboxes = $form.find(selector).add($form.filter(selector));
171 $uncheckedCheckboxes.each(function (i, el) {
172 // Check data attr first, then the option
173 $el = $(el);
174 uncheckedValue = $el.attr('data-unchecked-value');
175 if (uncheckedValue == null) {
176 uncheckedValue = opts.checkboxUncheckedValue;
177 }
178
179 // If there's an uncheckedValue, push it into the serialized formAsArray
180 if (uncheckedValue != null) {
181 if (el.name && el.name.indexOf("[][") !== -1) { // identify a non-supported
182 throw new Error("serializeJSON ERROR: checkbox unchecked values are not supported on nested arrays of objects like '"+el.name+"'. See https://github.com/marioizquierdo/jquery.serializeJSON/issues/67");
183 }
184 formAsArray.push({name: el.name, value: uncheckedValue});
185 }
186 });
187 },
188
189 // Returns and object with properties {name_without_type, type} from a given name.
190 // The type is null if none specified. Example:
191 // "foo" => {nameWithNoType: "foo", type: null}
192 // "foo:boolean" => {nameWithNoType: "foo", type: "boolean"}
193 // "foo[bar]:null" => {nameWithNoType: "foo[bar]", type: "null"}
194 extractTypeAndNameWithNoType: function(name) {
195 var match;
196 if (match = name.match(/(.*):([^:]+)$/)) {
197 return {nameWithNoType: match[1], type: match[2]};
198 } else {
199 return {nameWithNoType: name, type: null};
200 }
201 },
202
203
204 // Check if this input should be skipped when it has a falsy value,
205 // depending on the options to skip values by name or type, and the data-skip-falsy attribute.
206 shouldSkipFalsy: function($form, name, nameWithNoType, type, opts) {
207 var f = $.serializeJSON;
208
209 var skipFromDataAttr = f.attrFromInputWithName($form, name, 'data-skip-falsy');
210 if (skipFromDataAttr != null) {
211 return skipFromDataAttr !== 'false'; // any value is true, except if explicitly using 'false'
212 }
213
214 var optForFields = opts.skipFalsyValuesForFields;
215 if (optForFields && (optForFields.indexOf(nameWithNoType) !== -1 || optForFields.indexOf(name) !== -1)) {
216 return true;
217 }
218
219 var optForTypes = opts.skipFalsyValuesForTypes;
220 if (type == null) type = 'string'; // assume fields with no type are targeted as string
221 if (optForTypes && optForTypes.indexOf(type) !== -1) {
222 return true
223 }
224
225 return false;
226 },
227
228 // Finds the first input in $form with this name, and get the given attr from it.
229 // Returns undefined if no input or no attribute was found.
230 attrFromInputWithName: function($form, name, attrName) {
231 var escapedName, selector, $input, attrValue;
232 escapedName = name.replace(/(:|\.|\[|\]|\s)/g,'\\$1'); // every non-standard character need to be escaped by \\
233 selector = '[name="' + escapedName + '"]';
234 $input = $form.find(selector).add($form.filter(selector)); // NOTE: this returns only the first $input element if multiple are matched with the same name (i.e. an "array[]"). So, arrays with different element types specified through the data-value-type attr is not supported.
235 return $input.attr(attrName);
236 },
237
238 // Raise an error if the type is not recognized.
239 validateType: function(name, type, opts) {
240 var validTypes, f;
241 f = $.serializeJSON;
242 validTypes = f.optionKeys(opts ? opts.typeFunctions : f.defaultOptions.defaultTypes);
243 if (!type || validTypes.indexOf(type) !== -1) {
244 return true;
245 } else {
246 throw new Error("serializeJSON ERROR: Invalid type " + type + " found in input name '" + name + "', please use one of " + validTypes.join(', '));
247 }
248 },
249
250
251 // Split the input name in programatically readable keys.
252 // Examples:
253 // "foo" => ['foo']
254 // "[foo]" => ['foo']
255 // "foo[inn][bar]" => ['foo', 'inn', 'bar']
256 // "foo[inn[bar]]" => ['foo', 'inn', 'bar']
257 // "foo[inn][arr][0]" => ['foo', 'inn', 'arr', '0']
258 // "arr[][val]" => ['arr', '', 'val']
259 splitInputNameIntoKeysArray: function(nameWithNoType) {
260 var keys, f;
261 f = $.serializeJSON;
262 keys = nameWithNoType.split('['); // split string into array
263 keys = $.map(keys, function (key) { return key.replace(/\]/g, ''); }); // remove closing brackets
264 if (keys[0] === '') { keys.shift(); } // ensure no opening bracket ("[foo][inn]" should be same as "foo[inn]")
265 return keys;
266 },
267
268 // Set a value in an object or array, using multiple keys to set in a nested object or array:
269 //
270 // deepSet(obj, ['foo'], v) // obj['foo'] = v
271 // deepSet(obj, ['foo', 'inn'], v) // obj['foo']['inn'] = v // Create the inner obj['foo'] object, if needed
272 // deepSet(obj, ['foo', 'inn', '123'], v) // obj['foo']['arr']['123'] = v //
273 //
274 // deepSet(obj, ['0'], v) // obj['0'] = v
275 // deepSet(arr, ['0'], v, {useIntKeysAsArrayIndex: true}) // arr[0] = v
276 // deepSet(arr, [''], v) // arr.push(v)
277 // deepSet(obj, ['arr', ''], v) // obj['arr'].push(v)
278 //
279 // arr = [];
280 // deepSet(arr, ['', v] // arr => [v]
281 // deepSet(arr, ['', 'foo'], v) // arr => [v, {foo: v}]
282 // deepSet(arr, ['', 'bar'], v) // arr => [v, {foo: v, bar: v}]
283 // deepSet(arr, ['', 'bar'], v) // arr => [v, {foo: v, bar: v}, {bar: v}]
284 //
285 deepSet: function (o, keys, value, opts) {
286 var key, nextKey, tail, lastIdx, lastVal, f;
287 if (opts == null) { opts = {}; }
288 f = $.serializeJSON;
289 if (f.isUndefined(o)) { throw new Error("ArgumentError: param 'o' expected to be an object or array, found undefined"); }
290 if (!keys || keys.length === 0) { throw new Error("ArgumentError: param 'keys' expected to be an array with least one element"); }
291
292 key = keys[0];
293
294 // Only one key, then it's not a deepSet, just assign the value.
295 if (keys.length === 1) {
296 if (key === '') {
297 o.push(value); // '' is used to push values into the array (assume o is an array)
298 } else {
299 o[key] = value; // other keys can be used as object keys or array indexes
300 }
301
302 // With more keys is a deepSet. Apply recursively.
303 } else {
304 nextKey = keys[1];
305
306 // '' is used to push values into the array,
307 // with nextKey, set the value into the same object, in object[nextKey].
308 // Covers the case of ['', 'foo'] and ['', 'var'] to push the object {foo, var}, and the case of nested arrays.
309 if (key === '') {
310 lastIdx = o.length - 1; // asume o is array
311 lastVal = o[lastIdx];
312 if (f.isObject(lastVal) && (f.isUndefined(lastVal[nextKey]) || keys.length > 2)) { // if nextKey is not present in the last object element, or there are more keys to deep set
313 key = lastIdx; // then set the new value in the same object element
314 } else {
315 key = lastIdx + 1; // otherwise, point to set the next index in the array
316 }
317 }
318
319 // '' is used to push values into the array "array[]"
320 if (nextKey === '') {
321 if (f.isUndefined(o[key]) || !$.isArray(o[key])) {
322 o[key] = []; // define (or override) as array to push values
323 }
324 } else {
325 if (opts.useIntKeysAsArrayIndex && f.isValidArrayIndex(nextKey)) { // if 1, 2, 3 ... then use an array, where nextKey is the index
326 if (f.isUndefined(o[key]) || !$.isArray(o[key])) {
327 o[key] = []; // define (or override) as array, to insert values using int keys as array indexes
328 }
329 } else { // for anything else, use an object, where nextKey is going to be the attribute name
330 if (f.isUndefined(o[key]) || !f.isObject(o[key])) {
331 o[key] = {}; // define (or override) as object, to set nested properties
332 }
333 }
334 }
335
336 // Recursively set the inner object
337 tail = keys.slice(1);
338 f.deepSet(o[key], tail, value, opts);
339 }
340 }
341
342 };
343
344 }));
345