HTM5 Web Components State of the Union

Kito Mann (@kito99) . virtua.tech

Kito D. Mann (@kito99)

  • Principal Consultant at Virtua (http://virtua.tech)

  • Training, consulting, architecture, mentoring

    • JSF, Java EE, Polymer/Web Components, Angular

  • Official US PrimeFaces and PrimeNG partner

  • Author, JavaServer Faces in Action

  • Founder, JSF Central (http://www.jsfcentral.com)

Kito D. Mann (@kito99)

  • Co-host, Enterprise Java Newscast (http://enterprisejavanews.com)

  • Java Champion

  • Google Developer Expert in Web Technologies

  • Internationally recognized speaker

    • JavaOne, JavaZone, Devoxx, Devnexus, NFJS, etc.

  • JCP Member

    • JSF, MVC, JSF Portlet Bridge, Portlets

UI Components are Everywhere

page with components

UI Components are Everywhere

page with components annotated

UI Components are Everywhere

  • Component models have been popular since the early ninenties

    • Visual Basic, Delphi, PowerBuilder

    • WinForms, Windows Presentation Framework, ASP.NET

    • Swing, JavaFX, JavaServer Faces

UI Components are Everywhere

  • In the browser, component suites and frameworks must invent their own models:

    • YUI

    • KendoUI

    • Bootstrap

    • jQuery UI

    • Infragistics

    • React

    • Angular

Why do We Build Components?

  • Reusable UI functionality

    • Within a single application

    • Across multiple applications

  • You can focus on the core application functionality

HTML Markup Doesn’t Support Non-Native Components

page with components source

We Work with Abstractions

  • Programming model may be componentized, but native markup is not

We Work with Abstractions

PrimeFaces (JavaServer Faces) DataTable

<p:dataTable var="car" value="#{dtPaginatorView.cars}" rows="10"
                     paginator="true"
                     paginatorTemplate="{CurrentPageReport}  {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
                     rowsPerPageTemplate="5,10,15">
    <p:column headerText="Id">
        <h:outputText value="#{car.id}" />
    </p:column>

    <p:column headerText="Year">
        <h:outputText value="#{car.year}" />
    </p:column>

    <p:column headerText="Brand">
        <h:outputText value="#{car.brand}" />
    </p:column>

    <p:column headerText="Color">
        <h:outputText value="#{car.color}" />
    </p:column>
</p:dataTable>

We Work with Abstractions

Bootstrap Dropdowns

<div class="dropdown">
  <button class="btn btn-default dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-expanded="true">
    Dropdown
    <span class="caret"></span>
  </button>
  <ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu1">
    <li role="presentation"><a role="menuitem" tabindex="-1" href="#">Action</a></li>
    <li role="presentation"><a role="menuitem" tabindex="-1" href="#">Another action</a></li>
    <li role="presentation"><a role="menuitem" tabindex="-1" href="#">Something else here</a></li>
    <li role="presentation"><a role="menuitem" tabindex="-1" href="#">Separated link</a></li>
  </ul>
</div>

We Work with Abstractions

React, Vue, Angular, etc…​

little boxes

What is a Web Component?

  • Web components bring a native component model to HTML

What is a Web Component?

polymer paper example1

What is a Web Component?

<paper-action-dialog backdrop autoCloseDisabled layered="false">
  <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>

  <paper-button affirmative autofocus>Tap me to close</paper-button>
</paper-action-dialog>

What is a Web Component?

<firebase-app
  auth-domain="polymerfire-test.firebaseapp.com"
  database-url="https://polymerfire-test.firebaseio.com/"
  api-key="AIzaSyDTP-eiQezleFsV2WddFBAhF_WEzx_8v_g"
  storage-bucket="polymerfire-test.appspot.com"
  messaging-sender-id="544817973908">
</firebase-app>

<firebase-query
    id="query"
    app-name="notes"
    path="/notes/[[uid]]"
    data="{{data}}">
</firebase-query>

Demo

Web Components == Collection of HTML5 Standards

  • Custom Elements

  • HTML Templates

  • HTML Imports

  • Shadow DOM

Custom Elements

<vt-echo message="Hello Web Component world!"></vt-echo>

Custom Elements

/**
 vt-echo example custom element (v1 spec). Simply renders `message` inside <span>.

 @author Kito D. Mann (kito-public at virtua dot com), http://virtua.tech
 */
// tag::code[]
class VirtuaTrainingEcho extends HTMLElement {

    static get observedAttributes() {
        return ['message'];
    }

    // Store properties as attributes so they work both ways.
    get message() {
        return this.getAttribute('message');
    }

    set message(message) {
        this.setAttribute('message', message);
    }

    constructor() {
        super();
        console.log('inside constructor');
    }

    /** Fires after an instance has been inserted into the document */
    connectedCallback() {
        console.log('inside connectedCallback');
        this._content = document.createElement('span');
        this._content.innerText = this.message;
        this.appendChild(this._content);
    }

    /**
     * Fires after an instance has been removed from the document. Here
     * we stop the timer and remove event listeners.
     */
    disconnectedCallback() {
        console.log('inside disconnectedCallback');
    };

    /**
     * Fires after an attribute has been added, removed, or updated. Here we
     * change the `value` to `first` and restart the timer if `first` changes.
     */
    attributeChangedCallback(attr, oldVal, newVal) {
        console.log('inside attributeChangedCallback', 'attr:', attr, 'oldVal:', oldVal, 'newVal:', newVal);
    }

    /** Fires after an element has been moved to a new document */
    adoptedCallback() {
        console.log('inside adoptedCallback');
    }
}

// Registers <vt-counter> as a custom element
window.customElements.define('vt-echo', VirtuaTrainingEcho);
// end:code[]

Custom Elements

Custom Elements

custom elements browser support

HTML Templates

<template id="template">
    <div id="inner-div">
        <div>Inside the template, baby!</div>
    </div>
</template>
<script>

    // Copies the template and appends it to the DOM twice

    var templateClone = document.importNode(
        document.querySelector("#template").content, true);
    var body = document.querySelector("body");
    body.appendChild(templateClone);

    var templateClone2 = document.importNode(
        document.querySelector("#template").content, true);
    body.appendChild(templateClone2);
</script>

HTML Templates

template output

HTML Templates

html templates browser support

HTML Imports: using bootstrap in a page

<head>
<!-- Bootstrap -->
<!--[if lt IE 9]>
	<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
	<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
...
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script src="js/bootstrap.min.js"></script>
</body>

HTML Imports: bootstrap.html

<link href="css/bootstrap.min.css" rel="stylesheet">
<!--[if lt IE 9]>
	<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
	<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script src="js/bootstrap.min.js"></script>

HTML Imports

<head>
	...
	<link rel="import" src="imports/bootstrap.html"/>
</head>
<body>
	...
</body>

HTML Imports

html imports browser support

Shadow DOM

Shadow DOM

shadow dom browser support

Closing the Browser Gap

  • Chrome/Android and Opera support everything

  • IE doesn’t support anything

  • Safari supports Shadow DOM, HTML Templates, and Custom Elements

  • Firefox supports Shadow DOM, HTML Templates, and Custom Elements

  • Edge supports HTML Templates and is working on Custom Elements and Shadow DOM

Closing the Browser Gap

polyfill
[pol-ee-fil]
noun
In web development, a polyfill (or polyfiller) is downloadable code which provides facilities that are not built into a web browser. It implements technology that a developer expects the browser to provide natively, providing a more uniform API landscape. For example, many features of HTML5 are not supported by versions of Internet Explorer older than version 8 or 9, but can be used by web pages if those pages install a polyfill. Web shims and HTML5 Shivs are related concepts.

 — Wikipedia

Closing the Browser Gap

  • webcomponents.js

    • Polyfill for all specs created by Polymer group at Google

    • Can load necessary polyfills depending on the browser

    • Very performant

Closing the Browser Gap

webcomponents.js browser support

polyfill browser support

Web Components in the Wild: Suites

Web Components in the Wild

webcomponents org

Who uses this Stuff, Anyway?

  • Google

  • GE (Predix)

  • McDonald’s

  • ING

  • Comcast

  • Gannett (USA Today)

  • Netflix

  • Coca-cola

  • Electronic Arts

  • The Salvation Army

Google and Web Components

  • Spearheaded many of the specs

  • Developed Polymer project

  • Use Web Components in over 700 projects

    • Over 1 billion users

    • Over 4,000 custom web components

    • Examples: Chrome, Play, Fi, YouTube, and Translate

Writing Web Components

Polymer

  • Library from Google for building web components

  • Most popular choice for writing web components

  • Extensive set of tools

    • CLI, build, testing, etc.

  • Used in over 4 million web pages

  • Heavily promoted by Google

  • New projects: LitElement, Material Web Components, PWA Starter Kit

Polymer

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Polymer Echo Web Component Demo</title>

    <!-- Importing Web Components Polyfill -->
    <script src="../../bower_components/webcomponentsjs/webcomponents-loader.js"></script>

    <!-- Import our custom element -->
    <link rel="import" href="vt-echo-polymer.html">
</head>
<body>
<h1>Polymer Echo Web Component Demo</h1>
<!-- Using our new custom element -->
<!-- tag::element[] -->
<vt-echo-polymer message="Hello Web Component world!"></vt-echo-polymer>
<!-- end::element[] -->
</body>
</html>

Polymer

<link rel="import" href="../../bower_components/polymer/polymer-element.html">

<!--
vt-echo-slate example custom element (v1 spec) using SkateJS. Simply renders `message` inside <span>.

 @author Kito D. Mann (kito-public at virtua dot com), http://virtua.tech
-->
<dom-module id="vt-echo-polymer">

    <template>
        <style>
            :host {
                display: inline-block;
            }

            span {
                padding: 1rem;
                background-color: aliceblue;
            }
        </style>

        <span>[[message]]</span>
    </template>

    <script>
        class VirtuaTrainingEchoPolymer extends Polymer.Element {

            static get is() {
                return 'vt-echo-polymer';
            }

            static get properties() {
                return {
                    message: {
                        type: String,
                        notify: true,
                        reflectToAttribute: true
                    }
                }
            }

            constructor() {
                super();
                console.log('inside constructor');
            }

            /** Polymer-specific: fires once after the component has been added to the DOM. */
            ready() {
                super.ready();
                console.log('inside ready');
            }

            /** Fires after an instance has been inserted into the document */
            connectedCallback() {
                super.connectedCallback();
                console.log('inside connectedCallback');
            }

            /**
             * Fires after an instance has been removed from the document. Here
             * we stop the timer and remove event listeners.
             */
            disconnectedCallback() {
                super.disconnectedCallback();
                console.log('inside disconnectedCallback');
            };

            /**
             * Fires after an attribute has been added, removed, or updated.
             */
            attributeChangedCallback(attr, oldVal, newVal) {
                super.attributeChangedCallback(attr, oldVal, newVal);
                console.log('inside attributeChangedCallback', 'attr:', attr, 'oldVal:', oldVal, 'newVal:', newVal);
            }

            /** Fires after an element has been moved to a new document */
            adoptedCallback() {
                super.adoptedCallback();
                console.log('inside adoptedCallback');
            }
        }

        // Registers <vt-echo-polymer> as a custom element
        window.customElements.define(VirtuaTrainingEchoPolymer.is, VirtuaTrainingEchoPolymer);
    </script>

</dom-module>

Polymer

Skate

  • Functional library for writing web components

  • Uses only Custom Elements and Shadow DOM

  • Simplifies handling attributes and properties

  • Composed of functional mixins for handling properties, rendering, etc.

  • Includes renderers for Preact, React, and lit-html

  • Extremly small

Skate

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Echo Skate Web Component Demo</title>

    <!-- Importing Web Components Polyfill -->
    <script src="../../bower_components/webcomponentsjs/webcomponents-loader.js"></script>

    <!-- Import Skate -->
    <script type="module" src="../../node_modules/skatejs/esnext/index.js"></script>

    <!-- Import our custom element -->
    <script type="module" src="vt-echo-skate.js"></script>
</head>
<body>
<h1>Echo Skate Web Component Demo</h1>
<!-- Using our new custom element -->
<!-- tag::element[] -->
<vt-echo-skate message="Hello Web Component world!"></vt-echo-skate>
<!-- end::element[] -->
</body>
</html>

Skate

import {props, withComponent} from '../../node_modules/skatejs/esnext/index.js';

/**
 vt-echo-skate example custom element (v1 spec) using SkateJS. Simply renders `message` inside <span>.

 @author Kito D. Mann (kito-public at virtua dot com), http://virtua.tech
 */

/** withComponent includes withChildren, withProps, withRenderer and withUnique mixins */
const Component = withComponent();

class VirtuaTrainingEchoSkate extends Component {

    constructor() {
        super();
        console.log('inside constructor');
        this.message = '';
    }

    /** Skate callback from withRenderer; performs the actual rendering */
    rendererCallback(renderRoot, renderCallback) {
        renderRoot.innerHTML = '';
        renderRoot.appendChild(renderCallback());
    }

    /** Skate callback from withRenderer; returns content to be rendered. */
    renderCallback({message}) {
        console.log('inside renderCallback');
        let el = document.createElement('span');
        el.innerHTML = message;
        return el;
    }

    /** Skate callback from withRenderer; called when rendering has completed */
    renderedCallback() {
        console.log('inside renderedCallback');
    }

    /** Skate callback from withProps; called when properties have been set (not necessarily changed) */
    propsSetCallback(next, prev) {
        console.log('inside propSetCallback', 'next:', next, 'prev:', prev);
    }

    /** Skate callback from withProps; called when properties have changed */
    propsChangedCallback(next, prev) {
        super.propsChangedCallback(next, prev);
        console.log('inside propsChangedCallback', 'next:', next, 'prev:', prev);
    }

    /** Skate callback from withChildren; called when the children have changed */
    childrenChangedCallback() {
        console.log('inside childrenChangedCalback');
    }

    /** Fires after an instance has been inserted into the document */
    connectedCallback() {
        super.connectedCallback();
        console.log('inside connectedCallback');
    }

    /**
     * Fires after an instance has been removed from the document. Here
     * we stop the timer and remove event listeners.
     */
    disconnectedCallback() {
        super.disconnectedCallback();
        console.log('inside disconnectedCallback');
    }

    /**
     * Fires after an attribute has been added, removed, or updated.
     */
    attributeChangedCallback(attr, oldVal, newVal) {
        super.attributeChangedCallback(attr, oldVal, newVal);
        console.log('inside attributeChangedCallback', 'attr:', attr, 'oldVal:', oldVal, 'newVal:', newVal);
    }

    /**  Fires after an element has been moved to a new document. Not polyfilled. */
    adoptedCallback() {
        super.adoptedCallback();
        console.log('inside adoptedCallback');
    }
}

VirtuaTrainingEchoSkate.props = {
    message: props.string
};
VirtuaTrainingEchoSkate.is = 'vt-echo-skate';


// Registers <vt-echo-slate> as a custom element
window.customElements.define(VirtuaTrainingEchoSkate.is, VirtuaTrainingEchoSkate);

Skate

Stencil

  • Compiler that generates standard web components

    • Custom Elements and optionally Shadow DOM

  • Uses TypeScript and JSX

  • Provides these features:

    • Reactive data-binding

    • Virtual DOM

    • Async rendering

    • Support for objects in properties

    • Server-side rendering (SSR)

Stencil

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Echo Stencil Web Component Demo</title>

    <!-- Importing Web Components Polyfill -->
    <script src="../../bower_components/webcomponentsjs/webcomponents-loader.js"></script>

    <!-- Import our custom element -->
    <script src="../../stencil-output/build/app.js"></script>

</head>
<body>
<h1>Echo Stencil Web Component Demo</h1>
<!-- Using our new custom element -->
<!-- tag::element[] -->
<vt-echo-stencil message="Hello Web Component world!"></vt-echo-stencil>
<!-- end::element[] -->
</body>
</html>

Stencil

import {Component, Prop} from '@stencil/core';


@Component({
    tag: 'vt-echo-stencil',
    styleUrl: 'vt-echo-stencil.scss',
    shadow: true
})
export class VirtuaTrainingEchoStencil {

    @Prop() message: string;

    render() {
        return (
            <span>   asdas{this.message}</span>
        );
    }

    /**
     * Stencil callback; called when the component is about to load but has not rendered yet.
     *
     * This is a good place to make any last minute updates before rendering.
     */
    componentWillLoad() {
        console.log('Inside componentWillLoad()', 'The component is about to be rendered');
    }

    /**
     * Stencil callback; called component has been loaded and has rendered.
     *
     * Updating data in this event may cause the component to re-render.
     */
    componentDidLoad() {
        console.log('Inside componentDidLoad()', 'The component has been rendered');
    }

    /**
     * Stencil callback; called when the component is about to update and re-render.
     *
     * Called multiple times throughout the life of the component as it updates.
     */
    componentWillUpdate() {
        console.log('Inside componentWillUpdate()', 'The component will update');
    }

    /**
     * Stencil callback; called when the component has finished updating.
     *
     * Called after componentWillUpdate
     */
    componentDidUpdate() {
        console.log('Inside componentDidUpdate()', 'The component did update');
    }

    /**
     * Stencil callback; called when the component has unloaded and the element will be destroyed.
     */
    componentDidUnload() {
        console.log('Inside componentDidUnload', 'The view has been removed from the DOM');
    }
}

Stencil

 :host {
   display: inline-block;
 }

span {
  padding: 1rem;
  background-color: aliceblue;
}

Stencil

Playing with Others?

Angular

React

  • Web Components can be used within a React app

    • May require some additional work for handling event or executing methods

  • React can be used inside of a web component

  • Using Web Components in React

Vue

Ensuring Interop

  • Smaller amount of library features = Better interop

  • Send data down through properties, data up through events

  • Check out your framework’s compatibility at Custom Elements Everywhere

Playing with Others!

The Future is Now