Ever tried using window.confirm()
before? It's a remarkable method that is very handy whenever you want your users to really be sure of what they're doing. However, have you tried styling it? Just like with window.alert()
it's impossible, so we'll need to create our own Confirm Modal. I'll show you how!
What to solve
First, it's useful to map out what we're trying to solve. It's important that our modal can do three things:
- Ask the user the question they should answer (e. g. 'Do you really want to delete your user account?')
- Let the user say 'Yes'
- Let the user say 'No'
Also, for developers, window.confirm()
is so easy to use. We don't want to make it much harder for the developers using our custom confirm than it is to do const theyAreSure = window.confirm('Are you sure');
.
Another thing the native modal comes with is the modal itself. We don't want the devs using our component to create a lot of markup every time they need to ask their users to confirm something, which means our custom modal needs to produce this markup automatically.
Ultimately, it should
- Be easy to use
- Not run any code before the user says 'yes'
How to solve it
Markup
For the sake of this tutorial, it's not too important to specify a convoluted markup, so let's just use this simple code as our HTML base:
<dialog class="confirm-dialog">
<div class="confirm-dialog-question">Do you really want to delete your user account?</div>
<div class="confirm-dialog-button-group">
<button class="confirm-dialog-button confirm-dialog-button--false" type="button">Noo</button>
<button class="confirm-dialog-button confirm-dialog-button--true" type="button">Yes!</button>
</div>
</dialog>
If you're unfamiliar with the <dialog>
element, go check out MDN's documentation about it! As a short introduction, it's a native element supported by Chrome, Firefox and Opera (there is a polyfill as well) which you can use to show a modal with the showModal()
method as such:
function createDialog() {
const dialog = document.createElement('dialog');
dialog.textContent = '✨✨✨';
document.body.appendChild(dialog);
dialog.showModal();
}
JavaScript API
By making use of the Promise API together with async
/await
, we can solve two of the things we listed earlier: We can make the code easy to use and we can wait for a signal for when (or if) to actually run the code that deletes every user in the db.
Ultimately, we would want the use of our component to look something like this:
async function deleteUsers() {
const dialog = new ConfirmModal({
questionText: 'Are you sure you want to delete every user?'
});
const deleteEveryUser = await dialog.confirm();
if (deleteEveryUser) {
// ...
}
}
This makes for a easy to use component, but would this work?
JavaScript's await
stops code execution until the Promise that it's waiting for has been either resolved or rejected. The Promise can be resolved by a function triggered by an Event and this is how we'll structure our code. When creating a new Promise, we will add an event listener to the two buttons and, depending on which of the buttons is clicked, resolve the Promise to either true
or false
- whether or not the user confirms.
Solving it
Let's start by creating a ConfirmDialog
class for our component. Its constructor will need three things:
- The question text
- The 'Yes' button's text
- The 'No' button's text
class ConfirmDialog {
constructor({
questionText,
trueButtonText,
falseButtonText
}) {
this.questionText = questionText || 'Are you sure?';
this.trueButtonText = trueButtonText || 'Yes';
this.falseButtonText = falseButtonText || 'No';
this.dialog = undefined;
this.trueButton = undefined;
this.falseButton = undefined;
this.parent = document.body;
this._createDialog();
this._appendDialog();
}
}
I've created one method which creates the <dialog>
element and its children, one which appends it to the <body>
, and one which removes it from the body and then deletes our ConfirmDialog
object. They look as such:
_createDialog() {
this.dialog = document.createElement("dialog");
this.dialog.classList.add("confirm-dialog");
const question = document.createElement("div");
question.textContent = this.questionText;
question.classList.add("confirm-dialog-question");
this.dialog.appendChild(question);
const buttonGroup = document.createElement("div");
buttonGroup.classList.add("confirm-dialog-button-group");
this.dialog.appendChild(buttonGroup);
this.falseButton = document.createElement("button");
this.falseButton.classList.add(
"confirm-dialog-button",
"confirm-dialog-button--false"
);
this.falseButton.type = "button";
this.falseButton.textContent = this.falseButtonText;
buttonGroup.appendChild(this.falseButton);
this.trueButton = document.createElement("button");
this.trueButton.classList.add(
"confirm-dialog-button",
"confirm-dialog-button--true"
);
this.trueButton.type = "button";
this.trueButton.textContent = this.trueButtonText;
buttonGroup.appendChild(this.trueButton);
}
_appendDialog() {
this.parent.appendChild(this.dialog);
}
_destroy() {
this.parent.removeChild(this.dialog);
delete this;
}
Now, for the final part. Let's create the confirm()
method. Inside it we need to show the modal, and to create event listeners for the two yes/no buttons and make them resolve to either true
or false
and then remove every trace of the component itself.
confirm() {
return new Promise((resolve, reject) => {
const somethingWentWrongUponCreation =
!this.dialog || !this.trueButton || !this.falseButton;
if (somethingWentWrongUponCreation) {
reject("Something went wrong upon modal creation");
}
this.dialog.showModal();
this.trueButton.addEventListener("click", () => {
resolve(true);
this._destroy();
});
this.falseButton.addEventListener("click", () => {
resolve(false);
this._destroy();
});
});
}
Nice! I've tested it here:
Top comments (8)
Dialog seems awesome! I will certainly be keeping my eyes on this feature as it progresses.
For now, at least according to Can I Use..., it seems like support is low. It actually looks like Firefox only supports it after some config changes. MDN states this as well.
Hopefully this will be ready for prime-time soon! The ::backdrop pseudo-element is equally exciting to me as well!
For now, we'll just have to use the polyfill ☀️
Best thing, I have till now. I have been searching Custom Confirm Dialog for a while. Found this. Implemented this and smiled. Thanks for the awesomeness.
Thanks you ^
Dialog is a wonderful element but sadly it is not supported by Edge, IE and Safari on all platforms.
Thanks for the tutorial though! VanillaJS is the way go.
Thanks! The polyfill I linked is supported by IE 11 (possibly lower as well) and implements the
showModal()
method we're using.Some comments may only be visible to logged-in visitors. Sign in to view all comments.