@jinntec/fore 1.3.0 → 1.4.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 +4 -4
- package/dist/fore-dev.js.map +1 -1
- package/dist/fore.js +3 -3
- package/dist/fore.js.map +1 -1
- package/package.json +1 -1
- package/resources/fore.css +6 -1
- package/src/ForeElementMixin.js +4 -0
- package/src/actions/abstract-action.js +77 -35
- package/src/actions/fx-action.js +9 -9
- package/src/actions/fx-append.js +1 -1
- package/src/actions/fx-confirm.js +1 -1
- package/src/actions/fx-copy.js +68 -0
- package/src/actions/fx-delete.js +1 -1
- package/src/actions/fx-dispatch.js +1 -1
- package/src/actions/fx-hide.js +1 -1
- package/src/actions/fx-insert.js +1 -1
- package/src/actions/fx-message.js +1 -1
- package/src/actions/fx-refresh.js +2 -2
- package/src/actions/fx-reload.js +1 -1
- package/src/actions/fx-replace.js +1 -1
- package/src/actions/fx-return.js +1 -1
- package/src/actions/fx-send.js +2 -2
- package/src/actions/fx-setfocus.js +1 -1
- package/src/actions/fx-setvalue.js +1 -1
- package/src/actions/fx-show.js +1 -1
- package/src/actions/fx-toggle.js +1 -1
- package/src/actions/fx-update.js +1 -1
- package/src/fore.js +58 -10
- package/src/fx-bind.js +5 -0
- package/src/fx-fore.js +36 -35
- package/src/fx-instance.js +70 -70
- package/src/fx-model.js +12 -17
- package/src/fx-submission.js +420 -416
- package/src/getInScopeContext.js +2 -4
- package/src/modelitem.js +3 -1
- package/src/ui/abstract-control.js +1 -1
- package/src/ui/fx-control.js +2 -2
- package/src/ui/fx-switch.js +9 -1
- package/src/ui/fx-trigger.js +19 -18
- package/src/xpath-evaluation.js +21 -9
- package/src/xpath-util.js +13 -28
package/src/fx-fore.js
CHANGED
|
@@ -5,7 +5,6 @@ import '@jinntec/jinn-toast';
|
|
|
5
5
|
import {evaluateXPathToBoolean, evaluateXPathToNodes, evaluateXPathToString} from './xpath-evaluation.js';
|
|
6
6
|
import getInScopeContext from './getInScopeContext.js';
|
|
7
7
|
import {XPathUtil} from './xpath-util.js';
|
|
8
|
-
import {AbstractAction} from "./actions/abstract-action";
|
|
9
8
|
|
|
10
9
|
/**
|
|
11
10
|
* Main class for Fore.Outermost container element for each Fore application.
|
|
@@ -15,9 +14,9 @@ import {AbstractAction} from "./actions/abstract-action";
|
|
|
15
14
|
* fx-fore is the outermost container for each form. A form can have exactly one model
|
|
16
15
|
* with arbitrary number of instances.
|
|
17
16
|
*
|
|
18
|
-
* Main
|
|
17
|
+
* Main responsibilities are initialization and updating of model and instances, update of UI (refresh) and global messaging.
|
|
19
18
|
*
|
|
20
|
-
* @event compute-exception - dispatched in case the dependency graph is
|
|
19
|
+
* @event compute-exception - dispatched in case the dependency graph is circular
|
|
21
20
|
* @event refresh-done - dispatched after a refresh() run
|
|
22
21
|
* @event ready - dispatched after Fore has fully been initialized
|
|
23
22
|
* @event error - dispatches error when template expression fails to evaluate
|
|
@@ -56,7 +55,7 @@ export class FxFore extends HTMLElement {
|
|
|
56
55
|
constructor() {
|
|
57
56
|
super();
|
|
58
57
|
this.model = {};
|
|
59
|
-
this.addEventListener('model-construct-done', this._handleModelConstructDone);
|
|
58
|
+
// this.addEventListener('model-construct-done', this._handleModelConstructDone);
|
|
60
59
|
this.addEventListener('message', this._displayMessage);
|
|
61
60
|
this.addEventListener('error', this._displayError);
|
|
62
61
|
window.addEventListener('compute-exception', e => {
|
|
@@ -68,19 +67,11 @@ export class FxFore extends HTMLElement {
|
|
|
68
67
|
|
|
69
68
|
const style = `
|
|
70
69
|
:host {
|
|
71
|
-
|
|
72
|
-
height:auto;
|
|
73
|
-
padding:var(--model-element-padding);
|
|
74
|
-
font-family:Roboto, sans-serif;
|
|
75
|
-
color:var(--paper-grey-900);
|
|
70
|
+
display: block;
|
|
76
71
|
}
|
|
77
72
|
:host ::slotted(fx-model){
|
|
78
73
|
display:none;
|
|
79
74
|
}
|
|
80
|
-
// :host(.fx-ready){
|
|
81
|
-
// animation: fadein .4s forwards;
|
|
82
|
-
// display:block;
|
|
83
|
-
// }
|
|
84
75
|
|
|
85
76
|
#modalMessage .dialogActions{
|
|
86
77
|
text-align:center;
|
|
@@ -144,14 +135,6 @@ export class FxFore extends HTMLElement {
|
|
|
144
135
|
#messageContent{
|
|
145
136
|
margin-top:40px;
|
|
146
137
|
}
|
|
147
|
-
@keyframes fadein {
|
|
148
|
-
0% {
|
|
149
|
-
opacity:0;
|
|
150
|
-
}
|
|
151
|
-
100% {
|
|
152
|
-
opacity:1;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
138
|
`;
|
|
156
139
|
|
|
157
140
|
const html = `
|
|
@@ -183,7 +166,7 @@ export class FxFore extends HTMLElement {
|
|
|
183
166
|
|
|
184
167
|
connectedCallback() {
|
|
185
168
|
this.style.visibility = 'hidden';
|
|
186
|
-
|
|
169
|
+
console.time('init');
|
|
187
170
|
/*
|
|
188
171
|
document.addEventListener('ready', (e) =>{
|
|
189
172
|
if(e.target !== this){
|
|
@@ -220,7 +203,7 @@ export class FxFore extends HTMLElement {
|
|
|
220
203
|
}
|
|
221
204
|
|
|
222
205
|
const slot = this.shadowRoot.querySelector('slot');
|
|
223
|
-
slot.addEventListener('slotchange', event => {
|
|
206
|
+
slot.addEventListener('slotchange', async event => {
|
|
224
207
|
|
|
225
208
|
// preliminary addition for auto-conversion of non-prefixed element into prefixed elements. See fore.js
|
|
226
209
|
if(this.hasAttribute('convert')){
|
|
@@ -239,19 +222,20 @@ export class FxFore extends HTMLElement {
|
|
|
239
222
|
modelElement = generatedModel;
|
|
240
223
|
}
|
|
241
224
|
if (!modelElement.inited) {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
225
|
+
console.info(
|
|
226
|
+
`%cFore is processing URL ${window.location.href}`,
|
|
227
|
+
"background:#64b5f6; color:white; padding:1rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;",
|
|
228
|
+
);
|
|
246
229
|
|
|
247
230
|
if (this.src) {
|
|
248
231
|
console.log('########## FORE: loaded from ... ', this.src, '##########');
|
|
249
232
|
}
|
|
250
|
-
modelElement.modelConstruct();
|
|
233
|
+
await modelElement.modelConstruct();
|
|
234
|
+
this._handleModelConstructDone();
|
|
251
235
|
}
|
|
252
236
|
this.model = modelElement;
|
|
253
237
|
});
|
|
254
|
-
this.addEventListener('path-mutated',
|
|
238
|
+
this.addEventListener('path-mutated', () => {
|
|
255
239
|
// console.log('path-mutated event received', e.detail.path, e.detail.index);
|
|
256
240
|
this.someInstanceDataStructureChanged = true;
|
|
257
241
|
});
|
|
@@ -300,7 +284,7 @@ export class FxFore extends HTMLElement {
|
|
|
300
284
|
|
|
301
285
|
// console.log('thefore', theFore)
|
|
302
286
|
if (!theFore) {
|
|
303
|
-
Fore.
|
|
287
|
+
Fore.dispatch(this, 'error', {
|
|
304
288
|
detail: {
|
|
305
289
|
message: `Fore element not found in '${this.src}'. Maybe wrapped within 'template' element?`,
|
|
306
290
|
},
|
|
@@ -324,9 +308,13 @@ export class FxFore extends HTMLElement {
|
|
|
324
308
|
*/
|
|
325
309
|
handleIntersect(entries, observer) {
|
|
326
310
|
console.time('refreshLazy');
|
|
311
|
+
|
|
327
312
|
entries.forEach(entry => {
|
|
328
313
|
const {target} = entry;
|
|
329
314
|
|
|
315
|
+
const fore = Fore.getFore(target);
|
|
316
|
+
if(fore.initialRun) return;
|
|
317
|
+
|
|
330
318
|
if (entry.isIntersecting) {
|
|
331
319
|
console.log('in view', entry);
|
|
332
320
|
// console.log('repeat in view entry', entry.target);
|
|
@@ -497,7 +485,18 @@ export class FxFore extends HTMLElement {
|
|
|
497
485
|
}
|
|
498
486
|
|
|
499
487
|
_processTemplateExpressions() {
|
|
500
|
-
for (const node of this.storedTemplateExpressionByNode.keys()) {
|
|
488
|
+
for (const node of Array.from(this.storedTemplateExpressionByNode.keys())) {
|
|
489
|
+
if (node.nodeType === Node.ATTRIBUTE_NODE) {
|
|
490
|
+
// Attribute nodes are not contained by the document, but their owner elements are!
|
|
491
|
+
if (!node.ownerDocument.contains(node.ownerElement)) {
|
|
492
|
+
this.storedTemplateExpressionByNode.delete(node);
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
} else if (!node.ownerDocument.contains(node)) {
|
|
496
|
+
// For all other nodes, if the document does not contain them, they are dead
|
|
497
|
+
this.storedTemplateExpressionByNode.delete(node);
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
501
500
|
this._processTemplateExpression({
|
|
502
501
|
node,
|
|
503
502
|
expr: this.storedTemplateExpressionByNode.get(node),
|
|
@@ -512,7 +511,7 @@ export class FxFore extends HTMLElement {
|
|
|
512
511
|
const {expr} = exprObj;
|
|
513
512
|
const {node} = exprObj;
|
|
514
513
|
// console.log('expr ', expr);
|
|
515
|
-
this.evaluateTemplateExpression(expr, node
|
|
514
|
+
this.evaluateTemplateExpression(expr, node);
|
|
516
515
|
}
|
|
517
516
|
|
|
518
517
|
/**
|
|
@@ -567,7 +566,7 @@ export class FxFore extends HTMLElement {
|
|
|
567
566
|
|
|
568
567
|
/**
|
|
569
568
|
* called when `model-construct-done` event is received to
|
|
570
|
-
* start initing
|
|
569
|
+
* start initing the UI.
|
|
571
570
|
*
|
|
572
571
|
* @private
|
|
573
572
|
*/
|
|
@@ -583,7 +582,7 @@ export class FxFore extends HTMLElement {
|
|
|
583
582
|
&& condition !== 'true'
|
|
584
583
|
&& condition !== ''){
|
|
585
584
|
window.addEventListener('beforeunload', event => {
|
|
586
|
-
const mustDisplay = evaluateXPathToBoolean(
|
|
585
|
+
const mustDisplay = evaluateXPathToBoolean(condition, this.getModel().getDefaultContext(), this)
|
|
587
586
|
if(mustDisplay){
|
|
588
587
|
console.log('have to display confirmation')
|
|
589
588
|
return event.returnValue = 'are you sure';
|
|
@@ -747,7 +746,8 @@ export class FxFore extends HTMLElement {
|
|
|
747
746
|
};
|
|
748
747
|
*/
|
|
749
748
|
|
|
750
|
-
|
|
749
|
+
// First refresh should be forced
|
|
750
|
+
await this.refresh(true);
|
|
751
751
|
// this.style.display='block'
|
|
752
752
|
this.classList.add('fx-ready');
|
|
753
753
|
document.body.classList.add('fx-ready');
|
|
@@ -761,6 +761,7 @@ export class FxFore extends HTMLElement {
|
|
|
761
761
|
`%cPage Initialization done`,
|
|
762
762
|
"background:#64b5f6; color:white; padding:1rem; display:block; white-space: nowrap; border-radius:0.3rem;width:100%;",
|
|
763
763
|
);
|
|
764
|
+
console.timeEnd('init');
|
|
764
765
|
console.log('dataChanged', FxModel.dataChanged);
|
|
765
766
|
}
|
|
766
767
|
|
package/src/fx-instance.js
CHANGED
|
@@ -1,6 +1,43 @@
|
|
|
1
1
|
import { Fore } from './fore.js';
|
|
2
2
|
import { evaluateXPathToFirstNode } from './xpath-evaluation.js';
|
|
3
3
|
|
|
4
|
+
async function handleResponse(response) {
|
|
5
|
+
const { status } = response;
|
|
6
|
+
if (status >= 400) {
|
|
7
|
+
// console.log('response status', status);
|
|
8
|
+
alert(`response status: ${status} - failed to load data for '${this.src}' - stopping.`);
|
|
9
|
+
throw new Error(`failed to load data - status: ${status}`);
|
|
10
|
+
}
|
|
11
|
+
const responseContentType = response.headers.get('content-type').toLowerCase();
|
|
12
|
+
// console.log('********** responseContentType *********', responseContentType);
|
|
13
|
+
if (responseContentType.startsWith('text/html')) {
|
|
14
|
+
// const htmlResponse = response.text();
|
|
15
|
+
// return new DOMParser().parseFromString(htmlResponse, 'text/html');
|
|
16
|
+
// return response.text();
|
|
17
|
+
return response.text().then(result =>
|
|
18
|
+
// console.log('xml ********', result);
|
|
19
|
+
new DOMParser().parseFromString(result, 'text/html'),
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
if (
|
|
23
|
+
responseContentType.startsWith('text/plain') ||
|
|
24
|
+
responseContentType.startsWith('text/markdown')
|
|
25
|
+
) {
|
|
26
|
+
// console.log("********** inside res plain *********");
|
|
27
|
+
return response.text();
|
|
28
|
+
}
|
|
29
|
+
if (responseContentType.startsWith('application/json')) {
|
|
30
|
+
// console.log("********** inside res json *********");
|
|
31
|
+
return response.json();
|
|
32
|
+
}
|
|
33
|
+
if (responseContentType.startsWith('application/xml')) {
|
|
34
|
+
const text = await response.text();
|
|
35
|
+
// console.log('xml ********', result);
|
|
36
|
+
return new DOMParser().parseFromString(text, 'application/xml');
|
|
37
|
+
}
|
|
38
|
+
return 'done';
|
|
39
|
+
}
|
|
40
|
+
|
|
4
41
|
/**
|
|
5
42
|
* Container for data instances.
|
|
6
43
|
*
|
|
@@ -59,16 +96,15 @@ export class FxInstance extends HTMLElement {
|
|
|
59
96
|
*/
|
|
60
97
|
async init() {
|
|
61
98
|
// console.log('fx-instance init');
|
|
62
|
-
await this._initInstance()
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
});
|
|
99
|
+
await this._initInstance();
|
|
100
|
+
this.dispatchEvent(
|
|
101
|
+
new CustomEvent('instance-loaded', {
|
|
102
|
+
composed: true,
|
|
103
|
+
bubbles: true,
|
|
104
|
+
detail: { instance: this },
|
|
105
|
+
}),
|
|
106
|
+
);
|
|
107
|
+
return this;
|
|
72
108
|
}
|
|
73
109
|
|
|
74
110
|
evalXPath(xpath) {
|
|
@@ -155,22 +191,21 @@ export class FxInstance extends HTMLElement {
|
|
|
155
191
|
|
|
156
192
|
async _loadData() {
|
|
157
193
|
const url = `${this.src}`;
|
|
158
|
-
const contentType = Fore.getContentType(this, 'get');
|
|
159
194
|
|
|
160
|
-
if(url.startsWith('localStore')){
|
|
161
|
-
const key = url.substring(url.indexOf(':')+1);
|
|
195
|
+
if (url.startsWith('localStore')) {
|
|
196
|
+
const key = url.substring(url.indexOf(':') + 1);
|
|
162
197
|
|
|
163
198
|
const doc = new DOMParser().parseFromString('<data></data>', 'application/xml');
|
|
164
199
|
const root = doc.firstElementChild;
|
|
165
200
|
this.instanceData = doc;
|
|
166
201
|
|
|
167
|
-
if(!key){
|
|
202
|
+
if (!key) {
|
|
168
203
|
console.warn('no key specified for localStore');
|
|
169
204
|
return;
|
|
170
205
|
}
|
|
171
206
|
|
|
172
207
|
const serialized = localStorage.getItem(key);
|
|
173
|
-
if(!serialized){
|
|
208
|
+
if (!serialized) {
|
|
174
209
|
console.warn(`Data for key ${key} cannot be found`);
|
|
175
210
|
this._useInlineData();
|
|
176
211
|
return;
|
|
@@ -181,63 +216,28 @@ export class FxInstance extends HTMLElement {
|
|
|
181
216
|
doc.firstElementChild.replaceWith(data.firstElementChild);
|
|
182
217
|
return;
|
|
183
218
|
}
|
|
219
|
+
const contentType = Fore.getContentType(this, 'get');
|
|
184
220
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
.then(response => {
|
|
194
|
-
const { status } = response;
|
|
195
|
-
if (status >= 400) {
|
|
196
|
-
// console.log('response status', status);
|
|
197
|
-
alert(`response status: ${status} - failed to load data for '${this.src}' - stopping.`);
|
|
198
|
-
throw new Error(`failed to load data - status: ${status}`);
|
|
199
|
-
}
|
|
200
|
-
const responseContentType = response.headers.get('content-type').toLowerCase();
|
|
201
|
-
// console.log('********** responseContentType *********', responseContentType);
|
|
202
|
-
if (responseContentType.startsWith('text/html')) {
|
|
203
|
-
// const htmlResponse = response.text();
|
|
204
|
-
// return new DOMParser().parseFromString(htmlResponse, 'text/html');
|
|
205
|
-
// return response.text();
|
|
206
|
-
return response.text().then(result =>
|
|
207
|
-
// console.log('xml ********', result);
|
|
208
|
-
new DOMParser().parseFromString(result, 'text/html'),
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
if (
|
|
212
|
-
responseContentType.startsWith('text/plain') ||
|
|
213
|
-
responseContentType.startsWith('text/markdown')
|
|
214
|
-
) {
|
|
215
|
-
// console.log("********** inside res plain *********");
|
|
216
|
-
return response.text();
|
|
217
|
-
}
|
|
218
|
-
if (responseContentType.startsWith('application/json')) {
|
|
219
|
-
// console.log("********** inside res json *********");
|
|
220
|
-
return response.json();
|
|
221
|
-
}
|
|
222
|
-
if (responseContentType.startsWith('application/xml')) {
|
|
223
|
-
return response.text().then(result =>
|
|
224
|
-
// console.log('xml ********', result);
|
|
225
|
-
new DOMParser().parseFromString(result, 'application/xml'),
|
|
226
|
-
);
|
|
227
|
-
}
|
|
228
|
-
return 'done';
|
|
229
|
-
})
|
|
230
|
-
.then(data => {
|
|
231
|
-
if (data.nodeType) {
|
|
232
|
-
this.instanceData = data;
|
|
233
|
-
console.log('instanceData loaded: ', this.id, this.instanceData);
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
this.instanceData = data;
|
|
237
|
-
})
|
|
238
|
-
.catch(error => {
|
|
239
|
-
throw new Error(`failed loading data ${error}`);
|
|
221
|
+
try {
|
|
222
|
+
const response = await fetch(url, {
|
|
223
|
+
method: 'GET',
|
|
224
|
+
mode: 'cors',
|
|
225
|
+
credentials: 'include',
|
|
226
|
+
headers: {
|
|
227
|
+
'Content-Type': contentType,
|
|
228
|
+
},
|
|
240
229
|
});
|
|
230
|
+
const { status } = response;
|
|
231
|
+
const data = await handleResponse(response);
|
|
232
|
+
if (data.nodeType) {
|
|
233
|
+
this.instanceData = data;
|
|
234
|
+
console.log('instanceData loaded: ', this.id, this.instanceData);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
this.instanceData = data;
|
|
238
|
+
} catch (error) {
|
|
239
|
+
throw new Error(`failed loading data ${error}`);
|
|
240
|
+
}
|
|
241
241
|
}
|
|
242
242
|
|
|
243
243
|
_getContentType() {
|
package/src/fx-model.js
CHANGED
|
@@ -95,7 +95,7 @@ export class FxModel extends HTMLElement {
|
|
|
95
95
|
* @event model-construct-done is fired once all instances have be loaded or after generating instance
|
|
96
96
|
*
|
|
97
97
|
*/
|
|
98
|
-
modelConstruct() {
|
|
98
|
+
async modelConstruct() {
|
|
99
99
|
// console.log('### <<<<< dispatching model-construct >>>>>');
|
|
100
100
|
// this.dispatchEvent(new CustomEvent('model-construct', { detail: this }));
|
|
101
101
|
Fore.dispatch(this, 'model-construct', {model: this});
|
|
@@ -109,17 +109,20 @@ export class FxModel extends HTMLElement {
|
|
|
109
109
|
promises.push(instance.init());
|
|
110
110
|
});
|
|
111
111
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
112
|
+
// Wait until all the instances are built
|
|
113
|
+
await Promise.all(promises);
|
|
114
|
+
|
|
115
|
+
this.instances = Array.from(instances);
|
|
116
|
+
// console.log('_modelConstruct this.instances ', this.instances);
|
|
117
|
+
// Await until the model-construct-done event is handled off
|
|
118
|
+
|
|
119
|
+
await Fore.dispatch(this, 'model-construct-done', {model: this});
|
|
120
|
+
this.inited = true;
|
|
121
|
+
this.updateModel();
|
|
119
122
|
console.groupEnd();
|
|
120
123
|
} else {
|
|
121
124
|
// ### if there's no instance one will created
|
|
122
|
-
this.dispatchEvent(
|
|
125
|
+
await this.dispatchEvent(
|
|
123
126
|
new CustomEvent('model-construct-done', {
|
|
124
127
|
composed: false,
|
|
125
128
|
bubbles: true,
|
|
@@ -387,14 +390,6 @@ export class FxModel extends HTMLElement {
|
|
|
387
390
|
modelItem.constraint = compute;
|
|
388
391
|
this.formElement.addToRefresh(modelItem); // let fore know that modelItem needs refresh
|
|
389
392
|
if (!compute) valid = false;
|
|
390
|
-
// ### alerts are added only once during model-construct. Otherwise they would add up in each run of revalidate()
|
|
391
|
-
if (!this.modelConstructed) {
|
|
392
|
-
// todo: get alert from attribute or child element
|
|
393
|
-
const alert = bind.getAlert();
|
|
394
|
-
if (alert) {
|
|
395
|
-
modelItem.addAlert(alert);
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
393
|
}
|
|
399
394
|
}
|
|
400
395
|
if (typeof bind.hasAttribute === 'function' && bind.hasAttribute('required')) {
|