Web Components

Estelle Weyl

@estellevw

estelle.github.io/components

Web Components

Notes / links ➹

Web Components: Encapsulated and reusable components for the web.

Current state of affairs

google-map { display: block; height: 600px;}
<google-map latitude="37.77493" longitude="-122.41942"></google-map>
<template is="auto-binding">
<google-map-directions map="{{map}}"
      startAddress="San Francisco"
      endAddress="Mountain View"
      travelMode="TRANSIT"></google-map-directions>
  <google-map map="{{map}}" latitude="37.779"
              longitude="-122.3892"></google-map>
</template>

All you add to your HTML

<link rel="import" href="google-map-directions.html">
<google-map latitude="37.779" longitude="-122.3892" minZoom="9" maxZoom="11" fit>
  <google-map-marker latitude="37.779" longitude="-122.3892"
                     title="Go Giants!" draggable="true">
    <img src="http://www. ... SFGiants.png" />
  </google-map-marker>
</google-map>

<google-map-directions startAddress="San Francisco" endAddress="Mountain View"></google-map-directions>

Benefits of Web Components

  • Semantic
  • JS for behavior, CSS for presentation, HTML for content
  • Silo-ability
  • Style encapsulation
  • Native (better performance)
  • Reducing number of DOM nodes to those needed at runtime.
  • modularized and organized code: less prone to errors.
  • Reusable interface elements and functionality.
  • Easily open sourced: share interface elements/code between projects/developers.
  • Two-way binding

Components of Web Components

a.k.a. Table of Contents

  • <template>
  • Shadow DOM (and <content>)
  • HTML imports
  • Custom Elements

Notes / links ➹

Our Task

<name-tag>
  <p class="role">Speaker</p>
  <p class="name">Estelle Weyl</p>
</name-tag>

Spec 1: Templates

template element in action

Spec 2: Shadow DOM

Shadow Root in action

Spec 3: HTML Import

HTML imports in action

Spec 4: Custom Elements

custom element in action

Browser Support

  • Safari (templates only)
  • Firefox (TMP 25%, rest behind flag)
  • Chrome
     

  • Opera
     

  • IE
     

Libraries & Polyfills

  • Polymer
  • x-tag
  • Bosonic

Current State of Affairs

Static Code

<div class="name-tag">
  <div class="outer">
    <div class="logo">JSSummit<span>2015</span></div>
    <div class="tagline">Online JavaScript Conference</div>
    <div class="inner">
      <div class="name">Estelle Weyl</div>
      <div class="role">Speaker</div>
    </div>
    <div class="footer">Follow us on Twitter @JSSummit<br>
      Follow the conversation using #JSSummit
    </div>
  </div>
</div>

A bit more semantic

<div class="name-tag">
  <div class="outer">
    <header>JSSummit<span>2015</span>
      <p>Online JavaScript Conference</p>
    </header>
     <main>
      <p class="name">Estelle Weyl</p>
      <p class="role">Speaker</p>
    </main>
    <footer>Follow us on Twitter @JSSummit<br>
      Follow the conversation using #JSSummit
    </footer>
  </div>
</div>

Server-side code

<div class="name-tag">
  <div class="outer">
    <div class="logo">JSSummit<span>2015</span></div>
    <div class="tagline">Online JavaScript Conference</div>
    <div class="inner">
      <div class="name">Estelle Weyl</div>
      <div class="role">Speaker</div>
    </div>
    <div class="footer">Follow us on Twitter @JSSummit<br>
      Follow the conversation using #JSSummit
    </div>
  </div>
</div>

<div class="name-tag">
  <div class="outer">
    <div class="logo">JSSummit<span>2015</span></div>
    <div class="tagline">Online JavaScript Conference</div>
    <div class="inner">
      <div class="name">Niniane Wang</div>
      <div class="role">CTO, Minted</div>
    </div>
    <div class="footer">Follow us on Twitter @JSSummit<br>
      Follow the conversation using #JSSummit
    </div>
  </div>
</div>

<div class="name-tag">
  <div class="outer">
    <div class="logo">JSSummit<span>2015</span></div>
    <div class="tagline">Online JavaScript Conference</div>
    <div class="inner">
      <div class="name">Tim Berners-Lee</div>
      <div class="role">Web Developer</div>
    </div>
    <div class="footer">Follow us on Twitter @JSSummit<br>
      Follow the conversation using #JSSummit
    </div>
  </div>
