@jinntec/fore 2.8.0 → 3.0.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 +4581 -1863
- package/dist/fore.js +4598 -1871
- package/package.json +3 -1
- package/src/DependentXPathQueries.js +37 -2
- package/src/ForeElementMixin.js +64 -38
- package/src/actions/fx-delete.js +424 -73
- package/src/actions/fx-insert.js +471 -73
- package/src/actions/fx-setattribute.js +5 -5
- package/src/actions/fx-setvalue.js +11 -9
- package/src/fore.js +28 -71
- package/src/functions/fx-functionlib.js +138 -12
- package/src/functions/mylib.js +13 -0
- package/src/functions/registerFunction.js +78 -20
- package/src/fx-bind.js +128 -73
- package/src/fx-fore.js +201 -97
- package/src/fx-instance.js +138 -142
- package/src/fx-model.js +292 -102
- package/src/fx-submission.js +246 -135
- package/src/fx-var.js +28 -4
- package/src/json/JSONDomFacade.js +84 -0
- package/src/json/JSONLens.js +67 -0
- package/src/json/JSONNode.js +248 -0
- package/src/json/lensFromNode.js +5 -0
- package/src/modelitem.js +16 -2
- package/src/tools/fx-action-log.js +1 -1
- package/src/ui/UIElement.js +16 -2
- package/src/ui/fx-items.js +26 -32
- package/src/ui/fx-repeat.js +682 -246
- package/src/ui/fx-repeatitem.js +16 -1
- package/src/ui/repeat-base.js +8 -4
- package/src/withDraggability.js +0 -1
- package/src/xpath-evaluation.js +1763 -740
- package/src/xpath-path.js +274 -24
- package/src/xpath-util.js +92 -46
package/src/fx-bind.js
CHANGED
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
import { XPathUtil } from './xpath-util.js';
|
|
10
10
|
import getInScopeContext from './getInScopeContext.js';
|
|
11
11
|
import { getPath } from './xpath-path.js';
|
|
12
|
-
import { evaluateXPathToFirstNode } from 'fontoxpath';
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
14
|
* FxBind declaratively attaches constraints to nodes in the data (instances).
|
|
@@ -157,86 +156,75 @@ export class FxBind extends ForeElementMixin {
|
|
|
157
156
|
*/
|
|
158
157
|
init(model) {
|
|
159
158
|
this.model = model;
|
|
160
|
-
// console.log('init binding ', this);
|
|
161
159
|
this._getInstanceId();
|
|
162
160
|
this.bindType = this.getModel().getInstance(this.instanceId).type;
|
|
163
|
-
// console.log('binding type ', this.bindType);
|
|
164
161
|
|
|
162
|
+
// ✅ Always evaluate nodeset first (XML + JSON)
|
|
163
|
+
this._evalInContext();
|
|
164
|
+
|
|
165
|
+
// ✅ Build dependency graph for both types
|
|
166
|
+
this._buildBindGraph();
|
|
167
|
+
|
|
168
|
+
// ✅ Create modelitems for both types
|
|
165
169
|
if (this.bindType === 'xml') {
|
|
166
|
-
this._evalInContext();
|
|
167
|
-
this._buildBindGraph();
|
|
168
170
|
this._createModelItems();
|
|
171
|
+
} else if (this.bindType === 'json') {
|
|
172
|
+
this._createModelItemsForJSON();
|
|
169
173
|
}
|
|
170
|
-
// todo: support json
|
|
171
174
|
|
|
172
|
-
// ### process child bindings
|
|
173
175
|
this._processChildren(model);
|
|
174
176
|
}
|
|
175
177
|
|
|
176
178
|
_buildBindGraph() {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const path = getPath(node, instance);
|
|
182
|
-
this.model.mainGraph.addNode(path, node);
|
|
183
|
-
|
|
184
|
-
/* ### catching references in the 'ref' itself...
|
|
185
|
-
todo: investigate cases where 'ref' attributes use predicates pointing to other nodes. These would not be handled
|
|
186
|
-
in current implementation.
|
|
187
|
-
|
|
188
|
-
General question: are there valid use-cases for using a 'filter' expression to narrow the nodeset
|
|
189
|
-
where to apply constraints? Guess yes and if it's 'just' for reducing the amount of necessary modelItem objects.
|
|
179
|
+
// ✅ Works for XML and JSON (JSON nodes have getPath()/getPath() handles __jsonlens__)
|
|
180
|
+
this.nodeset.forEach(node => {
|
|
181
|
+
const instanceId = XPathUtil.resolveInstance(this, this.ref);
|
|
182
|
+
const path = getPath(node, instanceId);
|
|
190
183
|
|
|
191
|
-
|
|
192
|
-
// const foreignRefs = this.getReferences(this.ref);
|
|
184
|
+
this.model.mainGraph.addNode(path, node);
|
|
193
185
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
186
|
+
if (this.calculate) {
|
|
187
|
+
this.model.mainGraph.addNode(`${path}:calculate`, node);
|
|
188
|
+
this.model.mainGraph.addDependency(path, `${path}:calculate`);
|
|
189
|
+
}
|
|
199
190
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
191
|
+
const calculateRefs = this._getReferencesForProperty(this.calculate, node);
|
|
192
|
+
if (calculateRefs.length !== 0) {
|
|
193
|
+
this._addDependencies(calculateRefs, node, path, 'calculate', instanceId);
|
|
194
|
+
}
|
|
204
195
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
196
|
+
if (!this.calculate) {
|
|
197
|
+
const readonlyRefs = this._getReferencesForProperty(this.readonly, node);
|
|
198
|
+
if (readonlyRefs.length !== 0) {
|
|
199
|
+
this._addDependencies(readonlyRefs, node, path, 'readonly', instanceId);
|
|
200
|
+
} else if (this.readonly) {
|
|
201
|
+
this.model.mainGraph.addNode(`${path}:readonly`, node);
|
|
212
202
|
}
|
|
203
|
+
}
|
|
213
204
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
}
|
|
205
|
+
const requiredRefs = this._getReferencesForProperty(this.required, node);
|
|
206
|
+
if (requiredRefs.length !== 0) {
|
|
207
|
+
this._addDependencies(requiredRefs, node, path, 'required', instanceId);
|
|
208
|
+
} else if (this.required) {
|
|
209
|
+
this.model.mainGraph.addNode(`${path}:required`, node);
|
|
210
|
+
}
|
|
221
211
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
212
|
+
const relevantRefs = this._getReferencesForProperty(this.relevant, node);
|
|
213
|
+
if (relevantRefs.length !== 0) {
|
|
214
|
+
this._addDependencies(relevantRefs, node, path, 'relevant', instanceId);
|
|
215
|
+
} else if (this.relevant) {
|
|
216
|
+
this.model.mainGraph.addNode(`${path}:relevant`, node);
|
|
217
|
+
}
|
|
228
218
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
219
|
+
const constraintRefs = this._getReferencesForProperty(this.constraint, node);
|
|
220
|
+
if (constraintRefs.length !== 0) {
|
|
221
|
+
this._addDependencies(constraintRefs, node, path, 'constraint', instanceId);
|
|
222
|
+
} else if (this.constraint) {
|
|
223
|
+
this.model.mainGraph.addNode(`${path}:constraint`, node);
|
|
224
|
+
this.model.mainGraph.addDependency(path, `${path}:constraint`);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
238
227
|
}
|
|
239
|
-
|
|
240
228
|
/**
|
|
241
229
|
* Resolves a referenced ModelItem using the model's graph and node registry.
|
|
242
230
|
* @param {string} refName
|
|
@@ -266,26 +254,25 @@ export class FxBind extends ForeElementMixin {
|
|
|
266
254
|
* @param {string} path The path to the start of the reference
|
|
267
255
|
* @param {string} property The property with this dependency
|
|
268
256
|
*/
|
|
269
|
-
_addDependencies(refs, node, path, property) {
|
|
270
|
-
// console.log('_addDependencies',path);
|
|
257
|
+
_addDependencies(refs, node, path, property, instanceId) {
|
|
271
258
|
const nodeHash = `${path}:${property}`;
|
|
259
|
+
|
|
272
260
|
if (refs.length !== 0) {
|
|
273
261
|
if (!this.model.mainGraph.hasNode(nodeHash)) {
|
|
274
262
|
this.model.mainGraph.addNode(nodeHash, node);
|
|
275
263
|
}
|
|
264
|
+
|
|
276
265
|
refs.forEach(ref => {
|
|
277
|
-
const
|
|
266
|
+
const otherPath = getPath(ref, instanceId);
|
|
278
267
|
|
|
279
|
-
|
|
280
|
-
|
|
268
|
+
// keep old XML-only hack
|
|
269
|
+
if (this.bindType === 'xml' && otherPath.endsWith('text()[1]')) return;
|
|
281
270
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
if (!this.model.mainGraph.hasNode(otherPath)) {
|
|
285
|
-
this.model.mainGraph.addNode(otherPath, ref);
|
|
286
|
-
}
|
|
287
|
-
this.model.mainGraph.addDependency(nodeHash, otherPath);
|
|
271
|
+
if (!this.model.mainGraph.hasNode(otherPath)) {
|
|
272
|
+
this.model.mainGraph.addNode(otherPath, ref);
|
|
288
273
|
}
|
|
274
|
+
|
|
275
|
+
this.model.mainGraph.addDependency(nodeHash, otherPath);
|
|
289
276
|
});
|
|
290
277
|
} else {
|
|
291
278
|
this.model.mainGraph.addNode(nodeHash, node);
|
|
@@ -311,6 +298,23 @@ export class FxBind extends ForeElementMixin {
|
|
|
311
298
|
return null;
|
|
312
299
|
}
|
|
313
300
|
|
|
301
|
+
_createModelItemsForJSON() {
|
|
302
|
+
const fore = this.closest('fx-fore');
|
|
303
|
+
const instanceId = this.instanceId;
|
|
304
|
+
|
|
305
|
+
this.nodeset.forEach(jsonNode => {
|
|
306
|
+
const path = getPath(jsonNode, instanceId);
|
|
307
|
+
|
|
308
|
+
// ✅ ModelItem node should be the JSONNode itself (lens), NOT JSONLens
|
|
309
|
+
const newItem = new ModelItem(path, this.getBindingExpr(), jsonNode, this, instanceId, fore);
|
|
310
|
+
|
|
311
|
+
const alert = this.getAlert();
|
|
312
|
+
if (alert) newItem.addAlert(alert);
|
|
313
|
+
|
|
314
|
+
this.getModel().registerModelItem(newItem);
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
314
318
|
/**
|
|
315
319
|
* overwrites
|
|
316
320
|
*/
|
|
@@ -346,8 +350,11 @@ export class FxBind extends ForeElementMixin {
|
|
|
346
350
|
const inst = this.getModel().getInstance(this.instanceId);
|
|
347
351
|
if (inst.type === 'xml') {
|
|
348
352
|
this.nodeset = evaluateXPathToNodes(this.ref, inscopeContext, this);
|
|
353
|
+
} else if (inst.type === 'json') {
|
|
354
|
+
// ✅ JSON must also resolve the nodeset via XPath evaluation
|
|
355
|
+
this.nodeset = evaluateXPathToNodes(this.ref, inscopeContext, this);
|
|
349
356
|
} else {
|
|
350
|
-
this.nodeset =
|
|
357
|
+
this.nodeset = [];
|
|
351
358
|
}
|
|
352
359
|
}
|
|
353
360
|
}
|
|
@@ -458,6 +465,33 @@ export class FxBind extends ForeElementMixin {
|
|
|
458
465
|
}
|
|
459
466
|
|
|
460
467
|
getReferences(propertyExpr) {
|
|
468
|
+
// For XML, DependencyNotifyingDomFacade reliably reports the nodes touched during evaluation.
|
|
469
|
+
// For JSON lens nodes, the domFacade hook does not fire (evaluation goes through our lens resolver),
|
|
470
|
+
// so we must extract lookup tokens and resolve them explicitly.
|
|
471
|
+
|
|
472
|
+
if (!propertyExpr) return [];
|
|
473
|
+
|
|
474
|
+
// JSON path: resolve dependencies by parsing lens lookups in the expression.
|
|
475
|
+
if (this.bindType === 'json') {
|
|
476
|
+
const touchedNodes = new Set();
|
|
477
|
+
const tokens = this._extractJsonLookupTokens(propertyExpr);
|
|
478
|
+
|
|
479
|
+
// Evaluate each token in the *current* context node (each item in nodeset)
|
|
480
|
+
this.nodeset.forEach(node => {
|
|
481
|
+
tokens.forEach(token => {
|
|
482
|
+
try {
|
|
483
|
+
const refs = evaluateXPathToNodes(token, node, this);
|
|
484
|
+
refs.forEach(r => touchedNodes.add(r));
|
|
485
|
+
} catch (_e) {
|
|
486
|
+
// ignore: dependency extraction must never break bind initialization
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
return Array.from(touchedNodes.values());
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// XML path: use dom facade for accurate dependency tracking
|
|
461
495
|
const touchedNodes = new Set();
|
|
462
496
|
const domFacade = new DependencyNotifyingDomFacade(otherNode => touchedNodes.add(otherNode));
|
|
463
497
|
this.nodeset.forEach(node => {
|
|
@@ -465,6 +499,27 @@ export class FxBind extends ForeElementMixin {
|
|
|
465
499
|
});
|
|
466
500
|
return Array.from(touchedNodes.values());
|
|
467
501
|
}
|
|
502
|
+
_extractJsonLookupTokens(expr) {
|
|
503
|
+
if (!expr) return [];
|
|
504
|
+
|
|
505
|
+
const src = String(expr);
|
|
506
|
+
const tokens = new Set();
|
|
507
|
+
|
|
508
|
+
// instance('id')?a?b?c or instance('id')?*
|
|
509
|
+
const instRe = /instance\s*\([^)]*\)\s*(?:\?\s*\*|\?\s*[a-zA-Z_][\w-]*)+/g;
|
|
510
|
+
let m;
|
|
511
|
+
while ((m = instRe.exec(src)) !== null) {
|
|
512
|
+
if (m[0]) tokens.add(m[0].replace(/\s+/g, ''));
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// relative lookups like ?title, ?year, ?ui, ?query (ignore ?*)
|
|
516
|
+
const relRe = /\?[a-zA-Z_][\w-]*/g;
|
|
517
|
+
while ((m = relRe.exec(src)) !== null) {
|
|
518
|
+
if (m[0] && m[0] !== '?*') tokens.add(m[0]);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return Array.from(tokens);
|
|
522
|
+
}
|
|
468
523
|
|
|
469
524
|
/*
|
|
470
525
|
static getReferencesForRef(ref,nodeset){
|