@addyosmani

At the start of the web we had tags..like
<form> and <select>. We didn't need much more to build our pages.

They were declarative, encapsulated and meaningful. They had default UI.

They'd inform us if something interesting happened.

Over time we've stuffed boilerplate, widgets and plugins into our pages.

We end up with bloated HTML. bloated JS. Or both.

What if HTML was expressive enough to allow us to create our own tags to fill in the gaps in the platform?

Examples

Polymer


polymer-project.org

What is Polymer?

Polyfills?

A framework?

UI widgets?

Let's go back in time!

Get A T-Shirt!

<select>
  <option>Small</option>
  <option>Medium</option>
  <option>Large</option>
</select>

<select id="schwag">
  ...
  <option disabled>Medium</option>
  <option disabled>Large</option>
  <option selected>XX-large</option>
</select>

<select size="4" multiple>
  <option>Do</option>
  <option>Re</option>
  <option>Mi</option>
  ...
</select>

<select>
  <optgroup label="French cars">
    <option>Bugatti</option>
    <option>Citroen</option>
  </optgroup>
  ...
</select>

Where are we now?

Tabs: a common component on the web

Reminder: building one today..

Pile on the JavaScript

Our markup is terrible

Seriously?

Markup can be meaningful again

  <my-tabs>
    <my-tab selected>...</my-tab>
    <my-tab>...</my-tab>
    <my-tab>...</my-tab>
  </my-tabs>
  var tabs = document.createElement('my-tabs');
  var tab1 = document.createElement('my-tab');
  var tab2 = document.createElement('my-tab');
  var tab3 = document.createElement('my-tab');
  tabs.appendChild(tab1);
  tabs.appendChild(tab2);
  tabs.appendChild(tab3);
  tab1.setAttribute('selected', true);

Markup can be meaningful again

<hangout-module>
  <hangout-chat from="Paul, Addy" profile="118075919496626375791">
    <hangout-discussion>
      <hangout-message from="Paul" profile="profile.png" datetime="2013-07-17T12:02">
        <p>Feelin' this Web Components thing.</p>
        <p>Heard of it?</p>
      </hangout-message>
      <hangout-message from="Addy" datetime="2013-07-17T12:12">...</hangout-message>
      <hangout-message>...</hangout-message>
      ...
    </hangout-discussion>
  </hangout-chat>
  <hangout-chat></hangout-chat>
</hangout-module>

Polymer


Built on standards

Templates

HTML Imports

Custom Elements

Shadow DOM


Using a component today

<link rel="stylesheet" type="text/css" href="my-widget.css" />
<script src="my-widget.js"></script>
<div data-my-widget />
$(function() {
  $('[data-my-widget]').myWidget();
});
div.innerHTML = '<div data-my-widget />'
$(div).find('[data-my-widget]').myWidget();

The Web Component way

Import:

<link rel="import" href="my-widget.html" />

Consume:

<my-widget />
div.innerHTML = '<my-widget />';

Get encapsulation for free:

#document-fragment
  <div>
    <input type="text" />
    <button>Go</button>
  </div>

Custom Elements

Register a new custom element my-element

var MyElement = document.registerElement('my-element');
document.body.appendChild(new MyElement());

Instantiate a custom element in JS

var myElement = document.createElement('my-element');
myElement.addEventListener('click', function(e) {
  alert('Thanks!');
});

Custom elements can perform their own initialisation

