How to Create a Confirmation Dialogue in Vue.js

How to Create a Confirmation Dialogue in Vue.js

Introduction

A confirmation dialogue is a UI pattern where the user will be given a choice to continue with their action or to cancel it. It is commonly used with destructive or irreversible actions, to make sure that the user would indeed want to proceed.

In this article,we'll be implementing a reusable and modular confirmation dialogue in Vue.js.

Creating a Reusable Popup Component

Let's start by creating a reusable base component for any kind of popup components. That way we don't have to re-implement the popup mechanics multiple times. This can later be reused to create anything from an alert box to a newsletter popup.

Let's start off with the template:

<!-- components/PopupModal.vue -->

<template>
    <transition name="fade">
        <div class="popup-modal" v-if="isVisible">
            <div class="window">
                <slot></slot>
            </div>
        </div>
    </transition>
</template>

Notice we added an empty <slot></slot> tag to the template. This tag allows us to insert any content into the PopupModal element at the <slot></slot> tag. To read more about how slots works, see the Vue guide on slots.

We've also added the <transition name="fade"> tag to the template. We'll use this in the next section to animate a fade-in/fade-out effect on the dialogue.

Then, we'll add the data(), open() and close() event functions:

<!-- components/PopupModal.vue -->

<script>
export default {
    name: 'PopupModal',

    data: () => ({
        isVisible: false,
    }),

    methods: {
        open() {
            this.isVisible = true
        },

        close() {
            this.isVisible = false
        },
    },
}
</script>

And finally, let's add some style to it:

<!-- components/PopupModal.vue -->

<style scoped>
/* css class for the transition */
.fade-enter-active,
.fade-leave-active {
    transition: opacity 0.3s;
}
.fade-enter,
.fade-leave-to {
    opacity: 0;
}

.popup-modal {
    background-color: rgba(0, 0, 0, 0.5);
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    padding: 0.5rem;
    display: flex;
    align-items: center;
    z-index: 1;
}

.window {
    background: #fff;
    border-radius: 5px;
    box-shadow: 2px 4px 8px rgba(0, 0, 0, 0.2);
    max-width: 480px;
    margin-left: auto;
    margin-right: auto;
    padding: 1rem;
}
</style>

Confirmation Dialogue Animation

In the template tag you will see a transition tag <transition name="fade">. This is used to animate simple enter/leave states. Anything inside this tag will animate if it was added or remove from the tag.

We are using a conditional v-if="isVisible" to hide and show the popup. You can read more about it at the Vue guide on transitions.

To specify how the content transitions, we've called our animation fade. To implement this transition in CSS, we'll add classes prefixed with the name fade, matching our name attribute of the <transition> tag.

All it does is animate the opacity when the popup closes and opens:

.fade-enter-active,
.fade-leave-active {
    transition: opacity 0.3s;
}
.fade-enter,
.fade-leave-to {
    opacity: 0;
}

Inheriting the Popup Component

To create our confirmation dialogue, we will inherit the PopupModal by composition, and customize the reusable modal window to become a confirmation dialogue.

Let's create a new file, components/ConfirmDialogue.vue and define a template inside of it:

<!-- components/ConfirmDialogue.vue -->

<template>
    <popup-modal ref="popup">
        <h2 style="margin-top: 0">{{ title }}</h2>
        <p>{{ message }}</p>
        <div class="btns">
            <button class="cancel-btn" @click="_cancel">{{ cancelButton }}</button>
            <span class="ok-btn" @click="_confirm">{{ okButton }}</span>
        </div>
    </popup-modal>
</template>

Because we defined the <slot></slot> tag in the popup-modal component, everything that we put between its component tags (<popup-modal></popup-modal>) will be rendered between its <slot> tags instead.

We also added a ref="popup" to the popup-modal tag. By setting that attribute, we can now access the popup-modal instance with this.$refs.popup. We'll use that reference to call open() and close() on the popup modal.

Then, let's implement the methods from the parent component:

<!-- components/ConfirmDialogue.vue -->

<script>
import PopupModal from './PopupModal.vue'

