@jinntec/fore 1.8.0 → 1.9.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jinntec/fore",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "Fore - declarative user interfaces in plain HTML",
5
5
  "module": "./index.js",
6
6
  "publishConfig": {
@@ -34,6 +34,7 @@
34
34
  "fontoxpath": "^3.30.0"
35
35
  },
36
36
  "devDependencies": {
37
+ "@types/cypress": "^1.1.3",
37
38
  "@babel/plugin-proposal-class-properties": "^7.17.12",
38
39
  "@open-wc/building-rollup": "^2.0.1",
39
40
  "@open-wc/eslint-config": "^7.0.0",
@@ -47,6 +48,7 @@
47
48
  "@rollup/plugin-strip": "^2.1.0",
48
49
  "@skypack/package-check": "^0.2.2",
49
50
  "@webcomponents/webcomponentsjs": "^2.6.0",
51
+ "cypress": "^13.3.0",
50
52
  "deepmerge": "^4.2.2",
51
53
  "es-dev-server": "^2.1.0",
52
54
  "eslint": "^8.16.0",
@@ -82,7 +84,10 @@
82
84
  "start:build": "cd dist && es-dev-server --open",
83
85
  "build": "rimraf dist && rollup -c rollup.config.js",
84
86
  "start": "es-dev-server --app-index doc/index.html --node-resolve --watch --open",
85
- "install-demos": "cd demo && npm i"
87
+ "preversion": "npm run test",
88
+ "prepare": "npm run build",
89
+ "install-demos": "npm i && cd demo && npm i",
90
+ "start-cypress": "es-dev-server --app-index doc/index.html --node-resolve"
86
91
  },
