Web Components – The Future Web

Tweet about this on TwitterShare on LinkedIn0Share on Google+0Share on Reddit0Buffer this pageFlattr the authorEmail this to someonePrint this page

Web Components & Polymers – The Fabric of Future

This blog post is about Web Components. What they are, why do we need them and how to go about creating a few of our own. We will also look at the browser support, make sure our Web Components work everywhere with the help of polyfills like Polymer. And finally, we will be using Yeoman to scaffold a Polymer project and using Yeoman sub-generators to scaffold new Web Components.

So, let’s get rolling.

Contents

The Examples in this tutorial can be found at GitHub.

Introduction

If you are kind of a serious developer and have worked on more than 4 – 5 web based projects, there would be instances where you have copy pasted chunks of HTML, CSS & JS code into your new project from existing projects.

A simple example I can think of is using Bootstrap for a new project. I would generally go to TWBS site, get the CDN link for CSS, then go to the components and inspect them in dev tools. Then copy paste the complete chunk into my project.

Well, this is a solution, but this can be done a lot better with Web Components.

In simple,

Web Components  === Write your own HTML tags & behaviours

Like an Audio tag or Video tag or filterList tag or Tumblr Search Tag.

Imagine you had an option to build your project like

Doesn’t this look a lot readable?  And since our custom tags takes arguments like the app-header tag – with a list of menu items, the same header component can be reused, with different menu items across pages and projects.

With such an implementing a cloud based multi-tenant applications is like a walk in the park!

Web Components

Web components are a collection of APIs that browser developers use. Yes, this is the secret recipe that people at Mozilla or Chrome use to provide us tags like the video, audio, select, textarea etc. Have you ever wondered how a input tag would actually be rendered? Take a look Screen Shot 2014-03-26 at 7.18.31 pmYup!! Its a div tag! Don’t worry about the #shadow-root, we will take a deeper look soon! And a Video tag? Screen Shot 2014-03-26 at 7.44.55 pm What does this mean? If we have the power to write our own custom tags, we for sure will not depend on browsers for support! For example : How long have we actually waited from the time we heard about the audio tag, till we see it fully working in modern browsers? – A lot! Right? And also, almost all of our custom code is reusable.

So this is what Web Components gives us power to do.

What are Web Components made of?

A “Web Component” is brought to life with the below 4 pieces

  • HTML Templates (w3c)

  • Shadow Dom (w3c)

  • Custom Elements (w3c)

  • HTML Imports (w3c)

Lets take a look at each component.

HTML Templates

In simple terms, these are just <template></template>. Its just like any other HTML tag but,

  • They are inert – i.e. code inside this tag is just parsed, Not rendered on page load.
  • Example:
  • The image src will not be fired. The audio will not play.
  • Any script written inside will not run.
  • Most importantly, the content inside the tag is hidden from DOM. We can’t find it using say jQuery like  $("template img") or  document.querySelectorAll("template img").

If we are building a new component, this is the place where all the HTML CSS & JS will be placed. Then when the time comes, we will pull out the content from the template tag, clone it & then insert it into the DOM.

Shadow DOM

First lets do something awesome and then learn about Shadow DOM. Fire up Google Chrome (don’t tell me you don’t have one!) and then open a new tab & type in  chrome://flags. Next, search for “Experimental Web Platform Features” and enable that feature. Close all the running Chrome instances and fire up a new one.

Next, let’s open up a new tab, open dev tools (ctrl+shft+i or cmd+shft+i) and click on the gear icon. Then, under elements section check “Show Shadow DOM” and close the dev tools for now. Screen Shot 2014-03-27 at 3.46.18 pm Click here and you will see a few HTML elements. Right Click >> Inspect Element. You will now see the Shadow DOM of that element. Explore for a while and come back to see more on it.

Shadow DOM in simple terms will let you encapsulate markup, styles and scripts. This is one of the key piece of Object Oriented Programming, that restricts the visibility and accessibility of content/code of an Object to the Outside World.

Imagine a page that is built using various jQuery plugins from different vendors. And we are copy pasting the CSS & JS. How many times have you noticed that one plugin is interfering with the other, say CSS classes. And then you had to go and make changes to the plugin specific CSS to fix the issues. This means, the styles are not encapsulated.

Before understanding Shadow DOM, we need to know more about document trees.

