Introduction to Web Components

The future of web programming

Jason Mayes

Web Solutions Engineer, Google.

Follow along...

http://goo.gl/FFPxi7

It all starts with elements...

Elements are the building blocks of the web.

<select>
  <option value="volvo">Volvo</option>
  <option value="saab">Saab</option>
  <option value="mercedes">Mercedes</option>
  <option value="audi">Audi</option>
</select>

They are encapsulated, configurable and programmable.

Encapsulation

The internal HTML / CSS / JavaScript is not exposed.

 

It's default CSS styles do not effect the rest of your page - scope is restricted.

Think about how would you create a dropdown using HTML / CSS / JavaScript without using <select>? All of this logic exists but is hidden from you.

Configurablity

For example you can add "disabled" attribute and the UI updates automatically:

<select>
  <option value="volvo" disabled>Volvo</option>
  <option value="saab">Saab</option>
  <option value="opel">Opel</option>
  <option value="audi">Audi</option>
</select>

Just by changing HTML attribute! No CSS / JS knowledge needed.

Programmability

Often some form of API is exposed to directly manipulate the element using JavaScript:

dropdown.selectedIndex = 2;

I want an <x> element

What if you could make new elements to do something specific which anyone could then use?

<x-tabs>

<x-graph>

<x-meme>

Current solutions

Import 3rd party code and hope it does not conflict

Web components!

Simply a collection of standards using technologies from HTML, CSS and JavaScript allowing you to build new "elements" that can be reused easily and reliably. 

Example: Google Maps

<google-map latitude="37.77493" longitude="-122.41942"></google-map>

Google Maps in 1 line of HTML markup. Link to project

Much better than...

<!DOCTYPE html>
<html>
  <head>
    <title>Simple Map</title>
    <meta name="viewport" content="initial-scale=1.0, user-scalable=no">
    <meta charset="utf-8">
    <style>
      html, body, #map-canvas {
        height: 100%;
        margin: 0px;
        padding: 0px
      }
    </style>
    <script src="https://maps.googleapis.com/maps/api/js?v=3.exp"></script>
    <script>
var map;
function initialize() {
  var mapOptions = {
    zoom: 8,
    center: new google.maps.LatLng(-34.397, 150.644)
  };
  map = new google.maps.Map(document.getElementById('map-canvas'),
      mapOptions);
}

google.maps.event.addDomListener(window, 'load', initialize);

    </script>
  </head>
  <body>
    <div id="map-canvas"></div>
  </body>
</html>

Example: Text to Speech

Text to speech in 1 line of HTML

<voice-player autoplay text="Hello everyone! I can speak!"></voice-player>

And voice recognition in just a few lines...

<voice-recognition id="recognition-element"></voice-recognition>

<script>
  var element = document.querySelector('#recognition-element');
  element.start();
</script>

Example: Instagram

<x-instagram tag="javascript" count="10"></x-instagram>

Instagram API search 1 line of HTML. Link to project

Example: Webcam access

<video is="camera" audio autoplay controls></video>

You can even extend existing HTML elements. This user extended the <video> element to create a webcam element. Link to project

// Cross browser support to fetch the correct getUserMedia object.
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia
  || navigator.mozGetUserMedia || navigator.msGetUserMedia;
// Cross browser support for window.URL.
window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;

if (navigator.getUserMedia) { 
  // Ask for permission to use the webcam.
  navigator.getUserMedia({video:true}, success, error);
} 
else { 
  // Not supported by browser.
  alert('Your browser does not support getUserMedia');
}

function success(stream) {
  var video = document.getElementById('camStream');
  // Create a new object URL to use as the video's source.
  video.src = (window.URL && window.URL.createObjectURL(stream)) || stream;
  video.play();
}

function error() {
  // Handle errors...
}

Much easier than all of this...

Example: Google Analytics

<google-analytics domain="example.com" code="UA-XXXXX-Y"></google-analytics>

You can use web components for APIs too - such as Google Analytics. Doesn't have to be visual. Link to project

Here, nothing is rendered to the screen, but the analytics code is generated and added. Much cleaner than this:

<script type="text/javascript">

  var _gaq = _gaq || [];
  _gaq.push(['_setAccount', 'UA-XXXXX-X']);
  _gaq.push(['_trackPageview']);

  (function() {
    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
  })();

</script>

What are people making?

And much much more... Check out customelements.io!

Key parts

Custom Elements - allow you to define your own elements and extend existing DOM objects

Shadow DOM - DOM and CSS encapsulation for element

HTML Imports - include any custom element using an easy to use HTML tag

Templates - declare fragments of DOM to be instantiated at runtime.

Browser support?

Natively speaking, pretty good, but we still need polyfills for gaps in some browsers... Namely Safari and IE in 2014.

Poly what?

Polyfills are simply snippets / library of code that provide the technology that the browser should be providing natively. This allows us to use things like web components with older browsers which never supported them by default.

 

