@jinntec/fore 1.0.0-3 → 1.0.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 (62) hide show
  1. package/README.md +25 -34
  2. package/dist/fore-dev.js +43 -0
  3. package/dist/fore-dev.js.map +1 -0
  4. package/dist/fore.js +36 -1
  5. package/dist/fore.js.map +1 -0
  6. package/index.js +3 -0
  7. package/package.json +34 -38
  8. package/resources/fore.css +35 -50
  9. package/src/DependencyNotifyingDomFacade.js +10 -16
  10. package/src/ForeElementMixin.js +25 -18
  11. package/src/actions/abstract-action.js +17 -9
  12. package/src/actions/fx-action.js +6 -4
  13. package/src/actions/fx-append.js +8 -17
  14. package/src/actions/fx-confirm.js +3 -1
  15. package/src/actions/fx-delete.js +6 -3
  16. package/src/actions/fx-dispatch.js +9 -8
  17. package/src/actions/fx-hide.js +10 -6
  18. package/src/actions/fx-insert.js +49 -39
  19. package/src/actions/fx-message.js +3 -1
  20. package/src/actions/fx-refresh.js +11 -1
  21. package/src/actions/fx-replace.js +68 -0
  22. package/src/actions/fx-return.js +42 -0
  23. package/src/actions/fx-send.js +3 -1
  24. package/src/actions/fx-setvalue.js +58 -51
  25. package/src/actions/fx-show.js +8 -4
  26. package/src/actions/fx-toggle.js +15 -10
  27. package/src/actions/fx-update.js +3 -1
  28. package/src/dep_graph.js +4 -2
  29. package/src/drawdown.js +67 -82
  30. package/src/fore.js +158 -11
  31. package/src/functions/fx-function.js +11 -3
  32. package/src/fx-bind.js +42 -202
  33. package/src/fx-fore.js +105 -70
  34. package/src/fx-header.js +3 -1
  35. package/src/fx-instance.js +9 -1
  36. package/src/fx-model.js +31 -23
  37. package/src/fx-submission.js +73 -47
  38. package/src/fx-var.js +7 -4
  39. package/src/getInScopeContext.js +37 -11
  40. package/src/modelitem.js +4 -4
  41. package/src/relevance.js +65 -0
  42. package/src/ui/abstract-control.js +55 -35
  43. package/src/ui/fx-alert.js +7 -1
  44. package/src/ui/fx-case.js +3 -1
  45. package/src/ui/fx-container.js +7 -1
  46. package/src/ui/fx-control.js +283 -33
  47. package/src/ui/fx-dialog.js +54 -40
  48. package/src/ui/fx-group.js +3 -1
  49. package/src/ui/fx-hint.js +4 -1
  50. package/src/ui/fx-inspector.js +117 -17
  51. package/src/ui/fx-items.js +8 -8
  52. package/src/ui/fx-output.js +14 -5
  53. package/src/ui/fx-repeat.js +33 -41
  54. package/src/ui/fx-repeatitem.js +10 -4
  55. package/src/ui/fx-switch.js +3 -1
  56. package/src/ui/fx-trigger.js +3 -1
  57. package/src/xpath-evaluation.js +121 -131
  58. package/src/xpath-util.js +14 -7
  59. package/dist/fore-all.js +0 -140
  60. package/src/.DS_Store +0 -0
  61. package/src/actions/.DS_Store +0 -0
  62. package/src/ui/.DS_Store +0 -0
@@ -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;
@@ -62,6 +38,103 @@ const xhtmlNamespaceResolver = prefix => {
62
38
  return undefined;
63
39
  };
64
40
 
