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

 

 

  • 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,436