@jinntec/fore 1.8.0 → 1.10.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jinntec/fore",
3
- "version": "1.8.0",
3
+ "version": "1.10.0",
4
4
  "description": "Fore - declarative user interfaces in plain HTML",
5
5
  "module": "./index.js",
6
6
  "publishConfig": {
@@ -34,6 +34,7 @@
34
34
  "fontoxpath": "^3.30.0"
35
35
  },
36
36
  "devDependencies": {
37
+ "@types/cypress": "^1.1.3",
37
38
  "@babel/plugin-proposal-class-properties": "^7.17.12",
38
39
  "@open-wc/building-rollup": "^2.0.1",
39
40
  "@open-wc/eslint-config": "^7.0.0",
@@ -47,6 +48,7 @@
47
48
  "@rollup/plugin-strip": "^2.1.0",
48
49
  "@skypack/package-check": "^0.2.2",
49
50
  "@webcomponents/webcomponentsjs": "^2.6.0",
51
+ "cypress": "^13.3.0",
50
52
  "deepmerge": "^4.2.2",
51
53
  "es-dev-server": "^2.1.0",
52
54
  "eslint": "^8.16.0",
@@ -82,7 +84,10 @@
82
84
  "start:build": "cd dist && es-dev-server --open",
83
85
  "build": "rimraf dist && rollup -c rollup.config.js",
84
86
  "start": "es-dev-server --app-index doc/index.html --node-resolve --watch --open",
85
- "install-demos": "cd demo && npm i"
87
+ "preversion": "npm run test",
88
+ "prepare": "npm run build",
89
+ "install-demos": "npm i && cd demo && npm i",
90
+ "start-cypress": "es-dev-server --app-index doc/index.html --node-resolve"
86
91
  },
