@jinntec/fore 2.5.0 → 2.7.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/dist/fore-dev.js +36088 -9
  2. package/dist/fore.js +35918 -9
  3. package/index.js +3 -1
  4. package/package.json +10 -4
  5. package/resources/fore.css +30 -5
  6. package/src/DataObserver.js +181 -0
  7. package/src/DependencyNotifyingDomFacade.js +27 -21
  8. package/src/DependentXPathQueries.js +32 -0
  9. package/src/ForeElementMixin.js +60 -26
  10. package/src/actions/abstract-action.js +24 -29
  11. package/src/actions/fx-append.js +25 -2
  12. package/src/actions/fx-call.js +2 -2
  13. package/src/actions/fx-delete.js +58 -21
  14. package/src/actions/fx-hide.js +1 -1
  15. package/src/actions/fx-insert.js +62 -48
  16. package/src/actions/fx-load.js +7 -2
  17. package/src/actions/fx-refresh.js +15 -5
  18. package/src/actions/fx-replace.js +18 -3
  19. package/src/actions/fx-reset.js +6 -0
  20. package/src/actions/fx-send.js +10 -7
  21. package/src/actions/fx-setattribute.js +12 -0
  22. package/src/actions/fx-setfocus.js +11 -8
  23. package/src/actions/fx-setvalue.js +20 -2
  24. package/src/actions/fx-show.js +4 -2
  25. package/src/actions/fx-toggle.js +1 -1
  26. package/src/extract-predicate-deps.js +57 -0
  27. package/src/extractPredicateDependencies.js +36 -0
  28. package/src/fore.js +78 -36
  29. package/src/fx-bind.js +128 -23
  30. package/src/fx-connection.js +24 -7
  31. package/src/fx-fore.js +552 -306
  32. package/src/fx-instance.js +9 -11
  33. package/src/fx-model.js +154 -65
  34. package/src/fx-submission.js +45 -51
  35. package/src/fx-var.js +5 -0
  36. package/src/getInScopeContext.js +8 -8
  37. package/src/modelitem.js +218 -72
  38. package/src/tools/fx-action-log.js +21 -19
  39. package/src/tools/fx-log-settings.js +4 -2
  40. package/src/ui/TemplateExpression.js +12 -0
  41. package/src/ui/UIElement.js +206 -0
  42. package/src/ui/abstract-control.js +15 -7
  43. package/src/ui/fx-case.js +15 -3
  44. package/src/ui/fx-container.js +10 -3
  45. package/src/ui/fx-control-menu.js +207 -0
  46. package/src/ui/fx-control.js +116 -32
  47. package/src/ui/fx-dialog.js +2 -2
  48. package/src/ui/fx-group.js +14 -0
  49. package/src/ui/fx-items.js +10 -4
  50. package/src/ui/fx-repeat-attributes.js +111 -35
  51. package/src/ui/fx-repeat.js +364 -87
  52. package/src/ui/fx-repeat.updated.js +821 -0
  53. package/src/ui/fx-repeatitem.js +23 -20
  54. package/src/ui/fx-switch.js +5 -3
  55. package/src/ui/fx-upload.js +36 -40
  56. package/src/ui/repeat-base.js +532 -0
  57. package/src/withDraggability.js +8 -4
  58. package/src/xpath-evaluation.js +26 -8
  59. package/src/xpath-path.js +79 -0
  60. package/src/xpath-util.js +107 -11
  61. package/dist/fore-dev.js.map +0 -1
  62. package/dist/fore.js.map +0 -1
  63. package/src/ui/fx-select.js +0 -89
@@ -4,6 +4,7 @@ import getInScopeContext from '../getInScopeContext.js';
4
4
  import { Fore } from '../fore.js';
5
5
  import { FxFore } from '../fx-fore.js';
6
6
  import { XPathUtil } from '../xpath-util.js';
7
+ import { getDocPath } from '../xpath-path.js';
7
8
 
