lightview 1.6.2-b → 1.6.5-b
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 +35 -1
- package/components/chart.html +6 -5
- package/components/gauge.html +1 -1
- package/examples/chart.html +11 -9
- package/examples/medium/remote.html +59 -0
- package/examples/xor.html +1 -1
- package/jest.config.json +1 -1
- package/lightview.js +170 -165
- package/package.json +1 -1
- package/test/basic.html +1 -1
- package/test/extended.html +29 -0
- package/test/extended.test.mjs +270 -0
- package/types.js +47 -13
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# lightview v1.
|
|
1
|
+
# lightview v1.6.5b (BETA)
|
|
2
2
|
|
|
3
3
|
Small, simple, powerful web UI and micro front end creation ...
|
|
4
4
|
|
|
@@ -6,3 +6,37 @@ Great ideas from Svelte, React, Vue and Riot combined into one small tool: < 7K
|
|
|
6
6
|
|
|
7
7
|
See the docs and examples at [https://lightview.dev](https://lightview.dev).
|
|
8
8
|
|
|
9
|
+
Meanwhile, here is what you get:
|
|
10
|
+
|
|
11
|
+
1) Single file and <a href="#local-templates" target=_self>template</a> components.
|
|
12
|
+
|
|
13
|
+
1) [Sandboxed remote components](https://lightview.dev/#sandboxed-components) and micro front ends</a>.
|
|
14
|
+
|
|
15
|
+
1) [Unit testable](https://lightview.dev/#unit-testing) components and a [debug mode](https://lightview.dev/#debugging) for using standard JavaScript debuggers</a>.
|
|
16
|
+
|
|
17
|
+
1) No pre-deployment transpilation/compilation required.
|
|
18
|
+
|
|
19
|
+
1) Svelte like variable usage, i.e. write your state modifying code like normal code.
|
|
20
|
+
|
|
21
|
+
1) Extended variable type declarations including `min`, `max` and `step` on `number` or limits on `string` and `array` lengths.
|
|
22
|
+
|
|
23
|
+
1) [TypeScript like](https://lightview.dev/#variables) runtime type checking of variables in components.
|
|
24
|
+
|
|
25
|
+
1) Automatic server retrieval and update of variables declared as `remote`.
|
|
26
|
+
|
|
27
|
+
1) Automatic import, export, cross-component sync, or reactive response to attributes/props/variables. See [superVariable](https://lightview.dev/#super-variable).
|
|
28
|
+
|
|
29
|
+
1) [Automatic form variable creation and binding](https://lightview.dev/#auto-binding-forms).
|
|
30
|
+
|
|
31
|
+
1) [Attribute directives](https://lightview.dev/#attribute-directives) like `l-if`, and a single powerful `l-for` that handles array and object keys, values, and entries.
|
|
32
|
+
|
|
33
|
+
1) Reactive string template literals for content and attribute value replacement.
|
|
34
|
+
|
|
35
|
+
1) No virtual DOM. The Lightview dependency tracker laser targets just those nodes that need updates.
|
|
36
|
+
|
|
37
|
+
1) SPA, and MPA friendly ... somewhat SEO friendly and short steps away from fully SEO friendly.
|
|
38
|
+
|
|
39
|
+
1) A [component library](https://lightview.dev/components) including charts and gauges.
|
|
40
|
+
|
|
41
|
+
1) Lots of live [editable examples](https://lightview.dev/#examples).
|
|
42
|
+
|
package/components/chart.html
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
});
|
|
24
24
|
let chart,
|
|
25
25
|
datatable,
|
|
26
|
-
options;
|
|
26
|
+
options = {};
|
|
27
27
|
self.variables({type: "string", title: "string", style: "string"}, {observed});
|
|
28
28
|
if(style) target.setAttribute("style",style);
|
|
29
29
|
self.addRow = (row) => {
|
|
@@ -36,6 +36,9 @@
|
|
|
36
36
|
chart.draw(datatable, options);
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
|
+
self.setOptions = (newOptions) => {
|
|
40
|
+
Object.assign(options,newOptions);
|
|
41
|
+
};
|
|
39
42
|
self.init = () => {
|
|
40
43
|
const node = self.firstChild,
|
|
41
44
|
callback = (textNode, oldValue, newValue) => {
|
|
@@ -48,9 +51,7 @@
|
|
|
48
51
|
target.innerText = e + newValue;
|
|
49
52
|
}
|
|
50
53
|
};
|
|
51
|
-
options =
|
|
52
|
-
title: title
|
|
53
|
-
};
|
|
54
|
+
if(!options.title && title) options.title = title;
|
|
54
55
|
chart = new google.visualization[type](target);
|
|
55
56
|
node.characterDataMutationCallback = callback;
|
|
56
57
|
callback(node, node.textContent, node.textContent);
|
|
@@ -67,7 +68,7 @@
|
|
|
67
68
|
resizeObserver.observe(target);
|
|
68
69
|
self.removeAttribute("hidden");
|
|
69
70
|
};
|
|
70
|
-
addEventListener("connected", ({target}) => {
|
|
71
|
+
self.addEventListener("connected", ({target}) => {
|
|
71
72
|
google.charts.load("current", {"packages": ["corechart","gauge"]});
|
|
72
73
|
google.charts.setOnLoadCallback(self.init);
|
|
73
74
|
});
|
package/components/gauge.html
CHANGED
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
resizeObserver.observe(target);
|
|
49
49
|
self.removeAttribute("hidden");
|
|
50
50
|
};
|
|
51
|
-
addEventListener("connected", ({target}) => {
|
|
51
|
+
self.addEventListener("connected", ({target}) => {
|
|
52
52
|
google.charts.load("current", {"packages": ["corechart","gauge"]});
|
|
53
53
|
google.charts.setOnLoadCallback(self.init);
|
|
54
54
|
});
|
package/examples/chart.html
CHANGED
|
@@ -50,15 +50,17 @@
|
|
|
50
50
|
</l-chart>
|
|
51
51
|
<script>
|
|
52
52
|
const gauges = document.getElementById("myGauges");
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
53
|
+
gauges.addEventListener("connected",() => {
|
|
54
|
+
setInterval(function() {
|
|
55
|
+
gauges.setValue(0, 1, 40 + Math.round(60 * Math.random()));
|
|
56
|
+
}, 6000);
|
|
57
|
+
setInterval(function() {
|
|
58
|
+
gauges.setValue(1, 1, 40 + Math.round(60 * Math.random()));
|
|
59
|
+
}, 5000);
|
|
60
|
+
setInterval(function() {
|
|
61
|
+
gauges.setValue(2, 1, 60 + Math.round(40 * Math.random()));
|
|
62
|
+
}, 7500);
|
|
63
|
+
})
|
|
62
64
|
</script>
|
|
63
65
|
</body>
|
|
64
66
|
</html>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<html>
|
|
2
|
+
<head>
|
|
3
|
+
<title>Lightview:Examples:Medium:Remote</title>
|
|
4
|
+
<!--
|
|
5
|
+
for convenience, import the chart component from the Lightview component library
|
|
6
|
+
alias it to r-chart rather than use the default l-chart
|
|
7
|
+
we could use any gauge component that exposes methods to update its view
|
|
8
|
+
-->
|
|
9
|
+
<link href="../../components/chart.html" rel="module" crossorigin="use-credentials" as="r-chart">
|
|
10
|
+
<!--
|
|
11
|
+
load the lightview library, about 7K
|
|
12
|
+
use the body of this file to create a custom element to replace itself
|
|
13
|
+
-->
|
|
14
|
+
<script src="../../lightview.js?as=x-body"></script>
|
|
15
|
+
</head>
|
|
16
|
+
<body>
|
|
17
|
+
<!--
|
|
18
|
+
layout the dashboad using the chart component r-chart
|
|
19
|
+
-->
|
|
20
|
+
<div style="width:100%;text-align:center">
|
|
21
|
+
<!--
|
|
22
|
+
set the initial value 0 for all components in a relaxed JSON5 configuration data block
|
|
23
|
+
-->
|
|
24
|
+
<r-chart id="dashboard" style="display:inline-block" type="Gauge" title="Server Status">
|
|
25
|
+
[
|
|
26
|
+
['Label', 'Value'], // gauge will always take two columns, Label and Value
|
|
27
|
+
['Memory', 0],
|
|
28
|
+
['CPU', 0],
|
|
29
|
+
['Network', 0]
|
|
30
|
+
]
|
|
31
|
+
</r-chart>
|
|
32
|
+
</div>
|
|
33
|
+
<script type="lightview/module">
|
|
34
|
+
// use local, normal variables for as much as possible
|
|
35
|
+
const {remote} = await import("../../types.js"), // load the functional type 'remote`
|
|
36
|
+
sensorIndex = { // map sensor names to indexes in the dashboard data
|
|
37
|
+
memory:0,
|
|
38
|
+
cpu:1,
|
|
39
|
+
network:2
|
|
40
|
+
},
|
|
41
|
+
dashboard = document.body.getElementById("dashboard"),
|
|
42
|
+
path = "https://lightview.dev/sensors/"; // replace base path for your own implementation
|
|
43
|
+
// create remote reactive variables for sensors with differing refresh rates (ttl in milliseconds)
|
|
44
|
+
self.variables({memory:"number"},{remote:remote({ttl:5000,path})});
|
|
45
|
+
self.variables({cpu:"number"},{remote:remote({ttl:2500,path})});
|
|
46
|
+
self.variables({network:"number"},{remote:remote({ttl:1500,path})});
|
|
47
|
+
dashboard.addEventListener("connected",() => {
|
|
48
|
+
dashboard.setOptions({ // when dashboard component has finished initializing, set more options
|
|
49
|
+
redFrom: 90, redTo: 100,
|
|
50
|
+
yellowFrom:75, yellowTo: 90,
|
|
51
|
+
minorTicks: 5});
|
|
52
|
+
addEventListener("change",({variableName,value}) => { // execute the magic with a localized eventListener
|
|
53
|
+
const index = sensorIndex[variableName];
|
|
54
|
+
dashboard.setValue(index, 1, value);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
</script>
|
|
58
|
+
</body>
|
|
59
|
+
</html>
|
package/examples/xor.html
CHANGED
package/jest.config.json
CHANGED
package/lightview.js
CHANGED
|
@@ -241,10 +241,13 @@ const {observe} = (() => {
|
|
|
241
241
|
const variable = target[property],
|
|
242
242
|
{type, value, shared, exported, constant, reactive, remote} = variable;
|
|
243
243
|
if (constant) throw new TypeError(`${property}:${type} is a constant`);
|
|
244
|
-
newValue = type.validate ? type.validate(newValue,target[property]) : coerce(newValue,type);
|
|
244
|
+
if(newValue!=null || type.required) newValue = type.validate ? type.validate(newValue,target[property]) : coerce(newValue,type);
|
|
245
245
|
const newtype = typeof (newValue),
|
|
246
246
|
typetype = typeof (type);
|
|
247
|
-
if (newValue == null
|
|
247
|
+
if ((newValue == null && !type.required) ||
|
|
248
|
+
type === "any" ||
|
|
249
|
+
(newtype === type && typetype==="string") ||
|
|
250
|
+
(typetype === "function" && !type.validate && (newValue && newtype === "object" && newValue instanceof type) || variable.validityState?.valid)) {
|
|
248
251
|
if (value !== newValue) {
|
|
249
252
|
event.oldValue = value;
|
|
250
253
|
target[property].value = reactive ? Reactor(newValue) : newValue; // do first to prevent loops
|
|
@@ -260,7 +263,7 @@ const {observe} = (() => {
|
|
|
260
263
|
if (typetype === "function" && newValue && newtype === "object") {
|
|
261
264
|
throw new TypeError(`Can't assign instance of '${newValue.constructor.name}' to variable '${property}:${type.name.replace("bound ", "")}'`)
|
|
262
265
|
}
|
|
263
|
-
throw new TypeError(`Can't assign '${typeof (newValue)} ${newtype === "string" ? '"' + newValue + '"' : newValue}' to variable '${property}:${typetype === "function" ? type.name.replace("bound ", "") : type}'`)
|
|
266
|
+
throw new TypeError(`Can't assign '${typeof (newValue)} ${newtype === "string" ? '"' + newValue + '"' : newValue}' to variable '${property}:${typetype === "function" ? type.name.replace("bound ", "") : type} ${type.required ? "required" : ""}'`)
|
|
264
267
|
},
|
|
265
268
|
keys() {
|
|
266
269
|
return [...Object.keys(vars)];
|
|
@@ -339,7 +342,7 @@ const {observe} = (() => {
|
|
|
339
342
|
}
|
|
340
343
|
return nodes;
|
|
341
344
|
}
|
|
342
|
-
|
|
345
|
+
|
|
343
346
|
const resolveNodeOrText = (node, component, safe) => {
|
|
344
347
|
const type = typeof (node),
|
|
345
348
|
template = type === "string" ? node.trim() : node.template;
|
|
@@ -444,13 +447,13 @@ const {observe} = (() => {
|
|
|
444
447
|
eventlisteners = {};
|
|
445
448
|
// needs to be local to the instance
|
|
446
449
|
Object.defineProperty(this,"changeListener",{value:
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
450
|
+
function({variableName, value}) {
|
|
451
|
+
if (currentComponent.changeListener.targets.has(variableName)) {
|
|
452
|
+
value = typeof (value) === "string" || !value ? value : JSON.stringify(value);
|
|
453
|
+
if (value == null) removeComponentAttribute(currentComponent, variableName);
|
|
454
|
+
else setComponentAttribute(currentComponent, variableName, value);
|
|
455
|
+
}
|
|
456
|
+
}});
|
|
454
457
|
this.vars = {
|
|
455
458
|
...reserved,
|
|
456
459
|
addEventListener: {
|
|
@@ -505,23 +508,25 @@ const {observe} = (() => {
|
|
|
505
508
|
instances.delete(this);
|
|
506
509
|
}
|
|
507
510
|
|
|
508
|
-
connectedCallback() {
|
|
511
|
+
async connectedCallback() {
|
|
509
512
|
const ctx = this,
|
|
510
513
|
shadow = ctx.shadowRoot;
|
|
511
514
|
for (const attr of this.defaultAttributes) this.hasAttribute(attr.name) || this.setAttribute(attr.name, attr.value);
|
|
512
515
|
const scripts = shadow.querySelectorAll("script"),
|
|
513
|
-
promises = []
|
|
514
|
-
|
|
516
|
+
promises = [];
|
|
517
|
+
// scriptpromises = [];
|
|
515
518
|
for (const script of [...scripts]) {
|
|
516
519
|
if (script.attributes.src?.value?.includes("/lightview.js")) continue;
|
|
517
520
|
const currentScript = document.createElement("script");
|
|
518
521
|
if (script.className !== "lightview" && !((script.attributes.type?.value || "").includes("lightview/"))) {
|
|
519
522
|
for (const attr of script.attributes) currentScript.setAttribute(attr.name,attr.value);
|
|
520
|
-
scriptpromises.push(new Promise((resolve) => {
|
|
521
|
-
currentScript.onload = () => resolve();
|
|
522
|
-
}))
|
|
523
523
|
shadow.appendChild(currentScript);
|
|
524
|
-
|
|
524
|
+
await new Promise((resolve) => {
|
|
525
|
+
currentScript.onload = () => {
|
|
526
|
+
currentScript.remove();
|
|
527
|
+
resolve();
|
|
528
|
+
}
|
|
529
|
+
})
|
|
525
530
|
continue;
|
|
526
531
|
};
|
|
527
532
|
const scriptid = Math.random() + "";
|
|
@@ -531,166 +536,166 @@ const {observe} = (() => {
|
|
|
531
536
|
currentScript.classList.remove("lightview");
|
|
532
537
|
const text = script.innerHTML.replaceAll(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm, "$1").replaceAll(/\r?\n/g, "");
|
|
533
538
|
currentScript.innerHTML = `Object.getPrototypeOf(async function(){}).constructor('if(window["${scriptid}"]?.ctx) { const ctx = window["${scriptid}"].ctx; { with(ctx) { ${text}; } } }')().then(() => window["${scriptid}"]()); `;
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
// wait for all regular scripts to load first
|
|
544
|
-
Promise.all(scriptpromises).then(() => shadow.appendChild(currentScript));
|
|
545
|
-
}));
|
|
539
|
+
await new Promise((resolve) => {
|
|
540
|
+
window[scriptid] = () => {
|
|
541
|
+
delete window[scriptid];
|
|
542
|
+
currentScript.remove();
|
|
543
|
+
resolve();
|
|
544
|
+
}
|
|
545
|
+
window[scriptid].ctx = ctx.varsProxy;
|
|
546
|
+
shadow.appendChild(currentScript);
|
|
547
|
+
})
|
|
546
548
|
}
|
|
547
|
-
Promise.all(promises).then(() => {
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
549
|
+
// Promise.all(promises).then(() => {
|
|
550
|
+
const nodes = getNodes(ctx);
|
|
551
|
+
nodes.forEach((node) => {
|
|
552
|
+
if (node.nodeType === Node.TEXT_NODE && node.template.includes("${")) {
|
|
553
|
+
observe(() => resolveNodeOrText(node, this));
|
|
554
|
+
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
555
|
+
// resolve the value before all else;
|
|
556
|
+
const attr = node.attributes.value,
|
|
557
|
+
template = attr?.template;
|
|
558
|
+
if (attr && template) {
|
|
559
|
+
let value = resolveNodeOrText(attr, this),
|
|
560
|
+
eltype = node.attributes.type ? resolveNodeOrText(node.attributes.type, ctx) : null;
|
|
561
|
+
const template = attr.template;
|
|
562
|
+
if (template) {
|
|
563
|
+
if (/\$\{[a-zA-z_]+\}/g.test(template)) {
|
|
564
|
+
const name = template.substring(2, template.length - 1);
|
|
565
|
+
if(!this.vars[name] || this.vars[name].reactive) {
|
|
566
|
+
bindInput(node, name, this, value);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
observe(() => {
|
|
570
|
+
const value = resolveNodeOrText(template, ctx);
|
|
571
|
+
if(value!==undefined) {
|
|
572
|
+
if (eltype === "checkbox") {
|
|
573
|
+
if (coerce(value, "boolean") === true) {
|
|
574
|
+
node.setAttribute("checked", "");
|
|
575
|
+
node.checked = true;
|
|
576
|
+
} else {
|
|
577
|
+
node.removeAttribute("checked");
|
|
578
|
+
node.checked = false;
|
|
565
579
|
}
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
if (
|
|
571
|
-
if (
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
} else {
|
|
575
|
-
node.removeAttribute("checked");
|
|
576
|
-
node.checked = false;
|
|
580
|
+
} else if (node.tagName === "SELECT") {
|
|
581
|
+
let values = [value];
|
|
582
|
+
if (node.hasAttribute("multiple")) values = coerce(value, Array);
|
|
583
|
+
[...node.querySelectorAll("option")].forEach(async (option) => {
|
|
584
|
+
if (option.hasAttribute("value")) {
|
|
585
|
+
if (values.includes(resolveNodeOrText(option.attributes.value, ctx))) {
|
|
586
|
+
option.setAttribute("selected", "");
|
|
587
|
+
option.selected = true;
|
|
577
588
|
}
|
|
578
|
-
} else if (
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
[...node.querySelectorAll("option")].forEach(async (option) => {
|
|
582
|
-
if (option.hasAttribute("value")) {
|
|
583
|
-
if (values.includes(resolveNodeOrText(option.attributes.value, ctx))) {
|
|
584
|
-
option.setAttribute("selected", "");
|
|
585
|
-
option.selected = true;
|
|
586
|
-
}
|
|
587
|
-
} else if (values.includes(resolveNodeOrText(option.innerText, ctx))) {
|
|
588
|
-
option.setAttribute("selected", "");
|
|
589
|
-
option.selected = true;
|
|
590
|
-
}
|
|
591
|
-
})
|
|
592
|
-
} else if (eltype!=="radio") {
|
|
593
|
-
attr.value = value;
|
|
589
|
+
} else if (values.includes(resolveNodeOrText(option.innerText, ctx))) {
|
|
590
|
+
option.setAttribute("selected", "");
|
|
591
|
+
option.selected = true;
|
|
594
592
|
}
|
|
595
|
-
}
|
|
596
|
-
})
|
|
593
|
+
})
|
|
594
|
+
} else if (eltype!=="radio") {
|
|
595
|
+
attr.value = value;
|
|
596
|
+
}
|
|
597
597
|
}
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
[...node.attributes].forEach(async (attr) => {
|
|
602
|
+
if (attr.name === "value" && attr.template) return;
|
|
603
|
+
const {name, value} = attr,
|
|
604
|
+
vname = node.attributes.name?.value;
|
|
605
|
+
if (name === "type" && value=="radio" && vname) {
|
|
606
|
+
bindInput(node, vname, this);
|
|
607
|
+
observe(() => {
|
|
608
|
+
const varvalue = Function("context", "with(context) { return `${" + vname + "}` }")(ctx.varsProxy);
|
|
609
|
+
if (node.attributes.value.value == varvalue) {
|
|
610
|
+
node.setAttribute("checked", "");
|
|
611
|
+
node.checked = true;
|
|
612
|
+
} else {
|
|
613
|
+
node.removeAttribute("checked");
|
|
614
|
+
node.checked = false;
|
|
615
|
+
}
|
|
616
|
+
});
|
|
598
617
|
}
|
|
599
|
-
[...node.attributes].forEach(async (attr) => {
|
|
600
|
-
if (attr.name === "value" && attr.template) return;
|
|
601
|
-
const {name, value} = attr,
|
|
602
|
-
vname = node.attributes.name?.value;
|
|
603
|
-
if (name === "type" && value=="radio" && vname) {
|
|
604
|
-
bindInput(node, vname, this);
|
|
605
|
-
observe(() => {
|
|
606
|
-
const varvalue = Function("context", "with(context) { return `${" + vname + "}` }")(ctx.varsProxy);
|
|
607
|
-
if (node.attributes.value.value == varvalue) {
|
|
608
|
-
node.setAttribute("checked", "");
|
|
609
|
-
node.checked = true;
|
|
610
|
-
} else {
|
|
611
|
-
node.removeAttribute("checked");
|
|
612
|
-
node.checked = false;
|
|
613
|
-
}
|
|
614
|
-
});
|
|
615
|
-
}
|
|
616
618
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
})
|
|
633
|
-
} else if (type === "l-on") {
|
|
634
|
-
let listener;
|
|
635
|
-
observe(() => {
|
|
636
|
-
const value = resolveNodeOrText(attr, this);
|
|
637
|
-
if (listener) node.removeEventListener(params[0], listener);
|
|
638
|
-
listener = this[value] || window[value] || Function(value);
|
|
639
|
-
addListener(node, params[0], listener);
|
|
640
|
-
})
|
|
641
|
-
} else if (type === "l-if") {
|
|
642
|
-
observe(() => {
|
|
643
|
-
const value = resolveNodeOrText(attr, this);
|
|
644
|
-
node.style.setProperty("display", value === "true" ? "revert" : "none");
|
|
645
|
-
})
|
|
646
|
-
} else if (type === "l-for") {
|
|
647
|
-
node.template ||= node.innerHTML;
|
|
648
|
-
observe(() => {
|
|
649
|
-
const [what = "each", vname = "item", index = "index", array = "array", after = false] = params,
|
|
650
|
-
value = resolveNodeOrText(attr, this),
|
|
651
|
-
coerced = coerce(value, what === "each" ? Array : "object"),
|
|
652
|
-
target = what === "each" ? coerced : Object[what](coerced),
|
|
653
|
-
html = target.reduce( (html, item, i, target) => {
|
|
654
|
-
return html += Function("vars", "context", "with(vars) { with(context) { return `" + node.template + "` }}")(
|
|
655
|
-
ctx.varsProxy,
|
|
656
|
-
{
|
|
657
|
-
[vname]: item,
|
|
658
|
-
[index]: i,
|
|
659
|
-
[array]: target
|
|
660
|
-
})
|
|
661
|
-
}, ""),
|
|
662
|
-
parsed = parser.parseFromString(html, "text/html");
|
|
663
|
-
if (!window.lightviewDebug) {
|
|
664
|
-
if (after) {
|
|
665
|
-
node.style.setProperty("display", "none")
|
|
666
|
-
} else {
|
|
667
|
-
while (node.lastElementChild) node.lastElementChild.remove();
|
|
668
|
-
}
|
|
619
|
+
const [type, ...params] = name.split(":");
|
|
620
|
+
if (type === "") { // name is :something
|
|
621
|
+
observe(() => {
|
|
622
|
+
const value = attr.value;
|
|
623
|
+
if (params[0]) {
|
|
624
|
+
if (value === "true") node.setAttribute(params[0], "")
|
|
625
|
+
else node.removeAttribute(params[0]);
|
|
626
|
+
} else {
|
|
627
|
+
const elvalue = node.attributes.value ? resolveNodeOrText(node.attributes.value, ctx) : null,
|
|
628
|
+
eltype = node.attributes.type ? resolveNodeOrText(node.attributes.type, ctx) : null;
|
|
629
|
+
if (eltype === "checkbox" || node.tagName === "OPTION") {
|
|
630
|
+
if (elvalue === "true") node.setAttribute("checked", "")
|
|
631
|
+
else node.removeAttribute("checked");
|
|
669
632
|
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
633
|
+
}
|
|
634
|
+
})
|
|
635
|
+
} else if (type === "l-on") {
|
|
636
|
+
let listener;
|
|
637
|
+
observe(() => {
|
|
638
|
+
const value = resolveNodeOrText(attr, this);
|
|
639
|
+
if (listener) node.removeEventListener(params[0], listener);
|
|
640
|
+
listener = this[value] || window[value] || Function(value);
|
|
641
|
+
addListener(node, params[0], listener);
|
|
642
|
+
})
|
|
643
|
+
} else if (type === "l-if") {
|
|
644
|
+
observe(() => {
|
|
645
|
+
const value = resolveNodeOrText(attr, this);
|
|
646
|
+
node.style.setProperty("display", value === "true" ? "revert" : "none");
|
|
647
|
+
})
|
|
648
|
+
} else if (type === "l-for") {
|
|
649
|
+
node.template ||= node.innerHTML;
|
|
650
|
+
observe(() => {
|
|
651
|
+
const [what = "each", vname = "item", index = "index", array = "array", after = false] = params,
|
|
652
|
+
value = resolveNodeOrText(attr, this),
|
|
653
|
+
coerced = coerce(value, what === "each" ? Array : "object"),
|
|
654
|
+
target = what === "each" ? coerced : Object[what](coerced),
|
|
655
|
+
html = target.reduce( (html, item, i, target) => {
|
|
656
|
+
return html += Function("vars", "context", "with(vars) { with(context) { return `" + node.template + "` }}")(
|
|
657
|
+
ctx.varsProxy,
|
|
658
|
+
{
|
|
659
|
+
[vname]: item,
|
|
660
|
+
[index]: i,
|
|
661
|
+
[array]: target
|
|
662
|
+
})
|
|
663
|
+
}, ""),
|
|
664
|
+
parsed = parser.parseFromString(html, "text/html");
|
|
665
|
+
if (!window.lightviewDebug) {
|
|
666
|
+
if (after) {
|
|
667
|
+
node.style.setProperty("display", "none")
|
|
668
|
+
} else {
|
|
669
|
+
while (node.lastElementChild) node.lastElementChild.remove();
|
|
673
670
|
}
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
})
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
671
|
+
}
|
|
672
|
+
while (parsed.body.firstChild) {
|
|
673
|
+
if (after) node.parentElement.insertBefore(parsed.body.firstChild, node);
|
|
674
|
+
else node.appendChild(parsed.body.firstChild);
|
|
675
|
+
}
|
|
676
|
+
})
|
|
677
|
+
} else if(attr.template) {
|
|
678
|
+
observe(() => {
|
|
679
|
+
resolveNodeOrText(attr, this);
|
|
680
|
+
})
|
|
681
|
+
}
|
|
682
|
+
})
|
|
683
|
+
}
|
|
687
684
|
})
|
|
685
|
+
shadow.normalize();
|
|
686
|
+
observer ||= createObserver(ctx, framed);
|
|
687
|
+
observer.observe(ctx, {attributeOldValue: true, subtree:true, characterData:true, characterDataOldValue:true});
|
|
688
|
+
//ctx.vars.postEvent.value("connected");
|
|
689
|
+
this.dispatchEvent(new Event("connected"));
|
|
690
|
+
// })
|
|
688
691
|
}
|
|
689
692
|
adoptedCallback(callback) {
|
|
690
|
-
this.
|
|
693
|
+
this.dispatchEvent(new Event("adopted"));
|
|
694
|
+
//this.vars.postEvent.value("adopted");
|
|
691
695
|
}
|
|
692
696
|
disconnectedCallback() {
|
|
693
|
-
this.
|
|
697
|
+
this.dispatchEvent(new Event("disconnected"));
|
|
698
|
+
//this.vars.postEvent.value("disconnected");
|
|
694
699
|
}
|
|
695
700
|
get observedAttributes() {
|
|
696
701
|
return CustomElement.observedAttributes;
|
|
@@ -782,7 +787,7 @@ const {observe} = (() => {
|
|
|
782
787
|
variable.remote = remote;
|
|
783
788
|
remote.handleRemote({variable, config:remote.config, reactive,component:this});
|
|
784
789
|
}
|
|
785
|
-
if(type.validate) type.validate(variable.value,variable);
|
|
790
|
+
if(type.validate && variable.value!==undefined) type.validate(variable.value,variable);
|
|
786
791
|
});
|
|
787
792
|
}
|
|
788
793
|
return Object.entries(this.vars)
|
package/package.json
CHANGED
package/test/basic.html
CHANGED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>Extended</title>
|
|
6
|
+
<script src="../lightview.js?as=x-body"></script>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<script type="lightview/module">
|
|
10
|
+
const {array,boolean,number,object,string} = await import("../types.js");
|
|
11
|
+
|
|
12
|
+
self.variables({strictarray:array},{set:[]});
|
|
13
|
+
self.variables({strictboolean:boolean},{set:true});
|
|
14
|
+
self.variables({strictnumber:number},{set:0});
|
|
15
|
+
self.variables({strictobject:object},{set:{}});
|
|
16
|
+
self.variables({strictstring:string},{set:"test"});
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
self.variables({requiredarray:array({required:true})});
|
|
20
|
+
self.variables({requiredboolean:boolean({required:true})});
|
|
21
|
+
self.variables({requirednumber:number({required:true})});
|
|
22
|
+
self.variables({requiredobject:object({required:true})});
|
|
23
|
+
self.variables({requiredstring:string({required:true})});
|
|
24
|
+
|
|
25
|
+
//self.setVariableValue("requirednumber",null);
|
|
26
|
+
|
|
27
|
+
</script>
|
|
28
|
+
</body>
|
|
29
|
+
</html>
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import 'expect-puppeteer';
|
|
2
|
+
function reviver(property,value) {
|
|
3
|
+
if(value==="@-Infinity") return -Infinity;
|
|
4
|
+
if(value==="@Infinity") return Infinity;
|
|
5
|
+
if(value==="@NaN") return NaN;
|
|
6
|
+
return value;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
describe('Lightview - Variables', () => {
|
|
10
|
+
beforeAll(async () => {
|
|
11
|
+
await page.goto('http://localhost:8080/test/extended.html');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('should be titled "Extended"', async () => {
|
|
15
|
+
await expect(page.title()).resolves.toMatch('Extended');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('strictarray - should be array', async () => {
|
|
19
|
+
const result = await page.evaluate(() => {
|
|
20
|
+
return document.body.getVariableValue("strictarray")
|
|
21
|
+
});
|
|
22
|
+
expect(Array.isArray(result)).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('strictarray - should set', async () => {
|
|
26
|
+
const result = await page.evaluate(() => {
|
|
27
|
+
try {
|
|
28
|
+
document.body.setVariableValue("strictarray",[]);
|
|
29
|
+
} catch(e) {
|
|
30
|
+
return e;
|
|
31
|
+
}
|
|
32
|
+
return document.body.getVariableValue("strictarray")
|
|
33
|
+
});
|
|
34
|
+
expect(Array.isArray(result)).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('strictarray - should allow null', async () => {
|
|
38
|
+
const result = await page.evaluate(() => {
|
|
39
|
+
try {
|
|
40
|
+
document.body.setVariableValue("strictarray",undefined);
|
|
41
|
+
document.body.setVariableValue("strictarray",null);
|
|
42
|
+
} catch(e) {
|
|
43
|
+
return e;
|
|
44
|
+
}
|
|
45
|
+
return document.body.getVariableValue("strictarray")
|
|
46
|
+
});
|
|
47
|
+
expect(result).toBe(null);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('strictarray - should throw', async () => {
|
|
51
|
+
const result = await page.evaluate(() => {
|
|
52
|
+
try {
|
|
53
|
+
document.body.setVariableValue("strictarray","false");
|
|
54
|
+
} catch(e) {
|
|
55
|
+
return e.message;
|
|
56
|
+
}
|
|
57
|
+
return document.body.getVariableValue("strictarray");
|
|
58
|
+
});
|
|
59
|
+
const {name,validityState} = JSON.parse(result,reviver);
|
|
60
|
+
expect(name).toBe("strictarray");
|
|
61
|
+
expect(validityState.typeMismatch).toBe(true);
|
|
62
|
+
expect(validityState.value).toBe("false");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('strictboolean - should be boolean', async () => {
|
|
66
|
+
const result = await page.evaluate(() => {
|
|
67
|
+
return document.body.getVariableValue("strictboolean")
|
|
68
|
+
});
|
|
69
|
+
expect(result).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('strictboolean - should set', async () => {
|
|
73
|
+
const result = await page.evaluate(() => {
|
|
74
|
+
try {
|
|
75
|
+
document.body.setVariableValue("strictboolean",false);
|
|
76
|
+
} catch(e) {
|
|
77
|
+
return e.message;
|
|
78
|
+
}
|
|
79
|
+
return document.body.getVariableValue("strictboolean")
|
|
80
|
+
});
|
|
81
|
+
expect(result).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('strictboolean - should allow null', async () => {
|
|
85
|
+
const result = await page.evaluate(() => {
|
|
86
|
+
try {
|
|
87
|
+
document.body.setVariableValue("strictboolean",undefined);
|
|
88
|
+
document.body.setVariableValue("strictboolean",null);
|
|
89
|
+
} catch(e) {
|
|
90
|
+
return e.message;
|
|
91
|
+
}
|
|
92
|
+
return document.body.getVariableValue("strictboolean")
|
|
93
|
+
});
|
|
94
|
+
expect(result).toBe(null);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('strictboolean - should throw', async () => {
|
|
98
|
+
const result = await page.evaluate(() => {
|
|
99
|
+
try {
|
|
100
|
+
document.body.setVariableValue("strictboolean","true");
|
|
101
|
+
} catch(e) {
|
|
102
|
+
return e.message;
|
|
103
|
+
}
|
|
104
|
+
return document.body.getVariableValue("strictboolean");
|
|
105
|
+
});
|
|
106
|
+
const {name,validityState} = JSON.parse(result,reviver);
|
|
107
|
+
expect(name).toBe("strictboolean");
|
|
108
|
+
expect(validityState.typeMismatch).toBe(true);
|
|
109
|
+
expect(validityState.value).toBe("true");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('strictnumber - should be number', async () => {
|
|
113
|
+
const result = await page.evaluate(() => {
|
|
114
|
+
return document.body.getVariableValue("strictnumber")
|
|
115
|
+
});
|
|
116
|
+
expect(result).toBe(0);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('strictnumber - should set', async () => {
|
|
120
|
+
const result = await page.evaluate(() => {
|
|
121
|
+
try {
|
|
122
|
+
document.body.setVariableValue("strictnumber",1);
|
|
123
|
+
} catch(e) {
|
|
124
|
+
return e.message;
|
|
125
|
+
}
|
|
126
|
+
return document.body.getVariableValue("strictnumber")
|
|
127
|
+
});
|
|
128
|
+
expect(result).toBe(1);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('strictnumber - should allow null', async () => {
|
|
132
|
+
const result = await page.evaluate(() => {
|
|
133
|
+
try {
|
|
134
|
+
document.body.setVariableValue("strictnumber",undefined);
|
|
135
|
+
document.body.setVariableValue("strictnumber",null);
|
|
136
|
+
} catch(e) {
|
|
137
|
+
return e.message;
|
|
138
|
+
}
|
|
139
|
+
return document.body.getVariableValue("strictnumber")
|
|
140
|
+
});
|
|
141
|
+
expect(result).toBe(null);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('strictnumber - should throw', async () => {
|
|
145
|
+
const result = await page.evaluate(() => {
|
|
146
|
+
try {
|
|
147
|
+
document.body.setVariableValue("strictnumber","0");
|
|
148
|
+
} catch(e) {
|
|
149
|
+
return e.message;
|
|
150
|
+
}
|
|
151
|
+
return document.body.getVariableValue("strictnumber");
|
|
152
|
+
});
|
|
153
|
+
const {name,validityState} = JSON.parse(result,reviver);
|
|
154
|
+
expect(name).toBe("strictnumber");
|
|
155
|
+
expect(validityState.typeMismatch).toBe(true);
|
|
156
|
+
expect(validityState.value).toBe("0");
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('strictobject - should be object', async () => {
|
|
160
|
+
const result = await page.evaluate(() => {
|
|
161
|
+
return document.body.getVariableValue("strictobject")
|
|
162
|
+
});
|
|
163
|
+
expect(typeof(result)).toBe("object");
|
|
164
|
+
expect(Object.keys(result).length).toBe(0);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test('strictobject - should set', async () => {
|
|
168
|
+
const result = await page.evaluate(() => {
|
|
169
|
+
try {
|
|
170
|
+
document.body.setVariableValue("strictobject", {test:"test"});
|
|
171
|
+
} catch(e) {
|
|
172
|
+
return e.message;
|
|
173
|
+
}
|
|
174
|
+
return document.body.getVariableValue("strictobject")
|
|
175
|
+
});
|
|
176
|
+
expect(typeof(result)).toBe("object");
|
|
177
|
+
expect(Object.keys(result).length).toBe(1);
|
|
178
|
+
expect(result.test).toBe("test");
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test('strictobject - should allow null', async () => {
|
|
182
|
+
const result = await page.evaluate(() => {
|
|
183
|
+
try {
|
|
184
|
+
document.body.setVariableValue("strictobject",undefined);
|
|
185
|
+
document.body.setVariableValue("strictobject",null);
|
|
186
|
+
} catch(e) {
|
|
187
|
+
return e.message;
|
|
188
|
+
}
|
|
189
|
+
return document.body.getVariableValue("strictobject")
|
|
190
|
+
});
|
|
191
|
+
expect(result).toBe(null);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test('strictobject - should throw', async () => {
|
|
195
|
+
const result = await page.evaluate(() => {
|
|
196
|
+
try {
|
|
197
|
+
document.body.setVariableValue("strictobject","false");
|
|
198
|
+
} catch(e) {
|
|
199
|
+
return e.message;
|
|
200
|
+
}
|
|
201
|
+
return document.body.getVariableValue("strictobject");
|
|
202
|
+
});
|
|
203
|
+
const {name,validityState} = JSON.parse(result,reviver);
|
|
204
|
+
expect(name).toBe("strictobject");
|
|
205
|
+
expect(validityState.typeMismatch).toBe(true);
|
|
206
|
+
expect(validityState.value).toBe("false");
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test('strictstring - should be string', async () => {
|
|
210
|
+
const result = await page.evaluate(() => {
|
|
211
|
+
return document.body.getVariableValue("strictstring")
|
|
212
|
+
});
|
|
213
|
+
expect(result).toBe("test");
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test('strictstring - should set', async () => {
|
|
217
|
+
const result = await page.evaluate(() => {
|
|
218
|
+
try {
|
|
219
|
+
document.body.setVariableValue("strictstring","anothertest");
|
|
220
|
+
} catch(e) {
|
|
221
|
+
return e.message;
|
|
222
|
+
}
|
|
223
|
+
return document.body.getVariableValue("strictstring")
|
|
224
|
+
});
|
|
225
|
+
expect(result).toBe("anothertest");
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test('strictstring - should allow null', async () => {
|
|
229
|
+
const result = await page.evaluate(() => {
|
|
230
|
+
try {
|
|
231
|
+
document.body.setVariableValue("strictstring",undefined);
|
|
232
|
+
document.body.setVariableValue("strictstring",null);
|
|
233
|
+
} catch(e) {
|
|
234
|
+
return e.message;
|
|
235
|
+
}
|
|
236
|
+
return document.body.getVariableValue("strictstring")
|
|
237
|
+
});
|
|
238
|
+
expect(result).toBe(null);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test('strictstring - should throw', async () => {
|
|
242
|
+
const result = await page.evaluate(() => {
|
|
243
|
+
try {
|
|
244
|
+
document.body.setVariableValue("strictstring",0);
|
|
245
|
+
} catch(e) {
|
|
246
|
+
return e.message;
|
|
247
|
+
}
|
|
248
|
+
return document.body.getVariableValue("strictstring");
|
|
249
|
+
});
|
|
250
|
+
const {name,validityState} = JSON.parse(result,reviver);
|
|
251
|
+
expect(name).toBe("strictstring");
|
|
252
|
+
expect(validityState.typeMismatch).toBe(true);
|
|
253
|
+
expect(validityState.value).toBe(0);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test('requirednumber - should throw and not allow null', async () => {
|
|
257
|
+
const result = await page.evaluate(() => {
|
|
258
|
+
try {
|
|
259
|
+
document.body.setVariableValue("requirednumber",null);
|
|
260
|
+
} catch(e) {
|
|
261
|
+
return e.message;
|
|
262
|
+
}
|
|
263
|
+
return document.body.getVariableValue("requirednumber")
|
|
264
|
+
});
|
|
265
|
+
const {name,validityState} = JSON.parse(result,reviver);
|
|
266
|
+
expect(name).toBe("requirednumber");
|
|
267
|
+
expect(validityState.valueMissing).toBe(true);
|
|
268
|
+
//expect(validityState.value).toBe(null);
|
|
269
|
+
});
|
|
270
|
+
})
|
package/types.js
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
const toJSON = (value) => {
|
|
2
|
+
if([-Infinity,Infinity].includes(value)) return `@${value}`;
|
|
3
|
+
if(typeof(value)==="number" && isNaN(value)) return "@NaN";
|
|
4
|
+
if(value && typeof(value)==="object") {
|
|
5
|
+
return Object.entries(value)
|
|
6
|
+
.reduce((json,[key,value]) => {
|
|
7
|
+
if(value && typeof(value)==="object" && value.toJSON) value = value.toJSON();
|
|
8
|
+
json[key] = toJSON(value);
|
|
9
|
+
return json;
|
|
10
|
+
},Array.isArray(value) ? [] : {})
|
|
11
|
+
}
|
|
12
|
+
return value;
|
|
13
|
+
};
|
|
14
|
+
function reviver(property,value) {
|
|
15
|
+
if(value==="@-Infinity") return -Infinity;
|
|
16
|
+
if(value==="@Infinity") return Infinity;
|
|
17
|
+
if(value==="@NaN") return NaN;
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
|
|
1
21
|
function ValidityState(options) {
|
|
2
22
|
if(!this || !(this instanceof ValidityState)) return new ValidityState(options);
|
|
3
23
|
Object.assign(this,{
|
|
@@ -15,9 +35,17 @@ function ValidityState(options) {
|
|
|
15
35
|
},options);
|
|
16
36
|
}
|
|
17
37
|
|
|
38
|
+
function DataType(options) {
|
|
39
|
+
if(!this || !(this instanceof DataType)) return new DataType(options);
|
|
40
|
+
Object.assign(this,options);
|
|
41
|
+
}
|
|
42
|
+
DataType.prototype.toJSON = function() {
|
|
43
|
+
return toJSON(this);
|
|
44
|
+
}
|
|
45
|
+
|
|
18
46
|
const tryParse = (value) => {
|
|
19
47
|
try {
|
|
20
|
-
return JSON.parse(value+"")
|
|
48
|
+
return JSON.parse(value+"",reviver)
|
|
21
49
|
} catch(e) {
|
|
22
50
|
|
|
23
51
|
}
|
|
@@ -25,7 +53,7 @@ const tryParse = (value) => {
|
|
|
25
53
|
|
|
26
54
|
const ifInvalid = (variable) => {
|
|
27
55
|
variable.validityState.type = typeof(variable.type)==="string" ? variable.type : variable.type.type;
|
|
28
|
-
throw new TypeError(JSON.stringify(variable));
|
|
56
|
+
throw new TypeError(JSON.stringify(DataType(variable)));
|
|
29
57
|
// or could return existing value variable.value
|
|
30
58
|
// or could return nothing
|
|
31
59
|
}
|
|
@@ -99,6 +127,7 @@ const array = ({coerce=false, required = false,whenInvalid = ifInvalid,maxlength
|
|
|
99
127
|
}
|
|
100
128
|
}
|
|
101
129
|
array.validate = validateArray;
|
|
130
|
+
array.whenInvalid = ifInvalid;
|
|
102
131
|
array.coerce = false;
|
|
103
132
|
array.required = false;
|
|
104
133
|
|
|
@@ -110,7 +139,7 @@ const validateBoolean = function(value,variable) {
|
|
|
110
139
|
if(this.required && value==null) {
|
|
111
140
|
variable.validityState = ValidityState({valueMissing: true});
|
|
112
141
|
} else {
|
|
113
|
-
const result =
|
|
142
|
+
const result = this.coerce ? tryParse(value) : value;
|
|
114
143
|
if(typeof(result)!=="boolean") {
|
|
115
144
|
variable.validityState = ValidityState({typeMismatch: true, value});
|
|
116
145
|
} else {
|
|
@@ -135,6 +164,7 @@ const boolean = ({coerce=false,required=false, whenInvalid = ifInvalid,...rest})
|
|
|
135
164
|
}
|
|
136
165
|
}
|
|
137
166
|
boolean.validate = validateBoolean;
|
|
167
|
+
boolean.whenInvalid = ifInvalid;
|
|
138
168
|
boolean.coerce = false;
|
|
139
169
|
boolean.required = false;
|
|
140
170
|
|
|
@@ -186,11 +216,12 @@ const number = ({coerce=false,required = false,whenInvalid = ifInvalid,min=-Infi
|
|
|
186
216
|
}
|
|
187
217
|
}
|
|
188
218
|
number.validate = validateNumber;
|
|
219
|
+
number.whenInvalid = ifInvalid;
|
|
189
220
|
number.min = -Infinity;
|
|
190
221
|
number.max = Infinity;
|
|
191
222
|
number.coerce = false;
|
|
192
223
|
number.required = false;
|
|
193
|
-
number.
|
|
224
|
+
number.allowNaN = true;
|
|
194
225
|
number.step = 1;
|
|
195
226
|
|
|
196
227
|
const validateObject = function(value,variable) {
|
|
@@ -225,6 +256,7 @@ const object = ({coerce=false, required = false,whenInvalid = ifInvalid,...rest}
|
|
|
225
256
|
}
|
|
226
257
|
}
|
|
227
258
|
object.validate = validateObject;
|
|
259
|
+
object.whenInvalid = ifInvalid;
|
|
228
260
|
object.coerce = false;
|
|
229
261
|
object.required = false;
|
|
230
262
|
|
|
@@ -269,6 +301,7 @@ const string = ({coerce=false, required = false,whenInvalid = ifInvalid, maxleng
|
|
|
269
301
|
}
|
|
270
302
|
}
|
|
271
303
|
string.validate = validateString;
|
|
304
|
+
string.whenInvalid = ifInvalid;
|
|
272
305
|
string.coerce = false;
|
|
273
306
|
string.required = false;
|
|
274
307
|
string.maxlength = Infinity;
|
|
@@ -306,6 +339,7 @@ const symbol = ({coerce=false,required=false, whenInvalid = ifInvalid,...rest})
|
|
|
306
339
|
}
|
|
307
340
|
}
|
|
308
341
|
symbol.validate = validateSymbol;
|
|
342
|
+
symbol.whenInvalid = ifInvalid;
|
|
309
343
|
symbol.coerce = false;
|
|
310
344
|
symbol.required = false;
|
|
311
345
|
|
|
@@ -404,9 +438,9 @@ const handleRemote = async ({variable, config, reactive, component},doput) => {
|
|
|
404
438
|
: get(href,variable));
|
|
405
439
|
if(variable.value===undefined) variable.value = value; // do not await here
|
|
406
440
|
} else if (remote && type === "object") {
|
|
407
|
-
let href;
|
|
408
441
|
if(!config.path) config.path = `./${variable.name}`;
|
|
409
|
-
if(config.path)
|
|
442
|
+
if(config.path.endsWith("/")) config.path = `${config.path}${variable.name}`;
|
|
443
|
+
const href = new URL(config.path,window.location.href).href;
|
|
410
444
|
if(!config.get || !config.put) {
|
|
411
445
|
if(!href) throw new Error(`A remote path is required if no put function is provided for remote data`)
|
|
412
446
|
if(!config.get) config.get = get;
|
|
@@ -425,12 +459,12 @@ const handleRemote = async ({variable, config, reactive, component},doput) => {
|
|
|
425
459
|
}
|
|
426
460
|
if(value) {
|
|
427
461
|
const json = await value;
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
462
|
+
//value.then((json) => {
|
|
463
|
+
if (json && typeof (json) === "object" && reactive) {
|
|
464
|
+
variable.value = remoteProxy({json, variable,config, reactive, component});
|
|
465
|
+
} else {
|
|
466
|
+
component.setVariableValue(variable.name,json);
|
|
467
|
+
}
|
|
434
468
|
//})
|
|
435
469
|
}
|
|
436
470
|
}
|
|
@@ -442,4 +476,4 @@ const remote = (config) => {
|
|
|
442
476
|
}
|
|
443
477
|
}
|
|
444
478
|
|
|
445
|
-
export {ValidityState,any,array,boolean,number,string,symbol,remote}
|
|
479
|
+
export {ValidityState,any,array,boolean,number,object,string,symbol,remote,reviver}
|