web-component-base-class

Base class to simplify the creation of web components

Usage no npm install needed!

<script type="module">
  import webComponentBaseClass from 'https://cdn.skypack.dev/web-component-base-class';
</script>

README

webComponentBaseClass

Library to simplify web component creation and reduce repetitive code for web components. I love web components, but some of it can be a bit cumbersome or repetitive to write especially if you want to use shadow dom. This library was created to simplify some of its creation and add some simple helper functions and objects to work with them after creation. This library doesn't add any fancy data binding features to keep it simple and I personally don't really like it to hide away the way data transfers between the DOM and JavaScript. Some of the helper functions are inspired by polymer.

Production

The following steps can be used to install for production

Installation

npm install web-component-base-class

Development

The following steps can be used if you want to develop on this library

Installation

Clone the git repo

git clone https://github.com/virtualcodewarrior/webComponentBaseClass.git

Install dev dependencies

npm install

Build

Building is only needed to minify this library. The following command will minify the src files and put them in a dist folder:

npm run build

Manual Testing

To manually test the examples you can start a local web server using:

npm start

This will start a web server on your local machine on port 2211. If you want to use another port you can modify package.json and set a different port. Open a browser to http://localhost:2211/examples/ to see the library in action. This uses the minified library output, so you would have to build it first.

Automatic testing

To run the unit tests once, run

npm test

To run the unit tests in watch mode, run

npm run watch-test

Generate documenttation

npm run doc

Usage

To use this library you will have to extend your web component class from the exported webComponentBaseClass:

import { webComponentBaseClass } from '../../src/webComponentBaseClass.js';

const changeHandlerKey = Symbol('changeHandler');

const componentName = 'my-element';
window.customElements.define(componentName, class extends webComponentBaseClass {
    static get is() { return componentName; }
    constructor() {
        super();
        // extra required initialization goes here ...
    
        // change observer implementation example for a property
        this[changeHandlerKey] = (newValue, oldValue) => {
        }
    }

    // here we add some properties to this web component
    static get properties() {
        return {
            propertyName: { // the name of the property
                type: String, // (required) the type of the property, one of Array, Boolean, Number, Object, String
                value: 'value', // (optional) default value for the property
                reflectToAttribute: true, // (optional) indicate if you want the component attribute to always reflect the current property value
                observer: changeHandlerKey, // (optional) the name or a symbol for a function in the class to be called when the value of the property is changed
            },
            // add as many properties as you need ...
        };
    }

    // optional callback function that will be called after this instance of the web component
    // has been added to the DOM
    attached() {
        // extra initialization that only can be done after an instance of the class has been attached to the DOM
    }

    // optional callback function that will be called after this instance of the web component has been removed from the DOM
    detached() {
        // extra cleanup that only can be done after an instance of the class has been removed from the DOM
    }

    // string representation of the template to use with this web component
    // note that providing the template element is optional and that if the template wrapper is not provided
    // the whole content will be wrapped within a template element
    static get template() {
        return `
            <style>
                /* put you styling here */
            </style>
            <!-- The content of the template goes here -->
        `;
    }
});

This library has examples provided which also can be used for creating your own web components. See the examples directory in the repo.

Utility functions and objects

There is a number of extra utility functions available to you inside of the class, to make it easier to write your code.

$ object

The class has an utility object called $ which can be accessed from within the class using this.$. The object will contain all the elements that are declared with an 'id' attribute within the template definition of the web component. For instance when you define the following template content:

<template id="my-element">
    <input id="example-input">
    <pre id="output"></pre>
    <input id="exampleOutput">
    <button id="btn-1"></button>
    <button id="test-button"></button>
    <button id="Bla"></button>
    <div id="some-container">
        <span id="container-content"></span>
    </div>
</template>

Then the content of this.$ will be

this.$ = {
    exampleInput: inputElement,
    output: preElement,
    exampleOutput: inputElement,
    btn1: buttonElement,
    testButton: buttonElement,
    Bla: buttonElement,
    someContainer: divElement,
    containerContent: spanElement,
}

Note that id's that contain dashes '-' will be converted to camel case to make it easier to access them. In the code you can use this to directly access those elements without having to re-query them. e.g.

    ...
    attached() {
        this.$.containerContent.textContent = "my content"; // this will set the text content of #container-content to 'my content'
    }
    ...

If you remove elements with an id or add new elements with an id through code, you should call refreshQuickAccess to recreate the object.

$ function

The $(selector) function is a shorthand function for this.shadowRoot.querySelector(selector). The query is limited to the shadow DOM. This can be used everywhere you want to do querySelector on the shadowDom only. For instance calling const input = this.$('input'); on the web component template definition used in the previous example, will return the input element with the id of 'example-input'. If the selector cannot find a match undefined will be returned.

$$ function

The $$(selector) function is a shorthand for Array.from(this.shadowRoot.querySelectorAll(selector)). The query is limited to the shadow DOM. This can be used everywhere you want to do querySelectorAll on the shadowDom only and want to get back an array instead of an 'array like' object that is normally returned from querySelectorAll. This means you can use forEach and all the other array functions directly on the result. If no objects matching the selector can be found, an empty array will be returned. e.g.

this.$$('button').forEach((button) => { button.disabled = true; }); // disable all our buttons

addAutoEventListener function

addAutoEventListener(HTMLElement, EventName, CallbackFunction). This function allows you to attach an eventListener to the specified HTMLElement. Any event listener attached using this function will automatically be cleaned up if the web component gets removed from the DOM This makes it easier to add closure callback function without having to worry about how you clean them up later. e.g.

attached() {
    this.addAutoEventListener(this.$.testButton, 'click', () => { alert('button clicked'); });
}

removeAutoEventListener function

removeAutoEventListener(HTMLElement, EventName, CallbackFunction). This function allows you to clean up an eventListener previously added using the addAutoEventListener function. Of course if you want to use this function you will have to keep track of the element, event name and callback yourself so you can remove them properly.

refreshQuickAccess function

call this function to rebuild the map of id to elements after you have manually modified the content of the shadow dom by removing or adding elements that have an id attribute.

onAttached and onDetached

In some cases it might be useful to know when a web component has been added or removed from the DOM for a script using the web component as opposed to the scripts inside of the web component. If a script needs to do additional things after a web component was added or removed, it can define a function that will be called after a web component has been attached or detached from the DOM.

e.g.

const myElement = document.createElement('my-element');
myElement.onAttached = () => {
    // initialize extra stuff here after the element has been added to the DOM
}

myElement.onDetached = () => {
    // cleanup here after the element has been removed from the DOM
}

document.body.appendChild(myElement);

If you assign the onAttached function after the component was already attached, the callback will be called immediately.