The other day, when generating PDF document signatures with html2canvas, I was using a
.appendTo() the clone to the document
body before I was done manipulating it. It seemed that none of the API calls that I was making to the Umbrella JS instance were being applied to the clone once the template clone was rendered to the DOM.
To be honest, it's been a looong time since I've worked directly with a
DocumentFragment. Most of my work these days is done in AngularJS and Angular, which provides HTML directives that, more or less, obviate the need for
DocumentFragment usage. As such, the rules around fragments have slowly left my head.
When I was working on the demo the other day, I was thinking about a fragment like it was a collection of arbitrary references. Somewhat akin to an Array of DOM elements. But, that's not what it is at all - it's a light-weight DOM tree. This is a critical distinction because any given DOM node can only exist in one place within a DOM tree at one time. As such, by adding the contents of a fragment to the
body, we are implicitly removing the contents from the fragment.
Again, any given DOM node can only exist in one part of the DOM at a time.
This behavior is explicitly outlined in the
DocumentFragment documentation on MDN:
A common use for
DocumentFragmentis to create one, assemble a DOM subtree within it, then append or insert the fragment into the DOM using
Nodeinterface methods such as
insertBefore(). Doing this moves the fragment's nodes into the DOM, leaving behind an empty
And, this is exactly what was happening to me. Only, since I was using Umbrella JS to access the DOM, it wasn't immediately clear to me that the fragment was suddenly empty. That's because an Umbrella JS instance - like a jQuery instance - will happily work with an empty collection, turning all API methods into no-ops.
<template> and then inject it into the DOM. It contains a single paragraph tag; and, I'm going to try to adjust the
textContent both before and after the injection:
As you can see, I'm wrapping the fragment in an Umbrella JS instance. Then, I'm using the
.find() methods to try and locate the embedded paragraph tag. And, when we run this in the browser, we get the following output:
Notice that the rendered DOM only shows the "Before appending" text, not the "After appending" text. That's because the Umbrella JS wrapper for the fragment was emptied out the moment I appended the fragment to the
body element. As such, the call to:
clone.find( "p" )
... resulted in an empty Umbrella JS collection, which happily turned the
.text( "After appending to body." )
... into a silent no-op.
This only happens because I was re-finding the
p tag via the fragment wrapper. If I had stored a reference to the
p tag directly, this code would have worked as expected.
To be clear, none of this is a bug. Both Umbrella JS and the
DocumentFragment are working just as they are documented. The only error here was in my mental model for how fragments work; and, how they change once injected into the rendered DOM. I'm only writing this up as a means to pound it into my head.
Want to use code from this post? Check out the license.