Build your first web component with Polymer

Harness the power of the Shadow DOM to create your own elements and help componentise the web

Web Component with Polymer

Harness the power of the Shadow DOM to create your own elements and help componentise the web

Web Component with Polymer

The new web hero will be Web Components – an emerging set of web standards which include: custom elements, shadow DOM, HTML imports and many others.

The custom elements API is a way to declare your own HTML elements, the only limitation is that it must be hyphenated like `my-element` in order to not clash with standardised elements. The shadow DOM encapsulates markup so that you only see the top-most level (known as the ‘host’) whereas HTML imports lets you download HTML snippets without Ajax with the <link> element. Polymer is a Google project that brings these together in a future-friendly fashion. This means that it will polyfill as it needs to, but if native implementations exist then it’ll use that instead. There are many layers to Polymer, it covers the polyfill, core elements, ‘paper’ elements, an API to create your own elements and an app designer. To get to know Polymer we’re going to build a responsive, accessible, touch-enabled gallery. In this tutorial, we are using Polymer 0.4.2 which uses platform.js, in Polymer 0.5 this is renamed to webcomponents.js.

Install with Bower

Polymer’s preferred method of installation is with Bower – a front-end package manager system which organises dependencies. Head to to install Bower. In a new folder we init Bower which creates an empty config file, and save and install Polymer as a dependency.

001 $ bower init
002 $ bower install --save Polymer/polymer

Loading platform

Create an index.html file in the root folder of your project. We will add a meta viewport tag to dictate how it responds on mobile devices and include the polyfill library: platform.js. The last is a link tag with a new `rel` value, `import` and this will point to a new file, which we haven’t created yet, that will be called gallery.html.

001 <meta charset="UTF-8">
002 <meta name="viewport" content="width=device-width, initial-    scale=1">
003 <title>Polymer Gallery</title>
004 <script src="bower_components/platform/platform.js"></script>
005 <link rel="import" href=“/elements/gallery.html">

Register element

Create a ‘elements’ folder and a ‘gallery.html’ file. This won’t have a document type declaration, <head> or <body>. Custom Polymer elements are wrapped in <polymer-element>. We tell polymer-element the element name and its attributes. We’ll specify width, an array of images and if it automatically scrolls (and how frequently).

001 <link rel="import" href="../bower_components/polymer/polymer.    html">
002 <polymer-element name="shadow-gallery" attributes="width images auto interval">

The template

The heart of all HTML is the markup. In a Web Component this is stored within <template>, templates can be nested within each other. Polymer uses a syntax like Handlebars but has its own implementation called TemplateBinding. This allows us to use attributes passed to the element with ease.

001  <div id="viewport">
002     <ul id="gallery" touch-action="none">            
003        <template repeat="{{image in images}}">
004                 <li>
005                    <img src="{{image.src}}" alt="{{image.alt}}">
006                  </li>
007              </template>
008         </ul>                                
009    </div>    
010   </template>

Polymer object

At the very least, our Polymer instantiation can provide defaults if attributes are not provided. If you don’t have any JavaScript interaction then you can add `noscript` as an attribute and it’ll self-register. These give Polymer some hints as to what type the attributes should be treated as.

001 <script>
002 Polymer({                                    
003     images: [],
004       width: 100,    
005   offset: -400,
006      auto: false,    
007     interval: 5000,
008     slide: null                            
009 )};
010 </script>    

Event handlers

Polymer has event handlers built in with an `on-eventname` syntax, all you have to do is pass the name of the related function. Note that we have a tabindex, which gives us focusable keyboard control and an indicator to screen readers that these links are controls for our gallery.

001     <a href="#" on-click="{{previous}}" title="Previous"     tabindex="1" aria-controls="gallery">&lt;</a>
002     <a href="#" on-click="{{next}}" title="Next" tabindex="2"     aria-controls="gallery">&gt;</a>

Passing attributes

We can use our gallery in our index.html file simply by including the element on the page and passing some attributes. We’ll add to this later on as we add more features. If you inspect this with Chrome, it’ll differentiate the shadow root of the element to show how its contents are encapsulated.

001     <shadow-gallery width="400"                
002      aria-orientation="horizontal"
003   images='[{"src": "",     "alt": "Image 1”}'>
004   </shadow-gallery>

Next slide

Our next function will move everything to the left by the set width. We can access attributes and other properties declared on our Polymer element with `this`. Polymer templates have two-way binding – as soon as we update this value it’ll be updated everywhere that we use it in the template.

001 next: function (e) {
002       if (e)
003    e.preventDefault();
004   if (this.offset > -this.width * (this.images.length - 1))
005  this.offset -= this.width;
006  },

Previous slide

We use the same technique here to go the other way. This time increasing the offset by the amount of one item but only if the current offset is less than zero. We check for the existence of the event because we’ll trigger this from other methods without passing an event.

001 previous: function (e) {
002   if (e)
003   e.preventDefault();
004       if (this.offset < 0)
005     this.offset += this.width;
006   },

Style custom elements

Now we’ll add style – these live in the template tag so your structure will look like: polymer-element > template > style + div ^ script. All styles are scoped to this element so we can use IDs and bold statements like ‘all list items will float left’ and not have to worry about affecting outside elements.

001 <style>
002 #viewport {
003   width: {{width}}px;
004     height: 200px;
005     overflow: hidden;
006      position: relative;
007 }
008 li { float: left; }
009 img { max-width: {{width}}px; }
010 </style>

