@webqit/oohtml 3.0.1-8 → 3.1.0

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 +530 -228
  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
@@ -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 borrows from how elements like images work; terminating with either a `load` or an `error` event*:
225
226
 
226
227
  ```js
227
228
  foo.addEventListener('load', loadCallback);
@@ -250,19 +251,20 @@ 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 references and are 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 previously slotted contents have *all* been programmatically removed and slot is empty.
260
262
 
261
263
  </details>
262
264
 
263
- <details><summary>With SSR Support</summary>
265
+ <details><summary>With SSR support</summary>
264
266
 
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.
267
+ 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
268
 
267
269
  The above resolved imports would thus give us something like:
268
270
 
@@ -275,23 +277,23 @@ The above resolved imports would thus give us something like:
275
277
  </body>
276
278
  ```
277
279
 
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:
280
+ But they also will 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:
279
281
 
280
282
  ```html
281
283
  <!--&lt;import ref="/foo/nested#fragment2" nodecount="1"&gt;&lt;/import&gt;-->
282
284
  ```
283
285
 
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!
286
+ Now, on getting to the client and "hydrating" the `<import>` element, that extra bit of information gets 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
287
 
286
288
  > 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
289
 
288
290
  </details>
289
291
 
290
- ### Programmatic Module Imports
292
+ ### Imperative Module Imports
291
293
 
292
294
  JavaScript applications will need more than a declarative import mechanism.
293
295
 
294
- Here, we get an *HTMLImports* API for programmatic module import:
296
+ Here, we get an *HTMLImports* API for imperative module import:
295
297
 
296
298
  ```js
297
299
  const moduleObject1 = document.import('/foo#fragment1');
@@ -303,7 +305,7 @@ const moduleObject2 = document.import('/foo/nested#fragment2');
303
305
  console.log(moduleObject2.value); // divElement
304
306
  ```
305
307
 
306
- **-->** *with an observable `moduleObject.value` property for working asynchronously; e.g. awaiting and handling remote modules*:
308
+ **-->** *with the `moduleObject.value` property being a live property for when results are asynchronous; e.g. asynchronously loaded modules*:
307
309
 
308
310
  ```js
309
311
  Observer.observe(moduleObject2, 'value', e => {
@@ -319,7 +321,7 @@ document.import('/foo#fragment1', divElement => {
319
321
  });
320
322
  ```
321
323
 
322
- **-->** *with an optional `live` parameter for staying subscribed to any mutations made to source module elements*:
324
+ **-->** *with an optional `live` parameter for staying subscribed to updated results*:
323
325
 
324
326
  ```js
325
327
  const moduleObject2 = document.import('/foo/nested#fragment2', true/*live*/);
@@ -335,38 +337,25 @@ document.import('/foo#fragment1', true/*live*/, divElement => {
335
337
  });
336
338
  ```
337
339
 
338
- *...both of which would get notified on doing the below*:
340
+ *...both of which gets notified on doing the below*:
339
341
 
340
342
  ```js
341
343
  document.querySelector('template[def="foo"]').content.firstElementChild.remove();
342
344
  ```
343
345
 
344
- **-->** *with an optional `AbortSignal` parameter for aborting module mutation events*:
346
+ **-->** *with a `moduleObject.abort()` method for unsubscribing from live updates*:
345
347
 
346
- ```js
347
- const abortController = new AbortController;
348
- ```
348
+ **-->** *with an optional `signal` parameter for passing in a custom `AbortSignal` instance*:
349
349
 
350
350
  ```js
351
+ const abortController = new AbortController;
351
352
  const moduleObject2 = document.import('/foo/nested#fragment2', { live: true, signal: abortController.signal });
352
353
  ```
353
354
 
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
355
  ```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);
356
+ setTimeout(() => {
357
+ abortController.abort(); // which would also call moduleObject2.abort()
358
+ }, 1000);
370
359
  ```
371
360
 
372
361
  <details><summary>Extended Imports concepts</summary>
@@ -375,7 +364,7 @@ setTimeout(() => abortController.abort(), 1000);
375
364
 
376
365
  We can defer module loading until we really need them.
377
366
 
378
- Here, we get the `loading="lazy"` directive for that; and loading is only then triggered on the first attempt to import their contents:
367
+ 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
368
 
380
369
  ```html
381
370
  <!-- Loading doesn't happen until the first time this is being accessed -->
@@ -391,43 +380,6 @@ Here, we get the `loading="lazy"` directive for that; and loading is only then t
391
380
  const moduleObject2 = document.import('/foo#fragment1'); // Triggers module loading and resolves at moduleObject2.value on load success
392
381
  ```
393
382
 
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
383
  ### Module Inheritance
432
384
 
433
385
  We'll often have repeating markup structures.
@@ -478,80 +430,142 @@ Here, we get module nesting with inheritance to simplify that:
478
430
 
479
431
  ### Imports Contexts
480
432
 
481
- We should be able to define a base path at arbitrary levels in the page against which to resolve import *refs* in subtree.
433
+ We should be able to have *relative* import refs that resolve against local contexts in the document tree.
434
+
435
+ Here, we call those arbitrary contexts "Imports Contexts", and these could be:
482
436
 
