@jinntec/fore 1.0.0-1 → 1.0.0-2

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/src/fx-model.js CHANGED
@@ -16,11 +16,13 @@ export class FxModel extends HTMLElement {
16
16
  this.instances = [];
17
17
  this.modelItems = [];
18
18
  this.defaultContext = {};
19
+ this.changed = [];
19
20
 
20
21
  // this.mainGraph = new DepGraph(false);
21
22
  this.inited = false;
22
23
  this.modelConstructed = false;
23
24
  this.attachShadow({ mode: 'open' });
25
+ this.computes = 0;
24
26
  }
25
27
 
26
28
  get formElement() {
@@ -39,7 +41,7 @@ export class FxModel extends HTMLElement {
39
41
  console.log('model-construct-done fired ', e.detail.model.instances);
40
42
  });
41
43
 
42
- // logging
44
+ this.skipUpdate = false;
43
45
  }
44
46
 
45
47
  static lazyCreateModelItem(model, ref, node) {
@@ -83,10 +85,20 @@ export class FxModel extends HTMLElement {
83
85
  return mi;
84
86
  }
85
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
+ */
86
97
  modelConstruct() {
87
98
  console.log('### <<<<< dispatching model-construct >>>>>');
88
99
  this.dispatchEvent(new CustomEvent('model-construct', { detail: this }));
89
100
 
101
+ console.time('instance-loading');
90
102
  const instances = this.querySelectorAll('fx-instance');
91
103
  if (instances.length > 0) {
92
104
  console.group('init instances');
@@ -121,6 +133,7 @@ export class FxModel extends HTMLElement {
121
133
  }),
122
134
  );
123
135
  }
136
+ console.timeEnd('instance-loading');
124
137
  this.inited = true;
125
138
  }
126
139
 
