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