@jinntec/fore 1.7.2 → 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/index.js CHANGED
@@ -56,6 +56,7 @@ import './src/actions/fx-reload.js';
56
56
  import './src/actions/fx-reset.js';
57
57
  import './src/actions/fx-load.js';
58
58
  import './src/actions/fx-toggleboolean.js';
59
+ import './src/actions/fx-call.js';
59
60
 
60
61
  import './src/functions/fx-function.js';
61
62
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jinntec/fore",
3
- "version": "1.7.2",
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
- "postinstall": "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",
@@ -0,0 +1,74 @@
1
+ import '../fx-model.js';
2
+ import {AbstractAction} from './abstract-action.js';
3
+ import {evaluateXPath, evaluateXPathToString} from "../xpath-evaluation.js";
4
+ import {Fore} from "../fore";
5
+ import getInScopeContext from "../getInScopeContext";
6
+
7
+ /**
8
+ * `fx-call`
9
+ *
10
+ * @customElement
11
+ */
12
+ export default class FxCall extends AbstractAction {
13
+ static get properties() {
14
+ return {
15
+ ...super.properties,
16
+ action: {
17
+ type: String,
18
+ },
19
+ fn: {
20
+ type: String,
21
+ },
22
+ };
23
+ }
24
+
25
+ constructor() {
26
+ super();
27
+ this.action = '';
28
+ this.fn = '';
29
+ }
30
+
31
+ connectedCallback() {
32
+ if (super.connectedCallback) {
33
+ super.connectedCallback();
34
+ }
35
+ if (this.hasAttribute('action')) {
36
+ this.action = this.getAttribute('action');
37
+ } else if(this.hasAttribute('function')){
38
+ this.fn = this.getAttribute('function');
39
+
40
+ }else{
41
+ throw new Error('fx-call must specify an "action" or "function" attribute');
42
+ }
43
+ }
44
+
45
+ async perform() {
46
+ super.perform();
47
+ if(this.action){
48
+ await this._callAction();
49
+ }
50
+ // execute function
51
+ if(this.fn){
52
+ this._callFunction();
53
+ }
54
+ }
55
+
56
+ /**
57
+ * find action and execute it
58
+ */
59
+ async _callAction(){
60
+ const action = document.querySelector(`#${this.action}`);
61
+ if(action){
62
+ await action.perform();
63
+ }
64
+ }
65
+
66
+ _callFunction(){
67
+ const inscope = getInScopeContext(this, 'instance()',this);
68
+ evaluateXPath(this.fn, inscope, this);
69
+ }
70
+ }
71
+
72
+ if (!customElements.get('fx-call')) {
73
+ window.customElements.define('fx-call', FxCall);
74
+ }
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);
@@ -20,8 +20,7 @@ async function handleResponse(response) {
20
20
  );
21
21
  }
