Web Components
in ES6 ES2015
Andreas Argelius
Nov 18, 2015
Andreas Argelius
Engineer originally from Sweden, recently moved to Japan to work with JavaScript development.
Enjoys studying Japanese, eating good food and drinking beer.
Avid runner currently preparing for Yokohama Marathon.
I help make a service called Monaca.
Monaca is ...
- ... a building and packaging service for hybrid mobile apps.
- ... a Cloud IDE optimized for hybrid app development.
- ... a backend for hybrid apps.
- ... a hybrid app debugger with live-reloading.
I also work on
A hybrid app UI component library
Onsen UI
Why is it called Onsen UI?
- Onsen (温泉) is Japanese for "hot spring".
- Very popular vacation spot. Both among people and monkeys.
- Can also be translated to "spa".
- SPA is used as an abbreviation of "Single Page Application".
Introducing
Onsen UI 2.0 Beta
- Material Design
- Custom Elements
- Written in ES2015
Some of the features are:
AngularJS
React
Custom Elements
<h3>iOS switches</h3>
<ons-switch></ons-switch>
<ons-switch checked></ons-switch>
<ons-switch checked modifier="green"></ons-switch>
<h3>Material Design switches</h3>
<ons-switch modifier="material"></ons-switch>
<ons-switch modifier="material" checked></ons-switch>
All components are custom tags:
What is
Web Components?
- Custom Elements
- Shadow DOM
- HTML templates
- HTML imports
Actually Onsen UI is only using the Custom Elements API.
A collection of technologies including...
Creating
Custom Elements
Elements are defined using
document.registerElement(name, options)
It's a very simple API.
Example
var proto = Object.create(HTMLElement.prototype);
proto.createdCallback = function() {
this.innerHTML = 'The Force will be with you.';
setInterval (function () {
this.innerHTML = this.innerHTML ===
'Always.' ? 'The Force will be with you.' : 'Always.';
}.bind(this), 2000);
};
document.registerElement('my-fancy-element', {prototype: proto});
Lifecycle callbacks
-
createdCallback()
-
attachedCallback()
-
detachedCallback()
-
attributeChangedCallback()
There are four different lifecycle callbacks:
Web Components in the wild
GitHub is using the Custom Elements API.
Browser support
From MDN:
Only supported in Chrome!
Polyfill: github.com/webcomponents/webcomponentsjs
The polyfill the Huge!
- The current version is 115KB minified.
- The Shadow DOM polyfill requires 70KB.
- We decided to use only use the Custom Elements API in Onsen UI. It's 16.4KB.
We have our reasons...
- Too big. Far too big.
- HTML imports doesn't fit into build pipeline.
- Shadow DOM doesn't solve anything that proper namespacing does not.
- JavaScript strings work fine as templates.
ES2015
The language formerly known as ES6
ES2015
- New JavaScript standard finalized this year.
- Currently not 100% supported in browsers.
New features
- Classes. "class" has been a reserved word for a long time now. Finally it has some use.
- Arrow functions. Another way to define functions.
- Spread operator, Promises, etc.
ES6 classes
class Person {
constructor(name = 'Andreas') {
this.name = 'Andreas';
}
get name() {
return this._name;
}
set name(n) {
if (typeof n !== 'string') {
throw new Error('Name must be a string.');
}
this._name = n;
}
greeting() {
alert(`Hello! My name is ${this.name}!`);
}
}
Introduces constructor, getters, setters, default arguments.
Arrow functions
let add = (a, b) => {
return a + b;
};
// Even shorter
add = (a, b) => a + b;
// Nice in functional expressions.
let lengths = names.map((n) => n.length);
// Before
lengths = names.map(function(n) {
return n.length;
});
Yet another way to define functions in JavaScript!
Binds lexical scope
function Person(age) {
var that = this;
that.age = age;
setInterval (function () {
that.age++;
});
}
function Person(age) {
this.age = age;
setInterval (function () {
this.age++;
}.bind(this));
}
function Person(age) {
this.age = age;
setInterval (() => {
this.age++;
});
}
We used to do like this:
Or like this:
Arrow functions bind the lexical "this" value. So we can do like this:
ES2015 features not yet implemented in all browsers!
Babel
Babel will translate ES2015 code into code that can be interpreted and executed by all modern browers.
'use strict';
var fn = function fn(name) {
console.log('May the force be with you, ' + name);
};
fn('Andreas');
const fn = (name) => {
console.log(`May the force be with you, ${name}`);
};
fn('Andreas');
babel --presets es2015 force.js
Wouldn't it be great if we could make Custom Elements as class instances?
We can!
class HelloElement extends HTMLElement {
createdElement() {
this.innerHTML = `Hello, ${this.name}!`
}
attributeChangedCallback(name, from, to) {
this.innerHTML = `Hello, ${to}`;
}
get name() {
return this.getAttribute('name') || 'Andreas';
}
}
document.registerElement('hello-there', HelloWorldElement);
Sample app
Source is available on GitHub
class StarRating extends HTMLElement {
createdCallback() {
const template = doc.querySelector('template') || document.createElement('template');
const clone = document.importNode(template.content, true);
this.createShadowRoot();
this.shadowRoot.appendChild(clone);
this._createStars().forEach((star) => {
this.shadowRoot.appendChild(star);
});
this._colorStars();
}
attributeChangedCallback(name, from, to) {
if (name === 'value') {
this._colorStars();
}
}
get value() {
const v = parseFloat(this.getAttribute('value') || 0);
if (!(v >= 0)) {
return 0;
}
return v;
}
set value(v) {
if (!(parseFloat(v) >= 0)) {
throw new Error('Value must be a number larger or equal to 0.');
}
this.setAttribute('value', v);
}
get max() {
return parseInt (this.getAttribute ('max') || 5);
}
set max(v) {
throw new Error('The maximum value can\'t be changed.');
}
get _stars() {
return Array.from(this.shadowRoot.childNodes)
.filter ((node) => node.nodeName.toLowerCase () === 'span');
}
_createStars() {
const stars = [];
for (let i = 0; i < this.max; i++) {
const star = document.createElement('span');
star.innerHTML = '★';
stars.push(star);
const innerStar = document.createElement('span');
innerStar.innerHTML = '★';
star.appendChild(innerStar);
}
return stars;
}
_colorStars() {
const value = this.value;
let p;
for (var i = 0; i < this.max; i++) {
const star = this._stars[i];
const innerStar = star.children[0];
if (i + 1 <= value) {
innerStar.style.width = '100%';
}
else {
innerStar.style.width = 0;
}
p = value - i;
if (p > 0 && p < 1) {
innerStar.style.width = p * 100 + '%';
}
}
}
}
window.StarRatingElement = document.registerElement('star-rating', StarRating);
Unit tests with Karma
Thank you for listening!
Web Components in ES2015
By Andreas A
Web Components in ES2015
- 1,116