Templating in HTML
kittygiraudel.com
kittygiraudel.com
That's why my team made the lit-html library, which uses JS tagged template literals to make <template> elements for you, clone them and interpolate data where the JS expressions are, and then update the result really, really fast with new data:
import {render, html} from 'https://unpkg.com/lit?module';
let count = 0;
function increment() {
count++;
renderCount();
}
function renderCount() {
render(html`
<p>The count is ${count}</p>
<button @click=${increment}>Increment</button>
`, document.body);
}
renderCount();
You can try that out here: https://lit.dev/playground/#gist=1eff9baed1251fc60dd7da8b7f9...We're also working with Apple on a proposal called Template Instantiation which brings interpolations and updates into HTML itself (though the pandemic and things really stalled that work for the moment).
[1] https://lit.dev/docs/v1/lit-html/introduction/ [2] https://lit.dev/docs/libraries/standalone-templates/
The organization of the lit-html and lit-element packages didn't change: they're separate and lit-element depends on lit-html. The only difference is that we added the lit package that rolls them both up and we talk about templating in one place in the docs rather than two (lit-html and lit-element used to have separate sites). We did this because most people used lit-element and the separation was confusing to some and a little bit of rough DX for most.
https://github.com/WICG/webcomponents/blob/gh-pages/proposal...
Perhaps there are more recent versions.
I liked the spirit of the proposal, but never studied it.
But I really wish there were an HTML-native way to load <template> from separate files, the same way we do with CSS and JS. Not a show-stopper, but it’d be very nice to have.
Seems like the JS folks blocked this from happening. It's weird to me to think we've arrived at the point where JS considerations have come to dominate for something that was built to be a document sharing platform.
[1]: https://github.com/WICG/webcomponents/blob/gh-pages/proposal... [2]: https://web.dev/css-module-scripts/
spankaalee: not true, they were removed because.... they were't compatible with JS
---
goatlover: JS considerations have come to dominate for something that was built to be a document sharing platform.
spankalee: here are two proposals which are... Javascript-only, don't work without javascript, and add more Javascript to the platform
---
¯\_(ツ)_/¯
Not JS. Chrome, and their decade-old crusade to make web components happen, no matter the cost or common sense
...but it should. It's shocking to me that we've had the modern paradigm of client-side-rendering frameworks for over a decade now, without so much as an RFC for any kind of native support.
For the sake of render performance, bundle size, removing a need for transpilation, compatibility across frameworks. There are a million reasons this should be happening in the browser at this point. I'm sure it's a complicated standard to come up with (for one: HTML-based vs JS-based rendering), but why does it feel like nobody's even trying?
For server-side rendering, SGML (and also some other template "engines") provide HTML-aware, type-checked, parametric macro expansion. Actually, SGML templating works transparently on the client side and the server side.
Within the browser OTOH, there's already JavaScript, making every dynamic feature added to HTML inessential for better or worse, like it has for over 25 years now. That's just how it was decided a long time ago, and adding half-assed features to HTML like the template element (which requires JavaScript, in turn, thus doesn't add any essential capability) all the time is exactly the thing we shouldn't be doing when the damn "web stack" is already bloated as fuck.
I didn't expect the paragraph to end that way. I would write it:
Let’s start with the fact that <template> do not enabling you to do anything that’s not possible otherwise. In that way, it’s more of a convenience tool really. If you have significant HTML structures that need to be injected at runtime, you can use "display:none" to hide the HTML structure, then copy node to make and show a copy, for example a dialog box
Anyway, that's how I've been doing it. If <template> has some advantages, I'll start using it
- The elements in it are parsed into a different document and are inert until cloned or adopted into the main document. Images don't load, scripts don't run, styles don't apply, etc. This is very important.
- The content model validation is turned off, so a <template> can contain a <td>
- Mutation observers and events are not fired for parsed content.
- The <template> element itself can appear anywhere, including restricted content elements like <table>
- Other HTML processing libraries generally know to ignore <template>, unlike other content just hidden with CSS.
This makes parsing faster and guaranteed to have no side effects, and conveys the correct semantics to the browser and tools.
Also if you need to use the same HTML elsewhere, just copy the innerHTML of such an element and insert anywhere you want to.
Yes, but it's not obvious that it would be made visible in a different place while keeping the original invisible.
<img> inside <template> is not attempted to be loaded because “it presents nothing in rendering”
https://html.spec.whatwg.org/multipage/scripting.html#the-te...
The <dialog> element is now supported by all modern browser. I suggest you use it. It has a lot of niceties over implementing one your self, including capturing the focus, correct announcing to assistive technologies, styling the ::backdrop element, etc.
The alternative is a "display: none" block, but that triggers a calculation of styles, where <template> can never be rendered so it's evaluated (likely in parallel to JS) by the browser without style calculations.
The optimisations lose weight if the <template> tag is added programatically later. For it to be maximally efficient; all of the <template> tags must be inlined in your HTML prior to your application loading. You can play around with loading it asynchronously to ensure nothing blocks.
WebAIM[0]:
> display:none or visibility: hidden
> The content is removed from the visual flow of the page and is ignored by screen readers
[0] https://webaim.org/techniques/css/invisiblecontent/#techniqu...
https://developer.mozilla.org/en-US/docs/Web/Accessibility/A...
aria-hidden is for elements that were rendered but are hidden (it's redundant on elements with a display value of `none`)
Otherwise you probably don't need <template>, in fact they don't handle events the same as the rest of the DOM and will likely cause more pain
https://web.dev/declarative-shadow-dom/
I even tried to build a library around this, but still don’t know how to finish it hahaha.
1. Define Template using <template id="card-template">...</template>
2. Consume Template using <use-template id="card1" refid="card-template">..</use-template>
With substitution variables/text, this feature could be amazing.At which point, are you just re-creating JavaScript?
const $template = function(template) {
// make copy of template content
const root = template.content.cloneNode(true);
// create proxy object for accessing named nodes and root
const obj = {
get $root() {
// after template root is called, remove this getter function
delete obj.$root;
// return root only once
return(root);
}
};
// find all named template nodes and add to proxy object
for (const node of root.querySelectorAll('[data-tmpl-name]'))
obj[node.dataset.tmplName] = node;
// otherwise create template node content wrapped in proxy which
// makes attempts to overwrite properties an error.
return(new Proxy(obj, {
set: () => { throw (new Error(`Attempt to overwrite a template node!`)); }
}));
}
You can use it with something like: <template id="some_id">
<div>
<h3 data-tmpl-name="header"></h3>
<h5 data-tmpl-name="title"></h5>
</div>
<p data-tmpl-name="info"></p>
</template>
And then just: function of_some_sort() {
const t = $template(document.getElementById('some_id'));
t.header.innerHTML = 'The Header';
t.title.innerHTML = 'The title';
t.info.innerHTML = 'More stuff.';
document.body.append(t.$root);
}
This is was the first version I made. It's not hard to get from here
to a version that can fill the template with a data object for you. In
my most recent version, you could do: function of_another_sort() {
document.body.append($template(document.getElementById('some_id'), {
header: 'The Header',
title: ['first title', 'second title'],
info: (elem) => {
elem.setAttribute('functions', 'work');
elem.innerHTML = 'and receive the internal element itself.';
}
}));
}
Arrays duplicate the internal element and make copies of it, objects
recurse into the element building a 'key.path.name' while looking for
data-tmpl-name to replace. Functions get a copy of the element for
more than just innerText/innerHTML replacement. A null or false value
eliminates the internal element, and a true value passes it through
unchanged.If you've ever used the old ruby library Amrita, it's basically that, but for HTML Template elements. You can easily do all this in about 100 lines of JS.
Made a simple gist: https://gist.github.com/vidaj/b27f1140a8ae711c7df2372c4b5cfe... It supports using templates as slot content and updating only the slots you want. Add in some event listeners, and you got basic binding support.
I've recently used that on an interview project I did (https://github.com/pretzelhands/ubiquitous-sniffle) and it surprisingly takes you quite far with very little effort.
The only major annoyance is really manually keeping track of the elements in the DOM and .innerText and .innerHTML-ing everything that needs a dynamic value. But it's manageable if you keep it confined.
> TypeError: path must be absolute or specify root to res.sendFile
when I try to visit it.
https://developer.mozilla.org/en-US/docs/Web/API/Document/cr...
> The template element can have template contents, but such template contents are not children of the template element itself. Instead, they are stored in a DocumentFragment associated with a different Document — without a browsing context — so as to avoid the template contents interfering with the main Document.
[0]: https://html.spec.whatwg.org/multipage/syntax.html#template-...
it's exciting to see web standards (finally) taking direction from web developers rather than corporate interests, despite chrome being so prominent. as a random aside, i'm especially hoping forms get more love, like the common behaviors (datetime entry, combobox, validation/feedback, etc.) that every developer has to wrangle with over and over. and it's great to see things like the <modal> element becoming almost fully usable without js (it still needs to be triggered via js, but can be closed with a method='dialog' form button).
I’m gonna go ahead though and declare frontend validation (as good as) finished. the constraint validation API is amazing to work with (if you are not afraid of intercepting the submit event using JavaScript).
and for validation, i'd like it to be js-less, as it's such an integral part of the form use case. there are recent features that help, like :user-invalid and :focus-within, but it's still far from ideal for default behavior. something as simple as styling labels based on validation state of the input is only possible with the advent of :has(), which is still incomplete/experimental in firefox, but even that's still clunky (there's a combinatorial explosion of possible states to cover, having to consider :disabled, :hover, :required, :focus, etc.).
I've been using <template> for almost a decade.
<script id="app-item-template" type="text/x-custom-template">
I'll definitely have <template> in mind from now on.Thank you!
Any reason you’d write this instead of window.HTMLTemplateElement, which is shorter to type and more efficient to evaluate?