loading...
Cover image for Stepping stones: Web Components

Stepping stones: Web Components

omnoms profile image Marko V ・3 min read

Today I delved into web components to learn the innards of web components, before getting into angular elements and understanding how angular elements wraps the native stuff.

So I did a floating icon, wrapping a little bit of functionality before rendering content. It's like a preamble to a work-related task where I will create a common floating container-area for our floating buttons for things like chat, surveys, etc.

Initial thoughts

I keep IE11 in the back of my head, and so many things, need to be "reworked" to get this to function in IE11, even a basic thing such as this. Ignoring the ES6 class issue it was quite easy to get the "bound" properties. However, all bound properties are sent as strings, so no fancy schmancy conversions happening.

So to send an object, it would be sent as a string or you need to do so atleast, and functions are also passed as string, so you could eval() those, for better or worse, mostly worse, or if it's non-native events that you wish to raise, you can just raise them normally and parent elements can capture them through addEventListener or similar methodology.

The template

If I wanted to make something entirely self-contained, I had to create the elements through JS and not through a template definition made in an html file because then you would have to have that template in the consuming markup. Maybe that's not a problem for you. But for the intents that I have, where I want to be able to ship out custom components to other projects disconnected from mine, that's not ideal.

So I had to do a fair bit of document.createElement() in my code to detach it from that dependency and have my "template" through code.

I'll have to go over this code once more to make it IE11 safe.

It was surprisingly easy to get started from scratch. Next part of this will likely be angular elements or the IE11 variation.

chatButton.js

customElements.define('chat-button',
    class extends HTMLElement {
        _imgSrc = "";
        _initials = "";
        constructor() {
            super();
            this.parseImg();
            this.parseInitials();
            const buttonContent = document.createElement("span");
            buttonContent.id= "chat-button-content";
            buttonContent.addEventListener("click", this._clickFn);
            if(this._imgSrc !== "") {
                let img = document.createElement("img");
                img.src = this._imgSrc;
                img.className = "chat__icon";
                buttonContent.appendChild(img);
            } else {
                let initSpan = document.createElement("span");
                initSpan.textContent = this._initials;
                initSpan.className = "chat__initials";
                buttonContent.appendChild(initSpan);
            }
            const randomColor = this.getRandColor();
            const style = document.createElement("style");
            const styleStr = `
                #chat-button-content {
                    display: inline-block;
                    height: 50px; 
                    width: 50px;
                    border-radius: 50px;
                    box-shadow: 2px 2px 3px #999;
                    overflow: hidden;
                    text-align: center;
                    margin: 5px;
                    ${this._imgSrc === ""?"background-color: " + randomColor: ""}
                }
                #chat-button-content > .chat__icon {
                    margin: auto; 
                    width: 50px;
                    height: 50px;
                    max-width: 200px;
                }
                #chat-button-content > .chat__icon > img {
                    position: absolute;
                    left: 50%;
                    top: 50%;
                    height: 100%;
                    width: auto;
                }
                #chat-button-content > .chat__initials {
                    vertical-align: center;
                    line-height: 50px;
                }`;
            style.textContent = styleStr;
            var wrapper = document.createElement("div");
            wrapper.appendChild(style);
            wrapper.appendChild(buttonContent);
            this.attachShadow({mode: 'open'}).appendChild(wrapper);
        }
        getRandomInt(max) {
            return Math.floor(Math.random() * Math.floor(max));
        }
        getRandColor() {
            const r = this.getRandomInt(16).toString(16);
            const g = this.getRandomInt(16).toString(16);
            const b = this.getRandomInt(16).toString(16);
            return "#" + r+g+b;
        }
        parseImg() {
            const img = this.getAttribute("img");
            if(Object.prototype.toString.call(img) === "[object String]" && img !== "") {
                this._imgSrc = img;
            } 
        }
        parseInitials() {
            const initials = this.getAttribute("initials");
            if(Object.prototype.toString.call(initials) === "[object String]" && initials !== "") {
                this._initials = initials;
            }
        }

        /// LIFE-CYCLE
        connectedCallback() {
            console.log("Connected.");
        }
        disconnectedCallback() {
            console.log('Disconnected.');
        }          
        adoptedCallback() {
            console.log('Adopted.');
        }
        attributeChangedCallback(name, oldValue, newValue) {
            console.log('Attributes changed.', name, oldValue, newValue);
        }
    }
);

index.html

<html>
    <head>
        <title>
            WebComponent test
        </title>
        <script defer src="chatButton.js"></script>
        <style>
            .chat__container {
                position: fixed;
                bottom: 60px;
                right: 60px;
            }
        </style>
        <script>
            function _myClickFunction() {
                console.log("Callback!");
            }
        </script>
    </head>
    <body>
        <div class="chat__container">
            <chat-button onClick="_myClickFunction()" img="https://vision.org.au/business/wp-content/uploads/sites/14/2019/08/1600-business-success.jpg" initials="AD"></chat-button>
            <chat-button initials="JD"></chat-button>
        </div>
    </body>
</html>

reference;
https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM
https://github.com/mdn/web-components-examples/blob/master/popup-info-box-web-component/main.js

Posted on by:

omnoms profile

Marko V

@omnoms

Fullstack Dev Tech Lead

Discussion

markdown guide