@jinntec/fore 1.2.0 → 1.3.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 (42) hide show
  1. package/dist/fore-dev.js +8 -8
  2. package/dist/fore-dev.js.map +1 -1
  3. package/dist/fore.js +7 -7
  4. package/dist/fore.js.map +1 -1
  5. package/index.js +1 -0
  6. package/package.json +2 -2
  7. package/resources/fore.css +95 -72
  8. package/src/actions/abstract-action.js +48 -6
  9. package/src/actions/fx-action.js +1 -2
  10. package/src/actions/fx-confirm.js +2 -2
  11. package/src/actions/fx-delete.js +53 -62
  12. package/src/actions/fx-hide.js +3 -1
  13. package/src/actions/fx-message.js +25 -1
  14. package/src/actions/fx-reload.js +30 -0
  15. package/src/actions/fx-send.js +11 -1
  16. package/src/actions/fx-setfocus.js +32 -5
  17. package/src/actions/fx-setvalue.js +4 -4
  18. package/src/actions/fx-show.js +1 -0
  19. package/src/actions/fx-toggle.js +5 -0
  20. package/src/events.js +24 -0
  21. package/src/fore.js +71 -8
  22. package/src/fx-bind.js +1 -1
  23. package/src/fx-fore.js +65 -14
  24. package/src/fx-instance.js +33 -8
  25. package/src/fx-model.js +435 -441
  26. package/src/fx-submission.js +76 -62
  27. package/src/getInScopeContext.js +91 -83
  28. package/src/modelitem.js +2 -2
  29. package/src/relevance.js +1 -1
  30. package/src/ui/abstract-control.js +108 -22
  31. package/src/ui/fx-alert.js +0 -1
  32. package/src/ui/fx-container.js +22 -26
  33. package/src/ui/fx-control.js +84 -34
  34. package/src/ui/fx-group.js +5 -0
  35. package/src/ui/fx-inspector.js +5 -0
  36. package/src/ui/fx-output.js +14 -14
  37. package/src/ui/fx-repeat.js +2 -2
  38. package/src/ui/fx-repeatitem.js +5 -3
  39. package/src/ui/fx-switch.js +11 -7
  40. package/src/ui/fx-trigger.js +15 -9
  41. package/src/xpath-evaluation.js +28 -17
  42. package/src/xpath-util.js +13 -0
