{"id":1432,"date":"2014-12-17T01:13:13","date_gmt":"2014-12-17T06:13:13","guid":{"rendered":"http:\/\/cbateman.com\/blog\/?p=1432"},"modified":"2015-05-13T22:03:08","modified_gmt":"2015-05-14T03:03:08","slug":"a-no-nonsense-guide-to-web-components-part-1-the-specs","status":"publish","type":"post","link":"https:\/\/cbateman.com\/blog\/a-no-nonsense-guide-to-web-components-part-1-the-specs\/","title":{"rendered":"A No-Nonsense Guide to Web Components, Part 1: The Specs"},"content":{"rendered":"<p>This is Part 1 of a 3-part series.<\/p>\r\n<ul>\r\n<li>Part 1: The Specs<\/li>\r\n<li><a href=\"http:\/\/cbateman.com\/blog\/a-no-nonsense-guide-to-web-components-part-2-practical-use\">Part 2: Practical Use (Browser Support and Other Challenges)<\/a><\/li>\r\n<li><a href=\"http:\/\/cbateman.com\/blog\/web-components-in-angular-ember-and-react\/\">Part 3: Web Components in Angular, Ember, and React<\/a><\/li>\r\n<\/ul>\r\n<h2>Introduction<\/h2>\r\n<p>This is a crash course for getting familiar with Web Components. It strives to be concise, rather than exhaustive. There a lot of other great resources available on the topic: check out HTML5 Rocks&#8217; <a href=\"http:\/\/www.html5rocks.com\/en\/search?q=web+components\">tutorials<\/a> and <a href=\"https:\/\/github.com\/mateusortiz\/webcomponents-the-right-way\">this massive list of resources<\/a>.<\/p>\r\n\r\n<h2>Why Web components?<\/h2>\r\n<p><strong>First<\/strong> \u2014 they&#8217;re easy to manage \u2013 thay can instantiate themselves and clean up after themselves.<\/p>\r\n<p><strong>Second<\/strong> \u2014 Simple, declarative usage means they&#8217;re easy to include and configure:<\/p>\r\n<pre class=\"pre\"><code class=\"lang-markup\">&lt;link rel=\"import\" href=\"my-dialog.htm\"&gt;\r\n\r\n&lt;my-dialog heading=\"A Dialog\"&gt;Lorem ipsum&lt;\/my-dialog&gt;<\/code><\/pre>\r\n<p><strong>Third<\/strong> \u2014 they&#8217;re modular and reusable. A standard format for building, implementing, and interacting with UI components means that it&#8217;s easy to use them across different frameworks and environments.<\/p>\r\n<p><strong>Fourth<\/strong> \u2014 they can provide encapsulation for components&#8217; styles and HTML. So they play nice with other styles and things happening on a page.<\/p>\r\n<h2>The Specs<\/h2>\r\n<p>Web Components are made up of 4 separate specifications. They go together nicely, but you don&#8217;t have to use them all \u2013 you can pick and choose based on your situation. <strong>Custom Elements<\/strong> and <strong>Shadow DOM<\/strong> are most important; <strong>HTML Imports<\/strong> and <strong>Templates<\/strong> are really just handy. <\/p>\r\n<p>We&#8217;re sticking to native code for now (no polyfills), so be sure to use a browser that <a href=\"http:\/\/caniuse.com\/\">supports<\/a> the spec, when looking at the demos.<\/p>\r\n<h3>Custom Elements<\/h3>\r\n<p>Custom Elements are the heart of Web Components. This API lets you create new elements, add public methods to them, and gives you 4 lifecycle callbacks to manage them.<\/p>\r\n<p>When you use a typical JS-based component (for example, like: <code>var myModal = new Modal(...);<\/code>), the instance has to be stored somewhere, so that you or others can call functions on it later (<code>myModal.show();<\/code>). With Custom Elements &ndash; the actual element in the DOM <em>is<\/em> the instance. The functions are available right on the element.<\/p>\r\n<p>All you have to do is create an object to be used as your element&#8217;s prototype, add the callbacks and functions to it, and then register it with a hyphenated name (all custom elements must have a hypen \u2013 that&#8217;s how you know they&#8217;re not native elements).<\/p>\r\n<pre class=\"pre\"><code class=\"lang-javascript\">\/\/ &lt;my-element&gt;&lt;\/my-element&gt;\r\n\r\nvar myProto = Object.create(HTMLElement.prototype);\r\n\r\n\/\/ Lifecycle callbacks\r\nmyProto.createdCallback = function() {\r\n    \/\/ initialize, render templates, etc.\r\n};\r\nmyProto.attachedCallback = function() {\r\n    \/\/ called when element is inserted into the DOM\r\n    \/\/ good place to add event listeners\r\n};\r\nmyProto.detachedCallback = function() {\r\n    \/\/ called when element is removed from the DOM\r\n    \/\/ good place to remove event listeners\r\n};\r\nmyProto.attributeChangedCallback = function(name, oldVal, newVal) {\r\n    \/\/ make changes based on attribute changes\r\n};\r\n\r\n\/\/ Add a public method\r\nmyProto.doSomething = function() { ... };\r\n\r\n\r\ndocument.registerElement('my-element', {prototype: myProto});<\/code><\/pre>\r\n<p>This is fantastic, because it means that your components can both self-initialize and self-destroy.<\/p>\r\n<p>Let&#8217;s say you have a page where a user action can open up a new widget. This particular widget adds some keyboard listeners to the page to check for shortcuts. Today, you&#8217;d probably call a JS function to initialize the widget. And when the user closes it, you&#8217;d need to call a destroy function that would remove the event listeners. Because you don&#8217;t want those listeners to stick around \u2013 using up memory and continuing to take action on events.<\/p>\r\n<p>With Custom Elements \u2013 you (or your framework) don&#8217;t have to worry about those details. Just insert the element into the DOM to initialize it, and when you remove it from the DOM, it can clean up after itself. Awesome!<\/p>\r\n<p>You can also extend native elements, like this:<\/p>\r\n<pre class=\"pre\"><code>\/\/ &lt;input is=\"my-input\"&gt;\r\ndocument.registerElement('my-input, {\r\n    prototype: myProto,\r\n    type: 'input'\r\n});<\/code><\/pre>\r\n<p><strong>Update 4\/2015:<\/strong> Extending with <code>is=<\/code> will most likely be <a href=\"https:\/\/lists.w3.org\/Archives\/Public\/public-webapps\/2015AprJun\/0515.html\">removed<\/a> from the spec. It may return in a different form, in a future version.<\/p>\r\n<p><a href=\"https:\/\/chrisbateman.github.io\/guide-to-web-components\/demos\/custom-elements.htm\">Custom Elements Demo<\/a><\/p>\r\n<h3>Shadow DOM<\/h3>\r\n<p>Shadow DOM encapsulates elements. It allows you to hide a number of elements inside of an element &#8211; much like browsers do with their native UI elements (e.g. the controls in a <code>&lt;video&gt;<\/code>). This prevents other code on the page from accidentally messing with your element &#8211; and vice-versa.<\/p>\r\n<pre class=\"pre\"><code class=\"lang-javascript\">var shadowRoot = element.createShadowRoot();\r\nshadowRoot.appendChild(whatever);<\/code><\/pre>\r\n<p>You can add CSS inside a shadow root, and it won&#8217;t select elements outside of the shadow root (Note that you can&#8217;t put <code>&lt;link&gt;<\/code> tags in a shadow root. To reference an external stylesheet, use <code>@import<\/code> in a <code>&lt;style&gt;<\/code> tag). To target the element holding the shadow root, just use the <code>:host<\/code> selector.<\/p>\r\n<p>And conversely, CSS selectors on the page won&#8217;t select elements inside a shadow root (and that goes for querySelector too). But those elements will still inherit inheritable properties (like font-family).<\/p>\r\n<p style=\"text-decoration:line-through;\">If the page needs to style something that&#8217;s in a shadow root, it&#8217;s still possible, and the intention of your CSS will be very obvious (which is a good thing):<\/p>\r\n<pre class=\"pre\" style=\"text-decoration:line-through;\"><code class=\"lang-css\">my-element::shadow p {\r\n    \/* selects &lt;p&gt; tags in shadow roots of &lt;my-element&gt;'s *\/\r\n}\r\nbody \/deep\/ p {\r\n    \/* selects all &lt;p&gt; tags - in shadow roots or not *\/\r\n}<\/code><\/pre>\r\n<p><strong>Update 4\/2015:<\/strong> The above features are being removed from the spec.<p>\r\n<p>There&#8217;s one other thing you can do with Shadow DOM: leave an element&#8217;s contents outside of the shadow root \u2013 so they&#8217;re still accessible to the page \u2013 but visually reflow them as if they <em>were<\/em> in the shadow root. Just add a <code>&lt;content&gt;<\/code> element, and it will reflow any children of the root element:<\/p>\r\n<pre class=\"pre\"><code class=\"lang-markup\">&lt;my-element&gt;\r\n    #shadow-root#\r\n        &lt;content&gt;&lt;\/content&gt;\r\n        &lt;p&gt;one&lt;\/p&gt;\r\n    #\/shadow-root#\r\n    &lt;p&gt;two&lt;\/p&gt;\r\n    &lt;p&gt;three&lt;\/p&gt;\r\n&lt;\/my-element&gt;<\/code><\/pre>\r\n<p>In that example \u2013 you&#8217;ll see the lines orderd as &#8220;two three one&#8221; but only the &#8220;one&#8221; is actually encapsulated in the shadow root.<\/p>\r\n<p>Note that if there wasn&#8217;t a <code>&lt;content&gt;<\/code> element, the &#8220;two&#8221; and &#8220;three&#8221; paragraphs would not be visible (a shadow root hides the other children).<\/p>\r\n<p><a href=\"https:\/\/chrisbateman.github.io\/guide-to-web-components\/demos\/shadow-dom.htm\">Shadow DOM Demo<\/a><\/p>\r\n<h3>HTML Imports<\/h3>\r\n<p>Imports give you a single place to put the styles, scripts, and templates required for a component, so pages only need to include one thing.<\/p>\r\n<pre class=\"pre\"><code class=\"lang-markup\">&lt;link rel=\"import\" href=\"dialog.htm\"&gt;<\/code><\/pre>\r\n<p>CSS in an import will apply to the page, and scripts will execute in the usual global context.<\/p>\r\n<p>Other regular HTML elements in the import will <strong>not<\/strong> be visible on the page or accessible to things like querySelector. Though you can access anything in the import if you need to, like this: <code>linkElement.import.querySelector('#template');<\/code><\/p>\r\n<p>It&#8217;s very important to note that Imports will block the rendering of your page (same as plain JS and CSS resources do) \u2013 unless you add the <code>async<\/code> attribute (you can listen for the <code>load<\/code> event). Helpfully, scripts in an async import will still execute in order.<\/p>\r\n<p><a href=\"https:\/\/chrisbateman.github.io\/guide-to-web-components\/demos\/html-imports.htm\">HTML Imports Demo<\/a><\/p>\r\n<h3>HTML Templates<\/h3>\r\n<p>For storing HTML templates, you may have used strings in JS, or perhaps a <code>&lt;script&gt;<\/code> tag with a non-standard <code>type<\/code>. But now there&#8217;s a dedicated element for it:<\/p>\r\n<pre class=\"pre\"><code class=\"lang-markup\">&lt;template id=\"MyTemplate\"&gt;\r\n  &lt;div&gt;Some stuff&lt;\/div&gt;\r\n&lt;\/template&gt;<\/code><\/pre>\r\n<p>Using it is pretty straightforward:<\/p>\r\n<pre class=\"pre\"><code class=\"lang-javascript\">var clone = document.importNode(templateNode.content, true); \/\/ 2nd parameter for \"deep\" clone\r\n\/\/ now you can append the clone wherever you like<\/code><\/pre>\r\n<p>That&#8217;s it \u2013 nothing too fancy.<\/p>\r\n<p>And when it comes to Web Components \u2013 Templates are pretty useless without Imports (where else would you put them?).<\/p>\r\n<p><a href=\"https:\/\/chrisbateman.github.io\/guide-to-web-components\/demos\/html-templates.htm\">HTML Templates Demo<\/a><\/p>\r\n<h2>All Together Now<\/h2>\r\n<p>Now that we&#8217;ve looked at the pieces in isolation, let&#8217;s see how they look together. This is a super-basic example of how you might build a Web Component without any frameworks or polyfills.<\/p>\r\n<p><a href=\"https:\/\/chrisbateman.github.io\/guide-to-web-components\/demos\/all-together-now.htm\">Web Components Demo<\/a><\/p>\r\n<p>Fun stuff, but don&#8217;t get too excited just yet. In <a href=\"http:\/\/cbateman.com\/blog\/a-no-nonsense-guide-to-web-components-part-2-practical-use\">Part 2<\/a> we&#8217;ll talk about using Web Components in real life.<\/p>\r\n","protected":false},"excerpt":{"rendered":"<p>This is Part 1 of a 3-part series. Part 1: The Specs Part 2: Practical Use (Browser Support and Other Challenges) Part 3: Web Components in Angular, Ember, and React Introduction This is a crash course for getting familiar with Web Components. It strives to be concise, rather than exhaustive. There a lot of other great resources available on the&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1432","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/cbateman.com\/blog\/wp-json\/wp\/v2\/posts\/1432"}],"collection":[{"href":"https:\/\/cbateman.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cbateman.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cbateman.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/cbateman.com\/blog\/wp-json\/wp\/v2\/comments?post=1432"}],"version-history":[{"count":20,"href":"https:\/\/cbateman.com\/blog\/wp-json\/wp\/v2\/posts\/1432\/revisions"}],"predecessor-version":[{"id":1573,"href":"https:\/\/cbateman.com\/blog\/wp-json\/wp\/v2\/posts\/1432\/revisions\/1573"}],"wp:attachment":[{"href":"https:\/\/cbateman.com\/blog\/wp-json\/wp\/v2\/media?parent=1432"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cbateman.com\/blog\/wp-json\/wp\/v2\/categories?post=1432"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cbateman.com\/blog\/wp-json\/wp\/v2\/tags?post=1432"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}