@jinntec/fore 1.0.0-1 → 1.0.0-2
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 +10 -0
- package/dist/fore-all.js +10 -10
- package/dist/fore.js +1 -1
- package/index.js +3 -0
- package/package.json +4 -1
- package/resources/fore.css +73 -1
- package/src/DependencyNotifyingDomFacade.js +9 -1
- package/src/ForeElementMixin.js +15 -15
- package/src/actions/abstract-action.js +6 -5
- package/src/actions/fx-action.js +1 -0
- package/src/actions/fx-dispatch.js +10 -2
- package/src/actions/fx-insert.js +8 -8
- package/src/actions/fx-send.js +1 -1
- package/src/actions/fx-setvalue.js +1 -3
- package/src/dep_graph.js +9 -0
- package/src/drawdown.js +172 -0
- package/src/fore.js +53 -3
- package/src/fx-bind.js +8 -7
- package/src/fx-fore.js +180 -31
- package/src/fx-instance.js +27 -6
- package/src/fx-model.js +175 -34
- package/src/fx-submission.js +26 -7
- package/src/getInScopeContext.js +1 -1
- package/src/ui/abstract-control.js +8 -3
- package/src/ui/fx-alert.js +16 -20
- package/src/ui/fx-container.js +9 -4
- package/src/ui/fx-control.js +76 -39
- package/src/ui/fx-inspector.js +44 -0
- package/src/ui/fx-items.js +117 -0
- package/src/ui/fx-output.js +42 -2
- package/src/ui/fx-repeat.js +47 -15
- package/src/ui/fx-repeatitem.js +11 -4
- package/src/xpath-evaluation.js +242 -80
- package/src/xpath-util.js +47 -12
package/src/fx-fore.js
CHANGED
|
@@ -4,24 +4,34 @@ import './fx-model.js';
|
|
|
4
4
|
import '@jinntec/jinn-toast';
|
|
5
5
|
import { evaluateXPathToNodes, evaluateXPathToString } from './xpath-evaluation.js';
|
|
6
6
|
import getInScopeContext from './getInScopeContext.js';
|
|
7
|
+
import { XPathUtil } from './xpath-util.js';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Main class for Fore.Outermost container element for each Fore application.
|
|
10
11
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
12
|
* Root element for Fore. Kicks off initialization and displays messages.
|
|
14
13
|
*
|
|
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 responsiblities are initialization of model, update of UI (refresh) and global messaging
|
|
17
|
+
* Main responsiblities are initialization and updating of model and instances, update of UI (refresh) and global messaging.
|
|
18
|
+
*
|
|
19
|
+
*
|
|
19
20
|
*
|
|
20
21
|
* @ts-check
|
|
21
22
|
*/
|
|
22
23
|
export class FxFore extends HTMLElement {
|
|
23
24
|
static get properties() {
|
|
24
25
|
return {
|
|
26
|
+
/**
|
|
27
|
+
* Setting this marker attribute will refresh the UI in a lazy fashion just updating elements being
|
|
28
|
+
* in viewport.
|
|
29
|
+
*
|
|
30
|
+
* this feature is still experimental and should be used with caution and extra testing
|
|
31
|
+
*/
|
|
32
|
+
lazyRefresh: {
|
|
33
|
+
type: Boolean,
|
|
34
|
+
},
|
|
25
35
|
model: {
|
|
26
36
|
type: Object,
|
|
27
37
|
},
|
|
@@ -31,6 +41,14 @@ export class FxFore extends HTMLElement {
|
|
|
31
41
|
};
|
|
32
42
|
}
|
|
33
43
|
|
|
44
|
+
/**
|
|
45
|
+
* attaches handlers for
|
|
46
|
+
*
|
|
47
|
+
* - `model-construct-done` to trigger the processing of the UI
|
|
48
|
+
* - `message` - to display a message triggered by an fx-message action
|
|
49
|
+
* - `error` - to display an error message
|
|
50
|
+
* - 'compute-exception` - warn about circular dependencies in graph
|
|
51
|
+
*/
|
|
34
52
|
constructor() {
|
|
35
53
|
super();
|
|
36
54
|
this.model = {};
|
|
@@ -42,12 +60,11 @@ export class FxFore extends HTMLElement {
|
|
|
42
60
|
});
|
|
43
61
|
|
|
44
62
|
this.ready = false;
|
|
45
|
-
|
|
46
63
|
this.storedTemplateExpressionByNode = new Map();
|
|
47
64
|
|
|
48
65
|
const style = `
|
|
49
66
|
:host {
|
|
50
|
-
display:
|
|
67
|
+
display: none;
|
|
51
68
|
height:auto;
|
|
52
69
|
padding:var(--model-element-padding);
|
|
53
70
|
font-family:Roboto, sans-serif;
|
|
@@ -56,6 +73,11 @@ export class FxFore extends HTMLElement {
|
|
|
56
73
|
:host ::slotted(fx-model){
|
|
57
74
|
display:none;
|
|
58
75
|
}
|
|
76
|
+
:host(.fx-ready){
|
|
77
|
+
animation: fadein .4s forwards;
|
|
78
|
+
display:block;
|
|
79
|
+
}
|
|
80
|
+
|
|
59
81
|
#modalMessage .dialogActions{
|
|
60
82
|
text-align:center;
|
|
61
83
|
}
|
|
@@ -75,7 +97,7 @@ export class FxFore extends HTMLElement {
|
|
|
75
97
|
visibility: visible;
|
|
76
98
|
opacity: 1;
|
|
77
99
|
}
|
|
78
|
-
|
|
100
|
+
|
|
79
101
|
.popup {
|
|
80
102
|
margin: 70px auto;
|
|
81
103
|
background: #fff;
|
|
@@ -111,13 +133,21 @@ export class FxFore extends HTMLElement {
|
|
|
111
133
|
.popup .close:focus{
|
|
112
134
|
outline:none;
|
|
113
135
|
}
|
|
114
|
-
|
|
136
|
+
|
|
115
137
|
.popup .close:hover {
|
|
116
138
|
color: #06D85F;
|
|
117
139
|
}
|
|
118
140
|
#messageContent{
|
|
119
141
|
margin-top:40px;
|
|
120
142
|
}
|
|
143
|
+
@keyframes fadein {
|
|
144
|
+
0% {
|
|
145
|
+
opacity:0;
|
|
146
|
+
}
|
|
147
|
+
100% {
|
|
148
|
+
opacity:1;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
121
151
|
`;
|
|
122
152
|
|
|
123
153
|
const html = `
|
|
@@ -143,6 +173,16 @@ export class FxFore extends HTMLElement {
|
|
|
143
173
|
}
|
|
144
174
|
|
|
145
175
|
connectedCallback() {
|
|
176
|
+
this.lazyRefresh = this.hasAttribute('refresh-on-view');
|
|
177
|
+
if (this.lazyRefresh) {
|
|
178
|
+
const options = {
|
|
179
|
+
root: null,
|
|
180
|
+
rootMargin: '0px',
|
|
181
|
+
threshold: 0.3,
|
|
182
|
+
};
|
|
183
|
+
this.intersectionObserver = new IntersectionObserver(this.handleIntersect, options);
|
|
184
|
+
}
|
|
185
|
+
|
|
146
186
|
const slot = this.shadowRoot.querySelector('slot');
|
|
147
187
|
slot.addEventListener('slotchange', event => {
|
|
148
188
|
const children = event.target.assignedElements();
|
|
@@ -155,18 +195,62 @@ export class FxFore extends HTMLElement {
|
|
|
155
195
|
modelElement = generatedModel;
|
|
156
196
|
}
|
|
157
197
|
if (!modelElement.inited) {
|
|
158
|
-
console.log(
|
|
198
|
+
console.log(
|
|
199
|
+
`########## FORE: kick off processing for ... ${window.location.href} ##########`,
|
|
200
|
+
);
|
|
159
201
|
modelElement.modelConstruct();
|
|
160
202
|
}
|
|
161
203
|
this.model = modelElement;
|
|
162
204
|
});
|
|
163
205
|
}
|
|
164
206
|
|
|
207
|
+
/**
|
|
208
|
+
* refreshes the UI by using IntersectionObserver API. This is the handler being called
|
|
209
|
+
* by the observer whenever elements come into / move out of viewport.
|
|
210
|
+
* @param entries
|
|
211
|
+
* @param observer
|
|
212
|
+
*/
|
|
213
|
+
handleIntersect(entries, observer) {
|
|
214
|
+
console.time('refreshLazy');
|
|
215
|
+
entries.forEach(entry => {
|
|
216
|
+
const { target } = entry;
|
|
217
|
+
|
|
218
|
+
if (entry.isIntersecting) {
|
|
219
|
+
console.log('in view', entry);
|
|
220
|
+
// console.log('repeat in view entry', entry.target);
|
|
221
|
+
// const target = entry.target;
|
|
222
|
+
// if(target.hasAttribute('refresh-on-view')){
|
|
223
|
+
target.classList.add('loaded');
|
|
224
|
+
// }
|
|
225
|
+
|
|
226
|
+
// todo: too restrictive here? what if target is a usual html element? shouldn't it refresh downwards?
|
|
227
|
+
if (typeof target.refresh === 'function') {
|
|
228
|
+
console.log('refreshing target', target);
|
|
229
|
+
target.refresh(target, true);
|
|
230
|
+
} else {
|
|
231
|
+
console.log('refreshing children', target);
|
|
232
|
+
Fore.refreshChildren(target, true);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
entries[0].target.getOwnerForm().dispatchEvent(new CustomEvent('refresh-done'));
|
|
237
|
+
|
|
238
|
+
console.timeEnd('refreshLazy');
|
|
239
|
+
}
|
|
240
|
+
|
|
165
241
|
evaluateToNodes(xpath, context) {
|
|
166
242
|
return evaluateXPathToNodes(xpath, context, this);
|
|
167
243
|
}
|
|
168
244
|
|
|
169
|
-
disconnectedCallback() {
|
|
245
|
+
disconnectedCallback() {
|
|
246
|
+
/*
|
|
247
|
+
this.removeEventListener('model-construct-done', this._handleModelConstructDone);
|
|
248
|
+
this.removeEventListener('message', this._displayMessage);
|
|
249
|
+
this.removeEventListener('error', this._displayError);
|
|
250
|
+
this.storedTemplateExpressionByNode=null;
|
|
251
|
+
this.shadowRoot = undefined;
|
|
252
|
+
*/
|
|
253
|
+
}
|
|
170
254
|
|
|
171
255
|
/**
|
|
172
256
|
* refreshes the whole UI by visiting each bound element (having a 'ref' attribute) and applying the state of
|
|
@@ -176,16 +260,39 @@ export class FxFore extends HTMLElement {
|
|
|
176
260
|
* AVT:
|
|
177
261
|
*
|
|
178
262
|
*/
|
|
179
|
-
async refresh() {
|
|
263
|
+
async refresh(force) {
|
|
180
264
|
// refresh () {
|
|
181
265
|
console.group('### refresh');
|
|
182
|
-
// await this.updateComplete;
|
|
183
266
|
|
|
184
|
-
|
|
185
|
-
|
|
267
|
+
console.time('refresh');
|
|
268
|
+
|
|
269
|
+
/*
|
|
270
|
+
const changedModelItems = this.getModel().changed;
|
|
271
|
+
const graph = this.getModel().mainGraph;
|
|
272
|
+
let doRefresh = true;
|
|
273
|
+
changedModelItems.forEach(item => {
|
|
274
|
+
if(graph.hasNode(item.path)) {
|
|
275
|
+
const deps = graph.dependentsOf(item.path, false);
|
|
276
|
+
if (deps.length === 0) {
|
|
277
|
+
doRefresh=false;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
this.getModel().changed = [];
|
|
282
|
+
|
|
283
|
+
if (!doRefresh) {
|
|
284
|
+
this.dispatchEvent(new CustomEvent('refresh-done'));
|
|
285
|
+
return ;
|
|
286
|
+
}
|
|
287
|
+
*/
|
|
288
|
+
// ### refresh Fore UI elements
|
|
289
|
+
console.time('refreshChildren');
|
|
290
|
+
Fore.refreshChildren(this, true);
|
|
291
|
+
console.timeEnd('refreshChildren');
|
|
186
292
|
|
|
187
293
|
// ### refresh template expressions
|
|
188
294
|
this._updateTemplateExpressions();
|
|
295
|
+
console.timeEnd('refresh');
|
|
189
296
|
|
|
190
297
|
console.groupEnd();
|
|
191
298
|
console.log('### <<<<< dispatching refresh-done - end of UI update cycle >>>>>');
|
|
@@ -203,14 +310,18 @@ export class FxFore extends HTMLElement {
|
|
|
203
310
|
_updateTemplateExpressions() {
|
|
204
311
|
// Note the fact we're going over HTML here: therefore the `html` prefix.
|
|
205
312
|
const search =
|
|
206
|
-
"(descendant-or-self::*/(text(), @*))[matches(.,'\\{.*\\}')] except descendant-or-self::
|
|
313
|
+
"(descendant-or-self::*/(text(), @*))[matches(.,'\\{.*\\}')] except descendant-or-self::fx-model/descendant-or-self::node()/(., @*)";
|
|
207
314
|
|
|
208
315
|
const tmplExpressions = evaluateXPathToNodes(search, this, this);
|
|
209
316
|
console.log('template expressions found ', tmplExpressions);
|
|
210
317
|
|
|
318
|
+
if (!this.storedTemplateExpressions) {
|
|
319
|
+
this.storedTemplateExpressions = [];
|
|
320
|
+
}
|
|
321
|
+
|
|
211
322
|
/*
|
|
212
|
-
|
|
213
|
-
|
|
323
|
+
storing expressions and their nodes for re-evaluation
|
|
324
|
+
*/
|
|
214
325
|
Array.from(tmplExpressions).forEach(node => {
|
|
215
326
|
if (this.storedTemplateExpressionByNode.has(node)) {
|
|
216
327
|
// If the node is already known, do not process it twice
|
|
@@ -268,13 +379,15 @@ export class FxFore extends HTMLElement {
|
|
|
268
379
|
}
|
|
269
380
|
// Templates are special: they use the namespace configuration from the place where they are
|
|
270
381
|
// being defined
|
|
271
|
-
|
|
382
|
+
const instanceId = XPathUtil.getInstanceId(naked);
|
|
383
|
+
// console.log('target instance ', instanceId);
|
|
384
|
+
const inst = this.getModel().getInstance(instanceId);
|
|
272
385
|
try {
|
|
273
|
-
const result = evaluateXPathToString(naked, inscope, node, null,
|
|
386
|
+
const result = evaluateXPathToString(naked, inscope, node, null, inst);
|
|
274
387
|
|
|
275
388
|
// console.log('result of eval ', result);
|
|
276
389
|
const replaced = expr.replaceAll(match, result);
|
|
277
|
-
console.log('result of replacing ', replaced);
|
|
390
|
+
// console.log('result of replacing ', replaced);
|
|
278
391
|
|
|
279
392
|
if (node.nodeType === Node.ATTRIBUTE_NODE) {
|
|
280
393
|
const parent = node.ownerElement;
|
|
@@ -286,7 +399,7 @@ export class FxFore extends HTMLElement {
|
|
|
286
399
|
}
|
|
287
400
|
|
|
288
401
|
if (replaced.includes('{')) {
|
|
289
|
-
console.log('need to go next round');
|
|
402
|
+
// console.log('need to go next round');
|
|
290
403
|
|
|
291
404
|
// todo: duplicated code here - see above
|
|
292
405
|
naked = replaced.substring(1, replaced.length);
|
|
@@ -305,25 +418,28 @@ export class FxFore extends HTMLElement {
|
|
|
305
418
|
return node.value;
|
|
306
419
|
}
|
|
307
420
|
if (node.nodeType === Node.TEXT_NODE) {
|
|
308
|
-
return node.textContent;
|
|
421
|
+
return node.textContent.trim();
|
|
309
422
|
}
|
|
310
423
|
return null;
|
|
311
424
|
}
|
|
312
425
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
}
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
|
|
426
|
+
/**
|
|
427
|
+
* called when `model-construct-done` event is received to
|
|
428
|
+
* start initing of the UI.
|
|
429
|
+
*
|
|
430
|
+
* @private
|
|
431
|
+
*/
|
|
323
432
|
_handleModelConstructDone() {
|
|
324
433
|
this._initUI();
|
|
325
434
|
}
|
|
326
435
|
|
|
436
|
+
/**
|
|
437
|
+
* If there's no instance element found in a fx-model during init it will construct
|
|
438
|
+
* an instance from UI bindings.
|
|
439
|
+
*
|
|
440
|
+
* @returns {Promise<void>}
|
|
441
|
+
* @private
|
|
442
|
+
*/
|
|
327
443
|
async _lazyCreateInstance() {
|
|
328
444
|
const model = this.querySelector('fx-model');
|
|
329
445
|
if (model.instances.length === 0) {
|
|
@@ -422,11 +538,31 @@ export class FxFore extends HTMLElement {
|
|
|
422
538
|
}
|
|
423
539
|
*/
|
|
424
540
|
|
|
541
|
+
/**
|
|
542
|
+
* Start the initialization of the UI by
|
|
543
|
+
*
|
|
544
|
+
* 1. checking if a instance needs to be generated
|
|
545
|
+
* 2. attaching lazy loading intersection observers if `refresh-on-view` attributes are found
|
|
546
|
+
* 3. doing a full refresh of the UI
|
|
547
|
+
*
|
|
548
|
+
* @returns {Promise<void>}
|
|
549
|
+
* @private
|
|
550
|
+
*/
|
|
425
551
|
async _initUI() {
|
|
426
552
|
console.log('### _initUI()');
|
|
427
553
|
|
|
428
554
|
await this._lazyCreateInstance();
|
|
555
|
+
|
|
556
|
+
const options = {
|
|
557
|
+
root: null,
|
|
558
|
+
rootMargin: '0px',
|
|
559
|
+
threshold: 0.3,
|
|
560
|
+
};
|
|
561
|
+
|
|
429
562
|
await this.refresh();
|
|
563
|
+
// this.style.display='block'
|
|
564
|
+
this.classList.add('fx-ready');
|
|
565
|
+
|
|
430
566
|
this.ready = true;
|
|
431
567
|
console.log('### <<<<< dispatching ready >>>>>');
|
|
432
568
|
console.log('########## modelItems: ', this.getModel().modelItems);
|
|
@@ -434,6 +570,19 @@ export class FxFore extends HTMLElement {
|
|
|
434
570
|
this.dispatchEvent(new CustomEvent('ready', {}));
|
|
435
571
|
}
|
|
436
572
|
|
|
573
|
+
registerLazyElement(element) {
|
|
574
|
+
if (this.intersectionObserver) {
|
|
575
|
+
// console.log('registerLazyElement',element);
|
|
576
|
+
this.intersectionObserver.observe(element);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
unRegisterLazyElement(element) {
|
|
581
|
+
if (this.intersectionObserver) {
|
|
582
|
+
this.intersectionObserver.unobserve(element);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
437
586
|
/**
|
|
438
587
|
*
|
|
439
588
|
* @returns {FxModel}
|
package/src/fx-instance.js
CHANGED
|
@@ -144,6 +144,7 @@ export class FxInstance extends HTMLElement {
|
|
|
144
144
|
|
|
145
145
|
_createInstanceData() {
|
|
146
146
|
if (this.type === 'xml') {
|
|
147
|
+
// const doc = new DOMParser().parseFromString('<data data-id="default"></data>', 'application/xml');
|
|
147
148
|
const doc = new DOMParser().parseFromString('<data></data>', 'application/xml');
|
|
148
149
|
this.instanceData = doc;
|
|
149
150
|
}
|
|
@@ -167,7 +168,19 @@ export class FxInstance extends HTMLElement {
|
|
|
167
168
|
.then(response => {
|
|
168
169
|
const responseContentType = response.headers.get('content-type').toLowerCase();
|
|
169
170
|
console.log('********** responseContentType *********', responseContentType);
|
|
170
|
-
if (responseContentType.startsWith('text/
|
|
171
|
+
if (responseContentType.startsWith('text/html')) {
|
|
172
|
+
// const htmlResponse = response.text();
|
|
173
|
+
// return new DOMParser().parseFromString(htmlResponse, 'text/html');
|
|
174
|
+
// return response.text();
|
|
175
|
+
return response.text().then(result =>
|
|
176
|
+
// console.log('xml ********', result);
|
|
177
|
+
new DOMParser().parseFromString(result, 'text/html'),
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
if (
|
|
181
|
+
responseContentType.startsWith('text/plain') ||
|
|
182
|
+
responseContentType.startsWith('text/markdown')
|
|
183
|
+
) {
|
|
171
184
|
// console.log("********** inside res plain *********");
|
|
172
185
|
return response.text();
|
|
173
186
|
}
|
|
@@ -176,16 +189,20 @@ export class FxInstance extends HTMLElement {
|
|
|
176
189
|
return response.json();
|
|
177
190
|
}
|
|
178
191
|
if (responseContentType.startsWith('application/xml')) {
|
|
179
|
-
return response.text().then(result =>
|
|
180
|
-
console.log('xml ********', result);
|
|
181
|
-
|
|
182
|
-
|
|
192
|
+
return response.text().then(result =>
|
|
193
|
+
// console.log('xml ********', result);
|
|
194
|
+
new DOMParser().parseFromString(result, 'application/xml'),
|
|
195
|
+
);
|
|
183
196
|
}
|
|
184
197
|
return 'done';
|
|
185
198
|
})
|
|
186
199
|
.then(data => {
|
|
200
|
+
if (data.nodeType) {
|
|
201
|
+
this.instanceData = data;
|
|
202
|
+
console.log('instanceData loaded: ', this.instanceData);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
187
205
|
this.instanceData = data;
|
|
188
|
-
console.log('instanceData loaded: ', this.instanceData);
|
|
189
206
|
})
|
|
190
207
|
.catch(error => {
|
|
191
208
|
throw new Error(`failed loading data ${error}`);
|
|
@@ -218,6 +235,10 @@ export class FxInstance extends HTMLElement {
|
|
|
218
235
|
// todo: move innerHTML out to shadowDOM (for later reset)
|
|
219
236
|
} else if (this.type === 'json') {
|
|
220
237
|
this.instanceData = JSON.parse(this.textContent);
|
|
238
|
+
} else if (this.type === 'html') {
|
|
239
|
+
this.instanceData = this.firstElementChild.children;
|
|
240
|
+
} else if (this.type === 'text') {
|
|
241
|
+
this.instanceData = this.textContent;
|
|
221
242
|
} else {
|
|
222
243
|
console.warn('unknow type for data ', this.type);
|
|
223
244
|
}
|