41
+ export function resolveId(id, sourceObject, nodeName = null) {
42
+ const allMatchingTargetObjects = fxEvaluateXPathToNodes(
43
+ 'outermost(ancestor-or-self::fx-fore[1]/(descendant::fx-fore|descendant::*[@id = $id]))[not(self::fx-fore)]',
44
+ sourceObject,
45
+ null,
46
+ { id },
47
+ { namespaceResolver: xhtmlNamespaceResolver },
48
+ );
49
+
50
+ if (allMatchingTargetObjects.length === 0) {
51
+ return null;
52
+ }
53
+
54
+ if (
55
+ allMatchingTargetObjects.length === 1 &&
56
+ fxEvaluateXPathToBoolean(
57
+ '(ancestor::fx-fore | ancestor::fx-repeat)[last()]/self::fx-fore',
58
+ allMatchingTargetObjects[0],
59
+ null,
60
+ null,
61
+ { namespaceResolver: xhtmlNamespaceResolver },
62
+ )
63
+ ) {
64
+ // If the target element is not repeated, then the search for the target object is trivial since
65
+ // there is only one associated with the target element that bears the matching ID. This is true
66
+ // regardless of whether or not the source object is repeated. However, if the target element is
67
+ // repeated, then additional information must be used to help select a target object from among
68
+ // those associated with the identified target element.
69
+ const targetObject = allMatchingTargetObjects[0];
70
+ if (nodeName && targetObject.localName !== nodeName) {
71
+ return null;
72
+ }
73
+ return targetObject;
74
+ }
75
+
76
+ // SPEC:
77
+
78
+ // 12.2.1 References to Elements within a repeat Element
79
+
80
+ // When the target element that is identified by the IDREF of a source object has one or more
81
+ // repeat elements as ancestors, then the set of ancestor repeats are partitioned into two
82
+ // subsets, those in common with the source element and those that are not in common. Any ancestor
83
+ // repeat elements of the target element not in common with the source element are descendants of
84
+ // the repeat elements that the source and target element have in common, if any.
85
+
86
+ // For the repeat elements that are in common, the desired target object exists in the same set of
87
+ // run-time objects that contains the source object. Then, for each ancestor repeat of the target
88
+ // element that is not in common with the source element, the current index of the repeat
89
+ // determines the set of run-time objects that contains the desired target object.
90
+ for (const ancestorRepeatItem of fxEvaluateXPathToNodes(
91
+ 'ancestor::fx-repeatitem => reverse()',
92
+ sourceObject,
93
+ null,
94
+ null,
95
+ { namespaceResolver: xhtmlNamespaceResolver },
96
+ )) {
97
+ const foundTargetObjects = allMatchingTargetObjects.filter(to =>
98
+ ancestorRepeatItem.contains(to),
99
+ );
100
+ switch (foundTargetObjects.length) {
101
+ case 0:
102
+ // Nothing found: ignore
103
+ break;
104
+ case 1: {
105
+ // A single one is found: the target object is directly in a common repeat
106
+ const targetObject = foundTargetObjects[0];
107
+ if (nodeName && targetObject.localName !== nodeName) {
108
+ return null;
109
+ }
110
+ return targetObject;
111
+ }
112
+ default: {
113
+ // Multiple target objects are found: they are in a repeat that is not common with the source object
114
+ // We found a target object in a common repeat! We now need to find the one that is in the repeatitem identified at the current index
115
+ const targetObject = foundTargetObjects.find(to =>
116
+ fxEvaluateXPathToNodes(
117
+ 'every $ancestor of ancestor::fx-repeatitem satisfies $ancestor is $ancestor/../child::fx-repeatitem[../@repeat-index]',
118
+ to,
119
+ null,
120
+ {},
121
+ ),
122
+ );
123
+ if (!targetObject) {
124
+ // Nothing valid found for whatever reason. This might be something dynamic?
125
+ return null;
126
+ }
127
+ if (nodeName && targetObject.localName !== nodeName) {
128
+ return null;
129
+ }
130
+ return targetObject;
131
+ }
132
+ }
133
+ }
134
+ // We found no target objects in common repeats. The id is unresolvable
135
+ return null;
136
+ }
137
+
65
138
  /**
66
139
  * Resolve a namespace. Needs a namespace prefix and the element that is most closely related to the
67
140
  * XPath in which the namespace is being resolved. The prefix will be resolved by using the
@@ -120,7 +193,7 @@ function createNamespaceResolver(xpathQuery, formElement) {
120
193
  let instance;
121
194
  if (instanceReferences[0] === 'default') {
122
195
  const actualForeElement = fxEvaluateXPathToFirstNode(
123
- 'ancestor-or-self::fx-fore',
196
+ 'ancestor-or-self::fx-fore[1]',
124
197
  formElement,
125
198
  null,
126
199
  null,
@@ -187,6 +260,10 @@ function createNamespaceResolverForNode(query, contextNode, formElement) {
187
260
  return createNamespaceResolver(query, formElement);
188
261
  }
189
262
 
263
+ // A global registry of function names that are declared in Fore by a developer using the
264
+ // `fx-function` element. These should be available without providing a prefix as well
265
+ export const globallyDeclaredFunctionLocalNames = [];
266
+
190
267
  /**
191
268
  * Implementation of the functionNameResolver passed to FontoXPath to
192
269
  * redirect function resolving for unprefixed functions to either the fn or the xf namespace
@@ -207,7 +284,12 @@ function functionNameResolver({ prefix, localName }, _arity) {
207
284
  case 'logtree':
208
285
  return { namespaceURI: XFORMS_NAMESPACE_URI, localName };
209
286
  default:
210
- if (prefix === '' || prefix === 'fn') {
287
+ if (prefix === '' && globallyDeclaredFunctionLocalNames.includes(localName)) {
288
+ // The function has been declared without a prefix and is called here without a prefix.
289
+ // Just make this work. It is the developer-friendly way
290
+ return { namespaceURI: 'http://www.w3.org/2005/xquery-local-functions', localName };
291
+ }
292
+ if (prefix === 'fn') {
211
293
  return { namespaceURI: 'http://www.w3.org/2005/xpath-functions', localName };
212
294
  }
213
295
  if (prefix === 'local') {
@@ -236,9 +318,11 @@ function getVariablesInScope(formElement) {
236
318
  }
237
319
 
238
320
  const variables = {};
239
- for (const key of closestActualFormElement.inScopeVariables.keys()) {
240
- const varElement = closestActualFormElement.inScopeVariables.get(key);
241
- variables[key] = varElement.value;
321
+ if (closestActualFormElement.inScopeVariables) {
322
+ for (const key of closestActualFormElement.inScopeVariables.keys()) {
323
+ const varElement = closestActualFormElement.inScopeVariables.get(key);
324
+ variables[key] = varElement.value;
325
+ }
242
326
  }
243
327
  return variables;
244
328
  }
@@ -260,7 +344,7 @@ export function evaluateXPath(xpath, contextNode, formElement, variables = {}) {
260
344
  contextNode,
261
345
  null,
262
346
  { ...variablesInScope, ...variables },
263
- 'xs:anyType',
347
+ fxEvaluateXPath.ALL_RESULTS_TYPE,
264
348
  {
265
349
  currentContext: { formElement, variables },
266
350
  moduleImports: {
@@ -438,102 +522,6 @@ export function evaluateXPathToNumber(
438
522
  /**
439
523
  * Resolve an id in scope. Behaves like the algorithm defined on https://www.w3.org/community/xformsusers/wiki/XForms_2.0#idref-resolve
440
524
  */
