pict-section-formeditor 1.0.6 → 1.0.7

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.
@@ -0,0 +1,345 @@
1
+ const libPict = require('pict');
2
+ const libPictApplication = require('pict-application');
3
+ const libPictSectionFormEditor = require('../../source/Pict-Section-FormEditor.js');
4
+
5
+ // The list of available manifests for the selector
6
+ const _ManifestList =
7
+ [
8
+ { Name: 'New Form', File: false },
9
+ { Name: 'Patient Intake (large)', File: 'manifests/Patient-Intake.json' },
10
+ { Name: 'Project Proposal (large)', File: 'manifests/Project-Proposal.json' },
11
+ { Name: 'Complex Table', File: 'manifests/Complex-Table.json' },
12
+ { Name: 'Manyfest Editor', File: 'manifests/Manyfest-Editor.json' },
13
+ { Name: 'Simple Form', File: 'manifests/Simple-Form.json' },
14
+ { Name: 'Gradebook - Student', File: 'manifests/Gradebook-Student.json' },
15
+ { Name: 'Gradebook - Assignment', File: 'manifests/Gradebook-Assignment.json' },
16
+ { Name: 'Distill (Entity Bundles)', File: 'manifests/Distill-Example.json' }
17
+ ];
18
+
19
+ class FormEditorFlexExampleApplication extends libPictApplication
20
+ {
21
+ constructor(pFable, pOptions, pServiceHash)
22
+ {
23
+ super(pFable, pOptions, pServiceHash);
24
+
25
+ this._ManifestList = _ManifestList;
26
+ this._FormEditorView = null;
27
+ this._DragDropEnabled = false;
28
+ this._ShowHashes = false;
29
+ }
30
+
31
+ onAfterInitializeAsync(fCallback)
32
+ {
33
+ // Start with an empty manifest; the first sample will be loaded after render
34
+ this.pict.AppData.FormConfig =
35
+ {
36
+ Scope: 'NewForm',
37
+ Sections: [],
38
+ Descriptors: {}
39
+ };
40
+
41
+ // Store the manifest list for the selector template
42
+ this.pict.AppData.ManifestList = this._ManifestList;
43
+
44
+ // Override the default CSS to remove the fixed height and use natural flow
45
+ let tmpDefaultConfig = JSON.parse(JSON.stringify(libPictSectionFormEditor.default_configuration));
46
+
47
+ // Replace the fixed-height .pict-formeditor rule with a flow-friendly version
48
+ // The key change: remove `height: calc(100vh - 120px)` and `overflow: hidden`
49
+ // so the editor flows with the page. The properties panel gets `position: sticky`.
50
+ let tmpCSS = tmpDefaultConfig.CSS;
51
+
52
+ // Patch: .pict-formeditor — remove fixed height, allow natural flow
53
+ tmpCSS = tmpCSS.replace(
54
+ /\.pict-formeditor\s*\{[^}]*\}/,
55
+ `.pict-formeditor
56
+ {
57
+ position: relative;
58
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
59
+ font-size: 14px;
60
+ color: #3D3229;
61
+ background: #FDFCFA;
62
+ border: 1px solid #E8E3DA;
63
+ border-radius: 6px;
64
+ display: flex;
65
+ flex-direction: column;
66
+ }`
67
+ );
68
+
69
+ // Patch: .pict-fe-editor-layout — remove overflow:hidden and min-height:0
70
+ // so the two-column layout can grow naturally
71
+ tmpCSS = tmpCSS.replace(
72
+ /\.pict-fe-editor-layout\s*\{[^}]*\}/,
73
+ `.pict-fe-editor-layout
74
+ {
75
+ display: flex;
76
+ gap: 0;
77
+ flex: 1;
78
+ position: relative;
79
+ }`
80
+ );
81
+
82
+ // Patch: .pict-fe-editor-content — let it grow naturally
83
+ tmpCSS = tmpCSS.replace(
84
+ /\.pict-fe-editor-content\s*\{[^}]*\}/,
85
+ `.pict-fe-editor-content
86
+ {
87
+ flex: 1;
88
+ min-width: 300px;
89
+ display: flex;
90
+ flex-direction: column;
91
+ }`
92
+ );
93
+
94
+ // Patch: .pict-fe-tabcontent — remove min-height:0 and overflow:auto
95
+ // so content flows naturally instead of scrolling internally
96
+ tmpCSS = tmpCSS.replace(
97
+ /\.pict-fe-tabcontent\s*\{[^}]*\}/,
98
+ `.pict-fe-tabcontent
99
+ {
100
+ display: none;
101
+ padding: 16px;
102
+ flex: 1;
103
+ }`
104
+ );
105
+
106
+ // Add sticky properties panel CSS
107
+ tmpCSS += `
108
+
109
+ /* === Flex-height sticky panel overrides === */
110
+ .pict-fe-properties-panel-open
111
+ {
112
+ position: sticky;
113
+ top: 0;
114
+ align-self: flex-start;
115
+ max-height: 100vh;
116
+ overflow-y: auto;
117
+ }
118
+ `;
119
+
120
+ tmpDefaultConfig.CSS = tmpCSS;
121
+
122
+ // Add the FormEditor view with our patched configuration
123
+ this._FormEditorView = this.pict.addView('FormEditor',
124
+ Object.assign({}, tmpDefaultConfig,
125
+ {
126
+ ViewIdentifier: 'FormEditor',
127
+ ManifestDataAddress: 'AppData.FormConfig',
128
+ DefaultDestinationAddress: '#FormEditor-Container',
129
+ ActiveTab: 'visual',
130
+ Renderables:
131
+ [
132
+ {
133
+ RenderableHash: 'FormEditor-Container',
134
+ TemplateHash: 'FormEditor-Container-Template',
135
+ DestinationAddress: '#FormEditor-Container',
136
+ RenderMethod: 'replace'
137
+ }
138
+ ]
139
+ }), libPictSectionFormEditor);
140
+
141
+ this._FormEditorView.initialize();
142
+ this._FormEditorView.render();
143
+
144
+ // Wire up the import event
145
+ let tmpSelf = this;
146
+ this._FormEditorView.onImport = function(pManifests, pFileName)
147
+ {
148
+ tmpSelf._handleImportedManifests(pManifests, pFileName);
149
+ };
150
+
151
+ // Render the selector bar
152
+ this.renderSelector();
153
+
154
+ // Load the first file-based sample by default
155
+ let tmpDefaultIndex = 1;
156
+ let tmpSelect = document.getElementById('FormEditor-ManifestSelect');
157
+ if (tmpSelect)
158
+ {
159
+ tmpSelect.value = String(tmpDefaultIndex);
160
+ }
161
+ this.loadManifest(tmpDefaultIndex);
162
+
163
+ return super.onAfterInitializeAsync(fCallback);
164
+ }
165
+
166
+ renderSelector()
167
+ {
168
+ let tmpHTML = '';
169
+ tmpHTML += '<div class="pict-fe-selector-bar">';
170
+ tmpHTML += '<label class="pict-fe-selector-label" for="FormEditor-ManifestSelect">Load Configuration:</label>';
171
+ tmpHTML += '<select class="pict-fe-selector-select" id="FormEditor-ManifestSelect">';
172
+ for (let i = 0; i < this._ManifestList.length; i++)
173
+ {
174
+ tmpHTML += `<option value="${i}">${this._escapeHTML(this._ManifestList[i].Name)}</option>`;
175
+ }
176
+ tmpHTML += '</select>';
177
+ tmpHTML += `<button class="pict-fe-selector-btn" onclick="${this.pict.browserAddress}.PictApplication.loadSelectedManifest()">Load</button>`;
178
+ tmpHTML += `<button class="pict-fe-selector-btn" id="FormEditor-DragDropToggle" onclick="${this.pict.browserAddress}.PictApplication.toggleDragAndDrop()" style="margin-left:auto; background:#8A7F72;">Enable Drag &amp; Drop</button>`;
179
+ tmpHTML += `<button class="pict-fe-selector-btn" id="FormEditor-DisplayModeToggle" onclick="${this.pict.browserAddress}.PictApplication.toggleDisplayMode()" style="background:#8A7F72;">Show Hashes</button>`;
180
+ tmpHTML += '</div>';
181
+
182
+ this.pict.ContentAssignment.assignContent('#FormEditor-Selector', tmpHTML);
183
+ }
184
+
185
+ toggleDragAndDrop()
186
+ {
187
+ this._DragDropEnabled = !this._DragDropEnabled;
188
+
189
+ if (this._FormEditorView)
190
+ {
191
+ this._FormEditorView._DragDropProvider.setDragAndDropEnabled(this._DragDropEnabled);
192
+ }
193
+
194
+ let tmpToggleBtn = document.getElementById('FormEditor-DragDropToggle');
195
+ if (tmpToggleBtn)
196
+ {
197
+ tmpToggleBtn.textContent = this._DragDropEnabled ? 'Disable Drag & Drop' : 'Enable Drag & Drop';
198
+ tmpToggleBtn.style.background = this._DragDropEnabled ? '#E76F51' : '#8A7F72';
199
+ }
200
+ }
201
+
202
+ toggleDisplayMode()
203
+ {
204
+ this._ShowHashes = !this._ShowHashes;
205
+
206
+ if (this._FormEditorView)
207
+ {
208
+ this._FormEditorView._UtilitiesProvider.setInputDisplayMode(this._ShowHashes ? 'hash' : 'name');
209
+ }
210
+
211
+ let tmpToggleBtn = document.getElementById('FormEditor-DisplayModeToggle');
212
+ if (tmpToggleBtn)
213
+ {
214
+ tmpToggleBtn.textContent = this._ShowHashes ? 'Show Names' : 'Show Hashes';
215
+ tmpToggleBtn.style.background = this._ShowHashes ? '#5B6E5D' : '#8A7F72';
216
+ }
217
+ }
218
+
219
+ loadSelectedManifest()
220
+ {
221
+ let tmpSelect = document.getElementById('FormEditor-ManifestSelect');
222
+ if (tmpSelect)
223
+ {
224
+ this.loadManifest(parseInt(tmpSelect.value, 10));
225
+ }
226
+ }
227
+
228
+ loadManifest(pIndex)
229
+ {
230
+ if (pIndex < 0 || pIndex >= this._ManifestList.length)
231
+ {
232
+ return;
233
+ }
234
+
235
+ let tmpEntry = this._ManifestList[pIndex];
236
+
237
+ if (tmpEntry.ManifestData)
238
+ {
239
+ this.pict.AppData.FormConfig = tmpEntry.ManifestData;
240
+ this._refreshEditor();
241
+ return;
242
+ }
243
+
244
+ if (!tmpEntry.File)
245
+ {
246
+ this.pict.AppData.FormConfig =
247
+ {
248
+ Scope: 'NewForm',
249
+ Sections: [],
250
+ Descriptors: {}
251
+ };
252
+ this._refreshEditor();
253
+ return;
254
+ }
255
+
256
+ let tmpXHR = new XMLHttpRequest();
257
+ tmpXHR.open('GET', tmpEntry.File, true);
258
+ tmpXHR.onreadystatechange = () =>
259
+ {
260
+ if (tmpXHR.readyState === 4)
261
+ {
262
+ if (tmpXHR.status === 200)
263
+ {
264
+ try
265
+ {
266
+ this.pict.AppData.FormConfig = JSON.parse(tmpXHR.responseText);
267
+ this._refreshEditor();
268
+ }
269
+ catch (pError)
270
+ {
271
+ this.log.error(`Error parsing manifest JSON from ${tmpEntry.File}: ${pError.message}`);
272
+ }
273
+ }
274
+ else
275
+ {
276
+ this.log.error(`Error loading manifest from ${tmpEntry.File}: HTTP ${tmpXHR.status}`);
277
+ }
278
+ }
279
+ };
280
+ tmpXHR.send();
281
+ }
282
+
283
+ _handleImportedManifests(pManifests, pFileName)
284
+ {
285
+ let tmpManifestKeys = Object.keys(pManifests);
286
+ if (tmpManifestKeys.length <= 1)
287
+ {
288
+ return;
289
+ }
290
+
291
+ let tmpSourceLabel = pFileName.toLowerCase().endsWith('.json') ? 'JSON' : 'CSV';
292
+
293
+ for (let i = 1; i < tmpManifestKeys.length; i++)
294
+ {
295
+ let tmpKey = tmpManifestKeys[i];
296
+ let tmpFormName = pManifests[tmpKey].FormName || tmpKey;
297
+ let tmpEntry = { Name: `${tmpSourceLabel}: ${tmpFormName}`, File: false, ManifestData: pManifests[tmpKey] };
298
+ this._ManifestList.push(tmpEntry);
299
+ }
300
+
301
+ this.renderSelector();
302
+ }
303
+
304
+ loadManifestDirect(pManifestData)
305
+ {
306
+ this.pict.AppData.FormConfig = pManifestData;
307
+ this._refreshEditor();
308
+ }
309
+
310
+ _refreshEditor()
311
+ {
312
+ if (this._FormEditorView)
313
+ {
314
+ this._FormEditorView.render();
315
+ }
316
+ }
317
+
318
+ _escapeHTML(pString)
319
+ {
320
+ if (typeof pString !== 'string')
321
+ {
322
+ return '';
323
+ }
324
+ return pString
325
+ .replace(/&/g, '&amp;')
326
+ .replace(/</g, '&lt;')
327
+ .replace(/>/g, '&gt;')
328
+ .replace(/"/g, '&quot;');
329
+ }
330
+ }
331
+
332
+ module.exports = FormEditorFlexExampleApplication;
333
+
334
+ module.exports.default_configuration = (
335
+ {
336
+ Name: 'FormEditorFlexExample',
337
+ Hash: 'FormEditorFlexExample',
338
+ MainViewportViewIdentifier: 'FormEditor',
339
+ AutoSolveAfterInitialize: false,
340
+ AutoRenderMainViewportViewAfterInitialize: false,
341
+ pict_configuration:
342
+ {
343
+ Product: 'FormEditorFlexExample'
344
+ }
345
+ });
@@ -0,0 +1,65 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Form Editor - Flex Height Reference</title>
7
+ <!-- PICT Dynamic View CSS Container -->
8
+ <style id="PICT-CSS"></style>
9
+ <!-- Red Rock Mesa Theme — Flex Height Reference App -->
10
+ <style>
11
+ *, *::before, *::after { box-sizing: border-box; }
12
+ html { margin: 0; padding: 0; }
13
+ body { margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; background: #FAEDCD; color: #264653; }
14
+
15
+ /* --- Header Bar (sticky at top of page) --- */
16
+ .pict-example-header { position: sticky; top: 0; z-index: 100; display: flex; align-items: stretch; background: #264653; border-bottom: 3px solid #E76F51; }
17
+ .pict-example-badge { background: #E76F51; color: #fff; padding: 0.6rem 1rem; font-size: 0.7rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.1em; display: flex; align-items: center; gap: 0.5rem; }
18
+ .pict-example-badge svg { width: 14px; height: 14px; fill: #fff; flex-shrink: 0; }
19
+ .pict-example-app-name { padding: 0.6rem 1rem; color: #FAEDCD; font-size: 1.1rem; font-weight: 600; display: flex; align-items: center; }
20
+ .pict-example-module { margin-left: auto; padding: 0.6rem 1rem; color: #D4A373; font-size: 0.75rem; display: flex; align-items: center; letter-spacing: 0.03em; }
21
+
22
+ /* --- Content Area — flows naturally, no fixed height --- */
23
+ .pict-example-content { padding: 1.5rem; }
24
+
25
+ /* --- Selector Bar --- */
26
+ .pict-fe-selector-bar { display: flex; align-items: center; gap: 0.75rem; padding: 0.75rem 1rem; background: #fff; border: 1px solid #D4A373; border-radius: 6px; margin-bottom: 1rem; }
27
+ .pict-fe-selector-label { font-size: 0.85rem; font-weight: 600; color: #264653; white-space: nowrap; }
28
+ .pict-fe-selector-select { flex: 1; max-width: 320px; padding: 0.4rem 0.6rem; border: 1px solid #D4C4A8; border-radius: 4px; font-size: 0.9rem; color: #264653; background: #FFFCF7; }
29
+ .pict-fe-selector-select:focus { outline: none; border-color: #E76F51; box-shadow: 0 0 0 2px rgba(231,111,81,0.15); }
30
+ .pict-fe-selector-btn { padding: 0.4rem 1rem; border: none; border-radius: 4px; font-size: 0.85rem; font-weight: 600; color: #fff; background: #E76F51; cursor: pointer; white-space: nowrap; }
31
+ .pict-fe-selector-btn:hover { background: #C45A3E; }
32
+
33
+ /* --- Info Banner --- */
34
+ .pict-example-info { background: #fff; border: 1px solid #D4A373; border-radius: 6px; padding: 1rem 1.25rem; margin-bottom: 1rem; font-size: 0.85rem; line-height: 1.6; color: #3D3229; }
35
+ .pict-example-info h3 { margin: 0 0 0.5rem 0; font-size: 0.95rem; color: #9E6B47; }
36
+ .pict-example-info code { background: #F5F0E8; padding: 0.15em 0.4em; border-radius: 3px; font-size: 0.8rem; }
37
+ </style>
38
+ <script src="./pict.js" type="text/javascript"></script>
39
+ <script src="./codejar.js" type="text/javascript"></script>
40
+ <script type="text/javascript">Pict.safeOnDocumentReady(() => { Pict.safeLoadPictApplication(FormEditorFlexExample, 1)});</script>
41
+ </head>
42
+ <body>
43
+ <div class="pict-example-header">
44
+ <div class="pict-example-badge">
45
+ <svg viewBox="0 0 16 16"><polygon points="8,1 10,6 16,6 11,9.5 13,15 8,11.5 3,15 5,9.5 0,6 6,6"/></svg>
46
+ Pict Example
47
+ </div>
48
+ <div class="pict-example-app-name">Form Editor — Flex Height</div>
49
+ <div class="pict-example-module">pict-section-formeditor (sticky panel reference)</div>
50
+ </div>
51
+ <div class="pict-example-content">
52
+ <div class="pict-example-info">
53
+ <h3>Flex-Height Layout with Sticky Properties Panel</h3>
54
+ This reference app demonstrates the form editor embedded in a page that scrolls
55
+ naturally (no fixed viewport height). The properties panel on the right uses
56
+ <code>position: sticky</code> so it stays visible as you scroll through long
57
+ manifests. Load the <strong>Patient Intake</strong> or <strong>Project Proposal</strong>
58
+ manifests for large forms that exercise vertical scrolling.
59
+ </div>
60
+ <div id="FormEditor-Selector"></div>
61
+ <div id="FormEditor-Container"></div>
62
+ </div>
63
+ <script src="./form_editor_flex_example.js" type="text/javascript"></script>
64
+ </body>
65
+ </html>