87
92
  "keywords": [
88
93
  "Fore",
@@ -101,6 +101,10 @@ fx-alert {
101
101
  display: none;
102
102
  }
103
103
 
104
+ [valid] fx-alert{
105
+ display: none;
106
+ }
107
+
104
108
  .visited[invalid] fx-alert {
105
109
  display: block;
106
110
  }
@@ -114,7 +114,7 @@ export class DependencyNotifyingDomFacade {
114
114
  // eslint-disable-next-line class-methods-use-this
115
115
  getNextSibling(node, bucket) {
116
116
  for (let sibling = node.nextSibling; sibling; sibling = sibling.nextSibling) {
117
- if (!getBucketsForNode(sibling).includes(bucket)) {
117
+ if (bucket && !getBucketsForNode(sibling).includes(bucket)) {
118
118
  // eslint-disable-next-line no-continue
119
119
  continue;
120
120
  }
@@ -149,7 +149,7 @@ export class DependencyNotifyingDomFacade {
149
149
  previousSibling;
150
150
  previousSibling = previousSibling.previousSibling
151
151
  ) {
152
- if (!getBucketsForNode(previousSibling).includes(bucket)) {
152
+ if (bucket && !getBucketsForNode(previousSibling).includes(bucket)) {
153
153
  // eslint-disable-next-line no-continue
154
154
  continue;
155
155
  }
@@ -101,6 +101,8 @@ export class AbstractAction extends foreElementMixin(HTMLElement) {
101
101
  super();
102
102
  this.detail = {};
103
103
  this.needsUpdate = false;
104
+
105
+ this.changedPathsQueue = [];
104
106
  }
105
107
 
106
108
  connectedCallback() {
@@ -158,9 +160,9 @@ export class AbstractAction extends foreElementMixin(HTMLElement) {
158
160
 
159
161
  async performSafe() {
160
162
  try {
161
- await this.perform();
162
- // Return true to indicate success
163
- return true;
163
+ const changedPaths = await this.perform();
164
+ // Return truthy to indicate success
165
+ return changedPaths;
164
166
  } catch (error) {
165
167
  const stringifiedComponent = `<${this.localName} ${Array.from(this.attributes).map(attr=>`${attr.name}="${attr.value}"`).join(' ')}>…</${this.localName}>`;
166
168
  await Fore.dispatch(this, 'error', {
@@ -288,7 +290,8 @@ export class AbstractAction extends foreElementMixin(HTMLElement) {
288
290
  }
289
291
 
290
292
  // Perform the action once. But quit if it errored
291
- if (!this.performSafe()) {
293
+ const result = this.performSafe();
294
+ if (!result) {
292
295
  return;
293
296
  }
294
297
 
@@ -305,7 +308,8 @@ export class AbstractAction extends foreElementMixin(HTMLElement) {
305
308
 
306
309
  // After loop is done call actionPerformed to update the model and UI
307
310
  await loop();
308
- this._finalizePerform(resolveThisEvent);
311
+ // TODO: pass the result(s)
312
+ this._finalizePerform(resolveThisEvent, []);
309
313
 
310
314
  return;
311
315
  }
@@ -321,13 +325,18 @@ export class AbstractAction extends foreElementMixin(HTMLElement) {
321
325
  }
322
326
  }
323
327
 
324
- await this.performSafe();
325
- this._finalizePerform(resolveThisEvent);
328
+ const result = await this.performSafe();
329
+ if (!result) {
330
+ this._finalizePerform(resolveThisEvent, []);
331
+ return;
332
+ }
333
+ this._finalizePerform(resolveThisEvent, result);
334
+
326
335
  }
327
336
 
328
- _finalizePerform(resolveThisEvent) {
337
+ _finalizePerform(resolveThisEvent, changedPaths) {
329
338
  this.currentEvent = null;
330
- this.actionPerformed();
339
+ this.actionPerformed(changedPaths);
331
340
  if (FxFore.outermostHandler === this) {
332
341
  FxFore.outermostHandler = null;
333
342
  /*
@@ -363,6 +372,7 @@ export class AbstractAction extends foreElementMixin(HTMLElement) {
363
372
  this.evalInContext();
364
373
  }
365
374
 
375
+ /*
366
376
  this.dispatchEvent(
367
377
  new CustomEvent('execute-action', {
368
378
  composed: true,
@@ -371,13 +381,14 @@ export class AbstractAction extends foreElementMixin(HTMLElement) {
371
381
  detail: {action: this, event: this.event},
372
382
  }),
373
383
  );
384
+ */
374
385
 
375
386
  }
376
387
 
377
388
  /**
378
389
  * calls the update cycle if action signalled that update is needed.
379
390
  */
380
- actionPerformed() {
391
+ actionPerformed(changedPaths = []) {
381
392
  const model = this.getModel();
382
393
  if (!model) {
383
394
  return;
@@ -402,13 +413,15 @@ export class AbstractAction extends foreElementMixin(HTMLElement) {
402
413
  // console.log('running update cycle for outermostHandler', this);
403
414
  model.recalculate();
404
415
  model.revalidate();
405
- model.parentNode.refresh(true);
416
+ model.parentNode.refresh(false, [...this.changedPathsQueue, ...changedPaths]);
417
+ this.changedPathsQueue = [];
406
418
  this.dispatchActionPerformed();
407
419
  } else if (this.needsUpdate) {
408
420
  // console.log('Update delayed!');
409
421
  // We need an update, but the outermost action handler is not done yet. Make this clear!
410
422
  // console.log('running actionperformed on', this, ' to be updated by ', FxFore.outermostHandler);
411
423
  FxFore.outermostHandler.needsUpdate = true;
424
+ FxFore.outermostHandler.changedPathsQueue.push(...changedPaths);
412
425
  }
413
426
 
414
427
  // console.log('running actionperformed on', this, ' outermostHandler', FxFore.outermostHandler);
@@ -221,14 +221,19 @@ export class FxInsert extends AbstractAction {
221
221
  }
222
222
  }
223
223
  }
224
+ // instance('default')/items/item[index()]
224
225
 
225
226
  // console.log('insert context item ', insertLocationNode);
226
227
  // console.log('parent ', insertLocationNode.parentNode);
227
228
  // console.log('instance ', this.getModel().getDefaultContext());
228
229
  // Fore.dispatch()
229
230
 
230
- const inst = this.getModel().getInstance(XPathUtil.resolveInstance(this));
231
- // console.log('<<<<<<< resolved instance', inst);
231
+ const instanceId = XPathUtil.resolveInstance(this, this.getAttribute('context'));
232
+ const inst = this.getModel().getInstance(instanceId);
233
+ // console.log('<<<<<<< resolved instance', inst);
234
+ // Note: the parent to insert under is always the parent of the inserted node. The 'context' is not always the parent if the sequence is empty, or the position is different
235
+ const xpath = XPathUtil.getPath(originSequenceClone.parentNode, instanceId);
236
+
232
237
 
233
238
  const path = Fore.getDomNodeIndexString(originSequenceClone);
234
239
  this.dispatchEvent(
@@ -259,7 +264,9 @@ export class FxInsert extends AbstractAction {
259
264
  }),
260
265
  );
261
266
 
262
- this.needsUpdate = true;
267
+ this.needsUpdate = true;
268
+ console.log('Changed!', xpath)
269
+ return [xpath];
263
270
  }
264
271
 
265
272
  // eslint-disable-next-line class-methods-use-this
@@ -273,9 +280,9 @@ export class FxInsert extends AbstractAction {
273
280
  return null;
274
281
  }
275
282
 
276
- actionPerformed() {
277
- this.getModel().rebuild();
278
- super.actionPerformed();
283
+ actionPerformed(changedPaths) {
284
+ // this.getModel().rebuild();
285
+ super.actionPerformed(changedPaths);
279
286
  }
280
287
 
281
288
  /**
@@ -41,7 +41,7 @@ export class FxSetfocus extends AbstractAction {
41
41
  if(parentIItem){
42
42
  targetElement = parentIItem.querySelector(selector);
43
43
  this._focus(targetElement);
44
- return;
44
+ // return;
45
45
  }
46
46
 
47
47
  // ### the target element is hosted within a repeat
package/src/fore.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import getInScopeContext from "./getInScopeContext.js";
2
- import {evaluateXPathToString} from "./xpath-evaluation.js";
2
+ import {evaluateXPath, evaluateXPathToFirstNode, evaluateXPathToString, evaluateXPathToNodes} from "./xpath-evaluation.js";
3
3
  import { XPathUtil } from "./xpath-util.js";
4
4
 
5
5
  /**
@@ -18,6 +18,243 @@ export class Fore {
18
18
 
19
19
  static TYPE_DEFAULT = 'xs:string';
20
20
 
21
+ /**
22
+ * recursively walks along a template instance that contains all nodes relevant for editing and
23
+ * applying all nodes being present in partial instance onto it.
24
+ *
25
+ * @param start
26
+ * @param partial
27
+ * @returns {*}
28
+ * @private
29
+ */
30
+ static combine(start, partial, foreElement,expr){
31
+ if(!start) return;
32
+
33
+ const appended = false;
34
+ // ### get the path of the current element
35
+ let path = evaluateXPath('path()',start,foreElement)[0];
36
+ if(expr){
37
+ path = expr;
38
+ }
39
+ // const attrMap = new Map();
40
+ console.log('########process path', XPathUtil.shortenPath(path));
41
+ const predicates = Fore.buildPredicates(start);
42
+ // console.log('########process search', path);
43
+ // ### search the path in the partial instance
44
+ const toMerge = evaluateXPathToFirstNode( path,partial,foreElement);
45
+ if(expr){
46
+ console.log('skipped one - new toMerge', toMerge);
47
+ }
48
+ // console.log('tomerge node', toMerge);
49
+ // if(toMerge.length === 1){}
50
+ if(toMerge){
51
+ // console.log('merging nodes', toMerge);
52
+ const mergeAttrs = Fore.buildPredicates(toMerge);
53
+ if(predicates === mergeAttrs) {
54
+
55
+
56
+ // console.log('####### attr maps', attrMap, toMergeAttrMap);
57
+ // console.log('########process merge attrs', mergeAttrs);
58
+ if(start.getAttribute('type') !== toMerge.getAttribute('type') ){
59
+ console.log('###### element type attr not matching', start, toMerge);
60
+ const nextSibling = start.nextElementSibling;
61
+ if(nextSibling && nextSibling.nodeName === start.nodeName){
62
+ console.log('nextSibling', nextSibling, XPathUtil.shortenPath(path));
63
+ console.log('start', start, XPathUtil.shortenPath(path));
64
+ Fore.combine(start,partial,foreElement,path);
65
+ }
66
+ }
67
+
68
+ // ### iterate the attributes of the template node
69
+ // if(attrMap === toMergeAttrMap){
70
+ // if(start.attributes){
71
+ Array.from(start.attributes).forEach(attr => {
72
+ // ### if the template attribute has a matching attribute in partial...
73
+ if (toMerge.hasAttribute(attr.nodeName)) {
74
+ if (attr.nodeName !== 'xmlns') {
75
+ // console.log('overwrite attr', attr);
76
+ const toMergeAttr = toMerge.getAttribute(attr.nodeName);
77
+ // if (attr.nodeValue !== toMergeAttr.nodeValue) {
78
+ // ### apply the attribute value from partial to template
79
+ if(toMergeAttr){
80
+ start.setAttribute(attr.nodeName, toMergeAttr)
81
+ }
82
+ // }
83
+ }
84
+ }
85
+ });
86
+ // }
87
+ }else{
88
+ console.log('###### element in template but not in partial',start);
89
+ const nextSibling = start.nextElementSibling;
90
+ if(nextSibling && nextSibling.nodeName === start.nodeName){
91
+ Fore.combine(nextSibling,partial,foreElement,path);
92
+ }
93
+ }
94
+ // ### if we don't have children we still might have text
95
+ if(toMerge.children.length === 0){
96
+ const toMergeText = toMerge.textContent;
97
+ if(toMergeText){
98
+ start.textContent = toMergeText;
99
+ }
100
+ }
101
+
102
+ // ### if the template node does not have children but the partial has then copy them over
103
+ if(start.children.length === 0 && toMerge.children.length !== 0){
104
+ const mergeChildren = Array.from(toMerge.children);
105
+ mergeChildren.forEach(child => {
106
+ start.append(child);
107
+ });
108
+ }
109
+ }
110
+
111
+ // ### recurse
112
+ if(start.children){
113
+ Array.from(start.children).forEach(element => {
114
+ /*
115
+ console.log('stepping into element', element);
116
+ if(element.getAttribute('type') !== toMerge.getAttribute('type')){
117
+ // console.log('###### element in template but not in partial');
118
+ // const nextSibling = start.nextElementSibling;
119
+ const nextSibling = element.nextElementSibling;
120
+ if(nextSibling && nextSibling.nodeName === toMerge.nodeName){
121
+ console.log('nextSibling', nextSibling, XPathUtil.shortenPath(path));
122
+ Fore.combine(nextSibling,partial,foreElement,path);
123
+ }
124
+ }
125
+ */
126
+ return Fore.combine(element, partial,foreElement,null);
127
+
128
+
129
+ });
130
+ }
131
+ return start;
132
+ }
133
+ /*
134
+ static combine(start, partial, foreElement,expr){
135
+ if(!start) return;
136
+ // ### get the path of the current element
137
+ let path = evaluateXPath('path()',start,foreElement)[0];
138
+ if(expr){
139
+ path = expr;
140
+ }
141
+ // const attrMap = new Map();
142
+ console.log('########process path', XPathUtil.shortenPath(path));
143
+ const predicates = Fore.buildPredicates(start);
144
+ /!*
145
+ if(predicates){
146
+ path = path.substring(1, path.lastIndexOf('['));
147
+ }
148
+ path += Fore.buildPredicates(start);
149
+ *!/
150
+ // console.log('########process search', path);
151
+ // ### search the path in the partial instance
152
+ const toMerge = evaluateXPathToFirstNode( path,partial,foreElement);
153
+
154
+ // console.log('tomerge node', toMerge);
155
+ // if(toMerge.length === 1){}
156
+ if(toMerge){
157
+ // console.log('merging nodes', toMerge);
158
+ const mergeAttrs = Fore.buildPredicates(toMerge);
159
+ if(predicates === mergeAttrs) {
160
+
161
+
162
+ // console.log('####### attr maps', attrMap, toMergeAttrMap);
163
+ // console.log('########process merge attrs', mergeAttrs);
164
+ /!*
165
+ if(toMerge.getAttribute('type') !== start.getAttribute('type')){
166
+ // console.log('###### element in template but not in partial');
167
+ // const nextSibling = start.nextElementSibling;
168
+ const nextSibling = start.nextElementSibling;
169
+ if(nextSibling && nextSibling.nodeName === start.nodeName){
170
+ console.log('nextSibling', nextSibling, XPathUtil.shortenPath(path));
171
+ Fore.combine(nextSibling,partial,foreElement,path);
172
+ }
173
+ }
174
+ *!/
175
+
176
+ // ### iterate the attributes of the template node
177
+ // if(attrMap === toMergeAttrMap){
178
+ // if(start.attributes){
179
+ Array.from(start.attributes).forEach(attr => {
180
+ // ### if the template attribute has a matching attribute in partial...
181
+ if (toMerge.hasAttribute(attr.nodeName)) {
182
+ if (attr.nodeName !== 'xmlns') {
183
+ // console.log('overwrite attr', attr);
184
+ const toMergeAttr = toMerge.getAttribute(attr.nodeName);
185
+ // if (attr.nodeValue !== toMergeAttr.nodeValue) {
186
+ // ### apply the attribute value from partial to template
187
+ if(toMergeAttr){
188
+ start.setAttribute(attr.nodeName, toMergeAttr)
189
+ }
190
+ // }
191
+ }
192
+ }
193
+ });
194
+ // }
195
+ }else{
196
+ console.log('###### element in template but not in partial',start);
197
+ const nextSibling = start.nextElementSibling;
198
+ if(nextSibling && nextSibling.nodeName === start.nodeName){
199
+ Fore.combine(nextSibling,partial,foreElement,path);
200
+ }
201
+ }
202
+ // ### if we don't have children we still might have text
203
+ if(toMerge.children.length === 0){
204
+ const toMergeText = toMerge.textContent;
205
+ if(toMergeText){
206
+ start.textContent = toMergeText;
207
+ }
208
+ }
209
+
210
+ // ### if the template node does not have children but the partial has then copy them over
211
+ if(start.children.length === 0 && toMerge.children.length !== 0){
212
+ const mergeChildren = Array.from(toMerge.children);
213
+ mergeChildren.forEach(child => {
214
+ start.append(child);
215
+ });
216
+ }
217
+ }
218
+
219
+ // ### recurse
220
+ if(start.children){
221
+ Array.from(start.children).forEach(element => {
222
+ /!*
223
+ console.log('stepping into element', element);
224
+ if(element.getAttribute('type') !== toMerge.getAttribute('type')){
225
+ // console.log('###### element in template but not in partial');
226
+ // const nextSibling = start.nextElementSibling;
227
+ const nextSibling = element.nextElementSibling;
228
+ if(nextSibling && nextSibling.nodeName === toMerge.nodeName){
229
+ console.log('nextSibling', nextSibling, XPathUtil.shortenPath(path));
230
+ Fore.combine(nextSibling,partial,foreElement,path);
231
+ }
232
+ }
233
+ *!/
234
+ return Fore.combine(element, partial,foreElement);
235
+
236
+
237
+ });
238
+ }
239
+ return start;
240
+ }
241
+ */
242
+
243
+ static buildPredicates(node){
244
+ let attrPredicate='';
245
+ Array.from(node.attributes).forEach(attr =>{
246
+ // attrMap.set(attr.nodeName,attr.nodeValue);
247
+ // if(attr.nodeName !== 'xmlns'){
248
+ // if(attr.nodeValue !== ''){
249
+ // attrPredicate += `[@${attr.nodeName}='${attr.nodeValue}']`;
250
+ // }else{
251
+ attrPredicate += `[@${attr.nodeName}]`;
252
+ // }
253
+ // }
254
+ });
255
+ return attrPredicate
256
+ }
257
+
21
258
 
22
259
  /**
23
260
  * returns true if target element is the widget itself or some element within the widget.
package/src/fx-bind.js CHANGED
@@ -79,8 +79,10 @@ export class FxBind extends foreElementMixin(HTMLElement) {
79
79
 
80
80
  _buildBindGraph() {
81
81
  if (this.bindType === 'xml') {
82
- this.nodeset.forEach(node => {
83
- const path = XPathUtil.getPath(node);
82
+ this.nodeset.forEach(node => {
83
+ const instance = XPathUtil.resolveInstance(this);
84
+
85
+ const path = XPathUtil.getPath(node, instance);
84
86
  this.model.mainGraph.addNode(path, node);
85
87
 
86
88
  /* ### catching references in the 'ref' itself...
@@ -156,8 +158,10 @@ export class FxBind extends foreElementMixin(HTMLElement) {
156
158
  if (!this.model.mainGraph.hasNode(nodeHash)) {
157
159
  this.model.mainGraph.addNode(nodeHash, node);
158
160
  }
159
- refs.forEach(ref => {
160
- const otherPath = XPathUtil.getPath(ref);
161
+ refs.forEach(ref => {
162
+ const instance = XPathUtil.resolveInstance(this, path);
163
+
164
+ const otherPath = XPathUtil.getPath(ref, instance);
161
165
  // console.log('otherPath', otherPath)
162
166
 
163
167
  // todo: nasty hack to prevent duplicate pathes like 'a[1]' and 'a[1]/text()[1]' to end up as separate nodes in the graph
@@ -301,8 +305,10 @@ export class FxBind extends foreElementMixin(HTMLElement) {
301
305
  const targetNode = node;
302
306
 
303
307
  // const path = fx.evaluateXPath('path()',node);
304
- // const path = this.getPath(node);
305
- const path = XPathUtil.getPath(node);
308
+ // const path = this.getPath(node);
309
+ const instance = XPathUtil.resolveInstance(this, this.ref);
310
+
311
+ const path = XPathUtil.getPath(node, instance);
306
312
  // const shortPath = this.shortenPath(path);
307
313
 
308
314
  // ### constructing default modelitem - will get evaluated during recalculate()