@jinntec/fore 2.2.0 → 2.3.1

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 +1 -1
  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 +3 -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 +4 -6
  45. package/src/functions/fx-functionlib.js +39 -42
  46. package/src/functions/registerFunction.js +95 -86
  47. package/src/fx-bind.js +19 -24
  48. package/src/fx-connection.js +226 -0
  49. package/src/fx-fore.js +846 -815
  50. package/src/fx-header.js +4 -4
  51. package/src/fx-instance.js +23 -27
  52. package/src/fx-model.js +405 -383
  53. package/src/fx-submission.js +399 -397
  54. package/src/fx-var.js +6 -5
  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 +74 -56
  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 -959
  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,
@@ -139,137 +152,136 @@ export class FxModel extends HTMLElement {
139
152
  );
140
153
  }
141
154
 
142
- const functionlibImports = Array.from(this.querySelectorAll('fx-functionlib'));
143
- await Promise.all(functionlibImports.map(lib => lib.readyPromise));
144
- // console.timeEnd('instance-loading');
145
- this.inited = true;
146
- }
147
-
148
- registerModelItem(modelItem) {
149
- // console.log('ModelItem registered ', modelItem);
150
- this.modelItems.push(modelItem);
151
- }
152
-
153
- /**
154
- * update action triggering the update cycle
155
- */
156
- updateModel() {
157
- // console.time('updateModel');
158
- this.rebuild();
159
- /*
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
+ /*
160
173
  if (this.skipUpdate){
161
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%;');
162
175
  return;
163
176
  }
164
177
  */
165
- this.recalculate();
166
- this.revalidate();
167
- // console.log('updateModel finished with modelItems ', this.modelItems);
178
+ this.recalculate();
179
+ this.revalidate();
180
+ // console.log('updateModel finished with modelItems ', this.modelItems);
168
181
 
169
- // console.timeEnd('updateModel');
170
- }
182
+ // console.timeEnd('updateModel');
183
+ }
171
184
 
172
- rebuild() {
173
- console.log(`### <<<<< rebuild() '${this.fore.id}' >>>>>`);
185
+ rebuild() {
186
+ // console.log(`### <<<<< rebuild() '${this.fore.id}' >>>>>`);
174
187
 
175
- this.mainGraph = new DepGraph(false); // do: should be moved down below binds.length check but causes errors in tests.
176
- this.modelItems = [];
188
+ this.mainGraph = new DepGraph(false); // do: should be moved down below binds.length check but causes errors in tests.
189
+ this.modelItems = [];
177
190
 
178
- // trigger recursive initialization of the fx-bind elements
179
- const binds = this.querySelectorAll('fx-model > fx-bind');
180
- if (binds.length === 0) {
181
- // console.log('skipped model update');
182
- this.skipUpdate = true;
183
- return;
184
- }
185
-
186
- binds.forEach(bind => {
187
- bind.init(this);
188
- });
189
-
190
- console.log(`mainGraph`, this.mainGraph);
191
- console.log(`rebuild mainGraph calc order`, this.mainGraph.overallOrder());
192
-
193
- // this.dispatchEvent(new CustomEvent('rebuild-done', {detail: {maingraph: this.mainGraph}}));
194
- Fore.dispatch(this,'rebuild-done',{maingraph:this.mainGraph});
195
- 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;
196
197
  }
197
198
 
198
- /**
199
- * recalculation of all modelItems. Uses dependency graph to determine order of computation.
200
- *
201
- * todo: use 'changed' flag on modelItems to determine subgraph for recalculation. Flag already exists but is not used.
202
- */
203
- recalculate() {
204
-
205
- if (!this.mainGraph) {
206
- return;
207
- }
208
-
209
- console.log(`### <<<<< recalculate() '${this.fore.id}' >>>>>`);
210
-
211
- // console.log('changed nodes ', this.changed);
212
- this.computes = 0;
213
-
214
- this.subgraph = new DepGraph(false);
215
- // ### create the subgraph for all changed modelItems
216
- if (this.changed.length !== 0) {
217
- // ### build the subgraph
218
- this.changed.forEach(modelItem => {
219
- this.subgraph.addNode(modelItem.path, modelItem.node);
220
- // const dependents = this.mainGraph.dependantsOf(modelItem.path, false);
221
- // this._addSubgraphDependencies(modelItem.path);
222
- if (this.mainGraph.hasNode(modelItem.path)) {
223
- // const dependents = this.mainGraph.directDependantsOf(modelItem.path)
224
-
225
- const all = this.mainGraph.dependantsOf(modelItem.path, false);
226
- const dependents = all.reverse();
227
- if (dependents.length !== 0) {
228
- dependents.forEach(dep => {
229
- // const subdep = this.mainGraph.dependentsOf(dep,false);
230
- // subgraph.addDependency(dep, modelItem.path);
231
- const val = this.mainGraph.getNodeData(dep);
232
- this.subgraph.addNode(dep, val);
233
- if (dep.includes(':')) {
234
- const path = dep.substring(0, dep.indexOf(':'));
235
- this.subgraph.addNode(path, val);
236
-
237
- const deps = this.mainGraph.dependentsOf(modelItem.path, false);
238
- // if we find the dep to be first in list of dependents we are dependent on ourselves not adding edge to modelItem.path
239
- if (deps.indexOf(dep) !== 0) {
240
- this.subgraph.addDependency(dep, modelItem.path);
241
- }
242
- }
243
- // subgraph.addDependency(dep,modelItem.path);
244
- });
245
- }
246
- }
247
- });
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
+ }
248
220
 
249
- // ### compute the subgraph
250
- const ordered = this.subgraph.overallOrder(false);
251
- ordered.forEach(path => {
252
- if (this.mainGraph.hasNode(path)) {
253
- const node = this.mainGraph.getNodeData(path);
254
- 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);
255
253
  }
254
+ }
255
+ // subgraph.addDependency(dep,modelItem.path);
256
256
  });
