Web Components:
Lego Bricks of the Web
Who are we?
Abraham Williams
Senior Engineer at Bendyworks
Google Developer Expert
Google Developer Group Organizer
Pearl Latteier
Senior Engineer at Bendyworks
PhD from UW-Madison
Google Developer Group Organizer
slides.today
to follow along with today’s slides
First spec
Display an image
An HTML element
<img src=“img/picture.jpg”
alt=“pic” />
New spec
If the image is not found...
New spec
...show this kitten.
jQuery
<img src=“img/picture.jpg”
data-fallback=“img/kitten.jpg” />
$(“img[data-fallback]”).on(“error”, function() {
var fallback = $(this).attr(“data-fallback”);
$(this).attr(“src”, fallback);
});
New spec
On image not found errors,
ping the server.
jQuery
<img src=“img/picture.jpg” />
$(“img”).on(“error”, function(err) {
$.post(“/log”, {
“type”: ”not-found”,
“resource”: err.src,
“url”: location.href
});
});
New spec
Fade in image on load
jQuery
<img onload="fadeIn(this)"
src="img/picture.jpg"
style="display:none;" />
window.fadeIn = function(img) {
$(img).fadeIn(1000);
}
<img onload="fadeIn(this)"
src="img/picture.jpg"
style="display:none;"
data-fallback=“img/kitten.jpg” />
$(“img[data-fallback]”).on(“error”, function() {
var fallback = $(this).attr(“data-fallback”);
$(this).attr(“src”, fallback);
});
$(“img”).on(“error”, function(err) {
$.post(“/log”, {
“type”: ”not-found”,
“resource”: err.src,
“url”: location.href
});
});
window.fadeIn = function(img) {
$(img).fadeIn(1000);
}
<web-components>
A web component
is a reusable UI element
<custom-elements>
Define the API
<better-img url=“img/picture.jpg”
fallback=“img/default.jpg”
report=“log”
fade-in=“1000”>
</better-img>
Define
class BetterImg extends HTMLElement {
}
Define
class BetterImg extends HTMLElement {
constructor() { super(); }
}
Define
class BetterImg extends HTMLElement {
constructor() { super(); }
connectedCallback() {
}
}
Define
class BetterImg extends HTMLElement {
constructor() { super(); }
connectedCallback() {
this.innerHTML = ‘<img />’;
this.img.onerror = this.onerrorHandler;
this.setSrc(this.url);
}
}
Define
class BetterImg extends HTMLElement {
constructor() { super(); }
connectedCallback() {
this.innerHTML = ‘<img />’;
this.img.onerror = this.onerrorHandler;
this.setSrc(this.url);
}
}
customElements.define(“better-img”, BetterImg);
class BetterImg extends HTMLElement {
onerrorHandler(event) {
let betterImg = event.target.parentElement;
betterImg.useFallback();
betterImg.logError(event);
}
useFallback() {
this.setSrc(this.fallback);
}
logError(event) {
this.reportError(this.url);
}
}
Awesome!
<better-img url=“img/picture.jpg”
fallback=“img/default.jpg”
report=“/log”
fade-in=“1000”>
</better-img>
Custom Elements v1 Browser Support
👍 Chrome
🚧 Firefox
👍 Safari
❓ Edge
👍 Opera
✅ Polyfill
<html-imports>
better-img.html
<link rel=”stylesheet” href=”better-img.css”>
<script>
</script>
better-img.html
<link rel=”stylesheet” href=”better-img.css”>
<script>
class BetterImg extends HTMLElement {
constructor() { }
...
}
customElements.define(“better-img”, BetterImg);
</script>
index.html
<head>
</head>
<body>
</body>
index.html
<head>
<link rel=”import” href=”better-img.html”>
</head>
<body>
</body>
index.html
<head>
<link rel=”import” href=”better-img.html”>
</head>
<body>
<better-img ...></better-img>
</body>
<body>
<better-img url=“photo.jpg”>
<img src=“photo.jpg” />
</better-img>
</body>
HTML Imports Browser Support
👍 Chrome
👎 Firefox
👎 Safari
👎 Edge
👍 Opera
✅ Polyfill
index.html
<script type="module"
src="better-img.js"></script>
New spec
Pinterest style cards
jQuery
$([
"<div>",
" <div>",
" <img ... />",
" </div>",
" <div>",
' <a download href="...">',
' <i class="blueIcon-5">file_download</i>',
" </a>",
" <div>",
" ..."
"</div>"
].join("\n"));
<html-template>
photo-card.html
<template id=“photo-card”>
</template>
photo-card.html
<template id=“photo-card”>
<div>
<div>
</div>
<div>
<a download href="...">
<i class="material-icons">file_download</i>
</a>
</div>
...
</div>
</template>
photo-card.html
<template id=“photo-card”>
<div>
<div>
<better-img ...></better-img>
</div>
<div>
<a download href="...">
<i class="material-icons">file_download</i>
</a>
</div>
...
</div>
</template>
photo-card.html
<script>
class PhotoCard extends HTMLElement {
...
render() {
let card = this.template.content.cloneNode(true);
this.appendChild(card);
this.setTitle(‘Fun Title’);
}�}
</script>
<photo-card>
<div>
<better-img>
<img />
</better-img>
</div>
</photo-card>
HTML Template Browser Support
👍 Chrome
👍 Firefox
👍 Safari
👍 Edge
👍 Opera
Global code breaks everything!
// photo-card.css
img {
border: 1px solid red;
}
// index.css
.img-wrapper img {
border: 1px solid blue;
}
<!-- index.html -->
<div class=“img-wrapper”><photo-card></photo-card></div>
<shadow-dom>
Shadow DOM FTW
<slot>
<template id=“photo-card”>
<div>
<div>
<better-img ...></better-img>
</div>
<slot id="cardText"></slot>
...
</div>
</template>
<html>
<photo-card>
<p slot="cardText">
This html will be rendered in the slot.
<p>
</photo-card>
photo-card.html
<template id=“photo-card”>
<style>
img {
border: 1px solid red;
}
</style>
<div>
<better-img ...></better-img>
</div>
</template>
photo-card.html
class PhotoCard extends HTMLElement {
...
render() {
let card = this.template.content.cloneNode(true);
this.setTitle(‘Fun Title’);
}�}
photo-card.html
class PhotoCard extends HTMLElement {
...
render() {
let card = this.template.content.cloneNode(true);
this.attachShadow({ mode: 'open' });
this.shadowRoot.appendChild(card);
this.setTitle(‘Fun Title’);
}�}
Shadow DOM v1 Browser Support
👍 Chrome
🚧 Firefox
👍 Safari
👎 Edge
👍 Opera
✅ Polyfill
</web-components>
<polymer-project>
webcomponents.org
<paper-elements>
<paper-card heading="Card Title">
<div class="card-content">
Some content
</div>
<div class="card-actions">
<paper-button>
Some action
</paper-button>
</div>
</paper-card>
<photo-card>
<paper-card>
<better-img>
<img />
</better-img>
</paper-card>
</photo-card>
<sprinkle-space>
polymer.wc.sprinkle.space
photo-tabs.html
<script>
class PhotoTabs extends Polymer.Element {
static get is() {return 'photo-tabs'}
static get properties() {
return { currentPage: Number }
}
});
customElements.define(PhotoTabs.is, PhotoTabs);
</script>
photo-tabs.html
<paper-tabs selected="{{currentPage}}">
<paper-tab>Nature</paper-tab>
<paper-tab>London</paper-tab>
</paper-tabs>
<iron-pages selected="{{currentPage}}">
<div>
<photo-stream></photo-stream>
</div>
<div>
<photo-stream></photo-stream>
</div>
</iron-pages>
<photo-tabs>
<iron-pages>
<photo-stream>
<photo-card>
<paper-card>
<better-img>
</better-img>
</paper-card>
</photo-card>
</photo-stream>
</iron-pages>
</photo-tabs>
<pwa-today>
my-app.html
<app-location
route="{{route}}"></app-location>
<app-route
route="{{route}}"
pattern="/resources/:id"
data="{{routeData}}"></app-route>
<web-component-tester>
better-img_test.html
test('renders button', function() {
let btn = component.querySelector('button');
assert.isOk(btn);
});
https://bendyworks.com/blog/native-web-components
</polymer-project>
<firebase-hosting>
firebase init
firebase deploy
Source https://flic.kr/p/4V94wj
Resources
We design and build software that stands the test of time, specializing in Ruby on Rails, JavaScript, Clojure, and Ionic.
Craft a project with us at bendyworks.com
Find today’s slides at slides.today
Pearl Latteier
pearl@bendyworks.com
twitter.com/pblatteier
github.com/pearlbea
Abraham Williams
abraham@bendyworks.com
twitter.com/abraham
github.com/abraham