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