Building an Embeddable Widget With HTML, CSS, and Javascript

Building an Embeddable Widget With HTML, CSS, and Javascript

The use cases for widgets have risen over the past ten years within the web ecosystem as their importance has grown in different sectors ranging from e-commerce to fintech to health websites.

Widgets have made it easy and seamless for users to perform various actions on a website.

In this article, we will be going over a tutorial on how to build a simple messaging widget that can be embedded in any website.

Prerequisites

  1. Node installed on their local machine

  2. yarn or npm installed on your local machine (npm comes pre-installed with node)

  3. A text editor installed e.g VsCode.

  4. Basic knowledge of HTML, CSS, Javascript, and the terminal. (see link for a refresher on them)

What is a Widget?

A widget is a stand-alone app or component that can be added to a website. They usually work independently of the website in which they’re installed, hence, they have everything they need to run efficiently wherever they’re used. Widgets can be set up very easily - it’s as simple as copy-pasting a script or code block into your website for it to start working.

There are so many reasons or use cases for needing a widget in a website, they are useful with a lot of things like:

  • Helping business owners and customers communicate more easily via instant messaging.

  • Aiding the customer support team to efficiently interact with a user’s issues or bugs in a website. They can even serve as a means through which users can see FAQs on a website.

  • Enabling users to gain easy access to products and items in carts for eCommerce businesses.

Generally, widgets improve the overall customer retention/experiences of businesses and help drive sales and solve recurring issues amongst users. There are many kinds of widgets, ranging from customer support to messaging widgets, help widgets, search widgets, cart widgets, etc. Anything can be a widget as long as it has a specific functionality it’s meant to perform on a website.

Getting Started

For this tutorial, we will build a simple messaging widget that can be installed on any website. If you’re interested in getting the code for the final app, you can find it here.

We’ll be building our widget using Vite as our package bundler. It’s very easy and straightforward to set up.

Setting up the project

Open your terminal and paste in the command below to create a new folder and navigate into that folder:

mkdir messaging-widget
cd messaging-widget

Once you’re in the folder, paste in the command below to setup Vite:

yarn create vite

You’ll get some prompts that you’ll need to answer for the setup, use my responses below:

  • Project Name is: ./

  • Select a framework: Vanilla

  • Select a variant: Javascript

On doing this, you should get results similar to the one below:

Now that Vite has been set up successfully, we’ll need to install it as it’s a dev dependency in our project. To do this, run the code snippet below:

yarn install

Once the installation has been completed, you can now open the folder in VsCode by using the command below or locate the folder in your file manager and open it directly from there.

code .

Our project should now look like this now:

Adding the widget functionality

Now, go inside the main.js file and delete any code we have in there, the same applies to the counter.js file, delete all code in there and rename the file to asset.js, we’ll be using it later in the tutorial.

Paste in the following code block inside our main.js file:

class MessageWidget {
  constructor(position = "bottom-right") {
    this.position = this.getPosition(position);
    this.open = false;
    this.initialize();
    this.injectStyles();
  }

  position = "";
  open = false;
  widgetContent = null;  
}

We’ll be using a Javascript Class to encapsulate most of our logic for the widget. The constructor() method is automatically invoked whenever a new instance of our MessageWidget is instantiated. Our class accepts a position parameter that would be used to determine the widget's position in the DOM. We’ve assigned a default parameter for our position argument, hence, it’s not a requirement when the class is being instantiated.

We’re also setting some properties and invoking some methods inside the constructor, we’ll be creating them later on as we move. However, here are their responsibilities:

  • position - Used to save the position of the widget in the DOM.

  • open - Used to toggle the state of our widget i.e Open or Closed.

  • widgetContainer - HTML element for our widget.

  • initialize() - Invoked to display and create the UI for our widget in the DOM.

  • injectStyles() - Invoked to add the styling for our widget.

Continuing, let’s create the methods we’ve used above. To do this, copy the code snippet below and paste it below the constructor:

import { CLOSE_ICON, MESSAGE_ICON, styles } from "./assets.js";

class MessageWidget {
  constructor(position = "bottom-right") { ... }

  position = "";
  open = false;
  widgetContainer = null;

  getPosition(position) {
    const [vertical, horizontal] = position.split("-");
    return {
      [vertical]: "30px",
      [horizontal]: "30px",
    };
  }