</div>

JavaScript Code (view)

var data = [
    {name: 'Estelle Weyl', role: 'Speaker'},
    {name: 'Niniane Wang', role: 'CTO, Minted'},
    {name: 'Tim Berners-Lee', role: 'Web Developer'}
  ];

function createComponent() {
  var unit, clone, i = 0;
  // iterate creating tag per object
  for (i = 0; data.length > i; i += 1) {
    unit = data[i];
    clone = createNametag();
    clone.querySelector('.name').innerHTML = unit.name;
    clone.querySelector('.role').innerHTML = unit.role;
    document.body.appendChild(clone);
  }
}

function createNametag() {
  // Create the nodes
  var tmpl        = document.createElement('div'),
      outer       = document.createElement('div'),
      header      = document.createElement('header'),
      p           = document.createElement('p'),
      main        = document.createElement('main'),
      name        = document.createElement('p'),
      role        = document.createElement('p'),
      footer      = document.createElement('footer');
  // add classes
  tmpl.classList.add('name-tag');
  outer.classList.add('outer');
  name.classList.add('name');
  role.classList.add('role')
  // add content
  header.innerHTML = 'JSSummit>span>2014>/span>';
  p.textContent = "Online JavaScript Conference";
  footer.innerHTML = 'Follow us on Twitter @JSSummit>br/>Follow the conversation using #JSSummit';
  // put it all together
  outer.appendChild(header);
  outer.appendChild(p);
  main.appendChild(name);
  main.appendChild(role);
  outer.appendChild(main);
  outer.appendChild(footer);
  tmpl.appendChild(outer);

  return tmpl;
}

createComponent();


Client-Side Frameworks

<script id="myNametag" type="text/x-handlebars-template">
<div class="name-tag">
  <div class="outer">
    <header>JSSummit<span>2015</span>
      <p>Online JavaScript Conference</p>
    </header>
    <main>
      <p class="name">{{person.fullname}}</p>
      <p class="role">{{person.role}}</p>
    </main>
    <footer>Follow us on Twitter @JSSummit<br>
      Follow the conversation using #JSSummit
    </footer>
  </div>
</div>
</script>

Goal

<name-tag>
    <p class="role">Speaker</p>
    <p class="name">Estelle Weyl</p>
</name-tag>
<name-tag>
    <p class="name">Niniane Wang</p>
    <p class="role">CTO, Minted</p>
</name-tag>
<name-tag>
    <p class="name">Tim Berners-Lee</p>
    <p class="role">Web Developer</p>
</name-tag>

Order of role / name doesn't matter

Templates

  • <template>
  • Shadow DOM (and <content>)
  • HTML imports
  • Custom Elements

Definition

template (n) - A document or file having a preset format, used as a starting point for a particular application so that the format does not have to be recreated each time it is used.

<template> specification

The template element is used to declare fragments of HTML that can be cloned and inserted in the document by script.

In a rendering, the template element represents nothing

-- WHATWG Specification

<template> content

  • Inert until activated: content available only at runtime
  • No side effects: scripts don't run, images don't load, video doesn't play until template is used.
  • Outside of DOM: child nodes don't exist in DOM. Can't querySelector() content
  • Placable anywhere: head, body, iframe.
  • Could be used as a performance hack

Designers can create rich web user interaction (UI) using CSS and HTML markup only. Developers can focus on integrating the design elements.

Implementation

  1. Feature detect
  2. Declare template content
  3. Activate content

Browser Support

  • Safari
  • Firefox
  • Chrome
  • Opera
  • IE

Feature Detect

if('content' in document.createElement('template')) {
  // put template code here
} else {
  //polyfill
}

Include <template>

Clonable DOM that does nothing until activated

  • <template>
  • <style>
  • content
  • <content> (in Shadow DOM spec)
  • <script>
