@jinntec/fore 1.0.0-2 → 1.0.0-5

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.
@@ -4,6 +4,7 @@ import { Fore } from '../fore.js';
4
4
  import { foreElementMixin } from '../ForeElementMixin.js';
5
5
  import { evaluateXPath } from '../xpath-evaluation.js';
6
6
  import getInScopeContext from '../getInScopeContext.js';
7
+ import { XPathUtil } from '../xpath-util';
7
8
 
8
9
  /**
9
10
  * `fx-repeat`
@@ -99,28 +100,44 @@ export class FxRepeat extends foreElementMixin(HTMLElement) {
99
100
  this.index = idx + 1;
100
101
  });
101
102
  // todo: review - this is just used by append action - event consolidation ?
102
- this.addEventListener('index-changed', e => {
103
+ document.addEventListener('index-changed', e => {
103
104
  e.stopPropagation();
104
105
  if (!e.target === this) return;
105
106
  console.log('handle index event ', e);
106
107
  // const { item } = e.detail;
107
108
  // const idx = Array.from(this.children).indexOf(item);
108
109
  const { index } = e.detail;
109
- this.index = index;
110
+ this.index = Number(index);
110
111
  this.applyIndex(this.children[index - 1]);
111
112
  });
113
+ /*
112
114
  document.addEventListener('insert', e => {
113
115
  const nodes = e.detail.insertedNodes;
114
116
  this.index = e.detail.position;
115
117
  console.log('insert catched', nodes, this.index);
116
118
  });
117
-
118
- if (this.getOwnerForm().lazyRefresh) {
119
- this.mutationObserver = new MutationObserver(mutations => {
120
- console.log('mutations', mutations);
121
- this.refresh(true);
122
- });
123
- }
119
+ */
120
+
121
+ // if (this.getOwnerForm().lazyRefresh) {
122
+ this.mutationObserver = new MutationObserver(mutations => {
123
+ console.log('mutations', mutations);
124
+
125
+ if (mutations[0].type === 'childList') {
126
+ const added = mutations[0].addedNodes[0];
127
+ if (added) {
128
+ const path = XPathUtil.getPath(added);
129
+ console.log('path mutated', path);
130
+ // this.dispatch('path-mutated',{'path':path,'nodeset':this.nodeset,'index': this.index});
131
+ // this.index = index;
132
+ // const prev = mutations[0].previousSibling.previousElementSibling;
133
+ // const index = prev.index();
134
+ // this.applyIndex(this.index -1);
135
+
136
+ this.dispatch('path-mutated', { path, index: this.index });
137
+ }
138
+ }
139
+ });
140
+ // }
124
141
  this.getOwnerForm().registerLazyElement(this);
125
142
 
