@jinntec/fore 2.2.0 → 2.3.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/README.md +1 -1
- package/dist/fore-dev.js +2 -2
- package/dist/fore-dev.js.map +1 -1
- package/dist/fore.js +2 -2
- package/dist/fore.js.map +1 -1
- package/index.js +3 -2
- package/package.json +10 -38
- package/resources/fore.css +263 -262
- package/resources/vars.css +8 -0
- package/src/DependencyNotifyingDomFacade.js +1 -0
- package/src/ForeElementMixin.js +243 -228
- package/src/actions/abstract-action.js +407 -398
- package/src/actions/fx-action.js +6 -6
- package/src/actions/fx-append.js +1 -1
- package/src/actions/fx-call.js +63 -62
- package/src/actions/fx-confirm.js +11 -11
- package/src/actions/fx-construct-done.js +4 -4
- package/src/actions/fx-copy.js +36 -36
- package/src/actions/fx-delete.js +77 -79
- package/src/actions/fx-dispatch.js +14 -14
- package/src/actions/fx-hide.js +15 -15
- package/src/actions/fx-insert.js +9 -12
- package/src/actions/fx-insertchild.js +88 -0
- package/src/actions/fx-load.js +188 -185
- package/src/actions/fx-message.js +18 -19
- package/src/actions/fx-refresh.js +10 -10
- package/src/actions/fx-reload.js +16 -14
- package/src/actions/fx-replace.js +0 -1
- package/src/actions/fx-reset.js +31 -31
- package/src/actions/fx-send.js +59 -52
- package/src/actions/fx-setattribute.js +48 -49
- package/src/actions/fx-setfocus.js +54 -58
- package/src/actions/fx-setvalue.js +85 -81
- package/src/actions/fx-show.js +11 -11
- package/src/actions/fx-toggle.js +2 -2
- package/src/actions/fx-toggleboolean.js +38 -39
- package/src/actions/fx-unmodified.js +27 -0
- package/src/actions/fx-update.js +6 -6
- package/src/dep_graph.js +1 -1
- package/src/drawdown.js +23 -30
- package/src/events.js +19 -19
- package/src/fore.js +143 -125
- package/src/functions/common-function.js +24 -25
- package/src/functions/fx-function.js +4 -6
- package/src/functions/fx-functionlib.js +39 -42
- package/src/functions/registerFunction.js +95 -86
- package/src/fx-bind.js +19 -24
- package/src/fx-connection.js +226 -0
- package/src/fx-fore.js +846 -815
- package/src/fx-header.js +4 -4
- package/src/fx-instance.js +23 -27
- package/src/fx-model.js +405 -383
- package/src/fx-submission.js +399 -397
- package/src/fx-var.js +6 -5
- package/src/getInScopeContext.js +102 -89
- package/src/json-util.js +24 -24
- package/src/lab/fore-component.js +48 -52
- package/src/lab/instance-inspector.js +13 -16
- package/src/modelitem.js +48 -10
- package/src/relevance.js +12 -16
- package/src/tools/adi.js +858 -812
- package/src/tools/fx-action-log.js +377 -295
- package/src/tools/fx-devtools.js +210 -210
- package/src/tools/fx-dom-inspector.js +123 -122
- package/src/tools/fx-json-instance.js +262 -253
- package/src/tools/fx-log-item.js +6 -11
- package/src/tools/fx-log-settings.js +358 -301
- package/src/tools/fx-minimap.js +186 -177
- package/src/tools/helpers.js +1 -1
- package/src/ui/abstract-control.js +74 -56
- package/src/ui/fx-case.js +59 -7
- package/src/ui/fx-container.js +13 -14
- package/src/ui/fx-control.js +572 -538
- package/src/ui/fx-dialog.js +3 -3
- package/src/ui/fx-droptarget.js +3 -4
- package/src/ui/fx-group.js +4 -2
- package/src/ui/fx-hint.js +3 -0
- package/src/ui/fx-inspector.js +5 -5
- package/src/ui/fx-items.js +11 -11
- package/src/ui/fx-output.js +10 -12
- package/src/ui/fx-repeat-attributes.js +23 -27
- package/src/ui/fx-repeat.js +347 -344
- package/src/ui/fx-repeatitem.js +75 -68
- package/src/ui/fx-switch.js +39 -14
- package/src/ui/fx-trigger.js +4 -4
- package/src/withDraggability.js +193 -191
- package/src/xpath-evaluation.js +969 -959
- package/src/xpath-util.js +161 -134
package/src/ui/fx-control.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import XfAbstractControl from './abstract-control.js';
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
evaluateXPath,
|
|
4
|
+
evaluateXPathToString,
|
|
5
|
+
evaluateXPathToFirstNode,
|
|
6
6
|
} from '../xpath-evaluation.js';
|
|
7
7
|
import getInScopeContext from '../getInScopeContext.js';
|
|
8
|
-
import {Fore} from '../fore.js';
|
|
9
|
-
import {ModelItem} from
|
|
10
|
-
import {debounce} from
|
|
11
|
-
import {FxModel} from
|
|
8
|
+
import { Fore } from '../fore.js';
|
|
9
|
+
import { ModelItem } from '../modelitem.js';
|
|
10
|
+
import { debounce } from '../events.js';
|
|
11
|
+
import { FxModel } from '../fx-model.js';
|
|
12
12
|
|
|
13
13
|
const WIDGETCLASS = 'widget';
|
|
14
14
|
|
|
@@ -34,139 +34,147 @@ function debounce( func, timeout = 300) {
|
|
|
34
34
|
}
|
|
35
35
|
*/
|
|
36
36
|
export default class FxControl extends XfAbstractControl {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
constructor() {
|
|
38
|
+
super();
|
|
39
|
+
this.inited = false;
|
|
40
|
+
this.attachShadow({ mode: 'open' });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static get properties() {
|
|
44
|
+
return {
|
|
45
|
+
...XfAbstractControl.properties,
|
|
46
|
+
credentials: {
|
|
47
|
+
type: String,
|
|
48
|
+
},
|
|
49
|
+
initial: {
|
|
50
|
+
type: Boolean,
|
|
51
|
+
},
|
|
52
|
+
src: {
|
|
53
|
+
type: String,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
_getValueFromHtmlDom() {
|
|
59
|
+
if (this.valueProp === 'selectedOptions') {
|
|
60
|
+
// We have multiple! Just return that as space-separated for now
|
|
61
|
+
return [...this.widget.selectedOptions].map(option => option.value).join(' ');
|
|
41
62
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
_getValueFromHtmlDom() {
|
|
59
|
-
if (this.valueProp === 'selectedOptions') {
|
|
60
|
-
// We have multiple! Just return that as space-separated for now
|
|
61
|
-
return [...this.widget.selectedOptions].map(option => option.value).join(' ');
|
|
62
|
-
}
|
|
63
|
-
return this.widget[this.valueProp];
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
connectedCallback() {
|
|
67
|
-
this.initial = this.hasAttribute('initial') ? this.getAttribute('initial') : null;
|
|
68
|
-
this.src = this.hasAttribute('src') ? this.getAttribute('src') : null;
|
|
69
|
-
this.loaded = false;
|
|
70
|
-
this.initialNode = null;
|
|
71
|
-
this.debounceDelay = this.hasAttribute('debounce') ? this.getAttribute('debounce') : null;
|
|
72
|
-
|
|
73
|
-
this.updateEvent = this.hasAttribute('update-event')
|
|
74
|
-
? this.getAttribute('update-event')
|
|
75
|
-
: 'blur';
|
|
76
|
-
this.label = this.hasAttribute('label') ? this.getAttribute('label') : null;
|
|
77
|
-
const style = `
|
|
63
|
+
return this.widget[this.valueProp];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
connectedCallback() {
|
|
67
|
+
this.initial = this.hasAttribute('initial') ? this.getAttribute('initial') : null;
|
|
68
|
+
this.src = this.hasAttribute('src') ? this.getAttribute('src') : null;
|
|
69
|
+
this.loaded = false;
|
|
70
|
+
this.initialNode = null;
|
|
71
|
+
this.debounceDelay = this.hasAttribute('debounce') ? this.getAttribute('debounce') : null;
|
|
72
|
+
|
|
73
|
+
this.updateEvent = this.hasAttribute('update-event')
|
|
74
|
+
? this.getAttribute('update-event')
|
|
75
|
+
: 'blur';
|
|
76
|
+
this.label = this.hasAttribute('label') ? this.getAttribute('label') : null;
|
|
77
|
+
const style = `
|
|
78
78
|
:host{
|
|
79
79
|
display:inline-block;
|
|
80
80
|
}
|
|
81
81
|
`;
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
83
|
+
this.credentials = this.hasAttribute('credentials')
|
|
84
|
+
? this.getAttribute('credentials')
|
|
85
|
+
: 'same-origin';
|
|
86
|
+
if (!['same-origin', 'include', 'omit'].includes(this.credentials)) {
|
|
87
|
+
console.error(
|
|
88
|
+
`fx-submission: the value of credentials is not valid. Expected 'same-origin', 'include' or 'omit' but got '${this.credentials}'`,
|
|
89
|
+
this,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
89
92
|
|
|
90
|
-
|
|
93
|
+
this.shadowRoot.innerHTML = `
|
|
91
94
|
<style>
|
|
92
95
|
${style}
|
|
93
96
|
</style>
|
|
94
97
|
${this.renderHTML(this.ref)}
|
|
95
98
|
`;
|
|
96
99
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
this.addEventListener('keyup', () => {
|
|
128
|
-
FxModel.dataChanged = true;
|
|
129
|
-
});
|
|
100
|
+
this.widget = this.getWidget();
|
|
101
|
+
|
|
102
|
+
this.addEventListener('mousedown', e => {
|
|
103
|
+
// ### prevent mousedown events on all control content that is not the widget or within the widget
|
|
104
|
+
if (!Fore.isWidget(e.target) && !e.target?.classList.contains('fx-hint')) {
|
|
105
|
+
e.preventDefault();
|
|
106
|
+
// e.stopImmediatePropagation();
|
|
107
|
+
}
|
|
108
|
+
this.widget.focus();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const defaultValueProp = this.widget.hasAttribute('multiple') ? 'selectedOptions' : 'value';
|
|
112
|
+
this.valueProp = this.hasAttribute('value-prop')
|
|
113
|
+
? this.getAttribute('value-prop')
|
|
114
|
+
: defaultValueProp;
|
|
115
|
+
|
|
116
|
+
// console.log('widget ', this.widget);
|
|
117
|
+
let listenOn = this.widget; // default: usually listening on widget
|
|
118
|
+
|
|
119
|
+
if (this.hasAttribute('listen-on')) {
|
|
120
|
+
const q = this.getAttribute('listen-on');
|
|
121
|
+
const target = this.querySelector(q);
|
|
122
|
+
if (target) {
|
|
123
|
+
listenOn = target;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
130
126
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (this.debounceDelay) {
|
|
144
|
-
listenOn.addEventListener(
|
|
145
|
-
this.updateEvent,
|
|
146
|
-
debounce(this, () => {
|
|
147
|
-
// console.log('eventlistener ', this.updateEvent);
|
|
148
|
-
// console.info('handling Event:', event.type, listenOn);
|
|
149
|
-
this.setValue(this._getValueFromHtmlDom());
|
|
150
|
-
}, this.debounceDelay),
|
|
151
|
-
);
|
|
152
|
-
} else {
|
|
153
|
-
listenOn.addEventListener(this.updateEvent, (event) => {
|
|
154
|
-
this.setValue(this._getValueFromHtmlDom());
|
|
155
|
-
});
|
|
156
|
-
listenOn.addEventListener('blur', (event) => {
|
|
157
|
-
this.setValue(this._getValueFromHtmlDom());
|
|
158
|
-
},{once:true});
|
|
127
|
+
this.addEventListener('keyup', () => {
|
|
128
|
+
FxModel.dataChanged = true;
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// ### convenience marker event
|
|
132
|
+
if (this.updateEvent === 'enter') {
|
|
133
|
+
this.widget.addEventListener('keyup', event => {
|
|
134
|
+
if (event.keyCode === 13) {
|
|
135
|
+
// console.info('handling Event:', event.type, listenOn);
|
|
136
|
+
// Cancel the default action, if needed
|
|
137
|
+
event.preventDefault();
|
|
138
|
+
this.setValue(this._getValueFromHtmlDom());
|
|
159
139
|
}
|
|
140
|
+
});
|
|
141
|
+
this.updateEvent = 'blur'; // needs to be registered too
|
|
142
|
+
}
|
|
143
|
+
if (this.debounceDelay) {
|
|
144
|
+
listenOn.addEventListener(
|
|
145
|
+
this.updateEvent,
|
|
146
|
+
debounce(
|
|
147
|
+
this,
|
|
148
|
+
() => {
|
|
149
|
+
// console.log('eventlistener ', this.updateEvent);
|
|
150
|
+
// console.info('handling Event:', event.type, listenOn);
|
|
151
|
+
this.setValue(this._getValueFromHtmlDom());
|
|
152
|
+
},
|
|
153
|
+
this.debounceDelay,
|
|
154
|
+
),
|
|
155
|
+
);
|
|
156
|
+
} else {
|
|
157
|
+
listenOn.addEventListener(this.updateEvent, event => {
|
|
158
|
+
this.setValue(this._getValueFromHtmlDom());
|
|
159
|
+
});
|
|
160
|
+
listenOn.addEventListener(
|
|
161
|
+
'blur',
|
|
162
|
+
event => {
|
|
163
|
+
this.setValue(this._getValueFromHtmlDom());
|
|
164
|
+
},
|
|
165
|
+
{ once: true },
|
|
166
|
+
);
|
|
167
|
+
}
|
|
160
168
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
169
|
+
this.addEventListener('return', e => {
|
|
170
|
+
// console.log('catched return action on ', this);
|
|
171
|
+
// console.log('return detail', e.detail);
|
|
164
172
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
173
|
+
// console.log('return triggered on ', this);
|
|
174
|
+
// console.log('this.ref', this.ref);
|
|
175
|
+
// console.log('current outer instance', this.getInstance());
|
|
168
176
|
|
|
169
|
-
|
|
177
|
+
/*
|
|
170
178
|
console.log(
|
|
171
179
|
'???? why ???? current nodeset should point to the node of the outer control',
|
|
172
180
|
e.currentTarget.nodeset,
|
|
@@ -176,478 +184,504 @@ export default class FxControl extends XfAbstractControl {
|
|
|
176
184
|
this.nodeset,
|
|
177
185
|
);
|
|
178
186
|
*/
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
187
|
+
const newNodes = e.detail.nodeset;
|
|
188
|
+
// console.log('new nodeset', newNodes);
|
|
189
|
+
// console.log('currentTarget', e.currentTarget);
|
|
190
|
+
// console.log('target', e.target);
|
|
183
191
|
|
|
184
|
-
|
|
192
|
+
e.stopPropagation();
|
|
185
193
|
|
|
186
|
-
|
|
187
|
-
|
|
194
|
+
this._replaceNode(newNodes);
|
|
195
|
+
});
|
|
188
196
|
|
|
189
|
-
|
|
190
|
-
/*
|
|
197
|
+
this.widget.addEventListener('focus', () => {
|
|
198
|
+
/*
|
|
191
199
|
if (!this.classList.contains('visited')) {
|
|
192
200
|
this.classList.add('visited');
|
|
193
201
|
}
|
|
194
202
|
*/
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
this.template = this.querySelector('template');
|
|
206
|
+
this.boundInitialized = false;
|
|
207
|
+
this.static = !!this.widget.hasAttribute('static');
|
|
208
|
+
// console.log('template',this.template);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
_debounce(func, timeout = 300) {
|
|
212
|
+
let timer;
|
|
213
|
+
return (...args) => {
|
|
214
|
+
const context = this;
|
|
215
|
+
clearTimeout(timer);
|
|
216
|
+
timer = setTimeout(() => {
|
|
217
|
+
func.apply(context, args);
|
|
218
|
+
}, timeout);
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* updates the model with a new value by executing it's `<fx-setvalue>` action.
|
|
224
|
+
*
|
|
225
|
+
* In case the `as='node'` is given the bound node is replaced with the widgets' value with is
|
|
226
|
+
* expected to be a node again.
|
|
227
|
+
*
|
|
228
|
+
* @param val the new value to be set
|
|
229
|
+
*/
|
|
230
|
+
setValue(val) {
|
|
231
|
+
const modelitem = this.getModelItem();
|
|
232
|
+
|
|
233
|
+
if (this.getAttribute('class')) {
|
|
234
|
+
this.classList.add('visited');
|
|
235
|
+
} else {
|
|
236
|
+
this.setAttribute('class', 'visited');
|
|
201
237
|
}
|
|
202
238
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const context = this;
|
|
207
|
-
clearTimeout(timer);
|
|
208
|
-
timer = setTimeout(() => {
|
|
209
|
-
func.apply(context, args);
|
|
210
|
-
}, timeout);
|
|
211
|
-
};
|
|
239
|
+
if (modelitem?.readonly) {
|
|
240
|
+
console.warn('attempt to change readonly node', modelitem);
|
|
241
|
+
return; // do nothing when modelItem is readonly
|
|
212
242
|
}
|
|
213
243
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}else{
|
|
228
|
-
this.setAttribute('class','visited');
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (modelitem?.readonly) {
|
|
232
|
-
console.warn('attempt to change readonly node', modelitem);
|
|
233
|
-
return; // do nothing when modelItem is readonly
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
if (this.getAttribute('as') === 'node') {
|
|
237
|
-
const replace = this.shadowRoot.getElementById('replace');
|
|
238
|
-
const widgetValue = this.getWidget()[this.valueProp];
|
|
239
|
-
replace.replace(this.nodeset, widgetValue);
|
|
240
|
-
if (modelitem && widgetValue && widgetValue !== modelitem.value) {
|
|
241
|
-
modelitem.value = widgetValue;
|
|
242
|
-
FxModel.dataChanged = true;
|
|
243
|
-
replace.actionPerformed();
|
|
244
|
-
}
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
const setval = this.shadowRoot.getElementById('setvalue');
|
|
248
|
-
setval.setValue(modelitem, val);
|
|
249
|
-
|
|
250
|
-
if (this.modelItem instanceof ModelItem && !this.modelItem?.boundControls.includes(this)) {
|
|
251
|
-
this.modelItem.boundControls.push(this);
|
|
252
|
-
}
|
|
244
|
+
if (this.getAttribute('as') === 'node') {
|
|
245
|
+
const replace = this.shadowRoot.getElementById('replace');
|
|
246
|
+
const widgetValue = this.getWidget()[this.valueProp];
|
|
247
|
+
replace.replace(this.nodeset, widgetValue);
|
|
248
|
+
if (modelitem && widgetValue && widgetValue !== modelitem.value) {
|
|
249
|
+
modelitem.value = widgetValue;
|
|
250
|
+
FxModel.dataChanged = true;
|
|
251
|
+
replace.actionPerformed();
|
|
252
|
+
}
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const setval = this.shadowRoot.getElementById('setvalue');
|
|
256
|
+
setval.setValue(modelitem, val);
|
|
253
257
|
|
|
254
|
-
|
|
255
|
-
|
|
258
|
+
if (this.modelItem instanceof ModelItem && !this.modelItem?.boundControls.includes(this)) {
|
|
259
|
+
this.modelItem.boundControls.push(this);
|
|
256
260
|
}
|
|
257
261
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
262
|
+
setval.actionPerformed();
|
|
263
|
+
// this.visited = true;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
_replaceNode(node) {
|
|
267
|
+
// Note: clone the node while replacing to prevent the instances to leak through
|
|
268
|
+
if (node.nodeType === Node.ATTRIBUTE_NODE) {
|
|
269
|
+
this.modelItem.node.nodeValue = node.nodeValue;
|
|
270
|
+
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
271
|
+
this.modelItem.node.replaceWith(node.cloneNode(true));
|
|
272
|
+
} else if (node.nodeType === Node.TEXT_NODE) {
|
|
273
|
+
this.modelItem.node.nodeValue = node.textContent;
|
|
274
|
+
} else {
|
|
275
|
+
Fore.dispatch(this, 'warn', {
|
|
276
|
+
message: 'trying to replace a node that is neither an Attribute, Elemment or Text node',
|
|
277
|
+
});
|
|
270
278
|
}
|
|
279
|
+
// this.getOwnerForm().refresh();
|
|
280
|
+
}
|
|
271
281
|
|
|
272
|
-
|
|
273
|
-
|
|
282
|
+
renderHTML(ref) {
|
|
283
|
+
return `
|
|
274
284
|
${this.label ? `${this.label}` : ''}
|
|
275
285
|
<slot></slot>
|
|
276
286
|
${
|
|
277
|
-
|
|
278
|
-
?
|
|
287
|
+
this.hasAttribute('as') && this.getAttribute('as') === 'node'
|
|
288
|
+
? '<fx-replace id="replace" ref=".">'
|
|
279
289
|
: `<fx-setvalue id="setvalue" ref="${ref}"></fx-setvalue>`
|
|
280
|
-
|
|
290
|
+
}
|
|
281
291
|
|
|
282
292
|
`;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* The widget is the actual control being used in the UI e.g. a native input control or any
|
|
297
|
+
* other component that presents a control that can be interacted with.
|
|
298
|
+
*
|
|
299
|
+
* This function returns the widget by querying the children of this control for an element
|
|
300
|
+
* with `class="widget"`. If that cannot be found it searches for an native `input` of any type.
|
|
301
|
+
* If either cannot be found a `<input type="text">` is created.
|
|
302
|
+
*
|
|
303
|
+
* @returns {HTMLElement|*}
|
|
304
|
+
*/
|
|
305
|
+
getWidget() {
|
|
306
|
+
if (this.widget) return this.widget;
|
|
307
|
+
let widget = this.querySelector(`.${WIDGETCLASS}`);
|
|
308
|
+
if (!widget) {
|
|
309
|
+
widget = this.querySelector('input');
|
|
310
|
+
if (widget && !widget.classList.contains('widget')) {
|
|
311
|
+
widget.classList.add('widget');
|
|
312
|
+
}
|
|
283
313
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
314
|
+
if (!widget) {
|
|
315
|
+
const input = document.createElement('input');
|
|
316
|
+
input.classList.add(WIDGETCLASS);
|
|
317
|
+
input.setAttribute('type', 'text');
|
|
318
|
+
this.appendChild(input);
|
|
319
|
+
return input;
|
|
320
|
+
}
|
|
321
|
+
return widget;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* updates the widget from the modelItem value. During refresh the a control
|
|
326
|
+
* evaluates it's binding expression to determine the bound node. The bound node corresponds
|
|
327
|
+
* to a modelItem which acts a the state object of a node. The modelItem determines the value
|
|
328
|
+
* and the state of the node and set the `value` property of this class.
|
|
329
|
+
*
|
|
330
|
+
* @returns {Promise<void>}
|
|
331
|
+
*/
|
|
332
|
+
async updateWidgetValue() {
|
|
333
|
+
// this._getValueFromHtmlDom() = this.value;
|
|
334
|
+
|
|
335
|
+
let { widget } = this;
|
|
336
|
+
if (!widget) {
|
|
337
|
+
widget = this;
|
|
338
|
+
}
|
|
339
|
+
// ### value is bound to checkbox
|
|
340
|
+
if (this.valueProp === 'checked') {
|
|
341
|
+
if (this.value === 'true') {
|
|
342
|
+
widget.checked = true;
|
|
343
|
+
} else {
|
|
344
|
+
widget.checked = false;
|
|
345
|
+
}
|
|
346
|
+
return;
|
|
312
347
|
}
|
|
313
348
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
async updateWidgetValue() {
|
|
323
|
-
// this._getValueFromHtmlDom() = this.value;
|
|
324
|
-
|
|
325
|
-
let {widget} = this;
|
|
326
|
-
if (!widget) {
|
|
327
|
-
widget = this;
|
|
328
|
-
}
|
|
329
|
-
// ### value is bound to checkbox
|
|
330
|
-
if (this.valueProp === 'checked') {
|
|
331
|
-
if (this.value === 'true') {
|
|
332
|
-
widget.checked = true;
|
|
333
|
-
} else {
|
|
334
|
-
widget.checked = false;
|
|
335
|
-
}
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
349
|
+
// ### value is bound to radio
|
|
350
|
+
if(this.widget.type === 'radio'){
|
|
351
|
+
const matches = this.querySelector(`input[value=${this.value}]`);
|
|
352
|
+
if(matches){
|
|
353
|
+
matches.checked = true;
|
|
354
|
+
}
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
338
357
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
return;
|
|
358
|
+
if (this.valueProp === 'selectedOptions') {
|
|
359
|
+
const valueSet = new Set(this.value.split(' '));
|
|
360
|
+
for (const option of [...this.widget.querySelectorAll('option')]) {
|
|
361
|
+
if (valueSet.has(option.value)) {
|
|
362
|
+
option.selected = true;
|
|
363
|
+
} else {
|
|
364
|
+
option.selected = false;
|
|
349
365
|
}
|
|
366
|
+
}
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
350
369
|
|
|
351
|
-
if (this.hasAttribute('as')) {
|
|
352
|
-
const as = this.getAttribute('as');
|
|
353
|
-
|
|
354
|
-
// ### when there's an `as=text` attribute serialize nodeset to prettified string
|
|
355
|
-
if (as === 'text') {
|
|
356
|
-
const serializer = new XMLSerializer();
|
|
357
|
-
const pretty = Fore.prettifyXml(serializer.serializeToString(this.nodeset));
|
|
358
|
-
widget.value = pretty;
|
|
359
|
-
}
|
|
360
|
-
if (as === 'node' && this.nodeset !== widget.value) {
|
|
361
|
-
// const oldVal = this.nodeset.innerHTML;
|
|
362
|
-
const oldVal = this.nodeset;
|
|
363
|
-
if (widget.value) {
|
|
364
|
-
if (oldVal !== this.widget.value) {
|
|
365
|
-
// console.log('changed');
|
|
366
|
-
widget.value = this.nodeset.cloneNode(true);
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
widget.value = this.nodeset.cloneNode(true);
|
|
372
|
-
// todo: should be more like below but that can cause infinite loop when controll trigger update event due to calling a setter for property
|
|
373
|
-
// widget[this.valueProp] = this.nodeset.cloneNode(true);
|
|
374
|
-
// console.log('passed value to widget', widget.value);
|
|
375
|
-
}
|
|
376
370
|
|
|
371
|
+
if (this.hasAttribute('as')) {
|
|
372
|
+
const as = this.getAttribute('as');
|
|
373
|
+
|
|
374
|
+
// ### when there's an `as=text` attribute serialize nodeset to prettified string
|
|
375
|
+
if (as === 'text') {
|
|
376
|
+
const serializer = new XMLSerializer();
|
|
377
|
+
const pretty = Fore.prettifyXml(serializer.serializeToString(this.nodeset));
|
|
378
|
+
widget.value = pretty;
|
|
379
|
+
}
|
|
380
|
+
if (as === 'node' && this.nodeset !== widget.value) {
|
|
381
|
+
// const oldVal = this.nodeset.innerHTML;
|
|
382
|
+
const oldVal = this.nodeset;
|
|
383
|
+
if (widget.value) {
|
|
384
|
+
if (oldVal !== this.widget.value) {
|
|
385
|
+
// console.log('changed');
|
|
386
|
+
widget.value = this.nodeset.cloneNode(true);
|
|
377
387
|
return;
|
|
388
|
+
}
|
|
378
389
|
}
|
|
379
390
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
391
|
+
widget.value = this.nodeset.cloneNode(true);
|
|
392
|
+
// todo: should be more like below but that can cause infinite loop when controll trigger update event due to calling a setter for property
|
|
393
|
+
// widget[this.valueProp] = this.nodeset.cloneNode(true);
|
|
394
|
+
// console.log('passed value to widget', widget.value);
|
|
395
|
+
}
|
|
383
396
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
// console.log('initialNodes', this.initialNode);
|
|
387
|
-
}
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
388
399
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
400
|
+
// ### when there's a src Fore is used as widget and will be loaded from external file
|
|
401
|
+
if (this.src && !this.loaded && this.modelItem.relevant) {
|
|
402
|
+
// ### evaluate initial data if necessary
|
|
392
403
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
}
|
|
404
|
+
if (this.initial) {
|
|
405
|
+
this.initialNode = evaluateXPathToFirstNode(this.initial, this.nodeset, this);
|
|
406
|
+
// console.log('initialNodes', this.initialNode);
|
|
407
|
+
}
|
|
398
408
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
409
|
+
// ### load the markup from src
|
|
410
|
+
await this._loadForeFromSrc();
|
|
411
|
+
this.loaded = true;
|
|
412
|
+
|
|
413
|
+
// ### replace default instance of embedded Fore with initial nodes
|
|
414
|
+
// const innerInstance = this.querySelector('fx-instance');
|
|
415
|
+
// console.log('innerInstance',innerInstance);
|
|
416
|
+
return;
|
|
402
417
|
}
|
|
403
418
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
419
|
+
if (widget.value !== this.value) {
|
|
420
|
+
widget.value = this.value;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* loads an external Fore from an HTML file given by `src` attribute and embed it as child of this control.
|
|
426
|
+
*
|
|
427
|
+
* Will look for the `<fx-fore>` element within the returned HTML file and return that element.
|
|
428
|
+
*
|
|
429
|
+
* If that cannot be found an error is dispatched.
|
|
430
|
+
*
|
|
431
|
+
* todo: dispatch link error
|
|
432
|
+
* @private
|
|
433
|
+
*/
|
|
434
|
+
async _loadForeFromSrc() {
|
|
435
|
+
console.info(
|
|
436
|
+
`%cControl ref="${this.ref}" is loading ${this.src}`,
|
|
437
|
+
'background:#64b5f6; color:white; padding:0.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;',
|
|
438
|
+
);
|
|
439
|
+
try {
|
|
440
|
+
const response = await fetch(this.src, {
|
|
441
|
+
method: 'GET',
|
|
442
|
+
credentials: this.credentials,
|
|
443
|
+
mode: 'cors',
|
|
444
|
+
headers: {
|
|
445
|
+
'Content-Type': 'text/html',
|
|
446
|
+
},
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
const responseContentType = response.headers.get('content-type').toLowerCase();
|
|
450
|
+
// console.log('********** responseContentType *********', responseContentType);
|
|
451
|
+
let data;
|
|
452
|
+
if (responseContentType.startsWith('text/html')) {
|
|
453
|
+
data = await response.text().then(result =>
|
|
454
|
+
// console.log('xml ********', result);
|
|
455
|
+
new DOMParser().parseFromString(result, 'text/html'),
|
|
418
456
|
);
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
const doc = new DOMParser().parseFromString('<data></data>', 'application/xml');
|
|
451
|
-
// Note: Clone the input to prevent the inner fore from editing the outer node
|
|
452
|
-
// Also update the `initialNode` to make sure we have an up-to-date version
|
|
453
|
-
this.initialNode = evaluateXPathToFirstNode(this.initial, this.nodeset, this);
|
|
454
|
-
|
|
455
|
-
doc.firstElementChild.appendChild(this.initialNode.cloneNode(true));
|
|
456
|
-
defaultInst.instanceData = doc;
|
|
457
|
-
}
|
|
458
|
-
imported.model = imported.querySelector('fx-model');
|
|
459
|
-
imported.model.updateModel();
|
|
460
|
-
|
|
461
|
-
imported.refresh(true);
|
|
462
|
-
},
|
|
463
|
-
{once: true},
|
|
464
|
-
);
|
|
465
|
-
|
|
466
|
-
const dummy = this.querySelector('input');
|
|
467
|
-
/*
|
|
457
|
+
} else {
|
|
458
|
+
data = 'done';
|
|
459
|
+
}
|
|
460
|
+
// const theFore = fxEvaluateXPathToFirstNode('//fx-fore', data.firstElementChild);
|
|
461
|
+
const theFore = data.querySelector('fx-fore');
|
|
462
|
+
const imported = document.importNode(theFore, true);
|
|
463
|
+
|
|
464
|
+
imported.classList.add('widget'); // is the new widget
|
|
465
|
+
imported.addEventListener(
|
|
466
|
+
'model-construct-done',
|
|
467
|
+
e => {
|
|
468
|
+
const defaultInst = imported.querySelector('fx-instance');
|
|
469
|
+
if (this.initial) {
|
|
470
|
+
const doc = new DOMParser().parseFromString('<data></data>', 'application/xml');
|
|
471
|
+
// Note: Clone the input to prevent the inner fore from editing the outer node
|
|
472
|
+
// Also update the `initialNode` to make sure we have an up-to-date version
|
|
473
|
+
this.initialNode = evaluateXPathToFirstNode(this.initial, this.nodeset, this);
|
|
474
|
+
|
|
475
|
+
doc.firstElementChild.appendChild(this.initialNode.cloneNode(true));
|
|
476
|
+
defaultInst.instanceData = doc;
|
|
477
|
+
}
|
|
478
|
+
imported.model = imported.querySelector('fx-model');
|
|
479
|
+
imported.model.updateModel();
|
|
480
|
+
|
|
481
|
+
imported.refresh(true);
|
|
482
|
+
},
|
|
483
|
+
{ once: true },
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
const dummy = this.querySelector('input');
|
|
487
|
+
/*
|
|
468
488
|
todo: the mechanism to import constructed stylesheets as in fore-component is still missing here.
|
|
469
489
|
There no way yet to specify CSS for a embedded fx-fore in shadowDOM.
|
|
470
490
|
*/
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
getTemplate() {
|
|
498
|
-
return this.querySelector('template');
|
|
491
|
+
if (this.hasAttribute('shadow')) {
|
|
492
|
+
dummy.parentNode.removeChild(dummy);
|
|
493
|
+
this.shadowRoot.appendChild(imported);
|
|
494
|
+
} else if (!this.loaded) {
|
|
495
|
+
dummy.replaceWith(imported);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (!theFore) {
|
|
499
|
+
Fore.dispatch('error', {
|
|
500
|
+
detail: {
|
|
501
|
+
message: `Fore element not found in '${this.src}'. Maybe wrapped within 'template' element?`,
|
|
502
|
+
},
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
Fore.dispatch('loaded', { detail: { fore: theFore } });
|
|
506
|
+
} catch (error) {
|
|
507
|
+
// console.log('error', error);
|
|
508
|
+
Fore.dispatch(this, 'error', {
|
|
509
|
+
origin: this,
|
|
510
|
+
message: `control couldn't be loaded from src '${this.src}'`,
|
|
511
|
+
level: 'Error',
|
|
512
|
+
});
|
|
499
513
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
getTemplate() {
|
|
517
|
+
return this.querySelector('template');
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
async refresh(force) {
|
|
521
|
+
// console.log('fx-control refresh', this);
|
|
522
|
+
super.refresh(force);
|
|
523
|
+
// console.log('refresh template', this.template);
|
|
524
|
+
// const {widget} = this;
|
|
525
|
+
|
|
526
|
+
// ### if we find a ref on control we have a 'select' control of some kind
|
|
527
|
+
const widget = this.getWidget();
|
|
528
|
+
this._handleBoundWidget(widget);
|
|
529
|
+
this._handleDataAttributeBinding();
|
|
530
|
+
Fore.refreshChildren(this, force);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* handle non-Fore elements like 'select' and 'datalist' which have a 'data-ref' attribute
|
|
535
|
+
* @private
|
|
536
|
+
*/
|
|
537
|
+
_handleDataAttributeBinding() {
|
|
538
|
+
const dataRefd = this.querySelector('[data-ref]');
|
|
539
|
+
// Handle nested fx-controls
|
|
540
|
+
if (dataRefd && dataRefd.closest('fx-control') === this) {
|
|
541
|
+
this.boundList = dataRefd;
|
|
542
|
+
const ref = dataRefd.getAttribute('data-ref');
|
|
543
|
+
this._handleBoundWidget(dataRefd);
|
|
512
544
|
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* If the widget itself has a `ref` it binds to another nodeset to provide some
|
|
549
|
+
* dynamic items to be created from a template usually. Examples are dynamic select option lists
|
|
550
|
+
* or a set of checkboxes.
|
|
551
|
+
*
|
|
552
|
+
* @param widget the widget to handle
|
|
553
|
+
* @private
|
|
554
|
+
*/
|
|
555
|
+
_handleBoundWidget(widget) {
|
|
556
|
+
if (this.boundInitialized && this.static) return;
|
|
557
|
+
|
|
558
|
+
const ref = widget.hasAttribute('ref')
|
|
559
|
+
? widget.getAttribute('ref')
|
|
560
|
+
: widget.getAttribute('data-ref');
|
|
561
|
+
// if (widget && widget.hasAttribute('ref')) {
|
|
562
|
+
if (widget && ref) {
|
|
563
|
+
// ### eval nodeset for list control
|
|
564
|
+
const inscope = getInScopeContext(this, ref);
|
|
565
|
+
// const nodeset = evaluateXPathToNodes(ref, inscope, this);
|
|
566
|
+
const nodeset = evaluateXPath(ref, inscope, this);
|
|
567
|
+
|
|
568
|
+
// ### clear items
|
|
569
|
+
const { children } = widget;
|
|
570
|
+
Array.from(children).forEach(child => {
|
|
571
|
+
if (child.nodeName.toLowerCase() !== 'template') {
|
|
572
|
+
child.parentNode.removeChild(child);
|
|
525
573
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
// const nodeset = evaluateXPathToNodes(ref, inscope, this);
|
|
545
|
-
const nodeset = evaluateXPath(ref, inscope, this);
|
|
546
|
-
|
|
547
|
-
// ### clear items
|
|
548
|
-
const {children} = widget;
|
|
549
|
-
Array.from(children).forEach(child => {
|
|
550
|
-
if (child.nodeName.toLowerCase() !== 'template') {
|
|
551
|
-
child.parentNode.removeChild(child);
|
|
552
|
-
}
|
|
553
|
-
});
|
|
554
|
-
|
|
555
|
-
// ### bail out when nodeset is array and empty
|
|
556
|
-
if (Array.isArray(nodeset) && nodeset.length === 0) return;
|
|
557
|
-
|
|
558
|
-
// ### build the items
|
|
559
|
-
const {template} = this;
|
|
560
|
-
if (template) {
|
|
561
|
-
// ### handle 'selection' open and insert an empty option in that case
|
|
562
|
-
if (this.widget.nodeName === "SELECT" &&
|
|
563
|
-
this.widget.hasAttribute('selection') &&
|
|
564
|
-
this.widget.getAttribute('selection') === 'open') {
|
|
565
|
-
const firstTemplateChild = this.template.firstElementChild;
|
|
566
|
-
// todo: create the element which is used in the template instead of 'option'
|
|
567
|
-
const option = document.createElement('option');
|
|
568
|
-
this.widget.insertBefore(option, firstTemplateChild);
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
if (nodeset.length !== 0) {
|
|
572
|
-
// console.log('nodeset', nodeset);
|
|
573
|
-
const fragment = document.createDocumentFragment();
|
|
574
|
-
// console.time('offscreen');
|
|
575
|
-
Array.from(nodeset).forEach(node => {
|
|
576
|
-
// console.log('#### node', node);
|
|
577
|
-
// ### initialize new entry
|
|
578
|
-
const newEntry = this.createEntry();
|
|
579
|
-
fragment.appendChild(newEntry);
|
|
580
|
-
// ### set value
|
|
581
|
-
this.updateEntry(newEntry, node);
|
|
582
|
-
});
|
|
583
|
-
this.template.parentNode.appendChild(fragment);
|
|
584
|
-
// console.timeEnd('offscreen');
|
|
585
|
-
} else {
|
|
586
|
-
const newEntry = this.createEntry();
|
|
587
|
-
this.template.parentNode.appendChild(newEntry);
|
|
588
|
-
this.updateEntry(newEntry, nodeset);
|
|
589
|
-
}
|
|
590
|
-
this.boundInitialized = true;
|
|
591
|
-
}
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
// ### bail out when nodeset is array and empty
|
|
577
|
+
if (Array.isArray(nodeset) && nodeset.length === 0) return;
|
|
578
|
+
|
|
579
|
+
// ### build the items
|
|
580
|
+
const { template } = this;
|
|
581
|
+
if (template) {
|
|
582
|
+
// ### handle 'selection' open and insert an empty option in that case
|
|
583
|
+
if (
|
|
584
|
+
this.widget.nodeName === 'SELECT' &&
|
|
585
|
+
this.widget.hasAttribute('selection') &&
|
|
586
|
+
this.widget.getAttribute('selection') === 'open'
|
|
587
|
+
) {
|
|
588
|
+
const firstTemplateChild = this.template.firstElementChild;
|
|
589
|
+
// todo: create the element which is used in the template instead of 'option'
|
|
590
|
+
const option = document.createElement('option');
|
|
591
|
+
this.widget.insertBefore(option, firstTemplateChild);
|
|
592
592
|
}
|
|
593
|
-
}
|
|
594
593
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
//
|
|
602
|
-
|
|
594
|
+
if (nodeset.length !== 0) {
|
|
595
|
+
// console.log('nodeset', nodeset);
|
|
596
|
+
const fragment = document.createDocumentFragment();
|
|
597
|
+
// console.time('offscreen');
|
|
598
|
+
Array.from(nodeset).forEach(node => {
|
|
599
|
+
// console.log('#### node', node);
|
|
600
|
+
// ### initialize new entry
|
|
601
|
+
const newEntry = this.createEntry();
|
|
602
|
+
fragment.appendChild(newEntry);
|
|
603
|
+
// ### set value
|
|
604
|
+
this.updateEntry(newEntry, node);
|
|
605
|
+
});
|
|
606
|
+
this.template.parentNode.appendChild(fragment);
|
|
607
|
+
// console.timeEnd('offscreen');
|
|
608
|
+
} else {
|
|
609
|
+
const newEntry = this.createEntry();
|
|
610
|
+
this.template.parentNode.appendChild(newEntry);
|
|
611
|
+
this.updateEntry(newEntry, nodeset);
|
|
603
612
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
613
|
+
this.boundInitialized = true;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
if (this.valueProp === 'selectedOptions') {
|
|
617
|
+
const valueSet = new Set(this.value.split(' '));
|
|
618
|
+
const options = this.getWidget().querySelectorAll('option');
|
|
619
|
+
for (const option of [...options]) {
|
|
620
|
+
if (valueSet.has(option.value)) {
|
|
621
|
+
option.selected = true;
|
|
622
|
+
} else {
|
|
623
|
+
option.selected = false;
|
|
624
|
+
}
|
|
612
625
|
}
|
|
613
|
-
|
|
614
|
-
// ### set label
|
|
615
|
-
const optionLabel = newEntry.textContent;
|
|
616
|
-
this.evalLabel(optionLabel, node, newEntry);
|
|
617
|
-
// ### <<< needs rework
|
|
626
|
+
}
|
|
618
627
|
}
|
|
628
|
+
}
|
|
619
629
|
|
|
630
|
+
updateEntry(newEntry, node) {
|
|
631
|
+
// ### >>> todo: needs rework this code is heavily assuming a select control with 'value' attribute - not generic at all yet.
|
|
620
632
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
newEntry.textContent = label;
|
|
633
|
+
// if (this.widget.nodeName !== 'SELECT') return;
|
|
634
|
+
const valueAttribute = this._getValueAttribute(newEntry);
|
|
635
|
+
if (!valueAttribute) {
|
|
636
|
+
// Fore.dispatch(this,'warn',{message:'no value attribute specified for template entry.'});
|
|
637
|
+
return;
|
|
627
638
|
}
|
|
628
639
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
// const newEntry = document.importNode(content, true);
|
|
634
|
-
// this.template.parentNode.appendChild(newEntry);
|
|
635
|
-
// return newEntry;
|
|
636
|
-
}
|
|
640
|
+
const valueExpr = valueAttribute.value;
|
|
641
|
+
const cutted = valueExpr.substring(1, valueExpr.length - 1);
|
|
642
|
+
const evaluated = evaluateXPathToString(cutted, node, newEntry);
|
|
643
|
+
valueAttribute.value = evaluated;
|
|
637
644
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
let result;
|
|
641
|
-
Array.from(element.attributes).forEach(attribute => {
|
|
642
|
-
const attrVal = attribute.value;
|
|
643
|
-
if (attrVal.indexOf('{') !== -1) {
|
|
644
|
-
// console.log('avt found ', attribute);
|
|
645
|
-
result = attribute;
|
|
646
|
-
}
|
|
647
|
-
});
|
|
648
|
-
return result;
|
|
645
|
+
if (this.value === evaluated) {
|
|
646
|
+
newEntry.setAttribute('selected', 'selected');
|
|
649
647
|
}
|
|
648
|
+
|
|
649
|
+
// ### set label
|
|
650
|
+
const optionLabel = newEntry.textContent;
|
|
651
|
+
this.evalLabel(optionLabel, node, newEntry);
|
|
652
|
+
// ### <<< needs rework
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
evalLabel(optionLabel, node, newEntry) {
|
|
656
|
+
const labelExpr = optionLabel.substring(1, optionLabel.length - 1);
|
|
657
|
+
if (!labelExpr) return;
|
|
658
|
+
|
|
659
|
+
const label = evaluateXPathToString(labelExpr, node, this);
|
|
660
|
+
newEntry.textContent = label;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
createEntry() {
|
|
664
|
+
return this.template.content.firstElementChild.cloneNode(true);
|
|
665
|
+
// const content = this.template.content.firstElementChild.cloneNode(true);
|
|
666
|
+
// return content;
|
|
667
|
+
// const newEntry = document.importNode(content, true);
|
|
668
|
+
// this.template.parentNode.appendChild(newEntry);
|
|
669
|
+
// return newEntry;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// eslint-disable-next-line class-methods-use-this
|
|
673
|
+
_getValueAttribute(element) {
|
|
674
|
+
let result;
|
|
675
|
+
Array.from(element.attributes).forEach(attribute => {
|
|
676
|
+
const attrVal = attribute.value;
|
|
677
|
+
if (attrVal.indexOf('{') !== -1) {
|
|
678
|
+
// console.log('avt found ', attribute);
|
|
679
|
+
result = attribute;
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
return result;
|
|
683
|
+
}
|
|
650
684
|
}
|
|
651
685
|
if (!customElements.get('fx-control')) {
|
|
652
|
-
|
|
686
|
+
window.customElements.define('fx-control', FxControl);
|
|
653
687
|
}
|