1 of 84

Web Components:

Lego Bricks of the Web

2 of 84

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

3 of 84

slides.today

to follow along with today’s slides

4 of 84

First spec

Display an image

5 of 84

An HTML element

<img src=“img/picture.jpg”

alt=“pic” />

6 of 84

New spec

If the image is not found...

7 of 84

New spec

...show this kitten.

​

​

8 of 84

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);

});

9 of 84

New spec

​

On image not found errors,

ping the server.

10 of 84

jQuery

<img src=“img/picture.jpg” />

​

$(“img”).on(“error”, function(err) {

$.post(“/log”, {

“type”: ”not-found”,

“resource”: err.src,

“url”: location.href

});

});

11 of 84

New spec

Fade in image on load

12 of 84

jQuery

<img onload="fadeIn(this)"

src="img/picture.jpg"

style="display:none;" />

​

window.fadeIn = function(img) {

$(img).fadeIn(1000);

}

13 of 84

<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);

}

14 of 84

<web-components>

15 of 84

A web component

is a reusable UI element

16 of 84

  • Custom Elements
  • HTML Imports
  • HTML Templates
  • Shadow DOM

17 of 84

<custom-elements>

18 of 84

Define the API

<better-img url=“img/picture.jpg”

fallback=“img/default.jpg”

report=“log”

fade-in=“1000”>

</better-img>

19 of 84

Define

class BetterImg extends HTMLElement {

​

​

​

​

​

​

}

​

​

20 of 84

Define

class BetterImg extends HTMLElement {

constructor() { super(); }

​

​

​

​

​

}

​

​

21 of 84

Define

class BetterImg extends HTMLElement {

constructor() { super(); }

connectedCallback() {

}

}

​

​

22 of 84

Define

class BetterImg extends HTMLElement {

constructor() { super(); }

connectedCallback() {

this.innerHTML = ‘<img />’;

this.img.onerror = this.onerrorHandler;

this.setSrc(this.url);

}

}

​

​

23 of 84

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);

24 of 84

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);

}

}

25 of 84

Awesome!

<better-img url=“img/picture.jpg”

fallback=“img/default.jpg”

report=“/log”

fade-in=“1000”>

</better-img>

26 of 84

Custom Elements v1 Browser Support

👍 Chrome

🚧 Firefox

👍 Safari

❓ Edge

👍 Opera

✅ Polyfill

27 of 84

<html-imports>

28 of 84

better-img.html

<link rel=”stylesheet” href=”better-img.css”>

<script>

​

​

​

​

​

</script>

29 of 84

better-img.html

<link rel=”stylesheet” href=”better-img.css”>

<script>

class BetterImg extends HTMLElement {

constructor() { }

...

}

customElements.define(“better-img”, BetterImg);

</script>

30 of 84

index.html

<head>

​

</head>

<body>

​

</body>

31 of 84

index.html

<head>

<link rel=”import” href=”better-img.html”>

</head>

<body>

​

</body>

32 of 84

index.html

<head>

<link rel=”import” href=”better-img.html”>

</head>

<body>

<better-img ...></better-img>

</body>

33 of 84

<body>

<better-img url=“photo.jpg”>

<img src=“photo.jpg” />

</better-img>

</body>

34 of 84

HTML Imports Browser Support

👍 Chrome

👎 Firefox

👎 Safari

👎 Edge

👍 Opera

✅ Polyfill

35 of 84

index.html

​

​

<script type="module"

src="better-img.js"></script>

36 of 84

New spec

Pinterest style cards

37 of 84

jQuery

$([

"<div>",

" <div>",

" <img ... />",

" </div>",

" <div>",

' <a download href="...">',

' <i class="blueIcon-5">file_download</i>',

" </a>",

" <div>",

" ..."

"</div>"

].join("\n"));

38 of 84

<html-template>

39 of 84

photo-card.html

<template id=“photo-card”>

​

​

​

​

​

​

​

​

​

​

</template>

40 of 84

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>

41 of 84

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>

42 of 84

photo-card.html

<script>

class PhotoCard extends HTMLElement {

...

render() {

let card = this.template.content.cloneNode(true);

this.appendChild(card);

this.setTitle(‘Fun Title’);

}�}

</script>

43 of 84

​

<photo-card>

<div>

<better-img>

<img />

</better-img>

</div>

</photo-card>

44 of 84

HTML Template Browser Support

👍 Chrome

👍 Firefox

👍 Safari

👍 Edge

👍 Opera

45 of 84

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>

46 of 84

47 of 84

<shadow-dom>

48 of 84

Shadow DOM FTW

  • Isolated DOM
  • Scoped, simplified CSS
  • Composition

49 of 84

<slot>

<template id=“photo-card”>

<div>

<div>

<better-img ...></better-img>

</div>

<slot id="cardText"></slot>

...

</div>

</template>

50 of 84

<html>

<photo-card>

<p slot="cardText">

This html will be rendered in the slot.

<p>

</photo-card>

51 of 84

photo-card.html

<template id=“photo-card”>

<style>

img {

border: 1px solid red;

}

</style>

<div>

<better-img ...></better-img>

</div>

</template>

52 of 84

photo-card.html

class PhotoCard extends HTMLElement {

...

render() {

let card = this.template.content.cloneNode(true);

​

​

this.setTitle(‘Fun Title’);

}�}

53 of 84

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’);

}�}

54 of 84

55 of 84

Shadow DOM v1 Browser Support

👍 Chrome

🚧 Firefox

👍 Safari

👎 Edge

👍 Opera

✅ Polyfill

56 of 84

</web-components>

57 of 84

<polymer-project>

58 of 84

webcomponents.org

59 of 84

60 of 84

<paper-elements>

61 of 84

<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>

62 of 84

63 of 84

​

<photo-card>

<paper-card>

<better-img>

<img />

</better-img>

</paper-card>

</photo-card>

64 of 84

<sprinkle-space>

65 of 84

polymer.wc.sprinkle.space

66 of 84

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>

67 of 84

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>

68 of 84

69 of 84

70 of 84

71 of 84

<photo-tabs>

<iron-pages>

<photo-stream>

<photo-card>

<paper-card>

<better-img>

</better-img>

</paper-card>

</photo-card>

</photo-stream>

</iron-pages>

</photo-tabs>

72 of 84

<pwa-today>

73 of 84

74 of 84

my-app.html

<app-location

route="{{route}}"></app-location>

<app-route

route="{{route}}"

pattern="/resources/:id"

data="{{routeData}}"></app-route>

75 of 84

<web-component-tester>

  • Mocha
  • Chai
  • Sinon
  • Selenium
  • Accessibility Developer Tools

76 of 84

better-img_test.html

test('renders button', function() {

let btn = component.querySelector('button');

assert.isOk(btn);

});

​

77 of 84

https://bendyworks.com/blog/native-web-components

78 of 84

</polymer-project>

79 of 84

<firebase-hosting>

80 of 84

firebase init

firebase deploy

81 of 84

Source https://flic.kr/p/4V94wj

82 of 84

Resources

  • https://www.webcomponents.org/
  • https://www.polymer-project.org/
  • https://developers.google.com/web/fundamentals/architecture/building-components/
  • https://customelements.io/
  • https://www.youtube.com/ChromeDevelopers
  • https://jakearchibald.com/2017/es-modules-in-browsers/
  • https://github.com/polymer/web-component-tester
  • https://bendyworks.com/blog/native-web-components
  • https://slides.today ⇐ these slides

83 of 84

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

84 of 84

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

​