<template id="foo">
   <style>
      /* styles scoped to template only
   </style>
  <div class="someclass">
     template text here
    <div class="someotherclass">
    <content></content>
    </div>
  </div>
</template>
<template id="nameTagTemplate">
  <style>
    .outer {
      border: 1px solid black;
      background: url(../assets/wood.jpg) cover rgb(222,122,91);
      width: 21rem;
      font-size: 2.5rem;
      text-align: center;
      font-family: Rockwell, serif;
      color: white;
      margin-bottom: 1rem;
    }
    header {
      font-weight: bold;
      padding: 10px 0 0 0;
    }
    header span {
      color: #ccc;
      font-weight: normal;
    }
    p, footer {
      font-size: 1rem;
      font-weight: normal;
      margin: -5px 0 10px 0; padding: 0;
    }
    main {
      background: white;
      color: black;
      font-family: sans-serif;
      font-size: 1.8rem;
      height: 12rem;
      padding-top: 0.2em;
    }
    .name,
    main:first-line {
      font-weight: bold;
      font-size: 2.3rem;
      margin-bottom: 1rem;
    }
    footer {
      margin: 10px 0 2rem;
    }
  </style>
<div class="name-tag">
  <div class="outer">
    <header>JSSummit<span>2015</span>
      <p>Online JavaScript Conference</p>
    </header>
    <main>
      <content></content>
    </main>
    <footer>Follow us on Twitter @JSSummit<br>
      Follow the conversation using #JSSummit
    </footer>
  </div>
</div>
</template>
<template id="nameTagTemplate">
  <style>
    .outer {
      border: 1px solid black;
      background: url(../assets/wood.jpg) cover rgb(222,122,91); ....

... footer {
      margin: 10px 0 2rem;
    }
  </style>
<div class="name-tag">
  <div class="outer">
    <header>JSSummit<span>2015</span>
      <p>Online JavaScript Conference</p>
    </header>
    <main>
      <content></content>
    </main>
    <footer>Follow us on Twitter @JSSummit<br>
      Follow the conversation using #JSSummit
    </footer>
  </div>
</div>
</template>


HTML in Document

<div class="name-tag">
  <p class="role">Speaker</p>
  <p class="name">Estelle Weyl</p>
</div>
<div class="name-tag">
  <p class="name">Ninian Wang</p>
  <p class="role">CTO, Minted</p>
</div>
<div class="name-tag">
  <p class="name">Tim Berners-Lee</p>
  <p class="role">Web Developer</p>
</div>

JavaScript for Template

var hosts = document.querySelectorAll('.name-tag');
var template = document.getElementById('nameTagTemplate');
  for(var i = 0, count = hosts.length; i < count; i++) {
    var clone = document.importNode(template.content, true);
    clone.querySelector('content').innerHTML = hosts[i].innerHTML;
    hosts[i].parentNode.appendChild(clone);
    hosts[i].remove();
  }
}
var clone = document.importNode(template.content, true);
var clone = template.content.cloneNode(true); 

In browser

Benefits of <template>

  • Nothing!
    • inert
    • invisible
    • inactive
  • Reuseable
  • Importable
  • Can have scoped CSS

Shadow DOM

  • <template>
  • Shadow DOM (and <content>)
  • HTML imports
  • Custom Elements

<content>

<content>: insertion point that projects the text from the shadow host to shadow root.

<template id="nameTagTemplate">
  <style> ... </style>
<div class="name-tag">
  <div class="outer">
    <header>JSSummit<span>2015</span>
      <p>Online JavaScript Conference</p>
    </header>
    <main>
      <content></content>
    </main>
    <footer>Follow us on Twitter @JSSummit<br>
      Follow the conversation using #JSSummit
    </footer>
  </div>
</div>
</template>


HTML in Document

<div class="name-tag">
  <p class="role">Speaker</p>
  <p class="name">Estelle Weyl</p>
</div>
<div class="name-tag">
  <p class="name">Ninian Wang</p>
  <p class="role">CTO, Minted</p>
</div>
<div class="name-tag">
  <p class="name">Tim Berners-Lee</p>
  <p class="role">Web Developer</p>
</div>

JavaScript for Template

var hosts = document.querySelectorAll('.name-tag');
var template = document.querySelector('#nameTagTemplate');
for(var i = 0; i < hosts.length; i++) {
  var shadow = hosts[i].createShadowRoot();
  var clone = document.importNode(template.content, true);
  shadow.appendChild(clone);
}
var clone = document.importNode(template.content, true);
var clone = template.content.cloneNode(true); 

In browser

<content> & select attribute

<content>: insertion point that projects the text from the shadow host to shadow root.

<content select=".classInHost"> looks inside the shadow host for any element with a matching class of .classInHost. If an element in the shadow host has the class of classInHost, its contents will be rendered in the shadow DOM.

select attribute

<template id="nameTagTemplate">
<div class="name-tag">
  <div class="outer">
    <header>JSSummit<span>2015</span>
      <p>Online JavaScript Conference</p>
    </header>
    <main>
    <content select=".name"></content>
    <content select=".role"></content>
    </main>
    <footer>Follow us on Twitter @JSSummit<br>
      Follow the conversation using #JSSummit
    </footer>
  </div>
</div>
</template>

<div class="name-tag">
  <span class="role">Speaker</span>
  <span class="name">Estelle Weyl</span>
</div>

<script>
    var host = document.querySelector('.name-tag');
    var root = host.createShadowRoot();
    var template = document.querySelector('#nameTagTemplate');
    root.appendChild(template.content);
</script>

HTML, CSS & JS

<template>
<div class="outer">
    <header>JSSummit<span>2015</span>
      <p>Online JavaScript Conference</p>
    </header>
    <main>
      <content select=".name"></content>
      <content select=".role"></content>
    </main>
    <footer>Follow us on Twitter @JSSummit<br>
      Follow the conversation using #JSSummit
    </footer>
  </div>
  </div>
</div>
</template>

<div class="name-tag">
  <p class="role">Speaker</p>
  <p class="name">Estelle Weyl</p>
</div>
<div class="name-tag">
  <p class="name">Ninian Wang</p>
  <p class="role">CTO, Minted</p>
</div>
<div class="name-tag">
  <p class="name">Tim Berners-Lee</p>
  <p class="role">Web Developer</p>
</div>

<script>
var el = document.querySelectorAll('.name-tag');
var template = document.getElementById('nameTagTemplate');
for(var i = 0; i < el.length; i++) {
  var shadow = el[i].createShadowRoot();
  var clone = document.importNode(template.content, true);
  shadow.appendChild(clone);
}
</script>

All unclaimed content

<content></content>
<content select=""></conent>
<content select="*"></content>

In browser

Scoped CSS

By Example


shadow DOM for input type range

By Example


shadow DOM for input type datetime-local

By Example


shadow DOM for video controls

<template> & Shadow DOM

shadow DOM for our templated name tags

DOM Encapsulation

  • Not available to the rest of the page
    • Page-wide CSS doesn't impact it
    • CSS selectors don't target it
    • JavaScript is not aware of it
    • Nodes are not part of the regular DOM
  • Attributes of parent node can configure it
  • Shadow CSS is scoped
  • JavaScript can access via shadowRoot
  • <content> is part of Shadow DOM spec

Simple Example

* {color: red;}
<div class="spirit"></div>
<script>
  var host = document.querySelector('.spirit');
  var root = host.createShadowRoot();
  root.innerHTML = "<p>Just because you see me," +
       "does that mean I really exist?</p>";
</script>

Shadow DOM/Scoped CSS

Targeting the Shadow DOM

Targeting the custom element

/* unknown elements are unresolved */
:unresolved {
    opacity: 0; /* hide until resolved
  }
/* by default elements are always display:inline. */
:host {
    display: block;
  }
/* target the parent node by class or id */
:host(.customElementsClass) {
  ...
}
:host(:hover) { opacity: 1; }
:host(:active) { ... }

Shadow Root and Host:

shadow DOM for our templated name tags
 var root = host.createShadowRoot();

shadow root: 1st node of your shadow tree. Ancestor to all other nodes in the tree.

Shadow host content is not rendered. Rather, shadow root content gets rendered.

Visible or not?

while the shadow root is not accessible via the DOM, it is manipulated exactly like the DOM:

document.querySelectorAll('.outer').length === 0
var header = document.createElement('h1');
root.appendChild(header);

HTML Imports

  • <template>
  • Shadow DOM (and <content>)
  • HTML imports
  • Custom Elements

 

<!--#exec cgi="/cgi-bin/template.pl" -->

Feature Detect

if('import' in document.createElement('link')) {
  // you're good
} else {
  // you need to polyfill
}

Browser Support

  • Safari
  • Firefox
  • Chrome
  • Opera
  • IE

<link rel="import"...

<head>

  ....

  <link rel="import" href="/path/file.html">

  <link rel="import" href="//mysite.com/file.html">

  <link rel="import" href="https://CORSEnabled.com/file.html">

</head>

<head>

  ....

  <link rel="import" href="nameTagTemplate.html">

</head>

HTML

<!doctype html>
<html>
<head>
  <meta charset="utf-8"/>
  <title>Import a template</title>
  <script src="import.js"></script>
  <link rel="import" href="nametagtemplate.html"
      onload="loadHandler(event)" onerror="errorHandler(event)">
</head>
<body>
<div class="name-tag">
  <p class="role">Speaker</p>
  <p class="name">Estelle Weyl</p>
</div>
<div class="name-tag">
  <p class="name">Ninian Wang</p>
  <p class="role">CTO, Minted</p>
</div>
<div class="name-tag">
  <p class="name">Tim Berners-Lee</p>
  <p class="role">Web Developer</p>
</div>
</body>
</html>

nametagtemplate.html

<template id="nameTagTemplate">
<style>
    .outer {
      border: 1px solid black;
      background: url(../assets/wood.jpg) cover rgb(222,122,91);
      width: 21rem;
      font-size: 2.5rem;
      text-align: center;
      font-family: Rockwell, serif;
      color: white;
      margin-bottom: 1rem;
    }
    header {
      font-weight: bold;
      padding: 10px 0 0 0;
    }
    header span {
      color: #ccc;
      font-weight: normal;
    }
    p, footer {
      font-size: 1rem;
      font-weight: normal;
      margin: -5px 0 10px 0; padding: 0;
    }
    main {
      background: white;
      color: black;
      font-family: sans-serif;
      font-size: 1.8rem;
      height: 12rem;
      padding-top: 0.2em;
    }
    .name,
    main:first-line {
      font-weight: bold;
      font-size: 2.3rem;
      margin-bottom: 1rem;
    }
    footer {
      margin: 10px 0 2rem;
    }
</style>


<div class="outer">
  <header>JSSummit<span>2015</span>
    <p>Online JavaScript Conference</p>
  </header>
  <main>
  <content select=".name"></content>
  <content select=".role"></content>
  </main>
  <footer>Follow us on Twitter @JSSummit<br>
    Follow the conversation using #JSSummit
  </footer>
</div>
</template>

JavaScript

function loadHandler(e) {
  var content = document.querySelector('link[rel="import"]').import;
  var el = document.querySelectorAll('.name-tag');
  var template = content.getElementById('nameTagTemplate');
  for(var i = 0; i < el.length; i++) {
    var shadow = el[i].createShadowRoot();
    var clone = document.importNode(template.content, true);
    shadow.appendChild(clone);
  }
}
function errorHandler(e) {
  console.log('Error loading import: ' + e.target.href);
}

  • The import can access its own DOM and the DOM of the importing document
    • window.document refers to main document
    • importLinkFile.import refers to the imported document where importedLinkFile is link[rel=import]
  • <link> must have rel="import"

Activation

<!doctype html>
<html>
<head>
  <meta charset="utf-8"/>
  <title>What happens to imported content</title>
  <script src="import.js"></script>
  <link rel="import" href="template_textmediajs.html" id="foo">
</head>
<body><p>A template has been imported that has an H1, image and an alert, and we added some javascript!</p>

  <script>
    var myLink = document.getElementById('foo');
    var myTemplate = myLink.import.querySelector('template');
    var clonedTemplate = document.importNode(myTemplate.content, true);
    document.querySelector('body').appendChild(clonedTemplate);
  </script>
</body>
</html>

HTML imports

Inactive until activated:

imported <template>

<template>
  <h1>This is some text</h1>
  <img src="../assets/icons/icon-templates.png" alt="this is some media">
  <script>alert("This is some javascript");</script>
</template>

Imports

  • Include and reuse documents or document fragments
  • Shared dependencies are only loaded once.
  • Import has to be same origin, or CORS.
  • doc and import share window but not document

JS: importer v importee

<script>
  // the "importee", in this case the template
  var importedDoc = document.currentScript.ownerDocument;

  // the "importer", which has custom elements
  var parentDoc = document;

  parentDoc.body.appendChild(importedDoc.importNode(content, true));
</script>

Custom Elements

  • <template>
  • Shadow DOM (and <content>)
  • HTML imports
  • Custom Elements

<name-tag> custom element

<name-tag>
  <p class="name">Estelle Weyl</p>
  <p class="role">Speaker</p>
</name-tag>
<name-tag>
  <p class="name">Ninian Wang</p>
  <p class="role">CTO, Minted</p>
</name-tag>
<name-tag>
  <p class="name">Tim Berners-Lee</p>
  <p class="role">Web Developer</p>
</name-tag>

custom element by extending existing element

<div is="name-tag">
  <p class="name">Estelle Weyl</p>
  <p class="role">Speaker</p>
</div>
<div is="name-tag">
  <p class="name">Ninian Wang</p>
  <p class="role">CTO, Minted</p>
</div>
<div is="name-tag">
  <p class="name">Tim Berners-Lee</p>
  <p class="role">Web Developer</p>
</div>

Custom Elements

  1. Name the element: all lower-case and must contain a U+002D hyphen.
  2. Define a prototype
  3. Register the Element

Auto Registration

The custom element that you import creates a shadow DOM from a <template> then registers itself.

  • Imports can execute script, so use imports to define and register custom elements.

Element Callbacks

  1. createdCallback
    invoked after custom element is created, registered and prototype set.
  2. attachedCallback
    custom element is inserted
    Event handlers get added here
  3. detachedCallback
    custom element is removed
  4. attributeChangedCallback
    custom element's attribute is added, changed or removed.

Register Element

registerElement(custom-element)
var CustomElement = document.registerElement('custom-element');
document.body.appendChild(new CustomElement());

or

registerElement(custom-element, prototype)
var CustomElement = document.registerElement('custom-button', {
  prototype: Object.create(HTMLVideoElement.prototype)
});

Element Callbacks

var btnProto = Object.create(HTMLButtonElement.prototype);
btnProto.createdCallback = function () {
  console.log('custom element was created');
}
btnProto.attachedCallback = function (){
  console.log('custom element was added') ;
}
btnProto.detachedCallback = function () {
  console.log('custom element was removed');
}
btnProto.attributeChangedCallback = function (attr, old, newA)  {
  console.log('attributes were altered');
}
var btnProto = document.registerElement('my-button', {
  prototype: btnProto
});

Should be happening in ECMAScript 6

class MyButton extends HTMLButtonElement {
  createdCallback () {
    console.log('custom element was created');
  }
  attachedCallback () {
    console.log('custom element was added');
  }
  detachedCallback () {
    console.log('custom element was removed');
  }
  attributeChangedCallback (attr, old, newA) {
    console.log('attributes were altered');
  }
}
document.registerElement('my-button', MyButton);

Registering

var MyButton = document.registerElement('my-button', HTMLButtonElement);
var MyButton = document.registerElement('my-button', {
  prototype: Object.create(HTMLButtonElement.prototype)
});
var YourButton = document.registerElement('your-button', {
  prototype: MyButton
});

Including it...

var myButton = new MyButton();
myButton.textContent = 'some content';
document.body.appendChild(myButton);

or, simply

<my-button>some content<my-button>

Extending

var yourButton = document.createElement('button', 'your-button');
yourButton.textContent = "I am extended"
document.body.appendChild(yourButton);

which creates:

<button is="your-button">I am extended<button>

Targeting the importee from the importer

<style>
my-element {
    display: block; }
my-element:unresolved {
    opacity: 0; }
my-element::shadow {
    /*target the shadow root */ }
::shadow p {
    /* any p within any shadow root */ }
</style>
<head>
<body class="classInImporter">
  <my-element></my-element>
</body>

Targeting the importer from the importee

  <template>
    <style>
      :host-context(.classInImporter) { ... }
    </style>
    .... <!-- html template for <my-element> -->
  </template>
    
<body class="classInImporter">
  <my-element></my-element>
</body>

HTML: Custom Elements

<name-tag>
  <p class="name">Estelle Weyl</p>
  <p class="role">Speaker</p>
</name-tag>
<name-tag>
  <p class="name">Ninian Wang</p>
  <p class="role">CTO, Minted</p>
</name-tag>
<name-tag>
  <p class="name">Tim Berners-Lee</p>
  <p class="role">Web Developer</p>
</name-tag>

<name-tag> registration

var nameTag = Object.create(HTMLElement.prototype);

nameTag.createdCallback = function() {
   var template = document.getElementById('nameTagTemplate').content;
   var clone = template.cloneNode(true);
   var root = this.createShadowRoot();
   root.appendChild(clone);
 };

document.registerElement('name-tag', {
  prototype: nameTag
});

Custom Element using template