8
9
  /**
9
10
  * @param {number} howLong How long to wait, in ms
@@ -122,6 +123,7 @@ export class AbstractAction extends ForeElementMixin {
122
123
  disconnectedCallback() {}
123
124
 
124
125
  connectedCallback() {
126
+ super.connectedCallback();
125
127
  this.setAttribute('inert', 'true');
126
128
  this.style.display = 'none';
127
129
  this.propagate = this.hasAttribute('propagate') ? this.getAttribute('propagate') : 'continue';
@@ -176,6 +178,7 @@ export class AbstractAction extends ForeElementMixin {
176
178
  }
177
179
  } else {
178
180
  this.targetElement = this.parentNode;
181
+ if (!this.targetElement || this.targetElement.nodeType !== Node.ELEMENT_NODE) return;
179
182
  this.targetElement.addEventListener(this.event, e => this.execute(e), {
180
183
  capture: this.phase === 'capture',
181
184
  });
@@ -192,7 +195,7 @@ export class AbstractAction extends ForeElementMixin {
192
195
  await Fore.dispatch(this, 'error', {
193
196
  origin: this,
194
197
  message: 'Action execution failed',
195
- expr: XPathUtil.getDocPath(this),
198
+ expr: error,
196
199
  level: 'Error',
197
200
  });
198
201
  // Return false to indicate failure. Any loops must be canceled
@@ -212,30 +215,22 @@ export class AbstractAction extends ForeElementMixin {
212
215
  * @param e
213
216
  */
