@jinntec/fore 2.1.1 → 2.3.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/README.md +2 -2
- package/dist/fore-dev.js +2 -2
- package/dist/fore-dev.js.map +1 -1
- package/dist/fore.js +2 -2
- package/dist/fore.js.map +1 -1
- package/index.js +4 -2
- package/package.json +10 -38
- package/resources/fore.css +263 -262
- package/resources/vars.css +8 -0
- package/src/DependencyNotifyingDomFacade.js +1 -0
- package/src/ForeElementMixin.js +243 -228
- package/src/actions/abstract-action.js +407 -398
- package/src/actions/fx-action.js +6 -6
- package/src/actions/fx-append.js +1 -1
- package/src/actions/fx-call.js +63 -62
- package/src/actions/fx-confirm.js +11 -11
- package/src/actions/fx-construct-done.js +4 -4
- package/src/actions/fx-copy.js +36 -36
- package/src/actions/fx-delete.js +77 -79
- package/src/actions/fx-dispatch.js +14 -14
- package/src/actions/fx-hide.js +15 -15
- package/src/actions/fx-insert.js +9 -12
- package/src/actions/fx-insertchild.js +88 -0
- package/src/actions/fx-load.js +188 -185
- package/src/actions/fx-message.js +18 -19
- package/src/actions/fx-refresh.js +10 -10
- package/src/actions/fx-reload.js +16 -14
- package/src/actions/fx-replace.js +0 -1
- package/src/actions/fx-reset.js +31 -31
- package/src/actions/fx-send.js +59 -52
- package/src/actions/fx-setattribute.js +48 -49
- package/src/actions/fx-setfocus.js +54 -58
- package/src/actions/fx-setvalue.js +85 -81
- package/src/actions/fx-show.js +11 -11
- package/src/actions/fx-toggle.js +2 -2
- package/src/actions/fx-toggleboolean.js +38 -39
- package/src/actions/fx-unmodified.js +27 -0
- package/src/actions/fx-update.js +6 -6
- package/src/dep_graph.js +1 -1
- package/src/drawdown.js +23 -30
- package/src/events.js +19 -19
- package/src/fore.js +143 -125
- package/src/functions/common-function.js +24 -25
- package/src/functions/fx-function.js +6 -99
- package/src/functions/fx-functionlib.js +56 -0
- package/src/functions/registerFunction.js +112 -0
- package/src/fx-bind.js +19 -24
- package/src/fx-connection.js +226 -0
- package/src/fx-fore.js +844 -815
- package/src/fx-header.js +4 -4
- package/src/fx-instance.js +25 -29
- package/src/fx-model.js +405 -380
- package/src/fx-submission.js +399 -397
- package/src/fx-var.js +13 -12
- package/src/getInScopeContext.js +102 -89
- package/src/json-util.js +24 -24
- package/src/lab/fore-component.js +48 -52
- package/src/lab/instance-inspector.js +13 -16
- package/src/modelitem.js +48 -10
- package/src/relevance.js +12 -16
- package/src/tools/adi.js +858 -812
- package/src/tools/fx-action-log.js +377 -295
- package/src/tools/fx-devtools.js +210 -210
- package/src/tools/fx-dom-inspector.js +123 -122
- package/src/tools/fx-json-instance.js +262 -253
- package/src/tools/fx-log-item.js +6 -11
- package/src/tools/fx-log-settings.js +358 -301
- package/src/tools/fx-minimap.js +186 -177
- package/src/tools/helpers.js +1 -1
- package/src/ui/abstract-control.js +73 -53
- package/src/ui/fx-case.js +59 -7
- package/src/ui/fx-container.js +13 -14
- package/src/ui/fx-control.js +572 -538
- package/src/ui/fx-dialog.js +3 -3
- package/src/ui/fx-droptarget.js +3 -4
- package/src/ui/fx-group.js +4 -2
- package/src/ui/fx-hint.js +3 -0
- package/src/ui/fx-inspector.js +5 -5
- package/src/ui/fx-items.js +11 -11
- package/src/ui/fx-output.js +10 -12
- package/src/ui/fx-repeat-attributes.js +23 -27
- package/src/ui/fx-repeat.js +347 -344
- package/src/ui/fx-repeatitem.js +75 -68
- package/src/ui/fx-switch.js +39 -14
- package/src/ui/fx-trigger.js +4 -4
- package/src/withDraggability.js +193 -191
- package/src/xpath-evaluation.js +969 -958
- package/src/xpath-util.js +161 -134
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import ForeElementMixin from '../ForeElementMixin.js';
|
|
2
|
+
import registerFunction from './registerFunction.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Allows to extend a form with remote custom functions.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
export class FxFunctionlib extends ForeElementMixin {
|
|
9
|
+
constructor() {
|
|
10
|
+
super();
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @type {Function}
|
|
14
|
+
*/
|
|
15
|
+
this._resolve = null;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @type {Promise<undefined>}
|
|
19
|
+
*/
|
|
20
|
+
this.readyPromise = new Promise(resolve => (this._resolveLoading = resolve));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async connectedCallback() {
|
|
24
|
+
this.style.display = 'none';
|
|
25
|
+
|
|
26
|
+
const src = this.getAttribute('src');
|
|
27
|
+
|
|
28
|
+
const result = await fetch(src);
|
|
29
|
+
if (!result.ok) {
|
|
30
|
+
console.error(`Loading function library at ${src} failed.`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const body = await result.text();
|
|
34
|
+
const document = new DOMParser().parseFromString(body, 'text/html');
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @type {HTMLElement[]}
|
|
38
|
+
*/
|
|
39
|
+
const functions = Array.from(document.querySelectorAll('fx-function'));
|
|
40
|
+
// TODO: also recurse into new function libraries here?
|
|
41
|
+
for (const func of functions) {
|
|
42
|
+
const functionObject = {
|
|
43
|
+
type: func.getAttribute('type'),
|
|
44
|
+
signature: func.getAttribute('signature'),
|
|
45
|
+
functionBody: func.innerText,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
registerFunction(functionObject, this);
|
|
49
|
+
}
|
|
50
|
+
this._resolveLoading(undefined);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!customElements.get('fx-functionlib')) {
|
|
55
|
+
customElements.define('fx-functionlib', FxFunctionlib);
|
|
56
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { createTypedValueFactory, registerCustomXPathFunction } from 'fontoxpath';
|
|
2
|
+
import { evaluateXPath, globallyDeclaredFunctionLocalNames } from '../xpath-evaluation';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param functionObject {{signature: string, type: string|null, functionBody: string}}
|
|
6
|
+
* @param formElement {HTMLElement} The form element connected to this function. Used to determine inscope context
|
|
7
|
+
* @returns {undefined}
|
|
8
|
+
*/
|
|
9
|
+
export default function registerFunction(functionObject, formElement) {
|
|
10
|
+
if (functionObject.signature === null) {
|
|
11
|
+
console.error('signature is a required attribute');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const type = functionObject.type ?? 'text/xpath';
|
|
15
|
+
|
|
16
|
+
// Parse the signature to something useful
|
|
17
|
+
// Signature is of the form `my:sumproduct($p as xs:decimal*, $q as xs:decimal*) as xs:decimal` or local:something($a as item()*) as item()*
|
|
18
|
+
const signatureParseResult = functionObject.signature.match(
|
|
19
|
+
/(?:(?<prefix>[a-zA-Z_\-][a-zA-Z0-9_\-]*):)?(?<localName>[a-zA-Z_\-][a-zA-Z0-9_\-]*\s*)\((?<params>(?:[^()]*|\([^()]*\))*)\)\s*(?:as\s+(?<returnType>.*))?/,
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
if (!signatureParseResult) {
|
|
23
|
+
throw new Error(`Function signature ${functionObject.signature} could not be parsed`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const { prefix, localName, params, returnType } = signatureParseResult.groups;
|
|
27
|
+
|
|
28
|
+
// TODO: lookup prefix
|
|
29
|
+
const functionIdentifier =
|
|
30
|
+
prefix === 'local' || !prefix
|
|
31
|
+
? { namespaceURI: 'http://www.w3.org/2005/xquery-local-functions', localName }
|
|
32
|
+
: `${prefix}:${localName}`;
|
|
33
|
+
|
|
34
|
+
// Make the function available globally w/o a prefix. See the functionNameResolver for for how
|
|
35
|
+
// functionObject is picked up
|
|
36
|
+
if (!prefix) {
|
|
37
|
+
globallyDeclaredFunctionLocalNames.push(localName);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const paramParts = params
|
|
41
|
+
? params.split(',').map(param => {
|
|
42
|
+
const match = param.match(/(?<variableName>\$[^\s]+)(?:\sas\s(?<varType>[^\s]+))/);
|
|
43
|
+
if (!match) {
|
|
44
|
+
throw new Error(`Param ${param} could not be parsed`);
|
|
45
|
+
}
|
|
46
|
+
const { variableName, varType } = match.groups;
|
|
47
|
+
return {
|
|
48
|
+
variableName,
|
|
49
|
+
variableType: varType || 'item()*',
|
|
50
|
+
};
|
|
51
|
+
})
|
|
52
|
+
: [];
|
|
53
|
+
|
|
54
|
+
switch (type) {
|
|
55
|
+
case 'text/javascript': {
|
|
56
|
+
// eslint-disable-next-line no-new-func
|
|
57
|
+
const fun = new Function(
|
|
58
|
+
'_domFacade',
|
|
59
|
+
...paramParts.map(paramPart => paramPart.variableName),
|
|
60
|
+
'form',
|
|
61
|
+
functionObject.functionBody,
|
|
62
|
+
);
|
|
63
|
+
registerCustomXPathFunction(
|
|
64
|
+
functionIdentifier,
|
|
65
|
+
paramParts.map(paramPart => paramPart.variableType),
|
|
66
|
+
returnType || 'item()*',
|
|
67
|
+
(...args) =>
|
|
68
|
+
fun.apply(formElement.getInScopeContext(), [...args, formElement.getOwnerForm()]),
|
|
69
|
+
);
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
case 'text/xquf':
|
|
74
|
+
case 'text/xquery':
|
|
75
|
+
case 'text/xpath': {
|
|
76
|
+
const typedValueFactories = paramParts.map(param =>
|
|
77
|
+
createTypedValueFactory(param.variableType),
|
|
78
|
+
);
|
|
79
|
+
const language =
|
|
80
|
+
type === 'text/xpath'
|
|
81
|
+
? 'XPath3.1'
|
|
82
|
+
: type === 'text/xquery'
|
|
83
|
+
? 'XQuery3.1'
|
|
84
|
+
: 'XQueryUpdate3.1';
|
|
85
|
+
const fun = (domFacade, ...args) =>
|
|
86
|
+
evaluateXPath(
|
|
87
|
+
functionObject.functionBody,
|
|
88
|
+
formElement.getInScopeContext(),
|
|
89
|
+
formElement.getOwnerForm(),
|
|
90
|
+
paramParts.reduce((variablesByName, paramPart, i) => {
|
|
91
|
+
// Because we know the XPath type here (from the function declaration) we do not have to depend on the implicit typings
|
|
92
|
+
variablesByName[paramPart.variableName.replace('$', '')] = typedValueFactories[i](
|
|
93
|
+
args[i],
|
|
94
|
+
domFacade,
|
|
95
|
+
);
|
|
96
|
+
return variablesByName;
|
|
97
|
+
}, {}),
|
|
98
|
+
{ language },
|
|
99
|
+
);
|
|
100
|
+
registerCustomXPathFunction(
|
|
101
|
+
functionIdentifier,
|
|
102
|
+
paramParts.map(paramPart => paramPart.variableType),
|
|
103
|
+
returnType || 'item()*',
|
|
104
|
+
fun,
|
|
105
|
+
);
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
default:
|
|
110
|
+
throw new Error(`Unexpected mimetype ${type} for function`);
|
|
111
|
+
}
|
|
112
|
+
}
|
package/src/fx-bind.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DependencyNotifyingDomFacade } from './DependencyNotifyingDomFacade.js';
|
|
2
|
-
import
|
|
2
|
+
import ForeElementMixin from './ForeElementMixin.js';
|
|
3
3
|
import { ModelItem } from './modelitem.js';
|
|
4
4
|
import {
|
|
5
5
|
evaluateXPathToBoolean,
|
|
@@ -22,7 +22,7 @@ import getInScopeContext from './getInScopeContext.js';
|
|
|
22
22
|
*
|
|
23
23
|
* @customElements
|
|
24
24
|
*/
|
|
25
|
-
export class FxBind extends
|
|
25
|
+
export class FxBind extends ForeElementMixin {
|
|
26
26
|
static READONLY_DEFAULT = false;
|
|
27
27
|
|
|
28
28
|
static REQUIRED_DEFAULT = false;
|
|
@@ -80,10 +80,10 @@ export class FxBind extends foreElementMixin(HTMLElement) {
|
|
|
80
80
|
|
|
81
81
|
_buildBindGraph() {
|
|
82
82
|
if (this.bindType === 'xml') {
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
this.nodeset.forEach((node) => {
|
|
84
|
+
const instance = XPathUtil.resolveInstance(this, this.ref);
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
const path = XPathUtil.getPath(node, instance);
|
|
87
87
|
this.model.mainGraph.addNode(path, node);
|
|
88
88
|
|
|
89
89
|
/* ### catching references in the 'ref' itself...
|
|
@@ -93,7 +93,6 @@ export class FxBind extends foreElementMixin(HTMLElement) {
|
|
|
93
93
|
General question: are there valid use-cases for using a 'filter' expression to narrow the nodeset
|
|
94
94
|
where to apply constraints? Guess yes and if it's 'just' for reducing the amount of necessary modelItem objects.
|
|
95
95
|
|
|
96
|
-
|
|
97
96
|
*/
|
|
98
97
|
// const foreignRefs = this.getReferences(this.ref);
|
|
99
98
|
|
|
@@ -159,10 +158,10 @@ export class FxBind extends foreElementMixin(HTMLElement) {
|
|
|
159
158
|
if (!this.model.mainGraph.hasNode(nodeHash)) {
|
|
160
159
|
this.model.mainGraph.addNode(nodeHash, node);
|
|
161
160
|
}
|
|
162
|
-
|
|
163
|
-
|
|
161
|
+
refs.forEach((ref) => {
|
|
162
|
+
const instance = XPathUtil.resolveInstance(this, path);
|
|
164
163
|
|
|
165
|
-
|
|
164
|
+
const otherPath = XPathUtil.getPath(ref, instance);
|
|
166
165
|
// console.log('otherPath', otherPath)
|
|
167
166
|
|
|
168
167
|
// todo: nasty hack to prevent duplicate pathes like 'a[1]' and 'a[1]/text()[1]' to end up as separate nodes in the graph
|
|
@@ -180,7 +179,7 @@ export class FxBind extends foreElementMixin(HTMLElement) {
|
|
|
180
179
|
|
|
181
180
|
_processChildren(model) {
|
|
182
181
|
const childbinds = this.querySelectorAll(':scope > fx-bind');
|
|
183
|
-
Array.from(childbinds).forEach(bind => {
|
|
182
|
+
Array.from(childbinds).forEach((bind) => {
|
|
184
183
|
// console.log('init child bind ', bind);
|
|
185
184
|
bind.init(model);
|
|
186
185
|
});
|
|
@@ -209,14 +208,14 @@ export class FxBind extends foreElementMixin(HTMLElement) {
|
|
|
209
208
|
if (this.ref === '' || this.ref === null) {
|
|
210
209
|
this.nodeset = inscopeContext;
|
|
211
210
|
} else if (Array.isArray(inscopeContext)) {
|
|
212
|
-
inscopeContext.forEach(n => {
|
|
211
|
+
inscopeContext.forEach((n) => {
|
|
213
212
|
if (XPathUtil.isSelfReference(this.ref)) {
|
|
214
213
|
this.nodeset = inscopeContext;
|
|
215
214
|
} else {
|
|
216
215
|
// eslint-disable-next-line no-lonely-if
|
|
217
216
|
if (this.ref) {
|
|
218
217
|
const localResult = evaluateXPathToNodes(this.ref, n, this);
|
|
219
|
-
localResult.forEach(item => {
|
|
218
|
+
localResult.forEach((item) => {
|
|
220
219
|
this.nodeset.push(item);
|
|
221
220
|
});
|
|
222
221
|
/*
|
|
@@ -244,7 +243,7 @@ export class FxBind extends foreElementMixin(HTMLElement) {
|
|
|
244
243
|
if (Array.isArray(this.nodeset)) {
|
|
245
244
|
// console.log('################################################ ', this.nodeset);
|
|
246
245
|
// Array.from(this.nodeset).forEach((n, index) => {
|
|
247
|
-
Array.from(this.nodeset).forEach(n => {
|
|
246
|
+
Array.from(this.nodeset).forEach((n) => {
|
|
248
247
|
// console.log('node ',n);
|
|
249
248
|
// this._createModelItem(n, index);
|
|
250
249
|
this._createModelItem(n);
|
|
@@ -306,10 +305,10 @@ export class FxBind extends foreElementMixin(HTMLElement) {
|
|
|
306
305
|
const targetNode = node;
|
|
307
306
|
|
|
308
307
|
// const path = fx.evaluateXPath('path()',node);
|
|
309
|
-
|
|
310
|
-
|
|
308
|
+
// const path = this.getPath(node);
|
|
309
|
+
const instance = XPathUtil.resolveInstance(this, this.ref);
|
|
311
310
|
|
|
312
|
-
|
|
311
|
+
const path = XPathUtil.getPath(node, instance);
|
|
313
312
|
// const shortPath = this.shortenPath(path);
|
|
314
313
|
|
|
315
314
|
// ### constructing default modelitem - will get evaluated during recalculate()
|
|
@@ -333,7 +332,6 @@ export class FxBind extends foreElementMixin(HTMLElement) {
|
|
|
333
332
|
newItem.addAlert(alert);
|
|
334
333
|
}
|
|
335
334
|
|
|
336
|
-
|
|
337
335
|
this.getModel().registerModelItem(newItem);
|
|
338
336
|
}
|
|
339
337
|
|
|
@@ -356,7 +354,7 @@ export class FxBind extends foreElementMixin(HTMLElement) {
|
|
|
356
354
|
getReferences(propertyExpr) {
|
|
357
355
|
const touchedNodes = new Set();
|
|
358
356
|
const domFacade = new DependencyNotifyingDomFacade(otherNode => touchedNodes.add(otherNode));
|
|
359
|
-
this.nodeset.forEach(node => {
|
|
357
|
+
this.nodeset.forEach((node) => {
|
|
360
358
|
evaluateXPathToString(propertyExpr, node, this, domFacade);
|
|
361
359
|
});
|
|
362
360
|
return Array.from(touchedNodes.values());
|
|
@@ -412,15 +410,15 @@ export class FxBind extends foreElementMixin(HTMLElement) {
|
|
|
412
410
|
this.instanceId = XPathUtil.getInstanceId(bindExpr);
|
|
413
411
|
return;
|
|
414
412
|
}
|
|
415
|
-
if(!this.instanceId && this.parentNode.nodeName === 'FX-BIND'){
|
|
413
|
+
if (!this.instanceId && this.parentNode.nodeName === 'FX-BIND') {
|
|
416
414
|
let parent = this.parentNode;
|
|
417
|
-
while(parent && !this.instanceId){
|
|
415
|
+
while (parent && !this.instanceId) {
|
|
418
416
|
const ref = parent.getBindingExpr();
|
|
419
417
|
if (ref.startsWith('instance(')) {
|
|
420
418
|
this.instanceId = XPathUtil.getInstanceId(ref);
|
|
421
419
|
return;
|
|
422
420
|
}
|
|
423
|
-
if(parent.parentNode.nodeName !== 'FX-BIND'){
|
|
421
|
+
if (parent.parentNode.nodeName !== 'FX-BIND') {
|
|
424
422
|
this.instanceId = 'default';
|
|
425
423
|
break;
|
|
426
424
|
}
|
|
@@ -429,9 +427,6 @@ export class FxBind extends foreElementMixin(HTMLElement) {
|
|
|
429
427
|
}
|
|
430
428
|
this.instanceId = 'default';
|
|
431
429
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
430
|
}
|
|
436
431
|
|
|
437
432
|
if (!customElements.get('fx-bind')) {
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import ForeElementMixin from './ForeElementMixin.js';
|
|
2
|
+
import { Fore } from './fore.js';
|
|
3
|
+
import { FxModel } from './fx-model.js';
|
|
4
|
+
|
|
5
|
+
class FxConnection extends ForeElementMixin {
|
|
6
|
+
constructor() {
|
|
7
|
+
super();
|
|
8
|
+
this.attachShadow({ mode: 'open' });
|
|
9
|
+
this._url = '';
|
|
10
|
+
this._socket = null;
|
|
11
|
+
this._heartbeatInterval = FxConnection._defaultHeartbeatInterval;
|
|
12
|
+
this._heartbeatTimer = null;
|
|
13
|
+
this._messageFormat = FxConnection._defaultMessageFormat;
|
|
14
|
+
this._onMessage = this._onMessage.bind(this);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
_render() {
|
|
18
|
+
const style = `
|
|
19
|
+
:host {
|
|
20
|
+
display: none;
|
|
21
|
+
}
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
const html = `
|
|
25
|
+
<slot></slot>
|
|
26
|
+
${
|
|
27
|
+
this._messageFormat === 'xml'
|
|
28
|
+
? '<fx-replace id="replace" ref="."></fx-replace>'
|
|
29
|
+
: '<fx-setvalue id="setvalue" ref="."></fx-setvalue>'
|
|
30
|
+
}
|
|
31
|
+
`;
|
|
32
|
+
this.shadowRoot.innerHTML = `
|
|
33
|
+
<style>
|
|
34
|
+
${style}
|
|
35
|
+
</style>
|
|
36
|
+
${html}
|
|
37
|
+
`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static get observedAttributes() {
|
|
41
|
+
return ['url', 'heartbeat', 'message-format'];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
connectedCallback() {
|
|
45
|
+
if (this.hasAttribute('url')) {
|
|
46
|
+
this._url = this.getAttribute('url');
|
|
47
|
+
}
|
|
48
|
+
if (this.hasAttribute('heartbeat')) {
|
|
49
|
+
this._heartbeatInterval = parseInt(this.getAttribute('heartbeat'));
|
|
50
|
+
this._setupHeartbeat();
|
|
51
|
+
}
|
|
52
|
+
if (this.hasAttribute('message-format')) {
|
|
53
|
+
this._messageFormat = this.getAttribute('message-format');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this._render();
|
|
57
|
+
|
|
58
|
+
this.getOwnerForm().addEventListener('model-construct-done', e => {
|
|
59
|
+
console.log('Fore model ready');
|
|
60
|
+
this._connect();
|
|
61
|
+
|
|
62
|
+
this.evalInContext();
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
disconnectedCallback() {
|
|
67
|
+
this._disconnect();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
71
|
+
switch (name) {
|
|
72
|
+
case 'url':
|
|
73
|
+
this._url = newValue;
|
|
74
|
+
this._connect();
|
|
75
|
+
break;
|
|
76
|
+
case 'heartbeat':
|
|
77
|
+
this._heartbeatInterval = parseInt(newValue);
|
|
78
|
+
this._setupHeartbeat();
|
|
79
|
+
break;
|
|
80
|
+
case 'messageformat':
|
|
81
|
+
this._messageFormat = newValue;
|
|
82
|
+
break;
|
|
83
|
+
default:
|
|
84
|
+
// Do nothing for other attributes
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
send(data) {
|
|
90
|
+
this.evalInContext();
|
|
91
|
+
data = this.nodeset;
|
|
92
|
+
if (this._socket && this._socket.readyState === WebSocket.OPEN) {
|
|
93
|
+
let message;
|
|
94
|
+
switch (this._messageFormat) {
|
|
95
|
+
case 'json':
|
|
96
|
+
message = JSON.stringify(data);
|
|
97
|
+
break;
|
|
98
|
+
case 'xml':
|
|
99
|
+
message = new XMLSerializer().serializeToString(data);
|
|
100
|
+
break;
|
|
101
|
+
case 'text':
|
|
102
|
+
message = data.textContent;
|
|
103
|
+
break;
|
|
104
|
+
default:
|
|
105
|
+
throw new Error(`Unsupported message format: ${this._messageFormat}`);
|
|
106
|
+
}
|
|
107
|
+
this._sendMessage(message);
|
|
108
|
+
} else {
|
|
109
|
+
throw new Error('WebSocket is not connected');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
_connect() {
|
|
114
|
+
this._disconnect();
|
|
115
|
+
if (this._url) {
|
|
116
|
+
this._socket = new WebSocket(this._url);
|
|
117
|
+
this._socket.addEventListener('open', this._onOpen.bind(this));
|
|
118
|
+
this._socket.addEventListener('message', this._onMessage);
|
|
119
|
+
this._socket.addEventListener('close', this._onClose.bind(this));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
_disconnect() {
|
|
124
|
+
if (this._socket) {
|
|
125
|
+
this._socket.removeEventListener('open', this._onOpen.bind(this));
|
|
126
|
+
this._socket.removeEventListener('message', event => this._onMessage(event));
|
|
127
|
+
this._socket.removeEventListener('close', this._onClose.bind(this));
|
|
128
|
+
this._socket.close();
|
|
129
|
+
this._socket = null;
|
|
130
|
+
}
|
|
131
|
+
if (this._heartbeatTimer) {
|
|
132
|
+
clearInterval(this._heartbeatTimer);
|
|
133
|
+
this._heartbeatTimer = null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
_onOpen(event) {
|
|
138
|
+
this.dispatchEvent(new CustomEvent('open', { detail: event }));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
_onMessage(event) {
|
|
142
|
+
let message;
|
|
143
|
+
switch (this._messageFormat) {
|
|
144
|
+
case 'json':
|
|
145
|
+
message = JSON.parse(event.data);
|
|
146
|
+
break;
|
|
147
|
+
case 'xml':
|
|
148
|
+
const parser = new DOMParser();
|
|
149
|
+
message = parser.parseFromString(event.data, 'application/xml');
|
|
150
|
+
this.getModelItem().value = message;
|
|
151
|
+
FxModel.dataChanged = true;
|
|
152
|
+
this.getModel().changed.push(this.modelItem);
|
|
153
|
+
this.getModel().updateModel();
|
|
154
|
+
this.getOwnerForm().refresh(true);
|
|
155
|
+
break;
|
|
156
|
+
case 'text':
|
|
157
|
+
message = event.data;
|
|
158
|
+
break;
|
|
159
|
+
default:
|
|
160
|
+
throw new Error(`Unsupported message format: ${this._messageFormat}`);
|
|
161
|
+
}
|
|
162
|
+
console.log('dispatching channel-message', message);
|
|
163
|
+
|
|
164
|
+
Fore.dispatch(this, 'channel-message', { message });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
_onClose(event) {
|
|
168
|
+
this.dispatchEvent(new CustomEvent('close', { detail: event }));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
_setupHeartbeat() {
|
|
172
|
+
if (this._heartbeatInterval > 0) {
|
|
173
|
+
if (this._heartbeatTimer) {
|
|
174
|
+
clearInterval(this._heartbeatTimer);
|
|
175
|
+
}
|
|
176
|
+
this._heartbeatTimer = setInterval(() => {
|
|
177
|
+
this._sendMessage('');
|
|
178
|
+
}, this._heartbeatInterval);
|
|
179
|
+
} else if (this._heartbeatTimer) {
|
|
180
|
+
clearInterval(this._heartbeatTimer);
|
|
181
|
+
this._heartbeatTimer = null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
_sendMessage(message) {
|
|
186
|
+
if (this._socket && this._socket.readyState === WebSocket.OPEN) {
|
|
187
|
+
this._socket.send(message);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
static get _defaultHeartbeatInterval() {
|
|
192
|
+
return 0;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
static get _defaultMessageFormat() {
|
|
196
|
+
return 'json';
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
get url() {
|
|
200
|
+
return this._url;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
set url(value) {
|
|
204
|
+
this.setAttribute('url', value);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
get heartbeat() {
|
|
208
|
+
return this._heartbeatInterval;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
set heartbeat(value) {
|
|
212
|
+
this.setAttribute('heartbeat', value);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
get messageformat() {
|
|
216
|
+
return this._messageFormat;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
set messageformat(value) {
|
|
220
|
+
this.setAttribute('messageformat', value);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (!customElements.get('fx-connection')) {
|
|
225
|
+
customElements.define('fx-connection', FxConnection);
|
|
226
|
+
}
|