Vocabulary [w3c]

  • A document tree is a node tree [DOM] whose root node is a document.
  • A shadow host is an element that hosts one or more node trees.
  • A shadow tree is a node tree hosted by a shadow host.
  • A shadow root is the root node of a shadow tree.
  • If more than one shadow tree is hosted by the same shadow host, the more recently added shadow tree is called the younger shadow tree and the less recently added shadow tree is called the older shadow tree.
  • If there is no older shadow tree than a given shadow tree, the shadow tree is called the oldest shadow tree.
  • If there is no younger shadow tree than a given shadow tree, the shadow tree is called the youngest shadow tree.
  • The older shadow root is the root node of the older shadow tree
  • The younger shadow root is the root node of the younger shadow tree
  • The oldest shadow root is the root node of the oldest shadow tree
  • The youngest shadow root is the root node of the youngest shadow tree

What is all this? This is the basics of Shadow Tree! Very confusing right! I was confused too. This is the concept that rules how a Shadow DOM gets going.

Shadow DOM example

In order to try the below examples, use Chrome Canary v31 or greater. And also make sure you have enabled the following in Chrome’s  chrome://flags

  • Experimental Web Platform features
  • Experimental JavaScript

Lets create a simple template, that will display a message.

The Shadow DOM-ming (pull the stuff from the template and insert it into the holder)

The final output to the DOM will be

Got the flow? Couple of things

1. If my parent DOM tree has another node (div/span/p ..) with class msg, the style class inside the template will not affect it. – Encapsulation!

2. When the DOM insertion happens, you can modify the content inside the template using insertion points like

Here the content tag does the magic! Do observe the select attribute.

How does Shadow DOM work?

Understanding this piece was super complex. If you are looking to just dip your feet into Web Components, you can skip this part. from W3C

..  The shadow DOM allows multiple DOM trees (in addition to the document tree) to be composed into one larger tree when rendered. The existence of multiple DOM trees is enabled by letting any element in the document tree to host one or more additional DOM trees. These shadow trees are governed by a set of rules that establish encapsulation boundaries while retaining the standard DOM composability semantics.

The encapsulation boundaries between the document tree and shadow trees are called shadow boundaries. The elements that host shadow trees are called shadow hosts, and the roots of the shadow trees are called shadow roots.

Super duper confusing right? I guess this will help you understand how DOM trees work. The Left tree is the DOM tree and the right is the template tag excerpt. Screen Shot 2014-03-28 at 12.10.01 pm When we create a new Shadow root and append the template to DOM, we get the below tree Screen Shot 2014-03-28 at 12.10.12 pmStill confused? Watch this video

Shadow DOM resources

Custom Elements

HTML Templates + Shadow DOM = Custom Elements

Yep! Its that simple. Custom Elements are a “fancy” way to render the template content. In the earlier example, instead of

<div id="myMessage"></div> if I had a  <message></message>

Would’nt this make more sense? And more readable and like awesome!

Custom Elements can also be extended from existing elements, and every custom element exposes a few life cycle methods.

Creating Custom Elements

Using the  registerElement() , we register a new element.

The  registerElement()  takes 2 arguments. First the elements name (mandatory) and second the HTMLElement prototype object (optional). The only restriction while creating a custom elements name is it should consist of a dash (-). This is to make sure that custom tags can be distinguished from regular elements and also to make sure that new elements created in HTML6 (say) will not interfere with custom elements we have created.

You can also inherit from existing elements like

Now, <custom-button> </custom-button>, will have all the features of a button tag and you can add more to it. This is how we use it Declare the tag

Next create the definition using JS

And then init with the new keyword

Custom Element Creation algorithm

