@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.
- package/dist/fore-dev.js +4472 -2645
- package/dist/fore.js +4417 -2622
- package/index.js +2 -0
- package/package.json +10 -4
- package/resources/fore.css +2 -6
- package/src/DependencyNotifyingDomFacade.js +27 -21
- package/src/ForeElementMixin.js +282 -277
- package/src/actions/abstract-action.js +7 -12
- package/src/actions/fx-append.js +23 -2
- package/src/actions/fx-call.js +2 -2
- package/src/actions/fx-delete.js +54 -29
- package/src/actions/fx-insert.js +23 -15
- package/src/actions/fx-load.js +2 -2
- package/src/actions/fx-refresh.js +15 -5
- package/src/actions/fx-replace.js +18 -3
- package/src/actions/fx-reset.js +6 -0
- package/src/actions/fx-send.js +10 -7
- package/src/actions/fx-setattribute.js +12 -0
- package/src/actions/fx-setfocus.js +11 -8
- package/src/actions/fx-setvalue.js +93 -93
- package/src/actions/fx-toggle.js +1 -1
- package/src/extract-predicate-deps.js +57 -0
- package/src/extractPredicateDependencies.js +36 -0
- package/src/fore.js +41 -16
- package/src/fx-bind.js +128 -23
- package/src/fx-connection.js +24 -7
- package/src/fx-fore.js +506 -259
- package/src/fx-instance.js +9 -11
- package/src/fx-model.js +154 -65
- package/src/fx-submission.js +19 -30
- package/src/fx-var.js +5 -0
- package/src/getInScopeContext.js +7 -8
- package/src/modelitem.js +247 -112
- package/src/tools/fx-action-log.js +21 -19
- package/src/tools/fx-log-settings.js +4 -2
- package/src/ui/TemplateExpression.js +12 -0
- package/src/ui/UIElement.js +125 -10
- package/src/ui/abstract-control.js +5 -0
- package/src/ui/fx-case.js +15 -3
- package/src/ui/fx-container.js +5 -0
- package/src/ui/fx-control-menu.js +23 -14
- package/src/ui/fx-control.js +55 -15
- package/src/ui/fx-items.js +10 -4
- package/src/ui/fx-repeat-attributes.js +111 -35
- package/src/ui/fx-repeat.js +332 -85
- package/src/ui/fx-repeatitem.js +23 -20
- package/src/ui/fx-switch.js +5 -3
- package/src/ui/fx-upload.js +36 -40
- package/src/ui/repeat-base.js +532 -0
- package/src/withDraggability.js +8 -4
- package/src/xpath-evaluation.js +26 -8
- package/src/xpath-path.js +79 -0
- package/src/xpath-util.js +357 -289
package/src/fx-instance.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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 =
|
|
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 {
|
|
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
|
|
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
|
|
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 (
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
139
|
+
|
|
98
140
|
if (node === null || node === undefined) return null;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
//
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}
|
|
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(
|
|
486
|
-
return this.modelItems.find(
|
|
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
|
}
|
package/src/fx-submission.js
CHANGED
|
@@ -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
|
-
|
|
170
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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')) {
|
package/src/getInScopeContext.js
CHANGED
|
@@ -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
|
-
//
|
|
115
|
-
//
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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')) {
|