22
22
  if (
23
- responseContentType.startsWith('text/plain') ||
24
- responseContentType.startsWith('text/markdown')
23
+ responseContentType.startsWith('text/')
25
24
  ) {
26
25
  // console.log("********** inside res plain *********");
27
26
  return response.text();
@@ -53,6 +52,7 @@ export class FxInstance extends HTMLElement {
53
52
  this.attachShadow({ mode: 'open' });
54
53
  this.originalInstance = null;
55
54
  this.partialInstance = null;
55
+ this.credentials = '';
56
56
  }
57
57
 
58
58
  connectedCallback() {
@@ -67,6 +67,13 @@ export class FxInstance extends HTMLElement {
67
67
  this.id = 'default';
68
68
  }
69
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
+
70
77
  if (this.hasAttribute('type')) {
71
78
  this.type = this.getAttribute('type');
72
79
  } else {
@@ -207,6 +214,10 @@ export class FxInstance extends HTMLElement {
207
214
  this.instanceData = {};
208
215
  this.originalInstance = [...this.instanceData];
209
216
  }
217
+ if(this.type === 'text'){
218
+ this.instanceData = '';
219
+ this.originalInstance = '';
220
+ }
210
221
  }
211
222
 
212
223
  async _loadData() {
@@ -240,10 +251,8 @@ export class FxInstance extends HTMLElement {
240
251
  try {
241
252
  const response = await fetch(url, {
242
253
  method: 'GET',
243
- /*
254
+ credentials: this.credentials,
244
255
  mode: 'cors',
245
- credentials: 'include',
246
- */
247
256
  headers: {
248
257
  'Content-Type': contentType,
249
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
 
@@ -242,6 +248,9 @@ export class FxSubmission extends foreElementMixin(HTMLElement) {
242
248
  if (serialized && instance.getAttribute('type') === 'json') {
243
249
  data = JSON.parse(serialized);
244
250
  }
251
+ if (serialized && instance.getAttribute('type') === 'text') {
252
+ data = serialized;
253
+ }
245
254
  return data;
246
255
  }
247
256
 
@@ -263,6 +272,9 @@ export class FxSubmission extends foreElementMixin(HTMLElement) {
263
272
  // console.warn('JSON serialization is not yet supported')
264
273
  return JSON.stringify(relevantNodes);
265
274
  }
275
+ if (instanceType === 'text') {
276
+ return relevantNodes;
277
+ }
266
278
  throw new Error('unknown instance type ', instanceType);
267
279
  }
268
280
 
@@ -393,11 +405,26 @@ export class FxSubmission extends foreElementMixin(HTMLElement) {
393
405
  }
394
406
  }
395
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
+ }
396
416
  if (this.replace === 'all') {
397
- document.open();
398
- document.write(data);
399
- document.close();
400
- window.location.href = resolvedUrl;
417
+ const target = this._getProperty('target');
418
+ if(target && target === '_blank'){
419
+ const win = window.open("", "_blank");
420
+ win.document.write(`<pre>${data}</pre>`);
421
+ win.document.close();
422
+ }else{
423
+ document.open();
424
+ document.write(data);
425
+ document.close();
426
+ window.location.href = resolvedUrl;
427
+ }
401
428
  // document.getElementsByTagName('html')[0].innerHTML = data;
402
429
  }
403
430
  if (this.replace === 'target' && contentType.startsWith('text/html')) {
@@ -415,51 +442,22 @@ export class FxSubmission extends foreElementMixin(HTMLElement) {
415
442
  }
416
443
  }
417
444
 
418
-
419
- /*
420
- _mergeXML(xml1, xml2) {
421
- const parser = new DOMParser();
422
- const serializer = new XMLSerializer();
423
-
424
- // const doc1 = parser.parseFromString(xml1, 'text/xml');
425
- // const doc2 = parser.parseFromString(xml2, 'text/xml');
426
-
427
- this.mergeNodes(xml1.documentElement, xml2.documentElement);
428
-
429
- // return serializer.serializeToString(xml1);
430
- return xml1;
431
- }
432
- */
433
-
434
- /*
435
- _mergeNodes(node1, node2) {
436
- const childNodes1 = node1.childNodes;
437
- const childNodes2 = node2.childNodes;
438
-
439
- for (let i = 0; i < childNodes2.length; i++) {
440
- const child2 = childNodes2[i];
441
- let nodeMerged = false;
442
-
443
- if (child2.nodeType === 1) { // Element Node
444
- for (let j = 0; j < childNodes1.length; j++) {
445
- const child1 = childNodes1[j];
446
- if (child1.nodeType === 1 && child1.tagName === child2.tagName) {
447
- this._mergeNodes(child1, child2);
448
- nodeMerged = true;
449
- break;
450
- }
451
- }
452
- }
453
-
454
- if (!nodeMerged) {
455
- const clonedNode = child2.cloneNode(true);
456
- node1.appendChild(clonedNode);
457
- }
458
- }
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
+ */
459
458
  }
460
- */
461
459
 
462
- /*
460
+ /*
463
461
  mergeNodes(node1, node2) {
464
462
  // Overwrite attributes in node1 with values from node2
465
463
  for (const { name, value } of node2.attributes) {
@@ -490,97 +488,7 @@ export class FxSubmission extends foreElementMixin(HTMLElement) {
490
488
  });
491
489
  }
492
490
  */
493
- /**
494
- * select relevant nodes
495
- *
496
- * @returns {*}
497
- */
498
- /*
499
- selectRelevant(type) {
500
- console.log('selectRelevant' ,type)
501
- switch (type){
502
- case 'xml':
503
- return this._relevantXmlNodes();
504
- default:
505
- console.warn(`relevance selection not supported for type:${this.type}`);
506
- return this.nodeset;
507
- }
508
- }
509
- */
510
-
511
- // todo: support for 'empty'
512
- /*
513
- _relevantXmlNodes() {
514
- // ### no relevance selection - current nodeset is used 'as-is'
515
- if (this.nonrelevant === 'keep') {
516
- return this.nodeset;
517
- }
518
-
519
- // first check if nodeset of submission is relevant - otherwise bail out
520
- const mi = this.getModel().getModelItem(this.nodeset);
521
- if (mi && !mi.relevant) return null;
522
-
523
- const doc = new DOMParser().parseFromString('<data></data>', 'application/xml');
524
- const root = doc.firstElementChild;
525
-
526
- if (this.nodeset.children.length === 0 && this._isRelevant(this.nodeset)) {
527
- return this.nodeset;
528
- }
529
- return this._filterRelevant(this.nodeset, root);
530
- }
531
- */
532
-
533
- /*
534
- _filterRelevant(node, result) {
535
- const { childNodes } = node;
536
- Array.from(childNodes).forEach(n => {
537
- if (this._isRelevant(n)) {
538
- const clone = n.cloneNode(false);
539
- result.appendChild(clone);
540
- const { attributes } = n;
541
- if (attributes) {
542
- Array.from(attributes).forEach(attr => {
543
- if (this._isRelevant(attr)) {
544
- clone.setAttribute(attr.nodeName, attr.value);
545
- } else if (this.nonrelevant === 'empty') {
546
- clone.setAttribute(attr.nodeName, '');
547
- } else {
548
- clone.removeAttribute(attr.nodeName);
549
- }
550
- });
551
- }
552
- return this._filterRelevant(n, clone);
553
- }
554
- return null;
555
- });
556
- return result;
557
- }
558
- */
559
491
 
560
- /*
561
- _isRelevant(node) {
562
- const mi = this.getModel().getModelItem(node);
563
- if (!mi || mi.relevant) {
564
- return true;
565
- }
566
- return false;
567
- }
568
- */
569
-
570
- _handleError() {
571
- // this.dispatch('submit-error', {});
572
- Fore.dispatch(this, 'submit-error', {});
573
- /*
574
- console.log('ERRRORRRRR');
575
- this.dispatchEvent(
576
- new CustomEvent('submit-error', {
577
- composed: true,
578
- bubbles: true,
579
- detail: {},
580
- }),
581
- );
582
- */
583
- }
584
492
  }
585
493
 
586
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
  }