Below is an excerpt from W3C, explaining the custom element creation algorithm, have a quick glance, don’t worry if you do not understand anything – the custom tags will still work without this knowledge.

  • 1. Let ERROR be None
  • 2. Convert TYPE to lowercase
  • 3. If DOCUMENT is an HTML document, convert NAME to lowercase
  • 4. If TYPE is an invalid custom element type, set ERROR to InvalidType and stop.
  • 5. Let NAMESPACE be HTML Namespace
  • 6. If PROTOTYPE’s interface inherits from SVGElement, set NAMESPACE to SVG Namespace
  • 7. If there already exists a definition with the same TYPE, set ERROR to DuplicateDefinition and stop.
  • 8. If NAME was provided and is not null:
    • Let BASE be the element interface for NAME and NAMESPACE
    • If BASE does not exist or is an interface for a custom element, set ERROR to InvalidName and stop.
  • 9. Otherwise:
    • If NAMESPACE is SVG Namespace, set ERROR to InvalidName and stop.
    • Let NAME be TYPE
  • 10. LIFECYCLE be lifecycle callbacks
  • 11. Transfer callback named created to LIFECYCLE from property named createdCallback on PROTOTYPE
  • 12. Transfer callback named attached to LIFECYCLE from property named attachedCallback on PROTOTYPE
  • 13. Transfer callback named detached to LIFECYCLE from property named detachedCallback on PROTOTYPE
  • 14. Transfer callback named attributeChanged to LIFECYCLE from property named attributeChangedCallback on PROTOTYPE
  • 15. Let DEFINITION be an element definition with custom element type set to TYPE, local name to NAME, namespace to NAMESPACE, custom element prototype to PROTOTYPE, and lifecycle callbacks to LIFECYCLE.

Custom Elements Lifecycle Methods

Event Trigger Point
createdCallback When an instance of the custom element is created
attachedCallback When an instance of custom element is attached to the DOM
detachedCallback When an instance of custom element is removed from the DOM
attributeChangedCallback When an attribute of the custom element is added or updated or removed

So a complete glance of what we have seen so far would be

Using Custom Tag 

Custom Tag Definition

HTML  Imports

So far, we have created a new custom tag, that will display a message. To make this code reusable, we will write the tag definition inside  a my-message.html file. Then using HTML imports, we will include the definition on all the pages we use the my-message tag.

Importing HTML is not an existing feature. This has been made available to us as part of the Web Components spec.

Usage

or

Import will tell the parser to go and fetch the HTML document & make it available for use. That typically mean, you can do this

A complete example with all pieces of Web Components will be

index.html

/templates/my-message.html

This is a bird’s eye view of Web Components. Now, lets do some hands on stuff and create a few cool Web Components.

Browser Support

Before we get started, we need to see how many of the above components are supported. Here is a quick view

Feature Safari Firefox Nightly Google Chrome Opera Internet Explorer
HTML Templates No Yes Yes No No
Shadow DOM No No Yes No No
Custom Elements No Yes Yes No No
HTML Imports No No No No No

So, to make sure the Future of web can be used from today itself, we have a couple of “major” Polyfills. One Google’s Polymer and next is Mozilla’s Brick. In this post, we will use Polymer to create some components.

Polymer

Polymer is more than a Poly fill, it is a complete Web UI Framework. Polymer consists of

  • Foundation (platform.js) This is the polyfill layer
  • Core (polymer.js) A wrapper for foundation layer, with helpers
  • Elements: UI and non-UI components built on Core.

Platform.js has a support for the following

  • Shadow DOM
  • Custom Elements
  • HTML Imports
  • DOM Mutation Oberservers and Object.observe()
  • Pointer Events
  • Model-Driven Views (MDV)
  • Web Animations

More info here.

Yeoman

Yeoman is

THE WEB’S SCAFFOLDING TOOL FOR MODERN WEBAPPS

If you have used Yeoman before, you know the power of it. It is a single scaffolding tool that you need to create & manage your projects. You can read more here.

Yeoman Polymer Generator

We are going to use Yeoman’s Polymer generator to create a new project. First lets install Yeoman. First Install Node. Details here. Next, we will install Yeoman.