document.registerElement('my-element', {
  prototype: Object.create(HTMLElement.prototype, {
...

Custom Elements can respond to change

Custom elements can respond to attribute changes

document.registerElement('my-element', {
  prototype: Object.create(HTMLElement.prototype, {
    attributeChangedCallback: {
      value: function(attr, oldVal, newVal) {
        this.innerHTML = '<h1>ATTRIBUTE CHANGED!</h1>';;
      }
    }
  })
});

Other lifecycle events:

  • createdCallback
  • attachedCallback
  • detachedCallback

Custom Elements can be extended

Extending native custom elements

var MegaButton = document.registerElement('mega-button', {
  // inherit the prototype of HTMLButtonElement
  prototype: Object.create(HTMLButtonElement.prototype)
});

Type extension custom element:

<button is="mega-button">


Custom Elements can encapsulate styles & DOM

Custom elements have encapsulated styles by default

shadow = this.createShadowRoot();
shadow.innerHTML =
  "<style>span { color: green; }</style>" +
  "<span>I'm green</span>";
<my-element />
<span>I'm not green</span>

Custom elements can have hidden internal DOM structures (shadow host)

<button>Hello, world!</button>
<script>
var host = document.querySelector('button');
var root = host.createShadowRoot();
root.textContent = 'How you doing?';
</script>

Custom Elements can be styled

Style the current element hosting a shadow tree

:host {
  color: green;
}

Piece one level of the Shadow DOM (^) ~ upper shadow boundary

my-element ^ h2{
  color: green;
}

Pierce every layer of the Shadow DOM (^^) ~ all shadow boundaries

my-element ^^ h2{
  font-family: 'Comic Sans';
}

What is Polymer?

Polyfill libraries

Shadow DOM, Custom Elements, HTML Imports, Pointer Events...

Sugaring layer

expresses opinionated way to use web component concepts together

UI Components

comprehensive set (in progress)

Philosophy & Goals

Everything is an element

Everything is an element

Philosophy & Goals

Everything is an element

HTML is cool. DOM feels good.


Eliminate boilerplate

Remove tediousness of building web component-based apps

Utilize the modern web platform

Support modern browsers


Polymer

polymer-project.org/docs/elements/

Everything is an element

AJAX...using DOM

<script src="platform.js"></script>
<link rel="import" href="polymer-ajax.html">
<polymer-ajax url="http://gdata.youtube.com/feeds/api/videos/"
              params='{"alt":"json"}'></polymer-ajax>
var ajax = document.querySelector('polymer-ajax');

ajax.addEventListener('polymer-response', function(e) {
  console.log(JSON.parse(this.response).feed.entry);
});

ajax.go();

Real-world examples of polymer-ajax

Everything is an element

read files...using DOM

<script src="platform.js"></script>
<link rel="import" href="polymer-file.html">
<polymer-file readas="dataurl"></polymer-file>
var pFile = document.querySelector('polymer-file');

pFile.addEventListener('polymer-result', function(e) {
  console.log(this.result);
});

pFile.blob = new Blob(['abc'], {type: 'text/plain'}); // Set the file to read

pFile.read();

Everything is an element

flexbox...using DOM

<script src="platform.js"></script>
<link rel="import" href="polymer-flex-layout.html">
<polymer-flex-layout vertical iscontainer>
  <div>Header</div>
  <div flex>Body</div>
  <div>Footer</div>
</polymer-flex-layout>
Header
Body
Footer

Polymer elements

non-visual utility elements

Layout

<polymer-layout>

<polymer-flex-layout>

<polymer-grid-layout>

View

<polymer-media-query>

<polymer-page>

Services / libs

<polymer-shared-lib>

<polymer-google-jsapi>

Data

<polymer-localstorage>

<polymer-xhr>

<polymer-jsonp>

<polymer-file>

<polymer-meta>

Behavior / interaction

<polymer-signals>

<polymer-selector>

Polymer UI elements

visual elements

<polymer-ui-accordion> demo

<polymer-ui-animated-pages>

<polymer-ui-overlay>

<polymer-ui-card> demo

<polymer-ui-sidebar-menu> demo

<polymer-ui-tabs> demo

<polymer-ui-toggle-button> demo

<polymer-ui-theme-aware>

...

Reusability...the non-visual polymer-elements are used to implement these!

Polymer

polymer-project.org/polymer.html

Custom elements without Polymer :(

<template id="template">
  <style>input { color: orange; }</style>
  <input type="text">
</template>

<script>
var proto = Object.create(HTMLElement.prototype, {
  createdCallback: {
    value: function() {
      var t = document.querySelector('#template');
      this.createShadowRoot().appendChild(t.content.cloneNode(true));
    }
  }
});

var MyInput = document.registerElement('my-input', {prototype: proto});
</script>

Custom elements with Polymer :)

declarative custom elements

<link rel="import" href="polymer.html"/>
  1. Create an element definition

    <polymer-element name="my-input" constructor="MyInput" noscript>
      <!-- Note: Polymer creates Shadow DOM from the first <template>. -->
      <template>
        <style>input { color: orange; }</style>
        <input type="text">
      </template>
    </polymer-element>
    

  2. Instantiate - declare it, create DOM, or use new in JS

    <my-input></my-input>
    // var myInput = document.createElement('my-input');
    // var myInput = new MyInput();
    

Default attributes

User-defined attributes are included on each instance of the element:

<polymer-element name="my-input" customattr class="active">
  <template>...</template>
</polymer-element>

<my-input></my-input>

Instances include your attributes:

  <my-input customattr class="active"></my-input>

Complex elements require more juice...

define an API



  • Properties/methods are added to prototype
  • this refers to the element itself (e.g. this.localName == "my-input")
  • Can reference external scripts/stylesheets (e.g. CSP friendly)

Publishing properties & data-binding

  1. Inside the element → data-binding via the attribute
  2. From outside world → users can initialization the property via its attribute


  • User overrides color but type remains its default ("text")
  • Since val isn't published, can't use it as a bindable attribute.

Features in action

$ node finding / changed watchers / declarative event handlers

Using <polymer-file> in another element:

<script src="polymer.min.js"></script>
<link rel="import" href="polymer-file.html">
<polymer-element name="read-me" on-click="{{onClick}}">
  <template>
    <polymer-file id="file" readas="arraybuffer" result="{{result}}"></polymer-file>
  </template>
  <script>
    Polymer('read-me', {
      resultChanged: function() {
       console.log(this.result);
      },
      onClick: function(e, detail, sender) {
       this.$.file.read();
     }
    });
  </script>
</polymer-element>
var el = document.createElement('read-me');
el.blob = new Blob(['abc'], {type: 'text/plain'});

Dynamic markup

additional magic for HTML <template>

Conditionals:

<template if="{{ isActive }}">
  <!-- shown if isActive property is true -->
</template>

<template if="{{ showDefault || users.length < 10 }}">
  ...
</template>

Iteration:

<template repeat="{{ user in users }}">
  <template repeat="{{ file in user.files }}">
    {{ user.name }} owners {{ file.name }}
  </template>
</template>

Features in action

responsive design...using DOM (Bonus)

<script src="platform.js"></script>
<link rel="import" href="polymer-media-query.html">
<polymer-element name="responsive-element" attributes="responsive">
  <template>
    <polymer-media-query query="max-width:640px"
          queryMatches="{{isPhone}}"></polymer-media-query>
    <polymer-media-query query="max-width:1024px"
          queryMatches="{{isTablet}}"></polymer-media-query>

    <template if="{{isPhone && responsive}}">
     <!-- Phone markup. -->
    </template>
    <template if="{{isTablet && responsive}}">
     <!-- Tablet markup. -->
    </template>
    <template if="{{!responsive}}">
     <!-- Default markup for non-responsive case. -->
    </template>

  </template>
  <script>Polymer('responsive-element', {responsive: false});</script>
</polymer-element>

<responsive-element responsive></<responsive-element>

Polymer

Utilize the modern web platform.

1st-class support for spec features...

Lifecycle callbacks

Support for the lifecycle methods...but shorter names!

Polymer('my-input', {
  ready: function() { ... }, // Polymer addition for when element is fully initialized.
  created: function() { ... },
  ready: function() { ... },
  attached: function() { ... },
  detached: function() { ... },
  attributeChanged: function(attrName, oldVal, newVal) { ... }
});

Use cases:

  • perform setup/teardown work
  • notification when element is inserted/removed from page

Scoped styling

Support for styling features (scoped styles, ^ and ^^ selectors)

 <polymer-element name="x-foo">
   <template>
     <style>
       x-foo { ... }
       x-foo:hover { ... }
       x-foo.foo > .bar, .foo x-foo > bar {...}
     </style>
   ...
  • Polymer attempts to polyfill most Shadow DOM style features

Bundle & deliver CSS/HTML/JS

Reuse others' components:

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

<polymer-element name="awesome-menu">
  <template>
    <x-toolbar responsive>
      <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>
  ...
</polymer-element>
<link rel="import" href="awesome-menu.html">
<awesome-menu></awesome-menu>

Polymer Tooling

Helping you stay more productive

Polymer + X-Tag sitting in a tree..

They can work great together

Use Platform.js to load X-Tag elements via Imports

<script src="../platform/platform.js"></script>
<link rel="import" href="x-iconbutton.html">
<link rel="import" href="x-tabbar.html">

X-Tag can be used as normal in an import

<link rel="import" href="x-tag-core.html">
<link rel="stylesheet" href="../iconbutton/src/iconbutton.css">
<script src="../iconbutton/src/iconbutton.js"></script>

Summary of Polymer features

declarative web components

  • Declarative element registration: <polymer-element>
  • Declarative inheritance: <polymer-element extends="...">
  • Declarative two-way data-binding: <input id="input" value="{{foo}}">
  • Declarative event handlers: <button on-click="{{handleClick}}">
  • Published properties: xFoo.bar = 5 <-> <x-foo bar="5">
  • Property change watchers: barChanged: function() {...}
  • Automatic node finding: this.$.input.value = 5
  • PointerEvents / PointerGestures by default
Be declarative. Write less code.

Hopefully you're excited.

Let's componentize the web.

Build elements. Your friends are gonna be like..

<thank-you>