@jinntec/fore 2.2.0 → 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 +1 -1
- 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 +3 -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 +4 -6
- package/src/functions/fx-functionlib.js +39 -42
- package/src/functions/registerFunction.js +95 -86
- package/src/fx-bind.js +19 -24
- package/src/fx-connection.js +226 -0
- package/src/fx-fore.js +846 -815
- package/src/fx-header.js +4 -4
- package/src/fx-instance.js +23 -27
- package/src/fx-model.js +405 -383
- package/src/fx-submission.js +399 -397
- package/src/fx-var.js +6 -5
- 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 +74 -56
- 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 -959
- 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,209 +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
|
-
}
|
|
258
|
-
|
|
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
|
-
}
|
|
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
|
+
}
|
|
271
278
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
279
|
+
this.src = this.hasAttribute('src') ? this.getAttribute('src') : null;
|
|
280
|
+
if (this.src) {
|
|
281
|
+
this._loadFromSrc();
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
275
284
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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
|
+
);
|
|
319
|
+
|
|
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);
|
|
306
325
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
326
|
+
registerVariables(child);
|
|
327
|
+
}
|
|
328
|
+
})(this);
|
|
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;
|
|
320
347
|
}
|
|
348
|
+
}
|
|
321
349
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
}
|
|
328
|
-
const search = window.location.search;
|
|
329
|
-
const urlParams = new URLSearchParams(search);
|
|
330
|
-
if(urlParams.has('inspect')){
|
|
331
|
-
const devtools = document.createElement('fx-devtools');
|
|
332
|
-
document.body.appendChild(devtools);
|
|
333
|
-
}
|
|
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;
|
|
334
355
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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);
|
|
340
361
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
*
|
|
354
|
-
* Will extract the `fx-fore` element from that target file and use and replace current `fx-fore` element with the loaded one.
|
|
355
|
-
* @private
|
|
356
|
-
*/
|
|
357
|
-
async _loadFromSrc() {
|
|
358
|
-
// console.log('########## loading Fore from ', this.src, '##########');
|
|
359
|
-
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);
|
|
360
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'));
|
|
361
442
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
* by the observer whenever elements come into / move out of viewport.
|
|
365
|
-
* @param entries
|
|
366
|
-
* @param observer
|
|
367
|
-
*/
|
|
368
|
-
handleIntersect(entries, observer) {
|
|
369
|
-
// console.time('refreshLazy');
|
|
370
|
-
|
|
371
|
-
entries.forEach(entry => {
|
|
372
|
-
const {target} = entry;
|
|
373
|
-
|
|
374
|
-
const fore = Fore.getFore(target);
|
|
375
|
-
if(fore.initialRun) return;
|
|
376
|
-
|
|
377
|
-
if (entry.isIntersecting) {
|
|
378
|
-
// console.log('in view', entry);
|
|
379
|
-
// console.log('repeat in view entry', entry.target);
|
|
380
|
-
// const target = entry.target;
|
|
381
|
-
// if(target.hasAttribute('refresh-on-view')){
|
|
382
|
-
target.classList.add('loaded');
|
|
383
|
-
// }
|
|
384
|
-
|
|
385
|
-
// todo: too restrictive here? what if target is a usual html element? shouldn't it refresh downwards?
|
|
386
|
-
if (typeof target.refresh === 'function') {
|
|
387
|
-
// console.log('refreshing target', target);
|
|
388
|
-
target.refresh(target, true);
|
|
389
|
-
} else {
|
|
390
|
-
// console.log('refreshing children', target);
|
|
391
|
-
Fore.refreshChildren(target, true);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
});
|
|
395
|
-
entries[0].target.getOwnerForm().dispatchEvent(new CustomEvent('refresh-done'));
|
|
396
|
-
|
|
397
|
-
// console.timeEnd('refreshLazy');
|
|
398
|
-
}
|
|
443
|
+
// console.timeEnd('refreshLazy');
|
|
444
|
+
}
|
|
399
445
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
446
|
+
evaluateToNodes(xpath, context) {
|
|
447
|
+
return evaluateXPathToNodes(xpath, context, this);
|
|
448
|
+
}
|
|
403
449
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
450
|
+
disconnectedCallback() {
|
|
451
|
+
this.removeEventListener('dragstart', this.dragstart);
|
|
452
|
+
/*
|
|
407
453
|
this.removeEventListener('model-construct-done', this._handleModelConstructDone);
|
|
408
454
|
this.removeEventListener('message', this._displayMessage);
|
|
409
455
|
this.removeEventListener('error', this._displayError);
|
|
410
456
|
this.storedTemplateExpressionByNode=null;
|
|
411
457
|
this.shadowRoot = undefined;
|
|
412
458
|
*/
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
/*
|
|
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
|
+
/*
|
|
444
489
|
|
|
445
490
|
if (!changedPaths) {
|
|
446
491
|
changedPaths = this.toRefresh.map(item => item.path);
|
|
@@ -475,89 +520,89 @@ export class FxFore extends HTMLElement {
|
|
|
475
520
|
}
|
|
476
521
|
}
|
|
477
522
|
*/
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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
|
+
}
|
|
500
545
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
});
|
|
516
|
-
needsRefresh = true;
|
|
517
|
-
}
|
|
518
|
-
}
|
|
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
|
+
});
|
|
519
560
|
});
|
|
520
|
-
|
|
521
|
-
|
|
561
|
+
needsRefresh = true;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
this.toRefresh = [];
|
|
566
|
+
/*
|
|
522
567
|
if (!needsRefresh) {
|
|
523
568
|
console.log('no dependants to refresh');
|
|
524
569
|
}
|
|
525
570
|
*/
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
/*
|
|
571
|
+
} else {
|
|
572
|
+
// ### resetting visited state for controls to refresh
|
|
573
|
+
/*
|
|
529
574
|
const visited = this.parentNode.querySelectorAll('.visited');
|
|
530
575
|
Array.from(visited).forEach(v =>{
|
|
531
576
|
v.classList.remove('visited');
|
|
532
577
|
});
|
|
533
578
|
*/
|
|
534
579
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
580
|
+
if (this.inited) {
|
|
581
|
+
Fore.refreshChildren(this, true);
|
|
582
|
+
}
|
|
583
|
+
// console.timeEnd('refreshChildren');
|
|
584
|
+
}
|
|
540
585
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
586
|
+
// ### refresh template expressions
|
|
587
|
+
if (this.initialRun || this._scanForNewTemplateExpressionsNextRefresh) {
|
|
588
|
+
this._updateTemplateExpressions();
|
|
589
|
+
this._scanForNewTemplateExpressionsNextRefresh = false; // reset
|
|
590
|
+
}
|
|
546
591
|
|
|
547
|
-
|
|
592
|
+
this._processTemplateExpressions();
|
|
548
593
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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', {});
|
|
555
600
|
|
|
556
|
-
|
|
557
|
-
|
|
601
|
+
// this.isRefreshing = true;
|
|
602
|
+
// this.parentNode.closest('fx-fore')?.refresh(false, changedPaths);
|
|
558
603
|
|
|
559
|
-
|
|
560
|
-
/*
|
|
604
|
+
const subFores = Array.from(this.querySelectorAll('fx-fore'));
|
|
605
|
+
/*
|
|
561
606
|
calling the parent to refresh causes errors and inconsistent state. Also it is questionable
|
|
562
607
|
if a child should actually interact with its parent in this way.
|
|
563
608
|
|
|
@@ -572,293 +617,283 @@ export class FxFore extends HTMLElement {
|
|
|
572
617
|
// await this.parentNode.closest('fx-fore')?.refresh(false);
|
|
573
618
|
}
|
|
574
619
|
*/
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
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 = [];
|
|
582
646
|
}
|
|
583
647
|
|
|
584
|
-
|
|
585
|
-
* entry point for processing of template expression enclosed in '{}' brackets.
|
|
586
|
-
*
|
|
587
|
-
* Expressions are found with an XPath search. For each node an entry is added to the storedTemplateExpressionByNode map.
|
|
588
|
-
*
|
|
589
|
-
*
|
|
590
|
-
* @private
|
|
591
|
-
*/
|
|
592
|
-
_updateTemplateExpressions() {
|
|
593
|
-
const search =
|
|
594
|
-
"(descendant-or-self::*!(text(), @*))[contains(., '{')][substring-after(., '{') => contains('}')][not(ancestor-or-self::fx-model)]";
|
|
595
|
-
|
|
596
|
-
const tmplExpressions = evaluateXPathToNodes(search, this, this);
|
|
597
|
-
// console.log('template expressions found ', tmplExpressions);
|
|
598
|
-
|
|
599
|
-
if (!this.storedTemplateExpressions) {
|
|
600
|
-
this.storedTemplateExpressions = [];
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
// console.log('######### storedTemplateExpressions', this.storedTemplateExpressions.length);
|
|
648
|
+
// console.log('######### storedTemplateExpressions', this.storedTemplateExpressions.length);
|
|
604
649
|
|
|
605
|
-
|
|
650
|
+
/*
|
|
606
651
|
storing expressions and their nodes for re-evaluation
|
|
607
652
|
*/
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
this.storedTemplateExpressionByNode.delete(node);
|
|
639
|
-
continue;
|
|
640
|
-
}
|
|
641
|
-
} else if (!XPathUtil.contains(this, node)) {
|
|
642
|
-
// For all other nodes, if this `fore` element does not contain them, they are dead
|
|
643
|
-
this.storedTemplateExpressionByNode.delete(node);
|
|
644
|
-
continue;
|
|
645
|
-
}
|
|
646
|
-
this._processTemplateExpression({
|
|
647
|
-
node,
|
|
648
|
-
expr: this.storedTemplateExpressionByNode.get(node),
|
|
649
|
-
});
|
|
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;
|
|
650
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
|
+
});
|
|
651
693
|
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
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
|
+
}
|
|
661
759
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
if(node.nodeType === Node.TEXT_NODE && node.parentNode.closest('[nonrelevant]')) return;
|
|
673
|
-
if(node.nodeType === Node.ELEMENT_NODE && node.closest('[nonrelevant]')) return;
|
|
674
|
-
|
|
675
|
-
// if(node.closest('[nonrelevant]')) return;
|
|
676
|
-
const replaced = expr.replace(/{[^}]*}/g, match => {
|
|
677
|
-
if (match === '{}') return match;
|
|
678
|
-
const naked = match.substring(1, match.length - 1);
|
|
679
|
-
const inscope = getInScopeContext(node, naked);
|
|
680
|
-
if (!inscope) {
|
|
681
|
-
console.warn('no inscope context for expr', naked);
|
|
682
|
-
const errNode =
|
|
683
|
-
node.nodeType === Node.TEXT_NODE || node.nodeType === Node.ATTRIBUTE_NODE
|
|
684
|
-
? node.parentNode
|
|
685
|
-
: node;
|
|
686
|
-
return match;
|
|
687
|
-
}
|
|
688
|
-
// Templates are special: they use the namespace configuration from the place where they are
|
|
689
|
-
// being defined
|
|
690
|
-
const instanceId = XPathUtil.getInstanceId(naked);
|
|
691
|
-
|
|
692
|
-
// If there is an instance referred
|
|
693
|
-
const inst = instanceId ? this.getModel().getInstance(instanceId) : this.getModel().getDefaultInstance();
|
|
694
|
-
|
|
695
|
-
try {
|
|
696
|
-
return evaluateXPathToString(naked, inscope, node, null, inst);
|
|
697
|
-
} catch (error) {
|
|
698
|
-
console.warn('ignoring unparseable expr', error);
|
|
699
|
-
|
|
700
|
-
return match;
|
|
701
|
-
}
|
|
702
|
-
});
|
|
703
|
-
|
|
704
|
-
// Update to the new value. Don't do it though if nothing changed to prevent iframes or
|
|
705
|
-
// images from reloading for example
|
|
706
|
-
if (node.nodeType === Node.ATTRIBUTE_NODE) {
|
|
707
|
-
const parent = node.ownerElement;
|
|
708
|
-
if (parent.getAttribute(node.nodeName) !== replaced) {
|
|
709
|
-
parent.setAttribute(node.nodeName, replaced);
|
|
710
|
-
}
|
|
711
|
-
} else if (node.nodeType === Node.TEXT_NODE) {
|
|
712
|
-
if (node.textContent !== replaced) {
|
|
713
|
-
node.textContent = replaced;
|
|
714
|
-
}
|
|
715
|
-
}
|
|
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;
|
|
716
770
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
_getTemplateExpression(node) {
|
|
720
|
-
if(this.ignoredNodes){
|
|
721
|
-
if(node.nodeType === Node.ATTRIBUTE_NODE){
|
|
722
|
-
node = node.ownerElement;
|
|
723
|
-
}
|
|
724
|
-
const found = this.ignoredNodes.find( (n) => n.contains(node));
|
|
725
|
-
if(found) return null;
|
|
726
|
-
}
|
|
727
|
-
if (node.nodeType === Node.ATTRIBUTE_NODE) {
|
|
728
|
-
return node.value;
|
|
729
|
-
}
|
|
730
|
-
if (node.nodeType === Node.TEXT_NODE) {
|
|
731
|
-
return node.textContent.trim();
|
|
732
|
-
}
|
|
733
|
-
return null;
|
|
771
|
+
if (node.nodeType === Node.ATTRIBUTE_NODE) {
|
|
772
|
+
return node.value;
|
|
734
773
|
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
* called when `model-construct-done` event is received to
|
|
738
|
-
* start initing the UI.
|
|
739
|
-
*
|
|
740
|
-
* @private
|
|
741
|
-
*/
|
|
742
|
-
_handleModelConstructDone() {
|
|
743
|
-
/*
|
|
744
|
-
listening on beforeunload after model is constructed - this is to be able to evaluate a condition on the data
|
|
745
|
-
that specifies whether or not to show confirmation.
|
|
746
|
-
*/
|
|
747
|
-
if(this.hasAttribute('show-confirmation')){
|
|
748
|
-
const condition = this.getAttribute('show-confirmation');
|
|
749
|
-
if(condition
|
|
750
|
-
&& condition !== 'show-confirmation'
|
|
751
|
-
&& condition !== 'true'
|
|
752
|
-
&& condition !== ''){
|
|
753
|
-
window.addEventListener('beforeunload', event => {
|
|
754
|
-
const mustDisplay = evaluateXPathToBoolean(condition, this.getModel().getDefaultContext(), this)
|
|
755
|
-
if(mustDisplay){
|
|
756
|
-
return event.returnValue = 'are you sure';
|
|
757
|
-
}
|
|
758
|
-
event.preventDefault();
|
|
759
|
-
})
|
|
760
|
-
}else{
|
|
761
|
-
window.addEventListener('beforeunload', event => {
|
|
762
|
-
// if(AbstractAction.dataChanged){
|
|
763
|
-
if(FxModel.dataChanged){
|
|
764
|
-
return event.returnValue = 'are you sure';
|
|
765
|
-
}
|
|
766
|
-
event.preventDefault();
|
|
767
|
-
})
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
this._initUI();
|
|
774
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
775
|
+
return node.textContent.trim();
|
|
772
776
|
}
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
if(parentFore){
|
|
791
|
-
const shared = parentFore.getModel().instances.filter(shared => shared.hasAttribute('shared'));
|
|
792
|
-
if(shared.length !==0) return;
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
// still need to catch just in case...
|
|
796
|
-
try{
|
|
797
|
-
if (model.instances.length === 0) {
|
|
798
|
-
// console.log('### lazy creation of instance');
|
|
799
|
-
const generatedInstance = document.createElement('fx-instance');
|
|
800
|
-
model.appendChild(generatedInstance);
|
|
801
|
-
|
|
802
|
-
const generated = document.implementation.createDocument(null, 'data', null);
|
|
803
|
-
// const newData = this._generateInstance(this, generated.firstElementChild);
|
|
804
|
-
this._generateInstance(this, generated.firstElementChild);
|
|
805
|
-
generatedInstance.instanceData = generated;
|
|
806
|
-
model.instances.push(generatedInstance);
|
|
807
|
-
// console.log('generatedInstance ', this.getModel().getDefaultInstanceData());
|
|
808
|
-
Fore.dispatch(this,'instance-loaded',{instance:this});
|
|
809
|
-
}
|
|
810
|
-
}catch (e) {
|
|
811
|
-
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;
|
|
812
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);
|
|
813
818
|
}
|
|
814
819
|
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
const ref = start.getAttribute('ref');
|
|
822
|
-
|
|
823
|
-
if (ref.includes('/')) {
|
|
824
|
-
// console.log('complex path to create ', ref);
|
|
825
|
-
const steps = ref.split('/');
|
|
826
|
-
steps.forEach(step => {
|
|
827
|
-
// const generated = document.createElement(ref);
|
|
828
|
-
parent = this._generateNode(parent, step, start);
|
|
829
|
-
});
|
|
830
|
-
} else {
|
|
831
|
-
parent = this._generateNode(parent, ref, start);
|
|
832
|
-
}
|
|
833
|
-
}
|
|
820
|
+
if (parentFore) {
|
|
821
|
+
const shared = parentFore
|
|
822
|
+
.getModel()
|
|
823
|
+
.instances.filter(shared => shared.hasAttribute('shared'));
|
|
824
|
+
if (shared.length !== 0) return;
|
|
825
|
+
}
|
|
834
826
|
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
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
|
+
}
|
|
842
868
|
}
|
|
843
869
|
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
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;
|
|
853
884
|
}
|
|
885
|
+
parent.appendChild(generated);
|
|
886
|
+
parent = generated;
|
|
887
|
+
return parent;
|
|
888
|
+
}
|
|
854
889
|
|
|
855
|
-
|
|
890
|
+
/*
|
|
856
891
|
_createStep(){
|
|
857
892
|
|
|
858
893
|
}
|
|
859
894
|
*/
|
|
860
895
|
|
|
861
|
-
|
|
896
|
+
/*
|
|
862
897
|
_generateInstance(start, parent) {
|
|
863
898
|
if (start.hasAttribute('ref')) {
|
|
864
899
|
const ref = start.getAttribute('ref');
|
|
@@ -869,7 +904,6 @@ export class FxFore extends HTMLElement {
|
|
|
869
904
|
steps.forEach(step => {
|
|
870
905
|
console.log('step ', step);
|
|
871
906
|
|
|
872
|
-
|
|
873
907
|
});
|
|
874
908
|
}
|
|
875
909
|
|
|
@@ -892,25 +926,25 @@ export class FxFore extends HTMLElement {
|
|
|
892
926
|
}
|
|
893
927
|
*/
|
|
894
928
|
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
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();
|
|
912
946
|
|
|
913
|
-
|
|
947
|
+
/*
|
|
914
948
|
const options = {
|
|
915
949
|
root: null,
|
|
916
950
|
rootMargin: '0px',
|
|
@@ -918,160 +952,158 @@ export class FxFore extends HTMLElement {
|
|
|
918
952
|
};
|
|
919
953
|
*/
|
|
920
954
|
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
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;
|
|
946
996
|
}
|
|
997
|
+
}
|
|
947
998
|
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
if (draggedItem.getAttribute('drop-action') === 'copy') {
|
|
953
|
-
event.dataTransfer.dropEffect = 'copy';
|
|
954
|
-
event.dataTransfer.effectAllowed = 'copy';
|
|
955
|
-
this.draggedItem = draggedItem.cloneNode(true);
|
|
956
|
-
this.draggedItem.setAttribute('drop-action', 'move');
|
|
957
|
-
this.copiedElements.add(this.draggedItem);
|
|
958
|
-
} else {
|
|
959
|
-
event.dataTransfer.dropEffect = 'move';
|
|
960
|
-
event.dataTransfer.effectAllowed = 'move';
|
|
961
|
-
this.draggedItem = draggedItem;
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
_handleDrop (event) {
|
|
966
|
-
console.log('DROP ON BODY', this)
|
|
967
|
-
if (!this.draggedItem) {
|
|
968
|
-
return;
|
|
969
|
-
}
|
|
970
|
-
// A drop on 'body' should be a removal.
|
|
971
|
-
if (event.dataTransfer.dropEffect === 'none') {
|
|
972
|
-
if (this.copiedElements.has(this.originalDraggedItem)) {
|
|
973
|
-
this.originalDraggedItem.remove();
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
this.originalDraggedItem = null
|
|
977
|
-
this.draggedItem = null;
|
|
978
|
-
event.stopPropagation();
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
registerLazyElement(element) {
|
|
982
|
-
if (this.intersectionObserver) {
|
|
983
|
-
// console.log('registerLazyElement',element);
|
|
984
|
-
this.intersectionObserver.observe(element);
|
|
985
|
-
}
|
|
999
|
+
_handleDrop(event) {
|
|
1000
|
+
console.log('DROP ON BODY', this);
|
|
1001
|
+
if (!this.draggedItem) {
|
|
1002
|
+
return;
|
|
986
1003
|
}
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
}
|
|
993
|
-
|
|
994
|
-
/**
|
|
995
|
-
*
|
|
996
|
-
* @returns {FxModel}
|
|
997
|
-
*/
|
|
998
|
-
getModel() {
|
|
999
|
-
return this.querySelector('fx-model');
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
_displayMessage(e) {
|
|
1003
|
-
// console.log('_displayMessage',e);
|
|
1004
|
-
const {level} = e.detail;
|
|
1005
|
-
const msg = e.detail.message;
|
|
1006
|
-
this._showMessage(level, msg);
|
|
1007
|
-
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
|
+
}
|
|
1008
1009
|
}
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
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);
|
|
1016
1019
|
}
|
|
1020
|
+
}
|
|
1017
1021
|
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
const path = XPathUtil.shortenPath(evaluateXPathToString('path()',e.target,this));
|
|
1022
|
-
const toast = this.shadowRoot.querySelector('#warn');
|
|
1023
|
-
toast.showToast(`WARN: ${path}:${msg}`);
|
|
1022
|
+
unRegisterLazyElement(element) {
|
|
1023
|
+
if (this.intersectionObserver) {
|
|
1024
|
+
this.intersectionObserver.unobserve(element);
|
|
1024
1025
|
}
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
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);
|
|
1039
1068
|
}
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
console.log('copyToClipboard' , target.value)
|
|
1043
|
-
navigator.clipboard.writeText(target.value);
|
|
1044
|
-
|
|
1069
|
+
if (this.strict) {
|
|
1070
|
+
this._displayError(e);
|
|
1045
1071
|
}
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
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);
|
|
1062
1093
|
}
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
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
|
+
/*
|
|
1075
1107
|
const parentRepeat = item.closest('fx-repeat');
|
|
1076
1108
|
if(parentRepeat){
|
|
1077
1109
|
this.dispatchEvent(
|
|
@@ -1085,47 +1117,46 @@ export class FxFore extends HTMLElement {
|
|
|
1085
1117
|
}
|
|
1086
1118
|
*/
|
|
1087
1119
|
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
// ### create wrapper element
|
|
1099
|
-
const repeatFromAttr = new FxRepeatAttributes();
|
|
1100
|
-
// const repeatFromAttr = document.createElement('fx-repeat-attributes');
|
|
1101
|
-
|
|
1102
|
-
// ### copy the value of 'data-ref' to 'ref' on fx-repeat-attributes
|
|
1103
|
-
repeatFromAttr.setAttribute('ref',item.getAttribute('data-ref'));
|
|
1104
|
-
// 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);
|
|
1105
1129
|
|
|
1106
|
-
|
|
1107
|
-
|
|
1130
|
+
// ### create wrapper element
|
|
1131
|
+
const repeatFromAttr = new FxRepeatAttributes();
|
|
1132
|
+
// const repeatFromAttr = document.createElement('fx-repeat-attributes');
|
|
1108
1133
|
|
|
1109
|
-
|
|
1110
|
-
|
|
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');
|
|
1111
1137
|
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
table.parentNode.removeChild(table);
|
|
1115
|
-
}else{
|
|
1116
|
-
item.parentNode.insertBefore(repeatFromAttr,item);
|
|
1117
|
-
item.parentNode.removeChild(item);
|
|
1118
|
-
}
|
|
1138
|
+
// ### append the cloned original element to fx-repeat-attributes
|
|
1139
|
+
repeatFromAttr.appendChild(host);
|
|
1119
1140
|
|
|
1120
|
-
|
|
1121
|
-
|
|
1141
|
+
// ### insert fx-repeat-attributes element before element with the 'data-ref'
|
|
1142
|
+
// repeats[0].parentNode.insertBefore(repeatFromAttr,repeats[0]);
|
|
1122
1143
|
|
|
1123
|
-
|
|
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);
|
|
1124
1150
|
}
|
|
1125
|
-
|
|
1151
|
+
|
|
1152
|
+
// ### remove original item from DOM
|
|
1153
|
+
item.setAttribute('insertPoint', '');
|
|
1154
|
+
});
|
|
1126
1155
|
}
|
|
1156
|
+
this.repeatsFromAttributesCreated = true;
|
|
1157
|
+
}
|
|
1127
1158
|
}
|
|
1128
1159
|
|
|
1129
1160
|
if (!customElements.get('fx-fore')) {
|
|
1130
|
-
|
|
1161
|
+
customElements.define('fx-fore', FxFore);
|
|
1131
1162
|
}
|