@jinntec/fore 2.4.1 → 2.5.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/src/fx-fore.js CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  import getInScopeContext from './getInScopeContext.js';
12
12
  import { XPathUtil } from './xpath-util.js';
13
13
  import { FxRepeatAttributes } from './ui/fx-repeat-attributes.js';
14
+ import { ModelItem } from './modelitem.js';
14
15
 
15
16
  /**
16
17
  * Makes the dirty state of the form.
@@ -49,6 +50,13 @@ export class FxFore extends HTMLElement {
49
50
 
50
51
  static get properties() {
51
52
  return {
53
+ /**
54
+ * wether to create nodes that are missing in the loaded data and
55
+ * auto-create nodes when there's a binding found in the UI.
56
+ */
57
+ createNodes: {
58
+ type: Boolean,
59
+ },
52
60
  /**
53
61
  * ignore certain nodes for template expression search
54
62
  */
@@ -238,6 +246,7 @@ export class FxFore extends HTMLElement {
238
246
  : 'update';
239
247
  // this.mergePartial = this.hasAttribute('merge-partial')? true:false;
240
248
  this.mergePartial = false;
249
+ this.createNodes = this.hasAttribute('create-nodes') ? true : false;
241
250
  }
242
251
 
243
252
  connectedCallback() {
@@ -245,23 +254,23 @@ export class FxFore extends HTMLElement {
245
254
  console.time('init');
246
255
  this.strict = !!this.hasAttribute('strict');
247
256
  /*
248
- document.addEventListener('ready', (e) =>{
249
- if(e.target !== this){
250
- // e.preventDefault();
251
- console.log('>>> e', e);
252
- console.log('event this', this);
253
- // console.log('event eventPhase', e.eventPhase);
254
- // console.log('event cancelable', e.cancelable);
255
- console.log('event target', e.target);
256
- console.log('event composed', e.composedPath());
257
- console.log('<<< event stopping');
258
- e.stopPropagation();
259
- }else{
260
- console.log('event proceed', this);
261
- }
262
- // e.stopImmediatePropagation();
263
- },true);
264
- */
257
+ document.addEventListener('ready', (e) =>{
258
+ if(e.target !== this){
259
+ // e.preventDefault();
260
+ console.log('>>> e', e);
261
+ console.log('event this', this);
262
+ // console.log('event eventPhase', e.eventPhase);
263
+ // console.log('event cancelable', e.cancelable);
264
+ console.log('event target', e.target);
265
+ console.log('event composed', e.composedPath());
266
+ console.log('<<< event stopping');
267
+ e.stopPropagation();
268
+ }else{
269
+ console.log('event proceed', this);
270
+ }
271
+ // e.stopImmediatePropagation();
272
+ },true);
273
+ */
265
274
  this.ignoreExpressions = this.hasAttribute('ignore-expressions')
266
275
  ? this.getAttribute('ignore-expressions')
267
276
  : null;
@@ -359,6 +368,11 @@ export class FxFore extends HTMLElement {
359
368
  const devtools = document.createElement('fx-devtools');
360
369
  document.body.appendChild(devtools);
361
370
  }
371
+ if (urlParams.has('lens')) {
372
+ const lens = document.createElement('fx-lens');
373
+ document.body.appendChild(lens);
374
+ lens.setAttribute('open', 'open');
375
+ }
362
376
  }
363
377
 
364
378
  /**
@@ -450,12 +464,12 @@ export class FxFore extends HTMLElement {
450
464
  disconnectedCallback() {
451
465
  this.removeEventListener('dragstart', this.dragstart);
452
466
  /*
453
- this.removeEventListener('model-construct-done', this._handleModelConstructDone);
454
- this.removeEventListener('message', this._displayMessage);
455
- this.removeEventListener('error', this._displayError);
456
- this.storedTemplateExpressionByNode=null;
457
- this.shadowRoot = undefined;
458
- */
467
+ this.removeEventListener('model-construct-done', this._handleModelConstructDone);
468
+ this.removeEventListener('message', this._displayMessage);
469
+ this.removeEventListener('error', this._displayError);
470
+ this.storedTemplateExpressionByNode=null;
471
+ this.shadowRoot = undefined;
472
+ */
459
473
  }
460
474
 
