@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-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),
@@ -438,7 +467,7 @@ export class FxModel extends HTMLElement {
438
467
  }
439
468
  }
440
469
  });
441
- // console.log('modelItems after revalidate: ', this.modelItems);
470
+ console.log('modelItems after revalidate: ', this.modelItems);
442
471
  return valid;
443
472
  }
444
473
 
@@ -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();
@@ -50,8 +50,8 @@ export class FxSubmission extends ForeElementMixin {
50
50
  ? this.getAttribute('mediatype')
51
51
  : 'application/xml';
52
52
 
53
- this.responseMediatype = this.hasAttribute('response-mediatype')
54
- ? this.getAttribute('response-mediatype')
53
+ this.responseMediatype = this.hasAttribute('responsemediatype')
54
+ ? this.getAttribute('responsemediatype')
55
55
  : this.mediatype;
56
56
  this.url = this.hasAttribute('url') ? this.getAttribute('url') : null;
57
57
 
@@ -83,7 +83,11 @@ export class FxSubmission extends ForeElementMixin {
83
83
  }
84
84
 
85
85
  async _submit() {
86
- // console.log('submitting....', this.getAttribute('id'));
86
+ console.info(
87
+ `%csubmitting #${this.id}`,
88
+ 'background:yellow; color:black; padding:.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;',
89
+ );
90
+
87
91
  this.evalInContext();
88
92
  const model = this.getModel();
89
93
 
@@ -232,6 +236,11 @@ export class FxSubmission extends ForeElementMixin {
232
236
 
233
237
  if (!response.ok || response.status > 400) {
234
238
  // this.dispatch('submit-error', { message: `Error while submitting ${this.id}` });
239
+ console.info(
240
+ `%csubmit-error #${this.id}`,
241
+ 'background:red; color:black; padding:.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;',
242
+ );
243
+
235
244
  Fore.dispatch(this, 'submit-error', { message: `Error while submitting ${this.id}` });
236
245
  return;
237
246
  }
@@ -255,6 +264,13 @@ export class FxSubmission extends ForeElementMixin {
255
264
  // this.dispatch('submit-done', {});
256
265
  // console.log(`### <<<<< ${this.id} submit-done >>>>>`);
257
266
  Fore.dispatch(this, 'submit-done', {});
267
+ /*
268
+ console.info(
269
+ `%csubmit-done #${this.id}`,
270
+ 'background:green; color:white; padding:.5rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;width:100%;',
271
+ );
272
+ */
273
+
258
274
  } catch (error) {
259
275
  Fore.dispatch(this, 'submit-error', { error: error.message });
260
276
  } finally {
@@ -354,13 +370,6 @@ export class FxSubmission extends ForeElementMixin {
354
370
  _handleResponse(data, resolvedUrl, contentType) {
355
371
  // console.log('_handleResponse ', data);
356
372
 
357
- /*
358
- // ### responses need to be handled depending on their type.
359
- if(this.type === 'json'){
360
-
361
- }
362
- */
363
-
364
373
  const targetInstance = this._getTargetInstance();
365
374
 
366
375
  /*
@@ -386,16 +395,26 @@ export class FxSubmission extends ForeElementMixin {
386
395
  if (this.replace === 'instance') {
387
396
  if (targetInstance) {
388
397
  if (this.targetref) {
398
+
389
399
  const [theTarget] = evaluateXPath(
390
- this.targetref,
391
- targetInstance.instanceData.firstElementChild,
392
- this,
400
+ this.targetref,
401
+ targetInstance.instanceData.firstElementChild,
402
+ this,
393
403
  );
394
404
  console.log('theTarget', theTarget);
395
- const clone = data.firstElementChild;
396
- const parent = theTarget.parentNode;
397
- parent.replaceChild(clone, theTarget);
398
- console.log('finally ', parent);
405
+ if(this.responseMediatype === 'application/xml' || this.responseMediatype === 'text/html'){
406
+ const clone = data.firstElementChild;
407
+ const parent = theTarget.parentNode;
408
+ parent.replaceChild(clone, theTarget);
409
+ console.log('finally ', parent);
410
+ }
411
+ if(this.responseMediatype.startsWith('text/')){
412
+ theTarget.textContent = data;
413
+ }
414
+ if(this.responseMediatype === 'application/json'){
415
+ console.warn('targetref is not supported for application/json responses')
416
+ }
417
+
399
418
  } else if (this.into) {
400
419
  const [theTarget] = evaluateXPath(
401
420
  this.into,
@@ -447,12 +466,22 @@ export class FxSubmission extends ForeElementMixin {
447
466
  }
448
467
  // document.getElementsByTagName('html')[0].innerHTML = data;
449
468
  }
450
- if (this.replace === 'target' && contentType.startsWith('text/html')) {
469
+ if (this.replace === 'target') {
451
470
  // const target = this.getAttribute('target');
452
471
  const target = this._getProperty('target');
453
472
  const targetNode = document.querySelector(target);
454
473
  if (targetNode) {
455
- 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
+ }
456
485
  } else {
457
486
  Fore.dispatch(this, 'submit-error', {
458
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
+ }
@@ -20,6 +20,7 @@ class FxSwitch extends FxContainer {
20
20
  this.formerCase = {};
21
21
  this.selectedCase = null;
22
22
  this.cases = null;
23
+ this.visitedResetted = false;
23
24
  }
24
25
 
25
26
  connectedCallback() {
@@ -74,10 +75,13 @@ class FxSwitch extends FxContainer {
74
75
  }
75
76
 
76
77
  _resetVisited() {
78
+ if(this.visitedResetted) return;
79
+
77
80
  const visited = this.selectedCase.querySelectorAll('.visited');
78
81
  Array.from(visited).forEach(v => {
79
82
  v.classList.remove('visited');
80
83
  });
84
+ this.visitedResetted = true;
81
85
  }
82
86
 
83
87
  /**