@jinntec/fore 2.1.1 → 2.3.0

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.
Files changed (88) hide show
  1. package/README.md +2 -2
  2. package/dist/fore-dev.js +2 -2
  3. package/dist/fore-dev.js.map +1 -1
  4. package/dist/fore.js +2 -2
  5. package/dist/fore.js.map +1 -1
  6. package/index.js +4 -2
  7. package/package.json +10 -38
  8. package/resources/fore.css +263 -262
  9. package/resources/vars.css +8 -0
  10. package/src/DependencyNotifyingDomFacade.js +1 -0
  11. package/src/ForeElementMixin.js +243 -228
  12. package/src/actions/abstract-action.js +407 -398
  13. package/src/actions/fx-action.js +6 -6
  14. package/src/actions/fx-append.js +1 -1
  15. package/src/actions/fx-call.js +63 -62
  16. package/src/actions/fx-confirm.js +11 -11
  17. package/src/actions/fx-construct-done.js +4 -4
  18. package/src/actions/fx-copy.js +36 -36
  19. package/src/actions/fx-delete.js +77 -79
  20. package/src/actions/fx-dispatch.js +14 -14
  21. package/src/actions/fx-hide.js +15 -15
  22. package/src/actions/fx-insert.js +9 -12
  23. package/src/actions/fx-insertchild.js +88 -0
  24. package/src/actions/fx-load.js +188 -185
  25. package/src/actions/fx-message.js +18 -19
  26. package/src/actions/fx-refresh.js +10 -10
  27. package/src/actions/fx-reload.js +16 -14
  28. package/src/actions/fx-replace.js +0 -1
  29. package/src/actions/fx-reset.js +31 -31
  30. package/src/actions/fx-send.js +59 -52
  31. package/src/actions/fx-setattribute.js +48 -49
  32. package/src/actions/fx-setfocus.js +54 -58
  33. package/src/actions/fx-setvalue.js +85 -81
  34. package/src/actions/fx-show.js +11 -11
  35. package/src/actions/fx-toggle.js +2 -2
  36. package/src/actions/fx-toggleboolean.js +38 -39
  37. package/src/actions/fx-unmodified.js +27 -0
  38. package/src/actions/fx-update.js +6 -6
  39. package/src/dep_graph.js +1 -1
  40. package/src/drawdown.js +23 -30
  41. package/src/events.js +19 -19
  42. package/src/fore.js +143 -125
  43. package/src/functions/common-function.js +24 -25
  44. package/src/functions/fx-function.js +6 -99
  45. package/src/functions/fx-functionlib.js +56 -0
  46. package/src/functions/registerFunction.js +112 -0
  47. package/src/fx-bind.js +19 -24
  48. package/src/fx-connection.js +226 -0
  49. package/src/fx-fore.js +844 -815
  50. package/src/fx-header.js +4 -4
  51. package/src/fx-instance.js +25 -29
  52. package/src/fx-model.js +405 -380
  53. package/src/fx-submission.js +399 -397
  54. package/src/fx-var.js +13 -12
  55. package/src/getInScopeContext.js +102 -89
  56. package/src/json-util.js +24 -24
  57. package/src/lab/fore-component.js +48 -52
  58. package/src/lab/instance-inspector.js +13 -16
  59. package/src/modelitem.js +48 -10
  60. package/src/relevance.js +12 -16
  61. package/src/tools/adi.js +858 -812
  62. package/src/tools/fx-action-log.js +377 -295
  63. package/src/tools/fx-devtools.js +210 -210
  64. package/src/tools/fx-dom-inspector.js +123 -122
  65. package/src/tools/fx-json-instance.js +262 -253
  66. package/src/tools/fx-log-item.js +6 -11
  67. package/src/tools/fx-log-settings.js +358 -301
  68. package/src/tools/fx-minimap.js +186 -177
  69. package/src/tools/helpers.js +1 -1
  70. package/src/ui/abstract-control.js +73 -53
  71. package/src/ui/fx-case.js +59 -7
  72. package/src/ui/fx-container.js +13 -14
  73. package/src/ui/fx-control.js +572 -538
  74. package/src/ui/fx-dialog.js +3 -3
  75. package/src/ui/fx-droptarget.js +3 -4
  76. package/src/ui/fx-group.js +4 -2
  77. package/src/ui/fx-hint.js +3 -0
  78. package/src/ui/fx-inspector.js +5 -5
  79. package/src/ui/fx-items.js +11 -11
  80. package/src/ui/fx-output.js +10 -12
  81. package/src/ui/fx-repeat-attributes.js +23 -27
  82. package/src/ui/fx-repeat.js +347 -344
  83. package/src/ui/fx-repeatitem.js +75 -68
  84. package/src/ui/fx-switch.js +39 -14
  85. package/src/ui/fx-trigger.js +4 -4
  86. package/src/withDraggability.js +193 -191
  87. package/src/xpath-evaluation.js +969 -958
  88. package/src/xpath-util.js +161 -134
package/src/fx-fore.js CHANGED
@@ -1,11 +1,29 @@
1
- import {Fore} from './fore.js';
1
+ import { Fore } from './fore.js';
2
2
  import './fx-instance.js';
3
- import {FxModel} from './fx-model.js';
3
+ import { FxModel } from './fx-model.js';
4
4
  import '@jinntec/jinn-toast';
5
- import {evaluateXPathToBoolean, evaluateXPathToNodes, evaluateXPathToFirstNode, evaluateXPathToString} from './xpath-evaluation.js';
5
+ import {
6
+ evaluateXPathToBoolean,
7
+ evaluateXPathToNodes,
8
+ evaluateXPathToFirstNode,
9
+ evaluateXPathToString,
10
+ } from './xpath-evaluation.js';
6
11
  import getInScopeContext from './getInScopeContext.js';
7
- import {XPathUtil} from './xpath-util.js';
8
- import {FxRepeatAttributes} from './ui/fx-repeat-attributes.js';
12
+ import { XPathUtil } from './xpath-util.js';
13
+ import { FxRepeatAttributes } from './ui/fx-repeat-attributes.js';
14
+
15
+ /**
16
+ * Makes the dirty state of the form.
17
+ *
18
+ * Clean when there are no changes yet or all is submitted,
19
+ * Dirty when there are unsaved changes.
20
+ *
21
+ * We might need a 'saving' state later
22
+ */
23
+ const dirtyStates = {
24
+ CLEAN: 'clean',
25
+ DIRTY: 'dirty',
26
+ };
9
27
 
10
28
  /**
11
29
  * Main class for Fore.Outermost container element for each Fore application.
@@ -25,88 +43,92 @@ import {FxRepeatAttributes} from './ui/fx-repeat-attributes.js';
25
43
  * @ts-check
26
44
  */
