@jinntec/fore 2.4.2 → 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-model.js CHANGED
@@ -51,7 +51,7 @@ export class FxModel extends HTMLElement {
51
51
  <slot></slot>
52
52
  `;
53
53
 
54
- /*
54
+ /*
55
55
  this.addEventListener('model-construct-done', () => {
56
56
  // this.modelConstructed = true;
57
57
  // console.log('model-construct-done fired ', this.modelConstructed);
@@ -65,12 +65,38 @@ export class FxModel extends HTMLElement {
65
65
  this.fore = this.parentNode;
66
66
  }
67
67
 
68
- static lazyCreateModelItem(model, ref, node) {
68
+ /**
69
+ * @param {FxModel} model The model to create a model item for
70
+ * @param {string} ref The XPath ref that led to this model item
71
+ * @param {Node} node The node the XPath led to
72
+ * @param {ForeElementMixin} formElement The form element making this model. Used to resolve variables against
73
+ */
74
+ static lazyCreateModelItem(model, ref, node, formElement) {
69
75
  // console.log('lazyCreateModelItem ', node);
70
-
76
+ const instanceId = XPathUtil.resolveInstance(formElement, ref);
77
+
78
+ if (model.parentNode.createNodes && (node === null || node === undefined)) {
79
+ // ### intializing ModelItem with default values (as there is no <fx-bind> matching for given ref)
80
+ const mi = new ModelItem(
81
+ undefined,
82
+ ref,
83
+ Fore.READONLY_DEFAULT,
84
+ false,
85
+ Fore.REQUIRED_DEFAULT,
86
+ Fore.CONSTRAINT_DEFAULT,
87
+ Fore.TYPE_DEFAULT,
88
+ null,
89
+ this,
90
+ instanceId,
91
+ );
92
+
93
+ // console.log('new ModelItem is instanceof ModelItem ', mi instanceof ModelItem);
94
+ model.registerModelItem(mi);
95
+ return mi;
96
+ }
71
97
  let targetNode = {};
72
98
  if (node === null || node === undefined) return null;
73
- if (node.nodeType === node.TEXT_NODE) {
99
+ if (node.nodeType === Node.TEXT_NODE) {
74
100
  // const parent = node.parentNode;
75
101
  // console.log('PARENT ', parent);
76
102
  targetNode = node.parentNode;
@@ -81,9 +107,7 @@ export class FxModel extends HTMLElement {
81
107
  // const path = fx.evaluateXPath('path()',node);
82
108
  let path;
83
109
  if (node.nodeType) {
84
- const instance = XPathUtil.resolveInstance(model, ref);
85
-
86
- path = XPathUtil.getPath(node, instance);
110
+ path = XPathUtil.getPath(node, instanceId);
87
111
  } else {
88
112
  path = null;
89
113
  targetNode = node;
@@ -101,6 +125,7 @@ export class FxModel extends HTMLElement {
101
125
  Fore.TYPE_DEFAULT,
102
126
  targetNode,
103
127
  this,
128
+ instanceId,
104
129
  );
105
130
 
106
131
  // console.log('new ModelItem is instanceof ModelItem ', mi instanceof ModelItem);
@@ -118,7 +143,11 @@ export class FxModel extends HTMLElement {
118
143
  *
119
144
  */
120
145
  async modelConstruct() {
121
- console.log(`### <<<<< dispatching model-construct for '${this.fore.id}' >>>>>`);
146
+ console.info(
147
+ `%cdispatching model-construct for #${this.parentNode.id}`,
148
+ 'background:lightblue; color:black; padding:.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;',
149
+ );
150
+
122
151
  // this.dispatchEvent(new CustomEvent('model-construct', { detail: this }));
123
152
  Fore.dispatch(this, 'model-construct', { model: this });
124
153
 
@@ -132,25 +161,25 @@ export class FxModel extends HTMLElement {
132
161
 
133
162
  // Wait until all the instances are built
134
163
  await Promise.all(promises);
135
- this.instances = Array.from(instances);
136
- // console.log('_modelConstruct this.instances ', this.instances);
137
- // Await until the model-construct-done event is handled off
138
- this.modelConstructed = true;
139
- await Fore.dispatch(this, 'model-construct-done', {model: this});
140
- this.inited = true;
141
- this.updateModel();
142
- } else {
143
- // ### if there's no instance one will created
144
- console.log(`### <<<<< dispatching model-construct-done for '${this.fore.id}' >>>>>`);
145
- this.modelConstructed = true;
146
- await this.dispatchEvent(
147
- new CustomEvent('model-construct-done', {
148
- composed: false,
149
- bubbles: true,
150
- detail: {model: this},
151
- }),
152
- );
153
- }
164
+ this.instances = Array.from(instances);
165
+ // console.log('_modelConstruct this.instances ', this.instances);
166
+ // Await until the model-construct-done event is handled off
167
+ this.modelConstructed = true;
168
+ await Fore.dispatch(this, 'model-construct-done', { model: this });
169
+ this.inited = true;
170
+ this.updateModel();
171
+ } else {
172
+ // ### if there's no instance one will created
173
+ console.log(`### <<<<< dispatching model-construct-done for '${this.fore.id}' >>>>>`);
174
+ this.modelConstructed = true;
175
+ await this.dispatchEvent(
176
+ new CustomEvent('model-construct-done', {
177
+ composed: false,
178
+ bubbles: true,
179
+ detail: { model: this },
180
+ }),
181
+ );
182
+ }
154
183
 