214
217
  async execute(e) {
215
- if(!this.getModel().modelConstructed) return;
218
+ if (!this.getModel().modelConstructed) return;
216
219
  // console.log(this, this.event);
217
- if(this.event){
218
- if(this.event === 'submit-done'){
219
- console.info(
220
- `%csubmit-done ${this.event} #${this?.parentNode?.id}`,
221
- 'background:lime; color:black; padding:.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;',
222
- );
223
- }else{
224
- console.info(
225
- `%cexecuting ${this.constructor.name} ${this.event}`,
226
- 'background:lime; color:black; padding:.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;',
227
- );
220
+ if (this.event) {
221
+ if (this.event === 'submit-done') {
222
+ console.info(`📌 ${this.event} #${this?.parentNode?.id}`);
223
+ } else {
224
+ console.info(`📌 ${this.constructor.name} ${this.event}`);
228
225
  }
229
-
230
- }else{
226
+ } else {
231
227
  console.info(
232
- `%cexecuting ${this.constructor.name}`,
233
- 'background:limegreen; color:black; margin-left:1rem; padding:.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;',
234
- this
228
+ `%cexecuting ${this.constructor.name}`,
229
+ 'background:limegreen; color:black; margin-left:1rem; padding:.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;',
230
+ this,
235
231
  );
236
232
  }
237
233
 
238
-
239
234
  if (e && e.target.nodeType !== Node.DOCUMENT_NODE && e.target !== window) {
240
235
  /*
241
236
  ### ignore event if there's a parent fore and the current element is NOT part of it. This avoids
@@ -260,7 +255,7 @@ export class AbstractAction extends ForeElementMixin {
260
255
  let resolveThisEvent = () => {};
261
256
  if (e && e.listenerPromises) {
262
257
  e.listenerPromises.push(
263
- new Promise((resolve) => {
258
+ new Promise(resolve => {
264
259
  resolveThisEvent = resolve;
265
260
  }),
266
261
  );
@@ -269,9 +264,9 @@ export class AbstractAction extends ForeElementMixin {
269
264
  // Outermost handling
270
265
  if (FxFore.outermostHandler === null) {
271
266
  console.log(
272
- `%coutermost Action on ${this.getOwnerForm().id}`,
273
- 'background:darkblue; color:white; padding:0.3rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;',
274
- this,
267
+ `%coutermost Action on ${this.getOwnerForm().id}`,
268
+ 'background:darkblue; color:white; padding:0.3rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;',
269
+ this,
275
270
  );
276
271
 
277
272
  FxFore.outermostHandler = this;
@@ -406,9 +401,9 @@ export class AbstractAction extends ForeElementMixin {
406
401
  this.actionPerformed();
407
402
  if (FxFore.outermostHandler === this) {
408
403
  console.log(
409
- `%cfinalizing outermost Action on ${this.getOwnerForm().id}`,
410
- 'background:darkblue; color:white; padding:0.3rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;',
411
- this,
404
+ `%cfinalizing outermost Action on ${this.getOwnerForm()?.id}`,
405
+ 'background:darkblue; color:white; padding:0.3rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;',
406
+ this,
412
407
  );
413
408
 
414
409
  FxFore.outermostHandler = null;
@@ -467,8 +462,8 @@ export class AbstractAction extends ForeElementMixin {
467
462
  return;
468
463
  }
469
464
  if (
470
- FxFore.outermostHandler
471
- && !XPathUtil.contains(FxFore.outermostHandler.ownerDocument, FxFore.outermostHandler)
465
+ FxFore.outermostHandler &&
466
+ !XPathUtil.contains(FxFore.outermostHandler.ownerDocument, FxFore.outermostHandler)
472
467
  ) {
473
468
  // The old outermostHandler fell out of the document. An error has happened.
474
469
  // Just remove the old one and act like we are starting anew.
@@ -480,7 +475,7 @@ export class AbstractAction extends ForeElementMixin {
480
475
  // console.log('running update cycle for outermostHandler', this);
481
476
  model.recalculate();
482
477
  model.revalidate();
483
- model.parentNode.refresh(true);
478
+ this.getOwnerForm().refresh(false);
484
479
  this.dispatchActionPerformed();
485
480
  } else if (this.needsUpdate) {
486
481
  // console.log('Update delayed!');
@@ -1,12 +1,11 @@
1
1
  import { AbstractAction } from './abstract-action.js';
2
2
  import { Fore } from '../fore.js';
3
3
  import { resolveId } from '../xpath-evaluation.js';
4
+ import { XPathUtil } from '../xpath-util.js';
4
5
 
5
6
  /**
6
7
  * `fx-append` appends an entry to a repeat.
7
8
  *
8
- *
9
- *
10
9
  * @deprecated - will be replaced with `fx-insert`
11
10
  * @fires index-changed - fired after new item is appended
12
11
  * @customElement
@@ -76,6 +75,8 @@ class FxAppend extends AbstractAction {
76
75
 
77
76
  actionPerformed() {
78
77
  super.actionPerformed();
78
+ const mi = this.getModelItem();
79
+ mi.notify();
79
80
  // const repeat = document.getElementById(this.repeat);
80
81
  // repeat.setIndex(repeat.nodeset.length);
81
82
  this._dispatch();
@@ -103,12 +104,34 @@ class FxAppend extends AbstractAction {
103
104
 
104
105
  // const rootNode = document.createElement(repeat.ref);
105
106
  // const rootNode = inscope.ownerDocument.createElement(repeat.ref);
107
+ /**
108
+ * @type {Element}
109
+ */
106
110
  const rootNode = inscope.ownerDocument.createElement(repeat.ref);
107
111
 
108
112
  // const data = this._dataFromRefs(rootNode, templ.content)
109
113
  const data = this._generateInstance(templ.content, rootNode);
110
114
  // console.log('_dataFromTemplate DATA', data);
111
115
  inscope.appendChild(data);
116
+ parentForm.signalChangeToElement(inscope.localName);
117
+ parentForm.signalChangeToElement(data.localName);
118
+
119
+ const instanceId = XPathUtil.resolveInstance(this, this.ref);
120
+ const inst = this.getModel().getInstance(instanceId);
121
+
122
+ const fore = this.getOwnerForm();
123
+
124
+ Fore.dispatch(inst, 'insert', {
125
+ insertedNodes: data,
126
+ insertedParent: inscope,
127
+ ref: this.ref,
128
+ location: data.previousSibling,
129
+ position: 'after',
130
+ instanceId: inst.id,
131
+ foreId: fore.id,
132
+ index: Array.from(inscope.children).indexOf(data),
133
+ });
134
+
112
135
  // console.log('appended new item ', data);
113
136
  // return data;
114
137
  }
@@ -3,7 +3,7 @@ import { AbstractAction } from './abstract-action.js';
3
3
  import { evaluateXPath } from '../xpath-evaluation.js';
4
4
  import { Fore } from '../fore.js';
5
5
  import getInScopeContext from '../getInScopeContext.js';
6
- import { XPathUtil } from '../xpath-util.js';
6
+ import { getDocPath } from '../xpath-path.js';
7
7
 
8
8
  /**
9
9
  * `fx-call`
@@ -67,7 +67,7 @@ export default class FxCall extends AbstractAction {
67
67
  Fore.dispatch(this, 'error', {
68
68
  origin: this,
69
69
  message: `Action '${this.action}' not found`,
70
- expr: XPathUtil.getDocPath(this),
70
+ expr: getDocPath(this),
71
71
  level: 'Error',
72
72
  });
73
73
  }
@@ -1,7 +1,6 @@
1
- import * as fx from 'fontoxpath';
2
1
  import { AbstractAction } from './abstract-action.js';
3
2
  import { Fore } from '../fore.js';
4
- import { evaluateXPathToNodes, evaluateXPathToString } from '../xpath-evaluation.js';
3
+ import { evaluateXPathToNodes } from '../xpath-evaluation.js';
5
4
  import { XPathUtil } from '../xpath-util.js';
6
5
  import getInScopeContext from '../getInScopeContext.js';
7
6
 
@@ -52,40 +51,78 @@ class FxDelete extends AbstractAction {
52
51
  }),
53
52
  );
54
53
 
54
+ const fore = this.getOwnerForm();
55
+
55
56
  let parent;
57
+
58
+ const removedNodes = [];
56
59
  if (Array.isArray(nodesToDelete)) {
57
- if (nodesToDelete.length === 0) return;
60
+ if (nodesToDelete.length === 0) {
61
+ return;
62
+ }
58
63
  parent = nodesToDelete[0].parentNode;
64
+
59
65
  nodesToDelete.forEach(item => {
60
- this._deleteNode(parent, item);
66
+ if (this._deleteNode(parent, item)) {
67
+ fore.signalChangeToElement(item.localName);
68
+ removedNodes.push(item);
69
+ }
61
70
  });
71
+ if (removedNodes.length) {
72
+ fore.signalChangeToElement(parent.localName);
73
+ }
62
74
  } else {
63
75
  parent = nodesToDelete.parentNode;
64
- this._deleteNode(parent, nodesToDelete);
76
+ if (this._deleteNode(parent, nodesToDelete)) {
77
+ fore.signalChangeToElement(parent.localName);
78
+
79
+ fore.signalChangeToElement(nodesToDelete.localName);
80
+ removedNodes.push(nodesToDelete);
81
+ }
82
+ }
83
+
84
+ if (removedNodes.length) {
85
+ await Fore.dispatch(instance, 'deleted', {
86
+ ref: path,
87
+ deletedNodes: removedNodes,
88
+ instanceId,
89
+ parent,
90
+ foreId: fore.id,
91
+ });
92
+ this.needsUpdate = true;
65
93
  }
66
- const foreId = this.getOwnerForm().id;
67
- await Fore.dispatch(instance, 'deleted', { ref: path, deletedNodes: nodesToDelete , instanceId:instanceId, foreId:foreId});
68
- this.needsUpdate = true;
69
94
  }
70
95
 
96
+ /**
97
+ * Delete a node (if allowed). Does not hold for JSON
98
+ *
99
+ * @param {ParentNode} parent - The parent of the node to remove
100
+ * @param {ChildNode} node - The child to remove
101
+ *
102
+ * @returns {boolean} Whether the delete is allowed and succeeded
103
+ */
71
104
  _deleteNode(parent, node) {
72
- if (parent.nodeType === Node.DOCUMENT_NODE) return;
73
- if (node.nodeType === Node.DOCUMENT_NODE) return;
74
- if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) return;
75
- if (node.parentNode === null) return;
105
+ if (
106
+ parent.nodeType === Node.DOCUMENT_NODE ||
107
+ node.nodeType === Node.DOCUMENT_NODE ||
108
+ node.nodeType === Node.DOCUMENT_FRAGMENT_NODE ||
109
+ node.parentNode === null
110
+ ) {
111
+ return false;
112
+ }
76
113
 
77
- const mi = this.getModelItem();
78
- if (mi.readonly) return;
114
+ const mi = this.getModel().getModelItem(node);
115
+ // Note that the model item can be absent, For elements that had no controls on them.
116
+ // In that case, allow removals
117
+ if (mi?.readonly) {
118
+ return false;
119
+ }
79
120
 
80
121
  parent.removeChild(node);
81
- }
82
122
 
83
- /**
84
- * overwriting as we need to perform additional rebuild()
85
- */
86
- actionPerformed() {
87
- this.getModel().rebuild();
88
- super.actionPerformed();
123
+ this.getModel().removeModelItem(node);
124
+
125
+ return true;
89
126
  }
90
127
  }
91
128
 
@@ -38,7 +38,7 @@ export class FxHide extends AbstractAction {
38
38
  );
39
39
 
40
40
  const dialog = resolveId(this.dialog, this);
41
- dialog.hide();
41
+ dialog.close();
42
42
  Fore.dispatch(dialog, 'dialog-hidden', {});
43
43
  }
