Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions files/en-us/web/api/customelementregistry/get/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,16 @@ customElements.define(
"my-paragraph",
class extends HTMLElement {
constructor() {
let templateContent = document.getElementById("custom-paragraph").content;
const template = document.getElementById("custom-paragraph");
super() // returns element this scope
.attachShadow({ mode: "open" }) // sets AND returns this.shadowRoot
.append(templateContent.cloneNode(true));
.append(document.importNode(template.content, true));
}
},
);

// Return a reference to the my-paragraph constructor
let ctor = customElements.get("my-paragraph");
const ctor = customElements.get("my-paragraph");
```

## Specifications
Expand Down
4 changes: 2 additions & 2 deletions files/en-us/web/api/customelementregistry/getname/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ The name for the previously defined custom element, or `null` if there is no cus
```js
class MyParagraph extends HTMLElement {
constructor() {
let templateContent = document.getElementById("custom-paragraph").content;
const template = document.getElementById("custom-paragraph");
super() // returns element this scope
.attachShadow({ mode: "open" }) // sets AND returns this.shadowRoot
.append(templateContent.cloneNode(true));
.append(document.importNode(template.content, true));
}
}

Expand Down
16 changes: 7 additions & 9 deletions files/en-us/web/api/document/importnode/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,13 @@ browser-compat: api.Document.importNode

{{APIRef("DOM")}}

The {{domxref("Document")}} object's **`importNode()`** method creates a copy of a
{{domxref("Node")}} or {{domxref("DocumentFragment")}} from another document, to be
inserted into the current document later.
The **`importNode()`** method of the {{domxref("Document")}} interface creates a copy of a {{domxref("Node")}} or {{domxref("DocumentFragment")}} from another document, to be inserted into the current document later.

The imported node is not yet included in the document tree. To include it, you need to
call an insertion method such as {{domxref("Node.appendChild", "appendChild()")}} or
{{domxref("Node.insertBefore", "insertBefore()")}} with a node that _is_
currently in the document tree.
The imported node is not yet included in the document tree. To include it, you need to call an insertion method such as {{domxref("Node.appendChild", "appendChild()")}} or {{domxref("Node.insertBefore", "insertBefore()")}} with a node that _is_ currently in the document tree.

Unlike {{domxref("document.adoptNode()")}}, the original node is not removed from its
original document. The imported node is a clone of the original.
Unlike {{domxref("document.adoptNode()")}}, the original node is not removed from its original document. The imported node is a clone of the original.

The {{domxref("Node.cloneNode()")}} method also creates a copy of a node. The difference is that `importNode()` clones the node in the context of the calling document, whereas `cloneNode()` uses the document of the node being cloned. The document context determines the {{domxref("CustomElementRegistry")}} for constructing any custom elements. For this reason, to clone nodes to be used in another document, use `importNode()` on the target document. The {{domxref("HTMLTemplateElement.content")}} is owned by a separate document, so it should also be cloned using `document.importNode()` so that custom element descendants are constructed using the definitions in the current document. See the {{domxref("Node.cloneNode()")}} page's examples for more details.

## Syntax

Expand Down Expand Up @@ -50,6 +46,8 @@ The copied `importedNode` in the scope of the importing document.

## Examples

### Using importNode()

