@webqit/oohtml 3.0.1-9 → 3.1.2

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.
Files changed (50) hide show
  1. package/README.md +532 -229
  2. package/dist/bindings-api.js +1 -1
  3. package/dist/bindings-api.js.map +3 -3
  4. package/dist/context-api.js +1 -1
  5. package/dist/context-api.js.map +3 -3
  6. package/dist/data-binding.js +16 -36
  7. package/dist/data-binding.js.map +3 -3
  8. package/dist/html-imports.js +1 -1
  9. package/dist/html-imports.js.map +3 -3
  10. package/dist/main.js +31 -37
  11. package/dist/main.js.map +3 -3
  12. package/dist/main.lite.js +56 -0
  13. package/dist/main.lite.js.map +7 -0
  14. package/dist/namespaced-html.js +1 -1
  15. package/dist/namespaced-html.js.map +3 -3
  16. package/dist/scoped-css.js +3 -3
  17. package/dist/scoped-css.js.map +2 -2
  18. package/dist/scoped-js.js +1 -21
  19. package/dist/scoped-js.js.map +3 -3
  20. package/package.json +3 -5
  21. package/src/api.global.js +11 -0
  22. package/src/api.global.lite.js +11 -0
  23. package/src/bindings-api/DOMBindingsContext.js +51 -0
  24. package/src/bindings-api/index.js +30 -42
  25. package/src/context-api/DOMContext.js +128 -0
  26. package/src/context-api/DOMContextResponse.js +15 -0
  27. package/src/context-api/DOMContexts.js +89 -0
  28. package/src/context-api/DuplicateContextError.js +2 -0
  29. package/src/context-api/_DOMContextRequestEvent.js +49 -0
  30. package/src/context-api/index.js +20 -22
  31. package/src/data-binding/index.js +20 -26
  32. package/src/html-imports/{_HTMLImportsProvider.js → HTMLImportsContext.js} +42 -33
  33. package/src/html-imports/{_HTMLExportsManager.js → HTMLModule.js} +40 -38
  34. package/src/html-imports/_HTMLImportElement.js +17 -17
  35. package/src/html-imports/index.js +46 -49
  36. package/src/index.js +2 -7
  37. package/src/namespaced-html/index.js +3 -7
  38. package/src/scoped-css/index.js +1 -1
  39. package/src/scoped-js/index.js +2 -13
  40. package/src/util.js +8 -5
  41. package/test/imports.test.js +1 -3
  42. package/test/index.js +1 -1
  43. package/test/modules.test.js +35 -38
  44. package/src/bindings-api/_HTMLBindingsProvider.js +0 -51
  45. package/src/context-api/ContextReturnValue.js +0 -22
  46. package/src/context-api/HTMLContext.js +0 -85
  47. package/src/context-api/HTMLContextProvider.js +0 -175
  48. package/src/context-api/_ContextRequestEvent.js +0 -26
  49. package/src/html-imports/_HTMLImportElement copy.js +0 -217
  50. package/src/targets.browser.js +0 -10
package/README.md CHANGED
@@ -25,7 +25,9 @@ Vanilla HTML is unsurprisingly becoming a compelling option for an increasing nu
25
25
 
26
26
  This project pursues an object-oriented approach to HTML and implicitly revisits much of what inhibits the idea of a *component* architecture for HTML!
27
27
 
28
+ <!--
28
29
  └ [See more in the introductory blog post](https://dev.to/oxharris/the-web-native-equations-1m1p-temp-slug-6661657?preview=ba70ad2c17f05b5761bc74516dbde8c9eff8b581a0420d87334fd9ef6bab9d6e6d3ab6aaf3fe02542bb9e7250d0a88a6df91dae40919aabcc9a07320)<sup>draft</sup>
30
+ -->
29
31
 
30
32
  </details>
31
33
 
@@ -66,7 +68,7 @@ user
66
68
  └── email
67
69
  ```
68
70
 
69
- **-->** *with a corresponding API that exposes said structure to JavaScript applications*:
71
+ **-->** *with a complementary API that exposes said structure to JavaScript applications*:
70
72
 
71
73
  ```js
72
74
  // The document.namespace API
@@ -142,7 +144,7 @@ Here, we get the `scoped` attribute for *scoping* said element-specific styleshe
142
144
  </div>
143
145
  ```
144
146
 
145
- **-->** *with a corresponding API that exposes said assets to JavaScript applications*:
147
+ **-->** *with a complementary API that exposes said assets to JavaScript applications*:
146
148
 
147
149
  ```js
148
150
  let { styleSheets, scripts } = user; // APIs that are analogous to the document.styleSheets, document.scripts properties
@@ -201,7 +203,7 @@ Here, we get the `def` attribute for defining those - either as whole *module* o
201
203
 
202
204
  We shouldn't need a different mechanism to work with remote content.
203
205
 
204
- Here, we get the `<template src>` element for that:
206
+ Here, we get remote-loading modules using the `src` attribute:
205
207
 
206
208
  ```html
207
209
  <template def="foo" src="/foo.html"></template>
@@ -218,10 +220,9 @@ Here, we get the `<template src>` element for that:
218
220
  ```html
219
221
  -- file: /nested.html --
220
222
  <div def="fragment2"></div>
221
- --
222
223
  ```
223
224
 
224
- **-->** *which extends how elements like images already work; terminating with either a `load` or an `error` event*:
225
+ **-->** *which borrow from how `src` works in elements like `<img>`; terminating with either a `load` or an `error` event*:
225
226
 
226
227
  ```js
227
228
  foo.addEventListener('load', loadCallback);
@@ -230,9 +231,9 @@ foo.addEventListener('error', errorCallback);
230
231
 
231
232
  ### Declarative Module Imports
232
233
 
233
- The essence of a module is for reuse.
234
+ HTML snippets should be reusable in "HTML"!
234
235
 
235
- Here, we get an `<import>` element that lets us do that declaratively:
236
+ Here, we get an `<import>` element that lets us do just that:
236
237
 
237
238
  ```html
238
239
  <body>
@@ -250,19 +251,21 @@ Here, we get an `<import>` element that lets us do that declaratively:
250
251
 
251
252
  <details><summary>All in realtime</summary>
252
253
 
253
- As a realtime module system, `<import> `elements maintain a live relationship with given module definition elements (`<template def>`) and are resolved again in the event that:
254
- + the `<import>` element points to another module — either by `ref` change or by a change in `importscontext` (below).
255
- + the module definition element (`<template def>`) has had its contents updated, either programmatically or automatically from having loaded.
254
+ Import *refs* are live bindings that are highly sensitive to:
255
+
256
+ + changes in the *ref* itself (in being defined/undefined/redefined)
257
+ + changes in the referenced *defs* themselves (in being added/removed/loaded)
258
+
259
+ And an `<import>` element that has been resolved will self-restore in the event that:
256
260
 
257
- Conversely, an `<import>` element that has been resolved will self-restore in the event that:
258
- + the `<import>` element no longer points to a module; or the module has been emptied or removed.
259
- + the previously slotted contents have been programmatically removed and slot is empty.
261
+ + the above changes resolve to no imports.
262
+ + the previously slotted contents have *all* been programmatically removed and slot is empty.
260
263
 
261
264
  </details>
262
265
 
263
- <details><summary>With SSR Support</summary>
266
+ <details><summary>With SSR support</summary>
264
267
 
265
- 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.
268
+ On the server, these `<import>` elements will retain their place in the DOM, but this time, serialized into comment nodes, while having their output rendered just above them as siblings.
266
269
 
267
270
  The above resolved imports would thus give us something like:
268
271
 
@@ -275,23 +278,23 @@ The above resolved imports would thus give us something like:
275
278
  </body>
276
279
  ```
277
280
 
278
- But they also would need 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:
281
+ But they also will need to remember the exact imported nodes that they manage so as to be able to re-establish relevant relationships on getting to the client. This information is automatically encoded as part of the serialised element itself, in something like:
279
282
 
280
283
  ```html
281
284
  <!--&lt;import ref="/foo/nested#fragment2" nodecount="1"&gt;&lt;/import&gt;-->
282
285
  ```
283
286
 
284
- 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!
287
+ Now, on getting to the client and getting "hydrated" back into an `<import>` element, that extra bit of information is decoded, and original relationships are formed again. But, the `<import>` element itself stays invisible in the DOM while still continuing to kick as above!
285
288
 
286
289
  > 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)