There are two common libraries used for Web Components:

 

- Polymer (by Google)

- X-Tag (by Mozilla)

Hello Polymer

Polymer is a JavaScript library built upon Web Components and contains the required polyfills to be used in all modern web browsers. It also provides a high level layer to use the key web component concepts together in a nice declarative way.

Polymer

The Polymer stack is full of features, and those important polyfills. 

Writing your first component

Lets make a custom meme component!

 

What configurable inputs will it have?

 

- An image

- Width / Height

- Some text

- Text position (top and left in pixels)

 

Let's get started...

Download Polymer

First, download the latest version of polymer from:

 

http://www.polymer-project.org/

 

Important files to note:

 

- platform.js  (polyfills needed to run in browsers that do not support web components natively)

- polymer.js  (an opinionated way to write web components)

 

Save these two files to your JavaScript folder in your project.

index.html

An example HTML page which will use our custom element:

<!doctype html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Meme Component</title>
    <meta name="author" content="Jason Mayes" />
    <script src="js/platform.js"></script>
    <link rel="import" href="x-meme.html" />
  </head>
  <body unresolved>
    <x-meme src="http://placekitten.com/300/300" width="300" height="300" text="Oh hai! I'm a cat!" top="250" left="50"></x-meme>
  </body>
</html>

unresolved? Prevents the "flash" of unstyled content on browsers that lack native support for custom elements.

x-meme.html

This is where our component definition lives

<script src="js/polymer.js"></script>

<polymer-element name="x-meme">
  <template>
    <p>Hello from <strong>x-meme</strong>. This is my Shadow DOM.</p>
  </template>
  <script>
    Polymer('x-meme', {
      created: function() {
        // TODO...
      }
    });
  </script>
</polymer-element>

<polymer-element>

Polymer reserves special attributes to be used on this tag:

Adding attributes

Tell Polymer our element has attributes: src, text, width and height. Also specify default values.

<script src="js/polymer.js"></script>

<polymer-element name="x-meme" attributes="src text width height top left">
  <template>
    <p>Hello from <strong>x-meme</strong>. This is my Shadow DOM.</p>
  </template>
  <script>
    Polymer('x-meme', {
      created: function() {
        // An instance of the element is created. Initialize attributes with
        // default values. This also hints at the "type" for these attributes.
        this.src = '';
        this.text = '';
        this.width = 'auto';
        this.height = 'auto';
        this.top = '0';
        this.left = '0';
      }
    });
  </script>
</polymer-element>

Element lifecycle methods

Lets add methods to handle key events:

<script src="js/polymer.js"></script>

<polymer-element name="x-meme" attributes="src text width height">
  <template>
    <p>Hello from <strong>x-meme</strong>. This is my Shadow DOM.</p>
  </template>
  <script>
    Polymer('x-meme', {
      created: function() {
        // An instance of the element is created. Initialize attributes with
        // default values. This also hints at the "type" for these attributes.
        this.src = 'http://placekitten.com/100/100';
        this.text = '';
        this.width = 'auto';
        this.height = 'auto';
        this.top = '0';
        this.left = '0';
      },
      ready: function() {
        // The element was created and fully prepared (eg shadow DOM, event 
        // listeners etc). #Polymer only, not part of W3C specification.
      },
      attached: function () {
        // Element instance was attached to DOM.
        this.src = this.getAttribute('src');
        this.text = this.getAttribute('text');
        var w = this.getAttribute('width');
        this.width = (w !== null) ? w + 'px' : 'auto';
        var h = this.getAttribute('height');
        this.height = (h !== null) ? h + 'px' : 'auto';
        this.top = this.getAttribute('top') + 'px';
        this.left = this.getAttribute('left') + 'px';
      },
      domReady: function() {
        // Elements children are also now guaranteed to exist.
        // #Polymer only, not part of specification.
      },
      detached: function() {
        // Element is removed from the DOM.
      },
      attributeChanged: function(attrName, oldVal, newVal) {
        // Be notified when an attribute on the element changes.
        if (attrName === 'height' || attrName === 'width' || attrName === 'top' ||
            attrName === 'left') {
          this[attrName] = newVal + 'px';
        } else {
          this[attrName] = newVal;
        }
      }
    });
  </script>
</polymer-element>

Data binding

The properties you define in the "created" callback (eg this.text) are automatically available to be "bound" to your template HTML snippet using a moustache like syntax:

<script src="js/polymer.js"></script>