87
92
  "keywords": [
88
93
  "Fore",
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
+ * ignore certain nodes for template expression search
34
+ */
35
+ ignoreExpressions:{
36
+ type: String
37
+ },
32
38
  /**
33
39
  * merge-partial
34
40
  */
@@ -173,8 +179,8 @@ export class FxFore extends HTMLElement {
173
179
 
174
180
  <jinn-toast id="message" gravity="bottom" position="left"></jinn-toast>
175
181
  <jinn-toast id="sticky" gravity="bottom" position="left" duration="-1" close="true" data-class="sticky-message"></jinn-toast>
176
- <jinn-toast id="error" text="error" duration="-1" data-class="error" close="true" position="left" gravity="bottom" escape-markup="false"></jinn-toast>
177
- <jinn-toast id="warn" text="warning" duration="-1" data-class="warning" close="true" position="right" gravity="bottom"></jinn-toast>
182
+ <jinn-toast id="error" text="error" duration="-1" data-class="error" close="true" position="right" gravity="top" escape-markup="false"></jinn-toast>
183
+ <jinn-toast id="warn" text="warning" duration="5000" data-class="warning" position="left" gravity="top"></jinn-toast>
178
184
  <slot id="default"></slot>
179
185
  <slot name="messages"></slot>
180
186
  <div id="modalMessage" class="overlay">
@@ -225,6 +231,7 @@ export class FxFore extends HTMLElement {
225
231
  // e.stopImmediatePropagation();
226
232
  },true);
227
233
  */
234
+ this.ignoreExpressions = this.hasAttribute('ignore-expressions') ? this.getAttribute('ignore-expressions'): null;
228
235
 
229
236
  this.lazyRefresh = this.hasAttribute('refresh-on-view');
230
237
  if (this.lazyRefresh) {
@@ -254,6 +261,10 @@ export class FxFore extends HTMLElement {
254
261
  return;
255
262
  }
256
263
 
264
+ if(this.ignoreExpressions){
265
+ this.ignoredNodes = Array.from(this.querySelectorAll(this.ignoreExpressions));
266
+ }
267
+
257
268
  const children = event.target.assignedElements();
258
269
  let modelElement = children.find(
259
270
  modelElem => modelElem.nodeName.toUpperCase() === 'FX-MODEL',
@@ -547,7 +558,9 @@ export class FxFore extends HTMLElement {
547
558
  const expr = this._getTemplateExpression(node);
548
559
 
549
560
  // console.log('storedTemplateExpressionByNode', this.storedTemplateExpressionByNode);
550
- this.storedTemplateExpressionByNode.set(node, expr);
561
+ if(expr){
562
+ this.storedTemplateExpressionByNode.set(node, expr);
563
+ }
551
564
  });
552
565
  // console.log('stored template expressions ', this.storedTemplateExpressionByNode);
553
566
 
@@ -629,6 +642,13 @@ export class FxFore extends HTMLElement {
629
642
 
630
643
  // eslint-disable-next-line class-methods-use-this
631
644
  _getTemplateExpression(node) {
645
+ if(this.ignoredNodes){
646
+ if(node.nodeType === Node.ATTRIBUTE_NODE){
647
+ node = node.ownerElement;
648
+ }
649
+ const found = this.ignoredNodes.find( (n) => n.contains(node));
650
+ if(found) return null;
651
+ }
632
652
  if (node.nodeType === Node.ATTRIBUTE_NODE) {
633
653
  return node.value;
634
654
  }
@@ -920,9 +940,9 @@ export class FxFore extends HTMLElement {
920
940
  this.shadowRoot.getElementById('messageContent').innerText = msg;
921
941
  // this.shadowRoot.getElementById('modalMessage').open();
922
942
  this.shadowRoot.getElementById('modalMessage').classList.add('show');
923
- } else if (level === 'sticky') {
943
+ } else if (level === 'sticky' || level === 'error' || level === 'warn') {
924
944
  // const notification = this.$.modeless;
925
- this.shadowRoot.querySelector('#sticky').showToast(msg);
945
+ this.shadowRoot.querySelector(`#${level}`).showToast(msg);
926
946
  } else {
927
947
  const toast = this.shadowRoot.querySelector('#message');
928
948
  toast.showToast(msg);
@@ -52,6 +52,7 @@ export class FxInstance extends HTMLElement {
52
52
  this.attachShadow({ mode: 'open' });
53
53
  this.originalInstance = null;
54
54
  this.partialInstance = null;
55
+ this.credentials = '';
55
56
  }
56
57
 
57
58
  connectedCallback() {
@@ -66,6 +67,13 @@ export class FxInstance extends HTMLElement {
66
67
  this.id = 'default';
67
68
  }
68
69
 
70
+ this.credentials = this.hasAttribute('credentials')
71
+ ? this.getAttribute('credentials')
72
+ : 'same-origin';
73
+ if (!['same-origin', 'include', 'omit'].includes(this.credentials)) {
74
+ console.error(`fx-submission: the value of credentials is not valid. Expected 'same-origin', 'include' or 'omit' but got '${this.credentials}'`, this);
75
+ }
76
+
69
77
  if (this.hasAttribute('type')) {
70
78
  this.type = this.getAttribute('type');
71
79
  } else {
@@ -243,10 +251,8 @@ export class FxInstance extends HTMLElement {
243
251
  try {
244
252
  const response = await fetch(url, {
245
253
  method: 'GET',
246
- /*
254
+ credentials: this.credentials,
247
255
  mode: 'cors',
248
- credentials: 'include',
249
- */
250
256
  headers: {
251
257
  'Content-Type': contentType,
252
258
  },
@@ -12,6 +12,7 @@ export class FxSubmission extends foreElementMixin(HTMLElement) {
12
12
  constructor() {
13
13
  super();
14
14
  this.attachShadow({mode: 'open'});
15
+ this.credentials = '';
15
16
  this.parameters = new Map();
16
17
  }
17
18
 
@@ -56,6 +57,12 @@ export class FxSubmission extends foreElementMixin(HTMLElement) {
56
57
  : 'application/xml';
57
58
 
58
59
  this.validate = this.getAttribute('validate') ? this.getAttribute('validate') : 'true';
60
+ this.credentials = this.hasAttribute('credentials')
61
+ ? this.getAttribute('credentials')
62
+ : 'same-origin';
63
+ if (!['same-origin', 'include', 'omit'].includes(this.credentials)) {
64
+ console.error(`fx-submission: the value of credentials is not valid. Expected 'same-origin', 'include' or 'omit' but got '${this.credentials}'`, this);
65
+ }
59
66
  this.shadowRoot.innerHTML = this.renderHTML();
60
67
  }
61
68
 
@@ -96,9 +103,8 @@ export class FxSubmission extends foreElementMixin(HTMLElement) {
96
103
  _getProperty(attrName){
97
104
  if(this.parameters.has(attrName)){
98
105
  return this.parameters.get(attrName);
99
- } else {
100
- return this.getAttribute(attrName);
101
106
  }
107
+ return this.getAttribute(attrName);
102
108
  }
103
109
 
104
110
  /**
@@ -191,10 +197,8 @@ export class FxSubmission extends foreElementMixin(HTMLElement) {
191
197
  try {
192
198
  const response = await fetch(resolvedUrl, {
193
199
  method: this.method,
194
- /*
195
- mode: 'cors',
196
- credentials: 'include',
197
- */
200
+ credentials: this.credentials,
201
+ mode:'cors',
198
202
  headers,
199
203
  body: serialized,
200
204
  });
@@ -207,9 +211,7 @@ export class FxSubmission extends foreElementMixin(HTMLElement) {
207
211
 
208
212
  const contentType = response.headers.get('content-type').toLowerCase();
209
213
  if (
210
- contentType.startsWith('text/plain') ||
211
- contentType.startsWith('text/html') ||
212
- contentType.startsWith('text/markdown')
214
+ contentType.startsWith('text/')
213
215
  ) {
214
216
  const text = await response.text();
215
217
  this._handleResponse(text, resolvedUrl,contentType);
@@ -231,6 +233,10 @@ export class FxSubmission extends foreElementMixin(HTMLElement) {
231
233
  Fore.dispatch(this, 'submit-error', {error: error.message});
232
234
  } finally {
233
235
  this.parameters.clear();
236
+ const download = document.querySelector('[download]');
237
+ if(download){
238
+ document.body.removeChild(download);
239
+ }
234
240
  }
235
241
  }
236
242
 
@@ -399,11 +405,19 @@ export class FxSubmission extends foreElementMixin(HTMLElement) {
399
405
  }
400
406
  }
401
407
 
408
+ if (this.replace === 'download') {
409
+ const target = this._getProperty('target');
410
+ const downloadLink = document.createElement('a');
411
+ downloadLink.setAttribute('download',target);
412
+ downloadLink.setAttribute('href',`data:${contentType},${data}`);
413
+ document.body.appendChild(downloadLink);
414
+ downloadLink.click();
415
+ }
402
416
  if (this.replace === 'all') {
403
417
  const target = this._getProperty('target');
404
418
  if(target && target === '_blank'){
405
419
  const win = window.open("", "_blank");
406
- win.document.write(data);
420
+ win.document.write(`<pre>${data}</pre>`);
407
421
  win.document.close();
408
422
  }else{
409
423
  document.open();
@@ -428,51 +442,22 @@ export class FxSubmission extends foreElementMixin(HTMLElement) {
428
442
  }
429
443
  }
430
444
 
431
-
432
- /*
433
- _mergeXML(xml1, xml2) {
434
- const parser = new DOMParser();
435
- const serializer = new XMLSerializer();
436
-
437
- // const doc1 = parser.parseFromString(xml1, 'text/xml');
438
- // const doc2 = parser.parseFromString(xml2, 'text/xml');
439
-
440
- this.mergeNodes(xml1.documentElement, xml2.documentElement);
441
-
442
- // return serializer.serializeToString(xml1);
443
- return xml1;
445
+ _handleError() {
446
+ // this.dispatch('submit-error', {});
447
+ Fore.dispatch(this, 'submit-error', {});
448
+ /*
449
+ console.log('ERRRORRRRR');
450
+ this.dispatchEvent(
451
+ new CustomEvent('submit-error', {
452
+ composed: true,
453
+ bubbles: true,
454
+ detail: {},
455
+ }),
456
+ );
457
+ */
444
458
  }
445
- */
446
459
 
447
- /*
448
- _mergeNodes(node1, node2) {
449
- const childNodes1 = node1.childNodes;
450
- const childNodes2 = node2.childNodes;
451
-
452
- for (let i = 0; i < childNodes2.length; i++) {
453
- const child2 = childNodes2[i];
454
- let nodeMerged = false;
455
-
456
- if (child2.nodeType === 1) { // Element Node
457
- for (let j = 0; j < childNodes1.length; j++) {
458
- const child1 = childNodes1[j];
459
- if (child1.nodeType === 1 && child1.tagName === child2.tagName) {
460
- this._mergeNodes(child1, child2);
461
- nodeMerged = true;
462
- break;
463
- }
464
- }
465
- }
466
-
467
- if (!nodeMerged) {
468
- const clonedNode = child2.cloneNode(true);
469
- node1.appendChild(clonedNode);
470
- }
471
- }
472
- }
473
- */
474
-
475
- /*
460
+ /*
476
461
  mergeNodes(node1, node2) {
477
462
  // Overwrite attributes in node1 with values from node2
478
463
  for (const { name, value } of node2.attributes) {
@@ -503,97 +488,7 @@ export class FxSubmission extends foreElementMixin(HTMLElement) {
503
488
  });
504
489
  }
505
490
  */
506
- /**
507
- * select relevant nodes
508
- *
509
- * @returns {*}
510
- */
511
- /*
512
- selectRelevant(type) {
513
- console.log('selectRelevant' ,type)
514
- switch (type){
515
- case 'xml':
516
- return this._relevantXmlNodes();
517
- default:
518
- console.warn(`relevance selection not supported for type:${this.type}`);
519
- return this.nodeset;
520
- }
521
- }
522
- */
523
491
 
524
- // todo: support for 'empty'
525
- /*
526
- _relevantXmlNodes() {
527
- // ### no relevance selection - current nodeset is used 'as-is'
528
- if (this.nonrelevant === 'keep') {
529
- return this.nodeset;
530
- }
531
-
532
- // first check if nodeset of submission is relevant - otherwise bail out
533
- const mi = this.getModel().getModelItem(this.nodeset);
534
- if (mi && !mi.relevant) return null;
535
-
536
- const doc = new DOMParser().parseFromString('<data></data>', 'application/xml');
537
- const root = doc.firstElementChild;
538
-
539
- if (this.nodeset.children.length === 0 && this._isRelevant(this.nodeset)) {
540
- return this.nodeset;
541
- }
542
- return this._filterRelevant(this.nodeset, root);
543
- }
544
- */
545
-
546
- /*
547
- _filterRelevant(node, result) {
548
- const { childNodes } = node;
549
- Array.from(childNodes).forEach(n => {
550
- if (this._isRelevant(n)) {
551
- const clone = n.cloneNode(false);
552
- result.appendChild(clone);
553
- const { attributes } = n;
554
- if (attributes) {
555
- Array.from(attributes).forEach(attr => {
556
- if (this._isRelevant(attr)) {
557
- clone.setAttribute(attr.nodeName, attr.value);
558
- } else if (this.nonrelevant === 'empty') {
559
- clone.setAttribute(attr.nodeName, '');
560
- } else {
561
- clone.removeAttribute(attr.nodeName);
562
- }
563
- });
564
- }
565
- return this._filterRelevant(n, clone);
566
- }
567
- return null;
568
- });
569
- return result;
570
- }
571
- */
572
-
573
- /*
574
- _isRelevant(node) {
575
- const mi = this.getModel().getModelItem(node);
576
- if (!mi || mi.relevant) {
577
- return true;
578
- }
579
- return false;
580
- }
581
- */
582
-
583
- _handleError() {
584
- // this.dispatch('submit-error', {});
585
- Fore.dispatch(this, 'submit-error', {});
586
- /*
587
- console.log('ERRRORRRRR');
588
- this.dispatchEvent(
589
- new CustomEvent('submit-error', {
590
- composed: true,
591
- bubbles: true,
592
- detail: {},
593
- }),
594
- );
595
- */
596
- }
597
492
  }
598
493
 
599
494
  if (!customElements.get('fx-submission')) {
@@ -43,6 +43,9 @@ export default class FxControl extends XfAbstractControl {
43
43
  static get properties() {
44
44
  return {
45
45
  ...XfAbstractControl.properties,
46
+ credentials:{
47
+ type: String
48
+ },
46
49
  initial: {
47
50
  type: Boolean
48
51
  }
@@ -74,6 +77,13 @@ export default class FxControl extends XfAbstractControl {
74
77
  }
75
78
  `;
76
79
 
80
+ this.credentials = this.hasAttribute('credentials')
81
+ ? this.getAttribute('credentials')
82
+ : 'same-origin';
83
+ if (!['same-origin', 'include', 'omit'].includes(this.credentials)) {
84
+ console.error(`fx-submission: the value of credentials is not valid. Expected 'same-origin', 'include' or 'omit' but got '${this.credentials}'`, this);
85
+ }
86
+
77
87
  this.shadowRoot.innerHTML = `
78
88
  <style>
79
89
  ${style}
@@ -369,7 +379,7 @@ export default class FxControl extends XfAbstractControl {
369
379
  }
370
380
 
371
381
  // ### when there's a url Fore is used as widget and will be loaded from external file
372
- if (this.url && !this.loaded) {
382
+ if (this.url && !this.loaded && this.modelItem.relevant) {
373
383
  // ### evaluate initial data if necessary
374
384
 
375
385
  if (this.initial) {
@@ -417,10 +427,8 @@ export default class FxControl extends XfAbstractControl {
417
427
  try {
418
428
  const response = await fetch(this.url, {
419
429
  method: 'GET',
420
- /*
421
- mode: 'cors',
422
- credentials: 'include',
423
- */
430
+ credentials: this.credentials,
431
+ mode: 'cors',
424
432
  headers: {
425
433
  'Content-Type': 'text/html',
426
434
  },
@@ -446,9 +454,12 @@ export default class FxControl extends XfAbstractControl {
446
454
  'model-construct-done',
447
455
  e => {
448
456
  const defaultInst = imported.querySelector('fx-instance');
449
- if (this.initialNode) {
457
+ if (this.initial) {
450
458
  const doc = new DOMParser().parseFromString('<data></data>', 'application/xml');
451
459
  // Note: Clone the input to prevent the inner fore from editing the outer node
460
+ // Also update the `initialNode` to make sure we have an up-to-date version
461
+ this.initialNode = evaluateXPathToFirstNode(this.initial, this.nodeset, this);
462
+
452
463
  doc.firstElementChild.appendChild(this.initialNode.cloneNode(true));
453
464
  defaultInst.setInstanceData(doc);
454
465
  }