44
44
  }
@@ -7,6 +7,7 @@ import {
7
7
  } from '../xpath-evaluation.js';
8
8
  import { XPathUtil } from '../xpath-util';
9
9
  import { Fore } from '../fore.js';
10
+ import { getPath } from '../xpath-path.js';
10
11
 
11
12
  /**
12
13
  * `fx-insert`
@@ -60,31 +61,6 @@ export class FxInsert extends AbstractAction {
60
61
  this.keepValues = !!this.hasAttribute('keep-values');
61
62
  }
62
63
 
63
- _getOriginSequence(inscope, targetSequence) {
64
- let originSequence;
65
- if (this.origin) {
66
- // ### if there's an origin attribute use it
67
- let originTarget;
68
- try {
69
- originTarget = evaluateXPathToFirstNode(this.origin, inscope, this);
70
- if (Array.isArray(originTarget) && originTarget.length === 0) {
71
- console.warn('invalid origin for this insert action - ignoring...', this);
72
- originSequence = null;
73
- }
74
- originSequence = originTarget;
75
- } catch (error) {
76
- console.warn('invalid origin for this insert action - ignoring...', this);
77
- }
78
- } else if (targetSequence) {
79
- // ### use last item of targetSequence
80
- originSequence = targetSequence;
81
- if (originSequence && !this.keepValues) {
82
- this._clear(originSequence);
83
- }
84
- }
85
- return originSequence;
86
- }
87
-
88
64
  _cloneOriginSequence(inscope, targetSequence) {
89
65
  let originSequenceClone;
90
66
  if (this.origin) {
@@ -96,13 +72,25 @@ export class FxInsert extends AbstractAction {
96
72
  */
