@jinntec/fore 1.0.0-5 → 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 (63) hide show
  1. package/README.md +6 -27
  2. package/dist/fore-dev.js +43 -0
  3. package/dist/fore-dev.js.map +1 -0
  4. package/dist/fore.js +37 -0
  5. package/dist/fore.js.map +1 -0
  6. package/index.js +2 -1
  7. package/package.json +35 -39
  8. package/resources/fore.css +22 -54
  9. package/src/DependencyNotifyingDomFacade.js +5 -13
  10. package/src/ForeElementMixin.js +13 -20
  11. package/src/actions/abstract-action.js +14 -9
  12. package/src/actions/fx-action.js +6 -5
  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 +9 -6
  18. package/src/actions/fx-insert.js +27 -14
  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 +13 -9
  27. package/src/actions/fx-update.js +3 -1
  28. package/src/dep_graph.js +1 -1
  29. package/src/drawdown.js +67 -82
  30. package/src/fore.js +141 -25
  31. package/src/functions/fx-function.js +11 -3
  32. package/src/fx-bind.js +39 -199
  33. package/src/fx-fore.js +101 -68
  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 +23 -16
  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 +4 -2
  46. package/src/ui/fx-control.js +283 -33
  47. package/src/ui/fx-dialog.js +50 -45
  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 +7 -5
  52. package/src/ui/fx-output.js +14 -5
  53. package/src/ui/fx-repeat.js +13 -26
  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 +114 -102
  58. package/src/xpath-util.js +1 -5
  59. package/dist/fore-all.js +0 -140
  60. package/dist/fore-debug.js +0 -140
  61. package/src/.DS_Store +0 -0
  62. package/src/actions/.DS_Store +0 -0
  63. package/src/ui/.DS_Store +0 -0
@@ -1,6 +1,7 @@
1
1
  import '../fx-model.js';
2
2
  import { foreElementMixin } from '../ForeElementMixin.js';
3
3
  import { ModelItem } from '../modelitem.js';
4
+ import { Fore } from '../fore.js';
4
5
 
5
6
  /**
6
7
  * `AbstractControl` -
@@ -26,7 +27,7 @@ export default class AbstractControl extends foreElementMixin(HTMLElement) {
26
27
  /**
27
28
  * (re)apply all modelItem state properties to this control. model -> UI
28
29
  */