287
290
 
288
291
  </details>
289
292
 
290
- ### Programmatic Module Imports
293
+ ### Imperative Module Imports
291
294
 
292
295
  JavaScript applications will need more than a declarative import mechanism.
293
296
 
294
- Here, we get an *HTMLImports* API for programmatic module import:
297
+ Here, we get an *HTMLImports* API for imperative module import:
295
298
 
296
299
  ```js
297
300
  const moduleObject1 = document.import('/foo#fragment1');
@@ -303,7 +306,7 @@ const moduleObject2 = document.import('/foo/nested#fragment2');
303
306
  console.log(moduleObject2.value); // divElement
304
307
  ```
305
308
 
306
- **-->** *with an observable `moduleObject.value` property for working asynchronously; e.g. awaiting and handling remote modules*:
309
+ **-->** *with the `moduleObject.value` property being a live property for when results are delivered asynchronous; e.g. with an asynchronously loaded module*:
307
310
 
308
311
  ```js
309
312
  Observer.observe(moduleObject2, 'value', e => {
@@ -319,7 +322,7 @@ document.import('/foo#fragment1', divElement => {
319
322
  });
320
323
  ```
321
324
 
322
- **-->** *with an optional `live` parameter for staying subscribed to any mutations made to source module elements*:
325
+ **-->** *with an optional `live` parameter for staying subscribed to updated results*:
323
326
 
324
327
  ```js
325
328
  const moduleObject2 = document.import('/foo/nested#fragment2', true/*live*/);
@@ -335,38 +338,25 @@ document.import('/foo#fragment1', true/*live*/, divElement => {
335
338
  });
336
339
  ```
337
340
 
338
- *...both of which would get notified on doing the below*:
341
+ *...both of which get notified on doing the below*:
339
342
 
340
343
  ```js
341
344
  document.querySelector('template[def="foo"]').content.firstElementChild.remove();
342
345
  ```
343
346
 
344
- **-->** *with an optional `AbortSignal` parameter for aborting module mutation events*:
347
+ **-->** *with a `moduleObject.abort()` method for unsubscribing from live updates*:
345
348
 
346
- ```js
347
- const abortController = new AbortController;
348
- ```
349
+ **-->** *with an optional `signal` parameter for passing in a custom `AbortSignal` instance*:
349
350
 
350
351
  ```js
352
+ const abortController = new AbortController;
351
353
  const moduleObject2 = document.import('/foo/nested#fragment2', { live: true, signal: abortController.signal });
352
354
  ```
353
355
 
354
- *...in the absence of which an `AbortSignal` instance is automatically created internally for the same purpose, such that we're always able to do*:
355
-
356
- ```js
357
- moduleObject2.abort();
358
- ```
359
-
360
- *...whereas in the `callback` approach, no automatic `AbortSignals` are created*:
361
-
362
356
  ```js
363
- document.import('/foo#fragment1', { live: true, signal: abortController.signal }, divElement => {
364
- console.log(divElement); // To be received after remote module has been loaded
365
- });
366
- ```
367
-
368
- ```js
369
- setTimeout(() => abortController.abort(), 1000);
357
+ setTimeout(() => {
358
+ abortController.abort(); // which would also call moduleObject2.abort()
359
+ }, 1000);
370
360
  ```
371
361
 
372
362
  <details><summary>Extended Imports concepts</summary>
@@ -375,7 +365,7 @@ setTimeout(() => abortController.abort(), 1000);
375
365
 
376
366
  We can defer module loading until we really need them.
377
367
 
378
- Here, we get the `loading="lazy"` directive for that; and loading is only then triggered on the first attempt to import their contents:
368
+ Here, we get the `loading="lazy"` directive for that; and loading is only then triggered on the first attempt to import them or their contents:
379
369
 
380
370
  ```html
381
371
  <!-- Loading doesn't happen until the first time this is being accessed -->
@@ -391,43 +381,6 @@ Here, we get the `loading="lazy"` directive for that; and loading is only then t
391
381
  const moduleObject2 = document.import('/foo#fragment1'); // Triggers module loading and resolves at moduleObject2.value on load success
392
382
  ```
393
383
 
394
- ### Scoped Modules
395
-
396
- Some modules will only be relevant within a specific context in the page, and these shouldn't map to the global document scope.
397
-
398
- Here, we get the `scoped` attribute for scoping those to their respective contexts, and thus, implicitly have an *object-scoped* module system:
399
-
400
- ```html
401
- <section> <!-- object with own modules -->
402
-
403
- <template def="foo" scoped> <!-- Scoped to host object and not available globally -->
404
- <div def="fragment1"></div>
405
- </template>
406
-
407
- <div>
408
- <import ref="foo#fragment1"></import> <!-- Relative path (beginning without a slash), resolves to the local module: foo#fragment1 -->
409
- <import ref="/foo#fragment1"></import> <!-- Absolute path, resolves to the global module: /foo#fragment1 -->
410
- </div>
411
-
412
- </section>
413
- ```
414
-
415
- **-->** *with an equivalent `Element.prototype.import()` API for accessing said scoped modules*:
416
-
417
- ```js
418
- // Using the HTMLImports API
419
- const moduleHost = document.querySelector('div');
420
- const localImport1 = moduleHost.import('foo#fragment1'); // the local module: foo#fragment1
421
- console.log(localImport1); // { value: div }
422
- ```
423
-
424
- ```js
425
- // Using the HTMLImports API
426
- const moduleHost = document.querySelector('div');
427
- const globalImport1 = moduleHost.import('/foo#fragment1'); // the global module: foo#fragment1
428
- console.log(globalImport1); // { value: div }
429
- ```
430
-
431
384
  ### Module Inheritance
432
385
 
433
386
  We'll often have repeating markup structures.
@@ -478,80 +431,142 @@ Here, we get module nesting with inheritance to simplify that:
478
431
 
479
432
  ### Imports Contexts
480
433
 
