Learning Web Components

with joe Marini

my notes from his Lynda.com video training

Notes

Web components -

  1. Simplify the process of building web aps
    • underlying complexity hidden by components
  2. Promote the principles of reuse
    • build once, and use again and again
  3. Provide encapsulation of appearance and behavior
    • keep HTML, script, and CSS separate from other components

W3C Standards for custom elements and packaging

Web Component Standards

  1. ** HTML Template** - defines how to declare fragments of markup that go unused at page load, but can be instantiated later at runtime

  2. ** HTML Imports** - Allows HTML markup content to be defined as an external file and then included into other webpages

  3. ** Custom Elements ** - specifies how to create new types of DOM elements along with custom properties, behavior, and attributes

  4. ** Shadow DOM ** - Provide a way to encapsulate custom elements and their related styling so that they are isolated from the rest of the page

Html Template

  1. Use Template tag, hidded, until inserted, can be placed anyway that html can go

  2. Supported by Chrome, Edge, Firefox, Safari

  3. Declaring a Template:

<template id='mytemplate'>
    <p> This is some template content</p>
    <img src='myimage.png'>
    <script>
        console.log("This is some script code");
    </script>
</template
var myTempl = document.getElementByID("tmplID");
var newNode = document.importNode(myTempl.content, true);
newNode.innerHTML = "some content";
document.body.appendChild(newNode);

Overview of HTML Imports

  1. Imports provide for modularity of code and things
<link rel="import" href="path/to/content.html">
- Import does not mean "include this content here"
- The browser automatically de-dupes imports
- Scripts in imports do not block the main page
  1. Polyfills - browser support
    • must always precede anything else we want to import via link rel="import"

import_template

Custom Elements

The ability to write our own tags

When the browser encounters a tag that it does not know about; it parses that tag as an HTMLUnknownElement

The rules of custom elements:

  1. Make sure custom element names contain hyphens
  2. Create a JavaScript class to define behavior
  3. Call customElements.define() to define the element

Shadow DOM

Today's web apps are very brittle due to:
- HTML, CSS, and JavaScript are global by nature
- clashes between IDs, CSS styles
- JavaScript globals can be inadvertently modified
Shadow DOM solves these problems with encapsulation
- Content is kept local to the element

  1. Creating Shadow DOM
var shadowRoot = element.attachShadow({ mode: "open"});
  1. Ways to add content to the element
shadowRoot.innerHTML = "<p>some html code...</p>"; OR
shadowRoot.appendChild(someNode);

Composition and Content

  1. Composition determines how a custom element structure and appearance are built out of other primitive elements
  2. Light DOM - User-suppled content
    • <slot></slot> - determines where the child content provided by developer will be inserted
    • Slots can be empty OR provide fallback content
  3. Styling Shadow DOM
    • use :host selector
    • it applies styles to the custom element
  4. Custom Elements Styles must be polyfilled as well, by placing the picture below in the connectedCallback():
    css_polyfill

and by putting this snippet in right before we define the custom element:

css_polyfill2

A Complete Custom Element Template

<template id="msg-ban-tmpl">
    <style>
        :host {
            display: none
        }

        :host([visible]) {
            display: block;
        }

        :host([type="critical"]) {
            background: red;
            border: 1pt solid maroon;
            color: white;
            font-weight:600
        }

        :host #closebox {
            cursor: pointer;
        }
    </style>
    <div id='content' class="message">
        <span id='closebox'>[X] </span>
        <slot></slot>
    </div>
</template>

<script>
(function() {
    let thisImportDoc = document.currentScript.ownerDocument;

    class MessageBanner extends HTMLElement {
        constructor() {
            super(); // always call super() first
        }

        /** Indicates which attributes will trigger the
            attributeChangedCallback function **/
        static get observedAttributes() {
            return ["visible"];
        }
        static get template() {
            if (!this._template) {
                this._template = thisImportDoc.querySelector("#msg-ban-tmpl");
            }
            return this._template;
        }
        /** Custom Component Reactions **/
        connectedCallback() {
            console.log("message-banner connected to page");

            let shadowRoot = this.attachShadow({mode: "open"});
            let tmpl = MessageBanner.template;
            shadowRoot.appendChild(
                document.importNode(tmpl.content,true)
            );

            // click event to hide the message
            var closebox = shadowRoot.querySelector("#closebox");
            closebox.addEventListener("click", e => {
                this.visible = false;
                console.log("message-banner closed");
            });

            // If the browser does not natively support ShadowDOM, we have
            // to polyfill the styling mechanism for the component
            const supportsNativeShadowDOM = !!HTMLElement.prototype.attachShadow;
            if (!supportsNativeShadowDOM) {
                ShadyCSS.applyStyle(this);
            }
        }

        disconnectedCallback() {
            console.log("message-banner disconnected from page");
        }

        adoptedCallback() {
            console.log("message-banner adopted in page");
        }

        attributeChangedCallback(name, oldValue, newValue) {
            console.log("message-banner attr: " + name + " changed from '" +
                oldValue + "' to '" + newValue + "'");
        }

        /** Expose the visible attribute as getter and setter **/
        get visible() {
            return this.hasAttribute('visible');
        }

        set visible(val) {
            if (val) {
                this.setAttribute('visible', '');
            } else {
                this.removeAttribute('visible');
            }
        }
    }

    // This is only for browsers that don't yet have native ShadowDOM 
    // support. When native support is present this call does nothing
    ShadyCSS.prepareTemplate(MessageBanner.template, "message-banner");

    customElements.define("message-banner", MessageBanner);
})();
</script>
Learning Web Components
Share this