@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
@@ -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();
@@ -100,7 +97,7 @@ export class FxSubmission extends ForeElementMixin {
100
97
  this.getOwnerForm().classList.add('submit-validation-failed');
101
98
  // ### allow alerts to pop up
102
99
  // this.dispatch('submit-error', {});
103
- Fore.dispatch(this, 'submit-error', {});
100
+ Fore.dispatch(this, 'submit-error', { status: 0, message: 'validation failed' });
104
101
  this.getModel().parentNode.refresh(true);
105
102
  return;
106
103
  }
@@ -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', {});
@@ -183,6 +184,7 @@ export class FxSubmission extends ForeElementMixin {
183
184
  const serialized = localStorage.getItem(key);
184
185
  if (!serialized) {
185
186
  Fore.dispatch(this, 'submit-error', {
187
+ status: 400,
186
188
  message: `Error reading key ${key} from localstorage`,
187
189
  });
188
190
  this.parameters.clear();
@@ -237,22 +239,29 @@ export class FxSubmission extends ForeElementMixin {
237
239
  if (!response.ok || response.status > 400) {
238
240
  // this.dispatch('submit-error', { message: `Error while submitting ${this.id}` });
239
241
  console.info(
240
- `%csubmit-error #${this.id}`,
241
- 'background:red; color:black; padding:.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;',
242
+ `%csubmit-error #${this.id}`,
243
+ 'background:red; color:black; padding:.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;',
242
244
  );
243
245
 
244
- Fore.dispatch(this, 'submit-error', { message: `Error while submitting ${this.id}` });
246
+ Fore.dispatch(this, 'submit-error', {
247
+ status: response.status,
248
+ message: `Error during submit ${this.id}`,
249
+ });
245
250
  return;
246
251
  }
247
252
 
248
- const contentType = response.headers.get('content-type').toLowerCase();
253
+ const contentType = response.headers
254
+ .get('content-type')
255
+ .split(';')[0]
256
+ .trim()
257
+ .toLowerCase();
249
258
  if (contentType.startsWith('text/')) {
250
259
  const text = await response.text();
251
260
  this._handleResponse(text, resolvedUrl, contentType);
252
- } else if (contentType.startsWith('application/json')) {
261
+ } else if (contentType.endsWith('/json') || contentType.endsWith('+json')) {
253
262
  const json = await response.json();
254
263
  this._handleResponse(json, resolvedUrl, contentType);
255
- } else if (contentType.startsWith('application/xml')) {
264
+ } else if (contentType.endsWith('/xml') || contentType.endsWith('+xml')) {
256
265
  const text = await response.text();
257
266
  const xml = new DOMParser().parseFromString(text, 'application/xml');
258
267
  this._handleResponse(xml, resolvedUrl, contentType);
@@ -264,15 +273,14 @@ export class FxSubmission extends ForeElementMixin {
264
273
  // this.dispatch('submit-done', {});
265
274
  // console.log(`### <<<<< ${this.id} submit-done >>>>>`);
266
275
  Fore.dispatch(this, 'submit-done', {});
267
- /*
276
+ /*
268
277
  console.info(
269
278
  `%csubmit-done #${this.id}`,
270
279
  'background:green; color:white; padding:.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;',
271
280
  );
272
281
  */
273
-
274
282
  } catch (error) {
275
- Fore.dispatch(this, 'submit-error', { error: error.message });
283
+ Fore.dispatch(this, 'submit-error', { status: 500, error: error.message });
276
284
  } finally {
277
285
  this.parameters.clear();
278
286
  const download = document.querySelector('[download]');
@@ -372,49 +380,30 @@ export class FxSubmission extends ForeElementMixin {
372
380
 
373
381
  const targetInstance = this._getTargetInstance();
374
382
 
375
- /*
376
- if(this.replace === 'merge'){
377
- if(targetInstance.type !== 'xml') {
378
- Fore.dispatch(this, "warn", {'message': 'merging of instances only work for type xml'});
379
- }
380
- if (targetInstance && targetInstance.type === 'xml') {
381
- targetInstance.partialInstance = data;
382
- // const resultDoc = new DOMParser(`${data.nodeName}`, 'application/xml');
383
- // console.log('resultDoc', resultDoc)
384
- const merged = Fore.combine(targetInstance.instanceData.firstElementChild, data.firstElementChild, this,null);
385
- console.log('merged', merged);
386
-
387
- targetInstance.instanceData = merged;
388
- console.log('merging partial instance',targetInstance.partialInstance)
389
- this.model.updateModel();
390
- this.getOwnerForm().refresh(true);
391
- }
392
- }
393
- */
394
-
395
383
  if (this.replace === 'instance') {
396
384
  if (targetInstance) {
397
385
  if (this.targetref) {
398
-
399
386
  const [theTarget] = evaluateXPath(
400
- this.targetref,
401
- targetInstance.instanceData.firstElementChild,
402
- this,
387
+ this.targetref,
388
+ targetInstance.instanceData.firstElementChild,
389
+ this,
403
390
  );
404
391
  console.log('theTarget', theTarget);
405
- if(this.responseMediatype === 'application/xml' || this.responseMediatype === 'text/html'){
392
+ if (
393
+ this.responseMediatype === 'application/xml' ||
394
+ this.responseMediatype === 'text/html'
395
+ ) {
406
396
  const clone = data.firstElementChild;
407
397
  const parent = theTarget.parentNode;
408
398
  parent.replaceChild(clone, theTarget);
409
399
  console.log('finally ', parent);
410
400
  }
411
- if(this.responseMediatype.startsWith('text/')){
401
+ if (this.responseMediatype.startsWith('text/')) {
412
402
  theTarget.textContent = data;
413
403
  }
414
- if(this.responseMediatype === 'application/json'){
415
- console.warn('targetref is not supported for application/json responses')
404
+ if (this.responseMediatype === 'application/json') {
405
+ console.warn('targetref is not supported for application/json responses');
416
406
  }
417
-
418
407
  } else if (this.into) {
419
408
  const [theTarget] = evaluateXPath(
420
409
  this.into,
@@ -446,12 +435,16 @@ export class FxSubmission extends ForeElementMixin {
446
435
 
447
436
  if (this.replace === 'download') {
448
437
  const target = this._getProperty('target');
438
+ if (!target) {
439
+ throw new Error(`${this.id} needs to specify "target" attribute`);
440
+ }
449
441
  const downloadLink = document.createElement('a');
450
442
  downloadLink.setAttribute('download', target);
451
- downloadLink.setAttribute('href', `data:${contentType},${data}`);
443
+ downloadLink.setAttribute('href', `data:${contentType},${encodeURIComponent(data)}`);
452
444
  document.body.appendChild(downloadLink);
453
445
  downloadLink.click();
454
446
  }
447
+
455
448
  if (this.replace === 'all') {
456
449
  const target = this._getProperty('target');
457
450
  if (target && target === '_blank') {
@@ -471,11 +464,10 @@ export class FxSubmission extends ForeElementMixin {
471
464
  const target = this._getProperty('target');
472
465
  const targetNode = document.querySelector(target);
473
466
  if (targetNode) {
474
-
475
- if(contentType.startsWith('text/html')){
467
+ if (contentType.startsWith('text/html')) {
476
468
  targetNode.innerHTML = data;
477
469
  }
478
- if(this.responseMediatype.startsWith('image/svg')){
470
+ if (this.responseMediatype.startsWith('image/svg')) {
479
471
  const parser = new DOMParser();
480
472
  const svgDoc = parser.parseFromString(data, 'image/svg+xml');
481
473
 
@@ -493,10 +485,11 @@ export class FxSubmission extends ForeElementMixin {
493
485
  }
494
486
  }
495
487
 
488
+ /*
496
489
  _handleError() {
497
490
  // this.dispatch('submit-error', {});
498
491
  Fore.dispatch(this, 'submit-error', {});
499
- /*
492
+ /!*
500
493
  console.log('ERRRORRRRR');
501
494
  this.dispatchEvent(
502
495
  new CustomEvent('submit-error', {
@@ -505,8 +498,9 @@ export class FxSubmission extends ForeElementMixin {
505
498
  detail: {},
506
499
  }),
507
500
  );
508
- */
501
+ *!/
509
502
  }
503
+ */
510
504
 
511
505
  /*
512
506
  mergeNodes(node1, node2) {
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')) {