pict-section-form 1.3.0 → 1.3.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pict-section-form",
3
- "version": "1.3.0",
4
- "description": "Pict dynamic form sections",
3
+ "version": "1.3.2",
4
+ "description": "Pict dynamic forms",
5
5
  "main": "source/Pict-Section-Form.js",
6
6
  "directories": {
7
7
  "test": "test"
@@ -38,7 +38,7 @@
38
38
  "browser-env": "^3.3.0",
39
39
  "eslint": "^9.39.2",
40
40
  "jquery": "^4.0.0",
41
- "pict": "^1.0.386",
41
+ "pict": "^1.0.387",
42
42
  "pict-application": "^1.0.34",
43
43
  "pict-docuserve": "^1.4.19",
44
44
  "pict-service-commandlineutility": "^1.0.20",
@@ -234,11 +234,9 @@ class DynamicTabularData extends libPictProvider
234
234
  return false;
235
235
  }
236
236
  )
237
- // We expect this view to be in the set.
238
- for (let i = 0; i < tmpViewsToRender.length; i++)
239
- {
240
- tmpViewsToRender[i].render();
241
- }
237
+ // We expect this view to be in the set. Re-rendering an entity-selector
238
+ // table clobbers its selected values in the model, so preserve them.
239
+ this._renderViewsPreservingRecordSet(tmpGroup.RecordSetAddress, tmpViewsToRender);
242
240
 
243
241
  // Rebuild any OTHER views whose DynamicColumns are sourced from this record set
244
242
  // (e.g. a "% Passing" table whose columns are generated from this "Products" table)
@@ -494,11 +492,9 @@ class DynamicTabularData extends libPictProvider
494
492
  return false;
495
493
  }
496
494
  )
497
- // We expect this view to be in the set.
498
- for (let i = 0; i < tmpViewsToRender.length; i++)
499
- {
500
- tmpViewsToRender[i].render();
501
- }
495
+ // We expect this view to be in the set. Re-rendering an entity-selector
496
+ // table clobbers its selected values in the model, so preserve them.
497
+ this._renderViewsPreservingRecordSet(tmpGroup.RecordSetAddress, tmpViewsToRender);
502
498
 
503
499
  // Rebuild any OTHER views whose DynamicColumns are sourced from this record set
504
500
  // (e.g. a "% Passing" table whose columns are generated from this "Products" table)
@@ -518,6 +514,57 @@ class DynamicTabularData extends libPictProvider
518
514
  }
519
515
  }
520
516
 
