pict-section-form 1.2.2 → 1.2.4
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/eslint.config.mjs +2 -2
- package/package.json +9 -5
- package/source/providers/Pict-Provider-DynamicTabularData.js +204 -25
- package/source/providers/Pict-Provider-Informary.js +22 -21
- package/test/Pict-Provider-Informary-RenderSelector_tests.js +145 -0
- package/test/Pict-Provider-Informary_tests.js +111 -0
- package/test/PictSectionForm-Basic_tests.js +3 -0
- package/test/PictSectionForm-Tabular-Features_tests.js +90 -0
- package/tsconfig.build.json +16 -0
- package/tsconfig.json +1 -1
- package/types/source/providers/Pict-Provider-DynamicTabularData.d.ts +33 -0
- package/types/source/providers/Pict-Provider-DynamicTabularData.d.ts.map +1 -1
- package/types/source/providers/Pict-Provider-Informary.d.ts +12 -13
- package/types/source/providers/Pict-Provider-Informary.d.ts.map +1 -1
- package/types/source/providers/layouts/Pict-Layout-Custom.d.ts.map +1 -1
- package/types/source/providers/layouts/Pict-Layout-Record.d.ts.map +1 -1
- package/types/source/providers/layouts/Pict-Layout-Tabular.d.ts +202 -0
- package/types/source/providers/layouts/Pict-Layout-Tabular.d.ts.map +1 -1
- package/types/source/providers/layouts/Pict-Layout-VerticalRecord.d.ts.map +1 -1
- package/types/source/services/ManifestFactory.d.ts +2 -1
- package/types/source/services/ManifestFactory.d.ts.map +1 -1
package/eslint.config.mjs
CHANGED
|
@@ -3,8 +3,8 @@ import pluginJs from "@eslint/js";
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
export default [
|
|
6
|
-
{ files: ["source/**"], languageOptions: { sourceType: "commonjs" } },
|
|
7
|
-
{ languageOptions: { globals: { ...globals.browser, ...globals.mocha, } } },
|
|
6
|
+
{ files: ["source/**", "test/**"], languageOptions: { sourceType: "commonjs" } },
|
|
7
|
+
{ languageOptions: { globals: { ...globals.browser, ...globals.mocha, ...globals.node, } } },
|
|
8
8
|
pluginJs.configs.recommended,
|
|
9
9
|
{ rules: { "no-prototype-builtins": "off", "no-unused-vars": "warn", "no-case-declarations": "warn" } },
|
|
10
10
|
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pict-section-form",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.4",
|
|
4
4
|
"description": "Pict dynamic form sections",
|
|
5
5
|
"main": "source/Pict-Section-Form.js",
|
|
6
6
|
"directories": {
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
"build": "npx quack build",
|
|
22
22
|
"test": "npx quack test",
|
|
23
23
|
"lint": "eslint source/**",
|
|
24
|
-
"types": "tsc -p .",
|
|
24
|
+
"types": "tsc -p ./tsconfig.build.json",
|
|
25
|
+
"check": "tsc -p ./tsconfig.json --noEmit",
|
|
25
26
|
"docker-dev-build": "docker build ./ -f Dockerfile_LUXURYCode -t pict-section-form-image:local",
|
|
26
27
|
"docker-dev-run": "docker run -it -d --name pict-section-form-dev -p 48888:8080 -p 49999:9999 -v \"$PWD/.config:/home/coder/.config\" -v \"$PWD:/home/coder/pict-section-form\" -u \"$(id -u):$(id -g)\" -e \"DOCKER_USER=$USER\" pict-section-form-image:local",
|
|
27
28
|
"docker-dev-shell": "docker exec -it pict-section-form-dev /bin/bash"
|
|
@@ -31,13 +32,16 @@
|
|
|
31
32
|
"license": "MIT",
|
|
32
33
|
"devDependencies": {
|
|
33
34
|
"@eslint/js": "^9.39.2",
|
|
35
|
+
"@types/chai": "^4.3.20",
|
|
36
|
+
"@types/mocha": "^10.0.10",
|
|
37
|
+
"@types/node": "^22.19.21",
|
|
34
38
|
"browser-env": "^3.3.0",
|
|
35
39
|
"eslint": "^9.39.2",
|
|
36
40
|
"jquery": "^4.0.0",
|
|
37
|
-
"pict": "^1.0.
|
|
41
|
+
"pict": "^1.0.386",
|
|
38
42
|
"pict-application": "^1.0.34",
|
|
39
43
|
"pict-docuserve": "^1.4.19",
|
|
40
|
-
"pict-service-commandlineutility": "^1.0.
|
|
44
|
+
"pict-service-commandlineutility": "^1.0.20",
|
|
41
45
|
"quackage": "^1.3.0",
|
|
42
46
|
"tui-grid": "^4.21.22",
|
|
43
47
|
"typescript": "^5.9.3"
|
|
@@ -48,7 +52,7 @@
|
|
|
48
52
|
"marked": "^4.3.0",
|
|
49
53
|
"pict-provider": "^1.0.13",
|
|
50
54
|
"pict-section-excalidraw": "^1.0.3",
|
|
51
|
-
"pict-section-markdowneditor": "^1.0.
|
|
55
|
+
"pict-section-markdowneditor": "^1.0.19",
|
|
52
56
|
"pict-section-tuigrid": "^1.0.31",
|
|
53
57
|
"pict-template": "^1.0.15",
|
|
54
58
|
"pict-view": "^1.0.68"
|
|
@@ -318,13 +318,21 @@ class DynamicTabularData extends libPictProvider
|
|
|
318
318
|
this.log.error(`Dynamic View [${pView.UUID}]::[${pView.Hash}] Group ${tmpGroup.Hash} attempting to move row [${pRowIndex}] to [${pNewRowIndex}] but the index is out of bounds.`);
|
|
319
319
|
return false;
|
|
320
320
|
}
|
|
321
|
+
let tmpOriginalLength = tmpDestinationObject.length;
|
|
321
322
|
let tmpElementToBeMoved = tmpDestinationObject.splice(tmpRowIndex, 1);
|
|
322
323
|
tmpDestinationObject.splice(tmpNewRowIndex, 0, tmpElementToBeMoved[0]);
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
//
|
|
326
|
-
//
|
|
327
|
-
|
|
324
|
+
|
|
325
|
+
// Position-keyed DynamicColumns (KeyBy:"Position") store dependent cell data by the
|
|
326
|
+
// source row's INDEX, so a source reorder must apply the same permutation to every
|
|
327
|
+
// dependent row's positional cells -- otherwise user-entered values stay put while
|
|
328
|
+
// their column re-labels to a different source row. Must run BEFORE the dependent
|
|
329
|
+
// views re-resolve their columns below. No-op for value-keyed generators.
|
|
330
|
+
this._moveDependentPositionalColumns(tmpGroup.RecordSetAddress, tmpRowIndex, tmpNewRowIndex, tmpOriginalLength);
|
|
331
|
+
|
|
332
|
+
// Render (source + dependent views) BEFORE solving so dependent DynamicColumns tables
|
|
333
|
+
// don't blank until the next edit, and the solve's DOM side effects survive. Matches
|
|
334
|
+
// the add/delete handlers' order.
|
|
335
|
+
this._repaintAfterRowReorder(tmpGroup);
|
|
328
336
|
}
|
|
329
337
|
}
|
|
330
338
|
}
|
|
@@ -353,17 +361,23 @@ class DynamicTabularData extends libPictProvider
|
|
|
353
361
|
this.log.error(`Dynamic View [${pView.UUID}]::[${pView.Hash}] Group ${tmpGroup.Hash} attempting to move row [${pRowIndex}] down but it's already at the bottom.`);
|
|
354
362
|
return false;
|
|
355
363
|
}
|
|
364
|
+
let tmpOriginalLength = tmpDestinationObject.length;
|
|
356
365
|
let tmpElementToBeMoved = tmpDestinationObject.splice(tmpRowIndex, 1);
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
//
|
|
361
|
-
//
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
//
|
|
365
|
-
|
|
366
|
-
|
|
366
|
+
let tmpNewRowIndex = tmpRowIndex + 1;
|
|
367
|
+
tmpDestinationObject.splice(tmpNewRowIndex, 0, tmpElementToBeMoved[0]);
|
|
368
|
+
|
|
369
|
+
// Position-keyed DynamicColumns (KeyBy:"Position") store dependent cell data by the
|
|
370
|
+
// source row's INDEX, so a source reorder must apply the same permutation to every
|
|
371
|
+
// dependent row's positional cells -- otherwise user-entered values stay put while
|
|
372
|
+
// their column re-labels to a different source row. Must run BEFORE the dependent
|
|
373
|
+
// views re-resolve their columns below. No-op for value-keyed generators.
|
|
374
|
+
this._moveDependentPositionalColumns(tmpGroup.RecordSetAddress, tmpRowIndex, tmpNewRowIndex, tmpOriginalLength);
|
|
375
|
+
|
|
376
|
+
// Render (source + dependent views) BEFORE solving so the solve's DOM side effects
|
|
377
|
+
// (e.g. SetGroupVisibility hiding a validation message) act on the freshly rebuilt
|
|
378
|
+
// DOM and survive, and so dependent DynamicColumns tables don't blank until the next
|
|
379
|
+
// edit. Matches the add/delete handlers' order.
|
|
380
|
+
this._repaintAfterRowReorder(tmpGroup);
|
|
367
381
|
}
|
|
368
382
|
}
|
|
369
383
|
}
|
|
@@ -397,17 +411,23 @@ class DynamicTabularData extends libPictProvider
|
|
|
397
411
|
this.log.error(`Dynamic View [${pView.UUID}]::[${pView.Hash}] Group ${tmpGroup.Hash} attempting to move row [${pRowIndex}] but the index is out of bounds.`);
|
|
398
412
|
return false;
|
|
399
413
|
}
|
|
414
|
+
let tmpOriginalLength = tmpDestinationObject.length;
|
|
400
415
|
let tmpElementToBeMoved = tmpDestinationObject.splice(tmpRowIndex, 1);
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
//
|
|
405
|
-
//
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
//
|
|
409
|
-
|
|
410
|
-
|
|
416
|
+
let tmpNewRowIndex = tmpRowIndex - 1;
|
|
417
|
+
tmpDestinationObject.splice(tmpNewRowIndex, 0, tmpElementToBeMoved[0]);
|
|
418
|
+
|
|
419
|
+
// Position-keyed DynamicColumns (KeyBy:"Position") store dependent cell data by the
|
|
420
|
+
// source row's INDEX, so a source reorder must apply the same permutation to every
|
|
421
|
+
// dependent row's positional cells -- otherwise user-entered values stay put while
|
|
422
|
+
// their column re-labels to a different source row. Must run BEFORE the dependent
|
|
423
|
+
// views re-resolve their columns below. No-op for value-keyed generators.
|
|
424
|
+
this._moveDependentPositionalColumns(tmpGroup.RecordSetAddress, tmpRowIndex, tmpNewRowIndex, tmpOriginalLength);
|
|
425
|
+
|
|
426
|
+
// Render (source + dependent views) BEFORE solving so the solve's DOM side effects
|
|
427
|
+
// (e.g. SetGroupVisibility hiding a validation message) act on the freshly rebuilt
|
|
428
|
+
// DOM and survive, and so dependent DynamicColumns tables don't blank until the next
|
|
429
|
+
// edit. Matches the add/delete handlers' order.
|
|
430
|
+
this._repaintAfterRowReorder(tmpGroup);
|
|
411
431
|
}
|
|
412
432
|
}
|
|
413
433
|
}
|
|
@@ -656,6 +676,165 @@ class DynamicTabularData extends libPictProvider
|
|
|
656
676
|
}
|
|
657
677
|
}
|
|
658
678
|
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* For position-keyed DynamicColumns (KeyBy: "Position") sourced from
|
|
682
|
+
* pSourceRecordSetAddress, REORDER each dependent row's positional cells to
|
|
683
|
+
* mirror a source row that moved from pOldIndex to pNewIndex. The source array
|
|
684
|
+
* was already spliced (remove at pOldIndex, insert at pNewIndex); this applies
|
|
685
|
+
* the identical permutation to every dependent row's positional cell VALUES so
|
|
686
|
+
* the data stays attached to its column when the columns re-resolve to the new
|
|
687
|
+
* source order. Without it, a reorder leaves user-entered cells under the wrong
|
|
688
|
+
* (renamed) column. Solver-filled rows re-derive on the next solve regardless;
|
|
689
|
+
* applying the move to them too is harmless (the solve overwrites them). No-op
|
|
690
|
+
* for value-keyed generators (their data stays attached to the stable value).
|
|
691
|
+
*
|
|
692
|
+
* Must run AFTER the source splice and BEFORE the dependent views re-resolve +
|
|
693
|
+
* the marshal repaints them. Symmetric with _spliceDependentPositionalColumns.
|
|
694
|
+
*
|
|
695
|
+
* @param {string} pSourceRecordSetAddress - RecordSetAddress of the moved-within source.
|
|
696
|
+
* @param {number} pOldIndex - Source row's index before the move.
|
|
697
|
+
* @param {number} pNewIndex - Source row's index after the move.
|
|
698
|
+
* @param {number} pLength - Source row count (unchanged by a move).
|
|
699
|
+
*/
|
|
700
|
+
_moveDependentPositionalColumns(pSourceRecordSetAddress, pOldIndex, pNewIndex, pLength)
|
|
701
|
+
{
|
|
702
|
+
if ((typeof pSourceRecordSetAddress !== 'string') || (pSourceRecordSetAddress.length < 1))
|
|
703
|
+
{
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
if (pOldIndex === pNewIndex)
|
|
707
|
+
{
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
if (!this.pict.views.PictFormMetacontroller)
|
|
711
|
+
{
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
let tmpManifestFactory = this.fable.ManifestFactory;
|
|
715
|
+
if (!tmpManifestFactory || (typeof tmpManifestFactory._parseDynamicColumnTemplate !== 'function'))
|
|
716
|
+
{
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
let tmpViews = this.pict.views.PictFormMetacontroller.filterViews((pViewToTest) => { return pViewToTest.isPictSectionForm; });
|
|
720
|
+
for (let v = 0; v < tmpViews.length; v++)
|
|
721
|
+
{
|
|
722
|
+
let tmpView = tmpViews[v];
|
|
723
|
+
let tmpGroups = tmpView.getGroups();
|
|
724
|
+
for (let g = 0; g < tmpGroups.length; g++)
|
|
725
|
+
{
|
|
726
|
+
let tmpGroup = tmpGroups[g];
|
|
727
|
+
if (!Array.isArray(tmpGroup.DynamicColumns) || (tmpGroup.DynamicColumns.length < 1))
|
|
728
|
+
{
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
731
|
+
let tmpDependentRows = tmpView.sectionManifest.getValueByHash(tmpView.getMarshalDestinationObject(), tmpGroup.RecordSetAddress);
|
|
732
|
+
if (!Array.isArray(tmpDependentRows) || (tmpDependentRows.length < 1))
|
|
733
|
+
{
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
for (let c = 0; c < tmpGroup.DynamicColumns.length; c++)
|
|
737
|
+
{
|
|
738
|
+
let tmpGenerator = tmpGroup.DynamicColumns[c];
|
|
739
|
+
if (!tmpGenerator || (tmpGenerator.SourceAddress !== pSourceRecordSetAddress))
|
|
740
|
+
{
|
|
741
|
+
continue;
|
|
742
|
+
}
|
|
743
|
+
// Only position-keyed generators store data by index; value-keyed columns keep
|
|
744
|
+
// their data attached to the (stable) value, so they need no reordering.
|
|
745
|
+
if (tmpGenerator.KeyBy !== 'Position')
|
|
746
|
+
{
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
if (typeof tmpGenerator.InformaryDataAddressTemplate !== 'string')
|
|
750
|
+
{
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
// Resolve the positional cell address for each column index once.
|
|
754
|
+
let tmpAddresses = [];
|
|
755
|
+
for (let k = 0; k < pLength; k++)
|
|
756
|
+
{
|
|
757
|
+
tmpAddresses[k] = tmpManifestFactory._parseDynamicColumnTemplate(tmpGenerator.InformaryDataAddressTemplate, { __Index: k, __RowNumber: k + 1 });
|
|
758
|
+
}
|
|
759
|
+
for (let r = 0; r < tmpDependentRows.length; r++)
|
|
760
|
+
{
|
|
761
|
+
let tmpRow = tmpDependentRows[r];
|
|
762
|
+
if (!tmpRow || (typeof tmpRow !== 'object'))
|
|
763
|
+
{
|
|
764
|
+
continue;
|
|
765
|
+
}
|
|
766
|
+
// Read the current positional cell values, apply the identical
|
|
767
|
+
// remove-at-old / insert-at-new permutation, then write them back.
|
|
768
|
+
let tmpValues = [];
|
|
769
|
+
for (let k = 0; k < pLength; k++)
|
|
770
|
+
{
|
|
771
|
+
tmpValues[k] = tmpAddresses[k] ? tmpView.sectionManifest.getValueByHash(tmpRow, tmpAddresses[k]) : undefined;
|
|
772
|
+
}
|
|
773
|
+
let tmpMoved = tmpValues.splice(pOldIndex, 1);
|
|
774
|
+
tmpValues.splice(pNewIndex, 0, tmpMoved[0]);
|
|
775
|
+
for (let k = 0; k < pLength; k++)
|
|
776
|
+
{
|
|
777
|
+
if (tmpAddresses[k])
|
|
778
|
+
{
|
|
779
|
+
tmpView.sectionManifest.setValueByHash(tmpRow, tmpAddresses[k], tmpValues[k]);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* Shared tail for the row-reorder handlers (move up / move down / set index).
|
|
790
|
+
* Repaints the moved source table and every dependent view, in the RENDER phase
|
|
791
|
+
* BEFORE solving + marshaling, then solves and marshals. Mirrors what the add /
|
|
792
|
+
* delete handlers do so a reorder doesn't blank dependent DynamicColumns tables
|
|
793
|
+
* (or the rest of their section) until the next edit. Render happens before the
|
|
794
|
+
* solve so the solve's DOM side effects (e.g. SetGroupVisibility) land on the
|
|
795
|
+
* freshly rebuilt DOM and survive.
|
|
796
|
+
*
|
|
797
|
+
* @param {Object} pGroup - The reordered group (its RecordSetAddress drives dependents).
|
|
798
|
+
*/
|
|
799
|
+
_repaintAfterRowReorder(pGroup)
|
|
800
|
+
{
|
|
801
|
+
// Render every view that renders this record set as a table (the source table itself
|
|
802
|
+
// plus any sibling views bound to the same RecordSetAddress).
|
|
803
|
+
let tmpViewsToRender = this.pict.views.PictFormMetacontroller.filterViews(
|
|
804
|
+
(pViewToTestForGroup) =>
|
|
805
|
+
{
|
|
806
|
+
if (!pViewToTestForGroup.isPictSectionForm)
|
|
807
|
+
{
|
|
808
|
+
return false;
|
|
809
|
+
}
|
|
810
|
+
let tmpGroupsToTest = pViewToTestForGroup.getGroups();
|
|
811
|
+
for (let i = 0; i < tmpGroupsToTest.length; i++)
|
|
812
|
+
{
|
|
813
|
+
if (tmpGroupsToTest[i].RecordSetAddress == pGroup.RecordSetAddress)
|
|
814
|
+
{
|
|
815
|
+
return true;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
return false;
|
|
819
|
+
});
|
|
820
|
+
for (let i = 0; i < tmpViewsToRender.length; i++)
|
|
821
|
+
{
|
|
822
|
+
tmpViewsToRender[i].render();
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// Rebuild any OTHER views whose DynamicColumns are sourced from this record set
|
|
826
|
+
// HERE, in the render phase -- BEFORE the marshal below -- so the column DOM is
|
|
827
|
+
// correct (and re-labeled to the new order) when the marshal fills it. Without
|
|
828
|
+
// this the dependent table is only rebuilt mid-marshal (onDataMarshalToForm),
|
|
829
|
+
// which renders AFTER the cells were filled and blanks them until the next edit.
|
|
830
|
+
this._rebuildDependentDynamicColumnViews(pGroup.RecordSetAddress);
|
|
831
|
+
|
|
832
|
+
// Run the solver
|
|
833
|
+
this.pict.providers.DynamicSolver.solveViews();
|
|
834
|
+
|
|
835
|
+
// We've re-rendered but we don't know what needs to be marshaled based on the solve that ran above so marshal everything
|
|
836
|
+
this.pict.views.PictFormMetacontroller.marshalFormSections();
|
|
837
|
+
}
|
|
659
838
|
}
|
|
660
839
|
|
|
661
840
|
module.exports = DynamicTabularData;
|
|
@@ -96,7 +96,7 @@ class PictDynamicFormsInformary extends libPictProvider
|
|
|
96
96
|
*
|
|
97
97
|
* @param {object} pAppStateData - The application state data object to marshal the form data to.
|
|
98
98
|
* @param {string} pFormHash - The form hash representing the form elements.
|
|
99
|
-
* @param {
|
|
99
|
+
* @param {import('manyfest')} pManifest - The manifest object used to map form data to the application state data.
|
|
100
100
|
* @param {string} [pDatum] - The datum hash to pull in. If not provided, all data is marshalled.
|
|
101
101
|
* @param {number|string} [pRecordIndex] - The record index to pull in. If not provided, all data is marshalled.
|
|
102
102
|
*/
|
|
@@ -120,19 +120,19 @@ class PictDynamicFormsInformary extends libPictProvider
|
|
|
120
120
|
* Marshals a specific form element's data to the application state data.
|
|
121
121
|
*
|
|
122
122
|
* @param {string} pFormHash - The hash of the form.
|
|
123
|
-
* @param {HTMLElement}
|
|
124
|
-
* @param {
|
|
123
|
+
* @param {HTMLElement} pFormElement - The form element to marshal.
|
|
124
|
+
* @param {import('manyfest')} pManifest - The manifest object to set values.
|
|
125
125
|
* @param {Object} pAppStateData - The application state data object.
|
|
126
126
|
* @param {any} [pDatumFilter] - Optional filter for datum address.
|
|
127
127
|
* @param {any} [pRecordIndexFilter] - Optional filter for record index.
|
|
128
128
|
* @returns {boolean} - Returns false if the element falls outside the filters or if the browser value is null.
|
|
129
129
|
*/
|
|
130
|
-
marshalSpecicificFormElementToData(pFormHash,
|
|
130
|
+
marshalSpecicificFormElementToData(pFormHash, pFormElement, pManifest, pAppStateData, pDatumFilter, pRecordIndexFilter)
|
|
131
131
|
{
|
|
132
|
-
const tmpDatumAddress =
|
|
132
|
+
const tmpDatumAddress = pFormElement.getAttribute('data-i-datum');
|
|
133
133
|
|
|
134
|
-
const tmpContainerAddress =
|
|
135
|
-
const tmpIndex =
|
|
134
|
+
const tmpContainerAddress = pFormElement.getAttribute('data-i-container');
|
|
135
|
+
const tmpIndex = pFormElement.getAttribute('data-i-index');
|
|
136
136
|
|
|
137
137
|
// Process the filters
|
|
138
138
|
// TODO: Now that this is a function, having these filters here is not good. We need to move this to the caller. But the above getAttribute is required... rethink filtering?
|
|
@@ -160,13 +160,14 @@ class PictDynamicFormsInformary extends libPictProvider
|
|
|
160
160
|
|
|
161
161
|
if (!tmpContainerAddress)
|
|
162
162
|
{
|
|
163
|
-
|
|
163
|
+
pManifest.setValueAtAddress(pAppStateData, tmpDatumAddress, tmpBrowserValue);
|
|
164
164
|
}
|
|
165
165
|
else
|
|
166
166
|
{
|
|
167
167
|
// Compose the address .. right now only arrays
|
|
168
|
-
|
|
169
|
-
}
|
|
168
|
+
pManifest.setValueAtAddress(pAppStateData, this.getComposedContainerAddress(tmpContainerAddress, tmpIndex, tmpDatumAddress), tmpBrowserValue);
|
|
169
|
+
}
|
|
170
|
+
return true;
|
|
170
171
|
}
|
|
171
172
|
|
|
172
173
|
/**
|
|
@@ -174,7 +175,7 @@ class PictDynamicFormsInformary extends libPictProvider
|
|
|
174
175
|
*
|
|
175
176
|
* @param {object} pAppStateData - The application state data to marshal into the form. Usually AppData but can be other objects.
|
|
176
177
|
* @param {string} pFormHash - The hash of the form to marshal data into. This is the data-i-form attribute.
|
|
177
|
-
* @param {
|
|
178
|
+
* @param {import('manyfest')} pManifest - The manifest object. If not provided, the generic manifest is used.
|
|
178
179
|
*/
|
|
179
180
|
marshalDataToForm(pAppStateData, pFormHash, pManifest)
|
|
180
181
|
{
|
|
@@ -185,20 +186,19 @@ class PictDynamicFormsInformary extends libPictProvider
|
|
|
185
186
|
// Enumerate the form elements, and put data in them for each address
|
|
186
187
|
for (let i = 0; i < tmpFormElements.length; i++)
|
|
187
188
|
{
|
|
188
|
-
this.marshalSpecificElementDataToForm(
|
|
189
|
+
this.marshalSpecificElementDataToForm(tmpFormElements[i], tmpManifest, pAppStateData);
|
|
189
190
|
}
|
|
190
191
|
}
|
|
191
192
|
|
|
192
193
|
/**
|
|
193
194
|
* Marshals specific element data to a form.
|
|
194
195
|
*
|
|
195
|
-
* @param {string} pFormHash - The hash of the form.
|
|
196
196
|
* @param {HTMLElement} pFormElement - The form element to marshal data to.
|
|
197
|
-
* @param {
|
|
198
|
-
* @param {
|
|
197
|
+
* @param {import('manyfest')} pManifest - The manifest object containing data retrieval methods.
|
|
198
|
+
* @param {Record<string, any>} pAppStateData - The application state data.
|
|
199
199
|
* @returns {boolean} Returns false if the form element does not have a datum address.
|
|
200
200
|
*/
|
|
201
|
-
marshalSpecificElementDataToForm(
|
|
201
|
+
marshalSpecificElementDataToForm(pFormElement, pManifest, pAppStateData)
|
|
202
202
|
{
|
|
203
203
|
let tmpDatumAddress = pFormElement.getAttribute('data-i-datum');
|
|
204
204
|
|
|
@@ -213,7 +213,7 @@ class PictDynamicFormsInformary extends libPictProvider
|
|
|
213
213
|
|
|
214
214
|
if (!tmpContainerAddress)
|
|
215
215
|
{
|
|
216
|
-
let tmpAppStateValue =
|
|
216
|
+
let tmpAppStateValue = pManifest.getValueAtAddress(pAppStateData, tmpDatumAddress);
|
|
217
217
|
|
|
218
218
|
if (this.pict.LogNoisiness > 3)
|
|
219
219
|
{
|
|
@@ -222,12 +222,12 @@ class PictDynamicFormsInformary extends libPictProvider
|
|
|
222
222
|
|
|
223
223
|
if (tmpAppStateValue != null)
|
|
224
224
|
{
|
|
225
|
-
this.pict.ContentAssignment.assignContent(
|
|
225
|
+
this.pict.ContentAssignment.assignContent(pFormElement, tmpAppStateValue);
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
228
|
else
|
|
229
229
|
{
|
|
230
|
-
let tmpAppStateValue =
|
|
230
|
+
let tmpAppStateValue = pManifest.getValueAtAddress(pAppStateData, this.getComposedContainerAddress(tmpContainerAddress, tmpIndex, tmpDatumAddress));
|
|
231
231
|
|
|
232
232
|
if (this.pict.LogNoisiness > 3)
|
|
233
233
|
{
|
|
@@ -236,14 +236,15 @@ class PictDynamicFormsInformary extends libPictProvider
|
|
|
236
236
|
|
|
237
237
|
if (tmpAppStateValue != null)
|
|
238
238
|
{
|
|
239
|
-
this.pict.ContentAssignment.assignContent(
|
|
239
|
+
this.pict.ContentAssignment.assignContent(pFormElement, tmpAppStateValue);
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
|
+
return true;
|
|
242
243
|
}
|
|
243
244
|
|
|
244
245
|
/**
|
|
245
246
|
* Manually marshals data to a form by assigning content based on context in the descriptor.
|
|
246
|
-
* @param {
|
|
247
|
+
* @param {Record<string, any>} pInput - The input manifest descriptor to marshal data to form from.
|
|
247
248
|
* @returns boolean if assignment was successful
|
|
248
249
|
*/
|
|
249
250
|
manualMarshalDataToFormByInput(pInput)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Regression guard for the Informary "assign straight to the element" marshal optimization
|
|
3
|
+
(marshalSpecificElementDataToForm assigns to pFormElement directly instead of re-resolving a
|
|
4
|
+
selector built from that element's own data-i-* attributes — a per-cell full-document scan).
|
|
5
|
+
|
|
6
|
+
That optimization is only correct because the selector getContentBrowserAddress() builds is
|
|
7
|
+
UNIQUE — it resolves to exactly the one element being marshalled. This test renders a real form
|
|
8
|
+
(non-tabular fields + a multi-row tabular group) into a jsdom DOM and asserts, for EVERY
|
|
9
|
+
datum-bound cell, that the actual getContentBrowserAddress() output resolves to exactly that
|
|
10
|
+
element. If a future template change makes the (form, datum, container, index) tuple non-unique,
|
|
11
|
+
the marshal would write to the wrong/extra element — and this test fails.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const libBrowserEnv = require('browser-env');
|
|
15
|
+
libBrowserEnv();
|
|
16
|
+
|
|
17
|
+
const Chai = require('chai');
|
|
18
|
+
const Expect = Chai.expect;
|
|
19
|
+
|
|
20
|
+
const libPict = require('pict');
|
|
21
|
+
const libPictSectionForm = require('../source/Pict-Section-Form.js');
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {(pict: libPict) => void} fOnReady
|
|
25
|
+
*/
|
|
26
|
+
function buildRenderedForm(fOnReady)
|
|
27
|
+
{
|
|
28
|
+
const tmpManifest =
|
|
29
|
+
{
|
|
30
|
+
Scope: 'InformarySelectorForm',
|
|
31
|
+
Descriptors:
|
|
32
|
+
{
|
|
33
|
+
Title: { Name: 'Title', Hash: 'Title', DataType: 'String', PictForm: { Section: 'Sheet', Group: 'Meta', InputType: 'Text' } },
|
|
34
|
+
Teacher: { Name: 'Teacher', Hash: 'Teacher', DataType: 'String', PictForm: { Section: 'Sheet', Group: 'Meta', InputType: 'Text' } }
|
|
35
|
+
},
|
|
36
|
+
Sections:
|
|
37
|
+
[
|
|
38
|
+
{
|
|
39
|
+
Hash: 'Sheet', Name: 'Sheet', Groups:
|
|
40
|
+
[
|
|
41
|
+
{ Hash: 'Meta', Name: 'Meta' },
|
|
42
|
+
{ Hash: 'Grades', Name: 'Grades', Layout: 'Tabular', RecordSetAddress: 'Grades', RecordManifest: 'GradeRow' }
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
ReferenceManifests:
|
|
47
|
+
{
|
|
48
|
+
GradeRow:
|
|
49
|
+
{
|
|
50
|
+
Scope: 'GradeRow',
|
|
51
|
+
Descriptors:
|
|
52
|
+
{
|
|
53
|
+
StudentName: { Name: 'Student', Hash: 'StudentName', DataType: 'String', PictForm: { Section: 'Sheet', Group: 'Grades', InputType: 'Text' } },
|
|
54
|
+
Score: { Name: 'Score', Hash: 'Score', DataType: 'Number', PictForm: { Section: 'Sheet', Group: 'Grades', InputType: 'Text' } }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
const tmpAppData =
|
|
60
|
+
{
|
|
61
|
+
Title: 'Term 1', Teacher: 'Ms. Frizzle',
|
|
62
|
+
Grades:
|
|
63
|
+
[
|
|
64
|
+
{ StudentName: 'Alice', Score: 95 },
|
|
65
|
+
{ StudentName: 'Bob', Score: 80 },
|
|
66
|
+
{ StudentName: 'Carol', Score: 60 },
|
|
67
|
+
{ StudentName: 'Dan', Score: 72 }
|
|
68
|
+
]
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
class TestApp extends libPictSectionForm.PictFormApplication
|
|
72
|
+
{
|
|
73
|
+
onAfterInitialize() { this.solve(); return super.onAfterInitialize(); }
|
|
74
|
+
}
|
|
75
|
+
TestApp.default_configuration = JSON.parse(JSON.stringify(libPictSectionForm.PictFormApplication.default_configuration));
|
|
76
|
+
TestApp.default_configuration.pict_configuration =
|
|
77
|
+
{
|
|
78
|
+
Product: 'InformarySelectorTest',
|
|
79
|
+
DefaultAppData: tmpAppData,
|
|
80
|
+
DefaultFormManifest: tmpManifest
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
document.body.innerHTML = '<div id="Pict-Form-Container"></div>';
|
|
84
|
+
const _Pict = new libPict(TestApp.default_configuration.pict_configuration);
|
|
85
|
+
_Pict.LogNoisiness = 0;
|
|
86
|
+
_Pict.addApplication('InformarySelectorTest', TestApp.default_configuration, TestApp);
|
|
87
|
+
_Pict.PictApplication.initializeAsync(() =>
|
|
88
|
+
{
|
|
89
|
+
// browser-env doesn't drive the render cycle on init; render the form views explicitly so
|
|
90
|
+
// the data-i-* cells land in the DOM.
|
|
91
|
+
Object.keys(_Pict.views).forEach((pHash) =>
|
|
92
|
+
{
|
|
93
|
+
const tmpView = _Pict.views[pHash];
|
|
94
|
+
if (tmpView && tmpView.isPictSectionForm)
|
|
95
|
+
{
|
|
96
|
+
try { tmpView.render(); }
|
|
97
|
+
catch (pError) { /* a view that can't render in jsdom is fine; we assert on what did */ }
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
fOnReady(_Pict);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
suite('Pict Provider Informary — render-time selector uniqueness', () =>
|
|
105
|
+
{
|
|
106
|
+
test('getContentBrowserAddress resolves to EXACTLY its own element for every datum cell', (fDone) =>
|
|
107
|
+
{
|
|
108
|
+
buildRenderedForm((_Pict) =>
|
|
109
|
+
{
|
|
110
|
+
try
|
|
111
|
+
{
|
|
112
|
+
const tmpInformary = _Pict.providers.Informary;
|
|
113
|
+
const tmpElements = document.querySelectorAll('[data-i-form][data-i-datum]');
|
|
114
|
+
// Guard against a vacuous pass (render produced no cells).
|
|
115
|
+
Expect(tmpElements.length).to.be.greaterThan(6, 'the form rendered its datum cells to the DOM');
|
|
116
|
+
|
|
117
|
+
let tmpTabularChecked = 0;
|
|
118
|
+
let tmpNonTabularChecked = 0;
|
|
119
|
+
tmpElements.forEach((pElement) =>
|
|
120
|
+
{
|
|
121
|
+
const tmpForm = pElement.getAttribute('data-i-form');
|
|
122
|
+
const tmpDatum = pElement.getAttribute('data-i-datum');
|
|
123
|
+
const tmpContainer = pElement.getAttribute('data-i-container');
|
|
124
|
+
const tmpIndexRaw = pElement.getAttribute('data-i-index');
|
|
125
|
+
// Mirror the marshal: tmpIndex = Number(getAttribute('data-i-index')).
|
|
126
|
+
const tmpIndex = (tmpIndexRaw == null) ? tmpIndexRaw : Number(tmpIndexRaw);
|
|
127
|
+
if (tmpContainer) { tmpTabularChecked++; } else { tmpNonTabularChecked++; }
|
|
128
|
+
|
|
129
|
+
// The ACTUAL selector the old marshal re-resolved, from the element's own attributes.
|
|
130
|
+
const tmpSelector = tmpInformary.getContentBrowserAddress(tmpForm, tmpDatum, tmpContainer, tmpIndex);
|
|
131
|
+
const tmpMatches = document.querySelectorAll(tmpSelector);
|
|
132
|
+
Expect(tmpMatches.length).to.equal(1, `selector [${tmpSelector}] must match exactly one element`);
|
|
133
|
+
Expect(tmpMatches[0]).to.equal(pElement, `selector [${tmpSelector}] must resolve to its own element`);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Make sure we exercised BOTH selector shapes — the 4-part container/index (tabular)
|
|
137
|
+
// AND the 2-part (non-tabular) path through getContentBrowserAddress.
|
|
138
|
+
Expect(tmpTabularChecked).to.be.greaterThan(0, 'exercised tabular container/index cells');
|
|
139
|
+
Expect(tmpNonTabularChecked).to.be.greaterThan(0, 'exercised non-tabular cells');
|
|
140
|
+
fDone();
|
|
141
|
+
}
|
|
142
|
+
catch (pError) { fDone(pError); }
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
});
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Unit tests for the Informary provider's element marshal.
|
|
3
|
+
|
|
4
|
+
Focus: marshalSpecificElementDataToForm must assign content straight to the form
|
|
5
|
+
element it is handed, NOT re-resolve a selector built from that element's own
|
|
6
|
+
attributes (a full-document scan per cell -> O(n^2) across a large tabular marshal).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const libBrowserEnv = require('browser-env');
|
|
10
|
+
libBrowserEnv();
|
|
11
|
+
|
|
12
|
+
const Chai = require('chai');
|
|
13
|
+
const Expect = Chai.expect;
|
|
14
|
+
|
|
15
|
+
const libPict = require('pict');
|
|
16
|
+
const libInformary = require('../source/providers/Pict-Provider-Informary.js');
|
|
17
|
+
|
|
18
|
+
suite(
|
|
19
|
+
'Pict Provider Informary',
|
|
20
|
+
() =>
|
|
21
|
+
{
|
|
22
|
+
suite(
|
|
23
|
+
'marshalSpecificElementDataToForm assigns to the element in hand',
|
|
24
|
+
() =>
|
|
25
|
+
{
|
|
26
|
+
const buildHarness = () =>
|
|
27
|
+
{
|
|
28
|
+
const tmpPict = new libPict({ Product: 'MockInformary', ProductVersion: '1.0.0' });
|
|
29
|
+
tmpPict.addProviderSingleton('Informary', libInformary.default_configuration, libInformary);
|
|
30
|
+
const tmpAssignCalls = [];
|
|
31
|
+
// Capture what assignContent is handed.
|
|
32
|
+
tmpPict.ContentAssignment.assignContent = (pAddress, pContent) =>
|
|
33
|
+
{
|
|
34
|
+
tmpAssignCalls.push({ Address: pAddress, Content: pContent });
|
|
35
|
+
};
|
|
36
|
+
// The whole point of the fix: the marshal must NOT resolve a selector.
|
|
37
|
+
tmpPict.ContentAssignment.getElement = (pAddress) =>
|
|
38
|
+
{
|
|
39
|
+
throw new Error(`getElement must not be called during the element marshal; got [${pAddress}]`);
|
|
40
|
+
};
|
|
41
|
+
return { pict: tmpPict, informary: tmpPict.providers.Informary, assigns: tmpAssignCalls };
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const buildElement = (pAttributes) =>
|
|
45
|
+
{
|
|
46
|
+
return { getAttribute: (pName) => (pName in pAttributes ? pAttributes[pName] : null) };
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const tmpManifest = { getValueAtAddress: (pData, pAddress) => pData[pAddress] };
|
|
50
|
+
|
|
51
|
+
test(
|
|
52
|
+
'non-container datum: assigns the value straight to the passed element',
|
|
53
|
+
(fDone) =>
|
|
54
|
+
{
|
|
55
|
+
const tmpHarness = buildHarness();
|
|
56
|
+
const tmpElement = buildElement({ 'data-i-datum': 'Greeting', 'data-i-container': null, 'data-i-index': '0' });
|
|
57
|
+
tmpHarness.informary.marshalSpecificElementDataToForm(tmpElement, tmpManifest, { Greeting: 'hello' });
|
|
58
|
+
Expect(tmpHarness.assigns.length).to.equal(1);
|
|
59
|
+
// The ELEMENT itself, never a string selector.
|
|
60
|
+
Expect(tmpHarness.assigns[0].Address).to.equal(tmpElement);
|
|
61
|
+
Expect(typeof tmpHarness.assigns[0].Address).to.not.equal('string');
|
|
62
|
+
Expect(tmpHarness.assigns[0].Content).to.equal('hello');
|
|
63
|
+
fDone();
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
test(
|
|
68
|
+
'container datum: assigns the composed-address value straight to the passed element',
|
|
69
|
+
(fDone) =>
|
|
70
|
+
{
|
|
71
|
+
const tmpHarness = buildHarness();
|
|
72
|
+
const tmpElement = buildElement({ 'data-i-datum': 'Score', 'data-i-container': 'Grades', 'data-i-index': '2' });
|
|
73
|
+
const tmpKey = tmpHarness.informary.getComposedContainerAddress('Grades', 2, 'Score');
|
|
74
|
+
const tmpData = {};
|
|
75
|
+
tmpData[tmpKey] = 99;
|
|
76
|
+
tmpHarness.informary.marshalSpecificElementDataToForm(tmpElement, tmpManifest, tmpData);
|
|
77
|
+
Expect(tmpHarness.assigns.length).to.equal(1);
|
|
78
|
+
Expect(tmpHarness.assigns[0].Address).to.equal(tmpElement);
|
|
79
|
+
Expect(tmpHarness.assigns[0].Content).to.equal(99);
|
|
80
|
+
fDone();
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
test(
|
|
85
|
+
'element with no datum address is skipped (returns false, no assignment)',
|
|
86
|
+
(fDone) =>
|
|
87
|
+
{
|
|
88
|
+
const tmpHarness = buildHarness();
|
|
89
|
+
const tmpElement = buildElement({});
|
|
90
|
+
const tmpResult = tmpHarness.informary.marshalSpecificElementDataToForm(tmpElement, tmpManifest, {});
|
|
91
|
+
Expect(tmpResult).to.equal(false);
|
|
92
|
+
Expect(tmpHarness.assigns.length).to.equal(0);
|
|
93
|
+
fDone();
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
test(
|
|
98
|
+
'a null app-state value does not assign (no spurious blank write)',
|
|
99
|
+
(fDone) =>
|
|
100
|
+
{
|
|
101
|
+
const tmpHarness = buildHarness();
|
|
102
|
+
const tmpElement = buildElement({ 'data-i-datum': 'Missing', 'data-i-container': null, 'data-i-index': '0' });
|
|
103
|
+
tmpHarness.informary.marshalSpecificElementDataToForm(tmpElement, tmpManifest, {});
|
|
104
|
+
Expect(tmpHarness.assigns.length).to.equal(0);
|
|
105
|
+
fDone();
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
);
|
|
@@ -39,6 +39,7 @@ class DoNothingApplication extends libPictSectionForm.PictFormApplication
|
|
|
39
39
|
{
|
|
40
40
|
this.solve();
|
|
41
41
|
this._testDone();
|
|
42
|
+
return true;
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
45
|
|
|
@@ -65,10 +66,12 @@ class OrderedSolverApplication extends DoNothingApplication
|
|
|
65
66
|
super.onAfterSolve();
|
|
66
67
|
this.pict.log.info('OrderedSolverApplication onAfterSolve called.');
|
|
67
68
|
this._testDone?.();
|
|
69
|
+
return true;
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
onAfterInitialize()
|
|
71
73
|
{
|
|
74
|
+
return true;
|
|
72
75
|
}
|
|
73
76
|
}
|
|
74
77
|
|
|
@@ -569,6 +569,96 @@ suite('PictSectionForm Tabular Features', () =>
|
|
|
569
569
|
}, fDone);
|
|
570
570
|
});
|
|
571
571
|
|
|
572
|
+
test('Moving a source row reorders dependent position-keyed cells (data follows the column)', (fDone) =>
|
|
573
|
+
{
|
|
574
|
+
let App = makeApplication({
|
|
575
|
+
Hash: 'Grades',
|
|
576
|
+
Layout: 'Tabular',
|
|
577
|
+
RecordSetAddress: 'Grades',
|
|
578
|
+
RecordManifest: 'GradeRowEditor',
|
|
579
|
+
DynamicColumns:
|
|
580
|
+
[
|
|
581
|
+
{
|
|
582
|
+
SourceAddress: 'Assignments',
|
|
583
|
+
KeyBy: 'Position',
|
|
584
|
+
HashTemplate: 'PassingCol_{~D:Record.__Index~}',
|
|
585
|
+
NameTemplate: '{~D:Record.Title~}',
|
|
586
|
+
InformaryDataAddressTemplate: 'PassingCol_{~D:Record.__Index~}',
|
|
587
|
+
DataType: 'String',
|
|
588
|
+
PictForm: { InputType: 'Text' }
|
|
589
|
+
}
|
|
590
|
+
]
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
// Each dependent row carries user-entered positional cells (one per source column).
|
|
594
|
+
Grades:
|
|
595
|
+
[
|
|
596
|
+
{ Section: 'A', StudentName: 'Alice', PassingCol_0: 'a0', PassingCol_1: 'a1', PassingCol_2: 'a2' },
|
|
597
|
+
{ Section: 'A', StudentName: 'Bob', PassingCol_0: 'b0', PassingCol_1: 'b1', PassingCol_2: 'b2' }
|
|
598
|
+
]
|
|
599
|
+
});
|
|
600
|
+
bootstrap(App, (_Pict) =>
|
|
601
|
+
{
|
|
602
|
+
// Source order is [Addition(0), Photosynthesis(1), Reading 1(2)]. Move row 0 -> 2,
|
|
603
|
+
// the way moveDynamicTableRowDown twice / setDynamicTableRowIndex(0,2) would, then
|
|
604
|
+
// apply the SAME permutation to every dependent row's positional cells.
|
|
605
|
+
_Pict.providers.DynamicTabularData._moveDependentPositionalColumns('Assignments', 0, 2, 3);
|
|
606
|
+
|
|
607
|
+
let tmpRow0 = _Pict.AppData.Grades[0];
|
|
608
|
+
let tmpRow1 = _Pict.AppData.Grades[1];
|
|
609
|
+
// [a0,a1,a2] with source 0->2 becomes [a1,a2,a0] -- the value under each column
|
|
610
|
+
// moved with its source position, so data stays attached to its column.
|
|
611
|
+
Expect(tmpRow0.PassingCol_0).to.equal('a1', 'row 0 col 0 took the value from old position 1');
|
|
612
|
+
Expect(tmpRow0.PassingCol_1).to.equal('a2', 'row 0 col 1 took the value from old position 2');
|
|
613
|
+
Expect(tmpRow0.PassingCol_2).to.equal('a0', 'row 0 col 2 took the moved value from old position 0');
|
|
614
|
+
Expect(tmpRow1.PassingCol_0).to.equal('b1', 'row 1 permuted identically');
|
|
615
|
+
Expect(tmpRow1.PassingCol_1).to.equal('b2', 'row 1 permuted identically');
|
|
616
|
+
Expect(tmpRow1.PassingCol_2).to.equal('b0', 'row 1 permuted identically');
|
|
617
|
+
}, fDone);
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
test('Reordering a source row re-labels dependent columns in the render phase (no blank-out on move)', (fDone) =>
|
|
621
|
+
{
|
|
622
|
+
let App = makeApplication({
|
|
623
|
+
Hash: 'Grades',
|
|
624
|
+
Layout: 'Tabular',
|
|
625
|
+
RecordSetAddress: 'Grades',
|
|
626
|
+
RecordManifest: 'GradeRowEditor',
|
|
627
|
+
DynamicColumns:
|
|
628
|
+
[
|
|
629
|
+
{
|
|
630
|
+
SourceAddress: 'Assignments',
|
|
631
|
+
KeyBy: 'Position',
|
|
632
|
+
HashTemplate: 'PassingCol_{~D:Record.__Index~}',
|
|
633
|
+
NameTemplate: '{~D:Record.Title~}',
|
|
634
|
+
InformaryDataAddressTemplate: 'PassingCol_{~D:Record.__Index~}',
|
|
635
|
+
DataType: 'String',
|
|
636
|
+
PictForm: { InputType: 'Text' }
|
|
637
|
+
}
|
|
638
|
+
]
|
|
639
|
+
});
|
|
640
|
+
bootstrap(App, (_Pict) =>
|
|
641
|
+
{
|
|
642
|
+
let tmpView = _Pict.views['PictSectionForm-Class'];
|
|
643
|
+
let tmpGroup = tmpView.sectionDefinition.Groups[0];
|
|
644
|
+
Expect(tmpGroup.supportingManifest.elementDescriptors['PassingCol_0'].Name).to.equal('Addition', 'col 0 header before move');
|
|
645
|
+
Expect(tmpGroup.supportingManifest.elementDescriptors['PassingCol_2'].Name).to.equal('Reading 1', 'col 2 header before move');
|
|
646
|
+
|
|
647
|
+
// Reorder the source the way the move handler splices it (move index 0 -> 2), then
|
|
648
|
+
// run the render-phase rebuild the move handler now performs BEFORE marshaling.
|
|
649
|
+
let tmpMoved = _Pict.AppData.Assignments.splice(0, 1);
|
|
650
|
+
_Pict.AppData.Assignments.splice(2, 0, tmpMoved[0]);
|
|
651
|
+
_Pict.providers.DynamicTabularData._rebuildDependentDynamicColumnViews('Assignments');
|
|
652
|
+
|
|
653
|
+
// Column hashes are position-keyed (stable), but their labels must re-resolve to the
|
|
654
|
+
// NEW source order here in the render phase -- not mid-marshal, which would blank the
|
|
655
|
+
// freshly filled cells until the next edit.
|
|
656
|
+
Expect(tmpGroup.supportingManifest.elementDescriptors['PassingCol_0'].Name).to.equal('Photosynthesis', 'col 0 re-labeled to new position 0');
|
|
657
|
+
Expect(tmpGroup.supportingManifest.elementDescriptors['PassingCol_1'].Name).to.equal('Reading 1', 'col 1 re-labeled to new position 1');
|
|
658
|
+
Expect(tmpGroup.supportingManifest.elementDescriptors['PassingCol_2'].Name).to.equal('Addition', 'col 2 re-labeled to the moved row');
|
|
659
|
+
}, fDone);
|
|
660
|
+
});
|
|
661
|
+
|
|
572
662
|
test('Renaming a source row reports namesChanged and refreshes the column header in place', (fDone) =>
|
|
573
663
|
{
|
|
574
664
|
let App = makeApplication({
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"include": ["source"],
|
|
3
|
+
"compilerOptions":
|
|
4
|
+
{
|
|
5
|
+
"target": "es2019",
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"allowJs": true,
|
|
8
|
+
"checkJs": true,
|
|
9
|
+
"declaration": true,
|
|
10
|
+
"emitDeclarationOnly": true,
|
|
11
|
+
"outDir": "types",
|
|
12
|
+
"declarationMap": true,
|
|
13
|
+
"module": "commonjs",
|
|
14
|
+
"resolveJsonModule": true
|
|
15
|
+
}
|
|
16
|
+
}
|
package/tsconfig.json
CHANGED
|
@@ -121,6 +121,39 @@ declare class DynamicTabularData extends libPictProvider {
|
|
|
121
121
|
* @param {number} pOriginalLength - Source row count BEFORE the removal.
|
|
122
122
|
*/
|
|
123
123
|
_spliceDependentPositionalColumns(pSourceRecordSetAddress: string, pDeletedIndex: number, pOriginalLength: number): void;
|
|
124
|
+
/**
|
|
125
|
+
* For position-keyed DynamicColumns (KeyBy: "Position") sourced from
|
|
126
|
+
* pSourceRecordSetAddress, REORDER each dependent row's positional cells to
|
|
127
|
+
* mirror a source row that moved from pOldIndex to pNewIndex. The source array
|
|
128
|
+
* was already spliced (remove at pOldIndex, insert at pNewIndex); this applies
|
|
129
|
+
* the identical permutation to every dependent row's positional cell VALUES so
|
|
130
|
+
* the data stays attached to its column when the columns re-resolve to the new
|
|
131
|
+
* source order. Without it, a reorder leaves user-entered cells under the wrong
|
|
132
|
+
* (renamed) column. Solver-filled rows re-derive on the next solve regardless;
|
|
133
|
+
* applying the move to them too is harmless (the solve overwrites them). No-op
|
|
134
|
+
* for value-keyed generators (their data stays attached to the stable value).
|
|
135
|
+
*
|
|
136
|
+
* Must run AFTER the source splice and BEFORE the dependent views re-resolve +
|
|
137
|
+
* the marshal repaints them. Symmetric with _spliceDependentPositionalColumns.
|
|
138
|
+
*
|
|
139
|
+
* @param {string} pSourceRecordSetAddress - RecordSetAddress of the moved-within source.
|
|
140
|
+
* @param {number} pOldIndex - Source row's index before the move.
|
|
141
|
+
* @param {number} pNewIndex - Source row's index after the move.
|
|
142
|
+
* @param {number} pLength - Source row count (unchanged by a move).
|
|
143
|
+
*/
|
|
144
|
+
_moveDependentPositionalColumns(pSourceRecordSetAddress: string, pOldIndex: number, pNewIndex: number, pLength: number): void;
|
|
145
|
+
/**
|
|
146
|
+
* Shared tail for the row-reorder handlers (move up / move down / set index).
|
|
147
|
+
* Repaints the moved source table and every dependent view, in the RENDER phase
|
|
148
|
+
* BEFORE solving + marshaling, then solves and marshals. Mirrors what the add /
|
|
149
|
+
* delete handlers do so a reorder doesn't blank dependent DynamicColumns tables
|
|
150
|
+
* (or the rest of their section) until the next edit. Render happens before the
|
|
151
|
+
* solve so the solve's DOM side effects (e.g. SetGroupVisibility) land on the
|
|
152
|
+
* freshly rebuilt DOM and survive.
|
|
153
|
+
*
|
|
154
|
+
* @param {Object} pGroup - The reordered group (its RecordSetAddress drives dependents).
|
|
155
|
+
*/
|
|
156
|
+
_repaintAfterRowReorder(pGroup: any): void;
|
|
124
157
|
}
|
|
125
158
|
declare namespace DynamicTabularData {
|
|
126
159
|
export { _DefaultProviderConfiguration as default_configuration, ElementDescriptor };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Pict-Provider-DynamicTabularData.d.ts","sourceRoot":"","sources":["../../../source/providers/Pict-Provider-DynamicTabularData.js"],"names":[],"mappings":";AAaA;;;GAGG;AAEH;;GAEG;AACH;IAcE,kBAAkB;IAClB,SADW,GAAG,CACF;IACZ,6BAA6B;IAC7B,MADW,OAAO,MAAM,CAAC,CAChB;IAKV;;;;;;OAMG;IACH,6CAHW,MAAM,GACJ,cAAa,OAAO,CAehC;IAED;;;;;;;OAOG;IACH,+CAJW,MAAM,eACN,MAAM,GACJ,iBAAiB,GAAC,OAAO,CAiBrC;IAED;;;;;;;OAOG;IACH,oDAJW,MAAM,cACN,MAAM,GACJ,iBAAiB,GAAC,OAAO,CA+BrC;IAED;;;;;;;OAOG;IACH,8CAJW,MAAM,kBACN,MAAM,GACJ,OAAO,MAAO,CAkD1B;IAED;;;;;OAKG;IACH,+CAFW,MAAM,QAqEhB;IAGD;;;;;OAKG;IACH,4DAFW,MAAM,QAyBhB;IAED;;;;;;;;OAQG;IACH,iDALW,MAAM,aACN,MAAM,GAAC,MAAM,gBACb,MAAM,GACJ,OAAO,
|
|
1
|
+
{"version":3,"file":"Pict-Provider-DynamicTabularData.d.ts","sourceRoot":"","sources":["../../../source/providers/Pict-Provider-DynamicTabularData.js"],"names":[],"mappings":";AAaA;;;GAGG;AAEH;;GAEG;AACH;IAcE,kBAAkB;IAClB,SADW,GAAG,CACF;IACZ,6BAA6B;IAC7B,MADW,OAAO,MAAM,CAAC,CAChB;IAKV;;;;;;OAMG;IACH,6CAHW,MAAM,GACJ,cAAa,OAAO,CAehC;IAED;;;;;;;OAOG;IACH,+CAJW,MAAM,eACN,MAAM,GACJ,iBAAiB,GAAC,OAAO,CAiBrC;IAED;;;;;;;OAOG;IACH,oDAJW,MAAM,cACN,MAAM,GACJ,iBAAiB,GAAC,OAAO,CA+BrC;IAED;;;;;;;OAOG;IACH,8CAJW,MAAM,kBACN,MAAM,GACJ,OAAO,MAAO,CAkD1B;IAED;;;;;OAKG;IACH,+CAFW,MAAM,QAqEhB;IAGD;;;;;OAKG;IACH,4DAFW,MAAM,QAyBhB;IAED;;;;;;;;OAQG;IACH,iDALW,MAAM,aACN,MAAM,GAAC,MAAM,gBACb,MAAM,GACJ,OAAO,CAoCnB;IAED;;;;;;;OAOG;IACH,iDAJW,MAAM,aACN,MAAM,GAAC,MAAM,GACX,OAAO,CAqCnB;IAED;;;;;;;OAOG;IACH,+CAJW,MAAM,aACN,MAAM,GAAC,MAAM,GACX,OAAO,CA0CnB;IAGD;;;;;;;OAOG;IACH,+CAJW,MAAM,aACN,MAAM,GAAC,MAAM,GACX,OAAO,CA6EnB;IAED;;;;;;;;;OASG;IACH,6DAFW,MAAM,QAqDhB;IAED;;;;;;;;;;;OAWG;IACH,2DAJW,MAAM,iBACN,MAAM,mBACN,MAAM,QAoFhB;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACH,yDALW,MAAM,aACN,MAAM,aACN,MAAM,WACN,MAAM,QAwFhB;IAED;;;;;;;;;;OAUG;IACH,2CAsCC;CACD;;;;;AAn0BD,kCAAkC;AAClC,6CADW,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAS3B;;;;;UAIW,MAAM"}
|
|
@@ -44,47 +44,46 @@ declare class PictDynamicFormsInformary extends libPictProvider {
|
|
|
44
44
|
*
|
|
45
45
|
* @param {object} pAppStateData - The application state data object to marshal the form data to.
|
|
46
46
|
* @param {string} pFormHash - The form hash representing the form elements.
|
|
47
|
-
* @param {
|
|
47
|
+
* @param {import('manyfest')} pManifest - The manifest object used to map form data to the application state data.
|
|
48
48
|
* @param {string} [pDatum] - The datum hash to pull in. If not provided, all data is marshalled.
|
|
49
49
|
* @param {number|string} [pRecordIndex] - The record index to pull in. If not provided, all data is marshalled.
|
|
50
50
|
*/
|
|
51
|
-
marshalFormToData(pAppStateData: object, pFormHash: string, pManifest:
|
|
51
|
+
marshalFormToData(pAppStateData: object, pFormHash: string, pManifest: import("manyfest"), pDatum?: string, pRecordIndex?: number | string): void;
|
|
52
52
|
/**
|
|
53
53
|
* Marshals a specific form element's data to the application state data.
|
|
54
54
|
*
|
|
55
55
|
* @param {string} pFormHash - The hash of the form.
|
|
56
|
-
* @param {HTMLElement}
|
|
57
|
-
* @param {
|
|
56
|
+
* @param {HTMLElement} pFormElement - The form element to marshal.
|
|
57
|
+
* @param {import('manyfest')} pManifest - The manifest object to set values.
|
|
58
58
|
* @param {Object} pAppStateData - The application state data object.
|
|
59
59
|
* @param {any} [pDatumFilter] - Optional filter for datum address.
|
|
60
60
|
* @param {any} [pRecordIndexFilter] - Optional filter for record index.
|
|
61
61
|
* @returns {boolean} - Returns false if the element falls outside the filters or if the browser value is null.
|
|
62
62
|
*/
|
|
63
|
-
marshalSpecicificFormElementToData(pFormHash: string,
|
|
63
|
+
marshalSpecicificFormElementToData(pFormHash: string, pFormElement: HTMLElement, pManifest: import("manyfest"), pAppStateData: any, pDatumFilter?: any, pRecordIndexFilter?: any): boolean;
|
|
64
64
|
/**
|
|
65
65
|
* Marshals data from some application state object to a specific subset of browser form elements.
|
|
66
66
|
*
|
|
67
67
|
* @param {object} pAppStateData - The application state data to marshal into the form. Usually AppData but can be other objects.
|
|
68
68
|
* @param {string} pFormHash - The hash of the form to marshal data into. This is the data-i-form attribute.
|
|
69
|
-
* @param {
|
|
69
|
+
* @param {import('manyfest')} pManifest - The manifest object. If not provided, the generic manifest is used.
|
|
70
70
|
*/
|
|
71
|
-
marshalDataToForm(pAppStateData: object, pFormHash: string, pManifest:
|
|
71
|
+
marshalDataToForm(pAppStateData: object, pFormHash: string, pManifest: import("manyfest")): void;
|
|
72
72
|
/**
|
|
73
73
|
* Marshals specific element data to a form.
|
|
74
74
|
*
|
|
75
|
-
* @param {string} pFormHash - The hash of the form.
|
|
76
75
|
* @param {HTMLElement} pFormElement - The form element to marshal data to.
|
|
77
|
-
* @param {
|
|
78
|
-
* @param {
|
|
76
|
+
* @param {import('manyfest')} pManifest - The manifest object containing data retrieval methods.
|
|
77
|
+
* @param {Record<string, any>} pAppStateData - The application state data.
|
|
79
78
|
* @returns {boolean} Returns false if the form element does not have a datum address.
|
|
80
79
|
*/
|
|
81
|
-
marshalSpecificElementDataToForm(
|
|
80
|
+
marshalSpecificElementDataToForm(pFormElement: HTMLElement, pManifest: import("manyfest"), pAppStateData: Record<string, any>): boolean;
|
|
82
81
|
/**
|
|
83
82
|
* Manually marshals data to a form by assigning content based on context in the descriptor.
|
|
84
|
-
* @param {
|
|
83
|
+
* @param {Record<string, any>} pInput - The input manifest descriptor to marshal data to form from.
|
|
85
84
|
* @returns boolean if assignment was successful
|
|
86
85
|
*/
|
|
87
|
-
manualMarshalDataToFormByInput(pInput:
|
|
86
|
+
manualMarshalDataToFormByInput(pInput: Record<string, any>): false | void;
|
|
88
87
|
/**
|
|
89
88
|
* Manually marshals tabular data to a form by assigning content based on context in the descriptor.
|
|
90
89
|
* @param {object} pInput - The input manifest descriptor to marshal data to form from.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Pict-Provider-Informary.d.ts","sourceRoot":"","sources":["../../../source/providers/Pict-Provider-Informary.js"],"names":[],"mappings":";AAcA;;;GAGG;AACH;IAeE,kBAAkB;IAClB,SADW,GAAG,CACF;IACZ,qFAAqF;IACrF,MADW,OAAO,MAAM,CAAC,GAAG;QAAE,WAAW,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,OAAO,UAAU,CAAC,CAAA;KAAE,CACxE;IAIT,oCAAwE;IAGzE;;;;;OAKG;IACH,2BAHW,MAAM,GACJ,WAAW,EAAE,CAOzB;IAED;;;;;;;;;;OAUG;IACH,oCANW,MAAM,cACN,MAAM,cACN,MAAM,GAAC,IAAI,UACX,MAAM,GAAC,MAAM,GACX,MAAM,CAalB;IAED;;;;;;;OAOG;IACH,wCALW,MAAM,UACN,MAAM,GAAC,MAAM,cACb,MAAM,GACJ,MAAM,CAKlB;IAED;;;;;;;;OAQG;IACH,iCANW,MAAM,aACN,MAAM,aACN,
|
|
1
|
+
{"version":3,"file":"Pict-Provider-Informary.d.ts","sourceRoot":"","sources":["../../../source/providers/Pict-Provider-Informary.js"],"names":[],"mappings":";AAcA;;;GAGG;AACH;IAeE,kBAAkB;IAClB,SADW,GAAG,CACF;IACZ,qFAAqF;IACrF,MADW,OAAO,MAAM,CAAC,GAAG;QAAE,WAAW,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,OAAO,UAAU,CAAC,CAAA;KAAE,CACxE;IAIT,oCAAwE;IAGzE;;;;;OAKG;IACH,2BAHW,MAAM,GACJ,WAAW,EAAE,CAOzB;IAED;;;;;;;;;;OAUG;IACH,oCANW,MAAM,cACN,MAAM,cACN,MAAM,GAAC,IAAI,UACX,MAAM,GAAC,MAAM,GACX,MAAM,CAalB;IAED;;;;;;;OAOG;IACH,wCALW,MAAM,UACN,MAAM,GAAC,MAAM,cACb,MAAM,GACJ,MAAM,CAKlB;IAED;;;;;;;;OAQG;IACH,iCANW,MAAM,aACN,MAAM,aACN,OAAO,UAAU,CAAC,WAClB,MAAM,iBACN,MAAM,GAAC,MAAM,QAgBvB;IAED;;;;;;;;;;OAUG;IACH,8CARW,MAAM,gBACN,WAAW,aACX,OAAO,UAAU,CAAC,qCAElB,GAAG,uBACH,GAAG,GACD,OAAO,CA2CnB;IAED;;;;;;OAMG;IACH,iCAJW,MAAM,aACN,MAAM,aACN,OAAO,UAAU,CAAC,QAa5B;IAED;;;;;;;OAOG;IACH,+CALW,WAAW,aACX,OAAO,UAAU,CAAC,iBAClB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GACjB,OAAO,CA4CnB;IAED;;;;OAIG;IACH,uCAHW,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,gBA2B7B;IAED;;;;;OAKG;IACH,8CAJW,MAAM,aACN,MAAM,gBA4BhB;IAED;;;;;OAKG;IACH,sCAHW,MAAM,UACN,MAAM,QAKhB;IAED;;;;;;OAMG;IACH,6CAJW,MAAM,UACN,MAAM,aACN,MAAM,QAKhB;CACD;;;;;AAvUD,kCAAkC;AAClC,6CADW,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAS3B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Pict-Layout-Custom.d.ts","sourceRoot":"","sources":["../../../../source/providers/layouts/Pict-Layout-Custom.js"],"names":[],"mappings":";AAEA;IAEC,2DAUC;IANA,6BAA6B;IAC7B,MADW,OAAO,MAAM,CAAC,CAChB;IACT,6BAA6B;IAC7B,OADW,OAAO,MAAM,CAAC,CACf;
|
|
1
|
+
{"version":3,"file":"Pict-Layout-Custom.d.ts","sourceRoot":"","sources":["../../../../source/providers/layouts/Pict-Layout-Custom.js"],"names":[],"mappings":";AAEA;IAEC,2DAUC;IANA,6BAA6B;IAC7B,MADW,OAAO,MAAM,CAAC,CAChB;IACT,6BAA6B;IAC7B,OADW,OAAO,MAAM,CAAC,CACf;CA0DX"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Pict-Layout-Record.d.ts","sourceRoot":"","sources":["../../../../source/providers/layouts/Pict-Layout-Record.js"],"names":[],"mappings":";AAEA;IAEC,2DAUC;IANA,6BAA6B;IAC7B,MADW,OAAO,MAAM,CAAC,CAChB;IACT,6BAA6B;IAC7B,OADW,OAAO,MAAM,CAAC,CACf;
|
|
1
|
+
{"version":3,"file":"Pict-Layout-Record.d.ts","sourceRoot":"","sources":["../../../../source/providers/layouts/Pict-Layout-Record.js"],"names":[],"mappings":";AAEA;IAEC,2DAUC;IANA,6BAA6B;IAC7B,MADW,OAAO,MAAM,CAAC,CAChB;IACT,6BAA6B;IAC7B,OADW,OAAO,MAAM,CAAC,CACf;CA8GX"}
|
|
@@ -205,6 +205,208 @@ declare class TabularLayout extends libPictSectionGroupLayout {
|
|
|
205
205
|
* @param {Object} pGroup
|
|
206
206
|
*/
|
|
207
207
|
_reapplyTabularSelectionHighlights(pView: any, pGroup: any): void;
|
|
208
|
+
/**
|
|
209
|
+
* Normalize a Group.ColumnChooser config value.
|
|
210
|
+
*
|
|
211
|
+
* Accepts `true` (use all defaults) or an object
|
|
212
|
+
* `{ Enabled, DataAddress, ButtonLabel, DefaultHiddenColumns }`. Returns null
|
|
213
|
+
* when the chooser is not enabled (the feature is strictly opt-in).
|
|
214
|
+
*
|
|
215
|
+
* - `DataAddress` — address (relative to the form's marshal destination) where
|
|
216
|
+
* the array of hidden column hashes is stored, so it persists with the form data.
|
|
217
|
+
* - `ButtonLabel` — text for the trigger button above the table.
|
|
218
|
+
* - `DefaultHiddenColumns` — column hashes hidden until the user changes them
|
|
219
|
+
* (merged with any descriptor-level `PictForm.TabularDefaultHidden` flags).
|
|
220
|
+
*
|
|
221
|
+
* @param {boolean|Object} pConfigValue
|
|
222
|
+
* @param {string} pDefaultDataAddress
|
|
223
|
+
* @returns {{DataAddress: string, ButtonLabel: string, DefaultHiddenColumns: Array<string>}|null}
|
|
224
|
+
*/
|
|
225
|
+
_normalizeColumnChooserConfig(pConfigValue: boolean | any, pDefaultDataAddress: string): {
|
|
226
|
+
DataAddress: string;
|
|
227
|
+
ButtonLabel: string;
|
|
228
|
+
DefaultHiddenColumns: Array<string>;
|
|
229
|
+
} | null;
|
|
230
|
+
/**
|
|
231
|
+
* Lazily normalize (and cache on the group) the ColumnChooser config. The
|
|
232
|
+
* template bake re-normalizes each pass; this accessor covers code paths
|
|
233
|
+
* (marshal hooks, inline handlers) that may run against a group whose
|
|
234
|
+
* template hasn't been baked yet.
|
|
235
|
+
*
|
|
236
|
+
* @param {Object} pGroup
|
|
237
|
+
* @returns {{DataAddress: string, ButtonLabel: string, DefaultHiddenColumns: Array<string>}|null}
|
|
238
|
+
*/
|
|
239
|
+
_ensureTabularColumnChooserConfig(pGroup: any): {
|
|
240
|
+
DataAddress: string;
|
|
241
|
+
ButtonLabel: string;
|
|
242
|
+
DefaultHiddenColumns: Array<string>;
|
|
243
|
+
} | null;
|
|
244
|
+
/**
|
|
245
|
+
* The absolute address (within the form's marshal destination) of the
|
|
246
|
+
* chooser's hidden-column-hash array.
|
|
247
|
+
*
|
|
248
|
+
* @param {Object} pView
|
|
249
|
+
* @param {{DataAddress: string}} pChooserConfig
|
|
250
|
+
* @returns {string}
|
|
251
|
+
*/
|
|
252
|
+
_getTabularHiddenColumnsAddress(pView: any, pChooserConfig: {
|
|
253
|
+
DataAddress: string;
|
|
254
|
+
}): string;
|
|
255
|
+
/**
|
|
256
|
+
* The set of column hashes hidden BY DEFAULT for a group: the chooser
|
|
257
|
+
* config's DefaultHiddenColumns plus every descriptor flagged
|
|
258
|
+
* `PictForm.TabularDefaultHidden`. These apply only until the user changes
|
|
259
|
+
* column visibility (which writes an explicit array into the form data).
|
|
260
|
+
*
|
|
261
|
+
* @param {Object} pGroup
|
|
262
|
+
* @returns {Array<string>}
|
|
263
|
+
*/
|
|
264
|
+
_getTabularColumnChooserDefaultHidden(pGroup: any): Array<string>;
|
|
265
|
+
/**
|
|
266
|
+
* The EFFECTIVE set of chooser-hidden column hashes for a group: the array
|
|
267
|
+
* stored in the form data when the user has made choices, otherwise the
|
|
268
|
+
* configured defaults. Returns null when the chooser is not enabled, so
|
|
269
|
+
* callers can use a single falsy check to keep the legacy code path intact.
|
|
270
|
+
*
|
|
271
|
+
* @param {Object} pView
|
|
272
|
+
* @param {Object} pGroup
|
|
273
|
+
* @returns {Set<string>|null}
|
|
274
|
+
*/
|
|
275
|
+
_getTabularColumnChooserHiddenSet(pView: any, pGroup: any): Set<string> | null;
|
|
276
|
+
/**
|
|
277
|
+
* A canonical string for the group's effective hidden-column set, used to
|
|
278
|
+
* detect (on marshal) that loaded form data carries different column
|
|
279
|
+
* visibility than the table template was baked with.
|
|
280
|
+
*
|
|
281
|
+
* @param {Object} pView
|
|
282
|
+
* @param {Object} pGroup
|
|
283
|
+
* @returns {string}
|
|
284
|
+
*/
|
|
285
|
+
_getTabularColumnChooserStateKey(pView: any, pGroup: any): string;
|
|
286
|
+
/**
|
|
287
|
+
* The columns the chooser can manage, in manifest order. Statically hidden
|
|
288
|
+
* descriptors (`PictForm.TabularHidden`) are never choosable and never
|
|
289
|
+
* listed. Each entry carries the descriptor's manifest index so inline
|
|
290
|
+
* handlers can address it without string-escaping concerns.
|
|
291
|
+
*
|
|
292
|
+
* @param {Object} pView
|
|
293
|
+
* @param {Object} pGroup
|
|
294
|
+
* @returns {Array<{Key: string, Name: string, ColumnIndex: number, Visible: boolean}>}
|
|
295
|
+
*/
|
|
296
|
+
_getTabularChoosableColumns(pView: any, pGroup: any): Array<{
|
|
297
|
+
Key: string;
|
|
298
|
+
Name: string;
|
|
299
|
+
ColumnIndex: number;
|
|
300
|
+
Visible: boolean;
|
|
301
|
+
}>;
|
|
302
|
+
/**
|
|
303
|
+
* DOM element id for one of the chooser's baked elements (TRIGGER / POPOVER),
|
|
304
|
+
* namespaced by form and group so multiple tabular groups can each carry
|
|
305
|
+
* their own chooser.
|
|
306
|
+
*
|
|
307
|
+
* @param {Object} pView
|
|
308
|
+
* @param {Object} pGroup
|
|
309
|
+
* @param {string} pElement
|
|
310
|
+
* @returns {string}
|
|
311
|
+
*/
|
|
312
|
+
_getTabularColumnChooserElementId(pView: any, pGroup: any, pElement: string): string;
|
|
313
|
+
/**
|
|
314
|
+
* Builds the chooser bar baked above the table: a right-aligned trigger
|
|
315
|
+
* button (with a "n hidden" hint when columns are hidden) plus the empty
|
|
316
|
+
* popover container the open action renders into.
|
|
317
|
+
*
|
|
318
|
+
* @param {Object} pView
|
|
319
|
+
* @param {Object} pGroup
|
|
320
|
+
* @returns {string}
|
|
321
|
+
*/
|
|
322
|
+
_buildTabularColumnChooserBarHTML(pView: any, pGroup: any): string;
|
|
323
|
+
/**
|
|
324
|
+
* Renders the chooser popover's content (backdrop + panel of checkbox rows +
|
|
325
|
+
* reset footer) into its baked container. Runs on open and after each toggle
|
|
326
|
+
* (the table re-render replaces the popover element, so its content must be
|
|
327
|
+
* repainted to keep the menu open across toggles).
|
|
328
|
+
*
|
|
329
|
+
* @param {Object} pView
|
|
330
|
+
* @param {Object} pGroup
|
|
331
|
+
*/
|
|
332
|
+
_renderTabularColumnChooserPopover(pView: any, pGroup: any): void;
|
|
333
|
+
/**
|
|
334
|
+
* Reflect the chooser popover's open/closed state on its container element,
|
|
335
|
+
* positioning it against the trigger when opening.
|
|
336
|
+
*
|
|
337
|
+
* @param {Object} pView
|
|
338
|
+
* @param {Object} pGroup
|
|
339
|
+
* @param {boolean} pOpen
|
|
340
|
+
*/
|
|
341
|
+
_paintTabularColumnChooserOpenState(pView: any, pGroup: any, pOpen: boolean): void;
|
|
342
|
+
/**
|
|
343
|
+
* Position the (fixed) chooser popover against its trigger button, flipping
|
|
344
|
+
* above when the room below is genuinely cramped — same approach as the
|
|
345
|
+
* recordset's column chooser, so no ancestor overflow can clip it.
|
|
346
|
+
*
|
|
347
|
+
* @param {Object} pView
|
|
348
|
+
* @param {Object} pGroup
|
|
349
|
+
* @param {HTMLElement} pPopover
|
|
350
|
+
*/
|
|
351
|
+
_positionTabularColumnChooserPopover(pView: any, pGroup: any, pPopover: HTMLElement): void;
|
|
352
|
+
/**
|
|
353
|
+
* Rebuild + re-render a tabular view and re-marshal the form data into it.
|
|
354
|
+
* Same tail as sortTabularColumn: the rebuild re-bakes the table template
|
|
355
|
+
* (column set, headers, chooser bar), the render repaints, the marshal
|
|
356
|
+
* pushes current values back into the freshly built inputs.
|
|
357
|
+
*
|
|
358
|
+
* @param {Object} pView
|
|
359
|
+
*/
|
|
360
|
+
_rebuildTabularGroupView(pView: any): void;
|
|
361
|
+
/**
|
|
362
|
+
* Inline-handler entry point: opens/closes a group's column chooser popover
|
|
363
|
+
* (the trigger button's handler). Open/closed is derived from the popover's
|
|
364
|
+
* DOM class, not an instance flag — a re-render replaces the popover element
|
|
365
|
+
* (visually closed), so a flag would go stale and demand a double-click.
|
|
366
|
+
*
|
|
367
|
+
* @param {string} pViewHash
|
|
368
|
+
* @param {number|string} pGroupIndex
|
|
369
|
+
* @returns {boolean}
|
|
370
|
+
*/
|
|
371
|
+
toggleTabularColumnChooser(pViewHash: string, pGroupIndex: number | string): boolean;
|
|
372
|
+
/**
|
|
373
|
+
* Inline-handler entry point: closes a group's column chooser popover (the
|
|
374
|
+
* backdrop's handler).
|
|
375
|
+
*
|
|
376
|
+
* @param {string} pViewHash
|
|
377
|
+
* @param {number|string} pGroupIndex
|
|
378
|
+
* @returns {boolean}
|
|
379
|
+
*/
|
|
380
|
+
closeTabularColumnChooser(pViewHash: string, pGroupIndex: number | string): boolean;
|
|
381
|
+
/**
|
|
382
|
+
* Inline-handler entry point: shows/hides one column (a chooser checkbox's
|
|
383
|
+
* handler). Writes the updated hidden-hash array into the form data (so it
|
|
384
|
+
* persists with a save), rebuilds the table template without the column,
|
|
385
|
+
* re-renders, re-marshals, then re-opens the popover the re-render closed.
|
|
386
|
+
*
|
|
387
|
+
* Hiding never touches the underlying record data — the column's values
|
|
388
|
+
* stay in the record set and reappear when the column is shown again.
|
|
389
|
+
*
|
|
390
|
+
* Refuses to hide the last visible column (the checkbox snaps back).
|
|
391
|
+
*
|
|
392
|
+
* @param {string} pViewHash
|
|
393
|
+
* @param {number|string} pGroupIndex
|
|
394
|
+
* @param {number|string} pColumnIndex - The column's manifest index (stable within a bake).
|
|
395
|
+
* @param {boolean} pVisible - true to show the column, false to hide it.
|
|
396
|
+
* @returns {boolean}
|
|
397
|
+
*/
|
|
398
|
+
toggleTabularColumnVisibility(pViewHash: string, pGroupIndex: number | string, pColumnIndex: number | string, pVisible: boolean): boolean;
|
|
399
|
+
/**
|
|
400
|
+
* Inline-handler entry point: resets a group's column visibility to its
|
|
401
|
+
* configured defaults (the reset footer button's handler). Writes the
|
|
402
|
+
* default hidden set into the form data explicitly — the user interacted,
|
|
403
|
+
* so the state should serialize deterministically with a save.
|
|
404
|
+
*
|
|
405
|
+
* @param {string} pViewHash
|
|
406
|
+
* @param {number|string} pGroupIndex
|
|
407
|
+
* @returns {boolean}
|
|
408
|
+
*/
|
|
409
|
+
resetTabularColumnVisibility(pViewHash: string, pGroupIndex: number | string): boolean;
|
|
208
410
|
}
|
|
209
411
|
import libPictSectionGroupLayout = require("../Pict-Provider-DynamicLayout.js");
|
|
210
412
|
//# sourceMappingURL=Pict-Layout-Tabular.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Pict-Layout-Tabular.d.ts","sourceRoot":"","sources":["../../../../source/providers/layouts/Pict-Layout-Tabular.js"],"names":[],"mappings":";AAEA;IAEC,
|
|
1
|
+
{"version":3,"file":"Pict-Layout-Tabular.d.ts","sourceRoot":"","sources":["../../../../source/providers/layouts/Pict-Layout-Tabular.js"],"names":[],"mappings":";AAEA;IAEC,2DAiEC;IA7DA,6BAA6B;IAC7B,MADW,OAAO,MAAM,CAAC,CAChB;IACT,6BAA6B;IAC7B,OADW,OAAO,MAAM,CAAC,CACf;IA4DX;;;;;;;;;;;OAWG;IACH,kFAHW,MAAM,GACJ,MAAM,CAwBlB;IAED;;;;;;;;;OASG;IACH,mEAFa,GAAG,CAoBf;IAED;;;;;;;OAOG;IACH,+BAJW,GAAG,WACH,GAAG,GACD,MAAM,CAelB;IAED;;;;;;;;;;OAUG;IACH,6BALW,MAAM,eACN,MAAM,GAAC,MAAM,gBACb,MAAM,GAAC,MAAM,GACX,OAAO,CAkEnB;IAED;;;;;;OAMG;IACH,iCAJW,MAAM,iBAEJ,MAAM,CAsBlB;IAED;;;;;;;;;;;;OAYG;IACH,sDAFa,KAAK,CAAC,KAAK,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAC,CAAC,CAAC,CA6I/E;IAED;;;;;;;;;;;;;OAaG;IACH,wDAoFC;IAED;;;;;;;;;OASG;IACH,qDAJW,MAAM,GAAC,MAAM,WACb,MAAM,GAAC,MAAM,GACX,MAAM,CAuDlB;IAED;;;;;;;;;;OAUG;IACH,2DAJW,MAAM,GAAC,MAAM,WACb,MAAM,GAAC,MAAM,GACX,MAAM,CAUlB;IAED,kCAYC;IAED;;;;;;;;;;;;;;;;;OAiBG;IACH,wCALW,OAAO,MAAO,uBACd,MAAM,0BACN,MAAM,GACJ;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAC,GAAC,IAAI,CAsBnF;IAED;;;;;;;OAOG;IACH,0DAHW;QAAC,WAAW,EAAE,MAAM,CAAA;KAAC,GACnB,MAAM,CAKlB;IAED;;;;;;;OAOG;IACH,wDAHW;QAAC,WAAW,EAAE,MAAM,CAAA;KAAC,GACnB,KAAK,CAAC,OAAO,CAAC,CAM1B;IAED;;;;;;;;OAQG;IACH,uDALW;QAAC,WAAW,EAAE,MAAM,CAAA;KAAC,UACrB,MAAM,GAAC,MAAM,aACb,OAAO,GACL,KAAK,CAAC,OAAO,CAAC,CAY1B;IAED;;;;;;;;;;;OAWG;IACH,qCANW,MAAM,eACN,MAAM,GAAC,MAAM,WACb,MAAM,GAAC,MAAM,YACb,OAAO,GACL,OAAO,CAsBnB;IAED;;;;;;;;;OASG;IACH,wCANW,MAAM,eACN,MAAM,GAAC,MAAM,gBACb,MAAM,GAAC,MAAM,YACb,OAAO,GACL,OAAO,CAsBnB;IAED;;;;;;;OAOG;IACH,kEAwBC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,4CAJW,OAAO,MAAO,uBACd,MAAM,GACJ;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,oBAAoB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;KAAC,GAAC,IAAI,CAwBhG;IAED;;;;;;;;OAQG;IACH,gDAFa;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,oBAAoB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;KAAC,GAAC,IAAI,CAShG;IAED;;;;;;;OAOG;IACH,4DAHW;QAAC,WAAW,EAAE,MAAM,CAAA;KAAC,GACnB,MAAM,CAKlB;IAED;;;;;;;;OAQG;IACH,oDAFa,KAAK,CAAC,MAAM,CAAC,CA6BzB;IAED;;;;;;;;;OASG;IACH,4DAFa,GAAG,CAAC,MAAM,CAAC,GAAC,IAAI,CAmB5B;IAED;;;;;;;;OAQG;IACH,2DAFa,MAAM,CAUlB;IAED;;;;;;;;;OASG;IACH,sDAFa,KAAK,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAC,CAAC,CA2BrF;IAED;;;;;;;;;OASG;IACH,qEAHW,MAAM,GACJ,MAAM,CAKlB;IAED;;;;;;;;OAQG;IACH,4DAFa,MAAM,CAiBlB;IAED;;;;;;;;OAQG;IACH,kEAqBC;IAED;;;;;;;OAOG;IACH,oEAFW,OAAO,QAcjB;IAED;;;;;;;;OAQG;IACH,wEAFW,WAAW,QAiCrB;IAED;;;;;;;OAOG;IACH,2CAYC;IAED;;;;;;;;;OASG;IACH,sCAJW,MAAM,eACN,MAAM,GAAC,MAAM,GACX,OAAO,CAuBnB;IAED;;;;;;;OAOG;IACH,qCAJW,MAAM,eACN,MAAM,GAAC,MAAM,GACX,OAAO,CAgBnB;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,yCANW,MAAM,eACN,MAAM,GAAC,MAAM,gBACb,MAAM,GAAC,MAAM,YACb,OAAO,GACL,OAAO,CAkDnB;IAED;;;;;;;;;OASG;IACH,wCAJW,MAAM,eACN,MAAM,GAAC,MAAM,GACX,OAAO,CAwBnB;CAsYD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Pict-Layout-VerticalRecord.d.ts","sourceRoot":"","sources":["../../../../source/providers/layouts/Pict-Layout-VerticalRecord.js"],"names":[],"mappings":";AAEA;IAEC,2DAUC;IANA,6BAA6B;IAC7B,MADW,OAAO,MAAM,CAAC,CAChB;IACT,6BAA6B;IAC7B,OADW,OAAO,MAAM,CAAC,CACf;
|
|
1
|
+
{"version":3,"file":"Pict-Layout-VerticalRecord.d.ts","sourceRoot":"","sources":["../../../../source/providers/layouts/Pict-Layout-VerticalRecord.js"],"names":[],"mappings":";AAEA;IAEC,2DAUC;IANA,6BAA6B;IAC7B,MADW,OAAO,MAAM,CAAC,CAChB;IACT,6BAA6B;IAC7B,OADW,OAAO,MAAM,CAAC,CACf;CAgEX"}
|
|
@@ -53,13 +53,14 @@ declare class ManifestFactory extends libFableServiceProviderBase {
|
|
|
53
53
|
*
|
|
54
54
|
* @param {Object} pView - The view containing the group.
|
|
55
55
|
* @param {Object} pGroup - The group object (must already have supportingManifest).
|
|
56
|
-
* @returns {{added: Array<string>, removed: Array<string>, unchanged: Array<string>, changed: boolean}}
|
|
56
|
+
* @returns {{added: Array<string>, removed: Array<string>, unchanged: Array<string>, changed: boolean, namesChanged: boolean}}
|
|
57
57
|
*/
|
|
58
58
|
_resolveDynamicColumns(pView: any, pGroup: any): {
|
|
59
59
|
added: Array<string>;
|
|
60
60
|
removed: Array<string>;
|
|
61
61
|
unchanged: Array<string>;
|
|
62
62
|
changed: boolean;
|
|
63
|
+
namesChanged: boolean;
|
|
63
64
|
};
|
|
64
65
|
/**
|
|
65
66
|
* Adds a manifest descriptor to the manifest.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ManifestFactory.d.ts","sourceRoot":"","sources":["../../../source/services/ManifestFactory.js"],"names":[],"mappings":";AAOA;IAEC,2DA4CC;IAtCA,6KAA6K;IAC7K,OADW,OAAO,MAAM,CAAC,GAAG;QAAE,6CAA6C,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,GAAG,CAAC;QAAC,WAAW,EAAE,MAAM,OAAO,UAAU,CAAC,CAAA;KAAE,CAC/J;IAMV,cAAmC;IAcnC,+BAAoC;IAEpC,sBAA2B;IAC3B,oBAAyB;IASzB,2BAA2B;IAE3B,gCAAgD;IAChD,sCAAwC;IACxC,kCAA0C;IAG3C;;;;;OAKG;IACH,2BAHW,MAAM,GACL,MAAM,CASjB;IAED;;;;;;;;;OASG;IACH,
|
|
1
|
+
{"version":3,"file":"ManifestFactory.d.ts","sourceRoot":"","sources":["../../../source/services/ManifestFactory.js"],"names":[],"mappings":";AAOA;IAEC,2DA4CC;IAtCA,6KAA6K;IAC7K,OADW,OAAO,MAAM,CAAC,GAAG;QAAE,6CAA6C,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,GAAG,CAAC;QAAC,WAAW,EAAE,MAAM,OAAO,UAAU,CAAC,CAAA;KAAE,CAC/J;IAMV,cAAmC;IAcnC,+BAAoC;IAEpC,sBAA2B;IAC3B,oBAAyB;IASzB,2BAA2B;IAE3B,gCAAgD;IAChD,sCAAwC;IACxC,kCAA0C;IAG3C;;;;;OAKG;IACH,2BAHW,MAAM,GACL,MAAM,CASjB;IAED;;;;;;;;;OASG;IACH,uCAuKC;IAED;;;;;;OAMG;IACH,uCAJW,MAAM,iBAEJ,MAAM,CAsBlB;IAED;;;;;;;;;;;;;;OAcG;IACH,iDAFa;QAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QAAC,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QAAC,SAAS,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,YAAY,EAAE,OAAO,CAAA;KAAC,CAsP7H;IAED;;;;OAIG;IACH,8CAOC;IAED;;;;;;OAMG;IACH,iCAJW,MAAM,OAiBhB;IAED;;;;;;;OAOG;IACH,mCALW,MAAM,MAAO,cACb,MAAM,OAkBhB;IAED;;;;OAIG;IACH,8BAFa,OAAO,CAenB;IAED,2FAyDC;IAED;;;;;;;OAOG;IACH,kEA8hBC;IAED;;;;;OAKG;IACH,kCAHW,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,WACnB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,QAmC7B;IAED;;;;;;;;;OASG;IACH,2GAGC;IAED,0DAsGC;IAED;;;;;;OAMG;IACH,gBAJW,GAAG,GAEF,OAAO,CAclB;IAED;;;;;;OAMG;IACH,0CAJW,GAAG,GAEF,GAAG,CA8Dd;CACD;;+BAGU,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC"}
|