@appius-fr/apx 2.6.1 → 2.6.2
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/dist/APX.dev.mjs +63 -19
- package/dist/APX.mjs +1 -1
- package/dist/APX.prod.mjs +1 -1
- package/dist/APX.standalone.js +67 -17
- package/dist/APX.standalone.js.map +1 -1
- package/modules/tools/form-packer/README.md +12 -14
- package/modules/tools/form-packer/augment-apx.mjs +4 -2
- package/modules/tools/form-packer/packToJson.mjs +58 -16
- package/package.json +1 -1
|
@@ -123,12 +123,14 @@ The module intelligently handles mixed structures (objects with both numeric and
|
|
|
123
123
|
|
|
124
124
|
## API
|
|
125
125
|
|
|
126
|
-
### `APX.tools.packFormToJSON(form)`
|
|
126
|
+
### `APX.tools.packFormToJSON(form, options?)`
|
|
127
127
|
|
|
128
128
|
Converts an HTML form element into a JSON object.
|
|
129
129
|
|
|
130
130
|
**Parameters:**
|
|
131
131
|
- `form` (HTMLFormElement): The form element to convert
|
|
132
|
+
- `options` (Object, optional):
|
|
133
|
+
- `numericKeysAlwaysArray` (boolean, default `false`): If `true`, every numeric bracket key (e.g. `[26]`) is treated as an array index (pre-2.6.2 behaviour). If `false`, the heuristic applies: array only when indices are dense 0,1,2,…,n; otherwise object with string keys.
|
|
132
134
|
|
|
133
135
|
**Returns:** (Object) The JSON object representation of the form data
|
|
134
136
|
|
|
@@ -137,30 +139,26 @@ Converts an HTML form element into a JSON object.
|
|
|
137
139
|
**Example:**
|
|
138
140
|
```javascript
|
|
139
141
|
const form = document.querySelector('#myForm');
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
} catch (error) {
|
|
144
|
-
console.error('Error:', error.message);
|
|
145
|
-
}
|
|
142
|
+
const data = APX.tools.packFormToJSON(form);
|
|
143
|
+
// Legacy: always use arrays for numeric keys
|
|
144
|
+
const dataLegacy = APX.tools.packFormToJSON(form, { numericKeysAlwaysArray: true });
|
|
146
145
|
```
|
|
147
146
|
|
|
148
|
-
### `APX('form').pack()`
|
|
147
|
+
### `APX('form').pack(options?)`
|
|
149
148
|
|
|
150
149
|
Converts the first selected form element into a JSON object. This is a chainable method on APX objects.
|
|
151
150
|
|
|
151
|
+
**Parameters:**
|
|
152
|
+
- `options` (Object, optional): Same as the second argument of `packFormToJSON` (e.g. `{ numericKeysAlwaysArray: true }`).
|
|
153
|
+
|
|
152
154
|
**Returns:** (Object) The JSON object representation of the form data
|
|
153
155
|
|
|
154
156
|
**Throws:** (Error) If no element is found or the first element is not a form
|
|
155
157
|
|
|
156
158
|
**Example:**
|
|
157
159
|
```javascript
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
console.log(data);
|
|
161
|
-
} catch (error) {
|
|
162
|
-
console.error('Error:', error.message);
|
|
163
|
-
}
|
|
160
|
+
const data = APX('form#myForm').pack();
|
|
161
|
+
const dataLegacy = APX('form#myForm').pack({ numericKeysAlwaysArray: true });
|
|
164
162
|
```
|
|
165
163
|
|
|
166
164
|
---
|
|
@@ -8,12 +8,14 @@ import { packFormToJSON } from './packToJson.mjs';
|
|
|
8
8
|
export default function (apx) {
|
|
9
9
|
/**
|
|
10
10
|
* Convertit le premier formulaire sélectionné en objet JSON
|
|
11
|
+
* @param {Object} [options] - Options passées à packFormToJSON (ex. { numericKeysAlwaysArray: true })
|
|
11
12
|
* @returns {Object} L'objet JSON résultant
|
|
12
13
|
* @throws {Error} Si aucun formulaire n'est trouvé ou si le premier élément n'est pas un formulaire
|
|
13
14
|
* @example
|
|
14
15
|
* const data = APX('form.myformclass').pack();
|
|
16
|
+
* const dataLegacy = APX('form').pack({ numericKeysAlwaysArray: true });
|
|
15
17
|
*/
|
|
16
|
-
apx.pack = function () {
|
|
18
|
+
apx.pack = function (options) {
|
|
17
19
|
const firstElement = this.first();
|
|
18
20
|
if (!firstElement) {
|
|
19
21
|
throw new Error('No element found');
|
|
@@ -21,7 +23,7 @@ export default function (apx) {
|
|
|
21
23
|
if (firstElement.tagName !== 'FORM') {
|
|
22
24
|
throw new Error('Element is not a form');
|
|
23
25
|
}
|
|
24
|
-
return packFormToJSON(firstElement);
|
|
26
|
+
return packFormToJSON(firstElement, options);
|
|
25
27
|
};
|
|
26
28
|
|
|
27
29
|
return apx;
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Convertit un formulaire HTML en objet JSON
|
|
3
3
|
* @param {HTMLFormElement} form - Le formulaire à convertir
|
|
4
|
+
* @param {Object} [options] - Options de conversion
|
|
5
|
+
* @param {boolean} [options.numericKeysAlwaysArray=false] - Si true, toute clé numérique est traitée comme index de tableau (comportement d'avant 2.6.2). Sinon, heuristique : tableau seulement si indices denses 0,1,2,…,n.
|
|
4
6
|
* @returns {Object} L'objet JSON résultant
|
|
5
7
|
* @throws {TypeError} Si form n'est pas un HTMLFormElement
|
|
6
8
|
*/
|
|
7
|
-
export const packFormToJSON = (form) => {
|
|
9
|
+
export const packFormToJSON = (form, options = {}) => {
|
|
8
10
|
// Validation de l'entrée
|
|
9
11
|
if (!form || !(form instanceof HTMLFormElement)) {
|
|
10
12
|
throw new TypeError('packFormToJSON expects an HTMLFormElement');
|
|
11
13
|
}
|
|
12
14
|
|
|
15
|
+
const numericKeysAlwaysArray = options.numericKeysAlwaysArray === true;
|
|
16
|
+
|
|
13
17
|
const formData = new FormData(form);
|
|
14
18
|
const jsonData = {};
|
|
15
19
|
|
|
@@ -48,6 +52,7 @@ export const packFormToJSON = (form) => {
|
|
|
48
52
|
|
|
49
53
|
const pathUsage = new Map(); // Map<pathString, PathUsage>
|
|
50
54
|
const keyAnalysis = new Map(); // Map<basePath, {hasNumeric: boolean, hasString: boolean}>
|
|
55
|
+
const numericIndicesByPath = new Map(); // Map<pathString, Set<number>> — indices under each parent path
|
|
51
56
|
const allEntries = [];
|
|
52
57
|
|
|
53
58
|
/**
|
|
@@ -100,6 +105,13 @@ export const packFormToJSON = (form) => {
|
|
|
100
105
|
if (part.type === 'key') {
|
|
101
106
|
currentPath = currentPath ? `${currentPath}[${part.name}]` : part.name;
|
|
102
107
|
} else if (part.type === 'numeric') {
|
|
108
|
+
const parentPath = currentPath;
|
|
109
|
+
let set = numericIndicesByPath.get(parentPath);
|
|
110
|
+
if (!set) {
|
|
111
|
+
set = new Set();
|
|
112
|
+
numericIndicesByPath.set(parentPath, set);
|
|
113
|
+
}
|
|
114
|
+
set.add(part.index);
|
|
103
115
|
currentPath = `${currentPath}[${part.index}]`;
|
|
104
116
|
} else if (part.type === 'array') {
|
|
105
117
|
currentPath = `${currentPath}[]`;
|
|
@@ -190,6 +202,34 @@ export const packFormToJSON = (form) => {
|
|
|
190
202
|
return analysis?.hasNumeric && analysis?.hasString;
|
|
191
203
|
};
|
|
192
204
|
|
|
205
|
+
/**
|
|
206
|
+
* True if indices are exactly 0, 1, 2, ..., n (dense, sequential from 0).
|
|
207
|
+
* @param {number[]} indices - Sorted array of indices
|
|
208
|
+
* @returns {boolean}
|
|
209
|
+
*/
|
|
210
|
+
const isDenseSequential = (indices) => {
|
|
211
|
+
if (indices.length === 0) return false;
|
|
212
|
+
const sorted = [...indices].sort((a, b) => a - b);
|
|
213
|
+
for (let j = 0; j < sorted.length; j++) {
|
|
214
|
+
if (sorted[j] !== j) return false;
|
|
215
|
+
}
|
|
216
|
+
return true;
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* True if numeric keys under this path should be treated as array indices.
|
|
221
|
+
* With numericKeysAlwaysArray: always true when path has numeric children.
|
|
222
|
+
* Otherwise: true only when indices are dense 0..n.
|
|
223
|
+
* @param {string} parentPath - Path to the container that has numeric children
|
|
224
|
+
* @returns {boolean}
|
|
225
|
+
*/
|
|
226
|
+
const useArrayForNumericPath = (parentPath) => {
|
|
227
|
+
const set = numericIndicesByPath.get(parentPath);
|
|
228
|
+
if (!set || set.size === 0) return true;
|
|
229
|
+
if (numericKeysAlwaysArray) return true;
|
|
230
|
+
return isDenseSequential([...set]);
|
|
231
|
+
};
|
|
232
|
+
|
|
193
233
|
/**
|
|
194
234
|
* Convertit un tableau en objet en préservant les indices numériques comme propriétés
|
|
195
235
|
* @param {Array} arr - Le tableau à convertir
|
|
@@ -285,8 +325,9 @@ export const packFormToJSON = (form) => {
|
|
|
285
325
|
* @param {*} value - La valeur à assigner
|
|
286
326
|
* @param {Object} parent - Le conteneur parent
|
|
287
327
|
* @param {string} basePath - Le chemin de base pour la détection de conflit
|
|
328
|
+
* @param {string} parentPath - Chemin du conteneur actuel (pour heuristique numeric)
|
|
288
329
|
*/
|
|
289
|
-
const processFinalValue = (container, part, value, parent, basePath) => {
|
|
330
|
+
const processFinalValue = (container, part, value, parent, basePath, parentPath) => {
|
|
290
331
|
if (part.type === 'array') {
|
|
291
332
|
// Tableau explicite avec []
|
|
292
333
|
container[part.name] ??= [];
|
|
@@ -296,13 +337,18 @@ export const packFormToJSON = (form) => {
|
|
|
296
337
|
container[part.name] = [container[part.name], value];
|
|
297
338
|
}
|
|
298
339
|
} else if (part.type === 'numeric') {
|
|
299
|
-
// Indice numérique final
|
|
300
340
|
const { index } = part;
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
container.
|
|
341
|
+
const useArray = useArrayForNumericPath(parentPath);
|
|
342
|
+
if (useArray) {
|
|
343
|
+
container = ensureArray(container, parent.key, parent.container);
|
|
344
|
+
while (container.length <= index) {
|
|
345
|
+
container.push(undefined);
|
|
346
|
+
}
|
|
347
|
+
container[index] = value;
|
|
348
|
+
} else {
|
|
349
|
+
container = ensureObject(container, parent.key, parent.container, true);
|
|
350
|
+
container[index] = value;
|
|
304
351
|
}
|
|
305
|
-
container[index] = value;
|
|
306
352
|
} else {
|
|
307
353
|
// Clé simple finale
|
|
308
354
|
const conflict = hasConflict(basePath);
|
|
@@ -348,16 +394,14 @@ export const packFormToJSON = (form) => {
|
|
|
348
394
|
*/
|
|
349
395
|
const processIntermediatePart = (container, part, nextPart, parent, basePath, parts, i, key) => {
|
|
350
396
|
if (part.type === 'numeric') {
|
|
351
|
-
// Indice numérique : le container doit être un tableau ou un objet (selon conflit)
|
|
352
397
|
const { index } = part;
|
|
353
|
-
const
|
|
398
|
+
const parentPath = buildPathString(parts.slice(0, i));
|
|
399
|
+
const useArray = useArrayForNumericPath(parentPath) && !hasConflict(basePath);
|
|
354
400
|
|
|
355
|
-
if (
|
|
356
|
-
// Conflit : utiliser un objet (les indices seront des propriétés)
|
|
401
|
+
if (!useArray) {
|
|
357
402
|
container = ensureObject(container, parent.key, parent.container, true);
|
|
358
403
|
container[index] ??= {};
|
|
359
404
|
if (typeof container[index] !== 'object' || container[index] === null) {
|
|
360
|
-
// Cette erreur ne devrait jamais se produire si la détection fonctionne correctement
|
|
361
405
|
const pathParts = parts.slice(0, i + 1);
|
|
362
406
|
const currentPath = buildPathString(pathParts);
|
|
363
407
|
throw new Error(
|
|
@@ -367,14 +411,12 @@ export const packFormToJSON = (form) => {
|
|
|
367
411
|
);
|
|
368
412
|
}
|
|
369
413
|
} else {
|
|
370
|
-
// Pas de conflit : utiliser un tableau
|
|
371
414
|
container = ensureArray(container, parent.key, parent.container);
|
|
372
415
|
while (container.length <= index) {
|
|
373
416
|
container.push(undefined);
|
|
374
417
|
}
|
|
375
418
|
container[index] ??= {};
|
|
376
419
|
if (typeof container[index] !== 'object' || container[index] === null) {
|
|
377
|
-
// Cette erreur ne devrait jamais se produire si la détection fonctionne correctement
|
|
378
420
|
const pathParts = parts.slice(0, i + 1);
|
|
379
421
|
const currentPath = buildPathString(pathParts);
|
|
380
422
|
throw new Error(
|
|
@@ -459,8 +501,8 @@ export const packFormToJSON = (form) => {
|
|
|
459
501
|
const parent = path[path.length - 1];
|
|
460
502
|
|
|
461
503
|
if (isLast) {
|
|
462
|
-
|
|
463
|
-
processFinalValue(container, part, value, parent, basePath);
|
|
504
|
+
const parentPath = buildPathString(parts.slice(0, i));
|
|
505
|
+
processFinalValue(container, part, value, parent, basePath, parentPath);
|
|
464
506
|
} else {
|
|
465
507
|
// Partie intermédiaire : créer la structure
|
|
466
508
|
const newContainer = processIntermediatePart(container, part, nextPart, parent, basePath, parts, i, key);
|