@jinntec/fore 2.4.2 → 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,285 +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 = {};
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
178
 
183
- isNotBound() {
184
- return !this.hasAttribute('ref');
185
- }
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;
200
+ }
186
201
 
187
- isBound() {
188
- return this.hasAttribute('ref');
189
- }
202
+ isNotBound() {
203
+ return !this.hasAttribute('ref');
204
+ }
190
205
 
191
- getBindingExpr() {
192
- if (this.hasAttribute('ref')) {
193
- return this.getAttribute('ref');
206
+ isBound() {
207
+ return this.hasAttribute('ref');
194
208
  }
195
- // try to get closest parent bind
196
- const parent = XPathUtil.getClosest('[ref]', this.parentNode);
197
- if (!parent) {
198
- return 'instance()'; // the default instance
209
+
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');
199
220
  }
200
- return parent.getAttribute('ref');
201
- }
202
221
 
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);
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');
210
231
  }
211
- return this.getModel().getInstance('default');
212
- }
213
232
 
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);
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;
225
246
  }
226
- return null;
227
- }
228
247
 
229
- /**
230
- * @returns {import('./modelitem.js').ModelItem}
231
- */
232
- getModelItem() {
233
- // return this.model.bindingMap.find(m => m.refnode === this.nodeset);
234
- // return this.getModel().bindingMap.find(m => m.refnode === this.nodeset);
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
+ }
235
257
 
236
- const mi = this.getModel().getModelItem(this.nodeset);
237
- if (mi) {
238
- this.modelItem = mi;
239
- }
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
+ }
240
272
 
241
- const repeated = XPathUtil.getClosest('fx-repeatitem', this);
242
- let existed;
243
- if (repeated) {
244
- const { index } = repeated;
245
- if (Array.isArray(this.nodeset)) {
246
- existed = this.getModel().getModelItem(this.nodeset[index - 1]);
247
- } else {
248
- existed = this.getModel().getModelItem(this.nodeset);
249
- }
250
- } else {
251
- existed = this.getModel().getModelItem(this.nodeset);
252
- }
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;
253
284
 
254
- if (!existed) {
255
- return FxModel.lazyCreateModelItem(this.getModel(), this.ref, this.nodeset);
285
+ return existed;
256
286
  }
257
- return existed;
258
- }
259
287
 
260
- /**
261
- * Returns the effective value for the element.
262
- * a: look for 'value' attribute and if present evaluate it and return the resulting value
263
- * b: look for textContent and return the value if present
264
- * c: return null
265
- * @returns {string}
266
- */
267
- getValue() {
268
- if (this.hasAttribute('value')) {
269
- const valAttr = this.getAttribute('value');
270
- try {
271
- const inscopeContext = getInScopeContext(this, valAttr);
272
- return evaluateXPathToString(valAttr, inscopeContext, this.getOwnerForm());
273
- } catch (error) {
274
- console.error(error);
275
- Fore.dispatch(this, 'error', { message: error });
276
- }
277
- }
278
- if (this.textContent) {
279
- return this.textContent;
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;
280
314
  }
281
- return null;
282
- }
283
315
 
284
- /**
285
- * @returns {Node}
286
- */
287
- getInScopeContext() {
288
- return getInScopeContext(this.getAttributeNode('ref') || this, this.ref);
289
- }
316
+ /**
317
+ * @returns {Node}
318
+ */
319
+ getInScopeContext() {
320
+ return getInScopeContext(
321
+ this.getAttributeNode('ref') || this,
322
+ this.ref,
323
+ );
324
+ }
290
325
 
291
- /**
292
- * Set variables in scope here
293
- * @param {Map} inScopeVariables
294
- */
295
- setInScopeVariables(inScopeVariables) {
296
- this.inScopeVariables = inScopeVariables;
297
- }
326
+ /**
327
+ * Set variables in scope here
328
+ * @param {Map} inScopeVariables
329
+ */
330
+ setInScopeVariables(inScopeVariables) {
331
+ this.inScopeVariables = inScopeVariables;
332
+ }
298
333
  }