@jinntec/fore 0.25.0 → 1.0.0-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.
Files changed (41) hide show
  1. package/README.md +75 -22
  2. package/dist/fore-all.js +11 -11
  3. package/dist/fore.js +1 -1
  4. package/index.js +5 -1
  5. package/package.json +17 -6
  6. package/resources/fore.css +121 -4
  7. package/resources/toastify.css +3 -1
  8. package/src/DependencyNotifyingDomFacade.js +9 -1
  9. package/src/ForeElementMixin.js +83 -12
  10. package/src/actions/abstract-action.js +101 -27
  11. package/src/actions/fx-action.js +4 -2
  12. package/src/actions/fx-append.js +21 -18
  13. package/src/actions/fx-confirm.js +22 -0
  14. package/src/actions/fx-dispatch.js +10 -2
  15. package/src/actions/fx-insert.js +35 -30
  16. package/src/actions/fx-message.js +7 -1
  17. package/src/actions/fx-send.js +1 -1
  18. package/src/actions/fx-setvalue.js +2 -9
  19. package/src/dep_graph.js +9 -0
  20. package/src/drawdown.js +172 -0
  21. package/src/fore.js +126 -18
  22. package/src/functions/fx-function.js +2 -2
  23. package/src/fx-bind.js +11 -7
  24. package/src/fx-fore.js +283 -67
  25. package/src/fx-header.js +20 -0
  26. package/src/fx-instance.js +54 -10
  27. package/src/fx-model.js +175 -38
  28. package/src/fx-submission.js +235 -53
  29. package/src/getInScopeContext.js +2 -3
  30. package/src/ui/abstract-control.js +23 -44
  31. package/src/ui/fx-alert.js +20 -19
  32. package/src/ui/fx-container.js +9 -4
  33. package/src/ui/fx-control.js +92 -37
  34. package/src/ui/fx-inspector.js +44 -0
  35. package/src/ui/fx-items.js +104 -20
  36. package/src/ui/fx-output.js +92 -3
  37. package/src/ui/fx-repeat.js +87 -80
  38. package/src/ui/fx-repeatitem.js +38 -48
  39. package/src/ui/fx-trigger.js +49 -27
  40. package/src/xpath-evaluation.js +533 -235
  41. package/src/xpath-util.js +50 -12
@@ -1,5 +1,7 @@
1
1
  import XfAbstractControl from './abstract-control.js';
2
- import { evaluateXPathToNodes, evaluateXPath, evaluateXPathToString } from '../xpath-evaluation.js';
2
+ import { evaluateXPath, evaluateXPathToString, evaluateXPathToNodes } from '../xpath-evaluation.js';
3
+ import getInScopeContext from '../getInScopeContext.js';
4
+ import { Fore } from '../fore.js';
3
5
 
4
6
  const WIDGETCLASS = 'widget';
5
7
 
@@ -12,7 +14,7 @@ const WIDGETCLASS = 'widget';
12
14
  * @customElement
13
15
  * @demo demo/index.html
14
16
  */