Windows Mac/*nix
npm install -g yo  sudo npm install -g yo

Next, we will install the polymer generator

Windows Mac/*nix
npm install -g generator-polymer    sudo npm install -g generator-polymer

Once this is done, create a new folder and name it myWebComponents. Then create a new folder called as helloPolymer.  Using terminal/prompt, CD into the helloPolymer folder. Then run

yo polymer

Yeoman will ask if, you would want Twitter Bootstrap for Sass. Enter N

You should see something like this

Screen Shot 2014-03-28 at 7.47.12 pmAnd the process will go on for a few minutes, till Yeoman runs npm install and bower install. Once this is done, the prompt will return and your folder structure should look like

Screen Shot 2014-03-28 at 7.51.00 pmIf you do not see node_modules folder, run npm install && bower install  manually. If you do not see the app/bower_components, run bower install .

Back to prompt run  grunt server, [you can know more about Grunt here] Screen Shot 2014-03-30 at 4.54.25 pmthis will build the project, run it at port 9000 & will start listening for changes on the project files. When the command is completed, it will open a page that should look something like Screen Shot 2014-03-30 at 4.58.17 pmand when you view source, you will find 2 custom tags <polymer-greeting> & <polymer-list>. This is a sample code that comes as part of the scaffolding.

Generated Files Structure

We will be mostly working with the app folder during the development phase. And both app & test folders in case of a Test Driven Development. Once the development or part of development is completed, you can generate a build from the dev folder, using  grunt build, we will look at it a bit later.

App folder

The app folder consist of our index.html file and elements folder. The elements folder is the place where we store all the “custom tag” definitions, like <polymer-greeting> & <polymer-list>. We include these in our index.html.

The primary necessity for this project is the platform.js as mentioned earlier. This will provide the support for all the other stuff we are doing. Next, the definition of the custom element we want to use. Also do note the WebComponentsReady event listener in index.html. This is kind of like document ready in jQuery, very handy.

Test Folder

The project scaffolds mocha to run tests for the project. You can use any other framework as you need. Simply add it to the package.json and run npm install. To execute the default test, back to prompt/terminal run  grunt test. This will run the test configured inside test/spec/test.js. And the results can be found when you open test/index.html. Standard stuff.

Dist Folder

Finally the distribution folder. After the development is done, we want to build our code and make it production ready with minification, gzipping etc. For that, run  grunt build to build the project from app folder and to see the output run  grunt server:dist. PS: You can modify the Grunt tasks as per your taste with the Gruntfile.js file present at the root of the folder.

As of today, if you did see that  grunt server:dist will run your project but there won’t be a proper output. This is because of this issue and this is the solution Screen Shot 2014-03-30 at 5.31.19 pmWe need to copy the bower_componets folder from the app folder to the dist folder. Another option is to use grunt-bowercopy to copy the files to the dist folder. Or, you can update the app folder to copy the polymer.js file to the scripts folder and ship it along with all the other files to dist. Your call.

For now, we will copy the bower_components folder from app folder to dist folder manually. First run  grunt server:dist and then copy the folder from app to dist, else this command will clear the folder contents. And yes, you need to do this every time. Then, refresh the page in browser and you should see the expected output.

As per the solution given by Addy Osmani above, you will also find a build.html, which consist of smushed up code for all the elements with references to app folder.

Creating a New Custom Element with Yeoman

Yeoman has this cool things called as sub generators. This can be used to easily add more “components” to our project. Like, adding a new Custom tag. Lets create a new tag named mega-menu. The idea behind this tag is we would want the developer using this tag to provide us the menu items

and we will render them out like Screen Shot 2014-03-30 at 7.47.57 pm (CSS3 Menu Source). And for this example. we will be doing only a 2 level menu.

If you want you can create a new project and work on it, else, we can add this new element to our current project.

For this example, lets add the new element to our helloPolymer project itself. Back to terminal and run

  yo polymer:element mega-menu

and you will be presented with a screen like Screen Shot 2014-03-30 at 6.13.35 pm Use arrows keys to move up and down, space bar to select & enter to complete this step.  Select only import to your index.html.. option here and hit enter, next yeoman would ask Screen Shot 2014-03-30 at 6.15.49 pmLeave blank by hitting the return key. In the later stage of the project, If you want to import any other components that your new component is dependent on, you can do it here. Next, Screen Shot 2014-03-30 at 6.17.39 pm Leave it blank for now. If you want to include any polymer components like an accordion or animated pages you can do so here. A list of Polymer components can be found here.

And finally we are done! Screen Shot 2014-03-30 at 6.19.58 pm You can go to app/elements and you will find a new file named mega-menu.html. This file will consist of the tag definition. By default, we have some code added here. The template & the Shadow DOM script are wrapped inside a polymer-element tag. Do observer the style tag inside the template.  :host is a pretty awesome selector for Web Components, This selector selects the parent tag, i.e. mega-menu. You can use this to style the parent, like display : block, inline; borders & margins etc.

Next, the script. This has a method named Polymer(tag-name, properties). This method is the wrapper for creating a Shadow DOM.

Line 2 : applyAuthorStyles, this will let the author (or the tag user) style the content inside Shadow DOM. Default is false.

Line 3 : resetStyleInheritance, Indicates whether or not the inheritable CSS properties are set to the initial value. Default is false.

Line 10 : enteredView, same as attachedCallback

Line 12 : leftView : same as detachedCallback

If you go to index.html, you will see that the mega-menu tag has already been added to the page, but it says <polymer-mega-menu>.

If you want to change it to <mega-menu>, you can by changing the value of name attribute inside mega-menu.html, polymer-element tag & updating the first argument of the Polymer(). Then you can use <mega-menu> in index.html.

Again, the above step is optional & totally your preference. I did it just to make the component more readable.

Now, let get coding the details. Open app/index.html and update the mega menu tag and remove the unused tag and the final index.html would be

And then, we will update the app/elements/mega-menu.html to read the menu items and build a menu. Now, inside the template tag, create a new ul with id as wrapper like

<ul id="wrapper"></ul>

Then inside the ready method, we will read the parent and child tags of the  mega-menu tag & build our markup. The ready method would be something like

The above code can be written in a lot of different ways and a lot efficiently, but this is just a tutorial.

Do notice the  this.$.wrapper on line 14. This is how we can access the contents inside the template tag using Polymer’s API of $. Here wrapper is the id of the ul tag.

And finally the styles. We will ship the styles as part of the component, so we are going to add this style tag inside the template tag.

And the final mega-menu.html will look like

Save all the files. Back to prompt/terminal, and run  grunt server and you should see a page like Screen Shot 2014-03-30 at 7.55.04 pm You can tweak the styles & positioning to your need. Depending on the component, you can also leverage the other methods inside the Polymer(), like enteredView(), attributeChanged() etc.

PS : If you are planning to register events, you need to use the attribute like

<button on-click="{{handleClick}}"></button>  

and inside the Polymer(), you will create a new method named handleClick. Same thing goes for other events. 

This concludes our tour of Web Components & building our own with Yeoman Polymer generator.


Thanks for reading! Do comment.
@arvindr21

Tweet about this on TwitterShare on LinkedIn0Share on Google+0Share on Reddit0Buffer this pageFlattr the authorEmail this to someonePrint this page
  • Francis

    Hi,
    Fascinating article! I heard of dark matter but not of shadow DOM!
    I’m a bit hesitant to plunge into the subject as the paint seems not to have dried yet on the specs, and browser coverage sounds problematic (am I wrong?)

    • / Arvind Ravulavaru

      Hello Francis. Yes, Web Components are pretty new in the market. And yes, the browser support is sketchy. To be honest (the latest release of) Chrome is the only browser to have a full Web Components support. But as with any technology there will be challenges.

      To get started off with Web Components, you can always use polyfills. Once you get a feel of web components and what they are really capable of, you can make a decision of using it or not. And do keep in mind that Web components are not production ready yet.

      • Francis

        I think it has great potential. I did some Flex in my time and one of its major attractions was the ease with which you can create namespaces/components. When you can say that of javascript it really will be a step forward.

        • / Arvind Ravulavaru

          Yes it does add a lot of value. Thanks for sharing your views Francis.

  • Owen Densmore

    My chrome and canary have “Show user agent shadow DOM” rather than “Show Shadow DOM” which seems to eliminate the ability to see the shadow DOM for standard elements.

    Is that a change of some sort or am I making some sort of mistake?

    Thanks for the clear article.

    • / Arvind Ravulavaru

      Thanks Owen!

      I have inspected a few elements present on this page : http://jsfiddle.net/arvindravulavaru/ZgL8k/1/show on Chrome 35.0.1916v and I am able to see the Shadow DOM. I have enabled the same “Show user agent shadow DOM” config option.

      Can you inspect elements on the above page and check if you are able to see the Shadow DOM?

      • Owen Densmore

        It’s all working now, not sure why it didn’t initially show the elements. This is so sweet!

        Just curious: any opinions/ideas of WebComponents vs React?

        • / Arvind Ravulavaru

          Great!
          I did take a quick look at React a few days ago, but not enough for comparisons. Will dig deeper do this weekend and let you know. :)
          Thanks.