```js
const iframe = document.querySelector("iframe");
const oldNode = iframe.contentWindow.document.getElementById("myNode");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ function displayStatus() {
while (fragmentViewer.hasChildNodes()) {
fragmentViewer.removeChild(fragmentViewer.lastChild);
}
for (entry of fragment.children) {
for (const entry of fragment.children) {
fragmentViewer.appendChild(entry.cloneNode(true));
}
}
Expand Down
30 changes: 26 additions & 4 deletions files/en-us/web/api/htmltemplateelement/content/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,41 @@ browser-compat: api.HTMLTemplateElement.content

{{APIRef("Web Components")}}

The **`HTMLTemplateElement.content`** property returns a
`<template>` element's template contents (a
{{domxref("DocumentFragment")}}).
The **`content`** property of the {{domxref("HTMLTemplateElement")}} interface returns the `<template>` element's template contents as a {{domxref("DocumentFragment")}}. This content's {{domxref("Node/ownerDocument", "ownerDocument")}} is a separate {{domxref("Document")}} from the one that contains the `<template>` element itself — unless the containing document is itself constructed for the purpose of holding template content.

The {{domxref("Node.cloneNode()")}} and {{domxref("Document.importNode()")}} methods both create a copy of a node. The difference is that `importNode()` clones the node in the context of the calling document, whereas `cloneNode()` uses the document of the node being cloned. The document context determines the {{domxref("CustomElementRegistry")}} for constructing any custom elements. For this reason, use `document.importNode()` to clone the `content` fragment so that custom element descendants are constructed using the definitions in the current document, rather than the separate document that owns the template content. See the {{domxref("Node.cloneNode()")}} page's examples for more details.

## Value

A {{domxref("DocumentFragment")}}.

## Examples

### Using importNode() with template content

```js
const templateElement = document.querySelector("#foo");
const documentFragment = templateElement.content.cloneNode(true);
const documentFragment = document.importNode(templateElement.content, true);
// Now you can insert the documentFragment into the DOM
```

### The ownerDocument of template content

For `<template>` elements created in the context of a normal HTML document, the `ownerDocument` of the `content` is a separate, freshly created document:

```js
const template = document.createElement("template");
console.log(template.content.ownerDocument === document); // false
console.log(template.content.ownerDocument.URL); // "about:blank"
```

If the `<template>` element is created in the context of a document that itself was created for the purpose of holding template content, then the `ownerDocument` of the `content` is the same as that of the containing document:

```js
const template1 = document.createElement("template");
const docForTemplate = template1.content.ownerDocument;
const template2 = docForTemplate.createElement("template");
console.log(template2.content.ownerDocument === docForTemplate); // true
```

## Specifications
Expand Down
17 changes: 8 additions & 9 deletions files/en-us/web/api/intersection_observer_api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -600,11 +600,12 @@ To get a feeling for how thresholds work, try scrolling the box below around. Ea
let observers = [];

startup = () => {
let wrapper = document.querySelector(".wrapper");
const wrapper = document.querySelector(".wrapper");
const template = document.querySelector("#boxTemplate");

// Options for the observers

let observerOptions = {
const observerOptions = {
root: null,
rootMargin: "0px",
threshold: [],
Expand All @@ -615,7 +616,7 @@ startup = () => {
// since there will be so many of them (for each percentage
// point).

let thresholdSets = [
const thresholdSets = [
[],
[0.5],
[0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0],
Expand All @@ -629,12 +630,10 @@ startup = () => {
// Add each box, creating a new observer for each

for (let i = 0; i < 4; i++) {
let template = document
.querySelector("#boxTemplate")
.content.cloneNode(true);
let boxID = `box${i + 1}`;
template.querySelector(".sampleBox").id = boxID;
wrapper.appendChild(document.importNode(template, true));
const newBox = document.importNode(template.content, true);
const boxID = `box${i + 1}`;
newBox.querySelector(".sampleBox").id = boxID;
wrapper.appendChild(newBox);

// Set up the observer for this box

Expand Down
58 changes: 40 additions & 18 deletions files/en-us/web/api/node/clonenode/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,16 @@ browser-compat: api.Node.cloneNode

{{APIRef("DOM")}}

The **`cloneNode()`** method of the {{domxref("Node")}} interface
returns a duplicate of the node on which this method was called.
Its parameter controls if the subtree contained in a node is also cloned or not.
The **`cloneNode()`** method of the {{domxref("Node")}} interface returns a duplicate of the node on which this method was called. Its parameter controls if the subtree contained in the node is also cloned or not.

Cloning a node copies all of its attributes and their values,
including intrinsic (inline) listeners. It does _not_ copy event listeners added
using [`addEventListener()`](/en-US/docs/Web/API/EventTarget/addEventListener) or
those assigned to element properties (e.g., `node.onclick = someFunction`).
Additionally, for a {{HTMLElement("canvas")}} element, the painted image is not copied.
By default, cloning a node copies all of its attributes and their values, including event listeners specified via attributes. By setting the `deep` parameter, you can also copy the subtree contained in the node. It does _not_ copy any other internal data, such as event listeners added using [`addEventListener()`](/en-US/docs/Web/API/EventTarget/addEventListener) or `onevent` properties (e.g., `node.onclick = someFunction`), or the painted image for a {{HTMLElement("canvas")}} element.

The {{domxref("Document.importNode()")}} method also creates a copy of a node. The difference is that `importNode()` clones the node in the context of the calling document, whereas `cloneNode()` uses the document of the node being cloned. The document context determines the {{domxref("CustomElementRegistry")}} for constructing any custom elements. For this reason, to clone nodes to be used in another document, use `importNode()` on the target document. The {{domxref("HTMLTemplateElement.content")}} is owned by a separate document, so it should also be cloned using `document.importNode()` so that custom element descendants are constructed using the definitions in the current document.

> [!WARNING]
> `cloneNode()` may lead to duplicate element IDs in a document!
>
> If the original node has an `id` attribute, and the clone
> will be placed in the same document, then you should modify the clone's ID to be
> unique.
> `cloneNode()` may lead to duplicate element IDs in a document! If the original node has an `id` attribute, and the clone will be placed in the same document, then you should modify the clone's ID to be unique.
>
> Also, `name` attributes may need to be modified,
> depending on whether duplicate names are expected.

To clone a node to insert into a _different_ document, use
{{domxref("Document.importNode()")}} instead.
> Also, `name` attributes may need to be modified, depending on whether duplicate names are expected.

## Syntax

Expand Down Expand Up @@ -60,11 +48,45 @@ using {{domxref("Node.appendChild()")}} or a similar method.

## Example

### Using cloneNode()

```js
const p = document.getElementById("para1");
const p2 = p.cloneNode(true);
```

### Using cloneNode() with templates

Avoid using `cloneNode()` on the content of a {{htmlelement("template")}} element, because if the template contains custom elements, they will not be upgraded until they are inserted into the document.

```js
class MyElement extends HTMLElement {
constructor() {
super();
console.log("MyElement created");
}
}
customElements.define("my-element", MyElement);

