@jinntec/fore 1.10.3 → 2.1.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/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 -0
- package/package.json +1 -1
- package/src/actions/abstract-action.js +101 -67
- package/src/actions/fx-confirm.js +3 -3
- package/src/actions/fx-construct-done.js +28 -0
- package/src/actions/fx-insert.js +53 -51
- package/src/actions/fx-load.js +3 -35
- package/src/actions/fx-refresh.js +3 -0
- package/src/actions/fx-setattribute.js +68 -0
- package/src/actions/fx-toggle.js +1 -1
- package/src/fore.js +79 -2
- package/src/fx-bind.js +1 -0
- package/src/fx-fore.js +116 -66
- package/src/fx-instance.js +1 -0
- package/src/fx-model.js +36 -11
- package/src/fx-submission.js +25 -5
- package/src/getInScopeContext.js +11 -5
- package/src/json-util.js +27 -0
- package/src/relevance.js +18 -0
- package/src/ui/abstract-control.js +2 -0
- package/src/ui/fx-case.js +5 -2
- package/src/ui/fx-container.js +22 -0
- package/src/ui/fx-control.js +25 -31
- package/src/ui/fx-droptarget.js +9 -0
- package/src/ui/fx-inspector.js +16 -12
- package/src/ui/fx-repeat-attributes.js +4 -2
- package/src/ui/fx-repeat.js +366 -355
- package/src/ui/fx-repeatitem.js +78 -86
- package/src/ui/fx-switch.js +7 -2
- package/src/ui/fx-trigger.js +0 -1
- package/src/withDraggability.js +218 -0
- package/src/xpath-evaluation.js +137 -0
package/src/fore.js
CHANGED
|
@@ -18,6 +18,79 @@ export class Fore {
|
|
|
18
18
|
|
|
19
19
|
static TYPE_DEFAULT = 'xs:string';
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Loads and return a piece of HTML
|
|
23
|
+
* @param url - the Url to load from
|
|
24
|
+
* @returns {Promise<string>}
|
|
25
|
+
*/
|
|
26
|
+
static async loadHtml(url){
|
|
27
|
+
try{
|
|
28
|
+
const response = await fetch(url, {
|
|
29
|
+
method: 'GET',
|
|
30
|
+
mode: 'cors',
|
|
31
|
+
credentials: 'same-origin',
|
|
32
|
+
headers: {
|
|
33
|
+
'Content-Type': "text/html",
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
const responseContentType = response.headers.get('content-type').toLowerCase();
|
|
37
|
+
if (responseContentType.startsWith('text/html')) {
|
|
38
|
+
return response.text();
|
|
39
|
+
} else {
|
|
40
|
+
Fore.dispatch(this, 'error', {
|
|
41
|
+
message: `Response has wrong contentType '${responseContentType}'. Should be 'text/html'`,
|
|
42
|
+
level:'Error'
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
} catch (e){
|
|
46
|
+
Fore.dispatch(this, 'error', {
|
|
47
|
+
message: `Html couldn't be loaded from '${url}'`,
|
|
48
|
+
level:'Error'
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* loads a Fore element from given `src`. Always returns the first occurrence of a `<fx-fore>`. The retured element
|
|
55
|
+
* will replace the `replace` element in the DOM.
|
|
56
|
+
*
|
|
57
|
+
* @param replace the element with a `src` attribute to resolvé.
|
|
58
|
+
* @param src the Url to resolve
|
|
59
|
+
* @param selector a querySelector expression to fetch certain element from loaded document
|
|
60
|
+
* @returns {Promise<void>}
|
|
61
|
+
*/
|
|
62
|
+
static async loadForeFromSrc(replace, src, selector){
|
|
63
|
+
if(!src){
|
|
64
|
+
Fore.dispatch(this, 'error', {
|
|
65
|
+
detail: {
|
|
66
|
+
message: `No 'src' attribute present`,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
await Fore.loadHtml(src)
|
|
71
|
+
.then(data => {
|
|
72
|
+
const parsed = new DOMParser().parseFromString(data, 'text/html');
|
|
73
|
+
// const theFore = parsed.querySelector('fx-fore');
|
|
74
|
+
const foreElement = parsed.querySelector(selector);
|
|
75
|
+
// console.log('foreElement', foreElement)
|
|
76
|
+
if (!foreElement) {
|
|
77
|
+
Fore.dispatch(this, 'error', {
|
|
78
|
+
detail: {
|
|
79
|
+
message: `Fore element not found in '${src}'. Maybe wrapped within 'template' element?`,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
foreElement.setAttribute('from-src', src);
|
|
84
|
+
const thisAttrs = replace.attributes;
|
|
85
|
+
Array.from(thisAttrs).forEach(attr =>{
|
|
86
|
+
if(attr.name !== 'src'){
|
|
87
|
+
foreElement.setAttribute(attr.name,attr.value);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
replace.replaceWith(foreElement);
|
|
91
|
+
return foreElement;
|
|
92
|
+
});
|
|
93
|
+
}
|
|
21
94
|
static buildPredicates(node){
|
|
22
95
|
let attrPredicate='';
|
|
23
96
|
Array.from(node.attributes).forEach(attr =>{
|
|
@@ -339,6 +412,7 @@ export class Fore {
|
|
|
339
412
|
return 'done';
|
|
340
413
|
}
|
|
341
414
|
|
|
415
|
+
/*
|
|
342
416
|
static evaluateAttributeTemplateExpression(expr, node) {
|
|
343
417
|
const matches = expr.match(/{[^}]*}/g);
|
|
344
418
|
if (matches) {
|
|
@@ -354,6 +428,7 @@ export class Fore {
|
|
|
354
428
|
}
|
|
355
429
|
return expr;
|
|
356
430
|
}
|
|
431
|
+
*/
|
|
357
432
|
|
|
358
433
|
static fadeInElement(element) {
|
|
359
434
|
const duration = 600;
|
|
@@ -467,6 +542,7 @@ export class Fore {
|
|
|
467
542
|
static stringifiedComponent(element){
|
|
468
543
|
return `<${element.localName} ${Array.from(element.attributes).map(attr=>`${attr.name}="${attr.value}"`).join(' ')}>…</${element.localName}>`;
|
|
469
544
|
}
|
|
545
|
+
/*
|
|
470
546
|
static async loadForeFromUrl(hostElement, url) {
|
|
471
547
|
// console.log('########## loading Fore from ', this.src, '##########');
|
|
472
548
|
await fetch(url, {
|
|
@@ -538,7 +614,7 @@ export class Fore {
|
|
|
538
614
|
// this.appendChild(imported);
|
|
539
615
|
}
|
|
540
616
|
})
|
|
541
|
-
|
|
617
|
+
/!*.catch(error => {
|
|
542
618
|
hostElement.dispatchEvent(
|
|
543
619
|
new CustomEvent('error', {
|
|
544
620
|
composed: false,
|
|
@@ -549,8 +625,9 @@ export class Fore {
|
|
|
549
625
|
},
|
|
550
626
|
}),
|
|
551
627
|
);
|
|
552
|
-
})
|
|
628
|
+
});*!/
|
|
553
629
|
}
|
|
630
|
+
*/
|
|
554
631
|
|
|
555
632
|
/**
|
|
556
633
|
* clear all text nodes and attribute values to get a 'clean' template.
|
package/src/fx-bind.js
CHANGED
package/src/fx-fore.js
CHANGED
|
@@ -26,6 +26,7 @@ import {FxRepeatAttributes} from './ui/fx-repeat-attributes.js';
|
|
|
26
26
|
*/
|
|
27
27
|
export class FxFore extends HTMLElement {
|
|
28
28
|
static outermostHandler = null;
|
|
29
|
+
static draggedItem = null;
|
|
29
30
|
|
|
30
31
|
static get properties() {
|
|
31
32
|
return {
|
|
@@ -56,6 +57,9 @@ export class FxFore extends HTMLElement {
|
|
|
56
57
|
ready: {
|
|
57
58
|
type: Boolean,
|
|
58
59
|
},
|
|
60
|
+
strict:{
|
|
61
|
+
type: Boolean
|
|
62
|
+
},
|
|
59
63
|
/**
|
|
60
64
|
*
|
|
61
65
|
*/
|
|
@@ -100,6 +104,8 @@ export class FxFore extends HTMLElement {
|
|
|
100
104
|
// updates are included in that one
|
|
101
105
|
this.outermostHandler = null;
|
|
102
106
|
|
|
107
|
+
this.copiedElements = new WeakSet();
|
|
108
|
+
|
|
103
109
|
const style = `
|
|
104
110
|
:host {
|
|
105
111
|
display: block;
|
|
@@ -176,7 +182,6 @@ export class FxFore extends HTMLElement {
|
|
|
176
182
|
`;
|
|
177
183
|
|
|
178
184
|
const html = `
|
|
179
|
-
<noscript>This page uses Web Components and needs JavaScript to be enabled..</noscript>
|
|
180
185
|
<!-- <slot name="errors"></slot> -->
|
|
181
186
|
<jinn-toast id="message" gravity="bottom" position="left"></jinn-toast>
|
|
182
187
|
<jinn-toast id="sticky" gravity="bottom" position="left" duration="-1" close="true" data-class="sticky-message"></jinn-toast>
|
|
@@ -204,7 +209,7 @@ export class FxFore extends HTMLElement {
|
|
|
204
209
|
|
|
205
210
|
this.toRefresh = [];
|
|
206
211
|
this.initialRun = true;
|
|
207
|
-
this.
|
|
212
|
+
this._scanForNewTemplateExpressionsNextRefresh = false;
|
|
208
213
|
this.repeatsFromAttributesCreated = false;
|
|
209
214
|
this.validateOn = this.hasAttribute('validate-on') ? this.getAttribute('validate-on'):'update';
|
|
210
215
|
// this.mergePartial = this.hasAttribute('merge-partial')? true:false;
|
|
@@ -214,6 +219,7 @@ export class FxFore extends HTMLElement {
|
|
|
214
219
|
connectedCallback() {
|
|
215
220
|
this.style.visibility = 'hidden';
|
|
216
221
|
console.time('init');
|
|
222
|
+
this.strict = this.hasAttribute('strict') ? true : false;
|
|
217
223
|
/*
|
|
218
224
|
document.addEventListener('ready', (e) =>{
|
|
219
225
|
if(e.target !== this){
|
|
@@ -255,6 +261,7 @@ export class FxFore extends HTMLElement {
|
|
|
255
261
|
const slot = this.shadowRoot.querySelector('slot#default');
|
|
256
262
|
slot.addEventListener('slotchange', async event => {
|
|
257
263
|
// preliminary addition for auto-conversion of non-prefixed element into prefixed elements. See fore.js
|
|
264
|
+
console.log(`### <<<<< slotchange on '${this.id}' >>>>>`);
|
|
258
265
|
if(this.inited) return;
|
|
259
266
|
if(this.hasAttribute('convert')){
|
|
260
267
|
this.replaceWith(Fore.copyDom(this));
|
|
@@ -280,8 +287,8 @@ export class FxFore extends HTMLElement {
|
|
|
280
287
|
}
|
|
281
288
|
if (!modelElement.inited) {
|
|
282
289
|
console.info(
|
|
283
|
-
|
|
284
|
-
"background:#64b5f6; color:white; padding
|
|
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%;",
|
|
285
292
|
);
|
|
286
293
|
|
|
287
294
|
await modelElement.modelConstruct();
|
|
@@ -296,6 +303,9 @@ export class FxFore extends HTMLElement {
|
|
|
296
303
|
this.addEventListener('path-mutated', () => {
|
|
297
304
|
this.someInstanceDataStructureChanged = true;
|
|
298
305
|
});
|
|
306
|
+
this.addEventListener('refresh', () => {
|
|
307
|
+
this.refresh(true);
|
|
308
|
+
});
|
|
299
309
|
|
|
300
310
|
}
|
|
301
311
|
|
|
@@ -319,6 +329,15 @@ export class FxFore extends HTMLElement {
|
|
|
319
329
|
}
|
|
320
330
|
}
|
|
321
331
|
|
|
332
|
+
/**
|
|
333
|
+
* Raise a flag that there might be new template expressions under some node. This happens with
|
|
334
|
+
* repeats updating (new repeat items can have new template expressions) or switches changing their case (new case = new raw HTML)
|
|
335
|
+
*/
|
|
336
|
+
scanForNewTemplateExpressionsNextRefresh() {
|
|
337
|
+
// TODO: also ask for the root of any new HTML: this can prevent some very deep queries.
|
|
338
|
+
this._scanForNewTemplateExpressionsNextRefresh = true;
|
|
339
|
+
}
|
|
340
|
+
|
|
322
341
|
/**
|
|
323
342
|
* loads a Fore from an URL given by `src`.
|
|
324
343
|
*
|
|
@@ -327,54 +346,7 @@ export class FxFore extends HTMLElement {
|
|
|
327
346
|
*/
|
|
328
347
|
async _loadFromSrc() {
|
|
329
348
|
// console.log('########## loading Fore from ', this.src, '##########');
|
|
330
|
-
await
|
|
331
|
-
method: 'GET',
|
|
332
|
-
mode: 'cors',
|
|
333
|
-
credentials: 'include',
|
|
334
|
-
headers: {
|
|
335
|
-
'Content-Type': 'text/html',
|
|
336
|
-
},
|
|
337
|
-
})
|
|
338
|
-
.then(response => {
|
|
339
|
-
const responseContentType = response.headers.get('content-type').toLowerCase();
|
|
340
|
-
console.log('********** responseContentType *********', responseContentType);
|
|
341
|
-
if (responseContentType.startsWith('text/html')) {
|
|
342
|
-
// const htmlResponse = response.text();
|
|
343
|
-
// return new DOMParser().parseFromString(htmlResponse, 'text/html');
|
|
344
|
-
// return response.text();
|
|
345
|
-
return response.text().then(result =>
|
|
346
|
-
// console.log('xml ********', result);
|
|
347
|
-
new DOMParser().parseFromString(result, 'text/html'),
|
|
348
|
-
);
|
|
349
|
-
}
|
|
350
|
-
return 'done';
|
|
351
|
-
})
|
|
352
|
-
.then(data => {
|
|
353
|
-
// const theFore = fxEvaluateXPathToFirstNode('//fx-fore', data.firstElementChild);
|
|
354
|
-
const theFore = data.querySelector('fx-fore');
|
|
355
|
-
|
|
356
|
-
// console.log('thefore', theFore)
|
|
357
|
-
if (!theFore) {
|
|
358
|
-
Fore.dispatch(this, 'error', {
|
|
359
|
-
detail: {
|
|
360
|
-
message: `Fore element not found in '${this.src}'. Maybe wrapped within 'template' element?`,
|
|
361
|
-
},
|
|
362
|
-
});
|
|
363
|
-
}
|
|
364
|
-
theFore.setAttribute('from-src', this.src);
|
|
365
|
-
const thisAttrs = this.attributes;
|
|
366
|
-
Array.from(thisAttrs).forEach(attr =>{
|
|
367
|
-
if(attr.name !== 'src'){
|
|
368
|
-
theFore.setAttribute(attr.name,attr.value);
|
|
369
|
-
}
|
|
370
|
-
});
|
|
371
|
-
this.replaceWith(theFore);
|
|
372
|
-
})
|
|
373
|
-
.catch(() => {
|
|
374
|
-
Fore.dispatch(this, 'error', {
|
|
375
|
-
message: `'${this.src}' not found or does not contain Fore element.`,
|
|
376
|
-
});
|
|
377
|
-
});
|
|
349
|
+
await Fore.loadForeFromSrc(this,this.src,'fx-fore');
|
|
378
350
|
}
|
|
379
351
|
|
|
380
352
|
/**
|
|
@@ -420,6 +392,7 @@ export class FxFore extends HTMLElement {
|
|
|
420
392
|
}
|
|
421
393
|
|
|
422
394
|
disconnectedCallback() {
|
|
395
|
+
this.removeEventListener('dragstart', this.dragstart);
|
|
423
396
|
/*
|
|
424
397
|
this.removeEventListener('model-construct-done', this._handleModelConstructDone);
|
|
425
398
|
this.removeEventListener('message', this._displayMessage);
|
|
@@ -443,8 +416,11 @@ export class FxFore extends HTMLElement {
|
|
|
443
416
|
|
|
444
417
|
Fore.refreshChildren(this, true);
|
|
445
418
|
this._updateTemplateExpressions();
|
|
446
|
-
this.
|
|
419
|
+
this._scanForNewTemplateExpressionsNextRefresh = false; // reset
|
|
447
420
|
this._processTemplateExpressions();
|
|
421
|
+
|
|
422
|
+
console.log(`### <<<<< refresh-done ${this.id} >>>>>`);
|
|
423
|
+
|
|
448
424
|
Fore.dispatch(this, 'refresh-done', {});
|
|
449
425
|
|
|
450
426
|
// console.groupEnd();
|
|
@@ -493,7 +469,7 @@ export class FxFore extends HTMLElement {
|
|
|
493
469
|
return;
|
|
494
470
|
}
|
|
495
471
|
this.isRefreshing = true;
|
|
496
|
-
console.log(
|
|
472
|
+
console.log(`### <<<<< refresh() on '${this.id}' >>>>>`);
|
|
497
473
|
|
|
498
474
|
// refresh () {
|
|
499
475
|
// ### refresh Fore UI elements
|
|
@@ -553,24 +529,44 @@ export class FxFore extends HTMLElement {
|
|
|
553
529
|
}
|
|
554
530
|
|
|
555
531
|
// ### refresh template expressions
|
|
556
|
-
if (this.initialRun || this.
|
|
532
|
+
if (this.initialRun || this._scanForNewTemplateExpressionsNextRefresh) {
|
|
557
533
|
this._updateTemplateExpressions();
|
|
558
|
-
this.
|
|
534
|
+
this._scanForNewTemplateExpressionsNextRefresh = false; // reset
|
|
559
535
|
}
|
|
536
|
+
|
|
560
537
|
this._processTemplateExpressions();
|
|
561
538
|
|
|
562
539
|
// console.log('### <<<<< dispatching refresh-done - end of UI update cycle >>>>>');
|
|
563
540
|
// this.dispatchEvent(new CustomEvent('refresh-done'));
|
|
564
541
|
// this.initialRun = false;
|
|
565
542
|
this.style.visibility='visible';
|
|
543
|
+
console.log(`### <<<<< refresh-done ${this.id} >>>>>`);
|
|
566
544
|
Fore.dispatch(this, 'refresh-done', {});
|
|
567
545
|
|
|
568
546
|
// this.isRefreshing = true;
|
|
569
547
|
// this.parentNode.closest('fx-fore')?.refresh(false, changedPaths);
|
|
570
|
-
|
|
571
|
-
|
|
548
|
+
|
|
549
|
+
const subFores = Array.from(this.querySelectorAll('fx-fore'));
|
|
550
|
+
/*
|
|
551
|
+
calling the parent to refresh causes errors and inconsistent state. Also it is questionable
|
|
552
|
+
if a child should actually interact with its parent in this way.
|
|
553
|
+
|
|
554
|
+
This only affects the refreshing NOT the data mutation itself which is happening as expected.
|
|
555
|
+
|
|
556
|
+
Current solution is that a child that wants the parent to refresh must do so by adding an additional
|
|
557
|
+
event handler that dispatches an event upwards and having a handler in the parent to refresh itself.
|
|
558
|
+
|
|
559
|
+
So refreshed propagate downwards but not upwards which is at least an option to consider.
|
|
560
|
+
|
|
561
|
+
if(this.parentNode.nodeType !== Node.DOCUMENT_FRAGMENT_NODE){
|
|
562
|
+
// await this.parentNode.closest('fx-fore')?.refresh(false);
|
|
563
|
+
}
|
|
564
|
+
*/
|
|
565
|
+
for (const subFore of subFores) {
|
|
572
566
|
// subFore.refresh(false, changedPaths);
|
|
573
|
-
|
|
567
|
+
if(subFore.ready){
|
|
568
|
+
await subFore.refresh(false);
|
|
569
|
+
}
|
|
574
570
|
}
|
|
575
571
|
this.isRefreshing = false;
|
|
576
572
|
}
|
|
@@ -672,11 +668,11 @@ export class FxFore extends HTMLElement {
|
|
|
672
668
|
const naked = match.substring(1, match.length - 1);
|
|
673
669
|
const inscope = getInScopeContext(node, naked);
|
|
674
670
|
if (!inscope) {
|
|
671
|
+
console.warn('no inscope context for expr', naked);
|
|
675
672
|
const errNode =
|
|
676
673
|
node.nodeType === Node.TEXT_NODE || node.nodeType === Node.ATTRIBUTE_NODE
|
|
677
674
|
? node.parentNode
|
|
678
675
|
: node;
|
|
679
|
-
console.warn('no inscope context for ', errNode);
|
|
680
676
|
return match;
|
|
681
677
|
}
|
|
682
678
|
// Templates are special: they use the namespace configuration from the place where they are
|
|
@@ -695,11 +691,17 @@ export class FxFore extends HTMLElement {
|
|
|
695
691
|
}
|
|
696
692
|
});
|
|
697
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
|
|
698
696
|
if (node.nodeType === Node.ATTRIBUTE_NODE) {
|
|
699
697
|
const parent = node.ownerElement;
|
|
700
|
-
|
|
698
|
+
if (parent.getAttribute(node.nodeName) !== replaced) {
|
|
699
|
+
parent.setAttribute(node.nodeName, replaced);
|
|
700
|
+
}
|
|
701
701
|
} else if (node.nodeType === Node.TEXT_NODE) {
|
|
702
|
-
|
|
702
|
+
if (node.textContent !== replaced) {
|
|
703
|
+
node.textContent = replaced;
|
|
704
|
+
}
|
|
703
705
|
}
|
|
704
706
|
}
|
|
705
707
|
|
|
@@ -770,7 +772,11 @@ export class FxFore extends HTMLElement {
|
|
|
770
772
|
const model = this.querySelector('fx-model');
|
|
771
773
|
|
|
772
774
|
// ##### lazy creation should NOT take place if there's a parent Fore using shared instances
|
|
773
|
-
const parentFore = this.parentNode.closest('fx-fore');
|
|
775
|
+
const parentFore = this.parentNode.nodeType !== Node.DOCUMENT_FRAGMENT_NODE ? this.parentNode.closest('fx-fore'): null;
|
|
776
|
+
if(this.parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE){
|
|
777
|
+
console.log('fragment',this.parentNode)
|
|
778
|
+
}
|
|
779
|
+
|
|
774
780
|
if(parentFore){
|
|
775
781
|
const shared = parentFore.getModel().instances.filter(shared => shared.hasAttribute('shared'));
|
|
776
782
|
if(shared.length !==0) return;
|
|
@@ -888,7 +894,7 @@ export class FxFore extends HTMLElement {
|
|
|
888
894
|
*/
|
|
889
895
|
async _initUI() {
|
|
890
896
|
// console.log('### _initUI()');
|
|
891
|
-
console.log(
|
|
897
|
+
console.log(`### <<<<< _initUI '${this.id}' >>>>>`);
|
|
892
898
|
|
|
893
899
|
if (!this.initialRun) return;
|
|
894
900
|
this.classList.add('initialRun');
|
|
@@ -929,8 +935,51 @@ export class FxFore extends HTMLElement {
|
|
|
929
935
|
Fore.dispatch(this, 'ready', {});
|
|
930
936
|
// console.log('dataChanged', FxModel.dataChanged);
|
|
931
937
|
console.timeEnd('init');
|
|
938
|
+
|
|
939
|
+
this.addEventListener('dragstart', this._handleDragStart);
|
|
940
|
+
// this.addEventListener('dragend', this._handleDragEnd);
|
|
941
|
+
this.handleDrop = event => this._handleDrop(event);
|
|
942
|
+
this.ownerDocument.body.addEventListener('drop', this.handleDrop);
|
|
943
|
+
this.ownerDocument.body.addEventListener('dragover', e=>{
|
|
944
|
+
e.preventDefault();
|
|
945
|
+
e.stopPropagation();
|
|
946
|
+
e.dataTransfer.dropEffect = "move";
|
|
947
|
+
});
|
|
932
948
|
}
|
|
933
949
|
|
|
950
|
+
_handleDragStart (event) {
|
|
951
|
+
const draggedItem = event.target.closest('[draggable="true"]');
|
|
952
|
+
this.originalDraggedItem = draggedItem;
|
|
953
|
+
console.log('DRAG START', this);
|
|
954
|
+
if (draggedItem.getAttribute('drop-action') === 'copy') {
|
|
955
|
+
event.dataTransfer.dropEffect = 'copy';
|
|
956
|
+
event.dataTransfer.effectAllowed = 'copy';
|
|
957
|
+
this.draggedItem = draggedItem.cloneNode(true);
|
|
958
|
+
this.draggedItem.setAttribute('drop-action', 'move');
|
|
959
|
+
this.copiedElements.add(this.draggedItem);
|
|
960
|
+
} else {
|
|
961
|
+
event.dataTransfer.dropEffect = 'move';
|
|
962
|
+
event.dataTransfer.effectAllowed = 'move';
|
|
963
|
+
this.draggedItem = draggedItem;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
_handleDrop (event) {
|
|
968
|
+
console.log('DROP ON BODY', this)
|
|
969
|
+
if (!this.draggedItem) {
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
// A drop on 'body' should be a removal.
|
|
973
|
+
if (event.dataTransfer.dropEffect === 'none') {
|
|
974
|
+
if (this.copiedElements.has(this.originalDraggedItem)) {
|
|
975
|
+
this.originalDraggedItem.remove();
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
this.originalDraggedItem = null
|
|
979
|
+
this.draggedItem = null;
|
|
980
|
+
event.stopPropagation();
|
|
981
|
+
}
|
|
982
|
+
|
|
934
983
|
registerLazyElement(element) {
|
|
935
984
|
if (this.intersectionObserver) {
|
|
936
985
|
// console.log('registerLazyElement',element);
|
|
@@ -986,8 +1035,9 @@ export class FxFore extends HTMLElement {
|
|
|
986
1035
|
if(e.detail.expr){
|
|
987
1036
|
console.error('Failing expression',e.detail.expr);
|
|
988
1037
|
}
|
|
989
|
-
|
|
990
|
-
|
|
1038
|
+
if(this.strict){
|
|
1039
|
+
this._displayError(e);
|
|
1040
|
+
}
|
|
991
1041
|
}
|
|
992
1042
|
|
|
993
1043
|
_copyToClipboard(target){
|
package/src/fx-instance.js
CHANGED
|
@@ -110,6 +110,7 @@ export class FxInstance extends HTMLElement {
|
|
|
110
110
|
async init() {
|
|
111
111
|
// console.log('fx-instance init');
|
|
112
112
|
await this._initInstance();
|
|
113
|
+
console.log(`### <<<<< instance ${this.id} loaded >>>>> `);
|
|
113
114
|
this.dispatchEvent(
|
|
114
115
|
new CustomEvent('instance-loaded', {
|
|
115
116
|
composed: true,
|
package/src/fx-model.js
CHANGED
|
@@ -106,7 +106,7 @@ export class FxModel extends HTMLElement {
|
|
|
106
106
|
*
|
|
107
107
|
*/
|
|
108
108
|
async modelConstruct() {
|
|
109
|
-
console.log(
|
|
109
|
+
console.log(`### <<<<< dispatching model-construct for '${this.fore.id}' >>>>>`);
|
|
110
110
|
// this.dispatchEvent(new CustomEvent('model-construct', { detail: this }));
|
|
111
111
|
Fore.dispatch(this, 'model-construct', {model: this});
|
|
112
112
|
|
|
@@ -129,7 +129,7 @@ export class FxModel extends HTMLElement {
|
|
|
129
129
|
this.updateModel();
|
|
130
130
|
} else {
|
|
131
131
|
// ### if there's no instance one will created
|
|
132
|
-
console.log(
|
|
132
|
+
console.log(`### <<<<< dispatching model-construct-done for '${this.fore.id}' >>>>>`);
|
|
133
133
|
await this.dispatchEvent(
|
|
134
134
|
new CustomEvent('model-construct-done', {
|
|
135
135
|
composed: false,
|
|
@@ -167,7 +167,7 @@ export class FxModel extends HTMLElement {
|
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
rebuild() {
|
|
170
|
-
console.log(
|
|
170
|
+
console.log(`### <<<<< rebuild() '${this.fore.id}' >>>>>`);
|
|
171
171
|
|
|
172
172
|
this.mainGraph = new DepGraph(false); // do: should be moved down below binds.length check but causes errors in tests.
|
|
173
173
|
this.modelItems = [];
|
|
@@ -203,7 +203,7 @@ export class FxModel extends HTMLElement {
|
|
|
203
203
|
return;
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
console.log(
|
|
206
|
+
console.log(`### <<<<< recalculate() '${this.fore.id}' >>>>>`);
|
|
207
207
|
|
|
208
208
|
// console.log('changed nodes ', this.changed);
|
|
209
209
|
this.computes = 0;
|
|
@@ -368,7 +368,7 @@ export class FxModel extends HTMLElement {
|
|
|
368
368
|
|
|
369
369
|
if (this.modelItems.length === 0) return true;
|
|
370
370
|
|
|
371
|
-
console.log(
|
|
371
|
+
console.log(`### <<<<< revalidate() '${this.fore.id}' >>>>>`);
|
|
372
372
|
|
|
373
373
|
// reset submission validation
|
|
374
374
|
// this.parentNode.classList.remove('submit-validation-failed')
|
|
@@ -447,7 +447,10 @@ export class FxModel extends HTMLElement {
|
|
|
447
447
|
}
|
|
448
448
|
|
|
449
449
|
getDefaultInstance() {
|
|
450
|
-
|
|
450
|
+
if (this.instances.length) {
|
|
451
|
+
return this.instances[0];
|
|
452
|
+
}
|
|
453
|
+
return this.getInstance('default');
|
|
451
454
|
}
|
|
452
455
|
|
|
453
456
|
getDefaultInstanceData() {
|
|
@@ -459,18 +462,40 @@ export class FxModel extends HTMLElement {
|
|
|
459
462
|
// console.log('instances ', this.instances);
|
|
460
463
|
// console.log('instances array ',Array.from(this.instances));
|
|
461
464
|
|
|
462
|
-
|
|
463
|
-
|
|
465
|
+
let found;
|
|
466
|
+
if(id === 'default'){
|
|
467
|
+
found = this.instances[0];
|
|
468
|
+
}
|
|
469
|
+
// ### lookup in local instances first
|
|
464
470
|
if(!found) {
|
|
465
|
-
|
|
471
|
+
const instArray = Array.from(this.instances);
|
|
472
|
+
found = instArray.find(inst => inst.id === id);
|
|
473
|
+
const parentFore = this.fore.parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE
|
|
474
|
+
? this.fore.parentNode.host.closest('fx-fore')
|
|
475
|
+
: this.fore.parentNode.closest('fx-fore');
|
|
476
|
+
|
|
477
|
+
}
|
|
478
|
+
// ### lookup in parent Fore if present
|
|
479
|
+
if(!found){
|
|
480
|
+
// const parentFore = this.fore.parentNode.closest('fx-fore');
|
|
481
|
+
const parentFore = this.fore.parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE
|
|
482
|
+
? this.fore.parentNode.host.closest('fx-fore')
|
|
483
|
+
: this.fore.parentNode.closest('fx-fore');
|
|
466
484
|
if (parentFore) {
|
|
467
485
|
console.log('shared instances from parent', this.parentNode.id);
|
|
468
486
|
const parentInstances = parentFore.getModel().instances;
|
|
469
487
|
const shared = parentInstances.filter(shared => shared.hasAttribute('shared'));
|
|
470
488
|
found = shared.find(found => found.id === id);
|
|
471
489
|
}
|
|
490
|
+
|
|
472
491
|
}
|
|
473
|
-
if(
|
|
492
|
+
if(found){
|
|
493
|
+
return found;
|
|
494
|
+
}
|
|
495
|
+
if (id === 'default') {
|
|
496
|
+
return this.getDefaultInstance(); // if id is not found always defaults to first in doc order
|
|
497
|
+
}
|
|
498
|
+
if(!found && this.fore.strict){
|
|
474
499
|
// return this.getDefaultInstance(); // if id is not found always defaults to first in doc order
|
|
475
500
|
Fore.dispatch(this, 'error', {
|
|
476
501
|
origin: this,
|
|
@@ -478,7 +503,7 @@ export class FxModel extends HTMLElement {
|
|
|
478
503
|
level:'Error'
|
|
479
504
|
});
|
|
480
505
|
}
|
|
481
|
-
return
|
|
506
|
+
return null;
|
|
482
507
|
}
|
|
483
508
|
|
|
484
509
|
evalBinding(bindingExpr) {
|
package/src/fx-submission.js
CHANGED
|
@@ -48,14 +48,16 @@ export class FxSubmission extends foreElementMixin(HTMLElement) {
|
|
|
48
48
|
? this.getAttribute('serialization')
|
|
49
49
|
: 'xml';
|
|
50
50
|
|
|
51
|
-
this.url = this.hasAttribute('url') ? this.getAttribute('url'):null;
|
|
52
|
-
|
|
53
|
-
this.targetref = this.hasAttribute('targetref') ? this.getAttribute('targetref') : null;
|
|
54
|
-
|
|
55
51
|
this.mediatype = this.hasAttribute('mediatype')
|
|
56
52
|
? this.getAttribute('mediatype')
|
|
57
53
|
: 'application/xml';
|
|
58
54
|
|
|
55
|
+
this.responseMediatype = this.hasAttribute('response-mediatype') ? this.getAttribute('response-mediatype') : this.mediatype;
|
|
56
|
+
this.url = this.hasAttribute('url') ? this.getAttribute('url') : null;
|
|
57
|
+
|
|
58
|
+
this.targetref = this.hasAttribute('targetref') ? this.getAttribute('targetref') : null;
|
|
59
|
+
|
|
60
|
+
|
|
59
61
|
this.validate = this.getAttribute('validate') ? this.getAttribute('validate') : 'true';
|
|
60
62
|
this.credentials = this.hasAttribute('credentials')
|
|
61
63
|
? this.getAttribute('credentials')
|
|
@@ -85,7 +87,7 @@ export class FxSubmission extends foreElementMixin(HTMLElement) {
|
|
|
85
87
|
|
|
86
88
|
model.recalculate();
|
|
87
89
|
|
|
88
|
-
if (this.validate === 'true') {
|
|
90
|
+
if (this.validate === 'true' && this.method !== 'get') {
|
|
89
91
|
const valid = model.revalidate();
|
|
90
92
|
if (!valid) {
|
|
91
93
|
console.log('validation failed. Submission stopped');
|
|
@@ -134,6 +136,23 @@ export class FxSubmission extends foreElementMixin(HTMLElement) {
|
|
|
134
136
|
|
|
135
137
|
// let serialized = serializer.serializeToString(relevant);
|
|
136
138
|
if (this.method.toLowerCase() === 'get') {
|
|
139
|
+
/*
|
|
140
|
+
todo: serialize the bound instance element names as get parameters and using their text values
|
|
141
|
+
as param values. leave out empty params and create querystring from the result.Elements may
|
|
142
|
+
have exactly level deep or are otherwise ignored.
|
|
143
|
+
<data>
|
|
144
|
+
<id>1234</id>
|
|
145
|
+
<name>john</name>
|
|
146
|
+
<zip></zip>
|
|
147
|
+
<!-- ignored as no direct text value -->
|
|
148
|
+
<phone>
|
|
149
|
+
<mobile></mobile>
|
|
150
|
+
<phone>
|
|
151
|
+
</data>
|
|
152
|
+
results in: ?id=1234&name=john to be appended to this.url on fetch
|
|
153
|
+
|
|
154
|
+
*/
|
|
155
|
+
|
|
137
156
|
serialized = undefined;
|
|
138
157
|
}
|
|
139
158
|
// console.log('data being send', serialized);
|
|
@@ -182,6 +201,7 @@ export class FxSubmission extends foreElementMixin(HTMLElement) {
|
|
|
182
201
|
const key = resolvedUrl.substring(resolvedUrl.indexOf(':') + 1);
|
|
183
202
|
localStorage.removeItem(key);
|
|
184
203
|
const newInst = new DOMParser().parseFromString('<data></data>', 'application/xml');
|
|
204
|
+
this.replace = 'instance';
|
|
185
205
|
this._handleResponse(newInst);
|
|
186
206
|
console.log('### <<<<< submit-done >>>>>');
|
|
187
207
|
Fore.dispatch(this, 'submit-done', {});
|