Style the gallery

Unlike maintaining site-wide CSS, IDs are encouraged within Web Components. Polymer adds a way to conveniently access elements with IDs, in this example we’d be able to access the gallery with `this.$.gallery`. We’ll be using a CSS transition to animate the movement of the gallery. In older browsers, this will go straight to each one without the animation.

001 #gallery {
002 list-style: none;
003 margin: 0;
004 padding: 0;
005  position: absolute;
006  transition: all 0.25s linear;
007     transform: translateX({{offset}}px);
008 width: {{images.length * width}}px;            
009 }

Automatic scrolling

Just for fun, we’ll add an automatic scroll. This requires two properties, a Boolean to trigger it and a number to control how frequently it scrolls. This is similar to the `next` function except instead of not doing anything when it gets to the end it scrolls to the start.

001     domReady: function () { 
002     var self = this;
003     if ( {
004         self.slide = setInterval(function () {
005           if (self.offset > -self.width * (self.images.length - 1))
006   ;
007                 else
008             self.offset = 0; 
009     }, self.interval);
010     }
011 },

Cleaning up

When the element is removed from the DOM the detached event is fired, enabling us to clear up any event listeners or intervals. In the official spec. each of these event methods has Callback appended to the end so it would be ‘detachedCallback’. Polymer removes Callback to make it more consistent with other event callbacks.

001 detached: function () {
002      clearInterval(this.slide);
003  }

Keyboard control

We’ll add keyboard support to our gallery by listening to the keydown event. Ours is limited to just going left and right. Unfortunately this will affect all galleries if there are multiple instances on the page. A check could be added to see if the gallery element has focus and then which key code to act on.

001 window.addEventListener('keydown', function (e) {
002        if (e.keyCode === 37) //left
003            self.previous();                    
004     else if (e.keyCode === 39) //right
005  ;
006        });

Install Polymer Gestures

Next we’ll add some simple gesture support too. Install polymer-gestures through Bower and add it alongside the other link tag in gallery.html. The polymer-gestures HTML file also imports all of the necessary JavaScript files that it depends on. This makes it very useful for loading by component.

001 $ bower install --save Polymer/polymer-gestures
002 <link rel="import" href="../bower_components/polymer-gestures/    polymer-gestures.html">

Track gestures

PolymerGestures normalises the properties returned in the event. It also adds the properties dx and dy, which contain the difference between the starting position and the position as a pixel value. We can work out if we’re going backwards or forwards by seeing if dx is less or more than zero.

001 PolymerGestures.addEventListener(this.$.gallery, 'trackend',     function (e) {    
002        if (e.dx < 0)
004     else
005        self.previous();
006     });

Prevent FOUC

We can style our custom element just like any other element. A new pseudo-class specific to Web Components is :unresolved. This is removed when the element has been upgraded – Polymer adds a fade of 200ms to add a smooth transition so that we can prevent a flash of unstyled content (FOUC).

001 <body unresolved>

:host selector

According to the W3C specification `shadow-gallery` will not match your own element from within itself. To do this we use the new `:host` pseudo-class. This is the selector with the lowest specificity, it can be easily overridden by the user of this Web Component. We’re going to make the element inline-block so it can sit amongst content.

001     :host {
002     display: inline-block;
003        }

:host-context selector

We can also target the :host with a specific selector with `:host-context`. We’re going to give the option to add a class and add a CSS grayscale filter. :host-context is a way to look at ancestors of your custom element so that you can change its appearance based on classes outside of your control.

001 :host-context(.filter-grayscale) {
002 -webkit-filter: grayscale(1);                
003     filter: grayscale(1);
004     transition: all 0.15s linear;
005     }

Add hover state

You might think that you’d be able to add pseudo-states to host-context like `:host-context(.filter-grayscale):hover` but this isn’t quite right. The entire selector needs to be within the parentheses. In this example we could also write `:host(.filter-grayscale:hover)` as we’re targeting a class on the element.

001 :host-context(.filter-grayscale:hover) {
002     -webkit-filter: grayscale(0);                
003     filter: grayscale(0);
004     }

Put it together

Now that our element is functionally complete, we can start putting it all together in our index.html file. It’s got more attributes than most other elements but we need to communicate the images somehow. Elements added by the user within shadow-gallery will appear in the DOM but not under the shadow tree node.

001 <shadow-gallery width="400" auto="true" interval="5000"
002     images='[{"src": "",     "alt": "Image 1"},
003   {"src": "", "alt":     "Image 2"},
004     {"src": "", "alt":    "Image 3"},
005     {"src": "", "alt":    "Image 4"},
006      {"src": "", "alt":      "Image 5"}]'>
007 </shadow-gallery>

Reuse our component

The beauty of Web Components is that encapsulation and reuse is built in so we can create a new one simply by including another element. We don’t have to worry about globals or elements interfering with each other. There’s so much more to the Polymer project and so much more that you can do with Web Components, so get creating!

001 <shadow-gallery class="filter-grayscale" width="400"
002 images='[{"src": "                nature/1", "alt": "Image 1"}, {"src": "http://lorempixel.                com/400/200/        nature/2", "alt":     "Image 2"}, {"src": "http://  lorempixel.        com/400/200/    nature/3", "alt": "Image 3"}]'>
003   </shadow-gallery>