@webqit/oohtml 2.1.60 → 2.1.62
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 +367 -90
- package/dist/html-imports.js +1 -1
- package/dist/html-imports.js.map +2 -2
- package/dist/main.js +1 -1
- package/dist/main.js.map +2 -2
- package/package.json +1 -1
- package/src/html-imports/index.js +4 -1
package/README.md
CHANGED
|
@@ -7,40 +7,36 @@
|
|
|
7
7
|
|
|
8
8
|
**[Overview](#overview) • [Modular HTML](#modular-html) • [HTML Imports](#html-imports) • [Reactive HTML](#reactive-html) • [Polyfill](#polyfill) • [Examples](#examples) • [License](#license)**
|
|
9
9
|
|
|
10
|
-
Object-Oriented HTML (OOHTML) is a set of
|
|
11
|
-
|
|
12
|
-
OOHTML is an upcoming proposal!
|
|
10
|
+
Object-Oriented HTML (OOHTML) is a set of features that extend standard HTML and the DOM to enable authoring modular, reusable and reactive markup - with a "buildless", web-native workflow as design goal! This project presents what "modern" HTML could look like!
|
|
13
11
|
|
|
14
12
|
## Motivation
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
The web has generally outgrown the idea of a monolith architecture on the UI! But enter HTML; the current authoring experience is one where an author is trying to think out one thing but forced to work out everything, in how the language for the job poses one global scope as the unit of abstraction for styles, scripts and element identifiers — enforcing many global dependencies; inflicting much global thinking!
|
|
14
|
+
Vanilla HTML is increasingly becoming the compelling option for web developers! But the current authoring experience still leaves much to be desired in how the language lacks modularity, reusability, and data binding! Authors still have to rely on tools - or, to say the least, do half of the work in HTML and half in JS - to get even basic things working!
|
|
19
15
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
This project is a proposal for a new standards work that revisits much of the oldish monolith-oriented constraints in HTML that inhibit the idea of a *component* architecture in HTML!
|
|
16
|
+
This project pursues an object-oriented approach to HTML and implicitly revisits much of what inhibits the idea of a *component* architecture for HTML!
|
|
23
17
|
|
|
24
18
|
└ [See more in the introductory blog post](https://dev.to/oxharris/the-web-native-equations-1m1p-temp-slug-6661657?preview=ba70ad2c17f05b5761bc74516dbde8c9eff8b581a0420d87334fd9ef6bab9d6e6d3ab6aaf3fe02542bb9e7250d0a88a6df91dae40919aabcc9a07320)<sup>draft</sup>
|
|
25
19
|
|
|
26
|
-
</details>
|
|
27
|
-
|
|
28
20
|
## Overview
|
|
29
21
|
|
|
30
|
-
|
|
22
|
+
On the agenda is a set of features that normalises how the modern UI lends itself to be built! And all of it comes as a special love letter to Single Page Applications!
|
|
31
23
|
|
|
32
24
|
+ [Modular HTML](#modular-html)
|
|
33
25
|
+ [HTML Imports](#html-imports)
|
|
34
|
-
+ [
|
|
35
|
-
+ [
|
|
26
|
+
+ [Data Binding](#data-binding)
|
|
27
|
+
+ [Data Plumbing](#data-plumbing)
|
|
36
28
|
|
|
37
29
|
> **Note** This is documentation for `OOHTML@2.x`. (Looking for [`OOHTML@1.x`](https://github.com/webqit/oohtml/tree/v1.10.4)?)
|
|
38
30
|
|
|
39
31
|
## Modular HTML
|
|
40
32
|
|
|
41
|
-
|
|
33
|
+
Modular HTML is a markup pattern that lets us write markup as self-contained objects - with each *encapsulating* its own structure, styling and logic - as against the regular idea of having everything converge and conflict on one global scope!
|
|
34
|
+
|
|
35
|
+
OOHTML makes this possible in just simple conventions - via two new attributes: `namespace` and `scoped`!
|
|
42
36
|
|
|
43
|
-
|
|
37
|
+
### Namespacing
|
|
38
|
+
|
|
39
|
+
The `namespace` attribute for designating an element as own naming context for identifiers - i.e. the `id` and `name` attributes:
|
|
44
40
|
|
|
45
41
|
```html
|
|
46
42
|
<div id="user" namespace>
|
|
@@ -51,6 +47,8 @@ The first set of features covers authoring objects with self-contained structure
|
|
|
51
47
|
</div>
|
|
52
48
|
```
|
|
53
49
|
|
|
50
|
+
*which translates really well to an object model*:
|
|
51
|
+
|
|
54
52
|
```html
|
|
55
53
|
user
|
|
56
54
|
├── url
|
|
@@ -58,16 +56,64 @@ user
|
|
|
58
56
|
└── email
|
|
59
57
|
```
|
|
60
58
|
|
|
59
|
+
*with a corresponding API that exposes said structure to JavaScript applications*:
|
|
60
|
+
|
|
61
61
|
```js
|
|
62
|
-
// The namespace API
|
|
62
|
+
// The document.namespace API
|
|
63
63
|
let { user } = document.namespace;
|
|
64
|
+
// The Element.prototype.namespace API
|
|
64
65
|
let { url, name, email } = user.namespace;
|
|
65
66
|
```
|
|
66
67
|
|
|
67
|
-
|
|
68
|
+
<details><summary>Learn more</summary>
|
|
69
|
+
|
|
70
|
+
You want to see how IDs are otherwise exposed as global variables:
|
|
71
|
+
|
|
72
|
+
```html
|
|
73
|
+
<div id="foo"><div>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```js
|
|
77
|
+
console.log(window.foo); // div
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
[Read more](https://stackoverflow.com/questions/6381425/is-there-a-spec-that-the-id-of-elements-should-be-made-global-variable)
|
|
81
|
+
|
|
82
|
+
</details>
|
|
83
|
+
|
|
84
|
+
└ A Namespace API that reflects the real-DOM™ in real-time, in conjunction with the general-purpose object observability API - [Observer API](https://github.com/webqit/observer):
|
|
85
|
+
|
|
86
|
+
```js
|
|
87
|
+
// Observing the addition or removal of elements with an ID
|
|
88
|
+
Observer.observe(document.namespace, changeCallback);
|
|
89
|
+
|
|
90
|
+
const paragraph = document.createElement('p');
|
|
91
|
+
paragraph.setAttribute('id', 'bar');
|
|
92
|
+
document.body.appendChild(paragraph); // Reported synchronously
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
// Observing the addition or removal of elements with an ID
|
|
97
|
+
paragraph.toggleAttribute('namespace', true);
|
|
98
|
+
Observer.observe(paragraph.namespace, changeCallback);
|
|
99
|
+
|
|
100
|
+
const span = document.createElement('span');
|
|
101
|
+
span.setAttribute('id', 'baz');
|
|
102
|
+
paragraph.appendChild(span); // Reported synchronously
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```js
|
|
106
|
+
function changeCallback(changes) {
|
|
107
|
+
console.log(changes[0].type, changes[0].key, changes[0].value, changes[0].oldValue);
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Style and Script Scoping
|
|
112
|
+
|
|
113
|
+
The `scoped` attribute for *scoping* element-specific stylesheets and scripts:
|
|
68
114
|
|
|
69
115
|
```html
|
|
70
|
-
<div
|
|
116
|
+
<div>
|
|
71
117
|
|
|
72
118
|
<style scoped>
|
|
73
119
|
:scope { color: red }
|
|
@@ -80,17 +126,23 @@ let { url, name, email } = user.namespace;
|
|
|
80
126
|
</div>
|
|
81
127
|
```
|
|
82
128
|
|
|
129
|
+
*with a corresponding API that exposes said assets to JavaScript applications*:
|
|
130
|
+
|
|
83
131
|
```js
|
|
84
132
|
let { styleSheets, scripts } = user; // APIs that are analogous to the document.styleSheets, document.scripts properties
|
|
85
133
|
```
|
|
86
134
|
|
|
87
|
-
└ [Modular HTML
|
|
135
|
+
└ [Modular HTML examples](#modular-html-examples)
|
|
88
136
|
|
|
89
137
|
## HTML Imports
|
|
90
138
|
|
|
91
|
-
|
|
139
|
+
HTML Imports is a realtime module system for *templating and reusing* HTML in HTML, and optionally in JavaScript! Something like it is the [`<defs>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/defs) and [`<use>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/use) system in SVG.
|
|
140
|
+
|
|
141
|
+
OOHTML makes this possible in just simple conventions - via a new `def` attribute and a complementary new `<import>` element!
|
|
142
|
+
|
|
143
|
+
### Module Definition
|
|
92
144
|
|
|
93
|
-
|
|
145
|
+
The `def` attribute for defining reusable markup - either as whole *module* or as *fragment*:
|
|
94
146
|
|
|
95
147
|
```html
|
|
96
148
|
<head>
|
|
@@ -104,7 +156,7 @@ The next set of features covers *templating and reusing objects* - in both *decl
|
|
|
104
156
|
</head>
|
|
105
157
|
```
|
|
106
158
|
|
|
107
|
-
└
|
|
159
|
+
└ Module nesting for code organization:
|
|
108
160
|
|
|
109
161
|
```html
|
|
110
162
|
<head>
|
|
@@ -120,16 +172,20 @@ The next set of features covers *templating and reusing objects* - in both *decl
|
|
|
120
172
|
</head>
|
|
121
173
|
```
|
|
122
174
|
|
|
123
|
-
|
|
175
|
+
### Remote Modules
|
|
176
|
+
|
|
177
|
+
The `<template src>` element for remote modules:
|
|
124
178
|
|
|
125
179
|
```html
|
|
126
180
|
<template def="foo" src="/foo.html"></template>
|
|
181
|
+
<!-- which links to the file below -->
|
|
127
182
|
```
|
|
128
183
|
|
|
129
184
|
```html
|
|
130
185
|
-- file: /foo.html --
|
|
131
186
|
<div def="fragment1"></div>
|
|
132
187
|
<template def="nested" src="/nested.html"></template>
|
|
188
|
+
<!-- which itself links to the file below -->
|
|
133
189
|
```
|
|
134
190
|
|
|
135
191
|
```html
|
|
@@ -138,11 +194,16 @@ The next set of features covers *templating and reusing objects* - in both *decl
|
|
|
138
194
|
--
|
|
139
195
|
```
|
|
140
196
|
|
|
197
|
+
*which extends how elements like images already work; terminating with either a `load` or an `error` event*:
|
|
198
|
+
|
|
141
199
|
```js
|
|
142
|
-
foo.addEventListener('load',
|
|
200
|
+
foo.addEventListener('load', loadCallback);
|
|
201
|
+
foo.addEventListener('error', errorCallback);
|
|
143
202
|
```
|
|
144
203
|
|
|
145
|
-
|
|
204
|
+
### Declarative Module Imports
|
|
205
|
+
|
|
206
|
+
The `<import>` element for declarative module import:
|
|
146
207
|
|
|
147
208
|
```html
|
|
148
209
|
<body>
|
|
@@ -158,20 +219,140 @@ foo.addEventListener('load', loadedCallback);
|
|
|
158
219
|
</body>
|
|
159
220
|
```
|
|
160
221
|
|
|
161
|
-
|
|
222
|
+
<details><summary>All in Realtime</summary>
|
|
223
|
+
|
|
224
|
+
As a realtime module system, `<import> `elements maintain a live relationship with given module definition `<template def>` elements and are resolved again in the event that:
|
|
225
|
+
+ the `<import>` element points to another module — either by `ref` change or by a change in `importscontext` (below).
|
|
226
|
+
+ the module definition `<template def>` element has had its contents updated, either programmatically or automatically from having loaded.
|
|
227
|
+
|
|
228
|
+
Conversely, an `<import>` element that has been resolved will self-restore in the event that:
|
|
229
|
+
+ the `<import>` element no longer points to a module; or the module has been emptied or removed.
|
|
230
|
+
+ the previously slotted contents have been programmatically removed and slot is empty.
|
|
231
|
+
|
|
232
|
+
</details>
|
|
233
|
+
|
|
234
|
+
<details><summary>With SSR Support</summary>
|
|
235
|
+
|
|
236
|
+
On the server, these `<import>` elements would retain their place in the DOM, but this time, serialized into comment nodes, while having their output rendered just above them as siblings.
|
|
237
|
+
|
|
238
|
+
The above resolved imports would thus give us something like:
|
|
239
|
+
|
|
240
|
+
```html
|
|
241
|
+
<body>
|
|
242
|
+
<div def="fragment1"></div>
|
|
243
|
+
<!--<import ref="/foo#fragment1"></import>-->
|
|
244
|
+
<div def="fragment2"></div>
|
|
245
|
+
<!--<import ref="/foo/nested#fragment2"></import>-->
|
|
246
|
+
</body>
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
But they also have to remember the exact imported nodes that they manage so as to be able to re-establish that relationship on getting to the client. This information is automatically encoded as part of the serialised element itself, in something like:
|
|
250
|
+
|
|
251
|
+
```html
|
|
252
|
+
<!--<import ref="/foo/nested#fragment2" nodecount="1"></import>-->
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Now that extra bit of information gets decoded and original relationships are formed again on getting to the client and "hydrating" the `<import>` element. But, the `<import>` element itself stays invisible in the DOM while still continuing to kick as above!
|
|
256
|
+
|
|
257
|
+
> Note: We know we're on the server when `window.webqit.env === 'server'`. This flag is automatically set by OOHTML's current SSR engine: [OOHTML-SSR](https://github.com/webqit/oohtml-ssr)
|
|
258
|
+
|
|
259
|
+
</details>
|
|
260
|
+
|
|
261
|
+
### Programmatic Module Imports
|
|
262
|
+
|
|
263
|
+
The *HTMLImports* API for programmatic module import:
|
|
162
264
|
|
|
163
265
|
```js
|
|
164
|
-
const
|
|
165
|
-
console.log(
|
|
266
|
+
const moduleObject1 = document.import('/foo#fragment1');
|
|
267
|
+
console.log(moduleObject1.value); // divElement
|
|
166
268
|
```
|
|
167
269
|
|
|
168
270
|
```js
|
|
169
|
-
const
|
|
170
|
-
console.log(
|
|
271
|
+
const moduleObject2 = document.import('/foo/nested#fragment2');
|
|
272
|
+
console.log(moduleObject2.value); // divElement
|
|
171
273
|
```
|
|
172
274
|
|
|
173
|
-
|
|
174
|
-
|
|
275
|
+
*with an observable `moduleObject.value` property for working asynchronously; e.g. awaiting and handling remote modules*:
|
|
276
|
+
|
|
277
|
+
```js
|
|
278
|
+
Observer.observe(moduleObject2, 'value', e => {
|
|
279
|
+
console.log(e.value); // divElement
|
|
280
|
+
});
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
*with an equivalent `callback` option on the `import()` method itself*:
|
|
284
|
+
|
|
285
|
+
```js
|
|
286
|
+
document.import('/foo#fragment1', divElement => {
|
|
287
|
+
console.log(divElement);
|
|
288
|
+
});
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
└ An optional `live` parameter for staying subscribed to any mutations made to source module elements:
|
|
292
|
+
|
|
293
|
+
```js
|
|
294
|
+
const moduleObject2 = document.import('/foo/nested#fragment2', true/*live*/);
|
|
295
|
+
console.log(moduleObject2.value);
|
|
296
|
+
Observer.observe(moduleObject2, 'value', e => {
|
|
297
|
+
console.log(e.value);
|
|
298
|
+
});
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
```js
|
|
302
|
+
document.import('/foo#fragment1', true/*live*/, divElement => {
|
|
303
|
+
console.log(divElement); // To be received after remote module has been loaded
|
|
304
|
+
});
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
*both of which would get notified on doing the below*:
|
|
308
|
+
|
|
309
|
+
```js
|
|
310
|
+
document.querySelector('template[def="foo"]').content.firstElementChild.remove();
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
└ An optional `AbortSignal` parameter for aborting module mutation events:
|
|
314
|
+
|
|
315
|
+
```js
|
|
316
|
+
const abortController = new AbortController;
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
```js
|
|
320
|
+
const moduleObject2 = document.import('/foo/nested#fragment2', { live: true, signal: abortController.signal });
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
```js
|
|
324
|
+
document.import('/foo#fragment1', { live: true, signal: abortController.signal }, divElement => {
|
|
325
|
+
console.log(divElement); // To be received after remote module has been loaded
|
|
326
|
+
});
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
```js
|
|
330
|
+
setTimeout(() => abortController.abort(), 1000);
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
<details><summary>Extended Imports concepts</summary>
|
|
334
|
+
|
|
335
|
+
### Lazy-Loading Modules
|
|
336
|
+
|
|
337
|
+
Remote modules with lazy-loading - which has modules loading on first time access:
|
|
338
|
+
|
|
339
|
+
```html
|
|
340
|
+
<!-- Loading doesn't happen until the first time this is being accessed -->
|
|
341
|
+
<template def="foo" src="/foo.html" loading="lazy"></template>
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
```html
|
|
345
|
+
<body>
|
|
346
|
+
<import ref="/foo#fragment1"></import> <!-- Triggers module loading and resolves on load success -->
|
|
347
|
+
</body>
|
|
348
|
+
```
|
|
349
|
+
```js
|
|
350
|
+
const moduleObject2 = document.import('/foo#fragment1'); // Triggers module loading and resolves at moduleObject2.value on load success
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Scoped Modules
|
|
354
|
+
|
|
355
|
+
The `scoped` attribute for an *object-scoped* module system:
|
|
175
356
|
|
|
176
357
|
```html
|
|
177
358
|
<section> <!-- object with own modules -->
|
|
@@ -188,6 +369,8 @@ console.log(import2); // { value: div }
|
|
|
188
369
|
</section>
|
|
189
370
|
```
|
|
190
371
|
|
|
372
|
+
*with an equivalent `Element.prototype.import()` API for accessing said scoped modules*:
|
|
373
|
+
|
|
191
374
|
```js
|
|
192
375
|
// Using the HTMLImports API
|
|
193
376
|
const moduleHost = document.querySelector('div');
|
|
@@ -202,11 +385,9 @@ const globalImport1 = moduleHost.import('/foo#fragment1'); // the global module:
|
|
|
202
385
|
console.log(globalImport1); // { value: div }
|
|
203
386
|
```
|
|
204
387
|
|
|
205
|
-
|
|
206
|
-
Extended Imports concepts
|
|
207
|
-
</summary>
|
|
388
|
+
### Module Inheritance
|
|
208
389
|
|
|
209
|
-
|
|
390
|
+
Module nesting with inheritance:
|
|
210
391
|
|
|
211
392
|
```html
|
|
212
393
|
<template def="foo">
|
|
@@ -250,34 +431,9 @@ Extended Imports concepts
|
|
|
250
431
|
</body>
|
|
251
432
|
```
|
|
252
433
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
```html
|
|
256
|
-
<!-- Loading doesn't happen until the first time this is being accessed -->
|
|
257
|
-
<template def="foo" src="/foo.html" loading="lazy"></template>
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
```html
|
|
261
|
-
<body>
|
|
262
|
-
<import ref="/foo#fragment1"></import> <!-- To be resolved after remote module has been loaded -->
|
|
263
|
-
</body>
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
```js
|
|
267
|
-
document.import('/foo#fragment1', divElement => {
|
|
268
|
-
console.log(divElement); // To be received after remote module has been loaded
|
|
269
|
-
});
|
|
270
|
-
```
|
|
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
|
-
```
|
|
434
|
+
### Imports Contexts
|
|
279
435
|
|
|
280
|
-
|
|
436
|
+
"Imports Contexts" for context-based *import resolution*:
|
|
281
437
|
|
|
282
438
|
```html
|
|
283
439
|
<body importscontext="/foo">
|
|
@@ -302,7 +458,7 @@ const globalImport2 = moduleHost.import('#fragment2'); // module:/foo/nested#fra
|
|
|
302
458
|
console.log(globalImport2); // { value: div }
|
|
303
459
|
```
|
|
304
460
|
|
|
305
|
-
└
|
|
461
|
+
└ "Imports Contexts" with named contexts:
|
|
306
462
|
|
|
307
463
|
```html
|
|
308
464
|
<body contextname="context1" importscontext="/foo/nested">
|
|
@@ -326,7 +482,7 @@ const globalImport2 = moduleHost.import('@context1#fragment2'); // module:/foo/n
|
|
|
326
482
|
console.log(globalImport2); // { value: div }
|
|
327
483
|
```
|
|
328
484
|
|
|
329
|
-
└
|
|
485
|
+
└ "Imports Contexts" with context inheritance:
|
|
330
486
|
|
|
331
487
|
```html
|
|
332
488
|
<body importscontext="/foo">
|
|
@@ -340,7 +496,9 @@ console.log(globalImport2); // { value: div }
|
|
|
340
496
|
</body>
|
|
341
497
|
```
|
|
342
498
|
|
|
343
|
-
|
|
499
|
+
### Scoped Modules and Imports Contexts
|
|
500
|
+
|
|
501
|
+
Object-scoped module system with context inheritance:
|
|
344
502
|
|
|
345
503
|
```html
|
|
346
504
|
<body contextname="context1" importscontext="/bar">
|
|
@@ -370,10 +528,148 @@ console.log(localOrGlobalImport2); // { value: div }
|
|
|
370
528
|
|
|
371
529
|
</details>
|
|
372
530
|
|
|
373
|
-
└ [HTML Imports
|
|
531
|
+
└ [HTML Imports examples](#html-imports-examples)
|
|
532
|
+
|
|
533
|
+
## Data Binding
|
|
534
|
+
|
|
535
|
+
Data binding is the concept of declaratively driving the UI with application-level data. It comes as mechanism that sits between the UI and the application itself, ensuring that the relevant parts of the UI are *automatically* updated as application state changes.
|
|
536
|
+
|
|
537
|
+
OOHTML makes this possible in just simple conventions - via a new comment-based data-binding syntax `<?{ }?>` and a complementary new `binding` attribute!
|
|
538
|
+
|
|
539
|
+
### Comment-Based Data-Binding
|
|
540
|
+
|
|
541
|
+
A web-native, comment-based data-binding syntax `<?{ }?>` which works like regular comment but stay "data-charged":
|
|
542
|
+
|
|
543
|
+
```js
|
|
544
|
+
<html>
|
|
545
|
+
<head>
|
|
546
|
+
<title><?{ app.title }?></title>
|
|
547
|
+
</head>
|
|
548
|
+
<body>
|
|
549
|
+
Hi, I'm <?{ app.name ?? 'Default name' }?>!
|
|
550
|
+
and here's another way to write the same comment: <!--?{ app.cool }?-->
|
|
551
|
+
</body>
|
|
552
|
+
</html>
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
<details><summary>With SSR Support</summary>
|
|
556
|
+
|
|
557
|
+
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.
|
|
558
|
+
|
|
559
|
+
The following: `<?{ 'Hello World' }?>` would thus give us: `<?{ 'Hello World' }?>Hello World`.
|
|
560
|
+
|
|
561
|
+
But they also have to remember the exact text node that they manage, so as to be able to re-establish that relationship on getting to the client. That information is automatically encoded as part of the declaration itself, and that brings us to having a typical server-rendered binding look like this:
|
|
562
|
+
|
|
563
|
+
```html
|
|
564
|
+
<?{ 'Hello World'; [=11] }?>Hello World
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
Now that extra bit of information gets decoded and original relationships are forned again on getting to the client. But the binding tag itself graciously disappears from the DOM, while the now "hydrated" text node continues to kick!
|
|
568
|
+
|
|
569
|
+
> Note: We know we're on the server when `window.webqit.env === 'server'`. This flag is automatically set by OOHTML's current SSR engine: [OOHTML-SSR](https://github.com/webqit/oohtml-ssr)
|
|
570
|
+
|
|
571
|
+
</details>
|
|
572
|
+
|
|
573
|
+
### Directives-Based Data-Binding
|
|
574
|
+
|
|
575
|
+
The `binding` attribute for a declarative and neat, key/value data-binding syntax:
|
|
576
|
+
|
|
577
|
+
```html
|
|
578
|
+
<div binding="<type><parameter>: <argument>;"></div>
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
*where*:
|
|
582
|
+
|
|
583
|
+
+ *`<type>` is the binding type, which is always a symbol*
|
|
584
|
+
+ *`<directive>` is the binding directive, which could be any of CSS property, class name, attribute name, Structural Directive*
|
|
585
|
+
+ *`<argument>` is the bound value or expression*
|
|
586
|
+
|
|
587
|
+
*which would give us the following for a CSS property*:
|
|
588
|
+
|
|
589
|
+
```html
|
|
590
|
+
<div binding="&color: someColor; &backgroundColor: 'red'"></div>
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
*with enough liberty to separate the binding type from the directive itself*:
|
|
594
|
+
|
|
595
|
+
```html
|
|
596
|
+
<div binding="& color: someColor; & backgroundColor: 'red'"></div>
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
*all of which can be seen here*:
|
|
600
|
+
|
|
601
|
+
| Symbol | Meaning | Usage |
|
|
602
|
+
| :---- | :---- | :---- |
|
|
603
|
+
| `&` | CSS Property | `<div binding="&color: someColor;"></div>` |
|
|
604
|
+
| `%` | Class Name | `<div binding="%active: app.isActive;"></div>` |
|
|
605
|
+
| `~` | Attribute Name | `<a binding="~href: person.profileUrl + '#bio';"></a>` |
|
|
606
|
+
| `@` | Structural Directive: | *See next table* |
|
|
607
|
+
|
|
608
|
+
<details><summary>Structural Directives</summary>
|
|
609
|
+
|
|
610
|
+
| Directive | Meaning | Usage |
|
|
611
|
+
| :---- | :---- | :---- |
|
|
612
|
+
| `@text` | For rendering plain text content | `<span binding="@text: firstName + ' ' + lastName;"></span>` |
|
|
613
|
+
| `@html` | For rendering markup content | `<span binding="@html: '<i>' + firstName + '</i>';"></span>` |
|
|
614
|
+
| `@items` | For rendering a list, with argument in the following format:<br>`<declaration> <of\|in> <iterable> / <importRef>` | *See next two tables* |
|
|
615
|
+
|
|
616
|
+
<details><summary>"For ... Of" Loops</summary>
|
|
617
|
+
|
|
618
|
+
| Idea | Usage |
|
|
619
|
+
| :---- | :---- |
|
|
620
|
+
| Loop over an array/iterable | `<ul binding="@items: value of [1,2,3] / 'foo#fragment';"></ul>` |
|
|
621
|
+
| Same as above but with a `key` declaration | `<ul binding="@items: (value, key) of [1,2,3] / 'foo#fragment';"></ul>` |
|
|
622
|
+
| Same as above but with different variable names | `<ul binding="@items: (product, id) of store.products / 'foo#fragment';"></ul>` |
|
|
623
|
+
| Same as above but with a dynamic `importRef` | `<ul binding="@items: (product, id) of store.products / store.importRef;"></ul>` |
|
|
624
|
+
|
|
625
|
+
</details>
|
|
626
|
+
|
|
627
|
+
<details><summary>"For ... In" Loops</summary>
|
|
628
|
+
|
|
629
|
+
| Idea | Usage |
|
|
630
|
+
| :---- | :---- |
|
|
631
|
+
| Loop over an object | `<ul binding="@items: key in { a: 1, b: 2 } / 'foo#fragment';"></ul>` |
|
|
632
|
+
| Same as above but with a `value` and `index` declaration | `<ul binding="@items: (key, value, index) in { a: 1, b: 2 } / 'foo#fragment';"></ul>` |
|
|
633
|
+
|
|
634
|
+
</details>
|
|
635
|
+
|
|
636
|
+
</details>
|
|
637
|
+
|
|
638
|
+
<details><summary>Example</summary>
|
|
639
|
+
|
|
640
|
+
```html
|
|
641
|
+
<section>
|
|
642
|
+
|
|
643
|
+
<!-- The "items" template -->
|
|
644
|
+
<template def="item" scoped>
|
|
645
|
+
<li binding="@text: key + ': ' + name;"></li>
|
|
646
|
+
</template>
|
|
647
|
+
|
|
648
|
+
<!-- The loop -->
|
|
649
|
+
<ul binding="@items: (name, key) of ['dog','cat','ram'] / 'item';"></ul>
|
|
650
|
+
|
|
651
|
+
</section>
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
</details>
|
|
374
655
|
|
|
375
|
-
|
|
656
|
+
<details><summary>All in Realtime</summary>
|
|
376
657
|
|
|
658
|
+
Lists are rendered in realtime, which means that in-place mutations - additions and removals - on the *iteratee* will be automatically reflected on the UI!
|
|
659
|
+
|
|
660
|
+
</details>
|
|
661
|
+
|
|
662
|
+
<details><summary>With SSR Support</summary>
|
|
663
|
+
|
|
664
|
+
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.
|
|
665
|
+
|
|
666
|
+
</details>
|
|
667
|
+
|
|
668
|
+
## Data Plumbing
|
|
669
|
+
|
|
670
|
+
*[TODO]: The Context API and Bindings API*
|
|
671
|
+
|
|
672
|
+
<!--
|
|
377
673
|
The last set of features covers the concept of "state", "bindings", and "reactivity" for those objects at the DOM level - in the most exciting form of the terms and as an upgrade path! This comes factored into the design as something intrinsic to the problem.
|
|
378
674
|
|
|
379
675
|
└ *The [Observer API](https://github.com/webqit/observer) for general-purpose object observability*:
|
|
@@ -397,27 +693,6 @@ Observer.set(obj, 'prop1', 'value1'); // Reported synchronously
|
|
|
397
693
|
Observer.deleteProperty(obj, 'prop1'); // Reported synchronously
|
|
398
694
|
```
|
|
399
695
|
|
|
400
|
-
└ *A Namespace API that reflects the real-DOM™ in real-time*:
|
|
401
|
-
|
|
402
|
-
```js
|
|
403
|
-
// Observing the addition or removal of elements with an ID
|
|
404
|
-
Observer.observe(document.namespace, changeCallback);
|
|
405
|
-
|
|
406
|
-
const paragraph = document.createElement('p');
|
|
407
|
-
paragraph.setAttribute('id', 'bar');
|
|
408
|
-
document.body.appendChild(paragraph); // Reported synchronously
|
|
409
|
-
```
|
|
410
|
-
|
|
411
|
-
```js
|
|
412
|
-
// Observing the addition or removal of elements with an ID
|
|
413
|
-
paragraph.toggleAttribute('namespace', true);
|
|
414
|
-
Observer.observe(paragraph.namespace, changeCallback);
|
|
415
|
-
|
|
416
|
-
const span = document.createElement('span');
|
|
417
|
-
span.setAttribute('id', 'baz');
|
|
418
|
-
paragraph.appendChild(span); // Reported synchronously
|
|
419
|
-
```
|
|
420
|
-
|
|
421
696
|
└ *A Bindings API for binding application-level state to an object*:
|
|
422
697
|
|
|
423
698
|
```js
|
|
@@ -485,6 +760,8 @@ Observer.set(element, 'liveProperty'); // Live expressions rerun
|
|
|
485
760
|
|
|
486
761
|
└ [Reactive HTML concepts](#)
|
|
487
762
|
|
|
763
|
+
-->
|
|
764
|
+
|
|
488
765
|
## Polyfill
|
|
489
766
|
|
|
490
767
|
OOHTML is being developed as something to be used today - via a polyfill.
|