- frontend bucharest meetup -
Because why the f*#k not?
(we've used worst things before)
Andrei Antal
KALON
<input type="text" value="Please insert a value...">
<input type="range">
The sin...
Scumbag browser vendors
Enabling the DOM they don't want you to see
Core elements of web components:
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
Defining HTML templates
<template id="template">
<style>
...
</style>
<div>
<h1>Web Components</h1>
<img src="image.jpeg">
</div>
</template>
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>
if(!window.HTMLTemplateElement) {
....
}
Feature detection
Build and use custom tags
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);
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
});
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
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 |
Feature detection
if(!document.registerElement) {
....
}
<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>
Structure of shadow DOM
document
element (shadow host)
shadow root
contents
<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
<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
<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>
Using templates
<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>
<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
if(!document.body.createShadowRoot) {
....
}
Feature detection
<!-- 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
<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>
<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>
<!-- 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>
if(!"import" in document.createElement("link")) {
....
}
Feature detection
Browser compatibility
<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>
<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>
<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 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
<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>
........
<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>
Reach me @
fb/antal.a.andrei
@andrei_antal
antal.andrei@icloud.com