From b2703886b6432e36fb0c98281e2f9adfae3f46ae Mon Sep 17 00:00:00 2001 From: brainfoolong Date: Fri, 2 Dec 2022 13:08:35 +0100 Subject: [PATCH] fixed bug with option `skipEmpty` in select fields (https://github.com/brainfoolong/form-data-json/issues/31) --- CHANGELOG.md | 4 + README.md | 2 +- build/dist.js | 2 +- dist/form-data-json.js | 240 ++++++++--------------------- dist/form-data-json.min.js | 4 +- docs/scripts/form-data-json.js | 240 ++++++++--------------------- docs/scripts/form-data-json.min.js | 4 +- docs/tests/test.html | 18 ++- package.json | 2 +- src/form-data-json.js | 5 +- 10 files changed, 150 insertions(+), 371 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0271a94..505dccc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +### 2.1.4 - 2022-12-02 + +* fixed bug with option `skipEmpty` in select fields (https://github.com/brainfoolong/form-data-json/issues/31) + ### 2.1.2 and 2.1.3 - 2022-02-09 * fixed issue with skipEmpty and nested input value #25 diff --git a/README.md b/README.md index 9c31de0..0025552 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ You can edit this defaults globally by modifying `FormDataJson.defaultOptionsToJ * If true, then this does skip empty fields from the output * Empty is considered to be: * An empty array (for multiple selects/checkboxes) - * An empty input field (length = 0) + * An empty input field (length = 0 or null) * This does recursively remove keys * Example: {"agb":"1", "user" : [{"name" : ""},{"name" : ""}]} will be {"agb":"1"} * @type {boolean} diff --git a/build/dist.js b/build/dist.js index 2e91f3f..4a56b27 100644 --- a/build/dist.js +++ b/build/dist.js @@ -73,7 +73,7 @@ async function compile () { for (let file in babelConfigs) { let contents = '' contents += babel.transformSync(srcFileData, babelConfigs[file]).code - contents = contents.replace(/^\'use strict\';/mg, '\'use strict\';\n// ' + packageJson.name + ' | version: ' + packageJson.version + ' | url: ' + packageJson.homepage + '\n') + contents = contents.replace(/^["']use strict["'];/mg, '\'use strict\';\n// ' + packageJson.name + ' | version: ' + packageJson.version + ' | url: ' + packageJson.homepage + '\n') fs.writeFileSync(copyToFolder + '/' + file, contents) } } diff --git a/dist/form-data-json.js b/dist/form-data-json.js index f9e26e4..a9ab2b9 100644 --- a/dist/form-data-json.js +++ b/dist/form-data-json.js @@ -1,35 +1,30 @@ 'use strict'; -// form-data-json-convert | version: 2.1.3 | url: https://github.com/brainfoolong/form-data-json +// form-data-json-convert | version: 2.1.4 | url: https://github.com/brainfoolong/form-data-json + /** * Form Data Json Converter * @link https://github.com/brainfoolong/form-data-json * @licence MIT */ - var FormDataJson = /*#__PURE__*/function () { function FormDataJson() {} - /** * Default options for toJson() * @type {{}} */ - /** * Default options for fromJson() * @type {{}} */ - /** * All input types that are buttons * @type {string[]} */ - /** * All input types that have a checked status * @type {string[]} */ - /** * Get values from all form elements inside the given element * @param {*} el @@ -38,51 +33,49 @@ var FormDataJson = /*#__PURE__*/function () { */ FormDataJson.toJson = function toJson(el, options) { options = FormDataJson.merge(FormDataJson.defaultOptionsToJson, options); + /** * Check if given input is valid for given filters based on given options * @param {HTMLElement} input * @return {boolean} */ - function isValidInput(input) { // filter elements by input filter if (typeof options.inputFilter === 'function' && options.inputFilter(input) !== true) { return false; - } // ignore disabled fields - + } + // ignore disabled fields if (!options.includeDisabled && input.disabled) { return false; } + var inputType = (input.type || 'text').toLowerCase(); - var inputType = (input.type || 'text').toLowerCase(); // ignore button values - + // ignore button values if (!options.includeButtonValues && (input instanceof HTMLButtonElement || FormDataJson.buttonInputTypes.indexOf(inputType) > -1)) { return false; - } // ignore unchecked fields when no value is given - + } + // ignore unchecked fields when no value is given if (typeof options.uncheckedValue === 'undefined' && FormDataJson.checkedInputTypes.indexOf(inputType) > -1 && !input.checked) { return false; } - return true; } - var tree = FormDataJson.getFieldTree(el, isValidInput); var returnObject = options.flatList ? [] : {}; var files = []; + /** * Recursive get values * @param {Object} inputs * @param {Object} values */ - function recursion(inputs, values) { for (var key in inputs) { var row = inputs[key]; - var objectKey = options.flatList ? row.name : key; // next level - + var objectKey = options.flatList ? row.name : key; + // next level if (row.type === 'nested') { if (options.flatList) { recursion(row.tree, values); @@ -90,13 +83,10 @@ var FormDataJson = /*#__PURE__*/function () { values[key] = {}; recursion(row.tree, values[key]); } - continue; } - var input = row.input; var inputType = row.inputType; - if (inputType === 'file') { if (options.filesCallback) { files.push({ @@ -105,22 +95,17 @@ var FormDataJson = /*#__PURE__*/function () { 'input': input }); } - continue; } - var value = null; - if (row.type === 'radio') { for (var i = 0; i < row.inputs.length; i++) { var radioInput = row.inputs[i]; - if (radioInput.checked) { value = radioInput.value; break; } } - if (value === null) { value = options.uncheckedValue; } @@ -128,15 +113,12 @@ var FormDataJson = /*#__PURE__*/function () { value = input.checked ? input.value : options.uncheckedValue; } else if (input instanceof HTMLSelectElement) { var arr = []; - for (var _i = 0; _i < input.options.length; _i++) { var option = input.options[_i]; - if (option.selected) { arr.push((typeof option.value !== 'undefined' ? option.value : option.text).toString()); } } - if (input.multiple) { value = arr; } else { @@ -145,7 +127,6 @@ var FormDataJson = /*#__PURE__*/function () { } else { value = input.value; } - if (options.flatList) { values.push([row.name, value]); } else { @@ -153,208 +134,171 @@ var FormDataJson = /*#__PURE__*/function () { } } } + /** * Make an object to array if possible * @param {Object} object * @return {*} * @private */ - - function arrayify(object) { if (FormDataJson.isObject(object)) { var count = 0; var valid = true; - for (var key in object) { if (FormDataJson.isObject(object[key]) && !(object[key] instanceof Element)) { object[key] = arrayify(object[key]); } - if (parseInt(key) !== count) { valid = false; } - count++; } - if (valid) { var arr = []; - for (var i in object) { arr.push(object[i]); } - return arr; } } - return object; } + /** * Does some final cleanup before output data * @return {*} */ - - function output() { if (options.arrayify) { returnObject = arrayify(returnObject); } - if (options.skipEmpty) returnObject = removeEmpty(returnObject) || (options.flatList ? [] : {}); return returnObject; } + /** * Recursively remove empty keys * @param {Object} object * @param {number} depth * @return {Object|Array} */ - - function removeEmpty(object, depth) { var isArray = FormDataJson.isArray(object); var newObject = isArray ? [] : {}; var count = 0; - for (var key in object) { var value = object[key]; - if (options.flatList && !depth) { value = value[1]; } - + if (value === null || value === undefined || value === '') { + continue; + } if (FormDataJson.isObject(value) || FormDataJson.isArray(value)) { value = removeEmpty(value, (depth || 0) + 1) || ''; } - if (typeof value !== 'object' && FormDataJson.stringify(value) === '') { continue; } - if (isArray) { newObject.push(options.flatList && !depth ? [object[key][0], value] : value); } else { newObject[key] = value; } - count++; } - return count ? newObject : null; } - recursion(tree, returnObject); - if (files.length) { var _ret = function () { var filesDone = 0; var filesRequired = 0; - var _loop = function _loop(i) { var row = files[i]; var useObject = row.object; filesRequired += row.input.files.length; - var _loop2 = function _loop2(j) { var file = row.input.files[j]; var reader = new FileReader(); - reader.onload = function () { if (row.input.multiple) { if (!FormDataJson.isArray(useObject[row.key])) { useObject[row.key] = []; } - useObject[row.key].push(reader.result); } else { useObject[row.key] = reader.result; } - filesDone++; - if (filesDone === filesRequired) { options.filesCallback(output()); } }; - reader[options.fileReaderFunction](file); }; - for (var j = 0; j < row.input.files.length; j++) { _loop2(j); } }; - for (var i = 0; i < files.length; i++) { _loop(i); } - return { v: null }; }(); - if (typeof _ret === "object") return _ret.v; } else if (options.filesCallback) { options.filesCallback(output()); return null; } - return output(); } + /** * Fill given form values into all form elements inside given element * @param {*} el * @param {Object|Array} values * @param {Object=} options * @param {string=} keyPrefix Internal only - */ - ; - + */; FormDataJson.fromJson = function fromJson(el, values, options, keyPrefix) { if (!FormDataJson.isObject(values) && !FormDataJson.isArray(values)) return; options = FormDataJson.merge(FormDataJson.defaultOptionsFromJson, options); var tree = FormDataJson.getFieldTree(el); var lastUsedFlatListIndex = {}; - if (options.clearOthers) { FormDataJson.clear(el); } - if (options.resetOthers) { FormDataJson.reset(el); } + /** * Recursive set values * @param {*} inputs * @param {*} newValues */ - - function recursion(inputs, newValues) { if (!FormDataJson.isObject(newValues) && !FormDataJson.isArray(newValues)) return; - for (var key in inputs) { var row = inputs[key]; - var objectKey = options.flatList ? row.name : key; // next level - + var objectKey = options.flatList ? row.name : key; + // next level if (row.type === 'nested') { if (options.flatList) { recursion(row.tree, newValues); } else if (typeof newValues[objectKey] === 'object') { recursion(row.tree, newValues[objectKey]); } - continue; - } // flat list must search correct entry for given input name - - + } + // flat list must search correct entry for given input name if (options.flatList) { for (var i in newValues) { var value = newValues[i]; - if (value && value[0] === row.name) { if (lastUsedFlatListIndex[row.name] !== i) { lastUsedFlatListIndex[row.name] = i; @@ -363,64 +307,54 @@ var FormDataJson = /*#__PURE__*/function () { } } } - continue; - } // skip fields that are not presented in the value list - - + } + // skip fields that are not presented in the value list if (typeof newValues[objectKey] === 'undefined') { continue; } - FormDataJson.setInputValue(row, newValues[objectKey] || null, options.triggerChangeEvent); } } - recursion(tree, values); } + /** * Reset all input fields in the given element to their default values * @param {*} el - */ - ; - + */; FormDataJson.reset = function reset(el) { var tree = FormDataJson.getFieldTree(el); + /** * Recursive reset * @param {*} inputs */ - function recursion(inputs) { for (var key in inputs) { - var row = inputs[key]; // next level - + var row = inputs[key]; + // next level if (row.type === 'nested') { recursion(row.tree); continue; } - if (row.type === 'radio') { for (var i = 0; i < row.inputs.length; i++) { var radioInput = row.inputs[i]; radioInput.checked = radioInput.defaultChecked; } - continue; - } // ignore button elements, as reset would reset button labels, which is mostly not that what anybody want - + } + // ignore button elements, as reset would reset button labels, which is mostly not that what anybody want if (row.inputType && FormDataJson.buttonInputTypes.indexOf(row.inputType) > -1) { continue; } - var input = row.input; - if (FormDataJson.checkedInputTypes.indexOf(row.inputType) > -1) { input.checked = input.defaultChecked; } else if (input instanceof HTMLSelectElement) { var options = input.querySelectorAll('option'); - for (var _i2 = 0; _i2 < options.length; _i2++) { var option = options[_i2]; option.selected = option.defaultSelected; @@ -432,57 +366,50 @@ var FormDataJson = /*#__PURE__*/function () { } } } - recursion(tree); } + /** * Clear all input fields (to empty, unchecked, unselected) in the given element * @param {*} el - */ - ; - + */; FormDataJson.clear = function clear(el) { var tree = FormDataJson.getFieldTree(el); + /** * Recursive clear * @param {*} inputs */ - function recursion(inputs) { for (var key in inputs) { - var row = inputs[key]; // next level - + var row = inputs[key]; + // next level if (row.type === 'nested') { recursion(row.tree); continue; } - if (row.input) { // ignore button elements, as clear would unset button labels, which is mostly not that what anybody want if (FormDataJson.buttonInputTypes.indexOf(row.inputType) > -1) { continue; } } - FormDataJson.setInputValue(row, null); } } - recursion(tree); } + /** * Set input value * @param {*} row * @param {*|null} newValue Null will unset the value * @param {boolean=} triggerChangeEvent * @private - */ - ; - + */; FormDataJson.setInputValue = function setInputValue(row, newValue, triggerChangeEvent) { var triggerChange = triggerChangeEvent ? function (el) { var ev = null; - if (typeof Event === 'function') { ev = new Event('change', { 'bubbles': true @@ -491,50 +418,41 @@ var FormDataJson = /*#__PURE__*/function () { ev = document.createEvent('Event'); ev.initEvent('change', true, true); } - el.dispatchEvent(ev); } : null; - if (row.type === 'radio') { var _changed = []; - for (var i = 0; i < row.inputs.length; i++) { var radioInput = row.inputs[i]; if (radioInput.checked) _changed.push(radioInput); radioInput.checked = false; - if (newValue !== null && FormDataJson.stringify(radioInput.value) === FormDataJson.stringify(newValue)) { if (!radioInput.checked) _changed.push(radioInput); radioInput.checked = true; break; } } - if (triggerChange) { for (var _i3 in _changed) { triggerChange(_changed[_i3]); } } - return; } - var input = row.input; - var inputType = row.inputType; // ignore file input, they cannot be set + var inputType = row.inputType; + // ignore file input, they cannot be set if (inputType === 'file') { return; } - var changed = false; - if (inputType === 'checkbox') { newValue = newValue === true || newValue !== null && FormDataJson.stringify(input.value) === FormDataJson.stringify(newValue); if (newValue !== input.checked) changed = true; input.checked = newValue; } else if (input instanceof HTMLSelectElement) { var newValueArr = newValue; - if (newValueArr === null || newValueArr === undefined) { newValueArr = []; } else if (FormDataJson.isObject(newValueArr)) { @@ -542,13 +460,11 @@ var FormDataJson = /*#__PURE__*/function () { } else if (!FormDataJson.isArray(newValueArr)) { newValueArr = [newValueArr]; } - for (var _i4 = 0; _i4 < input.options.length; _i4++) { var option = input.options[_i4]; var optionValue = (typeof option.value !== 'undefined' ? option.value : option.text).toString(); if (option.selected !== false) changed = true; option.selected = false; - for (var j = 0; j < newValueArr.length; j++) { if (optionValue === FormDataJson.stringify(newValueArr[j])) { if (option.selected !== true) changed = true; @@ -561,46 +477,40 @@ var FormDataJson = /*#__PURE__*/function () { if (input.value !== newValue) changed = true; input.value = newValue; } - if (changed && triggerChange) { triggerChange(input); } } + /** * Convert any value to a string * A object/array/undefined will be an ampty string * Boolean will be 1 or 0 * @param {*} value * @private - */ - ; - + */; FormDataJson.stringify = function stringify(value) { if (value === undefined) return ''; if (typeof value === 'object') return ''; if (typeof value === 'boolean') return value ? '1' : '0'; return value + ''; } + /** * Get all input fields as a multidimensional tree, as it would later appear in json data, but with input elements as values * @param {*} el * @param {function=} isValidInput A function to check for valid input * @return {Object} * @private - */ - ; - + */; FormDataJson.getFieldTree = function getFieldTree(el, isValidInput) { el = FormDataJson.getElement(el); - if (!el) { return []; } - var inputs = el.querySelectorAll('select, textarea, input, button'); var inputTree = {}; var autoIncrementCounts = {}; - for (var i = 0; i < inputs.length; i++) { var input = inputs[i]; if (!input.name || input.name.length === 0) continue; @@ -608,7 +518,6 @@ var FormDataJson = /*#__PURE__*/function () { var inputType = (input.type || 'text').toLowerCase(); var name = input.name; var keyParts = input.name.replace(/\]/g, '').split('['); - if (name.substr(-2) === '[]') { if (input instanceof HTMLSelectElement && input.multiple) { // special for multiple selects, we skip the last empty part to fix double nested arrays @@ -619,26 +528,22 @@ var FormDataJson = /*#__PURE__*/function () { keyParts.pop(); } } - var keyPartsLength = keyParts.length; var useObject = inputTree; var currentName = ''; - for (var j = 0; j < keyPartsLength; j++) { var keyPart = keyParts[j]; - var newCurrentName = currentName ? currentName + '[' + keyPart + ']' : keyPart; // auto increment key part - + var newCurrentName = currentName ? currentName + '[' + keyPart + ']' : keyPart; + // auto increment key part if (keyPart === '') { if (typeof autoIncrementCounts[newCurrentName] === 'undefined') { autoIncrementCounts[newCurrentName] = 0; } - keyPart = autoIncrementCounts[newCurrentName].toString(); autoIncrementCounts[newCurrentName]++; } - - currentName = currentName ? currentName + '[' + keyPart + ']' : keyPart; // last level - + currentName = currentName ? currentName + '[' + keyPart + ']' : keyPart; + // last level if (keyPartsLength - 1 === j) { // radio elements are special if ((input.type || 'text').toLowerCase() === 'radio') { @@ -650,7 +555,6 @@ var FormDataJson = /*#__PURE__*/function () { 'inputs': [] }; } - useObject[keyPart].inputs.push(input); } else { useObject[keyPart] = { @@ -669,44 +573,39 @@ var FormDataJson = /*#__PURE__*/function () { 'tree': {} }; } - useObject = useObject[keyPart].tree; } } } - return inputTree; } + /** * Check if arg is a real object (not null) and no array * @param {*} arg * @return {boolean} * @private - */ - ; - + */; FormDataJson.isObject = function isObject(arg) { return arg && typeof arg === 'object' && Object.prototype.toString.call(arg) !== '[object Array]'; } + /** * Check if arg is arr * @param {*} arg * @return {boolean} * @private - */ - ; - + */; FormDataJson.isArray = function isArray(arg) { return Array.isArray(arg); } + /** * Get html element out of given parameter * @param {HTMLElement|JQuery|$|string} param * @return {HTMLElement|null} * @private - */ - ; - + */; FormDataJson.getElement = function getElement(param) { if (typeof param === 'string') return document.querySelector(param); if (param instanceof HTMLElement) return param; @@ -715,53 +614,43 @@ var FormDataJson = /*#__PURE__*/function () { console.warn('FormDataJson: Unsupported element passed. Supported is HTMLElement, a string query selector, JQuery or $ object'); return null; } + /** * Merge from left to right and return a new object * @param {Object} a * @param {Object} b * @return {Object} * @private - */ - ; - + */; FormDataJson.merge = function merge(a, b) { var c = {}; - for (var key in a) { c[key] = a[key]; } - for (var _key in b) { c[_key] = b[_key]; } - return c; }; - return FormDataJson; }(); // module exports - - FormDataJson.defaultOptionsToJson = { /** * Include all disabled field values * @type {boolean} */ 'includeDisabled': false, - /** * Include all button field values * @type {boolean} */ 'includeButtonValues': false, - /** * Include all unchecked radio/checkboxes as given value when they are unchecked * If undefined, than the unchecked field will be ignored in output * @type {*} */ 'uncheckedValue': undefined, - /** * A function, where first parameter is the input field to check for * Must return true if the field should be included @@ -769,7 +658,6 @@ FormDataJson.defaultOptionsToJson = { * @type {function|null} */ 'inputFilter': null, - /** * Does return an array list, with same values as native Array.from(new FormData(form)) * A list will look like [["inputName", "inputValue"], ["inputName", "inputValue"]] @@ -777,18 +665,16 @@ FormDataJson.defaultOptionsToJson = { * @type {boolean} */ 'flatList': false, - /** * If true, then this does skip empty fields from the output * Empty is considered to be: * An empty array (for multiple selects/checkboxes) - * An empty input field (length = 0) + * An empty input field (length = 0 or null) * This does recursively remove keys * Example: {"agb":"1", "user" : [{"name" : ""},{"name" : ""}]} will be {"agb":"1"} * @type {boolean} */ 'skipEmpty': false, - /** * A function that will be called when all file fields are read as base64 data uri * First passed parameter to this function are the form values including all file data @@ -796,14 +682,12 @@ FormDataJson.defaultOptionsToJson = { * @type {function|null} */ 'filesCallback': null, - /** * By default, files are read as base64 data url * Possible values are: readAsDataURL, readAsBinaryString, readAsText, readAsArrayBuffer * @type {string} */ 'fileReaderFunction': 'readAsDataURL', - /** * If true than values try to be a real Array instead of Object where possible * If false than all values that are multiple (multiple select, same input names checkboxes, unnamed array indexes, etc...) will be objects @@ -818,21 +702,18 @@ FormDataJson.defaultOptionsFromJson = { * @type {boolean} */ 'flatList': false, - /** * If true, than all fields that are not exist in the passed values object, will be cleared/emptied * Not exist means, the value must be undefined * @type {boolean} */ 'clearOthers': false, - /** * If true, than all fields that are not exist in the passed values object, will be reset * Not exist means, the value must be undefined * @type {boolean} */ 'resetOthers': false, - /** * If true, when a fields value has changed, a "change" event will be fired * @type {boolean} @@ -841,7 +722,6 @@ FormDataJson.defaultOptionsFromJson = { }; FormDataJson.buttonInputTypes = ['button', 'submit', 'reset', 'image']; FormDataJson.checkedInputTypes = ['checkbox', 'radio']; - if (typeof module !== 'undefined' && module.exports) { module.exports = FormDataJson; } \ No newline at end of file diff --git a/dist/form-data-json.min.js b/dist/form-data-json.min.js index 6709219..4f3ab3a 100644 --- a/dist/form-data-json.min.js +++ b/dist/form-data-json.min.js @@ -1,3 +1,3 @@ 'use strict'; -// form-data-json-convert | version: 2.1.3 | url: https://github.com/brainfoolong/form-data-json -var FormDataJson=function(){function a(){}return a.toJson=function toJson(b,c){function d(b){if("function"==typeof c.inputFilter&&!0!==c.inputFilter(b))return!1;if(!c.includeDisabled&&b.disabled)return!1;var d=(b.type||"text").toLowerCase();return!(!c.includeButtonValues&&(b instanceof HTMLButtonElement||-1 -1)) { return false; - } // ignore unchecked fields when no value is given - + } + // ignore unchecked fields when no value is given if (typeof options.uncheckedValue === 'undefined' && FormDataJson.checkedInputTypes.indexOf(inputType) > -1 && !input.checked) { return false; } - return true; } - var tree = FormDataJson.getFieldTree(el, isValidInput); var returnObject = options.flatList ? [] : {}; var files = []; + /** * Recursive get values * @param {Object} inputs * @param {Object} values */ - function recursion(inputs, values) { for (var key in inputs) { var row = inputs[key]; - var objectKey = options.flatList ? row.name : key; // next level - + var objectKey = options.flatList ? row.name : key; + // next level if (row.type === 'nested') { if (options.flatList) { recursion(row.tree, values); @@ -90,13 +83,10 @@ var FormDataJson = /*#__PURE__*/function () { values[key] = {}; recursion(row.tree, values[key]); } - continue; } - var input = row.input; var inputType = row.inputType; - if (inputType === 'file') { if (options.filesCallback) { files.push({ @@ -105,22 +95,17 @@ var FormDataJson = /*#__PURE__*/function () { 'input': input }); } - continue; } - var value = null; - if (row.type === 'radio') { for (var i = 0; i < row.inputs.length; i++) { var radioInput = row.inputs[i]; - if (radioInput.checked) { value = radioInput.value; break; } } - if (value === null) { value = options.uncheckedValue; } @@ -128,15 +113,12 @@ var FormDataJson = /*#__PURE__*/function () { value = input.checked ? input.value : options.uncheckedValue; } else if (input instanceof HTMLSelectElement) { var arr = []; - for (var _i = 0; _i < input.options.length; _i++) { var option = input.options[_i]; - if (option.selected) { arr.push((typeof option.value !== 'undefined' ? option.value : option.text).toString()); } } - if (input.multiple) { value = arr; } else { @@ -145,7 +127,6 @@ var FormDataJson = /*#__PURE__*/function () { } else { value = input.value; } - if (options.flatList) { values.push([row.name, value]); } else { @@ -153,208 +134,171 @@ var FormDataJson = /*#__PURE__*/function () { } } } + /** * Make an object to array if possible * @param {Object} object * @return {*} * @private */ - - function arrayify(object) { if (FormDataJson.isObject(object)) { var count = 0; var valid = true; - for (var key in object) { if (FormDataJson.isObject(object[key]) && !(object[key] instanceof Element)) { object[key] = arrayify(object[key]); } - if (parseInt(key) !== count) { valid = false; } - count++; } - if (valid) { var arr = []; - for (var i in object) { arr.push(object[i]); } - return arr; } } - return object; } + /** * Does some final cleanup before output data * @return {*} */ - - function output() { if (options.arrayify) { returnObject = arrayify(returnObject); } - if (options.skipEmpty) returnObject = removeEmpty(returnObject) || (options.flatList ? [] : {}); return returnObject; } + /** * Recursively remove empty keys * @param {Object} object * @param {number} depth * @return {Object|Array} */ - - function removeEmpty(object, depth) { var isArray = FormDataJson.isArray(object); var newObject = isArray ? [] : {}; var count = 0; - for (var key in object) { var value = object[key]; - if (options.flatList && !depth) { value = value[1]; } - + if (value === null || value === undefined || value === '') { + continue; + } if (FormDataJson.isObject(value) || FormDataJson.isArray(value)) { value = removeEmpty(value, (depth || 0) + 1) || ''; } - if (typeof value !== 'object' && FormDataJson.stringify(value) === '') { continue; } - if (isArray) { newObject.push(options.flatList && !depth ? [object[key][0], value] : value); } else { newObject[key] = value; } - count++; } - return count ? newObject : null; } - recursion(tree, returnObject); - if (files.length) { var _ret = function () { var filesDone = 0; var filesRequired = 0; - var _loop = function _loop(i) { var row = files[i]; var useObject = row.object; filesRequired += row.input.files.length; - var _loop2 = function _loop2(j) { var file = row.input.files[j]; var reader = new FileReader(); - reader.onload = function () { if (row.input.multiple) { if (!FormDataJson.isArray(useObject[row.key])) { useObject[row.key] = []; } - useObject[row.key].push(reader.result); } else { useObject[row.key] = reader.result; } - filesDone++; - if (filesDone === filesRequired) { options.filesCallback(output()); } }; - reader[options.fileReaderFunction](file); }; - for (var j = 0; j < row.input.files.length; j++) { _loop2(j); } }; - for (var i = 0; i < files.length; i++) { _loop(i); } - return { v: null }; }(); - if (typeof _ret === "object") return _ret.v; } else if (options.filesCallback) { options.filesCallback(output()); return null; } - return output(); } + /** * Fill given form values into all form elements inside given element * @param {*} el * @param {Object|Array} values * @param {Object=} options * @param {string=} keyPrefix Internal only - */ - ; - + */; FormDataJson.fromJson = function fromJson(el, values, options, keyPrefix) { if (!FormDataJson.isObject(values) && !FormDataJson.isArray(values)) return; options = FormDataJson.merge(FormDataJson.defaultOptionsFromJson, options); var tree = FormDataJson.getFieldTree(el); var lastUsedFlatListIndex = {}; - if (options.clearOthers) { FormDataJson.clear(el); } - if (options.resetOthers) { FormDataJson.reset(el); } + /** * Recursive set values * @param {*} inputs * @param {*} newValues */ - - function recursion(inputs, newValues) { if (!FormDataJson.isObject(newValues) && !FormDataJson.isArray(newValues)) return; - for (var key in inputs) { var row = inputs[key]; - var objectKey = options.flatList ? row.name : key; // next level - + var objectKey = options.flatList ? row.name : key; + // next level if (row.type === 'nested') { if (options.flatList) { recursion(row.tree, newValues); } else if (typeof newValues[objectKey] === 'object') { recursion(row.tree, newValues[objectKey]); } - continue; - } // flat list must search correct entry for given input name - - + } + // flat list must search correct entry for given input name if (options.flatList) { for (var i in newValues) { var value = newValues[i]; - if (value && value[0] === row.name) { if (lastUsedFlatListIndex[row.name] !== i) { lastUsedFlatListIndex[row.name] = i; @@ -363,64 +307,54 @@ var FormDataJson = /*#__PURE__*/function () { } } } - continue; - } // skip fields that are not presented in the value list - - + } + // skip fields that are not presented in the value list if (typeof newValues[objectKey] === 'undefined') { continue; } - FormDataJson.setInputValue(row, newValues[objectKey] || null, options.triggerChangeEvent); } } - recursion(tree, values); } + /** * Reset all input fields in the given element to their default values * @param {*} el - */ - ; - + */; FormDataJson.reset = function reset(el) { var tree = FormDataJson.getFieldTree(el); + /** * Recursive reset * @param {*} inputs */ - function recursion(inputs) { for (var key in inputs) { - var row = inputs[key]; // next level - + var row = inputs[key]; + // next level if (row.type === 'nested') { recursion(row.tree); continue; } - if (row.type === 'radio') { for (var i = 0; i < row.inputs.length; i++) { var radioInput = row.inputs[i]; radioInput.checked = radioInput.defaultChecked; } - continue; - } // ignore button elements, as reset would reset button labels, which is mostly not that what anybody want - + } + // ignore button elements, as reset would reset button labels, which is mostly not that what anybody want if (row.inputType && FormDataJson.buttonInputTypes.indexOf(row.inputType) > -1) { continue; } - var input = row.input; - if (FormDataJson.checkedInputTypes.indexOf(row.inputType) > -1) { input.checked = input.defaultChecked; } else if (input instanceof HTMLSelectElement) { var options = input.querySelectorAll('option'); - for (var _i2 = 0; _i2 < options.length; _i2++) { var option = options[_i2]; option.selected = option.defaultSelected; @@ -432,57 +366,50 @@ var FormDataJson = /*#__PURE__*/function () { } } } - recursion(tree); } + /** * Clear all input fields (to empty, unchecked, unselected) in the given element * @param {*} el - */ - ; - + */; FormDataJson.clear = function clear(el) { var tree = FormDataJson.getFieldTree(el); + /** * Recursive clear * @param {*} inputs */ - function recursion(inputs) { for (var key in inputs) { - var row = inputs[key]; // next level - + var row = inputs[key]; + // next level if (row.type === 'nested') { recursion(row.tree); continue; } - if (row.input) { // ignore button elements, as clear would unset button labels, which is mostly not that what anybody want if (FormDataJson.buttonInputTypes.indexOf(row.inputType) > -1) { continue; } } - FormDataJson.setInputValue(row, null); } } - recursion(tree); } + /** * Set input value * @param {*} row * @param {*|null} newValue Null will unset the value * @param {boolean=} triggerChangeEvent * @private - */ - ; - + */; FormDataJson.setInputValue = function setInputValue(row, newValue, triggerChangeEvent) { var triggerChange = triggerChangeEvent ? function (el) { var ev = null; - if (typeof Event === 'function') { ev = new Event('change', { 'bubbles': true @@ -491,50 +418,41 @@ var FormDataJson = /*#__PURE__*/function () { ev = document.createEvent('Event'); ev.initEvent('change', true, true); } - el.dispatchEvent(ev); } : null; - if (row.type === 'radio') { var _changed = []; - for (var i = 0; i < row.inputs.length; i++) { var radioInput = row.inputs[i]; if (radioInput.checked) _changed.push(radioInput); radioInput.checked = false; - if (newValue !== null && FormDataJson.stringify(radioInput.value) === FormDataJson.stringify(newValue)) { if (!radioInput.checked) _changed.push(radioInput); radioInput.checked = true; break; } } - if (triggerChange) { for (var _i3 in _changed) { triggerChange(_changed[_i3]); } } - return; } - var input = row.input; - var inputType = row.inputType; // ignore file input, they cannot be set + var inputType = row.inputType; + // ignore file input, they cannot be set if (inputType === 'file') { return; } - var changed = false; - if (inputType === 'checkbox') { newValue = newValue === true || newValue !== null && FormDataJson.stringify(input.value) === FormDataJson.stringify(newValue); if (newValue !== input.checked) changed = true; input.checked = newValue; } else if (input instanceof HTMLSelectElement) { var newValueArr = newValue; - if (newValueArr === null || newValueArr === undefined) { newValueArr = []; } else if (FormDataJson.isObject(newValueArr)) { @@ -542,13 +460,11 @@ var FormDataJson = /*#__PURE__*/function () { } else if (!FormDataJson.isArray(newValueArr)) { newValueArr = [newValueArr]; } - for (var _i4 = 0; _i4 < input.options.length; _i4++) { var option = input.options[_i4]; var optionValue = (typeof option.value !== 'undefined' ? option.value : option.text).toString(); if (option.selected !== false) changed = true; option.selected = false; - for (var j = 0; j < newValueArr.length; j++) { if (optionValue === FormDataJson.stringify(newValueArr[j])) { if (option.selected !== true) changed = true; @@ -561,46 +477,40 @@ var FormDataJson = /*#__PURE__*/function () { if (input.value !== newValue) changed = true; input.value = newValue; } - if (changed && triggerChange) { triggerChange(input); } } + /** * Convert any value to a string * A object/array/undefined will be an ampty string * Boolean will be 1 or 0 * @param {*} value * @private - */ - ; - + */; FormDataJson.stringify = function stringify(value) { if (value === undefined) return ''; if (typeof value === 'object') return ''; if (typeof value === 'boolean') return value ? '1' : '0'; return value + ''; } + /** * Get all input fields as a multidimensional tree, as it would later appear in json data, but with input elements as values * @param {*} el * @param {function=} isValidInput A function to check for valid input * @return {Object} * @private - */ - ; - + */; FormDataJson.getFieldTree = function getFieldTree(el, isValidInput) { el = FormDataJson.getElement(el); - if (!el) { return []; } - var inputs = el.querySelectorAll('select, textarea, input, button'); var inputTree = {}; var autoIncrementCounts = {}; - for (var i = 0; i < inputs.length; i++) { var input = inputs[i]; if (!input.name || input.name.length === 0) continue; @@ -608,7 +518,6 @@ var FormDataJson = /*#__PURE__*/function () { var inputType = (input.type || 'text').toLowerCase(); var name = input.name; var keyParts = input.name.replace(/\]/g, '').split('['); - if (name.substr(-2) === '[]') { if (input instanceof HTMLSelectElement && input.multiple) { // special for multiple selects, we skip the last empty part to fix double nested arrays @@ -619,26 +528,22 @@ var FormDataJson = /*#__PURE__*/function () { keyParts.pop(); } } - var keyPartsLength = keyParts.length; var useObject = inputTree; var currentName = ''; - for (var j = 0; j < keyPartsLength; j++) { var keyPart = keyParts[j]; - var newCurrentName = currentName ? currentName + '[' + keyPart + ']' : keyPart; // auto increment key part - + var newCurrentName = currentName ? currentName + '[' + keyPart + ']' : keyPart; + // auto increment key part if (keyPart === '') { if (typeof autoIncrementCounts[newCurrentName] === 'undefined') { autoIncrementCounts[newCurrentName] = 0; } - keyPart = autoIncrementCounts[newCurrentName].toString(); autoIncrementCounts[newCurrentName]++; } - - currentName = currentName ? currentName + '[' + keyPart + ']' : keyPart; // last level - + currentName = currentName ? currentName + '[' + keyPart + ']' : keyPart; + // last level if (keyPartsLength - 1 === j) { // radio elements are special if ((input.type || 'text').toLowerCase() === 'radio') { @@ -650,7 +555,6 @@ var FormDataJson = /*#__PURE__*/function () { 'inputs': [] }; } - useObject[keyPart].inputs.push(input); } else { useObject[keyPart] = { @@ -669,44 +573,39 @@ var FormDataJson = /*#__PURE__*/function () { 'tree': {} }; } - useObject = useObject[keyPart].tree; } } } - return inputTree; } + /** * Check if arg is a real object (not null) and no array * @param {*} arg * @return {boolean} * @private - */ - ; - + */; FormDataJson.isObject = function isObject(arg) { return arg && typeof arg === 'object' && Object.prototype.toString.call(arg) !== '[object Array]'; } + /** * Check if arg is arr * @param {*} arg * @return {boolean} * @private - */ - ; - + */; FormDataJson.isArray = function isArray(arg) { return Array.isArray(arg); } + /** * Get html element out of given parameter * @param {HTMLElement|JQuery|$|string} param * @return {HTMLElement|null} * @private - */ - ; - + */; FormDataJson.getElement = function getElement(param) { if (typeof param === 'string') return document.querySelector(param); if (param instanceof HTMLElement) return param; @@ -715,53 +614,43 @@ var FormDataJson = /*#__PURE__*/function () { console.warn('FormDataJson: Unsupported element passed. Supported is HTMLElement, a string query selector, JQuery or $ object'); return null; } + /** * Merge from left to right and return a new object * @param {Object} a * @param {Object} b * @return {Object} * @private - */ - ; - + */; FormDataJson.merge = function merge(a, b) { var c = {}; - for (var key in a) { c[key] = a[key]; } - for (var _key in b) { c[_key] = b[_key]; } - return c; }; - return FormDataJson; }(); // module exports - - FormDataJson.defaultOptionsToJson = { /** * Include all disabled field values * @type {boolean} */ 'includeDisabled': false, - /** * Include all button field values * @type {boolean} */ 'includeButtonValues': false, - /** * Include all unchecked radio/checkboxes as given value when they are unchecked * If undefined, than the unchecked field will be ignored in output * @type {*} */ 'uncheckedValue': undefined, - /** * A function, where first parameter is the input field to check for * Must return true if the field should be included @@ -769,7 +658,6 @@ FormDataJson.defaultOptionsToJson = { * @type {function|null} */ 'inputFilter': null, - /** * Does return an array list, with same values as native Array.from(new FormData(form)) * A list will look like [["inputName", "inputValue"], ["inputName", "inputValue"]] @@ -777,18 +665,16 @@ FormDataJson.defaultOptionsToJson = { * @type {boolean} */ 'flatList': false, - /** * If true, then this does skip empty fields from the output * Empty is considered to be: * An empty array (for multiple selects/checkboxes) - * An empty input field (length = 0) + * An empty input field (length = 0 or null) * This does recursively remove keys * Example: {"agb":"1", "user" : [{"name" : ""},{"name" : ""}]} will be {"agb":"1"} * @type {boolean} */ 'skipEmpty': false, - /** * A function that will be called when all file fields are read as base64 data uri * First passed parameter to this function are the form values including all file data @@ -796,14 +682,12 @@ FormDataJson.defaultOptionsToJson = { * @type {function|null} */ 'filesCallback': null, - /** * By default, files are read as base64 data url * Possible values are: readAsDataURL, readAsBinaryString, readAsText, readAsArrayBuffer * @type {string} */ 'fileReaderFunction': 'readAsDataURL', - /** * If true than values try to be a real Array instead of Object where possible * If false than all values that are multiple (multiple select, same input names checkboxes, unnamed array indexes, etc...) will be objects @@ -818,21 +702,18 @@ FormDataJson.defaultOptionsFromJson = { * @type {boolean} */ 'flatList': false, - /** * If true, than all fields that are not exist in the passed values object, will be cleared/emptied * Not exist means, the value must be undefined * @type {boolean} */ 'clearOthers': false, - /** * If true, than all fields that are not exist in the passed values object, will be reset * Not exist means, the value must be undefined * @type {boolean} */ 'resetOthers': false, - /** * If true, when a fields value has changed, a "change" event will be fired * @type {boolean} @@ -841,7 +722,6 @@ FormDataJson.defaultOptionsFromJson = { }; FormDataJson.buttonInputTypes = ['button', 'submit', 'reset', 'image']; FormDataJson.checkedInputTypes = ['checkbox', 'radio']; - if (typeof module !== 'undefined' && module.exports) { module.exports = FormDataJson; } \ No newline at end of file diff --git a/docs/scripts/form-data-json.min.js b/docs/scripts/form-data-json.min.js index 6709219..4f3ab3a 100644 --- a/docs/scripts/form-data-json.min.js +++ b/docs/scripts/form-data-json.min.js @@ -1,3 +1,3 @@ 'use strict'; -// form-data-json-convert | version: 2.1.3 | url: https://github.com/brainfoolong/form-data-json -var FormDataJson=function(){function a(){}return a.toJson=function toJson(b,c){function d(b){if("function"==typeof c.inputFilter&&!0!==c.inputFilter(b))return!1;if(!c.includeDisabled&&b.disabled)return!1;var d=(b.type||"text").toLowerCase();return!(!c.includeButtonValues&&(b instanceof HTMLButtonElement||-1 - + @@ -285,7 +285,7 @@

@@ -343,7 +343,7 @@

@@ -465,6 +465,18 @@

Testing option "skipEmpty"

+ + + diff --git a/package.json b/package.json index 4e895a3..df0637c 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "type": "git", "url": "git+https://github.com/brainfoolong/form-data-json.git" }, - "version": "2.1.3", + "version": "2.1.4", "scripts": { "dist": "node build/dist.js" }, diff --git a/src/form-data-json.js b/src/form-data-json.js index 3cf7141..baec0dc 100644 --- a/src/form-data-json.js +++ b/src/form-data-json.js @@ -51,7 +51,7 @@ class FormDataJson { * If true, then this does skip empty fields from the output * Empty is considered to be: * An empty array (for multiple selects/checkboxes) - * An empty input field (length = 0) + * An empty input field (length = 0 or null) * This does recursively remove keys * Example: {"agb":"1", "user" : [{"name" : ""},{"name" : ""}]} will be {"agb":"1"} * @type {boolean} @@ -295,6 +295,9 @@ class FormDataJson { if (options.flatList && !depth) { value = value[1] } + if (value === null || value === undefined || value === '') { + continue + } if (FormDataJson.isObject(value) || FormDataJson.isArray(value)) { value = removeEmpty(value, (depth || 0) + 1) || '' }