97
73
  // this.setInScopeVariables(this.detail);
98
74
 
99
- // originTarget = evaluateXPathToFirstNode(this.origin, inscope, this);
100
- originTarget = evaluateXPathToFirstNode(this.origin, inscope, this);
101
- if (Array.isArray(originTarget) && originTarget.length === 0) {
102
- console.warn('invalid origin for this insert action - ignoring...', this);
103
- originSequenceClone = null;
75
+ /*
76
+ if in 'create-nodes' mode and origin targets a repeat, the repeat
77
+ we use the already during initData() created nodeset as a template for insertion
78
+ */
79
+ if (this.origin.startsWith('#') && this.getOwnerForm().createNodes) {
80
+ const repeat = this.getOwnerForm().querySelector(this.origin);
81
+ originSequenceClone = repeat.createdNodeset.cloneNode(true);
82
+ if (!originSequenceClone) {
83
+ console.error(`createdNodeset for repeat ${this.origin} does not exist`);
84
+ }
85
+ } else {
86
+ // originTarget = evaluateXPathToFirstNode(this.origin, inscope, this);
87
+ originTarget = evaluateXPathToFirstNode(this.origin, inscope, this);
88
+ if (Array.isArray(originTarget) && originTarget.length === 0) {
89
+ console.warn('invalid origin for this insert action - ignoring...', this);
90
+ originSequenceClone = null;
91
+ }
92
+ originSequenceClone = originTarget.cloneNode(true);
104
93
  }
105
- originSequenceClone = originTarget.cloneNode(true);
106
94
  } catch (error) {
107
95
  console.warn('invalid origin for this insert action - ignoring...', this);
108
96
  }
