@jinntec/fore 2.4.2 → 2.6.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/dist/fore-dev.js +34262 -3
- package/dist/fore.js +34124 -3
- package/index.js +2 -0
- package/package.json +2 -2
- package/resources/fore.css +30 -1
- package/src/DataObserver.js +181 -0
- package/src/DependentXPathQueries.js +32 -0
- package/src/ForeElementMixin.js +289 -254
- package/src/actions/abstract-action.js +38 -5
- package/src/actions/fx-append.js +2 -0
- package/src/actions/fx-delete.js +13 -1
- package/src/actions/fx-hide.js +1 -1
- package/src/actions/fx-insert.js +47 -39
- package/src/actions/fx-load.js +7 -2
- package/src/actions/fx-send.js +0 -2
- package/src/actions/fx-setvalue.js +104 -82
- package/src/actions/fx-show.js +4 -2
- package/src/fore.js +40 -22
- package/src/fx-bind.js +3 -2
- package/src/fx-fore.js +320 -164
- package/src/fx-instance.js +3 -2
- package/src/fx-model.js +63 -34
- package/src/fx-submission.js +38 -22
- package/src/getInScopeContext.js +2 -1
- package/src/modelitem.js +107 -91
- package/src/ui/UIElement.js +91 -0
- package/src/ui/abstract-control.js +14 -9
- package/src/ui/fx-container.js +7 -4
- package/src/ui/fx-control-menu.js +198 -0
- package/src/ui/fx-control.js +64 -41
- package/src/ui/fx-dialog.js +2 -2
- package/src/ui/fx-group.js +14 -0
- package/src/ui/fx-repeat.js +42 -3
- package/src/ui/fx-repeatitem.js +3 -0
- package/src/ui/fx-upload.js +304 -0
- package/src/xpath-util.js +310 -168
- package/dist/fore-dev.js.map +0 -1
- package/dist/fore.js.map +0 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import '../fx-model.js';
|
|
2
|
-
import ForeElementMixin from '../ForeElementMixin.js';
|
|
3
2
|
import { ModelItem } from '../modelitem.js';
|
|
4
3
|
import { Fore } from '../fore.js';
|
|
5
4
|
import getInScopeContext from '../getInScopeContext.js';
|
|
6
5
|
import { evaluateXPathToFirstNode } from '../xpath-evaluation.js';
|
|
6
|
+
import { UIElement } from './UIElement.js';
|
|
7
7
|
|
|
8
8
|
function isDifferent(oldNodeValue, oldControlValue, newControlValue) {
|
|
9
9
|
if (oldNodeValue === null) {
|
|
@@ -13,7 +13,7 @@ function isDifferent(oldNodeValue, oldControlValue, newControlValue) {
|
|
|
13
13
|
if the oldControlValue is null we know the widget is used for the first time and is not considered
|
|
14
14
|
a value change.
|
|
15
15
|
*/
|
|
16
|
-
if(oldControlValue === null) return false;
|
|
16
|
+
if (oldControlValue === null) return false;
|
|
17
17
|
|
|
18
18
|
if (newControlValue && oldControlValue && newControlValue.nodeType && oldControlValue.nodeType) {
|
|
19
19
|
return newControlValue.outerHTML !== oldControlValue.outerHTML;
|
|
@@ -31,7 +31,7 @@ function isDifferent(oldNodeValue, oldControlValue, newControlValue) {
|
|
|
31
31
|
* is a general base class for control elements.
|
|
32
32
|
*
|
|
33
33
|
*/
|
|
34
|
-
export default class AbstractControl extends
|
|
34
|
+
export default class AbstractControl extends UIElement {
|
|
35
35
|
constructor() {
|
|
36
36
|
super();
|
|
37
37
|
this.value = null;
|
|
@@ -41,6 +41,7 @@ export default class AbstractControl extends ForeElementMixin {
|
|
|
41
41
|
this.widget = null;
|
|
42
42
|
this.visited = false;
|
|
43
43
|
this.force = false;
|
|
44
|
+
this.ondemand = false;
|
|
44
45
|
// this.attachShadow({ mode: 'open' });
|
|
45
46
|
}
|
|
46
47
|
|
|
@@ -54,20 +55,22 @@ export default class AbstractControl extends ForeElementMixin {
|
|
|
54
55
|
*/
|
|
55
56
|
async refresh(force) {
|
|
56
57
|
if (force) this.force = true;
|
|
57
|
-
// console.log('### AbstractControl.refresh on : ', this);
|
|
58
58
|
|
|
59
59
|
// Save the old value of this control. this may be the stringified version, contrast to the node in `nodeset`
|
|
60
60
|
const oldValue = this.value;
|
|
61
61
|
|
|
62
|
+
// if (this.ondemand && !this.value) return;
|
|
63
|
+
// console.log('### AbstractControl.refresh on : ', this);
|
|
64
|
+
|
|
62
65
|
// if(this.repeated) return
|
|
63
66
|
if (this.isNotBound()) return;
|
|
64
67
|
|
|
65
68
|
// await this.updateComplete;
|
|
66
69
|
// await this.getWidget();
|
|
67
|
-
this.oldVal = this.nodeset ? this.nodeset : null;
|
|
68
|
-
// console.log('oldVal',this.oldVal);
|
|
69
70
|
|
|
70
71
|
this.evalInContext();
|
|
72
|
+
this.oldVal = this.nodeset ? this.nodeset : null;
|
|
73
|
+
// console.log('oldVal',this.oldVal);
|
|
71
74
|
|
|
72
75
|
// todo this if should be removed - see above
|
|
73
76
|
if (this.isBound()) {
|
|
@@ -169,13 +172,15 @@ export default class AbstractControl extends ForeElementMixin {
|
|
|
169
172
|
if (!this.getOwnerForm().ready) return; // state change event do not fire during init phase (initial refresh)
|
|
170
173
|
// if oldVal is null we haven't received a concrete value yet
|
|
171
174
|
|
|
172
|
-
if (this.localName
|
|
173
|
-
if (isDifferent(this.oldVal,
|
|
175
|
+
if (!(this.localName === 'fx-control' || this.localName === 'fx-upload')) return;
|
|
176
|
+
if (isDifferent(this.oldVal, oldValue, this.value)) {
|
|
174
177
|
const model = this.getModel();
|
|
175
178
|
Fore.dispatch(this, 'value-changed', {
|
|
176
179
|
path: this.modelItem.path,
|
|
177
180
|
value: this.modelItem.value,
|
|
178
|
-
oldvalue:
|
|
181
|
+
oldvalue: oldValue,
|
|
182
|
+
instanceId: this.modelItem.instanceId,
|
|
183
|
+
foreId: this.getOwnerForm().id,
|
|
179
184
|
});
|
|
180
185
|
}
|
|
181
186
|
}
|
package/src/ui/fx-container.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import '../fx-model.js';
|
|
2
|
-
import
|
|
2
|
+
import { UIElement } from './UIElement.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* `fx-container` -
|
|
6
6
|
* is a general class for container elements.
|
|
7
7
|
*
|
|
8
8
|
*/
|
|
9
|
-
export class FxContainer extends
|
|
9
|
+
export class FxContainer extends UIElement {
|
|
10
10
|
static get properties() {
|
|
11
11
|
return {
|
|
12
12
|
...super.properties,
|
|
@@ -24,6 +24,8 @@ export class FxContainer extends ForeElementMixin {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
connectedCallback() {
|
|
27
|
+
super.connectedCallback();
|
|
28
|
+
|
|
27
29
|
this.src = this.hasAttribute('src') ? this.getAttribute('src') : null;
|
|
28
30
|
const style = `
|
|
29
31
|
:host {
|
|
@@ -41,8 +43,9 @@ export class FxContainer extends ForeElementMixin {
|
|
|
41
43
|
</style>
|
|
42
44
|
${html}
|
|
43
45
|
`;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
if (this.ref !== '') {
|
|
47
|
+
this.getOwnerForm().registerLazyElement(this);
|
|
48
|
+
}
|
|
46
49
|
|
|
47
50
|
/*
|
|
48
51
|
this.addEventListener('mousedown', e => {
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import XfAbstractControl from './abstract-control.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This class finds and lists all elements with an 'on-demand' attribute and offers them
|
|
5
|
+
* in a popup list for activation. 'on-demand' is not a state like 'relevant' but just
|
|
6
|
+
* shows/hides controls on demand. The controls still behave as usual otherwise.
|
|
7
|
+
*
|
|
8
|
+
*
|
|
9
|
+
*/
|
|
10
|
+
export class FxControlMenu extends XfAbstractControl {
|
|
11
|
+
connectedCallback() {
|
|
12
|
+
this.attachShadow({ mode: 'open' });
|
|
13
|
+
this.selectExpr = this.getAttribute('select');
|
|
14
|
+
|
|
15
|
+
const style = `
|
|
16
|
+
:host {
|
|
17
|
+
display: inline-block;
|
|
18
|
+
position: relative;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.menu {
|
|
22
|
+
display: none;
|
|
23
|
+
position: absolute;
|
|
24
|
+
top: 100%;
|
|
25
|
+
left: 0;
|
|
26
|
+
z-index: 10;
|
|
27
|
+
background: white;
|
|
28
|
+
border: 1px solid #ccc;
|
|
29
|
+
padding: 0.5em;
|
|
30
|
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
|
|
31
|
+
min-width: 10em;
|
|
32
|
+
white-space:nowrap;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.menu.visible {
|
|
36
|
+
display: block;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.menu a {
|
|
40
|
+
display: block;
|
|
41
|
+
padding: 0.25em 0.5em;
|
|
42
|
+
text-decoration: none;
|
|
43
|
+
color: black;
|
|
44
|
+
cursor: pointer;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.menu a:hover {
|
|
48
|
+
background-color: #eee;
|
|
49
|
+
}
|
|
50
|
+
`;
|
|
51
|
+
|
|
52
|
+
this.shadowRoot.innerHTML = `
|
|
53
|
+
<style>${style}</style>
|
|
54
|
+
<slot></slot>
|
|
55
|
+
<div class="menu" part="menu"></div>
|
|
56
|
+
`;
|
|
57
|
+
|
|
58
|
+
this.menuEl = this.shadowRoot.querySelector('.menu');
|
|
59
|
+
|
|
60
|
+
// Slotted button click
|
|
61
|
+
const slot = this.shadowRoot.querySelector('slot');
|
|
62
|
+
slot.addEventListener('slotchange', () => {
|
|
63
|
+
const nodes = slot.assignedNodes({ flatten: true });
|
|
64
|
+
const button = nodes.find(
|
|
65
|
+
node => node.nodeType === Node.ELEMENT_NODE && node.tagName === 'BUTTON',
|
|
66
|
+
);
|
|
67
|
+
if (button) {
|
|
68
|
+
button.addEventListener('click', e => {
|
|
69
|
+
e.preventDefault();
|
|
70
|
+
e.stopPropagation();
|
|
71
|
+
this.updateMenu();
|
|
72
|
+
this.menuEl.classList.toggle('visible');
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Update menu on custom event
|
|
78
|
+
document.addEventListener('update-control-menu', () => {
|
|
79
|
+
this.updateMenu();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Close on outside click
|
|
83
|
+
document.addEventListener('click', e => {
|
|
84
|
+
const inside = this.contains(e.target) || this.shadowRoot.contains(e.target);
|
|
85
|
+
if (!inside) {
|
|
86
|
+
this.menuEl.classList.remove('visible');
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Close on Escape
|
|
91
|
+
document.addEventListener('keydown', e => {
|
|
92
|
+
if (e.key === 'Escape') {
|
|
93
|
+
this.menuEl.classList.remove('visible');
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (this.getAttribute('mode') === 'hide-on-empty') {
|
|
98
|
+
this.getOwnerForm().addEventListener('ready', () => {
|
|
99
|
+
const container = document.querySelector(this.selectExpr);
|
|
100
|
+
if (!container) return;
|
|
101
|
+
|
|
102
|
+
const widgets = container.querySelectorAll('.widget');
|
|
103
|
+
widgets.forEach(widget => {
|
|
104
|
+
const value = widget.value?.trim();
|
|
105
|
+
const control = widget.closest('fx-control');
|
|
106
|
+
if (control && (value === '' || value == null)) {
|
|
107
|
+
control.setAttribute('on-demand', 'true');
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// After marking empty controls, update the menu
|
|
112
|
+
this.updateMenu();
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const container = document.querySelector(this.selectExpr);
|
|
117
|
+
container?.addEventListener('show-control', event => {
|
|
118
|
+
this.updateMenu();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
this.updateMenu();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
updateMenu() {
|
|
125
|
+
const container = document.querySelector(this.selectExpr);
|
|
126
|
+
if (!container) return;
|
|
127
|
+
|
|
128
|
+
let targets = [];
|
|
129
|
+
|
|
130
|
+
if (container.hasAttribute('on-demand')) {
|
|
131
|
+
if (container.nodeName === 'FX-REPEAT') {
|
|
132
|
+
// If it's an <fx-repeat> with on-demand, use only the container
|
|
133
|
+
targets = [container];
|
|
134
|
+
} else {
|
|
135
|
+
// If it's not <fx-repeat>, include container and inner [on-demand] targets
|
|
136
|
+
targets = [container, ...container.querySelectorAll('[on-demand]')];
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
// If container is not on-demand, only look for inner [on-demand]
|
|
140
|
+
targets = Array.from(container.querySelectorAll('[on-demand]'));
|
|
141
|
+
}
|
|
142
|
+
this._currentTargets = targets;
|
|
143
|
+
this.menuEl.innerHTML = ''; // Clear menu
|
|
144
|
+
|
|
145
|
+
// Find the slotted button
|
|
146
|
+
const slot = this.shadowRoot.querySelector('slot');
|
|
147
|
+
const assignedNodes = slot.assignedNodes({ flatten: true });
|
|
148
|
+
const button = assignedNodes.find(
|
|
149
|
+
node => node.nodeType === Node.ELEMENT_NODE && node.tagName === 'BUTTON',
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
if (button) {
|
|
153
|
+
button.disabled = targets.length === 0;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (targets.length === 0) {
|
|
157
|
+
this.menuEl.classList.remove('visible');
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
targets.forEach((el, index) => {
|
|
162
|
+
let label = el.getAttribute('aria-label');
|
|
163
|
+
if (!label) {
|
|
164
|
+
label = el.querySelector('label')?.textContent.trim() || `Item ${index + 1}`;
|
|
165
|
+
}
|
|
166
|
+
if (!label) {
|
|
167
|
+
console.warn(
|
|
168
|
+
'no label found - cannot create menu entry for ',
|
|
169
|
+
el,
|
|
170
|
+
' - please add aria-label or label element to control',
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
const item = document.createElement('a');
|
|
174
|
+
item.href = '#';
|
|
175
|
+
item.textContent = label;
|
|
176
|
+
|
|
177
|
+
item.addEventListener('click', e => {
|
|
178
|
+
e.preventDefault();
|
|
179
|
+
if (typeof el.activate === 'function') {
|
|
180
|
+
el.activate();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
this.menuEl.classList.remove('visible');
|
|
184
|
+
|
|
185
|
+
// Wait one frame to let DOM updates (like on-demand removal) take effect
|
|
186
|
+
requestAnimationFrame(() => {
|
|
187
|
+
this.updateMenu();
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
this.menuEl.appendChild(item);
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (!customElements.get('fx-control-menu')) {
|
|
197
|
+
customElements.define('fx-control-menu', FxControlMenu);
|
|
198
|
+
}
|
package/src/ui/fx-control.js
CHANGED
|
@@ -37,6 +37,7 @@ export default class FxControl extends XfAbstractControl {
|
|
|
37
37
|
constructor() {
|
|
38
38
|
super();
|
|
39
39
|
this.inited = false;
|
|
40
|
+
this.nodeset = null;
|
|
40
41
|
this.attachShadow({ mode: 'open' });
|
|
41
42
|
}
|
|
42
43
|
|
|
@@ -55,7 +56,7 @@ export default class FxControl extends XfAbstractControl {
|
|
|
55
56
|
};
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
|
|
59
|
+
_getValueOfWidget() {
|
|
59
60
|
if (this.valueProp === 'selectedOptions') {
|
|
60
61
|
// We have multiple! Just return that as space-separated for now
|
|
61
62
|
return [...this.widget.selectedOptions].map(option => option.value).join(' ');
|
|
@@ -75,10 +76,25 @@ export default class FxControl extends XfAbstractControl {
|
|
|
75
76
|
: 'blur';
|
|
76
77
|
this.label = this.hasAttribute('label') ? this.getAttribute('label') : null;
|
|
77
78
|
const style = `
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
:host {
|
|
80
|
+
display: flex;
|
|
81
|
+
align-items: center;
|
|
82
|
+
gap: 0.4em;
|
|
83
|
+
position: relative;
|
|
84
|
+
}
|
|
85
|
+
.trash {
|
|
86
|
+
position: absolute;
|
|
87
|
+
right:0;
|
|
88
|
+
top:0;
|
|
89
|
+
cursor: pointer;
|
|
90
|
+
color: #888;
|
|
91
|
+
font-size: 0.65rem;
|
|
92
|
+
align-self: center;
|
|
93
|
+
}
|
|
94
|
+
.trash:hover {
|
|
95
|
+
color: red;
|
|
96
|
+
}
|
|
97
|
+
`;
|
|
82
98
|
|
|
83
99
|
this.credentials = this.hasAttribute('credentials')
|
|
84
100
|
? this.getAttribute('credentials')
|
|
@@ -135,7 +151,7 @@ export default class FxControl extends XfAbstractControl {
|
|
|
135
151
|
// console.info('handling Event:', event.type, listenOn);
|
|
136
152
|
// Cancel the default action, if needed
|
|
137
153
|
event.preventDefault();
|
|
138
|
-
this.setValue(this.
|
|
154
|
+
this.setValue(this._getValueOfWidget());
|
|
139
155
|
}
|
|
140
156
|
});
|
|
141
157
|
this.updateEvent = 'blur'; // needs to be registered too
|
|
@@ -148,47 +164,26 @@ export default class FxControl extends XfAbstractControl {
|
|
|
148
164
|
() => {
|
|
149
165
|
// console.log('eventlistener ', this.updateEvent);
|
|
150
166
|
// console.info('handling Event:', event.type, listenOn);
|
|
151
|
-
this.setValue(this.
|
|
167
|
+
this.setValue(this._getValueOfWidget());
|
|
152
168
|
},
|
|
153
169
|
this.debounceDelay,
|
|
154
170
|
),
|
|
155
171
|
);
|
|
156
172
|
} else {
|
|
157
173
|
listenOn.addEventListener(this.updateEvent, event => {
|
|
158
|
-
this.setValue(this.
|
|
174
|
+
this.setValue(this._getValueOfWidget());
|
|
159
175
|
});
|
|
160
176
|
listenOn.addEventListener(
|
|
161
177
|
'blur',
|
|
162
178
|
event => {
|
|
163
|
-
this.setValue(this.
|
|
179
|
+
this.setValue(this._getValueOfWidget());
|
|
164
180
|
},
|
|
165
181
|
{ once: true },
|
|
166
182
|
);
|
|
167
183
|
}
|
|
168
184
|
|
|
169
185
|
this.addEventListener('return', e => {
|
|
170
|
-
// console.log('catched return action on ', this);
|
|
171
|
-
// console.log('return detail', e.detail);
|
|
172
|
-
|
|
173
|
-
// console.log('return triggered on ', this);
|
|
174
|
-
// console.log('this.ref', this.ref);
|
|
175
|
-
// console.log('current outer instance', this.getInstance());
|
|
176
|
-
|
|
177
|
-
/*
|
|
178
|
-
console.log(
|
|
179
|
-
'???? why ???? current nodeset should point to the node of the outer control',
|
|
180
|
-
e.currentTarget.nodeset,
|
|
181
|
-
);
|
|
182
|
-
console.log(
|
|
183
|
-
'???? why ???? current nodeset should point to the node of the outer control',
|
|
184
|
-
this.nodeset,
|
|
185
|
-
);
|
|
186
|
-
*/
|
|
187
186
|
const newNodes = e.detail.nodeset;
|
|
188
|
-
// console.log('new nodeset', newNodes);
|
|
189
|
-
// console.log('currentTarget', e.currentTarget);
|
|
190
|
-
// console.log('target', e.target);
|
|
191
|
-
|
|
192
187
|
e.stopPropagation();
|
|
193
188
|
|
|
194
189
|
this._replaceNode(newNodes);
|
|
@@ -205,7 +200,22 @@ export default class FxControl extends XfAbstractControl {
|
|
|
205
200
|
this.template = this.querySelector('template');
|
|
206
201
|
this.boundInitialized = false;
|
|
207
202
|
this.static = !!this.widget.hasAttribute('static');
|
|
208
|
-
|
|
203
|
+
super.connectedCallback();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* activates a control that uses 'on-demand' attribute
|
|
208
|
+
*/
|
|
209
|
+
activate() {
|
|
210
|
+
console.log('fx-control.activate() called');
|
|
211
|
+
this.removeAttribute('on-demand');
|
|
212
|
+
this.style.display = '';
|
|
213
|
+
this.refresh(true);
|
|
214
|
+
Fore.dispatch(this, 'show-control', {});
|
|
215
|
+
// Focus the widget after the control becomes visible
|
|
216
|
+
requestAnimationFrame(() => {
|
|
217
|
+
this.getWidget()?.focus();
|
|
218
|
+
});
|
|
209
219
|
}
|
|
210
220
|
|
|
211
221
|
_debounce(func, timeout = 300) {
|
|
@@ -259,7 +269,7 @@ export default class FxControl extends XfAbstractControl {
|
|
|
259
269
|
this.modelItem.boundControls.push(this);
|
|
260
270
|
}
|
|
261
271
|
|
|
262
|
-
setval.actionPerformed();
|
|
272
|
+
setval.actionPerformed(false);
|
|
263
273
|
// this.visited = true;
|
|
264
274
|
}
|
|
265
275
|
|
|
@@ -280,9 +290,11 @@ export default class FxControl extends XfAbstractControl {
|
|
|
280
290
|
}
|
|
281
291
|
|
|
282
292
|
renderHTML(ref) {
|
|
293
|
+
const showTrash = this.hasAttribute('on-demand');
|
|
294
|
+
const showIcon = this.closest('[show-icon]');
|
|
283
295
|
return `
|
|
284
296
|
${this.label ? `${this.label}` : ''}
|
|
285
|
-
|
|
297
|
+
<slot></slot>
|
|
286
298
|
${
|
|
287
299
|
this.hasAttribute('as') && this.getAttribute('as') === 'node'
|
|
288
300
|
? '<fx-replace id="replace" ref=".">'
|
|
@@ -331,6 +343,13 @@ export default class FxControl extends XfAbstractControl {
|
|
|
331
343
|
*/
|
|
332
344
|
async updateWidgetValue() {
|
|
333
345
|
// this._getValueFromHtmlDom() = this.value;
|
|
346
|
+
if (this.value) {
|
|
347
|
+
// Fire and forget this: the value is set right away anyway
|
|
348
|
+
this.setAttribute('has-value', '');
|
|
349
|
+
Fore.dispatch(this, 'show-control');
|
|
350
|
+
} else {
|
|
351
|
+
this.removeAttribute('has-value');
|
|
352
|
+
}
|
|
334
353
|
|
|
335
354
|
let { widget } = this;
|
|
336
355
|
if (!widget) {
|
|
@@ -347,9 +366,9 @@ export default class FxControl extends XfAbstractControl {
|
|
|
347
366
|
}
|
|
348
367
|
|
|
349
368
|
// ### value is bound to radio
|
|
350
|
-
if(this.widget.type === 'radio'){
|
|
369
|
+
if (this.widget.type === 'radio') {
|
|
351
370
|
const matches = this.querySelector(`input[value=${this.value}]`);
|
|
352
|
-
if(matches){
|
|
371
|
+
if (matches) {
|
|
353
372
|
matches.checked = true;
|
|
354
373
|
}
|
|
355
374
|
return;
|
|
@@ -367,7 +386,6 @@ export default class FxControl extends XfAbstractControl {
|
|
|
367
386
|
return;
|
|
368
387
|
}
|
|
369
388
|
|
|
370
|
-
|
|
371
389
|
if (this.hasAttribute('as')) {
|
|
372
390
|
const as = this.getAttribute('as');
|
|
373
391
|
|
|
@@ -630,24 +648,29 @@ export default class FxControl extends XfAbstractControl {
|
|
|
630
648
|
updateEntry(newEntry, node) {
|
|
631
649
|
// ### >>> todo: needs rework this code is heavily assuming a select control with 'value' attribute - not generic at all yet.
|
|
632
650
|
|
|
633
|
-
|
|
634
|
-
const valueAttribute = this._getValueAttribute(newEntry);
|
|
651
|
+
const valueAttribute = newEntry.getAttribute('value');
|
|
635
652
|
if (!valueAttribute) {
|
|
636
653
|
// Fore.dispatch(this,'warn',{message:'no value attribute specified for template entry.'});
|
|
637
654
|
return;
|
|
638
655
|
}
|
|
639
656
|
|
|
640
|
-
const valueExpr = valueAttribute
|
|
657
|
+
const valueExpr = valueAttribute;
|
|
641
658
|
const cutted = valueExpr.substring(1, valueExpr.length - 1);
|
|
642
659
|
const evaluated = evaluateXPathToString(cutted, node, newEntry);
|
|
643
|
-
|
|
660
|
+
newEntry.setAttribute('value', evaluated);
|
|
644
661
|
|
|
645
662
|
if (this.value === evaluated) {
|
|
646
663
|
newEntry.setAttribute('selected', 'selected');
|
|
647
664
|
}
|
|
648
665
|
|
|
666
|
+
if (newEntry.hasAttribute('title')) {
|
|
667
|
+
let titleExpr = newEntry.getAttribute('title');
|
|
668
|
+
titleExpr = titleExpr.substring(1, titleExpr.length - 1);
|
|
669
|
+
const evaluated = evaluateXPathToString(titleExpr, node, newEntry);
|
|
670
|
+
newEntry.setAttribute('title', evaluated);
|
|
671
|
+
}
|
|
649
672
|
// ### set label
|
|
650
|
-
const optionLabel = newEntry.textContent;
|
|
673
|
+
const optionLabel = newEntry.textContent.trim();
|
|
651
674
|
this.evalLabel(optionLabel, node, newEntry);
|
|
652
675
|
// ### <<< needs rework
|
|
653
676
|
}
|
package/src/ui/fx-dialog.js
CHANGED
|
@@ -61,7 +61,7 @@ export class FxDialog extends HTMLElement {
|
|
|
61
61
|
`;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
showModal() {
|
|
65
65
|
window.addEventListener(
|
|
66
66
|
'keyup',
|
|
67
67
|
e => {
|
|
@@ -75,7 +75,7 @@ export class FxDialog extends HTMLElement {
|
|
|
75
75
|
this.classList.add('show');
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
async
|
|
78
|
+
async close() {
|
|
79
79
|
await Fore.fadeOutElement(this, 400);
|
|
80
80
|
this.classList.remove('show');
|
|
81
81
|
}
|
package/src/ui/fx-group.js
CHANGED
|
@@ -95,6 +95,20 @@ class FxGroup extends FxContainer {
|
|
|
95
95
|
// context item
|
|
96
96
|
Fore.refreshChildren(this, !!force);
|
|
97
97
|
}
|
|
98
|
+
|
|
99
|
+
// todo: this code should go
|
|
100
|
+
/**
|
|
101
|
+
* activates a control that uses 'on-demand' attribute
|
|
102
|
+
*/
|
|
103
|
+
activate() {
|
|
104
|
+
console.log('fx-group.activate() called');
|
|
105
|
+
this.removeAttribute('on-demand');
|
|
106
|
+
this.style.display = '';
|
|
107
|
+
if (this.isBound()) {
|
|
108
|
+
this.refresh(true);
|
|
109
|
+
}
|
|
110
|
+
Fore.dispatch(this, 'show-group', {});
|
|
111
|
+
}
|
|
98
112
|
}
|
|
99
113
|
|
|
100
114
|
if (!customElements.get('fx-group')) {
|
package/src/ui/fx-repeat.js
CHANGED
|
@@ -6,6 +6,7 @@ import { evaluateXPath } from '../xpath-evaluation.js';
|
|
|
6
6
|
import getInScopeContext from '../getInScopeContext.js';
|
|
7
7
|
import { XPathUtil } from '../xpath-util.js';
|
|
8
8
|
import { withDraggability } from '../withDraggability.js';
|
|
9
|
+
import { UIElement } from './UIElement.js';
|
|
9
10
|
|
|
10
11
|
// import {DependencyNotifyingDomFacade} from '../DependencyNotifyingDomFacade';
|
|
11
12
|
|
|
@@ -25,7 +26,7 @@ import { withDraggability } from '../withDraggability.js';
|
|
|
25
26
|
* todo: it should be seriously be considered to extend FxContainer instead but needs refactoring first.
|
|
26
27
|
* @extends {ForeElementMixin}
|
|
27
28
|
*/
|
|
28
|
-
export class FxRepeat extends withDraggability(
|
|
29
|
+
export class FxRepeat extends withDraggability(UIElement, false) {
|
|
29
30
|
static get properties() {
|
|
30
31
|
return {
|
|
31
32
|
...super.properties,
|
|
@@ -80,7 +81,7 @@ export class FxRepeat extends withDraggability(ForeElementMixin, false) {
|
|
|
80
81
|
const rItems = this.querySelectorAll(':scope > fx-repeatitem');
|
|
81
82
|
this.applyIndex(rItems[this.index - 1]);
|
|
82
83
|
|
|
83
|
-
this.getOwnerForm().refresh({ reason: 'index-function' });
|
|
84
|
+
this.getOwnerForm().refresh({ reason: 'index-function', elementLocalnamesWithChanges: [] });
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
applyIndex(repeatItem) {
|
|
@@ -107,6 +108,7 @@ export class FxRepeat extends withDraggability(ForeElementMixin, false) {
|
|
|
107
108
|
// console.log('connectedCallback',this);
|
|
108
109
|
// this.display = window.getComputedStyle(this, null).getPropertyValue("display");
|
|
109
110
|
this.ref = this.getAttribute('ref');
|
|
111
|
+
this.dependencies.addXPath(this.ref);
|
|
110
112
|
// this.ref = this._getRef();
|
|
111
113
|
// console.log('### fx-repeat connected ', this.id);
|
|
112
114
|
this.addEventListener('item-changed', e => {
|
|
@@ -171,6 +173,7 @@ export class FxRepeat extends withDraggability(ForeElementMixin, false) {
|
|
|
171
173
|
const html = `
|
|
172
174
|
<slot name="header"></slot>
|
|
173
175
|
<slot></slot>
|
|
176
|
+
<slot name="footer"></slot>
|
|
174
177
|
`;
|
|
175
178
|
this.shadowRoot.innerHTML = `
|
|
176
179
|
<style>
|
|
@@ -197,7 +200,7 @@ export class FxRepeat extends withDraggability(ForeElementMixin, false) {
|
|
|
197
200
|
|
|
198
201
|
init() {
|
|
199
202
|
// ### there must be a single 'template' child
|
|
200
|
-
console.log('##### repeat init ', this.id);
|
|
203
|
+
// console.log('##### repeat init ', this.id);
|
|
201
204
|
// if(!this.inited) this.init();
|
|
202
205
|
// does not use this.evalInContext as it is expecting a nodeset instead of single node
|
|
203
206
|
this._evalNodeset();
|
|
@@ -298,6 +301,11 @@ export class FxRepeat extends withDraggability(ForeElementMixin, false) {
|
|
|
298
301
|
|
|
299
302
|
newItem.nodeset = this.nodeset[position - 1];
|
|
300
303
|
newItem.index = position;
|
|
304
|
+
|
|
305
|
+
if (this.getOwnerForm().createNodes) {
|
|
306
|
+
this.getOwnerForm().initData(newItem);
|
|
307
|
+
}
|
|
308
|
+
|
|
301
309
|
// Tell the owner form we might have new template expressions here
|
|
302
310
|
this.getOwnerForm().scanForNewTemplateExpressionsNextRefresh();
|
|
303
311
|
}
|
|
@@ -316,6 +324,7 @@ export class FxRepeat extends withDraggability(ForeElementMixin, false) {
|
|
|
316
324
|
// Fore.refreshChildren(clone,true);
|
|
317
325
|
const fore = this.getOwnerForm();
|
|
318
326
|
if (!fore.lazyRefresh || force) {
|
|
327
|
+
// Turn the possibly conditional force refresh into a forced one: we changed our children
|
|
319
328
|
Fore.refreshChildren(this, force);
|
|
320
329
|
}
|
|
321
330
|
// this.style.display = 'block';
|
|
@@ -393,6 +402,16 @@ export class FxRepeat extends withDraggability(ForeElementMixin, false) {
|
|
|
393
402
|
|
|
394
403
|
this.appendChild(repeatItem);
|
|
395
404
|
|
|
405
|
+
if (this.getOwnerForm().createNodes) {
|
|
406
|
+
this.getOwnerForm().initData(repeatItem);
|
|
407
|
+
const repeatItemClone = repeatItem.nodeset.cloneNode(true);
|
|
408
|
+
this.clearTextValues(repeatItemClone);
|
|
409
|
+
|
|
410
|
+
// this.createdNodeset = repeatItem.nodeset.cloneNode(true);
|
|
411
|
+
this.createdNodeset = repeatItemClone;
|
|
412
|
+
// console.log('createdNodeset', this.createdNodeset)
|
|
413
|
+
}
|
|
414
|
+
|
|
396
415
|
if (repeatItem.index === 1) {
|
|
397
416
|
this.applyIndex(repeatItem);
|
|
398
417
|
}
|
|
@@ -401,6 +420,26 @@ export class FxRepeat extends withDraggability(ForeElementMixin, false) {
|
|
|
401
420
|
this._initVariables(repeatItem);
|
|
402
421
|
});
|
|
403
422
|
}
|
|
423
|
+
clearTextValues(node) {
|
|
424
|
+
if (!node) return;
|
|
425
|
+
|
|
426
|
+
// Clear text node content
|
|
427
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
428
|
+
node.nodeValue = '';
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Clear all attribute values
|
|
432
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
433
|
+
for (const attr of Array.from(node.attributes)) {
|
|
434
|
+
attr.value = ''; // Clear attribute value
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Recursively clear child nodes
|
|
439
|
+
for (const child of node.childNodes) {
|
|
440
|
+
this.clearTextValues(child);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
404
443
|
|
|
405
444
|
_initVariables(newRepeatItem) {
|
|
406
445
|
const inScopeVariables = new Map(this.inScopeVariables);
|