Observe and react to changes in distributed nodes in web components, Shadow DOM v0.
It behaves, and is used similarly to Shadow DOM v1's "slotchange" event.
Just 2.1k gzipped
Shadow DOM v0, unlike Shadow DOM v1, has no specification for watching changes to distributed nodes. Because the support for v1 is not as high as v0 at this point, this library exists to provide the ability to watch distribution changes to content elements in v0, while still working with the webcomponents.js polyfill library.
const contentElement = shadow.getElementById('#content');
contentElement.addEventListener('contentchange', event => {
// React to distributed node changes! Keep reading for details on the event data
});
You can install it from npm
npm install --save content-change
If you're not using package management, you can download a ZIP file and use lib/content-change.min.js
.
The script must be located before web components are imported. If you are using the webcomponents.js
polyfill, it doesn't matter if is located before or after the inclusion of webcomponent.js
.
<script src="yourpath/content-change.min.js"></script>
<!-- now import your web components -->
This library is UMD wrapped and if you are using a module bundler, you have a couple of different options. You can just import the package to the appropriate location, or you can import the only functions available, watch
, and unwatch
.
import ContentChange from 'content-change';
// or just import the watch function
import {watch} from 'content-change';
If the library is directly sourced to the window, it operates on the ContentChange
global namespace (window.ContentChange
).
#Usage Only 1 mutation observer is created per host. No additional MutationObservers are spawned on distributed nodes. Because this library provides functionality that is non-spec, it does not modify the api's and objects of any Shadow DOM behavior.
A simple call inside of either the createdCallback
, or the attachedCallback
is made:
ContentChange.watch(hostElement);
watch
accepts the host element as an argument.
There is a benefit to this approach as you can specify which components are watched, rather than this library attempting to watch all created components.
The final piece is to specify which content
elements should have an event handler. The event is called contentchange
and is triggered on content
elements. This is done exactly like the slotchange
event on the slot
elements in Shadow DOM v1.
// get the content element you want to watch for distributed changes in from the shadow dom
const content = shadow.querySelector('selectorForYourContentElement');
content.addEventListener('contentchange', e => {
console.log(e.detail);
});
You can stop watching a particular web component completely, but this is different from removing the listeners on the content elements. No longer "watching" the component removes the MutationObserver, giving you back some performance.
ContentChange.unwatch(hostElement);
unwatch
accepts the host element as an argument.
Because the event is created with CustomEvent, the information you will look for in the event will be under the detail key:
e.detail
. There are three different event type
's, you can receive:
- nodesAdded
- nodesRemoved
- mutation
The event detail will contain an object that looks like the following:
{
"type": "nodesAdded",
"nodesAdded": [Nodes] // An array of added nodes
}
The event detail will contain an object that looks like the following:
{
"type": "nodesRemoved",
"nodesRemoved": [Nodes] // An array of removed nodes
}
When a currently distributed node has a mutation occur on it, you will receive the following:
{
"type": "mutation",
"mutation": MutationRecord
}
This can be attribute changes, added or removed nodes on the distributed node, characterData changes, etc.
Note: If the mutation causes the node to no longer be distributed, you will receive a nodesRemoved
event instead.
Chrome | Firefox | Safari | IE | Edge | Chrome Android | Mobile Safari |
---|---|---|---|---|---|---|
✓ | ✓ | 7+ | 11 | ✓ | ✓ | ✓ |
I will soon make a small github page so that a live example may be viewed. For now, the contents of the files will be placed here:
A simple web component
<template id="example-component">
<div>I am an example component</div>
<!-- Distribute paragraphs -->
<content select="p"></content>
</template>
<script>
(function () {
var doc = (document._currentScript || document.currentScript).ownerDocument;
var objectPrototype = Object.create(HTMLElement.prototype);
objectPrototype.createdCallback = function () {
var shadow = this.createShadowRoot();
var template = doc.querySelector('#example-component');
shadow.appendChild(template.content.cloneNode(true));
// Watch this components distributed nodes ("this" is currently the host element)
window.ContentChange.watch(this);
var pContent = shadow.querySelector('content[select="p"]');
pContent.addEventListener('contentchange', function (e) {
// "this" is the content element
console.log(this, e.detail);
});
};
document.registerElement('example-component', {
prototype: objectPrototype
});
})();
</script>
The web page that the web component will be rendered on
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Watch Changes in distributed nodes in Shadow DOM v0</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/0.7.23/webcomponents.min.js"></script>
<script src="lib/content-change.js"></script>
<link rel="import" href="example-component.html">
</head>
<body>
<example-component>
<p>This will be a distributed node</p>
</example-component>
<script>
// Manipulate the example-component so we see changes
var example = document.querySelector('example-component');
var p = document.createElement('p');
p.textContent = 'Dynamically created';
// Add to the component at some point
window.setTimeout(function () {
example.appendChild(p);
}, 500);
// Modify a distributed node some point later
window.setTimeout(function () {
p.classList.add('foo');
}, 750);
// Remove a distributed node
window.setTimeout(function () {
example.removeChild(p);
}, 1000);
</script>
</body>
</html>