[Simple Web Apps] 1. Simple Toast notifications

[Simple Web Apps] 1. Simple Toast notifications

We all would have come across something like the above on our everyday websites. They are just a couple of different examples of toast notifications.

What are Toast Notifications?

Toast Notifications have taken several names and forms over the last few decades of the internet’s rise. They are also called as pop-up notifications, modal notifications, pop-over notifications, snack bar notifications, desktop notifications, notification bubbles, or simple notifications in general.

(Wiki Reference - https://en.wikipedia.org/wiki/Pop-up_notification)

Whatever the name it is called, it boils down to some form of communication with the user. Let’s look for some common scenarios where it is widely used:

  1. Linkedin - You have various features in it and one feature is to send a connection request. As of this writing on Feb 25th, 2023, LinkedIn still uses a toast notification on the bottom left to communicate to the users that a connection request has been sent.

  2. Youtube - When you add some video to “Add to Watch Later”, it shows a toast notification on the bottom left about the same.

  3. Facebook - You have multiple occurrences of toast notifications in Facebook right from posting a feed, adding a friend, …

These are just a handful of examples where you see toast notifications every day. To get more inspiration on how else they can be - https://dribbble.com/tags/toast_message.

Now that we understood what a toast notification is, let’s implement a simple toast notification using typescript.

Implementation using Typescript

let BODY: HTMLBodyElement;
let CONTAINER: HTMLDivElement;
const TOAST_CONTAINER_ID = 'toastContainerElement';
const DEFAULTS: any = {
    display: 'flex',
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    padding: '10px',
};
const addStyles = (element: HTMLElement, styles: any): void => {
    const keys = Object.keys(styles);
    for (let i = 0; i < keys.length; ++i) {
        const key: any = keys[i] as string;
        element.style[key] = styles[key];
    }
}

const getContainerElement = (): HTMLDivElement => {
    const container = document.createElement('div');
    addStyles(container, {
        position: 'fixed',
        top: 0,
        left: 0,
        right: 0,
        padding: 0,
        margin: 0,
        display: 'flex',
        flex: 1,
        flexDirection: 'column'
    })
    container.id = TOAST_CONTAINER_ID;
    return container;
}

const appendToBody = (element: HTMLElement, replace: boolean = true): void => {
    if (!BODY) {
        BODY = document.body as HTMLBodyElement;
    }
    if (!CONTAINER) {
        CONTAINER = getContainerElement()
    }
    if (replace) {
        (document.getElementById(TOAST_CONTAINER_ID) as HTMLDivElement)?.replaceChildren();
    }
    CONTAINER.appendChild(element);
    BODY.replaceChildren(CONTAINER);
}

const render = (message: string, props: { background: string, color: string }) => {
    const toast = document.createElement('div');
    toast.innerHTML = message;
    addStyles(toast, { ...DEFAULTS, ...props });
    appendToBody(toast)
}

class Toast {

    private constructor() {
        throw Error('Instatiation is not permitted')
    }

    static info(message: string): void {
        render(message, {
            background: 'grey',
            color: 'black'
        })
    }

    static error(message: string): void {
        render(message, {
            background: 'red',
            color: 'white'
        })
    }

    static success(message: string): void {
        render(message, {
            background: 'green',
            color: 'white'
        })
    }
}

export default Toast;

Implementation using JavaScript

Ideally, you can use transpilers to convert the above typescript code to javascript in your project. If you are not using typescript, here is a JavaScript implementation of the same.

let BODY;
let CONTAINER;
const TOAST_CONTAINER_ID = 'toastContainerElement';
const DEFAULTS = {
    display: 'flex',
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    padding: '10px',
};
const addStyles = (element, styles) => {
    const keys = Object.keys(styles);
    for (let i = 0; i < keys.length; ++i) {
        const key = keys[i];
        element.style[key] = styles[key];
    }
};
const getContainerElement = () => {
    const container = document.createElement('div');
    addStyles(container, {
        position: 'fixed',
        top: 0,
        left: 0,
        right: 0,
        padding: 0,
        margin: 0,
        display: 'flex',
        flex: 1,
        flexDirection: 'column'
    });
    container.id = TOAST_CONTAINER_ID;
    return container;
};
const appendToBody = (element, replace = true) => {
    var _a;
    if (!BODY) {
        BODY = document.body;
    }
    if (!CONTAINER) {
        CONTAINER = getContainerElement();
    }
    if (replace) {
        (_a = document.getElementById(TOAST_CONTAINER_ID)) === null || _a === void 0 ? void 0 : _a.replaceChildren();
    }
    CONTAINER.appendChild(element);
    BODY.replaceChildren(CONTAINER);
};
const render = (message, props) => {
    const toast = document.createElement('div');
    toast.innerHTML = message;
    addStyles(toast, Object.assign(Object.assign({}, DEFAULTS), props));
    appendToBody(toast);
};
class Toast {
    constructor() {
        throw Error('Instantiation is not permitted');
    }
    static info(message) {
        render(message, {
            background: 'grey',
            color: 'black'
        });
    }
    static error(message) {
        render(message, {
            background: 'red',
            color: 'white'
        });
    }
    static success(message) {
        render(message, {
            background: 'green',
            color: 'white'
        });
    }
}

How to use it?

Include the above toast notification file in your project and call with any of the following:

Toast.info('Any informative message to be displayed');
Toast.error('Any error message to be displayed');
Toast.success('Any success message to be displayed');

How it displays?

Explanation

We have the Toast class. It exposes 3 helper methods:

  1. static info(message: string): void

  2. static error(message: string): void

  3. static success(message: string): void

As the name indicates, each does a specific thing. In the case of info - it displays an informative message, the error displays an error message, and success displays a success message.

Also, if you notice, all the methods are declared static. This means you must call them only using className and dot notation like: Toast.info(

The Toast class’ constructor throws an Exception:

private constructor() {
    throw Error('Instantiation is not permitted')
}

💡 NOTE: This is explicitly done so that we can avoid unwanted instantiations of the class and also make the API consumption easy and simpler

Apart from the Toast class, we have a bunch of other methods that help in rendering the DOM element according to the type of message selected. I am not going into depth on them as they are not very specific to Toast notifications in particular. They deal with general DOM manipulations.

💡 NOTE: If you have noticed how it works, it essentially displays the toast notification and the next toast notification replaces the existing notification. While this is not an issue, sometimes you may want to stack all of them together rather than replacing the existing ones

appendToBody(toast); is being called in the render method. Notice that the function appendToBody takes an optional parameter called replace and it currently defaults to true

💡 Tip: For the toast notifications to be stacked, you can just update the call to something like appendToBody(toast, false); this would not replace the notifications but stack them.

💡 Tip: This Toast class uses static methods. You can alternatively modify the implementation to work based on instances. As an example, you can introduce a getInstance() method that would return you the instance of the class based on the Singleton design pattern. You can then continue with something like this: Toast.getInstance().info('message')

In the next article, we will look into how we can customize this notification feature to support a few other configurations.

If you like this article, make sure to check out my other articles here. I make sure to post a new article every day. Also, make sure to sign up for the newsletter to directly receive the new articles in your inbox.

Cheers,

Arunkumar Sri Sailapathi.

#2Articles1Week