@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.
Files changed (41) hide show
  1. package/dist/fore-dev.js +4 -4
  2. package/dist/fore-dev.js.map +1 -1
  3. package/dist/fore.js +3 -3
  4. package/dist/fore.js.map +1 -1
  5. package/package.json +1 -1
  6. package/resources/fore.css +6 -1
  7. package/src/ForeElementMixin.js +4 -0
  8. package/src/actions/abstract-action.js +77 -35
  9. package/src/actions/fx-action.js +9 -9
  10. package/src/actions/fx-append.js +1 -1
  11. package/src/actions/fx-confirm.js +1 -1
  12. package/src/actions/fx-copy.js +68 -0
  13. package/src/actions/fx-delete.js +1 -1
  14. package/src/actions/fx-dispatch.js +1 -1
  15. package/src/actions/fx-hide.js +1 -1
  16. package/src/actions/fx-insert.js +1 -1
  17. package/src/actions/fx-message.js +1 -1
  18. package/src/actions/fx-refresh.js +2 -2
  19. package/src/actions/fx-reload.js +1 -1
  20. package/src/actions/fx-replace.js +1 -1
  21. package/src/actions/fx-return.js +1 -1
  22. package/src/actions/fx-send.js +2 -2
  23. package/src/actions/fx-setfocus.js +1 -1
  24. package/src/actions/fx-setvalue.js +1 -1
  25. package/src/actions/fx-show.js +1 -1
  26. package/src/actions/fx-toggle.js +1 -1
  27. package/src/actions/fx-update.js +1 -1
  28. package/src/fore.js +58 -10
  29. package/src/fx-bind.js +5 -0
  30. package/src/fx-fore.js +36 -35
  31. package/src/fx-instance.js +70 -70
  32. package/src/fx-model.js +12 -17
  33. package/src/fx-submission.js +420 -416
  34. package/src/getInScopeContext.js +2 -4
  35. package/src/modelitem.js +3 -1
  36. package/src/ui/abstract-control.js +1 -1
  37. package/src/ui/fx-control.js +2 -2
  38. package/src/ui/fx-switch.js +9 -1
  39. package/src/ui/fx-trigger.js +19 -18
  40. package/src/xpath-evaluation.js +21 -9
  41. 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 responsiblities are initialization and updating of model and instances, update of UI (refresh) and global messaging.
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 cirular
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
- // display: none;
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
- console.info(
243
- `%cFore is processing URL ${window.location.href}`,
244
- "background:#64b5f6; color:white; padding:1rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;",
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', e => {
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.dispatchEvent(this, 'error', {
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, this);
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 of the UI.
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(showConfirm, this.getModel().getDefaultContext(), this)
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
- await this.refresh();
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
 
@@ -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().then(() => {
63
- this.dispatchEvent(
64
- new CustomEvent('instance-loaded', {
65
- composed: true,
66
- bubbles: true,
67
- detail: { instance: this },
68
- }),
69
- );
70
- return this;
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
- await fetch(url, {
186
- method: 'GET',
187
- mode: 'cors',
188
- credentials: 'include',
189
- headers: {
190
- 'Content-Type': contentType,
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
- Promise.all(promises).then(() => {
113
- this.instances = Array.from(instances);
114
- // console.log('_modelConstruct this.instances ', this.instances);
115
- this.updateModel();
116
- this.inited = true;
117
- Fore.dispatch(this, 'model-construct-done', {model: this});
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')) {