@jinntec/fore 1.0.0-0 → 1.0.0-3

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.
@@ -30,18 +30,21 @@ export class FxContainer extends foreElementMixin(HTMLElement) {
30
30
  </style>
31
31
  ${html}
32
32
  `;
33
+
34
+ this.getOwnerForm().registerLazyElement(this);
33
35
  }
34
36
 
35
37
  /**
36
38
  * (re)apply all state properties to this control.
37
39
  */
38
- refresh() {
39
- console.log('### FxContainer.refresh on : ', this);
40
+ refresh(force) {
41
+ if (!force && this.hasAttribute('refresh-on-view')) return;
42
+ // console.log('### FxContainer.refresh on : ', this);
40
43
 
41
44
  if (this.isBound()) {
42
45
  this.evalInContext();
43
46
  this.modelItem = this.getModelItem();
44
- this.value = this.modelItem.value;
47
+ // this.value = this.modelItem.value;
45
48
  }
46
49
 
47
50
  // await this.updateComplete;
@@ -50,7 +53,7 @@ export class FxContainer extends foreElementMixin(HTMLElement) {
50
53
  if (this._getForm().ready) {
51
54
  this.handleModelItemProperties();
52
55
  }
53
- Fore.refreshChildren(this);
56
+ Fore.refreshChildren(this, force);
54
57
  }
55
58
 
56
59
  handleModelItemProperties() {
@@ -78,6 +81,8 @@ export class FxContainer extends foreElementMixin(HTMLElement) {
78
81
 
79
82
  handleRelevant() {
80
83
  // console.log('mip valid', this.modelItem.enabled);
84
+ if (!this.modelItem) return;
85
+
81
86
  if (this.isEnabled() !== this.modelItem.enabled) {
82
87
  if (this.modelItem.enabled) {
83
88
  this.dispatchEvent(new CustomEvent('enabled', {}));
@@ -1,5 +1,7 @@
1
1
  import XfAbstractControl from './abstract-control.js';
2
- import { 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,7 +56,7 @@ 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);
58
60
 
59
61
  // ### convenience marker event
60
62
  if (this.updateEvent === 'enter') {
@@ -64,15 +66,17 @@ class FxControl extends XfAbstractControl {
64
66
  event.preventDefault();
65
67
  this.setValue(this.widget[this.valueProp]);
66
68
  }
67
- // console.log('enter handler ', this.updateEvent);
68
- // this.setValue(this.widget[this.valueProp]);
69
69
  });
70
70
  this.updateEvent = 'blur'; // needs to be registered too
71
71
  }
72
72
  this.widget.addEventListener(this.updateEvent, () => {
73
- console.log('eventlistener ', this.updateEvent);
73
+ // console.log('eventlistener ', this.updateEvent);
74
74
  this.setValue(this.widget[this.valueProp]);
75
75
  });
76
+
77
+ const slot = this.shadowRoot.querySelector('slot');
78
+ this.template = this.querySelector('template');
79
+ // console.log('template',this.template);
76
80
  }
77
81
 
78
82
  setValue(val) {
@@ -95,6 +99,7 @@ class FxControl extends XfAbstractControl {
95
99
  * @returns {HTMLElement|*}
96
100
  */
97
101
  getWidget() {
102
+ if (this.widget) return this.widget;
98
103
  let widget = this.querySelector(`.${WIDGETCLASS}`);
99
104
  if (!widget) {
100
105
  widget = this.querySelector('input');
@@ -127,23 +132,34 @@ class FxControl extends XfAbstractControl {
127
132
  }
128
133
  }
129
134
 
130
- async refresh() {
135
+ getTemplate() {
136
+ return this.querySelector('template');
137
+ }
138
+
139
+ async refresh(force) {
140
+ // console.log('fx-control refresh', this);
131
141
  super.refresh();
142
+ // console.log('refresh template', this.template);
132
143
  // const {widget} = this;
133
144
 
134
145
  // ### if we find a ref on control we have a 'select' control of some kind
135
- // todo: review - seems a bit implicite to draw that 'itemset decision' just from the existence of a 'ref'
136
- if (this.widget.hasAttribute('ref')) {
137
- const tmpl = this.widget.querySelector('template');
138
-
146
+ const widget = this.getWidget();
147
+ if (widget.hasAttribute('ref')) {
139
148
  // ### eval nodeset for list control
140
- const ref = this.widget.getAttribute('ref');
141
- const inscope = this.getInScopeContext();
142
- const formElement = this.closest('fx-fore');
143
- const nodeset = evaluateXPath(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);
144
160
 
145
161
  // ### clear items
146
- const { children } = this.widget;
162
+ const { children } = widget;
147
163
  Array.from(children).forEach(child => {
148
164
  if (child.nodeName.toLowerCase() !== 'template') {
149
165
  child.parentNode.removeChild(child);
@@ -151,33 +167,54 @@ class FxControl extends XfAbstractControl {
151
167
  });
152
168
 
153
169
  // ### build the items
154
- Array.from(nodeset).forEach(node => {
155
- console.log('#### node', node);
156
- const content = tmpl.content.firstElementChild.cloneNode(true);
157
- const newEntry = document.importNode(content, true);
158
- // console.log('newEntry ', newEntry);
159
- this.widget.appendChild(newEntry);
160
-
161
- // ### initialize new entry
162
- // ### set value
163
- const valueAttribute = this._getValueAttribute(newEntry);
164
- const valueExpr = valueAttribute.value;
165
- const cutted = valueExpr.substring(1, valueExpr.length - 1);
166
- const evaluated = evaluateXPath(cutted, node, formElement);
167
- valueAttribute.value = evaluated;
168
-
169
- if (this.value === evaluated) {
170
- 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);
171
184
  }
185
+ }
186
+ }
187
+ Fore.refreshChildren(this, force);
188
+ }
172
189
 
173
- // ### set label
174
- const optionLabel = newEntry.textContent;
175
- 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.
176
192
 
177
- const label = evaluateXPathToString(labelExpr, node, formElement);
178
- newEntry.textContent = label;
179
- });
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');
180
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;
181
218
  }
182
219
 
183
220
  // eslint-disable-next-line class-methods-use-this
@@ -0,0 +1,68 @@
1
+ export class FxDialog extends HTMLElement {
2
+
3
+ static get properties() {
4
+ return {
5
+ id: String
6
+ };
7
+ }
8
+
9
+ constructor() {
10
+ super();
11
+ this.attachShadow({mode: 'open'});
12
+ }
13
+
14
+ connectedCallback() {
15
+ const style = `
16
+ :host {
17
+ display: none;
18
+ height: 100vh;
19
+ width:100vw;
20
+ position:fixed;
21
+ left:0;
22
+ top:0;
23
+ right:0;
24
+ bottom:0;
25
+ }
26
+ `;
27
+
28
+ this.shadowRoot.innerHTML = this.render(style);
29
+ this.id = this.getAttribute('id');
30
+
31
+ // const dialog = document.getElementById(this.id);
32
+
33
+ const closeBtn = this.querySelector('.close-dialog');
34
+ if (closeBtn) {
35
+ closeBtn.addEventListener('click', (e) => {
36
+ document.getElementById(this.id).classList.remove('show');
37
+ });
38
+ }
39
+
40
+ this.focus();
41
+ }
42
+
43
+ render(styles) {
44
+ return `
45
+ <style>
46
+ ${styles}
47
+ </style>
48
+ <slot></slot>
49
+ `;
50
+ }
51
+
52
+ open(){
53
+ window.addEventListener('keyup', (e) => {
54
+ if (e.key === "Escape") {
55
+ this.hide();
56
+ }
57
+ },{once:true});
58
+
59
+ this.classList.add('show');
60
+ }
61
+
62
+ hide(){
63
+ this.classList.remove('show');
64
+ }
65
+
66
+ }
67
+
68
+ customElements.define('fx-dialog', FxDialog);
@@ -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);
@@ -0,0 +1,120 @@
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';
6
+
7
+ /**
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
14
+ */
15
+ export class FxItems extends FxControl {
16
+ static get properties() {
17
+ return {
18
+ ...super.properties,
19
+ valueAttr: {
20
+ type: String,
21
+ },
22
+ };
23
+ }
24
+
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
+ // adding space around value to allow matching of 'words'
109
+ const spaced = ` ${evaluated} `;
110
+
111
+ const valAttr = ` ${this.getAttribute('value')} `;
112
+ input.value = evaluated;
113
+ input.setAttribute('id', id);
114
+ if (valAttr.indexOf(spaced) !== -1) {
115
+ input.checked = true;
116
+ }
117
+ }
118
+ }
119
+
120
+ customElements.define('fx-items', FxItems);
@@ -1,4 +1,7 @@
1
1
  import XfAbstractControl from './abstract-control.js';
2
+ import { evaluateXPath, evaluateXPathToStrings } 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.
@@ -20,8 +23,6 @@ export class FxOutput extends XfAbstractControl {
20
23
  }
21
24
 
22
25
  connectedCallback() {
23
- console.log('connectedCallback output', this.shadowRoot);
24
-
25
26
  const style = `
26
27
  :host {
27
28
  display: inline-block;
@@ -32,12 +33,21 @@ export class FxOutput extends XfAbstractControl {
32
33
  .label{
33
34
  display: inline-block;
34
35
  }
36
+ table,tbody{
37
+ width:100%;
38
+ }
39
+ th{
40
+ text-align:left;
41
+ }
42
+ td{
43
+ padding-right:1rem;
44
+ }
35
45
  `;
36
46
 
37
47
  const outputHtml = `
38
48
  <slot name="label"></slot>
39
49
  <span id="value">
40
- <slot></slot>
50
+ <slot id="main"></slot>
41
51
  </span>
42
52
  `;
43
53
 
@@ -50,6 +60,7 @@ export class FxOutput extends XfAbstractControl {
50
60
  // this.widget = this.shadowRoot.querySelector('#widget');
51
61
  // this.widget = this.getWidget();
52
62
  // console.log('widget ', this.widget);
63
+ this.mediatype = this.hasAttribute('mediatype') ? this.getAttribute('mediatype') : null;
53
64
 
54
65
  this.addEventListener('slotchange', e => {
55
66
  console.log('slotchange ', e);
@@ -57,18 +68,32 @@ export class FxOutput extends XfAbstractControl {
57
68
  }
58
69
 
59
70
  async refresh() {
60
- // ### 1. eval 'value' attr
71
+ // Resolve the ref first. The ref will set the `nodeset` which is important for the 'context'
72
+ if (this.ref) {
73
+ await super.refresh();
74
+ }
61
75
 
76
+ // ### 2. Eval the value
62
77
  if (this.valueAttr) {
63
78
  this.value = this.getValue();
64
79
  await this.updateWidgetValue();
65
- return;
66
80
  }
67
- // ### 2. eval 'ref' attr
68
- if (this.ref) {
69
- super.refresh();
81
+ }
82
+
83
+ getValue() {
84
+ // return 'foobar';
85
+ try {
86
+ const inscopeContext = getInScopeContext(this, this.valueAttr);
87
+ if (this.hasAttribute('html')) {
88
+ return evaluateXPath(this.valueAttr, inscopeContext, this);
89
+ }
90
+
91
+ return evaluateXPathToStrings(this.valueAttr, inscopeContext, this)[0];
92
+ } catch (error) {
93
+ console.error(error);
94
+ this.dispatch('error', { message: error });
70
95
  }
71
- // ### 3. use inline content which is there anyway
96
+ return null;
72
97
  }
73
98
 
74
99
  getWidget() {
@@ -77,7 +102,50 @@ export class FxOutput extends XfAbstractControl {
77
102
  }
78
103
 
79
104
  async updateWidgetValue() {
105
+ console.log('updateWidgetValue');
80
106
  const valueWrapper = this.shadowRoot.getElementById('value');
107
+
108
+ if (this.mediatype === 'markdown') {
109
+ const md = markdown(this.nodeset);
110
+ this.innerHtml = md;
111
+ }
112
+
113
+ if (this.mediatype === 'html') {
114
+ if (this.modelItem.node) {
115
+ /*
116
+ valueWrapper.innerHTML = this.modelItem.node.outerHTML;
117
+ return;
118
+ */
119
+
120
+ const { node } = this.modelItem;
121
+
122
+ if (node.nodeType) {
123
+ // const mainSlot = this.shadowRoot.querySelector('#main');
124
+ // valueWrapper.appendChild(node);
125
+
126
+ // todo: checking if ownerDocument of node and ownerDocument of this are the same - otherwise import first
127
+ // const imported = this.ownerDocument.importNode(node,true);
128
+ // const clone = node.cloneNode(true);
129
+
130
+ this.appendChild(node);
131
+ // this.innerHtml = node;
132
+ // this.innerHTML = node;
133
+ return;
134
+ }
135
+ Object.entries(node).map(obj => {
136
+ // valueWrapper.appendChild(obj[1]);
137
+ this.appendChild(obj[1]);
138
+ });
139
+ return;
140
+ }
141
+
142
+ // this.innerHTML = this.value.outerHTML;
143
+ valueWrapper.innerHTML = this.value.outerHTML;
144
+
145
+ // this.shadowRoot.appendChild(this.value);
146
+ return;
147
+ }
148
+
81
149
  valueWrapper.innerHTML = this.value;
82
150
  }
83
151