@@ -136,13 +124,11 @@ export class FxInsert extends AbstractAction {
136
124
  let targetSequence = [];
137
125
  const inscopeContext = getInScopeContext(this);
138
126
 
127
+ const fore = this.getOwnerForm();
128
+
139
129
  // ### 'context' attribute takes precedence over 'ref'
140
130
  if (this.hasAttribute('context')) {
141
- [context] = evaluateXPathToNodes(
142
- this.getAttribute('context'),
143
- inscopeContext,
144
- this.getOwnerForm(),
145
- );
131
+ [context] = evaluateXPathToNodes(this.getAttribute('context'), inscopeContext, this);
146
132
  inscope = inscopeContext;
147
133
  }
148
134
 
@@ -154,10 +140,18 @@ export class FxInsert extends AbstractAction {
154
140
  targetSequence = evaluateXPathToNodes(this.ref, inscope, this);
155
141
  }
156
142
  }
143
+ // const originSequenceClone = this._cloneOriginSequence(inscope, targetSequence);
144
+
157
145
  const originSequenceClone = this._cloneOriginSequence(inscope, targetSequence);
158
146
  if (!originSequenceClone) return; // if no origin back out without effect
159
147
 
148
+ /**
149
+ * @type {Node}
150
+ */
160
151
  let insertLocationNode;
152
+ /**
153
+ * @type {number}
154
+ */
161
155
  let index;
162
156
 
163
157
  // if the targetSequence is empty but we got an originSequence use inscope as context and ignore 'at' and 'position'
