@jinntec/fore 2.7.1 → 2.7.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jinntec/fore",
3
- "version": "2.7.1",
3
+ "version": "2.7.2",
4
4
  "description": "Fore - declarative user interfaces in plain HTML",
5
5
  "module": "./index.js",
6
6
  "publishConfig": {
@@ -160,11 +160,11 @@ export default class ForeElementMixin extends HTMLElement {
160
160
  } else {
161
161
  // this.nodeset = fx.evaluateXPathToFirstNode(this.ref, inscopeContext, null, {namespaceResolver: this.namespaceResolver});
162
162
  if (!inscopeContext) return;
163
- const { nodeType } = inscopeContext;
164
- if (nodeType && !XPathUtil.isAbsolutePath(this.ref)) {
165
- this.nodeset = evaluateXPathToFirstNode(this.ref, inscopeContext, this);
163
+ if (this.nodeName === 'FX-REPEAT') {
164
+ // Repeats are special: they have multiple nodes in their nodeset
165
+ this.nodeset = evaluateXPath(this.ref, inscopeContext, this);
166
166
  } else {
167
- [this.nodeset] = evaluateXPath(this.ref, inscopeContext, this);
167
+ this.nodeset = evaluateXPath(this.ref, inscopeContext, this)[0] || null;
168
168
  }
169
169
  }
170
170
  // console.log('UiElement evaluated to nodeset: ', this.nodeset);
package/src/fx-fore.js CHANGED
@@ -443,7 +443,7 @@ export class FxFore extends HTMLElement {
443
443
  this.model = modelElement;
444
444
 
445
445
  this.style.visibility = 'hidden';
446
- console.time('init');
446
+ // console.time('init');
447
447
  this.strict = !!this.hasAttribute('strict');
448
448
  /*
449
449
  document.re('ready', (e) =>{
@@ -545,10 +545,12 @@ export class FxFore extends HTMLElement {
545
545
  'value-changed',
546
546
  () => {
547
547
  this.dirtyState = dirtyStates.DIRTY;
548
+ this.classList.toggle('fx-modified')
548
549
  },
549
550
  { once: true },
550
551
  );
551
552
  this.dirtyState = dirtyStates.CLEAN;
553
+ this.classList.remove('fx-modified');
552
554
  }
553
555
 
554
556
  /**
@@ -819,7 +821,7 @@ export class FxFore extends HTMLElement {
819
821
  }
820
822
 
821
823
  _processTemplateExpressions() {
822
- console.log('processing template expressions ', this.storedTemplateExpressionByNode);
824
+ // console.log('processing template expressions ', this.storedTemplateExpressionByNode);
823
825
  for (const node of Array.from(this.storedTemplateExpressionByNode.keys())) {
824
826
  if (node.nodeType === Node.ATTRIBUTE_NODE) {
825
827
  // Attribute nodes are not contained by the document, but their owner elements are!
@@ -931,8 +933,6 @@ export class FxFore extends HTMLElement {
931
933
  * @private
932
934
  */
933
935
  _handleModelConstructDone() {
934
- this.markAsClean();
935
-
936
936
  if (this.showConfirmation) {
937
937
  window.addEventListener('beforeunload', event => {
938
938
  if (this.dirtyState === dirtyStates.DIRTY) {
@@ -1096,6 +1096,7 @@ export class FxFore extends HTMLElement {
1096
1096
  // console.log('### modelItems: ', this.getModel().modelItems);
1097
1097
  Fore.dispatch(this, 'ready', {});
1098
1098
  // console.log('dataChanged', FxModel.dataChanged);
1099
+ this.markAsClean();
1099
1100
 
1100
1101
  this.addEventListener('dragstart', this._handleDragStart);
1101
1102
  // this.addEventListener('dragend', this._handleDragEnd);
@@ -1218,7 +1219,7 @@ export class FxFore extends HTMLElement {
1218
1219
  */
1219
1220
  initData(root = this) {
1220
1221
  // const created = new Promise(resolve => {
1221
- console.log('INIT');
1222
+ // console.log('INIT');
1222
1223
  // const boundControls = Array.from(root.querySelectorAll('[ref]:not(fx-model *),fx-repeatitem'));
1223
1224
 
1224
1225
  /**
@@ -1243,11 +1244,11 @@ export class FxFore extends HTMLElement {
1243
1244
  // Repeat items are dumb. They do not respond to evalInContext
1244
1245
  bound.evalInContext();
1245
1246
  }
1246
- if (bound.nodeset !== null && !(Array.isArray(bound.nodeset) && bound.nodeset.length > 0)) {
1247
- console.log('Node exists', bound.nodeset);
1247
+ if (bound.nodeset !== null && !(Array.isArray(bound.nodeset) && bound.nodeset.length === 0)) {
1248
+ // console.log('Node exists', bound.nodeset);
1248
1249
  continue;
1249
1250
  }
1250
- console.log('Node does not exists', bound.ref);
1251
+ // console.log('Node does not exists', bound.ref);
1251
1252
 
1252
1253
  // We need to create that node!
1253
1254
  const previousControl = boundControls[i - 1];
@@ -1256,8 +1257,8 @@ export class FxFore extends HTMLElement {
1256
1257
  // First: parent
1257
1258
  if (previousControl && previousControl.contains(bound)) {
1258
1259
  // Parent is here.
1259
- console.log('insert into', bound, previousControl);
1260
- console.log('insert into nodeset', bound.nodeset);
1260
+ // console.log('insert into', bound, previousControl);
1261
+ // console.log('insert into nodeset', bound.nodeset);
1261
1262
  /**
1262
1263
  * @type {ParentNode}
1263
1264
  */
@@ -1288,7 +1289,10 @@ export class FxFore extends HTMLElement {
1288
1289
  }
1289
1290
  }
1290
1291
  bound.evalInContext();
1292
+ if (bound.nodeName !== 'FX-REPEAT') {
1293
+ // Do not try to get a bind for a nodeSET of a repeat. there are multiple.
1291
1294
  bound.getModelItem().bind?.evalInContext();
1295
+ }
1292
1296
 
1293
1297
  // console.log('CREATED child', newElement);
1294
1298
  // console.log('new control evaluated to ', control.nodeset);
@@ -1301,24 +1305,19 @@ export class FxFore extends HTMLElement {
1301
1305
  let ourParent = XPathUtil.getParentBindingElement(bound);
1302
1306
  // console.log('ourParent', ourParent);
1303
1307
  let siblingControl = null;
1304
- /*
1305
- for (let j = i - 1; j >= 0; --j) {
1306
- const potentialSibling = boundControls[j];
1307
- if (XPathUtil.getParentBindingElement(potentialSibling) === ourParent) {
1308
- siblingControl = potentialSibling;
1309
- break; // Exit once the sibling is found
1310
- }
1311
- }
1312
- */
1308
+
1313
1309
  for (let j = i - 1; j > 0; --j) {
1314
1310
  const siblingOrDescendant = boundControls[j];
1311
+ if (siblingOrDescendant.nodeset && !('nodeType' in siblingOrDescendant.nodeset)) {
1312
+ continue;
1313
+ }
1315
1314
  if (XPathUtil.getParentBindingElement(siblingOrDescendant) === ourParent) {
1316
1315
  siblingControl = siblingOrDescendant;
1317
1316
  break;
1318
1317
  }
1319
1318
  }
1320
1319
  if (!siblingControl) {
1321
- console.log('No sibling found for', bound);
1320
+ // console.log('No sibling found for', bound);
1322
1321
  }
1323
1322
  // console.log('sibling', siblingControl);
1324
1323
  // todo: review: should this not just be inscopeContext?
@@ -1335,6 +1334,11 @@ export class FxFore extends HTMLElement {
1335
1334
  const ref = bound.ref;
1336
1335
 
1337
1336
  const newNode = this._createNodes(ref, parentNodeset);
1337
+ if (!newNode) {
1338
+ // We could not make the node for some reason. Maybe it's something like `instance('XXX')`?
1339
+ continue;
1340
+ }
1341
+
1338
1342
  if (newNode.nodeType === Node.ATTRIBUTE_NODE) {
1339
1343
  parentNodeset.setAttributeNode(newNode);
1340
1344
  } else {
@@ -1403,7 +1407,7 @@ export class FxFore extends HTMLElement {
1403
1407
  _handleDragStart(event) {
1404
1408
  const draggedItem = event.target.closest('[draggable="true"]');
1405
1409
  this.originalDraggedItem = draggedItem;
1406
- console.log('DRAG START', this);
1410
+ // console.log('DRAG START', this);
1407
1411
  if (draggedItem.getAttribute('drop-action') === 'copy') {
1408
1412
  event.dataTransfer.dropEffect = 'copy';
1409
1413
  event.dataTransfer.effectAllowed = 'copy';
@@ -1418,7 +1422,7 @@ export class FxFore extends HTMLElement {
1418
1422
  }
1419
1423
 
1420
1424
  _handleDrop(event) {
1421
- console.log('DROP ON BODY', this);
1425
+ // console.log('DROP ON BODY', this);
1422
1426
  if (!this.draggedItem) {
1423
1427
  return;
1424
1428
  }
@@ -250,21 +250,17 @@ export class FxSubmission extends ForeElementMixin {
250
250
  return;
251
251
  }
252
252
 
253
- const contentType = response.headers
254
- .get('content-type')
255
- .split(';')[0]
256
- .trim()
257
- .toLowerCase();
258
- if (contentType.startsWith('text/')) {
253
+ const contentType = response.headers.get('content-type').split(';')[0].trim().toLowerCase();
254
+ if (contentType.endsWith('/xml') || contentType.endsWith('+xml')) {
255
+ const text = await response.text();
256
+ const xml = new DOMParser().parseFromString(text, 'application/xml');
257
+ this._handleResponse(xml, resolvedUrl, contentType);
258
+ } else if (contentType.startsWith('text/')) {
259
259
  const text = await response.text();
260
260
  this._handleResponse(text, resolvedUrl, contentType);
261
261
  } else if (contentType.endsWith('/json') || contentType.endsWith('+json')) {
262
262
  const json = await response.json();
263
263
  this._handleResponse(json, resolvedUrl, contentType);
264
- } else if (contentType.endsWith('/xml') || contentType.endsWith('+xml')) {
265
- const text = await response.text();
266
- const xml = new DOMParser().parseFromString(text, 'application/xml');
267
- this._handleResponse(xml, resolvedUrl, contentType);
268
264
  } else {
269
265
  const blob = await response.blob();
270
266
  this._handleResponse(blob, resolvedUrl, contentType);
@@ -3,11 +3,9 @@ import {
3
3
  evaluateXPath,
4
4
  evaluateXPathToString,
5
5
  evaluateXPathToFirstNode,
6
- evaluateXPathToBoolean,
7
6
  } from '../xpath-evaluation.js';
8
7
  import getInScopeContext from '../getInScopeContext.js';
9
8
  import { Fore } from '../fore.js';
10
- import { ModelItem } from '../modelitem.js';
11
9
  import { debounce } from '../events.js';
12
10
  import { FxModel } from '../fx-model.js';
13
11
  import { DependencyNotifyingDomFacade } from '../DependencyNotifyingDomFacade';
@@ -42,6 +40,11 @@ export default class FxControl extends XfAbstractControl {
42
40
  this.inited = false;
43
41
  this.nodeset = null;
44
42
  this.attachShadow({ mode: 'open' });
43
+
44
+ /**
45
+ * Flag that is raised while refreshing, to ignore any updates from the widget inside of us
46
+ */
47
+ this._isRefreshing = false;
45
48
  }
46
49
 
47
50
  static get properties() {
@@ -64,6 +67,13 @@ export default class FxControl extends XfAbstractControl {
64
67
  // We have multiple! Just return that as space-separated for now
65
68
  return [...this.widget.selectedOptions].map(option => option.value).join(' ');
66
69
  }
70
+ if (this.getAttribute('as') === 'xml') {
71
+ // We are setting serialized XML here, so when roundtripping, parse it
72
+ const value = this.widget[this.valueProp];
73
+ const parser = new DOMParser();
74
+ const doc = parser.parseFromString(value, 'application/xml');
75
+ return doc.documentElement;
76
+ }
67
77
  return this.widget[this.valueProp];
68
78
  }
69
79
 
@@ -174,6 +184,10 @@ export default class FxControl extends XfAbstractControl {
174
184
  );
175
185
  } else {
176
186
  listenOn.addEventListener(this.updateEvent, event => {
187
+ if (this._isRefreshing) {
188
+ // We are refreshing. No use in updating
189
+ return;
190
+ }
177
191
  this.setValue(this._getValueOfWidget());
178
192
  });
179
193
  listenOn.addEventListener(
@@ -210,7 +224,7 @@ export default class FxControl extends XfAbstractControl {
210
224
  * activates a control that uses 'on-demand' attribute
211
225
  */
212
226
  activate() {
213
- console.log('fx-control.activate() called');
227
+ // console.log('fx-control.activate() called');
214
228
  this.removeAttribute('on-demand');
215
229
  this.style.display = '';
216
230
  this.refresh(true);
@@ -241,7 +255,7 @@ export default class FxControl extends XfAbstractControl {
241
255
  * @param val the new value to be set
242
256
  */
243
257
  setValue(val) {
244
- console.log('Control.setValue', val, 'on', this);
258
+ // console.log('Control.setValue', val, 'on', this);
245
259
  const modelitem = this.getModelItem();
246
260
 
247
261
  if (this.getAttribute('class')) {
@@ -257,10 +271,9 @@ export default class FxControl extends XfAbstractControl {
257
271
 
258
272
  if (this.getAttribute('as') === 'node') {
259
273
  const replace = this.shadowRoot.getElementById('replace');
260
- const widgetValue = this.getWidget()[this.valueProp];
261
- replace.replace(this.nodeset, widgetValue);
262
- if (modelitem && widgetValue && widgetValue !== modelitem.value) {
263
- modelitem.value = widgetValue;
274
+ replace.replace(this.nodeset, val);
275
+ if (modelitem && val && val !== modelitem.value) {
276
+ modelitem.value = val;
264
277
  FxModel.dataChanged = true;
265
278
  replace.actionPerformed();
266
279
  }
@@ -398,11 +411,14 @@ export default class FxControl extends XfAbstractControl {
398
411
  if (this.hasAttribute('as')) {
399
412
  const as = this.getAttribute('as');
400
413
 
401
- // ### when there's an `as=text` attribute serialize nodeset to prettified string
402
- if (as === 'text') {
414
+ // ### when there's an `as="xml"` attribute serialize nodeset to prettified string
415
+ if (as === 'xml') {
403
416
  const serializer = new XMLSerializer();
404
- const pretty = Fore.prettifyXml(serializer.serializeToString(this.nodeset));
405
- widget.value = pretty;
417
+ const pretty = serializer.serializeToString(this.nodeset);
418
+ if (widget[this.valueProp] === pretty) {
419
+ return;
420
+ }
421
+ widget[this.valueProp] = pretty;
406
422
  }
407
423
  if (as === 'node' && this.nodeset !== widget.value) {
408
424
  // const oldVal = this.nodeset.innerHTML;
@@ -547,15 +563,20 @@ export default class FxControl extends XfAbstractControl {
547
563
  }
548
564
 
549
565
  async refresh(force = false) {
550
- console.log('🔄 fx-control refresh', this);
551
- super.refresh(force);
552
- // console.log('refresh template', this.template);
553
- // const {widget} = this;
554
-
555
- // ### if we find a ref on control we have a 'select' control of some kind
556
- const widget = this.getWidget();
557
- this._handleBoundWidget(widget, force);
558
- this._handleDataAttributeBinding();
566
+ try {
567
+ this._isRefreshing = true;
568
+ // console.log('🔄 fx-control refresh', this);
569
+ super.refresh(force);
570
+ // console.log('refresh template', this.template);
571
+ // const {widget} = this;
572
+
573
+ // ### if we find a ref on control we have a 'select' control of some kind
574
+ const widget = this.getWidget();
575
+ this._handleBoundWidget(widget, force);
576
+ this._handleDataAttributeBinding();
577
+ } finally {
578
+ this._isRefreshing = false;
579
+ }
559
580
  Fore.refreshChildren(this, force);
560
581
  }
561
582
 
@@ -2,7 +2,6 @@ import { Fore } from '../fore.js';
2
2
  import XfAbstractControl from './abstract-control.js';
3
3
  import { evaluateXPath, evaluateXPathToStrings } from '../xpath-evaluation.js';
4
4
  import getInScopeContext from '../getInScopeContext.js';
5
- // import {markdown} from '../drawdown.js';
6
5
 
7
6
  /**
8
7
  * todo: review placing of value. should probably work with value attribute and not allow slotted content.
@@ -46,7 +45,7 @@ export class FxOutput extends XfAbstractControl {
46
45
 
47
46
  const outputHtml = `
48
47
  <slot name="label"></slot>
49
-
48
+
50
49
  <span id="value">
51
50
  <slot name="default"></slot>
52
51
  </span>
@@ -632,11 +632,13 @@ export class FxRepeat extends withDraggability(UIElement, false) {
632
632
 
633
633
  if (this.getOwnerForm().createNodes) {
634
634
  this.getOwnerForm().initData(repeatItem);
635
- const repeatItemClone = repeatItem.nodeset.cloneNode(true);
636
- this.clearTextValues(repeatItemClone);
637
-
638
- // this.createdNodeset = repeatItem.nodeset.cloneNode(true);
639
- this.createdNodeset = repeatItemClone;
635
+ if (repeatItem.nodeset.nodeType) {
636
+ // Do not try to d things with repeats that do not reason over nodes
637
+ const repeatItemClone = repeatItem.nodeset.cloneNode(true);
638
+ this.clearTextValues(repeatItemClone);
639
+ // this.createdNodeset = repeatItem.nodeset.cloneNode(true);
640
+ this.createdNodeset = repeatItemClone;
641
+ }
640
642
  // console.log('createdNodeset', this.createdNodeset)
641
643
  }
642
644
 
@@ -13,7 +13,6 @@ import {
13
13
 
14
14
  import { XPathUtil } from './xpath-util.js';
15
15
  import { prettifyXml } from './functions/common-function.js';
16
- import * as fx from 'fontoxpath';
17
16
 
18
17
  const XFORMS_NAMESPACE_URI = 'http://www.w3.org/2002/xforms';
19
18
 
@@ -489,6 +488,7 @@ export function evaluateXPath(xpath, contextNode, formElement, variables = {}, o
489
488
  { ...variablesInScope, ...variables },
490
489
  fxEvaluateXPath.ALL_RESULTS_TYPE,
491
490
  {
491
+ xmlSerializer: new XMLSerializer(),
492
492
  debug: true,
493
493
  currentContext: { formElement, variables },
494
494
  moduleImports: {
@@ -553,6 +553,7 @@ export function evaluateXPathToFirstNode(xpath, contextNode, formElement) {
553
553
  currentContext: { formElement },
554
554
  functionNameResolver,
555
555
  namespaceResolver,
556
+ xmlSerializer: new XMLSerializer(),
556
557
  });
557
558
  // console.log('evaluateXPathToFirstNode',xpath, result);
558
559
  return result;
@@ -570,6 +571,7 @@ export function evaluateXPathToFirstNode(xpath, contextNode, formElement) {
570
571
  }),
571
572
  );
572
573
  }
574
+ return null;
573
575
  }
574
576
 
575
577
  /**
@@ -592,6 +594,7 @@ export function evaluateXPathToNodes(xpath, contextNode, formElement) {
592
594
  xf: XFORMS_NAMESPACE_URI,
593
595
  },
594
596
  namespaceResolver,
597
+ xmlSerializer: new XMLSerializer(),
595
598
  });
596
599
  // console.log('evaluateXPathToNodes',xpath, result);
597
600
  return result;
@@ -631,6 +634,7 @@ export function evaluateXPathToBoolean(xpath, contextNode, formElement) {
631
634
  xf: XFORMS_NAMESPACE_URI,
632
635
  },
633
636
  namespaceResolver,
637
+ xmlSerializer: new XMLSerializer(),
634
638
  });
635
639
  } catch (e) {
636
640
  formElement.dispatchEvent(
@@ -671,6 +675,7 @@ export function evaluateXPathToString(xpath, contextNode, formElement, domFacade
671
675
  xf: XFORMS_NAMESPACE_URI,
672
676
  },
673
677
  namespaceResolver,
678
+ xmlSerializer: new XMLSerializer(),
674
679
  });
675
680
  } catch (e) {
676
681
  formElement.dispatchEvent(
@@ -714,6 +719,7 @@ export function evaluateXPathToStrings(xpath, contextNode, formElement, domFacad
714
719
  xf: XFORMS_NAMESPACE_URI,
715
720
  },
716
721
  namespaceResolver,
722
+ xmlSerializer: new XMLSerializer(),
717
723
  },
718
724
  );
719
725
  } catch (e) {
@@ -755,6 +761,7 @@ export function evaluateXPathToNumber(xpath, contextNode, formElement, domFacade
755
761
  xf: XFORMS_NAMESPACE_URI,
756
762
  },
757
763
  namespaceResolver,
764
+ xmlSerializer: new XMLSerializer(),
758
765
  });
759
766
  } catch (e) {
760
767
  formElement.dispatchEvent(