@webqit/oohtml 3.0.1-12 → 3.0.1-3
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 +130 -180
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -375,7 +375,7 @@ setTimeout(() => abortController.abort(), 1000);
|
|
|
375
375
|
|
|
376
376
|
We can defer module loading until we really need them.
|
|
377
377
|
|
|
378
|
-
Here, we get the `loading="lazy"` directive for that; and loading is only then triggered on the first attempt to import
|
|
378
|
+
Here, we get the `loading="lazy"` directive for that; and loading is only then triggered on the first attempt to import its contents:
|
|
379
379
|
|
|
380
380
|
```html
|
|
381
381
|
<!-- Loading doesn't happen until the first time this is being accessed -->
|
|
@@ -593,42 +593,12 @@ Here, we get a comment-based data-binding tag `<?{ }?>` which works like regular
|
|
|
593
593
|
<title><?{ app.title }?></title>
|
|
594
594
|
</head>
|
|
595
595
|
<body>
|
|
596
|
-
Hi, I'm <?{ name ?? 'Default name' }?>!
|
|
597
|
-
and here's another way to write the same comment: <!--?{ cool }?-->
|
|
596
|
+
Hi, I'm <?{ app.name ?? 'Default name' }?>!
|
|
597
|
+
and here's another way to write the same comment: <!--?{ app.cool }?-->
|
|
598
598
|
</body>
|
|
599
599
|
</html>
|
|
600
600
|
```
|
|
601
601
|
|
|
602
|
-
<details><summary>Resolution details</summary>
|
|
603
|
-
|
|
604
|
-
Here, JavaScript references are resolved from the closest node up the document hierarchy that exposes a corresponding *binding* on its Bindings API ([discussed below](#bindings-api)). Thus, for the above markup, our underlying data structure could be anything like the below:
|
|
605
|
-
|
|
606
|
-
```js
|
|
607
|
-
document.bind({ name: 'James Boye', cool: '100%', app: { title: 'Demo App' } });
|
|
608
|
-
document.body.bind({ name: 'John Doe' });
|
|
609
|
-
```
|
|
610
|
-
|
|
611
|
-
```js
|
|
612
|
-
document: { name: 'James Boye', cool: '100%', app: { title: 'Demo App' } }
|
|
613
|
-
└── html
|
|
614
|
-
├── head
|
|
615
|
-
└── body: { name: 'John Doe' }
|
|
616
|
-
```
|
|
617
|
-
|
|
618
|
-
Now, the `name` reference remains bound to the `name` *binding* on the `<body>` element until the meaning of "closest node" changes again:
|
|
619
|
-
|
|
620
|
-
```js
|
|
621
|
-
delete document.body.bindings.name;
|
|
622
|
-
```
|
|
623
|
-
|
|
624
|
-
While the `cool` reference remains bound to the `cool` *binding* on the `document` node until the meaning of "closest node" changes again:
|
|
625
|
-
|
|
626
|
-
```js
|
|
627
|
-
document.body.bindings.cool = '200%';
|
|
628
|
-
```
|
|
629
|
-
|
|
630
|
-
</details>
|
|
631
|
-
|
|
632
602
|
<details><summary>With SSR Support</summary>
|
|
633
603
|
|
|
634
604
|
On the server, these data-binding tags would retain their place in the DOM while having their output rendered to their right in a text node.
|
|
@@ -677,7 +647,7 @@ Here, we get the `binding` attribute for a declarative and neat, key/value data-
|
|
|
677
647
|
|
|
678
648
|
| Directive | Type | Usage |
|
|
679
649
|
| :---- | :---- | :---- |
|
|
680
|
-
| `&` | CSS Property | `<div binding="& color:someColor; & backgroundColor:
|
|
650
|
+
| `&` | CSS Property | `<div binding="& color:someColor; & backgroundColor:someColor;"></div>` |
|
|
681
651
|
| `%` | Class Name | `<div binding="% active:app.isActive; % expanded:app.isExpanded;"></div>` |
|
|
682
652
|
| `~` | Attribute Name | `<a binding="~ href:person.profileUrl+'#bio'; ~ title:'Click me';"></a>` |
|
|
683
653
|
| | Boolean Attribute | `<a binding="~ ?required:formField.required; ~ ?aria-checked: formField.checked"></a>` |
|
|
@@ -706,53 +676,23 @@ Here, we get the `binding` attribute for a declarative and neat, key/value data-
|
|
|
706
676
|
|
|
707
677
|
</details>
|
|
708
678
|
|
|
709
|
-
<details><summary>Resolution details</summary>
|
|
710
|
-
|
|
711
|
-
Here, JavaScript references are resolved from the closest node up the document hierarchy that exposes a corresponding *binding* on its Bindings API ([discussed below](#bindings-api)). Thus, for the above CSS bindings, our underlying data structure could be anything like the below:
|
|
712
|
-
|
|
713
|
-
```js
|
|
714
|
-
document.bind({ someColor: 'green', someBgColor: 'yellow' });
|
|
715
|
-
document.body.bind({ someBgColor: 'silver' });
|
|
716
|
-
```
|
|
717
|
-
|
|
718
|
-
```js
|
|
719
|
-
document: { someColor: 'green', someBgColor: 'yellow' }
|
|
720
|
-
└── html
|
|
721
|
-
├── head
|
|
722
|
-
└── body: { someBgColor: 'silver' }
|
|
723
|
-
```
|
|
724
|
-
|
|
725
|
-
Now, the `someBgColor` reference remains bound to the `someBgColor` *binding* on the `<body>` element until the meaning of "closest node" changes again:
|
|
726
|
-
|
|
727
|
-
```js
|
|
728
|
-
delete document.body.bindings.someBgColor;
|
|
729
|
-
```
|
|
730
|
-
|
|
731
|
-
While the `someColor` reference remains bound to the `someColor` *binding* on the `document` node until the meaning of "closest node" changes again:
|
|
732
|
-
|
|
733
|
-
```js
|
|
734
|
-
document.body.bindings.someColor = 'brown';
|
|
735
|
-
```
|
|
736
|
-
|
|
737
|
-
</details>
|
|
738
|
-
|
|
739
679
|
<details><summary>All in realtime</summary>
|
|
740
680
|
|
|
741
|
-
|
|
681
|
+
Lists are rendered in realtime, which means that in-place mutations - additions and removals - on the *iteratee* will be automatically reflected on the UI!
|
|
742
682
|
|
|
743
683
|
</details>
|
|
744
684
|
|
|
745
685
|
<details><summary>With SSR Support</summary>
|
|
746
686
|
|
|
747
|
-
|
|
687
|
+
Generated item elements are automatically assigned a corresponding index with a `data-index` attribute! This helps in remapping generated item nodes to their respective entry in *iteratee* - universally.
|
|
748
688
|
|
|
749
689
|
</details>
|
|
750
690
|
|
|
751
691
|
### Quantum Scripts
|
|
752
692
|
|
|
753
|
-
We often still need to write more serious reactive logic on the UI than a declarative data-binding language can provide
|
|
693
|
+
We often still need to write more serious reactive logic on the UI than a declarative data-binding language can provide. But we shouldn't need to reach for special tooling or some "serious" programming paradigm on top of JavaScript.
|
|
754
694
|
|
|
755
|
-
Here, from the same `<script>` element we
|
|
695
|
+
Here, from the same `<script>` element we write everyday, we get a direct upgrade path to reactive programming in just an attribute: `quantum`:
|
|
756
696
|
|
|
757
697
|
```html
|
|
758
698
|
<script quantum>
|
|
@@ -849,17 +789,9 @@ It's Imperative Reactive Programming ([IRP](https://en.wikipedia.org/wiki/Reacti
|
|
|
849
789
|
|
|
850
790
|
Here, the runtime executes your code in a special execution mode that gets literal JavaScript expressions to statically reflect changes. This makes a lot of things possible on the UI! The [Quantum JS](https://github.com/webqit/quantum-js) documentation has a detailed run down.
|
|
851
791
|
|
|
852
|
-
Now, in each case above, reactivity terminates on script's removal from the DOM.
|
|
853
|
-
|
|
854
|
-
```js
|
|
855
|
-
const script = document.querySelector('script[quantum]');
|
|
856
|
-
// const script = document.querySelector('main').scripts[0];
|
|
857
|
-
script.abort();
|
|
858
|
-
```
|
|
859
|
-
|
|
860
|
-
But while that is automatic, DOM event handlers bound via `addEventListener()` would still need to be terminated in their own way.
|
|
792
|
+
Now, in each case above, reactivity terminates on script's removal from the DOM. But of course, DOM event handlers bound via `addEventListener()` would still need to be terminated in their own way.
|
|
861
793
|
|
|
862
|
-
|
|
794
|
+
<details>
|
|
863
795
|
|
|
864
796
|
## Data Plumbing
|
|
865
797
|
|
|
@@ -960,7 +892,7 @@ Observer.set(element, 'liveProperty'); // Live expressions rerun
|
|
|
960
892
|
|
|
961
893
|
## Polyfill
|
|
962
894
|
|
|
963
|
-
OOHTML is being developed as something to be used today
|
|
895
|
+
OOHTML is being developed as something to be used today - via a polyfill.
|
|
964
896
|
|
|
965
897
|
<details><summary>Load from a CDN<br>
|
|
966
898
|
└───────── <a href="https://bundlephobia.com/result?p=@webqit/oohtml"><img align="right" src="https://img.shields.io/bundlephobia/minzip/@webqit/oohtml?label=&style=flat&colorB=black"></a></summary>
|
|
@@ -981,8 +913,9 @@ OOHTML is being developed as something to be used today—via a polyfill. This i
|
|
|
981
913
|
|
|
982
914
|
</details>
|
|
983
915
|
|
|
984
|
-
<details><summary>
|
|
985
|
-
|
|
916
|
+
<details><summary>Extended usage concepts</summary>
|
|
917
|
+
|
|
918
|
+
To use the polyfill on server-side DOM instances as made possible by libraries like [jsdom](https://github.com/jsdom/jsdom), simply install and initialize the library `@webqit/oohtml` with the DOM instance:
|
|
986
919
|
|
|
987
920
|
```bash
|
|
988
921
|
npm i @webqit/oohtml
|
|
@@ -996,15 +929,9 @@ import init from '@webqit/oohtml';
|
|
|
996
929
|
init.call( window[, options = {} ]);
|
|
997
930
|
```
|
|
998
931
|
|
|
999
|
-
|
|
932
|
+
But all things "SSR" for OOHTML are best left to the [`@webqit/oohtml-ssr`](https://github.com/webqit/oohtml-ssr) package!
|
|
1000
933
|
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
</details>
|
|
1004
|
-
|
|
1005
|
-
<details><summary>Extended usage concepts</summary>
|
|
1006
|
-
|
|
1007
|
-
If you'll be going ahead to build a real app to see OOHTML in action, you may want to consider also using:
|
|
934
|
+
Also, if you'll be going ahead to build a real app to see OOHTML in action, you may want to consider also using:
|
|
1008
935
|
|
|
1009
936
|
+ the [`@webqit/oohtml-cli`](https://github.com/webqit/oohtml-cli) package for operating a file-based templating system.
|
|
1010
937
|
|
|
@@ -1042,7 +969,7 @@ If you'll be going ahead to build a real app to see OOHTML in action, you may wa
|
|
|
1042
969
|
|
|
1043
970
|
...still gives the `window` object in the console.
|
|
1044
971
|
|
|
1045
|
-
+ **Scoped/Quantum Scripts**. This feature is an extension of [Quantum JS](https://github.com/webqit/quantum-js)
|
|
972
|
+
+ **Scoped/Quantum Scripts**. This feature is an extension of [Quantum JS](https://github.com/webqit/quantum-js). The default OOHTML build is based on the [Quantum JS Lite APIs](https://github.com/webqit/quantum-js#quantum-js-lite) and this means that `<script quantum></script>` and `<script scoped></script>` elements are parsed "asynchronously", in the same timing as `<script type="module"></script>`!
|
|
1046
973
|
|
|
1047
974
|
This timing works perfectly generally, but if you have a requirment to have classic scripts follow their [native synchronous timing](https://html.spec.whatwg.org/multipage/parsing.html#scripts-that-modify-the-page-as-it-is-being-parsed), then you'd need to use the *realtime* OOHTML build:
|
|
1048
975
|
|
|
@@ -1100,72 +1027,84 @@ If you'll be going ahead to build a real app to see OOHTML in action, you may wa
|
|
|
1100
1027
|
|
|
1101
1028
|
## Examples
|
|
1102
1029
|
|
|
1103
|
-
Here are a few examples in the wide range of use cases these features cover.
|
|
1030
|
+
Here are a few examples in the wide range of use cases these features cover.
|
|
1031
|
+
|
|
1032
|
+
+ [Example 1: *Single Page Application*](#example-1-single-page-application)
|
|
1033
|
+
+ [Example 2: *Multi-Level Namespacing*](#example-2-multi-level-namespacing)
|
|
1034
|
+
+ [Example 3: *Dynamic Shadow DOM*](#example-3-dynamic-shadow-dom)
|
|
1035
|
+
+ [Example 4: *Declarative Lists*](#example-4-declarative-lists)
|
|
1036
|
+
+ [Example 5: *Imperative Lists*](#example-4-imperative-lists)
|
|
1104
1037
|
|
|
1105
|
-
|
|
1106
|
-
└───────── </summary>
|
|
1038
|
+
### Example 1: *Single Page Application*
|
|
1107
1039
|
|
|
1108
1040
|
The following is how something you could call a Single Page Application ([SPA](https://en.wikipedia.org/wiki/Single-page_application)) could be made - with zero tooling:
|
|
1109
1041
|
|
|
1110
|
-
|
|
1042
|
+
+ *First, two components that are themselves analogous to a Single File Component ([SFC](https://vuejs.org/guide/scaling-up/sfc.html))*:
|
|
1111
1043
|
|
|
1112
|
-
|
|
1113
|
-
<template def="pages">
|
|
1044
|
+
<details><summary>Code</summary>
|
|
1114
1045
|
|
|
1115
|
-
|
|
1116
|
-
<
|
|
1117
|
-
|
|
1118
|
-
|
|
1046
|
+
```html
|
|
1047
|
+
<template def="pages">
|
|
1048
|
+
|
|
1049
|
+
<template def="layout">
|
|
1050
|
+
<header def="header"></header>
|
|
1051
|
+
<footer def="footer"></footer>
|
|
1052
|
+
</template>
|
|
1053
|
+
|
|
1054
|
+
<!-- Home Page -->
|
|
1055
|
+
<template def="home" extends="layout">
|
|
1056
|
+
<main def="main" namespace>
|
|
1057
|
+
<h1 id="banner">Home Page</h1>
|
|
1058
|
+
<a id="cta" href="#/products">Go to Products</a>
|
|
1059
|
+
<template scoped></template>
|
|
1060
|
+
<style scoped></style>
|
|
1061
|
+
<script scoped></script>
|
|
1062
|
+
</main>
|
|
1063
|
+
</template>
|
|
1064
|
+
|
|
1065
|
+
<!-- Products Page -->
|
|
1066
|
+
<template def="products" extends="layout">
|
|
1067
|
+
<main def="main" namespace>
|
|
1068
|
+
<h1 id="banner">Products Page</h1>
|
|
1069
|
+
<a id="cta" href="#/home">Go to Home</a>
|
|
1070
|
+
<template scoped></template>
|
|
1071
|
+
<style scoped></style>
|
|
1072
|
+
<script scoped></script>
|
|
1073
|
+
</main>
|
|
1074
|
+
</template>
|
|
1119
1075
|
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
<main def="main" namespace>
|
|
1123
|
-
<h1 id="banner">Home Page</h1>
|
|
1124
|
-
<a id="cta" href="#/products">Go to Products</a>
|
|
1125
|
-
<template scoped></template>
|
|
1126
|
-
<style scoped></style>
|
|
1127
|
-
<script scoped></script>
|
|
1128
|
-
</main>
|
|
1129
|
-
</template>
|
|
1076
|
+
</template>
|
|
1077
|
+
```
|
|
1130
1078
|
|
|
1131
|
-
|
|
1132
|
-
<template def="products" extends="layout">
|
|
1133
|
-
<main def="main" namespace>
|
|
1134
|
-
<h1 id="banner">Products Page</h1>
|
|
1135
|
-
<a id="cta" href="#/home">Go to Home</a>
|
|
1136
|
-
<template scoped></template>
|
|
1137
|
-
<style scoped></style>
|
|
1138
|
-
<script scoped></script>
|
|
1139
|
-
</main>
|
|
1140
|
-
</template>
|
|
1079
|
+
</details>
|
|
1141
1080
|
|
|
1142
|
-
|
|
1143
|
-
```
|
|
1081
|
+
+ *Then a 2-line router that alternates the view based on the URL hash*:
|
|
1144
1082
|
|
|
1145
|
-
|
|
1083
|
+
<details><summary>Code</summary>
|
|
1146
1084
|
|
|
1147
|
-
```html
|
|
1148
|
-
<body importscontext="/pages/home">
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
</body>
|
|
1160
|
-
```
|
|
1085
|
+
```html
|
|
1086
|
+
<body importscontext="/pages/home">
|
|
1087
|
+
|
|
1088
|
+
<import ref="#header"></import>
|
|
1089
|
+
<import ref="#main"></import>
|
|
1090
|
+
<import ref="#footer"></import>
|
|
1091
|
+
|
|
1092
|
+
<script>
|
|
1093
|
+
const route = () => { document.body.setAttribute('importscontext', '/pages' + location.hash.substring(1)); };
|
|
1094
|
+
window.addEventListener('hashchange', route);
|
|
1095
|
+
</script>
|
|
1096
|
+
|
|
1097
|
+
</body>
|
|
1098
|
+
```
|
|
1161
1099
|
|
|
1162
|
-
</details>
|
|
1100
|
+
</details>
|
|
1163
1101
|
|
|
1164
|
-
|
|
1165
|
-
└───────── </summary>
|
|
1102
|
+
### Example 2: *Multi-Level Namespacing*
|
|
1166
1103
|
|
|
1167
1104
|
The following is a Listbox component lifted directly from the [ARIA Authoring Practices Guide (APG)](https://www.w3.org/WAI/ARIA/apg/patterns/listbox/examples/listbox-grouped/#sc_label) but with IDs effectively "contained" at different levels within the component using the `namespace` attribute.
|
|
1168
1105
|
|
|
1106
|
+
<details><summary>Code</summary>
|
|
1107
|
+
|
|
1169
1108
|
```html
|
|
1170
1109
|
<div namespace class="listbox-area">
|
|
1171
1110
|
<div>
|
|
@@ -1231,65 +1170,75 @@ The following is a Listbox component lifted directly from the [ARIA Authoring Pr
|
|
|
1231
1170
|
|
|
1232
1171
|
</details>
|
|
1233
1172
|
|
|
1234
|
-
|
|
1235
|
-
└───────── </summary>
|
|
1173
|
+
### Example 3: *Dynamic Shadow DOM*
|
|
1236
1174
|
|
|
1237
1175
|
The following is a custom element that derives its Shadow DOM from an imported `<tenplate>` element. The idea is to have different Shadow DOM layouts defined and let the "usage" context decide which variant is imported!
|
|
1238
1176
|
|
|
1239
|
-
|
|
1177
|
+
+ *First, two layout options defined for the Shadow DOM*:
|
|
1240
1178
|
|
|
1241
|
-
|
|
1242
|
-
<template def="vendor1">
|
|
1179
|
+
<details><summary>Code</summary>
|
|
1243
1180
|
|
|
1244
|
-
|
|
1245
|
-
<template def="
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1181
|
+
```html
|
|
1182
|
+
<template def="vendor1">
|
|
1183
|
+
|
|
1184
|
+
<template def="components-layout1">
|
|
1185
|
+
<template def="magic-button">
|
|
1186
|
+
<span id="icon"></span> <span id="text"></span>
|
|
1187
|
+
</template>
|
|
1188
|
+
</template>
|
|
1189
|
+
|
|
1190
|
+
<template def="components-layout2">
|
|
1191
|
+
<template def="magic-button">
|
|
1192
|
+
<span id="text"></span> <span id="icon"></span>
|
|
1193
|
+
</template>
|
|
1194
|
+
</template>
|
|
1249
1195
|
|
|
1250
|
-
<template def="components-layout2">
|
|
1251
|
-
<template def="magic-button">
|
|
1252
|
-
<span id="text"></span> <span id="icon"></span>
|
|
1253
1196
|
</template>
|
|
1254
|
-
|
|
1197
|
+
```
|
|
1255
1198
|
|
|
1256
|
-
</
|
|
1257
|
-
```
|
|
1199
|
+
</details>
|
|
1258
1200
|
|
|
1259
|
-
|
|
1201
|
+
+ *Next, the Shadow DOM creation that imports its layout from context*:
|
|
1260
1202
|
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1203
|
+
<details><summary>Code</summary>
|
|
1204
|
+
|
|
1205
|
+
```js
|
|
1206
|
+
customElements.define('magic-button', class extends HTMLElement {
|
|
1207
|
+
connectedCallback() {
|
|
1208
|
+
const shadowRoot = this.attachShadow({ mode: 'open' });
|
|
1209
|
+
this.import('@vendor1/magic-button', template => {
|
|
1210
|
+
shadowRoot.appendChild( template.content.cloneNode(true) );
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1267
1213
|
});
|
|
1268
|
-
|
|
1269
|
-
});
|
|
1270
|
-
```
|
|
1214
|
+
```
|
|
1271
1215
|
|
|
1272
|
-
|
|
1216
|
+
</details>
|
|
1273
1217
|
|
|
1274
|
-
|
|
1275
|
-
<div contextname="vendor1" importscontext="/vendor1/components-layout1">
|
|
1218
|
+
+ *Then, the part where we just drop the component in "layout" contexts*:
|
|
1276
1219
|
|
|
1277
|
-
|
|
1220
|
+
<details><summary>Code</summary>
|
|
1278
1221
|
|
|
1279
|
-
|
|
1280
|
-
<
|
|
1281
|
-
</aside>
|
|
1222
|
+
```html
|
|
1223
|
+
<div contextname="vendor1" importscontext="/vendor1/components-layout1">
|
|
1282
1224
|
|
|
1283
|
-
|
|
1284
|
-
```
|
|
1225
|
+
<magic-button></magic-button>
|
|
1285
1226
|
|
|
1286
|
-
|
|
1227
|
+
<aside contextname="vendor1" importscontext="/vendor1/components-layout2">
|
|
1228
|
+
<magic-button></magic-button>
|
|
1229
|
+
</aside>
|
|
1287
1230
|
|
|
1288
|
-
|
|
1289
|
-
|
|
1231
|
+
</div>
|
|
1232
|
+
```
|
|
1233
|
+
|
|
1234
|
+
</details>
|
|
1235
|
+
|
|
1236
|
+
### Example 4: *Declarative Lists*
|
|
1290
1237
|
|
|
1291
1238
|
The following is a hypothetical list page!
|
|
1292
1239
|
|
|
1240
|
+
<details><summary>Code</summary>
|
|
1241
|
+
|
|
1293
1242
|
```html
|
|
1294
1243
|
<section>
|
|
1295
1244
|
|
|
@@ -1306,11 +1255,12 @@ The following is a hypothetical list page!
|
|
|
1306
1255
|
|
|
1307
1256
|
</details>
|
|
1308
1257
|
|
|
1309
|
-
|
|
1310
|
-
└───────── </summary>
|
|
1258
|
+
### Example 4: *Imperative Lists*
|
|
1311
1259
|
|
|
1312
1260
|
The following is much like the above, but imperative. Additions and removals on the data items are also statically reflected!
|
|
1313
1261
|
|
|
1262
|
+
<details><summary>Code</summary>
|
|
1263
|
+
|
|
1314
1264
|
```html
|
|
1315
1265
|
<section namespace>
|
|
1316
1266
|
|