pict-section-form 1.3.3 → 1.3.5
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/package.json
CHANGED
|
@@ -330,7 +330,7 @@ class DynamicTabularData extends libPictProvider
|
|
|
330
330
|
// Render (source + dependent views) BEFORE solving so dependent DynamicColumns tables
|
|
331
331
|
// don't blank until the next edit, and the solve's DOM side effects survive. Matches
|
|
332
332
|
// the add/delete handlers' order.
|
|
333
|
-
this._repaintAfterRowReorder(tmpGroup);
|
|
333
|
+
this._repaintAfterRowReorder(pView, tmpGroup);
|
|
334
334
|
}
|
|
335
335
|
}
|
|
336
336
|
}
|
|
@@ -375,7 +375,7 @@ class DynamicTabularData extends libPictProvider
|
|
|
375
375
|
// (e.g. SetGroupVisibility hiding a validation message) act on the freshly rebuilt
|
|
376
376
|
// DOM and survive, and so dependent DynamicColumns tables don't blank until the next
|
|
377
377
|
// edit. Matches the add/delete handlers' order.
|
|
378
|
-
this._repaintAfterRowReorder(tmpGroup);
|
|
378
|
+
this._repaintAfterRowReorder(pView, tmpGroup);
|
|
379
379
|
}
|
|
380
380
|
}
|
|
381
381
|
}
|
|
@@ -425,7 +425,7 @@ class DynamicTabularData extends libPictProvider
|
|
|
425
425
|
// (e.g. SetGroupVisibility hiding a validation message) act on the freshly rebuilt
|
|
426
426
|
// DOM and survive, and so dependent DynamicColumns tables don't blank until the next
|
|
427
427
|
// edit. Matches the add/delete handlers' order.
|
|
428
|
-
this._repaintAfterRowReorder(tmpGroup);
|
|
428
|
+
this._repaintAfterRowReorder(pView, tmpGroup);
|
|
429
429
|
}
|
|
430
430
|
}
|
|
431
431
|
}
|
|
@@ -843,43 +843,34 @@ class DynamicTabularData extends libPictProvider
|
|
|
843
843
|
*
|
|
844
844
|
* @param {Object} pGroup - The reordered group (its RecordSetAddress drives dependents).
|
|
845
845
|
*/
|
|
846
|
-
_repaintAfterRowReorder(pGroup)
|
|
846
|
+
_repaintAfterRowReorder(pView, pGroup)
|
|
847
847
|
{
|
|
848
|
-
//
|
|
849
|
-
//
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
{
|
|
860
|
-
if (tmpGroupsToTest[i].RecordSetAddress == pGroup.RecordSetAddress)
|
|
861
|
-
{
|
|
862
|
-
return true;
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
return false;
|
|
866
|
-
});
|
|
867
|
-
// Re-rendering an entity-selector table clobbers its selected values in the
|
|
868
|
-
// model, so preserve them across the render.
|
|
869
|
-
this._renderViewsPreservingRecordSet(pGroup.RecordSetAddress, tmpViewsToRender);
|
|
870
|
-
|
|
871
|
-
// Rebuild any OTHER views whose DynamicColumns are sourced from this record set
|
|
872
|
-
// HERE, in the render phase -- BEFORE the marshal below -- so the column DOM is
|
|
873
|
-
// correct (and re-labeled to the new order) when the marshal fills it. Without
|
|
874
|
-
// this the dependent table is only rebuilt mid-marshal (onDataMarshalToForm),
|
|
875
|
-
// which renders AFTER the cells were filled and blanks them until the next edit.
|
|
848
|
+
// A reorder (move up/down/set-index) preserves the row COUNT -- the source table's DOM rows
|
|
849
|
+
// already exist; only which row holds which value changes. A full source-view render() would
|
|
850
|
+
// tear down and re-create every cell widget, and each entity-selector select2 re-init costs
|
|
851
|
+
// roughly O(document size), so on a large form that is SECONDS per move. Skip the source
|
|
852
|
+
// render and let the marshal below push the reordered model values into the existing DOM rows
|
|
853
|
+
// in place. (Add/delete change the row count and still render via their own paths.) Skipping
|
|
854
|
+
// the render also removes the need for the snapshot/restore in _renderViewsPreservingRecordSet:
|
|
855
|
+
// with no destructive render, the entity values are never clobbered.
|
|
856
|
+
|
|
857
|
+
// Dependent DynamicColumns views still need their generated column labels re-resolved for the
|
|
858
|
+
// new order (cheap -- those tables carry no entity widgets).
|
|
876
859
|
this._rebuildDependentDynamicColumnViews(pGroup.RecordSetAddress);
|
|
877
860
|
|
|
878
|
-
// Run the solver
|
|
861
|
+
// Run the solver, then marshal the model back out to the existing form DOM.
|
|
879
862
|
this.pict.providers.DynamicSolver.solveViews();
|
|
880
|
-
|
|
881
|
-
// We've re-rendered but we don't know what needs to be marshaled based on the solve that ran above so marshal everything
|
|
882
863
|
this.pict.views.PictFormMetacontroller.marshalFormSections();
|
|
864
|
+
|
|
865
|
+
// marshalFormSections updates simple cells in place, but a stateful widget (an entity
|
|
866
|
+
// selector's select2) will not switch to a reordered value whose <option> isn't the one it
|
|
867
|
+
// currently shows. Ask each input provider to repaint its display for the new row order:
|
|
868
|
+
// entity selectors force their select2 to the row's current value, everything else no-ops.
|
|
869
|
+
// Cheap (in-place value set, no widget teardown) versus the full re-render this replaced.
|
|
870
|
+
if (pView && (typeof pView.runInputProviderFunctions === 'function'))
|
|
871
|
+
{
|
|
872
|
+
pView.runInputProviderFunctions('onTabularRowReorderRepaint', null, null, this.fable.getUUID());
|
|
873
|
+
}
|
|
883
874
|
}
|
|
884
875
|
}
|
|
885
876
|
|
|
@@ -156,6 +156,45 @@ class PictInputExtensionProvider extends libPictProvider
|
|
|
156
156
|
return true;
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Repaint this input's rendered display from the model after a tabular row REORDER
|
|
161
|
+
* (move up/down/set-index), which preserves the row count but changes which row holds
|
|
162
|
+
* which value. A plain marshal updates simple inputs in place, but stateful widgets
|
|
163
|
+
* (e.g. an entity-selector's select2) will not switch to a value whose option isn't the
|
|
164
|
+
* one currently shown; those providers override this to force their display to the row's
|
|
165
|
+
* current value. Default is a no-op.
|
|
166
|
+
*
|
|
167
|
+
* @param {Object} pView - The view object.
|
|
168
|
+
* @param {Object} pGroup - The group definition object.
|
|
169
|
+
* @param {number} pRow - The row index.
|
|
170
|
+
* @param {Object} pInput - The input object.
|
|
171
|
+
* @param {any} pValue - The value to display.
|
|
172
|
+
* @param {string} pHTMLSelector - The HTML selector.
|
|
173
|
+
* @param {string} pTransactionGUID - The transaction GUID for the event dispatch.
|
|
174
|
+
* @returns {boolean} - Returns true.
|
|
175
|
+
*/
|
|
176
|
+
onTabularRowReorderRepaint(pView, pGroup, pRow, pInput, pValue, pHTMLSelector, pTransactionGUID)
|
|
177
|
+
{
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Tabular sibling of onTabularRowReorderRepaint. Default is a no-op.
|
|
183
|
+
*
|
|
184
|
+
* @param {Object} pView - The view object.
|
|
185
|
+
* @param {Object} pGroup - The group definition object.
|
|
186
|
+
* @param {Object} pInput - The input object.
|
|
187
|
+
* @param {any} pValue - The value to display.
|
|
188
|
+
* @param {string} pHTMLSelector - The HTML selector.
|
|
189
|
+
* @param {number} pRowIndex - The row index of the tabular data.
|
|
190
|
+
* @param {string} pTransactionGUID - The transaction GUID for the event dispatch.
|
|
191
|
+
* @returns {boolean} - Returns true.
|
|
192
|
+
*/
|
|
193
|
+
onTabularRowReorderRepaintTabular(pView, pGroup, pInput, pValue, pHTMLSelector, pRowIndex, pTransactionGUID)
|
|
194
|
+
{
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
|
|
159
198
|
/**
|
|
160
199
|
* Handles data requests for the Pict-Provider-InputExtension.
|
|
161
200
|
*
|
|
@@ -1389,15 +1389,17 @@ class TabularLayout extends libPictSectionGroupLayout
|
|
|
1389
1389
|
// Leading (non-data) columns that precede the data cells: row-select, row labels, left controls.
|
|
1390
1390
|
let tmpLeadingColumnCount = (tmpRowSelectionEnabled ? 1 : 0) + tmpRowLabels.length + (tmpEditingLeft ? 1 : 0);
|
|
1391
1391
|
|
|
1392
|
-
// When EditingControlsPosition isn't 'right' (the default),
|
|
1393
|
-
//
|
|
1394
|
-
//
|
|
1395
|
-
//
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1392
|
+
// When EditingControlsPosition isn't 'right' (the default), the right-side editing-
|
|
1393
|
+
// controls slots are suppressed PER GROUP by gating their emission on tmpEditingRight
|
|
1394
|
+
// below (the RowHeader-ExtraPostfix parity cell and the per-row Row-ExtraPostfix cell).
|
|
1395
|
+
//
|
|
1396
|
+
// We must NOT suppress by registering an empty view-specific override here:
|
|
1397
|
+
// getViewSpecificTemplateHash() is keyed by the view's formsTemplateSetPrefix, which is
|
|
1398
|
+
// shared by EVERY tabular group in the section. A single 'hidden'/'left' group registering
|
|
1399
|
+
// the empty override would shadow the default editing-controls template for sibling groups
|
|
1400
|
+
// that DO want controls ('right'), deleting their menu column from the DOM entirely. The
|
|
1401
|
+
// gate is the group's own EditingControlsPosition, so each group is independent and a
|
|
1402
|
+
// view-specific override (if a host registers one) still applies to the groups that emit.
|
|
1401
1403
|
|
|
1402
1404
|
// The chooser bar sits ABOVE the group's table container (a div can't live
|
|
1403
1405
|
// inside <table> without the browser foster-parenting it out anyway).
|
|
@@ -1542,7 +1544,12 @@ class TabularLayout extends libPictSectionGroupLayout
|
|
|
1542
1544
|
tmpDataColumnCount++;
|
|
1543
1545
|
}
|
|
1544
1546
|
|
|
1545
|
-
|
|
1547
|
+
// Right-side editing-controls header parity cell -- only when this group keeps its
|
|
1548
|
+
// controls on the right. Gated per group (matches the extra-header-row parity above).
|
|
1549
|
+
if (tmpEditingRight)
|
|
1550
|
+
{
|
|
1551
|
+
tmpTemplate += tmpMetatemplateGenerator.getMetatemplateTemplateReference(pView, `-TabularTemplate-RowHeader-ExtraPostfix`, `getGroup("${pGroup.GroupIndex}")`);
|
|
1552
|
+
}
|
|
1546
1553
|
tmpTemplate += tmpMetatemplateGenerator.getMetatemplateTemplateReference(pView, `-TabularTemplate-RowHeader-Postfix`, `getGroup("${pGroup.GroupIndex}")`);
|
|
1547
1554
|
|
|
1548
1555
|
// Warn on header alignment mismatches (data-column count must equal each extra-header
|
|
@@ -1584,7 +1591,13 @@ class TabularLayout extends libPictSectionGroupLayout
|
|
|
1584
1591
|
}
|
|
1585
1592
|
|
|
1586
1593
|
tmpTemplateSetVirtualRowTemplate += `\n\n{~T:${pGroup.SectionTabularRowTemplateHash}:Record~}\n`;
|
|
1587
|
-
|
|
1594
|
+
// Right-side editing-controls cell (del/up/down) -- only when this group keeps its
|
|
1595
|
+
// controls on the right. Gated per group so a sibling group's 'hidden'/'left' setting
|
|
1596
|
+
// can't suppress this group's column. 'left' emits its controls via {~TEC:~} above.
|
|
1597
|
+
if (tmpEditingRight)
|
|
1598
|
+
{
|
|
1599
|
+
tmpTemplateSetVirtualRowTemplate += tmpMetatemplateGenerator.getMetatemplateTemplateReferenceRaw(pView, `-TabularTemplate-Row-ExtraPostfix`, `Record`);
|
|
1600
|
+
}
|
|
1588
1601
|
tmpTemplateSetVirtualRowTemplate += tmpMetatemplateGenerator.getMetatemplateTemplateReference(pView, `-TabularTemplate-Row-Postfix`, `getGroup("${pGroup.GroupIndex}")`);
|
|
1589
1602
|
|
|
1590
1603
|
// This is a custom template expression
|
|
@@ -1687,4 +1687,132 @@ suite('PictSectionForm Tabular Features', () =>
|
|
|
1687
1687
|
}, fDone);
|
|
1688
1688
|
});
|
|
1689
1689
|
});
|
|
1690
|
+
|
|
1691
|
+
// ------------------------------------------------------------------------
|
|
1692
|
+
// Regression: editing controls are PER GROUP, not per section.
|
|
1693
|
+
// A 'hidden'/'left' tabular group used to register a view-shared empty
|
|
1694
|
+
// template override (keyed by formsTemplateSetPrefix, which every group in
|
|
1695
|
+
// the section shares), which deleted the right-side controls column of any
|
|
1696
|
+
// sibling 'right' group -- the column was absent from the DOM, not just
|
|
1697
|
+
// hidden. The fix gates the controls emission on the group's own
|
|
1698
|
+
// EditingControlsPosition instead of a shared override.
|
|
1699
|
+
// ------------------------------------------------------------------------
|
|
1700
|
+
suite('Editing controls position is per-group (no section-wide bleed)', () =>
|
|
1701
|
+
{
|
|
1702
|
+
function makeTwoGroupApplication(pGroupA, pGroupB)
|
|
1703
|
+
{
|
|
1704
|
+
let App = makeApplication(pGroupA);
|
|
1705
|
+
App.default_configuration.pict_configuration.DefaultFormManifest.Sections[0].Groups.push(JSON.parse(JSON.stringify(pGroupB)));
|
|
1706
|
+
return App;
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
test("A sibling 'hidden' group does not suppress a 'right' group's controls column", (fDone) =>
|
|
1710
|
+
{
|
|
1711
|
+
let App = makeTwoGroupApplication(
|
|
1712
|
+
{ Hash: 'Students', Layout: 'Tabular', RecordSetAddress: 'Students', RecordManifest: 'StudentEditor', EditingControlsPosition: 'hidden' },
|
|
1713
|
+
{ Hash: 'Grades', Layout: 'Tabular', RecordSetAddress: 'Grades', RecordManifest: 'GradeRowEditor', EditingControlsPosition: 'right' }
|
|
1714
|
+
);
|
|
1715
|
+
bootstrap(App, (_Pict) =>
|
|
1716
|
+
{
|
|
1717
|
+
let tmpView = _Pict.views['PictSectionForm-Class'];
|
|
1718
|
+
let tmpLayout = _Pict.providers['Pict-Layout-Tabular'];
|
|
1719
|
+
let tmpHidden = tmpView.sectionDefinition.Groups[0];
|
|
1720
|
+
let tmpRight = tmpView.sectionDefinition.Groups[1];
|
|
1721
|
+
|
|
1722
|
+
// Bake in section order -- the 'hidden' group first, which is what used to
|
|
1723
|
+
// poison the section by registering the view-shared empty override.
|
|
1724
|
+
let tmpHiddenTemplate = tmpLayout.generateGroupLayoutTemplate(tmpView, tmpHidden);
|
|
1725
|
+
let tmpRightTemplate = tmpLayout.generateGroupLayoutTemplate(tmpView, tmpRight);
|
|
1726
|
+
|
|
1727
|
+
// (1) ROOT-CAUSE GUARD: a hidden/left group must NOT register a section-shared
|
|
1728
|
+
// empty override at the view-specific prefix (this is what shadowed the default
|
|
1729
|
+
// controls template for every other group in the section).
|
|
1730
|
+
Expect(tmpView.checkViewSpecificTemplate('-TabularTemplate-Row-ExtraPostfix')).to.equal(false, 'no section-shared per-row controls override registered');
|
|
1731
|
+
Expect(tmpView.checkViewSpecificTemplate('-TabularTemplate-RowHeader-ExtraPostfix')).to.equal(false, 'no section-shared header controls override registered');
|
|
1732
|
+
|
|
1733
|
+
// (2) The 'hidden' group itself emits no right-side controls (header parity or row cell).
|
|
1734
|
+
Expect(tmpHidden.SectionTabularRowVirtualTemplateHash).to.be.a('string');
|
|
1735
|
+
let tmpHiddenRow = _Pict.TemplateProvider.templates[tmpHidden.SectionTabularRowVirtualTemplateHash];
|
|
1736
|
+
Expect(tmpHiddenTemplate).to.not.contain('-TabularTemplate-RowHeader-ExtraPostfix', 'hidden group emits no header controls cell');
|
|
1737
|
+
Expect(tmpHiddenRow).to.not.contain('-TabularTemplate-Row-ExtraPostfix', 'hidden group emits no per-row controls cell');
|
|
1738
|
+
|
|
1739
|
+
// (3) The 'right' sibling KEEPS its controls (header parity + per-row cell).
|
|
1740
|
+
Expect(tmpRight.SectionTabularRowVirtualTemplateHash).to.be.a('string');
|
|
1741
|
+
let tmpRightRow = _Pict.TemplateProvider.templates[tmpRight.SectionTabularRowVirtualTemplateHash];
|
|
1742
|
+
Expect(tmpRightTemplate).to.contain('-TabularTemplate-RowHeader-ExtraPostfix', 'right group keeps its header controls cell');
|
|
1743
|
+
Expect(tmpRightRow).to.contain('-TabularTemplate-Row-ExtraPostfix', 'right group keeps its per-row controls cell');
|
|
1744
|
+
|
|
1745
|
+
// (4) ...and that per-row reference resolves to the REAL del/up/down control
|
|
1746
|
+
// (the default template is intact and reachable, not an empty override).
|
|
1747
|
+
let tmpDefaultControl = _Pict.TemplateProvider.templates[tmpView.defaultTemplatePrefix + '-TabularTemplate-Row-ExtraPostfix'];
|
|
1748
|
+
Expect(tmpDefaultControl).to.contain('deleteDynamicTableRow', 'default row-controls template still wired to the delete/move handlers');
|
|
1749
|
+
}, fDone);
|
|
1750
|
+
});
|
|
1751
|
+
|
|
1752
|
+
test("A lone 'right' (default) group keeps its controls column", (fDone) =>
|
|
1753
|
+
{
|
|
1754
|
+
let App = makeApplication({ Hash: 'Students', Layout: 'Tabular', RecordSetAddress: 'Students', RecordManifest: 'StudentEditor' });
|
|
1755
|
+
bootstrap(App, (_Pict) =>
|
|
1756
|
+
{
|
|
1757
|
+
let tmpView = _Pict.views['PictSectionForm-Class'];
|
|
1758
|
+
let tmpLayout = _Pict.providers['Pict-Layout-Tabular'];
|
|
1759
|
+
let tmpGroup = tmpView.sectionDefinition.Groups[0];
|
|
1760
|
+
let tmpTemplate = tmpLayout.generateGroupLayoutTemplate(tmpView, tmpGroup);
|
|
1761
|
+
let tmpRow = _Pict.TemplateProvider.templates[tmpGroup.SectionTabularRowVirtualTemplateHash];
|
|
1762
|
+
Expect(tmpTemplate).to.contain('-TabularTemplate-RowHeader-ExtraPostfix', 'default (right) group has its header controls cell');
|
|
1763
|
+
Expect(tmpRow).to.contain('-TabularTemplate-Row-ExtraPostfix', 'default (right) group has its per-row controls cell');
|
|
1764
|
+
}, fDone);
|
|
1765
|
+
});
|
|
1766
|
+
});
|
|
1767
|
+
|
|
1768
|
+
suite('Row reorder performance (move skips the full source re-render)', () =>
|
|
1769
|
+
{
|
|
1770
|
+
test('moveDynamicTableRowUp reorders in place without full-rendering the source view, and repaints inputs via the hook', (fDone) =>
|
|
1771
|
+
{
|
|
1772
|
+
let App = makeApplication({
|
|
1773
|
+
Hash: 'Students',
|
|
1774
|
+
Layout: 'Tabular',
|
|
1775
|
+
RecordSetAddress: 'Students',
|
|
1776
|
+
RecordManifest: 'StudentEditor'
|
|
1777
|
+
});
|
|
1778
|
+
bootstrap(App, (_Pict) =>
|
|
1779
|
+
{
|
|
1780
|
+
let tmpView = _Pict.views['PictSectionForm-Class'];
|
|
1781
|
+
let tmpDTD = _Pict.providers.DynamicTabularData;
|
|
1782
|
+
|
|
1783
|
+
// A move must NOT go through the expensive full source-view render -- that path tears
|
|
1784
|
+
// down and re-initializes every cell widget (each entity-selector select2 re-init is
|
|
1785
|
+
// what made BatchSheetCombined take seconds per move)...
|
|
1786
|
+
let tmpFullRenderCalls = 0;
|
|
1787
|
+
let tmpOriginalRender = tmpDTD._renderViewsPreservingRecordSet;
|
|
1788
|
+
tmpDTD._renderViewsPreservingRecordSet = function (...pArgs)
|
|
1789
|
+
{
|
|
1790
|
+
tmpFullRenderCalls++;
|
|
1791
|
+
return tmpOriginalRender.apply(this, pArgs);
|
|
1792
|
+
};
|
|
1793
|
+
// ...and it MUST ask the input providers to repaint their display for the new order.
|
|
1794
|
+
let tmpRepaintHookCalls = 0;
|
|
1795
|
+
let tmpOriginalRun = tmpView.runInputProviderFunctions;
|
|
1796
|
+
tmpView.runInputProviderFunctions = function (pFunctionName)
|
|
1797
|
+
{
|
|
1798
|
+
if (pFunctionName === 'onTabularRowReorderRepaint') { tmpRepaintHookCalls++; }
|
|
1799
|
+
return tmpOriginalRun.apply(this, arguments);
|
|
1800
|
+
};
|
|
1801
|
+
|
|
1802
|
+
let tmpBefore = _Pict.AppData.Students.map((pRow) => pRow.StudentName);
|
|
1803
|
+
tmpDTD.moveDynamicTableRowUp(tmpView, 0, 1); // 'Bob' (index 1) moves up to index 0
|
|
1804
|
+
|
|
1805
|
+
let tmpAfter = _Pict.AppData.Students.map((pRow) => pRow.StudentName);
|
|
1806
|
+
|
|
1807
|
+
tmpDTD._renderViewsPreservingRecordSet = tmpOriginalRender;
|
|
1808
|
+
tmpView.runInputProviderFunctions = tmpOriginalRun;
|
|
1809
|
+
|
|
1810
|
+
Expect(tmpBefore[0]).to.equal('Alice', 'sanity: row 0 started as Alice');
|
|
1811
|
+
Expect(tmpAfter[0]).to.equal('Bob', 'moved row is now first in the model');
|
|
1812
|
+
Expect(tmpAfter[1]).to.equal('Alice', 'displaced row shifted down');
|
|
1813
|
+
Expect(tmpFullRenderCalls).to.equal(0, 'a reorder must not full-render the source view (the slow select2 teardown)');
|
|
1814
|
+
Expect(tmpRepaintHookCalls).to.equal(1, 'a reorder repaints inputs in place via the onTabularRowReorderRepaint hook');
|
|
1815
|
+
}, fDone);
|
|
1816
|
+
});
|
|
1817
|
+
});
|
|
1690
1818
|
});
|