<polymer-element name="x-meme" attributes="src text">
  <template>
    <div>
      <img src="{{src}}" />
      <p>{{text}}</p>
    </div>
  </template>
  <script>
    Polymer('x-meme', {
      created: function() {
        // An instance of the element is created. Initialize attributes with
        // default values. This also hints at the "type" for these attributes.
        this.src = 'http://placekitten.com/100/100';
        this.text = '';
        this.width = 'auto';
        this.height = 'auto';
        this.top = '0';
        this.left = '0';
      },
      ready: function() {
        // The element was created and fully prepared (eg shadow DOM, event 
        // listeners etc). #Polymer only, not part of W3C specification.
      },
      attached: function () {
        // Element instance was attached to DOM.
        this.src = this.getAttribute('src');
        this.text = this.getAttribute('text');
        var w = this.getAttribute('width');
        this.width = (w !== null) ? w + 'px' : 'auto';
        var h = this.getAttribute('height');
        this.height = (h !== null) ? h + 'px' : 'auto';
        this.top = this.getAttribute('top') + 'px';
        this.left = this.getAttribute('left') + 'px';
      },
      domReady: function() {
        // Elements children are also now guaranteed to exist.
        // #Polymer only, not part of specification.
      },
      detached: function() {
        // Element is removed from the DOM.
      },
      attributeChanged: function(attrName, oldVal, newVal) {
        // Be notified when an attribute on the element changes.
        if (attrName === 'height' || attrName === 'width' || attrName === 'top' ||
            attrName === 'left') {
          this[attrName] = newVal + 'px';
        } else {
          this[attrName] = newVal;
        }
      }
    });
  </script>
</polymer-element>

Adding style

Some special CSS selectors for this...

<script src="js/polymer.js"></script>
<link href='http://fonts.googleapis.com/css?family=Oswald:400,700' rel='stylesheet' type='text/css'>

<polymer-element name="x-meme" attributes="src text">
  <template>
    <style>
      :host {
        display:block;
      }

      div {
        position: relative;
        margin: 0;
        display: inline-block;
      }

      p {
        font-family: 'Oswald', sans-serif;
        font-weight: 700;
        font-size: 20pt;
        text-transform: uppercase;
        text-shadow: 0px 0px 4px rgba(150, 150, 150, 1);
        text-align: center;
        position: absolute;
        margin: 0;
        color: #ffffff;
        transition: all 0.3s ease-in-out;
        -webkit-transition: all 0.3s ease-in-out;
      }
      
      img {
        transition: all 0.3s ease-in-out;
        -webkit-transition: all 0.3s ease-in-out;
      }
    </style>

    <div>
      <img src="{{src}}" _style="width:{{width}}; height:{{height}};" />
      <p _style="top:{{top}}; left:{{left}};">{{text}}</p>
    </div>
  </template>
  <script>
    Polymer('x-meme', {
      created: function() {
        // An instance of the element is created. Initialize attributes with
        // default values. This also hints at the "type" for these attributes.
        this.src = 'http://placekitten.com/100/100';
        this.text = '';
        this.width = 'auto';
        this.height = 'auto';
        this.top = '0';
        this.left = '0';
      },
      ready: function() {
        // The element was created and fully prepared (eg shadow DOM, event 
        // listeners etc). #Polymer only, not part of W3C specification.
      },
      attached: function () {
        // Element instance was attached to DOM.
        this.src = this.getAttribute('src');
        this.text = this.getAttribute('text');
        var w = this.getAttribute('width');
        this.width = (w !== null) ? w + 'px' : 'auto';
        var h = this.getAttribute('height');
        this.height = (h !== null) ? h + 'px' : 'auto';
        this.top = this.getAttribute('top') + 'px';
        this.left = this.getAttribute('left') + 'px';
      },
      domReady: function() {
        // Elements children are also now guaranteed to exist.
        // #Polymer only, not part of specification.
      },
      detached: function() {
        // Element is removed from the DOM.
      },
      attributeChanged: function(attrName, oldVal, newVal) {
        // Be notified when an attribute on the element changes.
        if (attrName === 'height' || attrName === 'width' || attrName === 'top' ||
            attrName === 'left') {
          this[attrName] = newVal + 'px';
        } else {
          this[attrName] = newVal;
        }
      }
    });
  </script>
</polymer-element>

Bind style values to variables, using _style attribute.

Woohoo!

We have made our first component. These Memes are better than Photoshopped ones: Searchable, indexable for SEO, can dynamically be updated, and used by anyone. 

Further reading...

Some great websites to dive deeper if you wish to learn more:

 

- WebComponents.org webcomponents.org
- Polymer project polymer-project.org

- Polymer Reference polymer-project.org/docs/polymer/

- Vulcanize your code polymer-project.org/articles/concatenating-web-components.html

- HTML5 Rocks html5rocks.com
- Examples customelements.io

- W3C spec w3c.github.io/webcomponents/spec/custom/

Thank you!

Add me if you are interested in:

  • Web / Graphic Design
  • Latest Technologies / Innovations
  • Creative / Illustration / 3D
  • Programming / Computer Science
  • Photography / Photoshop 
  • Psychology / Philosophy
  • Digital Marketing
  • Robotics / Space / Astronomy
  • Things that make you go wow

Google+: http://goo.gl/EMLKP 

Twitter: @jason_mayes

Made with Slides.com