@jinntec/fore 1.0.0-3 → 1.0.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 (62) hide show
  1. package/README.md +25 -34
  2. package/dist/fore-dev.js +43 -0
  3. package/dist/fore-dev.js.map +1 -0
  4. package/dist/fore.js +36 -1
  5. package/dist/fore.js.map +1 -0
  6. package/index.js +3 -0
  7. package/package.json +34 -38
  8. package/resources/fore.css +35 -50
  9. package/src/DependencyNotifyingDomFacade.js +10 -16
  10. package/src/ForeElementMixin.js +25 -18
  11. package/src/actions/abstract-action.js +17 -9
  12. package/src/actions/fx-action.js +6 -4
  13. package/src/actions/fx-append.js +8 -17
  14. package/src/actions/fx-confirm.js +3 -1
  15. package/src/actions/fx-delete.js +6 -3
  16. package/src/actions/fx-dispatch.js +9 -8
  17. package/src/actions/fx-hide.js +10 -6
  18. package/src/actions/fx-insert.js +49 -39
  19. package/src/actions/fx-message.js +3 -1
  20. package/src/actions/fx-refresh.js +11 -1
  21. package/src/actions/fx-replace.js +68 -0
  22. package/src/actions/fx-return.js +42 -0
  23. package/src/actions/fx-send.js +3 -1
  24. package/src/actions/fx-setvalue.js +58 -51
  25. package/src/actions/fx-show.js +8 -4
  26. package/src/actions/fx-toggle.js +15 -10
  27. package/src/actions/fx-update.js +3 -1
  28. package/src/dep_graph.js +4 -2
  29. package/src/drawdown.js +67 -82
  30. package/src/fore.js +158 -11
  31. package/src/functions/fx-function.js +11 -3
  32. package/src/fx-bind.js +42 -202
  33. package/src/fx-fore.js +105 -70
  34. package/src/fx-header.js +3 -1
  35. package/src/fx-instance.js +9 -1
  36. package/src/fx-model.js +31 -23
  37. package/src/fx-submission.js +73 -47
  38. package/src/fx-var.js +7 -4
  39. package/src/getInScopeContext.js +37 -11
  40. package/src/modelitem.js +4 -4
  41. package/src/relevance.js +65 -0
  42. package/src/ui/abstract-control.js +55 -35
  43. package/src/ui/fx-alert.js +7 -1
  44. package/src/ui/fx-case.js +3 -1
  45. package/src/ui/fx-container.js +7 -1
  46. package/src/ui/fx-control.js +283 -33
  47. package/src/ui/fx-dialog.js +54 -40
  48. package/src/ui/fx-group.js +3 -1
  49. package/src/ui/fx-hint.js +4 -1
  50. package/src/ui/fx-inspector.js +117 -17
  51. package/src/ui/fx-items.js +8 -8
  52. package/src/ui/fx-output.js +14 -5
  53. package/src/ui/fx-repeat.js +33 -41
  54. package/src/ui/fx-repeatitem.js +10 -4
  55. package/src/ui/fx-switch.js +3 -1
  56. package/src/ui/fx-trigger.js +3 -1
  57. package/src/xpath-evaluation.js +121 -131
  58. package/src/xpath-util.js +14 -7
  59. package/dist/fore-all.js +0 -140
  60. package/src/.DS_Store +0 -0
  61. package/src/actions/.DS_Store +0 -0
  62. package/src/ui/.DS_Store +0 -0
package/src/ui/fx-case.js CHANGED
@@ -44,4 +44,6 @@ class FxCase extends HTMLElement {
44
44
  }
45
45
  }
46
46
 
47
- window.customElements.define('fx-case', FxCase);
47
+ if (!customElements.get('fx-case')) {
48
+ window.customElements.define('fx-case', FxCase);
49
+ }
@@ -44,6 +44,10 @@ export class FxContainer extends foreElementMixin(HTMLElement) {
44
44
  if (this.isBound()) {
45
45
  this.evalInContext();
46
46
  this.modelItem = this.getModelItem();
47
+ if (!this.modelItem.boundControls.includes(this)) {
48
+ this.modelItem.boundControls.push(this);
49
+ }
50
+
47
51
  // this.value = this.modelItem.value;
48
52
  }
49
53
 
@@ -107,4 +111,6 @@ export class FxContainer extends foreElementMixin(HTMLElement) {
107
111
  }
108
112
  }