15
- class FxControl extends XfAbstractControl {
17
+ export default class FxControl extends XfAbstractControl {
16
18
  constructor() {
17
19
  super();
18
20
  this.inited = false;
@@ -54,11 +56,27 @@ class FxControl extends XfAbstractControl {
54
56
  `;
55
57
 
56
58
  this.widget = this.getWidget();
57
- console.log('widget ', this.widget);
59
+ // console.log('widget ', this.widget);
60
+
61
+ // ### convenience marker event
62
+ if (this.updateEvent === 'enter') {
63
+ this.widget.addEventListener('keyup', event => {
64
+ if (event.keyCode === 13) {
65
+ // Cancel the default action, if needed
66
+ event.preventDefault();
67
+ this.setValue(this.widget[this.valueProp]);
68
+ }
69
+ });
70
+ this.updateEvent = 'blur'; // needs to be registered too
71
+ }
58
72
  this.widget.addEventListener(this.updateEvent, () => {
59
- console.log('eventlistener ', this.updateEvent);
73
+ // console.log('eventlistener ', this.updateEvent);
60
74
  this.setValue(this.widget[this.valueProp]);
61
75
  });
76
+
77
+ const slot = this.shadowRoot.querySelector('slot');
78
+ this.template = this.querySelector('template');
79
+ // console.log('template',this.template);
62
80
  }
63
81
 
64
82
  setValue(val) {
@@ -76,7 +94,12 @@ class FxControl extends XfAbstractControl {
76
94
  `;
77
95
  }
78
96
 
97
+ /**
98
+ *
99
+ * @returns {HTMLElement|*}
100
+ */
79
101
  getWidget() {
102
+ if (this.widget) return this.widget;
80
103
  let widget = this.querySelector(`.${WIDGETCLASS}`);
81
104
  if (!widget) {
82
105
  widget = this.querySelector('input');
@@ -109,23 +132,34 @@ class FxControl extends XfAbstractControl {
109
132
  }
110
133
  }
111
134
 
112
- async refresh() {
135
+ getTemplate() {
136
+ return this.querySelector('template');
137
+ }
138
+
139
+ async refresh(force) {
140
+ // console.log('fx-control refresh', this);
113
141
  super.refresh();
142
+ // console.log('refresh template', this.template);
114
143
  // const {widget} = this;
115
144
 
116
145
  // ### if we find a ref on control we have a 'select' control of some kind
117
- // todo: review - seems a bit implicite to draw that 'itemset decision' just from the existence of a 'ref'
118
- if (this.widget.hasAttribute('ref')) {
119
- const tmpl = this.widget.querySelector('template');
120
-
146
+ const widget = this.getWidget();
147
+ if (widget.hasAttribute('ref')) {
121
148
  // ### eval nodeset for list control
122
- const ref = this.widget.getAttribute('ref');
123
- const inscope = this.getInScopeContext();
124
- const formElement = this.closest('fx-fore');
125
- const nodeset = evaluateXPathToNodes(ref, inscope, formElement);
149
+ const ref = widget.getAttribute('ref');
150
+ /*
151
+ actually a ref on a select or similar component should point to a different instance
152
+ with an absolute expr e.g. 'instance('theId')/...'
153
+
154
+ todo: even bail out if ref is not absolute?
155
+ */
156
+
157
+ const inscope = getInScopeContext(this, ref);
158
+ // const nodeset = evaluateXPathToNodes(ref, inscope, this);
159
+ const nodeset = evaluateXPath(ref, inscope, this);
126
160
 
127
161
  // ### clear items
128
- const { children } = this.widget;
162
+ const { children } = widget;
129
163
  Array.from(children).forEach(child => {
130
164
  if (child.nodeName.toLowerCase() !== 'template') {
131
165
  child.parentNode.removeChild(child);
@@ -133,33 +167,54 @@ class FxControl extends XfAbstractControl {
133
167
  });
134
168
 
135
169
  // ### build the items
136
- Array.from(nodeset).forEach(node => {
137
- console.log('#### node', node);
138
- const content = tmpl.content.firstElementChild.cloneNode(true);
139
- const newEntry = document.importNode(content, true);
140
- // console.log('newEntry ', newEntry);
141
- this.widget.appendChild(newEntry);
142
-
143
- // ### initialize new entry
144
- // ### set value
145
- const valueAttribute = this._getValueAttribute(newEntry);
146
- const valueExpr = valueAttribute.value;
147
- const cutted = valueExpr.substring(1, valueExpr.length - 1);
148
- const evaluated = evaluateXPath(cutted, node, formElement);
149
- valueAttribute.value = evaluated;
150
-
151
- if (this.value === evaluated) {
152
- newEntry.setAttribute('selected', 'selected');
170
+ if (this.template) {
171
+ if (nodeset.length) {
172
+ // console.log('nodeset', nodeset);
173
+ Array.from(nodeset).forEach(node => {
174
+ // console.log('#### node', node);
175
+ const newEntry = this.createEntry();
176
+
177
+ // ### initialize new entry
178
+ // ### set value
179
+ this.updateEntry(newEntry, node);
180
+ });
181
+ } else {
182
+ const newEntry = this.createEntry();
183
+ this.updateEntry(newEntry, nodeset);
153
184
  }
185
+ }
186
+ }
187
+ Fore.refreshChildren(this, force);
188
+ }
154
189
 
155
- // ### set label
156
- const optionLabel = newEntry.textContent;
157
- const labelExpr = optionLabel.substring(1, optionLabel.length - 1);
190
+ updateEntry(newEntry, node) {
191
+ // ### >>> todo: needs rework this code is heavily assuming a select control with 'value' attribute - not generic at all yet.
158
192
 
159
- const label = evaluateXPathToString(labelExpr, node, formElement);
160
- newEntry.textContent = label;
161
- });
193
+ if (this.widget.nodeName !== 'SELECT') return;
194
+ const valueAttribute = this._getValueAttribute(newEntry);
195
+ const valueExpr = valueAttribute.value;
196
+ const cutted = valueExpr.substring(1, valueExpr.length - 1);
197
+ const evaluated = evaluateXPath(cutted, node, newEntry);
198
+ valueAttribute.value = evaluated;
199
+
200
+ if (this.value === evaluated) {
201
+ newEntry.setAttribute('selected', 'selected');
162
202
  }
203
+
204
+ // ### set label
205
+ const optionLabel = newEntry.textContent;
206
+ const labelExpr = optionLabel.substring(1, optionLabel.length - 1);
207
+
208
+ const label = evaluateXPathToString(labelExpr, node, newEntry);
209
+ newEntry.textContent = label;
210
+ // ### <<< needs rework
211
+ }
212
+
213
+ createEntry() {
214
+ const content = this.template.content.firstElementChild.cloneNode(true);
215
+ const newEntry = document.importNode(content, true);
216
+ this.template.parentNode.appendChild(newEntry);
217
+ return newEntry;
163
218
  }
164
219
 
165
220
  // eslint-disable-next-line class-methods-use-this
@@ -0,0 +1,44 @@
1
+ /**
2
+ * lists out all live instances in html 'details' and 'summary' elements.
3
+ */
4
+ export class FxInspector extends HTMLElement {
5
+ connectedCallback() {
6
+ const style = `
7
+ :host {
8
+ display: block;
9
+ width:100%;
10
+ background:var(--inspector-bg);
11
+ }
12
+ pre{
13
+ background:var(--inspector-pre-bg);
14
+ color:var(--inspector-color);
15
+ max-height:var(--inspector-instance-height,300px);
16
+ overflow:scroll;
17
+ }
18
+ `;
19
+
20
+ const instances = Array.from(document.querySelectorAll('fx-instance'));
21
+ this.innerHTML = `
22
+ <style>
23
+ ${style}
24
+ </style>
25
+ <slot></slot>
26
+ ${instances
27
+ .map(
28
+ (instance, index) => `
29
+ <details ${index === 0 ? `open` : ''}>
30
+ <summary>${instance.id}</summary>
31
+ <pre>{log('${instance.id}')}</pre>
32
+ </details>
33
+ `,
34
+ )
35
+ .join('')}
36
+ `;
37
+
38
+ this.addEventListener('slotchange', e => {
39
+ console.log('slotchange ', e);
40
+ });
41
+ }
42
+ }
43
+
44
+ customElements.define('fx-inspector', FxInspector);
@@ -1,33 +1,117 @@
1
- import { html, css } from 'lit-element';
2
-
3
1
  import XfAbstractControl from './abstract-control.js';
2
+ import { evaluateXPath, evaluateXPathToString, evaluateXPathToNodes } from '../xpath-evaluation.js';
3
+ import getInScopeContext from '../getInScopeContext.js';
4
+ import FxControl from './fx-control.js';
5
+ import { Fore } from '../fore.js';
4
6
 
5
7
  /**
6
- * todo
8
+ * FxItems provices a templated list over its bound nodes. It is not standalone but expects to be used
9
+ * within an fx-control element.
10
+ *
11
+ *
12
+ *
13
+ * @demo demo/selects3.html
7
14
  */
8
- export class FxItems extends XfAbstractControl {
9
- static get styles() {
10
- return css`
11
- :host {
12
- display: block;
13
- height: auto;
14
- font-size: 0.8em;
15
- font-weight: 400;
16
- font-style: italic;
17
- }
18
- `;
19
- }
20
-
15
+ export class FxItems extends FxControl {
21
16
  static get properties() {
22
17
  return {
23
18
  ...super.properties,
19
+ valueAttr: {
20
+ type: String,
21
+ },
24
22
  };
25
23
  }
26
24
 
27
- render() {
28
- return html`
29
- <slot></slot>
30
- `;
25
+ constructor() {
26
+ super();
27
+ this.valueAttr = this.hasAttribute('value') ? this.getAttribute('value') : null;
28
+ }
29
+
30
+ connectedCallback() {
31
+ super.connectedCallback();
32
+
33
+ this.addEventListener('click', e => {
34
+ const items = this.querySelectorAll('[value]');
35
+
36
+ let target;
37
+ if (e.target.nodeName === 'LABEL') {
38
+ target = document.getElementById(e.target.getAttribute('for'));
39
+ target.checked = !target.checked;
40
+ }
41
+
42
+ let val = '';
43
+ Array.from(items).forEach(item => {
44
+ if (item.checked) {
45
+ val += ` ${item.getAttribute('value')}`;
46
+ }
47
+ });
48
+ this.setAttribute('value', val.trim());
49
+
50
+ // ### check for parent control
51
+ const parentBind = this.parentNode.closest('[ref]');
52
+ if (!parentBind) return;
53
+ const modelitem = parentBind.getModelItem();
54
+ const setval = this.shadowRoot.getElementById('setvalue');
55
+ setval.setValue(modelitem, val.trim());
56
+ setval.actionPerformed();
57
+ });
58
+ }
59
+
60
+ getWidget() {
61
+ return this;
62
+ }
63
+
64
+ async updateWidgetValue() {
65
+ // console.log('setting items value');
66
+
67
+ const parentBind = this.parentNode.closest('[ref]');
68
+ if (parentBind) {
69
+ this.value = parentBind.value;
70
+ }
71
+ this.setAttribute('value', this.value);
72
+ }
73
+
74
+ /**
75
+ * Updates an entry by setting the label and the value.
76
+ *
77
+ * Will connect label and control with `for` attribute with generated id.
78
+ *
79
+ * attention: limitations here: assumes that there's an `label` element plus an element with an `value`
80
+ * attribute which it will update.
81
+ *
82
+ *
83
+ *
84
+ * @param newEntry
85
+ * @param node
86
+ */
87
+ updateEntry(newEntry, node) {
88
+ // console.log('fx-items updateEntry', this.value);
89
+ // super.updateEntry(newEntry,node);
90
+
91
+ // ### danger zone - highly specific - assumes knowledge of the template structure ###
92
+ // ### danger zone - highly specific - assumes knowledge of the template structure ###
93
+ // ### danger zone - highly specific - assumes knowledge of the template structure ###
94
+
95
+ const label = newEntry.querySelector('label');
96
+ label.textContent = node.textContent;
97
+
98
+ const id = Fore.createUUID();
99
+ label.setAttribute('for', id);
100
+
101
+ // getting element which has 'value' attr
102
+ const input = newEntry.querySelector('[value]');
103
+ // getting expr
104
+ const expr = input.value;
105
+ const cutted = expr.substring(1, expr.length - 1);
106
+ const evaluated = evaluateXPath(cutted, node, newEntry);
107
+
108
+ const valAttr = this.getAttribute('value');
109
+ input.value = evaluated;
110
+ input.setAttribute('id', id);
111
+ if (valAttr.indexOf(input.value) !== -1) {
112
+ input.checked = true;
113
+ }
31
114
  }
32
115
  }
116
+
33
117
  customElements.define('fx-items', FxItems);
@@ -1,4 +1,7 @@
1
1
  import XfAbstractControl from './abstract-control.js';
2
+ import { evaluateXPath, evaluateXPathToString } from '../xpath-evaluation.js';
3
+ import getInScopeContext from '../getInScopeContext.js';
4
+ // import {markdown} from '../drawdown.js';
2
5
 
3
6
  /**
4
7
  * todo: review placing of value. should probably work with value attribute and not allow slotted content.
@@ -7,17 +10,19 @@ export class FxOutput extends XfAbstractControl {
7
10
  static get properties() {
8
11
  return {
9
12
  ...super.properties,
13
+ valueAttr: {
14
+ type: String,
15
+ },
10
16
  };
11
17
  }
12
18
 
13
19
  constructor() {
14
20
  super();
15
21
  this.attachShadow({ mode: 'open' });
22
+ this.valueAttr = this.hasAttribute('value') ? this.getAttribute('value') : null;
16
23
  }
17
24
 
18
25
  connectedCallback() {
19
- console.log('connectedCallback output', this.shadowRoot);
20
-
21
26
  const style = `
22
27
  :host {
23
28
  display: inline-block;
@@ -28,12 +33,21 @@ export class FxOutput extends XfAbstractControl {
28
33
  .label{
29
34
  display: inline-block;
30
35
  }
36
+ table,tbody{
37
+ width:100%;
38
+ }
39
+ th{
40
+ text-align:left;
41
+ }
42
+ td{
43
+ padding-right:1rem;
44
+ }
31
45
  `;
32
46
 
33
47
  const outputHtml = `
34
48
  <slot name="label"></slot>
35
49
  <span id="value">
36
- <slot></slot>
50
+ <slot id="main"></slot>
37
51
  </span>
38
52
  `;
39
53
 
@@ -46,19 +60,94 @@ export class FxOutput extends XfAbstractControl {
46
60
  // this.widget = this.shadowRoot.querySelector('#widget');
47
61
  // this.widget = this.getWidget();
48
62
  // console.log('widget ', this.widget);
63
+ this.mediatype = this.hasAttribute('mediatype') ? this.getAttribute('mediatype') : null;
49
64
 
50
65
  this.addEventListener('slotchange', e => {
51
66
  console.log('slotchange ', e);
52
67
  });
53
68
  }
54
69
 
70
+ async refresh() {
71
+ // ### 1. eval 'value' attr
72
+ // await super.refresh();
73
+
74
+ if (this.valueAttr) {
75
+ this.value = this.getValue();
76
+ await this.updateWidgetValue();
77
+ return;
78
+ }
79
+ // ### 2. eval 'ref' attr
80
+ if (this.ref) {
81
+ super.refresh();
82
+ }
83
+ // ### 3. use inline content which is there anyway
84
+ }
85
+
86
+ getValue() {
87
+ // return 'foobar';
88
+ try {
89
+ const inscopeContext = getInScopeContext(this, this.valueAttr);
90
+ if (this.hasAttribute('html')) {
91
+ return evaluateXPath(this.valueAttr, inscopeContext, this);
92
+ }
93
+ return evaluateXPathToString(this.valueAttr, inscopeContext, this);
94
+ } catch (error) {
95
+ console.error(error);
96
+ this.dispatch('error', { message: error });
97
+ }
98
+ return null;
99
+ }
100
+
55
101
  getWidget() {
56
102
  const valueWrapper = this.shadowRoot.getElementById('value');
57
103
  return valueWrapper;
58
104
  }
59
105
 
60
106
  async updateWidgetValue() {
107
+ console.log('updateWidgetValue');
61
108
  const valueWrapper = this.shadowRoot.getElementById('value');
109
+
110
+ if (this.mediatype === 'markdown') {
111
+ const md = markdown(this.nodeset);
112
+ this.innerHtml = md;
113
+ }
114
+
115
+ if (this.mediatype === 'html') {
116
+ if (this.modelItem.node) {
117
+ /*
118
+ valueWrapper.innerHTML = this.modelItem.node.outerHTML;
119
+ return;
120
+ */
121
+
122
+ const node = this.modelItem.node;
123
+
124
+ if (node.nodeType) {
125
+ // const mainSlot = this.shadowRoot.querySelector('#main');
126
+ // valueWrapper.appendChild(node);
127
+
128
+ // todo: checking if ownerDocument of node and ownerDocument of this are the same - otherwise import first
129
+ // const imported = this.ownerDocument.importNode(node,true);
130
+ // const clone = node.cloneNode(true);
131
+
132
+ this.appendChild(node);
133
+ // this.innerHtml = node;
134
+ // this.innerHTML = node;
135
+ return;
136
+ }
137
+ Object.entries(node).map(obj => {
138
+ // valueWrapper.appendChild(obj[1]);
139
+ this.appendChild(obj[1]);
140
+ });
141
+ return;
142
+ }
143
+
144
+ // this.innerHTML = this.value.outerHTML;
145
+ valueWrapper.innerHTML = this.value.outerHTML;
146
+
147
+ // this.shadowRoot.appendChild(this.value);
148
+ return;
149
+ }
150
+
62
151
  valueWrapper.innerHTML = this.value;
63
152
  }
64
153