HTML Templates Can Be Mutated Just Like Any Other DOM
I've always thought of the HTML <template>
element as being static. And, if I needed to modify a template's contents, the modification would be performed after the template content was cloned (and before it was injected into the DOM). As such, I've never thought too deeply about the template mechanics themselves. I was, therefore, delightfully surprised this morning when I went to modify a template's content and it just worked—just like any other DOM (Document Object Model) mutation.
Run this demo in my JavaScript Demos project on GitHub.
View this code in my JavaScript Demos project on GitHub.
To demonstrate, I'm going to use Alpine.js to dynamically insert a <template>
element into the DOM with the click of a button. But, I'm also going to use setInterval()
- unrelated to Alpine.js - to alter the contents of said <template>
element:
<!doctype html>
<html lang="en">
<body>
<h1>
HTML Templates Can Be Mutated Just Like Any Other DOM
</h1>
<div x-data="{ isShowing: false }">
<button @click="( isShowing = ! isShowing )">
Toggle Template
</button>
<template x-if="isShowing" class="counter-template">
<p>
Counter: <strong class="counter">0</strong>
</p>
</template>
</div>
<script type="text/javascript" src="../../vendor/alpine/3.13.5/alpine.3.13.5.js" defer></script>
<script type="text/javascript">
var counter = 0;
// Update the contents of the template every second.
setInterval(
() => {
var target = document.querySelector( ".counter-template" )
// The template content represents a detached document fragment that
// provides its own DOM that can be inspected using querySelector().
.content
.querySelector( ".counter" )
;
target.innerText = ++counter;
},
1000
);
</script>
</body>
</html>
As you can see in my JavaScript code, I'm calling the .querySelector()
method twice. The contents of the <template>
element aren't quite in the DOM (depending on how hard you squint)—they're represented in their own Document Fragment. As such, in order to access the <strong>
tag that resides within the <template>
, we must first pass through the .content
property. And, once we're inside the fragment, we can mutate it just like we would any other document object model.
Now, when we run this Alpine.js demo, we can see the template contents updating every second:
As you can see in the browser's Elements tab, the innerText
of the <strong>
tag is being updated every second by our setInterval()
operator. Then, when we use Alpine.js to toggle the template into the DOM, the state of the template at that moment in time is what gets cloned into the web page.
I don't know how I'll use this exactly. But, knowing that a <template>
element's contents can be mutated prior to cloning is definitely something that'll be helpful in an edge-case somewhere.
Want to use code from this post? Check out the license.
Reader Comments
"depending on how hard you squint" made me laugh 🤣
Weird (and interesting) you have to go through the
.content
property though. Wonder why 🤔@Chris,
Yeah, this is where I just don't have the good words to explain it properly; which is partly because I don't understand it 100%. The
.content
property (as described by MDN) says:So, this term, "subtree", makes me think this is most technically part of the DOM - just a "non rendered" subtree 🤔 It's not nuance that I often think about, so I just don't have the good explanation at my fingertips.
@Ben Nadel,
Thanks for that reference (and the link), which I've checked out. But now that just leaves me with another question. It specifically says it's "read-only" and yet, you were able to mutate within. Maybe because it's a sub-element within the contents? IDK, but from the documentation alone...I would not have expected to be able to mutate the template contents.
I guess that's why we (meaning you) experiment, right?!
@Chris,
Great question. I assume all that means is that you can't directly overwrite the
.content
property itself. So, you couldn't, for example, progammatically calldocument.createFragment()
and then try to assign it to a template element. But, I'm just shooting from the hip here 😆Post A Comment — ❤️ I'd Love To Hear From You! ❤️
Post a Comment →