29
- async refresh(force) {
30
+ async refresh() {
30
31
  // console.log('### AbstractControl.refresh on : ', this);
31
32
 
32
33
  const currentVal = this.value;
@@ -36,6 +37,7 @@ export default class AbstractControl extends foreElementMixin(HTMLElement) {
36
37
 
37
38
  // await this.updateComplete;
38
39
  // await this.getWidget();
40
+ this.oldVal = this.nodeset ? this.nodeset : null;
39
41
  this.evalInContext();
40
42
 
41
43
  if (this.isBound()) {
@@ -51,12 +53,36 @@ export default class AbstractControl extends foreElementMixin(HTMLElement) {
51
53
  if (this.modelItem instanceof ModelItem) {
52
54
  // console.log('### XfAbstractControl.refresh modelItem : ', this.modelItem);
53
55
 
54
- this.value = this.modelItem.value;
56
+ if (this.hasAttribute('as') && this.getAttribute('as') === 'node') {
57
+ console.log('as', this.nodeset);
58
+ this.modelItem.value = this.nodeset;
59
+ this.value = this.modelItem.value;
60
+ } else {
61
+ this.value = this.modelItem.value;
62
+ }
63
+
64
+ // console.log('value of widget',this.value);
65
+
66
+ /*
67
+ * todo: find out on which foreign modelitems we might be dependant on when no binds are used.
68
+ *
69
+ * e.g. filter expr on 'ref' 'instance('countries')//country[@continent = instance('default')/continent]'
70
+ *
71
+ * the country node is dependant on instance('default')/continent here (foreign node).
72
+ *
73
+ * possible approach:
74
+ * - pipe ref expression through DependencyNotifyingDomFacade to get referred nodes.
75
+ * - lookup modelItems of referred nodes
76
+ * - add ourselves to boundControls of foreign modelItem -> this control will then get refreshed when the foreign modelItem is changed.
77
+ */
78
+
79
+ // const touched = FxBind.getReferencesForRef(this.ref,Array.from(this.nodeset));
80
+ // console.log('touched',touched);
55
81
 
56
82
  /*
57
83
  this is another case that highlights the fact that an init() function might make sense in general.
58
84
  */
59
- if(!this.modelItem.boundControls.includes(this)){
85
+ if (!this.modelItem.boundControls.includes(this)) {
60
86
  this.modelItem.boundControls.push(this);
61
87
  }
62
88
 
@@ -68,15 +94,10 @@ export default class AbstractControl extends foreElementMixin(HTMLElement) {
68
94
  // if(!this.closest('fx-fore').ready) return; // state change event do not fire during init phase (initial refresh)
69
95
  if (!this.getOwnerForm().ready) return; // state change event do not fire during init phase (initial refresh)
70
96
  if (currentVal !== this.value) {
71
- // console.log('dispatching value-changed for ', this);
72
- // console.log('value-changed path ', this.modelItem.path);
73
- this.dispatch('value-changed', { path: this.modelItem.path });
97
+ Fore.dispatch(this, 'value-changed', { path: this.modelItem.path });
74
98
  }
75
- // this.requestUpdate();
76
99
  }
77
100
  }
78
- // Fore.refreshChildren(this,force);
79
- // await this.updateComplete;
80
101
  }
81
102
 
82
103
  /**
@@ -89,13 +110,14 @@ export default class AbstractControl extends foreElementMixin(HTMLElement) {
89
110
  }
90
111
 
91
112
  handleModelItemProperties() {
92
- // console.log('form ready', this.getOwnerForm().ready);
113
+ // console.log('handleModelItemProperties',this.modelItem);
93
114
  this.handleRequired();
94
115
  this.handleReadonly();
95
116
  if (this.getOwnerForm().ready) {
96
117
  this.handleValid();
97
118
  }
98
119
  this.handleRelevant();
120
+ // todo: handleType()
99
121
  }
100
122
 
101
123
  _getForm() {
@@ -104,25 +126,23 @@ export default class AbstractControl extends foreElementMixin(HTMLElement) {
104
126
 
105
127
  _dispatchEvent(event) {
106
128
  if (this.getOwnerForm().ready) {
107
- this.dispatch(event, {});
129
+ Fore.dispatch(this, event, {});
108
130
  }
109
131
  }
110
132
 
111
133
  // eslint-disable-next-line class-methods-use-this
112
134
  handleRequired() {
113
135
  // console.log('mip required', this.modelItem.required);
114
- // const control = this.querySelector('#control');
115
136
  this.widget = this.getWidget();
137
+ // if (this.required !== this.modelItem.required) {
116
138
  if (this.isRequired() !== this.modelItem.required) {
117
139
  if (this.modelItem.required) {
118
- this.widget.setAttribute('required', 'required');
119
- this.classList.add('required');
140
+ this.widget.setAttribute('required', '');
141
+ this.setAttribute('required', '');
120
142
  this._dispatchEvent('required');
121
143
  } else {
122
144
  this.widget.removeAttribute('required');
123
- this.required = false;
124
- // this.removeAttribute('required');
125
- this.classList.toggle('required');
145
+ this.removeAttribute('required');
126
146
  this._dispatchEvent('optional');
127
147
  }
128
148
  }
@@ -132,15 +152,13 @@ export default class AbstractControl extends foreElementMixin(HTMLElement) {
132
152
  // console.log('mip readonly', this.modelItem.isReadonly);
133
153
  if (this.isReadonly() !== this.modelItem.readonly) {
134
154
  if (this.modelItem.readonly) {
135
- this.widget.setAttribute('readonly', 'readonly');
136
- // this.setAttribute('readonly','readonly');
137
- this.classList.toggle('readonly');
155
+ this.widget.setAttribute('readonly', '');
156
+ this.setAttribute('readonly', '');
138
157
  this._dispatchEvent('readonly');
139
158
  }
140
159
  if (!this.modelItem.readonly) {
141
160
  this.widget.removeAttribute('readonly');
142
- // this.removeAttribute('readonly');
143
- this.classList.toggle('readonly');
161
+ this.removeAttribute('readonly');
144
162
  this._dispatchEvent('readwrite');
145
163
  }
146
164
  }
@@ -153,12 +171,12 @@ export default class AbstractControl extends foreElementMixin(HTMLElement) {
153
171
 
154
172
  if (this.isValid() !== this.modelItem.constraint) {
155
173
  if (this.modelItem.constraint) {
156
- this.classList.remove('invalid');
157
174
  if (alert) alert.style.display = 'none';
158
175
  this._dispatchEvent('valid');
176
+ this.removeAttribute('invalid');
159
177
  } else {
178
+ this.setAttribute('invalid', '');
160
179
  // ### constraint is invalid - handle alerts
161
- this.classList.add('invalid');
162
180
  if (alert) {
163
181
  alert.style.display = 'block';
164
182
  }
@@ -185,6 +203,12 @@ export default class AbstractControl extends foreElementMixin(HTMLElement) {
185
203
 
186
204
  handleRelevant() {
187
205
  // console.log('mip valid', this.modelItem.enabled);
206
+ const item = this.modelItem.node;
207
+ if (Array.isArray(item) && item.length === 0) {
208
+ this._dispatchEvent('nonrelevant');
209
+ this.style.display = 'none';
210
+ return;
211
+ }
188
212
  if (this.isEnabled() !== this.modelItem.relevant) {
189
213
  if (this.modelItem.relevant) {
190
214
  this._dispatchEvent('relevant');
@@ -199,14 +223,12 @@ export default class AbstractControl extends foreElementMixin(HTMLElement) {
199
223
  }
200
224
 
201
225
  isRequired() {
202
- if (this.widget.hasAttribute('required')) {
203
- return true;
204
- }
205
- return false;
226
+ return this.hasAttribute('required');
206
227
  }
207
228
 
208
229
  isValid() {
209
- if (this.classList.contains('invalid')) {
230
+ // return this.valid;
231
+ if (this.hasAttribute('invalid')) {
210
232
  return false;
211
233
  }
212
234
  return true;
@@ -214,10 +236,7 @@ export default class AbstractControl extends foreElementMixin(HTMLElement) {
214
236
 
215
237
  isReadonly() {
216
238
  // const widget = this.querySelector('#widget');
217
- if (this.widget.hasAttribute('readonly')) {
218
- return true;
219
- }
220
- return false;
239
+ return this.hasAttribute('readonly');
221
240
  }
222
241
 
223
242
  isEnabled() {
@@ -257,5 +276,6 @@ export default class AbstractControl extends foreElementMixin(HTMLElement) {
257
276
  })();
258
277
  }
259
278
  }
260
-
261
- window.customElements.define('fx-abstract-control', AbstractControl);
279
+ if (!customElements.get('fx-abstract-control')) {
280
+ window.customElements.define('fx-abstract-control', AbstractControl);
281
+ }
@@ -29,9 +29,15 @@ export class FxAlert extends AbstractControl {
29
29
  `;
30
30
  }
31
31
 
32
+ getWidget() {
33
+ return this;
34
+ }
35
+
32
36
  async updateWidgetValue() {
33
37
  console.log('alert update', this);
34
38
  this.innerHTML = this.value;
35
39
  }
36
40
  }
37
- customElements.define('fx-alert', FxAlert);
41
+ if (!customElements.get('fx-alert')) {
42
+ customElements.define('fx-alert', FxAlert);
43
+ }
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,7 +44,7 @@ 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)){
47
+ if (!this.modelItem.boundControls.includes(this)) {
48
48
  this.modelItem.boundControls.push(this);
49
49
  }
50
50
 
@@ -111,4 +111,6 @@ export class FxContainer extends foreElementMixin(HTMLElement) {
111
111
  }
112
112
  }
113
113
 
114
- 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
+ }