@jinntec/fore 2.6.0 → 2.7.1

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 (53) hide show
  1. package/dist/fore-dev.js +4472 -2645
  2. package/dist/fore.js +4417 -2622
  3. package/index.js +2 -0
  4. package/package.json +10 -4
  5. package/resources/fore.css +2 -6
  6. package/src/DependencyNotifyingDomFacade.js +27 -21
  7. package/src/ForeElementMixin.js +282 -277
  8. package/src/actions/abstract-action.js +7 -12
  9. package/src/actions/fx-append.js +23 -2
  10. package/src/actions/fx-call.js +2 -2
  11. package/src/actions/fx-delete.js +54 -29
  12. package/src/actions/fx-insert.js +23 -15
  13. package/src/actions/fx-load.js +2 -2
  14. package/src/actions/fx-refresh.js +15 -5
  15. package/src/actions/fx-replace.js +18 -3
  16. package/src/actions/fx-reset.js +6 -0
  17. package/src/actions/fx-send.js +10 -7
  18. package/src/actions/fx-setattribute.js +12 -0
  19. package/src/actions/fx-setfocus.js +11 -8
  20. package/src/actions/fx-setvalue.js +93 -93
  21. package/src/actions/fx-toggle.js +1 -1
  22. package/src/extract-predicate-deps.js +57 -0
  23. package/src/extractPredicateDependencies.js +36 -0
  24. package/src/fore.js +41 -16
  25. package/src/fx-bind.js +128 -23
  26. package/src/fx-connection.js +24 -7
  27. package/src/fx-fore.js +506 -259
  28. package/src/fx-instance.js +9 -11
  29. package/src/fx-model.js +154 -65
  30. package/src/fx-submission.js +19 -30
  31. package/src/fx-var.js +5 -0
  32. package/src/getInScopeContext.js +7 -8
  33. package/src/modelitem.js +247 -112
  34. package/src/tools/fx-action-log.js +21 -19
  35. package/src/tools/fx-log-settings.js +4 -2
  36. package/src/ui/TemplateExpression.js +12 -0
  37. package/src/ui/UIElement.js +125 -10
  38. package/src/ui/abstract-control.js +5 -0
  39. package/src/ui/fx-case.js +15 -3
  40. package/src/ui/fx-container.js +5 -0
  41. package/src/ui/fx-control-menu.js +23 -14
  42. package/src/ui/fx-control.js +55 -15
  43. package/src/ui/fx-items.js +10 -4
  44. package/src/ui/fx-repeat-attributes.js +111 -35
  45. package/src/ui/fx-repeat.js +332 -85
  46. package/src/ui/fx-repeatitem.js +23 -20
  47. package/src/ui/fx-switch.js +5 -3
  48. package/src/ui/fx-upload.js +36 -40
  49. package/src/ui/repeat-base.js +532 -0
  50. package/src/withDraggability.js +8 -4
  51. package/src/xpath-evaluation.js +26 -8
  52. package/src/xpath-path.js +79 -0
  53. package/src/xpath-util.js +357 -289
@@ -8,7 +8,7 @@ async function handleResponse(fxInstance, response) {
8
8
  alert(`response status: ${status} - failed to load data for '${fxInstance.src}' - stopping.`);
9
9
  throw new Error(`failed to load data - status: ${status}`);
10
10
  }
11
- const responseContentType = response.headers.get('content-type').toLowerCase();
11
+ let responseContentType = response.headers.get('content-type').split(';')[0].trim().toLowerCase();
12
12
  // console.log('********** responseContentType *********', responseContentType);
13
13
  if (responseContentType.startsWith('text/html')) {
14
14
  // const htmlResponse = response.text();
@@ -19,23 +19,21 @@ async function handleResponse(fxInstance, response) {
19
19
  new DOMParser().parseFromString(result, 'text/html'),
20
20
  );
21
21
  }