const template = document.createElement("template");
template.innerHTML = `<my-element></my-element>`;

const clone = template.content.cloneNode(true);
// No log here; my-element is undefined in the template's document
customElements.upgrade(clone);
// Still no log; my-element is still undefined in the template's document
document.body.appendChild(clone);
// Logs "MyElement created"; my-element is now upgraded
```

Instead, use `document.importNode()` to clone the template content, so that any custom elements are upgraded using the definitions in the current document:

```js
const clone = document.importNode(template.content, true);
// Logs "MyElement created"; my-element is upgraded using document's definitions
document.body.appendChild(clone);
```

## Specifications

{{Specifications}}
Expand Down
7 changes: 2 additions & 5 deletions files/en-us/web/api/shadowroot/elementfrompoint/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,9 @@ customElements.define(
class extends HTMLElement {
constructor() {
super();
const templateContent = document.getElementById(
"my-custom-element-template",
).content;
const template = document.getElementById("my-custom-element-template");
const sRoot = this.attachShadow({ mode: "open" });
sRoot.appendChild(templateContent.cloneNode(true));

sRoot.appendChild(document.importNode(template.content, true));
// get the topmost element in the top left corner of the viewport
const srElement = this.shadowRoot.elementFromPoint(0, 0);
// apply a border to that element
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,13 @@ customElements.define(
let templateContent = template.content;

const shadowRoot = this.attachShadow({ mode: "open" });
shadowRoot.appendChild(templateContent.cloneNode(true));
shadowRoot.appendChild(document.importNode(templateContent, true));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For cases like this, I think it's actually fine to use cloneNode because it's immediately appended, no?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be fine, but IMO it's a lot easier to be consistent and use importNode everywhere.

}
},
);
```

The key point to note here is that we append a clone of the template content to the shadow root, created using the {{domxref("Node.cloneNode()")}} method.
The key point to note here is that we append a clone of the template content to the shadow root, created using the {{domxref("Document.importNode()")}} method.

