web Components
- frontend bucharest meetup -
Because why the f*#k not?
(we've used worst things before)
Andrei Antal
KALON
Contents
- web components - the what and the how
- Polymer 1.0 (now at 1.2) - sweet sugar candy components
- conclusion and discussions
WEB COMPONENTS
- Set of standards that allow us to bundle markup, styles and scripts into custom HTML elements
- Slowly gaining support on browsers - but for now we need a polyfill ( http://webcomponents.org/ - 117KB minified)
- Google as the main driving force
- We've been using them but now ca can also create our own
WEB COMPONENTS
<input type="text" value="Please insert a value...">
<input type="range">
WEB COMPONENTS
WEB COMPONENTS
WEB COMPONENTS
The sin...
WEB COMPONENTS
Scumbag browser vendors
WEB COMPONENTS
Enabling the DOM they don't want you to see
WEB COMPONENTS
Core elements of web components:
- HTML Templates
- Custom elements
- Shadow DOM
- HTML Imports
WEB COMPONENTS - HTML TEMPLATES
Hiding HTML elements
<div style="display:none">
<p>My image here: </p>
<img src="image.jpeg">
</div>
<script type="text/template" id="template">
<div>
<h1>Web Components</h1>
<img src="image.jpeg">
</div>
</script>
Template scripts
WEB COMPONENTS - HTML TEMPLATES
Defining HTML templates
<template id="template">
<style>
...
</style>
<div>
<h1>Web Components</h1>
<img src="image.jpeg">
</div>
</template>
WEB COMPONENTS - HTML TEMPLATES
Initialising the template
<div id="host"></div>
<script>
var template = document.querySelector('#template');
var clone = document.importNode(template.content, true);
var host = document.querySelector('#host');
host.appendChild(clone);
</script>
<div id="host"></div>
<script>
var template = document.querySelector('#template');
var clone = document.importNode(template.content, true);
var host = document.querySelector('#host');
host.appendChild(clone);
</script>
<div id="host"></div>
<script>
var template = document.querySelector('#template');
var clone = document.importNode(template.content, true);
var host = document.querySelector('#host');
host.appendChild(clone);
</script>
<div id="host"></div>
<script>
var template = document.querySelector('#template');
var clone = document.importNode(template.content, true);
var host = document.querySelector('#host');
host.appendChild(clone);
</script>
WEB COMPONENTS - HTML TEMPLATES
if(!window.HTMLTemplateElement) {
....
}
Feature detection
WEB COMPONENTS - CUSTOM ELEMENTS
Build and use custom tags
WEB COMPONENTS - CUSTOM ELEMENTS
Building a custom element
var XComponent = document.registerElement('x-component');
<x-component></x-component>
var XComponent = document.registerElement('x-component');
var dom = new XComponent();
document.body.appendChild(dom);
document.registerElement('x-component');
var dom = document.createElement('x-component');
document.body.appendChild(dom);
WEB COMPONENTS - CUSTOM ELEMENTS
Extending a custom element
var proto = Object.create(HTMLElement.prototype);
proto.name = 'Custom Element';
proto.alert = function() {
alert('This is ' + this.name);
};
document.registerElement(
'x-component', {
prototype: proto
});
var proto = Object.create(HTMLElement.prototype);
proto.name = 'Custom Element';
proto.alert = function() {
alert('This is ' + this.name);
};
document.registerElement(
'x-component', {
prototype: proto
});
var proto = Object.create(HTMLElement.prototype);
proto.name = 'Custom Element';
proto.alert = function() {
alert('This is ' + this.name);
};
document.registerElement(
'x-component', {
prototype: proto
});
WEB COMPONENTS - CUSTOM ELEMENTS
Extending an existing element
var XComponent = document.registerElement('x-component', {
extends: 'input',
prototype: Object.create(HTMLInputElement.prototype)
});
<input is="x-component">
Custom element example: github
WEB COMPONENTS - CUSTOM ELEMENTS
Lifecycle hooks
Callback name | Called when |
createdCallback | an instance of the element is created |
attachedCallback | an instance was inserted into the document |
detachedCallback | an instance was removed from the document |
attributeChangedCallback(attrName, oldVal, newVal) | an attribute was added, removed, or updated |
WEB COMPONENTS - CUSTOM ELEMENTS
Feature detection
if(!document.registerElement) {
....
}
WEB COMPONENTS - SHADOW DOM
WEB COMPONENTS - SHADOW DOM
WEB COMPONENTS - SHADOW DOM
<div id="host"></div>
<script>
var host = document.querySelector('#host');
var root = host.createShadowRoot();
var div = document.createElement('div');
div.textContent = 'This is Shadow DOM';
root.appendChild(div);
</script>
Building the Shadow DOM
<div id="host"></div>
<script>
var host = document.querySelector('#host');
var root = host.createShadowRoot();
var div = document.createElement('div');
div.textContent = 'This is Shadow DOM';
root.appendChild(div);
</script>
<div id="host"></div>
<script>
var host = document.querySelector('#host');
var root = host.createShadowRoot();
var div = document.createElement('div');
div.textContent = 'This is Shadow DOM';
root.appendChild(div);
</script>
WEB COMPONENTS - SHADOW DOM
Structure of shadow DOM
document
element (shadow host)
shadow root
contents
WEB COMPONENTS - SHADOW DOM
<div id="host">Light DOM</div>
<script>
var container = document.querySelector('#host');
var root1 = container.createShadowRoot();
var root2 = container.createShadowRoot();
root1.innerHTML = '<div>Root 1 FTW</div>';
root2.innerHTML = '<div>Root 2 FTW</div>';
</script>
Multiple shadow roots
WEB COMPONENTS - SHADOW DOM
<h1>Outer html</h1>
<div id="host">this content will be replaced</div>
<template id="hostTemplate">
<style>
h1 {
border: 1px solid black;
font-size:16px;
color:blue;
}
:host {
color:red;
}
</style>
<div class="outer">
<h1> Host HTML </h1>
styled from the shadow DOM
</div>
</template>
Using templates
WEB COMPONENTS - SHADOW DOM
<script>
var shadow = document.querySelector("#host").createShadowRoot();
var template = document.querySelector("#hostTemplate");
var clone = document.importNode(template.content, true);
shadow.appendChild(clone);
</script>
Using templates
<script>
var shadow = document.querySelector("#host").createShadowRoot();
var template = document.querySelector("#hostTemplate");
var clone = document.importNode(template.content, true);
shadow.appendChild(clone);
</script>
<script>
var shadow = document.querySelector("#host").createShadowRoot();
var template = document.querySelector("#hostTemplate");
var clone = document.importNode(template.content, true);
shadow.appendChild(clone);
</script>
<h1>Outer html</h1>
<div id="host">this content will be replaced</div>
WEB COMPONENTS - SHADOW DOM
Using templates
WEB COMPONENTS - SHADOW DOM
<div id="host">
<h1>This is Shadow DOM</h1>
</div>
Adding host element content
<template id="hostTemplate">
<style>
...
</style>
<div id="container">
<span> Something something dark side </span>
<content select="h1"></content> // Insert h1 here
</div>
</template>
WEB COMPONENTS - SHADOW DOM
<div id="host">
<h2>Andrei</h2>
<h2>Antal</h2>
<div id="title">Frontend developer</div>
<h4 class="f">wannabe</h4>
</div>
<template id="hostTemplate">
<header>
<content select="h2"></content>
</header>
<section>
<content select="#title"></content>
</section>
<footer>
<content select=".f"></content>
</footer>
</template>
Multiple insertion points
WEB COMPONENTS - SHADOW DOM
WEB COMPONENTS - SHADOW DOM
if(!document.body.createShadowRoot) {
....
}
Feature detection
WEB COMPONENTS - HTML IMPORTS
<!-- warning.html -->
<div class="warning">
<style scoped>
h3 {
color: red;
}
</style>
<h3>Warning!</h3>
<p>This page is under construction</p>
</div>
<div class="outdated">
<h3>Heads up!</h3>
<p>This content may be out of date</p>
</div>
Importing HTML documents into other HTML documents
WEB COMPONENTS - HTML IMPORTS
<head>
<link rel="import" href="warnings.html">
</head>
Importing HTML documents into other HTML documents
// Grab DOM from warning.html's document.
var el = content.querySelector('.warning');
<body>
...
<script>
var link = document.querySelector('link[rel="import"]');
var content = link.import;
document.body.appendChild(el.cloneNode(true));
</script>
</body>
WEB COMPONENTS - HTML IMPORTS
<head>
<link rel="import" href="bootstrap.html">
</head>
A practical example - including Bootstrap
<link rel="stylesheet" href="bootstrap.css">
<link rel="stylesheet" href="fonts.css">
<script src="jquery.js"></script>
<script src="bootstrap.js"></script>
<script src="bootstrap-tooltip.js"></script>
<script src="bootstrap-dropdown.js"></script>
...
<!-- scaffolding markup -->
<template>
...
</template>
WEB COMPONENTS - HTML IMPORTS
<!-- import.html -->
<template>
<h1>Hello World!</h1>
<!-- Img is not requested until the <template> goes live. -->
<img src="world.png">
<script>alert("Executed when the template is activated.");</script>
</template>
<head>
<link rel="import" href="import.html">
</head>
<body>
<div id="container"></div>
<script>
var link = document.querySelector('link[rel="import"]');
// Clone the <template> in the import.
var template = link.import.querySelector('template');
var clone = document.importNode(template.content, true);
document.querySelector("#container").appendChild(clone);
</script>
</body>
Including templates
<!-- import.html -->
<template>
<h1>Hello World!</h1>
<!-- Img is not requested until the <template> goes live. -->
<img src="world.png">
<script>alert("Executed when the template is activated.");</script>
</template>
<head>
<link rel="import" href="import.html">
</head>
<body>
<div id="container"></div>
<script>
var link = document.querySelector('link[rel="import"]');
// Clone the <template> in the import.
var template = link.import.querySelector('template');
var clone = document.importNode(template.content, true);
document.querySelector("#container").appendChild(clone);
</script>
</body>
<!-- import.html -->
<template>
<h1>Hello World!</h1>
<!-- Img is not requested until the <template> goes live. -->
<img src="world.png">
<script>alert("Executed when the template is activated.");</script>
</template>
<head>
<link rel="import" href="import.html">
</head>
<body>
<div id="container"></div>
<script>
var link = document.querySelector('link[rel="import"]');
// Clone the <template> in the import.
var template = link.import.querySelector('template');
var clone = document.importNode(template.content, true);
document.querySelector("#container").appendChild(clone);
</script>
</body>
WEB COMPONENTS - HTML IMPORTS
if(!"import" in document.createElement("link")) {
....
}
Feature detection
WEB COMPONENTS
Browser compatibility
POLYMER
- WEB COMPONENTS MADE SWEET -
- build on top of web components - by GOOGLE
- offers syntactic sugar for fast creation of reusable components
- adds a few tricks to components (eg. data binding)
- EXTENSIVE POLYMER ELEMENT CATALOG
POLYMER - element starter
<link rel="import" href="../bower_components/polymer/polymer.html">
.....
<link rel="import" href="comp/hello-folks.html">
</head>
<body>
<hello-folks></hello-folks>
....
<script>
Polymer({
is: "hello-folks",
})
</script>
<dom-module id="hello-folks">
<style>
h1 { color: red; }
h3 { color: blue; }
</style>
<template>
<h1>Hello</h1>
<h3>folks</h3>
</template>
</dom-module>
POLYMER - CUSTOM Properties
<script>
Polymer({
is: "hello-folks",
properties : {
aSimpleProp : String,
aComputedProp : {
type: String,
computed: "comp(aSimpleProp)"
}
},
comp(prop){
return "computed: " + prop
}
});
</script>
.....
<hello-folks a-simple-prop="a prop"></hello-folks>
....
<dom-module id="hello-folks">
<style>
h1 { color: red; }
h3 { color: blue; }
</style>
<template>
<h1>Hello</h1>
<h3>folks <span>{{aComputedProp}}</span></h3>
</template>
</dom-module>
POLYMER - DATA BINDING
<dom-module id="hello-folks">
<style>
h1 { color: red; }
h3 { color: blue; }
</style>
<template>
Name: <input value={{nameValue::input}}>
<h1>Hello</h1>
<h3> <span>[[nameValue]]</span></h3>
<img src$="https://www.example.com/profiles/{{userId}}.jpg">
</template>
</dom-module>
- [[ property]] - one way data binding
- {{ property }} - automatic (one/two way) data binding
POLYMER - DATA BINDING
<!-- Property binding -->
<my-element selected="{{value}}"></my-element>
<!-- results in <my-element>.selected = this.value; -->
<!-- Attribute binding -->
<my-element selected$="{{value}}"></my-element>
<!-- results in <my-element>.setAttribute('selected', this.value); -->
<!-- class -->
<div class$="{{foo}}"></div>
<!-- style -->
<div style$="{{background}}"></div>
<!-- href -->
<a href$="{{url}}">
<!-- label for -->
<label for$="{{bar}}"></label>
<!-- dataset -->
<div data-bar$="{{baz}}"></div>
Binding properties and attributes
Native elements binding
POLYMER - Iterating over data
<script>
Polymer({
is: "hello-folks",
ready: function(){
this.things = [
{name: "a thing"},
{name: "some other thing"},
{name: "the last thing"}]
}
});
</script>
.......
<dom-module id="things-list">
<template>
<h1>A list of things</h1>
<ul>
<template is="dom-repeat"
items={{things}}
as="thing"
index-as="thing_no">
<li>thing <i>[[thing_no+1]]</i> - <b>[[thing.name]]</b></li>
</template>
</ul>
</template>
........
.......
<dom-module id="things-list">
<template>
<h1>A list of things</h1>
<ul>
<template is="dom-repeat"
items={{things}}
as="thing"
index-as="thing_no">
<li>thing <i>[[thing_no+1]]</i> - <b>[[thing.name]]</b></li>
</template>
</ul>
</template>
........
.......
<dom-module id="things-list">
<template>
<h1>A list of things</h1>
<ul>
<template is="dom-repeat"
items={{things}}
as="thing"
index-as="thing_no">
<li>thing <i>[[thing_no+1]]</i> - <b>[[thing.name]]</b></li>
</template>
</ul>
</template>
........
.......
<dom-module id="things-list">
<template>
<h1>A list of things</h1>
<ul>
<template is="dom-repeat"
items={{things}}
as="thing"
index-as="thing_no">
<li>thing <i>[[thing_no+1]]</i> - <b>[[thing.name]]</b></li>
</template>
</ul>
</template>
........
.......
<dom-module id="things-list">
<template>
<h1>A list of things</h1>
<ul>
<template is="dom-repeat"
items={{things}}
as="thing"
index-as="thing_no">
<li>thing <i>[[thing_no+1]]</i> - <b>[[thing.name]]</b></li>
</template>
</ul>
</template>
........
.......
<dom-module id="things-list">
<template>
<h1>A list of things</h1>
<ul>
<template is="dom-repeat"
items={{things}}
as="thing"
index-as="thing_no">
<li>thing <i>[[thing_no+1]]</i> - <b>[[thing.name]]</b></li>
</template>
</ul>
</template>
........
POLYMER - ELEMENT COMPOSITION
<link rel="import" href="bower_components/polymer/polymer.html">
<link rel="import" href="components/entry-detail.html">
<dom-module id="entry-list">
<style>
</style>
<template>
<h1>ELEMENT LIST</h1>
<template is="dom-repeat" items="{{entries}}">
<h3> Element #<span>[[index]]</span></h3>
<entry-detail id="{{item.id}}">
</template>
</template>
</dom-module>
<script>
Polymer({
is: "hello-folks",
ready: function(){
this.entries = [...]
}
})
</script>
POLYMER - Routing
other options
CONCLUSION
- HTML sucks...
- CSS sucks...
CONCLUSION
CONCLUSION
THANKS
Reach me @
fb/antal.a.andrei
@andrei_antal
antal.andrei@icloud.com
Frontend Meetup - Web components
By Andrei Antal
Frontend Meetup - Web components
- 1,449