27
45
  export class FxFore extends HTMLElement {
28
- static outermostHandler = null;
29
- static draggedItem = null;
30
-
31
- static get properties() {
32
- return {
33
- /**
34
- * ignore certain nodes for template expression search
35
- */
36
- ignoreExpressions:{
37
- type: String
38
- },
39
- /**
40
- * merge-partial
41
- */
42
- mergePartial:{
43
- type: Boolean
44
- },
45
- /**
46
- * Setting this marker attribute will refresh the UI in a lazy fashion just updating elements being
47
- * in viewport.
48
- *
49
- * this feature is still experimental and should be used with caution and extra testing
50
- */
51
- lazyRefresh: {
52
- type: Boolean,
53
- },
54
- model: {
55
- type: Object,
56
- },
57
- ready: {
58
- type: Boolean,
59
- },
60
- strict:{
61
- type: Boolean
62
- },
63
- /**
64
- *
65
- */
66
- validateOn: {
67
- type: String
68
- },
69
- version: {
70
- type: String
71
- }
72
- };
73
- }
74
-
75
- /**
76
- * attaches handlers for
77
- *
78
- * - `model-construct-done` to trigger the processing of the UI
79
- * - `message` - to display a message triggered by an fx-message action
80
- * - `error` - to display an error message
81
- * - 'compute-exception` - warn about circular dependencies in graph
82
- */
83
- constructor() {
84
- super();
85
- this.version = '[VI]Version: {version} - built on {date}[/VI]';
86
-
87
- this.model = {};
88
- this.inited=false;
89
- // this.addEventListener('model-construct-done', this._handleModelConstructDone);
90
- // todo: refactoring - these should rather go into connectedcallback
91
- this.addEventListener('message', this._displayMessage);
92
- // this.addEventListener('error', this._displayError);
93
- this.addEventListener('error', this._logError);
94
- this.addEventListener('warn', this._displayWarning);
95
- // this.addEventListener('log', this._logError);
96
- window.addEventListener('compute-exception', e => {
97
- console.error('circular dependency: ', e);
98
- });
99
-
100
- this.ready = false;
101
- this.storedTemplateExpressionByNode = new Map();
102
-
103
- // Stores the outer most action handler. If an action handler is already running, all
104
- // updates are included in that one
105
- this.outermostHandler = null;
106
-
107
- this.copiedElements = new WeakSet();
108
-
109
- const style = `
46
+ static outermostHandler = null;
47
+
48
+ static draggedItem = null;
49
+
50
+ static get properties() {
51
+ return {
52
+ /**
53
+ * ignore certain nodes for template expression search
54
+ */
55
+ ignoreExpressions: {
56
+ type: String,
57
+ },
58
+ /**
59
+ * merge-partial
60
+ */
61
+ mergePartial: {
62
+ type: Boolean,
63
+ },
64
+ /**
65
+ * Setting this marker attribute will refresh the UI in a lazy fashion just updating elements being
66
+ * in viewport.
67
+ *
68
+ * this feature is still experimental and should be used with caution and extra testing
69
+ */
70
+ lazyRefresh: {
71
+ type: Boolean,
72
+ },
73
+ model: {
74
+ type: Object,
75
+ },
76
+ ready: {
77
+ type: Boolean,
78
+ },
79
+ strict: {
80
+ type: Boolean,
81
+ },
82
+ /**
83
+ *
84
+ */
85
+ validateOn: {
86
+ type: String,
87
+ },
88
+ version: {
89
+ type: String,
90
+ },
91
+ };
92
+ }
93
+
94
+ /**
95
+ * attaches handlers for
96
+ *
97
+ * - `model-construct-done` to trigger the processing of the UI
98
+ * - `message` - to display a message triggered by an fx-message action
99
+ * - `error` - to display an error message
100
+ * - 'compute-exception` - warn about circular dependencies in graph
101
+ */
102
+ constructor() {
103
+ super();
104
+ this.version = '[VI]Version: {version} - built on {date}[/VI]';
105
+
106
+ this.model = {};
107
+ this.inited = false;
108
+ // this.addEventListener('model-construct-done', this._handleModelConstructDone);
109
+ // todo: refactoring - these should rather go into connectedcallback
110
+ this.addEventListener('message', this._displayMessage);
111
+ // this.addEventListener('error', this._displayError);
112
+ this.addEventListener('error', this._logError);
113
+ this.addEventListener('warn', this._displayWarning);
114
+ // this.addEventListener('log', this._logError);
115
+ window.addEventListener('compute-exception', e => {
116
+ console.error('circular dependency: ', e);
117
+ });
118
+
119
+ this.ready = false;
120
+ this.storedTemplateExpressionByNode = new Map();
121
+
122
+ // Stores the outer most action handler. If an action handler is already running, all
123
+ // updates are included in that one
124
+ this.outermostHandler = null;
125
+
126
+ this.copiedElements = new WeakSet();
127
+
128
+ this.dirtyState = dirtyStates.CLEAN;
129
+ this.showConfirmation = false;
130
+
131
+ const style = `
110
132
  :host {
111
133
  display: block;
112
134
  }
@@ -181,7 +203,7 @@ export class FxFore extends HTMLElement {
181
203
  }
182
204
  `;
183
205
 
184
- const html = `
206
+ const html = `
185
207
  <!-- <slot name="errors"></slot> -->
186
208
  <jinn-toast id="message" gravity="bottom" position="left"></jinn-toast>
187
209
  <jinn-toast id="sticky" gravity="bottom" position="left" duration="-1" close="true" data-class="sticky-message"></jinn-toast>
@@ -199,28 +221,30 @@ export class FxFore extends HTMLElement {
199
221
  <slot name="event"></slot>
200
222
  `;
201
223
 
202
- this.attachShadow({mode: 'open'});
203
- this.shadowRoot.innerHTML = `
224
+ this.attachShadow({ mode: 'open' });
225
+ this.shadowRoot.innerHTML = `
204
226
  <style>
205
227
  ${style}
206
228
  </style>
207
229
  ${html}
208
230
  `;
209
231
 
210
- this.toRefresh = [];
211
- this.initialRun = true;
212
- this._scanForNewTemplateExpressionsNextRefresh = false;
213
- this.repeatsFromAttributesCreated = false;
214
- this.validateOn = this.hasAttribute('validate-on') ? this.getAttribute('validate-on'):'update';
215
- // this.mergePartial = this.hasAttribute('merge-partial')? true:false;
216
- this.mergePartial = false;
217
- }
218
-
219
- connectedCallback() {
220
- this.style.visibility = 'hidden';
221
- console.time('init');
222
- this.strict = this.hasAttribute('strict') ? true : false;
223
- /*
232
+ this.toRefresh = [];
233
+ this.initialRun = true;
234
+ this._scanForNewTemplateExpressionsNextRefresh = false;
235
+ this.repeatsFromAttributesCreated = false;
236
+ this.validateOn = this.hasAttribute('validate-on')
237
+ ? this.getAttribute('validate-on')
238
+ : 'update';
239
+ // this.mergePartial = this.hasAttribute('merge-partial')? true:false;
240
+ this.mergePartial = false;
241
+ }
242
+
243
+ connectedCallback() {
244
+ this.style.visibility = 'hidden';
245
+ console.time('init');
246
+ this.strict = !!this.hasAttribute('strict');
247
+ /*
224
248
  document.addEventListener('ready', (e) =>{
225
249
  if(e.target !== this){
226
250
  // e.preventDefault();
@@ -238,199 +262,230 @@ export class FxFore extends HTMLElement {
238
262
  // e.stopImmediatePropagation();
239
263
  },true);
240
264
  */
241
- this.ignoreExpressions = this.hasAttribute('ignore-expressions') ? this.getAttribute('ignore-expressions'): null;
242
-
243
- this.lazyRefresh = this.hasAttribute('refresh-on-view');
244
- if (this.lazyRefresh) {
245
- const options = {
246
- root: null,
247
- rootMargin: '0px',
248
- threshold: 0.3,
249
- };
250
- this.intersectionObserver = new IntersectionObserver(this.handleIntersect, options);
251
- }
252
-
253
- this.src = this.hasAttribute('src') ? this.getAttribute('src') : null;
254
- if (this.src) {
255
- this._loadFromSrc();
256
- return;
257
- }
265
+ this.ignoreExpressions = this.hasAttribute('ignore-expressions')
266
+ ? this.getAttribute('ignore-expressions')
267
+ : null;
268
+
269
+ this.lazyRefresh = this.hasAttribute('refresh-on-view');
270
+ if (this.lazyRefresh) {
271
+ const options = {
272
+ root: null,
273
+ rootMargin: '0px',
274
+ threshold: 0.3,
275
+ };
276
+ this.intersectionObserver = new IntersectionObserver(this.handleIntersect, options);
277
+ }
258
278
 
259
- this._injectDevtools();
260
-
261
- const slot = this.shadowRoot.querySelector('slot#default');
262
- slot.addEventListener('slotchange', async event => {
263
- // preliminary addition for auto-conversion of non-prefixed element into prefixed elements. See fore.js
264
- console.log(`### <<<<< slotchange on '${this.id}' >>>>>`);
265
- if(this.inited) return;
266
- if(this.hasAttribute('convert')){
267
- this.replaceWith(Fore.copyDom(this));
268
- // Fore.copyDom(this);
269
- return;
270
- }
279
+ this.src = this.hasAttribute('src') ? this.getAttribute('src') : null;
280
+ if (this.src) {
281
+ this._loadFromSrc();
282
+ return;
283
+ }
271
284
 
272
- if(this.ignoreExpressions){
273
- this.ignoredNodes = Array.from(this.querySelectorAll(this.ignoreExpressions));
274
- }
285
+ this._injectDevtools();
286
+
287
+ const slot = this.shadowRoot.querySelector('slot#default');
288
+ slot.addEventListener('slotchange', async event => {
289
+ // preliminary addition for auto-conversion of non-prefixed element into prefixed elements. See fore.js
290
+ // console.log(`### <<<<< slotchange on '${this.id}' >>>>>`);
291
+ if (this.inited) return;
292
+ if (this.hasAttribute('convert')) {
293
+ this.replaceWith(Fore.copyDom(this));
294
+ // Fore.copyDom(this);
295
+ return;
296
+ }
297
+
298
+ if (this.ignoreExpressions) {
299
+ this.ignoredNodes = Array.from(this.querySelectorAll(this.ignoreExpressions));
300
+ }
301
+
302
+ const children = event.target.assignedElements();
303
+ let modelElement = children.find(
304
+ modelElem => modelElem.nodeName.toUpperCase() === 'FX-MODEL',
305
+ );
306
+ if (!modelElement) {
307
+ const generatedModel = document.createElement('fx-model');
308
+ this.appendChild(generatedModel);
309
+ modelElement = generatedModel;
310
+ // We are going to get a new slotchange event immediately, because we changed a slot.
311
+ // so cancel this one.
312
+ return;
313
+ }
314
+ if (!modelElement.inited) {
315
+ console.info(
316
+ `%cFore is processing fx-fore#${this.id}`,
317
+ 'background:#64b5f6; color:white; padding:.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;',
318
+ );
275
319
 
276
- const children = event.target.assignedElements();
277
- let modelElement = children.find(
278
- modelElem => modelElem.nodeName.toUpperCase() === 'FX-MODEL',
279
- );
280
- if (!modelElement) {
281
- const generatedModel = document.createElement('fx-model');
282
- this.appendChild(generatedModel);
283
- modelElement = generatedModel;
284
- // We are going to get a new slotchange event immediately, because we changed a slot.
285
- // so cancel this one.
286
- return;
287
- }
288
- if (!modelElement.inited) {
289
- console.info(
290
- `%cFore is processing fx-fore#${this.id}`,
291
- "background:#64b5f6; color:white; padding:.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;",
292
- );
293
-
294
- await modelElement.modelConstruct();
295
- this._handleModelConstructDone();
320
+ const variables = new Map();
321
+ (function registerVariables(node) {
322
+ for (const child of node.children) {
323
+ if ('setInScopeVariables' in child) {
324
+ child.setInScopeVariables(variables);
296
325
  }
297
- this.model = modelElement;
298
-
299
- this._createRepeatsFromAttributes();
300
- this.inited = true;
301
-
302
- });
303
- this.addEventListener('path-mutated', () => {
304
- this.someInstanceDataStructureChanged = true;
305
- });
306
- this.addEventListener('refresh', () => {
307
- this.refresh(true);
308
- });
326
+ registerVariables(child);
327
+ }
328
+ })(this);
309
329
 
330
+ await modelElement.modelConstruct();
331
+ this._handleModelConstructDone();
332
+ }
333
+ this.model = modelElement;
334
+
335
+ this._createRepeatsFromAttributes();
336
+ this.inited = true;
337
+ });
338
+ this.addEventListener('path-mutated', () => {
339
+ this.someInstanceDataStructureChanged = true;
340
+ });
341
+ this.addEventListener('refresh', () => {
342
+ this.refresh(true);
343
+ });
344
+
345
+ if (this.hasAttribute('show-confirmation')) {
346
+ this.showConfirmation = true;
310
347
  }
348
+ }
311
349
 
312
- _injectDevtools(){
313
- if (this.ownerDocument.querySelector('fx-devtools')) {
314
- // There's already a devtools, so we can ignore this one.
315
- // One devtools can focus multiple fore elements
316
- return;
317
- }
318
- const search = window.location.search;
319
- const urlParams = new URLSearchParams(search);
320
- if(urlParams.has('inspect')){
321
- const devtools = document.createElement('fx-devtools');
322
- document.body.appendChild(devtools);
323
- }
350
+ _injectDevtools() {
351
+ if (this.ownerDocument.querySelector('fx-devtools')) {
352
+ // There's already a devtools, so we can ignore this one.
353
+ // One devtools can focus multiple fore elements
354
+ return;
324
355
  }
325
- addToRefresh(modelItem) {
326
- const found = this.toRefresh.find(mi => mi.path === modelItem.path);
327
- if (!found) {
328
- this.toRefresh.push(modelItem);
329
- }
356
+ const { search } = window.location;
357
+ const urlParams = new URLSearchParams(search);
358
+ if (urlParams.has('inspect')) {
359
+ const devtools = document.createElement('fx-devtools');
360
+ document.body.appendChild(devtools);
330
361
  }
331
-
332
- /**
333
- * Raise a flag that there might be new template expressions under some node. This happens with
334
- * repeats updating (new repeat items can have new template expressions) or switches changing their case (new case = new raw HTML)
335
- */
336
- scanForNewTemplateExpressionsNextRefresh() {
337
- // TODO: also ask for the root of any new HTML: this can prevent some very deep queries.
338
- this._scanForNewTemplateExpressionsNextRefresh = true;
339
- }
340
-
341
- /**
342
- * loads a Fore from an URL given by `src`.
343
- *
344
- * Will extract the `fx-fore` element from that target file and use and replace current `fx-fore` element with the loaded one.
345
- * @private
346
- */
347
- async _loadFromSrc() {
348
- // console.log('########## loading Fore from ', this.src, '##########');
349
- await Fore.loadForeFromSrc(this,this.src,'fx-fore');
362
+ }
363
+
364
+ /**
365
+ * Add a model item to the refresh list
366
+ *
367
+ * @param {import('./modelitem.js').ModelItem} modelItem
368
+ * @returns {void}
369
+ */
370
+ addToRefresh(modelItem) {
371
+ const found = this.toRefresh.find(mi => mi.path === modelItem.path);
372
+ if (!found) {
373
+ this.toRefresh.push(modelItem);
350
374
  }
375
+ }
376
+
377
+ /**
378
+ * Raise a flag that there might be new template expressions under some node. This happens with
379
+ * repeats updating (new repeat items can have new template expressions) or switches changing their case (new case = new raw HTML)
380
+ */
381
+ scanForNewTemplateExpressionsNextRefresh() {
382
+ // TODO: also ask for the root of any new HTML: this can prevent some very deep queries.
383
+ this._scanForNewTemplateExpressionsNextRefresh = true;
384
+ }
385
+
386
+ markAsClean() {
387
+ this.addEventListener(
388
+ 'value-changed',
389
+ () => {
390
+ this.dirtyState = dirtyStates.DIRTY;
391
+ },
392
+ { once: true },
393
+ );
394
+ this.dirtyState = dirtyStates.CLEAN;
395
+ }
396
+
397
+ /**
398
+ * loads a Fore from an URL given by `src`.
399
+ *
400
+ * Will extract the `fx-fore` element from that target file and use and replace current `fx-fore` element with the loaded one.
401
+ * @private
402
+ */
403
+ async _loadFromSrc() {
404
+ // console.log('########## loading Fore from ', this.src, '##########');
405
+ await Fore.loadForeFromSrc(this, this.src, 'fx-fore');
406
+ }
407
+
408
+ /**
409
+ * refreshes the UI by using IntersectionObserver API. This is the handler being called
410
+ * by the observer whenever elements come into / move out of viewport.
411
+ * @param entries
412
+ * @param observer
413
+ */
414
+ handleIntersect(entries, observer) {
415
+ // console.time('refreshLazy');
416
+
417
+ entries.forEach(entry => {
418
+ const { target } = entry;
419
+
420
+ const fore = Fore.getFore(target);
421
+ if (fore.initialRun) return;
422
+
423
+ if (entry.isIntersecting) {
424
+ // console.log('in view', entry);
425
+ // console.log('repeat in view entry', entry.target);
426
+ // const target = entry.target;
427
+ // if(target.hasAttribute('refresh-on-view')){
428
+ target.classList.add('loaded');
429
+ // }
430
+
431
+ // todo: too restrictive here? what if target is a usual html element? shouldn't it refresh downwards?
432
+ if (typeof target.refresh === 'function') {
433
+ // console.log('refreshing target', target);
434
+ target.refresh(target, true);
435
+ } else {
436
+ // console.log('refreshing children', target);
437
+ Fore.refreshChildren(target, true);
438
+ }
439
+ }
440
+ });
441
+ entries[0].target.getOwnerForm().dispatchEvent(new CustomEvent('refresh-done'));
351
442
 
352
- /**
353
- * refreshes the UI by using IntersectionObserver API. This is the handler being called
354
- * by the observer whenever elements come into / move out of viewport.
355
- * @param entries
356
- * @param observer
357
- */
358
- handleIntersect(entries, observer) {
359
- // console.time('refreshLazy');
360
-
361
- entries.forEach(entry => {
362
- const {target} = entry;
363
-
364
- const fore = Fore.getFore(target);
365
- if(fore.initialRun) return;
366
-
367
- if (entry.isIntersecting) {
368
- // console.log('in view', entry);
369
- // console.log('repeat in view entry', entry.target);
370
- // const target = entry.target;
371
- // if(target.hasAttribute('refresh-on-view')){
372
- target.classList.add('loaded');
373
- // }
374
-
375
- // todo: too restrictive here? what if target is a usual html element? shouldn't it refresh downwards?
376
- if (typeof target.refresh === 'function') {
377
- // console.log('refreshing target', target);
378
- target.refresh(target, true);
379
- } else {
380
- // console.log('refreshing children', target);
381
- Fore.refreshChildren(target, true);
382
- }
383
- }
384
- });
385
- entries[0].target.getOwnerForm().dispatchEvent(new CustomEvent('refresh-done'));
386
-
387
- // console.timeEnd('refreshLazy');
388
- }
443
+ // console.timeEnd('refreshLazy');
444
+ }
389
445
 
390
- evaluateToNodes(xpath, context) {
391
- return evaluateXPathToNodes(xpath, context, this);
392
- }
446
+ evaluateToNodes(xpath, context) {
447
+ return evaluateXPathToNodes(xpath, context, this);
448
+ }
393
449
 
394
- disconnectedCallback() {
395
- this.removeEventListener('dragstart', this.dragstart);
396
- /*
450
+ disconnectedCallback() {
451
+ this.removeEventListener('dragstart', this.dragstart);
452
+ /*
397
453
  this.removeEventListener('model-construct-done', this._handleModelConstructDone);
398
454
  this.removeEventListener('message', this._displayMessage);
399
455
  this.removeEventListener('error', this._displayError);
400
456
  this.storedTemplateExpressionByNode=null;
401
457
  this.shadowRoot = undefined;
402
458
  */
403
- }
404
-
405
- /**
406
- * refreshes the whole UI by visiting each bound element (having a 'ref' attribute) and applying the state of
407
- * the bound modelItem to the bound element.
408
- *
409
- *
410
- * force - boolean - if true will refresh all children disregarding toRefresh array
411
- *
412
- */
413
- async forceRefresh() {
414
- // console.time('refresh');
415
- // console.group('### forced refresh', this);
416
-
417
- Fore.refreshChildren(this, true);
418
- this._updateTemplateExpressions();
419
- this._scanForNewTemplateExpressionsNextRefresh = false; // reset
420
- this._processTemplateExpressions();
421
-
422
- console.log(`### <<<<< refresh-done ${this.id} >>>>>`);
423
-
424
- Fore.dispatch(this, 'refresh-done', {});
425
-
426
- // console.groupEnd();
427
- // console.timeEnd('refresh');
428
- }
429
-
430
- // async refresh(force, changedPaths) {
431
- async refresh(force) {
432
-
433
- /*
459
+ }
460
+
461
+ /**
462
+ * refreshes the whole UI by visiting each bound element (having a 'ref' attribute) and applying the state of
463
+ * the bound modelItem to the bound element.
464
+ *
465
+ *
466
+ * force - boolean - if true will refresh all children disregarding toRefresh array
467
+ *
468
+ */
469
+ async forceRefresh() {
470
+ // console.time('refresh');
471
+ // console.group('### forced refresh', this);
472
+
473
+ Fore.refreshChildren(this, true);
474
+ this._updateTemplateExpressions();
475
+ this._scanForNewTemplateExpressionsNextRefresh = false; // reset
476
+ this._processTemplateExpressions();
477
+
478
+ // console.log(`### <<<<< refresh-done ${this.id} >>>>>`);
479
+
480
+ Fore.dispatch(this, 'refresh-done', {});
481
+
482
+ // console.groupEnd();
483
+ // console.timeEnd('refresh');
484
+ }
485
+
486
+ // async refresh(force, changedPaths) {
487
+ async refresh(force) {
488
+ /*
434
489
 
435
490
  if (!changedPaths) {
436
491
  changedPaths = this.toRefresh.map(item => item.path);
@@ -465,89 +520,89 @@ export class FxFore extends HTMLElement {
465
520
  }
466
521
  }
467
522
  */
468
- if (this.isRefreshing) {
469
- return;
470
- }
471
- this.isRefreshing = true;
472
- console.log(`### <<<<< refresh() on '${this.id}' >>>>>`);
473
-
474
- // refresh () {
475
- // ### refresh Fore UI elements
476
- // if (!this.initialRun && this.toRefresh.length !== 0) {
477
- if (!force && !this.initialRun && this.toRefresh.length !== 0) {
478
- // console.log('toRefresh', this.toRefresh);
479
- let needsRefresh = false;
480
-
481
- // ### after recalculation the changed modelItems are copied to 'toRefresh' array for processing
482
- this.toRefresh.forEach(modelItem => {
483
- // check if modelItem has boundControls - if so, call refresh() for each of them
484
- const controlsToRefresh = modelItem.boundControls;
485
- if (controlsToRefresh) {
486
- controlsToRefresh.forEach(ctrl => {
487
- ctrl.refresh(force);
488
- });
489
- }
523
+ if (this.isRefreshing) {
524
+ return;
525
+ }
526
+ this.isRefreshing = true;
527
+ // console.log(`### <<<<< refresh() on '${this.id}' >>>>>`);
528
+
529
+ // refresh () {
530
+ // ### refresh Fore UI elements
531
+ // if (!this.initialRun && this.toRefresh.length !== 0) {
532
+ if (!force && !this.initialRun && this.toRefresh.length !== 0) {
533
+ // console.log('toRefresh', this.toRefresh);
534
+ let needsRefresh = false;
535
+
536
+ // ### after recalculation the changed modelItems are copied to 'toRefresh' array for processing
537
+ this.toRefresh.forEach(modelItem => {
538
+ // check if modelItem has boundControls - if so, call refresh() for each of them
539
+ const controlsToRefresh = modelItem.boundControls;
540
+ if (controlsToRefresh) {
541
+ controlsToRefresh.forEach(ctrl => {
542
+ ctrl.refresh(force);
543
+ });
544
+ }
490
545
 
491
- // ### check if other controls depend on current modelItem
492
- const { mainGraph } = this.getModel();
493
- if (mainGraph && mainGraph.hasNode(modelItem.path)) {
494
- const deps = this.getModel().mainGraph.dependentsOf(modelItem.path, false);
495
- // ### iterate dependant modelItems and refresh all their boundControls
496
- if (deps.length !== 0) {
497
- deps.forEach(dep => {
498
- // ### if changed modelItem has a 'facet' path we use the basePath that is the locationPath without facet name
499
- const basePath = XPathUtil.getBasePath(dep);
500
- const modelItemOfDep = this.getModel().modelItems.find(mip => mip.path === basePath);
501
- // ### refresh all boundControls
502
- modelItemOfDep.boundControls.forEach(control => {
503
- control.refresh(force);
504
- });
505
- });
506
- needsRefresh = true;
507
- }
508
- }
546
+ // ### check if other controls depend on current modelItem
547
+ const { mainGraph } = this.getModel();
548
+ if (mainGraph && mainGraph.hasNode(modelItem.path)) {
549
+ const deps = this.getModel().mainGraph.dependentsOf(modelItem.path, false);
550
+ // ### iterate dependant modelItems and refresh all their boundControls
551
+ if (deps.length !== 0) {
552
+ deps.forEach(dep => {
553
+ // ### if changed modelItem has a 'facet' path we use the basePath that is the locationPath without facet name
554
+ const basePath = XPathUtil.getBasePath(dep);
555
+ const modelItemOfDep = this.getModel().modelItems.find(mip => mip.path === basePath);
556
+ // ### refresh all boundControls
557
+ modelItemOfDep.boundControls.forEach(control => {
558
+ control.refresh(force);
559
+ });
509
560
  });
510
- this.toRefresh = [];
511
- /*
561
+ needsRefresh = true;
562
+ }
563
+ }
564
+ });
565
+ this.toRefresh = [];
566
+ /*
512
567
  if (!needsRefresh) {
513
568
  console.log('no dependants to refresh');
514
569
  }
515
570
  */
516
- } else {
517
- // ### resetting visited state for controls to refresh
518
- /*
571
+ } else {
572
+ // ### resetting visited state for controls to refresh
573
+ /*
519
574
  const visited = this.parentNode.querySelectorAll('.visited');
520
575
  Array.from(visited).forEach(v =>{
521
576
  v.classList.remove('visited');
522
577
  });
523
578
  */
524
579
 
525
- if(this.inited){
526
- Fore.refreshChildren(this, true);
527
- }
528
- // console.timeEnd('refreshChildren');
529
- }
580
+ if (this.inited) {
581
+ Fore.refreshChildren(this, true);
582
+ }
583
+ // console.timeEnd('refreshChildren');
584
+ }
530
585
 
531
- // ### refresh template expressions
532
- if (this.initialRun || this._scanForNewTemplateExpressionsNextRefresh) {
533
- this._updateTemplateExpressions();
534
- this._scanForNewTemplateExpressionsNextRefresh = false; // reset
535
- }
586
+ // ### refresh template expressions
587
+ if (this.initialRun || this._scanForNewTemplateExpressionsNextRefresh) {
588
+ this._updateTemplateExpressions();
589
+ this._scanForNewTemplateExpressionsNextRefresh = false; // reset
590
+ }
536
591
 
537
- this._processTemplateExpressions();
592
+ this._processTemplateExpressions();
538
593
 
539
- // console.log('### <<<<< dispatching refresh-done - end of UI update cycle >>>>>');
540
- // this.dispatchEvent(new CustomEvent('refresh-done'));
541
- // this.initialRun = false;
542
- this.style.visibility='visible';
543
- console.log(`### <<<<< refresh-done ${this.id} >>>>>`);
544
- Fore.dispatch(this, 'refresh-done', {});
594
+ // console.log('### <<<<< dispatching refresh-done - end of UI update cycle >>>>>');
595
+ // this.dispatchEvent(new CustomEvent('refresh-done'));
596
+ // this.initialRun = false;
597
+ this.style.visibility = 'visible';
598
+ console.log(`### <<<<< refresh-done ${this.id} >>>>>`);
599
+ Fore.dispatch(this, 'refresh-done', {});
545
600
 
546
- // this.isRefreshing = true;
547
- // this.parentNode.closest('fx-fore')?.refresh(false, changedPaths);
601
+ // this.isRefreshing = true;
602
+ // this.parentNode.closest('fx-fore')?.refresh(false, changedPaths);
548
603
 
549
- const subFores = Array.from(this.querySelectorAll('fx-fore'));
550
- /*
604
+ const subFores = Array.from(this.querySelectorAll('fx-fore'));
605
+ /*
551
606
  calling the parent to refresh causes errors and inconsistent state. Also it is questionable
552
607
  if a child should actually interact with its parent in this way.
553
608
 
@@ -562,293 +617,283 @@ export class FxFore extends HTMLElement {
562
617
  // await this.parentNode.closest('fx-fore')?.refresh(false);
563
618
  }
564
619
  */
565
- for (const subFore of subFores) {
566
- // subFore.refresh(false, changedPaths);
567
- if(subFore.ready){
568
- await subFore.refresh(false);
569
- }
570
- }
571
- this.isRefreshing = false;
620
+ for (const subFore of subFores) {
621
+ // subFore.refresh(false, changedPaths);
622
+ if (subFore.ready) {
623
+ await subFore.refresh(false);
624
+ }
625
+ }
626
+ this.isRefreshing = false;
627
+ }
628
+
629
+ /**
630
+ * entry point for processing of template expression enclosed in '{}' brackets.
631
+ *
632
+ * Expressions are found with an XPath search. For each node an entry is added to the storedTemplateExpressionByNode map.
633
+ *
634
+ *
635
+ * @private
636
+ */
637
+ _updateTemplateExpressions() {
638
+ const search =
639
+ "(descendant-or-self::*!(text(), @*))[contains(., '{')][substring-after(., '{') => contains('}')][not(ancestor-or-self::fx-model)]";
640
+
641
+ const tmplExpressions = evaluateXPathToNodes(search, this, this);
642
+ // console.log('template expressions found ', tmplExpressions);
643
+
644
+ if (!this.storedTemplateExpressions) {
645
+ this.storedTemplateExpressions = [];
572
646
  }
573
647
 
574
- /**
575
- * entry point for processing of template expression enclosed in '{}' brackets.
576
- *
577
- * Expressions are found with an XPath search. For each node an entry is added to the storedTemplateExpressionByNode map.
578
- *
579
- *
580
- * @private
581
- */
582
- _updateTemplateExpressions() {
583
- const search =
584
- "(descendant-or-self::*!(text(), @*))[contains(., '{')][substring-after(., '{') => contains('}')][not(ancestor-or-self::fx-model)]";
585
-
586
- const tmplExpressions = evaluateXPathToNodes(search, this, this);
587
- // console.log('template expressions found ', tmplExpressions);
588
-
589
- if (!this.storedTemplateExpressions) {
590
- this.storedTemplateExpressions = [];
591
- }
592
-
593
- // console.log('######### storedTemplateExpressions', this.storedTemplateExpressions.length);
648
+ // console.log('######### storedTemplateExpressions', this.storedTemplateExpressions.length);
594
649
 
595
- /*
650
+ /*
596
651
  storing expressions and their nodes for re-evaluation
597
652
  */
598
- Array.from(tmplExpressions).forEach(node => {
599
- const ele = node.nodeType === Node.ATTRIBUTE_NODE ?
600
- node.ownerElement :
601
- node.parentNode;
602
- if (ele.closest('fx-fore') !== this) {
603
- // We found something in a sub-fore. Act like it's not there
604
- return;
605
- }
606
- if (this.storedTemplateExpressionByNode.has(node)) {
607
- // If the node is already known, do not process it twice
608
- return;
609
- }
610
- const expr = this._getTemplateExpression(node);
611
-
612
- // console.log('storedTemplateExpressionByNode', this.storedTemplateExpressionByNode);
613
- if(expr){
614
- this.storedTemplateExpressionByNode.set(node, expr);
615
- }
616
- });
617
- // console.log('stored template expressions ', this.storedTemplateExpressionByNode);
618
-
619
- // TODO: Should we clean up nodes that existed but are now gone?
620
- this._processTemplateExpressions();
621
- }
622
-
623
- _processTemplateExpressions() {
624
- for (const node of Array.from(this.storedTemplateExpressionByNode.keys())) {
625
- if (node.nodeType === Node.ATTRIBUTE_NODE) {
626
- // Attribute nodes are not contained by the document, but their owner elements are!
627
- if (!XPathUtil.contains(this, node.ownerElement)) {
628
- this.storedTemplateExpressionByNode.delete(node);
629
- continue;
630
- }
631
- } else if (!XPathUtil.contains(this, node)) {
632
- // For all other nodes, if this `fore` element does not contain them, they are dead
633
- this.storedTemplateExpressionByNode.delete(node);
634
- continue;
635
- }
636
- this._processTemplateExpression({
637
- node,
638
- expr: this.storedTemplateExpressionByNode.get(node),
639
- });
653
+ Array.from(tmplExpressions).forEach(node => {
654
+ const ele = node.nodeType === Node.ATTRIBUTE_NODE ? node.ownerElement : node.parentNode;
655
+ if (ele.closest('fx-fore') !== this) {
656
+ // We found something in a sub-fore. Act like it's not there
657
+ return;
658
+ }
659
+ if (this.storedTemplateExpressionByNode.has(node)) {
660
+ // If the node is already known, do not process it twice
661
+ return;
662
+ }
663
+ const expr = this._getTemplateExpression(node);
664
+
665
+ // console.log('storedTemplateExpressionByNode', this.storedTemplateExpressionByNode);
666
+ if (expr) {
667
+ this.storedTemplateExpressionByNode.set(node, expr);
668
+ }
669
+ });
670
+ // console.log('stored template expressions ', this.storedTemplateExpressionByNode);
671
+
672
+ // TODO: Should we clean up nodes that existed but are now gone?
673
+ this._processTemplateExpressions();
674
+ }
675
+
676
+ _processTemplateExpressions() {
677
+ for (const node of Array.from(this.storedTemplateExpressionByNode.keys())) {
678
+ if (node.nodeType === Node.ATTRIBUTE_NODE) {
679
+ // Attribute nodes are not contained by the document, but their owner elements are!
680
+ if (!XPathUtil.contains(this, node.ownerElement)) {
681
+ this.storedTemplateExpressionByNode.delete(node);
682
+ continue;
640
683
  }
684
+ } else if (!XPathUtil.contains(this, node)) {
685
+ // For all other nodes, if this `fore` element does not contain them, they are dead
686
+ this.storedTemplateExpressionByNode.delete(node);
687
+ continue;
688
+ }
689
+ this._processTemplateExpression({
690
+ node,
691
+ expr: this.storedTemplateExpressionByNode.get(node),
692
+ });
641
693
  }
642
-
643
- // eslint-disable-next-line class-methods-use-this
644
- _processTemplateExpression(exprObj) {
645
- // console.log('processing template expression ', exprObj);
646
-
647
- const {expr} = exprObj;
648
- const {node} = exprObj;
649
- // console.log('expr ', expr);
650
- this.evaluateTemplateExpression(expr, node);
694
+ }
695
+
696
+ // eslint-disable-next-line class-methods-use-this
697
+ _processTemplateExpression(exprObj) {
698
+ // console.log('processing template expression ', exprObj);
699
+
700
+ const { expr } = exprObj;
701
+ const { node } = exprObj;
702
+ // console.log('expr ', expr);
703
+ this.evaluateTemplateExpression(expr, node);
704
+ }
705
+
706
+ /**
707
+ * evaluate a template expression on a node either text- or attribute node.
708
+ * @param {string} expr The string to parse for expressions
709
+ * @param {Node} node the node which will get updated with evaluation result
710
+ */
711
+ evaluateTemplateExpression(expr, node) {
712
+ // ### do not evaluate template expressions with nonrelevant sections
713
+ if (node.nodeType === Node.ATTRIBUTE_NODE && node.ownerElement.closest('[nonrelevant]')) return;
714
+ if (node.nodeType === Node.TEXT_NODE && node.parentNode.closest('[nonrelevant]')) return;
715
+ if (node.nodeType === Node.ELEMENT_NODE && node.closest('[nonrelevant]')) return;
716
+
717
+ // if(node.closest('[nonrelevant]')) return;
718
+ const replaced = expr.replace(/{[^}]*}/g, match => {
719
+ if (match === '{}') return match;
720
+ const naked = match.substring(1, match.length - 1);
721
+ const inscope = getInScopeContext(node, naked);
722
+ if (!inscope) {
723
+ console.warn('no inscope context for expr', naked);
724
+ const errNode =
725
+ node.nodeType === Node.TEXT_NODE || node.nodeType === Node.ATTRIBUTE_NODE
726
+ ? node.parentNode
727
+ : node;
728
+ return match;
729
+ }
730
+ // Templates are special: they use the namespace configuration from the place where they are
731
+ // being defined
732
+ const instanceId = XPathUtil.getInstanceId(naked);
733
+
734
+ // If there is an instance referred
735
+ const inst = instanceId
736
+ ? this.getModel().getInstance(instanceId)
737
+ : this.getModel().getDefaultInstance();
738
+
739
+ try {
740
+ return evaluateXPathToString(naked, inscope, node, null, inst);
741
+ } catch (error) {
742
+ console.warn('ignoring unparseable expr', error);
743
+
744
+ return match;
745
+ }
746
+ });
747
+
748
+ // Update to the new value. Don't do it though if nothing changed to prevent iframes or
749
+ // images from reloading for example
750
+ if (node.nodeType === Node.ATTRIBUTE_NODE) {
751
+ const parent = node.ownerElement;
752
+ if (parent.getAttribute(node.nodeName) !== replaced) {
753
+ parent.setAttribute(node.nodeName, replaced);
754
+ }
755
+ } else if (node.nodeType === Node.TEXT_NODE) {
756
+ if (node.textContent !== replaced) {
757
+ node.textContent = replaced;
758
+ }
651
759
  }
652
-
653
- /**
654
- * evaluate a template expression on a node either text- or attribute node.
655
- * @param expr The string to parse for expressions
656
- * @param node the node which will get updated with evaluation result
657
- */
658
- evaluateTemplateExpression(expr, node) {
659
-
660
- // ### do not evaluate template expressions with nonrelevant sections
661
- if(node.nodeType === Node.ATTRIBUTE_NODE && node.ownerElement.closest('[nonrelevant]')) return;
662
- if(node.nodeType === Node.TEXT_NODE && node.parentNode.closest('[nonrelevant]')) return;
663
- if(node.nodeType === Node.ELEMENT_NODE && node.closest('[nonrelevant]')) return;
664
-
665
- // if(node.closest('[nonrelevant]')) return;
666
- const replaced = expr.replace(/{[^}]*}/g, match => {
667
- if (match === '{}') return match;
668
- const naked = match.substring(1, match.length - 1);
669
- const inscope = getInScopeContext(node, naked);
670
- if (!inscope) {
671
- console.warn('no inscope context for expr', naked);
672
- const errNode =
673
- node.nodeType === Node.TEXT_NODE || node.nodeType === Node.ATTRIBUTE_NODE
674
- ? node.parentNode
675
- : node;
676
- return match;
677
- }
678
- // Templates are special: they use the namespace configuration from the place where they are
679
- // being defined
680
- const instanceId = XPathUtil.getInstanceId(naked);
681
-
682
- // If there is an instance referred
683
- const inst = instanceId ? this.getModel().getInstance(instanceId) : this.getModel().getDefaultInstance();
684
-
685
- try {
686
- return evaluateXPathToString(naked, inscope, node, null, inst);
687
- } catch (error) {
688
- console.warn('ignoring unparseable expr', error);
689
-
690
- return match;
691
- }
692
- });
693
-
694
- // Update to the new value. Don't do it though if nothing changed to prevent iframes or
695
- // images from reloading for example
696
- if (node.nodeType === Node.ATTRIBUTE_NODE) {
697
- const parent = node.ownerElement;
698
- if (parent.getAttribute(node.nodeName) !== replaced) {
699
- parent.setAttribute(node.nodeName, replaced);
700
- }
701
- } else if (node.nodeType === Node.TEXT_NODE) {
702
- if (node.textContent !== replaced) {
703
- node.textContent = replaced;
704
- }
705
- }
760
+ }
761
+
762
+ // eslint-disable-next-line class-methods-use-this
763
+ _getTemplateExpression(node) {
764
+ if (this.ignoredNodes) {
765
+ if (node.nodeType === Node.ATTRIBUTE_NODE) {
766
+ node = node.ownerElement;
767
+ }
768
+ const found = this.ignoredNodes.find(n => n.contains(node));
769
+ if (found) return null;
706
770
  }
707
-
708
- // eslint-disable-next-line class-methods-use-this
709
- _getTemplateExpression(node) {
710
- if(this.ignoredNodes){
711
- if(node.nodeType === Node.ATTRIBUTE_NODE){
712
- node = node.ownerElement;
713
- }
714
- const found = this.ignoredNodes.find( (n) => n.contains(node));
715
- if(found) return null;
716
- }
717
- if (node.nodeType === Node.ATTRIBUTE_NODE) {
718
- return node.value;
719
- }
720
- if (node.nodeType === Node.TEXT_NODE) {
721
- return node.textContent.trim();
722
- }
723
- return null;
771
+ if (node.nodeType === Node.ATTRIBUTE_NODE) {
772
+ return node.value;
724
773
  }
725
-
726
- /**
727
- * called when `model-construct-done` event is received to
728
- * start initing the UI.
729
- *
730
- * @private
731
- */
732
- _handleModelConstructDone() {
733
- /*
734
- listening on beforeunload after model is constructed - this is to be able to evaluate a condition on the data
735
- that specifies whether or not to show confirmation.
736
- */
737
- if(this.hasAttribute('show-confirmation')){
738
- const condition = this.getAttribute('show-confirmation');
739
- if(condition
740
- && condition !== 'show-confirmation'
741
- && condition !== 'true'
742
- && condition !== ''){
743
- window.addEventListener('beforeunload', event => {
744
- const mustDisplay = evaluateXPathToBoolean(condition, this.getModel().getDefaultContext(), this)
745
- if(mustDisplay){
746
- return event.returnValue = 'are you sure';
747
- }
748
- event.preventDefault();
749
- })
750
- }else{
751
- window.addEventListener('beforeunload', event => {
752
- // if(AbstractAction.dataChanged){
753
- if(FxModel.dataChanged){
754
- return event.returnValue = 'are you sure';
755
- }
756
- event.preventDefault();
757
- })
758
- }
759
- }
760
-
761
- this._initUI();
774
+ if (node.nodeType === Node.TEXT_NODE) {
775
+ return node.textContent.trim();
762
776
  }
763
-
764
- /**
765
- * If there's no instance element found in a fx-model during init it will construct
766
- * an instance from UI bindings.
767
- *
768
- * @returns {Promise<void>}
769
- * @private
770
- */
771
- async _lazyCreateInstance() {
772
- const model = this.querySelector('fx-model');
773
-
774
- // ##### lazy creation should NOT take place if there's a parent Fore using shared instances
775
- const parentFore = this.parentNode.nodeType !== Node.DOCUMENT_FRAGMENT_NODE ? this.parentNode.closest('fx-fore'): null;
776
- if(this.parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE){
777
- console.log('fragment',this.parentNode)
778
- }
779
-
780
- if(parentFore){
781
- const shared = parentFore.getModel().instances.filter(shared => shared.hasAttribute('shared'));
782
- if(shared.length !==0) return;
783
- }
784
-
785
- // still need to catch just in case...
786
- try{
787
- if (model.instances.length === 0) {
788
- // console.log('### lazy creation of instance');
789
- const generatedInstance = document.createElement('fx-instance');
790
- model.appendChild(generatedInstance);
791
-
792
- const generated = document.implementation.createDocument(null, 'data', null);
793
- // const newData = this._generateInstance(this, generated.firstElementChild);
794
- this._generateInstance(this, generated.firstElementChild);
795
- generatedInstance.instanceData = generated;
796
- model.instances.push(generatedInstance);
797
- // console.log('generatedInstance ', this.getModel().getDefaultInstanceData());
798
- Fore.dispatch(this,'instance-loaded',{instance:this});
799
- }
800
- }catch (e) {
801
- console.warn('lazyCreateInstance created an error attempting to create a document', e.message);
777
+ return null;
778
+ }
779
+
780
+ /**
781
+ * called when `model-construct-done` event is received to
782
+ * start initing the UI.
783
+ *
784
+ * @private
785
+ */
786
+ _handleModelConstructDone() {
787
+ this.markAsClean();
788
+
789
+ if (this.showConfirmation) {
790
+ window.addEventListener('beforeunload', event => {
791
+ if (this.dirtyState === dirtyStates.DIRTY) {
792
+ event.preventDefault();
793
+ return true;
802
794
  }
795
+ return false;
796
+ });
797
+ }
798
+ this._initUI();
799
+ }
800
+
801
+ /**
802
+ * If there's no instance element found in a fx-model during init it will construct
803
+ * an instance from UI bindings.
804
+ *
805
+ * @returns {Promise<void>}
806
+ * @private
807
+ */
808
+ async _lazyCreateInstance() {
809
+ const model = this.querySelector('fx-model');
810
+
811
+ // ##### lazy creation should NOT take place if there's a parent Fore using shared instances
812
+ const parentFore =
813
+ this.parentNode.nodeType !== Node.DOCUMENT_FRAGMENT_NODE
814
+ ? this.parentNode.closest('fx-fore')
815
+ : null;
816
+ if (this.parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
817
+ console.log('fragment', this.parentNode);
803
818
  }
804
819
 
805
- /**
806
- * @param {Element} start
807
- * @param {Element} parent
808
- */
809
- _generateInstance(start, parent) {
810
- if (start.hasAttribute('ref') && !Fore.isActionElement(start.nodeName)) {
811
- const ref = start.getAttribute('ref');
812
-
813
- if (ref.includes('/')) {
814
- // console.log('complex path to create ', ref);
815
- const steps = ref.split('/');
816
- steps.forEach(step => {
817
- // const generated = document.createElement(ref);
818
- parent = this._generateNode(parent, step, start);
819
- });
820
- } else {
821
- parent = this._generateNode(parent, ref, start);
822
- }
823
- }
820
+ if (parentFore) {
821
+ const shared = parentFore
822
+ .getModel()
823
+ .instances.filter(shared => shared.hasAttribute('shared'));
824
+ if (shared.length !== 0) return;
825
+ }
824
826
 
825
- if (start.hasChildNodes()) {
826
- const list = start.children;
827
- for (let i = 0; i < list.length; i += 1) {
828
- this._generateInstance(list[i], parent);
829
- }
830
- }
831
- return parent;
827
+ // still need to catch just in case...
828
+ try {
829
+ if (model.instances.length === 0) {
830
+ // console.log('### lazy creation of instance');
831
+ const generatedInstance = document.createElement('fx-instance');
832
+ model.appendChild(generatedInstance);
833
+
834
+ const generated = document.implementation.createDocument(null, 'data', null);
835
+ // const newData = this._generateInstance(this, generated.firstElementChild);
836
+ this._generateInstance(this, generated.firstElementChild);
837
+ generatedInstance.instanceData = generated;
838
+ model.instances.push(generatedInstance);
839
+ // console.log('generatedInstance ', this.getModel().getDefaultInstanceData());
840
+ Fore.dispatch(this, 'instance-loaded', { instance: this });
841
+ }
842
+ } catch (e) {
843
+ console.warn(
844
+ 'lazyCreateInstance created an error attempting to create a document',
845
+ e.message,
846
+ );
847
+ }
848
+ }
849
+
850
+ /**
851
+ * @param {Element} start
852
+ * @param {Element} parent
853
+ */
854
+ _generateInstance(start, parent) {
855
+ if (start.hasAttribute('ref') && !Fore.isActionElement(start.nodeName)) {
856
+ const ref = start.getAttribute('ref');
857
+
858
+ if (ref.includes('/')) {
859
+ // console.log('complex path to create ', ref);
860
+ const steps = ref.split('/');
861
+ steps.forEach(step => {
862
+ // const generated = document.createElement(ref);
863
+ parent = this._generateNode(parent, step, start);
864
+ });
865
+ } else {
866
+ parent = this._generateNode(parent, ref, start);
867
+ }
832
868
  }
833
869
 
834
- // eslint-disable-next-line class-methods-use-this
835
- _generateNode(parent, step, start) {
836
- const generated = parent.ownerDocument.createElement(step);
837
- if (start.children.length === 0) {
838
- generated.textContent = start.textContent;
839
- }
840
- parent.appendChild(generated);
841
- parent = generated;
842
- return parent;
870
+ if (start.hasChildNodes()) {
871
+ const list = start.children;
872
+ for (let i = 0; i < list.length; i += 1) {
873
+ this._generateInstance(list[i], parent);
874
+ }
875
+ }
876
+ return parent;
877
+ }
878
+
879
+ // eslint-disable-next-line class-methods-use-this
880
+ _generateNode(parent, step, start) {
881
+ const generated = parent.ownerDocument.createElement(step);
882
+ if (start.children.length === 0) {
883
+ generated.textContent = start.textContent;
843
884
  }
885
+ parent.appendChild(generated);
886
+ parent = generated;
887
+ return parent;
888
+ }
844
889
 
845
- /*
890
+ /*
846
891
  _createStep(){
847
892
 
848
893
  }
849
894
  */
850
895
 
851
- /*
896
+ /*
852
897
  _generateInstance(start, parent) {
853
898
  if (start.hasAttribute('ref')) {
854
899
  const ref = start.getAttribute('ref');
@@ -859,7 +904,6 @@ export class FxFore extends HTMLElement {
859
904
  steps.forEach(step => {
860
905
  console.log('step ', step);
861
906
 
862
-
863
907
  });
864
908
  }
865
909
 
@@ -882,37 +926,25 @@ export class FxFore extends HTMLElement {
882
926
  }
883
927
  */
884
928
 
885
- /**
886
- * Start the initialization of the UI by
887
- *
888
- * 1. checking if a instance needs to be generated
889
- * 2. attaching lazy loading intersection observers if `refresh-on-view` attributes are found
890
- * 3. doing a full refresh of the UI
891
- *
892
- * @returns {Promise<void>}
893
- * @private
894
- */
895
- async _initUI() {
896
- // console.log('### _initUI()');
897
- console.log(`### <<<<< _initUI '${this.id}' >>>>>`);
898
-
899
- if (!this.initialRun) return;
900
- this.classList.add('initialRun');
901
- await this._lazyCreateInstance();
902
-
903
- // console.log('registering variables!');
904
- const variables = new Map();
905
- (function registerVariables(node) {
906
- for (const child of node.children) {
907
- if ('setInScopeVariables' in child) {
908
- child.setInScopeVariables(variables);
909
- }
910
- registerVariables(child);
911
- }
912
- })(this);
913
- // console.log('Found variables:', variables);
929
+ /**
930
+ * Start the initialization of the UI by
931
+ *
932
+ * 1. checking if a instance needs to be generated
933
+ * 2. attaching lazy loading intersection observers if `refresh-on-view` attributes are found
934
+ * 3. doing a full refresh of the UI
935
+ *
936
+ * @returns {Promise<void>}
937
+ * @private
938
+ */
939
+ async _initUI() {
940
+ // console.log('### _initUI()');
941
+ console.log(`### <<<<< _initUI '${this.id}' >>>>>`);
942
+
943
+ if (!this.initialRun) return;
944
+ this.classList.add('initialRun');
945
+ await this._lazyCreateInstance();
914
946
 
915
- /*
947
+ /*
916
948
  const options = {
917
949
  root: null,
918
950
  rootMargin: '0px',
@@ -920,160 +952,158 @@ export class FxFore extends HTMLElement {
920
952
  };
921
953
  */
922
954
 
923
- // First refresh should be forced
924
- await this.refresh(true);
925
- // this.style.display='block'
926
- this.classList.add('fx-ready');
927
- document.body.classList.add('fx-ready');
928
-
929
- this.ready = true;
930
- this.initialRun = false;
931
- // console.log('### >>>>> dispatching ready >>>>>', this);
932
- console.log(`### <<<<< ${this.id} ready >>>>>`);
933
-
934
- // console.log('### modelItems: ', this.getModel().modelItems);
935
- Fore.dispatch(this, 'ready', {});
936
- // console.log('dataChanged', FxModel.dataChanged);
937
- console.timeEnd('init');
938
-
939
- this.addEventListener('dragstart', this._handleDragStart);
940
- // this.addEventListener('dragend', this._handleDragEnd);
941
- this.handleDrop = event => this._handleDrop(event);
942
- this.ownerDocument.body.addEventListener('drop', this.handleDrop);
943
- this.ownerDocument.body.addEventListener('dragover', e=>{
944
- e.preventDefault();
945
- e.stopPropagation();
946
- e.dataTransfer.dropEffect = "move";
947
- });
948
- }
949
-
950
- _handleDragStart (event) {
951
- const draggedItem = event.target.closest('[draggable="true"]');
952
- this.originalDraggedItem = draggedItem;
953
- console.log('DRAG START', this);
954
- if (draggedItem.getAttribute('drop-action') === 'copy') {
955
- event.dataTransfer.dropEffect = 'copy';
956
- event.dataTransfer.effectAllowed = 'copy';
957
- this.draggedItem = draggedItem.cloneNode(true);
958
- this.draggedItem.setAttribute('drop-action', 'move');
959
- this.copiedElements.add(this.draggedItem);
960
- } else {
961
- event.dataTransfer.dropEffect = 'move';
962
- event.dataTransfer.effectAllowed = 'move';
963
- this.draggedItem = draggedItem;
964
- }
965
- }
966
-
967
- _handleDrop (event) {
968
- console.log('DROP ON BODY', this)
969
- if (!this.draggedItem) {
970
- return;
971
- }
972
- // A drop on 'body' should be a removal.
973
- if (event.dataTransfer.dropEffect === 'none') {
974
- if (this.copiedElements.has(this.originalDraggedItem)) {
975
- this.originalDraggedItem.remove();
976
- }
977
- }
978
- this.originalDraggedItem = null
979
- this.draggedItem = null;
980
- event.stopPropagation();
981
- }
982
-
983
- registerLazyElement(element) {
984
- if (this.intersectionObserver) {
985
- // console.log('registerLazyElement',element);
986
- this.intersectionObserver.observe(element);
987
- }
988
- }
989
-
990
- unRegisterLazyElement(element) {
991
- if (this.intersectionObserver) {
992
- this.intersectionObserver.unobserve(element);
993
- }
955
+ // First refresh should be forced
956
+ await this.refresh(true);
957
+ // this.style.display='block'
958
+ this.classList.add('fx-ready');
959
+ document.body.classList.add('fx-ready');
960
+
961
+ this.ready = true;
962
+ this.initialRun = false;
963
+ // console.log('### >>>>> dispatching ready >>>>>', this);
964
+ console.log(`### <<<<< ${this.id} ready >>>>>`);
965
+
966
+ // console.log('### modelItems: ', this.getModel().modelItems);
967
+ Fore.dispatch(this, 'ready', {});
968
+ // console.log('dataChanged', FxModel.dataChanged);
969
+ console.timeEnd('init');
970
+
971
+ this.addEventListener('dragstart', this._handleDragStart);
972
+ // this.addEventListener('dragend', this._handleDragEnd);
973
+ this.handleDrop = event => this._handleDrop(event);
974
+ this.ownerDocument.body.addEventListener('drop', this.handleDrop);
975
+ this.ownerDocument.body.addEventListener('dragover', e => {
976
+ e.preventDefault();
977
+ e.stopPropagation();
978
+ e.dataTransfer.dropEffect = 'move';
979
+ });
980
+ }
981
+
982
+ _handleDragStart(event) {
983
+ const draggedItem = event.target.closest('[draggable="true"]');
984
+ this.originalDraggedItem = draggedItem;
985
+ console.log('DRAG START', this);
986
+ if (draggedItem.getAttribute('drop-action') === 'copy') {
987
+ event.dataTransfer.dropEffect = 'copy';
988
+ event.dataTransfer.effectAllowed = 'copy';
989
+ this.draggedItem = draggedItem.cloneNode(true);
990
+ this.draggedItem.setAttribute('drop-action', 'move');
991
+ this.copiedElements.add(this.draggedItem);
992
+ } else {
993
+ event.dataTransfer.dropEffect = 'move';
994
+ event.dataTransfer.effectAllowed = 'move';
995
+ this.draggedItem = draggedItem;
994
996
  }
997
+ }
995
998
 
996
- /**
997
- *
998
- * @returns {FxModel}
999
- */
1000
- getModel() {
1001
- return this.querySelector('fx-model');
999
+ _handleDrop(event) {
1000
+ console.log('DROP ON BODY', this);
1001
+ if (!this.draggedItem) {
1002
+ return;
1002
1003
  }
1003
-
1004
- _displayMessage(e) {
1005
- // console.log('_displayMessage',e);
1006
- const {level} = e.detail;
1007
- const msg = e.detail.message;
1008
- this._showMessage(level, msg);
1009
- e.stopPropagation();
1004
+ // A drop on 'body' should be a removal.
1005
+ if (event.dataTransfer.dropEffect === 'none') {
1006
+ if (this.copiedElements.has(this.originalDraggedItem)) {
1007
+ this.originalDraggedItem.remove();
1008
+ }
1010
1009
  }
1011
-
1012
- _displayError(e) {
1013
- // const { error } = e.detail;
1014
- const msg = e.detail.message;
1015
- // this._showMessage('modal', msg);
1016
- const toast = this.shadowRoot.querySelector('#error');
1017
- toast.showToast(msg);
1010
+ this.originalDraggedItem = null;
1011
+ this.draggedItem = null;
1012
+ event.stopPropagation();
1013
+ }
1014
+
1015
+ registerLazyElement(element) {
1016
+ if (this.intersectionObserver) {
1017
+ // console.log('registerLazyElement',element);
1018
+ this.intersectionObserver.observe(element);
1018
1019
  }
1020
+ }
1019
1021
 
1020
- _displayWarning(e){
1021
- const msg = e.detail.message;
1022
- // this._showMessage('modal', msg);
1023
- const path = XPathUtil.shortenPath(evaluateXPathToString('path()',e.target,this));
1024
- const toast = this.shadowRoot.querySelector('#warn');
1025
- toast.showToast(`WARN: ${path}:${msg}`);
1022
+ unRegisterLazyElement(element) {
1023
+ if (this.intersectionObserver) {
1024
+ this.intersectionObserver.unobserve(element);
1026
1025
  }
1027
-
1028
-
1029
- _logError(e) {
1030
- e.stopPropagation();
1031
- e.preventDefault();
1032
-
1033
- console.error('ERROR',e.detail.message);
1034
- console.error(e.detail.origin);
1035
- if(e.detail.expr){
1036
- console.error('Failing expression',e.detail.expr);
1037
- }
1038
- if(this.strict){
1039
- this._displayError(e);
1040
- }
1026
+ }
1027
+
1028
+ /**
1029
+ *
1030
+ * @returns {FxModel}
1031
+ */
1032
+ getModel() {
1033
+ return this.querySelector('fx-model');
1034
+ }
1035
+
1036
+ _displayMessage(e) {
1037
+ // console.log('_displayMessage',e);
1038
+ const { level } = e.detail;
1039
+ const msg = e.detail.message;
1040
+ this._showMessage(level, msg);
1041
+ e.stopPropagation();
1042
+ }
1043
+
1044
+ _displayError(e) {
1045
+ // const { error } = e.detail;
1046
+ const msg = e.detail.message;
1047
+ // this._showMessage('modal', msg);
1048
+ const toast = this.shadowRoot.querySelector('#error');
1049
+ toast.showToast(msg);
1050
+ }
1051
+
1052
+ _displayWarning(e) {
1053
+ const msg = e.detail.message;
1054
+ // this._showMessage('modal', msg);
1055
+ const path = XPathUtil.shortenPath(evaluateXPathToString('path()', e.target, this));
1056
+ const toast = this.shadowRoot.querySelector('#warn');
1057
+ toast.showToast(`WARN: ${path}:${msg}`);
1058
+ }
1059
+
1060
+ _logError(e) {
1061
+ e.stopPropagation();
1062
+ e.preventDefault();
1063
+
1064
+ console.error('ERROR', e.detail.message);
1065
+ console.error(e.detail.origin);
1066
+ if (e.detail.expr) {
1067
+ console.error('Failing expression', e.detail.expr);
1041
1068
  }
1042
-
1043
- _copyToClipboard(target){
1044
- console.log('copyToClipboard' , target.value)
1045
- navigator.clipboard.writeText(target.value);
1046
-
1069
+ if (this.strict) {
1070
+ this._displayError(e);
1047
1071
  }
1048
-
1049
- _showMessage(level, msg) {
1050
- if (level === 'modal') {
1051
- // this.$.messageContent.innerText = msg;
1052
- // this.$.modalMessage.open();
1053
-
1054
- this.shadowRoot.getElementById('messageContent').innerText = msg;
1055
- // this.shadowRoot.getElementById('modalMessage').open();
1056
- this.shadowRoot.getElementById('modalMessage').classList.add('show');
1057
- } else if (level === 'sticky' || level === 'error' || level === 'warn') {
1058
- // const notification = this.$.modeless;
1059
- this.shadowRoot.querySelector(`#${level}`).showToast(msg);
1060
- } else {
1061
- const toast = this.shadowRoot.querySelector('#message');
1062
- toast.showToast(msg);
1063
- }
1072
+ }
1073
+
1074
+ _copyToClipboard(target) {
1075
+ console.log('copyToClipboard', target.value);
1076
+ navigator.clipboard.writeText(target.value);
1077
+ }
1078
+
1079
+ _showMessage(level, msg) {
1080
+ if (level === 'modal') {
1081
+ // this.$.messageContent.innerText = msg;
1082
+ // this.$.modalMessage.open();
1083
+
1084
+ this.shadowRoot.getElementById('messageContent').innerText = msg;
1085
+ // this.shadowRoot.getElementById('modalMessage').open();
1086
+ this.shadowRoot.getElementById('modalMessage').classList.add('show');
1087
+ } else if (level === 'sticky' || level === 'error' || level === 'warn') {
1088
+ // const notification = this.$.modeless;
1089
+ this.shadowRoot.querySelector(`#${level}`).showToast(msg);
1090
+ } else {
1091
+ const toast = this.shadowRoot.querySelector('#message');
1092
+ toast.showToast(msg);
1064
1093
  }
1065
-
1066
- /**
1067
- * wraps the element having a 'data-ref' attribute with an fx-repeat-attributes element.
1068
- * @private
1069
- */
1070
- _createRepeatsFromAttributes() {
1071
- if(this.repeatsFromAttributesCreated) return;
1072
- const repeats = this.querySelectorAll('[data-ref]');
1073
- if(repeats){
1074
- Array.from(repeats).forEach(item =>{
1075
- if(item.closest('fx-control')) return;
1076
- /*
1094
+ }
1095
+
1096
+ /**
1097
+ * wraps the element having a 'data-ref' attribute with an fx-repeat-attributes element.
1098
+ * @private
1099
+ */
1100
+ _createRepeatsFromAttributes() {
1101
+ if (this.repeatsFromAttributesCreated) return;
1102
+ const repeats = this.querySelectorAll('[data-ref]');
1103
+ if (repeats) {
1104
+ Array.from(repeats).forEach(item => {
1105
+ if (item.closest('fx-control')) return;
1106
+ /*
1077
1107
  const parentRepeat = item.closest('fx-repeat');
1078
1108
  if(parentRepeat){
1079
1109
  this.dispatchEvent(
@@ -1087,47 +1117,46 @@ export class FxFore extends HTMLElement {
1087
1117
  }
1088
1118
  */
1089
1119
 
1090
- const table = item.parentNode.closest('table');
1091
- let host;
1092
- if(table){
1093
- host = table.cloneNode(true);
1094
- }else{
1095
- host = item.cloneNode(true);
1096
- }
1097
- // ### clone original item to move it into fx-repeat-attributes
1098
- // const host = item.cloneNode(true);
1099
-
1100
- // ### create wrapper element
1101
- const repeatFromAttr = new FxRepeatAttributes();
1102
- // const repeatFromAttr = document.createElement('fx-repeat-attributes');
1103
-
1104
- // ### copy the value of 'data-ref' to 'ref' on fx-repeat-attributes
1105
- repeatFromAttr.setAttribute('ref',item.getAttribute('data-ref'));
1106
- // item.removeAttribute('data-ref');
1120
+ const table = item.parentNode.closest('table');
1121
+ let host;
1122
+ if (table) {
1123
+ host = table.cloneNode(true);
1124
+ } else {
1125
+ host = item.cloneNode(true);
1126
+ }
1127
+ // ### clone original item to move it into fx-repeat-attributes
1128
+ // const host = item.cloneNode(true);
1107
1129
 
1108
- // ### append the cloned original element to fx-repeat-attributes
1109
- repeatFromAttr.appendChild(host);
1130
+ // ### create wrapper element
1131
+ const repeatFromAttr = new FxRepeatAttributes();
1132
+ // const repeatFromAttr = document.createElement('fx-repeat-attributes');
1110
1133
 
1111
- // ### insert fx-repeat-attributes element before element with the 'data-ref'
1112
- // repeats[0].parentNode.insertBefore(repeatFromAttr,repeats[0]);
1134
+ // ### copy the value of 'data-ref' to 'ref' on fx-repeat-attributes
1135
+ repeatFromAttr.setAttribute('ref', item.getAttribute('data-ref'));
1136
+ // item.removeAttribute('data-ref');
1113
1137
 
1114
- if(table){
1115
- table.parentNode.insertBefore(repeatFromAttr,table);
1116
- table.parentNode.removeChild(table);
1117
- }else{
1118
- item.parentNode.insertBefore(repeatFromAttr,item);
1119
- item.parentNode.removeChild(item);
1120
- }
1138
+ // ### append the cloned original element to fx-repeat-attributes
1139
+ repeatFromAttr.appendChild(host);
1121
1140
 
1122
- // ### remove original item from DOM
1123
- item.setAttribute('insertPoint','');
1141
+ // ### insert fx-repeat-attributes element before element with the 'data-ref'
1142
+ // repeats[0].parentNode.insertBefore(repeatFromAttr,repeats[0]);
1124
1143
 
1125
- });
1144
+ if (table) {
1145
+ table.parentNode.insertBefore(repeatFromAttr, table);
1146
+ table.parentNode.removeChild(table);
1147
+ } else {
1148
+ item.parentNode.insertBefore(repeatFromAttr, item);
1149
+ item.parentNode.removeChild(item);
1126
1150
  }
1127
- this.repeatsFromAttributesCreated = true;
1151
+
1152
+ // ### remove original item from DOM
1153
+ item.setAttribute('insertPoint', '');
1154
+ });
1128
1155
  }
1156
+ this.repeatsFromAttributesCreated = true;
1157
+ }
1129
1158
  }
1130
1159
 
1131
1160
  if (!customElements.get('fx-fore')) {
1132
- customElements.define('fx-fore', FxFore);
1161
+ customElements.define('fx-fore', FxFore);
1133
1162
  }