And because we are appending its contents to a shadow DOM, we can include some styling information inside the template in a {{htmlelement("style")}} element, which is then encapsulated inside the custom element.
This wouldn't work if we just appended it to the standard DOM.
Expand Down Expand Up @@ -255,7 +255,7 @@ customElements.define(
"element-details-template",
).content;
const shadowRoot = this.attachShadow({ mode: "open" });
shadowRoot.appendChild(template.cloneNode(true));
shadowRoot.appendChild(document.importNode(template, true));
}
},
);
Expand Down
6 changes: 2 additions & 4 deletions files/en-us/web/api/window/customelements/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,9 @@ customElements.define(
class extends HTMLElement {
constructor() {
super();
const template = document.getElementById(
"element-details-template",
).content;
const template = document.getElementById("element-details-template");
const shadowRoot = this.attachShadow({ mode: "open" }).appendChild(
template.cloneNode(true),
document.importNode(template.content, true),
);
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ customElements.define(

const template = document.getElementById("card-template");
const shadow = this.attachShadow({ mode: "open" });
shadow.appendChild(template.content.cloneNode(true));
shadow.appendChild(document.importNode(template.content, true));

const elementStyle = document.createElement("style");
elementStyle.textContent = `
Expand Down Expand Up @@ -117,12 +117,11 @@ customElements.define(
class extends HTMLElement {
constructor() {
super();
let template = document.getElementById("person-template");
let templateContent = template.content;
const template = document.getElementById("person-template");

const shadowRoot = this.attachShadow({ mode: "open" });

let style = document.createElement("style");
const style = document.createElement("style");
style.textContent =
"div { padding: 10px; border: 1px solid gray; width: 200px; margin: 10px; }" +
"h2 { margin: 0 0 10px; }" +
Expand All @@ -132,7 +131,7 @@ customElements.define(
"::slotted(span) {text-decoration: underline;} ";

shadowRoot.appendChild(style);
shadowRoot.appendChild(templateContent.cloneNode(true));
shadowRoot.appendChild(document.importNode(template.content, true));
}
},
);
Expand Down
18 changes: 9 additions & 9 deletions files/en-us/web/html/reference/elements/template/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@ There are two main ways to use the `<template>` element.

By default, the element's content is not rendered.
The corresponding {{domxref("HTMLTemplateElement")}} interface includes a standard {{domxref("HTMLTemplateElement.content", "content")}} property (without an equivalent content/markup attribute). This `content` property is read-only and holds a {{domxref("DocumentFragment")}} that contains the DOM subtree represented by the template.
This fragment can be cloned via the {{domxref("Node.cloneNode", "cloneNode")}} method and inserted into the DOM.

Be careful when using the `content` property because the returned `DocumentFragment` can exhibit unexpected behavior.
For more details, see the [Avoiding DocumentFragment pitfalls](#avoiding_documentfragment_pitfalls) section below.
The {{domxref("Node.cloneNode()")}} and {{domxref("Document.importNode()")}} methods both create a copy of a node. The difference is that `importNode()` clones the node in the context of the calling document, whereas `cloneNode()` uses the document of the node being cloned. The document context determines the {{domxref("CustomElementRegistry")}} for constructing any custom elements. For this reason, use `document.importNode()` to clone the `content` fragment so that custom element descendants are constructed using the definitions in the current document, rather than the separate document that owns the template content. See the {{domxref("Node.cloneNode()")}} page's examples for more details.

Note that the `DocumentFragment` container itself should not have data attached to it. See the [Data on the DocumentFragment is not cloned](#data_on_the_documentfragment_is_not_cloned) example for more details.

### Declarative Shadow DOM

Expand Down Expand Up @@ -109,15 +109,15 @@ if ("content" in document.createElement("template")) {
const template = document.querySelector("#productrow");

// Clone the new row and insert it into the table
const clone = template.content.cloneNode(true);
const clone = document.importNode(template.content, true);
let td = clone.querySelectorAll("td");
td[0].textContent = "1235646565";
td[1].textContent = "Stuff";

tbody.appendChild(clone);

// Clone the new row and insert it into the table
const clone2 = template.content.cloneNode(true);
const clone2 = document.importNode(template.content, true);
td = clone2.querySelectorAll("td");
td[0].textContent = "0384928528";
td[1].textContent = "Acme Kidney Beans 2";
Expand Down Expand Up @@ -254,7 +254,7 @@ This also focuses the parent element as shown below.

![Screenshot of the code where the element has focus](template_with_focus.png)

## Avoiding DocumentFragment pitfalls
## Data on the DocumentFragment is not cloned

When a {{domxref("DocumentFragment")}} value is passed, {{domxref("Node.appendChild")}} and similar methods move only the _child nodes_ of that value into the target node. Therefore, it is usually preferable to attach event handlers to the children of a `DocumentFragment`, rather than to the `DocumentFragment` itself.

Expand All @@ -280,11 +280,11 @@ function clickHandler(event) {
event.target.append(" — Clicked this div");
}

const firstClone = template.content.cloneNode(true);
const firstClone = document.importNode(template.content, true);
firstClone.addEventListener("click", clickHandler);
container.appendChild(firstClone);

const secondClone = template.content.cloneNode(true);
const secondClone = document.importNode(template.content, true);
secondClone.children[0].addEventListener("click", clickHandler);
container.appendChild(secondClone);
```
Expand All @@ -293,7 +293,7 @@ container.appendChild(secondClone);

Since `firstClone` is a `DocumentFragment`, only its children are added to `container` when `appendChild` is called; the event handlers of `firstClone` are not copied. In contrast, because an event handler is added to the first _child node_ of `secondClone`, the event handler is copied when `appendChild` is called, and clicking on it works as one would expect.

{{EmbedLiveSample('Avoiding_DocumentFragment_pitfall')}}
{{EmbedLiveSample(' Data on the DocumentFragment is not cloned')}}

## Technical summary

Expand Down
Loading