Who is this guy?

I have 40 min to cover the entire future web platform :(

FAIL

Web Components?

<gangnam-style></gangnam-style>

<button is="mega-button">Mega button</button>

Building blocks of Web Components

  • Shadow DOM - mortar/glue
    • DOM & style encapsulation boundaries
  • HTML Templates - scaffold/blueprint
    • inert chunks of clonable DOM. Can be activated for later use (e.g. MDV)
  • Custom Elements - toolbelt
    • create new HTML elements - expand HTML's existing vocabulary
    • extend existing DOM objects with new imperative APIs
  • HTML Imports

A collection of new API primitives in the browser

HTML Templates

Declaring HTML templates

Contains inert chunks of markup intended to be used later:

<template id="mytemplate">
  <img src=""> <!-- dynamically populate at runtime -->
  <div class="comment"></div>
</template>
  1. Working directly w/ DOM
  2. Parsed; not rendered
    • <script> not run, stylesheets/images not loaded, media not played
  3. Hidden from document. Cannot traverse into its DOM
    • e.g. document.querySelector('#mytemplate .comment') == null

Using template content

Two ways to access the guts:

  1. template.content (a document fragment)
  2. template.innerHTML

Clone .content → stamp it out → goes "live"

<template id="mytemplate">
  <img src="">
  <div class="comment"></div>
</template>
<script>
  var t = document.querySelector('#mytemplate');
  t.content.querySelector('img').src = 'logo.png'; // Populate the src at runtime.
  document.body.appendChild(t.content.cloneNode(true));
</script>

Demo

<template>
  <span>Instance: <b>0</b></span>
  <script>alert('kthxbai!')</script>
</template>
<button onlclick="useIt()">Stamp</button>
<script>
  function useIt() {
    var content = document.querySelector('template').content; // 1. Get guts.

    var b = content.querySelector('b');
    b.textContent = parseInt(b.textContent) + 1; // 2. Modify template's DOM at runtime.

    document.body.appendChild(content.cloneNode(true)); // 3. Clone to stamp it out.
  }
</script>

HTML Templates

browser support

Building blocks of Web Components

HTML Templates

Shadow DOM

Custom Elements

HTML Imports

Shadow DOM Dude DOM

Shadow DOM Dude
Shadow DOM gives us markup encapsulation, style boundaries, and exposes (to web developers) the same mechanics browsers vendors have been using to implement their internal UI.
-me

The web has encapsulation...

<iframe>

BLOATED

But there's more lurking in the shadows...

  • Truth #1: DOM nodes can already "host" hidden DOM
  • Truth #2: The hidden DOM can't be accessed from outside JS

<input type="date"> <input type="time">

  • Other examples: <video> <textarea> <details>

Browser vendors have been holding out on us!

Creating Shadow DOM

<div id="host">
  <h1>My Title</h1>
  <h2>My Subtitle</h2>
  <div>

...other content...</div>
</div>
<div id="host">
  #document-fragment
    <h2>Yo, you got replaced!</h2>
    <div>by my awesome content</div>
</div>
var host = document.querySelector('#host');
var shadow = host.createShadowRoot();
shadow.innerHTML = '<h2>Yo, you got replaced!</h2>' +
                   '<div>by my awesome content</div>';

My Title

My Subtitle

...other content...

Style encapsulation for free

  • Styles defined in Shadow DOM are scoped by default
  • Page's styles don't bleed across the shadow boundary (unless we let them)
var shadow = document.querySelector('#host').createShadowRoot();
shadow.innerHTML = '<style>h2 { color: red; }</style>' + 
                   '<h2>Yo, you got replaced!</h2>' + 
                   '<div>by my awesome content</div>';
// shadow.resetStyleInheritance = true; // click me
// shadow.applyAuthorStyles = true; // click me

My Title

My Subtitle

...other content...

Styling the host

  • @host at-rule selects the ShadowRoot's host element.
  • Allows reacting to different states:
<style>
@host {
  * { opacity: 0.2; transition: opacity 400ms ease-in-out; }
  *:hover { opacity: 1; }
}
</style>

My Title

My Subtitle

...other content...

Insertion points

  • select uses CSS selectors to specify where top level Light DOM gets funneled into.
<div id="host">
  <h1 class="title">My Title</h1>
  <h2>My Subtitle</h2>
  <div>...amazing description...</div>
</div>
<style>h2 { color: red; }</style>
<hgroup>
  <content select="h2"></content>
  <content select=".title"></content>
</hgroup>
<div>You got enhanced</div>
<content select="*"></content>

My Title

My Subtitle

...amazing description...

Tool: Shadow DOM Visualizer

Shadow DOM

browser support

Building blocks of Web Components

HTML Templates

Shadow DOM

Custom Elements

HTML Imports

Custom Elements

Creating new HTML elements

Element definition registers the new tag with the browser:

<element name="x-foo" constructor="XFoo">...</element>
  • <x-foo> is an HTMLElement until registered
  • Use :unresolved pseudo-class to prevent FOUC (Chrome 28)

    :unresolved { opacity: 0; }
    x-foo { opacity: 1; transition: opacity 300ms; }
    

  • Lifecycle callbacks (ready, inserted, removed) give insight to element's life

    • e.g. readyCallback() is invoked when the custom element is created AND its definition has been registered

Basic <element> definition

<element name="x-foo" constructor="XFoo">
<section>I'm an x-foo!</section>
<script>
  var section = this.querySelector('section');
  this.register({
    prototype: {
      readyCallback: function() {
        this.textContent = section.textContent; // this == <element>
      },
      foo: function() { alert('foo() called'); }
    }
  });
</script>
</element>

Using a custom element

After registration, use it like any standard DOM element

By declaring it

<x-foo></x-foo>

By creating DOM in JS

var elem = document.createElement('x-foo');
elem.addEventListener('click', function(e) {
  e.target.foo(); // alert 'foo() called'.
});

By using new (if the constructor attribute was defined)

var elem = new XFoo();
document.body.appendChild(elem);

Registering elements in JS

going the imperative route

  • document.register() takes the tag name and description prototype:
var XFooProto = Object.create(HTMLElement.prototype);

// Setup optional lifecycle callbacks: ready, inserted, removed.
XFooProto.readyCallback = function() {
  this.textContent = "I'm an x-foo!";
};

// Define its JS API.
XFooProto.foo = function() { alert('foo() called'); };

var XFoo = document.register('x-foo', {prototype: XFooProto});
//var xFoo = new XFoo();
//var xFoo = document.createElement('x-foo');

Extending existing HTML elements

declarative version

In an <element> definition, use the extends attribute:

<element name="mega-button" extends="button"
         constructor="MegaButton">
  ...
  <script>
    // 1. Register.
    // 2. Define an API on prototype to play moo onclick.
  </script>
</element>

Extending existing HTML elements

imperative version

Rock the prototype you want to inherit from:

var MegaButtonProto = Object.create(HTMLButtonElement.prototype);
...
var MegaButton = document.register('mega-button', {prototype: MegaButtonProto});

An instance is called a type extension custom element

// <button is="mega-button">
var megaButton = document.createElement('button', 'mega-button');

// megaButton instanceof MegaButton === true

What about templates and Shadow DOM?

Custom elements Shadow DOM

Wrap markup in a <template> to make it inert. Create Shadow DOM from content:

<element name="x-foo-shadow" constructor="XFooShadow">
  <template>
    <style>
      @host { * { display: block; background: #ffcc00; ... } }
    </style>
    <section>I'm an x-foo-shadow. Gots me some Shadow DOM!</section>
  </template>
  <script>
    var template = this.querySelector('template');
    this.register({ // this == <element>
      prototype: {
        readyCallback: function() {
          this.createShadowRoot().appendChild(template.content.cloneNode(true));
        }
      }
    });
  </script>
</element>

Define an internal structure with insertion points

<element name="my-tabs">
  <template>
    <style>...</style>
    <content select="h2"></content>
    <content select="section"></content>
  </template>
</element>
<my-tabs>
  <h2>Title</h2>
  <section>content</section>
  <h2>Title 2</h2>
  ...
</my-tabs>

Custom Elements

browser support

Building blocks of Web Components

HTML Templates

Shadow DOM

Custom Elements

HTML Imports

HTML Imports

HTML Imports

Package. Distribute. Share. Reuse.

<!DOCTYPE html>
<html>
  <head>
    <link rel="import" href="x-foo.html">
  </head>
  <body>
    <x-foo></x-foo> <!-- Element definition is in x-foo.html -->
  </body>
</html>
  • link.import.content is the Document x-foo.html
  • link.import.href - the link's href attribute, but resolved relative to the element.
  • .import is null for non-import links.

Reusing other components

<link rel="import" href="x-toolbar.html">
<link rel="import" href="menu-item.html">

<element name="awesome-menu">
  <template>
    <x-toolbar responsive="true">
      <menu-item src="images/do.png" selected>Do</menu-item>
      <menu-item src="images/re.png">Re</menu-item>
      <menu-item src="images/mi.png">Mi</menu-item>
    <x-toolbar>
  </template>
  ...
</element>
<link rel="import" href="awesome-menu.html">
<awesome-menu></awesome-menu>

HTML Imports

browser support

Building blocks of Web Components

HTML Templates

Shadow DOM

Custom Elements

HTML Imports

Demos

Meme Generator

<my-meme src="images/beaches.jpg">
  <h1 contenteditable>Stay classy</h1>
  <h2 contenteditable>Web!</h2>
</my-meme>

<photo-booth></photo-booth>

Try it today? Yes!

Finito.