22
- if (responseContentType.startsWith('text/')) {
23
- // console.log("********** inside res plain *********");
24
- return response.text();
25
- }
26
- if (responseContentType.startsWith('application/json')) {
22
+ if (responseContentType.endsWith('/json') || responseContentType.endsWith('+json')) {
27
23
  // console.log("********** inside res json *********");
28
24
  return response.json();
29
25
  }
30
- if (
31
- responseContentType.startsWith('application/xml') ||
32
- responseContentType.startsWith('text/xml')
33
- ) {
26
+ if (responseContentType.endsWith('/xml') || responseContentType.endsWith('+xml')) {
34
27
  // See https://www.rfc-editor.org/rfc/rfc7303
35
28
  const text = await response.text();
36
29
  // console.log('xml ********', result);
37
30
  return new DOMParser().parseFromString(text, 'application/xml');
38
31
  }
32
+ if (responseContentType.startsWith('text/')) {
33
+ // console.log("********** inside res plain *********");
34
+ return response.text();
35
+ }
36
+
39
37
  throw new Error(`unable to handle response content type: ${responseContentType}`);
40
38
  }
41
39
 
@@ -210,7 +208,7 @@ export class FxInstance extends HTMLElement {
210
208
  }
211
209
  if (this.type === 'json') {
212
210
  this.instanceData = {};
213
- this.originalInstance = [...this.instanceData];
211
+ this.originalInstance = { ...this.instanceData };
214
212
  }
215
213
  if (this.type === 'text') {
216
214
  this.instanceData = this.innerText;
package/src/fx-model.js CHANGED
@@ -2,14 +2,15 @@ import { DepGraph } from './dep_graph.js';
2
2
  import { Fore } from './fore.js';
3
3
  import './fx-instance.js';
4
4
  import { ModelItem } from './modelitem.js';
5
- import { evaluateXPath, evaluateXPathToBoolean } from './xpath-evaluation.js';
5
+ import { getPath } from './xpath-path.js';
6
+ import { evaluateXPath, evaluateXPathToBoolean, evaluateXPathToNodes } from './xpath-evaluation.js';
6
7
  import { XPathUtil } from './xpath-util.js';
7
8
 
8
9
  /**
9
- * The model of this Fore scope. It holds all the intances, binding, submissions and custom functions that
10
- * as required.
10
+ * The model of this Fore scope. It holds all the intances, binding, submissions and custom
11
+ * functions that as required.
11
12
  *
12
- * The model is updatin by executing rebuild (as needed), recalculate and revalidate in turn.
13
+ * The model is updated by executing rebuild (as needed), recalculate and revalidate in turn.
13
14
  *
14
15
  * After the cycle is run all modelItems have updated their stete to reflect latest computations.
15
16
  *
@@ -38,8 +39,16 @@ export class FxModel extends HTMLElement {
38
39
  this.attachShadow({ mode: 'open' });
39
40
  this.computes = 0;
40
41
  this.fore = {};
42
+
43
+ /**
44
+ * @type {import('./fx-bind.js').FxBind[]}
45
+ */
46
+ this.binds = [];
41
47
  }
42
48
 
49
+ /**
50
+ * @returns {import('./fx-fore.js').FxFore}
51
+ */
43
52
  get formElement() {
44
53
  return this.parentElement;
45
54
  }
@@ -66,67 +75,98 @@ export class FxModel extends HTMLElement {
66
75
  }
67
76
 
68
77
  /**
78
+ * Get the correct fx-bind for this element. Assumes the refs of all binds are always downwards.
79
+ *
80
+ * @param {ChildNode | Attr} elementOrAttribute - the element or attribute to resolve
81
+ *
82
+ * @returns {import('./fx-bind.js').FxBind | null}
83
+ */
84
+ getBindForElement(elementOrAttribute) {
85
+ if (typeof elementOrAttribute !== 'object' || !('nodeType' in elementOrAttribute)) {
86
+ // We only do binds over nodes. Not JSON.
87
+ return null;
88
+ }
89
+ /**
90
+ * @type {import('./fx-bind.js').FxBind | FxModel}
91
+ */
92
+ let bindForParent;
93
+ const parent =
94
+ elementOrAttribute.nodeType === elementOrAttribute.ATTRIBUTE_NODE
95
+ ? elementOrAttribute.ownerElement
96
+ : elementOrAttribute.parentNode;
97
+ if (!parent?.parentElement) {
98
+ // The root. Search from here
99
+ bindForParent = this;
100
+ } else {
101
+ bindForParent = this.getBindForElement(parent);
102
+ }
103
+ if (!bindForParent) {
104
+ return null;
105
+ }
106
+
107
+ /**
108
+ * @type {import('./fx-bind.js').FxBind[]}
109
+ */
110
+ const childBinds = Array.from(bindForParent.children).filter(c => c.nodeName === 'FX-BIND');
111
+ for (const childBind of childBinds) {
112
+ const ref = childBind.ref;
113
+ const matches = evaluateXPathToNodes(ref, parent, childBind);
114
+ if (matches.includes(elementOrAttribute)) {
115
+ return childBind;
116
+ }
117
+ }
118
+ return null;
119
+ }
120
+
121
+ /**
122
+ * Lazily create a ModelItem for nodes not explicitly bound via fx-bind
69
123
  * @param {FxModel} model The model to create a model item for
70
124
  * @param {string} ref The XPath ref that led to this model item
71
125
  * @param {Node} node The node the XPath led to
72
- * @param {ForeElementMixin} formElement The form element making this model. Used to resolve variables against
126
+ * @param {ForeElementMixin)} formElement The form element making this model. Used to resolve variables against
127
+ * @returns {ModelItem}
73
128
  */
74
129
  static lazyCreateModelItem(model, ref, node, formElement) {
75
- // console.log('lazyCreateModelItem ', node);
76
130
  const instanceId = XPathUtil.resolveInstance(formElement, ref);
131
+ const fore = model.formElement;
77
132
 
78
- if (model.parentNode.createNodes && (node === null || node === undefined)) {
79
- // ### intializing ModelItem with default values (as there is no <fx-bind> matching for given ref)
80
- const mi = new ModelItem(
81
- undefined,
82
- ref,
83
- Fore.READONLY_DEFAULT,
84
- false,
85
- Fore.REQUIRED_DEFAULT,
86
- Fore.CONSTRAINT_DEFAULT,
87
- Fore.TYPE_DEFAULT,
88
- null,
89
- this,
90
- instanceId,
91
- );
92
-
93
- // console.log('new ModelItem is instanceof ModelItem ', mi instanceof ModelItem);
133
+ if (fore?.createNodes && (node === null || node === undefined)) {
134
+ const mi = new ModelItem(undefined, ref, null, null, instanceId, fore);
135
+ mi.isSynthetic = true;
94
136
  model.registerModelItem(mi);
95
137
  return mi;
96
138
  }
97
- let targetNode = {};
139
+
98
140
  if (node === null || node === undefined) return null;
99
- if (node.nodeType === Node.TEXT_NODE) {
100
- // const parent = node.parentNode;
101
- // console.log('PARENT ', parent);
102
- targetNode = node.parentNode;
103
- } else {
104
- targetNode = node;
141
+
142
+ let targetNode = node.nodeType === Node.TEXT_NODE ? node.parentNode : node;
143
+
144
+ let path = null;
145
+ if (targetNode?.nodeType) {
146
+ path = getPath(targetNode, instanceId);
105
147
  }
106
148
 
107
- // const path = fx.evaluateXPath('path()',node);
108
- let path;
109
- if (node.nodeType) {
110
- path = XPathUtil.getPath(node, instanceId);
111
- } else {
112
- path = null;
113
- targetNode = node;
149
+ // Check if a ModelItem with the same path already exists
150
+ if (path) {
151
+ const existingModelItem = model.modelItems.find(mi => mi.path === path);
152
+ if (existingModelItem) {
153
+ // Update the node reference if needed
154
+ if (existingModelItem.node !== targetNode) {
155
+ existingModelItem.node = targetNode;
156
+ }
157
+ return existingModelItem;
158
+ }
114
159
  }
115
- // const path = XPathUtil.getPath(node);
116
160
 
117
- // ### intializing ModelItem with default values (as there is no <fx-bind> matching for given ref)
118
161
  const mi = new ModelItem(
119
162
  path,
120
163
  ref,
121
- Fore.READONLY_DEFAULT,
122
- Fore.RELEVANT_DEFAULT,
123
- Fore.REQUIRED_DEFAULT,
124
- Fore.CONSTRAINT_DEFAULT,
125
- Fore.TYPE_DEFAULT,
126
164
  targetNode,
127
- this,
165
+ model.getBindForElement(targetNode),
128
166
  instanceId,
167
+ fore,
129
168
  );
169
+ mi.isSynthetic = true;
130
170
 
131
171
  // console.log('new ModelItem is instanceof ModelItem ', mi instanceof ModelItem);
132
172
  model.registerModelItem(mi);
@@ -143,10 +183,7 @@ export class FxModel extends HTMLElement {
143
183
  *
144
184
  */
145
185
  async modelConstruct() {
146
- console.info(
147
- `%cdispatching model-construct for #${this.parentNode.id}`,
148
- 'background:lightblue; color:black; padding:.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;',
149
- );
186
+ console.info(`📌 model-construct for #${this.parentNode.id}`);
150
187
 
151
188
  // this.dispatchEvent(new CustomEvent('model-construct', { detail: this }));
152
189
  Fore.dispatch(this, 'model-construct', { model: this });
@@ -211,8 +248,25 @@ export class FxModel extends HTMLElement {
211
248
  // console.timeEnd('updateModel');
212
249
  }
213
250
 
251
+ /**
252
+ * (Recursively) remove the model item of a node.
253
+ * @param {Node} node - The node for which to remove the model item
254
+ */
255
+ removeModelItem(node) {
256
+ const index = this.modelItems.findIndex(mi => mi.node === node);
257
+ // The model item is not always there. Might be the case if a node is 'skipped' during rendering. All paths jump over it.
258
+ // It may still have descendants that can have model items
259
+ if (index !== -1) {
260
+ this.modelItems.splice(index, 1);
261
+ }
262
+
263
+ for (const child of Array.from(node.childNodes)) {
264
+ this.removeModelItem(child);
265
+ }
266
+ }
267
+
214
268
  rebuild() {
215
- // console.log(`### <<<<< rebuild() '${this.fore.id}' >>>>>`);
269
+ console.log(`🔷 rebuild() '${this.fore.id}'`);
216
270
 
217
271
  this.mainGraph = new DepGraph(false); // do: should be moved down below binds.length check but causes errors in tests.
218
272
  this.modelItems = [];
@@ -229,12 +283,18 @@ export class FxModel extends HTMLElement {
229
283
  bind.init(this);
230
284
  });
231
285
 
286
+ if (this.formElement.createNodes) {
287
+ // initData should be running here as well: we just got a whole new instance that may be
288
+ // incomplete
289
+ this.formElement.initData();
290
+ }
291
+
232
292
  console.log('mainGraph', this.mainGraph);
233
293
  console.log('rebuild mainGraph calc order', this.mainGraph.overallOrder());
234
294
 
235
295
  // this.dispatchEvent(new CustomEvent('rebuild-done', {detail: {maingraph: this.mainGraph}}));
236
296
  Fore.dispatch(this, 'rebuild-done', { maingraph: this.mainGraph });
237
- console.log('mainGraph', this.mainGraph);
297
+ // console.log('mainGraph', this.mainGraph);
238
298
  }
239
299
 
240
300
  /**
@@ -242,12 +302,12 @@ export class FxModel extends HTMLElement {
242
302
  *
243
303
  * todo: use 'changed' flag on modelItems to determine subgraph for recalculation. Flag already exists but is not used.
244
304
  */
245
- recalculate() {
305
+ async recalculate() {
246
306
  if (!this.mainGraph) {
247
307
  return;
248
308
  }
249
309
 
250
- // console.log(`### <<<<< recalculate() '${this.fore.id}' >>>>>`);
310
+ console.log(`🔷🔷 recalculate() '${this.fore.id}'`);
251
311
 
252
312
  // console.log('changed nodes ', this.changed);
253
313
  this.computes = 0;
@@ -295,8 +355,10 @@ export class FxModel extends HTMLElement {
295
355
  this.compute(node, path);
296
356
  }
297
357
  });
358
+ /*
298
359
  const toRefresh = [...this.changed];
299
360
  this.formElement.toRefresh = toRefresh;
361
+ */
300
362
  this.changed = [];
301
363
  Fore.dispatch(this, 'recalculate-done', { graph: this.subgraph, computes: this.computes });
302
364
  } else {
@@ -374,11 +436,24 @@ export class FxModel extends HTMLElement {
374
436
  const compute = evaluateXPath(expr, modelItem.node, this);
375
437
  modelItem.value = compute;
376
438
  modelItem.readonly = true; // calculated nodes are always readonly
439
+ modelItem.notify(); // Notify observers directly
377
440
  } else if (property !== 'constraint' && property !== 'type') {
441
+ /*
442
+ console.log(
443
+ 'recalculating path ',
444
+ path,
445
+ ' Expr:',
446
+ expr,
447
+ 'modelitem value',
448
+ modelItem.node.textContent,
449
+ );
450
+ */
378
451
  // ### re-compute the Boolean value of all facets expect 'constraint' and 'type' which are handled in revalidate()
379
452
  if (expr) {
380
453
  const compute = evaluateXPathToBoolean(expr, modelItem.node, this);
381
454
  modelItem[property] = compute;
455
+ // modelItem.notify(); // Notify observers directly
456
+ this.fore.addToBatchedNotifications(modelItem);
382
457
  /*
383
458
  console.log(
384
459
  `recalculating path ${path} - Expr:'${expr}' computed`,
@@ -411,7 +486,7 @@ export class FxModel extends HTMLElement {
411
486
  revalidate() {
412
487
  if (this.modelItems.length === 0) return true;
413
488
 
414
- // console.log(`### <<<<< revalidate() '${this.fore.id}' >>>>>`);
489
+ console.log(`🔷🔷🔷 revalidate() '${this.fore.id}'`);
415
490
 
416
491
  // reset submission validation
417
492
  // this.parentNode.classList.remove('submit-validation-failed')
@@ -430,7 +505,8 @@ export class FxModel extends HTMLElement {
430
505
  const compute = evaluateXPathToBoolean(constraint, modelItem.node, this);
431
506
  // console.log('modelItem validity computed: ', compute);
432
507
  modelItem.constraint = compute;
433
- this.formElement.addToRefresh(modelItem); // let fore know that modelItem needs refresh
508
+ // this.formElement.addToRefresh(modelItem); // let fore know that modelItem needs refresh
509
+ modelItem.notify(); // Notify observers directly
434
510
  if (!compute) {
435
511
  console.log('validation failed on modelitem ', modelItem);
436
512
  valid = false;
@@ -443,8 +519,9 @@ export class FxModel extends HTMLElement {
443
519
  const compute = evaluateXPathToBoolean(required, modelItem.node, this);
444
520
  // console.log('modelItem required computed: ', compute);
445
521
  modelItem.required = compute;
446
- this.formElement.addToRefresh(modelItem); // let fore know that modelItem needs refresh
447
- if (!modelItem.node.textContent) {
522
+ // this.formElement.addToRefresh(modelItem); // let fore know that modelItem needs refresh
523
+ modelItem.notify(); // Notify observers directly
524
+ if (modelItem.required && !modelItem.node.textContent) {
448
525
  /*
449
526
  console.log(
450
527
  'node is required but has no value ',
@@ -468,6 +545,15 @@ export class FxModel extends HTMLElement {
468
545
  }
469
546
  });
470
547
  console.log('modelItems after revalidate: ', this.modelItems);
548
+ console.log('changed after revalidate: ', this.changed);
549
+ console.log(
550
+ 'changed after revalidate changed: ',
551
+ Array.from(this.parentNode._localNamesWithChanges),
552
+ );
553
+ console.log(
554
+ 'changed after revalidate batchedNotifications: ',
555
+ Array.from(this.parentNode.batchedNotifications),
556
+ );
471
557
  return valid;
472
558
  }
473
559
 
@@ -478,12 +564,12 @@ export class FxModel extends HTMLElement {
478
564
  }
479
565
 
480
566
  /**
481
- *
482
- * @param {Node} node
483
- * @returns {ModelItem}
567
+ * Find a ModelItem by exact node or path
568
+ * @param {Node|string} nodeOrPath
569
+ * @returns {ModelItem|null}
484
570
  */
485
- getModelItem(node) {
486
- return this.modelItems.find(m => m.node === node);
571
+ getModelItem(nodeOrPath) {
572
+ return this.modelItems.find(mi => mi.node === nodeOrPath || mi.path === nodeOrPath) || null;
487
573
  }
488
574
 
489
575
  /**
@@ -513,6 +599,9 @@ export class FxModel extends HTMLElement {
513
599
  return this.instances[0].getInstanceData();
514
600
  }
515
601
 
602
+ /**
603
+ * @returns {import('./fx-instance.js').FxInstance}
604
+ */
516
605
  getInstance(id) {
517
606
  // console.log('getInstance ', id);
518
607
  // console.log('instances ', this.instances);
@@ -526,10 +615,6 @@ export class FxModel extends HTMLElement {
526
615
  if (!found) {
527
616
  const instArray = Array.from(this.instances);
528
617
  found = instArray.find(inst => inst.id === id);
529
- const parentFore =
530
- this.fore.parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE
531
- ? this.fore.parentNode.host.closest('fx-fore')
532
- : this.fore.parentNode.closest('fx-fore');
533
618
  }
534
619
  // ### lookup in parent Fore if present
535
620
  if (!found) {
@@ -545,6 +630,10 @@ export class FxModel extends HTMLElement {
545
630
  found = shared.find(found => found.id === id);
546
631
  }
547
632
  }
633
+ // search for shared instances in the whole document
634
+ if (!found) {
635
+ found = document.querySelector(`fx-instance[id="${id}"][shared]`);
636
+ }
548
637
  if (found) {
549
638
  return found;
550
639
  }
@@ -83,10 +83,7 @@ export class FxSubmission extends ForeElementMixin {
83
83
  }
84
84
 
85
85
  async _submit() {
86
- console.info(
87
- `%csubmitting #${this.id}`,
88
- 'background:yellow; color:black; padding:.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;',
89
- );
86
+ console.info(`🚀 #${this.id}`);
90
87
 
91
88
  this.evalInContext();
92
89
  const model = this.getModel();
@@ -166,8 +163,12 @@ export class FxSubmission extends ForeElementMixin {
166
163
 
167
164
  // if (resolvedUrl === '#echo') {
168
165
  if (resolvedUrl.startsWith('#echo')) {
169
- const data = this._parse(serialized, instance);
170
- this._handleResponse(data);
166
+ if (this.replace === 'download') {
167
+ this._handleResponse(serialized, resolvedUrl, 'application/xml');
168
+ } else {
169
+ const data = this._parse(serialized, instance);
170
+ this._handleResponse(data, resolvedUrl, 'application/xml');
171
+ }
171
172
  // this.dispatch('submit-done', {});
172
173
  console.log('### <<<<< submit-done >>>>>');
173
174
  Fore.dispatch(this, 'submit-done', {});
@@ -249,14 +250,18 @@ export class FxSubmission extends ForeElementMixin {
249
250
  return;
250
251
  }
251
252
 
252
- const contentType = response.headers.get('content-type').toLowerCase();
253
+ const contentType = response.headers
254
+ .get('content-type')
255
+ .split(';')[0]
256
+ .trim()
257
+ .toLowerCase();
253
258
  if (contentType.startsWith('text/')) {
254
259
  const text = await response.text();
255
260
  this._handleResponse(text, resolvedUrl, contentType);
256
- } else if (contentType.startsWith('application/json')) {
261
+ } else if (contentType.endsWith('/json') || contentType.endsWith('+json')) {
257
262
  const json = await response.json();
258
263
  this._handleResponse(json, resolvedUrl, contentType);
259
- } else if (contentType.startsWith('application/xml')) {
264
+ } else if (contentType.endsWith('/xml') || contentType.endsWith('+xml')) {
260
265
  const text = await response.text();
261
266
  const xml = new DOMParser().parseFromString(text, 'application/xml');
262
267
  this._handleResponse(xml, resolvedUrl, contentType);
@@ -375,26 +380,6 @@ export class FxSubmission extends ForeElementMixin {
375
380
 
376
381
  const targetInstance = this._getTargetInstance();
377
382
 
378
- /*
379
- if(this.replace === 'merge'){
380
- if(targetInstance.type !== 'xml') {
381
- Fore.dispatch(this, "warn", {'message': 'merging of instances only work for type xml'});
382
- }
383
- if (targetInstance && targetInstance.type === 'xml') {
384
- targetInstance.partialInstance = data;
385
- // const resultDoc = new DOMParser(`${data.nodeName}`, 'application/xml');
386
- // console.log('resultDoc', resultDoc)
387
- const merged = Fore.combine(targetInstance.instanceData.firstElementChild, data.firstElementChild, this,null);
388
- console.log('merged', merged);
389
-
390
- targetInstance.instanceData = merged;
391
- console.log('merging partial instance',targetInstance.partialInstance)
392
- this.model.updateModel();
393
- this.getOwnerForm().refresh(true);
394
- }
395
- }
396
- */
397
-
398
383
  if (this.replace === 'instance') {
399
384
  if (targetInstance) {
400
385
  if (this.targetref) {
@@ -450,12 +435,16 @@ export class FxSubmission extends ForeElementMixin {
450
435
 
451
436
  if (this.replace === 'download') {
452
437
  const target = this._getProperty('target');
438
+ if (!target) {
439
+ throw new Error(`${this.id} needs to specify "target" attribute`);
440
+ }
453
441
  const downloadLink = document.createElement('a');
454
442
  downloadLink.setAttribute('download', target);
455
- downloadLink.setAttribute('href', `data:${contentType},${data}`);
443
+ downloadLink.setAttribute('href', `data:${contentType},${encodeURIComponent(data)}`);
456
444
  document.body.appendChild(downloadLink);
457
445
  downloadLink.click();
458
446
  }
447
+
459
448
  if (this.replace === 'all') {
460
449
  const target = this._getProperty('target');
461
450
  if (target && target === '_blank') {
package/src/fx-var.js CHANGED
@@ -45,6 +45,11 @@ export class FxVariable extends ForeElementMixin {
45
45
  // Clone the preceding variables to make sure we are not going to get access to variables we
46
46
  // should not get access to
47
47
  this.inScopeVariables = new Map(inScopeVariables);
48
+
49
+ // Set precedingVariables based on inScopeVariables
50
+ this.precedingVariables = Array.from(inScopeVariables.entries()).map(([name, variable]) => {
51
+ return { name, value: variable.value };
52
+ });
48
53
  }
49
54
  }
50
55
  if (!customElements.get('fx-var')) {
@@ -111,14 +111,13 @@ export default function getInScopeContext(node, ref) {
111
111
  const repeatItemFromAttrs = parentElement.closest('.fx-repeatitem');
112
112
 
113
113
  if (repeatItemFromAttrs) {
114
- // ### determine correct inscopecontext by determining the index of the repeatitem in its parent list and
115
- // ### using that as an index on the repeat nodeset
116
- const parent = repeatItemFromAttrs.parentNode;
117
- const index = Array.from(parent.children).indexOf(repeatItemFromAttrs);
118
-
119
- // ### fetching nodeset from fx-repeat-attributes element
120
- const repeatFromAttributes = XPathUtil.getClosest('fx-repeat-attributes', parentElement);
121
- return repeatFromAttributes.nodeset[index];
114
+ // Determine the context by using the cache present on the fx-repeat-attributes. Do not attempt
115
+ // to use the index here, it might change when deleting / inserting items
116
+ const repeatFromAttributes =
117
+ /** @type {import('./ui/fx-repeat-attributes.js').FxRepeatAttributes} */ (
118
+ XPathUtil.getClosest('fx-repeat-attributes', parentElement)
119
+ );
120
+ return repeatFromAttributes.getContextForRepeatItem(repeatItemFromAttrs);
122
121
  }
123
122
 
124
123
  if (parentElement.hasAttribute('context')) {