  async initialize() {
    /**
     * Create and append a div element to the document body
     */
    const container = document.createElement("div");
    container.style.position = "fixed";
    Object.keys(this.position).forEach(
      (key) => (container.style[key] = this.position[key])
    );
    document.body.appendChild(container);

    /**
     * Create a button element and give it a class of button__container
     */
    const buttonContainer = document.createElement("button");
    buttonContainer.classList.add("button__container");

    /**
     * Create a span element for the widget icon, give it a class of `widget__icon`, and update its innerHTML property to an icon that would serve as the widget icon.
     */
    const widgetIconElement = document.createElement("span");
    widgetIconElement.innerHTML = MESSAGE_ICON;
    widgetIconElement.classList.add("widget__icon");
    this.widgetIcon = widgetIconElement;

    /**
     * Create a span element for the close icon, give it a class of `widget__icon` and `widget__hidden` which would be removed whenever the widget is closed, and update its innerHTML property to an icon that would serve as the widget icon during that state.
     */
    const closeIconElement = document.createElement("span");
    closeIconElement.innerHTML = CLOSE_ICON;
    closeIconElement.classList.add("widget__icon", "widget__hidden");
    this.closeIcon = closeIconElement;

    /**
     * Append both icons created to the button element and add a `click` event listener on the button to toggle the widget open and close.
     */
    buttonContainer.appendChild(this.widgetIcon);
    buttonContainer.appendChild(this.closeIcon);
    buttonContainer.addEventListener("click", this.toggleOpen.bind(this));

    /**
     * Create a container for the widget and add the following classes:- `widget__hidden`, `widget__container`
     */
    this.widgetContainer = document.createElement("div");
    this.widgetContainer.classList.add("widget__hidden", "widget__container");

    /**
     * Invoke the `createWidget()` method
     */
    this.createWidgetContent();

    /**
     * Append the widget's content and the button to the container
     */
    container.appendChild(this.widgetContainer);
    container.appendChild(buttonContainer);
  }

  createWidgetContent() {
    this.widgetContainer.innerHTML = `
        <header class="widget__header">
            <h3>Start a conversation</h3>
            <p>We usually respond within a few hours</p>
        </header>
        <form>
            <div class="form__field">
                <label for="name">Name</label>
                <input
                  type="text"
                  id="name"
                  name="name"
                  placeholder="Enter your name"
                />
            </div>
            <div class="form__field">
                <label for="email">Email</label>
                <input
                  type="email"
                  id="email"
                  name="email"
                  placeholder="Enter your email"
                />
            </div>
            <div class="form__field">
                <label for="subject">Subject</label>
                <input
                  type="text"
                  id="subject"
                  name="subject"
                  placeholder="Enter Message Subject"
                />
            </div>
            <div class="form__field">
                <label for="message">Message</label>
                <textarea
                  id="message"
                  name="message"
                  placeholder="Enter your message"
                  rows="6"
                ></textarea>
            </div>
            <button>Send Message</button>
        </form>
    `;
  }

  injectStyles() {
    const styleTag = document.createElement("style");
    styleTag.innerHTML = styles.replace(/^\s+|\n/gm, "");
    document.head.appendChild(styleTag);
  }

  toggleOpen() {
    this.open = !this.open;
    if (this.open) {
      this.widgetIcon.classList.add("widget__hidden");
      this.closeIcon.classList.remove("widget__hidden");
      this.widgetContainer.classList.remove("widget__hidden");
    } else {
      this.createWidgetContent();
      this.widgetIcon.classList.remove("widget__hidden");
      this.closeIcon.classList.add("widget__hidden");
      this.widgetContainer.classList.add("widget__hidden");
    }
  }
}

The code snippet adds the three methods in our class, namely:

  • getPosition() - used to generate the numerical positions of the widget in the DOM

  • createWidgetContent() - used to generate the HTML content for our widget

  • initialize() - responsible for displaying and creating the UI for our widget in the DOM. I’ve added short comments in the body of the method to explain what’s being done in each step.

  • injectStyles() - responsible for injecting the HTML stylings for the widget inside the page’s document.

Adding our styling

The next thing we are going to do is add our styling and assets to the asset.js file we created earlier. Paste in the following code inside asset.js:

