@fregante/dom-form-serializer 2.1.0-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +271 -0
- package/dist/dom-form-serializer.js +354 -0
- package/dist/dom-form-serializer.js.map +1 -0
- package/dist/dom-form-serializer.mjs +345 -0
- package/dist/dom-form-serializer.mjs.map +1 -0
- package/lib/InputReaders.js +59 -0
- package/lib/InputWriters.js +62 -0
- package/lib/KeyAssignmentValidators.js +9 -0
- package/lib/KeyExtractors.js +8 -0
- package/lib/TypeRegistry.js +23 -0
- package/lib/assignKeyValue.js +31 -0
- package/lib/deserialize.js +45 -0
- package/lib/flattenData.js +34 -0
- package/lib/getElementType.js +14 -0
- package/lib/getInputElements.js +39 -0
- package/lib/index.js +4 -0
- package/lib/keyJoiner.js +3 -0
- package/lib/keySplitter.js +9 -0
- package/lib/serialize.js +47 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2016 Jeferson Daniel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# DOM Form Serializer
|
|
2
|
+
|
|
3
|
+
[](https://github.com/jefersondaniel/dom-form-serializer/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/dom-form-serializer)
|
|
5
|
+
[](https://www.npmjs.com/package/dom-form-serializer)
|
|
6
|
+
|
|
7
|
+
Serialize forms fields into a JSON representation.
|
|
8
|
+
|
|
9
|
+
## About
|
|
10
|
+
|
|
11
|
+
This project is a fork of [Backbone.Syphon](https://github.com/marionettejs/backbone.syphon) that has no dependency on backbone and jquery. It aims to make it easy to serialize the fields of a form into a simple JSON object.
|
|
12
|
+
|
|
13
|
+
### Installing
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
npm install dom-form-serializer
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Basic Usage
|
|
20
|
+
|
|
21
|
+
### Serialize
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
var serialize = require('dom-form-serializer').serialize
|
|
25
|
+
serialize(document.querySelector('#form'))
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Keys Retrieved By "name" Attribute
|
|
29
|
+
|
|
30
|
+
The default behavior for serializing fields is to use the field's "name" attribute as the key in the serialized object.
|
|
31
|
+
|
|
32
|
+
```html
|
|
33
|
+
<form id="form">
|
|
34
|
+
<input name="a">
|
|
35
|
+
<select name="b"></select>
|
|
36
|
+
<textarea name="c"></textarea>
|
|
37
|
+
</form>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
serialize(document.querySelector('#form'))
|
|
42
|
+
|
|
43
|
+
// will produce =>
|
|
44
|
+
|
|
45
|
+
{
|
|
46
|
+
a: "",
|
|
47
|
+
b: "",
|
|
48
|
+
c: ""
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Checkboxes
|
|
53
|
+
|
|
54
|
+
By default, a checkbox will return a boolean value signifying whether or not it is checked.
|
|
55
|
+
|
|
56
|
+
```html
|
|
57
|
+
<form id="form">
|
|
58
|
+
<input type="checkbox" name="a">
|
|
59
|
+
<input type="checkbox" name="b" checked>
|
|
60
|
+
<input type="checkbox" name="c" indeterminate>
|
|
61
|
+
</form>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
```js
|
|
65
|
+
serialize(document.querySelector('#form'));
|
|
66
|
+
|
|
67
|
+
// will produce =>
|
|
68
|
+
|
|
69
|
+
{
|
|
70
|
+
a: false,
|
|
71
|
+
b: true,
|
|
72
|
+
c: null
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Radio Button Groups
|
|
77
|
+
|
|
78
|
+
Radio button groups (grouped by the input element "name" attribute) will produce a single value, from the selected
|
|
79
|
+
radio button.
|
|
80
|
+
|
|
81
|
+
```html
|
|
82
|
+
<form id="form">
|
|
83
|
+
<input type="radio" name="a" value="1">
|
|
84
|
+
<input type="radio" name="a" value="2" checked>
|
|
85
|
+
<input type="radio" name="a" value="3">
|
|
86
|
+
<input type="radio" name="a" value="4">
|
|
87
|
+
</form>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
```js
|
|
91
|
+
serialize(document.querySelector('#form'))
|
|
92
|
+
|
|
93
|
+
// will produce =>
|
|
94
|
+
|
|
95
|
+
{
|
|
96
|
+
a: "2"
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
This behavior can be changed by registering a different set of Key Extractors, Input Readers, and Key Assignment
|
|
101
|
+
Validators. See the tests
|
|
102
|
+
[serialize.spec.js](https://github.com/jefersondaniel/dom-form-serializer/blob/master/test/serialize.spec.js) for more examples on these.
|
|
103
|
+
|
|
104
|
+
### Drop Down Lists
|
|
105
|
+
|
|
106
|
+
Serializing drop down lists (`<select>`) will result in value of the selected option.
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
```html
|
|
110
|
+
<form id="form">
|
|
111
|
+
<select name="foo">
|
|
112
|
+
<option value="bar"></option>
|
|
113
|
+
</select>
|
|
114
|
+
</form>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
```js
|
|
119
|
+
serialize(document.querySelector('#form'))
|
|
120
|
+
|
|
121
|
+
// will produce =>
|
|
122
|
+
|
|
123
|
+
{
|
|
124
|
+
foo: "bar"
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Multiple Select Boxes
|
|
129
|
+
|
|
130
|
+
Serializing multiple select boxes (`<select multiple>`) will yield the selected options as an array.
|
|
131
|
+
|
|
132
|
+
```html
|
|
133
|
+
<form id="form">
|
|
134
|
+
<select name="foo" multiple>
|
|
135
|
+
<option value="foo"></option>
|
|
136
|
+
<option value="bar" selected></option>
|
|
137
|
+
<option value="baz" selected></option>
|
|
138
|
+
</select>
|
|
139
|
+
</form>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
```js
|
|
143
|
+
serialize(document.querySelector('#form'))
|
|
144
|
+
|
|
145
|
+
// will produce =>
|
|
146
|
+
|
|
147
|
+
{
|
|
148
|
+
foo: ["bar", "baz"]
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Basic Usage: Deserialize
|
|
153
|
+
|
|
154
|
+
You can also deserialize an object's values back into their field equivalent. It uses the same conventions and configuration as the serialization process, with the introduction of Input Writers to handle populating the fields with the values
|
|
155
|
+
|
|
156
|
+
```html
|
|
157
|
+
<form id="form">
|
|
158
|
+
<input type="text" name="a">
|
|
159
|
+
<input type="text" name="b">
|
|
160
|
+
</form>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
```js
|
|
164
|
+
var data = {
|
|
165
|
+
a: "foo",
|
|
166
|
+
b: "bar"
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
deserialize(document.querySelector('#form'), data);
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
This will populate the form field elements with the correct values from the `data` parameter.
|
|
173
|
+
|
|
174
|
+
## Ignored Input Types
|
|
175
|
+
|
|
176
|
+
The following types of input are ignored, and not included in the resulting JavaScript object:
|
|
177
|
+
|
|
178
|
+
* `<input type="submit">` buttons
|
|
179
|
+
* `<input type="reset"`> buttons
|
|
180
|
+
* standard `<button>` tags
|
|
181
|
+
|
|
182
|
+
If you need to get a value from the specific button that was clicked, you can use a DOM event to listen for that element being manipulated (clicked, for example) and manually grab
|
|
183
|
+
the data you need.
|
|
184
|
+
|
|
185
|
+
### Ignoring Other Input Types
|
|
186
|
+
|
|
187
|
+
You can define ignored selectors using the ignoredTypes option.
|
|
188
|
+
|
|
189
|
+
```js
|
|
190
|
+
// ignore all <textarea> input elements
|
|
191
|
+
serialize(element, {ignoredTypes: ['textarea']})
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Serializing Nested Attributes And Field Names
|
|
195
|
+
|
|
196
|
+
`serialize` will parse nested attribute names and create a nested result object, using the Rails standard of `name="foo[bar][baz]"` by default.
|
|
197
|
+
|
|
198
|
+
```html
|
|
199
|
+
<form>
|
|
200
|
+
<input type="text" name="foo[bar]" value="a value">
|
|
201
|
+
<input type="text" name="foo[baz][quux]" value="another value">
|
|
202
|
+
</form>
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
will produce
|
|
206
|
+
|
|
207
|
+
```js
|
|
208
|
+
{
|
|
209
|
+
foo: {
|
|
210
|
+
bar: "a value",
|
|
211
|
+
baz: {
|
|
212
|
+
quux: "another value"
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Array Inputs
|
|
219
|
+
|
|
220
|
+
`serialize` will parse multiple inputs named after the convention `name="foo[bar][]"` into elements of the array `bar`.
|
|
221
|
+
|
|
222
|
+
```html
|
|
223
|
+
<form>
|
|
224
|
+
<input type="checkbox" name="foo[bar][]" value="baz" checked="checked">
|
|
225
|
+
<input type="checkbox" name="foo[bar][]" value="qux" checked="checked">
|
|
226
|
+
</form>
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
will produce
|
|
230
|
+
|
|
231
|
+
```js
|
|
232
|
+
{
|
|
233
|
+
foo: {
|
|
234
|
+
bar: ["baz", "qux"]
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Custom splitters
|
|
240
|
+
|
|
241
|
+
If your keys are split by something else than the Rails Array convention (for example `name="foo.bar.quux"`), you may pass this delimiter into `serialize` using the `keySplitter` option.
|
|
242
|
+
|
|
243
|
+
```html
|
|
244
|
+
<form id="form">
|
|
245
|
+
<input type="text" name="widget" value="wombat">
|
|
246
|
+
<input type="text" name="foo.bar" value="baz">
|
|
247
|
+
<input type="text" name="foo.baz.quux" value="qux">
|
|
248
|
+
</form>
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
```js
|
|
252
|
+
serialize(document.querySelector('#form'), { keySplitter: key => key.split('.') })
|
|
253
|
+
|
|
254
|
+
// will produce =>
|
|
255
|
+
|
|
256
|
+
{
|
|
257
|
+
widget: "wombat",
|
|
258
|
+
foo: {
|
|
259
|
+
bar: "baz",
|
|
260
|
+
baz: {
|
|
261
|
+
quux: "qux"
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
# Acknowledgments
|
|
269
|
+
|
|
270
|
+
[Backbone.Syphon](https://github.com/marionettejs/backbone.syphon)
|
|
271
|
+
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
(function (global, factory) {
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.DOMFormSerializer = {}));
|
|
5
|
+
})(this, (function (exports) { 'use strict';
|
|
6
|
+
|
|
7
|
+
class TypeRegistry {
|
|
8
|
+
constructor() {
|
|
9
|
+
let initial = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
10
|
+
this.registeredTypes = initial;
|
|
11
|
+
}
|
|
12
|
+
get(type) {
|
|
13
|
+
if (typeof this.registeredTypes[type] !== 'undefined') {
|
|
14
|
+
return this.registeredTypes[type];
|
|
15
|
+
} else {
|
|
16
|
+
return this.registeredTypes.default;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
register(type, item) {
|
|
20
|
+
if (typeof this.registeredTypes[type] === 'undefined') {
|
|
21
|
+
this.registeredTypes[type] = item;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
registerDefault(item) {
|
|
25
|
+
this.register('default', item);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
class KeyExtractors extends TypeRegistry {
|
|
30
|
+
constructor(options) {
|
|
31
|
+
super(options);
|
|
32
|
+
this.registerDefault(el => el.getAttribute('name') || '');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
class InputReaders extends TypeRegistry {
|
|
37
|
+
constructor(options) {
|
|
38
|
+
super(options);
|
|
39
|
+
this.registerDefault(el => el.value);
|
|
40
|
+
this.register('checkbox', el => el.getAttribute('value') !== null ? el.checked ? el.getAttribute('value') : null : el.checked);
|
|
41
|
+
this.register('select', el => getSelectValue(el));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Read select values
|
|
47
|
+
*
|
|
48
|
+
* @see {@link https://github.com/jquery/jquery/blob/master/src/attributes/val.js|Github}
|
|
49
|
+
* @param {object} Select element
|
|
50
|
+
* @return {string|Array} Select value(s)
|
|
51
|
+
*/
|
|
52
|
+
function getSelectValue(elem) {
|
|
53
|
+
let value, option, i;
|
|
54
|
+
const options = elem.options;
|
|
55
|
+
const index = elem.selectedIndex;
|
|
56
|
+
const one = elem.type === 'select-one';
|
|
57
|
+
const values = one ? null : [];
|
|
58
|
+
const max = one ? index + 1 : options.length;
|
|
59
|
+
if (index < 0) {
|
|
60
|
+
i = max;
|
|
61
|
+
} else {
|
|
62
|
+
i = one ? index : 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Loop through all the selected options
|
|
66
|
+
for (; i < max; i++) {
|
|
67
|
+
option = options[i];
|
|
68
|
+
|
|
69
|
+
// Support: IE <=9 only
|
|
70
|
+
// IE8-9 doesn't update selected after form reset
|
|
71
|
+
if ((option.selected || i === index) &&
|
|
72
|
+
// Don't return options that are disabled or in a disabled optgroup
|
|
73
|
+
!option.disabled && !(option.parentNode.disabled && option.parentNode.tagName.toLowerCase() === 'optgroup')) {
|
|
74
|
+
// Get the specific value for the option
|
|
75
|
+
value = option.value;
|
|
76
|
+
|
|
77
|
+
// We don't need an array for one selects
|
|
78
|
+
if (one) {
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Multi-Selects return an array
|
|
83
|
+
values.push(value);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return values;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
class KeyAssignmentValidators extends TypeRegistry {
|
|
90
|
+
constructor(options) {
|
|
91
|
+
super(options);
|
|
92
|
+
this.registerDefault(() => true);
|
|
93
|
+
this.register('radio', el => el.checked);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function keySplitter(key) {
|
|
98
|
+
const matches = key.match(/[^[\]]+/g);
|
|
99
|
+
let lastKey;
|
|
100
|
+
if (key.length > 1 && key.indexOf('[]') === key.length - 2) {
|
|
101
|
+
lastKey = matches.pop();
|
|
102
|
+
matches.push([lastKey]);
|
|
103
|
+
}
|
|
104
|
+
return matches;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function getElementType(el) {
|
|
108
|
+
let typeAttr;
|
|
109
|
+
const tagName = el.tagName;
|
|
110
|
+
let type = tagName;
|
|
111
|
+
if (tagName.toLowerCase() === 'input') {
|
|
112
|
+
typeAttr = el.getAttribute('type');
|
|
113
|
+
if (typeAttr) {
|
|
114
|
+
type = typeAttr;
|
|
115
|
+
} else {
|
|
116
|
+
type = 'text';
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return type.toLowerCase();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function getInputElements(element, options) {
|
|
123
|
+
return Array.prototype.filter.call(element.querySelectorAll('input,select,textarea'), el => {
|
|
124
|
+
if (el.tagName.toLowerCase() === 'input' && (el.type === 'submit' || el.type === 'reset')) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
const myType = getElementType(el);
|
|
128
|
+
const extractor = options.keyExtractors.get(myType);
|
|
129
|
+
const identifier = extractor(el);
|
|
130
|
+
const foundInInclude = (options.include || []).indexOf(identifier) !== -1;
|
|
131
|
+
const foundInExclude = (options.exclude || []).indexOf(identifier) !== -1;
|
|
132
|
+
let foundInIgnored = false;
|
|
133
|
+
let reject = false;
|
|
134
|
+
if (options.ignoredTypes) {
|
|
135
|
+
for (const selector of options.ignoredTypes) {
|
|
136
|
+
if (el.matches(selector)) {
|
|
137
|
+
foundInIgnored = true;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (foundInInclude) {
|
|
142
|
+
reject = false;
|
|
143
|
+
} else {
|
|
144
|
+
if (options.include) {
|
|
145
|
+
reject = true;
|
|
146
|
+
} else {
|
|
147
|
+
reject = foundInExclude || foundInIgnored;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return !reject;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const UNSAFE_KEYS = ['__proto__', 'constructor', 'prototype'];
|
|
155
|
+
function assignKeyValue(obj, keychain, value) {
|
|
156
|
+
if (!keychain) {
|
|
157
|
+
return obj;
|
|
158
|
+
}
|
|
159
|
+
const key = keychain.shift();
|
|
160
|
+
|
|
161
|
+
// Refuse to walk into prototype-pollution sinks.
|
|
162
|
+
if (UNSAFE_KEYS.indexOf(key) !== -1) {
|
|
163
|
+
return obj;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// build the current object we need to store data
|
|
167
|
+
if (!obj[key]) {
|
|
168
|
+
obj[key] = Array.isArray(key) ? [] : {};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// if it's the last key in the chain, assign the value directly
|
|
172
|
+
if (keychain.length === 0) {
|
|
173
|
+
if (!Array.isArray(obj[key])) {
|
|
174
|
+
obj[key] = value;
|
|
175
|
+
} else if (value !== null) {
|
|
176
|
+
obj[key].push(value);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// recursive parsing of the array, depth-first
|
|
181
|
+
if (keychain.length > 0) {
|
|
182
|
+
assignKeyValue(obj[key], keychain, value);
|
|
183
|
+
}
|
|
184
|
+
return obj;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Get a JSON object that represents all of the form inputs, in this element.
|
|
189
|
+
*
|
|
190
|
+
* @param {HTMLElement} Root element
|
|
191
|
+
* @param {object} options
|
|
192
|
+
* @param {object} options.inputReaders
|
|
193
|
+
* @param {object} options.keyAssignmentValidators
|
|
194
|
+
* @param {object} options.keyExtractors
|
|
195
|
+
* @param {object} options.keySplitter
|
|
196
|
+
* @param {string[]} options.include
|
|
197
|
+
* @param {string[]} options.exclude
|
|
198
|
+
* @param {string[]} options.ignoredTypes
|
|
199
|
+
* @return {object}
|
|
200
|
+
*/
|
|
201
|
+
function serialize(element) {
|
|
202
|
+
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
203
|
+
let data = {};
|
|
204
|
+
options.keySplitter = options.keySplitter || keySplitter;
|
|
205
|
+
options.keyExtractors = new KeyExtractors(options.keyExtractors || {});
|
|
206
|
+
options.inputReaders = new InputReaders(options.inputReaders || {});
|
|
207
|
+
options.keyAssignmentValidators = new KeyAssignmentValidators(options.keyAssignmentValidators || {});
|
|
208
|
+
Array.prototype.forEach.call(getInputElements(element, options), el => {
|
|
209
|
+
const type = getElementType(el);
|
|
210
|
+
const keyExtractor = options.keyExtractors.get(type);
|
|
211
|
+
const key = keyExtractor(el);
|
|
212
|
+
const inputReader = options.inputReaders.get(type);
|
|
213
|
+
const value = inputReader(el);
|
|
214
|
+
const validKeyAssignment = options.keyAssignmentValidators.get(type);
|
|
215
|
+
if (validKeyAssignment(el, key, value)) {
|
|
216
|
+
const keychain = options.keySplitter(key);
|
|
217
|
+
data = assignKeyValue(data, keychain, value);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
return data;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
class InputWriters extends TypeRegistry {
|
|
224
|
+
constructor(options) {
|
|
225
|
+
super(options);
|
|
226
|
+
this.registerDefault((el, value) => {
|
|
227
|
+
el.value = value !== null && value !== void 0 ? value : '';
|
|
228
|
+
});
|
|
229
|
+
this.register('checkbox', (el, value) => {
|
|
230
|
+
if (value === null) {
|
|
231
|
+
el.indeterminate = true;
|
|
232
|
+
} else {
|
|
233
|
+
el.checked = Array.isArray(value) ? value.indexOf(el.value) !== -1 : value;
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
this.register('radio', function (el, value) {
|
|
237
|
+
if (value !== undefined) {
|
|
238
|
+
el.checked = el.value === value.toString();
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
this.register('select', setSelectValue);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
function makeArray(arr) {
|
|
245
|
+
const ret = [];
|
|
246
|
+
if (arr !== null) {
|
|
247
|
+
if (Array.isArray(arr)) {
|
|
248
|
+
ret.push.apply(ret, arr);
|
|
249
|
+
} else {
|
|
250
|
+
ret.push(arr);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return ret;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Write select values
|
|
258
|
+
*
|
|
259
|
+
* @see {@link https://github.com/jquery/jquery/blob/master/src/attributes/val.js|Github}
|
|
260
|
+
* @param {object} Select element
|
|
261
|
+
* @param {string|array} Select value
|
|
262
|
+
*/
|
|
263
|
+
function setSelectValue(elem, value) {
|
|
264
|
+
let optionSet, option;
|
|
265
|
+
const options = elem.options;
|
|
266
|
+
const values = makeArray(value);
|
|
267
|
+
let i = options.length;
|
|
268
|
+
while (i--) {
|
|
269
|
+
option = options[i];
|
|
270
|
+
/* eslint-disable no-cond-assign */
|
|
271
|
+
if (values.indexOf(option.value) > -1) {
|
|
272
|
+
option.setAttribute('selected', true);
|
|
273
|
+
optionSet = true;
|
|
274
|
+
}
|
|
275
|
+
/* eslint-enable no-cond-assign */
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Force browsers to behave consistently when non-matching value is set
|
|
279
|
+
if (!optionSet) {
|
|
280
|
+
elem.selectedIndex = -1;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function keyJoiner(parentKey, childKey) {
|
|
285
|
+
return parentKey + '[' + childKey + ']';
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function flattenData(data, parentKey) {
|
|
289
|
+
let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
290
|
+
const flatData = {};
|
|
291
|
+
const keyJoiner$1 = options.keyJoiner || keyJoiner;
|
|
292
|
+
for (let keyName in data) {
|
|
293
|
+
if (!Object.prototype.hasOwnProperty.call(data, keyName)) {
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
const value = data[keyName];
|
|
297
|
+
let hash = {};
|
|
298
|
+
|
|
299
|
+
// If there is a parent key, join it with
|
|
300
|
+
// the current, child key.
|
|
301
|
+
if (parentKey) {
|
|
302
|
+
keyName = keyJoiner$1(parentKey, keyName);
|
|
303
|
+
}
|
|
304
|
+
if (Array.isArray(value)) {
|
|
305
|
+
hash[keyName + '[]'] = value;
|
|
306
|
+
hash[keyName] = value;
|
|
307
|
+
} else if (typeof value === 'object') {
|
|
308
|
+
hash = flattenData(value, keyName, options);
|
|
309
|
+
} else {
|
|
310
|
+
hash[keyName] = value;
|
|
311
|
+
}
|
|
312
|
+
Object.assign(flatData, hash);
|
|
313
|
+
}
|
|
314
|
+
return flatData;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Use the given JSON object to populate all of the form inputs, in this element.
|
|
319
|
+
*
|
|
320
|
+
* @param {HTMLElement} Root element
|
|
321
|
+
* @param {object} options
|
|
322
|
+
* @param {object} options.inputWriters
|
|
323
|
+
* @param {object} options.keyExtractors
|
|
324
|
+
* @param {object} options.keySplitter
|
|
325
|
+
* @param {string[]} options.include
|
|
326
|
+
* @param {string[]} options.exclude
|
|
327
|
+
* @param {string[]} options.ignoredTypes
|
|
328
|
+
*/
|
|
329
|
+
function deserialize(form, data) {
|
|
330
|
+
let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
331
|
+
const flattenedData = flattenData(data, null, options);
|
|
332
|
+
const arrayValueIndexes = {};
|
|
333
|
+
options.keyExtractors = new KeyExtractors(options.keyExtractors || {});
|
|
334
|
+
options.inputWriters = new InputWriters(options.inputWriters || {});
|
|
335
|
+
Array.prototype.forEach.call(getInputElements(form, options), el => {
|
|
336
|
+
const type = getElementType(el);
|
|
337
|
+
const keyExtractor = options.keyExtractors.get(type);
|
|
338
|
+
const key = keyExtractor(el);
|
|
339
|
+
const inputWriter = options.inputWriters.get(type);
|
|
340
|
+
let value = flattenedData[key];
|
|
341
|
+
if (Array.isArray(value) && key.endsWith('[]') && type !== 'checkbox' && type !== 'select') {
|
|
342
|
+
const index = arrayValueIndexes[key] || 0;
|
|
343
|
+
value = value[index];
|
|
344
|
+
arrayValueIndexes[key] = index + 1;
|
|
345
|
+
}
|
|
346
|
+
inputWriter(el, value);
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
exports.deserialize = deserialize;
|
|
351
|
+
exports.serialize = serialize;
|
|
352
|
+
|
|
353
|
+
}));
|
|
354
|
+
//# sourceMappingURL=dom-form-serializer.js.map
|