@@ -165,12 +159,22 @@ export class FxInsert extends AbstractAction {
165
159
  if (context) {
166
160
  insertLocationNode = context;
167
161
  context.appendChild(originSequenceClone);
162
+ fore.signalChangeToElement(insertLocationNode.localName);
163
+ fore.signalChangeToElement(originSequenceClone.localName);
168
164
  index = 1;
169
165
  } else {
170
- // No context. We can insert into the `inscope`.
171
- insertLocationNode = inscope;
172
- inscope.appendChild(originSequenceClone);
173
- index = 1;
166
+ // No context but creating nodes from UI
167
+ if (!inscope && this.getOwnerForm().createNodes) {
168
+ const repeat = this.getOwnerForm().querySelector(this.origin);
169
+ inscope = getInScopeContext(repeat, repeat.ref);
170
+ insertLocationNode = inscope;
171
+ inscope.appendChild(originSequenceClone);
172
+ index = inscope.length - 1;
173
+ } else {
174
+ insertLocationNode = inscope;
175
+ inscope.appendChild(originSequenceClone);
176
+ index = 1;
177
+ }
174
178
  }
175
179
  } else {
176
180
  /* ### insert at position given by 'at' or use the last item in the targetSequence ### */
@@ -205,6 +209,8 @@ export class FxInsert extends AbstractAction {
205
209
  if (this.position && this.position === 'before') {
206
210
  // this.at -= 1;
207
211
  insertLocationNode.parentNode.insertBefore(originSequenceClone, insertLocationNode);
212
+ fore.signalChangeToElement(insertLocationNode.parentNode);
213
+ fore.signalChangeToElement(originSequenceClone.localName);
208
214
  }
209
215
 
210
216
  if (this.position && this.position === 'after') {
@@ -214,11 +220,17 @@ export class FxInsert extends AbstractAction {
214
220
  if (this.hasAttribute('context') && this.hasAttribute('ref')) {
215
221
  // index=1;
216
222
  inscope.append(originSequenceClone);
223
+ fore.signalChangeToElement(insertLocationNode);
224
+ fore.signalChangeToElement(originSequenceClone.localName);
217
225
  } else if (this.hasAttribute('context')) {
218
226
  index = 1;
219
227
  insertLocationNode.prepend(originSequenceClone);
228
+ fore.signalChangeToElement(insertLocationNode);
229
+ fore.signalChangeToElement(originSequenceClone.localName);
220
230
  } else {
221
231
  insertLocationNode.insertAdjacentElement('afterend', originSequenceClone);
232
+ fore.signalChangeToElement(insertLocationNode);
233
+ fore.signalChangeToElement(originSequenceClone.localName);
222
234
  }
223
235
  }
224
236
  }
@@ -235,7 +247,7 @@ export class FxInsert extends AbstractAction {
235
247
  // console.log('<<<<<<< resolved instance', inst);
236
248
  // Note: the parent to insert under is always the parent of the inserted node. The 'context' is not always the parent if the sequence is empty, or the position is different
237
249
  // const xpath = XPathUtil.getPath(originSequenceClone.parentNode, instanceId);
238
- const xpath = XPathUtil.getPath(insertLocationNode.parentNode, instanceId);
250
+ const xpath = getPath(insertLocationNode, instanceId);
239
251
 
240
252
  const path = Fore.getDomNodeIndexString(originSequenceClone);
241
253
  this.dispatchEvent(
@@ -246,13 +258,16 @@ export class FxInsert extends AbstractAction {
246
258
  detail: { action: this, event: this.event, path },
247
259
  }),
248
260
  );
249
-
250
261
  Fore.dispatch(inst, 'insert', {
251
- 'inserted-nodes': originSequenceClone,
252
- 'insert-location-node': insertLocationNode,
262
+ insertedNodes: originSequenceClone,
263
+ insertedParent: insertLocationNode.parentNode,
264
+ ref: this.ref,
265
+ location: insertLocationNode,
253
266
  position: this.position,
254
- instanceId:instanceId,
255
- foreId:this.getOwnerForm().id
267
+ instanceId,
268
+ foreId: fore.id,
269
+ index,
270
+ xpath,
256
271
  });
257
272
 
258
273
  // todo: this actually should dispatch to respective instance
@@ -269,7 +284,6 @@ export class FxInsert extends AbstractAction {
269
284
  );
270
285
 
271
286
  this.needsUpdate = true;
272
- console.log('Changed!', xpath);
273
287
  return [xpath];
274
288
  }
275
289
 
@@ -286,7 +300,7 @@ export class FxInsert extends AbstractAction {
286
300
 
287
301
  actionPerformed(changedPaths) {
288
302
  // ### make sure the necessary modelItems will get created
289
- this.getModel().rebuild();
303
+ // this.getModel().rebuild();
290
304
  super.actionPerformed();
291
305
  }
292
306
 
@@ -27,12 +27,13 @@ class FxLoad extends AbstractAction {
27
27
  super();
28
28
  this.attachShadow({ mode: 'open' });
29
29
  this.url = '';
30
+ this.replace=false;
30
31
  }
31
32
 
32
33
  connectedCallback() {
33
34
  super.connectedCallback();
34
35
  this.attachTo = this.hasAttribute('attach-to') ? this.getAttribute('attach-to') : '_self';
35
-
36
+ this.replace = this.hasAttribute('replace') ? true : false;
36
37
  // Add a 'doneEvent' to block the action chain untill the event fired on the element we're
37
38
  // loading something into.
38
39
  this.awaitEvent = this.hasAttribute('await') ? this.getAttribute('await') : '';
@@ -88,7 +89,11 @@ class FxLoad extends AbstractAction {
88
89
  resolved.removeEventListener(this.awaitEvent, eventListener);
89
90
  };
90
91
 
91
- resolved.appendChild(content);
92
+ if(this.replace){
93
+ this.parentNode.replaceChild(content,resolved);
94
+ }else{
95
+ resolved.appendChild(content);
96
+ }
92
97
  resolved.addEventListener(this.awaitEvent, eventListener);
93
98
 
94
99
  await waitForEvent;
@@ -22,23 +22,33 @@ class FxRefresh extends AbstractAction {
22
22
 
23
23
  if (this.hasAttribute('self')) {
24
24
  console.log(`### <<<<< refresh() self ${this} >>>>>`);
25
- const control = XPathUtil.getClosest('fx-control', this);
25
+ const control = XPathUtil.getClosest('fx-control, fx-output, fx-upload', this);
26
26
  if (control) {
27
- control.refresh();
27
+ control.refresh(true);
28
28
  return;
29
29
  }
30
30
  }
31
31
  if (this.hasAttribute('force')) {
32
32
  console.log(`### <<<<< refresh() force ${this} >>>>>`);
33
- this.getOwnerForm().forceRefresh();
33
+ this.getOwnerForm().refresh(true);
34
+ return;
35
+ }
36
+ if(this.hasAttribute('selector')){
37
+ const target = document.querySelector(this.getAttribute('selector'));
38
+ if (target && Fore.isUiElement(target.nodeName) && typeof target.refresh === 'function') {
39
+ target.refresh(true);
40
+ }
34
41
  return;
35
42
  }
36
43
  if (this.hasAttribute('control')) {
37
44
  const targetId = this.getAttribute('control');
38
45
  console.log(`### <<<<< refresh() control '${targetId}' >>>>>`);
39
- const ctrl = resolveId(targetId, this);
46
+ let ctrl = resolveId(targetId, this);
47
+ if(!ctrl){
48
+ ctrl = document.querySelector(`#${targetId}`);
49
+ }
40
50
  if (ctrl && Fore.isUiElement(ctrl.nodeName) && typeof ctrl.refresh === 'function') {
41
- ctrl.refresh();
51
+ ctrl.refresh(true);
42
52
  }
43
53
  return;
44
54
  }
@@ -2,6 +2,7 @@
2
2
  import '../fx-model.js';
3
3
  import { AbstractAction } from './abstract-action.js';
4
4
  import { evaluateXPathToFirstNode } from '../xpath-evaluation.js';
5
+ import { FxBind } from '../fx-bind.js';
5
6
 
6
7
  /**
7
8
  * `fx-replace` - replaces the node referred to with 'ref' with node referred to with 'with' attribute.
@@ -44,8 +45,9 @@ export default class FxReplace extends AbstractAction {
44
45
  }
45
46
 
46
47
  actionPerformed() {
47
- this.getModel().rebuild();
48
- super.actionPerformed();
48
+ this.getModel().updateModel();
49
+ this.getOwnerForm().refresh(true); //todo: optimize and update only the affected subtree
50
+ this.dispatchActionPerformed();
49
51
  }
50
52
 
51
53
  replace(toReplace, replaceWith) {
@@ -63,7 +65,20 @@ export default class FxReplace extends AbstractAction {
63
65
  const cloned = replaceWith.cloneNode(true);
64
66
  toReplace.replaceWith(cloned);
65
67
  }
66
- // const modelitem = this.getModelItem();
68
+ // todo: add selective update and create and notify modelItem for toReplace and replaceWith
69
+ /*
70
+ let replaceWithModelItem = this.getModel().getModelItem(replaceWith);
71
+ if (!replaceWithModelItem) {
72
+ const replaceWithModelItem = FxBind.createModelItem(
73
+ this.getAttribute('with'),
74
+ replaceWith,
75
+ this,
76
+ null,
77
+ );
78
+ this.getModel().registerModelItem(replaceWithModelItem);
79
+ this.getOwnerForm().addToBatchedNotifications(replaceWithModelItem);
80
+ }
81
+ */
67
82
  // this.getModel().changed.push(modelitem);
68
83
  this.needsUpdate = true;
69
84
  }
@@ -41,6 +41,12 @@ export class FxReset extends AbstractAction {
41
41
  data.reset();
42
42
  this.needsUpdate = true;
43
43
  }
44
+
45
+ actionPerformed() {
46
+ this.getModel().updateModel();
47
+ this.getOwnerForm().refresh(true);
48
+ this.dispatchActionPerformed();
49
+ }
44
50
  }
45
51
 
46
52
  if (!customElements.get('fx-reset')) {