A stand-alone working lightweight version of the W3C Custom Elements specification.
npm install document-register-element
should bring build/document-register-element.js inside the module folder.
Otherwise simply:
<script src="build/document-register-element.js"></script>
on your head element and you should be good to go.
Many thanks to cdnjs for hosting this script. Following an example on how to include it.
<script
src="//cdnjs.cloudflare.com/ajax/libs/document-register-element/0.1.6/document-register-element.js"
>/* W3C Custom Elements */</script>
If you see the first clock ticking, the TL;DR answer is yes.
A basic HTML example page
<!DOCTYPE html>
<html>
<head>
<title>testing my-element</title>
<script src="js/document-register-element.js"></script>
<script src="js/my-element.js"></script>
</head>
<body>
<my-element>
some content
</my-element>
</body>
with the following my-element.js
content
var MyElement = document.registerElement(
'my-element',
{
prototype: Object.create(
HTMLElement.prototype, {
createdCallback: {value: function() {
console.log('here I am ^_^ ');
console.log('with content: ', this.textContent);
}},
attachedCallback: {value: function() {
console.log('live on DOM ;-) ');
}},
detachedCallback: {value: function() {
console.log('leaving the DOM :-( )');
}},
attributeChangedCallback: {value: function(
name, previousValue, value
) {
if (previousValue == null) {
console.log(
'got a new attribute ', name,
' with value ', value
);
} else if (value == null) {
console.log(
'somebody removed ', name,
' its value was ', previousValue
);
} else {
console.log(
name,
' changed from ', previousValue,
' to ', value
);
}
}}
})
}
);
The Polymer framework has a CustomElements under the hood that requires other repositories and a build process that will end up creating 15KB of custom-elements.min.js
that will most likely not even work due a missing ES6 WeakMap that is needed for a big percentage of today browsing users out there.
Adding such polyfill in a reliable way means including this file plus different extra files from SES to make that work and yet you simply wanted to create some reusable DOM element ... so here an alternative!
document-register-element.js is a stand alone polyfill which aim is to support as many browsers as possible, without requiring extra dependencies, without needing a WeakMap
at all, in about 2KB minified and gzipped, around 13KB plain as it is ... strawberry on top, feel free to contribute without signing anything :)
Add if you want dom4 normalizer and you'll find yourself in a modern DOM environment that works reliably with today browsers with an eye always open on performance.
Here also the dedicate blog post to know even more, if interested :-)
The live test page is here, containing all tests as listed in the test file.
The following list of desktop browsers has been successfully tested:
- Chrome
- Firefox
- IE 9 or greater
- Safari
- Opera
The following list of mobile OS has been successfully tested:
- iOS 5.1 or greater
- Android 2.2 or greater
- FirefoxOS 1.1 or greater
- KindleFire 3 or greater
- Windows Phone 7 or greater
- Opera Mobile 12 or greater
- Blackberry OS 7* and OS 10
- webOS 2 or LG TV
- Samsung Bada OS 2 or greater
- NOKIA Asha with Express Browser
The good old BB OS 7 is the only one failing the test with className
which is not notified as attributeChanged
when it's changed. This means BB OS 7 will also fail with id
, however changing id
at runtime has never been a common or useful pattern.
Here a list of gotchas you might encounter when developing CustomElement components.
As described in issue 6 it's not possible to fully inherit a table, input, select, or other special element behaviors.
// This will NOT work as expected
document.registerElement(
'my-input',
{
prototype: Object.create(
HTMLInputElement.prototype
)
}
);
var mi = document.createElement('my-input');
The correct way to properly implement a custom input that will be also backward compatible is the following one:
// This will NOT work as expected
document.registerElement(
'my-input',
{
extends: 'input', // <== IMPORTANT
prototype: Object.create(
HTMLInputElement.prototype
)
}
);
// how to create the input
var mi = document.createElement(
'input', // the extend
'my-input' // the enriched custom definition
);
Another approach is to use just a basic HTMLElement
component and initialize its content at runtime.
document.registerElement(
'my-input',
{
prototype: Object.create(
HTMLElement.prototype,
{
createdCallback: {value: function () {
// here the input
this.el = this.appendChild(
document.createElement('input')
);
}}
}
)
}
);
var mi = document.createElement('my-input');
In this case every method that wants to interact with the input will refer this.el
instead of just this
.
If you change the style property via node.style.cssText
or node.style.backgroundColor = "red"
this change will most likely reflect through node.getAttribute("style")
.
In order to prevent footguns inside attributeChangedCallback
invocations causing potential stack overflows, the style
property has been filtered starting from version 0.1.1
, also reflecting current native implementation where changing this special property won't invoke the callback.
(yes, even using node.setAttribute("style", "value")
that you shouldn't ... just use node.style.cssText = "value"
instead)
I don't think there's any library out there able to bring IE8 to these levels, but the code used in this project is syntactically compatible with this old pal too ... moreover, if anyone would ever manage to bring the deprecated Mutation events API to IE8, being the observer
way more complex to shim, this library should work out of the box!