Custom Elements with Builtin Extends
Brings builtin extends to browsers that already have customElements
(i.e. Safari).
See document-register-element to polyfill upfront all other legacy browsers too.
Please don't file any bug until you've read the Constructor Caveat section, and be sure you use features detection to bring in this polyfill only where needed.
customElements.define(
'my-button',
class MyButton extends HTMLButtonElement {
static get observedAttributes() { return ['color']; }
attributeChangedCallback(name, oldValue, newValue, nsValue) {
this.style.color = newValue;
}
connectedCallback() {
this.addEventListener('click', this);
}
disconnectedCallback() {
this.removeEventListener('click', this);
}
handleEvent(event) {
const next = this.nextElementSibling ||
this.parentNode.appendChild(
document.createElement('div')
);
next.textContent = `${event.type} @ ${new Date}`;
}
},
{'extends': 'button'}
);
Live ES2015 test
Live ES5 test
All Possible Features Detections
This is all it takes to have all the right polyfills in place.
<script>
if (this.customElements) {
try {
customElements.define('built-in', document.createElement('p').constructor, {'extends':'p'});
} catch(_) {
document.write('<script src="//unpkg.com/@ungap/custom-elements-builtin"><\x2fscript>');
}
} else {
document.write('<script src="//unpkg.com/document-register-element"><\x2fscript>');
}
</script>
<script>
</script>
The, still readable, but minified version is here:
<script>
if(this.customElements)
try{customElements.define('built-in',document.createElement('p').constructor,{'extends':'p'})}
catch(s){document.write('<script src="//unpkg.com/@ungap/custom-elements-builtin"><\x2fscript>')}
else
document.write('<script src="//unpkg.com/document-register-element"><\x2fscript>');
</script>
If for some reason your server / header has issues in sending <script>
content:
<script>
if(this.customElements)
try{customElements.define('built-in',document.createElement('p').constructor,{'extends':'p'})}
catch(s){document.write(unescape('%3Cscript%20src%3D%22https%3A//unpkg.com/@ungap/custom-elements-builtin%22%3E%3C/script%3E'))}
else
document.write(unescape('%3Cscript%20src%3D%22https%3A//unpkg.com/document-register-element%22%3E%3C/script%3E'));
</script>
Constructor Caveat
You cannot use the constructor
in any meaningful way if you want to ensure API consistency, including setting properties, which means you cannot also use prop = value
within the class declaration, as it's not possible to reflect those, same as it's not possible to use a constructor.
Create new elements via document.createElement('button', {is: 'my-button'})
but do not use new MyButton
or incompatible browsers will throw right away because they made HTMLButtonElement
and all others not usable as classes.
If you'd like to use CustomElement.new()
instead of new CustomElement()
, have a look at the custom-elements-new utility.
If you need a reliable entry point to setup your custom builtins use the connectedCallback
method instead of the constructor
so you're also sure all attributes are eventually already known and you'll have full control.
Alternatively, use a WeakSet
to optionally invoke a setup.
const initialized = new WeakSet;
const setup = node => {
initialized.add(node);
node.live = true;
};
class MyButton extends HTMLButtonElement {
connectedCallback() {
if (!initialized.has(this))
setup(this);
}
}
You can do the same at the beginning of attributeChangedCallback
.
Compatible with ...
Any engine that supports genuine ES2015 syntax and the following features:
- global
MutationObserver
, customElements
, and Promise
assign
, create
, defineProperties
, and setPrototypeOf
from the Object