257
- const toRefresh = [...this.changed];
258
- this.formElement.toRefresh = toRefresh;
259
- this.changed = [];
260
- Fore.dispatch(this,'recalculate-done',{graph:this.subgraph,computes:this.computes})
261
- } else {
262
- const v = this.mainGraph.overallOrder(false);
263
- v.forEach(path => {
264
- const node = this.mainGraph.getNodeData(path);
265
- this.compute(node, path);
266
- });
267
- Fore.dispatch(this,'recalculate-done',{graph:this.mainGraph,computes:this.computes})
257
+ }
268
258
  }
269
- console.log('recalculate finished with modelItems ', this.modelItems);
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);
267
+ }
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 });
270
280
  }
281
+ console.log(`${this.parentElement.id} recalculate finished with modelItems `, this.modelItems);
282
+ }
271
283
 
272
- /*
284
+ /*
273
285
  _addSubgraphDependencies(path){
274
286
  const dependents = this.mainGraph.directDependantsOf(path)
275
287
 
@@ -306,17 +318,17 @@ export class FxModel extends HTMLElement {
306
318
  }
307
319
  */
308
320
 
309
- /**
310
- * (re-) computes a modelItem.
311
- * @param node - the node the modelItem is attached to
312
- * @param path - the canonical XPath of the node
313
- */
314
- compute(node, path) {
315
- const modelItem = this.getModelItem(node);
316
- if (modelItem && path.includes(':')) {
317
- const property = path.split(':')[1];
318
- if (property) {
319
- /*
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
+ /*
320
332
  if (property === 'readonly') {
321
333
  // make sure that calculated items are always readonly
322
334
  if(modelItem.bind['calculate']){
@@ -328,88 +340,90 @@ export class FxModel extends HTMLElement {
328
340
  }
329
341
  }
330
342
  */
331
- const expr = modelItem.bind[property];
332
- if (property === 'calculate') {
333
- const compute = evaluateXPath(expr, modelItem.node, this);
334
- modelItem.value = compute;
335
- modelItem.readonly = true; // calculated nodes are always readonly
336
- } else if (property !== 'constraint' && property !== 'type') {
337
- // ### re-compute the Boolean value of all facets expect 'constraint' and 'type' which are handled in revalidate()
338
- if (expr) {
339
- const compute = evaluateXPathToBoolean(expr, modelItem.node, this);
340
- modelItem[property] = compute;
341
- /*
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
+ /*
342
354
  console.log(
343
355
  `recalculating path ${path} - Expr:'${expr}' computed`,
344
356
  modelItem[property],
345
357
  );
346
358
  */
347
- }
348
- }
349
- }
350
- this.computes += 1;
359
+ }
351
360
  }
361
+ }
362
+ this.computes += 1;
352
363
  }
