Skip to content

Instantly share code, notes, and snippets.

@ducksoupdev
Forked from AdaRoseCannon/HTMLElementPlus.js
Created November 1, 2018 16:24
Show Gist options
  • Save ducksoupdev/84e96e7f8a6f38a57f176bf52fa8747e to your computer and use it in GitHub Desktop.
Save ducksoupdev/84e96e7f8a6f38a57f176bf52fa8747e to your computer and use it in GitHub Desktop.
HTML Element Plus for Web Components

My Custom Elements Reusable Bits

I have extended HTMLElement as HTMLElementPlus to contain the bits of functionality I keep reimplementing.

There is an example usage in the HTML file below. And a Glitch here: Edit on Glitch

Attribute Callbacks

class MyEl extends HTMLElementPlus {...

Provides a callback when all attributes have been parsed, rather than one-by-one. allAttributesChangedCallback useful for waiting to handle all at once.

allAttributesChangedCallback gets called with an object with parsed attributes.

The parser can be set by setting the function static parseAttributeValue(name, value) in the class.

Default attribute values can be provided by setting the static defaultAttributeValue(name) function, so you can provide sensible fallback values.

Query the shadow dom by reference

E.g. an element in the shadow dom: <span ref="foobar"></span> can be queried using this.refs.foobar;

Easy event firing.

Fire an event using this.emitEvent('event-name', {foo: 'bar'});

This can be listed for using, el.addEventListener;

'use strict';
class HTMLElementPlus extends HTMLElement {
static defaultAttributeValue() {
/* the name of the attribute is parsed in as a parameter */
return;
}
static parseAttributeValue(name, value) {
return value;
}
constructor() {
super();
this.refs = new Proxy(
{},
{
get: this.__getFromShadowRoot.bind(this)
}
);
// Gets populated by attributeChangedCallback
this.__attributesMap = {};
this.__waitingOnAttr = (this.constructor.observedAttributes
? this.constructor.observedAttributes
: []
).filter(name => {
if (!this.attributes.getNamedItem(name)) {
this.__attributesMap[name] = this.constructor.defaultAttributeValue(name);
}
return !!this.attributes.getNamedItem(name);
});
// No attributes so update attribute never called.
// SO fire this anyway.
if (this.__waitingOnAttr.length === 0) {
this.allAttributesChangedCallback(this.__attributesMap);
}
}
__getFromShadowRoot(target, name) {
return this.shadowRoot.querySelector('[ref="' + name + '"]');
}
attributeChangedCallback(attr, oldValue, newValue) {
this.__attributesMap[attr] = this.constructor.parseAttributeValue.call(this,
attr,
newValue
);
if (this.__waitingOnAttr.length) {
const index = this.__waitingOnAttr.indexOf(attr);
if (index !== -1) {
// Remove it from array.
this.__waitingOnAttr.splice(index, 1);
}
}
// All attributes parsed
if (this.__waitingOnAttr.length === 0) {
this.allAttributesChangedCallback(this.__attributesMap);
}
}
emitEvent(name, detail) {
this.dispatchEvent(new CustomEvent(name, { detail, bubbles: true }));
}
allAttributesChangedCallback() {}
}
window.HTMLElementPlus = HTMLElementPlus;
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.rawgit.com/webcomponents/webcomponentsjs/edf84e6e/webcomponents-sd-ce.js"></script>
<script src="/html-element-plus.js"></script>
</head>
<body>
<my-el attr1="1" attr3="3" other-attr="foo:bar;"></my-el>
<template id="my-el-template">
<style>
:host pre {
background: lightgrey;
padding: 1em;
}
</style>
<h1>
My El Demo:
</h1>
<code><pre ref="content"></pre></code>
<slot></slot>
</template>
<script>
var myElTemplate = document.querySelector('#my-el-template');
if (window.ShadyCSS) {
window.ShadyCSS.prepareTemplate(myElTemplate, 'my-el');
}
class MyEl extends HTMLElementPlus {
constructor() {
super();
// Attach a shadow root to the element, so that the
// implementation is hidden in a 🎁.
let shadowRoot = this.attachShadow({mode: 'open'});
// Put the content of the template inside the shadow DOM.
this.shadowRoot.appendChild(document.importNode(myElTemplate.content, true));
if (window.ShadyCSS) {
window.ShadyCSS.styleElement(this);
}
}
static get observedAttributes() {
return [
'attr1',
'attr2',
'attr3'
]
}
static defaultAttributeValue(name) {
return 'no value, ' + name;
}
static parseAttributeValue(name, value) {
return Number(value);
}
allAttributesChangedCallback(data) {
this.refs.content.textContent = JSON.stringify(data, null, ' ');
}
}
window.addEventListener('DOMContentLoaded', function() {
customElements.define('my-el', MyEl);
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment