@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
|
@@ -11,43 +11,43 @@ import getInScopeContext from '../getInScopeContext.js';
|
|
|
11
11
|
* @customElement
|
|
12
12
|
*/
|
|
13
13
|
export default class FxSetvalue extends AbstractAction {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
14
|
+
static get properties() {
|
|
15
|
+
return {
|
|
16
|
+
...super.properties,
|
|
17
|
+
ref: {
|
|
18
|
+
type: String,
|
|
19
|
+
},
|
|
20
|
+
valueAttr: {
|
|
21
|
+
type: String,
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
constructor() {
|
|
27
|
+
super();
|
|
28
|
+
this.ref = '';
|
|
29
|
+
this.valueAttr = '';
|
|
30
|
+
}
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
connectedCallback() {
|
|
33
|
+
if (super.connectedCallback) {
|
|
34
|
+
super.connectedCallback();
|
|
35
|
+
}
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
this.valueAttr = this.getAttribute('value');
|
|
37
|
+
if (this.hasAttribute('ref')) {
|
|
38
|
+
this.ref = this.getAttribute('ref');
|
|
39
|
+
} else {
|
|
40
|
+
throw new Error('fx-setvalue must specify a "ref" attribute');
|
|
43
41
|
}
|
|
42
|
+
this.valueAttr = this.getAttribute('value');
|
|
43
|
+
}
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
async perform() {
|
|
46
|
+
super.perform();
|
|
47
|
+
let { value } = this;
|
|
48
|
+
if (this.valueAttr !== null) {
|
|
49
|
+
const inscopeContext = getInScopeContext(this, this.valueAttr);
|
|
50
|
+
/*
|
|
51
51
|
todo: review @martin - shouldn't we always return a string value?
|
|
52
52
|
this comes down to the question if setvalue should only allow setting of strings
|
|
53
53
|
which i tend to agree. Can't remember a case where i wanted to set an attribute
|
|
@@ -58,76 +58,76 @@ export default class FxSetvalue extends AbstractAction {
|
|
|
58
58
|
or getting unexpected results.
|
|
59
59
|
*/
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
} else {
|
|
70
|
-
value = '';
|
|
71
|
-
}
|
|
72
|
-
if (value?.nodeType && value.nodeType === Node.ATTRIBUTE_NODE) {
|
|
73
|
-
value = value.nodeValue;
|
|
74
|
-
}
|
|
75
|
-
const mi = this.getModelItem();
|
|
76
|
-
this.setValue(mi, value);
|
|
77
|
-
// todo: check this again - logically needsUpate should be set but makes tests fail
|
|
78
|
-
// this.needsUpdate = true;
|
|
61
|
+
[value] = evaluateXPath(this.valueAttr, inscopeContext, this, this.detail);
|
|
62
|
+
} else if (this.textContent !== '') {
|
|
63
|
+
value = this.textContent;
|
|
64
|
+
} else {
|
|
65
|
+
value = '';
|
|
66
|
+
}
|
|
67
|
+
if (value?.nodeType && value.nodeType === Node.ATTRIBUTE_NODE) {
|
|
68
|
+
value = value.nodeValue;
|
|
79
69
|
}
|
|
80
70
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
71
|
+
// Get the ModelItem without re-evaluating the context
|
|
72
|
+
const mi = this.getModelItem();
|
|
73
|
+
|
|
74
|
+
this.setValue(mi, value);
|
|
75
|
+
// todo: check this again - logically needsUpate should be set but makes tests fail
|
|
76
|
+
// this.needsUpdate = true;
|
|
77
|
+
}
|
|
86
78
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
79
|
+
/**
|
|
80
|
+
* need to overwrite default dispatchExecute to do it ourselves. This is necessary for tracking control value changes
|
|
81
|
+
* which call setvalue directly without perform().
|
|
82
|
+
*/
|
|
83
|
+
dispatchExecute() {}
|
|
90
84
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
85
|
+
setValue(modelItem, newVal) {
|
|
86
|
+
console.log('setValue', modelItem, newVal);
|
|
87
|
+
const item = modelItem;
|
|
88
|
+
if (!item) return;
|
|
94
89
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
new CustomEvent('execute-action', {
|
|
99
|
-
composed: true,
|
|
100
|
-
bubbles: true,
|
|
101
|
-
cancelable: true,
|
|
102
|
-
detail: {
|
|
103
|
-
action: targetElem,
|
|
104
|
-
event: ev,
|
|
105
|
-
value: newVal,
|
|
106
|
-
path,
|
|
107
|
-
},
|
|
108
|
-
}),
|
|
109
|
-
);
|
|
90
|
+
if (item.value !== newVal) {
|
|
91
|
+
// const path = XPathUtil.getPath(modelItem.node);
|
|
92
|
+
const path = Fore.getDomNodeIndexString(modelItem.node);
|
|
110
93
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
94
|
+
const ev = this.event;
|
|
95
|
+
const targetElem = this;
|
|
96
|
+
this.dispatchEvent(
|
|
97
|
+
new CustomEvent('execute-action', {
|
|
98
|
+
composed: true,
|
|
99
|
+
bubbles: true,
|
|
100
|
+
cancelable: true,
|
|
101
|
+
detail: {
|
|
102
|
+
action: targetElem,
|
|
103
|
+
event: ev,
|
|
104
|
+
value: newVal,
|
|
105
|
+
path,
|
|
106
|
+
},
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
if (newVal?.nodeType) {
|
|
111
|
+
if (newVal.nodeType === Node.ELEMENT_NODE) {
|
|
112
|
+
item.value = newVal;
|
|
113
|
+
}
|
|
114
|
+
if (newVal.nodeType === Node.ATTRIBUTE_NODE) {
|
|
115
|
+
item.value = newVal.getValue();
|
|
116
|
+
}
|
|
117
|
+
if (newVal.nodeType === Node.TEXT_NODE) {
|
|
118
|
+
item.value = newVal.textContent;
|
|
127
119
|
}
|
|
120
|
+
} else {
|
|
121
|
+
item.value = newVal;
|
|
122
|
+
item.node.textContent = newVal;
|
|
123
|
+
}
|
|
124
|
+
this.getModel().changed.push(modelItem);
|
|
125
|
+
this.needsUpdate = true;
|
|
126
|
+
modelItem.notify();
|
|
128
127
|
}
|
|
128
|
+
}
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
if (!customElements.get('fx-setvalue')) {
|
|
132
|
-
|
|
132
|
+
window.customElements.define('fx-setvalue', FxSetvalue);
|
|
133
133
|
}
|
package/src/actions/fx-toggle.js
CHANGED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// extract-predicate-deps.js
|
|
2
|
+
import { DependencyNotifyingDomFacade } from './DependencyNotifyingDomFacade.js';
|
|
3
|
+
import { evaluateXPathToBoolean } from './xpath-evaluation.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Extracts an instance ID from an XPath expression like instance('myInstance')/something
|
|
7
|
+
* @param {string} xpathExpr
|
|
8
|
+
* @returns {string|null} The extracted instance ID or null if not found
|
|
9
|
+
*/
|
|
10
|
+
function extractInstanceIdFromXPath(xpathExpr) {
|
|
11
|
+
const instanceRegex = /instance\(['"]([^)'"]+)['"]\)/;
|
|
12
|
+
const match = xpathExpr.match(instanceRegex);
|
|
13
|
+
return match ? match[1] : null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Gets the context node for a predicate XPath expression.
|
|
18
|
+
* @param {string} predicateExpr
|
|
19
|
+
* @param {import('./fx-model.js').FxModel} model
|
|
20
|
+
* @returns {Node}
|
|
21
|
+
*/
|
|
22
|
+
function getContextNodeForPredicate(predicateExpr, model) {
|
|
23
|
+
const instanceId = extractInstanceIdFromXPath(predicateExpr) || 'default';
|
|
24
|
+
const instance = model.getInstance(instanceId);
|
|
25
|
+
return instance.getDefaultContext();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Extracts and registers dependencies from XPath predicates.
|
|
30
|
+
*
|
|
31
|
+
* @param {string} ref - The XPath expression possibly containing predicates.
|
|
32
|
+
* @param {import('./fx-model.js').FxModel} model - The model to resolve instances from.
|
|
33
|
+
* @param {(modelItem: import('./modelitem.js').ModelItem) => void} register - A callback to register observers.
|
|
34
|
+
* @param {(node: Node) => import('./modelitem.js').ModelItem} resolveModelItem - A callback to resolve modelItems.
|
|
35
|
+
*/
|
|
36
|
+
export function extractPredicateDependencies(ref, contextNode, register, resolveModelItem) {
|
|
37
|
+
const predicateRegex = /\[(.*?)\]/g;
|
|
38
|
+
let match;
|
|
39
|
+
|
|
40
|
+
while ((match = predicateRegex.exec(ref)) !== null) {
|
|
41
|
+
const predicate = match[1];
|
|
42
|
+
try {
|
|
43
|
+
const domFacade = new DependencyNotifyingDomFacade(node => {
|
|
44
|
+
const mi = resolveModelItem(node);
|
|
45
|
+
if (mi) {
|
|
46
|
+
register(mi);
|
|
47
|
+
console.log(`[PredicateDependency] Observing ${mi.path} from predicate: [${predicate}]`);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const predContext = getContextNodeForPredicate(predicate, contextNode);
|
|
52
|
+
evaluateXPathToBoolean(predicate, predContext, { dispatchEvent() {} }, domFacade);
|
|
53
|
+
} catch (e) {
|
|
54
|
+
console.warn('Failed to evaluate predicate expression:', predicate, e);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// extract-predicate-deps.js
|
|
2
|
+
import { DependencyNotifyingDomFacade } from './DependencyNotifyingDomFacade.js';
|
|
3
|
+
import { evaluateXPathToBoolean } from './xpath-evaluation.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Extracts and registers dependencies from XPath predicates.
|
|
7
|
+
*
|
|
8
|
+
* @param {string} ref - The XPath expression possibly containing predicates.
|
|
9
|
+
* @param {Node} contextNode - The context node for evaluation.
|
|
10
|
+
* @param {(modelItem: import('./modelitem.js').ModelItem) => void} register - A callback to register observers.
|
|
11
|
+
* @param {(node: Node) => import('./modelitem.js').ModelItem} resolveModelItem - A callback to resolve modelItems.
|
|
12
|
+
*/
|
|
13
|
+
export function extractPredicateDependencies(ref, contextNode, register, resolveModelItem) {
|
|
14
|
+
const predicateRegex = /\[(.*?)\]/g;
|
|
15
|
+
let match;
|
|
16
|
+
const touchedNodes = new Set();
|
|
17
|
+
|
|
18
|
+
while ((match = predicateRegex.exec(ref)) !== null) {
|
|
19
|
+
const predicate = match[1];
|
|
20
|
+
try {
|
|
21
|
+
const domFacade = new DependencyNotifyingDomFacade(n => touchedNodes.add(n));
|
|
22
|
+
const fakeContext = { dispatchEvent: () => {} }; // prevent null dispatchEvent crash
|
|
23
|
+
evaluateXPathToBoolean(predicate, contextNode, undefined, domFacade);
|
|
24
|
+
|
|
25
|
+
touchedNodes.forEach(node => {
|
|
26
|
+
const mi = resolveModelItem(node);
|
|
27
|
+
if (mi) {
|
|
28
|
+
register(mi);
|
|
29
|
+
console.log(`[PredicateDependency] Observing ${mi.path} from predicate: [${predicate}]`);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
} catch (e) {
|
|
33
|
+
console.warn('Failed to evaluate predicate expression:', predicate, e);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
package/src/fore.js
CHANGED
|
@@ -17,7 +17,7 @@ export class Fore {
|
|
|
17
17
|
|
|
18
18
|
static REQUIRED_DEFAULT = false;
|
|
19
19
|
|
|
20
|
-
static RELEVANT_DEFAULT = true
|
|
20
|
+
static RELEVANT_DEFAULT = true
|
|
21
21
|
|
|
22
22
|
static CONSTRAINT_DEFAULT = true;
|
|
23
23
|
|
|
@@ -230,7 +230,7 @@ export class Fore {
|
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
static get UI_ELEMENTS() {
|
|
233
|
-
return [
|
|
233
|
+
return new Set([
|
|
234
234
|
'FX-ALERT',
|
|
235
235
|
'FX-CONTROL',
|
|
236
236
|
'FX-DIALOG',
|
|
@@ -252,11 +252,7 @@ export class Fore {
|
|
|
252
252
|
'FX-TRIGGER',
|
|
253
253
|
'FX-UPLOAD',
|
|
254
254
|
'FX-VAR',
|
|
255
|
-
];
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
static get MODEL_ELEMENTS() {
|
|
259
|
-
return ['FX-BIND', 'FX-FUNCTION', 'FX-MODEL', 'FX-INSTANCE', 'FX-SUBMISSION'];
|
|
255
|
+
]);
|
|
260
256
|
}
|
|
261
257
|
|
|
262
258
|
/**
|
|
@@ -264,13 +260,30 @@ export class Fore {
|
|
|
264
260
|
* @returns {boolean}
|
|
265
261
|
*/
|
|
266
262
|
static isUiElement(elementName) {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
return
|
|
263
|
+
return Fore.UI_ELEMENTS.has(elementName);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
static get MODEL_ELEMENTS() {
|
|
267
|
+
return ['FX-BIND', 'FX-FUNCTION', 'FX-MODEL', 'FX-INSTANCE', 'FX-SUBMISSION'];
|
|
272
268
|
}
|
|
273
269
|
|
|
270
|
+
|
|
271
|
+
static async initUI(startElement) {
|
|
272
|
+
const inited = new Promise(resolve => {
|
|
273
|
+
const { children } = startElement;
|
|
274
|
+
if (children) {
|
|
275
|
+
for (const element of Array.from(children)) {
|
|
276
|
+
if (element.nodeName.toUpperCase() === 'FX-FORE') {
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
if (Fore.isUiElement(element.nodeName) && typeof element.refresh === 'function') {
|
|
280
|
+
element.init();
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
resolve('done');
|
|
285
|
+
});
|
|
286
|
+
}
|
|
274
287
|
/**
|
|
275
288
|
* recursively refreshes all UI Elements.
|
|
276
289
|
*
|
|
@@ -311,7 +324,13 @@ export class Fore {
|
|
|
311
324
|
if (!force) {
|
|
312
325
|
continue;
|
|
313
326
|
}
|
|
327
|
+
/*
|
|
328
|
+
if(element.nodeName === 'FX-CASE') {
|
|
329
|
+
console.log('hey - got a case', element);
|
|
330
|
+
}
|
|
331
|
+
*/
|
|
314
332
|
if (force === true) {
|
|
333
|
+
// console.log('🔄 refreshing ', element);
|
|
315
334
|
// Unconditional force refresh
|
|
316
335
|
bound.refresh(force);
|
|
317
336
|
continue;
|
|
@@ -319,10 +338,13 @@ export class Fore {
|
|
|
319
338
|
if (typeof force !== 'object') {
|
|
320
339
|
continue;
|
|
321
340
|
}
|
|
341
|
+
/*
|
|
322
342
|
if (
|
|
323
343
|
force.reason === 'index-function' &&
|
|
324
344
|
bound.dependencies.isInvalidatedByIndexFunction()
|
|
325
345
|
) {
|
|
346
|
+
console.log('🔄 refreshing ', element);
|
|
347
|
+
|
|
326
348
|
bound.refresh(force);
|
|
327
349
|
continue;
|
|
328
350
|
}
|
|
@@ -330,12 +352,12 @@ export class Fore {
|
|
|
330
352
|
if (
|
|
331
353
|
bound.dependencies.isInvalidatedByChildlistChanges(force.elementLocalnamesWithChanges)
|
|
332
354
|
) {
|
|
355
|
+
console.log('🔄 refreshing ', element);
|
|
356
|
+
|
|
333
357
|
bound.refresh(force);
|
|
334
358
|
continue;
|
|
335
359
|
}
|
|
336
|
-
|
|
337
|
-
// console.log('refreshing', element, element?.ref);
|
|
338
|
-
// console.log('refreshing ',element);
|
|
360
|
+
*/
|
|
339
361
|
}
|
|
340
362
|
if (!(element.inert === true)) {
|
|
341
363
|
// testing for inert catches model and action elements and should just leave updateable html elements
|
|
@@ -542,7 +564,10 @@ export class Fore {
|
|
|
542
564
|
const reg = /(>)(<)(\/*)/g;
|
|
543
565
|
const wsexp = / *(.*) +\n/g;
|
|
544
566
|
const contexp = /(<.+>)(.+\n)/g;
|
|
545
|
-
xml = xml
|
|
567
|
+
xml = xml
|
|
568
|
+
.replace(reg, '$1\n$2$3')
|
|
569
|
+
.replace(wsexp, '$1\n')
|
|
570
|
+
.replace(contexp, '$1\n$2');
|
|
546
571
|
let formatted = '';
|
|
547
572
|
const lines = xml.split('\n');
|
|
548
573
|
let indent = 0;
|
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());
|