109
113
 
110
- window.customElements.define('fx-container', FxContainer);
114
+ if (!customElements.get('fx-container')) {
115
+ window.customElements.define('fx-container', FxContainer);
116
+ }
@@ -1,5 +1,9 @@
1
1
  import XfAbstractControl from './abstract-control.js';
2
- import { evaluateXPath, evaluateXPathToString, evaluateXPathToNodes } from '../xpath-evaluation.js';
2
+ import {
3
+ evaluateXPath,
4
+ evaluateXPathToString,
5
+ evaluateXPathToFirstNode,
6
+ } from '../xpath-evaluation.js';
3
7
  import getInScopeContext from '../getInScopeContext.js';
4
8
  import { Fore } from '../fore.js';
5
9
 
@@ -14,6 +18,16 @@ const WIDGETCLASS = 'widget';
14
18
  * @customElement
15
19
  * @demo demo/index.html
16
20
  */
21
+
22
+ function debounce(func, timeout = 300) {
23
+ let timer;
24
+ return (...args) => {
25
+ clearTimeout(timer);
26
+ timer = setTimeout(() => {
27
+ func.apply(this, args);
28
+ }, timeout);
29
+ };
30
+ }
17
31
  export default class FxControl extends XfAbstractControl {
18
32
  constructor() {
19
33
  super();
@@ -22,6 +36,12 @@ export default class FxControl extends XfAbstractControl {
22
36
  }
23
37
 
24
38
  connectedCallback() {
39
+ this.initial = this.hasAttribute('initial') ? this.getAttribute('initial') : null;
40
+ this.url = this.hasAttribute('url') ? this.getAttribute('url') : null;
41
+ this.loaded = false;
42
+ this.initialNode = null;
43
+ this.debounceDelay = this.hasAttribute('debounce') ? this.getAttribute('debounce') : null;
44
+
25
45
  this.updateEvent = this.hasAttribute('update-event')
26
46
  ? this.getAttribute('update-event')
27
47
  : 'blur';
@@ -33,21 +53,6 @@ export default class FxControl extends XfAbstractControl {
33
53
  }
34
54
  `;
35
55
 
36
- /*
37
- const controlHtml = `
38
- <slot></slot>
39
- <fx-setvalue id="setvalue" ref="${this.ref}"></fx-setvalue>
40
- `;
41
- */
42
-
43
- /*
44
- this.shadowRoot.innerHTML = `
45
- <style>
46
- ${style}
47
- </style>
48
- ${controlHtml}
49
- `
50
- */
51
56
  this.shadowRoot.innerHTML = `
52
57
  <style>
53
58
  ${style}
@@ -69,32 +74,116 @@ export default class FxControl extends XfAbstractControl {
69
74
  });
70
75
  this.updateEvent = 'blur'; // needs to be registered too
71
76
  }
72
- this.widget.addEventListener(this.updateEvent, () => {
73
- // console.log('eventlistener ', this.updateEvent);
74
- this.setValue(this.widget[this.valueProp]);
77
+ if (this.debounceDelay) {
78
+ this.widget.addEventListener(
79
+ this.updateEvent,
80
+ debounce(() => {
81
+ console.log('eventlistener ', this.updateEvent);
82
+ this.setValue(this.widget[this.valueProp]);
83
+ }, this.debounceDelay),
84
+ );
85
+ } else {
86
+ this.widget.addEventListener(this.updateEvent, () => {
87
+ console.log('eventlistener ', this.updateEvent);
88
+ this.setValue(this.widget[this.valueProp]);
89
+ });
90
+ }
91
+
92
+ this.addEventListener('return', e => {
93
+ console.log('catched return action on ', this);
94
+ console.log('return detail', e.detail);
95
+
96
+ console.log('return triggered on ', this);
97
+ console.log('this.ref', this.ref);
98
+ console.log('current outer instance', this.getInstance());
99
+
100
+ console.log(
101
+ '???? why ???? current nodeset should point to the node of the outer control',
102
+ e.currentTarget.nodeset,
103
+ );
104
+ console.log(
105
+ '???? why ???? current nodeset should point to the node of the outer control',
106
+ this.nodeset,
107
+ );
108
+ const newNodes = e.detail.nodeset;
109
+ console.log('new nodeset', newNodes);
110
+ console.log('currentTarget', e.currentTarget);
111
+ console.log('target', e.target);
112
+
113
+ e.stopPropagation();
114
+
115
+ this._replaceNode(newNodes);
75
116
  });
76
117
 
77
- const slot = this.shadowRoot.querySelector('slot');
78
118
  this.template = this.querySelector('template');
79
119
  // console.log('template',this.template);
80
120
  }
81
121
 
122
+ _debounce(func, timeout = 300) {
123
+ let timer;
124
+ return (...args) => {
125
+ const context = this;
126
+ clearTimeout(timer);
127
+ timer = setTimeout(() => {
128
+ func.apply(context, args);
129
+ }, timeout);
130
+ };
131
+ }
132
+
133
+ /**
134
+ * updates the model with a new value by executing it's `<fx-setvalue>` action.
135
+ *
136
+ * In case the `as='node'` is given the bound node is replaced with the widgets' value with is
137
+ * expected to be a node again.
138
+ *
139
+ * @param val the new value to be set
140
+ */
82
141
  setValue(val) {
83
142
  const modelitem = this.getModelItem();
143
+
144
+ if (modelitem?.readonly) return; // do nothing when modelItem is readonly
145
+
146
+ if (this.getAttribute('as') === 'node') {
147
+ const widgetValue = this.getWidget().value;
148
+ const replace = this.shadowRoot.getElementById('replace');
149
+ replace.replace(this.nodeset, this.getWidget().value);
150
+ if (widgetValue && widgetValue !== modelitem.value) {
151
+ modelitem.value = widgetValue;
152
+ replace.actionPerformed();
153
+ }
154
+ return;
155
+ }
84
156
  const setval = this.shadowRoot.getElementById('setvalue');
85
157
  setval.setValue(modelitem, val);
86
158
  setval.actionPerformed();
87
159
  }
88
160
 
161
+ _replaceNode(node) {
162
+ // Note: clone the node while replacing to prevent the instances to leak through
163
+ this.modelItem.node.replaceWith(node.cloneNode(true));
164
+ this.getOwnerForm().refresh();
165
+ }
166
+
89
167
  renderHTML(ref) {
90
168
  return `
91
169
  ${this.label ? `${this.label}` : ''}
92
170
  <slot></slot>
93
- <fx-setvalue id="setvalue" ref="${ref}"></fx-setvalue>
171
+ ${
172
+ this.hasAttribute('as') && this.getAttribute('as') === 'node'
173
+ ? `<fx-replace id="replace" ref=".">`
174
+ : `<fx-setvalue id="setvalue" ref="${ref}"></fx-setvalue>`
175
+ }
176
+
94
177
  `;
95
178
  }
96
179
 
97
180
  /**
181
+ * The widget is the actual control being used in the UI e.g. a native input control or any
182
+ * other component that presents a control that can be interacted with.
183
+ *
184
+ * This function returns the widget by querying the children of this control for an element
185
+ * with `class="widget"`. If that cannot be found it searches for an native `input` of any type.
186
+ * If either cannot be found a `<input type="text">` is created.
98
187
  *
99
188
  * @returns {HTMLElement|*}
100
189
  */
@@ -114,24 +203,169 @@ export default class FxControl extends XfAbstractControl {
114
203
  return widget;
115
204
  }
116
205
 
117
- // todo: check again
206
+ /**
207
+ * updates the widget from the modelItem value. During refresh the a control
208
+ * evaluates it's binding expression to determine the bound node. The bound node corresponds
209
+ * to a modelItem which acts a the state object of a node. The modelItem determines the value
210
+ * and the state of the node and set the `value` property of this class.
211
+ *
212
+ * @returns {Promise<void>}
213
+ */
118
214
  async updateWidgetValue() {
119
215
  // this.widget[this.valueProp] = this.value;
216
+
217
+ let { widget } = this;
218
+ if (!widget) {
219
+ widget = this;
220
+ }
221
+ // ### value is bound to checkbox
120
222
  if (this.valueProp === 'checked') {
121
223
  if (this.value === 'true') {
122
- this.widget.checked = true;
224
+ widget.checked = true;
123
225
  } else {
124
- this.widget.checked = false;
226
+ widget.checked = false;
125
227
  }
126
- } else {
127
- let { widget } = this;
128
- if (!widget) {
129
- widget = this;
228
+ return;
229
+ }
230
+
231
+ if (this.hasAttribute('as')) {
232
+ const as = this.getAttribute('as');
233
+
234
+ // ### when there's an `as=text` attribute serialize nodeset to prettified string
235
+ if (as === 'text') {
236
+ const serializer = new XMLSerializer();
237
+ const pretty = Fore.prettifyXml(serializer.serializeToString(this.nodeset));
238
+ widget.value = pretty;
239
+ }
240
+ if (as === 'node' && this.nodeset !== widget.value) {
241
+ const oldVal = this.nodeset.innerHTML;
242
+ if (widget.value) {
243
+ if (oldVal !== this.widget.value) {
244
+ console.log('changed');
245
+ return;
246
+ }
247
+ }
248
+
249
+ widget.value = this.nodeset.cloneNode(true);
250
+ console.log('passed value to widget', widget.value);
130
251
  }
252
+
253
+ return;
254
+ }
255
+
256
+ // ### when there's a url Fore is used as widget and will be loaded from external file
257
+ if (this.url && !this.loaded) {
258
+ // ### evaluate initial data if necessary
259
+ if (this.initial) {
260
+ this.initialNode = evaluateXPathToFirstNode(this.initial, this.nodeset, this);
261
+ console.log('initialNodes', this.initialNode);
262
+ }
263
+
264
+ // ### load the markup from Url
265
+ await this._loadForeFromUrl();
266
+ this.loaded = true;
267
+
268
+ // ### replace default instance of embedded Fore with initial nodes
269
+ // const innerInstance = this.querySelector('fx-instance');
270
+ // console.log('innerInstance',innerInstance);
271
+ return;
272
+ }
273
+
274
+ /*
275
+ if(this.url && !this.loaded){
276
+ this._loadForeFromUrl();
277
+ this.loaded=true;
278
+ return;
279
+ }
280
+ */
281
+ if (widget.value !== this.value) {
131
282
  widget.value = this.value;
132
283
  }
133
284
  }
134
285
 
286
+ /**
287
+ * loads an external Fore from an HTML file given by `url` attribute.
288
+ *
289
+ * Will look for the `<fx-fore>` element within the returned HTML file and return that element.
290
+ *
291
+ * If that cannot be found an error is dispatched.
292
+ *
293
+ * todo: dispatch link error
294
+ * @private
295
+ */
296
+ async _loadForeFromUrl() {
297
+ console.log('########## loading Fore from ', this.src, '##########');
298
+ try {
299
+ const response = await fetch(this.url, {
300
+ method: 'GET',
301
+ mode: 'cors',
302
+ credentials: 'include',
303
+ headers: {
304
+ 'Content-Type': 'text/html',
305
+ },
306
+ });
307
+ const responseContentType = response.headers.get('content-type').toLowerCase();
308
+ console.log('********** responseContentType *********', responseContentType);
309
+ let data;
310
+ if (responseContentType.startsWith('text/html')) {
311
+ data = await response.text().then(result =>
312
+ // console.log('xml ********', result);
313
+ new DOMParser().parseFromString(result, 'text/html'),
314
+ );
315
+ } else {
316
+ data = 'done';
317
+ }
318
+ // const theFore = fxEvaluateXPathToFirstNode('//fx-fore', data.firstElementChild);
319
+ const theFore = data.querySelector('fx-fore');
320
+ // console.log('thefore', theFore)
321
+ theFore.classList.add('widget'); // is the new widget
322
+ const dummy = this.querySelector('input');
323
+ if (this.hasAttribute('shadow')) {
324
+ dummy.parentNode.removeChild(dummy);
325
+ this.shadowRoot.appendChild(theFore);
326
+ } else {
327
+ dummy.replaceWith(theFore);
328
+ }
329
+
330
+ console.log(`########## loaded fore as component ##### ${this.url}`);
331
+ theFore.addEventListener(
332
+ 'model-construct-done',
333
+ e => {
334
+ console.log('subcomponent ready', e.target);
335
+ const defaultInst = theFore.querySelector('fx-instance');
336
+ console.log('defaultInst', defaultInst);
337
+ const doc = new DOMParser().parseFromString('<data></data>', 'application/xml');
338
+ // Note: Clone the input to prevent the inner fore from editing the outer node
339
+ doc.firstElementChild.appendChild(this.initialNode.cloneNode(true));
340
+ // defaultinst.setInstanceData(this.initialNode);
341
+ defaultInst.setInstanceData(doc);
342
+ console.log('new data', defaultInst.getInstanceData());
343
+ // theFore.getModel().modelConstruct();
344
+ theFore.getModel().updateModel();
345
+ theFore.refresh();
346
+ },
347
+ { once: true },
348
+ );
349
+
350
+ if (!theFore) {
351
+ this.dispatchEvent(
352
+ new CustomEvent('error', {
353
+ detail: {
354
+ message: `Fore element not found in '${this.src}'. Maybe wrapped within 'template' element?`,
355
+ },
356
+ }),
357
+ );
358
+ }
359
+ console.log('loaded');
360
+ this.dispatchEvent(new CustomEvent('loaded', { detail: { fore: theFore } }));
361
+ } catch (error) {
362
+ console.log('error', error);
363
+ this.getOwnerForm().dispatchEvent(
364
+ new CustomEvent('error', { detail: { message: `${this.url} not found` } }),
365
+ );
366
+ }
367
+ }
368
+
135
369
  getTemplate() {
136
370
  return this.querySelector('template');
137
371
  }
@@ -144,7 +378,20 @@ export default class FxControl extends XfAbstractControl {
144
378
 
145
379
  // ### if we find a ref on control we have a 'select' control of some kind
146
380
  const widget = this.getWidget();
147
- if (widget.hasAttribute('ref')) {
381
+ this._handleBoundWidget(widget);
382
+ Fore.refreshChildren(this, force);
383
+ }
384
+
385
+ /**
386
+ * If the widget itself has a `ref` it binds to another nodeset to provide some
387
+ * dynamic items to be created from a template usually. Examples are dynamic select option lists
388
+ * or a set of checkboxes.
389
+ *
390
+ * @param widget the widget to handle
391
+ * @private
392
+ */
393
+ _handleBoundWidget(widget) {
394
+ if (widget && widget.hasAttribute('ref')) {
148
395
  // ### eval nodeset for list control
149
396
  const ref = widget.getAttribute('ref');
150
397
  /*
@@ -158,6 +405,9 @@ export default class FxControl extends XfAbstractControl {
158
405
  // const nodeset = evaluateXPathToNodes(ref, inscope, this);
159
406
  const nodeset = evaluateXPath(ref, inscope, this);
160
407
 
408
+ // ### bail out when nodeset is array and empty
409
+ if (Array.isArray(nodeset) && nodeset.length === 0) return;
410
+
161
411
  // ### clear items
162
412
  const { children } = widget;
163
413
  Array.from(children).forEach(child => {
@@ -184,7 +434,6 @@ export default class FxControl extends XfAbstractControl {
184
434
  }
185
435
  }
186
436
  }
187
- Fore.refreshChildren(this, force);
188
437
  }
189
438
 
190
439
  updateEntry(newEntry, node) {
@@ -194,7 +443,7 @@ export default class FxControl extends XfAbstractControl {
194
443
  const valueAttribute = this._getValueAttribute(newEntry);
195
444
  const valueExpr = valueAttribute.value;
196
445
  const cutted = valueExpr.substring(1, valueExpr.length - 1);
197
- const evaluated = evaluateXPath(cutted, node, newEntry);
446
+ const evaluated = evaluateXPathToString(cutted, node, newEntry);
198
447
  valueAttribute.value = evaluated;
199
448
 
200
449
  if (this.value === evaluated) {
@@ -230,5 +479,6 @@ export default class FxControl extends XfAbstractControl {
230
479
  return result;
231
480
  }
232
481
  }
233
-
234
- window.customElements.define('fx-control', FxControl);
482
+ if (!customElements.get('fx-control')) {
483
+ window.customElements.define('fx-control', FxControl);
484
+ }
@@ -1,20 +1,21 @@
1
+ import { Fore } from '../fore.js';
2
+
1
3
  export class FxDialog extends HTMLElement {
4
+ static get properties() {
5
+ return {
6
+ id: String,
7
+ };
8
+ }
2
9
 
3
- static get properties() {
4
- return {
5
- id: String
6
- };
7
- }
10
+ constructor() {
11
+ super();
12
+ this.attachShadow({ mode: 'open' });
13
+ }
8
14
 
9
- constructor() {
10
- super();
11
- this.attachShadow({mode: 'open'});
12
- }
13
-
14
- connectedCallback() {
15
- const style = `
15
+ connectedCallback() {
16
+ const style = `
16
17
  :host {
17
- display: none;
18
+ display:none;
18
19
  height: 100vh;
19
20
  width:100vw;
20
21
  position:fixed;
@@ -22,47 +23,60 @@ export class FxDialog extends HTMLElement {
22
23
  top:0;
23
24
  right:0;
24
25
  bottom:0;
26
+ transition:opacity 0.4s linear;
25
27
  }
26
- `;
27
28
 
28
- this.shadowRoot.innerHTML = this.render(style);
29
- this.id = this.getAttribute('id');
29
+ `;
30
30
 
31
- // const dialog = document.getElementById(this.id);
31
+ this.shadowRoot.innerHTML = this.render(style);
32
+ this.id = this.getAttribute('id');
32
33
 
33
- const closeBtn = this.querySelector('.close-dialog');
34
- if (closeBtn) {
35
- closeBtn.addEventListener('click', (e) => {
36
- document.getElementById(this.id).classList.remove('show');
37
- });
38
- }
34
+ // const dialog = document.getElementById(this.id);
39
35
 
40
- this.focus();
36
+ const closeBtn = this.querySelector('.close-dialog');
37
+ if (closeBtn) {
38
+ closeBtn.addEventListener('click', () => {
39
+ this.classList.remove('show');
40
+ });
41
41
  }
42
42
 
43
- render(styles) {
44
- return `
43
+ this.addEventListener('transitionend', () => {
44
+ console.log('transitionend');
45
+ // this.style.display = 'none';
46
+ });
47
+
48
+ this.focus();
49
+ }
50
+
51
+ render(styles) {
52
+ return `
45
53
  <style>
46
54
  ${styles}
47
55
  </style>
48
56
  <slot></slot>
49
57
  `;
50
- }
58
+ }
51
59
 
52
- open(){
53
- window.addEventListener('keyup', (e) => {
54
- if (e.key === "Escape") {
55
- this.hide();
56
- }
57
- },{once:true});
58
-
59
- this.classList.add('show');
60
- }
60
+ open() {
61
+ window.addEventListener(
62
+ 'keyup',
63
+ e => {
64
+ if (e.key === 'Escape') {
65
+ this.hide();
66
+ }
67
+ },
68
+ { once: true },
69
+ );
61
70
 
62
- hide(){
63
- this.classList.remove('show');
64
- }
71
+ this.classList.add('show');
72
+ }
65
73
 
74
+ async hide() {
75
+ await Fore.fadeOutElement(this, 400);
76
+ this.classList.remove('show');
77
+ }
66
78
  }
67
79
 
68
- customElements.define('fx-dialog', FxDialog);
80
+ if (!customElements.get('fx-dialog')) {
81
+ customElements.define('fx-dialog', FxDialog);
82
+ }
@@ -86,4 +86,6 @@ class FxGroup extends FxContainer {
86
86
  }
87
87
  }
88
88
 
89
- window.customElements.define('fx-group', FxGroup);
89
+ if (!customElements.get('fx-group')) {
90
+ window.customElements.define('fx-group', FxGroup);
91
+ }
package/src/ui/fx-hint.js CHANGED
@@ -45,4 +45,7 @@ export class FxHint extends XfAbstractControl {
45
45
  }
46
46
  */
47
47
  }
48
- customElements.define('fx-hint', FxHint);
48
+
49
+ if (!customElements.get('fx-hint')) {
50
+ customElements.define('fx-hint', FxHint);
51
+ }