@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.
@@ -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
+ }