Toast.js, a Library for Toast messages
Last week I created a small library for creating Toast messages. Here, I thought I would share my process in creating it and the code behind it.
The HTML #
The Toast is made up of two elements. The Toast message itself, .toastjs
, and a container element, .toastjs-container
.
<div class="toastjs-container" role="alert" aria-hidden="true">
<div class="toastjs"><!-- Content here --></div>
</div>
Until a Toast is being displayed, the container is hidden using the aria-hidden
attribute.
The CSS #
To create the sliding in/out animation of the Toast, a transform is applied to the Toast container element. By default, the .toastjs-container
is positioned off-screen to the left.
.toastjs-container {
transform: translateX(-150%);
transition: transform 1s;
/* Other styles */
position: fixed;
bottom: 30px;
left: 30px;
width: calc(100% - 60px);
max-width: 400px;
}
When the Toast message is being displayed, as is determined by it's aria-hidden
attribute, the element is positioned to a normal state.
.toastjs-container[aria-hidden="false"] {
transform: translateX(0%);
}
The JavaScript #
The Toast is a custom Toast
prototype. There are five main methods behind it.
Toast.prototype._createElements #
This function creates the Toast element and it's container, with all the appropriate attributes and classes. It also appends it to the body
element.
Toast.prototype._createElements = function() {
return new Promise((resolve, reject) => {
this.toastContainerEl = document.createElement('div');
this.toastContainerEl.classList.add('toastjs-container');
this.toastContainerEl.setAttribute('role', 'alert');
this.toastContainerEl.setAttribute('aria-hidden', true);
this.toastEl = document.createElement('div');
this.toastEl.classList.add('toastjs');
this.toastContainerEl.appendChild(this.toastEl);
document.body.appendChild(this.toastContainerEl);
setTimeout(() => resolve(), 500);
})
};
Toast.prototype._addEventListeners #
This function adds event listeners to the buttons within the toast. By default, there is at least a close button. Optionally, there are custom buttons with custom callback functions that should be applied.
Toast.prototype._addEventListeners = function() {
document.querySelector('.toastjs-btn--close').addEventListener('click', () => {
this._close();
})
if ( this.options.customButtons ) {
const customButtonsElArray = Array.prototype.slice.call( document.querySelectorAll('.toastjs-btn--custom') );
customButtonsElArray.map( (el, index) => {
el.addEventListener('click', (event) => this.options.customButtons[index].onClick(event) );
});
}
};
Toast.prototype._close #
This is the function that is called when the Toast is closed. It first sets the correct aria-hidden
attribute on the Toast container, then resets the main Toast element.
Toast.prototype._close = function() {
return new Promise((resolve, reject) => {
this.toastContainerEl.setAttribute('aria-hidden', true);
setTimeout(() => {
this.toastEl.innerHTML = '';
this.toastEl.classList.remove('default', 'success', 'warning', 'danger');
if ( this.focusedElBeforeOpen ) {
this.focusedElBeforeOpen.focus();
}
resolve();
}, 1000);
});
};
Toast.prototype._open #
This is the function that is called when the Toast is opened. It does the following things -
- Sets the correct
aria-hidden
attribute to the Toast container - Adds the correct class to the Toast element
- Adds the correct message content to the Toast element
- Creates any custom buttons if specified
Toast.prototype._open = function() {
this.toastEl.classList.add(this.options.type);
this.toastContainerEl.setAttribute('aria-hidden', false);
let customButtons = '';
if ( this.options.customButtons ) {
customButtons = this.options.customButtons.map( (customButton, index) => {
return `<button type="button" class="toastjs-btn toastjs-btn--custom">${customButton.text}</button>`
} )
customButtons = customButtons.join('');
}
this.toastEl.innerHTML = `
<p>${this.options.message}</p>
<button type="button" class="toastjs-btn toastjs-btn--close">Close</button>
${customButtons}
`;
this.focusedElBeforeOpen = document.activeElement;
document.querySelector('.toastjs-btn--close').focus();
};
Toast.prototype._init #
Finally, this is the function that initialises the Toast. It calls all the other functions in their correct sequence.
Toast.prototype._init = function() {
Promise.resolve()
.then(() => {
if ( this.toastContainerEl ) {
return Promise.resolve();
}
return this._createElements();
})
.then(() => {
if ( this.toastContainerEl.getAttribute('aria-hidden') == 'false' ) {
return this._close();
}
return Promise.resolve();
})
.then(() => {
this._open();
this._addEventListeners();
})
};
You can view the full source for the library here, or view a demo.