353
-
354
- /**
355
- * Iterates all modelItems to calculate the validation status.
356
- *
357
- * Model alerts are given on 'fx-bind' elements as either attribute `alert` or as `fx-alert` child elements.
358
- *
359
- * During model-construct all model alerts are added to the modelItem if any
360
- *
361
- * to revalidate:
362
- * Gets the `constraint` attribute declaration from modelItem.bind
363
- * Computes the XPath to a Boolean
364
- * Updates the modelItem.constraint property
365
- *
366
- * todo: type checking
367
- * todo: run browser validation API
368
- *
369
- */
370
- revalidate() {
371
-
372
- if (this.modelItems.length === 0) return true;
373
-
374
- console.log(`### <<<<< revalidate() '${this.fore.id}' >>>>>`);
375
-
376
- // reset submission validation
377
- // this.parentNode.classList.remove('submit-validation-failed')
378
- let valid = true;
379
- this.modelItems.forEach(modelItem => {
380
- // console.log('validating node ', modelItem.node);
381
- const {bind} = modelItem;
382
- if (bind) {
383
- /*
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
+ /*
384
395
  todo: investigate why bind is an element when created in fx-bind.init() and an fx-bind object when
385
396
  created lazily.
386
397
  */
387
- if (typeof bind.hasAttribute === 'function' && bind.hasAttribute('constraint')) {
388
- const constraint = bind.getAttribute('constraint');
389
- if (constraint && modelItem.node) {
390
- const compute = evaluateXPathToBoolean(constraint, modelItem.node, this);
391
- // console.log('modelItem validity computed: ', compute);
392
- modelItem.constraint = compute;
393
- this.formElement.addToRefresh(modelItem); // let fore know that modelItem needs refresh
394
- if (!compute) {
395
- console.log('validation failed on modelitem ', modelItem);
396
- valid = false;
397
- }
398
- }
399
- }
400
- if (typeof bind.hasAttribute === 'function' && bind.hasAttribute('required')) {
401
- const required = bind.getAttribute('required');
402
- if (required) {
403
- const compute = evaluateXPathToBoolean(required, modelItem.node, this);
404
- // console.log('modelItem required computed: ', compute);
405
- modelItem.required = compute;
406
- this.formElement.addToRefresh(modelItem); // let fore know that modelItem needs refresh
407
- if (!modelItem.node.textContent) {
408
- console.log('node is required but has no value ', XPathUtil.getDocPath(modelItem.node));
409
- valid = false;
410
- }
411
- // if (!compute) valid = false;
412
- /*
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
+ /*
413
427
  if (!this.modelConstructed) {
414
428
  // todo: get alert from attribute or child element
415
429
  const alert = bind.getAlert();
@@ -418,105 +432,113 @@ export class FxModel extends HTMLElement {
418
432
  }
419
433
  }
420
434
  */
421
- }
422
- }
423
- }
424
- });
425
- console.log('modelItems after revalidate: ', this.modelItems);
426
- return valid;
427
- }
428
-
429
- addChanged(modelItem){
430
- if(this.inited){
431
- this.changed.push(modelItem);
435
+ }
432
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);
433
446
  }
434
-
435
- /**
436
- *
437
- * @param node
438
- * @returns {ModelItem}
439
- */
440
- getModelItem(node) {
441
- return this.modelItems.find(m => m.node === node);
442
- }
443
-
444
- /**
445
- * get the default evaluation context for this model.
446
- * @returns {Element} the
447
- */
448
- getDefaultContext() {
449
- return this.instances[0].getDefaultContext();
450
- }
451
-
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
+ */
452
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
+ */
453
475
  if (this.instances.length) {
454
476
  return this.instances[0];
455
477
  }
456
478
  return this.getInstance('default');
457
479
  }
458
480
 
459
- getDefaultInstanceData() {
460
- return this.instances[0].getInstanceData();
461
- }
462
-
463
- getInstance(id) {
464
- // console.log('getInstance ', id);
465
- // console.log('instances ', this.instances);
466
- // console.log('instances array ',Array.from(this.instances));
467
-
468
- let found;
469
- if(id === 'default'){
470
- found = this.instances[0];
471
- }
472
- // ### lookup in local instances first
473
- if(!found) {
474
- const instArray = Array.from(this.instances);
475
- found = instArray.find(inst => inst.id === id);
476
- const parentFore = this.fore.parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE
477
- ? this.fore.parentNode.host.closest('fx-fore')
478
- : this.fore.parentNode.closest('fx-fore');
481
+ getDefaultInstanceData() {
482
+ return this.instances[0].getInstanceData();
483
+ }
479
484
 
480
- }
481
- // ### lookup in parent Fore if present
482
- if(!found){
483
- // const parentFore = this.fore.parentNode.closest('fx-fore');
484
- const parentFore = this.fore.parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE
485
- ? this.fore.parentNode.host.closest('fx-fore')
486
- : this.fore.parentNode.closest('fx-fore');
487
- if (parentFore) {
488
- console.log('shared instances from parent', this.parentNode.id);
489
- const parentInstances = parentFore.getModel().instances;
490
- const shared = parentInstances.filter(shared => shared.hasAttribute('shared'));
491
- found = shared.find(found => found.id === id);
492
- }
485
+ getInstance(id) {
486
+ // console.log('getInstance ', id);
487
+ // console.log('instances ', this.instances);
488
+ // console.log('instances array ',Array.from(this.instances));
493
489
 
494
- }
495
- if(found){
496
- return found;
497
- }
498
- if (id === 'default') {
499
- return this.getDefaultInstance(); // if id is not found always defaults to first in doc order
500
- }
501
- if(!found && this.fore.strict){
502
- // return this.getDefaultInstance(); // if id is not found always defaults to first in doc order
503
- Fore.dispatch(this, 'error', {
504
- origin: this,
505
- message: `Instance '${id}' does not exist`,
506
- level:'Error'
507
- });
508
- }
509
- return null;
490
+ let found;
491
+ if (id === 'default') {
492
+ found = this.instances[0];
510
493
  }
511
-
512
- evalBinding(bindingExpr) {
513
- // console.log('MODEL.evalBinding ', bindingExpr);
514
- // default context of evaluation is always the default instance
515
- const result = this.instances[0].evalXPath(bindingExpr);
516
- 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
+ });
517
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
+ }
518
540
  }
519
541
 
520
542
  if (!customElements.get('fx-model')) {
521
- customElements.define('fx-model', FxModel);
543
+ customElements.define('fx-model', FxModel);
522
544
  }