@jinntec/fore 1.0.0-1 → 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.
@@ -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,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,117 @@
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
+ 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
+ }
114
+ }
115
+ }
116
+
117
+ customElements.define('fx-items', FxItems);
@@ -1,6 +1,7 @@
1
1
  import XfAbstractControl from './abstract-control.js';
2
2
  import { evaluateXPath, evaluateXPathToString } from '../xpath-evaluation.js';
3
3
  import getInScopeContext from '../getInScopeContext.js';
4
+ // import {markdown} from '../drawdown.js';
4
5
 
5
6
  /**
6
7
  * todo: review placing of value. should probably work with value attribute and not allow slotted content.
@@ -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);
@@ -93,12 +104,41 @@ export class FxOutput extends XfAbstractControl {
93
104
  }
94
105
 
95
106
  async updateWidgetValue() {
107
+ console.log('updateWidgetValue');
96
108
  const valueWrapper = this.shadowRoot.getElementById('value');
97
109
 
98
- if (this.hasAttribute('html')) {
110
+ if (this.mediatype === 'markdown') {
111
+ const md = markdown(this.nodeset);
112
+ this.innerHtml = md;
113
+ }
114
+
115
+ if (this.mediatype === 'html') {
99
116
  if (this.modelItem.node) {
117
+ /*
100
118
  valueWrapper.innerHTML = this.modelItem.node.outerHTML;
101
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;
102
142
  }
103
143
 
104
144
  // this.innerHTML = this.value.outerHTML;
@@ -88,6 +88,7 @@ export class FxRepeat extends foreElementMixin(HTMLElement) {
88
88
  }
89
89
 
90
90
  connectedCallback() {
91
+ // this.display = window.getComputedStyle(this, null).getPropertyValue("display");
91
92
  this.ref = this.getAttribute('ref');
92
93
  // console.log('### fx-repeat connected ', this.id);
93
94
  this.addEventListener('item-changed', e => {
@@ -114,16 +115,26 @@ export class FxRepeat extends foreElementMixin(HTMLElement) {
114
115
  console.log('insert catched', nodes, this.index);
115
116
  });
116
117
 
118
+ if (this.getOwnerForm().lazyRefresh) {
119
+ this.mutationObserver = new MutationObserver(mutations => {
120
+ console.log('mutations', mutations);
121
+ this.refresh(true);
122
+ });
123
+ }
124
+ this.getOwnerForm().registerLazyElement(this);
125
+
117
126
  const style = `
118
- .fade-out-bottom {
119
- -webkit-animation: fade-out-bottom 0.7s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
120
- animation: fade-out-bottom 0.7s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
121
- }
122
- .fade-out-bottom {
123
- -webkit-animation: fade-out-bottom 0.7s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
124
- animation: fade-out-bottom 0.7s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
125
- }
126
- `;
127
+ :host{
128
+ }
129
+ .fade-out-bottom {
130
+ -webkit-animation: fade-out-bottom 0.7s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
131
+ animation: fade-out-bottom 0.7s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
132
+ }
133
+ .fade-out-bottom {
134
+ -webkit-animation: fade-out-bottom 0.7s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
135
+ animation: fade-out-bottom 0.7s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
136
+ }
137
+ `;
127
138
  const html = `
128
139
  <slot name="header"></slot>
129
140
  <slot></slot>
@@ -160,6 +171,14 @@ export class FxRepeat extends foreElementMixin(HTMLElement) {
160
171
  const inscope = getInScopeContext(this, this.ref);
161
172
  // console.log('##### inscope ', inscope);
162
173
  // console.log('##### ref ', this.ref);
174
+ // now we got a nodeset and attach MutationObserver to it
175
+
176
+ if (this.mutationObserver && inscope.nodeName) {
177
+ this.mutationObserver.observe(inscope, {
178
+ childList: true,
179
+ subtree: true,
180
+ });
181
+ }
163
182
 
164
183
  const seq = evaluateXPath(this.ref, inscope, this.getOwnerForm());
165
184
  // const seq = evaluateXPathToNodes(this.ref, inscope, this.getOwnerForm());
@@ -188,12 +207,13 @@ export class FxRepeat extends foreElementMixin(HTMLElement) {
188
207
  throw new Error(`Unexpected result of repeat nodeset: ${seq}`);
189
208
  }
190
209
 
191
- async refresh() {
192
- console.group('fx-repeat.refresh on', this.id);
210
+ async refresh(force) {
211
+ // console.group('fx-repeat.refresh on', this.id);
193
212
 
194
213
  if (!this.inited) this.init();
214
+ console.time('repeat-refresh', this);
195
215
  this._evalNodeset();
196
- console.log('repeat refresh nodeset ', this.nodeset);
216
+ // console.log('repeat refresh nodeset ', this.nodeset);
197
217
  // console.log('repeatCount', this.repeatCount);
198
218
 
199
219
  const repeatItems = this.querySelectorAll(':scope > fx-repeatitem');
@@ -213,7 +233,7 @@ export class FxRepeat extends foreElementMixin(HTMLElement) {
213
233
  // remove repeatitem
214
234
  const itemToRemove = repeatItems[position - 1];
215
235
  itemToRemove.parentNode.removeChild(itemToRemove);
216
-
236
+ this.getOwnerForm().unRegisterLazyElement(itemToRemove);
217
237
  // this._fadeOut(itemToRemove);
218
238
  // Fore.fadeOutElement(itemToRemove)
219
239
  }
@@ -236,13 +256,25 @@ export class FxRepeat extends foreElementMixin(HTMLElement) {
236
256
  // ### update nodeset of repeatitems
237
257
  for (let position = 0; position < repeatItemCount; position += 1) {
238
258
  const item = repeatItems[position];
259
+ this.getOwnerForm().registerLazyElement(item);
260
+
239
261
  if (item.nodeset !== this.nodeset[position]) {
240
262
  item.nodeset = this.nodeset[position];
241
263
  }
242
264
  }
243
265
 
244
- Fore.refreshChildren(this);
266
+ // Fore.refreshChildren(clone,true);
267
+ const fore = this.getOwnerForm();
268
+ if (!fore.lazyRefresh || force) {
269
+ Fore.refreshChildren(this, force);
270
+ }
271
+ // this.style.display = 'block';
272
+ // this.style.display = this.display;
245
273
  this.setIndex(this.index);
274
+ console.timeEnd('repeat-refresh');
275
+
276
+ // this.replaceWith(clone);
277
+
246
278
  // this.repeatCount = contextSize;
247
279
  // console.log('repeatCount', this.repeatCount);
248
280
  console.groupEnd();
@@ -289,7 +321,7 @@ export class FxRepeat extends foreElementMixin(HTMLElement) {
289
321
  // todo: this is still weak - should handle that better maybe by an explicit slot?
290
322
  // this.template = this.firstElementChild;
291
323
  this.template = this.querySelector('template');
292
- console.log('### init template for repeat ', this.id, this.template);
324
+ // console.log('### init template for repeat ', this.id, this.template);
293
325
 
294
326
  if (this.template === null) {
295
327
  // console.error('### no template found for this repeat:', this.id);
@@ -22,7 +22,8 @@ export class FxRepeatitem extends foreElementMixin(HTMLElement) {
22
22
  this.inited = false;
23
23
 
24
24
  this.addEventListener('click', this._dispatchIndexChange);
25
- this.addEventListener('focusin', this._handleFocus);
25
+ // this.addEventListener('focusin', this._handleFocus);
26
+ this.addEventListener('focusin', this._dispatchIndexChange);
26
27
 
27
28
  this.attachShadow({ mode: 'open', delegatesFocus: true });
28
29
  }
@@ -30,7 +31,12 @@ export class FxRepeatitem extends foreElementMixin(HTMLElement) {
30
31
  _handleFocus() {
31
32
  this.parentNode.setIndex(this.index);
32
33
  // TODO: do this somewhere else, somewhere more central
33
- this.closest('fx-fore').refresh();
34
+
35
+ /**
36
+ * todo: resolve - this is problematic as it triggers a lot of unneeded refreshes but it needed
37
+ * when you want to support activating the right repeatitem when the user tabs through controls.
38
+ */
39
+ // this.closest('fx-fore').refresh();
34
40
  }
35
41
 
36
42
  _dispatchIndexChange() {
@@ -52,6 +58,7 @@ export class FxRepeatitem extends foreElementMixin(HTMLElement) {
52
58
  this.shadowRoot.innerHTML = `
53
59
  ${html}
54
60
  `;
61
+ this.getOwnerForm().registerLazyElement(this);
55
62
  }
56
63
 
57
64
  disconnectedCallback() {
@@ -72,7 +79,7 @@ export class FxRepeatitem extends foreElementMixin(HTMLElement) {
72
79
  return this.getModelItem()[this.index];
73
80
  }
74
81
 
75
- refresh() {
82
+ refresh(force) {
76
83
  // console.log('refresh repeatitem: ',this.nodeset);
77
84
  // console.log('refresh repeatitem nodeset: ',this.nodeset);
78
85
  this.modelItem = this.getModel().getModelItem(this.nodeset);
@@ -94,7 +101,7 @@ export class FxRepeatitem extends foreElementMixin(HTMLElement) {
94
101
  }
95
102
  */
96
103
 
97
- Fore.refreshChildren(this);
104
+ Fore.refreshChildren(this, force);
98
105
  }
99
106
  }
100
107