export default {
    name: 'ConfirmDialogue',

    components: { PopupModal },

    data: () => ({
        // Parameters that change depending on the type of dialogue
        title: undefined,
        message: undefined, // Main text content
        okButton: undefined, // Text for confirm button; leave it empty because we don't know what we're using it for
        cancelButton: 'Go Back', // text for cancel button
        
        // Private variables
        resolvePromise: undefined,
        rejectPromise: undefined,
    }),

    methods: {
        show(opts = {}) {
            this.title = opts.title
            this.message = opts.message
            this.okButton = opts.okButton
            if (opts.cancelButton) {
                this.cancelButton = opts.cancelButton
            }
            // Once we set our config, we tell the popup modal to open
            this.$refs.popup.open()
            // Return promise so the caller can get results
            return new Promise((resolve, reject) => {
                this.resolvePromise = resolve
                this.rejectPromise = reject
            })
        },

        _confirm() {
            this.$refs.popup.close()
            this.resolvePromise(true)
        },

        _cancel() {
            this.$refs.popup.close()
            this.resolvePromise(false)
            // Or you can throw an error
            // this.rejectPromise(new Error('User cancelled the dialogue'))
        },
    },
}
</script>

Finally, let's add some style to it to make it look a bit nicer:

<!-- components/ConfirmDialogue.vue -->

<style scoped>
.btns {
    display: flex;
    flex-direction: row;
    justify-content: space-between;
}

.ok-btn {
    color: red;
    text-decoration: underline;
    line-height: 2.5rem;
    cursor: pointer;
}

.cancel-btn {
    padding: 0.5em 1em;
    background-color: #d5eae7;
    color: #35907f;
    border: 2px solid #0ec5a4;
    border-radius: 5px;
    font-weight: bold;
    font-size: 16px;
    text-transform: uppercase;
    cursor: pointer;
}
</style>

Using the Confirmation Dialogue

To use the confirmation dialogue, you need to include only the components/ConfirmDialogue.vue component. For example, let's make a page with a 'Delete' button that makes sure if you really want to delete another page:

<template>
    <div>
        <h1>Delete Page</h1>
        <button class="delete-btn" @click="doDelete">Delete Page</button>
        <confirm-dialogue ref="confirmDialogue"></confirm-dialogue>
    </div>
</template>

<script>
import ConfirmDialogue from '../components/ConfirmDialogue.vue'

export default {
    components: { ConfirmDialogue },
    methods: {
        async doDelete() {
            const ok = await this.$refs.confirmDialogue.show({
                title: 'Delete Page',
                message: 'Are you sure you want to delete this page? It cannot be undone.',
                okButton: 'Delete Forever',
            })
            // If you throw an error, the method will terminate here unless you surround it wil try/catch
            if (ok) {
                alert('You have successfully delete this page.')
            } else {
                alert('You chose not to delete this page. Doing nothing now.')
            }
        },
    },
}
</script>

<style scoped>
.delete-btn {
    padding: 0.5em 1em;
    background-color: #eccfc9;
    color: #c5391a;
    border: 2px solid #ea3f1b;
    border-radius: 5px;
    font-weight: bold;
    font-size: 16px;
    text-transform: uppercase;
    cursor: pointer;
}
</style>

Free eBook: Git Essentials

Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!

Since we are using await in our method to get the result of the confirmation dialogue, we need to add async to our method definition.

Alternatively, you may prefer the promise style approach:

this.$refs.confirmDialogue.show({
    title: 'Delete Page',
    message: 'Are you sure you want to delete this page? It cannot be undone.',
    okButton: 'Delete Forever',
}).then((result) => {
    if (ok) {
        alert('You have successfully delete this page.')
    } else {
        alert('You chose not to delete this page. Doing nothing now.')
    }
})

To see why we've suggested throwing an error if the user cancels the confirmation dialogue, see how fluent the following code is:

await this.$refs.confirmDialogue.show({
    title: 'Delete Page',
    message: 'Are you sure you want to delete this page? It cannot be undone.',
    okButton: 'Delete Forever',
})
alert('Deleting this page.')

Since cancellation requires no action, there is simply no need to handle that state at all. And if you decide to handle a cancel request, simply wrap that code with a try/catch.

Conclusion

In this article, we've defined a reusable modal popup component in Vue.js, and inherited it to implement a confirmation dialogue. Then, we've added animations to it for aesthetic purposes and ran a couple of examples of how to use the component to prompt users for input.

Last Updated: December 23rd, 2020
Was this article helpful?

Improve your dev skills!

Get tutorials, guides, and dev jobs in your inbox.

No spam ever. Unsubscribe at any time. Read our Privacy Policy.

Want a remote job?

    Prepping for an interview?

    • Improve your skills by solving one coding problem every day
    • Get the solutions the next morning via email
    • Practice on actual problems asked by top companies, like:
     
     
     

    Getting Started with AWS in Node.js

    Build the foundation you'll need to provision, deploy, and run Node.js applications in the AWS cloud. Learn Lambda, EC2, S3, SQS, and more!

    © 2013-2021 Stack Abuse. All rights reserved.