441
- export function resolveId(id, sourceObject, nodeName = null) {
442
- const allMatchingTargetObjects = fxEvaluateXPathToNodes(
443
- 'outermost(ancestor-or-self::fx-fore[1]/(descendant::xf-fore|descendant::*[@id = $id]))[not(self::fx-fore)]',
444
- sourceObject,
445
- null,
446
- { id },
447
- { namespaceResolver: xhtmlNamespaceResolver },
448
- );
449
-
450
- if (allMatchingTargetObjects.length === 0) {
451
- return null;
452
- }
453
-
454
- if (
455
- allMatchingTargetObjects.length === 1 &&
456
- fxEvaluateXPathToBoolean(
457
- '(ancestor::fx-fore | ancestor::fx-repeat)[last()]/self::fx-fore',
458
- allMatchingTargetObjects[0],
459
- null,
460
- null,
461
- { namespaceResolver: xhtmlNamespaceResolver },
462
- )
463
- ) {
464
- // If the target element is not repeated, then the search for the target object is trivial since
465
- // there is only one associated with the target element that bears the matching ID. This is true
466
- // regardless of whether or not the source object is repeated. However, if the target element is
467
- // repeated, then additional information must be used to help select a target object from among
468
- // those associated with the identified target element.
469
- const targetObject = allMatchingTargetObjects[0];
470
- if (nodeName && targetObject.localName !== nodeName) {
471
- return null;
472
- }
473
- return targetObject;
474
- }
475
-
476
- // SPEC:
477
-
478
- // 12.2.1 References to Elements within a repeat Element
479
-
480
- // When the target element that is identified by the IDREF of a source object has one or more
481
- // repeat elements as ancestors, then the set of ancestor repeats are partitioned into two
482
- // subsets, those in common with the source element and those that are not in common. Any ancestor
483
- // repeat elements of the target element not in common with the source element are descendants of
484
- // the repeat elements that the source and target element have in common, if any.
485
-
486
- // For the repeat elements that are in common, the desired target object exists in the same set of
487
- // run-time objects that contains the source object. Then, for each ancestor repeat of the target
488
- // element that is not in common with the source element, the current index of the repeat
489
- // determines the set of run-time objects that contains the desired target object.
490
- for (const ancestorRepeatItem of fxEvaluateXPathToNodes(
491
- 'ancestor::fx-repeatitem => reverse()',
492
- sourceObject,
493
- null,
494
- null,
495
- { namespaceResolver: xhtmlNamespaceResolver },
496
- )) {
497
- const foundTargetObjects = allMatchingTargetObjects.filter(to =>
498
- ancestorRepeatItem.contains(to),
499
- );
500
- switch (foundTargetObjects.length) {
501
- case 0:
502
- // Nothing found: ignore
503
- break;
504
- case 1: {
505
- // A single one is found: the target object is directly in a common repeat
506
- const targetObject = foundTargetObjects[0];
507
- if (nodeName && targetObject.localName !== nodeName) {
508
- return null;
509
- }
510
- return targetObject;
511
- }
512
- default: {
513
- // Multiple target objects are found: they are in a repeat that is not common with the source object
514
- // We found a target object in a common repeat! We now need to find the one that is in the repeatitem identified at the current index
515
- const targetObject = foundTargetObjects.find(to =>
516
- fxEvaluateXPathToNodes(
517
- 'every $ancestor of ancestor::fx-repeatitem satisfies $ancestor is $ancestor/../child::fx-repeatitem[../@repeat-index]',
518
- to,
519
- null,
520
- {},
521
- ),
522
- );
523
- if (!targetObject) {
524
- // Nothing valid found for whatever reason. This might be something dynamic?
525
- return null;
526
- }
527
- if (nodeName && targetObject.localName !== nodeName) {
528
- return null;
529
- }
530
- return targetObject;
531
- }
532
- }
533
- }
534
- // We found no target objects in common repeats. The id is unresolvable
535
- return null;
536
- }
537
525
 
