@jinntec/fore 2.1.1 → 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.
Files changed (88) hide show
  1. package/README.md +2 -2
  2. package/dist/fore-dev.js +2 -2
  3. package/dist/fore-dev.js.map +1 -1
  4. package/dist/fore.js +2 -2
  5. package/dist/fore.js.map +1 -1
  6. package/index.js +4 -2
  7. package/package.json +10 -38
  8. package/resources/fore.css +263 -262
  9. package/resources/vars.css +8 -0
  10. package/src/DependencyNotifyingDomFacade.js +1 -0
  11. package/src/ForeElementMixin.js +243 -228
  12. package/src/actions/abstract-action.js +407 -398
  13. package/src/actions/fx-action.js +6 -6
  14. package/src/actions/fx-append.js +1 -1
  15. package/src/actions/fx-call.js +63 -62
  16. package/src/actions/fx-confirm.js +11 -11
  17. package/src/actions/fx-construct-done.js +4 -4
  18. package/src/actions/fx-copy.js +36 -36
  19. package/src/actions/fx-delete.js +77 -79
  20. package/src/actions/fx-dispatch.js +14 -14
  21. package/src/actions/fx-hide.js +15 -15
  22. package/src/actions/fx-insert.js +9 -12
  23. package/src/actions/fx-insertchild.js +88 -0
  24. package/src/actions/fx-load.js +188 -185
  25. package/src/actions/fx-message.js +18 -19
  26. package/src/actions/fx-refresh.js +10 -10
  27. package/src/actions/fx-reload.js +16 -14
  28. package/src/actions/fx-replace.js +0 -1
  29. package/src/actions/fx-reset.js +31 -31
  30. package/src/actions/fx-send.js +59 -52
  31. package/src/actions/fx-setattribute.js +48 -49
  32. package/src/actions/fx-setfocus.js +54 -58
  33. package/src/actions/fx-setvalue.js +85 -81
  34. package/src/actions/fx-show.js +11 -11
  35. package/src/actions/fx-toggle.js +2 -2
  36. package/src/actions/fx-toggleboolean.js +38 -39
  37. package/src/actions/fx-unmodified.js +27 -0
  38. package/src/actions/fx-update.js +6 -6
  39. package/src/dep_graph.js +1 -1
  40. package/src/drawdown.js +23 -30
  41. package/src/events.js +19 -19
  42. package/src/fore.js +143 -125
  43. package/src/functions/common-function.js +24 -25
  44. package/src/functions/fx-function.js +6 -99
  45. package/src/functions/fx-functionlib.js +56 -0
  46. package/src/functions/registerFunction.js +112 -0
  47. package/src/fx-bind.js +19 -24
  48. package/src/fx-connection.js +226 -0
  49. package/src/fx-fore.js +844 -815
  50. package/src/fx-header.js +4 -4
  51. package/src/fx-instance.js +25 -29
  52. package/src/fx-model.js +405 -380
  53. package/src/fx-submission.js +399 -397
  54. package/src/fx-var.js +13 -12
  55. package/src/getInScopeContext.js +102 -89
  56. package/src/json-util.js +24 -24
  57. package/src/lab/fore-component.js +48 -52
  58. package/src/lab/instance-inspector.js +13 -16
  59. package/src/modelitem.js +48 -10
  60. package/src/relevance.js +12 -16
  61. package/src/tools/adi.js +858 -812
  62. package/src/tools/fx-action-log.js +377 -295
  63. package/src/tools/fx-devtools.js +210 -210
  64. package/src/tools/fx-dom-inspector.js +123 -122
  65. package/src/tools/fx-json-instance.js +262 -253
  66. package/src/tools/fx-log-item.js +6 -11
  67. package/src/tools/fx-log-settings.js +358 -301
  68. package/src/tools/fx-minimap.js +186 -177
  69. package/src/tools/helpers.js +1 -1
  70. package/src/ui/abstract-control.js +73 -53
  71. package/src/ui/fx-case.js +59 -7
  72. package/src/ui/fx-container.js +13 -14
  73. package/src/ui/fx-control.js +572 -538
  74. package/src/ui/fx-dialog.js +3 -3
  75. package/src/ui/fx-droptarget.js +3 -4
  76. package/src/ui/fx-group.js +4 -2
  77. package/src/ui/fx-hint.js +3 -0
  78. package/src/ui/fx-inspector.js +5 -5
  79. package/src/ui/fx-items.js +11 -11
  80. package/src/ui/fx-output.js +10 -12
  81. package/src/ui/fx-repeat-attributes.js +23 -27
  82. package/src/ui/fx-repeat.js +347 -344
  83. package/src/ui/fx-repeatitem.js +75 -68
  84. package/src/ui/fx-switch.js +39 -14
  85. package/src/ui/fx-trigger.js +4 -4
  86. package/src/withDraggability.js +193 -191
  87. package/src/xpath-evaluation.js +969 -958
  88. package/src/xpath-util.js +161 -134
