@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
package/src/ui/fx-repeatitem.js
CHANGED
|
@@ -8,10 +8,13 @@ import { withDraggability } from '../withDraggability.js';
|
|
|
8
8
|
*
|
|
9
9
|
* @customElement
|
|
10
10
|
* @demo demo/index.html
|
|
11
|
+
*
|
|
12
|
+
* @extends {ForeElementMixin}
|
|
11
13
|
*/
|
|
12
14
|
export class FxRepeatitem extends withDraggability(ForeElementMixin, true) {
|
|
13
15
|
static get properties() {
|
|
14
16
|
return {
|
|
17
|
+
...super.properties,
|
|
15
18
|
inited: {
|
|
16
19
|
type: Boolean,
|
|
17
20
|
},
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import XfAbstractControl from './abstract-control.js';
|
|
2
|
+
import {
|
|
3
|
+
evaluateXPath,
|
|
4
|
+
evaluateXPathToString,
|
|
5
|
+
evaluateXPathToFirstNode,
|
|
6
|
+
} from '../xpath-evaluation.js';
|
|
7
|
+
import getInScopeContext from '../getInScopeContext.js';
|
|
8
|
+
import { Fore } from '../fore.js';
|
|
9
|
+
import { ModelItem } from '../modelitem.js';
|
|
10
|
+
import { debounce } from '../events.js';
|
|
11
|
+
import { FxModel } from '../fx-model.js';
|
|
12
|
+
|
|
13
|
+
const WIDGETCLASS = 'widget';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* `fx-upload` allows to embed uploaded content into XML.
|
|
17
|
+
*
|
|
18
|
+
* @customElement
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
export default class FxUpload extends XfAbstractControl {
|
|
23
|
+
constructor() {
|
|
24
|
+
super();
|
|
25
|
+
this.inited = false;
|
|
26
|
+
this.attachShadow({ mode: 'open' });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static get properties() {
|
|
30
|
+
return {
|
|
31
|
+
...XfAbstractControl.properties,
|
|
32
|
+
accept: {
|
|
33
|
+
type: String,
|
|
34
|
+
},
|
|
35
|
+
fileNameExpr: {
|
|
36
|
+
type: String,
|
|
37
|
+
},
|
|
38
|
+
mimetypeExpr: {
|
|
39
|
+
type: String,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
connectedCallback() {
|
|
44
|
+
|
|
45
|
+
this.updateEvent = 'change';
|
|
46
|
+
this.accept = this.hasAttribute('accept') ? this.getAttribute('accept'):'';
|
|
47
|
+
this.label = this.hasAttribute('label') ? this.getAttribute('label') : null;
|
|
48
|
+
const style = `
|
|
49
|
+
:host{
|
|
50
|
+
display:inline-block;
|
|
51
|
+
}
|
|
52
|
+
`;
|
|
53
|
+
|
|
54
|
+
this.shadowRoot.innerHTML = `
|
|
55
|
+
<style>
|
|
56
|
+
${style}
|
|
57
|
+
</style>
|
|
58
|
+
${this.renderHTML(this.ref)}
|
|
59
|
+
`;
|
|
60
|
+
|
|
61
|
+
this.widget = this.getWidget();
|
|
62
|
+
|
|
63
|
+
this.addEventListener('mousedown', e => {
|
|
64
|
+
// ### prevent mousedown events on all control content that is not the widget or within the widget
|
|
65
|
+
if (!Fore.isWidget(e.target) && !e.target?.classList.contains('fx-hint')) {
|
|
66
|
+
e.preventDefault();
|
|
67
|
+
// e.stopImmediatePropagation();
|
|
68
|
+
}
|
|
69
|
+
this.widget.focus();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// console.log('widget ', this.widget);
|
|
73
|
+
let listenOn = this.widget; // default: usually listening on widget
|
|
74
|
+
|
|
75
|
+
// ### convenience marker event
|
|
76
|
+
if (this.debounceDelay) {
|
|
77
|
+
listenOn.addEventListener(
|
|
78
|
+
this.updateEvent,
|
|
79
|
+
debounce(
|
|
80
|
+
this,
|
|
81
|
+
() => {
|
|
82
|
+
// console.log('eventlistener ', this.updateEvent);
|
|
83
|
+
// console.info('handling Event:', event.type, listenOn);
|
|
84
|
+
this._importUploadedContent();
|
|
85
|
+
},
|
|
86
|
+
this.debounceDelay,
|
|
87
|
+
),
|
|
88
|
+
);
|
|
89
|
+
} else {
|
|
90
|
+
listenOn.addEventListener(this.updateEvent, async event => {
|
|
91
|
+
this._importUploadedContent(event);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
this.boundInitialized = false;
|
|
95
|
+
this.fileNameExpr = this.getAttribute('filename');
|
|
96
|
+
this.mimetypeExpr = this.getAttribute('mimetype');
|
|
97
|
+
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async _importUploadedContent(event) {
|
|
101
|
+
console.log('_importUploadedContent',event);
|
|
102
|
+
const file = event.target.files[0];
|
|
103
|
+
|
|
104
|
+
this.evalInContext();
|
|
105
|
+
// update file ref
|
|
106
|
+
const fileNode = evaluateXPathToFirstNode(this.fileNameExpr,this.nodeset,this.getOwnerForm());
|
|
107
|
+
if(fileNode){
|
|
108
|
+
this.fileName = fileNode.nodeValue = file.name;
|
|
109
|
+
}
|
|
110
|
+
this.mimetype = file.type;
|
|
111
|
+
|
|
112
|
+
// update mediatype
|
|
113
|
+
const mimetypeNode = evaluateXPathToFirstNode(this.mimetypeExpr,this.nodeset, this.getOwnerForm());
|
|
114
|
+
if(mimetypeNode){
|
|
115
|
+
mimetypeNode.nodeValue = this.mimetype;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let content = await this._readFile(file);
|
|
119
|
+
// const setval = this.shadowRoot.getElementById('setvalue');
|
|
120
|
+
console.log('content',content);
|
|
121
|
+
if(file.type.endsWith('xml')){
|
|
122
|
+
const uploadedXML = new DOMParser().parseFromString(content, 'application/xml');
|
|
123
|
+
content = uploadedXML.firstElementChild;
|
|
124
|
+
}
|
|
125
|
+
this.setValue(content);
|
|
126
|
+
// // this.value=content;
|
|
127
|
+
// return content;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async _readFile(file) {
|
|
131
|
+
return new Promise((resolve, reject) => {
|
|
132
|
+
const reader = new FileReader();
|
|
133
|
+
|
|
134
|
+
// Determine how to read the file based on MIME type
|
|
135
|
+
const isTextFile = file.type.startsWith("text/");
|
|
136
|
+
const readMethod = isTextFile ? "readAsText" : "readAsDataURL";
|
|
137
|
+
|
|
138
|
+
reader.onload = () => {
|
|
139
|
+
const result = reader.result;
|
|
140
|
+
// If it's a binary file, return only the Base64 content without the MIME prefix
|
|
141
|
+
if (!isTextFile) {
|
|
142
|
+
const base64Content = result.split(",")[1]; // Remove MIME prefix
|
|
143
|
+
resolve(base64Content);
|
|
144
|
+
} else {
|
|
145
|
+
resolve(result); // Return plain text
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
reader.onerror = () => reject(new Error("Error reading file"));
|
|
150
|
+
|
|
151
|
+
// Start reading the file
|
|
152
|
+
reader[readMethod](file);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* updates the model with a new value by executing it's `<fx-setvalue>` action.
|
|
161
|
+
*
|
|
162
|
+
* In case the `as='node'` is given the bound node is replaced with the widgets' value with is
|
|
163
|
+
* expected to be a node again.
|
|
164
|
+
*
|
|
165
|
+
* @param val the new value to be set
|
|
166
|
+
*/
|
|
167
|
+
setValue(val) {
|
|
168
|
+
this.value=val;
|
|
169
|
+
const modelitem = this.getModelItem();
|
|
170
|
+
|
|
171
|
+
if(this.mimetype.endsWith('xml')){
|
|
172
|
+
this.nodeset.textContent = '';
|
|
173
|
+
this.nodeset.append(document.importNode(val,true));
|
|
174
|
+
}else{
|
|
175
|
+
modelitem.value = val;
|
|
176
|
+
if(this.mimetype.startsWith('text/')){
|
|
177
|
+
const cdata = this.nodeset.ownerDocument.createCDATASection(val);
|
|
178
|
+
this.nodeset.textContent = '';
|
|
179
|
+
this.nodeset.append(cdata);
|
|
180
|
+
} else {
|
|
181
|
+
this.nodeset.textContent = '';
|
|
182
|
+
this.nodeset.append(val);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
if (this.getAttribute('class')) {
|
|
188
|
+
this.classList.add('visited');
|
|
189
|
+
} else {
|
|
190
|
+
this.setAttribute('class', 'visited');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (modelitem?.readonly) {
|
|
194
|
+
console.warn('attempt to change readonly node', modelitem);
|
|
195
|
+
return; // do nothing when modelItem is readonly
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// const setval = this.shadowRoot.getElementById('setvalue');
|
|
199
|
+
// setval.setValue(modelitem, val);
|
|
200
|
+
|
|
201
|
+
if (this.modelItem instanceof ModelItem && !this.modelItem?.boundControls.includes(this)) {
|
|
202
|
+
this.modelItem.boundControls.push(this);
|
|
203
|
+
}
|
|
204
|
+
const model = this.getModel();
|
|
205
|
+
Fore.dispatch(this, 'value-changed', {
|
|
206
|
+
path: this.modelItem.path,
|
|
207
|
+
value: this.modelItem.value,
|
|
208
|
+
oldvalue: "",
|
|
209
|
+
instanceId:this.modelItem.instanceId,
|
|
210
|
+
foreId:this.getOwnerForm().id
|
|
211
|
+
});
|
|
212
|
+
this.getModel().updateModel();
|
|
213
|
+
this.getOwnerForm().refresh(true);
|
|
214
|
+
|
|
215
|
+
// console.log('data', this.getOwnerForm().getModel().getDefaultInstanceData());
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
renderHTML(ref) {
|
|
220
|
+
return `
|
|
221
|
+
${this.label ? `${this.label}` : ''}
|
|
222
|
+
<slot></slot>
|
|
223
|
+
<input type="file" ${this.accept !== '' ? `accept="${this.accept}"` : ''}>
|
|
224
|
+
<fx-setvalue id="setvalue" ref="${ref}"></fx-setvalue>
|
|
225
|
+
`;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* The widget is the actual control being used in the UI e.g. a native input control or any
|
|
230
|
+
* other component that presents a control that can be interacted with.
|
|
231
|
+
*
|
|
232
|
+
* This function returns the widget by querying the children of this control for an element
|
|
233
|
+
* with `class="widget"`. If that cannot be found it searches for an native `input` of any type.
|
|
234
|
+
* If either cannot be found a `<input type="text">` is created.
|
|
235
|
+
*
|
|
236
|
+
* @returns {HTMLElement|*}
|
|
237
|
+
*/
|
|
238
|
+
getWidget() {
|
|
239
|
+
return this.shadowRoot.querySelector('input');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* updates the widget from the modelItem value. During refresh the a control
|
|
244
|
+
* evaluates it's binding expression to determine the bound node. The bound node corresponds
|
|
245
|
+
* to a modelItem which acts a the state object of a node. The modelItem determines the value
|
|
246
|
+
* and the state of the node and set the `value` property of this class.
|
|
247
|
+
*
|
|
248
|
+
* @returns {Promise<void>}
|
|
249
|
+
*/
|
|
250
|
+
async updateWidgetValue() {
|
|
251
|
+
// this._getValueFromHtmlDom() = this.value;
|
|
252
|
+
|
|
253
|
+
let { widget } = this;
|
|
254
|
+
if (!widget) {
|
|
255
|
+
widget = this;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (widget.value !== this.value) {
|
|
259
|
+
// widget.value = this.value;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async refresh(force) {
|
|
264
|
+
// console.log('fx-control refresh', this);
|
|
265
|
+
super.refresh(force);
|
|
266
|
+
// ### if we find a ref on control we have a 'select' control of some kind
|
|
267
|
+
Fore.refreshChildren(this, force);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
evalLabel(optionLabel, node, newEntry) {
|
|
273
|
+
const labelExpr = optionLabel.substring(1, optionLabel.length - 1);
|
|
274
|
+
if (!labelExpr) return;
|
|
275
|
+
|
|
276
|
+
const label = evaluateXPathToString(labelExpr, node, this);
|
|
277
|
+
newEntry.textContent = label;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
createEntry() {
|
|
281
|
+
return this.template.content.firstElementChild.cloneNode(true);
|
|
282
|
+
// const content = this.template.content.firstElementChild.cloneNode(true);
|
|
283
|
+
// return content;
|
|
284
|
+
// const newEntry = document.importNode(content, true);
|
|
285
|
+
// this.template.parentNode.appendChild(newEntry);
|
|
286
|
+
// return newEntry;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// eslint-disable-next-line class-methods-use-this
|
|
290
|
+
_getValueAttribute(element) {
|
|
291
|
+
let result;
|
|
292
|
+
Array.from(element.attributes).forEach(attribute => {
|
|
293
|
+
const attrVal = attribute.value;
|
|
294
|
+
if (attrVal.indexOf('{') !== -1) {
|
|
295
|
+
// console.log('avt found ', attribute);
|
|
296
|
+
result = attribute;
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
return result;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (!customElements.get('fx-upload')) {
|
|
303
|
+
window.customElements.define('fx-upload', FxUpload);
|
|
304
|
+
}
|