517
+ /**
518
+ * Render every view in pViewsToRender while preserving pRecordSetAddress's data in
519
+ * the model. Re-rendering a table that contains entity-selector cells (Material /
520
+ * Organization / Product / etc.) destroys and recreates their select2 widgets; the
521
+ * transient empty widget gets read back into the model and wipes the selected value
522
+ * AND its resolved __Record for EVERY row -- the "selected Source / Name disappears
523
+ * after adding or deleting a row" data-loss bug. Because it clobbers the saved
524
+ * FormData, the value is then also missing from the exported PDF. A render() must be
525
+ * model-non-destructive, so snapshot the record set before rendering and restore any
526
+ * cleared fields afterward. No-op for plain (non-entity) tables, where render never
527
+ * changes the model (snapshot == post-render state, restore is a no-op).
528
+ *
529
+ * @param {string} pRecordSetAddress - RecordSetAddress of the table being re-rendered.
530
+ * @param {Array<Object>} pViewsToRender - Views bound to that record set to render.
531
+ */
532
+ _renderViewsPreservingRecordSet(pRecordSetAddress, pViewsToRender)
533
+ {
534
+ if (!Array.isArray(pViewsToRender) || (pViewsToRender.length < 1))
535
+ {
536
+ return;
537
+ }
538
+ // Snapshot the record set (deep clone each row) from a view that owns it.
539
+ let tmpRecordSet = false;
540
+ let tmpSnapshot = false;
541
+ for (let i = 0; i < pViewsToRender.length; i++)
542
+ {
543
+ let tmpCandidate = pViewsToRender[i].sectionManifest.getValueByHash(pViewsToRender[i].getMarshalDestinationObject(), pRecordSetAddress);
544
+ if (Array.isArray(tmpCandidate))
545
+ {
546
+ tmpRecordSet = tmpCandidate;
547
+ tmpSnapshot = tmpCandidate.map((pRow) => (pRow && (typeof pRow === 'object')) ? JSON.parse(JSON.stringify(pRow)) : pRow);
548
+ break;
549
+ }
550
+ }
551
+ for (let i = 0; i < pViewsToRender.length; i++)
552
+ {
553
+ pViewsToRender[i].render();
554
+ }
555
+ // Restore any model fields a destructive re-render cleared (entity value + __Record).
556
+ if (Array.isArray(tmpRecordSet) && Array.isArray(tmpSnapshot))
557
+ {
558
+ for (let i = 0; i < tmpRecordSet.length; i++)
559
+ {
560
+ if (tmpRecordSet[i] && (typeof tmpRecordSet[i] === 'object') && tmpSnapshot[i] && (typeof tmpSnapshot[i] === 'object'))
561
+ {
562
+ Object.assign(tmpRecordSet[i], tmpSnapshot[i]);
563
+ }
564
+ }
565
+ }
566
+ }
567
+
521
568
  /**
522
569
  * Rebuild every OTHER section-form view whose DynamicColumns are sourced from
523
570
  * pSourceRecordSetAddress: re-resolve their generated columns and rebuild their
@@ -817,10 +864,9 @@ class DynamicTabularData extends libPictProvider
817
864
  }
818
865
  return false;
819
866
  });
820
- for (let i = 0; i < tmpViewsToRender.length; i++)
821
- {
822
- tmpViewsToRender[i].render();
823
- }
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);
824
870
 
825
871
  // Rebuild any OTHER views whose DynamicColumns are sourced from this record set
826
872
  // HERE, in the render phase -- BEFORE the marshal below -- so the column DOM is
@@ -289,7 +289,10 @@ class PictInputObjectEditor extends libPictSectionInputExtension
289
289
  {
290
290
  RenderableHash: 'ObjectEditor-Container',
291
291
  TemplateHash: 'ObjectEditor-Container-Template',
292
- DestinationAddress: tmpSlotID,
292
+ // pict-view resolves a renderable's destination from ContentDestinationAddress
293
+ // (see Pict-View.buildRenderOptions). A bare DestinationAddress is ignored, so the
294
+ // container template never painted into the slot and the tree had nowhere to mount.
295
+ ContentDestinationAddress: tmpSlotID,
293
296
  RenderMethod: 'replace'
294
297
  }
295
298
  ]
@@ -329,7 +332,10 @@ class PictInputObjectEditor extends libPictSectionInputExtension
329
332
 
330
333
  try
331
334
  {
332
- let tmpResult = tmpView.render();
335
+ // Render the container into the CURRENT slot explicitly. The host slot's RawHTMLID is
336
+ // regenerated on every form re-render, so a re-used view instance would otherwise paint into
337
+ // its original (now-removed) slot; passing the destination here re-points it every mount.
338
+ let tmpResult = tmpView.render('ObjectEditor-Container', tmpSlotID);
333
339
  if (tmpResult && typeof tmpResult.then === 'function')
334
340
  {
335
341
  tmpResult.then(
@@ -1423,7 +1423,12 @@ class TabularLayout extends libPictSectionGroupLayout
1423
1423
  }
1424
1424
  if (tmpEditingRight)
1425
1425
  {
1426
- tmpTemplate += `<th class="pict-row-label-spacer"></th>`;
1426
+ // Parity cell for the right-side editing-controls column. Tag it with the same
1427
+ // pict-tabular-editing-controls-header class as the left-controls header (below)
1428
+ // so host CSS sizes it like the controls column AND collapses it in lockstep when
1429
+ // that column is hidden -- otherwise it lingers as an empty phantom column and the
1430
+ // stacked-header rule's bottom border overshoots the data columns.
1431
+ tmpTemplate += `<th class="pict-row-label-spacer pict-tabular-editing-controls-header"></th>`;
1427
1432
  }
1428
1433
  tmpTemplate += tmpMetatemplateGenerator.getMetatemplateTemplateReference(pView, `-TabularTemplate-ExtraHeaderRow-Postfix`, `getGroup("${pGroup.GroupIndex}")`);
1429
1434
  }
@@ -1456,7 +1461,9 @@ class TabularLayout extends libPictSectionGroupLayout
1456
1461
  }
1457
1462
  if (tmpEditingRight)
1458
1463
  {
1459
- tmpColumnSelectRow += `<th class="pict-row-label-spacer"></th>`;
1464
+ // Parity cell for the right-side editing-controls column (mirrors the extra-header
1465
+ // row above) -- collapses with the column via pict-tabular-editing-controls-header.
1466
+ tmpColumnSelectRow += `<th class="pict-row-label-spacer pict-tabular-editing-controls-header"></th>`;
1460
1467
  }
1461
1468
  tmpColumnSelectRow += `</tr>`;
1462
1469
  tmpTemplate += tmpColumnSelectRow;
@@ -1647,6 +1654,8 @@ class TabularLayout extends libPictSectionGroupLayout
1647
1654
  {
1648
1655
  pView.rebuildCustomTemplate();
1649
1656
  pView.render();
1657
+ // Refill the freshly rebuilt table DOM in this same marshal cycle.
1658
+ this._refillAfterDynamicColumnRebuild(pView);
1650
1659
  }
1651
1660
  finally
1652
1661
  {
@@ -1666,6 +1675,8 @@ class TabularLayout extends libPictSectionGroupLayout
1666
1675
  try
1667
1676
  {
1668
1677
  pView.render();
1678
+ // Same-cycle refill: a label-only re-render still repaints the cell DOM.
1679
+ this._refillAfterDynamicColumnRebuild(pView);
1669
1680
  }
1670
1681
  finally
1671
1682
  {
@@ -1691,6 +1702,8 @@ class TabularLayout extends libPictSectionGroupLayout
1691
1702
  {
1692
1703
  pView.rebuildCustomTemplate();
1693
1704
  pView.render();
1705
+ // Refill the freshly rebuilt table DOM in this same marshal cycle.
1706
+ this._refillAfterDynamicColumnRebuild(pView);
1694
1707
  }
1695
1708
  finally
1696
1709
  {
@@ -1706,6 +1719,30 @@ class TabularLayout extends libPictSectionGroupLayout
1706
1719
  this._reapplyTabularSelectionHighlights(pView, pGroup);
1707
1720
  return true;
1708
1721
  }
1722
+
1723
+ /**
1724
+ * After a mid-marshal DynamicColumns rebuild/re-render, the freshly painted table DOM
1725
+ * is empty: the enclosing onMarshalToView already ran its marshalDataToForm BEFORE this
1726
+ * hook re-rendered, so the dynamic cells lost the values that pass had just written and
1727
+ * would only refill on the NEXT marshal -- the "% Passing table blanks on row add/delete
1728
+ * or Source/Name select until another action brings it back" bug. Re-marshal the view's
1729
+ * data into the new DOM here so the cells repopulate within the SAME cycle. This is safe
1730
+ * to call from inside onDataMarshalToForm: marshalDataToForm only writes model values into
1731
+ * the inputs and does NOT itself invoke onDataMarshalToForm, so it cannot recurse here.
1732
+ *
1733
+ * @param {Object} pView
1734
+ */
1735
+ _refillAfterDynamicColumnRebuild(pView)
1736
+ {
1737
+ try
1738
+ {
1739
+ this.pict.providers.Informary.marshalDataToForm(pView.getMarshalDestinationObject(), pView.formID, pView.sectionManifest);
1740
+ }
1741
+ catch (pError)
1742
+ {
1743
+ this.log.error(`Error refilling tabular dynamic-column cells after rebuild: ${pError}`);
1744
+ }
1745
+ }
1709
1746
  }
1710
1747
 
1711
1748
  module.exports = TabularLayout;