@jinntec/fore 2.5.0 → 2.6.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.
@@ -1,9 +1,9 @@
1
1
  import { XPathUtil } from './xpath-util.js';
2
2
  import { FxModel } from './fx-model.js';
3
3
  import {
4
- evaluateXPath,
5
- evaluateXPathToFirstNode,
6
- evaluateXPathToString,
4
+ evaluateXPath,
5
+ evaluateXPathToFirstNode,
6
+ evaluateXPathToString,
7
7
  } from './xpath-evaluation.js';
8
8
  import getInScopeContext from './getInScopeContext.js';
9
9
  import { Fore } from './fore.js';
@@ -14,291 +14,320 @@ import DependentXPathQueries from './DependentXPathQueries.js';
14
14
  * @extends {HTMLElement}
15
15
  */
16
16
  export default class ForeElementMixin extends HTMLElement {
17
- static get properties() {
18
- return {
19
- /**
20
- * context object for evaluation
21
- */
22
- context: {
23
- type: Object,
24
- },
25
- /**
26
- * the model of this element
27
- */
28
- model: {
29
- type: Object,
30
- },
31
- /**
32
- * The modelitem object associated to the bound node holding the evaluated state.
33
- */
34
- modelItem: {
35
- type: Object,
36
- },
37
- /**
38
- * the node(s) bound by this element
39
- */
40
- nodeset: {
41
- type: Object,
42
- },
43
- /**
44
- * XPath binding expression pointing to bound node
45
- */
46
- ref: {
47
- type: String,
48
- },
49
- inScopeVariables: {
50
- type: Map,
51
- },
52
- };
53
- }
17
+ static get properties() {
18
+ return {
19
+ /**
20
+ * context object for evaluation
21
+ */
22
+ context: {
23
+ type: Object,
24
+ },
25
+ /**
26
+ * the model of this element
27
+ */
28
+ model: {
29
+ type: Object,
30
+ },
31
+ /**
32
+ * The modelitem object associated to the bound node holding the evaluated state.
33
+ */
34
+ modelItem: {
35
+ type: Object,
36
+ },
37
+ /**
38
+ * the node(s) bound by this element
39
+ */
40
+ nodeset: {
41
+ type: Object,
42
+ },
43
+ /**
44
+ * XPath binding expression pointing to bound node
45
+ */
46
+ ref: {
47
+ type: String,
48
+ },
49
+ inScopeVariables: {
50
+ type: Map,
51
+ },
52
+ };
53
+ }
54
54
 
55
- constructor() {
56
- super();
57
- this.context = null;
58
- this.model = null;
59
- this.modelItem = null;
60
- this.ref = this.hasAttribute('ref') ? this.getAttribute('ref') : '';
61
- /**
62
- * @type {Map<string, import('./fx-var.js').FxVariable>}
63
- */
64
- this.inScopeVariables = new Map();
55
+ constructor() {
56
+ super();
57
+ this.context = null;
58
+ this.model = null;
59
+ this.modelItem = null;
60
+ this.ref = this.hasAttribute('ref') ? this.getAttribute('ref') : '';
61
+ /**
62
+ * @type {Map<string, import('./fx-var.js').FxVariable>}
63
+ */
64
+ this.inScopeVariables = new Map();
65
65
 
66
- this._dependencies = new DependentXPathQueries();
67
- this._dependencies.setParentDependencies(this.parent?.closest('[ref]')?._dependencies);
68
- }
66
+ this.dependencies = new DependentXPathQueries();
67
+ }
69
68
 
70
- /**
71
- * @returns {import('./fx-model.js').FxModel}
72
- */
73
- getModel() {
74
- // console.log('getModel this ', this);
75
- if (this.model) {
76
- return this.model;
69
+ connectedCallback() {
70
+ if (this.parentElement) {
71
+ this.dependencies.setParentDependencies(
72
+ this.parentElement?.closest('[ref]')?.dependencies,
73
+ );
74
+ }
77
75
  }
78
- // const ownerForm = this.closest('fx-fore');
79
- // const ownerForm = this.getOwnerForm(this);
80
- const ownerForm = this.getOwnerForm();
81
- return ownerForm.querySelector('fx-model');
82
- }
83
76
 
84
- /**
85
- *
86
- * @returns {import('./fx-fore.js').FxFore} The fx-fore element associated with this form node
87
- */
88
- getOwnerForm() {
89
- let currentElement = this;
90
- while (currentElement && currentElement.parentNode) {
91
- // console.log('current ', currentElement);
77
+ /**
78
+ * @returns {import('./fx-model.js').FxModel}
79
+ */
80
+ getModel() {
81
+ // console.log('getModel this ', this);
82
+ if (this.model) {
83
+ return this.model;
84
+ }
85
+ // const ownerForm = this.closest('fx-fore');
86
+ // const ownerForm = this.getOwnerForm(this);
87
+ const ownerForm = this.getOwnerForm();
88
+ return ownerForm.querySelector('fx-model');
89
+ }
92
90
 
93
- if (currentElement.nodeName.toUpperCase() === 'FX-FORE') {
94
- return currentElement;
95
- }
91
+ /**
92
+ *
93
+ * @returns {import('./fx-fore.js').FxFore} The fx-fore element associated with this form node
94
+ */
95
+ getOwnerForm() {
96
+ let currentElement = this;
97
+ while (currentElement && currentElement.parentNode) {
98
+ // console.log('current ', currentElement);
96
99
 
97
- if (currentElement.parentNode instanceof DocumentFragment) {
98
- currentElement = currentElement.parentNode.host;
99
- } else {
100
- currentElement = currentElement.parentNode;
101
- }
102
- }
103
- return currentElement;
104
- }
100
+ if (currentElement.nodeName.toUpperCase() === 'FX-FORE') {
101
+ return currentElement;
102
+ }
105
103
 
106
- /**
107
- * evaluation of fx-bind and UiElements differ in details so that each class needs it's own implementation.
108
- */
109
- evalInContext() {
110
- this._dependencies.resetDependencies();
111
- // const inscopeContext = this.getInScopeContext();
112
- const model = this.getModel();
113
- if (!model) {
114
- return;
115
- }
116
- let inscopeContext;
117
- if (this.hasAttribute('context')) {
118
- inscopeContext = getInScopeContext(this.getAttributeNode('context') || this, this.context);
119
- }
120
- if (this.hasAttribute('ref')) {
121
- inscopeContext = getInScopeContext(this.getAttributeNode('ref') || this, this.ref);
122
- this._dependencies.addXPath(this.ref);
123
- }
124
- if (!inscopeContext && this.getModel().instances.length !== 0) {
125
- // ### always fall back to default context with there's neither a 'context' or 'ref' present
126
- inscopeContext = this.getModel().getDefaultInstance().getDefaultContext();
127
- // console.warn('no in scopeContext for ', this);
128
- // console.warn('using default context ', this);
129
- // return;
130
- }
131
- if (this.ref === '') {
132
- this.nodeset = inscopeContext;
133
- } else if (Array.isArray(inscopeContext)) {
134
- /*
135
- inscopeContext.forEach(n => {
136
- if (XPathUtil.isSelfReference(this.ref)) {
137
- this.nodeset = inscopeContext;
138
- } else {
139
- const localResult = evaluateXPathToFirstNode(this.ref, n, this);
140
- // console.log('local result: ', localResult);
141
- this.nodeset.push(localResult);
142
- }
143
- });
144
- */
145
- // this.nodeset = evaluateXPathToFirstNode(this.ref, inscopeContext[0], this);
146
- this.nodeset = evaluateXPath(this.ref, inscopeContext[0], this);
147
- } else {
148
- // this.nodeset = fx.evaluateXPathToFirstNode(this.ref, inscopeContext, null, {namespaceResolver: this.namespaceResolver});
149
- if (!inscopeContext) return;
150
- const { nodeType } = inscopeContext;
151
- if (nodeType && !XPathUtil.isAbsolutePath(this.ref)) {
152
- this.nodeset = evaluateXPathToFirstNode(this.ref, inscopeContext, this);
153
- } else {
154
- [this.nodeset] = evaluateXPath(this.ref, inscopeContext, this);
155
- }
104
+ if (currentElement.parentNode instanceof DocumentFragment) {
105
+ currentElement = currentElement.parentNode.host;
106
+ } else {
107
+ currentElement = currentElement.parentNode;
108
+ }
109
+ }
110
+ return currentElement;
156
111
  }
157
- // console.log('UiElement evaluated to nodeset: ', this.nodeset);
158
- }
159
112
 
160
- /**
161
- * resolves template expressions for a single attribute
162
- * @param {string} expr an attribute value containing curly brackets containing XPath expressions to evaluate
163
- * @param {Node} node the attribute node used for scoped resolution
164
- * @returns {string}
165
- * @protected
166
- */
167
- evaluateAttributeTemplateExpression(expr, node) {
168
- const matches = expr.match(/{[^}]*}/g);
169
- if (matches) {
170
- matches.forEach(match => {
171
- // console.log('match ', match);
172
- const naked = match.substring(1, match.length - 1);
173
- const inscope = getInScopeContext(node, naked);
174
- const result = evaluateXPathToString(naked, inscope, this);
175
- const replaced = expr.replaceAll(match, result);
176
- // console.log('replacing ', expr, ' with ', replaced);
177
- expr = replaced;
113
+ /**
114
+ * evaluation of fx-bind and UiElements differ in details so that each class needs it's own implementation.
115
+ */
116
+ evalInContext() {
117
+ this.dependencies.resetDependencies();
118
+ // const inscopeContext = this.getInScopeContext();
119
+ const model = this.getModel();
120
+ if (!model) {
121
+ return;
122
+ }
123
+ let inscopeContext;
124
+ if (this.hasAttribute('context')) {
125
+ inscopeContext = getInScopeContext(
126
+ this.getAttributeNode('context') || this,
127
+ this.context,
128
+ );
129
+ }
130
+ if (this.hasAttribute('ref')) {
131
+ inscopeContext = getInScopeContext(
132
+ this.getAttributeNode('ref') || this,
133
+ this.ref,
134
+ );
135
+ this.dependencies.addXPath(this.ref);
136
+ }
137
+ if (!inscopeContext && this.getModel().instances.length !== 0) {
138
+ // ### always fall back to default context with there's neither a 'context' or 'ref' present
139
+ inscopeContext = this.getModel()
140
+ .getDefaultInstance()
141
+ .getDefaultContext();
142
+ // console.warn('no in scopeContext for ', this);
143
+ // console.warn('using default context ', this);
144
+ // return;
145
+ }
146
+ if (this.ref === '') {
147
+ this.nodeset = inscopeContext;
148
+ } else if (Array.isArray(inscopeContext)) {
149
+ /*
150
+ inscopeContext.forEach(n => {
151
+ if (XPathUtil.isSelfReference(this.ref)) {
152
+ this.nodeset = inscopeContext;
153
+ } else {
154
+ const localResult = evaluateXPathToFirstNode(this.ref, n, this);
155
+ // console.log('local result: ', localResult);
156
+ this.nodeset.push(localResult);
157
+ }
178
158
  });
159
+ */
160
+ // this.nodeset = evaluateXPathToFirstNode(this.ref, inscopeContext[0], this);
161
+ this.nodeset = evaluateXPath(this.ref, inscopeContext[0], this);
162
+ } else {
163
+ // this.nodeset = fx.evaluateXPathToFirstNode(this.ref, inscopeContext, null, {namespaceResolver: this.namespaceResolver});
164
+ if (!inscopeContext) return;
165
+ const { nodeType } = inscopeContext;
166
+ if (nodeType && !XPathUtil.isAbsolutePath(this.ref)) {
167
+ this.nodeset = evaluateXPathToFirstNode(
168
+ this.ref,
169
+ inscopeContext,
170
+ this,
171
+ );
172
+ } else {
173
+ [this.nodeset] = evaluateXPath(this.ref, inscopeContext, this);
174
+ }
175
+ }
176
+ // console.log('UiElement evaluated to nodeset: ', this.nodeset);
179
177
  }
180
- return expr;
181
- }
182
-
183
- isNotBound() {
184
- return !this.hasAttribute('ref');
185
- }
186
178
 
187
- isBound() {
188
- return this.hasAttribute('ref');
189
- }
190
-
191
- getBindingExpr() {
192
- if (this.hasAttribute('ref')) {
193
- return this.getAttribute('ref');
194
- }
195
- // try to get closest parent bind
196
- const parent = XPathUtil.getClosest('[ref]', this.parentNode);
197
- if (!parent) {
198
- return 'instance()'; // the default instance
179
+ /**
180
+ * resolves template expressions for a single attribute
181
+ * @param {string} expr an attribute value containing curly brackets containing XPath expressions to evaluate
182
+ * @param {Node} node the attribute node used for scoped resolution
183
+ * @returns {string}
184
+ * @protected
185
+ */
186
+ evaluateAttributeTemplateExpression(expr, node) {
187
+ const matches = expr.match(/{[^}]*}/g);
188
+ if (matches) {
189
+ matches.forEach((match) => {
190
+ // console.log('match ', match);
191
+ const naked = match.substring(1, match.length - 1);
192
+ const inscope = getInScopeContext(node, naked);
193
+ const result = evaluateXPathToString(naked, inscope, this);
194
+ const replaced = expr.replaceAll(match, result);
195
+ // console.log('replacing ', expr, ' with ', replaced);
196
+ expr = replaced;
197
+ });
198
+ }
199
+ return expr;
199
200
  }
200
- return parent.getAttribute('ref');
201
- }
202
201
 
203
- /**
204
- * @returns {import('./fx-instance.js').FxInstance}
205
- */
206
- getInstance() {
207
- if (this.ref.startsWith('instance(')) {
208
- const instId = XPathUtil.getInstanceId(this.ref);
209
- return this.getModel().getInstance(instId);
202
+ isNotBound() {
203
+ return !this.hasAttribute('ref');
210
204
  }
211
- return this.getModel().getInstance('default');
212
- }
213
205
 
214
- _getParentBindingElement(start) {
215
- if (start.parentNode.host) {
216
- const { host } = start.parentNode;
217
- if (host.hasAttribute('ref')) {
218
- return host;
219
- }
220
- } else if (start.parentNode) {
221
- if (start.parentNode.hasAttribute('ref')) {
222
- return this.parentNode;
223
- }
224
- this._getParentBindingElement(this.parentNode);
206
+ isBound() {
207
+ return this.hasAttribute('ref');
225
208
  }
226
- return null;
227
- }
228
209
 
229
- /**
230
- * @returns {import('./modelitem.js').ModelItem}
231
- */
232
- getModelItem() {
233
- const mi = this.getModel().getModelItem(this.nodeset);
234
- if (mi) {
235
- this.modelItem = mi;
210
+ getBindingExpr() {
211
+ if (this.hasAttribute('ref')) {
212
+ return this.getAttribute('ref');
213
+ }
214
+ // try to get closest parent bind
215
+ const parent = XPathUtil.getClosest('[ref]', this.parentNode);
216
+ if (!parent) {
217
+ return 'instance()'; // the default instance
218
+ }
219
+ return parent.getAttribute('ref');
236
220
  }
237
221
 
238
- const repeated = XPathUtil.getClosest('fx-repeatitem', this);
239
- let existed;
240
- if (repeated) {
241
- const { index } = repeated;
242
- if (Array.isArray(this.nodeset)) {
243
- existed = this.getModel().getModelItem(this.nodeset[index - 1]);
244
- } else {
245
- existed = this.getModel().getModelItem(this.nodeset);
246
- }
247
- } else {
248
- existed = this.nodeset ? this.getModel().getModelItem(this.nodeset) : null;
222
+ /**
223
+ * @returns {import('./fx-instance.js').FxInstance}
224
+ */
225
+ getInstance() {
226
+ if (this.ref.startsWith('instance(')) {
227
+ const instId = XPathUtil.getInstanceId(this.ref);
228
+ return this.getModel().getInstance(instId);
229
+ }
230
+ return this.getModel().getInstance('default');
249
231
  }
250
232
 
251
- if (!existed) {
252
- const lazyCreatedModelItem = FxModel.lazyCreateModelItem(
253
- this.getModel(),
254
- this.ref,
255
- this.nodeset,
256
- this,
257
- );
258
- this.modelItem = lazyCreatedModelItem;
259
- return lazyCreatedModelItem;
233
+ _getParentBindingElement(start) {
234
+ if (start.parentNode.host) {
235
+ const { host } = start.parentNode;
236
+ if (host.hasAttribute('ref')) {
237
+ return host;
238
+ }
239
+ } else if (start.parentNode) {
240
+ if (start.parentNode.hasAttribute('ref')) {
241
+ return this.parentNode;
242
+ }
243
+ this._getParentBindingElement(this.parentNode);
244
+ }
245
+ return null;
260
246
  }
261
- this.modelItem = existed;
262
247
 
263
- return existed;
264
- }
248
+ /**
249
+ * @returns {import('./modelitem.js').ModelItem}
250
+ */
251
+ getModelItem() {
252
+ if (!this.getModel()) return;
253
+ const mi = this.getModel().getModelItem(this.nodeset);
254
+ if (mi) {
255
+ this.modelItem = mi;
256
+ }
257
+
258
+ const repeated = XPathUtil.getClosest('fx-repeatitem', this);
259
+ let existed;
260
+ if (repeated) {
261
+ const { index } = repeated;
262
+ if (Array.isArray(this.nodeset)) {
263
+ existed = this.getModel().getModelItem(this.nodeset[index - 1]);
264
+ } else {
265
+ existed = this.getModel().getModelItem(this.nodeset);
266
+ }
267
+ } else {
268
+ existed = this.nodeset
269
+ ? this.getModel().getModelItem(this.nodeset)
270
+ : null;
271
+ }
272
+
273
+ if (!existed) {
274
+ const lazyCreatedModelItem = FxModel.lazyCreateModelItem(
275
+ this.getModel(),
276
+ this.ref,
277
+ this.nodeset,
278
+ this,
279
+ );
280
+ this.modelItem = lazyCreatedModelItem;
281
+ return lazyCreatedModelItem;
282
+ }
283
+ this.modelItem = existed;
265
284
 
266
- /**
267
- * Returns the effective value for the element.
268
- * a: look for 'value' attribute and if present evaluate it and return the resulting value
269
- * b: look for textContent and return the value if present
270
- * c: return null
271
- * @returns {string}
272
- */
273
- getValue() {
274
- if (this.hasAttribute('value')) {
275
- const valAttr = this.getAttribute('value');
276
- try {
277
- const inscopeContext = getInScopeContext(this, valAttr);
278
- return evaluateXPathToString(valAttr, inscopeContext, this.getOwnerForm());
279
- } catch (error) {
280
- console.error(error);
281
- Fore.dispatch(this, 'error', { message: error });
282
- }
285
+ return existed;
283
286
  }
284
- if (this.textContent) {
285
- return this.textContent;
287
+
288
+ /**
289
+ * Returns the effective value for the element.
290
+ * a: look for 'value' attribute and if present evaluate it and return the resulting value
291
+ * b: look for textContent and return the value if present
292
+ * c: return null
293
+ * @returns {string}
294
+ */
295
+ getValue() {
296
+ if (this.hasAttribute('value')) {
297
+ const valAttr = this.getAttribute('value');
298
+ try {
299
+ const inscopeContext = getInScopeContext(this, valAttr);
300
+ return evaluateXPathToString(
301
+ valAttr,
302
+ inscopeContext,
303
+ this.getOwnerForm(),
304
+ );
305
+ } catch (error) {
306
+ console.error(error);
307
+ Fore.dispatch(this, 'error', { message: error });
308
+ }
309
+ }
310
+ if (this.textContent) {
311
+ return this.textContent;
312
+ }
313
+ return null;
286
314
  }
287
- return null;
288
- }
289
315
 
290
- /**
291
- * @returns {Node}
292
- */
293
- getInScopeContext() {
294
- return getInScopeContext(this.getAttributeNode('ref') || this, this.ref);
295
- }
316
+ /**
317
+ * @returns {Node}
318
+ */
319
+ getInScopeContext() {
320
+ return getInScopeContext(
321
+ this.getAttributeNode('ref') || this,
322
+ this.ref,
323
+ );
324
+ }
296
325
 
297
- /**
298
- * Set variables in scope here
299
- * @param {Map} inScopeVariables
300
- */
301
- setInScopeVariables(inScopeVariables) {
302
- this.inScopeVariables = inScopeVariables;
303
- }
326
+ /**
327
+ * Set variables in scope here
328
+ * @param {Map} inScopeVariables
329
+ */
330
+ setInScopeVariables(inScopeVariables) {
331
+ this.inScopeVariables = inScopeVariables;
332
+ }
304
333
  }