483
- Here, we get "Imports Contexts" for that:
437
+ + Simple Base Path Contexts ([below](#base-path-contexts))
438
+ + Scoped Module Contexts ([below](#scoped-module-contexts))
439
+ + Named Contexts ([below](#named-contexts))
440
+ + Extended Scoped Module Contexts ([below](#extended-scoped-module-contexts))
441
+
442
+ Alongside which we have a complementary `Element.prototype.import()` API for thinking in contexts.
443
+
444
+ #### "Base Path" Contexts
445
+
446
+ Base paths may be defined at arbitrary levels in the page using the `importscontext` attribute:
484
447
 
485
448
  ```html
486
449
  <body importscontext="/foo">
487
450
  <section>
488
- <import ref="#fragment1"></import> <!-- Relative path (beginning without a slash), resolves to: /foo#fragment1 -->
451
+ <import ref="#fragment1"></import> <!-- Relative path (beginning without a slash), resolving to: /foo#fragment1 -->
489
452
  </section>
490
453
  </body>
491
454
  ```
492
455
 
493
456
  ```html
494
457
  <body importscontext="/foo/nested">
458
+ <main>
459
+ <import ref="#fragment2"></import> <!-- Relative path (beginning without a slash), resolving to: /foo/nested#fragment2 -->
460
+ </main>
461
+ </body>
462
+ ```
463
+
464
+ **-->** *with said base paths being able to "nest" nicely*:
465
+
466
+ ```html
467
+ <body importscontext="/foo">
468
+
495
469
  <section>
496
- <import ref="#fragment2"></import> <!-- Relative path (beginning without a slash), resolves to: /foo/nested#fragment2 -->
470
+ <import ref="#fragment1"></import> <!-- Relative path (beginning without a slash), resolves to: /foo#fragment1 -->
497
471
  </section>
472
+
473
+ <div importscontext="nested"> <!-- Relative path (beginning without a slash), resolves to: /foo/nested -->
474
+
475
+ <main>
476
+ <import ref="#fragment2"></import> <!-- Relative path (beginning without a slash), resolves to: /foo/nested#fragment2 -->
477
+ </main>
478
+
479
+ </div>
480
+
498
481
  </body>
499
482
  ```
500
483
 
484
+ **-->** *with the `Element.prototype.import()` API for equivalent context-based imports*:
485
+
501
486
  ```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 }
487
+ // Using the HTMLImports API to import from context
488
+ const contextElement = document.querySelector('section');
489
+ const response = contextElement.import('#fragment1'); // Relative path (beginning without a slash), resolving to: /foo#fragment1
490
+ ```
491
+
492
+ ```js
493
+ // Using the HTMLImports API to import from context
494
+ const contextElement = document.querySelector('main');
495
+ const response = contextElement.import('#fragment1'); // Relative path (beginning without a slash), resolving to: /foo/nested#fragment1
506
496
  ```
507
497
 
508
- **-->** *with said "Imports Contexts" optionally having a name*:
498
+ #### "Scoped Module" Contexts
499
+
500
+ 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.
501
+
502
+ 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 are to Custom Elements):
509
503
 
510
504
  ```html
511
- <body contextname="context1" importscontext="/foo/nested">
505
+ <section> <!-- Host object -->
512
506
 
513
- <import ref="#fragment2"></import> <!-- Relative path (beginning without a slash), resolves to: /foo/nested#fragment2 -->
507
+ <template def="foo" scoped> <!-- Scoped to host object and not available globally -->
508
+ <div def="fragment1"></div>
509
+ </template>
514
510
 
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>
511
+ <div>
512
+ <import ref="foo#fragment1"></import> <!-- Relative path (beginning without a slash), resolving to the local module: foo#fragment1 -->
513
+ <import ref="/foo#fragment1"></import> <!-- Absolute path, resolving to the global module: /foo#fragment1 -->
514
+ </div>
521
515
 
522
- </body>
516
+ </section>
523
517
  ```
524
518
 
519
+ **-->** *with the `Element.prototype.import()` API for equivalent context-based imports*:
520
+
525
521
  ```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 }
522
+ // Using the HTMLImports API for local import
523
+ const contextElement = document.querySelector('div');
524
+ const localModule = moduleHost.import('#fragment1'); // Relative path (beginning without a slash), resolving to the local module: foo#fragment1
530
525
  ```
531
526
 
532
- **-->** *with said "Imports Contexts" being able to "nest" nicely*:
527
+ ```js
528
+ // Using the HTMLImports API for global import
529
+ const contextElement = document.querySelector('div');
530
+ const globalModule = contextElement.import('/foo#fragment1'); // Absolute path, resolving to the global module: /foo#fragment1
531
+ ```
532
+
533
+ #### Named Contexts
534
+
535
+ Imports Contexts may be named:
533
536
 
534
537
  ```html
535
- <body importscontext="/foo">
538
+ <body contextname="context1" importscontext="/foo/nested">
536
539
 
537
- <import ref="#fragment1"></import> <!-- Relative path (beginning without a slash), resolves to: /foo#fragment1 -->
540
+ <import ref="#fragment2"></import> <!-- Relative path (beginning without a slash), resolves to: /foo/nested#fragment2 -->
541
+
542
+ <section importscontext="/foo">
543
+ <import ref="#fragment1"></import> <!-- Relative path (beginning without a slash), resolves to: /foo#fragment1 -->
544
+
545
+ <div>
546
+ <import ref="@context1#fragment2"></import> <!-- Context-relative path (beginning with a context name), resolves to: /foo/nested#fragment2 -->
547
+ </div>
538
548
 
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
549
  </section>
542
550
 
543
551
  </body>
544
552
  ```
545
553
 
546
- ### Scoped Modules and Imports Contexts
554
+ **-->** *with the `Element.prototype.import()` API for equivalent context-based imports*:
547
555
 
548
- Scoped modules and Import Contexts shouldn't be mutually exclusive.
556
+ ```js
557
+ // Using the HTMLImports API to import from a named Imports Context
558
+ const contextElement = document.querySelector('div');
559
+ const result = contextElement.import('@context1#fragment2'); // Resolving to the module:/foo/nested#fragment2
560
+ ```
549
561
 
550
- Here, we're able to have *one* element implement *both* at the same time - with scoped modules inheriting Import Contexts:
562
+ #### Extended Scoped Module Contexts
563
+
564
+ Scoped Module Contexts may also have a Base Path Context that they inherit from:
551
565
 
552
566
  ```html
553
567
  <body contextname="context1" importscontext="/bar">
554
- <section importscontext="nested"> <!-- object with own modules, plus inherited context: /bar/nested -->
568
+ <section importscontext="nested"> <!-- object with Scoped Modules, plus inherited context: /bar/nested -->
555
569
 
556
570
  <template def="foo" scoped> <!-- Scoped to host object and not available globally -->
557
571
  <div def="fragment1"></div>
@@ -559,33 +573,36 @@ Here, we're able to have *one* element implement *both* at the same time - with
559
573
  </template>
560
574
 
561
575
  <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 -->
576
+ <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 -->
577
+ <import ref="/foo#fragment1"></import> <!-- Absolute path, resolving to the global module: /foo#fragment1 -->
578
+ <import ref="@context1#fragment1"></import> <!-- Relative path with a named context, resolving to the global module: /bar#fragment1 -->
565
579
  </div>
566
580
 
567
581
  </section>
568
582
  </body>
569
583
  ```
570
584
 
585
+ **-->** *with the `Element.prototype.import()` API for equivalent context-based imports*:
586
+
571
587
  ```js
572
588
  // 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 }
589
+ const contextElement = document.querySelector('div');
590
+ const result = contextElement.import('#fragment2'); // the local module: foo#fragment2, and if not found, the inherited module: /bar/nested#fragment2
576
591
  ```
577
592
 
578
593
  </details>
579
594
 
580
595
  ## Data Binding
581
596
 
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.
597
+ Data binding is about declaratively binding the UI to application data, wherein the relevant parts of the UI *automatically* update as application state changes.
583
598
 
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!
599
+ OOHTML makes this possible in just simple conventions - via a new comment-based data-binding syntax `<?{ }?>` and a complementary new `expr` attribute!
600
+
601
+ And there's one more: Quantum Scripts which brings the most advanced form of reactivity to HTML!
585
602
 
586
603
  ### Discrete Data-Binding
587
604
 
588
- Here, we get a comment-based data-binding tag `<?{ }?>` which works like regular comment but stay "data-charged":
605
+ 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
606
 
590
607
  ```js
591
608
  <html>
@@ -599,9 +616,9 @@ Here, we get a comment-based data-binding tag `<?{ }?>` which works like regular
599
616
  </html>
600
617
  ```
601
618
 
602
- <details><summary>Details</summary>
619
+ <details><summary>Resolution details</summary>
603
620
 
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:
621
+ 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
622
 
606
623
  ```js
607
624
  document.bind({ name: 'James Boye', cool: '100%', app: { title: 'Demo App' } });
@@ -615,7 +632,7 @@ document: { name: 'James Boye', cool: '100%', app: { title: 'Demo App' } }
615
632
  └── body: { name: 'John Doe' }
616
633
  ```
617
634
 
618
- So, above, the `name` reference remains bound to the `name` *binding* on the `<body>` element until the meaning of "closest node" changes again:
635
+ Now, the `name` reference remains bound to the `name` *binding* on the `<body>` element until the meaning of "closest node" changes again:
619
636
 
620
637
  ```js
621
638
  delete document.body.bindings.name;
@@ -627,21 +644,21 @@ While the `cool` reference remains bound to the `cool` *binding* on the `documen
627
644
  document.body.bindings.cool = '200%';
628
645
  ```
629
646
 
630
- <details>
647
+ </details>
631
648
 
632
- <details><summary>With SSR Support</summary>
649
+ <details><summary>With SSR support</summary>
633
650
 
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.
651
+ 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
652
 
636
- The following: `<?{ 'Hello World' }?>` would thus give us: `<?{ 'Hello World' }?>Hello World`.
653
+ The following expression: `<?{ 'Hello World' }?>` would thus give us: `<?{ 'Hello World' }?>Hello World`.
637
654
 
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:
655
+ 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
656
 
640
657
  ```html
641
658
  <?{ 'Hello World'; [=11] }?>Hello World
642
659
  ```
643
660
 
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!
661
+ 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
662
 
646
663
  > 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
664
 
@@ -649,51 +666,51 @@ Now that extra bit of information gets decoded and original relationships are fo
649
666
 
650
667
  ### Inline Data-Binding
651
668
 
652
- Here, we get the `binding` attribute for a declarative and neat, key/value data-binding syntax:
669
+ Here, we get the `expr` attribute for a declarative, neat, key/value data-binding syntax:
653
670
 
654
671
  ```html
655
- <div binding="<directive> <param>: <arg>;"></div>
672
+ <div expr="<directive> <param>: <arg>;"></div>
656
673
  ```
657
674
 
658
675
  **-->** *where*:
659
676
 
660
- + *`<directive>` is the directive, which is always a symbol*
677
+ + *`<directive>` is a directive, which is always a symbol*
661
678
  + *`<param>` is the parameter being bound, which could be a CSS property, class name, attribute name, Structural Directive - depending on the givin directive*
662
679
  + *`<arg>` is the bound value or expression*
663
680
 
664
681
  **-->** *which would give us the following for a CSS property*:
665
682
 
666
683
  ```html
667
- <div binding="& color:someColor; & backgroundColor:'red'"></div>
684
+ <div expr="& color:someColor; & backgroundColor:'red'"></div>
668
685
  ```
669
686
 
670
687
  **-->** *without being space-sensitive*:
671
688
 
672
689
  ```html
673
- <div binding="& color:someColor; &backgroundColor: 'red'"></div>
690
+ <div expr="& color:someColor; &backgroundColor: 'red'"></div>
674
691
  ```
675
692
 
676
693
  **-->** *the rest of which can be seen below*:
677
694
 
678
695
  | Directive | Type | Usage |
679
696
  | :---- | :---- | :---- |
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>` |
697
+ | `&` | CSS Property | `<div expr="& color:someColor; & backgroundColor:someBgColor;"></div>` |
698
+ | `%` | Class Name | `<div expr="% active:app.isActive; % expanded:app.isExpanded;"></div>` |
699
+ | `~` | Attribute Name | `<a expr="~ href:person.profileUrl+'#bio'; ~ title:'Click me';"></a>` |
700
+ | | Boolean Attribute | `<a expr="~ ?required:formField.required; ~ ?aria-checked: formField.checked"></a>` |
684
701
  | `@` | 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* |
702
+ | `@text` | Plain text content | `<span expr="@text:firstName+' '+lastName;"></span>` |
703
+ | `@html` | Markup content | `<span expr="@html: '<i>'+firstName+'</i>';"></span>` |
704
+ | `@items` | A list, of the following format | `<declaration> <of\|in> <iterable> / <importRef>`<br>*See next two tables* |
688
705
 
689
706
  <details><summary><code>For ... Of</code> Loops</summary>
690
707
 
691
708
  | Idea | Usage |
692
709
  | :---- | :---- |
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>` |
710
+ | A `for...of` loop over an array/iterable | `<ul expr="@items: value of [1,2,3] / 'foo#fragment';"></ul>` |
711
+ | Same as above but with a `key` declaration | `<ul expr="@items: (value,key) of [1,2,3] / 'foo#fragment';"></ul>` |
712
+ | Same as above but with different variable names | `<ul expr="@items: (product,id) of store.products / 'foo#fragment';"></ul>` |
713
+ | Same as above but with a dynamic `importRef` | `<ul expr="@items: (product,id) of store.products / store.importRef;"></ul>` |
697
714
 
698
715
  </details>
699
716
 
@@ -701,14 +718,14 @@ Here, we get the `binding` attribute for a declarative and neat, key/value data-
701
718
 
702
719
  | Idea | Usage |
703
720
  | :---- | :---- |
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>` |
721
+ | A `for...in` loop over an object | `<ul expr="@items: key in {a:1,b:2} / 'foo#fragment';"></ul>` |
722
+ | 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
723
 
707
724
  </details>
708
725
 
709
- <details><summary>Details</summary>
726
+ <details><summary>Resolution details</summary>
710
727
 
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:
728
+ 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
729
 
713
730
  ```js
714
731
  document.bind({ someColor: 'green', someBgColor: 'yellow' });
@@ -722,7 +739,7 @@ document: { someColor: 'green', someBgColor: 'yellow' }
722
739
  └── body: { someBgColor: 'silver' }
723
740
  ```
724
741
 
725
- So, above, the `someBgColor` reference remains bound to the `someBgColor` *binding* on the `<body>` element until the meaning of "closest node" changes again:
742
+ Now, the `someBgColor` reference remains bound to the `someBgColor` *binding* on the `<body>` element until the meaning of "closest node" changes again:
726
743
 
727
744
  ```js
728
745
  delete document.body.bindings.someBgColor;
@@ -734,7 +751,7 @@ While the `someColor` reference remains bound to the `someColor` *binding* on th
734
751
  document.body.bindings.someColor = 'brown';
735
752
  ```
736
753
 
737
- <details>
754
+ </details>
738
755
 
739
756
  <details><summary>All in realtime</summary>
740
757
 
@@ -742,9 +759,9 @@ Bindings are resolved in realtime! And in fact, for lists, in-place mutations -
742
759
 
743
760
  </details>
744
761
 
745
- <details><summary>With SSR Support</summary>
762
+ <details><summary>With SSR support</summary>
746
763
 
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.
764
+ 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
765
 
749
766
  </details>
750
767
 
@@ -752,7 +769,7 @@ For lists, generated item elements are automatically assigned a corresponding in
752
769
 
753
770
  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
771
 
755
- Here, from the same `<script>` element we already write, we get a direct upgrade path to reactive programming in just an attribute: `quantum`:
772
+ 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
773
 
757
774
  ```html
758
775
  <script quantum>
@@ -768,7 +785,7 @@ Here, from the same `<script>` element we already write, we get a direct upgrade
768
785
  </script>
769
786
  ```
770
787
 
771
- **-->** *which adds up really well with the idea of scoping*:
788
+ **-->** *which gives us fine-grained reactivity on top of literal JavaScript syntax; and which adds up really well with the `scoped` attribute*:
772
789
 
773
790
  ```html
774
791
  <main>
@@ -792,7 +809,7 @@ Here, from the same `<script>` element we already write, we get a direct upgrade
792
809
  </main>
793
810
  ```
794
811
 
795
- **-->** *with content being whatever you normally would write in a `<script>` element, but without the "manual" work for reactivity*:
812
+ **-->** *with content being whatever you normally would write in a `<script>` element, minus the "manual" work for reactivity*:
796
813
 
797
814
  ```html
798
815
  <main>
@@ -810,7 +827,7 @@ Here, from the same `<script>` element we already write, we get a direct upgrade
810
827
  </main>
811
828
  ```
812
829
 
813
- **-->** *within which other "live" APIs, like the Namespace API above, fit seamlessly*:
830
+ **-->** *within which dynamic application state/data, and even things like the Namespace API above, fit seamlessly*:
814
831
 
815
832
  ```html
816
833
  <main namespace>
@@ -843,13 +860,13 @@ const removeButton = () => {
843
860
  };
844
861
  ```
845
862
 
846
- <details><summary>Details</summary>
863
+ <details><summary>Learn more</summary>
847
864
 
848
865
  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
866
 
850
867
  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
868
 
852
- Now, in each case above, reactivity terminates on script's removal from the DOM. And a programmatic termination is sill possible:
869
+ Now, in each case above, reactivity terminates on script's removal from the DOM or via a programmatic approach:
853
870
 
854
871
  ```js
855
872
  const script = document.querySelector('script[quantum]');
@@ -863,115 +880,397 @@ But while that is automatic, DOM event handlers bound via `addEventListener()` w
863
880
 
864
881
  ## Data Plumbing
865
882
 
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.
883
+ 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
884
 
871
- └ *The [Observer API](https://github.com/webqit/observer) for general-purpose object observability*:
885
+ <details><summary>Example</summary>
872
886
 
873
887
  ```js
874
- function changeCallback(changes) {
875
- console.log(changes[0].type, changes[0].key, changes[0].value, changes[0].oldValue);
888
+ // Inside a custom element
889
+ connectedCallback() {
890
+ this.prop1 = 1;
891
+ this.prop2 = 2;
892
+ this.prop3 = 3;
893
+ this.style = 'tall-dark'; // ??? - conflict with the standard HTMLElement: style property
876
894
  }
877
895
  ```
878
896
 
879
897
  ```js
880
- const obj = {};
881
- Observer.observe(obj, changeCallback);
898
+ // Outside the component
899
+ const node = document.querySelector('my-element');
900
+ node.prop1 = 1;
901
+ node.prop2 = 2;
902
+ node.prop3 = 3;
903
+ node.normalize = true; // ??? - conflict with the standard Node: normalize() method
904
+ ```
905
+
906
+ </details>
907
+
908
+ This calls for a decent API and some data-flow mechanism!
909
+
910
+ ### The Bindings API
911
+
912
+ 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.
913
+
914
+ **-->** *it's an ordinary JavaScript object that can be read and mutated*:
915
+
916
+ ```js
917
+ // Read
918
+ console.log(document.bindings); // {}
919
+ // Modify
920
+ document.bindings.app = { title: 'Demo App' };
921
+ console.log(document.bindings.app); // { title: 'Demo App' }
922
+ ```
923
+
924
+ ```js
925
+ const node = document.querySelector('div');
926
+ // Read
927
+ console.log(node.bindings); // {}
928
+ // Modify
929
+ node.bindings.style = 'tall-dark';
930
+ node.bindings.normalize = true;
882
931
  ```
883
932
 
933
+ **-->** *with a complementary `bind()` method that lets us make mutations in batch*:
934
+
884
935
  ```js
885
- Observer.set(obj, 'prop1', 'value1'); // Reported synchronously
936
+ // ------------
937
+ // Set multiple properties
938
+ document.bind({ name: 'James Boye', cool: '100%', app: { title: 'Demo App' } });
939
+
940
+ // ------------
941
+ // Replace existing properties with a new set
942
+ document.bind({ signedIn: false, hot: '100%' });
943
+ // Inspect
944
+ console.log(document.bindings); // { signedIn: false, hot: '100%' }
945
+
946
+ // ------------
947
+ // Merge a new set of properties with existing
948
+ document.bind({ name: 'James Boye', cool: '100%' }, { merge: true });
949
+ // Inspect
950
+ console.log(document.bindings); // { signedIn: false, hot: '100%', name: 'James Boye', cool: '100%' }
886
951
  ```
887
952
 
953
+ **-->** *which also provides an easy way to pass data down a component tree*:
954
+
888
955
  ```js
889
- Observer.deleteProperty(obj, 'prop1'); // Reported synchronously
956
+ // Inside a custom element
957
+ connectedCallback() {
958
+ this.child1.bind(this.bindings.child1Data);
959
+ this.child2.bind(this.bindings.child2Data);
960
+ }
890
961
  ```
891
962
 
892
- *A Bindings API for binding application-level state to an object*:
963
+ **-->** *and with the Observer API in the picture for reactivity*:
893
964
 
894
965
  ```js
895
- // Observing document-level bindings
896
- Observer.observe(document.bindings, changeCallback);
966
+ Observer.observe(document.bindings, mutations => {
967
+ mutations.forEach(mutation => console.log(mutation));
968
+ });
969
+ ```
897
970
 
898
- // Set state
899
- document.bindings.userSignedIn = true;
971
+ ```js
972
+ // Inside a custom element
973
+ connectedCallback() {
974
+ Observer.observe(this.bindings, 'style', e => {
975
+ // Compunonent should magically change style
976
+ console.log(e.value);
977
+ });
978
+ }
979
+ ```
900
980
 
901
- // Set data object
902
- document.bindings.data = { prop1: 'value1' };
903
- Observer.set(document.bindings.data, 'prop2', 'value2');
981
+ ```js
982
+ const node = document.querySelector('my-element');
983
+ node.bindings.style = 'tall-dark';
904
984
  ```
905
985
 
986
+ <details><summary>Details</summary>
987
+
988
+ 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:
989
+
906
990
  ```js
907
- // Observing element-level bindings
908
- Observer.observe(element.bindings, changeCallback);
991
+ node.bindings.style = 'tall-dark'; // Reactive assignment
992
+ delete node.bindings.style; // Reactive deletion
993
+ ```
909
994
 
910
- // Set state
911
- element.bindings.isCollapsed = true;
995
+ For mutations at a deeper level to be reactive, the corresponding Observer API method must be used:
912
996
 
913
- // Set data object
914
- element.bindings.data = { prop1: 'value1' };
915
- Observer.set(element.bindings.data, 'prop2', 'value2');
997
+ ```js
998
+ Observer.set(document.bindings.app, 'title', 'Demo App!!!');
999
+ Observer.deleteProperty(document.bindings.app, 'title');
916
1000
  ```
917
1001
 
918
- └ *"Quantum Scripts" for reactive scripting*:
1002
+ </details>
919
1003
 
920
- ```html
921
- <script quantum>
922
- console.log(this) // window
1004
+ ### The Context API
1005
+
1006
+ A complex hierarchy of objects will 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.
1007
+
1008
+ Interestingly, the Context API is the resolution mechanism behind HTML Imports and Data Binding in OOHTML!
1009
+
1010
+ 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.
1011
+
1012
+ **-->** *with the `contexts.request()` method for firing requests*:
1013
+
1014
+ ```js
1015
+ // ------------
1016
+ // Get an arbitrary
1017
+ const node = document.querySelector('my-element');
1018
+
1019
+ // ------------
1020
+ // Prepare and fire request event
1021
+ const requestParams = { kind: 'html-imports', detail: '/foo#fragment1' };
1022
+ const contextResponse = node.contexts.request(requestParams);
923
1023
 
924
- console.log(window.liveProperty) // live expression
925
- console.log(liveProperty) // live expression; technically same as above
1024
+ // ------------
1025
+ // Handle response
1026
+ console.log(contextResponse.value); // It works!
1027
+ ```
1028
+
1029
+ **-->** *and the `contexts.attach()` and `contexts.detach()` methods for attaching/detaching providers at arbitrary levels in the DOM tree*:
926
1030
 
927
- if (document.bindings.userSignedIn) {
928
- // Live block
929
- console.log('User signed in!');
1031
+ ```js
1032
+ // ------------
1033
+ // Define a CustomContext class
1034
+ class FakeImportsContext extends DOMContext {
1035
+ static kind = 'html-imports';
1036
+ handle(event) {
1037
+ console.log(event.detail); // '/foo#fragment1'
1038
+ event.respondWith('It works!');
930
1039
  }
931
- </script>
1040
+ }
1041
+
1042
+ // ------------
1043
+ // Instantiate and attach to a node
1044
+ const fakeImportsContext = new FakeImportsContext;
1045
+ document.contexts.attach(fakeImportsContext);
1046
+
1047
+ // ------------
1048
+ // Detach anytime
1049
+ document.contexts.detach(fakeImportsContext);
932
1050
  ```
933
1051
 
1052
+ <details><summary>Details</summary>
1053
+
1054
+ In the current OOHTML implementation, the Context API interfaces are exposed via the global `webqit` object:
1055
+
934
1056
  ```js
935
- Observer.set(window, 'liveProperty'); // Live expressions rerun
1057
+ const { DOMContext, DOMContextRequestEvent, DOMContextResponse } = window.webqit;
936
1058
  ```
937
1059
 
938
- ```html
939
- <div>
940
- <script quantum scoped>
941
- console.log(this) // div
1060
+ In addition...
1061
+
1062
+ + a provider will automatically adopt the `contextname`, if any, of its host element:
942
1063
 
943
- console.log(this.liveProperty) // live expression
1064
+ ```html
1065
+ <div contextname="context1"></div>
1066
+ ```
944
1067
 
945
- if (this.bindings.isCollapsed) {
946
- // Live block
947
- console.log('Section collapsed!');
1068
+ ```js
1069
+ // Instantiate and attach to a node
1070
+ const host = document.querySelector('div');
1071
+ const fakeImportsContext = new FakeImportsContext;
1072
+ host.contexts.attach(fakeImportsContext);
1073
+ // Inspect name
1074
+ console.log(fakeImportsContext.name); // context1
1075
+ ```
1076
+
1077
+ ...which a request could target:
1078
+
1079
+ ```js
1080
+ const requestParams = { kind: FakeImportsContext.kind, targetContext: 'context1', detail: '/foo#fragment1' };
1081
+ const contextResponse = node.contexts.request(requestParams);
1082
+ ```
1083
+
1084
+ + and providers of same kind could be differentiated by an extra "detail" - an arbitrary value passed to the constructor:
1085
+
1086
+ ```js
1087
+ const fakeImportsContext = new FakeImportsContext('lorem');
1088
+ console.log(fakeImportsContext.detail); // lorem
1089
+ ```
1090
+
1091
+ + and a provider could indicate to manually match requests where the defualt "kind" matching, and optional "targetContext" matching, don't suffice:
1092
+
1093
+ ```js
1094
+ // Define a CustomContext class
1095
+ class CustomContext extends DOMContext {
1096
+ static kind = 'html-imports';
1097
+ matchEvent(event) {
1098
+ // The default request matching algorithm + "detail" matching
1099
+ return super.matchEvent() && event.detail === this.detail;
1100
+ }
1101
+ handle(event) {
1102
+ console.log(event.detail);
1103
+ event.respondWith('It works!');
1104
+ }
948
1105
  }
949
- </script>
1106
+ ```
1107
+
1108
+ + and a request could choose to stay subscribed to changes on the requested data; the request would simply add a `live` flag:
1109
+
1110
+ ```js
1111
+ // Set the "live" flag
1112
+ const requestParams = { kind: FakeImportsContext.kind, targetContext: 'context1', detail: '/foo#fragment1', live: true };
1113
+ ```
1114
+
1115
+ ...then stay alert to said updates on the returned `DOMContextResponse` object or specify a callback function at request time:
1116
+
1117
+ ```js
1118
+ // Handle response without a callback
1119
+ const contextResponse = node.contexts.request(requestParams);
1120
+ console.log(contextResponse.value); // It works!
1121
+ Observer.observe(contextResponse, 'value', e => {
1122
+ console.log(e.value); // It works live!
1123
+ });
1124
+ ```
1125
+
1126
+ ```js
1127
+ // Handle response with a callback
1128
+ node.contexts.request(requestParams, value => {
1129
+ console.log(value);
1130
+ // It works!
1131
+ // It works live!
1132
+ });
1133
+ ```
1134
+
1135
+ ...while provider simply checks for the `event.live` flag and keep the updates flowing:
1136
+
1137
+ ```js
1138
+ // Define a CustomContext class
1139
+ class CustomContext extends DOMContext {
1140
+ static kind = 'html-imports';
1141
+ handle(event) {
1142
+ event.respondWith('It works!');
1143
+ if (event.live) {
1144
+ setTimeout(() => {
1145
+ event.respondWith('It works live!');
1146
+ }, 5000);
1147
+ }
1148
+ }
1149
+ }
1150
+ ```
1151
+
1152
+ ...and optionally implements a `subscribed` and `unsubscribed` lifecycle hook for when a "live" event enters and leaves the instance:
1153
+
1154
+ ```js
1155
+ // Define a CustomContext class
1156
+ class CustomContext extends DOMContext {
1157
+ static kind = 'html-imports';
1158
+ subscribed(event) {
1159
+ console.log(this.subscriptions.size); // 1
1160
+ }
1161
+ unsubscribed(event) {
1162
+ console.log(this.subscriptions.size); // 0
1163
+ }
1164
+ handle(event) {
1165
+ event.respondWith('It works!');
1166
+ if (event.live) {
1167
+ setTimeout(() => {
1168
+ event.respondWith('It works live!');
1169
+ }, 5000);
1170
+ }
1171
+ }
1172
+ }
1173
+ ```
1174
+
1175
+ + live requests are terminated via the returned `DOMContextResponse` object...
1176
+
1177
+ ```js
1178
+ contextResponse.abort();
1179
+ ```
1180
+
1181
+ ...or via an initially specified custom `AbortSignal`:
1182
+
1183
+ ```js
1184
+ // Add a signal to the original request
1185
+ const abortController = new AbortController;
1186
+ const requestParams = { kind: FakeImportsContext.kind, targetContext: 'context1', detail: '/foo#fragment1', live: true, signal: abortController.signal };
1187
+ ```
1188
+
1189
+ ```js
1190
+ abortController.abort(); // Which also calls contextResponse.abort();
1191
+ ```
1192
+
1193
+ + 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:
1194
+
1195
+ ```js
1196
+ document: // 'fake-provider' here
1197
+ └── html
1198
+ ├── head
1199
+ └── body: // 'fake-provider' here. Our request above is now served from here.
1200
+ ```
1201
+
1202
+ 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.
1203
+
1204
+ ```js
1205
+ document: // 'fake-provider' here. Our request above is now served from here.
1206
+ └── html
1207
+ ├── head
1208
+ └── body:
1209
+ ```
1210
+
1211
+ While, in all, saving the requesting code that "admin" work!
1212
+
1213
+ </details>
1214
+
1215
+ **-->** *all of which gives us the imperative equivalent of context-based declarative features like HTMLImports and Data Binding*:
1216
+
1217
+ ```html
1218
+ <div contextname="vendor1">
1219
+ <div contextname="vendor2">
1220
+ ...
1221
+
1222
+ <my-element>
1223
+ <!-- Declarative import -->
1224
+ <import ref="@vendor1/foo#fragment1"></import>
1225
+ <!-- Declarative Data Binding -->
1226
+ <?{ @vendor2.app.title }?>
1227
+ </my-element>
1228
+
1229
+ ...
1230
+ </div>
950
1231
  </div>
951
1232
  ```
952
1233
 
953
1234
  ```js
954
- Observer.set(element, 'liveProperty'); // Live expressions rerun
1235
+ // ------------
1236
+ // Equivalent import() approach
1237
+ const contextResponse = myElement.import('@vendor1/foo#fragment1');
1238
+
1239
+ // ------------
1240
+ // Equivalent Context API approach
1241
+ const requestParams = { kind: 'html-imports', targetContext: 'vendor1', detail: 'foo#fragment1' };
1242
+ const contextResponse = myElement.contexts.request(requestParams);
1243
+
1244
+ // ------------
1245
+ // Handle response
1246
+ console.log(contextResponse.value);
955
1247
  ```
956
1248
 
957
- └ [Reactive HTML concepts](#)
1249
+ ```js
1250
+ // ------------
1251
+ // Context API request for bindings
1252
+ const requestParams = { kind: 'bindings', targetContext: 'vendor2', detail: 'app' };
1253
+ const contextResponse = myElement.contexts.request(requestParams);
958
1254
 
959
- -->
1255
+ // ------------
1256
+ // Handle response
1257
+ console.log(contextResponse.value.title);
1258
+ ```
960
1259
 
961
1260
  ## Polyfill
962
1261
 
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.
1262
+ 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
1263
 
965
1264
  <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>
1265
+ └───────── <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
1266
 
968
1267
  ```html
969
- <script src="https://unpkg.com/@webqit/oohtml/dist/main.js"></script>
1268
+ <script src="https://unpkg.com/@webqit/oohtml/dist/main.lite.js"></script>
970
1269
  ```
971
1270
 
972
1271
  └ This is to be placed early on in the document and should be a classic script without any `defer` or `async` directives!
973
1272
 
974
- └ For the Scoped Styles feature, you'd also need something like the [samthor/scoped](https://github.com/samthor/scoped) polyfill (more details below):
1273
+ └ 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
1274
 
976
1275
  ```html
977
1276
  <head>
@@ -985,18 +1284,19 @@ OOHTML is being developed as something to be used today—via a polyfill. This i
985
1284
  └───────── <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
1285
 
987
1286
  ```bash
988
- npm i @webqit/oohtml
1287
+ npm i @webqit/oohtml @webqit/quantum-js
989
1288
  ```
990
1289
 
991
1290
  ```js
992
1291
  // Import
1292
+ import * as Quantum from '@webqit/quantum-js/lite'; // Or from '@webqit/quantum-js'; See implementation notes below
993
1293
  import init from '@webqit/oohtml';
994
1294
 
995
1295
  // Initialize the lib
996
- init.call( window[, options = {} ]);
1296
+ init.call(window, Quantum[, options = {}]);
997
1297
  ```
998
1298
 
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.
1299
+ └ 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
1300
 
1001
1301
  └ But all things "SSR" for OOHTML are best left to the [`@webqit/oohtml-ssr`](https://github.com/webqit/oohtml-ssr) package!
1002
1302
 
@@ -1004,24 +1304,36 @@ init.call( window[, options = {} ]);
1004
1304
 
1005
1305
  <details><summary>Extended usage concepts</summary>
1006
1306
 
1007
- If you'll be going ahead to build a real app to see OOHTML in action, you may want to consider also using:
1307
+ If you'll be going ahead to build a real app with OOHTML, you may want to consider also using:
1008
1308
 
1009
1309
  + the [`@webqit/oohtml-cli`](https://github.com/webqit/oohtml-cli) package for operating a file-based templating system.
1010
1310
 
1011
- + the modest, OOHTML-based [Webflo](https://github.com/webqit/webflo) framework to greatly streamline your application development process!
1311
+ + the modest, OOHTML-based [Webflo](https://github.com/webqit/webflo) framework to greatly streamline your workflow!
1012
1312
 
1013
1313
  </details>
1014
1314
 
1015
1315
  <details><summary>Implementation Notes</summary>
1016
1316
 
1317
+ + **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).)
1318
+
1319
+ 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:
1320
+
1321
+ ```html
1322
+ <head>
1323
+ <script src="https://unpkg.com/@webqit/oohtml/dist/main.js"></script>
1324
+ </head>
1325
+ ```
1326
+
1327
+ └─ <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>
1328
+
1017
1329
  + **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
1330
 
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:
1331
+ 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
1332
 
1021
1333
  ```html
1022
1334
  <head>
1023
1335
  <meta name="scoped-js" content="script.mimeType=some-mime">
1024
- <script async src="https://unpkg.com/@webqit/oohtml/dist/main.js"></script>
1336
+ <script async src="https://unpkg.com/@webqit/oohtml/dist/main.lite.js"></script>
1025
1337
  </head>
1026
1338
  <body>
1027
1339
  <script type="some-mime" scoped>
@@ -1042,16 +1354,6 @@ If you'll be going ahead to build a real app to see OOHTML in action, you may wa
1042
1354
 
1043
1355
  ...still gives the `window` object in the console.
1044
1356
 
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
1357
  + **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
1358
 
1057
1359
  ```html
@@ -1100,7 +1402,7 @@ If you'll be going ahead to build a real app to see OOHTML in action, you may wa
1100
1402
 
1101
1403
  ## Examples
1102
1404
 
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.
1405
+ 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
1406
 
1105
1407
  <details><summary>Example 1: <i>Single Page Application</i><br>
1106
1408
  └───────── </summary>
@@ -1295,11 +1597,11 @@ The following is a hypothetical list page!
1295
1597
 
1296
1598
  <!-- The "items" template -->
1297
1599
  <template def="item" scoped>
1298
- <li><a binding="~href: '/animals#'+name;"><?{ index+': '+name }?></a></li>
1600
+ <li><a expr="~href: '/animals#'+name;"><?{ index+': '+name }?></a></li>
1299
1601
  </template>
1300
1602
 
1301
1603
  <!-- The loop -->
1302
- <ul binding="@items: (name,index) of ['dog','cat','ram'] / 'item';"></ul>
1604
+ <ul expr="@items: (name,index) of ['dog','cat','ram'] / 'item';"></ul>
1303
1605
 
1304
1606
  </section>
1305
1607
  ```