4.2 Web Components
Web components enable you to use custom HTML elements in your HTML documents, that will render as complex widgets: a better looking calendar, an input text with vocal recognition, a nice chart, etc
They're encapsulated as single html elements:
<x-gif src="http://i.imgur.com/iKXH4E2.gif" ping-pong></x-gif>
To include an element, use the <link>
element:
<link rel="import" href="dist/x-gif.html">
Resources: webcomponents.org customelements.io polyfill
The four specifiacations that make up Web components:
- HTML templates
- Shadow DOM
- Custom Elements
- HTML Imports
HTML templates
You create an html template
element that later you clone to add it to the DOM and display it on the page:
<template id="mytemplate">
<img src="" alt="great image">
<div class="comment"></div>
</template>
You normally add this template element on the top of the page, then clone it into your page with document.importNode(templateContent, true)
The parameter true
means it will be a deep copy of the content.
Simple example to clone:
// 1. Get a reference to you template element
var t = document.querySelector('#mytemplate');
// 2. Update it's contents (e.g. add a logo)
// Populate the src at runtime.
t.content.querySelector('img').src = 'http://webcomponents.github.io/img/logo.svg';
// 3. import the element (it will be a copy) to the document
// Clone the template, sort of "instantiation"!
var clone = document.importNode(t.content, true);
document.body.appendChild(clone);
The Shadow DOM
Provides encapsulation: to the parent document the Shadow DOM is just one element all the internals are hidden.
Shadow root
It's a kind of node
Shadow host
An element that has a shadow root associated with it is called a shadow host. It's the element that will contain the shadow root.
It's the shadow* host* that is rendered and not the shadow* root.*
How to use
- get a reference to a standard HTML DOM element (the host)
- run
createShadowRoot()
on it (the root) - the original content (e.g.
textContent
) is totally removed, new content has to be programatically added
<button>Hello, world (not rendered)!</button>
<script>
var host = document.querySelector('button');
var root = host.createShadowRoot();
root.textContent = 'the shadow root node is rendered';
</script>
Combining HTML templates with the shadow DOM
- create an HTML template element with html, css, js in it (
<template>...</template>
- select an HTML element in the document to declare it as root (
var root = document.querySelector("mydiv").createShadowRoot()
) - copy insert your template into root (
root.appendChild(document.importNode(t.content, true))
)
Insert the shadow host content into the cloned template
<template>
element provides a <content>
element. If you add this element to the template, then all the contents (including html elements) of the host element will be copied into it (the content element). The above syntax remains the same, you just need to add the <content>
child element to your <template>
.select
attribute
<content/>
blocks.select="queryselector"
to the <content>
element the will work as a selector inside the host element to select which child node to insert. External styling of the Shadow DOM
Insert CSS styles with JavaScript
root.innerHTML = '<style>h3{ color: red; }</style>' + //your content here
CSS :HOST
selector to style the host from the shadow root
Inside your shadow DOM element:
root.innerHTML = root.innerHTML = '\<style\>' +
':host { text-transform: uppercase; background-color:red;}' +
'\</style\>' +
'\<content\>\</content\>';
use :host(:hover)
to react to mouse events
The :host-context(<selector>)
pseudo class matches the host element if it or any of its ancestors matches the <selector>
.
Style the shadow DOM from the outside
Use ::shadow
selector (.active::shadow a{...}
)
HTML Custom Elements
<my-widget>
)- The element's name must have a dash
- The prototype must start from an already existing HTML element (from the spec) or another custom element
1. Define the custom element and contents to be injected in html
<body>
<my-widget>
<span id="titleToInject">Title injected</span>
<span id="partToInject">Paragraph injected</span>
</my-widget>
</body>
2. Create an HTML template with content
elements and select
attributes
<template id="mytemplate">
<style>
h1 {
color:white;
background:red;
}
</style>
<h1 part='heading'>
<content select="#titleToInject"></content>
</h1>
<p part="paragraph">
<content select="#partToInject">
</content>
</p>
</template>
3. Do the JavaScript ceremony
- copy import your template element
var clone = document.importNode(document.querySelector("#mytemplate").content, true)
- create a new object from
HTML.prototype
:var widgetProto = Object.create(HTMLElement.prototype)
- declare it as a shadow root when it's created:
widgetProto.createdCallback = () => this.createShadowRoot().appendChild(clone)
- register the new element with
document.registerElement
:var Widget = document.registerElement("my-widget", {prototype: widgetPrtoto});
HTML Imports
<link rel="imports" href="your_html_file">
in order to import all the html/css/js that define the Web components you plan to use