155
184
  const functionlibImports = Array.from(this.querySelectorAll('fx-functionlib'));
156
185
  await Promise.all(functionlibImports.map(lib => lib.readyPromise));
@@ -416,7 +445,7 @@ export class FxModel extends HTMLElement {
416
445
  modelItem.required = compute;
417
446
  this.formElement.addToRefresh(modelItem); // let fore know that modelItem needs refresh
418
447
  if (!modelItem.node.textContent) {
419
- /*
448
+ /*
420
449
  console.log(
421
450
  'node is required but has no value ',
422
451
  XPathUtil.getDocPath(modelItem.node),
@@ -468,17 +497,17 @@ export class FxModel extends HTMLElement {
468
497
  /**
469
498
  * @returns {import('./fx-instance.js').FxInstance}
470
499
  */
471
- getDefaultInstance() {
472
- /*
500
+ getDefaultInstance() {
501
+ /*
473
502
  if (this.instances.length === 0) {
474
503
  throw new Error('No instances defined. Fore cannot work without any <data/> elements.');
475
504
  }
476
505
  */
477
- if (this.instances.length) {
478
- return this.instances[0];
479
- }
480
- return this.getInstance('default');
506
+ if (this.instances.length) {
507
+ return this.instances[0];
481
508
  }
509
+ return this.getInstance('default');
510
+ }
482
511
 
483
512
  getDefaultInstanceData() {
484
513
  return this.instances[0].getInstanceData();
@@ -83,7 +83,6 @@ export class FxSubmission extends ForeElementMixin {
83
83
  }
84
84
 
85
85
  async _submit() {
86
- console.log('submitting....', this.getAttribute('id'));
87
86
  console.info(
88
87
  `%csubmitting #${this.id}`,
89
88
  'background:yellow; color:black; padding:.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;',
@@ -265,10 +264,12 @@ export class FxSubmission extends ForeElementMixin {
265
264
  // this.dispatch('submit-done', {});
266
265
  // console.log(`### <<<<< ${this.id} submit-done >>>>>`);
267
266
  Fore.dispatch(this, 'submit-done', {});
267
+ /*
268
268
  console.info(
269
269
  `%csubmit-done #${this.id}`,
270
270
  'background:green; color:white; padding:.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;',
271
271
  );
272
+ */
272
273
 
273
274
  } catch (error) {
274
275
  Fore.dispatch(this, 'submit-error', { error: error.message });
@@ -465,12 +466,22 @@ export class FxSubmission extends ForeElementMixin {
465
466
  }
466
467
  // document.getElementsByTagName('html')[0].innerHTML = data;
467
468
  }
468
- if (this.replace === 'target' && contentType.startsWith('text/html')) {
469
+ if (this.replace === 'target') {
469
470
  // const target = this.getAttribute('target');
470
471
  const target = this._getProperty('target');
471
472
  const targetNode = document.querySelector(target);
472
473
  if (targetNode) {
473
- targetNode.innerHTML = data;
474
+
475
+ if(contentType.startsWith('text/html')){
476
+ targetNode.innerHTML = data;
477
+ }
478
+ if(this.responseMediatype.startsWith('image/svg')){
479
+ const parser = new DOMParser();
480
+ const svgDoc = parser.parseFromString(data, 'image/svg+xml');
481
+
482
+ const objectURL = URL.createObjectURL(data);
483
+ targetNode.src = objectURL;
484
+ }
474
485
  } else {
475
486
  Fore.dispatch(this, 'submit-error', {
476
487
  message: `targetNode for selector ${target} not found`,
@@ -48,7 +48,7 @@ function _getInitialContext(node, ref) {
48
48
  }
49
49
 
50
50
  if (XPathUtil.isAbsolutePath(ref)) {
51
- const instanceId = XPathUtil.getInstanceId(ref);
51
+ const instanceId = XPathUtil.getInstanceId(ref, node);
52
52
  if (instanceId) {
53
53
  return model.getInstance(instanceId).getDefaultContext();
54
54
  }
package/src/modelitem.js CHANGED
@@ -17,8 +17,9 @@ export class ModelItem {
17
17
  * @param {string} type - string expression to set a datatype
18
18
  * @param {Node} node - the node the 'ref' expression is referring to
19
19
  * @param {import('./fx-bind').FxBind} bind - the fx-bind element having created this modelItem
20
+ * @param {string} instance - the fx-instance id having created this modelItem
20
21
  */
21
- constructor(path, ref, readonly, relevant, required, constraint, type, node, bind) {
22
+ constructor(path, ref, readonly, relevant, required, constraint, type, node, bind,instance) {
22
23
  /**
23
24
  * @type {string}
24
25
  */
@@ -61,10 +62,11 @@ export class ModelItem {
61
62
  */
62
63
  this.alerts = [];
63
64
  /**
64
- * @type {import('./ui/fx-control').default[]}
65
+ * @type {import('./ui/abstract-control').default[]}
65
66
  */
66
67
  this.boundControls = [];
67
68
  // this.value = this._getValue();
69
+ this.instanceId = instance;
68
70
  }
69
71
 
70
72
  /*
@@ -74,6 +76,9 @@ export class ModelItem {
74
76
  */
75
77
 
76
78
  get value() {
79
+ if (!this.node) {
80
+ return null;
81
+ }
77
82
  if (!this.node.nodeType) return this.node;
78
83
 
79
84
  if (this.node.nodeType === Node.ATTRIBUTE_NODE) {
@@ -64,10 +64,10 @@ export default class AbstractControl extends ForeElementMixin {
64
64
 
65
65
  // await this.updateComplete;
66
66
  // await this.getWidget();
67
- this.oldVal = this.nodeset ? this.nodeset : null;
68
- // console.log('oldVal',this.oldVal);
69
67
 
70
68
  this.evalInContext();
69
+ this.oldVal = this.nodeset ? this.nodeset : null;
70
+ // console.log('oldVal',this.oldVal);
71
71
 
72
72
  // todo this if should be removed - see above
73
73
  if (this.isBound()) {
@@ -169,13 +169,15 @@ export default class AbstractControl extends ForeElementMixin {
169
169
  if (!this.getOwnerForm().ready) return; // state change event do not fire during init phase (initial refresh)
170
170
  // if oldVal is null we haven't received a concrete value yet
171
171
 
172
- if (this.localName !== 'fx-control') return;
172
+ if (!(this.localName === 'fx-control' || this.localName === 'fx-upload')) return;
173
173
  if (isDifferent(this.oldVal, oldValue, this.value)) {
174
174
  const model = this.getModel();
175
175
  Fore.dispatch(this, 'value-changed', {
176
176
  path: this.modelItem.path,
177
177
  value: this.modelItem.value,
178
- oldvalue: this.oldVal,
178
+ oldvalue: oldValue,
179
+ instanceId:this.modelItem.instanceId,
180
+ foreId:this.getOwnerForm().id
179
181
  });
180
182
  }
181
183
  }
@@ -41,8 +41,9 @@ export class FxContainer extends ForeElementMixin {
41
41
  </style>
42
42
  ${html}
43
43
  `;
44
-
45
- this.getOwnerForm().registerLazyElement(this);
44
+ if(this.ref !== ''){
45
+ this.getOwnerForm().registerLazyElement(this);
46
+ }
46
47
 
47
48
  /*
48
49
  this.addEventListener('mousedown', e => {
@@ -37,6 +37,7 @@ export default class FxControl extends XfAbstractControl {
37
37
  constructor() {
38
38
  super();
39
39
  this.inited = false;
40
+ this.nodeset = null;
40
41
  this.attachShadow({ mode: 'open' });
41
42
  }
42
43
 
@@ -167,28 +168,7 @@ export default class FxControl extends XfAbstractControl {
167
168
  }
168
169
 
169
170
  this.addEventListener('return', e => {
170
- // console.log('catched return action on ', this);
171
- // console.log('return detail', e.detail);
172
-
173
- // console.log('return triggered on ', this);
174
- // console.log('this.ref', this.ref);
175
- // console.log('current outer instance', this.getInstance());
176
-
177
- /*
178
- console.log(
179
- '???? why ???? current nodeset should point to the node of the outer control',
180
- e.currentTarget.nodeset,
181
- );
182
- console.log(
183
- '???? why ???? current nodeset should point to the node of the outer control',
184
- this.nodeset,
185
- );
186
- */
187
171
  const newNodes = e.detail.nodeset;
188
- // console.log('new nodeset', newNodes);
189
- // console.log('currentTarget', e.currentTarget);
190
- // console.log('target', e.target);
191
-
192
172
  e.stopPropagation();
193
173
 
194
174
  this._replaceNode(newNodes);
@@ -347,9 +327,9 @@ export default class FxControl extends XfAbstractControl {
347
327
  }
348
328
 
349
329
  // ### value is bound to radio
350
- if(this.widget.type === 'radio'){
330
+ if (this.widget.type === 'radio') {
351
331
  const matches = this.querySelector(`input[value=${this.value}]`);
352
- if(matches){
332
+ if (matches) {
353
333
  matches.checked = true;
354
334
  }
355
335
  return;
@@ -367,7 +347,6 @@ export default class FxControl extends XfAbstractControl {
367
347
  return;
368
348
  }
369
349
 
370
-
371
350
  if (this.hasAttribute('as')) {
372
351
  const as = this.getAttribute('as');
373
352
 
@@ -298,6 +298,11 @@ export class FxRepeat extends withDraggability(ForeElementMixin, false) {
298
298
 
299
299
  newItem.nodeset = this.nodeset[position - 1];
300
300
  newItem.index = position;
301
+
302
+ if (this.getOwnerForm().createNodes) {
303
+ this.getOwnerForm().initData(newItem);
304
+ }
305
+
301
306
  // Tell the owner form we might have new template expressions here
302
307
  this.getOwnerForm().scanForNewTemplateExpressionsNextRefresh();
303
308
  }
@@ -393,6 +398,10 @@ export class FxRepeat extends withDraggability(ForeElementMixin, false) {
393
398
 
394
399
  this.appendChild(repeatItem);
395
400
 
401
+ if (this.getOwnerForm().createNodes) {
402
+ this.getOwnerForm().initData(repeatItem);
403
+ }
404
+
396
405
  if (repeatItem.index === 1) {
397
406
  this.applyIndex(repeatItem);
398
407
  }
@@ -8,10 +8,13 @@ import { withDraggability } from '../withDraggability.js';
8
8
  *
9
9
  * @customElement
10
10
  * @demo demo/index.html
11
+ *
12
+ * @extends {ForeElementMixin}
11
13
  */
12
14
  export class FxRepeatitem extends withDraggability(ForeElementMixin, true) {
13
15
  static get properties() {
14
16
  return {
17
+ ...super.properties,
15
18
  inited: {
16
19
  type: Boolean,
17
20
  },
@@ -0,0 +1,89 @@
1
+ export class FxSelect extends HTMLElement {
2
+ constructor() {
3
+ super();
4
+
5
+ // Attach Shadow DOM to encapsulate the component
6
+ this.attachShadow({ mode: 'open' });
7
+
8
+ // Create and attach the <select> element
9
+ this.selectElement = document.createElement('select');
10
+ this.shadowRoot.appendChild(this.selectElement);
11
+
12
+ // Set default values
13
+ this.batchSize = parseInt(this.getAttribute('batch-size')) || 20;
14
+ this.size = parseInt(this.getAttribute('size')) || 5;
15
+ this.currentLoadedItems = 0;
16
+ this.hasMoreItems = true; // Assume there are more items initially
17
+
18
+ // Set the size of the <select> element
19
+ this.selectElement.setAttribute('size', this.size);
20
+
21
+ // Bind the scroll event handler
22
+ this.handleScroll = this.handleScroll.bind(this);
23
+ }
24
+
25
+ connectedCallback() {
26
+ // Initial load of options
27
+ this.loadOptions(this.currentLoadedItems, this.batchSize);
28
+
29
+ // Attach scroll event listener
30
+ this.selectElement.addEventListener('scroll', this.handleScroll);
31
+ }
32
+
33
+ disconnectedCallback() {
34
+ // Clean up event listeners when the element is removed
35
+ this.selectElement.removeEventListener('scroll', this.handleScroll);
36
+ }
37
+
38
+ // Mock function to simulate fetching more options
39
+ fetchMoreItems(start, count) {
40
+ // Simulate an async fetch or data load (replace with real fetch if needed)
41
+ return new Promise((resolve) => {
42
+ setTimeout(() => {
43
+ const items = [];
44
+ for (let i = start; i < start + count; i++) {
45
+ if (i < 200) { // Let's say we stop after 200 items (remove this limit for real use)
46
+ items.push({ value: i, label: `Option ${i + 1}` });
47
+ }
48
+ }
49
+ resolve(items);
50
+ }, 500); // Simulate fetch delay
51
+ });
52
+ }
53
+
54
+ // Method to load a batch of options
55
+ async loadOptions(start, count) {
56
+ const fragment = document.createDocumentFragment();
57
+ const newItems = await this.fetchMoreItems(start, count);
58
+
59
+ if (newItems.length > 0) {
60
+ newItems.forEach(item => {
61
+ const option = document.createElement('option');
62
+ option.value = item.value;
63
+ option.textContent = item.label;
64
+ fragment.appendChild(option);
65
+ });
66
+
67
+ this.selectElement.appendChild(fragment);
68
+ this.currentLoadedItems += newItems.length;
69
+ } else {
70
+ // If no more items are returned, stop further loading
71
+ this.hasMoreItems = false;
72
+ }
73
+ }
74
+
75
+ // Scroll event handler to load more items
76
+ handleScroll() {
77
+ const { scrollTop, scrollHeight, clientHeight } = this.selectElement;
78
+ const isNearBottom = scrollTop + clientHeight >= scrollHeight - 10;
79
+
80
+ if (isNearBottom && this.hasMoreItems) {
81
+ this.loadOptions(this.currentLoadedItems, this.batchSize);
82
+ }
83
+ }
84
+ }
85
+
86
+ // Register the custom element
87
+ if (!customElements.get('fx-select')) {
88
+ customElements.define('fx-select', FxSelect);
89
+ }