lightview 1.5.1-b → 1.6.2-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/examples/counter.html +3 -9
- package/examples/counter.test.mjs +47 -0
- package/examples/invalid-template-literals.html +45 -0
- package/examples/remote.html +3 -1
- package/examples/remote.json +1 -1
- package/examples/sensors/index.html +30 -0
- package/examples/sensors/sensor-server.js +30 -0
- package/lightview.js +42 -183
- package/package.json +1 -1
- package/test/basic.html +1 -1
- package/types.js +141 -8
package/examples/counter.html
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<head>
|
|
2
|
-
<title>Counter</title>
|
|
2
|
+
<title>Lightview:Examples:Counter</title>
|
|
3
3
|
<script src="../lightview.js?as=x-body"></script>
|
|
4
4
|
</head>
|
|
5
5
|
|
|
@@ -9,15 +9,9 @@
|
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<script type="lightview/module">
|
|
12
|
-
|
|
13
|
-
self.variables({
|
|
14
|
-
count: "number"
|
|
15
|
-
}, {
|
|
16
|
-
reactive
|
|
17
|
-
});
|
|
18
|
-
count = 0;
|
|
12
|
+
self.variables({count: "number",}, {reactive,set:0});
|
|
19
13
|
self.bump = () => count++;
|
|
20
|
-
|
|
14
|
+
</script>
|
|
21
15
|
|
|
22
16
|
<style>
|
|
23
17
|
button {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import 'expect-puppeteer';
|
|
2
|
+
|
|
3
|
+
describe('Lightview:Example:Counter', () => {
|
|
4
|
+
beforeAll(async () => {
|
|
5
|
+
await page.goto('http://localhost:8080/examples/counter.html');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
test('should be titled "Lightview:Examples:Counter"', async () => {
|
|
9
|
+
await expect(page.title()).resolves.toMatch("Lightview:Examples:Counter");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('count should be a variable', async () => {
|
|
13
|
+
const result = await page.evaluate(async () => {
|
|
14
|
+
return document.body.getVariable("count");
|
|
15
|
+
});
|
|
16
|
+
expect(result).toBeDefined();
|
|
17
|
+
const {name,type,value,reactive} = result;
|
|
18
|
+
expect(name).toBe("count");
|
|
19
|
+
expect(type).toBe("number");
|
|
20
|
+
expect(value).toBe(0);
|
|
21
|
+
expect(reactive).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('bump should be called', async () => {
|
|
25
|
+
const result = await page.evaluate(async () => {
|
|
26
|
+
document.body.bump();
|
|
27
|
+
return document.body.getVariableValue("count");
|
|
28
|
+
});
|
|
29
|
+
expect(result).toBe(1);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('click should bump', async () => {
|
|
33
|
+
const buttonHandle = await page.evaluateHandle('document.body.querySelector("button")');
|
|
34
|
+
await buttonHandle.click();
|
|
35
|
+
const result = await page.evaluate(async () => {
|
|
36
|
+
return document.body.getVariableValue("count");
|
|
37
|
+
});
|
|
38
|
+
expect(result).toBe(2);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("should be custom element", async() => {
|
|
42
|
+
const result = await page.evaluate(async () => {
|
|
43
|
+
return document.body.constructor.name;
|
|
44
|
+
});
|
|
45
|
+
expect(result).toBe("CustomElement");
|
|
46
|
+
})
|
|
47
|
+
})
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<title>Invalid Template Literals</title>
|
|
5
|
+
<script src="../lightview.js?as=x-body"></script>
|
|
6
|
+
</head>
|
|
7
|
+
<body>
|
|
8
|
+
<p>
|
|
9
|
+
<button l-on:click="bump">Click count:${count}</button>
|
|
10
|
+
</p>
|
|
11
|
+
<div style="margin:20px">
|
|
12
|
+
<p>
|
|
13
|
+
${"<h1>"+(test++)+"</h1>"}
|
|
14
|
+
</p>
|
|
15
|
+
<p>
|
|
16
|
+
${(while (test)<10 { test++}; test)}
|
|
17
|
+
</p>
|
|
18
|
+
<p>
|
|
19
|
+
${(() =>test)()}
|
|
20
|
+
</p>
|
|
21
|
+
<p>
|
|
22
|
+
${(() = >test)()}
|
|
23
|
+
</p>
|
|
24
|
+
<p>
|
|
25
|
+
${function(){return \${test}})()}
|
|
26
|
+
</p>
|
|
27
|
+
<p>
|
|
28
|
+
${window.alert("ok")}
|
|
29
|
+
</p>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<script type="lightview/module">
|
|
33
|
+
self.variables({count: "number",test:"number"}, {reactive,set:0});
|
|
34
|
+
self.bump = () => count++;
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<style>
|
|
38
|
+
button {
|
|
39
|
+
margin: 20px;
|
|
40
|
+
background: gray
|
|
41
|
+
}
|
|
42
|
+
</style>
|
|
43
|
+
</body>
|
|
44
|
+
|
|
45
|
+
</html>
|
package/examples/remote.html
CHANGED
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
<script type="lightview/module">
|
|
16
|
-
|
|
16
|
+
const {remote} = await import("../types.js");
|
|
17
|
+
|
|
18
|
+
self.variables({myRemote:"object"},{reactive,remote:remote("http://localhost:8000/remote.json")});
|
|
17
19
|
|
|
18
20
|
await myRemote; // must await remotes before the first time they are used, e.g. before HTML is rendered
|
|
19
21
|
|
package/examples/remote.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"joe","age":
|
|
1
|
+
{"name":"joe","age":30}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<head>
|
|
3
|
+
<title>Lightview:Sensor Demo</title>
|
|
4
|
+
<link href="../../components/gauge.html" rel="module">
|
|
5
|
+
<script src="../../lightview.js?as=x-body"></script>
|
|
6
|
+
</head>
|
|
7
|
+
<body>
|
|
8
|
+
<div style="width:100%;text-align:center">
|
|
9
|
+
<div style="display:inline-block">
|
|
10
|
+
<l-gauge id="sensor1" type="Gauge" label="Sensor One" value="50"></l-gauge>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<div style="display:inline-block">
|
|
14
|
+
<l-gauge id="sensor2" type="Gauge" label="Sensor Two" value="50"></l-gauge>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<script type="lightview/module">
|
|
19
|
+
const {remote} = await import("../../types.js");
|
|
20
|
+
self.variables({sensor1:"number"},{remote:remote({ttl:5000,path:"https://lightview.dev/sensors/sensor1"})});
|
|
21
|
+
self.variables({sensor2:"number"},{remote:remote({ttl:7500,path:"https://lightview.dev/sensors/sensor2"})});
|
|
22
|
+
await sensor1;
|
|
23
|
+
await sensor2;
|
|
24
|
+
addEventListener("change",({variableName,value}) => {
|
|
25
|
+
const sensor = document.body.getElementById(variableName);
|
|
26
|
+
sensor.setValue(value);
|
|
27
|
+
});
|
|
28
|
+
</script>
|
|
29
|
+
</body>
|
|
30
|
+
</html>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const http = require("http"),
|
|
2
|
+
fs = require("fs"),
|
|
3
|
+
host = 'localhost',
|
|
4
|
+
port = 8000,
|
|
5
|
+
requestListener = async function (req, res) {
|
|
6
|
+
const path = `.${req.url}`;
|
|
7
|
+
res.setHeader("Access-Control-Allow-Origin","*");
|
|
8
|
+
res.setHeader("Access-Control-Allow-Methods", "GET,OPTIONS");
|
|
9
|
+
res.setHeader("Access-Control-Allow-Headers", "*");
|
|
10
|
+
res.setHeader("Content-Type", "application/json");
|
|
11
|
+
if(req.method==="OPTIONS") {
|
|
12
|
+
res.end();
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if(req.method==="GET") {
|
|
16
|
+
const value = `${40 + Math.round(60 * Math.random())}`;
|
|
17
|
+
console.log("GET",req.url,"<-",value);
|
|
18
|
+
res.setHeader("Content-Length", value.length);
|
|
19
|
+
res.write(value);
|
|
20
|
+
res.end();
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
console.log(req.method);
|
|
24
|
+
},
|
|
25
|
+
server = http.createServer(requestListener);
|
|
26
|
+
server.listen(port, host, () => {
|
|
27
|
+
console.log(`Server is running on http://${host}:${port}`);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
|
package/lightview.js
CHANGED
|
@@ -251,8 +251,8 @@ const {observe} = (() => {
|
|
|
251
251
|
target.postEvent.value("change", event);
|
|
252
252
|
if (event.defaultPrevented) {
|
|
253
253
|
target[property].value = value;
|
|
254
|
-
} else if(remote) {
|
|
255
|
-
handleRemote({variable,remote,reactive},true);
|
|
254
|
+
} else if(remote && remote.put) {
|
|
255
|
+
remote.handleRemote({variable,config:remote.config,reactive},true);
|
|
256
256
|
}
|
|
257
257
|
}
|
|
258
258
|
return true;
|
|
@@ -357,15 +357,6 @@ const {observe} = (() => {
|
|
|
357
357
|
}
|
|
358
358
|
return node?.nodeValue;
|
|
359
359
|
}
|
|
360
|
-
const render = (hasTemplate, render) => {
|
|
361
|
-
let observer;
|
|
362
|
-
if (hasTemplate) {
|
|
363
|
-
if (observer) observer.cancel();
|
|
364
|
-
observer = observe(render)
|
|
365
|
-
} else {
|
|
366
|
-
render();
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
360
|
const inputTypeToType = (inputType) => {
|
|
370
361
|
if (!inputType) return "any"
|
|
371
362
|
if (["text", "tel", "email", "url", "search", "radio", "color", "password"].includes(inputType)) return "string";
|
|
@@ -430,8 +421,7 @@ const {observe} = (() => {
|
|
|
430
421
|
reactive: {value: true, constant: true},
|
|
431
422
|
shared: {value: true, constant: true},
|
|
432
423
|
exported: {value: true, constant: true},
|
|
433
|
-
imported: {value: true, constant: true}
|
|
434
|
-
remote: {}
|
|
424
|
+
imported: {value: true, constant: true}
|
|
435
425
|
};
|
|
436
426
|
const createClass = (domElementNode, {observer, framed}) => {
|
|
437
427
|
const instances = new Set(),
|
|
@@ -475,7 +465,7 @@ const {observe} = (() => {
|
|
|
475
465
|
constant: true
|
|
476
466
|
},
|
|
477
467
|
postEvent: {
|
|
478
|
-
value: (eventName, event={}) => {
|
|
468
|
+
value: (eventName, event = {}) => {
|
|
479
469
|
//event = {...event}
|
|
480
470
|
event.type = eventName;
|
|
481
471
|
event.target = currentComponent;
|
|
@@ -558,13 +548,12 @@ const {observe} = (() => {
|
|
|
558
548
|
const nodes = getNodes(ctx);
|
|
559
549
|
nodes.forEach((node) => {
|
|
560
550
|
if (node.nodeType === Node.TEXT_NODE && node.template.includes("${")) {
|
|
561
|
-
|
|
551
|
+
observe(() => resolveNodeOrText(node, this));
|
|
562
552
|
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
563
553
|
// resolve the value before all else;
|
|
564
554
|
const attr = node.attributes.value,
|
|
565
555
|
template = attr?.template;
|
|
566
556
|
if (attr && template) {
|
|
567
|
-
//render(!!template, () => {
|
|
568
557
|
let value = resolveNodeOrText(attr, this),
|
|
569
558
|
eltype = node.attributes.type ? resolveNodeOrText(node.attributes.type, ctx) : null;
|
|
570
559
|
const template = attr.template;
|
|
@@ -612,22 +601,22 @@ const {observe} = (() => {
|
|
|
612
601
|
const {name, value} = attr,
|
|
613
602
|
vname = node.attributes.name?.value;
|
|
614
603
|
if (name === "type" && value=="radio" && vname) {
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
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
|
+
});
|
|
626
615
|
}
|
|
627
616
|
|
|
628
617
|
const [type, ...params] = name.split(":");
|
|
629
618
|
if (type === "") { // name is :something
|
|
630
|
-
|
|
619
|
+
observe(() => {
|
|
631
620
|
const value = attr.value;
|
|
632
621
|
if (params[0]) {
|
|
633
622
|
if (value === "true") node.setAttribute(params[0], "")
|
|
@@ -643,20 +632,20 @@ const {observe} = (() => {
|
|
|
643
632
|
})
|
|
644
633
|
} else if (type === "l-on") {
|
|
645
634
|
let listener;
|
|
646
|
-
|
|
635
|
+
observe(() => {
|
|
647
636
|
const value = resolveNodeOrText(attr, this);
|
|
648
637
|
if (listener) node.removeEventListener(params[0], listener);
|
|
649
638
|
listener = this[value] || window[value] || Function(value);
|
|
650
639
|
addListener(node, params[0], listener);
|
|
651
640
|
})
|
|
652
641
|
} else if (type === "l-if") {
|
|
653
|
-
|
|
642
|
+
observe(() => {
|
|
654
643
|
const value = resolveNodeOrText(attr, this);
|
|
655
644
|
node.style.setProperty("display", value === "true" ? "revert" : "none");
|
|
656
645
|
})
|
|
657
646
|
} else if (type === "l-for") {
|
|
658
647
|
node.template ||= node.innerHTML;
|
|
659
|
-
|
|
648
|
+
observe(() => {
|
|
660
649
|
const [what = "each", vname = "item", index = "index", array = "array", after = false] = params,
|
|
661
650
|
value = resolveNodeOrText(attr, this),
|
|
662
651
|
coerced = coerce(value, what === "each" ? Array : "object"),
|
|
@@ -683,8 +672,8 @@ const {observe} = (() => {
|
|
|
683
672
|
else node.appendChild(parsed.body.firstChild);
|
|
684
673
|
}
|
|
685
674
|
})
|
|
686
|
-
} else {
|
|
687
|
-
|
|
675
|
+
} else if(attr.template) {
|
|
676
|
+
observe(() => {
|
|
688
677
|
resolveNodeOrText(attr, this);
|
|
689
678
|
})
|
|
690
679
|
}
|
|
@@ -699,11 +688,9 @@ const {observe} = (() => {
|
|
|
699
688
|
}
|
|
700
689
|
adoptedCallback(callback) {
|
|
701
690
|
this.vars.postEvent.value("adopted");
|
|
702
|
-
super.adoptedCallback();
|
|
703
691
|
}
|
|
704
692
|
disconnectedCallback() {
|
|
705
693
|
this.vars.postEvent.value("disconnected");
|
|
706
|
-
super.disconnectedCallback();
|
|
707
694
|
}
|
|
708
695
|
get observedAttributes() {
|
|
709
696
|
return CustomElement.observedAttributes;
|
|
@@ -713,9 +700,12 @@ const {observe} = (() => {
|
|
|
713
700
|
}
|
|
714
701
|
|
|
715
702
|
getVariableNames() {
|
|
716
|
-
return Object.keys(this.vars)
|
|
717
|
-
|
|
718
|
-
|
|
703
|
+
return Object.keys(this.vars)
|
|
704
|
+
.filter(name => !(name in reserved) && !["self", "addEventListener", "postEvent"].includes(name))
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
getVariable(name) {
|
|
708
|
+
return this.vars[name] ? {...this.vars[name]} : undefined;
|
|
719
709
|
}
|
|
720
710
|
|
|
721
711
|
setVariableValue(variableName, value, {coerceTo = typeof (value)} = {}) {
|
|
@@ -751,17 +741,25 @@ const {observe} = (() => {
|
|
|
751
741
|
return this.vars[variableName]?.value;
|
|
752
742
|
}
|
|
753
743
|
|
|
754
|
-
variables(variables, {observed, reactive, shared, exported, imported, remote} = {}) { // options = {observed,reactive,shared,exported,imported}
|
|
744
|
+
variables(variables, {observed, reactive, shared, exported, imported, remote, constant,set} = {}) { // options = {observed,reactive,shared,exported,imported}
|
|
755
745
|
const addEventListener = this.varsProxy.addEventListener;
|
|
756
746
|
if (variables !== undefined) {
|
|
757
747
|
Object.entries(variables)
|
|
758
748
|
.forEach(([key, type]) => {
|
|
759
749
|
const variable = this.vars[key] ||= {name: key, type};
|
|
750
|
+
if(set!==undefined && constant!==undefined) throw new TypeError(`${key} has the constant value ${constant} and can't be set to ${set}`);
|
|
751
|
+
variable.value = set;
|
|
752
|
+
if(constant!==undefined) {
|
|
753
|
+
variable.constant = true;
|
|
754
|
+
variable.value = constant;
|
|
755
|
+
}
|
|
760
756
|
if (observed || imported) {
|
|
761
757
|
variable.value = this.hasAttribute(key) ? coerce(this.getAttribute(key), variable.type) : variable.value;
|
|
762
|
-
variable.observed = observed;
|
|
763
758
|
variable.imported = imported;
|
|
764
|
-
if(variable.observed)
|
|
759
|
+
if(variable.observed) {
|
|
760
|
+
variable.observed = observed;
|
|
761
|
+
this.observedAttributes.add(key);
|
|
762
|
+
}
|
|
765
763
|
}
|
|
766
764
|
if (reactive) {
|
|
767
765
|
variable.reactive = true;
|
|
@@ -780,10 +778,11 @@ const {observe} = (() => {
|
|
|
780
778
|
this.changeListener.targets.add(key);
|
|
781
779
|
}
|
|
782
780
|
if (remote) {
|
|
781
|
+
if(typeof(remote)==="function") remote = remote(`./${key}`);
|
|
783
782
|
variable.remote = remote;
|
|
784
|
-
handleRemote({variable, remote, reactive});
|
|
783
|
+
remote.handleRemote({variable, config:remote.config, reactive,component:this});
|
|
785
784
|
}
|
|
786
|
-
if(type.validate) type.validate(
|
|
785
|
+
if(type.validate) type.validate(variable.value,variable);
|
|
787
786
|
});
|
|
788
787
|
}
|
|
789
788
|
return Object.entries(this.vars)
|
|
@@ -792,146 +791,6 @@ const {observe} = (() => {
|
|
|
792
791
|
return result;
|
|
793
792
|
}, {});
|
|
794
793
|
}
|
|
795
|
-
|
|
796
|
-
constants(variables) {
|
|
797
|
-
if (variables !== undefined) {
|
|
798
|
-
Object.entries(variables)
|
|
799
|
-
.forEach(([key, value]) => {
|
|
800
|
-
const type = typeof (value) === "function" ? value : typeof (value),
|
|
801
|
-
variable = this.vars[key];
|
|
802
|
-
if (variable !== undefined) throw new TypeError(`${variable.constant ? "const" : "let"} ${key}:${variable.type} already declared.`);
|
|
803
|
-
if (value === undefined) throw new TypeError(`const ${key}:undefined must be initialized.`);
|
|
804
|
-
this.vars[key] = {type, value, constant: true};
|
|
805
|
-
})
|
|
806
|
-
}
|
|
807
|
-
return Object.entries(this.vars)
|
|
808
|
-
.reduce((result, [key, variable]) => {
|
|
809
|
-
if (variable.constant) result[key] = {...variable};
|
|
810
|
-
return result;
|
|
811
|
-
}, {});
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
const remoteProxy = ({json, variable,remote, reactive}) => {
|
|
817
|
-
const type = typeof (remote);
|
|
818
|
-
return new Proxy(json, {
|
|
819
|
-
get(target,property) {
|
|
820
|
-
if(property==="__remoteProxytarget__") return json;
|
|
821
|
-
return target[property];
|
|
822
|
-
},
|
|
823
|
-
async set(target, property, value) {
|
|
824
|
-
if(value && typeof(value)==="object" && value instanceof Promise) value = await value;
|
|
825
|
-
const oldValue = target[property];
|
|
826
|
-
if (oldValue !== value) {
|
|
827
|
-
let remotevalue;
|
|
828
|
-
if (type === "string") {
|
|
829
|
-
const href = new URL(remote,window.location.href).href;
|
|
830
|
-
remotevalue = patch({target,property,value,oldValue},href,variable);
|
|
831
|
-
} else if(remote && type==="object") {
|
|
832
|
-
let href;
|
|
833
|
-
if(remote.path) href = new URL(remote.path,window.location.href).href;
|
|
834
|
-
if(!remote.patch) {
|
|
835
|
-
if(!href) throw new Error(`A remote path is required is no put function is provided for remote data`)
|
|
836
|
-
remote.patch = patch;
|
|
837
|
-
}
|
|
838
|
-
remotevalue = remote.patch({target,property,value,oldValue},href,variable);
|
|
839
|
-
}
|
|
840
|
-
if(remotevalue) {
|
|
841
|
-
await remotevalue.then((newjson) => {
|
|
842
|
-
if (newjson && typeof (newjson) === "object" && reactive) {
|
|
843
|
-
const target = variable.value?.__reactorProxyTarget__ ? json : variable.value;
|
|
844
|
-
Object.entries(newjson).forEach(([key,newValue]) => {
|
|
845
|
-
if(target[key]!==newValue) target[key] = newValue;
|
|
846
|
-
})
|
|
847
|
-
Object.keys(target).forEach((key) => {
|
|
848
|
-
if(!(key in newjson)) delete target[key];
|
|
849
|
-
});
|
|
850
|
-
if(variable.value?.__reactorProxyTarget__) {
|
|
851
|
-
const dependents = variable.value.__dependents__,
|
|
852
|
-
observers = dependents[property] || [];
|
|
853
|
-
[...observers].forEach((f) => {
|
|
854
|
-
if (f.cancelled) dependents[property].delete(f);
|
|
855
|
-
else f();
|
|
856
|
-
})
|
|
857
|
-
}
|
|
858
|
-
} else {
|
|
859
|
-
variable.value = json;
|
|
860
|
-
}
|
|
861
|
-
})
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
return true;
|
|
865
|
-
}
|
|
866
|
-
})
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
const patch = ({target,property,value,oldValue},href,variable) => {
|
|
870
|
-
return fetch(href, {
|
|
871
|
-
method: "PATCH",
|
|
872
|
-
body: JSON.stringify({property,value,oldValue}),
|
|
873
|
-
headers: {
|
|
874
|
-
"Content-Type": "application/json"
|
|
875
|
-
}
|
|
876
|
-
}).then((response) => {
|
|
877
|
-
if (response.status < 400) return response.json();
|
|
878
|
-
})
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
const get = (href,variable) => {
|
|
882
|
-
return fetch(href)
|
|
883
|
-
.then((response) => {
|
|
884
|
-
if (response.status < 400) return response.json();
|
|
885
|
-
})
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
const put = (href,variable) => {
|
|
889
|
-
return fetch(href, {
|
|
890
|
-
method: "PUT",
|
|
891
|
-
body: JSON.stringify(variable.value),
|
|
892
|
-
headers: {
|
|
893
|
-
"Content-Type": "application/json"
|
|
894
|
-
}
|
|
895
|
-
}).then((response) => {
|
|
896
|
-
if (response.status === 200) return response.json();
|
|
897
|
-
})
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
const handleRemote = async ({variable, remote, reactive},doput) => {
|
|
901
|
-
const type = typeof (remote);
|
|
902
|
-
let value;
|
|
903
|
-
if (type === "string") {
|
|
904
|
-
const href = new URL(remote,window.location.href).href;
|
|
905
|
-
value = (doput
|
|
906
|
-
? put(href,variable)
|
|
907
|
-
: get(href,variable));
|
|
908
|
-
if(variable.value===undefined) variable.value = value;
|
|
909
|
-
} else if (remote && type === "object") {
|
|
910
|
-
let href;
|
|
911
|
-
if(remote.path) href = new URL(remote.path,window.location.href).href;
|
|
912
|
-
if(!remote.get || !remote.put) {
|
|
913
|
-
if(!href) throw new Error(`A remote path is required is no put function is provided for remote data`)
|
|
914
|
-
if(!remote.get) remote.get = get;
|
|
915
|
-
if(!remote.put) remote.put = put;
|
|
916
|
-
}
|
|
917
|
-
value = (doput
|
|
918
|
-
? remote.put(href,variable)
|
|
919
|
-
: remote.get(href,variable));
|
|
920
|
-
if(remote.ttl && !doput && !remote.intervalId) {
|
|
921
|
-
remote.intervalId = setInterval(async () => {
|
|
922
|
-
await handleRemote({variable, remote, reactive});
|
|
923
|
-
})
|
|
924
|
-
}
|
|
925
|
-
if(variable.value===undefined) variable.value = value;
|
|
926
|
-
}
|
|
927
|
-
if(value) {
|
|
928
|
-
variable.value = await value.then((json) => {
|
|
929
|
-
if (json && typeof (json) === "object" && reactive) {
|
|
930
|
-
return remoteProxy({json, variable,remote, reactive})
|
|
931
|
-
} else {
|
|
932
|
-
return json;
|
|
933
|
-
}
|
|
934
|
-
})
|
|
935
794
|
}
|
|
936
795
|
}
|
|
937
796
|
|
package/package.json
CHANGED
package/test/basic.html
CHANGED
package/types.js
CHANGED
|
@@ -40,7 +40,7 @@ const validateAny = function(value,variable) {
|
|
|
40
40
|
variable.validityState = ValidityState({valid:true});
|
|
41
41
|
return value;
|
|
42
42
|
}
|
|
43
|
-
return this.whenInvalid(variable);
|
|
43
|
+
return this.whenInvalid(variable,value);
|
|
44
44
|
}
|
|
45
45
|
const any = ({required=false,whenInvalid = ifInvalid,...rest}) => { // ...rest allows use of property "default", which is otherwise reserved
|
|
46
46
|
if(typeof(required)!=="boolean") throw new TypeError(`required, ${JSON.stringify(required)}, must be a boolean`);
|
|
@@ -78,7 +78,7 @@ const validateArray = function(value,variable) {
|
|
|
78
78
|
return result;
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
|
-
return this.whenInvalid(variable);
|
|
81
|
+
return this.whenInvalid(variable,value);
|
|
82
82
|
}
|
|
83
83
|
const array = ({coerce=false, required = false,whenInvalid = ifInvalid,maxlength=Infinity,minlength=0,...rest}) => {
|
|
84
84
|
if(typeof(coerce)!=="boolean") throw new TypeError(`coerce, ${JSON.stringify(coerce)}, must be a boolean`);
|
|
@@ -118,7 +118,7 @@ const validateBoolean = function(value,variable) {
|
|
|
118
118
|
return result;
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
|
-
return this.whenInvalid(variable);
|
|
121
|
+
return this.whenInvalid(variable,value);
|
|
122
122
|
}
|
|
123
123
|
const boolean = ({coerce=false,required=false, whenInvalid = ifInvalid,...rest}) =>{
|
|
124
124
|
if(typeof(coerce)!=="boolean") throw new TypeError(`coerce, ${JSON.stringify(coerce)}, must be a boolean`);
|
|
@@ -161,7 +161,7 @@ const validateNumber = function(value,variable) {
|
|
|
161
161
|
return result;
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
|
-
return this.whenInvalid(variable);
|
|
164
|
+
return this.whenInvalid(variable,value);
|
|
165
165
|
}
|
|
166
166
|
const number = ({coerce=false,required = false,whenInvalid = ifInvalid,min=-Infinity,max=Infinity,step = 1,allowNaN = true,...rest}) => {
|
|
167
167
|
if(typeof(coerce)!=="boolean") throw new TypeError(`coerce, ${JSON.stringify(coerce)}, must be a boolean`);
|
|
@@ -208,7 +208,7 @@ const validateObject = function(value,variable) {
|
|
|
208
208
|
return result;
|
|
209
209
|
}
|
|
210
210
|
}
|
|
211
|
-
return this.whenInvalid(variable);
|
|
211
|
+
return this.whenInvalid(variable,value);
|
|
212
212
|
}
|
|
213
213
|
const object = ({coerce=false, required = false,whenInvalid = ifInvalid,...rest}) => {
|
|
214
214
|
if(typeof(coerce)!=="boolean") throw new TypeError(`coerce, ${JSON.stringify(coerce)}, must be a boolean`);
|
|
@@ -247,7 +247,7 @@ const validateString = function(value,variable) {
|
|
|
247
247
|
return result;
|
|
248
248
|
}
|
|
249
249
|
}
|
|
250
|
-
return this.whenInvalid(variable);
|
|
250
|
+
return this.whenInvalid(variable,value);
|
|
251
251
|
}
|
|
252
252
|
const string = ({coerce=false, required = false,whenInvalid = ifInvalid, maxlength = Infinity, minlength = 0, pattern, ...rest}) => {
|
|
253
253
|
if(typeof(coerce)!=="boolean") throw new TypeError(`coerce, ${JSON.stringify(coerce)}, must be a boolean`);
|
|
@@ -289,7 +289,7 @@ const validateSymbol = function(value,variable) {
|
|
|
289
289
|
return result;
|
|
290
290
|
}
|
|
291
291
|
}
|
|
292
|
-
return this.whenInvalid(variable);
|
|
292
|
+
return this.whenInvalid(variable,value);
|
|
293
293
|
}
|
|
294
294
|
const symbol = ({coerce=false,required=false, whenInvalid = ifInvalid,...rest}) =>{
|
|
295
295
|
if(typeof(coerce)!=="boolean") throw new TypeError(`coerce, ${JSON.stringify(coerce)}, must be a boolean`);
|
|
@@ -309,4 +309,137 @@ symbol.validate = validateSymbol;
|
|
|
309
309
|
symbol.coerce = false;
|
|
310
310
|
symbol.required = false;
|
|
311
311
|
|
|
312
|
-
|
|
312
|
+
const remoteProxy = ({json, variable,config, reactive, component}) => {
|
|
313
|
+
const type = typeof (config);
|
|
314
|
+
return new Proxy(json, {
|
|
315
|
+
get(target,property) {
|
|
316
|
+
if(property==="__remoteProxytarget__") return json;
|
|
317
|
+
return target[property];
|
|
318
|
+
},
|
|
319
|
+
async set(target, property, value) {
|
|
320
|
+
if(value && typeof(value)==="object" && value instanceof Promise) value = await value;
|
|
321
|
+
const oldValue = target[property];
|
|
322
|
+
if (oldValue !== value) {
|
|
323
|
+
let remotevalue;
|
|
324
|
+
if (type === "string") {
|
|
325
|
+
const href = new URL(config,window.location.href).href;
|
|
326
|
+
remotevalue = patch({target,property,value,oldValue},href,variable);
|
|
327
|
+
} else if(config && type==="object") {
|
|
328
|
+
let href;
|
|
329
|
+
if(config.path) href = new URL(config.path,window.location.href).href;
|
|
330
|
+
if(!config.patch) {
|
|
331
|
+
if(!href) throw new Error(`A remote path is required is no put function is provided for remote data`)
|
|
332
|
+
config.patch = patch;
|
|
333
|
+
}
|
|
334
|
+
remotevalue = config.patch({target,property,value,oldValue},href,variable);
|
|
335
|
+
}
|
|
336
|
+
if(remotevalue) {
|
|
337
|
+
await remotevalue.then((newjson) => {
|
|
338
|
+
if (newjson && typeof (newjson) === "object" && reactive) {
|
|
339
|
+
const target = variable.value?.__reactorProxyTarget__ ? json : variable.value;
|
|
340
|
+
Object.entries(newjson).forEach(([key,newValue]) => {
|
|
341
|
+
if(target[key]!==newValue) target[key] = newValue;
|
|
342
|
+
})
|
|
343
|
+
Object.keys(target).forEach((key) => {
|
|
344
|
+
if(!(key in newjson)) delete target[key];
|
|
345
|
+
});
|
|
346
|
+
if(variable.value?.__reactorProxyTarget__) {
|
|
347
|
+
const dependents = variable.value.__dependents__,
|
|
348
|
+
observers = dependents[property] || [];
|
|
349
|
+
[...observers].forEach((f) => {
|
|
350
|
+
if (f.cancelled) dependents[property].delete(f);
|
|
351
|
+
else f();
|
|
352
|
+
})
|
|
353
|
+
}
|
|
354
|
+
} else {
|
|
355
|
+
component.setVariableValue(variable.name,newjson)
|
|
356
|
+
//variable.value = json;
|
|
357
|
+
}
|
|
358
|
+
})
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return true;
|
|
362
|
+
}
|
|
363
|
+
})
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const patch = ({target,property,value,oldValue},href,variable) => {
|
|
367
|
+
return fetch(href, {
|
|
368
|
+
method: "PATCH",
|
|
369
|
+
body: JSON.stringify({property,value,oldValue}),
|
|
370
|
+
headers: {
|
|
371
|
+
"Content-Type": "application/json"
|
|
372
|
+
}
|
|
373
|
+
}).then((response) => {
|
|
374
|
+
if (response.status < 400) return response.json();
|
|
375
|
+
})
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const get = (href,variable) => {
|
|
379
|
+
return fetch(href)
|
|
380
|
+
.then((response) => {
|
|
381
|
+
if (response.status < 400) return response.json();
|
|
382
|
+
})
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const put = (href,variable) => {
|
|
386
|
+
return fetch(href, {
|
|
387
|
+
method: "PUT",
|
|
388
|
+
body: JSON.stringify(variable.value),
|
|
389
|
+
headers: {
|
|
390
|
+
"Content-Type": "application/json"
|
|
391
|
+
}
|
|
392
|
+
}).then((response) => {
|
|
393
|
+
if (response.status === 200) return response.json();
|
|
394
|
+
})
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const handleRemote = async ({variable, config, reactive, component},doput) => {
|
|
398
|
+
const type = typeof (config);
|
|
399
|
+
let value;
|
|
400
|
+
if (type === "string") {
|
|
401
|
+
const href = new URL(config,window.location.href).href;
|
|
402
|
+
value = (doput
|
|
403
|
+
? put(href,variable)
|
|
404
|
+
: get(href,variable));
|
|
405
|
+
if(variable.value===undefined) variable.value = value; // do not await here
|
|
406
|
+
} else if (remote && type === "object") {
|
|
407
|
+
let href;
|
|
408
|
+
if(!config.path) config.path = `./${variable.name}`;
|
|
409
|
+
if(config.path) href = new URL(config.path,window.location.href).href;
|
|
410
|
+
if(!config.get || !config.put) {
|
|
411
|
+
if(!href) throw new Error(`A remote path is required if no put function is provided for remote data`)
|
|
412
|
+
if(!config.get) config.get = get;
|
|
413
|
+
if(!config.put && reactive) config.put = put;
|
|
414
|
+
}
|
|
415
|
+
value = (doput
|
|
416
|
+
? config.put(href,variable)
|
|
417
|
+
: config.get(href,variable));
|
|
418
|
+
if(config.ttl && !doput && !config.intervalId) {
|
|
419
|
+
config.intervalId = setInterval(async () => {
|
|
420
|
+
await handleRemote({variable, config, reactive, component});
|
|
421
|
+
//schedule();
|
|
422
|
+
},config.ttl);
|
|
423
|
+
}
|
|
424
|
+
if(variable.value===undefined) variable.value = value;
|
|
425
|
+
}
|
|
426
|
+
if(value) {
|
|
427
|
+
const json = await value;
|
|
428
|
+
//value.then((json) => {
|
|
429
|
+
if (json && typeof (json) === "object" && reactive) {
|
|
430
|
+
variable.value = remoteProxy({json, variable,config, reactive, component});
|
|
431
|
+
} else {
|
|
432
|
+
component.setVariableValue(variable.name,json);
|
|
433
|
+
}
|
|
434
|
+
//})
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const remote = (config) => {
|
|
439
|
+
return {
|
|
440
|
+
config,
|
|
441
|
+
handleRemote
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
export {ValidityState,any,array,boolean,number,string,symbol,remote}
|