538
526
  const contextFunction = (dynamicContext, string) => {
539
527
  const caller = dynamicContext.currentContext.formElement;
@@ -553,7 +541,7 @@ const contextFunction = (dynamicContext, string) => {
553
541
  }
554
542
  }
555
543
  const parent = XPathUtil.getParentBindingElement(caller);
556
- const p = caller.nodeName;
544
+ // const p = caller.nodeName;
557
545
  // const p = dynamicContext.domFacade.getParentElement();
558
546
 
559
547
  if (parent) return parent;
@@ -599,7 +587,7 @@ registerCustomXPathFunction(
599
587
  // return JSON.stringify(instance.getDefaultContext());
600
588
  } else {
601
589
  const def = new XMLSerializer().serializeToString(instance.getDefaultContext());
602
- return prettifyXml(def);
590
+ return Fore.prettifyXml(def);
603
591
  }
604
592
  }
605
593
  return null;
@@ -698,7 +686,7 @@ const instance = (dynamicContext, string) => {
698
686
  // TODO: handle no string passed (null will be passed instead)
699
687
 
700
688
  const formElement = fxEvaluateXPathToFirstNode(
701
- 'ancestor-or-self::fx-fore',
689
+ 'ancestor-or-self::fx-fore[1]',
702
690
  dynamicContext.currentContext.formElement,
703
691
  null,
704
692
  null,
@@ -771,6 +759,8 @@ registerCustomXPathFunction(
771
759
  ['xs:string?'],
772
760
  'item()?',
773
761
  (dynamicContext, arg) => {
762
+ if (!dynamicContext.currentContext.variables) return [];
763
+ if (!arg) return [];
774
764
  const payload = dynamicContext.currentContext.variables[arg];
775
765
  if (payload.nodeType) {
776
766
  console.log('got some node as js object');
package/src/xpath-util.js CHANGED
@@ -29,10 +29,6 @@ export class XPathUtil {
29
29
  return path != null && (path.startsWith('/') || path.startsWith('instance('));
30
30
  }
31
31
 
32
- static isRepeated(element) {
33
- return element.parentElement.closest('fx-repeatitem');
34
- }
35
-
36
32
  static isSelfReference(ref) {
37
33
  return ref === '.' || ref === './text()' || ref === 'text()' || ref === '' || ref === null;
38
34
  }
@@ -57,21 +53,32 @@ export class XPathUtil {
57
53
  throw new Error('no Fore element present');
58
54
  }
59
55
 
60
- // todo: this will need more work to look upward for instance() expr.
56
+ /**
57
+ * returns the instance id from a complete XPath using `instance()` function.
58
+ *
59
+ * Will return 'default' in case no ref is given at all or the `instance()` function is called without arg.
60
+ *
61
+ * Otherwise instance id is extracted from function and returned. If all fails null is returned.
62
+ * @param ref
63
+ * @returns {string}
64
+ */
61
65
  static getInstanceId(ref) {
62
66
  if (!ref) {
63
67
  return 'default';
64
68
  }
69
+ if (ref.startsWith('instance()')) {
70
+ return 'default';
71
+ }
65
72
  if (ref.startsWith('instance(')) {
66
73
  const result = ref.substring(ref.indexOf('(') + 1);
67
74
  return result.substring(1, result.indexOf(')') - 1);
68
75
  }
69
- return 'default';
76
+ return null;
70
77
  }
71
78
 
72
79
  // todo: certainly not ideal to rely on duplicating instance id on instance document - better way later ;)
73
80
  static getPath(node) {
74
- const path = fx.evaluateXPath('path()', node);
81
+ const path = fx.evaluateXPathToString('path()', node);
75
82
  /*
76
83
  const instanceId = node.ownerDocument.firstElementChild.getAttribute('id');
77
84
  if (instanceId !== null && instanceId !== 'default') {