@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.
- package/README.md +2 -2
- package/dist/fore-dev.js +2 -2
- package/dist/fore-dev.js.map +1 -1
- package/dist/fore.js +2 -2
- package/dist/fore.js.map +1 -1
- package/index.js +4 -2
- package/package.json +10 -38
- package/resources/fore.css +263 -262
- package/resources/vars.css +8 -0
- package/src/DependencyNotifyingDomFacade.js +1 -0
- package/src/ForeElementMixin.js +243 -228
- package/src/actions/abstract-action.js +407 -398
- package/src/actions/fx-action.js +6 -6
- package/src/actions/fx-append.js +1 -1
- package/src/actions/fx-call.js +63 -62
- package/src/actions/fx-confirm.js +11 -11
- package/src/actions/fx-construct-done.js +4 -4
- package/src/actions/fx-copy.js +36 -36
- package/src/actions/fx-delete.js +77 -79
- package/src/actions/fx-dispatch.js +14 -14
- package/src/actions/fx-hide.js +15 -15
- package/src/actions/fx-insert.js +9 -12
- package/src/actions/fx-insertchild.js +88 -0
- package/src/actions/fx-load.js +188 -185
- package/src/actions/fx-message.js +18 -19
- package/src/actions/fx-refresh.js +10 -10
- package/src/actions/fx-reload.js +16 -14
- package/src/actions/fx-replace.js +0 -1
- package/src/actions/fx-reset.js +31 -31
- package/src/actions/fx-send.js +59 -52
- package/src/actions/fx-setattribute.js +48 -49
- package/src/actions/fx-setfocus.js +54 -58
- package/src/actions/fx-setvalue.js +85 -81
- package/src/actions/fx-show.js +11 -11
- package/src/actions/fx-toggle.js +2 -2
- package/src/actions/fx-toggleboolean.js +38 -39
- package/src/actions/fx-unmodified.js +27 -0
- package/src/actions/fx-update.js +6 -6
- package/src/dep_graph.js +1 -1
- package/src/drawdown.js +23 -30
- package/src/events.js +19 -19
- package/src/fore.js +143 -125
- package/src/functions/common-function.js +24 -25
- package/src/functions/fx-function.js +6 -99
- package/src/functions/fx-functionlib.js +56 -0
- package/src/functions/registerFunction.js +112 -0
- package/src/fx-bind.js +19 -24
- package/src/fx-connection.js +226 -0
- package/src/fx-fore.js +844 -815
- package/src/fx-header.js +4 -4
- package/src/fx-instance.js +25 -29
- package/src/fx-model.js +405 -380
- package/src/fx-submission.js +399 -397
- package/src/fx-var.js +13 -12
- package/src/getInScopeContext.js +102 -89
- package/src/json-util.js +24 -24
- package/src/lab/fore-component.js +48 -52
- package/src/lab/instance-inspector.js +13 -16
- package/src/modelitem.js +48 -10
- package/src/relevance.js +12 -16
- package/src/tools/adi.js +858 -812
- package/src/tools/fx-action-log.js +377 -295
- package/src/tools/fx-devtools.js +210 -210
- package/src/tools/fx-dom-inspector.js +123 -122
- package/src/tools/fx-json-instance.js +262 -253
- package/src/tools/fx-log-item.js +6 -11
- package/src/tools/fx-log-settings.js +358 -301
- package/src/tools/fx-minimap.js +186 -177
- package/src/tools/helpers.js +1 -1
- package/src/ui/abstract-control.js +73 -53
- package/src/ui/fx-case.js +59 -7
- package/src/ui/fx-container.js +13 -14
- package/src/ui/fx-control.js +572 -538
- package/src/ui/fx-dialog.js +3 -3
- package/src/ui/fx-droptarget.js +3 -4
- package/src/ui/fx-group.js +4 -2
- package/src/ui/fx-hint.js +3 -0
- package/src/ui/fx-inspector.js +5 -5
- package/src/ui/fx-items.js +11 -11
- package/src/ui/fx-output.js +10 -12
- package/src/ui/fx-repeat-attributes.js +23 -27
- package/src/ui/fx-repeat.js +347 -344
- package/src/ui/fx-repeatitem.js +75 -68
- package/src/ui/fx-switch.js +39 -14
- package/src/ui/fx-trigger.js +4 -4
- package/src/withDraggability.js +193 -191
- package/src/xpath-evaluation.js +969 -958
- 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 {
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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
|
-
|
|
203
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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
|
-
|
|
273
|
-
|
|
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
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
)
|
|
280
|
-
|
|
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
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|
-
|
|
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
|
-
|
|
391
|
-
|
|
392
|
-
|
|
446
|
+
evaluateToNodes(xpath, context) {
|
|
447
|
+
return evaluateXPathToNodes(xpath, context, this);
|
|
448
|
+
}
|
|
393
449
|
|
|
394
|
-
|
|
395
|
-
|
|
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
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
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
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
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
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
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
|
-
|
|
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
|
-
|
|
517
|
-
|
|
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
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
580
|
+
if (this.inited) {
|
|
581
|
+
Fore.refreshChildren(this, true);
|
|
582
|
+
}
|
|
583
|
+
// console.timeEnd('refreshChildren');
|
|
584
|
+
}
|
|
530
585
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
586
|
+
// ### refresh template expressions
|
|
587
|
+
if (this.initialRun || this._scanForNewTemplateExpressionsNextRefresh) {
|
|
588
|
+
this._updateTemplateExpressions();
|
|
589
|
+
this._scanForNewTemplateExpressionsNextRefresh = false; // reset
|
|
590
|
+
}
|
|
536
591
|
|
|
537
|
-
|
|
592
|
+
this._processTemplateExpressions();
|
|
538
593
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
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
|
-
|
|
547
|
-
|
|
601
|
+
// this.isRefreshing = true;
|
|
602
|
+
// this.parentNode.closest('fx-fore')?.refresh(false, changedPaths);
|
|
548
603
|
|
|
549
|
-
|
|
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
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
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
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
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
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
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
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
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
|
-
|
|
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
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
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
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
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
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
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
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
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
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
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
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
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
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
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
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
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
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
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
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
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
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
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
|
-
|
|
1109
|
-
|
|
1130
|
+
// ### create wrapper element
|
|
1131
|
+
const repeatFromAttr = new FxRepeatAttributes();
|
|
1132
|
+
// const repeatFromAttr = document.createElement('fx-repeat-attributes');
|
|
1110
1133
|
|
|
1111
|
-
|
|
1112
|
-
|
|
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
|
-
|
|
1115
|
-
|
|
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
|
-
|
|
1123
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1161
|
+
customElements.define('fx-fore', FxFore);
|
|
1133
1162
|
}
|