@jinntec/fore 2.6.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 +4470 -2643
- package/dist/fore.js +4415 -2620
- 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-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-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 +357 -289
package/src/ui/UIElement.js
CHANGED
|
@@ -1,21 +1,143 @@
|
|
|
1
1
|
import ForeElementMixin from '../ForeElementMixin.js';
|
|
2
2
|
import { Fore } from '../fore.js';
|
|
3
|
+
import { evaluateXPath, evaluateXPathToBoolean, resolveId } from '../xpath-evaluation';
|
|
4
|
+
import { DependencyNotifyingDomFacade } from '../DependencyNotifyingDomFacade';
|
|
3
5
|
|
|
4
6
|
export class UIElement extends ForeElementMixin {
|
|
5
7
|
constructor() {
|
|
6
8
|
super();
|
|
9
|
+
|
|
10
|
+
this._removeEventListeners = [];
|
|
7
11
|
}
|
|
8
12
|
|
|
9
13
|
connectedCallback() {
|
|
10
14
|
super.connectedCallback();
|
|
11
|
-
this.ondemand = this.hasAttribute('on-demand')
|
|
15
|
+
this.ondemand = this.hasAttribute('on-demand');
|
|
12
16
|
this.wasOnDemandInitially = this.ondemand;
|
|
17
|
+
|
|
13
18
|
if (this.ondemand) {
|
|
14
19
|
this.addEventListener('show-control', () => {
|
|
15
20
|
this.removeAttribute('on-demand');
|
|
16
21
|
});
|
|
17
22
|
this.addTrashIcon();
|
|
18
23
|
}
|
|
24
|
+
|
|
25
|
+
const ref = this.getAttribute('ref');
|
|
26
|
+
// TODO: make this smarter, handling multiple index functions etc
|
|
27
|
+
if (ref && ref.includes('index(')) {
|
|
28
|
+
const repeatId = ref.match(/index\(['"](?<repeatId>[^'"]*)['"]\)/)?.groups?.repeatId;
|
|
29
|
+
if (repeatId) {
|
|
30
|
+
/**
|
|
31
|
+
* @type {import('./fx-repeat.js').FxRepeat}
|
|
32
|
+
*/
|
|
33
|
+
const repeat = resolveId(repeatId, this, 'fx-repeat');
|
|
34
|
+
const onRepeatItemChanged = () => {
|
|
35
|
+
this.getOwnerForm().addToBatchedNotifications(this);
|
|
36
|
+
};
|
|
37
|
+
repeat.addEventListener('item-changed', onRepeatItemChanged);
|
|
38
|
+
this._removeEventListeners.push(() =>
|
|
39
|
+
repeat.removeEventListener('item-changed', onRepeatItemChanged),
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
disconnectedCallback() {
|
|
46
|
+
if (this.modelItem && typeof this.modelItem.removeObserver === 'function') {
|
|
47
|
+
console.log(`[UIElement] Removing observer for ref="${this.ref}"`);
|
|
48
|
+
this.modelItem.removeObserver(this);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
for (const removeEventListener of this._removeEventListeners) {
|
|
52
|
+
removeEventListener();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/*
|
|
56
|
+
evalInContext() {
|
|
57
|
+
this.dependencies.resetDependencies();
|
|
58
|
+
const model = this.getModel();
|
|
59
|
+
if (!model) return;
|
|
60
|
+
|
|
61
|
+
const touchedNodes = new Set();
|
|
62
|
+
const domFacade = new DependencyNotifyingDomFacade(node => touchedNodes.add(node));
|
|
63
|
+
|
|
64
|
+
const context = this.getInScopeContext();
|
|
65
|
+
const result = evaluateXPath(this.ref, context, this, domFacade);
|
|
66
|
+
this.nodeset = Array.isArray(result) ? result : [result];
|
|
67
|
+
|
|
68
|
+
touchedNodes.forEach(node => {
|
|
69
|
+
const mi = model.getModelItem(node);
|
|
70
|
+
if (mi) {
|
|
71
|
+
mi.addObserver(this);
|
|
72
|
+
console.log(`[UIElement] Dynamically observing ${mi.path} due to XPath dependency`);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Manually evaluate predicate parts to ensure detection
|
|
77
|
+
const predicateRegex = /\[(.*?)\]/g;
|
|
78
|
+
let match;
|
|
79
|
+
while ((match = predicateRegex.exec(this.ref)) !== null) {
|
|
80
|
+
const predicate = match[1];
|
|
81
|
+
try {
|
|
82
|
+
const predicateContext = model.getDefaultInstance().getDefaultContext();
|
|
83
|
+
const predDomFacade = new DependencyNotifyingDomFacade(n => touchedNodes.add(n));
|
|
84
|
+
evaluateXPathToBoolean(predicate, predicateContext, this, predDomFacade);
|
|
85
|
+
|
|
86
|
+
touchedNodes.forEach(node => {
|
|
87
|
+
const mi = model.getModelItem(node);
|
|
88
|
+
if (mi) {
|
|
89
|
+
mi.addObserver(this);
|
|
90
|
+
console.log(`[UIElement] Observing ${mi.path} (from predicate: ${predicate})`);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
} catch (e) {
|
|
94
|
+
console.warn('Predicate evaluation failed for dependency tracking:', predicate, e);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
*/
|
|
99
|
+
|
|
100
|
+
attachObserver() {
|
|
101
|
+
const modelItem = this.getModelItem();
|
|
102
|
+
if (!modelItem || typeof modelItem.addObserver !== 'function') return;
|
|
103
|
+
|
|
104
|
+
if (!modelItem.observers) {
|
|
105
|
+
modelItem.observers = new Set();
|
|
106
|
+
}
|
|
107
|
+
if (modelItem.observers.has(this)) {
|
|
108
|
+
// console.log(`[UIElement] Observer already registered for ref="${this.ref}"`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
modelItem.addObserver(this);
|
|
112
|
+
// console.log(`[UIElement] attaching observer for ref="${this.ref}"`, this);
|
|
113
|
+
|
|
114
|
+
// if (typeof this.update === 'function') {
|
|
115
|
+
// this.update(modelItem);
|
|
116
|
+
// }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Called by ModelItem when it changes
|
|
121
|
+
* @param {import('../modelitem.js').ModelItem} modelItem - The ModelItem that changed
|
|
122
|
+
*/
|
|
123
|
+
update(modelItem) {
|
|
124
|
+
if (this.isBound()) {
|
|
125
|
+
// console.log('[UIElement] update()', modelItem);
|
|
126
|
+
// this.getOwnerForm().addToBatchedNotifications(modelItem);
|
|
127
|
+
this.refresh();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// init() {
|
|
132
|
+
// throw new Error('You have to implement the method init!');
|
|
133
|
+
// }
|
|
134
|
+
|
|
135
|
+
async refresh(force) {
|
|
136
|
+
console.log(`🔄 [UIElement] refresh() called for ref="${this.ref}"`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async refreshChildren(force) {
|
|
140
|
+
await Fore.refreshChildren(this, force);
|
|
19
141
|
}
|
|
20
142
|
|
|
21
143
|
activate() {
|
|
@@ -45,16 +167,11 @@ export class UIElement extends ForeElementMixin {
|
|
|
45
167
|
}
|
|
46
168
|
|
|
47
169
|
addTrashIcon() {
|
|
48
|
-
// Only show icon if explicitly marked by control-menu
|
|
49
|
-
|
|
50
170
|
if (!this.closest('[show-icon]')) return;
|
|
51
|
-
|
|
52
|
-
// const wrapper = this.shadowRoot.querySelector('.wrapper');
|
|
53
|
-
// if (!wrapper || wrapper.querySelector('.trash')) return;
|
|
54
171
|
const trash = this.querySelector('.trash');
|
|
55
172
|
if (trash) return;
|
|
173
|
+
|
|
56
174
|
const icon = document.createElement('span');
|
|
57
|
-
// icon.innerHTML = '🗑'; // trash icon
|
|
58
175
|
icon.innerHTML = `
|
|
59
176
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"
|
|
60
177
|
stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
|
@@ -62,10 +179,8 @@ export class UIElement extends ForeElementMixin {
|
|
|
62
179
|
<path d="M17.94 17.94C16.13 19.12 14.13 20 12 20C7 20 2.73 15.88 1 12C1.6 10.66 2.43 9.47 3.46 8.48M10.58 10.58C10.21 11.01 10 11.5 10 12C10 13.11 10.89 14 12 14C12.5 14 12.99 13.79 13.42 13.42M6.53 6.53C7.87 5.54 9.39 5 12 5C17 5 21.27 9.12 23 12C22.4 13.34 21.57 14.53 20.54 15.52M1 1L23 23"/>
|
|
63
180
|
</svg>
|
|
64
181
|
`;
|
|
65
|
-
|
|
66
182
|
icon.classList.add('trash');
|
|
67
183
|
icon.setAttribute('title', 'Hide');
|
|
68
|
-
// icon.setAttribute('part', 'trash');
|
|
69
184
|
icon.style.cursor = 'pointer';
|
|
70
185
|
icon.style.marginLeft = '0.5em';
|
|
71
186
|
|
|
@@ -81,11 +196,11 @@ export class UIElement extends ForeElementMixin {
|
|
|
81
196
|
}
|
|
82
197
|
|
|
83
198
|
removeTrashIcon() {
|
|
84
|
-
debugger;
|
|
85
199
|
const icon = this.querySelector('.trash');
|
|
86
200
|
if (icon) icon.remove();
|
|
87
201
|
}
|
|
88
202
|
}
|
|
203
|
+
|
|
89
204
|
if (!customElements.get('ui-element')) {
|
|
90
205
|
customElements.define('ui-element', UIElement);
|
|
91
206
|
}
|
|
@@ -125,6 +125,7 @@ export default class AbstractControl extends UIElement {
|
|
|
125
125
|
if (this.modelItem instanceof ModelItem) {
|
|
126
126
|
// console.log('### XfAbstractControl.refresh modelItem : ', this.modelItem);
|
|
127
127
|
|
|
128
|
+
this.attachObserver();
|
|
128
129
|
if (this.hasAttribute('as') && this.getAttribute('as') === 'node') {
|
|
129
130
|
// console.log('as', this.nodeset);
|
|
130
131
|
// this.modelItem.value = this.nodeset;
|
|
@@ -156,9 +157,11 @@ export default class AbstractControl extends UIElement {
|
|
|
156
157
|
/*
|
|
157
158
|
this is another case that highlights the fact that an init() function might make sense in general.
|
|
158
159
|
*/
|
|
160
|
+
/*
|
|
159
161
|
if (!this.modelItem.boundControls.includes(this)) {
|
|
160
162
|
this.modelItem.boundControls.push(this);
|
|
161
163
|
}
|
|
164
|
+
*/
|
|
162
165
|
|
|
163
166
|
// console.log('>>>>>>>> abstract refresh ', this.control);
|
|
164
167
|
// this.control[this.valueProp] = this.value;
|
|
@@ -318,9 +321,11 @@ export default class AbstractControl extends UIElement {
|
|
|
318
321
|
// if (alert) alert.style.display = 'none';
|
|
319
322
|
this._dispatchEvent('valid');
|
|
320
323
|
this.setAttribute('valid', '');
|
|
324
|
+
this.getWidget().setAttribute('aria-invalid', 'false');
|
|
321
325
|
this.removeAttribute('invalid');
|
|
322
326
|
} else {
|
|
323
327
|
this.setAttribute('invalid', '');
|
|
328
|
+
this.getWidget().setAttribute('aria-invalid', 'true');
|
|
324
329
|
this.removeAttribute('valid');
|
|
325
330
|
// ### constraint is invalid - handle alerts
|
|
326
331
|
/*
|
package/src/ui/fx-case.js
CHANGED
|
@@ -70,7 +70,7 @@ export class FxCase extends FxContainer {
|
|
|
70
70
|
// calls. Save all important state first.
|
|
71
71
|
const { parentNode } = this;
|
|
72
72
|
const replacement = await this._loadFromSrc();
|
|
73
|
-
if(!replacement){
|
|
73
|
+
if (!replacement) {
|
|
74
74
|
Fore.dispatch(this, 'error', {
|
|
75
75
|
detail: {
|
|
76
76
|
message: `HTML page couldn't be loaded`,
|
|
@@ -81,9 +81,21 @@ export class FxCase extends FxContainer {
|
|
|
81
81
|
await parentNode.replaceCase(this, replacement);
|
|
82
82
|
}
|
|
83
83
|
const model = ownerForm.getModel();
|
|
84
|
-
|
|
85
|
-
ownerForm.refresh(
|
|
84
|
+
ownerForm.addToBatchedNotifications(this);
|
|
85
|
+
ownerForm.refresh(false);
|
|
86
86
|
});
|
|
87
|
+
this.addEventListener('deselect', (event) => {
|
|
88
|
+
const ownerForm = this.getOwnerForm();
|
|
89
|
+
ownerForm.addToBatchedNotifications(event.target);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async refresh(force) {
|
|
94
|
+
console.log(`🔄 fx-case ${this.id} refresh`, force);
|
|
95
|
+
await super.refresh(force);
|
|
96
|
+
if (!this.isBound()) {
|
|
97
|
+
Fore.refreshChildren(this, force);
|
|
98
|
+
}
|
|
87
99
|
}
|
|
88
100
|
|
|
89
101
|
/**
|
package/src/ui/fx-container.js
CHANGED
|
@@ -78,9 +78,14 @@ export class FxContainer extends UIElement {
|
|
|
78
78
|
if (this.isBound()) {
|
|
79
79
|
this.evalInContext();
|
|
80
80
|
this.modelItem = this.getModelItem();
|
|
81
|
+
/*
|
|
81
82
|
if (this.modelItem && !this.modelItem.boundControls.includes(this)) {
|
|
82
83
|
this.modelItem.boundControls.push(this);
|
|
83
84
|
}
|
|
85
|
+
*/
|
|
86
|
+
if (this.modelItem) {
|
|
87
|
+
this.attachObserver();
|
|
88
|
+
}
|
|
84
89
|
this.handleModelItemProperties();
|
|
85
90
|
}
|
|
86
91
|
|
|
@@ -121,28 +121,37 @@ export class FxControlMenu extends XfAbstractControl {
|
|
|
121
121
|
this.updateMenu();
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
_getScopedContainer() {
|
|
125
|
+
const repeatItem = this.closest('fx-repeatitem');
|
|
126
|
+
if (repeatItem) return repeatItem;
|
|
127
|
+
|
|
128
|
+
if (this.selectExpr) {
|
|
129
|
+
return document.querySelector(this.selectExpr);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
124
135
|
updateMenu() {
|
|
125
|
-
const container =
|
|
136
|
+
const container = this._getScopedContainer();
|
|
126
137
|
if (!container) return;
|
|
127
138
|
|
|
128
139
|
let targets = [];
|
|
129
|
-
|
|
140
|
+
// ✅ Include container itself if it has on-demand
|
|
130
141
|
if (container.hasAttribute('on-demand')) {
|
|
131
|
-
|
|
132
|
-
// If it's an <fx-repeat> with on-demand, use only the container
|
|
133
|
-
targets = [container];
|
|
134
|
-
} else {
|
|
135
|
-
// If it's not <fx-repeat>, include container and inner [on-demand] targets
|
|
136
|
-
targets = [container, ...container.querySelectorAll('[on-demand]')];
|
|
137
|
-
}
|
|
138
|
-
} else {
|
|
139
|
-
// If container is not on-demand, only look for inner [on-demand]
|
|
140
|
-
targets = Array.from(container.querySelectorAll('[on-demand]'));
|
|
142
|
+
targets.push(container);
|
|
141
143
|
}
|
|
144
|
+
|
|
145
|
+
// ✅ Also include any descendant [on-demand] controls if not within repeat
|
|
146
|
+
if (container.nodeName !== 'FX-REPEAT') {
|
|
147
|
+
const innerTargets = Array.from(container.querySelectorAll('[on-demand]'));
|
|
148
|
+
targets.push(...innerTargets);
|
|
149
|
+
}
|
|
150
|
+
|
|
142
151
|
this._currentTargets = targets;
|
|
143
|
-
this.menuEl.innerHTML = '';
|
|
152
|
+
this.menuEl.innerHTML = '';
|
|
144
153
|
|
|
145
|
-
// Find the
|
|
154
|
+
// Find the button to disable if needed
|
|
146
155
|
const slot = this.shadowRoot.querySelector('slot');
|
|
147
156
|
const assignedNodes = slot.assignedNodes({ flatten: true });
|
|
148
157
|
const button = assignedNodes.find(
|
package/src/ui/fx-control.js
CHANGED
|
@@ -3,12 +3,15 @@ import {
|
|
|
3
3
|
evaluateXPath,
|
|
4
4
|
evaluateXPathToString,
|
|
5
5
|
evaluateXPathToFirstNode,
|
|
6
|
+
evaluateXPathToBoolean,
|
|
6
7
|
} from '../xpath-evaluation.js';
|
|
7
8
|
import getInScopeContext from '../getInScopeContext.js';
|
|
8
9
|
import { Fore } from '../fore.js';
|
|
9
10
|
import { ModelItem } from '../modelitem.js';
|
|
10
11
|
import { debounce } from '../events.js';
|
|
11
12
|
import { FxModel } from '../fx-model.js';
|
|
13
|
+
import { DependencyNotifyingDomFacade } from '../DependencyNotifyingDomFacade';
|
|
14
|
+
import { extractPredicateDependencies } from '../extract-predicate-deps.js';
|
|
12
15
|
|
|
13
16
|
const WIDGETCLASS = 'widget';
|
|
14
17
|
|
|
@@ -238,6 +241,7 @@ export default class FxControl extends XfAbstractControl {
|
|
|
238
241
|
* @param val the new value to be set
|
|
239
242
|
*/
|
|
240
243
|
setValue(val) {
|
|
244
|
+
console.log('Control.setValue', val, 'on', this);
|
|
241
245
|
const modelitem = this.getModelItem();
|
|
242
246
|
|
|
243
247
|
if (this.getAttribute('class')) {
|
|
@@ -265,9 +269,11 @@ export default class FxControl extends XfAbstractControl {
|
|
|
265
269
|
const setval = this.shadowRoot.getElementById('setvalue');
|
|
266
270
|
setval.setValue(modelitem, val);
|
|
267
271
|
|
|
272
|
+
/*
|
|
268
273
|
if (this.modelItem instanceof ModelItem && !this.modelItem?.boundControls.includes(this)) {
|
|
269
274
|
this.modelItem.boundControls.push(this);
|
|
270
275
|
}
|
|
276
|
+
*/
|
|
271
277
|
|
|
272
278
|
setval.actionPerformed(false);
|
|
273
279
|
// this.visited = true;
|
|
@@ -287,6 +293,9 @@ export default class FxControl extends XfAbstractControl {
|
|
|
287
293
|
});
|
|
288
294
|
}
|
|
289
295
|
// this.getOwnerForm().refresh();
|
|
296
|
+
const body = this.closest('body');
|
|
297
|
+
const outerfore = body.querySelector('fx-fore');
|
|
298
|
+
outerfore.refresh(true);
|
|
290
299
|
}
|
|
291
300
|
|
|
292
301
|
renderHTML(ref) {
|
|
@@ -496,6 +505,8 @@ export default class FxControl extends XfAbstractControl {
|
|
|
496
505
|
imported.model = imported.querySelector('fx-model');
|
|
497
506
|
imported.model.updateModel();
|
|
498
507
|
|
|
508
|
+
// Ensure initialRun is true for the first refresh
|
|
509
|
+
imported.initialRun = true;
|
|
499
510
|
imported.refresh(true);
|
|
500
511
|
},
|
|
501
512
|
{ once: true },
|
|
@@ -535,15 +546,15 @@ export default class FxControl extends XfAbstractControl {
|
|
|
535
546
|
return this.querySelector('template');
|
|
536
547
|
}
|
|
537
548
|
|
|
538
|
-
async refresh(force) {
|
|
539
|
-
|
|
549
|
+
async refresh(force = false) {
|
|
550
|
+
console.log('🔄 fx-control refresh', this);
|
|
540
551
|
super.refresh(force);
|
|
541
552
|
// console.log('refresh template', this.template);
|
|
542
553
|
// const {widget} = this;
|
|
543
554
|
|
|
544
555
|
// ### if we find a ref on control we have a 'select' control of some kind
|
|
545
556
|
const widget = this.getWidget();
|
|
546
|
-
this._handleBoundWidget(widget);
|
|
557
|
+
this._handleBoundWidget(widget, force);
|
|
547
558
|
this._handleDataAttributeBinding();
|
|
548
559
|
Fore.refreshChildren(this, force);
|
|
549
560
|
}
|
|
@@ -558,7 +569,7 @@ export default class FxControl extends XfAbstractControl {
|
|
|
558
569
|
if (dataRefd && dataRefd.closest('fx-control') === this) {
|
|
559
570
|
this.boundList = dataRefd;
|
|
560
571
|
const ref = dataRefd.getAttribute('data-ref');
|
|
561
|
-
this._handleBoundWidget(dataRefd);
|
|
572
|
+
this._handleBoundWidget(dataRefd, true); //todo: revisit !!! observer
|
|
562
573
|
}
|
|
563
574
|
}
|
|
564
575
|
|
|
@@ -570,7 +581,8 @@ export default class FxControl extends XfAbstractControl {
|
|
|
570
581
|
* @param widget the widget to handle
|
|
571
582
|
* @private
|
|
572
583
|
*/
|
|
573
|
-
_handleBoundWidget(widget) {
|
|
584
|
+
_handleBoundWidget(widget, force = false) {
|
|
585
|
+
// console.log('_handleBoundWidget', widget);
|
|
574
586
|
if (this.boundInitialized && this.static) return;
|
|
575
587
|
|
|
576
588
|
const ref = widget.hasAttribute('ref')
|
|
@@ -581,15 +593,24 @@ export default class FxControl extends XfAbstractControl {
|
|
|
581
593
|
// ### eval nodeset for list control
|
|
582
594
|
const inscope = getInScopeContext(this, ref);
|
|
583
595
|
// const nodeset = evaluateXPathToNodes(ref, inscope, this);
|
|
584
|
-
const nodeset = evaluateXPath(ref, inscope, this);
|
|
585
596
|
|
|
586
|
-
|
|
587
|
-
const
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
597
|
+
const touchedNodes = new Set();
|
|
598
|
+
const domFacade = new DependencyNotifyingDomFacade(node => touchedNodes.add(node));
|
|
599
|
+
|
|
600
|
+
const nodeset = evaluateXPath(ref, inscope, this, domFacade);
|
|
601
|
+
|
|
602
|
+
const contextNode = Array.isArray(inscope) ? inscope[0] : inscope;
|
|
603
|
+
// console.log('Extracting model', this.getModel());
|
|
604
|
+
// console.log('Extracting model inited', this.getModel().inited);
|
|
605
|
+
const model = this.getModel();
|
|
606
|
+
if (!contextNode) return;
|
|
607
|
+
// console.log('Extracting predicate deps from ref:', ref);
|
|
608
|
+
extractPredicateDependencies(
|
|
609
|
+
ref,
|
|
610
|
+
model,
|
|
611
|
+
mi => mi.addObserver(this),
|
|
612
|
+
node => model.getModelItem(node),
|
|
613
|
+
);
|
|
593
614
|
|
|
594
615
|
// ### bail out when nodeset is array and empty
|
|
595
616
|
if (Array.isArray(nodeset) && nodeset.length === 0) return;
|
|
@@ -597,6 +618,19 @@ export default class FxControl extends XfAbstractControl {
|
|
|
597
618
|
// ### build the items
|
|
598
619
|
const { template } = this;
|
|
599
620
|
if (template) {
|
|
621
|
+
// ### clear items
|
|
622
|
+
// if (force === true || !this.boundInitialized) {
|
|
623
|
+
if (force === true) {
|
|
624
|
+
const { children } = widget;
|
|
625
|
+
Array.from(children).forEach(child => {
|
|
626
|
+
if (child.nodeName.toLowerCase() !== 'template') {
|
|
627
|
+
child.parentNode.removeChild(child);
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
} else {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
|
|
600
634
|
// ### handle 'selection' open and insert an empty option in that case
|
|
601
635
|
if (
|
|
602
636
|
this.widget.nodeName === 'SELECT' &&
|
|
@@ -656,7 +690,7 @@ export default class FxControl extends XfAbstractControl {
|
|
|
656
690
|
|
|
657
691
|
const valueExpr = valueAttribute;
|
|
658
692
|
const cutted = valueExpr.substring(1, valueExpr.length - 1);
|
|
659
|
-
const evaluated = evaluateXPathToString(cutted, node,
|
|
693
|
+
const evaluated = evaluateXPathToString(cutted, node, this);
|
|
660
694
|
newEntry.setAttribute('value', evaluated);
|
|
661
695
|
|
|
662
696
|
if (this.value === evaluated) {
|
|
@@ -675,8 +709,14 @@ export default class FxControl extends XfAbstractControl {
|
|
|
675
709
|
// ### <<< needs rework
|
|
676
710
|
}
|
|
677
711
|
|
|
712
|
+
/**
|
|
713
|
+
*
|
|
714
|
+
* @param {string} optionLabel
|
|
715
|
+
* @param {Node} node
|
|
716
|
+
* @param {HTMLElement} newEntry
|
|
717
|
+
*/
|
|
678
718
|
evalLabel(optionLabel, node, newEntry) {
|
|
679
|
-
const labelExpr = optionLabel.substring(1, optionLabel.length - 1);
|
|
719
|
+
const labelExpr = optionLabel.trim().substring(1, optionLabel.length - 1);
|
|
680
720
|
if (!labelExpr) return;
|
|
681
721
|
|
|
682
722
|
const label = evaluateXPathToString(labelExpr, node, this);
|
package/src/ui/fx-items.js
CHANGED
|
@@ -60,11 +60,13 @@ export class FxItems extends FxControl {
|
|
|
60
60
|
e.preventDefault;
|
|
61
61
|
e.stopPropagation();
|
|
62
62
|
const items = this.querySelectorAll('[value]');
|
|
63
|
+
/*
|
|
63
64
|
let target;
|
|
64
65
|
if (e.target.nodeName === 'LABEL') {
|
|
65
66
|
target = resolveId(e.target.getAttribute('for'), this);
|
|
66
67
|
target.checked = !target.checked;
|
|
67
68
|
}
|
|
69
|
+
*/
|
|
68
70
|
|
|
69
71
|
let val = '';
|
|
70
72
|
Array.from(items).forEach(item => {
|
|
@@ -145,12 +147,16 @@ export class FxItems extends FxControl {
|
|
|
145
147
|
evaluated = node[cutted];
|
|
146
148
|
}
|
|
147
149
|
|
|
148
|
-
// adding space around value to allow matching of 'words'
|
|
149
|
-
const spaced = ` ${evaluated} `;
|
|
150
|
-
const valAttr = ` ${this.getAttribute('value')} `;
|
|
151
150
|
input.value = evaluated;
|
|
152
151
|
input.setAttribute('id', id);
|
|
153
|
-
|
|
152
|
+
// Normalize the current value (remove newlines, tabs, excessive spaces)
|
|
153
|
+
const currentValue = (this.getAttribute('value') || '').replace(/\s+/g, ' ').trim();
|
|
154
|
+
const valueList = currentValue.split(/\s+/); // Split on whitespace
|
|
155
|
+
|
|
156
|
+
// Check if the value is in the space-separated list of values
|
|
157
|
+
// const currentValue = this.getAttribute('value') || '';
|
|
158
|
+
// const valueList = currentValue.split(/\s+/);
|
|
159
|
+
if (valueList.includes(evaluated)) {
|
|
154
160
|
input.checked = true;
|
|
155
161
|
}
|
|
156
162
|
}
|