How are Javascript widgets made without iFrames?

I have a chat widget that I want to embed it other people's websites. It looks just like Intercom and all the other chat popups. I want to make the chat popup stick to the bottom-right hand corner of the screen regardless of where you scroll. However, when I import the chat app as an iframe and give it position: fixed; bottom: 0px; right: 15px;, the iframe does not go to where I expect it to go.

I realize that iframes are suboptimal for embedded JS widgets, and all the best embedded apps are importing .js files from file storage. After searching online for hours I have yet to find an explanation/tutorial on how to make those JS files that hook onto a and render the widget. How do you even make one of those pure javascript apps, and what are they called? (Not web components I assume, because there have been widgets for a long time).

Sorry if this question is kinda noob. I never knew this was a thing until I tried implementing it myself. Can anyone point me in the right direction on how to get started making JS web widgets? Thank you! (Maybe a ReactJS to VanillaJS converter would be super cool)

1 answer

  • answered 2018-03-11 16:23 Paul T.

    A pure Javascript App is called a SPA - Single Page Application - and they have full control over the document (page). But since you ask about embeding a widget, I don't think that is what this question is about (there are tons of info. on the web on SPAs).

    I was going to suggest that going forward you do this using Web Components - there are polyfills available today that make this work on nearly all browsers - but since your question mentioned that you wanted to know how it is done without it, I detail below one of my approaches.

    When creating a pure JS widget you need to ensure that you are aware that a) you do NOT have control over the global space and b) that it needs to play nice with the the rest of the page. Also, since you are not using Web Components (and are looking for a pure javascript (no libs)), then you also have to initialize the widget "manually" and then insert it to the page at the desired location - as oposed to a declaritive approach where you have an assigned HTML tag name for your widget that you just add to the document and magic happens :)

    Let me breack it down this way:

    Widget Factory

    Here is a simple Javascript Widget factory - the create() returns an HTML element with your widget:

    const Widget = Object.create({
        create(chatId) {
            const wdg = document.createElement("div")
            wdg.classList.add("chat-box");
            wdg.innerHTML = `<h1>Chat: ${ chatId }</h1>`;
            // Load your chat data into UI
            return wdg;
        }
    });
    

    To create a new widget (HTML Element) using the above you would:

    const myWidgetInstance = Widget.create("chat-12345");
    

    and to insert this widget into the page at a given location (ex. inside of a DIV element with id "chat_box", you would:

    document.getElementById("chat_box").appendChild(myWidgetInstance);
    

    So this is the basics of creating a Widget using the native (web) platform :)

    Creating a reusable/embeddable Component

    One of the key goals when you deliver a reusable and embeddable component is to ensure you don't rely on the global space. So your delivery approach (more like your build process) would package everything together in a JavaScript IIFD which would also create a private scope for all your code.

    The other important aspect of these type of singleton reusable/embeddable components is that your styles for the Element needs to ensure they don't "leak" out and impact the remainder of the page (needs to play nice with others). I am not going into detail on this area here. (FYI: this also the area where Web Component really come in handy)

    Here is an example of a Chat component that you could add to a page anywhere you would like it to appear. The component is delivered as a <script> tag with all code inside:

    <script>(function() {
        const Widget = Object.create({
            create(chatId) {
                const wdg = document.createElement("div");
                wdg.classList.add("chat-box");
                wdg.innerHTML = `<h1>Chat: ${ chatId }</h1>`;
                // Load your chat data into UI
                return wdg;
            }
        });
    
        const myWidgetInstance = Widget.create("chat-12345");
        const id = `chat-${ Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1) }`;
        document.write(`<div id="${ id }"></div>`);
        document.getElementById(id).appendChild(myWidgetInstance);
    })();</script>
    

    So you could use this in multiple places just by droping in this script tag in the desired locations:

    <body>
        <div>
            <h1>Chat 1</h1>
            <script>/* script tag show above */</script>
        </div>
        ...
        <div>
            <h1>Chat 2</h1>
            <script>/* script tag show above */</script>
        </div>
    </body>
    

    This is just a sample approach of how it could be done. You would have to add more in order to support passing options to each widget (ex. the chat id), defining styles as well other possible improvements that would make the runtime more efficient.

    Another approach

    You could add your "script" once and wait for the rest of the page to load, then search the document for a "known" set of elements (ex. any elemnet having a CSS Class of chat-box) and then initialize a widget inside of them (jQuery made this approach popular).

    Example: Note how data attributes can be used in DOM elements to store more data specific to your widget.

    <div class="chat-box" data-chatid="123"></div>
    
    <script>(function() {
        const Widget = Object.create({
            create(chatId) {
                const wdg = document.createElement("div");
                wdg.classList.add("chat-box");
                wdg.innerHTML = `<h1>Chat: ${ chatId }</h1>`;
                // Load your chat data into UI
                return wdg;
            }
        });
        const initWhenReady = () => {
            removeEventListener("DOMContentLoaded", initWhenReady);
            Array.prototype.forEach.call(document.querySelectorAll(".chat-box"), ele => {
                const myWidgetInstance = Widget.create(ele.dataset.chatid);
                ele.appendChild(myWidgetInstance);
            });
        };
        addEventListener('DOMContentLoaded', initWhenReady);
    })();</script>
    

    Hope this helps.