461
475
  /**
@@ -490,39 +504,39 @@ export class FxFore extends HTMLElement {
490
504
  async refresh(force) {
491
505
  /*
492
506
 
493
- if (!changedPaths) {
494
- changedPaths = this.toRefresh.map(item => item.path);
495
- } else {
496
- this.toRefresh.push(
497
- ...changedPaths
498
- .map(
499
- path =>
500
- this.getModel()
501
- .modelItems
502
- .find(item => item.path === path)
503
- )
504
- .filter(Boolean)
505
- );
506
-
507
- for(const changedPath of changedPaths) {
508
- for (const repeat of this.querySelectorAll('fx-repeat')) {
509
- if (repeat.closest('fx-fore') !== this) {
510
- continue;
511
- }
512
-
513
- if (repeat.touchedPaths && repeat.touchedPaths.has(changedPath)) {
514
- // Make a temporary model-item-like structure for this
515
- this.toRefresh.push({
516
- path: changedPath,
517
- boundControls: [repeat]
518
- });
519
-
520
- console.log('Found a repeat to update!!!', repeat)
507
+ if (!changedPaths) {
508
+ changedPaths = this.toRefresh.map(item => item.path);
509
+ } else {
510
+ this.toRefresh.push(
511
+ ...changedPaths
512
+ .map(
513
+ path =>
514
+ this.getModel()
515
+ .modelItems
516
+ .find(item => item.path === path)
517
+ )
518
+ .filter(Boolean)
519
+ );
520
+
521
+ for(const changedPath of changedPaths) {
522
+ for (const repeat of this.querySelectorAll('fx-repeat')) {
523
+ if (repeat.closest('fx-fore') !== this) {
524
+ continue;
525
+ }
526
+
527
+ if (repeat.touchedPaths && repeat.touchedPaths.has(changedPath)) {
528
+ // Make a temporary model-item-like structure for this
529
+ this.toRefresh.push({
530
+ path: changedPath,
531
+ boundControls: [repeat]
532
+ });
533
+
534
+ console.log('Found a repeat to update!!!', repeat)
535
+ }
521
536
  }
522
537
  }
523
- }
524
- }
525
- */
538
+ }
539
+ */
526
540
  if (this.isRefreshing) {
527
541
  return;
528
542
  }
@@ -567,18 +581,18 @@ export class FxFore extends HTMLElement {
567
581
  });
568
582
  this.toRefresh = [];
