@jinntec/fore 2.7.1 → 2.8.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 +3960 -3721
- package/dist/fore.js +12642 -16281
- package/package.json +1 -1
- package/resources/fore.css +3 -0
- package/src/ForeElementMixin.js +4 -4
- package/src/fx-bind.js +1 -1
- package/src/fx-fore.js +323 -164
- package/src/fx-instance.js +2 -2
- package/src/fx-submission.js +24 -10
- package/src/modelitem.js +5 -5
- package/src/tools/deprecation.md +1 -0
- package/src/tools/fx-action-log.js +2 -2
- package/src/ui/abstract-control.js +49 -16
- package/src/ui/fx-control.js +42 -21
- package/src/ui/fx-output.js +1 -2
- package/src/ui/fx-repeat.js +7 -5
- package/src/xpath-evaluation.js +8 -1
- package/src/xpath-util.js +3 -3
- package/src/DataObserver.js +0 -181
package/src/fx-instance.js
CHANGED
|
@@ -163,10 +163,10 @@ export class FxInstance extends HTMLElement {
|
|
|
163
163
|
// Note: use the getter here: it might provide us with stubbed data if anything async is racing,
|
|
164
164
|
// such as an @src attribute
|
|
165
165
|
const instanceData = this.getInstanceData();
|
|
166
|
-
if (this.type === 'xml') {
|
|
166
|
+
if (this.type === 'xml' || this.type === 'html') {
|
|
167
167
|
return instanceData.firstElementChild;
|
|
168
168
|
}
|
|
169
|
-
return instanceData;
|
|
169
|
+
return this.instanceData;
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
/**
|
package/src/fx-submission.js
CHANGED
|
@@ -250,21 +250,17 @@ export class FxSubmission extends ForeElementMixin {
|
|
|
250
250
|
return;
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
-
const contentType = response.headers
|
|
254
|
-
|
|
255
|
-
.
|
|
256
|
-
.
|
|
257
|
-
.
|
|
258
|
-
if (contentType.startsWith('text/')) {
|
|
253
|
+
const contentType = response.headers.get('content-type').split(';')[0].trim().toLowerCase();
|
|
254
|
+
if (contentType.endsWith('/xml') || contentType.endsWith('+xml')) {
|
|
255
|
+
const text = await response.text();
|
|
256
|
+
const xml = new DOMParser().parseFromString(text, 'application/xml');
|
|
257
|
+
this._handleResponse(xml, resolvedUrl, contentType);
|
|
258
|
+
} else if (contentType.startsWith('text/')) {
|
|
259
259
|
const text = await response.text();
|
|
260
260
|
this._handleResponse(text, resolvedUrl, contentType);
|
|
261
261
|
} else if (contentType.endsWith('/json') || contentType.endsWith('+json')) {
|
|
262
262
|
const json = await response.json();
|
|
263
263
|
this._handleResponse(json, resolvedUrl, contentType);
|
|
264
|
-
} else if (contentType.endsWith('/xml') || contentType.endsWith('+xml')) {
|
|
265
|
-
const text = await response.text();
|
|
266
|
-
const xml = new DOMParser().parseFromString(text, 'application/xml');
|
|
267
|
-
this._handleResponse(xml, resolvedUrl, contentType);
|
|
268
264
|
} else {
|
|
269
265
|
const blob = await response.blob();
|
|
270
266
|
this._handleResponse(blob, resolvedUrl, contentType);
|
|
@@ -381,6 +377,24 @@ export class FxSubmission extends ForeElementMixin {
|
|
|
381
377
|
const targetInstance = this._getTargetInstance();
|
|
382
378
|
|
|
383
379
|
if (this.replace === 'instance') {
|
|
380
|
+
const targetInstance = this._getTargetInstance();
|
|
381
|
+
|
|
382
|
+
// ### contentType handling
|
|
383
|
+
|
|
384
|
+
if (contentType.includes('html')) {
|
|
385
|
+
let effectiveData;
|
|
386
|
+
if (data.nodeType) {
|
|
387
|
+
effectiveData = data;
|
|
388
|
+
}
|
|
389
|
+
// ## try parsing
|
|
390
|
+
try {
|
|
391
|
+
effectiveData = new DOMParser().parseFromString(data, 'text/html');
|
|
392
|
+
} catch {
|
|
393
|
+
Fore.dispatch(this, 'error', { message: 'could not parse data as HTML' });
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
targetInstance.instanceData = effectiveData;
|
|
397
|
+
}
|
|
384
398
|
if (targetInstance) {
|
|
385
399
|
if (this.targetref) {
|
|
386
400
|
const [theTarget] = evaluateXPath(
|
package/src/modelitem.js
CHANGED
|
@@ -71,13 +71,13 @@ export class ModelItem {
|
|
|
71
71
|
if (!this.node) return;
|
|
72
72
|
const oldVal = this.value;
|
|
73
73
|
|
|
74
|
-
if (newVal?.nodeType === Node.DOCUMENT_NODE) {
|
|
74
|
+
if (newVal?.nodeType && newVal.nodeType === Node.DOCUMENT_NODE) {
|
|
75
75
|
this.node.replaceWith(newVal.firstElementChild);
|
|
76
|
-
this.node
|
|
77
|
-
} else if (newVal?.nodeType === Node.ELEMENT_NODE) {
|
|
76
|
+
// this.node.appendChild(newVal.firstElementChild);
|
|
77
|
+
} else if (newVal?.nodeType && newVal.nodeType === Node.ELEMENT_NODE) {
|
|
78
78
|
this.node.replaceWith(newVal);
|
|
79
|
-
this.node
|
|
80
|
-
} else if (this.node.nodeType === Node.ATTRIBUTE_NODE) {
|
|
79
|
+
// this.node.appendChild(newVal);
|
|
80
|
+
} else if (newVal?.nodeType && this.node.nodeType === Node.ATTRIBUTE_NODE) {
|
|
81
81
|
this.node.nodeValue = newVal;
|
|
82
82
|
} else {
|
|
83
83
|
this.node.textContent = newVal;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
This package is deprecated and no longer maintained or included in index.js
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { XPathUtil } from '../xpath-util';
|
|
1
|
+
import { XPathUtil } from '../xpath-util.js';
|
|
2
2
|
import './fx-log-item.js';
|
|
3
3
|
import { FxLogSettings } from './fx-log-settings.js';
|
|
4
4
|
import { getDocPath, getPath } from '../xpath-path.js';
|
|
@@ -521,7 +521,7 @@ export class FxActionLog extends HTMLElement {
|
|
|
521
521
|
*/
|
|
522
522
|
_logDetails(e) {
|
|
523
523
|
const eventType = e.type;
|
|
524
|
-
const path =
|
|
524
|
+
const path = getPath(e.target, '');
|
|
525
525
|
// console.log('>>>> _logDetails', path);
|
|
526
526
|
const cut = path.substring(path.indexOf('/fx-fore'), path.length);
|
|
527
527
|
const xpath = `/${cut}`;
|
|
@@ -286,6 +286,10 @@ export default class AbstractControl extends UIElement {
|
|
|
286
286
|
}
|
|
287
287
|
|
|
288
288
|
_toggleValid(valid) {
|
|
289
|
+
// Used by required handling (and potentially other callers).
|
|
290
|
+
// It must also fire validity events and sync aria-invalid.
|
|
291
|
+
const wasInvalid = this.hasAttribute('invalid');
|
|
292
|
+
|
|
289
293
|
if (valid) {
|
|
290
294
|
this.removeAttribute('invalid');
|
|
291
295
|
this.setAttribute('valid', '');
|
|
@@ -293,6 +297,25 @@ export default class AbstractControl extends UIElement {
|
|
|
293
297
|
this.removeAttribute('valid');
|
|
294
298
|
this.setAttribute('invalid', '');
|
|
295
299
|
}
|
|
300
|
+
this._syncAriaInvalid();
|
|
301
|
+
|
|
302
|
+
const isInvalid = this.hasAttribute('invalid');
|
|
303
|
+
// Only dispatch when the state actually changed
|
|
304
|
+
if (wasInvalid !== isInvalid) {
|
|
305
|
+
this._dispatchEvent(isInvalid ? 'invalid' : 'valid');
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
_syncAriaInvalid() {
|
|
310
|
+
// Keep widget aria-invalid in sync with the *control* state, regardless of
|
|
311
|
+
// whether invalidity comes from constraint, required emptiness, etc.
|
|
312
|
+
try {
|
|
313
|
+
const w = this.getWidget?.() || this.widget;
|
|
314
|
+
if (!w) return;
|
|
315
|
+
w.setAttribute('aria-invalid', this.hasAttribute('invalid') ? 'true' : 'false');
|
|
316
|
+
} catch (e) {
|
|
317
|
+
// ignore: widget might not exist yet
|
|
318
|
+
}
|
|
296
319
|
}
|
|
297
320
|
|
|
298
321
|
handleReadonly() {
|
|
@@ -321,8 +344,8 @@ export default class AbstractControl extends UIElement {
|
|
|
321
344
|
// if (alert) alert.style.display = 'none';
|
|
322
345
|
this._dispatchEvent('valid');
|
|
323
346
|
this.setAttribute('valid', '');
|
|
324
|
-
this.getWidget().setAttribute('aria-invalid', 'false');
|
|
325
347
|
this.removeAttribute('invalid');
|
|
348
|
+
this.getWidget().setAttribute('aria-invalid', 'false');
|
|
326
349
|
} else {
|
|
327
350
|
this.setAttribute('invalid', '');
|
|
328
351
|
this.getWidget().setAttribute('aria-invalid', 'true');
|
|
@@ -352,31 +375,41 @@ export default class AbstractControl extends UIElement {
|
|
|
352
375
|
this._dispatchEvent('invalid');
|
|
353
376
|
}
|
|
354
377
|
}
|
|
378
|
+
|
|
379
|
+
// Ensure aria-invalid matches the current control state even if
|
|
380
|
+
// we didn't enter the state-change branch above.
|
|
381
|
+
this._syncAriaInvalid();
|
|
382
|
+
|
|
355
383
|
}
|
|
356
384
|
|
|
357
385
|
handleRelevant() {
|
|
358
|
-
//
|
|
386
|
+
// IMPORTANT: don't clear relevant/nonrelevant BEFORE comparing states.
|
|
387
|
+
// Otherwise isEnabled() (based on attributes) always reads as "enabled"
|
|
388
|
+
// and we can never detect a transition back to relevant.
|
|
359
389
|
const item = this.modelItem.node;
|
|
360
|
-
|
|
361
|
-
this.
|
|
390
|
+
|
|
391
|
+
const wasEnabled = this.isEnabled();
|
|
392
|
+
|
|
393
|
+
// Determine new enabled state
|
|
394
|
+
let newEnabled = !!this.modelItem.relevant;
|
|
395
|
+
|
|
396
|
+
// If a nodeset resolves to an empty array, treat the control as nonrelevant
|
|
362
397
|
if (Array.isArray(item) && item.length === 0) {
|
|
363
|
-
|
|
364
|
-
this.setAttribute('nonrelevant', '');
|
|
365
|
-
// this.style.display = 'none';
|
|
366
|
-
return;
|
|
398
|
+
newEnabled = false;
|
|
367
399
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
// this._fadeIn(this, this.display);
|
|
400
|
+
|
|
401
|
+
// Apply attributes
|
|
402
|
+
if (newEnabled) {
|
|
372
403
|
this.setAttribute('relevant', '');
|
|
373
|
-
|
|
404
|
+
this.removeAttribute('nonrelevant');
|
|
374
405
|
} else {
|
|
375
|
-
this._dispatchEvent('nonrelevant');
|
|
376
|
-
// this._fadeOut(this);
|
|
377
406
|
this.setAttribute('nonrelevant', '');
|
|
378
|
-
|
|
407
|
+
this.removeAttribute('relevant');
|
|
379
408
|
}
|
|
409
|
+
|
|
410
|
+
// Dispatch only on actual change
|
|
411
|
+
if (wasEnabled !== newEnabled) {
|
|
412
|
+
this._dispatchEvent(newEnabled ? 'relevant' : 'nonrelevant');
|
|
380
413
|
}
|
|
381
414
|
}
|
|
382
415
|
|
package/src/ui/fx-control.js
CHANGED
|
@@ -3,11 +3,9 @@ import {
|
|
|
3
3
|
evaluateXPath,
|
|
4
4
|
evaluateXPathToString,
|
|
5
5
|
evaluateXPathToFirstNode,
|
|
6
|
-
evaluateXPathToBoolean,
|
|
7
6
|
} from '../xpath-evaluation.js';
|
|
8
7
|
import getInScopeContext from '../getInScopeContext.js';
|
|
9
8
|
import { Fore } from '../fore.js';
|
|
10
|
-
import { ModelItem } from '../modelitem.js';
|
|
11
9
|
import { debounce } from '../events.js';
|
|
12
10
|
import { FxModel } from '../fx-model.js';
|
|
13
11
|
import { DependencyNotifyingDomFacade } from '../DependencyNotifyingDomFacade';
|
|
@@ -42,6 +40,11 @@ export default class FxControl extends XfAbstractControl {
|
|
|
42
40
|
this.inited = false;
|
|
43
41
|
this.nodeset = null;
|
|
44
42
|
this.attachShadow({ mode: 'open' });
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Flag that is raised while refreshing, to ignore any updates from the widget inside of us
|
|
46
|
+
*/
|
|
47
|
+
this._isRefreshing = false;
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
static get properties() {
|
|
@@ -64,6 +67,13 @@ export default class FxControl extends XfAbstractControl {
|
|
|
64
67
|
// We have multiple! Just return that as space-separated for now
|
|
65
68
|
return [...this.widget.selectedOptions].map(option => option.value).join(' ');
|
|
66
69
|
}
|
|
70
|
+
if (this.getAttribute('as') === 'xml') {
|
|
71
|
+
// We are setting serialized XML here, so when roundtripping, parse it
|
|
72
|
+
const value = this.widget[this.valueProp];
|
|
73
|
+
const parser = new DOMParser();
|
|
74
|
+
const doc = parser.parseFromString(value, 'application/xml');
|
|
75
|
+
return doc.documentElement;
|
|
76
|
+
}
|
|
67
77
|
return this.widget[this.valueProp];
|
|
68
78
|
}
|
|
69
79
|
|
|
@@ -174,6 +184,10 @@ export default class FxControl extends XfAbstractControl {
|
|
|
174
184
|
);
|
|
175
185
|
} else {
|
|
176
186
|
listenOn.addEventListener(this.updateEvent, event => {
|
|
187
|
+
if (this._isRefreshing) {
|
|
188
|
+
// We are refreshing. No use in updating
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
177
191
|
this.setValue(this._getValueOfWidget());
|
|
178
192
|
});
|
|
179
193
|
listenOn.addEventListener(
|
|
@@ -210,7 +224,7 @@ export default class FxControl extends XfAbstractControl {
|
|
|
210
224
|
* activates a control that uses 'on-demand' attribute
|
|
211
225
|
*/
|
|
212
226
|
activate() {
|
|
213
|
-
console.log('fx-control.activate() called');
|
|
227
|
+
// console.log('fx-control.activate() called');
|
|
214
228
|
this.removeAttribute('on-demand');
|
|
215
229
|
this.style.display = '';
|
|
216
230
|
this.refresh(true);
|
|
@@ -241,7 +255,7 @@ export default class FxControl extends XfAbstractControl {
|
|
|
241
255
|
* @param val the new value to be set
|
|
242
256
|
*/
|
|
243
257
|
setValue(val) {
|
|
244
|
-
console.log('Control.setValue', val, 'on', this);
|
|
258
|
+
// console.log('Control.setValue', val, 'on', this);
|
|
245
259
|
const modelitem = this.getModelItem();
|
|
246
260
|
|
|
247
261
|
if (this.getAttribute('class')) {
|
|
@@ -257,10 +271,9 @@ export default class FxControl extends XfAbstractControl {
|
|
|
257
271
|
|
|
258
272
|
if (this.getAttribute('as') === 'node') {
|
|
259
273
|
const replace = this.shadowRoot.getElementById('replace');
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
modelitem.value = widgetValue;
|
|
274
|
+
replace.replace(this.nodeset, val);
|
|
275
|
+
if (modelitem && val && val !== modelitem.value) {
|
|
276
|
+
modelitem.value = val;
|
|
264
277
|
FxModel.dataChanged = true;
|
|
265
278
|
replace.actionPerformed();
|
|
266
279
|
}
|
|
@@ -398,11 +411,14 @@ export default class FxControl extends XfAbstractControl {
|
|
|
398
411
|
if (this.hasAttribute('as')) {
|
|
399
412
|
const as = this.getAttribute('as');
|
|
400
413
|
|
|
401
|
-
// ### when there's an `as=
|
|
402
|
-
if (as === '
|
|
414
|
+
// ### when there's an `as="xml"` attribute serialize nodeset to prettified string
|
|
415
|
+
if (as === 'xml') {
|
|
403
416
|
const serializer = new XMLSerializer();
|
|
404
|
-
const pretty =
|
|
405
|
-
widget.
|
|
417
|
+
const pretty = serializer.serializeToString(this.nodeset);
|
|
418
|
+
if (widget[this.valueProp] === pretty) {
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
widget[this.valueProp] = pretty;
|
|
406
422
|
}
|
|
407
423
|
if (as === 'node' && this.nodeset !== widget.value) {
|
|
408
424
|
// const oldVal = this.nodeset.innerHTML;
|
|
@@ -547,15 +563,20 @@ export default class FxControl extends XfAbstractControl {
|
|
|
547
563
|
}
|
|
548
564
|
|
|
549
565
|
async refresh(force = false) {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
566
|
+
try {
|
|
567
|
+
this._isRefreshing = true;
|
|
568
|
+
// console.log('🔄 fx-control refresh', this);
|
|
569
|
+
super.refresh(force);
|
|
570
|
+
// console.log('refresh template', this.template);
|
|
571
|
+
// const {widget} = this;
|
|
572
|
+
|
|
573
|
+
// ### if we find a ref on control we have a 'select' control of some kind
|
|
574
|
+
const widget = this.getWidget();
|
|
575
|
+
this._handleBoundWidget(widget, force);
|
|
576
|
+
this._handleDataAttributeBinding();
|
|
577
|
+
} finally {
|
|
578
|
+
this._isRefreshing = false;
|
|
579
|
+
}
|
|
559
580
|
Fore.refreshChildren(this, force);
|
|
560
581
|
}
|
|
561
582
|
|
package/src/ui/fx-output.js
CHANGED
|
@@ -2,7 +2,6 @@ import { Fore } from '../fore.js';
|
|
|
2
2
|
import XfAbstractControl from './abstract-control.js';
|
|
3
3
|
import { evaluateXPath, evaluateXPathToStrings } from '../xpath-evaluation.js';
|
|
4
4
|
import getInScopeContext from '../getInScopeContext.js';
|
|
5
|
-
// import {markdown} from '../drawdown.js';
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* todo: review placing of value. should probably work with value attribute and not allow slotted content.
|
|
@@ -46,7 +45,7 @@ export class FxOutput extends XfAbstractControl {
|
|
|
46
45
|
|
|
47
46
|
const outputHtml = `
|
|
48
47
|
<slot name="label"></slot>
|
|
49
|
-
|
|
48
|
+
|
|
50
49
|
<span id="value">
|
|
51
50
|
<slot name="default"></slot>
|
|
52
51
|
</span>
|
package/src/ui/fx-repeat.js
CHANGED
|
@@ -632,11 +632,13 @@ export class FxRepeat extends withDraggability(UIElement, false) {
|
|
|
632
632
|
|
|
633
633
|
if (this.getOwnerForm().createNodes) {
|
|
634
634
|
this.getOwnerForm().initData(repeatItem);
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
635
|
+
if (repeatItem.nodeset.nodeType) {
|
|
636
|
+
// Do not try to d things with repeats that do not reason over nodes
|
|
637
|
+
const repeatItemClone = repeatItem.nodeset.cloneNode(true);
|
|
638
|
+
this.clearTextValues(repeatItemClone);
|
|
639
|
+
// this.createdNodeset = repeatItem.nodeset.cloneNode(true);
|
|
640
|
+
this.createdNodeset = repeatItemClone;
|
|
641
|
+
}
|
|
640
642
|
// console.log('createdNodeset', this.createdNodeset)
|
|
641
643
|
}
|
|
642
644
|
|
package/src/xpath-evaluation.js
CHANGED
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
|
|
14
14
|
import { XPathUtil } from './xpath-util.js';
|
|
15
15
|
import { prettifyXml } from './functions/common-function.js';
|
|
16
|
-
import * as fx from 'fontoxpath';
|
|
17
16
|
|
|
18
17
|
const XFORMS_NAMESPACE_URI = 'http://www.w3.org/2002/xforms';
|
|
19
18
|
|
|
@@ -489,6 +488,7 @@ export function evaluateXPath(xpath, contextNode, formElement, variables = {}, o
|
|
|
489
488
|
{ ...variablesInScope, ...variables },
|
|
490
489
|
fxEvaluateXPath.ALL_RESULTS_TYPE,
|
|
491
490
|
{
|
|
491
|
+
xmlSerializer: new XMLSerializer(),
|
|
492
492
|
debug: true,
|
|
493
493
|
currentContext: { formElement, variables },
|
|
494
494
|
moduleImports: {
|
|
@@ -553,6 +553,7 @@ export function evaluateXPathToFirstNode(xpath, contextNode, formElement) {
|
|
|
553
553
|
currentContext: { formElement },
|
|
554
554
|
functionNameResolver,
|
|
555
555
|
namespaceResolver,
|
|
556
|
+
xmlSerializer: new XMLSerializer(),
|
|
556
557
|
});
|
|
557
558
|
// console.log('evaluateXPathToFirstNode',xpath, result);
|
|
558
559
|
return result;
|
|
@@ -570,6 +571,7 @@ export function evaluateXPathToFirstNode(xpath, contextNode, formElement) {
|
|
|
570
571
|
}),
|
|
571
572
|
);
|
|
572
573
|
}
|
|
574
|
+
return null;
|
|
573
575
|
}
|
|
574
576
|
|
|
575
577
|
/**
|
|
@@ -592,6 +594,7 @@ export function evaluateXPathToNodes(xpath, contextNode, formElement) {
|
|
|
592
594
|
xf: XFORMS_NAMESPACE_URI,
|
|
593
595
|
},
|
|
594
596
|
namespaceResolver,
|
|
597
|
+
xmlSerializer: new XMLSerializer(),
|
|
595
598
|
});
|
|
596
599
|
// console.log('evaluateXPathToNodes',xpath, result);
|
|
597
600
|
return result;
|
|
@@ -631,6 +634,7 @@ export function evaluateXPathToBoolean(xpath, contextNode, formElement) {
|
|
|
631
634
|
xf: XFORMS_NAMESPACE_URI,
|
|
632
635
|
},
|
|
633
636
|
namespaceResolver,
|
|
637
|
+
xmlSerializer: new XMLSerializer(),
|
|
634
638
|
});
|
|
635
639
|
} catch (e) {
|
|
636
640
|
formElement.dispatchEvent(
|
|
@@ -671,6 +675,7 @@ export function evaluateXPathToString(xpath, contextNode, formElement, domFacade
|
|
|
671
675
|
xf: XFORMS_NAMESPACE_URI,
|
|
672
676
|
},
|
|
673
677
|
namespaceResolver,
|
|
678
|
+
xmlSerializer: new XMLSerializer(),
|
|
674
679
|
});
|
|
675
680
|
} catch (e) {
|
|
676
681
|
formElement.dispatchEvent(
|
|
@@ -714,6 +719,7 @@ export function evaluateXPathToStrings(xpath, contextNode, formElement, domFacad
|
|
|
714
719
|
xf: XFORMS_NAMESPACE_URI,
|
|
715
720
|
},
|
|
716
721
|
namespaceResolver,
|
|
722
|
+
xmlSerializer: new XMLSerializer(),
|
|
717
723
|
},
|
|
718
724
|
);
|
|
719
725
|
} catch (e) {
|
|
@@ -755,6 +761,7 @@ export function evaluateXPathToNumber(xpath, contextNode, formElement, domFacade
|
|
|
755
761
|
xf: XFORMS_NAMESPACE_URI,
|
|
756
762
|
},
|
|
757
763
|
namespaceResolver,
|
|
764
|
+
xmlSerializer: new XMLSerializer(),
|
|
758
765
|
});
|
|
759
766
|
} catch (e) {
|
|
760
767
|
formElement.dispatchEvent(
|
package/src/xpath-util.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import * as fx from 'fontoxpath';
|
|
2
|
-
import { createNamespaceResolver } from './xpath-evaluation';
|
|
3
2
|
|
|
4
3
|
export class XPathUtil {
|
|
5
4
|
/**
|
|
@@ -44,10 +43,11 @@ export class XPathUtil {
|
|
|
44
43
|
* @param xpath
|
|
45
44
|
* @param doc {XMLDocument}
|
|
46
45
|
* @param fore
|
|
46
|
+
* @param namespaceResolver {function} optional namespace resolver function
|
|
47
47
|
* @return {Node|Attr}
|
|
48
48
|
*/
|
|
49
|
-
static createNodesFromXPath(xpath, doc, fore) {
|
|
50
|
-
const resolveNamespace =
|
|
49
|
+
static createNodesFromXPath(xpath, doc, fore, namespaceResolver = null) {
|
|
50
|
+
const resolveNamespace = namespaceResolver || (() => undefined);
|
|
51
51
|
|
|
52
52
|
if (!doc) {
|
|
53
53
|
doc = document.implementation.createDocument(null, null, null); // Create a new XML document if not provided
|
package/src/DataObserver.js
DELETED
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
/* Usage:
|
|
2
|
-
|
|
3
|
-
// Create or load your XML document
|
|
4
|
-
const xmlString = `
|
|
5
|
-
<root>
|
|
6
|
-
<item id="1">Initial Value</item>
|
|
7
|
-
</root>
|
|
8
|
-
`;
|
|
9
|
-
const parser = new DOMParser();
|
|
10
|
-
const xmlDoc = parser.parseFromString(xmlString, "application/xml");
|
|
11
|
-
const rootNode = xmlDoc.querySelector("root");
|
|
12
|
-
|
|
13
|
-
// Create an instance of the DataObserver
|
|
14
|
-
const xmlObserver = new DataObserver(200);
|
|
15
|
-
|
|
16
|
-
// Define a callback to process mutations
|
|
17
|
-
const handleXMLMutations = (mutationsList) => {
|
|
18
|
-
console.log("XML Mutations:", mutationsList);
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
// Attach the observer to the XML root node
|
|
22
|
-
xmlObserver.observe(rootNode, handleXMLMutations);
|
|
23
|
-
|
|
24
|
-
// Modify the XML document to trigger mutations
|
|
25
|
-
setTimeout(() => {
|
|
26
|
-
const newItem = xmlDoc.createElement("item");
|
|
27
|
-
newItem.textContent = "Newly Added Item";
|
|
28
|
-
rootNode.appendChild(newItem); // This triggers a mutation
|
|
29
|
-
}, 1000);
|
|
30
|
-
|
|
31
|
-
For JSON:
|
|
32
|
-
// Create a JSON object
|
|
33
|
-
const jsonObject = {
|
|
34
|
-
name: "John Doe",
|
|
35
|
-
age: 30,
|
|
36
|
-
hobbies: ["reading", "coding"],
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
// Create an instance of the DataObserver
|
|
40
|
-
const jsonObserver = new DataObserver(200);
|
|
41
|
-
|
|
42
|
-
// Define a callback to process changes
|
|
43
|
-
const handleJSONChanges = (changesList) => {
|
|
44
|
-
console.log("JSON Changes:", changesList);
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
// Attach the observer to the JSON object
|
|
48
|
-
jsonObserver.observe(jsonObject, handleJSONChanges);
|
|
49
|
-
|
|
50
|
-
// Modify the JSON object to trigger changes
|
|
51
|
-
setTimeout(() => {
|
|
52
|
-
jsonObject.name = "Jane Doe"; // This triggers a mutation
|
|
53
|
-
jsonObject.age = 31; // Another mutation
|
|
54
|
-
delete jsonObject.hobbies; // This triggers a delete mutation
|
|
55
|
-
}, 1000);
|
|
56
|
-
|
|
57
|
-
*/
|
|
58
|
-
export class DataObserver {
|
|
59
|
-
constructor(debounceTime = 0) {
|
|
60
|
-
this.observer = null; // Placeholder for MutationObserver (for XML)
|
|
61
|
-
this.debounceTime = debounceTime; // Time in milliseconds for optional debouncing
|
|
62
|
-
this.mutationsQueue = []; // To batch process mutations
|
|
63
|
-
this.debounceTimer = null; // Timer for debouncing
|
|
64
|
-
this.jsonProxy = null; // Proxy for JSON observation
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Attaches an observer to the given rootNode (DOM Node or JSON object)
|
|
69
|
-
* @param {Node|Object} rootNode - The XML DOM node or JSON object to observe
|
|
70
|
-
* @param {Function} callback - Function to process batch mutations
|
|
71
|
-
*/
|
|
72
|
-
observe(rootNode, callback) {
|
|
73
|
-
if (rootNode instanceof Node) {
|
|
74
|
-
// Handle XML Node
|
|
75
|
-
this.observeXML(rootNode, callback);
|
|
76
|
-
} else if (typeof rootNode === "object" && rootNode !== null) {
|
|
77
|
-
// Handle JSON Object
|
|
78
|
-
this.observeJSON(rootNode, callback);
|
|
79
|
-
} else {
|
|
80
|
-
throw new Error("Invalid rootNode. Must be a DOM Node or a JSON object.");
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Observes changes in an XML DOM node
|
|
86
|
-
* @param {Node} xmlNode - The XML DOM node to observe
|
|
87
|
-
* @param {Function} callback - Function to process batch mutations
|
|
88
|
-
*/
|
|
89
|
-
observeXML(xmlNode, callback) {
|
|
90
|
-
// Initialize a new MutationObserver
|
|
91
|
-
this.observer = new MutationObserver((mutationsList) => {
|
|
92
|
-
this.mutationsQueue.push(...mutationsList);
|
|
93
|
-
|
|
94
|
-
if (this.debounceTime > 0) {
|
|
95
|
-
clearTimeout(this.debounceTimer); // Clear previous timer
|
|
96
|
-
this.debounceTimer = setTimeout(() => {
|
|
97
|
-
this.processMutations(callback);
|
|
98
|
-
}, this.debounceTime);
|
|
99
|
-
} else {
|
|
100
|
-
this.processMutations(callback);
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
// Start observing the XML node
|
|
105
|
-
this.observer.observe(xmlNode, {
|
|
106
|
-
characterData: true,
|
|
107
|
-
childList: true,
|
|
108
|
-
subtree: true,
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Observes changes in a JSON object using a proxy
|
|
114
|
-
* @param {Object} jsonObject - The JSON object to observe
|
|
115
|
-
* @param {Function} callback - Function to process changes
|
|
116
|
-
*/
|
|
117
|
-
observeJSON(jsonObject, callback) {
|
|
118
|
-
const handler = {
|
|
119
|
-
set: (target, key, value) => {
|
|
120
|
-
this.mutationsQueue.push({ type: "update", target, key, value });
|
|
121
|
-
|
|
122
|
-
if (this.debounceTime > 0) {
|
|
123
|
-
clearTimeout(this.debounceTimer); // Clear previous timer
|
|
124
|
-
this.debounceTimer = setTimeout(() => {
|
|
125
|
-
this.processMutations(callback);
|
|
126
|
-
}, this.debounceTime);
|
|
127
|
-
} else {
|
|
128
|
-
this.processMutations(callback);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Perform the actual update
|
|
132
|
-
target[key] = value;
|
|
133
|
-
return true;
|
|
134
|
-
},
|
|
135
|
-
|
|
136
|
-
deleteProperty: (target, key) => {
|
|
137
|
-
this.mutationsQueue.push({ type: "delete", target, key });
|
|
138
|
-
|
|
139
|
-
if (this.debounceTime > 0) {
|
|
140
|
-
clearTimeout(this.debounceTimer); // Clear previous timer
|
|
141
|
-
this.debounceTimer = setTimeout(() => {
|
|
142
|
-
this.processMutations(callback);
|
|
143
|
-
}, this.debounceTime);
|
|
144
|
-
} else {
|
|
145
|
-
this.processMutations(callback);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Perform the actual delete
|
|
149
|
-
return delete target[key];
|
|
150
|
-
},
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
this.jsonProxy = new Proxy(jsonObject, handler);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Processes the mutations batch and clears the queue
|
|
158
|
-
* @param {Function} callback - Function to handle the batch of mutations
|
|
159
|
-
*/
|
|
160
|
-
processMutations(callback) {
|
|
161
|
-
if (this.mutationsQueue.length > 0) {
|
|
162
|
-
callback(this.mutationsQueue); // Pass the batch to the callback
|
|
163
|
-
this.mutationsQueue = []; // Clear the queue
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Disconnects the observer and clears any pending debounce timer
|
|
169
|
-
*/
|
|
170
|
-
disconnect() {
|
|
171
|
-
if (this.observer) {
|
|
172
|
-
this.observer.disconnect(); // Disconnect the MutationObserver
|
|
173
|
-
this.observer = null;
|
|
174
|
-
}
|
|
175
|
-
if (this.debounceTimer) {
|
|
176
|
-
clearTimeout(this.debounceTimer); // Clear any pending debounce timer
|
|
177
|
-
}
|
|
178
|
-
this.mutationsQueue = []; // Clear the mutation queue
|
|
179
|
-
this.jsonProxy = null; // Clear JSON proxy reference
|
|
180
|
-
}
|
|
181
|
-
}
|