@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.
- package/dist/fore-dev.js +36088 -9
- package/dist/fore.js +35918 -9
- package/index.js +3 -1
- package/package.json +10 -4
- package/resources/fore.css +30 -5
- package/src/DataObserver.js +181 -0
- package/src/DependencyNotifyingDomFacade.js +27 -21
- package/src/DependentXPathQueries.js +32 -0
- package/src/ForeElementMixin.js +60 -26
- package/src/actions/abstract-action.js +24 -29
- package/src/actions/fx-append.js +25 -2
- package/src/actions/fx-call.js +2 -2
- package/src/actions/fx-delete.js +58 -21
- package/src/actions/fx-hide.js +1 -1
- package/src/actions/fx-insert.js +62 -48
- package/src/actions/fx-load.js +7 -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 +20 -2
- package/src/actions/fx-show.js +4 -2
- 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 +78 -36
- package/src/fx-bind.js +128 -23
- package/src/fx-connection.js +24 -7
- package/src/fx-fore.js +552 -306
- package/src/fx-instance.js +9 -11
- package/src/fx-model.js +154 -65
- package/src/fx-submission.js +45 -51
- package/src/fx-var.js +5 -0
- package/src/getInScopeContext.js +8 -8
- package/src/modelitem.js +218 -72
- 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 +206 -0
- package/src/ui/abstract-control.js +15 -7
- package/src/ui/fx-case.js +15 -3
- package/src/ui/fx-container.js +10 -3
- package/src/ui/fx-control-menu.js +207 -0
- package/src/ui/fx-control.js +116 -32
- package/src/ui/fx-dialog.js +2 -2
- package/src/ui/fx-group.js +14 -0
- package/src/ui/fx-items.js +10 -4
- package/src/ui/fx-repeat-attributes.js +111 -35
- package/src/ui/fx-repeat.js +364 -87
- package/src/ui/fx-repeat.updated.js +821 -0
- 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 +107 -11
- package/dist/fore-dev.js.map +0 -1
- package/dist/fore.js.map +0 -1
- package/src/ui/fx-select.js +0 -89
package/src/fx-bind.js
CHANGED
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
} from './xpath-evaluation.js';
|
|
9
9
|
import { XPathUtil } from './xpath-util.js';
|
|
10
10
|
import getInScopeContext from './getInScopeContext.js';
|
|
11
|
+
import { getPath } from './xpath-path.js';
|
|
12
|
+
import { evaluateXPathToFirstNode } from 'fontoxpath';
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
15
|
* FxBind declaratively attaches constraints to nodes in the data (instances).
|
|
@@ -35,6 +37,9 @@ export class FxBind extends ForeElementMixin {
|
|
|
35
37
|
|
|
36
38
|
constructor() {
|
|
37
39
|
super();
|
|
40
|
+
/**
|
|
41
|
+
* @type {Node[]}
|
|
42
|
+
*/
|
|
38
43
|
this.nodeset = [];
|
|
39
44
|
this.model = {};
|
|
40
45
|
this.contextNode = {};
|
|
@@ -45,12 +50,102 @@ export class FxBind extends ForeElementMixin {
|
|
|
45
50
|
// console.log('connectedCallback ', this);
|
|
46
51
|
// this.id = this.hasAttribute('id')?this.getAttribute('id'):;
|
|
47
52
|
this.constraint = this.getAttribute('constraint');
|
|
48
|
-
this.ref = this.getAttribute('ref');
|
|
53
|
+
this.ref = this.getAttribute('ref') || '.';
|
|
49
54
|
this.readonly = this.getAttribute('readonly');
|
|
50
55
|
this.required = this.getAttribute('required');
|
|
51
56
|
this.relevant = this.getAttribute('relevant');
|
|
52
57
|
this.type = this.hasAttribute('type') ? this.getAttribute('type') : FxBind.TYPE_DEFAULT;
|
|
53
58
|
this.calculate = this.getAttribute('calculate');
|
|
59
|
+
// For self-references, just apply the facets to the parent bind
|
|
60
|
+
if (this.ref === '.') {
|
|
61
|
+
const parent = this.parentNode;
|
|
62
|
+
if (parent instanceof FxBind) {
|
|
63
|
+
// For overlapping binds, the last one wins
|
|
64
|
+
parent.calculate ||= this.calculate;
|
|
65
|
+
parent.readonly ||= this.readonly;
|
|
66
|
+
parent.required ||= this.required;
|
|
67
|
+
parent.relevant ||= this.relevant;
|
|
68
|
+
parent.constraint ||= this.constraint;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* @param {string} ref -
|
|
74
|
+
* @param {Node} node -
|
|
75
|
+
* @param {ForeElementMixin} boundElement -
|
|
76
|
+
*
|
|
77
|
+
* @returns {ModelItem}
|
|
78
|
+
*/
|
|
79
|
+
static createModelItem(ref, node, boundElement, opNum) {
|
|
80
|
+
const instanceId = XPathUtil.resolveInstance(boundElement, boundElement.ref);
|
|
81
|
+
if (Array.isArray(node)) {
|
|
82
|
+
node = node[0];
|
|
83
|
+
}
|
|
84
|
+
if (!node.nodeType) {
|
|
85
|
+
// This node set is not pointing to nodes, but some other type.
|
|
86
|
+
return new ModelItem(
|
|
87
|
+
ref,
|
|
88
|
+
'non-node item',
|
|
89
|
+
node,
|
|
90
|
+
null,
|
|
91
|
+
instanceId,
|
|
92
|
+
boundElement.getOwnerForm(),
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ✅ only the repeat item gets the _<opNum> suffix; children do not.
|
|
97
|
+
const basePath = XPathUtil.getPath(node, instanceId);
|
|
98
|
+
const path = opNum ? `${basePath}_${opNum}` : basePath;
|
|
99
|
+
|
|
100
|
+
// const path = XPathUtil.getPath(node, instanceId);
|
|
101
|
+
|
|
102
|
+
// naive approach to finding matching bind elements for given ref if not provided by caller.
|
|
103
|
+
// Use XPath and variables to escape XPaths in the ref
|
|
104
|
+
/**
|
|
105
|
+
* @type {import('./fx-bind.js').FxBind}
|
|
106
|
+
*/
|
|
107
|
+
/*
|
|
108
|
+
const bind = evaluateXPathToFirstNode(
|
|
109
|
+
'descendant::fx-bind[@ref=$ref]',
|
|
110
|
+
boundElement.getModel(),
|
|
111
|
+
null,
|
|
112
|
+
{
|
|
113
|
+
ref: boundElement.ref,
|
|
114
|
+
},
|
|
115
|
+
);
|
|
116
|
+
*/
|
|
117
|
+
const bind = boundElement.getOwnerForm().querySelector(`fx-bind[ref="${ref}"]`);
|
|
118
|
+
|
|
119
|
+
let modelItem = boundElement.getModel().getModelItem(node);
|
|
120
|
+
if (!modelItem) {
|
|
121
|
+
if (bind) {
|
|
122
|
+
modelItem = new ModelItem(
|
|
123
|
+
path,
|
|
124
|
+
boundElement.getBindingExpr(),
|
|
125
|
+
node,
|
|
126
|
+
bind,
|
|
127
|
+
instanceId,
|
|
128
|
+
boundElement.getOwnerForm(),
|
|
129
|
+
);
|
|
130
|
+
const alert = bind.getAlert();
|
|
131
|
+
if (alert) {
|
|
132
|
+
modelItem.addAlert(alert);
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
// no binding facets apply
|
|
136
|
+
modelItem = new ModelItem(
|
|
137
|
+
path,
|
|
138
|
+
boundElement.getBindingExpr(),
|
|
139
|
+
node,
|
|
140
|
+
null,
|
|
141
|
+
instanceId,
|
|
142
|
+
boundElement.getOwnerForm(),
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
// boundElement.getModel().registerModelItem(modelItem);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return modelItem;
|
|
54
149
|
}
|
|
55
150
|
|
|
56
151
|
/**
|
|
@@ -80,10 +175,10 @@ export class FxBind extends ForeElementMixin {
|
|
|
80
175
|
|
|
81
176
|
_buildBindGraph() {
|
|
82
177
|
if (this.bindType === 'xml') {
|
|
83
|
-
this.nodeset.forEach(
|
|
178
|
+
this.nodeset.forEach(node => {
|
|
84
179
|
const instance = XPathUtil.resolveInstance(this, this.ref);
|
|
85
180
|
|
|
86
|
-
const path =
|
|
181
|
+
const path = getPath(node, instance);
|
|
87
182
|
this.model.mainGraph.addNode(path, node);
|
|
88
183
|
|
|
89
184
|
/* ### catching references in the 'ref' itself...
|
|
@@ -142,6 +237,26 @@ export class FxBind extends ForeElementMixin {
|
|
|
142
237
|
}
|
|
143
238
|
}
|
|
144
239
|
|
|
240
|
+
/**
|
|
241
|
+
* Resolves a referenced ModelItem using the model's graph and node registry.
|
|
242
|
+
* @param {string} refName
|
|
243
|
+
* @returns {ModelItem | null}
|
|
244
|
+
*/
|
|
245
|
+
resolveModelItem(refName) {
|
|
246
|
+
if (!this.model?.mainGraph || !this.model?.getModelItemForNode) return null;
|
|
247
|
+
|
|
248
|
+
const suffixes = [`/${refName}`, `:${refName}`];
|
|
249
|
+
|
|
250
|
+
for (const [path, node] of this.model.mainGraph.nodeMap.entries()) {
|
|
251
|
+
if (suffixes.some(suffix => path.endsWith(suffix))) {
|
|
252
|
+
const modelItem = this.model.getModelItemForNode(node);
|
|
253
|
+
if (modelItem) return modelItem;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
|
|
145
260
|
/**
|
|
146
261
|
* Add the dependencies of this bind
|
|
147
262
|
*
|
|
@@ -158,10 +273,10 @@ export class FxBind extends ForeElementMixin {
|
|
|
158
273
|
if (!this.model.mainGraph.hasNode(nodeHash)) {
|
|
159
274
|
this.model.mainGraph.addNode(nodeHash, node);
|
|
160
275
|
}
|
|
161
|
-
refs.forEach(
|
|
276
|
+
refs.forEach(ref => {
|
|
162
277
|
const instance = XPathUtil.resolveInstance(this, path);
|
|
163
278
|
|
|
164
|
-
const otherPath =
|
|
279
|
+
const otherPath = getPath(ref, instance);
|
|
165
280
|
// console.log('otherPath', otherPath)
|
|
166
281
|
|
|
167
282
|
// todo: nasty hack to prevent duplicate pathes like 'a[1]' and 'a[1]/text()[1]' to end up as separate nodes in the graph
|
|
@@ -179,7 +294,7 @@ export class FxBind extends ForeElementMixin {
|
|
|
179
294
|
|
|
180
295
|
_processChildren(model) {
|
|
181
296
|
const childbinds = this.querySelectorAll(':scope > fx-bind');
|
|
182
|
-
Array.from(childbinds).forEach(
|
|
297
|
+
Array.from(childbinds).forEach(bind => {
|
|
183
298
|
// console.log('init child bind ', bind);
|
|
184
299
|
bind.init(model);
|
|
185
300
|
});
|
|
@@ -208,14 +323,14 @@ export class FxBind extends ForeElementMixin {
|
|
|
208
323
|
if (this.ref === '' || this.ref === null) {
|
|
209
324
|
this.nodeset = inscopeContext;
|
|
210
325
|
} else if (Array.isArray(inscopeContext)) {
|
|
211
|
-
inscopeContext.forEach(
|
|
326
|
+
inscopeContext.forEach(n => {
|
|
212
327
|
if (XPathUtil.isSelfReference(this.ref)) {
|
|
213
328
|
this.nodeset = inscopeContext;
|
|
214
329
|
} else {
|
|
215
330
|
// eslint-disable-next-line no-lonely-if
|
|
216
331
|
if (this.ref) {
|
|
217
332
|
const localResult = evaluateXPathToNodes(this.ref, n, this);
|
|
218
|
-
localResult.forEach(
|
|
333
|
+
localResult.forEach(item => {
|
|
219
334
|
this.nodeset.push(item);
|
|
220
335
|
});
|
|
221
336
|
/*
|
|
@@ -243,7 +358,7 @@ export class FxBind extends ForeElementMixin {
|
|
|
243
358
|
if (Array.isArray(this.nodeset)) {
|
|
244
359
|
// console.log('################################################ ', this.nodeset);
|
|
245
360
|
// Array.from(this.nodeset).forEach((n, index) => {
|
|
246
|
-
Array.from(this.nodeset).forEach(
|
|
361
|
+
Array.from(this.nodeset).forEach(n => {
|
|
247
362
|
// console.log('node ',n);
|
|
248
363
|
// this._createModelItem(n, index);
|
|
249
364
|
this._createModelItem(n);
|
|
@@ -308,25 +423,15 @@ export class FxBind extends ForeElementMixin {
|
|
|
308
423
|
// const path = this.getPath(node);
|
|
309
424
|
const instanceId = XPathUtil.resolveInstance(this, this.ref);
|
|
310
425
|
|
|
311
|
-
const path =
|
|
426
|
+
const path = getPath(node, instanceId);
|
|
312
427
|
// const shortPath = this.shortenPath(path);
|
|
313
428
|
|
|
314
429
|
// ### constructing default modelitem - will get evaluated during recalculate()
|
|
315
430
|
// ### constructing default modelitem - will get evaluated during recalculate()
|
|
316
431
|
// ### constructing default modelitem - will get evaluated during recalculate()
|
|
317
432
|
// const newItem = new ModelItem(shortPath,
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
this.getBindingExpr(),
|
|
321
|
-
FxBind.READONLY_DEFAULT,
|
|
322
|
-
FxBind.RELEVANT_DEFAULT,
|
|
323
|
-
FxBind.REQUIRED_DEFAULT,
|
|
324
|
-
FxBind.CONSTRAINT_DEFAULT,
|
|
325
|
-
this.type,
|
|
326
|
-
targetNode,
|
|
327
|
-
this,
|
|
328
|
-
instanceId
|
|
329
|
-
);
|
|
433
|
+
const fore = this.closest('fx-fore');
|
|
434
|
+
const newItem = new ModelItem(path, this.getBindingExpr(), targetNode, this, instanceId, fore);
|
|
330
435
|
|
|
331
436
|
const alert = this.getAlert();
|
|
332
437
|
if (alert) {
|
|
@@ -355,7 +460,7 @@ export class FxBind extends ForeElementMixin {
|
|
|
355
460
|
getReferences(propertyExpr) {
|
|
356
461
|
const touchedNodes = new Set();
|
|
357
462
|
const domFacade = new DependencyNotifyingDomFacade(otherNode => touchedNodes.add(otherNode));
|
|
358
|
-
this.nodeset.forEach(
|
|
463
|
+
this.nodeset.forEach(node => {
|
|
359
464
|
evaluateXPathToString(propertyExpr, node, this, domFacade);
|
|
360
465
|
});
|
|
361
466
|
return Array.from(touchedNodes.values());
|
package/src/fx-connection.js
CHANGED
|
@@ -38,7 +38,7 @@ ${
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
static get observedAttributes() {
|
|
41
|
-
return ['url', 'heartbeat', 'message-format'];
|
|
41
|
+
return ['url', 'heartbeat', 'message-format', 'messageformat'];
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
connectedCallback() {
|
|
@@ -78,6 +78,7 @@ ${
|
|
|
78
78
|
this._setupHeartbeat();
|
|
79
79
|
break;
|
|
80
80
|
case 'messageformat':
|
|
81
|
+
case 'message-format':
|
|
81
82
|
this._messageFormat = newValue;
|
|
82
83
|
break;
|
|
83
84
|
default:
|
|
@@ -87,19 +88,35 @@ ${
|
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
send(data) {
|
|
90
|
-
|
|
91
|
-
data
|
|
91
|
+
// If data is provided directly, use it; otherwise evaluate from context
|
|
92
|
+
if (data === undefined) {
|
|
93
|
+
this.evalInContext();
|
|
94
|
+
data = this.nodeset;
|
|
95
|
+
}
|
|
96
|
+
|
|
92
97
|
if (this._socket && this._socket.readyState === WebSocket.OPEN) {
|
|
93
98
|
let message;
|
|
94
99
|
switch (this._messageFormat) {
|
|
95
100
|
case 'json':
|
|
96
|
-
|
|
101
|
+
if (typeof data === 'string') {
|
|
102
|
+
message = data; // Assume it's already JSON string
|
|
103
|
+
} else {
|
|
104
|
+
message = JSON.stringify(data);
|
|
105
|
+
}
|
|
97
106
|
break;
|
|
98
107
|
case 'xml':
|
|
99
|
-
|
|
108
|
+
if (typeof data === 'string') {
|
|
109
|
+
message = data;
|
|
110
|
+
} else {
|
|
111
|
+
message = new XMLSerializer().serializeToString(data);
|
|
112
|
+
}
|
|
100
113
|
break;
|
|
101
114
|
case 'text':
|
|
102
|
-
|
|
115
|
+
if (typeof data === 'string') {
|
|
116
|
+
message = data;
|
|
117
|
+
} else {
|
|
118
|
+
message = data.textContent;
|
|
119
|
+
}
|
|
103
120
|
break;
|
|
104
121
|
default:
|
|
105
122
|
throw new Error(`Unsupported message format: ${this._messageFormat}`);
|
|
@@ -123,7 +140,7 @@ ${
|
|
|
123
140
|
_disconnect() {
|
|
124
141
|
if (this._socket) {
|
|
125
142
|
this._socket.removeEventListener('open', this._onOpen.bind(this));
|
|
126
|
-
this._socket.removeEventListener('message',
|
|
143
|
+
this._socket.removeEventListener('message', this._onMessage);
|
|
127
144
|
this._socket.removeEventListener('close', this._onClose.bind(this));
|
|
128
145
|
this._socket.close();
|
|
129
146
|
this._socket = null;
|