@webqit/oohtml 2.1.55 → 2.1.57
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 +205 -153
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -161,18 +161,17 @@ foo.addEventListener('load', loadedCallback);
|
|
|
161
161
|
└ *The HTMLImports API for programmatic module import*:
|
|
162
162
|
|
|
163
163
|
```js
|
|
164
|
-
document.import('/foo#fragment1'
|
|
165
|
-
|
|
166
|
-
});
|
|
164
|
+
const import1 = document.import('/foo#fragment1');
|
|
165
|
+
console.log(import1); // { value: div }
|
|
167
166
|
```
|
|
168
167
|
|
|
169
168
|
```js
|
|
170
|
-
document.import('/foo/nested#fragment2'
|
|
171
|
-
|
|
172
|
-
});
|
|
169
|
+
const import2 = document.import('/foo/nested#fragment2');
|
|
170
|
+
console.log(import2); // { value: div }
|
|
173
171
|
```
|
|
174
172
|
|
|
175
173
|
└ *Scoped templates for object-scoped module system*:
|
|
174
|
+
> With an equivalent `Element.prototype.import()` API for accessing scoped modules
|
|
176
175
|
|
|
177
176
|
```html
|
|
178
177
|
<section> <!-- object with own modules -->
|
|
@@ -191,16 +190,16 @@ document.import('/foo/nested#fragment2', divElement => {
|
|
|
191
190
|
|
|
192
191
|
```js
|
|
193
192
|
// Using the HTMLImports API
|
|
194
|
-
document.querySelector('div')
|
|
195
|
-
|
|
196
|
-
|
|
193
|
+
const moduleHost = document.querySelector('div');
|
|
194
|
+
const localImport1 = moduleHost.import('foo#fragment1'); // the local module: foo#fragment1
|
|
195
|
+
console.log(localImport1); // { value: div }
|
|
197
196
|
```
|
|
198
197
|
|
|
199
198
|
```js
|
|
200
199
|
// Using the HTMLImports API
|
|
201
|
-
document.querySelector('div')
|
|
202
|
-
|
|
203
|
-
|
|
200
|
+
const moduleHost = document.querySelector('div');
|
|
201
|
+
const globalImport1 = moduleHost.import('/foo#fragment1'); // the global module: foo#fragment1
|
|
202
|
+
console.log(globalImport1); // { value: div }
|
|
204
203
|
```
|
|
205
204
|
|
|
206
205
|
<details><summary>
|
|
@@ -270,6 +269,14 @@ document.import('/foo#fragment1', divElement => {
|
|
|
270
269
|
});
|
|
271
270
|
```
|
|
272
271
|
|
|
272
|
+
```js
|
|
273
|
+
const import1 = document.import('/foo#fragment1', true);
|
|
274
|
+
console.log(import1); // { value: undefined }
|
|
275
|
+
Observer.observe(import1, 'value', divElement => {
|
|
276
|
+
console.log(divElement); // To be received after remote module has been loaded
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
273
280
|
└ *"Imports Contexts" for context-based imports resolution*:
|
|
274
281
|
|
|
275
282
|
```html
|
|
@@ -290,9 +297,9 @@ document.import('/foo#fragment1', divElement => {
|
|
|
290
297
|
|
|
291
298
|
```js
|
|
292
299
|
// Using the HTMLImports API
|
|
293
|
-
document.querySelector('section')
|
|
294
|
-
|
|
295
|
-
|
|
300
|
+
const moduleHost = document.querySelector('section');
|
|
301
|
+
const globalImport2 = moduleHost.import('#fragment2'); // module:/foo/nested#fragment2
|
|
302
|
+
console.log(globalImport2); // { value: div }
|
|
296
303
|
```
|
|
297
304
|
|
|
298
305
|
└ *"Imports Contexts" with named contexts*:
|
|
@@ -314,9 +321,9 @@ document.querySelector('section').import('#fragment2', divElement => {
|
|
|
314
321
|
|
|
315
322
|
```js
|
|
316
323
|
// Using the HTMLImports API
|
|
317
|
-
document.querySelector('div')
|
|
318
|
-
|
|
319
|
-
|
|
324
|
+
const moduleHost = document.querySelector('div');
|
|
325
|
+
const globalImport2 = moduleHost.import('@context1#fragment2'); // module:/foo/nested#fragment2
|
|
326
|
+
console.log(globalImport2); // { value: div }
|
|
320
327
|
```
|
|
321
328
|
|
|
322
329
|
└ *"Imports Contexts" with context inheritance*:
|
|
@@ -356,9 +363,9 @@ document.querySelector('div').import('@context1#fragment2', divElement => {
|
|
|
356
363
|
|
|
357
364
|
```js
|
|
358
365
|
// Using the HTMLImports API
|
|
359
|
-
document.querySelector('div')
|
|
360
|
-
|
|
361
|
-
|
|
366
|
+
const moduleHost = document.querySelector('div');
|
|
367
|
+
const localOrGlobalImport2 = moduleHost.import('#fragment2'); // the local module: foo#fragment2, and if not found, resolves from context to the module: /bar/nested#fragment2
|
|
368
|
+
console.log(localOrGlobalImport2); // { value: div }
|
|
362
369
|
```
|
|
363
370
|
|
|
364
371
|
</details>
|
|
@@ -491,37 +498,7 @@ OOHTML is being developed as something to be used today - via a polyfill.
|
|
|
491
498
|
|
|
492
499
|
└ This is to be placed early on in the document and should be a classic script without any `defer` or `async` directives!
|
|
493
500
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
If you must load the script "async", one little trade-off has to be made for `<script scoped>` and `<script stateful>` elements to have them ignored by the browser until the polyfill comes picking them up: *employing a custom MIME type in place of the standard `text/javascript` and `module` types*, in which case, a `<meta name="scoped-js">` element is used to configure the polyfill to honor the custom MIME type:
|
|
497
|
-
|
|
498
|
-
```html
|
|
499
|
-
<head>
|
|
500
|
-
<meta name="scoped-js" content="script.mimeType=some-mime">
|
|
501
|
-
<script async src="https://unpkg.com/@webqit/oohtml/dist/main.js"></script>
|
|
502
|
-
</head>
|
|
503
|
-
<body>
|
|
504
|
-
<script type="some-mime" scoped>
|
|
505
|
-
console.log(this); // body
|
|
506
|
-
</script>
|
|
507
|
-
</body>
|
|
508
|
-
```
|
|
509
|
-
|
|
510
|
-
The custom MIME type strategy also comes in as a "fix" for when in a browser or other runtime where the polyfill is not able to intercept `<script scoped>` and `<script stateful>` elements ahead of the runtime - e.g. where...
|
|
511
|
-
|
|
512
|
-
```html
|
|
513
|
-
<body>
|
|
514
|
-
<script scoped>
|
|
515
|
-
console.log(this); // body
|
|
516
|
-
</script>
|
|
517
|
-
</body>
|
|
518
|
-
```
|
|
519
|
-
|
|
520
|
-
...still gives the `window` object in the console.
|
|
521
|
-
|
|
522
|
-
</details>
|
|
523
|
-
|
|
524
|
-
For the Scoped Styles feature, you'd also need something like the [samthor/scoped](https://github.com/samthor/scoped) polyfill (more details below):
|
|
501
|
+
└ For the Scoped Styles feature, you'd also need something like the [samthor/scoped](https://github.com/samthor/scoped) polyfill (more details below):
|
|
525
502
|
|
|
526
503
|
```html
|
|
527
504
|
<head>
|
|
@@ -559,6 +536,34 @@ Also, if you'll be going ahead to build a real app to see OOHTML in action, you
|
|
|
559
536
|
|
|
560
537
|
<details><summary>Implementation Notes</summary>
|
|
561
538
|
|
|
539
|
+
+ **Loading Requirements**. As specified above, the OOHTML script tag is to be placed early on in the document and should be a classic script without any `defer` or `async` directives!
|
|
540
|
+
|
|
541
|
+
If you must load the script "async", one little trade-off has to be made for `<script scoped>` and `<script stateful>` elements to have them ignored by the browser until the polyfill comes picking them up: *employing a custom MIME type in place of the standard `text/javascript` and `module` types*, in which case, a `<meta name="scoped-js">` element is used to configure the polyfill to honor the custom MIME type:
|
|
542
|
+
|
|
543
|
+
```html
|
|
544
|
+
<head>
|
|
545
|
+
<meta name="scoped-js" content="script.mimeType=some-mime">
|
|
546
|
+
<script async src="https://unpkg.com/@webqit/oohtml/dist/main.js"></script>
|
|
547
|
+
</head>
|
|
548
|
+
<body>
|
|
549
|
+
<script type="some-mime" scoped>
|
|
550
|
+
console.log(this); // body
|
|
551
|
+
</script>
|
|
552
|
+
</body>
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
The custom MIME type strategy also comes in as a "fix" for when in a browser or other runtime where the polyfill is not able to intercept `<script scoped>` and `<script stateful>` elements ahead of the runtime - e.g. where...
|
|
556
|
+
|
|
557
|
+
```html
|
|
558
|
+
<body>
|
|
559
|
+
<script scoped>
|
|
560
|
+
console.log(this); // body
|
|
561
|
+
</script>
|
|
562
|
+
</body>
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
...still gives the `window` object in the console.
|
|
566
|
+
|
|
562
567
|
+ **Scoped/Stateful Scripts**. This feature is an extension of [Stateful JS](https://github.com/webqit/stateful-js). The default OOHTML build is based on the [Stateful JS Lite APIs](https://github.com/webqit/stateful-js#stateful-js-lite) and this means that `<script stateful></script>` and `<script scoped></script>` elements are parsed "asynchronously", in the same timing as `<script type="module"></script>`!
|
|
563
568
|
|
|
564
569
|
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 need to the *realtime* OOHTML build:
|
|
@@ -623,70 +628,71 @@ Here are a few examples in the wide range of use cases these features cover.
|
|
|
623
628
|
+ [Example 2: *Multi-Level Namespacing*](#example-2-multi-level-namespacing)
|
|
624
629
|
+ [Example 3: *Dynamic Shadow DOM*](#example-3-dynamic-shadow-dom)
|
|
625
630
|
+ [Example 4: *List Items*](#example-4-list-items)
|
|
631
|
+
+ [Example 5: *Live List*](#example-4-live-list)
|
|
626
632
|
|
|
627
633
|
### Example 1: *Single Page Application*
|
|
628
634
|
|
|
629
635
|
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:
|
|
630
636
|
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
<details><summary>Code</summary>
|
|
634
|
-
|
|
635
|
-
```html
|
|
636
|
-
<template def="pages">
|
|
637
|
+
+ *First, two components that are themselves analogous to a Single File Component ([SFC](https://vuejs.org/guide/scaling-up/sfc.html))*:
|
|
637
638
|
|
|
638
|
-
|
|
639
|
-
<header def="header"></header>
|
|
640
|
-
<footer def="footer"></footer>
|
|
641
|
-
</template>
|
|
639
|
+
<details><summary>Code</summary>
|
|
642
640
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
<
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
641
|
+
```html
|
|
642
|
+
<template def="pages">
|
|
643
|
+
|
|
644
|
+
<template def="layout">
|
|
645
|
+
<header def="header"></header>
|
|
646
|
+
<footer def="footer"></footer>
|
|
647
|
+
</template>
|
|
648
|
+
|
|
649
|
+
<!-- Home Page -->
|
|
650
|
+
<template def="home" extends="layout">
|
|
651
|
+
<main def="main" namespace>
|
|
652
|
+
<h1 id="banner">Home Page</h1>
|
|
653
|
+
<a id="cta" href="#/products">Go to Products</a>
|
|
654
|
+
<template scoped></template>
|
|
655
|
+
<style scoped></style>
|
|
656
|
+
<script scoped></script>
|
|
657
|
+
</main>
|
|
658
|
+
</template>
|
|
659
|
+
|
|
660
|
+
<!-- Products Page -->
|
|
661
|
+
<template def="products" extends="layout">
|
|
662
|
+
<main def="main" namespace>
|
|
663
|
+
<h1 id="banner">Products Page</h1>
|
|
664
|
+
<a id="cta" href="#/home">Go to Home</a>
|
|
665
|
+
<template scoped></template>
|
|
666
|
+
<style scoped></style>
|
|
667
|
+
<script scoped></script>
|
|
668
|
+
</main>
|
|
669
|
+
</template>
|
|
664
670
|
|
|
665
|
-
</template>
|
|
666
|
-
```
|
|
671
|
+
</template>
|
|
672
|
+
```
|
|
667
673
|
|
|
668
|
-
</details>
|
|
674
|
+
</details>
|
|
669
675
|
|
|
670
|
-
|
|
676
|
+
+ *Then a 2-line router that alternates the view based on the URL hash*:
|
|
671
677
|
|
|
672
|
-
<details><summary>Code</summary>
|
|
678
|
+
<details><summary>Code</summary>
|
|
673
679
|
|
|
674
|
-
```html
|
|
675
|
-
<body importscontext="/pages/home">
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
</body>
|
|
687
|
-
```
|
|
680
|
+
```html
|
|
681
|
+
<body importscontext="/pages/home">
|
|
682
|
+
|
|
683
|
+
<import ref="#header"></import>
|
|
684
|
+
<import ref="#main"></import>
|
|
685
|
+
<import ref="#footer"></import>
|
|
686
|
+
|
|
687
|
+
<script>
|
|
688
|
+
const route = () => { document.body.setAttribute('importscontext', '/pages' + location.hash.substring(1)); };
|
|
689
|
+
window.addEventListener('hashchange', route);
|
|
690
|
+
</script>
|
|
691
|
+
|
|
692
|
+
</body>
|
|
693
|
+
```
|
|
688
694
|
|
|
689
|
-
</details>
|
|
695
|
+
</details>
|
|
690
696
|
|
|
691
697
|
### Example 2: *Multi-Level Namespacing*
|
|
692
698
|
|
|
@@ -763,93 +769,139 @@ The following is a Listbox component lifted directly from the [ARIA Authoring Pr
|
|
|
763
769
|
|
|
764
770
|
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!
|
|
765
771
|
|
|
766
|
-
|
|
772
|
+
+ *First, two layout options defined for the Shadow DOM*:
|
|
767
773
|
|
|
768
|
-
<details><summary>Code</summary>
|
|
774
|
+
<details><summary>Code</summary>
|
|
769
775
|
|
|
770
|
-
```html
|
|
771
|
-
<template def="vendor1">
|
|
776
|
+
```html
|
|
777
|
+
<template def="vendor1">
|
|
772
778
|
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
779
|
+
<template def="components-layout1">
|
|
780
|
+
<template def="magic-button">
|
|
781
|
+
<span id="icon"></span> <span id="text"></span>
|
|
782
|
+
</template>
|
|
783
|
+
</template>
|
|
778
784
|
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
785
|
+
<template def="components-layout2">
|
|
786
|
+
<template def="magic-button">
|
|
787
|
+
<span id="text"></span> <span id="icon"></span>
|
|
788
|
+
</template>
|
|
789
|
+
</template>
|
|
784
790
|
|
|
785
|
-
</template>
|
|
786
|
-
```
|
|
791
|
+
</template>
|
|
792
|
+
```
|
|
787
793
|
|
|
788
|
-
</details>
|
|
794
|
+
</details>
|
|
789
795
|
|
|
790
|
-
|
|
796
|
+
+ *Next, the Shadow DOM creation that imports its layout from context*:
|
|
791
797
|
|
|
792
|
-
<details><summary>Code</summary>
|
|
798
|
+
<details><summary>Code</summary>
|
|
793
799
|
|
|
794
|
-
```js
|
|
795
|
-
customElements.define('magic-button', class extends HTMLElement {
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
+
```js
|
|
801
|
+
customElements.define('magic-button', class extends HTMLElement {
|
|
802
|
+
connectedCallback() {
|
|
803
|
+
const shadowRoot = this.attachShadow({ mode: 'open' });
|
|
804
|
+
this.import('@vendor1/magic-button', template => {
|
|
805
|
+
shadowRoot.appendChild( template.content.cloneNode(true) );
|
|
806
|
+
});
|
|
807
|
+
}
|
|
800
808
|
});
|
|
801
|
-
|
|
802
|
-
});
|
|
803
|
-
```
|
|
809
|
+
```
|
|
804
810
|
|
|
805
|
-
</details>
|
|
811
|
+
</details>
|
|
806
812
|
|
|
807
|
-
|
|
813
|
+
+ *Then, the part where we just drop the component in "layout" contexts*:
|
|
814
|
+
|
|
815
|
+
<details><summary>Code</summary>
|
|
816
|
+
|
|
817
|
+
```html
|
|
818
|
+
<div contextname="vendor1" importscontext="/vendor1/components-layout1">
|
|
819
|
+
|
|
820
|
+
<magic-button></magic-button>
|
|
821
|
+
|
|
822
|
+
<aside contextname="vendor1" importscontext="/vendor1/components-layout2">
|
|
823
|
+
<magic-button></magic-button>
|
|
824
|
+
</aside>
|
|
825
|
+
|
|
826
|
+
</div>
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
</details>
|
|
830
|
+
|
|
831
|
+
### Example 4: *List Items*
|
|
832
|
+
|
|
833
|
+
The following is a "component" that derives its list items and other reusable snippets from "scoped" `<tenplate>` elements. The idea is to have a "self-contained" component that's all markup-based, not class-based!
|
|
808
834
|
|
|
809
835
|
<details><summary>Code</summary>
|
|
810
836
|
|
|
811
837
|
```html
|
|
812
|
-
<div
|
|
838
|
+
<div namespace>
|
|
813
839
|
|
|
814
|
-
<
|
|
840
|
+
<ul id="list"></ul>
|
|
815
841
|
|
|
816
|
-
<
|
|
817
|
-
<
|
|
818
|
-
</
|
|
842
|
+
<template def="item" scoped>
|
|
843
|
+
<li><a>Item</a></li>
|
|
844
|
+
</template>
|
|
845
|
+
|
|
846
|
+
<script scoped>
|
|
847
|
+
// Import item template
|
|
848
|
+
let itemImport = this.import('item');
|
|
849
|
+
let itemTemplate = itemImport.value;
|
|
850
|
+
|
|
851
|
+
// Iterate
|
|
852
|
+
[ 'Item 1', 'Item 2', 'Item 3' ].forEach(entry => {
|
|
853
|
+
const currentItem = itemTemplate.content.cloneNode(true);
|
|
854
|
+
// Add to DOM
|
|
855
|
+
this.namespace.list.appendChild(currentItem);
|
|
856
|
+
// Render
|
|
857
|
+
currentItem.innerHTML = entry;
|
|
858
|
+
});
|
|
859
|
+
</script>
|
|
819
860
|
|
|
820
861
|
</div>
|
|
821
862
|
```
|
|
822
863
|
|
|
823
864
|
</details>
|
|
824
865
|
|
|
825
|
-
### Example 4: *List
|
|
866
|
+
### Example 4: *Live List*
|
|
826
867
|
|
|
827
|
-
The following is
|
|
868
|
+
The following is the same list as above but implemented as a live list! Here, we make a few changes: the script element is Stateful; the loop itself now uses the literal `for ... of` construct, [which is capable of rendering live lists](https://github.com/webqit/stateful-js/wiki#with-control-structures), so that any additions and removals on the original list is statically reflected!
|
|
828
869
|
|
|
829
870
|
<details><summary>Code</summary>
|
|
830
871
|
|
|
831
872
|
```html
|
|
832
873
|
<div namespace>
|
|
833
874
|
|
|
834
|
-
<import ref="other"></import>
|
|
835
875
|
<ul id="list"></ul>
|
|
836
|
-
<import ref="other"></import>
|
|
837
876
|
|
|
838
877
|
<template def="item" scoped>
|
|
839
|
-
<li>
|
|
840
|
-
<a></a>
|
|
841
|
-
</li>
|
|
842
|
-
</template>
|
|
843
|
-
|
|
844
|
-
<template def="other" scoped>
|
|
845
|
-
<button>Call to Action >><button>
|
|
878
|
+
<li><a>Item</a></li>
|
|
846
879
|
</template>
|
|
847
880
|
|
|
848
881
|
<script scoped>
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
882
|
+
// Import item template
|
|
883
|
+
let itemImport = this.import('item');
|
|
884
|
+
let itemTemplate = itemImport.value;
|
|
885
|
+
|
|
886
|
+
// Iterate
|
|
887
|
+
let items = [ 'Item 1', 'Item 2', 'Item 3' ];
|
|
888
|
+
for (let entry of items) {
|
|
889
|
+
const currentItem = itemTemplate.content.cloneNode(true);
|
|
890
|
+
// Add to DOM
|
|
891
|
+
this.namespace.list.appendChild(currentItem);
|
|
892
|
+
// Remove from DOM whenever corresponding entry is removed
|
|
893
|
+
if (typeof entry === 'undefined') {
|
|
894
|
+
currentItem.remove();
|
|
895
|
+
continue;
|
|
896
|
+
}
|
|
897
|
+
// Render
|
|
898
|
+
currentItem.innerHTML = entry;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// Add a new entry
|
|
902
|
+
setTimeout(() => items.push('Item 4'), 1000);
|
|
903
|
+
// Remove an new entry
|
|
904
|
+
setTimeout(() => items.pop(), 2000);
|
|
853
905
|
</script>
|
|
854
906
|
|
|
855
907
|
</div>
|