@jinntec/fore 1.6.0 → 1.7.1

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/index.js CHANGED
@@ -55,6 +55,7 @@ import './src/actions/fx-hide.js';
55
55
  import './src/actions/fx-reload.js';
56
56
  import './src/actions/fx-reset.js';
57
57
  import './src/actions/fx-load.js';
58
+ import './src/actions/fx-toggleboolean.js';
58
59
 
59
60
  import './src/functions/fx-function.js';
60
61
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jinntec/fore",
3
- "version": "1.6.0",
3
+ "version": "1.7.1",
4
4
  "description": "Fore - declarative user interfaces in plain HTML",
5
5
  "module": "./index.js",
6
6
  "publishConfig": {
@@ -31,7 +31,7 @@
31
31
  ],
32
32
  "dependencies": {
33
33
  "@jinntec/jinn-toast": "^1.0.5",
34
- "fontoxpath": "^3.29.0"
34
+ "fontoxpath": "^3.30.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@babel/plugin-proposal-class-properties": "^7.17.12",
@@ -0,0 +1,58 @@
1
+ // import { FxAction } from './fx-action.js';
2
+ import '../fx-model.js';
3
+ import {AbstractAction} from './abstract-action.js';
4
+ import {evaluateXPath} from '../xpath-evaluation.js';
5
+ import {Fore} from '../fore.js';
6
+
7
+ /**
8
+ * `fx-setvalue`
9
+ *
10
+ * @customElement
11
+ */
12
+ export default class FxToggleboolean extends AbstractAction {
13
+ static get properties() {
14
+ return {
15
+ ...super.properties,
16
+ ref: {
17
+ type: String,
18
+ },
19
+ valueAttr: {
20
+ type: String,
21
+ },
22
+ value:{
23
+ type: Boolean
24
+ }
25
+ };
26
+ }
27
+
28
+ constructor() {
29
+ super();
30
+ this.ref = '';
31
+ this.valueAttr = '';
32
+ this.value = false;
33
+ }
34
+
35
+ connectedCallback() {
36
+ if (super.connectedCallback) {
37
+ super.connectedCallback();
38
+ }
39
+
40
+ if (this.hasAttribute('ref')) {
41
+ this.ref = this.getAttribute('ref');
42
+ } else {
43
+ throw new Error('fx-togglealue must specify a "ref" attribute');
44
+ }
45
+ }
46
+
47
+ async perform() {
48
+ super.perform();
49
+ const mi = this.getModelItem();
50
+ mi.value === 'true' ? mi.node.textContent='false' : mi.node.textContent='true';
51
+ this.needsUpdate = true;
52
+ }
53
+
54
+ }
55
+
56
+ if (!customElements.get('fx-toggleboolean')) {
57
+ window.customElements.define('fx-toggleboolean', FxToggleboolean);
58
+ }
package/src/fore.js CHANGED
@@ -28,7 +28,7 @@ export class Fore {
28
28
  if(target?.classList.contains("widget")) return true;
29
29
  let parent = target.parentNode;
30
30
  while(parent && parent.nodeName !== 'FX-CONTROL'){
31
- if(parent.classList.contains('widget')) return true;
31
+ if(parent?.classList?.contains('widget')) return true;
32
32
  parent = parent.parentNode;
33
33
  }
34
34
  return false;
@@ -195,6 +195,7 @@ export class Fore {
195
195
  Array.from(children).forEach(element => {
196
196
  if (element.nodeName.toUpperCase() === 'FX-FORE') {
197
197
  resolve('done');
198
+ return;
198
199
  }
199
200
  if (Fore.isUiElement(element.nodeName) && typeof element.refresh === 'function') {
200
201
  // console.log('refreshing', element, element?.ref);
@@ -1,4 +1,4 @@
1
- import { registerCustomXPathFunction } from 'fontoxpath';
1
+ import { registerCustomXPathFunction, createTypedValueFactory } from 'fontoxpath';
2
2
  import { foreElementMixin } from '../ForeElementMixin.js';
3
3
  import { evaluateXPath, globallyDeclaredFunctionLocalNames } from '../xpath-evaluation.js';
4
4
 
@@ -86,6 +86,7 @@ export class FxFunction extends foreElementMixin(HTMLElement) {
86
86
  case 'text/xquf':
87
87
  case 'text/xquery':
88
88
  case 'text/xpath': {
89
+ const typedValueFactories = paramParts.map(param => createTypedValueFactory(param.variableType));
89
90
  const language = type === 'text/xpath' ?
90
91
  'XPath3.1' : type === 'text/xquery' ?
91
92
  'XQuery3.1' : 'XQueryUpdate3.1';
@@ -94,11 +95,12 @@ export class FxFunction extends foreElementMixin(HTMLElement) {
94
95
  this.functionBody,
95
96
  this.getInScopeContext(),
96
97
  this.getOwnerForm(),
97
- paramParts.reduce((variablesByName, paramPart, i) => {
98
- variablesByName[paramPart.variableName.replace('$', '')] = args[i];
98
+ paramParts.reduce((variablesByName, paramPart, i) => {
99
+ // Because we know the XPath type here (from the function declaration) we do not have to depend on the implicit typings
100
+ variablesByName[paramPart.variableName.replace('$', '')] = typedValueFactories[i](args[i]);
99
101
  return variablesByName;
100
102
  }, {}),
101
- {language}
103
+ {language}
102
104
  );
103
105
  registerCustomXPathFunction(
104
106
  functionIdentifier,
package/src/fx-bind.js CHANGED
@@ -44,6 +44,7 @@ export class FxBind extends foreElementMixin(HTMLElement) {
44
44
  connectedCallback() {
45
45
  // console.log('connectedCallback ', this);
46
46
  // this.id = this.hasAttribute('id')?this.getAttribute('id'):;
47
+ this.constraint = this.getAttribute('constraint');
47
48
  this.ref = this.getAttribute('ref');
48
49
  this.readonly = this.getAttribute('readonly');
49
50
  this.required = this.getAttribute('required');
@@ -133,6 +134,7 @@ export class FxBind extends foreElementMixin(HTMLElement) {
133
134
  this._addDependencies(constraintRefs, node, path, 'constraint');
134
135
  } else if (this.constraint) {
135
136
  this.model.mainGraph.addNode(`${path}:constraint`, node);
137
+ this.model.mainGraph.addDependency(path, `${path}:constraint`);
136
138
  }
137
139
  });
138
140
  }
package/src/fx-fore.js CHANGED
@@ -29,6 +29,12 @@ export class FxFore extends HTMLElement {
29
29
 
30
30
  static get properties() {
31
31
  return {
32
+ /**
33
+ * merge-partial
34
+ */
35
+ mergePartial:{
36
+ type: Boolean
37
+ },
32
38
  /**
33
39
  * Setting this marker attribute will refresh the UI in a lazy fashion just updating elements being
34
40
  * in viewport.
@@ -189,6 +195,8 @@ export class FxFore extends HTMLElement {
189
195
  this.someInstanceDataStructureChanged = false;
190
196
  this.repeatsFromAttributesCreated = false;
191
197
  this.validateOn = this.hasAttribute('validate-on') ? this.getAttribute('validate-on'):'update';
198
+ // this.mergePartial = this.hasAttribute('merge-partial')? true:false;
199
+ this.mergePartial = false;
192
200
  }
193
201
 
194
202
  connectedCallback() {
@@ -520,6 +528,13 @@ export class FxFore extends HTMLElement {
520
528
  storing expressions and their nodes for re-evaluation
521
529
  */
522
530
  Array.from(tmplExpressions).forEach(node => {
531
+ const ele = node.nodeType === Node.ATTRIBUTE_NODE ?
532
+ node.ownerElement :
533
+ node.parentNode;
534
+ if (ele.closest('fx-fore') !== this) {
535
+ // We found something in a sub-fore. Act like it's not there
536
+ return;
537
+ }
523
538
  if (this.storedTemplateExpressionByNode.has(node)) {
524
539
  // If the node is already known, do not process it twice
525
540
  return;
@@ -676,6 +691,7 @@ export class FxFore extends HTMLElement {
676
691
  generatedInstance.instanceData = generated;
677
692
  model.instances.push(generatedInstance);
678
693
  // console.log('generatedInstance ', this.getModel().getDefaultInstanceData());
694
+ Fore.dispatch(this,'instance-loaded',{instance:this})
679
695
  }
680
696
  }
681
697
 
@@ -52,6 +52,7 @@ export class FxInstance extends HTMLElement {
52
52
  this.model = this.parentNode;
53
53
  this.attachShadow({ mode: 'open' });
54
54
  this.originalInstance = null;
55
+ this.partialInstance = null;
55
56
  }
56
57
 
57
58
  connectedCallback() {
@@ -92,6 +93,7 @@ export class FxInstance extends HTMLElement {
92
93
  </style>
93
94
  ${html}
94
95
  `;
96
+ this.partialInstance = {};
95
97
  }
96
98
 
97
99
  /**
@@ -113,7 +115,7 @@ export class FxInstance extends HTMLElement {
113
115
 
114
116
  reset(){
115
117
  // this._useInlineData();
116
- this.instanceData = this.originalInstance;
118
+ this.instanceData = this.originalInstance.cloneNode(true);
117
119
  }
118
120
 
119
121
  evalXPath(xpath) {
@@ -149,11 +151,13 @@ export class FxInstance extends HTMLElement {
149
151
  * @returns {Document|T|any|Element}
150
152
  */
151
153
  getDefaultContext() {
152
- // console.log('getDefaultContext ', this.instanceData.firstElementChild);
154
+ // Note: use the getter here: it might provide us with stubbed data if anything async is racing,
155
+ // such as an @src attribute
156
+ const instanceData = this.getInstanceData();
153
157
  if (this.type === 'xml') {
154
- return this.instanceData.firstElementChild;
158
+ return instanceData.firstElementChild;
155
159
  }
156
- return this.instanceData;
160
+ return instanceData;
157
161
  }
158
162
 
159
163
  /**
package/src/fx-model.js CHANGED
@@ -178,8 +178,8 @@ export class FxModel extends HTMLElement {
178
178
  bind.init(this);
179
179
  });
180
180
 
181
- // console.log(`mainGraph`, this.mainGraph);
182
- // console.log(`rebuild mainGraph calc order`, this.mainGraph.overallOrder());
181
+ console.log(`mainGraph`, this.mainGraph);
182
+ console.log(`rebuild mainGraph calc order`, this.mainGraph.overallOrder());
183
183
 
184
184
  // this.dispatchEvent(new CustomEvent('rebuild-done', {detail: {maingraph: this.mainGraph}}));
185
185
  Fore.dispatch(this,'rebuild-done',{maingraph:this.mainGraph});
@@ -435,12 +435,11 @@ export class FxModel extends HTMLElement {
435
435
  }
436
436
 
437
437
  getDefaultInstance() {
438
- return this?.instances[0];
438
+ return this.instances[0];
439
439
  }
440
440
 
441
441
  getDefaultInstanceData() {
442
- console.log('default instance data ', this.instances[0].instanceData);
443
- return this.instances[0].instanceData;
442
+ return this.instances[0].getInstanceData();
444
443
  }
445
444
 
446
445
  getInstance(id) {
@@ -323,8 +323,35 @@ export class FxSubmission extends foreElementMixin(HTMLElement) {
323
323
  }
324
324
  */
325
325
 
326
+ const targetInstance = this._getTargetInstance();
327
+
328
+ /*
329
+ if(this.replace === 'merge'){
330
+ if (targetInstance && targetInstance.type === 'xml') {
331
+ targetInstance.partialInstance = data;
332
+
333
+ const merged = this._mergeXML(targetInstance.instanceData,targetInstance.partialInstance);
334
+ console.log('merged', merged);
335
+
336
+ targetInstance.instanceData = merged;
337
+ console.log('merging partial instance',targetInstance.partialInstance)
338
+ /!*
339
+ targetInstance.instanceData not touched here as we want to keep the default instance unmodified as the full template for the UI.
340
+ *!/
341
+
342
+ // Skip any refreshes if the model is not yet inited#
343
+ // duplicate from replace='instance'
344
+ // if (this.model.inited) {
345
+ this.model.updateModel(); // force update
346
+ const owner = this.getOwnerForm();
347
+ // owner.mergePartial = true;
348
+ owner.refresh(true);
349
+ // }
350
+ }
351
+ }
352
+ */
353
+
326
354
  if (this.replace === 'instance') {
327
- const targetInstance = this._getTargetInstance();
328
355
  if (targetInstance) {
329
356
  if (this.targetref) {
330
357
  const [theTarget] = evaluateXPath(
@@ -388,6 +415,81 @@ export class FxSubmission extends foreElementMixin(HTMLElement) {
388
415
  }
389
416
  }
390
417
 
418
+
419
+ /*
420
+ _mergeXML(xml1, xml2) {
421
+ const parser = new DOMParser();
422
+ const serializer = new XMLSerializer();
423
+
424
+ // const doc1 = parser.parseFromString(xml1, 'text/xml');
425
+ // const doc2 = parser.parseFromString(xml2, 'text/xml');
426
+
427
+ this.mergeNodes(xml1.documentElement, xml2.documentElement);
428
+
429
+ // return serializer.serializeToString(xml1);
430
+ return xml1;
431
+ }
432
+ */
433
+
434
+ /*
435
+ _mergeNodes(node1, node2) {
436
+ const childNodes1 = node1.childNodes;
437
+ const childNodes2 = node2.childNodes;
438
+
439
+ for (let i = 0; i < childNodes2.length; i++) {
440
+ const child2 = childNodes2[i];
441
+ let nodeMerged = false;
442
+
443
+ if (child2.nodeType === 1) { // Element Node
444
+ for (let j = 0; j < childNodes1.length; j++) {
445
+ const child1 = childNodes1[j];
446
+ if (child1.nodeType === 1 && child1.tagName === child2.tagName) {
447
+ this._mergeNodes(child1, child2);
448
+ nodeMerged = true;
449
+ break;
450
+ }
451
+ }
452
+ }
453
+
454
+ if (!nodeMerged) {
455
+ const clonedNode = child2.cloneNode(true);
456
+ node1.appendChild(clonedNode);
457
+ }
458
+ }
459
+ }
460
+ */
461
+
462
+ /*
463
+ mergeNodes(node1, node2) {
464
+ // Overwrite attributes in node1 with values from node2
465
+ for (const { name, value } of node2.attributes) {
466
+ node1.setAttribute(name, value);
467
+ }
468
+
469
+ const childNodes1 = Array.from(node1.childNodes);
470
+ const childNodes2 = Array.from(node2.childNodes);
471
+
472
+ // Append all child nodes from node2 to node1
473
+ childNodes2.forEach(child2 => {
474
+ if (child2.nodeType === 1) {
475
+ // If it's an element node, check if a matching element exists in node1
476
+ const matchingElement = childNodes1.find(
477
+ child1 => child1.nodeType === 1 && child1.tagName === child2.tagName
478
+ );
479
+ if (matchingElement) {
480
+ this.mergeNodes(matchingElement, child2); // Recursively merge matching elements
481
+ } else {
482
+ const clonedNode = child2.cloneNode(true);
483
+ node1.appendChild(clonedNode);
484
+ }
485
+ } else {
486
+ // For text nodes, simply append them to node1
487
+ const clonedNode = child2.cloneNode(true);
488
+ node1.appendChild(clonedNode);
489
+ }
490
+ });
491
+ }
492
+ */
391
493
  /**
392
494
  * select relevant nodes
393
495
  *
@@ -55,7 +55,15 @@ function _getInitialContext(node, ref) {
55
55
  }
56
56
 
57
57
  export default function getInScopeContext(node, ref) {
58
+ // console.log('getInScopeContext', ref, node);
59
+
60
+
58
61
  const parentElement = _getElement(node);
62
+ // console.log('getInScopeContext parent', parentElement);
63
+
64
+ if(parentElement.closest('fx-fore').mergePartial){
65
+ console.log('mergePartial mode')
66
+ }
59
67
 
60
68
  if(parentElement.nodeName === 'FX-FORE'){
61
69
  return parentElement.getModel().getDefaultInstance().getDefaultContext();
@@ -400,7 +400,7 @@ export default class FxControl extends XfAbstractControl {
400
400
  }
401
401
 
402
402
  /**
403
- * loads an external Fore from an HTML file given by `url` attribute.
403
+ * loads an external Fore from an HTML file given by `url` attribute and embed it as child of this control.
404
404
  *
405
405
  * Will look for the `<fx-fore>` element within the returned HTML file and return that element.
406
406
  *
@@ -459,6 +459,10 @@ export default class FxControl extends XfAbstractControl {
459
459
  );
460
460
 
461
461
  const dummy = this.querySelector('input');
462
+ /*
463
+ todo: the mechanism to import constructed stylesheets as in fore-component is still missing here.
464
+ There no way yet to specify CSS for a embedded fx-fore in shadowDOM.
465
+ */
462
466
  if (this.hasAttribute('shadow')) {
463
467
  dummy.parentNode.removeChild(dummy);
464
468
  this.shadowRoot.appendChild(imported);
@@ -418,6 +418,7 @@ export function evaluateXPath(xpath, contextNode, formElement, variables = {}, o
418
418
  {...variablesInScope, ...variables},
419
419
  fxEvaluateXPath.ALL_RESULTS_TYPE,
420
420
  {
421
+ debug: true,
421
422
  currentContext: {formElement, variables},
422
423
  moduleImports: {
423
424
  xf: XFORMS_NAMESPACE_URI,
@@ -595,26 +596,25 @@ export function evaluateXPathToNumber(
595
596
 
596
597
  const contextFunction = (dynamicContext, string) => {
597
598
  const caller = dynamicContext.currentContext.formElement;
599
+ let instance = null;
598
600
  if (string) {
599
- const instance = resolveId(string, caller);
600
- if (instance) {
601
- if (instance.nodeName === 'FX-REPEAT') {
602
- const {nodeset} = instance;
603
- for (let parent = caller; parent; parent = parent.parentNode) {
604
- if (parent.parentNode === instance) {
605
- const offset = Array.from(parent.parentNode.children).indexOf(parent);
606
- return nodeset[offset];
607
- }
601
+ instance = resolveId(string, caller);
602
+ } else {
603
+ instance = XPathUtil.getParentBindingElement(caller);
604
+ }
605
+ if (instance) {
606
+ if (instance.nodeName === 'FX-REPEAT') {
607
+ const {nodeset} = instance;
608
+ for (let parent = caller; parent; parent = parent.parentNode) {
609
+ if (parent.parentNode === instance) {
610
+ const offset = Array.from(parent.parentNode.children).indexOf(parent);
611
+ return nodeset[offset];
608
612
  }
609
613
  }
610
- return instance.nodeset;
611
614
  }
615
+ return instance.nodeset;
612
616
  }
613
- const parent = XPathUtil.getParentBindingElement(caller);
614
- // const p = caller.nodeName;
615
- // const p = dynamicContext.domFacade.getParentElement();
616
617
 
617
- if (parent) return parent.nodeset;
618
618
  return caller.getInScopeContext();
619
619
  };
620
620
 
@@ -844,7 +844,12 @@ const instance = (dynamicContext, string) => {
844
844
  */
845
845
 
846
846
  if (inst) {
847
- return inst.getDefaultContext();
847
+ const context = inst.getDefaultContext();
848
+ if (!context) {
849
+ debugger;
850
+ return null;
851
+ }
852
+ return context;
848
853
  }
849
854
  return null;
850
855
  };