481
- We should be able to define a base path at arbitrary levels in the page against which to resolve import *refs* in subtree.
434
+ We should be able to have *relative* import refs that resolve against local contexts in the document tree.
435
+
436
+ Here, we call those arbitrary contexts "Imports Contexts", and these could be:
437
+
438
+ + Simple Base Path Contexts ([below](#base-path-contexts))
439
+ + Scoped Module Contexts ([below](#scoped-module-contexts))
440
+ + Named Contexts ([below](#named-contexts))
441
+ + Extended Scoped Module Contexts ([below](#extended-scoped-module-contexts))
442
+
443
+ Alongside which we have a complementary `Element.prototype.import()` API for thinking in contexts.
444
+
445
+ #### "Base Path" Contexts
482
446
 
483
- Here, we get "Imports Contexts" for that:
447
+ Base paths may be defined at arbitrary levels in the page using the `importscontext` attribute:
484
448
 
485
449
  ```html
486
450
  <body importscontext="/foo">
487
451
  <section>
488
- <import ref="#fragment1"></import> <!-- Relative path (beginning without a slash), resolves to: /foo#fragment1 -->
452
+ <import ref="#fragment1"></import> <!-- Relative path (beginning without a slash), resolving to: /foo#fragment1 -->
489
453
  </section>
490
454
  </body>
491
455
  ```
492
456
 
493
457
  ```html
494
458
  <body importscontext="/foo/nested">
459
+ <main>
460
+ <import ref="#fragment2"></import> <!-- Relative path (beginning without a slash), resolving to: /foo/nested#fragment2 -->
461
+ </main>
462
+ </body>
463
+ ```
464
+
465
+ **-->** *with said base paths being able to "nest" nicely*:
466
+
467
+ ```html
468
+ <body importscontext="/foo">
469
+
495
470
  <section>
496
- <import ref="#fragment2"></import> <!-- Relative path (beginning without a slash), resolves to: /foo/nested#fragment2 -->
471
+ <import ref="#fragment1"></import> <!-- Relative path (beginning without a slash), resolves to: /foo#fragment1 -->
497
472
  </section>
473
+
474
+ <div importscontext="nested"> <!-- Relative path (beginning without a slash), resolves to: /foo/nested -->
475
+
476
+ <main>
477
+ <import ref="#fragment2"></import> <!-- Relative path (beginning without a slash), resolves to: /foo/nested#fragment2 -->
478
+ </main>
479
+
480
+ </div>
481
+
498
482
  </body>
499
483
  ```
500
484
 
485
+ **-->** *with the `Element.prototype.import()` API for equivalent context-based imports*:
486
+
501
487
  ```js
502
- // Using the HTMLImports API
503
- const moduleHost = document.querySelector('section');
504
- const globalImport2 = moduleHost.import('#fragment2'); // module:/foo/nested#fragment2
505
- console.log(globalImport2); // { value: div }
488
+ // Using the HTMLImports API to import from context
489
+ const contextElement = document.querySelector('section');
490
+ const response = contextElement.import('#fragment1'); // Relative path (beginning without a slash), resolving to: /foo#fragment1
491
+ ```
492
+
493
+ ```js
494
+ // Using the HTMLImports API to import from context
495
+ const contextElement = document.querySelector('main');
496
+ const response = contextElement.import('#fragment1'); // Relative path (beginning without a slash), resolving to: /foo/nested#fragment1
506
497
  ```
507
498
 
508
- **-->** *with said "Imports Contexts" optionally having a name*:
499
+ #### "Scoped Module" Contexts
500
+
501
+ Some modules will only be relevant within a specific context in the page, and those wouldn't need to have a business with the global scope.
502
+
503
+ Here, we get the `scoped` attribute for scoping those to their respective contexts, to give us an *object-scoped* module system (like what Scoped Registries seek to be to Custom Elements):
509
504
 
510
505
  ```html
511
- <body contextname="context1" importscontext="/foo/nested">
506
+ <section> <!-- Host object -->
512
507
 
513
- <import ref="#fragment2"></import> <!-- Relative path (beginning without a slash), resolves to: /foo/nested#fragment2 -->
508
+ <template def="foo" scoped> <!-- Scoped to host object and not available globally -->
509
+ <div def="fragment1"></div>
510
+ </template>
514
511
 
515
- <section importscontext="/foo">
516
- <import ref="#fragment1"></import> <!-- Relative path (beginning without a slash), resolves to: /foo#fragment1 -->
517
- <div>
518
- <import ref="@context1#fragment2"></import> <!-- Context-relative path (beginning with a context name), resolves to: /foo/nested#fragment2 -->
519
- </div>
520
- </section>
512
+ <div>
513
+ <import ref="foo#fragment1"></import> <!-- Relative path (beginning without a slash), resolving to the local module: foo#fragment1 -->
514
+ <import ref="/foo#fragment1"></import> <!-- Absolute path, resolving to the global module: /foo#fragment1 -->
515
+ </div>
521
516
 
522
- </body>
517
+ </section>
523
518
  ```
524
519
 
520
+ **-->** *with the `Element.prototype.import()` API for equivalent context-based imports*:
521
+
525
522
  ```js
526
- // Using the HTMLImports API
527
- const moduleHost = document.querySelector('div');
528
- const globalImport2 = moduleHost.import('@context1#fragment2'); // module:/foo/nested#fragment2
529
- console.log(globalImport2); // { value: div }
523
+ // Using the HTMLImports API for local import
524
+ const contextElement = document.querySelector('div');
525
+ const localModule = moduleHost.import('#fragment1'); // Relative path (beginning without a slash), resolving to the local module: foo#fragment1
530
526
  ```
531
527
 
532
- **-->** *with said "Imports Contexts" being able to "nest" nicely*:
528
+ ```js
529
+ // Using the HTMLImports API for global import
530
+ const contextElement = document.querySelector('div');
531
+ const globalModule = contextElement.import('/foo#fragment1'); // Absolute path, resolving to the global module: /foo#fragment1
532
+ ```
533
+
534
+ #### Named Contexts
535
+
536
+ Imports Contexts may be named:
533
537
 
534
538
  ```html
535
- <body importscontext="/foo">
539
+ <body contextname="context1" importscontext="/foo/nested">
536
540
 
537
- <import ref="#fragment1"></import> <!-- Relative path (beginning without a slash), resolves to: /foo#fragment1 -->
541
+ <import ref="#fragment2"></import> <!-- Relative path (beginning without a slash), resolves to: /foo/nested#fragment2 -->
542
+
543
+ <section importscontext="/foo">
544
+ <import ref="#fragment1"></import> <!-- Relative path (beginning without a slash), resolves to: /foo#fragment1 -->
545
+
546
+ <div>
547
+ <import ref="@context1#fragment2"></import> <!-- Context-relative path (beginning with a context name), resolves to: /foo/nested#fragment2 -->
548
+ </div>
538
549
 
539
- <section importscontext="nested"> <!-- Relative path (beginning without a slash), resolves to: /foo/nested -->
540
- <import ref="#fragment2"></import> <!-- Relative path (beginning without a slash), resolves to: /foo/nested#fragment2 -->
541
550
  </section>
542
551
 
543
552
  </body>
544
553
  ```
545
554
 
546
- ### Scoped Modules and Imports Contexts
555
+ **-->** *with the `Element.prototype.import()` API for equivalent context-based imports*:
547
556
 
548
- Scoped modules and Import Contexts shouldn't be mutually exclusive.
557
+ ```js
558
+ // Using the HTMLImports API to import from a named Imports Context
559
+ const contextElement = document.querySelector('div');
560
+ const result = contextElement.import('@context1#fragment2'); // Resolving to the module:/foo/nested#fragment2
561
+ ```
562
+
563
+ #### Extended Scoped Module Contexts
549
564
 
550
- Here, we're able to have *one* element implement *both* at the same time - with scoped modules inheriting Import Contexts:
565
+ Scoped Module Contexts may also have a Base Path Context that they inherit from:
551
566
 
552
567
  ```html
553
568
  <body contextname="context1" importscontext="/bar">
554
- <section importscontext="nested"> <!-- object with own modules, plus inherited context: /bar/nested -->
569
+ <section importscontext="nested"> <!-- object with Scoped Modules, plus inherited context: /bar/nested -->
555
570
 
556
571
  <template def="foo" scoped> <!-- Scoped to host object and not available globally -->
557
572
  <div def="fragment1"></div>
@@ -559,33 +574,36 @@ Here, we're able to have *one* element implement *both* at the same time - with
559
574
  </template>
560
575
 
561
576
  <div>
562
- <import ref="foo#fragment2"></import> <!-- Relative path (beginning without a slash), resolves to the local module: foo#fragment2, and if not found, resolves from context to the module: /bar/nested/foo#2 -->
563
- <import ref="/foo#fragment1"></import> <!-- Absolute path, resolves to the global module: /foo#fragment1 -->
564
- <import ref="@context1#fragment1"></import> <!-- Resolves to the global module: /bar#fragment1 -->
577
+ <import ref="foo#fragment2"></import> <!-- Relative path (beginning without a slash), resolving to the local module: foo#fragment2, and if not found, the inherited module: /bar/nested/foo#2 -->
578
+ <import ref="/foo#fragment1"></import> <!-- Absolute path, resolving to the global module: /foo#fragment1 -->
579
+ <import ref="@context1#fragment1"></import> <!-- Relative path with a named context, resolving to the global module: /bar#fragment1 -->
565
580
  </div>
566
581
 
567
582
  </section>
568
583
  </body>
569
584
  ```
570
585
 
586
+ **-->** *with the `Element.prototype.import()` API for equivalent context-based imports*:
587
+
571
588
  ```js
572
589
  // Using the HTMLImports API
573
- const moduleHost = document.querySelector('div');
574
- const localOrGlobalImport2 = moduleHost.import('#fragment2'); // the local module: foo#fragment2, and if not found, resolves from context to the module: /bar/nested#fragment2
575
- console.log(localOrGlobalImport2); // { value: div }
590
+ const contextElement = document.querySelector('div');
591
+ const result = contextElement.import('#fragment2'); // the local module: foo#fragment2, and if not found, the inherited module: /bar/nested#fragment2
576
592
  ```
577
593
 
578
594
  </details>
579
595
 
580
596
  ## Data Binding
581
597
 
582
- Data binding is about declaratively binding the UI to application data, ensuring that the relevant parts of the UI are *automatically* updated as application state changes.
598
+ Data binding is about declaratively binding the UI to application data, wherein the relevant parts of the UI *automatically* update as application state changes.
599
+
600
+ OOHTML makes this possible in just simple conventions - via a new comment-based data-binding syntax `<?{ }?>` and a complementary new `expr` attribute!
583
601
 
584
- OOHTML makes this possible in just simple conventions - via a new comment-based data-binding syntax `<?{ }?>` and a complementary new `binding` attribute! And there's one more: Quantum Scripts which brings the most advanced form of reactivity to HTML!
602
+ And there's one more: Quantum Scripts which brings the most advanced form of reactivity to HTML!
585
603
 
586
604
  ### Discrete Data-Binding
587
605
 
588
- Here, we get a comment-based data-binding tag `<?{ }?>` which works like regular comment but stay "data-charged":
606
+ Here, we get a comment-based data-binding tag `<?{ }?>` which gives us a regular HTML comment but also an insertion point for application data:
589
607
 
590
608
  ```js
591
609
  <html>
@@ -599,9 +617,9 @@ Here, we get a comment-based data-binding tag `<?{ }?>` which works like regular
599
617
  </html>
600
618
  ```
601
619
 
602
- <details><summary>Details</summary>
620
+ <details><summary>Resolution details</summary>
603
621
 
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, the above markup could have an underlying data structure like the below:
622
+ Here, JavaScript references are resolved from the closest node up the document tree that exposes a corresponding *binding* on its Bindings API ([discussed below](#bindings-api)). Thus, for the above markup, our underlying data structure could be something like the below:
605
623
 
606
624
  ```js
607
625
  document.bind({ name: 'James Boye', cool: '100%', app: { title: 'Demo App' } });
@@ -615,7 +633,7 @@ document: { name: 'James Boye', cool: '100%', app: { title: 'Demo App' } }
615
633
  └── body: { name: 'John Doe' }
616
634
  ```
617
635
 
618
- So, above, the `name` reference remains bound to the `name` *binding* on the `<body>` element until the meaning of "closest node" changes again:
636
+ Now, the `name` reference remains bound to the `name` *binding* on the `<body>` element until the meaning of "closest node" changes again:
619
637
 
620
638
  ```js
621
639
  delete document.body.bindings.name;
@@ -629,19 +647,19 @@ document.body.bindings.cool = '200%';
629
647
 
630
648
  </details>
631
649
 
632
- <details><summary>With SSR Support</summary>
650
+ <details><summary>With SSR support</summary>
633
651
 
634
- 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.
652
+ On the server, these data-binding tags will retain their place in the DOM while having their output rendered to their right in a text node.
635
653
 
636
- The following: `<?{ 'Hello World' }?>` would thus give us: `<?{ 'Hello World' }?>Hello World`.
654
+ The following expression: `<?{ 'Hello World' }?>` would thus give us: `<?{ 'Hello World' }?>Hello World`.
637
655
 
638
- But they also would need 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:
656
+ But they also will need to remember the exact text node that they manage, so as to be able to re-establish relevant relationships 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 in the following form:
639
657
 
640
658
  ```html
641
659
  <?{ 'Hello World'; [=11] }?>Hello World
642
660
  ```
643
661
 
644
- 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!
662
+ Now, on getting to the client, that extra bit of information gets decoded, and original relationships are forned again. But the binding tag itself graciously disappears from the DOM, while the now "hydrated" text node continues to kick!
645
663
 
646
664
  > 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)
647
665
 
@@ -649,51 +667,51 @@ Now that extra bit of information gets decoded and original relationships are fo
649
667
 
650
668
  ### Inline Data-Binding
651
669
 
652
- Here, we get the `binding` attribute for a declarative and neat, key/value data-binding syntax:
670
+ Here, we get the `expr` attribute for a declarative, neat, key/value data-binding syntax:
653
671
 
654
672
  ```html
655
- <div binding="<directive> <param>: <arg>;"></div>
673
+ <div expr="<directive> <param>: <arg>;"></div>
656
674
  ```
657
675
 
658
676
  **-->** *where*:
659
677
 
660
- + *`<directive>` is the directive, which is always a symbol*
678
+ + *`<directive>` is a directive, which is always a symbol*
661
679
  + *`<param>` is the parameter being bound, which could be a CSS property, class name, attribute name, Structural Directive - depending on the givin directive*
662
680
  + *`<arg>` is the bound value or expression*
663
681
 
664
682
  **-->** *which would give us the following for a CSS property*:
665
683
 
666
684
  ```html
667
- <div binding="& color:someColor; & backgroundColor:'red'"></div>
685
+ <div expr="& color:someColor; & backgroundColor:'red'"></div>
668
686
  ```
669
687
 
670
688
  **-->** *without being space-sensitive*:
671
689
 
672
690
  ```html
673
- <div binding="& color:someColor; &backgroundColor: 'red'"></div>
691
+ <div expr="& color:someColor; &backgroundColor: 'red'"></div>
674
692
  ```
675
693
 
676
694
  **-->** *the rest of which can be seen below*:
677
695
 
678
696
  | Directive | Type | Usage |
679
697
  | :---- | :---- | :---- |
680
- | `&` | CSS Property | `<div binding="& color:someColor; & backgroundColor:someBgColor;"></div>` |
681
- | `%` | Class Name | `<div binding="% active:app.isActive; % expanded:app.isExpanded;"></div>` |
682
- | `~` | Attribute Name | `<a binding="~ href:person.profileUrl+'#bio'; ~ title:'Click me';"></a>` |
683
- | | Boolean Attribute | `<a binding="~ ?required:formField.required; ~ ?aria-checked: formField.checked"></a>` |
698
+ | `&` | CSS Property | `<div expr="& color:someColor; & backgroundColor:someBgColor;"></div>` |
699
+ | `%` | Class Name | `<div expr="% active:app.isActive; % expanded:app.isExpanded;"></div>` |
700
+ | `~` | Attribute Name | `<a expr="~ href:person.profileUrl+'#bio'; ~ title:'Click me';"></a>` |
701
+ | | Boolean Attribute | `<a expr="~ ?required:formField.required; ~ ?aria-checked: formField.checked"></a>` |
684
702
  | `@` | Structural Directive: | *See below* |
685
- | `@text` | Plain text content | `<span binding="@text:firstName+' '+lastName;"></span>` |
686
- | `@html` | Markup content | `<span binding="@html: '<i>'+firstName+'</i>';"></span>` |
687
- | `@items` | A list, with argument in the following format:<br>`<declaration> <of\|in> <iterable> / <importRef>` | *See next two tables* |
703
+ | `@text` | Plain text content | `<span expr="@text:firstName+' '+lastName;"></span>` |
704
+ | `@html` | Markup content | `<span expr="@html: '<i>'+firstName+'</i>';"></span>` |
705
+ | `@items` | A list, of the following format | `<declaration> <of\|in> <iterable> / <importRef>`<br>*See next two tables* |
688
706
 
689
707
  <details><summary><code>For ... Of</code> Loops</summary>
690
708
 
691
709
  | Idea | Usage |
692
710
  | :---- | :---- |
693
- | A `for...of` loop over an array/iterable | `<ul binding="@items: value of [1,2,3] / 'foo#fragment';"></ul>` |
694
- | Same as above but with a `key` declaration | `<ul binding="@items: (value,key) of [1,2,3] / 'foo#fragment';"></ul>` |
695
- | Same as above but with different variable names | `<ul binding="@items: (product,id) of store.products / 'foo#fragment';"></ul>` |
696
- | Same as above but with a dynamic `importRef` | `<ul binding="@items: (product,id) of store.products / store.importRef;"></ul>` |
711
+ | A `for...of` loop over an array/iterable | `<ul expr="@items: value of [1,2,3] / 'foo#fragment';"></ul>` |
712
+ | Same as above but with a `key` declaration | `<ul expr="@items: (value,key) of [1,2,3] / 'foo#fragment';"></ul>` |
713
+ | Same as above but with different variable names | `<ul expr="@items: (product,id) of store.products / 'foo#fragment';"></ul>` |
714
+ | Same as above but with a dynamic `importRef` | `<ul expr="@items: (product,id) of store.products / store.importRef;"></ul>` |
697
715
 
698
716
  </details>
699
717
 
@@ -701,14 +719,14 @@ Here, we get the `binding` attribute for a declarative and neat, key/value data-
701
719
 
702
720
  | Idea | Usage |
703
721
  | :---- | :---- |
704
- | A `for...in` loop over an object | `<ul binding="@items: key in {a:1,b:2} / 'foo#fragment';"></ul>` |
705
- | Same as above but with a `value` and `index` declaration | `<ul binding="@items: (key,value,index) in {a:1, b:2} / 'foo#fragment';"></ul>` |
722
+ | A `for...in` loop over an object | `<ul expr="@items: key in {a:1,b:2} / 'foo#fragment';"></ul>` |
723
+ | Same as above but with a `value` and `index` declaration | `<ul expr="@items: (key,value,index) in {a:1, b:2} / 'foo#fragment';"></ul>` |
706
724
 
707
725
  </details>
708
726
 
709
- <details><summary>Details</summary>
727
+ <details><summary>Resolution details</summary>
710
728
 
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, the above CSS example code could have an underlying data structure like the below:
729
+ Here, JavaScript references are resolved from the closest node up the document tree 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 something like the below:
712
730
 
713
731
  ```js
714
732
  document.bind({ someColor: 'green', someBgColor: 'yellow' });
@@ -722,7 +740,7 @@ document: { someColor: 'green', someBgColor: 'yellow' }
722
740
  └── body: { someBgColor: 'silver' }
723
741
  ```
724
742
 
725
- So, above, the `someBgColor` reference remains bound to the `someBgColor` *binding* on the `<body>` element until the meaning of "closest node" changes again:
743
+ Now, the `someBgColor` reference remains bound to the `someBgColor` *binding* on the `<body>` element until the meaning of "closest node" changes again:
726
744
 
727
745
  ```js
728
746
  delete document.body.bindings.someBgColor;
@@ -742,9 +760,9 @@ Bindings are resolved in realtime! And in fact, for lists, in-place mutations -
742
760
 
743
761
  </details>
744
762
 
745
- <details><summary>With SSR Support</summary>
763
+ <details><summary>With SSR support</summary>
746
764
 
747
- For lists, 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.
765
+ For lists, generated item elements are automatically assigned a corresponding key with a `data-key` attribute! This helps in remapping generated item nodes to their respective entry in *iteratee* during a rerendering or during hydration.
748
766
 
749
767
  </details>
750
768
 
@@ -752,7 +770,7 @@ For lists, generated item elements are automatically assigned a corresponding in
752
770
 
753
771
  We often still need to write more serious reactive logic on the UI than a declarative data-binding language can provide for. But we shouldn't need to reach for special tooling or some "serious" programming paradigm on top of JavaScript.
754
772
 
755
- Here, from the same `<script>` element we already write, we get a direct upgrade path to reactive programming in just an attribute: `quantum`:
773
+ Here, from the same `<script>` element we already write, we get a direct upgrade path to reactive programming in just the addition of an attribute: `quantum`:
756
774
 
757
775
  ```html
758
776
  <script quantum>
@@ -768,7 +786,7 @@ Here, from the same `<script>` element we already write, we get a direct upgrade
768
786
  </script>
769
787
  ```
770
788
 
771
- **-->** *which adds up really well with the idea of scoping*:
789
+ **-->** *which gives us fine-grained reactivity on top of literal JavaScript syntax; and which adds up really well with the `scoped` attribute*:
772
790
 
773
791
  ```html
774
792
  <main>
@@ -792,7 +810,7 @@ Here, from the same `<script>` element we already write, we get a direct upgrade
792
810
  </main>
793
811
  ```
794
812
 
795
- **-->** *with content being whatever you normally would write in a `<script>` element, but without the "manual" work for reactivity*:
813
+ **-->** *with content being whatever you normally would write in a `<script>` element, minus the "manual" work for reactivity*:
796
814
 
797
815
  ```html
798
816
  <main>
@@ -810,7 +828,7 @@ Here, from the same `<script>` element we already write, we get a direct upgrade
810
828
  </main>
811
829
  ```
812
830
 
813
- **-->** *within which other "live" APIs, like the Namespace API above, fit seamlessly*:
831
+ **-->** *within which dynamic application state/data, and even things like the Namespace API above, fit seamlessly*:
814
832
 
815
833
  ```html
816
834
  <main namespace>
@@ -843,13 +861,13 @@ const removeButton = () => {
843
861
  };
844
862
  ```
845
863
 
846
- <details><summary>Details</summary>
864
+ <details><summary>Learn more</summary>
847
865
 
848
866
  It's Imperative Reactive Programming ([IRP](https://en.wikipedia.org/wiki/Reactive_programming#Imperative)) right there and it's the [Quantum](https://github.com/webqit/quantum-js) runtime extension to JavaScript!
849
867
 
850
868
  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
869
 
852
- Now, in each case above, reactivity terminates on script's removal from the DOM. And a programmatic termination is sill possible:
870
+ Now, in each case above, reactivity terminates on script's removal from the DOM or via a programmatic approach:
853
871
 
854
872
  ```js
855
873
  const script = document.querySelector('script[quantum]');
@@ -863,115 +881,397 @@ But while that is automatic, DOM event handlers bound via `addEventListener()` w
863
881
 
864
882
  ## Data Plumbing
865
883
 
866
- *[TODO]: Bindings API & The Context API*
867
-
868
- <!--
869
- 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.
884
+ Components often need to manage, or be managed by, dynamic data. That could get pretty problematic and messy if all of that should go on DOM nodes as direct properties:
870
885
 
871
- └ *The [Observer API](https://github.com/webqit/observer) for general-purpose object observability*:
886
+ <details><summary>Example</summary>
872
887
 
873
888
  ```js
874
- function changeCallback(changes) {
875
- console.log(changes[0].type, changes[0].key, changes[0].value, changes[0].oldValue);
889
+ // Inside a custom element
890
+ connectedCallback() {
891
+ this.prop1 = 1;
892
+ this.prop2 = 2;
893
+ this.prop3 = 3;
894
+ this.style = 'tall-dark'; // ??? - conflict with the standard HTMLElement: style property
876
895
  }
877
896
  ```
878
897
 
879
898
  ```js
880
- const obj = {};
881
- Observer.observe(obj, changeCallback);
899
+ // Outside the component
900
+ const node = document.querySelector('my-element');
901
+ node.prop1 = 1;
902
+ node.prop2 = 2;
903
+ node.prop3 = 3;
904
+ node.normalize = true; // ??? - conflict with the standard Node: normalize() method
905
+ ```
906
+
907
+ </details>
908
+
909
+ This calls for a decent API and some data-flow mechanism!
910
+
911
+ ### The Bindings API
912
+
913
+ A place to maintain state need not be a complex state machine! Here, that comes as a simple, read/write, data object exposed on the document object and on DOM elements as a readonly `bindings` property. This is the Bindings API.
914
+
915
+ **-->** *it's an ordinary JavaScript object that can be read and mutated*:
916
+
917
+ ```js
918
+ // Read
919
+ console.log(document.bindings); // {}
920
+ // Modify
921
+ document.bindings.app = { title: 'Demo App' };
922
+ console.log(document.bindings.app); // { title: 'Demo App' }
923
+ ```
924
+
925
+ ```js
926
+ const node = document.querySelector('div');
927
+ // Read
928
+ console.log(node.bindings); // {}
929
+ // Modify
930
+ node.bindings.style = 'tall-dark';
931
+ node.bindings.normalize = true;
882
932
  ```
883
933
 
934
+ **-->** *with a complementary `bind()` method that lets us make mutations in batch*:
935
+
884
936
  ```js
885
- Observer.set(obj, 'prop1', 'value1'); // Reported synchronously
937
+ // ------------
938
+ // Set multiple properties
939
+ document.bind({ name: 'James Boye', cool: '100%', app: { title: 'Demo App' } });
940
+
941
+ // ------------
942
+ // Replace existing properties with a new set
943
+ document.bind({ signedIn: false, hot: '100%' });
944
+ // Inspect
945
+ console.log(document.bindings); // { signedIn: false, hot: '100%' }
946
+
947
+ // ------------
948
+ // Merge a new set of properties with existing
949
+ document.bind({ name: 'James Boye', cool: '100%' }, { merge: true });
950
+ // Inspect
951
+ console.log(document.bindings); // { signedIn: false, hot: '100%', name: 'James Boye', cool: '100%' }
886
952
  ```
887
953
 
954
+ **-->** *which also provides an easy way to passing data down a component tree*:
955
+
888
956
  ```js
889
- Observer.deleteProperty(obj, 'prop1'); // Reported synchronously
957
+ // Inside a custom element
958
+ connectedCallback() {
959
+ this.child1.bind(this.bindings.child1Data);
960
+ this.child2.bind(this.bindings.child2Data);
961
+ }
890
962
  ```
891
963
 
892
- *A Bindings API for binding application-level state to an object*:
964
+ **-->** *and with the Observer API in the picture for reactivity*:
893
965
 
894
966
  ```js
895
- // Observing document-level bindings
896
- Observer.observe(document.bindings, changeCallback);
967
+ Observer.observe(document.bindings, mutations => {
968
+ mutations.forEach(mutation => console.log(mutation));
969
+ });
970
+ ```
897
971
 
898
- // Set state
899
- document.bindings.userSignedIn = true;
972
+ ```js
973
+ // Inside a custom element
974
+ connectedCallback() {
975
+ Observer.observe(this.bindings, 'style', e => {
976
+ // Compunonent should magically change style
977
+ console.log(e.value);
978
+ });
979
+ }
980
+ ```
900
981
 
901
- // Set data object
902
- document.bindings.data = { prop1: 'value1' };
903
- Observer.set(document.bindings.data, 'prop2', 'value2');
982
+ ```js
983
+ const node = document.querySelector('my-element');
984
+ node.bindings.style = 'tall-dark';
904
985
  ```
905
986
 
987
+ <details><summary>Details</summary>
988
+
989
+ In the current OOHTML implementation, the `document.bindings` and `Element.prototype.bindings` APIs are implemented as proxies over their actual bindings interface to enable some interface-level reactivity. This lets us have reactivity over literal property assignments and deletions on these interfaces:
990
+
906
991
  ```js
907
- // Observing element-level bindings
908
- Observer.observe(element.bindings, changeCallback);
992
+ node.bindings.style = 'tall-dark'; // Reactive assignment
993
+ delete node.bindings.style; // Reactive deletion
994
+ ```
909
995
 
910
- // Set state
911
- element.bindings.isCollapsed = true;
996
+ For mutations at a deeper level to be reactive, the corresponding Observer API method must be used:
912
997
 
913
- // Set data object
914
- element.bindings.data = { prop1: 'value1' };
915
- Observer.set(element.bindings.data, 'prop2', 'value2');
998
+ ```js
999
+ Observer.set(document.bindings.app, 'title', 'Demo App!!!');
1000
+ Observer.deleteProperty(document.bindings.app, 'title');
916
1001
  ```
917
1002
 
918
- └ *"Quantum Scripts" for reactive scripting*:
1003
+ </details>
919
1004
 
920
- ```html
921
- <script quantum>
922
- console.log(this) // window
1005
+ ### The Context API
1006
+
1007
+ Component trees on the typical UI often call for more than the normal top-down flow of data that the Bindings API facilitates. A child may require the ability to look up the component tree to directly access specific data, or in other words, get data from "context". This is where a Context API comes in.
923
1008
 
924
- console.log(window.liveProperty) // live expression
925
- console.log(liveProperty) // live expression; technically same as above
1009
+ Interestingly, the Context API is the underlying resolution mechanism for HTML Imports and Data Binding in OOHTML!
1010
+
1011
+ Here, we simply leverage the DOM's existing event system to fire a "request" event and let an arbitrary "provider" in context fulfill the request. This becomes very simple with the Context API which is exposed on the document object and on element instances as a readonly `contexts` property.
1012
+
1013
+ **-->** *with the `contexts.request()` method for firing requests*:
1014
+
1015
+ ```js
1016
+ // ------------
1017
+ // Get an arbitrary
1018
+ const node = document.querySelector('my-element');
1019
+
1020
+ // ------------
1021
+ // Prepare and fire request event
1022
+ const requestParams = { kind: 'html-imports', detail: '/foo#fragment1' };
1023
+ const response = node.contexts.request(requestParams);
1024
+
1025
+ // ------------
1026
+ // Handle response
1027
+ console.log(response.value); // It works!
1028
+ ```
926
1029
 
927
- if (document.bindings.userSignedIn) {
928
- // Live block
929
- console.log('User signed in!');
1030
+ **-->** *and the `contexts.attach()` and `contexts.detach()` methods for attaching/detaching providers at arbitrary levels in the DOM tree*:
1031
+
1032
+ ```js
1033
+ // ------------
1034
+ // Define a CustomContext class
1035
+ class FakeImportsContext extends DOMContext {
1036
+ static kind = 'html-imports';
1037
+ handle(event) {
1038
+ console.log(event.detail); // '/foo#fragment1'
1039
+ event.respondWith('It works!');
930
1040
  }
931
- </script>
1041
+ }
1042
+
1043
+ // ------------
1044
+ // Instantiate and attach to a node
1045
+ const fakeImportsContext = new FakeImportsContext;
1046
+ document.contexts.attach(fakeImportsContext);
1047
+
1048
+ // ------------
1049
+ // Detach anytime
1050
+ document.contexts.detach(fakeImportsContext);
932
1051
  ```
933
1052
 
1053
+ <details><summary>Details</summary>
1054
+
1055
+ In the current OOHTML implementation, the Context API interfaces are exposed via the global `webqit` object:
1056
+
934
1057
  ```js
935
- Observer.set(window, 'liveProperty'); // Live expressions rerun
1058
+ const { DOMContext, DOMContextRequestEvent, DOMContextResponse } = window.webqit;
936
1059
  ```
937
1060
 
938
- ```html
939
- <div>
940
- <script quantum scoped>
941
- console.log(this) // div
1061
+ Now, by design...
1062
+
1063
+ + a provider will automatically adopt the `contextname`, if any, of its host element:
1064
+
1065
+ ```html
1066
+ <div contextname="context1"></div>
1067
+ ```
1068
+
1069
+ ```js
1070
+ // Instantiate and attach to a node
1071
+ const host = document.querySelector('div');
1072
+ const fakeImportsContext = new FakeImportsContext;
1073
+ host.contexts.attach(fakeImportsContext);
1074
+ // Inspect name
1075
+ console.log(fakeImportsContext.name); // context1
1076
+ ```
1077
+
1078
+ ...which a request could target:
942
1079
 
943
- console.log(this.liveProperty) // live expression
1080
+ ```js
1081
+ const requestParams = { kind: FakeImportsContext.kind, targetContext: 'context1', detail: '/foo#fragment1' };
1082
+ const response = node.contexts.request(requestParams);
1083
+ ```
1084
+
1085
+ + and providers of same kind could be differentiated by an extra "detail" - an arbitrary value passed to the constructor:
1086
+
1087
+ ```js
1088
+ const fakeImportsContext = new FakeImportsContext('lorem');
1089
+ console.log(fakeImportsContext.detail); // lorem
1090
+ ```
944
1091
 
945
- if (this.bindings.isCollapsed) {
946
- // Live block
947
- console.log('Section collapsed!');
1092
+ + and a provider could indicate to manually match requests where the defualt "kind" matching, and optional "targetContext" matching, don't suffice:
1093
+
1094
+ ```js
1095
+ // Define a CustomContext class
1096
+ class CustomContext extends DOMContext {
1097
+ static kind = 'html-imports';
1098
+ matchEvent(event) {
1099
+ // The default request matching algorithm + "detail" matching
1100
+ return super.matchEvent() && event.detail === this.detail;
1101
+ }
1102
+ handle(event) {
1103
+ console.log(event.detail);
1104
+ event.respondWith('It works!');
1105
+ }
948
1106
  }
949
- </script>
1107
+ ```
1108
+
1109
+ + and a request could choose to stay subscribed to changes on the requested data; the request would simply add a `live` flag:
1110
+
1111
+ ```js
1112
+ // Set the "live" flag
1113
+ const requestParams = { kind: FakeImportsContext.kind, targetContext: 'context1', detail: '/foo#fragment1', live: true };
1114
+ ```
1115
+
1116
+ ...then stay alert to said updates on the returned `DOMContextResponse` object or specify a callback function at request time:
1117
+
1118
+ ```js
1119
+ // Handle response without a callback
1120
+ const response = node.contexts.request(requestParams);
1121
+ console.log(response.value); // It works!
1122
+ Observer.observe(response, 'value', e => {
1123
+ console.log(e.value); // It works live!
1124
+ });
1125
+ ```
1126
+
1127
+ ```js
1128
+ // Handle response with a callback
1129
+ node.contexts.request(requestParams, value => {
1130
+ console.log(value);
1131
+ // It works!
1132
+ // It works live!
1133
+ });
1134
+ ```
1135
+
1136
+ ...while provider simply checks for the `event.live` flag and keep the updates flowing:
1137
+
1138
+ ```js
1139
+ // Define a CustomContext class
1140
+ class CustomContext extends DOMContext {
1141
+ static kind = 'html-imports';
1142
+ handle(event) {
1143
+ event.respondWith('It works!');
1144
+ if (event.live) {
1145
+ setTimeout(() => {
1146
+ event.respondWith('It works live!');
1147
+ }, 5000);
1148
+ }
1149
+ }
1150
+ }
1151
+ ```
1152
+
1153
+ ...and optionally implements a `subscribed` and `unsubscribed` lifecycle hook for when a "live" event enters and leaves the instance:
1154
+
1155
+ ```js
1156
+ // Define a CustomContext class
1157
+ class CustomContext extends DOMContext {
1158
+ static kind = 'html-imports';
1159
+ subscribed(event) {
1160
+ console.log(this.subscriptions.size); // 1
1161
+ }
1162
+ unsubscribed(event) {
1163
+ console.log(this.subscriptions.size); // 0
1164
+ }
1165
+ handle(event) {
1166
+ event.respondWith('It works!');
1167
+ if (event.live) {
1168
+ setTimeout(() => {
1169
+ event.respondWith('It works live!');
1170
+ }, 5000);
1171
+ }
1172
+ }
1173
+ }
1174
+ ```
1175
+
1176
+ + live requests are terminated via the returned `DOMContextResponse` object...
1177
+
1178
+ ```js
1179
+ response.abort();
1180
+ ```
1181
+
1182
+ ...or via an initially specified custom `AbortSignal`:
1183
+
1184
+ ```js
1185
+ // Add a signal to the original request
1186
+ const abortController = new AbortController;
1187
+ const requestParams = { kind: FakeImportsContext.kind, targetContext: 'context1', detail: '/foo#fragment1', live: true, signal: abortController.signal };
1188
+ ```
1189
+
1190
+ ```js
1191
+ abortController.abort(); // Which also calls response.abort();
1192
+ ```
1193
+
1194
+ + now, when a node in a provider's subtree is suddenly attached an identical provider, any live requests the super provider may be serving are automatically "claimed" by the sub provider:
1195
+
1196
+ ```js
1197
+ document: // 'fake-provider' here
1198
+ └── html
1199
+ ├── head
1200
+ └── body: // 'fake-provider' here. Our request above is now served from here.
1201
+ ```
1202
+
1203
+ And when the sub provider is suddenly detached from said node, any live requests it may have served are automatically hoisted back to super provider.
1204
+
1205
+ ```js
1206
+ document: // 'fake-provider' here. Our request above is now served from here.
1207
+ └── html
1208
+ ├── head
1209
+ └── body:
1210
+ ```
1211
+
1212
+ While, in all, saving the requesting code that "admin" work!
1213
+
1214
+ </details>
1215
+
1216
+ **-->** *all of which gives us the imperative equivalent of declarative context-based features like HTMLImports and Data Binding*:
1217
+
1218
+ ```html
1219
+ <div contextname="vendor1">
1220
+ <div contextname="vendor2">
1221
+ ...
1222
+
1223
+ <my-element>
1224
+ <!-- Declarative import -->
1225
+ <import ref="@vendor1/foo#fragment1"></import>
1226
+ <!-- Declarative Data Binding -->
1227
+ <?{ @vendor2.app.title }?>
1228
+ </my-element>
1229
+
1230
+ ...
1231
+ </div>
950
1232
  </div>
951
1233
  ```
952
1234
 
953
1235
  ```js
954
- Observer.set(element, 'liveProperty'); // Live expressions rerun
1236
+ // ------------
1237
+ // Equivalent import() approach
1238
+ const response = myElement.import('@vendor1/foo#fragment1');
1239
+
1240
+ // ------------
1241
+ // Equivalent Context API approach
1242
+ const requestParams = { kind: 'html-imports', targetContext: 'vendor1', detail: 'foo#fragment1' };
1243
+ const response = myElement.contexts.request(requestParams);
1244
+
1245
+ // ------------
1246
+ // Handle response
1247
+ console.log(response.value);
955
1248
  ```
956
1249
 
957
- └ [Reactive HTML concepts](#)
1250
+ ```js
1251
+ // ------------
1252
+ // Context API request for bindings
1253
+ const requestParams = { kind: 'bindings', targetContext: 'vendor2', detail: 'app' };
1254
+ const response = myElement.contexts.request(requestParams);
958
1255
 
959
- -->
1256
+ // ------------
1257
+ // Handle response
1258
+ console.log(response.value.title);
1259
+ ```
960
1260
 
961
1261
  ## Polyfill
962
1262
 
963
- OOHTML is being developed as something to be used today—via a polyfill. This is an active and intentional effort that continues to ensure that the project evolves through a practice-driven process.
1263
+ OOHTML is being developed as something to be used today—via a polyfill. This is an intentional effort that's helping to ensure that the project evolves through a practice-driven process.
964
1264
 
965
1265
  <details><summary>Load from a CDN<br>
966
- └───────── <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>
1266
+ └───────── <a href="https://bundlephobia.com/result?p=@webqit/oohtml"><img align="right" src="https://img.shields.io/badge/21.7%20kB-black"></a></summary>
967
1267
 
968
1268
  ```html
969
- <script src="https://unpkg.com/@webqit/oohtml/dist/main.js"></script>
1269
+ <script src="https://unpkg.com/@webqit/oohtml/dist/main.lite.js"></script>
970
1270
  ```
971
1271
 
972
1272
  └ This is to be placed early on in the document and should be a classic script without any `defer` or `async` directives!
973
1273
 
974
- └ For the Scoped Styles feature, you'd also need something like the [samthor/scoped](https://github.com/samthor/scoped) polyfill (more details below):
1274
+ └ For the Scoped Styles feature, you'd need an external polyfill like the [samthor/scoped](https://github.com/samthor/scoped) polyfill (more details below):
975
1275
 
976
1276
  ```html
977
1277
  <head>
@@ -985,18 +1285,19 @@ OOHTML is being developed as something to be used today—via a polyfill. This i
985
1285
  └───────── <a href="https://npmjs.com/package/@webqit/oohtml"><img align="right" src="https://img.shields.io/npm/v/@webqit/oohtml?style=flat&label=&colorB=black"></a></summary>
986
1286
 
987
1287
  ```bash
988
- npm i @webqit/oohtml
1288
+ npm i @webqit/oohtml @webqit/quantum-js
989
1289
  ```
990
1290
 
991
1291
  ```js
992
1292
  // Import
1293
+ import * as Quantum from '@webqit/quantum-js/lite'; // Or from '@webqit/quantum-js'; See implementation notes below
993
1294
  import init from '@webqit/oohtml';
994
1295
 
995
1296
  // Initialize the lib
996
- init.call( window[, options = {} ]);
1297
+ init.call(window, Quantum[, options = {}]);
997
1298
  ```
998
1299
 
999
- └ 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 as above.
1300
+ └ 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 with the DOM instance as above.
1000
1301
 
1001
1302
  └ But all things "SSR" for OOHTML are best left to the [`@webqit/oohtml-ssr`](https://github.com/webqit/oohtml-ssr) package!
1002
1303
 
@@ -1004,24 +1305,36 @@ init.call( window[, options = {} ]);
1004
1305
 
1005
1306
  <details><summary>Extended usage concepts</summary>
1006
1307
 
1007
- If you'll be going ahead to build a real app to see OOHTML in action, you may want to consider also using:
1308
+ If you'll be going ahead to build a real app with OOHTML, you may want to consider also using:
1008
1309
 
1009
1310
  + the [`@webqit/oohtml-cli`](https://github.com/webqit/oohtml-cli) package for operating a file-based templating system.
1010
1311
 
1011
- + the modest, OOHTML-based [Webflo](https://github.com/webqit/webflo) framework to greatly streamline your application development process!
1312
+ + the modest, OOHTML-based [Webflo](https://github.com/webqit/webflo) framework to greatly streamline your workflow!
1012
1313
 
1013
1314
  </details>
1014
1315
 
1015
1316
  <details><summary>Implementation Notes</summary>
1016
1317
 
1318
+ + **Scoped/Quantum Scripts**. This feature is an extension of [Quantum JS](https://github.com/webqit/quantum-js). While the main OOHTML build is based on the main Quantum JS APIs, a companion "OOHTML Lite" build is also available based on the [Quantum JS Lite](https://github.com/webqit/quantum-js#quantum-js-lite) edition. The trade-off is in the execution timing of `<script quantum></script>` and `<script scoped></script>` elements: being "synchronous/blocking" with the former, and "asynchronous/non-blocking" with the latter! (See [`async`/`defer`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attributes).)
1319
+
1320
+ Of the two, the "OOHTML Lite" edition is the recommend option (as used above) for faster load times unless there's 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), in which case you'd need the main OOHTML build:
1321
+
1322
+ ```html
1323
+ <head>
1324
+ <script src="https://unpkg.com/@webqit/oohtml/dist/main.js"></script>
1325
+ </head>
1326
+ ```
1327
+
1328
+ └─ <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>
1329
+
1017
1330
  + **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!
1018
1331
 
1019
- If you must load the script "async", one little trade-off has to be made for `<script scoped>` and `<script quantum>` 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:
1332
+ If you must load the script "async", one little trade-off would have to be made for `<script scoped>` and `<script quantum>` 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:
1020
1333
 
1021
1334
  ```html
1022
1335
  <head>
1023
1336
  <meta name="scoped-js" content="script.mimeType=some-mime">
1024
- <script async src="https://unpkg.com/@webqit/oohtml/dist/main.js"></script>
1337
+ <script async src="https://unpkg.com/@webqit/oohtml/dist/main.lite.js"></script>
1025
1338
  </head>
1026
1339
  <body>
1027
1340
  <script type="some-mime" scoped>
@@ -1042,16 +1355,6 @@ If you'll be going ahead to build a real app to see OOHTML in action, you may wa
1042
1355
 
1043
1356
  ...still gives the `window` object in the console.
1044
1357
 
1045
- + **Scoped/Quantum Scripts**. This feature is an extension of [Quantum JS](https://github.com/webqit/quantum-js) and the default OOHTML build is based on the [Quantum JS Lite APIs](https://github.com/webqit/quantum-js#quantum-js-lite). Now, while Quantum JS Lite yields faster load times, it also means that `<script quantum></script>` and `<script scoped></script>` elements are parsed "asynchronously", in the same timing as `<script type="module"></script>`!
1046
-
1047
- 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
-
1049
- ```html
1050
- <head>
1051
- <script src="https://unpkg.com/@webqit/oohtml/dist/main.realtime.js"></script>
1052
- </head>
1053
- ```
1054
-
1055
1358
  + **Scoped CSS**. This feature is only in "concept" implementation and doesn't work right now as is. The current implementation simply wraps `<style scoped>` blocks in an `@scope {}` block - which itself isn't supported in any browser. To try this "concept" implementation, set the `style.strategy` config to `@scope`:
1056
1359
 
1057
1360
  ```html
@@ -1100,7 +1403,7 @@ If you'll be going ahead to build a real app to see OOHTML in action, you may wa
1100
1403
 
1101
1404
  ## Examples
1102
1405
 
1103
- Here are a few examples in the wide range of use cases these features cover. While we'll demonstrate the most basic forms of these scenarios, it takes roughly the same principles to build an intricate form and a highly interactive UI.
1406
+ Here are a few examples in the wide range of use cases these features cover. While we'll demonstrate the most basic form of these scenarios, it takes roughly the same principles to build an intricate form and a highly interactive UI.
1104
1407
 
1105
1408
  <details><summary>Example 1: <i>Single Page Application</i><br>
1106
1409
  └───────── </summary>
@@ -1295,11 +1598,11 @@ The following is a hypothetical list page!
1295
1598
 
1296
1599
  <!-- The "items" template -->
1297
1600
  <template def="item" scoped>
1298
- <li><a binding="~href: '/animals#'+name;"><?{ index+': '+name }?></a></li>
1601
+ <li><a expr="~href: '/animals#'+name;"><?{ index+': '+name }?></a></li>
1299
1602
  </template>
1300
1603
 
1301
1604
  <!-- The loop -->
1302
- <ul binding="@items: (name,index) of ['dog','cat','ram'] / 'item';"></ul>
1605
+ <ul expr="@items: (name,index) of ['dog','cat','ram'] / 'item';"></ul>
1303
1606
 
1304
1607
  </section>
1305
1608
  ```