A JS1K CustomElement utility

Every single year since 2010 there is a JS1K Competition with a specific topic.

I’ve submitted already few times and never won there for at least a couple of reasons:

You cannot really have much in 1024 bytes and yet, beside one demo full of love I’ve posted in 2012, it’s the second time I’m submitting little APIs that can be also used outside the competition.

Still in 2012 it was a Markdown parser, lately published as tinydown module while this year I’ve submitted a zero dependencies and 1022 bytes version of my DOMClass library: I’ve called it dom-class-zero and following you can read its source.

A Simplified CustomElement API

One part of the bigger WebComponents specification is the definition of reusable custom DOM elements.

I’ve written a cross browser, stand-alone, polyfill which is currently used in the AMP Project, yet the amount of repetitive boilerplate needed to create these custom elements probably kept developers away from using it.

The aim of DOMClass, explained and demoed multiple times within this same blog, is to make the creation of a custom element as simple as this:

<!-- anywhere in a generic page -->
<my-element>
  Hello World!
</my-element>

<!-- once as component declaration
     before or after, it doesn't matter, really -->
<script>
var MyElement = DOMClass({
  name: 'my-element',
  stylesheet: '/css/ce/my-elemnt.css'
});
</script>

This is just one way to define custom elements through DOMClass, and it’s already compatible with my JS1K submission … but there’s more!

JS1K DOMClass Zero Features

The stylesheet feature has just recently landed in order to satisfy the apparently neededCSS on demand“ scenario.

There’s a “lazy load once“ technique implemented that will help you keep JS components declaration and logic decoupled from their presentation layer.

The full DOMClass library allow directly CSS declaration within the JS component but since there was no room to bring in restyle dependency I’ve kept it simple.

It is also possible to specify the component name, it’s possible to extend another user defined component (for full extend functionalities the DOMClass library is still the way to go), and last, but not least, all methods provided by the specification have simplified, easy to remember, shortcuts.

var MyComponent = DOMClass({
  // whenever a component is created either
  // via JS or automatically from the HTML
  constructor: function () {},
  // whenever the component is live on the page
  onAttached: function () {},
  // whenever the component is removed/replaced
  onDetached: function () {},
  // whenever an attribute is changed
  // via el.setAttribute or el.removeAttribute
  onChanged: function (attrName, oldVal, newVal) {
    // when oldVal is null, the attribute was created
    // when newVal is null, the attribute was removed
    // when oldVal and newVal are not null, it changed
  }
});

If you go to a new about:blank page, and you copy and paste the JS1K submitted code, you can try to write the following example:

// define your component
var BtnGroup = CE({
  name: 'btn-group',
  stylesheet: 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css',
  constructor: function () {
    this.classList.add('btn-group');
    this.setAttribute('role', 'group');
    this.setupChildren();
  },
  setupChildren: function () {
    Array.prototype.forEach.call(
      this.children,
      function (el) {
        el.classList.add('btn', 'btn-default');
      }
    );
  }
});

// try it out
document.body.innerHTML = ''.concat(
  '<btn-group>',
    '<button>Left</button>',
    '<button>Middle</button>',
    '<button>Right</button>',
  '</btn-group>'
);

The result should be similar to the one showed in this Bootstrap documentation.

Automatic conflict free components name

Having a predefined known name for a component is useful to also have a sperate CSS file pointing at such name, however there are cases where external CSS is not needed and the name is irrelevant.

Taking the previous example, we could have split in more components the very same logic as follow:

// named component
var BtnGroup = CE({
  name: 'btn-group',
  stylesheet: 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css',
  constructor: function () {
    this.classList.add('btn-group');
    this.setAttribute('role', 'group');
    // based on `items` attribute instead of HTML content
    (this.getAttribute('items') || '').split(',').forEach(
      function (value) {
        // create custom elements on demand
        this.appendChild(
          new BtnItem
        ).textContent = value;
      },
      this
    );
  }
});

// JS only component
var BtnItem = CE({
  constructor: function () {
    this.classList.add('btn', 'btn-default');
  }
});

// please note the `items` attribute
document.body.innerHTML =
  '<btn-group items="Left,Middle,Right"></btn-group>';

Using above example we have runtime style and runtime HTML layout on demand.

Extending other components

If there’s one thing that went wrong in the specification is the ability to extend native components: it’s still under discussion, not widely adopted, a bit over-engineered and too much complicated.

This 1024 entry cannot help much there, only DOMClass can, yet it’s possible to extend at least other components or behaviors through the zero version.

var XRectangle = CE({
  name: 'x-rectangle',
  onAttached: function () {
    this.style.cssText = ''.concat(
      'display:block;',
      'width:', parseFloat(this.getAttribute('width')), 'px;',
      'height:', parseFloat(this.getAttribute('height')), 'px;',
      'background-color:', this.getAttribute('bg')
    );
  }
});

//                [  custom   extend  ]
var XSquare = CE({ extends: XRectangle,
  name: 'x-square',
  constructor: function () {
    var size = this.getAttribute('size');
    this.setAttribute('width', size);
    this.setAttribute('height', size);
  }
});

// try it out
document.body.innerHTML = ''.concat(
  '<x-rectangle width=120 height=30 bg=red></x-rectangle>',
  '<x-square size=60 bg=green></x-square>'
);

As Summary

It’s always good to remember that tiny libraries can already achieve a lot and eventually be increased on demand.

It’s also good to remember that gzip or defalate can bring down a library size consistently, so that the full-features DOMClass is around 1.6K anyway and maybe it’s really time to start playing with it 😉

Enjoy other JS1K entries, and moreover, enjoy Custom Elements!

Andrea Giammarchi

Fullstack Web Developer, Senior Software Engineer, Team Leader, Architect, Node.js, JavaScript, HTML5, IoT, Trainer, Publisher, Technical Editor