569
583
  /*
570
- if (!needsRefresh) {
571
- console.log('no dependants to refresh');
572
- }
573
- */
584
+ if (!needsRefresh) {
585
+ console.log('no dependants to refresh');
586
+ }
587
+ */
574
588
  } else {
575
589
  // ### resetting visited state for controls to refresh
576
590
  /*
577
- const visited = this.parentNode.querySelectorAll('.visited');
578
- Array.from(visited).forEach(v =>{
579
- v.classList.remove('visited');
580
- });
581
- */
591
+ const visited = this.parentNode.querySelectorAll('.visited');
592
+ Array.from(visited).forEach(v =>{
593
+ v.classList.remove('visited');
594
+ });
595
+ */
582
596
 
583
597
  if (this.inited) {
584
598
  Fore.refreshChildren(this, force);
@@ -598,7 +612,11 @@ export class FxFore extends HTMLElement {
598
612
  // this.dispatchEvent(new CustomEvent('refresh-done'));
599
613
  // this.initialRun = false;
600
614
  this.style.visibility = 'visible';
601
- console.log(`### <<<<< refresh-done ${this.id} >>>>>`);
615
+ console.info(
616
+ `%crefresh-done on #${this.id}`,
617
+ 'background:darkorange; color:black; padding:.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;',
618
+ );
619
+
602
620
  Fore.dispatch(this, 'refresh-done', {});
603
621
 
604
622
  // this.isRefreshing = true;
@@ -606,20 +624,20 @@ export class FxFore extends HTMLElement {
606
624
 
607
625
  const subFores = Array.from(this.querySelectorAll('fx-fore'));
608
626
  /*
609
- calling the parent to refresh causes errors and inconsistent state. Also it is questionable
610
- if a child should actually interact with its parent in this way.
627
+ calling the parent to refresh causes errors and inconsistent state. Also it is questionable
628
+ if a child should actually interact with its parent in this way.
611
629
 
612
- This only affects the refreshing NOT the data mutation itself which is happening as expected.
630
+ This only affects the refreshing NOT the data mutation itself which is happening as expected.
613
631
 
614
- Current solution is that a child that wants the parent to refresh must do so by adding an additional
615
- event handler that dispatches an event upwards and having a handler in the parent to refresh itself.
632
+ Current solution is that a child that wants the parent to refresh must do so by adding an additional
633
+ event handler that dispatches an event upwards and having a handler in the parent to refresh itself.
616
634
 
617
- So refreshed propagate downwards but not upwards which is at least an option to consider.
635
+ So refreshed propagate downwards but not upwards which is at least an option to consider.
618
636
 
619
- if(this.parentNode.nodeType !== Node.DOCUMENT_FRAGMENT_NODE){
620
- // await this.parentNode.closest('fx-fore')?.refresh(false);
621
- }
622
- */
637
+ if(this.parentNode.nodeType !== Node.DOCUMENT_FRAGMENT_NODE){
638
+ // await this.parentNode.closest('fx-fore')?.refresh(false);
639
+ }
640
+ */
623
641
  for (const subFore of subFores) {
624
642
  // subFore.refresh(false, changedPaths);
625
643
  if (subFore.ready) {
@@ -651,8 +669,8 @@ export class FxFore extends HTMLElement {
651
669
  // console.log('######### storedTemplateExpressions', this.storedTemplateExpressions.length);
652
670
 
653
671
  /*
654
- storing expressions and their nodes for re-evaluation
655
- */
672
+ storing expressions and their nodes for re-evaluation
673
+ */
656
674
  Array.from(tmplExpressions).forEach(node => {
657
675
  const ele = node.nodeType === Node.ATTRIBUTE_NODE ? node.ownerElement : node.parentNode;
658
676
  if (ele.closest('fx-fore') !== this) {
@@ -891,43 +909,43 @@ export class FxFore extends HTMLElement {
891
909
  }
892
910
 
893
911
  /*
894
- _createStep(){
912
+ _createStep(){
895
913
 
896
- }
897
- */
914
+ }
915
+ */
898
916
 
899
917
  /*
900
- _generateInstance(start, parent) {
901
- if (start.hasAttribute('ref')) {
902
- const ref = start.getAttribute('ref');
918
+ _generateInstance(start, parent) {
919
+ if (start.hasAttribute('ref')) {
920
+ const ref = start.getAttribute('ref');
903
921
 
904
- if(ref.includes('/')){
905
- console.log('complex path to create ', ref);
906
- const steps = ref.split('/');
907
- steps.forEach(step => {
908
- console.log('step ', step);
922
+ if(ref.includes('/')){
923
+ console.log('complex path to create ', ref);
924
+ const steps = ref.split('/');
925
+ steps.forEach(step => {
926
+ console.log('step ', step);
909
927
 
910
- });
911
- }
928
+ });
929
+ }
912
930
 
913
- // const generated = document.createElement(ref);
914
- const generated = parent.ownerDocument.createElement(ref);
915
- if (start.children.length === 0) {
916
- generated.textContent = start.textContent;
931
+ // const generated = document.createElement(ref);
932
+ const generated = parent.ownerDocument.createElement(ref);
933
+ if (start.children.length === 0) {
934
+ generated.textContent = start.textContent;
935
+ }
936
+ parent.appendChild(generated);
937
+ parent = generated;
917
938
  }
918
- parent.appendChild(generated);
919
- parent = generated;
920
- }
921
939
 
922
- if (start.hasChildNodes()) {
923
- const list = start.children;
924
- for (let i = 0; i < list.length; i += 1) {
925
- this._generateInstance(list[i], parent);
940
+ if (start.hasChildNodes()) {
941
+ const list = start.children;
942
+ for (let i = 0; i < list.length; i += 1) {
943
+ this._generateInstance(list[i], parent);
944
+ }
926
945
  }
946
+ return parent;
927
947
  }
928
- return parent;
929
- }
930
- */
948
+ */
931
949
 
932
950
  /**
933
951
  * Start the initialization of the UI by
@@ -941,22 +959,29 @@ export class FxFore extends HTMLElement {
941
959
  */
942
960
  async _initUI() {
943
961
  // console.log('### _initUI()');
944
- console.log(`### <<<<< _initUI '${this.id}' >>>>>`);
962
+ console.info(
963
+ `%cinitUI #${this.id}`,
964
+ 'background:lightblue; color:black; padding:.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;',
965
+ );
945
966
 
946
967
  if (!this.initialRun) return;
947
968
  this.classList.add('initialRun');
948
969
  await this._lazyCreateInstance();
949
970
 
950
971
  /*
951
- const options = {
952
- root: null,
953
- rootMargin: '0px',
954
- threshold: 0.3,
955
- };
956
- */
972
+ const options = {
973
+ root: null,
974
+ rootMargin: '0px',
975
+ threshold: 0.3,
976
+ };
977
+ */
957
978
 
958
979
  // First refresh should be forced
980
+ if (this.createNodes) {
981
+ this.initData();
982
+ }
959
983
  await this.refresh(true);
984
+
960
985
  // this.style.display='block'
961
986
  this.classList.add('fx-ready');
962
987
  document.body.classList.add('fx-ready');
@@ -964,7 +989,12 @@ export class FxFore extends HTMLElement {
964
989
  this.ready = true;
965
990
  this.initialRun = false;
966
991
  // console.log('### >>>>> dispatching ready >>>>>', this);
967
- console.log(`### <<<<< ${this.id} ready >>>>>`);
992
+ console.info(
993
+ `%c #${this.id} is ready`,
994
+ 'background:lightblue; color:black; padding:.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;',
995
+ );
996
+
997
+ // console.log(`### <<<<< ${this.id} ready >>>>>`);
968
998
 
969
999
  // console.log('### modelItems: ', this.getModel().modelItems);
970
1000
  Fore.dispatch(this, 'ready', {});
@@ -982,6 +1012,133 @@ export class FxFore extends HTMLElement {
982
1012
  });
983
1013
  }
984
1014
 
1015
+ /**
1016
+ * @param {HTMLElement} root The root of the data initialization. fx-repeat overrides this when it makes new repeat items
1017
+ *
1018
+ */
1019
+ initData(root = this) {
1020
+ // const created = new Promise(resolve => {
1021
+ console.log('INIT');
1022
+ const boundControls = Array.from(root.querySelectorAll('[ref]:not(fx-model *),fx-repeatitem'));
1023
+ if (root.matches('fx-repeatitem')) {
1024
+ boundControls.unshift(root);
1025
+ }
1026
+ // console.log('_initD', boundControls);
1027
+ for (let i = 0; i < boundControls.length; i++) {
1028
+ const control = boundControls[i];
1029
+ if (!control.matches('fx-repeatitem')) {
1030
+ // Repeat items are dumb. They do not respond to evalInContext
1031
+ control.evalInContext();
1032
+ }
1033
+ let ownerDoc;
1034
+ if (control.nodeset !== null) {
1035
+ // console.log('Node exists', control.nodeset);
1036
+ continue;
1037
+ }
1038
+ // console.log('Node does not exists', control.ref);
1039
+
1040
+ // We need to create that node!
1041
+ const previousControl = boundControls[i - 1];
1042
+
1043
+ // Previous control can either be an ancestor of us, or a previous node, which can be a sibling, or a child of a sibling.
1044
+ // First: parent
1045
+ if (previousControl.contains(control)) {
1046
+ // Parent is here.
1047
+ // console.log('insert into', control,previousControl);
1048
+ // console.log('insert into nodeset', control.nodeset);
1049
+ const parentNodeset = previousControl.nodeset;
1050
+ // console.log('parentNodeset', parentNodeset);
1051
+
1052
+ // const parentModelItemNode = parentModelItem.node;
1053
+ const ref = control.ref;
1054
+ // const newElement = parentModelItemNode.ownerDocument.createElement(ref);
1055
+ if (parentNodeset.querySelector(`[ref="${ref}"]`)) {
1056
+ console.log(`Node with ref "${ref}" already exists.`);
1057
+ continue;
1058
+ }
1059
+
1060
+ const newElement = this._createNodes(ref, parentNodeset);
1061
+
1062
+ // Plonk it in at the start!
1063
+ parentNodeset.insertBefore(newElement, parentNodeset.firstChild);
1064
+ control.evalInContext();
1065
+ console.log('CREATED child', newElement);
1066
+ // console.log('new control evaluated to ', control.nodeset);
1067
+ // Done!
1068
+ continue;
1069
+ }
1070
+ // console.log('previousControl', previousControl);
1071
+ // console.log('control', control);
1072
+ // Is previousControl a sibling or a descendant of a logical sibling? Keep looking backwards until we share parents!
1073
+ const ourParent = XPathUtil.getParentBindingElement(control);
1074
+ // console.log('ourParent', ourParent);
1075
+ let siblingControl = null;
1076
+ /*
1077
+ for (let j = i - 1; j >= 0; --j) {
1078
+ const potentialSibling = boundControls[j];
1079
+ if (XPathUtil.getParentBindingElement(potentialSibling) === ourParent) {
1080
+ siblingControl = potentialSibling;
1081
+ break; // Exit once the sibling is found
1082
+ }
1083
+ }
1084
+ */
1085
+ for (let j = i - 1; j > 0; --j) {
1086
+ const siblingOrDescendant = boundControls[j];
1087
+ if (XPathUtil.getParentBindingElement(siblingOrDescendant) === ourParent) {
1088
+ siblingControl = siblingOrDescendant;
1089
+ break;
1090
+ }
1091
+ }
1092
+ if (!siblingControl) {
1093
+ throw new Error('Unexpected! there must be a sibling right?');
1094
+ }
1095
+ // console.log('sibling', siblingControl);
1096
+ const parentNodeset = ourParent.nodeset;
1097
+ const ref = control.ref;
1098
+ let referenceNodeset = siblingControl.nodeset;
1099
+ const newElement = this._createNodes(ref, parentNodeset);
1100
+
1101
+ // We know which node to insert this new element to, but it might be a descendant of a child of the actual parent. Walk up until we have a reference under our parent
1102
+ while (referenceNodeset?.parentNode && referenceNodeset?.parentNode !== parentNodeset) {
1103
+ referenceNodeset = referenceNodeset.parentNode;
1104
+ }
1105
+
1106
+ // Insert before the next sibling our our logical previous sibling
1107
+ parentNodeset.insertBefore(newElement, referenceNodeset.nextElementSibling);
1108
+ /*
1109
+ console.log('control inscope', control.getInScopeContext());
1110
+ console.log('control ref', control.ref);
1111
+ console.log('control new element parent', newElement.parentNode.nodeName);
1112
+ */
1113
+ control.evalInContext();
1114
+ // console.log('new control evaluated to ', control.nodeset);
1115
+ console.log('CREATED sibling', newElement);
1116
+ }
1117
+ // console.log('DATA', this.getModel().getDefaultContext());
1118
+ }
1119
+
1120
+ _createNodes(ref, referenceNode) {
1121
+ // console.log('creating', ref)
1122
+ // console.log('ownerDoc', referenceNode.ownerDocument);
1123
+ /*
1124
+ const existingNode = evaluateXPathToFirstNode(ref, referenceNode, this);
1125
+ if(existingNode){
1126
+ console.log(`Node already exists for ref: ${ref}`);
1127
+ return existingNode;
1128
+ }
1129
+ */
1130
+ console.log(`creating new node for ref: ${ref}`);
1131
+ let newElement;
1132
+ if (ref.includes('/')) {
1133
+ // multi-step ref expressions
1134
+ newElement = XPathUtil.createElementFromXPath(ref, referenceNode.ownerDocument, this);
1135
+ // console.log('new subtree', newElement);
1136
+ return newElement;
1137
+ } else {
1138
+ return XPathUtil.createElementFromXPath(ref, referenceNode.ownerDocument, this);
1139
+ }
1140
+ }
1141
+
985
1142
  _handleDragStart(event) {
986
1143
  const draggedItem = event.target.closest('[draggable="true"]');
987
1144
  this.originalDraggedItem = draggedItem;
@@ -1107,18 +1264,18 @@ export class FxFore extends HTMLElement {
1107
1264
  Array.from(repeats).forEach(item => {
1108
1265
  if (item.closest('fx-control')) return;
1109
1266
  /*
1110
- const parentRepeat = item.closest('fx-repeat');
1111
- if(parentRepeat){
1112
- this.dispatchEvent(
1113
- new CustomEvent('log', {
1114
- composed: false,
1115
- bubbles: true,
1116
- cancelable:true,
1117
- detail: { id:this.id, message: `nesting elements with data-ref attributes within fx-repeat is not supported by now`, level:'Error'},
1118
- }),
1119
- );
1120
- }
1121
- */
1267
+ const parentRepeat = item.closest('fx-repeat');
1268
+ if(parentRepeat){
1269
+ this.dispatchEvent(
1270
+ new CustomEvent('log', {
1271
+ composed: false,
1272
+ bubbles: true,
1273
+ cancelable:true,
1274
+ detail: { id:this.id, message: `nesting elements with data-ref attributes within fx-repeat is not supported by now`, level:'Error'},
1275
+ }),
1276
+ );
1277
+ }
1278
+ */
1122
1279
 
1123
1280
  const table = item.parentNode.closest('table');
1124
1281
  let host;
@@ -213,8 +213,9 @@ export class FxInstance extends HTMLElement {
213
213
  this.originalInstance = [...this.instanceData];
214
214
  }
215
215
  if (this.type === 'text') {
216
- this.instanceData = '';
217
- this.originalInstance = '';
216
+ this.instanceData = this.innerText;
217
+ this.originalInstance = this.innerText;
218
+ console.log('text data', this.instanceData);
218
219
  }
219
220
  }
220
221