export const styles = `
    .widget__container * {
        box-sizing: border-box;
    }        
    h3, p, input {
        margin: 0;
        padding: 0;
    }
    .widget__container {
        box-shadow: 0 0 18px 8px rgba(0, 0, 0, 0.1), 0 0 32px 32px rgba(0, 0, 0, 0.08);
        width: 400px;
        overflow: auto;
        right: -25px;
        bottom: 75px;
        position: absolute;
        transition: max-height .2s ease;
        font-family: Helvetica, Arial ,sans-serif;
        background-color: #e6e6e6a6;
        border-radius: 10px;
        box-sizing: border-box;
    }
    .widget__icon {
        cursor: pointer;
        width: 60%;
        position: absolute;
        top: 18px;
        left: 16px;
        transition: transform .3s ease;
    }
    .widget__hidden {
        transform: scale(0);
    }
    .button__container {
        border: none;
        background-color: #0f172a;
        width: 60px;
        height: 60px;
        border-radius: 50%;
        cursor: pointer;
    }
    .widget__container.hidden {
        max-height: 0px;
    }
    .widget__header {
        padding: 1rem 2rem 1.5rem;
        background-color: #000;
        color: #fff;
        border-top-left-radius: 10px;
        border-top-right-radius: 10px;
        text-align: center;
    }
    .widget__header h3 {
        font-size: 24px;
        font-weight: 400;
        margin-bottom: 8px;
    }
    form {
        padding: 2rem 1rem 1.5rem;
    }
    form .form__field {
        margin-bottom: 1.5rem;
        display: flex;
        flex-direction: column;
    }
    .form__field label {
        margin-bottom: 8px;
        font-size: 14px;
    }
    .form__field input,
    .form__field textarea {
        border: 1px solid #000000ad;
        border-radius: 3px;
        padding: 8px 10px;
        background-color: #fff;
    }
    .form__field input {
        height: 48px;
    }
    .form__field textarea::placeholder {
        font-family: Helvetica, Arial ,sans-serif;
    }
    form button {
        height: 48px;
        border-radius: 6px;
        font-size: 18px;
        background-color: #000;
        color: #fff;
        border: 0;
        width: 100%;
        cursor: pointer;
    }
    form button:hover {
        background-color: rgba(0, 0, 0, 95%);
    }
`;

export const MESSAGE_ICON = `
    <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-mail">
        <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
        <polyline points="22,6 12,13 2,6"></polyline>
    </svg>
`;

export const CLOSE_ICON = `
    <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="#FFFFFF" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x">
        <line x1="18" y1="6" x2="6" y2="18"></line>
        <line x1="6" y1="6" x2="18" y2="18"></line>
    </svg>
`;

This contains the styling for our messaging widget and the icons used in the widget. We’ll need to import them inside our main.js file, and paste the code below at the beginning of our file:

import { styles, CLOSE_ICON, MESSAGE_ICON } from "./assets.js";

The last thing for us is to create a function to initialize our MessagingWidget. The function created returns a new instance of the MessagingWidget every time it’s invoked.

To do this, copy the code snippet below and paste it below the constructor.

class MessagingWidget { ... }

function initializeWidget() {
  return new MessageWidget();
}

initializeWidget();

Testing our widget

Now, save all changes and paste the code below in your terminal to start a development server:

yarn dev

Then, open the generated port in your browser and you should have the following result:

Congratulations🎉, you now have a working widget. Hmm🤔…Wait a minute, remember I mentioned at the beginning, a widget can work on any website that it’s installed, how do we now make our widget installable and available to everyone?

Connecting our widget to a CDN

We’ll need to deploy our code to GitHub or npm and then connect it to a CDN (Content Delivery Network) so that we can have a link that can be shareable anywhere in the world. That’s not all! For the widget to be installed on a website, we’ll also need to have documentation or instruction which people can follow for them to perform the installation.

In our case, we'll be deploying our code to GitHub and connecting it to JsDelivr so that we can get our code on a CDN. There are many popular CDNs out there like Cloudflare, JsDelivr, RawCDN, etc. so feel free to choose anyone you find interesting.

To deploy your code online, create a repository on GitHub and push your code there. Once your code is already on GitHub, copy the link to the Javascript file, we’ll be needing it in a bit when we’re on JsDelivr.

Now, head on to JsDelivr and paste the link in the input element labeled GitHub. A new CDN link would be generated for you which can be used to access the code for the widget anywhere in the world.

Creating an instruction for installation

Having a straightforward method for installing your widgets is one of the things that need to be covered when building one. There are many methods or processes for installing widgets on websites. It all depends on who the widget is being created for. For example, if the users of the widget are developers, then they might need to install an SDK or a library for them to get started with it. However, the majority of widgets are created for non-technical users, hence, one of the most popular methods is creating a copy-paste code block that they can copy and paste into their sites instantly.

One other advantage of using a CDN is Minification. This helps us minify our code and remove all unnecessary space and characters in it thereby reducing the overall size and improving its load time. JsDelivr helps with this if the .min suffix is added to the URL.

In our case, we can create a script tag that can be copied by anyone interested in making use of our Messaging Widget. It would look like this:

// Copy the code below and paste it into the body of your website.
<script type="module" src="https://cdn.jsdelivr.net/gh/Young-Einstein10/messaging-widget@main/main.min.js"></script>

Conclusion

In this article, we’ve learned how to build a simple messaging widget with HTML, CSS, and Javascript. We also talked about CDNs and how they can be used to make our code available to everyone across the world.

I hope you’ve found this tutorial helpful. Feel free to reach out to me via LinkedIn or Twitter if there are any questions.

You can find the complete code for the tutorial on GitHub.

Resources