package/src/fx-model.js CHANGED
@@ -1,9 +1,9 @@
1
- import {DepGraph} from './dep_graph.js';
2
- import {Fore} from './fore.js';
1
+ import { DepGraph } from './dep_graph.js';
2
+ import { Fore } from './fore.js';
3
3
  import './fx-instance.js';
4
- import {ModelItem} from './modelitem.js';
5
- import {evaluateXPath, evaluateXPathToBoolean} from './xpath-evaluation.js';
6
- import {XPathUtil} from './xpath-util.js';
4
+ import { ModelItem } from './modelitem.js';
5
+ import { evaluateXPath, evaluateXPathToBoolean } from './xpath-evaluation.js';
6
+ import { XPathUtil } from './xpath-util.js';
7
7
 
8
8
  /**
9
9
  * The model of this Fore scope. It holds all the intances, binding, submissions and custom functions that
@@ -15,121 +15,134 @@ import {XPathUtil} from './xpath-util.js';
15
15
  *
16
16
  */
17
17
  export class FxModel extends HTMLElement {
18
- static dataChanged = false;
19
- constructor() {
20
- super();
21
- // this.id = '';
22
-
23
- this.instances = [];
24
- this.modelItems = [];
25
- this.defaultContext = {};
26
- this.changed = [];
27
-
28
- // this.mainGraph = new DepGraph(false);
29
- this.inited = false;
30
- this.modelConstructed = false;
31
- this.attachShadow({mode: 'open'});
32
- this.computes = 0;
33
- this.fore = {};
34
- }
18
+ static dataChanged = false;
35
19
 
36
- get formElement() {
37
- return this.parentElement;
38
- }
20
+ constructor() {
21
+ super();
22
+ // this.id = '';
39
23
 
40
- connectedCallback() {
41
- // console.log('connectedCallback ', this);
42
- this.shadowRoot.innerHTML = `
24
+ /**
25
+ * @type {import('./fx-instance.js').FxInstance[]}
26
+ */
27
+ this.instances = [];
28
+ /**
29
+ * @type {import('./modelitem.js').ModelItem[]}
30
+ */
31
+ this.modelItems = [];
32
+ this.defaultContext = {};
33
+ this.changed = [];
34
+
35
+ // this.mainGraph = new DepGraph(false);
36
+ this.inited = false;
37
+ this.modelConstructed = false;
38
+ this.attachShadow({ mode: 'open' });
39
+ this.computes = 0;
40
+ this.fore = {};
41
+ }
42
+
43
+ get formElement() {
44
+ return this.parentElement;
45
+ }
46
+
47
+ connectedCallback() {
48
+ // console.log('connectedCallback ', this);
49
+ this.setAttribute('inert', 'true');
50
+ this.shadowRoot.innerHTML = `
43
51
  <slot></slot>
44
52
  `;
45
53
 
46
- this.addEventListener('model-construct-done', () => {
47
- this.modelConstructed = true;
48
- // console.log('model-construct-done fired ', this.modelConstructed);
49
- // console.log('model-construct-done fired ', e.detail.model.instances);
50
- }, {once: true});
54
+ /*
55
+ this.addEventListener('model-construct-done', () => {
56
+ // this.modelConstructed = true;
57
+ // console.log('model-construct-done fired ', this.modelConstructed);
58
+ // console.log('model-construct-done fired ', e.detail.model.instances);
59
+ },
60
+ { once: true },
61
+ );
62
+ */
51
63
 
52
- this.skipUpdate = false;
53
- this.fore = this.parentNode;
64
+ this.skipUpdate = false;
65
+ this.fore = this.parentNode;
66
+ }
67
+
68
+ static lazyCreateModelItem(model, ref, node) {
69
+ // console.log('lazyCreateModelItem ', node);
70
+
71
+ let targetNode = {};
72
+ if (node === null || node === undefined) return null;
73
+ if (node.nodeType === node.TEXT_NODE) {
74
+ // const parent = node.parentNode;
75
+ // console.log('PARENT ', parent);
76
+ targetNode = node.parentNode;
77
+ } else {
78
+ targetNode = node;
54
79
  }
55
80
 
56
- static lazyCreateModelItem(model, ref, node) {
57
- // console.log('lazyCreateModelItem ', node);
81
+ // const path = fx.evaluateXPath('path()',node);
82
+ let path;
83
+ if (node.nodeType) {
84
+ const instance = XPathUtil.resolveInstance(model, ref);
58
85
 
59
- let targetNode = {};
60
- if (node === null || node === undefined) return null;
61
- if (node.nodeType === node.TEXT_NODE) {
62
- // const parent = node.parentNode;
63
- // console.log('PARENT ', parent);
64
- targetNode = node.parentNode;
65
- } else {
66
- targetNode = node;
67
- }
68
-
69
- // const path = fx.evaluateXPath('path()',node);
70
- let path;
71
- if (node.nodeType) {
72
- const instance = XPathUtil.resolveInstance(model, ref);
73
-
74
- path = XPathUtil.getPath(node, instance);
75
- } else {
76
- path = null;
77
- targetNode = node;
78
- }
79
- // const path = XPathUtil.getPath(node);
80
-
81
- // ### intializing ModelItem with default values (as there is no <fx-bind> matching for given ref)
82
- const mi = new ModelItem(
83
- path,
84
- ref,
85
- Fore.READONLY_DEFAULT,
86
- Fore.RELEVANT_DEFAULT,
87
- Fore.REQUIRED_DEFAULT,
88
- Fore.CONSTRAINT_DEFAULT,
89
- Fore.TYPE_DEFAULT,
90
- targetNode,
91
- this,
92
- );
93
-
94
- // console.log('new ModelItem is instanceof ModelItem ', mi instanceof ModelItem);
95
- model.registerModelItem(mi);
96
- return mi;
86
+ path = XPathUtil.getPath(node, instance);
87
+ } else {
88
+ path = null;
89
+ targetNode = node;
97
90
  }
98
-
99
- /**
100
- * modelConstruct starts actual processing of the model by
101
- *
102
- * 1. loading instances if present or constructing one
103
- * 2. calling updateModel to run the model update cycle of rebuild, recalculate and revalidate
104
- *
105
- * @event model-construct-done is fired once all instances have be loaded or after generating instance
106
- *
107
- */
108
- async modelConstruct() {
109
- console.log(`### <<<<< dispatching model-construct for '${this.fore.id}' >>>>>`);
110
- // this.dispatchEvent(new CustomEvent('model-construct', { detail: this }));
111
- Fore.dispatch(this, 'model-construct', {model: this});
112
-
113
- // console.time('instance-loading');
114
- const instances = this.querySelectorAll('fx-instance');
115
- if (instances.length > 0) {
116
- const promises = [];
117
- instances.forEach(instance => {
118
- promises.push(instance.init());
119
- });
120
-
121
- // Wait until all the instances are built
122
- await Promise.all(promises);
123
-
91
+ // const path = XPathUtil.getPath(node);
92
+
93
+ // ### intializing ModelItem with default values (as there is no <fx-bind> matching for given ref)
94
+ const mi = new ModelItem(
95
+ path,
96
+ ref,
97
+ Fore.READONLY_DEFAULT,
98
+ Fore.RELEVANT_DEFAULT,
99
+ Fore.REQUIRED_DEFAULT,
100
+ Fore.CONSTRAINT_DEFAULT,
101
+ Fore.TYPE_DEFAULT,
102
+ targetNode,
103
+ this,
104
+ );
105
+
106
+ // console.log('new ModelItem is instanceof ModelItem ', mi instanceof ModelItem);
107
+ model.registerModelItem(mi);
108
+ return mi;
109
+ }
110
+
111
+ /**
112
+ * modelConstruct starts actual processing of the model by
113
+ *
114
+ * 1. loading instances if present or constructing one
115
+ * 2. calling updateModel to run the model update cycle of rebuild, recalculate and revalidate
116
+ *
117
+ * @event model-construct-done is fired once all instances have be loaded or after generating instance
118
+ *
119
+ */
120
+ async modelConstruct() {
121
+ console.log(`### <<<<< dispatching model-construct for '${this.fore.id}' >>>>>`);
122
+ // this.dispatchEvent(new CustomEvent('model-construct', { detail: this }));
123
+ Fore.dispatch(this, 'model-construct', { model: this });
124
+
125
+ // console.time('instance-loading');
126
+ const instances = this.querySelectorAll('fx-instance');
127
+ if (instances.length > 0) {
128
+ const promises = [];
129
+ instances.forEach(instance => {
130
+ promises.push(instance.init());
131
+ });
132
+
133
+ // Wait until all the instances are built
134
+ await Promise.all(promises);
124
135
  this.instances = Array.from(instances);
125
136
  // console.log('_modelConstruct this.instances ', this.instances);
126
137
  // Await until the model-construct-done event is handled off
138
+ this.modelConstructed = true;
127
139
  await Fore.dispatch(this, 'model-construct-done', {model: this});
128
140
  this.inited = true;
129
141
  this.updateModel();
130
142
  } else {
131
143
  // ### if there's no instance one will created
132
144
  console.log(`### <<<<< dispatching model-construct-done for '${this.fore.id}' >>>>>`);
145
+ this.modelConstructed = true;
133
146
  await this.dispatchEvent(
134
147
  new CustomEvent('model-construct-done', {
135
148
  composed: false,
@@ -138,135 +151,137 @@ export class FxModel extends HTMLElement {
138
151
  }),
139
152
  );
140
153
  }
141
- // console.timeEnd('instance-loading');
142
- this.inited = true;
143
- }
144
154
 
145
- registerModelItem(modelItem) {
146
- // console.log('ModelItem registered ', modelItem);
147
- this.modelItems.push(modelItem);
148
- }
149
-
150
- /**
151
- * update action triggering the update cycle
152
- */
153
- updateModel() {
154
- // console.time('updateModel');
155
- this.rebuild();
156
- /*
155
+ const functionlibImports = Array.from(this.querySelectorAll('fx-functionlib'));
156
+ await Promise.all(functionlibImports.map(lib => lib.readyPromise));
157
+ // console.timeEnd('instance-loading');
158
+ this.inited = true;
159
+ }
160
+
161
+ registerModelItem(modelItem) {
162
+ // console.log('ModelItem registered ', modelItem);
163
+ this.modelItems.push(modelItem);
164
+ }
165
+
166
+ /**
167
+ * update action triggering the update cycle
168
+ */
169
+ updateModel() {
170
+ // console.time('updateModel');
171
+ this.rebuild();
172
+ /*
157
173
  if (this.skipUpdate){
158
174
  console.info('%crecalculate/revalidate skipped - no bindings', 'font-style: italic; background: #90a4ae; color:lightgrey; padding:0.3rem 5rem 0.3rem 0.3rem;display:block;width:100%;');
159
175
  return;
160
176
  }
161
177
  */
162
- this.recalculate();
163
- this.revalidate();
164
- // console.log('updateModel finished with modelItems ', this.modelItems);
178
+ this.recalculate();
179
+ this.revalidate();
180
+ // console.log('updateModel finished with modelItems ', this.modelItems);
165
181
 
166
- // console.timeEnd('updateModel');
167
- }
182
+ // console.timeEnd('updateModel');
183
+ }
168
184
 
169
- rebuild() {
170
- console.log(`### <<<<< rebuild() '${this.fore.id}' >>>>>`);
185
+ rebuild() {
186
+ // console.log(`### <<<<< rebuild() '${this.fore.id}' >>>>>`);
171
187
 
172
- this.mainGraph = new DepGraph(false); // do: should be moved down below binds.length check but causes errors in tests.
173
- this.modelItems = [];
174
-
175
- // trigger recursive initialization of the fx-bind elements
176
- const binds = this.querySelectorAll('fx-model > fx-bind');
177
- if (binds.length === 0) {
178
- // console.log('skipped model update');
179
- this.skipUpdate = true;
180
- return;
181
- }
188
+ this.mainGraph = new DepGraph(false); // do: should be moved down below binds.length check but causes errors in tests.
189
+ this.modelItems = [];
182
190
 
183
- binds.forEach(bind => {
184
- bind.init(this);
185
- });
186
-
187
- console.log(`mainGraph`, this.mainGraph);
188
- console.log(`rebuild mainGraph calc order`, this.mainGraph.overallOrder());
189
-
190
- // this.dispatchEvent(new CustomEvent('rebuild-done', {detail: {maingraph: this.mainGraph}}));
191
- Fore.dispatch(this,'rebuild-done',{maingraph:this.mainGraph});
192
- console.log('mainGraph', this.mainGraph);
191
+ // trigger recursive initialization of the fx-bind elements
192
+ const binds = this.querySelectorAll('fx-model > fx-bind');
193
+ if (binds.length === 0) {
194
+ // console.log('skipped model update');
195
+ this.skipUpdate = true;
196
+ return;
193
197
  }
194
198
 
195
- /**
196
- * recalculation of all modelItems. Uses dependency graph to determine order of computation.
197
- *
198
- * todo: use 'changed' flag on modelItems to determine subgraph for recalculation. Flag already exists but is not used.
199
- */
200
- recalculate() {
201
-
202
- if (!this.mainGraph) {
203
- return;
204
- }
205
-
206
- console.log(`### <<<<< recalculate() '${this.fore.id}' >>>>>`);
207
-
208
- // console.log('changed nodes ', this.changed);
209
- this.computes = 0;
210
-
211
- this.subgraph = new DepGraph(false);
212
- // ### create the subgraph for all changed modelItems
213
- if (this.changed.length !== 0) {
214
- // ### build the subgraph
215
- this.changed.forEach(modelItem => {
216
- this.subgraph.addNode(modelItem.path, modelItem.node);
217
- // const dependents = this.mainGraph.dependantsOf(modelItem.path, false);
218
- // this._addSubgraphDependencies(modelItem.path);
219
- if (this.mainGraph.hasNode(modelItem.path)) {
220
- // const dependents = this.mainGraph.directDependantsOf(modelItem.path)
221
-
222
- const all = this.mainGraph.dependantsOf(modelItem.path, false);
223
- const dependents = all.reverse();
224
- if (dependents.length !== 0) {
225
- dependents.forEach(dep => {
226
- // const subdep = this.mainGraph.dependentsOf(dep,false);
227
- // subgraph.addDependency(dep, modelItem.path);
228
- const val = this.mainGraph.getNodeData(dep);
229
- this.subgraph.addNode(dep, val);
230
- if (dep.includes(':')) {
231
- const path = dep.substring(0, dep.indexOf(':'));
232
- this.subgraph.addNode(path, val);
233
-
234
- const deps = this.mainGraph.dependentsOf(modelItem.path, false);
235
- // if we find the dep to be first in list of dependents we are dependent on ourselves not adding edge to modelItem.path
236
- if (deps.indexOf(dep) !== 0) {
237
- this.subgraph.addDependency(dep, modelItem.path);
238
- }
239
- }
240
- // subgraph.addDependency(dep,modelItem.path);
241
- });
242
- }
243
- }
244
- });
199
+ binds.forEach(bind => {
200
+ bind.init(this);
201
+ });
202
+
203
+ console.log('mainGraph', this.mainGraph);
204
+ console.log('rebuild mainGraph calc order', this.mainGraph.overallOrder());
205
+
206
+ // this.dispatchEvent(new CustomEvent('rebuild-done', {detail: {maingraph: this.mainGraph}}));
207
+ Fore.dispatch(this, 'rebuild-done', { maingraph: this.mainGraph });
208
+ console.log('mainGraph', this.mainGraph);
209
+ }
210
+
211
+ /**
212
+ * recalculation of all modelItems. Uses dependency graph to determine order of computation.
213
+ *
214
+ * todo: use 'changed' flag on modelItems to determine subgraph for recalculation. Flag already exists but is not used.
215
+ */
216
+ recalculate() {
217
+ if (!this.mainGraph) {
218
+ return;
219
+ }
245
220
 
246
- // ### compute the subgraph
247
- const ordered = this.subgraph.overallOrder(false);
248
- ordered.forEach(path => {
249
- if (this.mainGraph.hasNode(path)) {
250
- const node = this.mainGraph.getNodeData(path);
251
- this.compute(node, path);
221
+ // console.log(`### <<<<< recalculate() '${this.fore.id}' >>>>>`);
222
+
223
+ // console.log('changed nodes ', this.changed);
224
+ this.computes = 0;
225
+
226
+ this.subgraph = new DepGraph(false);
227
+ // ### create the subgraph for all changed modelItems
228
+ if (this.changed.length !== 0) {
229
+ // ### build the subgraph
230
+ this.changed.forEach(modelItem => {
231
+ this.subgraph.addNode(modelItem.path, modelItem.node);
232
+ // const dependents = this.mainGraph.dependantsOf(modelItem.path, false);
233
+ // this._addSubgraphDependencies(modelItem.path);
234
+ if (this.mainGraph.hasNode(modelItem.path)) {
235
+ // const dependents = this.mainGraph.directDependantsOf(modelItem.path)
236
+
237
+ const all = this.mainGraph.dependantsOf(modelItem.path, false);
238
+ const dependents = all.reverse();
239
+ if (dependents.length !== 0) {
240
+ dependents.forEach(dep => {
241
+ // const subdep = this.mainGraph.dependentsOf(dep,false);
242
+ // subgraph.addDependency(dep, modelItem.path);
243
+ const val = this.mainGraph.getNodeData(dep);
244
+ this.subgraph.addNode(dep, val);
245
+ if (dep.includes(':')) {
246
+ const path = dep.substring(0, dep.indexOf(':'));
247
+ this.subgraph.addNode(path, val);
248
+
249
+ const deps = this.mainGraph.dependentsOf(modelItem.path, false);
250
+ // if we find the dep to be first in list of dependents we are dependent on ourselves not adding edge to modelItem.path
251
+ if (deps.indexOf(dep) !== 0) {
252
+ this.subgraph.addDependency(dep, modelItem.path);
252
253
  }
254
+ }
255
+ // subgraph.addDependency(dep,modelItem.path);
253
256
  });
254
- const toRefresh = [...this.changed];
255
- this.formElement.toRefresh = toRefresh;
256
- this.changed = [];
257
- Fore.dispatch(this,'recalculate-done',{graph:this.subgraph,computes:this.computes})
258
- } else {
259
- const v = this.mainGraph.overallOrder(false);
260
- v.forEach(path => {
261
- const node = this.mainGraph.getNodeData(path);
262
- this.compute(node, path);
263
- });
264
- Fore.dispatch(this,'recalculate-done',{graph:this.mainGraph,computes:this.computes})
257
+ }
258
+ }
259
+ });
260
+
261
+ // ### compute the subgraph
262
+ const ordered = this.subgraph.overallOrder(false);
263
+ ordered.forEach(path => {
264
+ if (this.mainGraph.hasNode(path)) {
265
+ const node = this.mainGraph.getNodeData(path);
266
+ this.compute(node, path);
265
267
  }
266
- console.log('recalculate finished with modelItems ', this.modelItems);
268
+ });
269
+ const toRefresh = [...this.changed];
270
+ this.formElement.toRefresh = toRefresh;
271
+ this.changed = [];
272
+ Fore.dispatch(this, 'recalculate-done', { graph: this.subgraph, computes: this.computes });
273
+ } else {
274
+ const v = this.mainGraph.overallOrder(false);
275
+ v.forEach(path => {
276
+ const node = this.mainGraph.getNodeData(path);
277
+ this.compute(node, path);
278
+ });
279
+ Fore.dispatch(this, 'recalculate-done', { graph: this.mainGraph, computes: this.computes });
267
280
  }
281
+ console.log(`${this.parentElement.id} recalculate finished with modelItems `, this.modelItems);
282
+ }
268
283
 
269
- /*
284
+ /*
270
285
  _addSubgraphDependencies(path){
271
286
  const dependents = this.mainGraph.directDependantsOf(path)
272
287
 
@@ -303,17 +318,17 @@ export class FxModel extends HTMLElement {
303
318
  }
304
319
  */
305
320
 
306
- /**
307
- * (re-) computes a modelItem.
308
- * @param node - the node the modelItem is attached to
309
- * @param path - the canonical XPath of the node
310
- */
311
- compute(node, path) {
312
- const modelItem = this.getModelItem(node);
313
- if (modelItem && path.includes(':')) {
314
- const property = path.split(':')[1];
315
- if (property) {
316
- /*
321
+ /**
322
+ * (re-) computes a modelItem.
323
+ * @param {Node} node - the node the modelItem is attached to
324
+ * @param {string} path - the canonical XPath of the node
325
+ */
326
+ compute(node, path) {
327
+ const modelItem = this.getModelItem(node);
328
+ if (modelItem && path.includes(':')) {
329
+ const property = path.split(':')[1];
330
+ if (property) {
331
+ /*
317
332
  if (property === 'readonly') {
318
333
  // make sure that calculated items are always readonly
319
334
  if(modelItem.bind['calculate']){
@@ -325,88 +340,90 @@ export class FxModel extends HTMLElement {
325
340
  }
326
341
  }
327
342
  */
328
- const expr = modelItem.bind[property];
329
- if (property === 'calculate') {
330
- const compute = evaluateXPath(expr, modelItem.node, this);
331
- modelItem.value = compute;
332
- modelItem.readonly = true; // calculated nodes are always readonly
333
- } else if (property !== 'constraint' && property !== 'type') {
334
- // ### re-compute the Boolean value of all facets expect 'constraint' and 'type' which are handled in revalidate()
335
- if (expr) {
336
- const compute = evaluateXPathToBoolean(expr, modelItem.node, this);
337
- modelItem[property] = compute;
338
- /*
343
+ const expr = modelItem.bind[property];
344
+ if (property === 'calculate') {
345
+ const compute = evaluateXPath(expr, modelItem.node, this);
346
+ modelItem.value = compute;
347
+ modelItem.readonly = true; // calculated nodes are always readonly
348
+ } else if (property !== 'constraint' && property !== 'type') {
349
+ // ### re-compute the Boolean value of all facets expect 'constraint' and 'type' which are handled in revalidate()
350
+ if (expr) {
351
+ const compute = evaluateXPathToBoolean(expr, modelItem.node, this);
352
+ modelItem[property] = compute;
353
+ /*
339
354
  console.log(
340
355
  `recalculating path ${path} - Expr:'${expr}' computed`,
341
356
  modelItem[property],
342
357
  );
343
358
  */
344
- }
345
- }
346
- }
347
- this.computes += 1;
359
+ }
348
360
  }
361
+ }
362
+ this.computes += 1;
349
363
  }
350
-
351
- /**
352
- * Iterates all modelItems to calculate the validation status.
353
- *
354
- * Model alerts are given on 'fx-bind' elements as either attribute `alert` or as `fx-alert` child elements.
355
- *
356
- * During model-construct all model alerts are added to the modelItem if any
357
- *
358
- * to revalidate:
359
- * Gets the `constraint` attribute declaration from modelItem.bind
360
- * Computes the XPath to a Boolean
361
- * Updates the modelItem.constraint property
362
- *
363
- * todo: type checking
364
- * todo: run browser validation API
365
- *
366
- */
367
- revalidate() {
368
-
369
- if (this.modelItems.length === 0) return true;
370
-
371
- console.log(`### <<<<< revalidate() '${this.fore.id}' >>>>>`);
372
-
373
- // reset submission validation
374
- // this.parentNode.classList.remove('submit-validation-failed')
375
- let valid = true;
376
- this.modelItems.forEach(modelItem => {
377
- // console.log('validating node ', modelItem.node);
378
- const {bind} = modelItem;
379
- if (bind) {
380
- /*
364
+ }
365
+
366
+ /**
367
+ * Iterates all modelItems to calculate the validation status.
368
+ *
369
+ * Model alerts are given on 'fx-bind' elements as either attribute `alert` or as `fx-alert` child elements.
370
+ *
371
+ * During model-construct all model alerts are added to the modelItem if any
372
+ *
373
+ * to revalidate:
374
+ * Gets the `constraint` attribute declaration from modelItem.bind
375
+ * Computes the XPath to a Boolean
376
+ * Updates the modelItem.constraint property
377
+ *
378
+ * todo: type checking
379
+ * todo: run browser validation API
380
+ *
381
+ */
382
+ revalidate() {
383
+ if (this.modelItems.length === 0) return true;
384
+
385
+ // console.log(`### <<<<< revalidate() '${this.fore.id}' >>>>>`);
386
+
387
+ // reset submission validation
388
+ // this.parentNode.classList.remove('submit-validation-failed')
389
+ let valid = true;
390
+ this.modelItems.forEach(modelItem => {
391
+ // console.log('validating node ', modelItem.node);
392
+ const { bind } = modelItem;
393
+ if (bind) {
394
+ /*
381
395
  todo: investigate why bind is an element when created in fx-bind.init() and an fx-bind object when
382
396
  created lazily.
383
397
  */
384
- if (typeof bind.hasAttribute === 'function' && bind.hasAttribute('constraint')) {
385
- const constraint = bind.getAttribute('constraint');
386
- if (constraint && modelItem.node) {
387
- const compute = evaluateXPathToBoolean(constraint, modelItem.node, this);
388
- // console.log('modelItem validity computed: ', compute);
389
- modelItem.constraint = compute;
390
- this.formElement.addToRefresh(modelItem); // let fore know that modelItem needs refresh
391
- if (!compute) {
392
- console.log('validation failed on modelitem ', modelItem);
393
- valid = false;
394
- }
395
- }
396
- }
397
- if (typeof bind.hasAttribute === 'function' && bind.hasAttribute('required')) {
398
- const required = bind.getAttribute('required');
399
- if (required) {
400
- const compute = evaluateXPathToBoolean(required, modelItem.node, this);
401
- // console.log('modelItem required computed: ', compute);
402
- modelItem.required = compute;
403
- this.formElement.addToRefresh(modelItem); // let fore know that modelItem needs refresh
404
- if (!modelItem.node.textContent) {
405
- console.log('node is required but has no value ', XPathUtil.getDocPath(modelItem.node));
406
- valid = false;
407
- }
408
- // if (!compute) valid = false;
409
- /*
398
+ if (typeof bind.hasAttribute === 'function' && bind.hasAttribute('constraint')) {
399
+ const constraint = bind.getAttribute('constraint');
400
+ if (constraint && modelItem.node) {
401
+ const compute = evaluateXPathToBoolean(constraint, modelItem.node, this);
402
+ // console.log('modelItem validity computed: ', compute);
403
+ modelItem.constraint = compute;
404
+ this.formElement.addToRefresh(modelItem); // let fore know that modelItem needs refresh
405
+ if (!compute) {
406
+ console.log('validation failed on modelitem ', modelItem);
407
+ valid = false;
408
+ }
409
+ }
410
+ }
411
+ if (typeof bind.hasAttribute === 'function' && bind.hasAttribute('required')) {
412
+ const required = bind.getAttribute('required');
413
+ if (required) {
414
+ const compute = evaluateXPathToBoolean(required, modelItem.node, this);
415
+ // console.log('modelItem required computed: ', compute);
416
+ modelItem.required = compute;
417
+ this.formElement.addToRefresh(modelItem); // let fore know that modelItem needs refresh
418
+ if (!modelItem.node.textContent) {
419
+ console.log(
420
+ 'node is required but has no value ',
421
+ XPathUtil.getDocPath(modelItem.node),
422
+ );
423
+ valid = false;
424
+ }
425
+ // if (!compute) valid = false;
426
+ /*
410
427
  if (!this.modelConstructed) {
411
428
  // todo: get alert from attribute or child element
412
429
  const alert = bind.getAlert();
@@ -415,105 +432,113 @@ export class FxModel extends HTMLElement {
415
432
  }
416
433
  }
417
434
  */
418
- }
419
- }
420
- }
421
- });
422
- console.log('modelItems after revalidate: ', this.modelItems);
423
- return valid;
424
- }
425
-
426
- addChanged(modelItem){
427
- if(this.inited){
428
- this.changed.push(modelItem);
435
+ }
429
436
  }
437
+ }
438
+ });
439
+ // console.log('modelItems after revalidate: ', this.modelItems);
440
+ return valid;
441
+ }
442
+
443
+ addChanged(modelItem) {
444
+ if (this.inited) {
445
+ this.changed.push(modelItem);
430
446
  }
431
-
432
- /**
433
- *
434
- * @param node
435
- * @returns {ModelItem}
436
- */
437
- getModelItem(node) {
438
- return this.modelItems.find(m => m.node === node);
439
- }
440
-
441
- /**
442
- * get the default evaluation context for this model.
443
- * @returns {Element} the
444
- */
445
- getDefaultContext() {
446
- return this.instances[0].getDefaultContext();
447
- }
448
-
447
+ }
448
+
449
+ /**
450
+ *
451
+ * @param {Node} node
452
+ * @returns {ModelItem}
453
+ */
454
+ getModelItem(node) {
455
+ return this.modelItems.find(m => m.node === node);
456
+ }
457
+
458
+ /**
459
+ * get the default evaluation context for this model.
460
+ * @returns {Element}
461
+ */
462
+ getDefaultContext() {
463
+ return this.instances[0].getDefaultContext();
464
+ }
465
+
466
+ /**
467
+ * @returns {import('./fx-instance.js').FxInstance}
468
+ */
449
469
  getDefaultInstance() {
470
+ /*
471
+ if (this.instances.length === 0) {
472
+ throw new Error('No instances defined. Fore cannot work without any <data/> elements.');
473
+ }
474
+ */
450
475
  if (this.instances.length) {
451
476
  return this.instances[0];
452
477
  }
453
478
  return this.getInstance('default');
454
479
  }
455
480
 
456
- getDefaultInstanceData() {
457
- return this.instances[0].getInstanceData();
458
- }
459
-
460
- getInstance(id) {
461
- // console.log('getInstance ', id);
462
- // console.log('instances ', this.instances);
463
- // console.log('instances array ',Array.from(this.instances));
464
-
465
- let found;
466
- if(id === 'default'){
467
- found = this.instances[0];
468
- }
469
- // ### lookup in local instances first
470
- if(!found) {
471
- const instArray = Array.from(this.instances);
472
- found = instArray.find(inst => inst.id === id);
473
- const parentFore = this.fore.parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE
474
- ? this.fore.parentNode.host.closest('fx-fore')
475
- : this.fore.parentNode.closest('fx-fore');
481
+ getDefaultInstanceData() {
482
+ return this.instances[0].getInstanceData();
483
+ }
476
484
 
477
- }
478
- // ### lookup in parent Fore if present
479
- if(!found){
480
- // const parentFore = this.fore.parentNode.closest('fx-fore');
481
- const parentFore = this.fore.parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE
482
- ? this.fore.parentNode.host.closest('fx-fore')
483
- : this.fore.parentNode.closest('fx-fore');
484
- if (parentFore) {
485
- console.log('shared instances from parent', this.parentNode.id);
486
- const parentInstances = parentFore.getModel().instances;
487
- const shared = parentInstances.filter(shared => shared.hasAttribute('shared'));
488
- found = shared.find(found => found.id === id);
489
- }
485
+ getInstance(id) {
486
+ // console.log('getInstance ', id);
487
+ // console.log('instances ', this.instances);
488
+ // console.log('instances array ',Array.from(this.instances));
490
489
 
491
- }
492
- if(found){
493
- return found;
494
- }
495
- if (id === 'default') {
496
- return this.getDefaultInstance(); // if id is not found always defaults to first in doc order
497
- }
498
- if(!found && this.fore.strict){
499
- // return this.getDefaultInstance(); // if id is not found always defaults to first in doc order
500
- Fore.dispatch(this, 'error', {
501
- origin: this,
502
- message: `Instance '${id}' does not exist`,
503
- level:'Error'
504
- });
505
- }
506
- return null;
490
+ let found;
491
+ if (id === 'default') {
492
+ found = this.instances[0];
507
493
  }
508
-
509
- evalBinding(bindingExpr) {
510
- // console.log('MODEL.evalBinding ', bindingExpr);
511
- // default context of evaluation is always the default instance
512
- const result = this.instances[0].evalXPath(bindingExpr);
513
- return result;
494
+ // ### lookup in local instances first
495
+ if (!found) {
496
+ const instArray = Array.from(this.instances);
497
+ found = instArray.find(inst => inst.id === id);
498
+ const parentFore =
499
+ this.fore.parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE
500
+ ? this.fore.parentNode.host.closest('fx-fore')
501
+ : this.fore.parentNode.closest('fx-fore');
502
+ }
503
+ // ### lookup in parent Fore if present
504
+ if (!found) {
505
+ // const parentFore = this.fore.parentNode.closest('fx-fore');
506
+ const parentFore =
507
+ this.fore.parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE
508
+ ? this.fore.parentNode.host.closest('fx-fore')
509
+ : this.fore.parentNode.closest('fx-fore');
510
+ if (parentFore) {
511
+ console.log('shared instances from parent', this.parentNode.id);
512
+ const parentInstances = parentFore.getModel().instances;
513
+ const shared = parentInstances.filter(shared => shared.hasAttribute('shared'));
514
+ found = shared.find(found => found.id === id);
515
+ }
516
+ }
517
+ if (found) {
518
+ return found;
519
+ }
520
+ if (id === 'default') {
521
+ return this.getDefaultInstance(); // if id is not found always defaults to first in doc order
522
+ }
523
+ if (!found && this.fore.strict) {
524
+ // return this.getDefaultInstance(); // if id is not found always defaults to first in doc order
525
+ Fore.dispatch(this, 'error', {
526
+ origin: this,
527
+ message: `Instance '${id}' does not exist`,
528
+ level: 'Error',
529
+ });
514
530
  }
531
+ return null;
532
+ }
533
+
534
+ evalBinding(bindingExpr) {
535
+ // console.log('MODEL.evalBinding ', bindingExpr);
536
+ // default context of evaluation is always the default instance
537
+ const result = this.instances[0].evalXPath(bindingExpr);
538
+ return result;
539
+ }
515
540
  }
516
541
 
517
542
  if (!customElements.get('fx-model')) {
518
- customElements.define('fx-model', FxModel);
543
+ customElements.define('fx-model', FxModel);
519
544
  }