@@ -37,7 +37,7 @@ export class FxContainer extends foreElementMixin(HTMLElement) {
37
37
  /**
38
38
  * (re)apply all state properties to this control.
39
39
  */
40
- refresh(force) {
40
+ async refresh(force) {
41
41
  if (!force && this.hasAttribute('refresh-on-view')) return;
42
42
  // console.log('### FxContainer.refresh on : ', this);
43
43
 
@@ -47,21 +47,20 @@ export class FxContainer extends foreElementMixin(HTMLElement) {
47
47
  if (this.modelItem && !this.modelItem.boundControls.includes(this)) {
48
48
  this.modelItem.boundControls.push(this);
49
49
  }
50
-
51
- // this.value = this.modelItem.value;
50
+ this.handleModelItemProperties();
52
51
  }
53
52
 
54
- // await this.updateComplete;
55
-
56
53
  // state change event do not fire during init phase (initial refresh)
57
- if (this._getForm().ready) {
58
- this.handleModelItemProperties();
59
- }
60
- Fore.refreshChildren(this, force);
54
+ // if (this._getForm().ready) {
55
+ // this.handleModelItemProperties();
56
+ // }
57
+ // Fore.refreshChildren(this, force);
61
58
  }
62
59
 
60
+ /**
61
+ * anly relevance is processed for container controls
62
+ */
63
63
  handleModelItemProperties() {
64
- this.handleReadonly();
65
64
  this.handleRelevant();
66
65
  }
67
66
 
@@ -69,28 +68,25 @@ export class FxContainer extends foreElementMixin(HTMLElement) {
69
68
  return this.getModel().parentNode;
70
69
  }
71
70
 
72
- handleReadonly() {
73
- // console.log('mip readonly', this.modelItem.isReadonly);
74
- if (this.isReadonly() !== this.modelItem.readonly) {
75
- if (this.modelItem.readonly) {
76
- this.setAttribute('readonly', 'readonly');
77
- this.dispatchEvent(new CustomEvent('readonly', {}));
78
- }
79
- if (!this.modelItem.readonly) {
80
- this.removeAttribute('readonly');
81
- this.dispatchEvent(new CustomEvent('readwrite', {}));
82
- }
83
- }
84
- }
85
-
86
71
  handleRelevant() {
87
72
  // console.log('mip valid', this.modelItem.enabled);
88
- if (!this.modelItem) return;
73
+ if (!this.modelItem) {
74
+ console.log('container is not relevant');
75
+ this.removeAttribute('relevant','');
76
+ this.setAttribute('nonrelevant','');
77
+ this.dispatchEvent(new CustomEvent('disabled', {}));
78
+ return;
79
+ }
89
80
 
90
81
  if (this.isEnabled() !== this.modelItem.enabled) {
91
- if (this.modelItem.enabled) {
82
+ if (this.modelItem.relevant) {
83
+ // this.style.display = 'block';
84
+ this.removeAttribute('nonrelevant','');
85
+ this.setAttribute('relevant','');
92
86
  this.dispatchEvent(new CustomEvent('enabled', {}));
93
87
  } else {
88
+ this.removeAttribute('relevant','');
89
+ this.setAttribute('nonrelevant','');
94
90
  this.dispatchEvent(new CustomEvent('disabled', {}));
95
91
  }
96
92
  }
@@ -7,6 +7,8 @@ import {
7
7
  import getInScopeContext from '../getInScopeContext.js';
8
8
  import { Fore } from '../fore.js';
9
9
  import {ModelItem} from "../modelitem.js";
10
+ import {debounce} from "../events.js";
11
+ import {FxModel} from "../fx-model";
10
12
 
11
13
  const WIDGETCLASS = 'widget';
12
14
 
@@ -20,7 +22,8 @@ const WIDGETCLASS = 'widget';
20
22
  * @demo demo/index.html
21
23
  */
22
24
 
23
- function debounce(func, timeout = 300) {
25
+ /*
26
+ function debounce( func, timeout = 300) {
24
27
  let timer;
25
28
  return (...args) => {
26
29
  clearTimeout(timer);
@@ -29,6 +32,7 @@ function debounce(func, timeout = 300) {
29
32
  }, timeout);
30
33
  };
31
34
  }
35
+ */
32
36
  export default class FxControl extends XfAbstractControl {
33
37
  constructor() {
34
38
  super();
@@ -72,10 +76,16 @@ export default class FxControl extends XfAbstractControl {
72
76
  listenOn = target;
73
77
  }
74
78
  }
79
+
80
+
81
+ this.addEventListener('keyup', event => {
82
+ FxModel.dataChanged = true;
83
+ });
75
84
  // ### convenience marker event
76
85
  if (this.updateEvent === 'enter') {
77
86
  this.widget.addEventListener('keyup', event => {
78
87
  if (event.keyCode === 13) {
88
+ // console.info('handling Event:', event.type, listenOn);
79
89
  // Cancel the default action, if needed
80
90
  event.preventDefault();
81
91
  this.setValue(this.widget[this.valueProp]);
@@ -86,14 +96,15 @@ export default class FxControl extends XfAbstractControl {
86
96
  if (this.debounceDelay) {
87
97
  listenOn.addEventListener(
88
98
  this.updateEvent,
89
- debounce(() => {
90
- console.log('eventlistener ', this.updateEvent);
99
+ debounce(this,() => {
100
+ // console.log('eventlistener ', this.updateEvent);
101
+ // console.info('handling Event:', event.type, listenOn);
91
102
  this.setValue(this.widget[this.valueProp]);
92
103
  }, this.debounceDelay),
93
104
  );
94
105
  } else {
95
106
  listenOn.addEventListener(this.updateEvent, () => {
96
- console.log('eventlistener ', this.updateEvent);
107
+ // console.info('handling Event:', event.type, listenOn);
97
108
  this.setValue(this.widget[this.valueProp]);
98
109
  });
99
110
  }
@@ -125,6 +136,8 @@ export default class FxControl extends XfAbstractControl {
125
136
  });
126
137
 
127
138
  this.template = this.querySelector('template');
139
+ this.boundInitialized = false;
140
+ this.static = this.widget.hasAttribute('static')? true:false;
128
141
  // console.log('template',this.template);
129
142
  }
130
143
 
@@ -150,6 +163,8 @@ export default class FxControl extends XfAbstractControl {
150
163
  setValue(val) {
151
164
  const modelitem = this.getModelItem();
152
165
 
166
+ this.classList.add('visited');
167
+
153
168
  if (modelitem?.readonly){
154
169
  console.warn('attempt to change readonly node', modelitem);
155
170
  return; // do nothing when modelItem is readonly
@@ -161,6 +176,7 @@ export default class FxControl extends XfAbstractControl {
161
176
  replace.replace(this.nodeset, this.getWidget().value);
162
177
  if (modelitem && widgetValue && widgetValue !== modelitem.value) {
163
178
  modelitem.value = widgetValue;
179
+ FxModel.dataChanged = true;
164
180
  replace.actionPerformed();
165
181
  }
166
182
  return;
@@ -173,6 +189,7 @@ export default class FxControl extends XfAbstractControl {
173
189
  }
174
190
 
175
191
  setval.actionPerformed();
192
+ this.visited = true;
176
193
  }
177
194
 
178
195
  _replaceNode(node) {
@@ -268,7 +285,7 @@ export default class FxControl extends XfAbstractControl {
268
285
  widget.value = this.nodeset.cloneNode(true);
269
286
  // todo: should be more like below but that can cause infinite loop when controll trigger update event due to calling a setter for property
270
287
  // widget[this.valueProp] = this.nodeset.cloneNode(true);
271
- console.log('passed value to widget', widget.value);
288
+ // console.log('passed value to widget', widget.value);
272
289
  }
273
290
 
274
291
  return;
@@ -277,6 +294,7 @@ export default class FxControl extends XfAbstractControl {
277
294
  // ### when there's a url Fore is used as widget and will be loaded from external file
278
295
  if (this.url && !this.loaded) {
279
296
  // ### evaluate initial data if necessary
297
+
280
298
  if (this.initial) {
281
299
  this.initialNode = evaluateXPathToFirstNode(this.initial, this.nodeset, this);
282
300
  console.log('initialNodes', this.initialNode);
@@ -315,7 +333,11 @@ export default class FxControl extends XfAbstractControl {
315
333
  * @private
316
334
  */
317
335
  async _loadForeFromUrl() {
318
- console.log('########## loading Fore from ', this.src, '##########');
336
+ console.log('########## loading Fore from ', this.url, '##########');
337
+ console.info(
338
+ `%cFore is processing URL ${this.url}`,
339
+ "background:#64b5f6; color:white; padding:1rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;",
340
+ );
319
341
  try {
320
342
  const response = await fetch(this.url, {
321
343
  method: 'GET',
@@ -325,6 +347,7 @@ export default class FxControl extends XfAbstractControl {
325
347
  'Content-Type': 'text/html',
326
348
  },
327
349
  });
350
+
328
351
  const responseContentType = response.headers.get('content-type').toLowerCase();
329
352
  console.log('********** responseContentType *********', responseContentType);
330
353
  let data;
@@ -338,37 +361,42 @@ export default class FxControl extends XfAbstractControl {
338
361
  }
339
362
  // const theFore = fxEvaluateXPathToFirstNode('//fx-fore', data.firstElementChild);
340
363
  const theFore = data.querySelector('fx-fore');
364
+ const imported = document.importNode(theFore,true);
365
+
341
366
  // console.log('thefore', theFore)
342
- theFore.classList.add('widget'); // is the new widget
367
+ imported.classList.add('widget'); // is the new widget
368
+ console.log(`########## loaded fore as component ##### ${this.url}`);
369
+ imported.addEventListener(
370
+ 'model-construct-done',
371
+ e => {
372
+ console.log('subcomponent ready', e.target);
373
+ const defaultInst = imported.querySelector('fx-instance');
374
+ // console.log('defaultInst', defaultInst);
375
+ if(this.initialNode){
376
+ const doc = new DOMParser().parseFromString('<data></data>', 'application/xml');
377
+ // Note: Clone the input to prevent the inner fore from editing the outer node
378
+ doc.firstElementChild.appendChild(this.initialNode.cloneNode(true));
379
+ // defaultinst.setInstanceData(this.initialNode);
380
+ defaultInst.setInstanceData(doc);
381
+ }
382
+ // console.log('new data', defaultInst.getInstanceData());
383
+ // theFore.getModel().modelConstruct();
384
+ imported.getModel().updateModel();
385
+ imported.refresh();
386
+ },
387
+ { once: true },
388
+ );
389
+
343
390
  const dummy = this.querySelector('input');
344
391
  if (this.hasAttribute('shadow')) {
345
392
  dummy.parentNode.removeChild(dummy);
346
- this.shadowRoot.appendChild(theFore);
393
+ this.shadowRoot.appendChild(imported);
347
394
  } else {
348
- dummy.replaceWith(theFore);
395
+ console.log(this, 'replacing widget with',theFore);
396
+ dummy.replaceWith(imported);
397
+ // this.appendChild(imported);
349
398
  }
350
399
 
351
- console.log(`########## loaded fore as component ##### ${this.url}`);
352
- theFore.addEventListener(
353
- 'model-construct-done',
354
- e => {
355
- console.log('subcomponent ready', e.target);
356
- const defaultInst = theFore.querySelector('fx-instance');
357
- console.log('defaultInst', defaultInst);
358
- if(this.initialNode){
359
- const doc = new DOMParser().parseFromString('<data></data>', 'application/xml');
360
- // Note: Clone the input to prevent the inner fore from editing the outer node
361
- doc.firstElementChild.appendChild(this.initialNode.cloneNode(true));
362
- // defaultinst.setInstanceData(this.initialNode);
363
- defaultInst.setInstanceData(doc);
364
- }
365
- console.log('new data', defaultInst.getInstanceData());
366
- // theFore.getModel().modelConstruct();
367
- theFore.getModel().updateModel();
368
- theFore.refresh();
369
- },
370
- { once: true },
371
- );
372
400
 
373
401
  if (!theFore) {
374
402
  this.dispatchEvent(
@@ -414,6 +442,8 @@ export default class FxControl extends XfAbstractControl {
414
442
  * @private
415
443
  */
416
444
  _handleBoundWidget(widget) {
445
+ if(this.boundInitialized && this.static) return;
446
+
417
447
  if (widget && widget.hasAttribute('ref')) {
418
448
  // ### eval nodeset for list control
419
449
  const ref = widget.getAttribute('ref');
@@ -451,18 +481,36 @@ export default class FxControl extends XfAbstractControl {
451
481
 
452
482
  if (nodeset.length) {
453
483
  // console.log('nodeset', nodeset);
484
+ const fragment = document.createDocumentFragment();
485
+ console.time('offscreen');
486
+ /*
487
+ Array.from(nodeset).forEach(node => {
488
+ // console.log('#### node', node);
489
+ const newEntry = this.createEntry();
490
+ this.template.parentNode.appendChild(newEntry);
491
+ // ### initialize new entry
492
+ // ### set value
493
+ this.updateEntry(newEntry, node);
494
+ });
495
+ */
496
+ // this should actually perform better than the above but does not seem to make a measurable difference.
454
497
  Array.from(nodeset).forEach(node => {
455
498
  // console.log('#### node', node);
456
499
  const newEntry = this.createEntry();
500
+ fragment.appendChild(newEntry);
457
501
 
458
502
  // ### initialize new entry
459
503
  // ### set value
460
504
  this.updateEntry(newEntry, node);
461
505
  });
506
+ this.template.parentNode.appendChild(fragment);
507
+ console.timeEnd('offscreen');
462
508
  } else {
463
509
  const newEntry = this.createEntry();
510
+ this.template.parentNode.appendChild(newEntry);
464
511
  this.updateEntry(newEntry, nodeset);
465
512
  }
513
+ this.boundInitialized = true;
466
514
  }
467
515
  }
468
516
  }
@@ -491,10 +539,12 @@ export default class FxControl extends XfAbstractControl {
491
539
  }
492
540
 
493
541
  createEntry() {
494
- const content = this.template.content.firstElementChild.cloneNode(true);
495
- const newEntry = document.importNode(content, true);
496
- this.template.parentNode.appendChild(newEntry);
497
- return newEntry;
542
+ return this.template.content.firstElementChild.cloneNode(true);
543
+ // const content = this.template.content.firstElementChild.cloneNode(true);
544
+ // return content;
545
+ // const newEntry = document.importNode(content, true);
546
+ // this.template.parentNode.appendChild(newEntry);
547
+ // return newEntry;
498
548
  }
499
549
 
500
550
  // eslint-disable-next-line class-methods-use-this
@@ -84,6 +84,11 @@ class FxGroup extends FxContainer {
84
84
 
85
85
  console.groupEnd();
86
86
  }
87
+
88
+ async refresh(force) {
89
+ super.refresh(force);
90
+ Fore.refreshChildren(this,force);
91
+ }
87
92
  }
88
93
 
89
94
  if (!customElements.get('fx-group')) {
@@ -25,6 +25,7 @@ export class FxInspector extends HTMLElement {
25
25
  /*max-height: 33%;*/
26
26
  overflow: scroll;
27
27
  transition:width 0.3s ease;
28
+ z-index:100;
28
29
  }
29
30
  :host([open]){
30
31
  width: 30%;
@@ -134,6 +135,10 @@ export class FxInspector extends HTMLElement {
134
135
  }
135
136
 
136
137
  serializeDOM(data) {
138
+ if(!data){
139
+ console.warn('no data to serialize');
140
+ return ;
141
+ }
137
142
  // console.log('serializeDOM', data);
138
143
  const ser = new XMLSerializer().serializeToString(data);
139
144
  return Fore.prettifyXml(ser);
@@ -51,8 +51,9 @@ export class FxOutput extends XfAbstractControl {
51
51
 
52
52
  const outputHtml = `
53
53
  <slot name="label"></slot>
54
+
54
55
  <span id="value">
55
- <slot id="main"></slot>
56
+ <slot></slot>
56
57
  </span>
57
58
  `;
58
59
 
@@ -117,10 +118,11 @@ export class FxOutput extends XfAbstractControl {
117
118
  // console.log('updateWidgetValue');
118
119
  const valueWrapper = this.shadowRoot.getElementById('value');
119
120
 
120
- if (this.mediatype === 'markdown') {
121
- const md = markdown(this.nodeset);
122
- this.innerHtml = md;
123
- }
121
+
122
+ // if (this.mediatype === 'markdown') {
123
+ // const md = markdown(this.nodeset);
124
+ // this.innerHtml = md;
125
+ // }
124
126
 
125
127
  if (this.mediatype === 'html') {
126
128
  if (this.modelItem.node) {
@@ -132,16 +134,7 @@ export class FxOutput extends XfAbstractControl {
132
134
  const { node } = this.modelItem;
133
135
 
134
136
  if (node.nodeType) {
135
- // const mainSlot = this.shadowRoot.querySelector('#main');
136
- // valueWrapper.appendChild(node);
137
-
138
- // todo: checking if ownerDocument of node and ownerDocument of this are the same - otherwise import first
139
- // const imported = this.ownerDocument.importNode(node,true);
140
- // const clone = node.cloneNode(true);
141
-
142
137
  this.appendChild(node);
143
- // this.innerHtml = node;
144
- // this.innerHTML = node;
145
138
  return;
146
139
  }
147
140
  Object.entries(node).map(obj => {
@@ -158,6 +151,13 @@ export class FxOutput extends XfAbstractControl {
158
151
  return;
159
152
  }
160
153
 
154
+ if(this.mediatype === 'image'){
155
+ const img = document.createElement('img');
156
+ img.setAttribute('src',this.value);
157
+ this.appendChild(img);
158
+ return;
159
+ }
160
+
161
161
  valueWrapper.innerHTML = this.value;
162
162
  }
163
163
 
@@ -212,7 +212,7 @@ export class FxRepeat extends foreElementMixin(HTMLElement) {
212
212
  // console.group('fx-repeat.refresh on', this.id);
213
213
 
214
214
  if (!this.inited) this.init();
215
- console.time('repeat-refresh', this);
215
+ // console.time('repeat-refresh', this);
216
216
  this._evalNodeset();
217
217
  // console.log('repeat refresh nodeset ', this.nodeset);
218
218
  // console.log('repeatCount', this.repeatCount);
@@ -275,7 +275,7 @@ export class FxRepeat extends foreElementMixin(HTMLElement) {
275
275
  // this.style.display = 'block';
276
276
  // this.style.display = this.display;
277
277
  this.setIndex(this.index);
278
- console.timeEnd('repeat-refresh');
278
+ // console.timeEnd('repeat-refresh');
279
279
 
280
280
  // this.replaceWith(clone);
281
281
 
@@ -43,7 +43,7 @@ export class FxRepeatitem extends foreElementMixin(HTMLElement) {
43
43
  // console.log('_dispatchIndexChange on index ', this.index);
44
44
  if (this.parentNode) {
45
45
  this.parentNode.dispatchEvent(
46
- new CustomEvent('item-changed', { composed: true, bubbles: true, detail: { item: this } }),
46
+ new CustomEvent('item-changed', { composed: false, bubbles: true, detail: { item: this , index:this.index } }),
47
47
  );
48
48
  }
49
49
  }
@@ -90,12 +90,14 @@ export class FxRepeatitem extends foreElementMixin(HTMLElement) {
90
90
 
91
91
  if (this.modelItem && !this.modelItem.relevant) {
92
92
  // await Fore.fadeOutElement(this)
93
- this.style.display = 'none';
93
+ // this.style.display = 'none';
94
+ this.classList.add('nonrelevant');
94
95
  } else {
95
96
  // if(this.hasAttribute('repeat-index')){
96
97
  // Fore.fadeInElement(this);
97
98
  // }
98
- this.style.display = this.display;
99
+ // this.style.display = this.display;
100
+ this.classList.remove('nonrelevant');
99
101
  }
100
102
 
101
103
  /*
@@ -36,28 +36,31 @@ class FxSwitch extends FxContainer {
36
36
  `;
37
37
  }
38
38
 
39
- refresh() {
39
+ async refresh(force) {
40
40
  super.refresh();
41
- console.log('refresh on switch ');
41
+ // console.log('refresh on switch ');
42
42
  const cases = this.querySelectorAll(':scope > fx-case');
43
+ let selectedCase;
43
44
  if (this.isBound()) {
44
45
  Array.from(cases).forEach(caseElem => {
45
46
  const name = caseElem.getAttribute('name');
46
47
  if (name === this.modelItem.value) {
47
48
  caseElem.classList.add('selected-case');
49
+ selectedCase = caseElem;
48
50
  } else {
49
51
  caseElem.classList.remove('selected-case');
50
52
  }
51
53
  });
52
54
  } else {
53
- const selected = this.querySelector(':scope > .selected-case');
54
- if (!selected) {
55
- cases[0].classList.add('selected-case');
55
+ selectedCase = this.querySelector(':scope > .selected-case');
56
+ // if none is selected select the first as default
57
+ if (!selectedCase) {
58
+ selectedCase = cases[0];
59
+ selectedCase.classList.add('selected-case');
56
60
  }
57
61
  }
58
62
 
59
- Fore.refreshChildren(this);
60
- // console.log('value ', this.value);
63
+ Fore.refreshChildren(selectedCase,force);
61
64
  }
62
65
 
63
66
  toggle(caseElement) {
@@ -66,6 +69,7 @@ class FxSwitch extends FxContainer {
66
69
  if (caseElement === c) {
67
70
  // eslint-disable-next-line no-param-reassign
68
71
  c.classList.add('selected-case');
72
+ this.refresh();
69
73
  } else {
70
74
  // eslint-disable-next-line no-param-reassign
71
75
  c.classList.remove('selected-case');
@@ -1,9 +1,12 @@
1
1
  import XfAbstractControl from './abstract-control.js';
2
+ import {leadingDebounce} from "../events.js";
2
3
 
3
4
  export class FxTrigger extends XfAbstractControl {
4
5
  connectedCallback() {
5
6
  this.attachShadow({ mode: 'open' });
6
7
  this.ref = this.hasAttribute('ref') ? this.getAttribute('ref') : null;
8
+ this.debounceDelay = this.hasAttribute('debounce') ? this.getAttribute('debounce') : null;
9
+
7
10
  const style = `
8
11
  :host {
9
12
  cursor:pointer;
@@ -24,7 +27,17 @@ export class FxTrigger extends XfAbstractControl {
24
27
  elements[0].setAttribute('role', 'button');
25
28
 
26
29
  const element = elements[0];
27
- element.addEventListener('click', e => this.performActions(e));
30
+
31
+ if(this.debounceDelay){
32
+ this.addEventListener(
33
+ 'click',
34
+ leadingDebounce(this,(e) => {
35
+ this.performActions(e)
36
+ }, this.debounceDelay),
37
+ );
38
+ }else{
39
+ element.addEventListener('click', e => this.performActions(e));
40
+ }
28
41
  this.widget = element;
29
42
  // # terrible hack but browser behaves strange - seems to fire a 'click' for a button when it receives a
30
43
  // # 'Space' or 'Enter' key
@@ -36,14 +49,6 @@ export class FxTrigger extends XfAbstractControl {
36
49
  });
37
50
  }
38
51
  });
39
- /*
40
- this.addEventListener('click', e => this.performActions(e));
41
- this.addEventListener('keypress', (e) => {
42
- if(e.code === 'Space' || e.code === 'Enter'){
43
- this.performActions(e);
44
- }
45
- });
46
- */
47
52
  }
48
53
 
49
54
  // eslint-disable-next-line class-methods-use-this
@@ -89,6 +94,7 @@ export class FxTrigger extends XfAbstractControl {
89
94
  if (typeof child.execute === 'function') {
90
95
  // eslint-disable-next-line no-await-in-loop
91
96
  await child.execute(e);
97
+ // child.execute(e);
92
98
  }
93
99
  }
94
100
  };
@@ -146,6 +146,30 @@ export function resolveId(id, sourceObject, nodeName = null) {
146
146
  // Make namespace resolving use the `instance` element that is related to here
147
147
  const xmlDocument = new DOMParser().parseFromString('<xml />', 'text/xml');
148
148
 
149
+ function findInstanceReferences(xpathQuery) {
150
+ if (!xpathQuery.includes('instance')) {
151
+ // No call to the instance function anyway: short-circuit and prevent AST processing
152
+ return [];
153
+ }
154
+ const xpathAST = parseScript(xpathQuery, {}, xmlDocument);
155
+ const instanceReferences = fxEvaluateXPathToStrings(
156
+ `descendant::xqx:functionCallExpr
157
+ [xqx:functionName = "instance"]
158
+ /xqx:arguments
159
+ /xqx:stringConstantExpr
160
+ /xqx:value`,
161
+ xpathAST,
162
+ null,
163
+ {},
164
+ {
165
+ namespaceResolver: prefix =>
166
+ prefix === 'xqx' ? 'http://www.w3.org/2005/XQueryX' : undefined,
167
+ },
168
+ );
169
+
170
+ return instanceReferences;
171
+ }
172
+
149
173
  /**
150
174
  * Resolve a namespace. Needs a namespace prefix and the element that is most closely related to the
151
175
  * XPath in which the namespace is being resolved. The prefix will be resolved by using the
@@ -165,25 +189,12 @@ function createNamespaceResolver(xpathQuery, formElement) {
165
189
  if (cachedResolver) {
166
190
  return cachedResolver;
167
191
  }
168
-
169
- const xpathAST = parseScript(xpathQuery, {}, xmlDocument);
170
- let instanceReferences = fxEvaluateXPathToStrings(
171
- `descendant::xqx:functionCallExpr
172
- [xqx:functionName = "instance"]
173
- /xqx:arguments
174
- /xqx:stringConstantExpr
175
- /xqx:value`,
176
- xpathAST,
177
- null,
178
- {},
179
- {
180
- namespaceResolver: prefix =>
181
- prefix === 'xqx' ? 'http://www.w3.org/2005/XQueryX' : undefined,
182
- },
183
- );
192
+ let instanceReferences = findInstanceReferences(xpathQuery);
184
193
  if (instanceReferences.length === 0) {
185
194
  // No instance functions. Look up further in the hierarchy to see if we can deduce the intended context from there
186
- const ancestorComponent = fxEvaluateXPathToFirstNode('ancestor::*[@ref][1]', formElement);
195
+ const ancestorComponent = formElement.parentNode &&
196
+ formElement.parentNode.nodeType === formElement.ELEMENT &&
197
+ formElement.parentNode.closest('[ref]');
187
198
  if (ancestorComponent) {
188
199
  const resolver = createNamespaceResolver(
189
200
  ancestorComponent.getAttribute('ref'),
package/src/xpath-util.js CHANGED
@@ -76,6 +76,19 @@ export class XPathUtil {
76
76
  return null;
77
77
  }
78
78
 
79
+ static resolveInstance(boundElement){
80
+ const instanceId = XPathUtil.getInstanceId(boundElement.getAttribute('ref'));
81
+ if(instanceId !== null){
82
+ return instanceId;
83
+ }
84
+
85
+ const parentBinding = XPathUtil.getParentBindingElement(boundElement);
86
+ if(parentBinding){
87
+ return this.resolveInstance(parentBinding);
88
+ }
89
+ return 'default';
90
+ }
91
+
79
92
  // todo: certainly not ideal to rely on duplicating instance id on instance document - better way later ;)
80
93
  static getPath(node) {
81
94
  const path = fx.evaluateXPathToString('path()', node);