Revisiting the HTML Problem Space and Introducing OOHTML
Oxford Harrison
Posted on January 24, 2024
It's 2024! Is it perhaps time to give attention to HTML?
It's a time when many people are interested in going back to the basics! Even whole teams are looking to make the big return from rethinking best practices! (How we, in fact, got fed such a line remains a mystery!)
But perhaps, no one is talking about the alternative and how still "philosophical" it is to build anything of substance in just HTML, CSS and JS in 2024! In fact, you may contend that a lot still goes unanswered in our web-native playbooks!
I'd agree!
Actually, while it's now possible to streamline your workflow with features like native ES Modules + Import Maps (I can see you on that, Joe Pea), the Shadow DOM (I see that you love that, Passle), CSS nesting, etc., so much is still only philosophical!
For example, more recently, I decided to play the devil's advocate on the subject myself, and, so, threw in an obvious question: how do you do reactivity? (Hoping to hear something intriguing!) But it turns out, we still aren't close! And you're almost always disappointed each time!
It's something we cannot possibly be dogmatic about or evangelize as though there were no limitations when we talk about web-native development. That could make one come across as being out of touch with reality or as being merely dismissive of framework-era innovations!
Web-native development, from one perspective, simply addresses overheads!
That's the whole point about "leveraging native platform capabilities and streamlining your workflow and tooling budget"! — The web-native manifesto.
That's the whole point about native technologies like Web Components, speaking of which:
"One of the best outcomes from delegating our component model to the platform is that, when we do, many things get cheaper. Not only can we throw out the code to create and manage separate trees, browsers will increasingly compete on performance for apps structured this way." — Alex Russel
And should any aspect of that, at some point, begin to go counterproductive, such that you wind up with more boilerplates and homegrown tooling, or more runtime regressions — being oftentimes the counterargument, it should be time again to address overheads, not sugar coat them!
In the spirit of that, here is what I am happy to do today: discuss specific pain points that have yet to be addressed natively and touch each one with the relevant part of a proposal you're sure to be excited about: Object-Oriented HTML (OOHTML)!
OOHTML is a new set of HTML and DOM-level features designed to make the HTML-First + Progressive Enhancement paradigm a viable idea — initially developed out of need at WebQit! If you've ever tried, as much as we have, to make success of the above, you probably would come to the same design and architectural conclusions as below and you might as well be inclined to put your money where your mouth is!
Coincidentally, with vanilla HTML increasingly becoming an attractive option for the modern UI author — amidst a multitude of frameworks, it is a perfect time to revisit the HTML problem space!
This is a long discussion but I can tell you this will be worth your time and contribution! See what's on the agenda:
This isn't something Web Components do today! And I do in fact fear that inventing a WC-only solution to this effect, such that you now have to employ a custom <my-div> element where a regular <div> element would ordinarily suffice, would yet constitute a new form of overhead!
And I'd like to say that what we do in Lit today isn't to me a particularly attractive thing! TL;DR: a runtime solution to a markup-level problem is an absolute non-starter! That's to still miss the essence of a markup language!
You enter a framework like Vue or Svelte and you see something more like the ideal: a markup-based solution — wherein HTML is your first language:
<!-- Vue --><script setup>letcount=0;</script><template><button>Count is: {{ count }}</button></template>
<!-- Svelte --><script>letcount=0;</script><button>Count is: {count}!</button>
Something more HTML-ish now!
You are able to just write markup and progressively add logic, as against going ahead of the problem with a JS-first approach!
Now, you think of this as a native language feature, and you aren't totally mistaking! For that would be us delegating all of the work to the HTML parser and getting to write code that practically hits the ground running:
button.innerHTML='Count is: {count}!';
Which would mean: no compile step; no waiting for some JS dependency to generate the UI!
Introducing: Native Data Binding for HTML
But the Vue/Svelte conventions above aren't where this leads! Not exactly!
First, we do need a binding syntax that upholds, instead of change, the default behaviour of the opening brace { and closing brace } characters in HTML! Whereas these should behave as mere text characters, a new behaviour is introduced above wherein, across the active DOM, every arbitrary text sequence in that form — {count} — is treated as special and automatically transformed. That as a native language feature (i.e. browsers suddenly waking up with this new rule) would be a breaking change to older code that make up a large percentage of the web today:
<body>
Hello {curly braces}!
</body>
element.innerHTML='Hello {curly braces}!';
Consider the dilemma: should raw template tags be default-visible so as to be backwards-compatible with older code? Then, newer code would be having a "flash of unrendered content" and an inadvertent exposure of low-level implementation details! Should they be default hidden, then older code would be broken!
Given how the HTML language currently works, I found a sweet spot with a comment-based approach wherein regular HTML comments get to do the job and double up as dedicated insertion points for application data:
<body>
Hello {curly braces}!
<button>Count is: <?{count}?>!</button>
Or, using the comment syntax you might know: <button>Count is: <!--?{count}?-->!</button></body>
element.innerHTML='Count is: <?{count}?>. Hello {curly braces}!';
Data-binding expressions are now default-hidden while application data becomes available at any timing! And the idea of having a special behaviour for certain text characters now requires an explicit opt-in using a special opening character sequence <? (or the longer equivalent <!--?) and a special closing character sequence ?> (or the longer equivalent ?-->). (Of course, syntax could be improved on where possible!)
This is something I really do like and I found that it resonates with many developers!
We next come to attribute bindings and the first challenge becomes whether or not to support text interpolation within attributes as Svelte would have it?:
<img{src}alt="{name} dances."/>
Heck! That again would be a breaking change in how curly braces are treated within attributes, just as has been considered above!
Also, how do you hydrate server-rendered attributes such that, given the <img> above, the browser still knows to map the src and alt attributes to certain application data after having been rendered to?:
<imgsrc="/my/image.png"alt="John Doe dances."/>
Interestingly, I did set my foot on this path with OOHTML and that turned out messy!
Here is where Vue shines with its idea of having a separate, pseudo attribute do the binding for every given attribute:
<imgv-bind:src="src"v-bind:alt="computedExpr"><!-- Or, for short: <img :src="src" :alt="computedExpr"> -->
which renders to:
<imgv-bind:src="src"v-bind:alt="computedExpr"src="/my/image.png"alt="John Doe dances.">
thus, obviating the trouble with the former approach.
But heck! The attribute bloat... the sheer idea of an extra attribute for every given attribute!
What if we simply had one dedicated attribute for everything wherein we follow a key/value mapping syntax?:
<imgrender="src:src; alt:computedExpr"src="/my/image.png"alt="John Doe dances.">
That would give us a more compact binding syntax, plus the same benefit as with the Vue approach — of having the original binding logic retained, instead of replaced, after having been rendered!
We only now need to extend our syntax to support a wider range of DOM element features like "class list" and "style". For this, we introduce the idea of directives, or special symbols, that add the relevant meaning to each binding:
e.g. the tilde symbol ~ for attribute binding:
<imgrender="~src:src; ~alt:computedExpr"src="/my/image.png"alt="John Doe dances.">
I find this very conventional on the backdrop of existing syntaxes like CSS rules! And we've introduced no breaking changes and no funky attribute names!
But how should bindings be resolved? From an embedded <script> tag in scope as we have above?:
<script>letcount=0;</script><button>Count is: <?{ count }?>!</button>
That idea might be more suited to the Single File Component (SFC) architecture than to the DOM itself where the actual building blocks aren't SFCs but the very DOM nodes themselves!
Given the DOM, it makes more sense for the said JS references in binding expressions to have to do with the state of those DOM nodes:
<button>Count is: <?{ count }?>!</button>
button.count=0;
which also resonates with the object/property paradigm of Custom Elements:
But there goes one subtle challenge: using an element's root namespace for arbitrary state management and potentially conflicting with native methods and properties!
It turns out, a more decent approach would be to have a dedicated state object for this:
button.bindings.count=0;
connectedCallback(){this.bindings.count=0;}
And to make this even more flexible, we don't have to restrict the resolution scope of references to the immediate host elements. A more flexible approach would be to have things resolve from the closest node in scope who exposes said property, such that:
<body><div><div><button>Count is: <?{ count }?>!</button></div></div></body>
This way, it becomes possible for higher-level state (or even the global state that lives at document.bindings) to still be reflected at deeply nested elements in the page without having to mechanically pass data from node to mode down the tree!
Wanna Try?
Simply include the OOHTML polyfill from a CDN!
Polyfill
<head><script src="https://unpkg.com/@webqit/oohtml/dist/main.lite.js"></script><!-- other code --></head>
And here's something to try on a blank document:
<!DOCTYPE html><html><head><script src="https://unpkg.com/@webqit/oohtml/dist/main.lite.js"></script><script>// Global countdocument.bindings.count=0;setInterval(()=>document.bindings.count++,1000);// Local countcustomElements.define('my-counter',classextendsHTMLElement{connectedCallback(){this.bindings.count=0;setInterval(()=>this.bindings.count++,2000);}});</script></head><body><h1>Global count is: <?{ count }?>.</h1><my-counter>Local count is: <?{ count }?>.</my-counter></body></html>
And you could try writing the "Declarative Lists" example in the list of examples!
You may want to visit the Data-Binding section on the project README to learn more!
Introducing: Quantum Reactivity
Data binding isn't all there is to reactivity on the UI!
While you're able to express JavaScript logic within binding tags:
<h1>Global count is: <?{ count }?>.</h1><h2>Global count, doubled, is: <?{ count * 2 }?>.</h2>
you still need a way to write reactive logic in your main application code — i.e. the counter logic itself, above — as things begin to grow and require some form of dependency tracking.
Here's a sampling across frameworks of how our code could look in a reactive programming paradigm:
<!-- Vue --><script setup>import{ref,computed}from"vue";letcount=ref(0);letdoubleCount=computed(()=>count.value*2);</script><template><button>Count is: {{ count }}</button><button>Double count is: {{ doubleCount }}</button></template>
<!-- Svelte --><script>letcount=0;$:doubleCount=count*2;</script><button>Count is: {count}!</button><button>Double count is: {doubleCount}!</button>
<!-- Upcoming Svelte 5 --><script>letcount=$state(0);letdoubleCount=$derived(count*2);</script><button>Count is: {count}!</button><button>Double count is: {doubleCount}!</button>
But it's a whole new paradigm now from the ordinary imperative paradigm you started with! Many "reactive primitives" and much symbolism now, and there isn't a universal syntax for this form of programming!
Svelte 3, in 2019, did try to bring reactivity in vanilla JS syntax, as seen in the middle code above, but that was greatly limited in many ways! And, unfortunately, Svelte is unable to move forward with that idea in v5!
Problem with the above is: that whole idea of manually modelling relationships is an overhead; you're paying a price that need not be paid! Short explainer: you do not need to re-express the dependency graph of your logic when that's a baseline knowledge for any runtime! Plus, you could never do that manually as cheaply, and as accurately as a runtime would!
It turns out, we're able to again delegate this work to the platform and simply enable reactive programming in the ordinary imperative form of JavaScript! Impossible? That is what we sought to explore with the Quantum JavaScript project!
Quantum JS is a runtime extension to JavaScript that lets us write ordinary JavaScript and have it work reactively! Given our markup above, we're able to simply opt-in to reactive programming on the regular <script> element using the quantum attribute:
<script quantum>// Global countletcount=0;letdoubleCount=count*2;setInterval(()=>count++,1000);document.bindings.count=count;document.bindings.doubleCount=doubleCount;</script>
and for the Custom Element definition, we're able to turn our existing connectedCallback() method to a Quantum function using a special quantum flag:
// Local countcustomElements.define('my-counter',classextendsHTMLElement{quantumconnectedCallback(){letcount=0;letdoubleCount=count*2;setInterval(()=>count++,1000);this.bindings.count=count;this.bindings.doubleCount=doubleCount;}});
It's exciting how we practically now have a universal syntax across both reactive and non-reactive code and can now leverage metal-level accuracy and performance! And as a runtime extension to JavaScript, there isn't the "Svelte 3" sort of limitations like relying on symbolism or top-level let declaration, and others! On the contrary, you are able to enjoy the full range of the JS language — conditionals and loops, spread and "rest" syntax sugars, flow control statements, etc.
This Works Today!
Simply include the OOHTML Polyfill and you have a new useful magic right in the browser! You wanna try the "Imperative Lists" example in the list of examples to have a feel!
Polyfill
<head><script src="https://unpkg.com/@webqit/oohtml/dist/main.lite.js"></script><!-- other code --></head>
Let's say: Web Components, or, in other words, Custom Elements and the Shadow DOM — being collectively a powerful mechanism for encapsulating behaviour, structure, and styling!
Cool, but... there are times you aren't thinking about a "custom" element or some special technology called Shadow DOM when you say "component"!
a sampling of people's idea of that term should include:
a distinct unit of behaviour
e.g. a carousel component that enables a certain form of interaction consistently across instances, e.g. rotating through a series of images or content
a distinct structural form
e.g. a card component that models a certain structure consistently across instances, e.g. featuring an image, a title bar, a description line, a summary area
a distinct visual form
e.g. a grid component that presents elements in a certain pattern consistently across instances
This means that it isn't always a Web Components' call! For example, what have many "visual" components, like grid above, got to do with Custom Elements or Shadow DOM? And let's just say, this whole idea of subtree isolation which the Shadow DOM is best for, isn't always the idea for many, many "components"!
Consider, for example, structural components, like card above! I often just want a way to "model" the needed relationships, or, in other words, a way to associate the card's features like image and title bar with the card, and nothing more! (Something people have done for years with a naming convention like BEM!)
which would translate to writing CSS selectors that feel "namespaced":
.card{...}.card__title{...}
This means, you more often just want to write modular markup and less often want to opt-in to subtree isolation and a new rendering model!
The ideal authoring experience for HTML, therefore === having a way to do both elegantly: author modular markup by default and let the use case decide when to opt-in to the Shadow DOM!
Introducing: Namespacing
Enter modular markup: we currently have to rely on annoying naming conventions to model relationships. (E.g. BEM, above.) And for the relationships that work with only IDREFs, like the relationship between a <label> and an <input>, and others involving ARIA Relationship Attributes, we are challenged with HTML's global ID system wherein you have to generate IDs that must be unique throughout the document:
This has you thinking globally even when what you're working on has no global relevance! For a fairly-sized document, the level of coordination needed at the global level is often too unrealistic to happen by hand!
But how about a way to keep those relationships local, or implicitly namespaced? Meet the namespace attribute:
Notice how we haven't introduced a breaking change to how IDREFs are resolved in browsers! You need the tilde ~ character to denote "relativity", or "local" resolution.
You're able to use that in selectors to match things within a specified namespace:
fieldset#~address-line{}
document.querySelector('#~address-line');// null; not in the global namespacedocument.getElementById('~address-line');// null; not in the global namespacefieldset.querySelector('#~address-line');// input#address-line; in the fieldset namespace
In JavaScript, you are additionally able to access these elements declaratively using the namespace API:
let{city,...}=fieldset.namespace;
And you get reactivity for free on top of that — in being able to observe DOM changes happening within given namespace:
This is all currently possible using the OOHTML Polyfill!
The "Multi-Level Namespacing" example in the list of examples is something you may want to try!
Polyfill
<head><script src="https://unpkg.com/@webqit/oohtml/dist/main.lite.js"></script><!-- other code --></head>
You may also want to visit the Namespacing section in the project README to learn more!
I again ask that you share your thoughts and leave us a star 🌟!
Introducing: Style and Script Scoping
While namespacing helps us keep IDREFs relative, we still need a way to keep component-specific style sheets and scripts scoped! This is a common idea across frameworks!
Here, we are able to do that using a new scoped attribute:
<div><style>@scope{p{color:red;}}</style><p>this is red</p></div><p>not red</p>
But here's the deal with the attribute-based approach:
Free of an extra nesting level!
Consistent with the syntax for two other things in the scoping agenda: scoped scripts — <script scoped> — and scoped HTML modules — <template scoped> — as we'll see shortly!
Presents one way to interpret a <style> element: as either "entirely scoped" or "entirely unscoped"; which isn't the case with the @scope rule approach:
Notice how the latter requires you to parse the CSS source text itself to know what's going on with a <style> element, vs how the the former removes the guesswork at the attribute level!
Presents us an opportunity to have scoped style sheets that can truly make no global footprint, as in, this time, map to their immediate host element instead of to the document:
// Given that the scoped style sheet really has no global relevanceconsole.log(div.styleSheets.length);// 1// Only the second style sheet making a global footprintconsole.log(document.styleSheets.length);// 1
That said, here's how the scoped attribute works for the <style> element:
the :scoped pseudo selector is treated as a reference the style sheet's host element
the relative ID selector, e.g. #~child, is resolved within given namespace:
Code
<divnamespace><pid="child"></p><divnamespace><pid="child"></p></div><style scoped>/* matches only the first "p" */#~child{color:red;}/* matches all "p" as usual */#child{background-color:whitesmoke;}</style></div>
where multiple identical scoped styles exists, you are able to add an (experimental) shared directive, as in <style scoped shared>, to get only the first instance processed and shared by all host as an adopted style sheet, to optimise performance:
And we'd like for you to share your thoughts and leave us a star 🌟!
Question 3: How Do You Re-Use Components?
A component architecture is a way to organise code! That involves being able to work on disparate pieces of an idea and have them automatically come together! It's about the biggest need frameworks fill! Now, something like that in the HTML/DOM land is the <defs> and <use> system in SVG!
You could mention the <template> element which affords us a place to define reusable pieces of markup. But the <template> element is really only half the idea of a templating system; the remaining half left out to imperative DOM APIs:
This has meant doing half of the work in HTML and half in JS!
We do have the HTML Modules proposal, but that's still a JavaScript feature, not HTML!
But here's what I have always wanted: a simple define-and-use system in HTML, just as with the SVG <defs> and <use> concept!
Introducing: HTML Imports
HTML Imports is a system for templating and reusing objects - in both declarative and programmatic terms! It extends the language with a definition attribute — def, complements that with a new <import> element, and has everything working together as a real-time module system!
Here, we get a way to both define and reuse a snippet within same document, exactly as with the SVG <defs> and <use> system we've always had:
You use the def attribute to expose a <template> element, and optionally, its direct children, as definition:
<head><templatedef="foo"><divdef="fragment1">A module fragment that can be accessed independently</div><divdef="fragment2">Another module fragment that can be accessed independently</div><p>An element that isn't explicitly exposed.</p></template></head>
And you are able to nest modules nicely for code organisation:
And you can lazy-load modules using the loading="lazy" directive; meaning that loading doesn't happen until the first attempt to access the given model:
Just as with scoped styles and scripts above, you are able to scope <template> elements to create an object-scoped module system:
Notice how paths are resolved as to whether globally or relatively.
<section><!-- module host --><templatedef="foo"scoped><!-- Scoped to host object and not available globally --><divdef="fragment1"></div></template><div><importref="foo#fragment1"></import><!-- Relative path (beginning without a slash), resolves to the local module: foo#fragment1 --><importref="/foo#fragment1"></import><!-- Absolute path (beginning with a slash), resolves to the global module: /foo#fragment1 --></div></section>
Paths are also resolved the same way on the HTMLImports API:
// Showing relative path resolutiondocument.querySelector('div').import('foo#fragment1',divElement=>{console.log(divElement);// the local module: foo#fragment1});
// Showing absolute path resolutiondocument.querySelector('div').import('/foo#fragment1',divElement=>{console.log(divElement);// the global module: foo#fragment1});
Consider:
Custom elements can now, sometimes, be designed to have their logic decoupled from markup:
<my-element><!-- module host --><templatedef="foo"scoped><!-- Scoped to host object and not available globally --><divdef="fragment1"></div></template></my-element>
and in JS:
customElements.define('my-element',classextendsHTMLElement{connectedCallback(){const{value:divElement}=this.import('foo#fragment1');// the local module: foo#fragment1this.shadowRoot.appendChild(divElement.cloneNode());}});
Cool Yet?
This is something you may find really interesting! You'll find that there's a whole lot that a declarative define-and-use system in HTML can change in your daily workflow!
Simply grab the OOHTML polyfill and game on with some of the examples!
Polyfill
<head><script src="https://unpkg.com/@webqit/oohtml/dist/main.lite.js"></script><!-- other code --></head>
Once you're comfortable with the basics, you may also want to visit the rest of the story in the HTML Imports section in the project README!
And as before, we'd like for you to share your thoughts and leave us a star 🌟!
Question 4: How Do You Pass Data/Props?
In a hierarchy of components, parent components often need to pass data down to child components or, in some scenarios, just expose certain data for consumption by anyone, including child components!
On the first scenario, frameworks that do string concatenation for rendering, e.g. Lit, often let you do parent-to-child data-passing via markup/data interpolation, and everything together is parsed into what becomes the DOM:
// Example in Litrender(){returnhtml`
<h1>Parent Component</h1>
<!-- use the dot (.) syntax to pass data as a property -->
<child-component .data=${this.childData}></child-component>
`;}
By contrast, data-passing on an already constructed DOM tree is really what we're talking about when it comes to the DOM!
Interestingly, you could do something as simple as setting arbitrary properties directly on child elements from within a parent element:
// Inside a custom elementconnectedCallback(){constchild=this.querySelector('#child');child.data=this.childData;}
Only that this isn't very elegant, given that, again, we would be littering an element's root namespace with application data and potentially conflicting with native DOM properties and methods! This is to say, we need a better idea around here!
Introducing: The Bindings API
This is a simple, read/write, data object exposed on the document object and on DOM elements as a bindings property:
This is the same Bindings API we introduced in the data-binding section! But it happens to be designed for arbitrary state management - which fits right in with our case:
// Notice our use of the Bindings API connectedCallback(){constchild=this.querySelector('#child');child.bindings.data=this.bindings.childData;}
And above, if we went further to take advantage of the namespace API, we'd be able to access the said child elements declaratively, as against having to imperatively query the DOM:
// Notice our use of the Namespace API connectedCallback(){this.namespace.child.bind(this.bindings.childData);}
or...
// We could destructure the above for a more beautiful codeconnectedCallback(){let{child}=this.namespace;let{childData}=this.bindings;child.bind(childData);}
Now as a perk, we get reactivity for free on all of the moving parts above: a way to observe when this.bindings.childData changes and a way to observe when this.namespace.child changes:
// Notice the observersconnectedCallback(){constbind=(child,data)=>child.bind(data);// bind current valuesbind(this.namespace.child,this.bindings.childData);// Bind new values on changeObserver.observe(this.bindings,'childData',(e)=>bind(this.namespace.child,e.value));Observer.observe(this.namespace,'child',(e)=>bind(e.value,this.bindings.childData));}
Better yet, we are able to have that same reactivity happen declaratively if we implemented our connectedCallback() method as quantum function:
// Notice the double starquantumconnectedCallback(){let{child}=this.namespace;let{childData}=this.bindings;child.bind(childData);}
And when not dealing with class instances, we are able to achieve the same logic, and same reactivity, using a scoped, quantum script:
You'll find that it makes it easier to reason about state management in the DOM, and provides a better way to write Web Components!
Simply include the OOHTML polyfill from a CDN and game on!
Polyfill
<head><script src="https://unpkg.com/@webqit/oohtml/dist/main.lite.js"></script><!-- other code --></head>
And here's one take-home idea for a Single Page Application: the code below, wherein on each navigation to a URL, data is programmatically fetched (via fetch()), then JSONed to a JavaScript object and held as global state at document.bindings (which you're able to directly render anywhere in the page):
<!DOCTYPE html><html><head><script src="https://unpkg.com/@webqit/oohtml/dist/main.lite.js"></script><script>asyncfunctionroute(){constdata=awaitfetch(`http://localhost:3000/api/${location.hash.substring(1)}`).then(response=>response.json());document.bind({pageTitle:data.pageTitle});}window.addEventListener('hashchange',route);</script></head><body><h1>Page title is: <?{ pageTitle }?>.</h1></body></html>
(Now, you may fuse that with the "Single Page Application" example in the list of examples to implement individual page layouts for each URL!)
While we're able to explicitly bind data on DOM nodes using the Bindings API, giving parents a way to pass data down to child components, sometimes the goal is to just expose certain data at a certain level in the DOM tree for consumption by anyone, including child components! This time, instead of the below:
Now, while we've used the immediate parent node for our data source above, it is sometimes impossible to predetermine the exact location up the tree to find said data, in which case, the idea of walking up the DOM tree mechanically - this.parentNode.parentNode... - not only creates a tight coupling between components, but also fails quickly!
That's where a Context API comes in!
You'd find the same philosophy with React, and something similar with Lit, and perhaps something of same sort with other frameworks!
In our case, we simply leverage the DOM's existing event system to fire a "request" event and let an arbitrary "provider" in context fulfil the request. All of this is made available via an API named contexts, exposed on the document object and on DOM elements!
Here, we get a contexts.request() method for firing requests:
// ------------// Get an arbitraryconstnode=document.querySelector('my-element');// ------------// Prepare and fire request eventconstrequestParams={kind:'html-imports',detail:'/foo#fragment1'};constresponse=node.contexts.request(requestParams);// ------------// Handle responseconsole.log(response.value);// It works!
and a contexts.attach() and contexts.detach() methods for attaching/detaching providers at arbitrary levels in the DOM tree:
// ------------// Define a CustomContext classclassFakeImportsContextextendsDOMContext{statickind='html-imports';handle(event){console.log(event.detail);// '/foo#fragment1'event.respondWith('It works!');}}// ------------// Instantiate and attach to a nodeconstfakeImportsContext=newFakeImportsContext;document.contexts.attach(fakeImportsContext);// ------------// Detach anytimedocument.contexts.detach(fakeImportsContext);
And everything comes as one standardized API for looking up the document context for any use case! In fact, the Context API is integral to the Namespace API and the Data Binding and HTML Imports features in OOHTML!
Now, we are able to use the Context API to retrieve the said "binding" in our parent-child scenario earlier:
It goes without saying that this too is possible today using the OOHTML polyfill!
Polyfill
<head><script src="https://unpkg.com/@webqit/oohtml/dist/main.lite.js"></script><!-- other code --></head>
Take that as an invitation to explore and share your thoughts! And we'll be more than delighted to have you leave us a star 🌟!
That's now a wrap!
But that's probably a lot to unpack! Well, think of OOHTML as not one, but a suite of small, interrelated proposals with one agenda: towards a more dynamic, object-oriented HTML! While features may be discussed or explored individually, the one agenda helps us stay aligned with the original problem!
As with everything on the web, your contribution is how this gets better!
The polyfill is actively developed and kept in sync with the spec. You'll find that it goes a long way to help us not think in a vacuum! And it's gone as far as help us build interesting internal apps! (And that's a go ahead to give yours a shot! Not to mean that there aren't bugs there waiting to be discovered!)
Object-Oriented HTML (OOHTML) is a set of features that extend standard HTML and the DOM to enable authoring modular, reusable and reactive markup - with a "buildless" and intuitive workflow as design goal! This project revisits the HTML problem space to solve for an object-oriented approach to HTML!
Building Single Page Applications? OOHTML is a special love letter! Writing Web Components? Now you can do so with zero tooling! Love vanilla HTML but can't go far with that? Well, now you can!
Versions
This is documentation for OOHTML@4. (Looking for OOHTML@1?)