126
143
  const style = `
@@ -168,7 +185,7 @@ export class FxRepeat extends foreElementMixin(HTMLElement) {
168
185
  */
169
186
  _evalNodeset() {
170
187
  // const inscope = this.getInScopeContext();
171
- const inscope = getInScopeContext(this, this.ref);
188
+ const inscope = getInScopeContext(this.getAttributeNode('ref') || this, this.ref);
172
189
  // console.log('##### inscope ', inscope);
173
190
  // console.log('##### ref ', this.ref);
174
191
  // now we got a nodeset and attach MutationObserver to it
@@ -180,7 +197,7 @@ export class FxRepeat extends foreElementMixin(HTMLElement) {
180
197
  });
181
198
  }
182
199
 
183
- const seq = evaluateXPath(this.ref, inscope, this.getOwnerForm());
200
+ const seq = evaluateXPath(this.ref, inscope, this);
184
201
  // const seq = evaluateXPathToNodes(this.ref, inscope, this.getOwnerForm());
185
202
  if (seq === null) {
186
203
  // Empty sequence
@@ -248,6 +265,8 @@ export class FxRepeat extends foreElementMixin(HTMLElement) {
248
265
  newItem.appendChild(clonedTemplate);
249
266
  this.appendChild(newItem);
250
267
 
268
+ this._initVariables(newItem);
269
+
251
270
  newItem.nodeset = this.nodeset[position - 1];
252
271
  newItem.index = position;
253
272
  }
@@ -353,9 +372,24 @@ export class FxRepeat extends foreElementMixin(HTMLElement) {
353
372
  if (repeatItem.index === 1) {
354
373
  this.applyIndex(repeatItem);
355
374
  }
375
+
376
+ this._initVariables(repeatItem);
356
377
  });
357
378
  }
358
379
 
380
+ _initVariables(newRepeatItem) {
381
+ const inScopeVariables = new Map(this.inScopeVariables);
382
+ newRepeatItem.setInScopeVariables(inScopeVariables);
383
+ (function registerVariables(node) {
384
+ for (const child of node.children) {
385
+ if ('setInScopeVariables' in child) {
386
+ child.setInScopeVariables(inScopeVariables);
387
+ }
388
+ registerVariables(child);
389
+ }
390
+ })(newRepeatItem);
391
+ }
392
+
359
393
  _clone() {
360
394
  // const content = this.template.content.cloneNode(true);
361
395
  this.template = this.shadowRoot.querySelector('template');
@@ -368,6 +402,12 @@ export class FxRepeat extends foreElementMixin(HTMLElement) {
368
402
  item.removeAttribute('repeat-index');
369
403
  });
370
404
  }
405
+
406
+ setInScopeVariables(inScopeVariables) {
407
+ // Repeats are interesting: the variables should be scoped per repeat item, they should not be
408
+ // able to see the variables in adjacent repeat items!
409
+ this.inScopeVariables = new Map(inScopeVariables);
410
+ }
371
411
  }
372
412
 
373
413
  window.customElements.define('fx-repeat', FxRepeat);
@@ -80,6 +80,9 @@ export class FxTrigger extends XfAbstractControl {
80
80
  repeatedItem.click();
81
81
  }
82
82
 
83
+ // Update all child variables, but only once
84
+ this.querySelectorAll('fx-var').forEach(variableElement => variableElement.refresh());
85
+
83
86
  const forLoop = async () => {
84
87
  for (let i = 0; i < this.children.length; i += 1) {
85
88
  const child = this.children[i];
@@ -10,38 +10,14 @@ import {
10
10
  registerCustomXPathFunction,
11
11
  registerXQueryModule,
12
12
  } from 'fontoxpath';
13
+ import { Fore } from './fore.js';
14
+
13
15
  import { XPathUtil } from './xpath-util.js';
14
16
 
15
17
  const XFORMS_NAMESPACE_URI = 'http://www.w3.org/2002/xforms';
16
18
 
17
19
  const createdNamespaceResolversByXPathQueryAndNode = new Map();
18
20
 
19
- function prettifyXml(source) {
20
- const xmlDoc = new DOMParser().parseFromString(source, 'application/xml');
21
- const xsltDoc = new DOMParser().parseFromString(
22
- [
23
- // describes how we want to modify the XML - indent everything
24
- '<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform">',
25
- ' <xsl:strip-space elements="*"/>',
26
- ' <xsl:template match="para[content-style][not(text())]">', // change to just text() to strip space in text nodes
27
- ' <xsl:value-of select="normalize-space(.)"/>',
28
- ' </xsl:template>',
29
- ' <xsl:template match="node()|@*">',
30
- ' <xsl:copy><xsl:apply-templates select="node()|@*"/></xsl:copy>',
31
- ' </xsl:template>',
32
- ' <xsl:output indent="yes"/>',
33
- '</xsl:stylesheet>',
34
- ].join('\n'),
35
- 'application/xml',
36
- );
37
-
38
- const xsltProcessor = new XSLTProcessor();
39
- xsltProcessor.importStylesheet(xsltDoc);
40
- const resultDoc = xsltProcessor.transformToDocument(xmlDoc);
41
- const resultXml = new XMLSerializer().serializeToString(resultDoc);
42
- return resultXml;
43
- }
44
-
45
21
  function getCachedNamespaceResolver(xpath, node) {
46
22
  if (!createdNamespaceResolversByXPathQueryAndNode.has(xpath)) {
47
23
  return null;
@@ -217,6 +193,34 @@ function functionNameResolver({ prefix, localName }, _arity) {
217
193
  }
218
194
  }
219
195
 
196
+ /**
197
+ * Get the variables in scope of the form element. These are the values of the variables that
198
+ * logically precede the formElement that declares the XPath
199
+ *
200
+ * @param {Node} formElement The element that declares the XPath
201
+ *
202
+ * @return {Object} A key-value mapping of the variables
203
+ */
204
+ function getVariablesInScope(formElement) {
205
+ let closestActualFormElement = formElement;
206
+ while (closestActualFormElement && !('inScopeVariables' in closestActualFormElement)) {
207
+ closestActualFormElement = closestActualFormElement.parentNode;
208
+ }
209
+
210
+ if (!closestActualFormElement) {
211
+ return {};
212
+ }
213
+
214
+ const variables = {};
215
+ if(closestActualFormElement.inScopeVariables){
216
+ for (const key of closestActualFormElement.inScopeVariables.keys()) {
217
+ const varElement = closestActualFormElement.inScopeVariables.get(key);
218
+ variables[key] = varElement.value;
219
+ }
220
+ }
221
+ return variables;
222
+ }
223
+
220
224
  /**
221
225
  * Evaluate an XPath to _any_ type. When possible, prefer to use any other function to ensure the
222
226
  * type of the output is more predictable.
@@ -227,15 +231,23 @@ function functionNameResolver({ prefix, localName }, _arity) {
227
231
  */
228
232
  export function evaluateXPath(xpath, contextNode, formElement, variables = {}) {
229
233
  const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
234
+ const variablesInScope = getVariablesInScope(formElement);
230
235
 
231
- return fxEvaluateXPath(xpath, contextNode, null, variables, 'xs:anyType', {
232
- currentContext: { formElement, variables },
233
- moduleImports: {
234
- xf: XFORMS_NAMESPACE_URI,
236
+ return fxEvaluateXPath(
237
+ xpath,
238
+ contextNode,
239
+ null,
240
+ { ...variablesInScope, ...variables },
241
+ 'xs:anyType',
242
+ {
243
+ currentContext: { formElement, variables },
244
+ moduleImports: {
245
+ xf: XFORMS_NAMESPACE_URI,
246
+ },
247
+ functionNameResolver,
248
+ namespaceResolver,
235
249
  },
236
- functionNameResolver,
237
- namespaceResolver,
238
- });
250
+ );
239
251
  }
240
252
 
241
253
  /**
@@ -248,20 +260,15 @@ export function evaluateXPath(xpath, contextNode, formElement, variables = {}) {
248
260
  */
249
261
  export function evaluateXPathToFirstNode(xpath, contextNode, formElement) {
250
262
  const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
251
- return fxEvaluateXPathToFirstNode(
252
- xpath,
253
- contextNode,
254
- null,
255
- {},
256
- {
257
- defaultFunctionNamespaceURI: XFORMS_NAMESPACE_URI,
258
- moduleImports: {
259
- xf: XFORMS_NAMESPACE_URI,
260
- },
261
- currentContext: { formElement },
262
- namespaceResolver,
263
+ const variablesInScope = getVariablesInScope(formElement);
264
+ return fxEvaluateXPathToFirstNode(xpath, contextNode, null, variablesInScope, {
265
+ defaultFunctionNamespaceURI: XFORMS_NAMESPACE_URI,
266
+ moduleImports: {
267
+ xf: XFORMS_NAMESPACE_URI,
263
268
  },
264
- );
269
+ currentContext: { formElement },
270
+ namespaceResolver,
271
+ });
265
272
  }
266
273
 
267
274
  /**
@@ -274,20 +281,16 @@ export function evaluateXPathToFirstNode(xpath, contextNode, formElement) {
274
281
  */
275
282
  export function evaluateXPathToNodes(xpath, contextNode, formElement) {
276
283
  const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
277
- return fxEvaluateXPathToNodes(
278
- xpath,
279
- contextNode,
280
- null,
281
- {},
282
- {
283
- currentContext: { formElement },
284
- functionNameResolver,
285
- moduleImports: {
286
- xf: XFORMS_NAMESPACE_URI,
287
- },
288
- namespaceResolver,
284
+ const variablesInScope = getVariablesInScope(formElement);
285
+
286
+ return fxEvaluateXPathToNodes(xpath, contextNode, null, variablesInScope, {
287
+ currentContext: { formElement },
288
+ functionNameResolver,
289
+ moduleImports: {
290
+ xf: XFORMS_NAMESPACE_URI,
289
291
  },
290
- );
292
+ namespaceResolver,
293
+ });
291
294
  }
292
295
 
293
296
  /**
@@ -300,20 +303,16 @@ export function evaluateXPathToNodes(xpath, contextNode, formElement) {
300
303
  */
301
304
  export function evaluateXPathToBoolean(xpath, contextNode, formElement) {
302
305
  const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
303
- return fxEvaluateXPathToBoolean(
304
- xpath,
305
- contextNode,
306
- null,
307
- {},
308
- {
309
- currentContext: { formElement },
310
- functionNameResolver,
311
- moduleImports: {
312
- xf: XFORMS_NAMESPACE_URI,
313
- },
314
- namespaceResolver,
306
+ const variablesInScope = getVariablesInScope(formElement);
307
+
308
+ return fxEvaluateXPathToBoolean(xpath, contextNode, null, variablesInScope, {
309
+ currentContext: { formElement },
310
+ functionNameResolver,
311
+ moduleImports: {
312
+ xf: XFORMS_NAMESPACE_URI,
315
313
  },
316
- );
314
+ namespaceResolver,
315
+ });
317
316
  }
318
317
 
319
318
  /**
@@ -335,7 +334,38 @@ export function evaluateXPathToString(
335
334
  namespaceReferenceNode = formElement,
336
335
  ) {
337
336
  const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
338
- return fxEvaluateXPathToString(
337
+ const variablesInScope = getVariablesInScope(formElement);
338
+
339
+ return fxEvaluateXPathToString(xpath, contextNode, domFacade, variablesInScope, {
340
+ currentContext: { formElement },
341
+ functionNameResolver,
342
+ moduleImports: {
343
+ xf: XFORMS_NAMESPACE_URI,
344
+ },
345
+ namespaceResolver,
346
+ });
347
+ }
348
+
349
+ /**
350
+ * Evaluate an XPath to a set of strings
351
+ *
352
+ * @param {string} xpath The XPath to run
353
+ * @param {Node} contextNode The start of the XPath
354
+ * @param {Node} formElement The form element associated to the XPath
355
+ * @param {DomFacade} [domFacade=null] A DomFacade is used in bindings to intercept DOM
356
+ * access. This is used to determine dependencies between bind elements.
357
+ * @param {Node} formElement The element where the XPath is defined: used for namespace resolving
358
+ * @return {string}
359
+ */
360
+ export function evaluateXPathToStrings(
361
+ xpath,
362
+ contextNode,
363
+ formElement,
364
+ domFacade = null,
365
+ namespaceReferenceNode = formElement,
366
+ ) {
367
+ const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
368
+ return fxEvaluateXPathToStrings(
339
369
  xpath,
340
370
  contextNode,
341
371
  domFacade,
@@ -371,20 +401,16 @@ export function evaluateXPathToNumber(
371
401
  namespaceReferenceNode = formElement,
372
402
  ) {
373
403
  const namespaceResolver = createNamespaceResolverForNode(xpath, contextNode, formElement);
374
- return fxEvaluateXPathToNumber(
375
- xpath,
376
- contextNode,
377
- domFacade,
378
- {},
379
- {
380
- currentContext: { formElement },
381
- functionNameResolver,
382
- moduleImports: {
383
- xf: XFORMS_NAMESPACE_URI,
384
- },
385
- namespaceResolver,
404
+ const variablesInScope = getVariablesInScope(formElement);
405
+
406
+ return fxEvaluateXPathToNumber(xpath, contextNode, domFacade, variablesInScope, {
407
+ currentContext: { formElement },
408
+ functionNameResolver,
409
+ moduleImports: {
410
+ xf: XFORMS_NAMESPACE_URI,
386
411
  },
387
- );
412
+ namespaceResolver,
413
+ });
388
414
  }
389
415
 
390
416
  /**
@@ -487,6 +513,31 @@ export function resolveId(id, sourceObject, nodeName = null) {
487
513
  return null;
488
514
  }
489
515
 
516
+ const contextFunction = (dynamicContext, string) => {
517
+ const caller = dynamicContext.currentContext.formElement;
518
+ if (string) {
519
+ const instance = resolveId(string, caller);
520
+ if (instance) {
521
+ if (instance.nodeName === 'FX-REPEAT') {
522
+ const { nodeset } = instance;
523
+ for (let parent = caller; parent; parent = parent.parentNode) {
524
+ if (parent.parentNode === instance) {
525
+ const offset = Array.from(parent.parentNode.children).indexOf(parent);
526
+ return nodeset[offset];
527
+ }
528
+ }
529
+ }
530
+ return instance.nodeset;
531
+ }
532
+ }
533
+ const parent = XPathUtil.getParentBindingElement(caller);
534
+ const p = caller.nodeName;
535
+ // const p = dynamicContext.domFacade.getParentElement();
536
+
537
+ if (parent) return parent;
538
+ return caller.getInScopeContext();
539
+ };
540
+
490
541
  /**
491
542
  * @param id as string
492
543
  * @return instance data for given id serialized to string.
@@ -495,16 +546,18 @@ registerCustomXPathFunction(
495
546
  { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'context' },
496
547
  [],
497
548
  'item()?',
498
- (dynamicContext, string) => {
499
- const caller = dynamicContext.currentContext.formElement;
500
- const parent = XPathUtil.getParentBindingElement(caller);
501
- // const instance = resolveId('default', caller, 'fx-instance');
502
- const p = caller.nodeName;
503
- // const p = dynamicContext.domFacade.getParentElement();
504
-
505
- if (parent) return parent;
506
- return caller.getInScopeContext();
507
- },
549
+ contextFunction,
550
+ );
551
+
552
+ /**
553
+ * @param id as string
554
+ * @return instance data for given id serialized to string.
555
+ */
556
+ registerCustomXPathFunction(
557
+ { namespaceURI: XFORMS_NAMESPACE_URI, localName: 'context' },
558
+ ['xs:string'],
559
+ 'item()?',
560
+ contextFunction,
508
561
  );
509
562
 
510
563
  /**
@@ -524,7 +577,7 @@ registerCustomXPathFunction(
524
577
  // return JSON.stringify(instance.getDefaultContext());
525
578
  } else {
526
579
  const def = new XMLSerializer().serializeToString(instance.getDefaultContext());
527
- return prettifyXml(def);
580
+ return Fore.prettifyXml(def);
528
581
  }
529
582
  }
530
583
  return null;
package/src/xpath-util.js CHANGED
@@ -57,16 +57,27 @@ export class XPathUtil {
57
57
  throw new Error('no Fore element present');
58
58
  }
59
59
 
60
- // todo: this will need more work to look upward for instance() expr.
60
+ /**
61
+ * returns the instance id from a complete XPath using `instance()` function.
62
+ *
63
+ * Will return 'default' in case no ref is given at all or the `instance()` function is called without arg.
64
+ *
65
+ * Otherwise instance id is extracted from function and returned. If all fails null is returned.
66
+ * @param ref
67
+ * @returns {string}
68
+ */
61
69
  static getInstanceId(ref) {
62
70
  if (!ref) {
63
71
  return 'default';
64
72
  }
73
+ if (ref.startsWith('instance()')) {
74
+ return 'default';
75
+ }
65
76
  if (ref.startsWith('instance(')) {
66
77
  const result = ref.substring(ref.indexOf('(') + 1);
67
78
  return result.substring(1, result.indexOf(')') - 1);
68
79
  }
69
- return 'default';
80
+ return null;
70
81
  }
71
82
 
72
83
  // todo: certainly not ideal to rely on duplicating instance id on instance document - better way later ;)
@@ -88,4 +99,9 @@ export class XPathUtil {
88
99
  // ### cut-off root node ref
89
100
  return tmp1.substring(tmp1.indexOf('/'), tmp.length);
90
101
  }
102
+
103
+ static getBasePath(dep) {
104
+ const split = dep.split(':');
105
+ return split[0];
106
+ }
91
107
  }