Hemanth.HM

A Computer Polyglot, CLI + WEB ♥'r.

Custom Elements With ES6

| Comments

Custom Elements play an important role in Web Components spectrum. Here’s a list that talks about why we need them:

  • Define new DOM elements.

  • Extending from other elements.

  • Extending DOM APIs.

  • Providing custom functionality to a single tag.

A custom element is made up of a local name, namespace, prototype and lifecyle callbacks.

After creating the custom element, the element definition is added to a registry using the document.registerElement() method. The lifecycle of a custom element is listed below:

  1. Custom element is created before its definition is registered.

  2. Definition of a custom element is registered.

  3. Custom element instance is created after its definition was registered.

  4. Custom element is inserted into active document.

  5. Custom element is removed from active document.

  6. Custom element’s attribute is created, removed, or modified.

Creating custom element with ES6 is easy using it's class feature, but it's important to note that the only subclassing that works is of your own classes, we can't subclass builtins or platform objects, the issues are about allocation of the object (especially for DOM objects), about passing arguments to the superconstructor (arbitrary arguments) --- DOM objects don't really have callable constructors right now, so until the DOM catches up with ES6 specs we need to deal with these limitations.

Luckily, we could pass the extended element to registerElement because it's implementation looks at the passed object's prototype property!

It's even more important to note that expect IE (that too to a very small extent) none of the browsers have implemented classes and we will have to use transpilers like 6to5 or Google's Traceur.

Ok, enough of theory and drawbacks let's see some code!

Basic boilerplate:

1
2
3
4
5
6
7
8
9
10
11
class CustomElement extends HTMLElement {
    // Fires when an instance of the element is created.
    createdCallback() {};
    // Fires when an instance was inserted into the document.
    attachedCallback() {};
    // Fires when an instance was removed from the document.
    detachedCallback() {};
    // Fires when an attribute was added, removed, or updated.
    attributeChangedCallback(attr, oldVal, newVal) {};
}
document.registerElement('custom-element', CustomElement);

Now, let's try extending the HTMLSpanElement (interface).

1
2
3
4
5
6
class DateSpan extends HTMLSpanElement {
   createdCallback(){
     this.textContent = "Today's date: " + new Date().toJSON().slice(0,10);
   }
}
document.registerElement('date-today', DateSpan);

Transplied code with 6to5 would look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
"use strict";

var _inherits = function (child, parent) {
  child.prototype = Object.create(parent && parent.prototype, {
    constructor: {
      value: child,
      enumerable: false,
      writable: true,
      configurable: true
    }
  });
  if (parent) child.__proto__ = parent;
};

var DateSpan = (function () {
  var _HTMLSpanElement = HTMLSpanElement;
  var DateSpan = function DateSpan() {
    if (_HTMLSpanElement) {
      _HTMLSpanElement.apply(this, arguments);
    }
  };

  _inherits(DateSpan, _HTMLSpanElement);

  DateSpan.prototype.createdCallback = function () {
    this.textContent = "Today's date: " + new Date().toJSON().slice(0, 10);
  };

  return DateSpan;
})();

document.registerElement("date-today", DateSpan);

See <date-today></date-today> working:

Well, it was a silly little custom span element, let's make a better one.

One of favs, <xkcd-img> </xkcd-img> :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class XKCD extends HTMLImageElement {
    constructor(){}
    createdCallback() {
      var xhr = new XMLHttpRequest()
      xhr.open('GET','http://xkcd-imgs.herokuapp.com/')
      xhr.onload = function(data){
        this.style.backgroundImage = `url('${JSON.parse(data.target.response).url}')`;
        this.style.backgroundSize = "contain";
        this.style.backgroundRepeat = "no-repeat";
        this.style.position = "absolute";
        this.style.height = "100%";
        this.style.width = "100%"
      }.bind(this);
      xhr.send(null);
    };
}
document.registerElement('xkcd-img',XKCD);

See it in action:

Well, this is just the start, let's hope to see this grow better and faster...untill then happy hacking! :)

Comments