@@ -133,33 +146,45 @@ export class FxModel extends HTMLElement {
133
146
  * update action triggering the update cycle
134
147
  */
135
148
  updateModel() {
149
+ console.time('updateModel');
136
150
  this.rebuild();
151
+ if(this.skipUpdate) return;
137
152
  this.recalculate();
138
153
  this.revalidate();
154
+ console.timeEnd('updateModel');
139
155
  }
140
156
 
141
157
  rebuild() {
142
158
  console.group('### rebuild');
143
-
159
+ console.time('rebuild');
144
160
  this.mainGraph = new DepGraph(false);
145
161
  this.modelItems = [];
146
162
 
147
163
  // trigger recursive initialization of the fx-bind elements
148
164
  const binds = this.querySelectorAll('fx-model > fx-bind');
165
+ if(binds.length === 0 ) {
166
+ console.log('skipped model update');
167
+ this.skipUpdate = true;
168
+ return ;
169
+ }
149
170
  binds.forEach(bind => {
150
171
  bind.init(this);
151
172
  });
173
+ console.timeEnd('rebuild');
152
174
 
153
175
  // console.log(`dependencies of a `, this.mainGraph.dependenciesOf("/Q{}data[1]/Q{}a[1]:required"));
154
176
  // console.log(`dependencies of b `, this.mainGraph.dependenciesOf("/Q{}data[1]/Q{}b[1]:required"));
155
177
  console.log(`rebuild mainGraph`, this.mainGraph);
156
178
  console.log(`rebuild mainGraph calc order`, this.mainGraph.overallOrder());
179
+
180
+ this.dispatchEvent(new CustomEvent('rebuild-done', { detail: { maingraph: this.mainGraph } }));
181
+
157
182
  /*
158
- console.log(
159
- `rebuild finished with modelItems ${this.modelItems.length} item(s)`,
160
- this.modelItems,
161
- );
162
- */
183
+ console.log(
184
+ `rebuild finished with modelItems ${this.modelItems.length} item(s)`,
185
+ this.modelItems,
186
+ );
187
+ */
163
188
  console.groupEnd();
164
189
  }
165
190
 
@@ -172,32 +197,66 @@ export class FxModel extends HTMLElement {
172
197
  console.group('### recalculate');
173
198
  console.log('recalculate instances ', this.instances);
174
199
 
175
- const v = this.mainGraph.overallOrder();
176
- v.forEach(path => {
177
- const node = this.mainGraph.getNodeData(path);
178
- const modelItem = this.getModelItem(node);
179
-
180
- if (modelItem && path.includes(':')) {
181
- const property = path.split(':')[1];
182
- if (property) {
183
- if (property === 'calculate') {
184
- const expr = modelItem.bind[property];
185
- const compute = evaluateXPath(expr, modelItem.node, this);
186
- modelItem.value = compute;
187
- } else if (property !== 'constraint' && property !== 'type') {
188
- const expr = modelItem.bind[property];
189
- if (expr) {
190
- const compute = evaluateXPathToBoolean(expr, modelItem.node, this);
191
- modelItem[property] = compute;
192
- console.log(
193
- `recalculating path ${path} - Expr:'${expr}' computed`,
194
- modelItem[property],
195
- );
196
- }
200
+ console.time('recalculate');
201
+ this.computes = 0;
202
+
203
+ this.subgraph = new DepGraph(false);
204
+ if (this.changed.length !== 0) {
205
+ // ### build the subgraph
206
+ this.changed.forEach(modelItem => {
207
+ this.subgraph.addNode(modelItem.path, modelItem.node);
208
+ // const dependents = this.mainGraph.dependantsOf(modelItem.path, false);
209
+ // this._addSubgraphDependencies(modelItem.path);
210
+ if (this.mainGraph.hasNode(modelItem.path)) {
211
+ // const dependents = this.mainGraph.directDependantsOf(modelItem.path)
212
+
213
+ const all = this.mainGraph.dependantsOf(modelItem.path, false);
214
+ const dependents = all.reverse();
215
+ if (dependents.length !== 0) {
216
+ dependents.forEach(dep => {
217
+ // const subdep = this.mainGraph.dependentsOf(dep,false);
218
+ // subgraph.addDependency(dep, modelItem.path);
219
+ const val = this.mainGraph.getNodeData(dep);
220
+ this.subgraph.addNode(dep, val);
221
+ if (dep.includes(':')) {
222
+ const path = dep.substring(0, dep.indexOf(':'));
223
+ this.subgraph.addNode(path, val);
224
+
225
+ const deps = this.mainGraph.dependentsOf(modelItem.path, false);
226
+ // if we find the dep to be first in list of dependents we are dependent on ourselves not adding edge to modelItem.path
227
+ if (deps.indexOf(dep) !== 0) {
228
+ this.subgraph.addDependency(dep, modelItem.path);
229
+ }
230
+ }
231
+ // subgraph.addDependency(dep,modelItem.path);
232
+ });
197
233
  }
198
234
  }
199
- }
200
- });
235
+ });
236
+
237
+ // ### compute the subgraph
238
+ const ordered = this.subgraph.overallOrder(false);
239
+ ordered.forEach(path => {
240
+ if (this.mainGraph.hasNode(path)) {
241
+ const node = this.mainGraph.getNodeData(path);
242
+ this.compute(node, path);
243
+ }
244
+ });
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
+ });
256
+ }
257
+ console.log(`recalculated ${this.computes} modelItems`);
258
+
259
+ console.timeEnd('recalculate');
201
260
  console.log(
202
261
  `recalculate finished with modelItems ${this.modelItems.length} item(s)`,
203
262
  this.modelItems,
@@ -205,6 +264,86 @@ export class FxModel extends HTMLElement {
205
264
  console.groupEnd();
206
265
  }
207
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) {
334
+ const compute = evaluateXPathToBoolean(expr, modelItem.node, this);
335
+ modelItem[property] = compute;
336
+ console.log(
337
+ `recalculating path ${path} - Expr:'${expr}' computed`,
338
+ modelItem[property],
339
+ );
340
+ }
341
+ }
342
+ }
343
+ this.computes += 1;
344
+ }
345
+ }
346
+
208
347
  /**
209
348
  * Iterates all modelItems to calculate the validation status.
210
349
  *
@@ -224,6 +363,7 @@ export class FxModel extends HTMLElement {
224
363
  revalidate() {
225
364
  console.group('### revalidate');
226
365
 
366
+ console.time('revalidate');
227
367
  let valid = true;
228
368
  this.modelItems.forEach(modelItem => {
229
369
  // console.log('validating node ', modelItem.node);
@@ -231,9 +371,9 @@ export class FxModel extends HTMLElement {
231
371
  const { bind } = modelItem;
232
372
  if (bind) {
233
373
  /*
234
- todo: investigate why bind is an element when created in fx-bind.init() and an fx-bind object when
235
- created lazily.
236
- */
374
+ todo: investigate why bind is an element when created in fx-bind.init() and an fx-bind object when
375
+ created lazily.
376
+ */
237
377
  if (typeof bind.hasAttribute === 'function' && bind.hasAttribute('constraint')) {
238
378
  const constraint = bind.getAttribute('constraint');
239
379
  if (constraint) {
@@ -253,6 +393,7 @@ export class FxModel extends HTMLElement {
253
393
  }
254
394
  }
255
395
  });
396
+ console.timeEnd('revalidate');
256
397
  console.log('modelItems after revalidate: ', this.modelItems);
257
398
  console.groupEnd();
258
399
  return valid;
@@ -26,6 +26,9 @@ export class FxSubmission extends foreElementMixin(HTMLElement) {
26
26
  /** if present should be a existing instance id */
27
27
  this.instance = this.hasAttribute('instance') ? this.getAttribute('instance') : null;
28
28
 
29
+ /** if present will determine XPath where to insert a response into when mode is 'replace' */
30
+ this.into = this.hasAttribute('into') ? this.getAttribute('into') : null;
31
+
29
32
  /** http method */
30
33
  this.method = this.hasAttribute('method') ? this.getAttribute('method') : 'get';
31
34
 
@@ -173,9 +176,17 @@ export class FxSubmission extends foreElementMixin(HTMLElement) {
173
176
  body: serialized,
174
177
  });
175
178
 
176
- const contentType = response.headers.get('content-type').toLowerCase();
179
+ if (!response.ok || response.status > 400) {
180
+ this.dispatch('submit-error', { message: `Error while submitting ${this.id}` });
181
+ return;
182
+ }
177
183
 
178
- if (contentType.startsWith('text/plain') || contentType.startsWith('text/html')) {
184
+ const contentType = response.headers.get('content-type').toLowerCase();
185
+ if (
186
+ contentType.startsWith('text/plain') ||
187
+ contentType.startsWith('text/html') ||
188
+ contentType.startsWith('text/markdown')
189
+ ) {
179
190
  const text = await response.text();
180
191
  this._handleResponse(text);
181
192
  } else if (contentType.startsWith('application/json')) {
@@ -190,10 +201,6 @@ export class FxSubmission extends foreElementMixin(HTMLElement) {
190
201
  this._handleResponse(blob);
191
202
  }
192
203
 
193
- if (!response.ok || response.status > 400) {
194
- this.dispatch('submit-error', { message: `Error while submitting ${this.id}` });
195
- return;
196
- }
197
204
  this.dispatch('submit-done', {});
198
205
  }
199
206
 
@@ -265,12 +272,24 @@ export class FxSubmission extends foreElementMixin(HTMLElement) {
265
272
  const targetInstance = this._getTargetInstance();
266
273
  if (targetInstance) {
267
274
  if (this.targetref) {
268
- const theTarget = evaluateXPath(this.targetref, targetInstance, this.getOwnerForm());
275
+ const theTarget = evaluateXPath(
276
+ this.targetref,
277
+ targetInstance.instanceData.firstElementChild,
278
+ this,
279
+ );
269
280
  console.log('theTarget', theTarget);
270
281
  const clone = data.firstElementChild;
271
282
  const parent = theTarget.parentNode;
272
283
  parent.replaceChild(clone, theTarget);
273
284
  console.log('finally ', parent);
285
+ } else if (this.into) {
286
+ const theTarget = evaluateXPath(
287
+ this.into,
288
+ targetInstance.instanceData.firstElementChild,
289
+ this,
290
+ );
291
+ console.log('theTarget', theTarget);
292
+ theTarget.innerHTML = data;
274
293
  } else {
275
294
  const instanceData = data;
276
295
  targetInstance.instanceData = instanceData;
@@ -31,7 +31,7 @@ function _getInitialContext(node, ref) {
31
31
  const instanceId = XPathUtil.getInstanceId(ref);
32
32
  return model.getInstance(instanceId).getDefaultContext();
33
33
  }
34
- if (model.getDefaultInstance() !== null) {
34
+ if (model.getDefaultInstance() !== null && model.inited) {
35
35
  return model.getDefaultInstance().getDefaultContext();
36
36
  }
37
37
  return [];
@@ -26,8 +26,8 @@ export default class AbstractControl extends foreElementMixin(HTMLElement) {
26
26
  /**
27
27
  * (re)apply all modelItem state properties to this control. model -> UI
28
28
  */
29
- async refresh() {
30
- console.log('### AbstractControl.refresh on : ', this);
29
+ async refresh(force) {
30
+ // console.log('### AbstractControl.refresh on : ', this);
31
31
 
32
32
  const currentVal = this.value;
33
33
 
@@ -67,16 +67,21 @@ export default class AbstractControl extends foreElementMixin(HTMLElement) {
67
67
  // this.requestUpdate();
68
68
  }
69
69
  }
70
+ // Fore.refreshChildren(this,force);
70
71
  // await this.updateComplete;
71
72
  }
72
73
 
74
+ /**
75
+ *
76
+ * @returns {Promise<void>}
77
+ */
73
78
  // eslint-disable-next-line class-methods-use-this
74
79
  async updateWidgetValue() {
75
80
  throw new Error('You have to implement the method updateWidgetValue!');
76
81
  }
77
82
 
78
83
  handleModelItemProperties() {
79
- console.log('form ready', this.getOwnerForm().ready);
84
+ // console.log('form ready', this.getOwnerForm().ready);
80
85
  this.handleRequired();
81
86
  this.handleReadonly();
82
87
  if (this.getOwnerForm().ready) {
@@ -1,12 +1,14 @@
1
- import { html, css } from 'lit-element';
1
+ import AbstractControl from './abstract-control.js';
2
2
 
3
- import XfAbstractControl from './abstract-control.js';
3
+ export class FxAlert extends AbstractControl {
4
+ constructor() {
5
+ super();
6
+ this.attachShadow({ mode: 'open' });
7
+ }
4
8
 
5
- export class FxAlert extends XfAbstractControl {
6
- static get styles() {
7
- return css`
9
+ connectedCallback() {
10
+ const style = `
8
11
  :host {
9
- display: block;
10
12
  height: auto;
11
13
  font-size: 0.8em;
12
14
  font-weight: 400;
@@ -14,23 +16,17 @@ export class FxAlert extends XfAbstractControl {
14
16
  display: none;
15
17
  }
16
18
  `;
17
- }
18
-
19
- constructor() {
20
- super();
21
- this.style.display = 'none';
22
- }
23
-
24
- static get properties() {
25
- return {
26
- ...super.properties,
27
- };
28
- }
29
19
 
30
- render() {
31
- return html`
20
+ const html = `
32
21
  <slot></slot>
33
22
  `;
23
+
24
+ this.shadowRoot.innerHTML = `
25
+ <style>
26
+ ${style}
27
+ </style>
28
+ ${html}
29
+ `;
34
30
  }
35
31
 
36
32
  async updateWidgetValue() {
@@ -30,18 +30,21 @@ export class FxContainer extends foreElementMixin(HTMLElement) {
30
30
  </style>
31
31
  ${html}
32
32
  `;
33
+
34
+ this.getOwnerForm().registerLazyElement(this);
33
35
  }
34
36
 
35
37
  /**
36
38
  * (re)apply all state properties to this control.
37
39
  */
38
- refresh() {
39
- console.log('### FxContainer.refresh on : ', this);
40
+ refresh(force) {
41
+ if (!force && this.hasAttribute('refresh-on-view')) return;
42
+ // console.log('### FxContainer.refresh on : ', this);
40
43
 
41
44
  if (this.isBound()) {
42
45
  this.evalInContext();
43
46
  this.modelItem = this.getModelItem();
44
- this.value = this.modelItem.value;
47
+ // this.value = this.modelItem.value;
45
48
  }
46
49
 
47
50
  // await this.updateComplete;
@@ -50,7 +53,7 @@ export class FxContainer extends foreElementMixin(HTMLElement) {
50
53
  if (this._getForm().ready) {
51
54
  this.handleModelItemProperties();
52
55
  }
53
- Fore.refreshChildren(this);
56
+ Fore.refreshChildren(this, force);
54
57
  }
55
58
 
56
59
  handleModelItemProperties() {
@@ -78,6 +81,8 @@ export class FxContainer extends foreElementMixin(HTMLElement) {
78
81
 
79
82
  handleRelevant() {
80
83
  // console.log('mip valid', this.modelItem.enabled);
84
+ if (!this.modelItem) return;
85
+
81
86
  if (this.isEnabled() !== this.modelItem.enabled) {
82
87
  if (this.modelItem.enabled) {
83
88
  this.dispatchEvent(new CustomEvent('enabled', {}));