@webqit/oohtml 2.1.59 → 2.1.61
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 +363 -85
- package/dist/bindings-api.js +1 -1
- package/dist/bindings-api.js.map +2 -2
- package/dist/context-api.js +1 -1
- package/dist/context-api.js.map +2 -2
- package/dist/{html-bindings.js → data-binding.js} +4 -4
- package/dist/{html-bindings.js.map → data-binding.js.map} +3 -3
- package/dist/html-imports.js +1 -1
- package/dist/html-imports.js.map +2 -2
- package/dist/main.js +6 -6
- package/dist/main.js.map +3 -3
- package/dist/{html-namespaces.js → namespaced-html.js} +2 -2
- package/dist/{html-namespaces.js.map → namespaced-html.js.map} +2 -2
- package/package.json +4 -4
- package/src/bindings-api/_HTMLBindingsProvider.js +13 -0
- package/src/bindings-api/index.js +19 -8
- package/src/context-api/index.js +1 -0
- package/src/{html-bindings → data-binding}/index.js +17 -17
- package/src/html-imports/_HTMLImportElement.js +1 -1
- package/src/html-imports/index.js +6 -2
- package/src/index.js +9 -9
- package/src/{html-namespaces → namespaced-html}/index.js +1 -1
- package/test/imports.test.js +6 -4
- package/test/index.js +1 -1
- package/test/scoped-js.test.js +2 -2
- /package/src/{html-bindings → data-binding}/targets.browser.js +0 -0
- /package/src/{html-namespaces → namespaced-html}/targets.browser.js +0 -0
package/README.md
CHANGED
|
@@ -7,19 +7,15 @@
|
|
|
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 language features for authoring modular, reusable
|
|
11
|
-
|
|
12
|
-
OOHTML is an upcoming proposal!
|
|
10
|
+
Object-Oriented HTML (OOHTML) is a set of language features for authoring modular, reusable and reactive markup which brings us to a whole new authoring experience on the modern UI!
|
|
13
11
|
|
|
14
12
|
## Motivation
|
|
15
13
|
|
|
16
14
|
<details><summary>Show</summary>
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
Think too of how authors often have to do half of the work in HTML and half in JS just to have reusable markup!
|
|
16
|
+
Vanilla HTML is increasingly becoming a more attractive 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!
|
|
21
17
|
|
|
22
|
-
This project
|
|
18
|
+
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
19
|
|
|
24
20
|
└ [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
21
|
|
|
@@ -27,20 +23,27 @@ This project is a proposal for a new standards work that revisits much of the ol
|
|
|
27
23
|
|
|
28
24
|
## Overview
|
|
29
25
|
|
|
30
|
-
|
|
26
|
+
Here we extend standard HTML and the DOM to normalise certain fundamental concepts and paradigms that the ecosystem has explored over the years!
|
|
27
|
+
|
|
28
|
+
We go in three main focus areas:
|
|
31
29
|
|
|
32
30
|
+ [Modular HTML](#modular-html)
|
|
33
31
|
+ [HTML Imports](#html-imports)
|
|
34
|
-
+ [
|
|
32
|
+
+ [Data Binding](#data-binding)
|
|
33
|
+
+ [Data Binding](#data-binding)
|
|
35
34
|
+ [Put Together](#put-together)
|
|
36
35
|
|
|
37
36
|
> **Note** This is documentation for `OOHTML@2.x`. (Looking for [`OOHTML@1.x`](https://github.com/webqit/oohtml/tree/v1.10.4)?)
|
|
38
37
|
|
|
39
38
|
## Modular HTML
|
|
40
39
|
|
|
41
|
-
|
|
40
|
+
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!
|
|
41
|
+
|
|
42
|
+
OOHTML makes this possible in just simple conventions - via two new attributes: `namespace` and `scoped`!
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
### Namespacing
|
|
45
|
+
|
|
46
|
+
└ The `namespace` attribute for designating an element as own naming context for identifiers - i.e. the `id` and `name` attributes:
|
|
44
47
|
|
|
45
48
|
```html
|
|
46
49
|
<div id="user" namespace>
|
|
@@ -51,6 +54,8 @@ The first set of features covers authoring objects with self-contained structure
|
|
|
51
54
|
</div>
|
|
52
55
|
```
|
|
53
56
|
|
|
57
|
+
*which translates really well to an object model*:
|
|
58
|
+
|
|
54
59
|
```html
|
|
55
60
|
user
|
|
56
61
|
├── url
|
|
@@ -58,16 +63,58 @@ user
|
|
|
58
63
|
└── email
|
|
59
64
|
```
|
|
60
65
|
|
|
66
|
+
*with a corresponding API that exposes said structure to JavaScript applications*:
|
|
67
|
+
|
|
61
68
|
```js
|
|
62
|
-
// The namespace API
|
|
69
|
+
// The document.namespace API
|
|
63
70
|
let { user } = document.namespace;
|
|
71
|
+
// The Element.prototype.namespace API
|
|
64
72
|
let { url, name, email } = user.namespace;
|
|
65
73
|
```
|
|
66
74
|
|
|
67
|
-
|
|
75
|
+
<details><summary>Learn more</summary>
|
|
76
|
+
|
|
77
|
+
You want to see how IDs are otherwise exposed as global variables:
|
|
78
|
+
|
|
79
|
+
```html
|
|
80
|
+
<div id="foo"><div>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
console.log(window.foo); // div
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
[Read more](https://stackoverflow.com/questions/6381425/is-there-a-spec-that-the-id-of-elements-should-be-made-global-variable)
|
|
88
|
+
|
|
89
|
+
</details>
|
|
90
|
+
|
|
91
|
+
└ *A Namespace API that reflects the real-DOM™ in real-time*:
|
|
92
|
+
|
|
93
|
+
```js
|
|
94
|
+
// Observing the addition or removal of elements with an ID
|
|
95
|
+
Observer.observe(document.namespace, changeCallback);
|
|
96
|
+
|
|
97
|
+
const paragraph = document.createElement('p');
|
|
98
|
+
paragraph.setAttribute('id', 'bar');
|
|
99
|
+
document.body.appendChild(paragraph); // Reported synchronously
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
```js
|
|
103
|
+
// Observing the addition or removal of elements with an ID
|
|
104
|
+
paragraph.toggleAttribute('namespace', true);
|
|
105
|
+
Observer.observe(paragraph.namespace, changeCallback);
|
|
106
|
+
|
|
107
|
+
const span = document.createElement('span');
|
|
108
|
+
span.setAttribute('id', 'baz');
|
|
109
|
+
paragraph.appendChild(span); // Reported synchronously
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Style and Script Scoping
|
|
113
|
+
|
|
114
|
+
└ The `scoped` attribute for *scoping* element-specific stylesheets and scripts:
|
|
68
115
|
|
|
69
116
|
```html
|
|
70
|
-
<div
|
|
117
|
+
<div>
|
|
71
118
|
|
|
72
119
|
<style scoped>
|
|
73
120
|
:scope { color: red }
|
|
@@ -80,17 +127,23 @@ let { url, name, email } = user.namespace;
|
|
|
80
127
|
</div>
|
|
81
128
|
```
|
|
82
129
|
|
|
130
|
+
*with a corresponding API that exposes said assets to JavaScript applications*:
|
|
131
|
+
|
|
83
132
|
```js
|
|
84
133
|
let { styleSheets, scripts } = user; // APIs that are analogous to the document.styleSheets, document.scripts properties
|
|
85
134
|
```
|
|
86
135
|
|
|
87
|
-
└ [Modular HTML
|
|
136
|
+
└ [Modular HTML examples](#modular-html-examples)
|
|
88
137
|
|
|
89
138
|
## HTML Imports
|
|
90
139
|
|
|
91
|
-
|
|
140
|
+
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.
|
|
92
141
|
|
|
93
|
-
|
|
142
|
+
OOHTML makes this possible in just simple conventions - via a new `def` attribute and a complementary new `<import>` element!
|
|
143
|
+
|
|
144
|
+
### Module Definition
|
|
145
|
+
|
|
146
|
+
└ The `def` attribute for defining reusable markup - either as whole *module* or as *fragment*:
|
|
94
147
|
|
|
95
148
|
```html
|
|
96
149
|
<head>
|
|
@@ -104,7 +157,7 @@ The next set of features covers *templating and reusing objects* - in both *decl
|
|
|
104
157
|
</head>
|
|
105
158
|
```
|
|
106
159
|
|
|
107
|
-
└
|
|
160
|
+
└ Module nesting for code organization:
|
|
108
161
|
|
|
109
162
|
```html
|
|
110
163
|
<head>
|
|
@@ -120,16 +173,20 @@ The next set of features covers *templating and reusing objects* - in both *decl
|
|
|
120
173
|
</head>
|
|
121
174
|
```
|
|
122
175
|
|
|
123
|
-
|
|
176
|
+
### Remote Modules
|
|
177
|
+
|
|
178
|
+
└ The `<template src>` element for remote modules:
|
|
124
179
|
|
|
125
180
|
```html
|
|
126
181
|
<template def="foo" src="/foo.html"></template>
|
|
182
|
+
<!-- which links to the file below -->
|
|
127
183
|
```
|
|
128
184
|
|
|
129
185
|
```html
|
|
130
186
|
-- file: /foo.html --
|
|
131
187
|
<div def="fragment1"></div>
|
|
132
188
|
<template def="nested" src="/nested.html"></template>
|
|
189
|
+
<!-- which itself links to the file below -->
|
|
133
190
|
```
|
|
134
191
|
|
|
135
192
|
```html
|
|
@@ -138,11 +195,16 @@ The next set of features covers *templating and reusing objects* - in both *decl
|
|
|
138
195
|
--
|
|
139
196
|
```
|
|
140
197
|
|
|
198
|
+
*which extends how elements like images already work; terminating with either a `load` or an `error` event*:
|
|
199
|
+
|
|
141
200
|
```js
|
|
142
|
-
foo.addEventListener('load',
|
|
201
|
+
foo.addEventListener('load', loadCallback);
|
|
202
|
+
foo.addEventListener('error', errorCallback);
|
|
143
203
|
```
|
|
144
204
|
|
|
145
|
-
|
|
205
|
+
### Declarative Module Imports
|
|
206
|
+
|
|
207
|
+
└ The `<import>` element for declarative module import:
|
|
146
208
|
|
|
147
209
|
```html
|
|
148
210
|
<body>
|
|
@@ -158,20 +220,140 @@ foo.addEventListener('load', loadedCallback);
|
|
|
158
220
|
</body>
|
|
159
221
|
```
|
|
160
222
|
|
|
161
|
-
|
|
223
|
+
<details><summary>All in Realtime</summary>
|
|
224
|
+
|
|
225
|
+
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:
|
|
226
|
+
+ the `<import>` element points to another module — either by `ref` change or by a change in `importscontext` (below).
|
|
227
|
+
+ the module definition `<template def>` element has had its contents updated, either programmatically or automatically from having loaded.
|
|
228
|
+
|
|
229
|
+
Conversely, an `<import>` element that has been resolved will self-restore in the event that:
|
|
230
|
+
+ the `<import>` element no longer points to a module; or the module has been emptied or removed.
|
|
231
|
+
+ the previously slotted contents have been programmatically removed and slot is empty.
|
|
232
|
+
|
|
233
|
+
</details>
|
|
234
|
+
|
|
235
|
+
<details><summary>With SSR Support</summary>
|
|
236
|
+
|
|
237
|
+
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.
|
|
238
|
+
|
|
239
|
+
The above resolved imports would thus give us something like:
|
|
240
|
+
|
|
241
|
+
```html
|
|
242
|
+
<body>
|
|
243
|
+
<div def="fragment1"></div>
|
|
244
|
+
<!--<import ref="/foo#fragment1"></import>-->
|
|
245
|
+
<div def="fragment2"></div>
|
|
246
|
+
<!--<import ref="/foo/nested#fragment2"></import>-->
|
|
247
|
+
</body>
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
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:
|
|
251
|
+
|
|
252
|
+
```html
|
|
253
|
+
<!--<import ref="/foo/nested#fragment2" nodecount="1"></import>-->
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
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!
|
|
257
|
+
|
|
258
|
+
> 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)
|
|
259
|
+
|
|
260
|
+
</details>
|
|
261
|
+
|
|
262
|
+
### Programmatic Module Imports
|
|
263
|
+
|
|
264
|
+
└ The *HTMLImports* API for programmatic module import:
|
|
265
|
+
|
|
266
|
+
```js
|
|
267
|
+
const moduleObject1 = document.import('/foo#fragment1');
|
|
268
|
+
console.log(moduleObject1.value); // divElement
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
```js
|
|
272
|
+
const moduleObject2 = document.import('/foo/nested#fragment2');
|
|
273
|
+
console.log(moduleObject2.value); // divElement
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
*with an observable `moduleObject.value` property for working asynchronously; e.g. awaiting and handling remote modules*:
|
|
277
|
+
|
|
278
|
+
```js
|
|
279
|
+
Observer.observe(moduleObject2, 'value', e => {
|
|
280
|
+
console.log(e.value); // divElement
|
|
281
|
+
});
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
*with an equivalent `callback` option on the `import()` method itself*:
|
|
285
|
+
|
|
286
|
+
```js
|
|
287
|
+
document.import('/foo#fragment1', divElement => {
|
|
288
|
+
console.log(divElement);
|
|
289
|
+
});
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
└ An optional `live` parameter for staying subscribed to any mutations made to source module elements:
|
|
293
|
+
|
|
294
|
+
```js
|
|
295
|
+
const moduleObject2 = document.import('/foo/nested#fragment2', true/*live*/);
|
|
296
|
+
console.log(moduleObject2.value);
|
|
297
|
+
Observer.observe(moduleObject2, 'value', e => {
|
|
298
|
+
console.log(e.value);
|
|
299
|
+
});
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
```js
|
|
303
|
+
document.import('/foo#fragment1', true/*live*/, divElement => {
|
|
304
|
+
console.log(divElement); // To be received after remote module has been loaded
|
|
305
|
+
});
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
*both of which would get notified on doing the below*:
|
|
309
|
+
|
|
310
|
+
```js
|
|
311
|
+
document.querySelector('template[def="foo"]').content.firstElementChild.remove();
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
└ An optional `AbortSignal` parameter for aborting module mutation events:
|
|
315
|
+
|
|
316
|
+
```js
|
|
317
|
+
const abortController = new AbortController;
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
```js
|
|
321
|
+
const moduleObject2 = document.import('/foo/nested#fragment2', { live: true, signal: abortController.signal });
|
|
322
|
+
```
|
|
162
323
|
|
|
163
324
|
```js
|
|
164
|
-
|
|
165
|
-
console.log(
|
|
325
|
+
document.import('/foo#fragment1', { live: true, signal: abortController.signal }, divElement => {
|
|
326
|
+
console.log(divElement); // To be received after remote module has been loaded
|
|
327
|
+
});
|
|
166
328
|
```
|
|
167
329
|
|
|
168
330
|
```js
|
|
169
|
-
|
|
170
|
-
console.log(import2); // { value: div }
|
|
331
|
+
setTimeout(() => abortController.abort(), 1000);
|
|
171
332
|
```
|
|
172
333
|
|
|
173
|
-
|
|
174
|
-
|
|
334
|
+
<details><summary>Extended Imports concepts</summary>
|
|
335
|
+
|
|
336
|
+
### Lazy-Loading Modules
|
|
337
|
+
|
|
338
|
+
└ Remote modules with lazy-loading - which has modules loading on first time access:
|
|
339
|
+
|
|
340
|
+
```html
|
|
341
|
+
<!-- Loading doesn't happen until the first time this is being accessed -->
|
|
342
|
+
<template def="foo" src="/foo.html" loading="lazy"></template>
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
```html
|
|
346
|
+
<body>
|
|
347
|
+
<import ref="/foo#fragment1"></import> <!-- Triggers module loading and resolves on load success -->
|
|
348
|
+
</body>
|
|
349
|
+
```
|
|
350
|
+
```js
|
|
351
|
+
const moduleObject2 = document.import('/foo#fragment1'); // Triggers module loading and resolves at moduleObject2.value on load success
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Scoped Modules
|
|
355
|
+
|
|
356
|
+
└ The `scoped` attribute for an *object-scoped* module system:
|
|
175
357
|
|
|
176
358
|
```html
|
|
177
359
|
<section> <!-- object with own modules -->
|
|
@@ -188,6 +370,8 @@ console.log(import2); // { value: div }
|
|
|
188
370
|
</section>
|
|
189
371
|
```
|
|
190
372
|
|
|
373
|
+
*with an equivalent `Element.prototype.import()` API for accessing said scoped modules*:
|
|
374
|
+
|
|
191
375
|
```js
|
|
192
376
|
// Using the HTMLImports API
|
|
193
377
|
const moduleHost = document.querySelector('div');
|
|
@@ -202,11 +386,9 @@ const globalImport1 = moduleHost.import('/foo#fragment1'); // the global module:
|
|
|
202
386
|
console.log(globalImport1); // { value: div }
|
|
203
387
|
```
|
|
204
388
|
|
|
205
|
-
|
|
206
|
-
Extended Imports concepts
|
|
207
|
-
</summary>
|
|
389
|
+
### Module Inheritance
|
|
208
390
|
|
|
209
|
-
└
|
|
391
|
+
└ Module nesting with inheritance:
|
|
210
392
|
|
|
211
393
|
```html
|
|
212
394
|
<template def="foo">
|
|
@@ -250,34 +432,9 @@ Extended Imports concepts
|
|
|
250
432
|
</body>
|
|
251
433
|
```
|
|
252
434
|
|
|
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
|
-
```
|
|
435
|
+
### Imports Contexts
|
|
271
436
|
|
|
272
|
-
|
|
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
|
-
|
|
280
|
-
└ *"Imports Contexts" for context-based imports resolution*:
|
|
437
|
+
└ "Imports Contexts" for context-based *import resolution*:
|
|
281
438
|
|
|
282
439
|
```html
|
|
283
440
|
<body importscontext="/foo">
|
|
@@ -302,7 +459,7 @@ const globalImport2 = moduleHost.import('#fragment2'); // module:/foo/nested#fra
|
|
|
302
459
|
console.log(globalImport2); // { value: div }
|
|
303
460
|
```
|
|
304
461
|
|
|
305
|
-
└
|
|
462
|
+
└ "Imports Contexts" with named contexts:
|
|
306
463
|
|
|
307
464
|
```html
|
|
308
465
|
<body contextname="context1" importscontext="/foo/nested">
|
|
@@ -326,7 +483,7 @@ const globalImport2 = moduleHost.import('@context1#fragment2'); // module:/foo/n
|
|
|
326
483
|
console.log(globalImport2); // { value: div }
|
|
327
484
|
```
|
|
328
485
|
|
|
329
|
-
└
|
|
486
|
+
└ "Imports Contexts" with context inheritance:
|
|
330
487
|
|
|
331
488
|
```html
|
|
332
489
|
<body importscontext="/foo">
|
|
@@ -340,7 +497,9 @@ console.log(globalImport2); // { value: div }
|
|
|
340
497
|
</body>
|
|
341
498
|
```
|
|
342
499
|
|
|
343
|
-
|
|
500
|
+
### Scoped Modules and Imports Contexts
|
|
501
|
+
|
|
502
|
+
└ Object-scoped module system with context inheritance:
|
|
344
503
|
|
|
345
504
|
```html
|
|
346
505
|
<body contextname="context1" importscontext="/bar">
|
|
@@ -370,10 +529,148 @@ console.log(localOrGlobalImport2); // { value: div }
|
|
|
370
529
|
|
|
371
530
|
</details>
|
|
372
531
|
|
|
373
|
-
└ [HTML Imports
|
|
532
|
+
└ [HTML Imports examples](#html-imports-examples)
|
|
533
|
+
|
|
534
|
+
## Data Binding
|
|
535
|
+
|
|
536
|
+
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.
|
|
537
|
+
|
|
538
|
+
OOHTML makes this possible in just simple conventions - via a new comment-based data-binding syntax `<?{ }?>` and a complementary new `binding` attribute!
|
|
539
|
+
|
|
540
|
+
### Comment-Based Data-Binding
|
|
541
|
+
|
|
542
|
+
└ A web-native, comment-based data-binding syntax `<?{ }?>` which works like regular comment but stay "data-charged":
|
|
543
|
+
|
|
544
|
+
```js
|
|
545
|
+
<html>
|
|
546
|
+
<head>
|
|
547
|
+
<title><?{ app.title }?></title>
|
|
548
|
+
</head>
|
|
549
|
+
<body>
|
|
550
|
+
Hi, I'm <?{ app.name ?? 'Default name' }?>!
|
|
551
|
+
and here's another way to write the same comment: <!--?{ app.cool }?-->
|
|
552
|
+
</body>
|
|
553
|
+
</html>
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
<details><summary>With SSR Support</summary>
|
|
557
|
+
|
|
558
|
+
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.
|
|
559
|
+
|
|
560
|
+
The following: `<?{ 'Hello World' }?>` would thus give us: `<?{ 'Hello World' }?>Hello World`.
|
|
561
|
+
|
|
562
|
+
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:
|
|
563
|
+
|
|
564
|
+
```html
|
|
565
|
+
<?{ 'Hello World'; [=11] }?>Hello World
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
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!
|
|
569
|
+
|
|
570
|
+
> 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)
|
|
571
|
+
|
|
572
|
+
</details>
|
|
573
|
+
|
|
574
|
+
### Directives-Based Data-Binding
|
|
575
|
+
|
|
576
|
+
└ The `binding` attribute for a declarative and neat, key/value data-binding syntax:
|
|
577
|
+
|
|
578
|
+
```html
|
|
579
|
+
<div binding="<type><parameter>: <argument>;"></div>
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
*where*:
|
|
374
583
|
|
|
375
|
-
|
|
584
|
+
+ *`<type>` is the binding type, which is always a symbol*
|
|
585
|
+
+ *`<directive>` is the binding directive, which could be any of CSS property, class name, attribute name, Structural Directive*
|
|
586
|
+
+ *`<argument>` is the bound value or expression*
|
|
376
587
|
|
|
588
|
+
*which would give us the following for a CSS property*:
|
|
589
|
+
|
|
590
|
+
```html
|
|
591
|
+
<div binding="&color: someColor; &backgroundColor: 'red'"></div>
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
*with enough liberty to separate the binding type from the directive itself*:
|
|
595
|
+
|
|
596
|
+
```html
|
|
597
|
+
<div binding="& color: someColor; & backgroundColor: 'red'"></div>
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
*all of which can be seen here*:
|
|
601
|
+
|
|
602
|
+
| Symbol | Meaning | Usage |
|
|
603
|
+
| :---- | :---- | :---- |
|
|
604
|
+
| `&` | CSS Property | `<div binding="&color: someColor;"></div>` |
|
|
605
|
+
| `%` | Class Name | `<div binding="%active: app.isActive;"></div>` |
|
|
606
|
+
| `~` | Attribute Name | `<a binding="~href: person.profileUrl + '#bio';"></a>` |
|
|
607
|
+
| `@` | Structural Directive: | *See next table* |
|
|
608
|
+
|
|
609
|
+
<details><summary>Structural Directives</summary>
|
|
610
|
+
|
|
611
|
+
| Directive | Meaning | Usage |
|
|
612
|
+
| :---- | :---- | :---- |
|
|
613
|
+
| `@text` | For rendering plain text content | `<span binding="@text: firstName + ' ' + lastName;"></span>` |
|
|
614
|
+
| `@html` | For rendering markup content | `<span binding="@html: '<i>' + firstName + '</i>';"></span>` |
|
|
615
|
+
| `@items` | For rendering a list, with argument in the following format:<br>`<declaration> <of\|in> <iterable> / <importRef>` | *See next two tables* |
|
|
616
|
+
|
|
617
|
+
<details><summary>"For ... Of" Loops</summary>
|
|
618
|
+
|
|
619
|
+
| Idea | Usage |
|
|
620
|
+
| :---- | :---- |
|
|
621
|
+
| Loop over an array/iterable | `<ul binding="@items: value of [1,2,3] / 'foo#fragment';"></ul>` |
|
|
622
|
+
| Same as above but with a `key` declaration | `<ul binding="@items: (value, key) of [1,2,3] / 'foo#fragment';"></ul>` |
|
|
623
|
+
| Same as above but with different variable names | `<ul binding="@items: (product, id) of store.products / 'foo#fragment';"></ul>` |
|
|
624
|
+
| Same as above but with a dynamic `importRef` | `<ul binding="@items: (product, id) of store.products / store.importRef;"></ul>` |
|
|
625
|
+
|
|
626
|
+
</details>
|
|
627
|
+
|
|
628
|
+
<details><summary>"For ... In" Loops</summary>
|
|
629
|
+
|
|
630
|
+
| Idea | Usage |
|
|
631
|
+
| :---- | :---- |
|
|
632
|
+
| Loop over an object | `<ul binding="@items: key in { a: 1, b: 2 } / 'foo#fragment';"></ul>` |
|
|
633
|
+
| Same as above but with a `value` and `index` declaration | `<ul binding="@items: (key, value, index) in { a: 1, b: 2 } / 'foo#fragment';"></ul>` |
|
|
634
|
+
|
|
635
|
+
</details>
|
|
636
|
+
|
|
637
|
+
</details>
|
|
638
|
+
|
|
639
|
+
<details><summary>Example</summary>
|
|
640
|
+
|
|
641
|
+
```html
|
|
642
|
+
<section>
|
|
643
|
+
|
|
644
|
+
<!-- The "items" template -->
|
|
645
|
+
<template def="item" scoped>
|
|
646
|
+
<li binding="@text: key + ': ' + name;"></li>
|
|
647
|
+
</template>
|
|
648
|
+
|
|
649
|
+
<!-- The loop -->
|
|
650
|
+
<ul binding="@items: (name, key) of ['dog','cat','ram'] / 'item';"></ul>
|
|
651
|
+
|
|
652
|
+
</section>
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
</details>
|
|
656
|
+
|
|
657
|
+
<details><summary>All in Realtime</summary>
|
|
658
|
+
|
|
659
|
+
Lists are rendered in realtime, which means that in-place mutations - additions and removals - on the *iteratee* will be automatically reflected on the UI!
|
|
660
|
+
|
|
661
|
+
</details>
|
|
662
|
+
|
|
663
|
+
<details><summary>With SSR Support</summary>
|
|
664
|
+
|
|
665
|
+
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.
|
|
666
|
+
|
|
667
|
+
</details>
|
|
668
|
+
|
|
669
|
+
## Component Plumbing
|
|
670
|
+
|
|
671
|
+
*[TODO]: The Context API and Bindings API*
|
|
672
|
+
|
|
673
|
+
<!--
|
|
377
674
|
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
675
|
|
|
379
676
|
└ *The [Observer API](https://github.com/webqit/observer) for general-purpose object observability*:
|
|
@@ -397,27 +694,6 @@ Observer.set(obj, 'prop1', 'value1'); // Reported synchronously
|
|
|
397
694
|
Observer.deleteProperty(obj, 'prop1'); // Reported synchronously
|
|
398
695
|
```
|
|
399
696
|
|
|
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
697
|
└ *A Bindings API for binding application-level state to an object*:
|
|
422
698
|
|
|
423
699
|
```js
|
|
@@ -485,6 +761,8 @@ Observer.set(element, 'liveProperty'); // Live expressions rerun
|
|
|
485
761
|
|
|
486
762
|
└ [Reactive HTML concepts](#)
|
|
487
763
|
|
|
764
|
+
-->
|
|
765
|
+
|
|
488
766
|
## Polyfill
|
|
489
767
|
|
|
490
768
|
OOHTML is being developed as something to be used today - via a polyfill.
|