naoki1061
3/30/2017 - 5:16 PM

Dialog vue component that prevents from focusing behind itself.

Dialog vue component that prevents from focusing behind itself.

<template id="dialog">
    <transition name="modal">
        <div class="modal-mask" v-show="show">
            <div class="modal-wrapper">
                <div class="modal-container">

                    <div class="modal-header">
                        <p class="title">{{ title }}</p>
                    </div>

                    <div class="modal-body">
                        <p>{{ message }}</p>
                    </div>

                    <div class="modal-footer">
                        <button class="dialog-button" @click="ok"     v-show="okCallback">OK</button>
                        <button class="dialog-button" @click="cancel" v-show="cancelCallback">Cancel</button>
                    </div>
                </div>
            </div>
        </div>
    </transition>
</template>

<script>
import Vue from 'vue'

export default {
    name: 'dialog',
    data () {
        return {
            show    : false,
            title   : '',
            message : '',
            isFirst : true,
            okCallback     : false,
            cancelCallback : false,
        }
    },
    created: function () {
        global.eventHub.$on('dialog', this.showDialog)
    },
    methods: {
        showDialog: function (params) {
            this.show           = true
            this.title          = params.title
            this.message        = params.message
            this.okCallback     = params.ok
            this.cancelCallback = params.cancel

            if (params.dismiss) {
                setTimeout( () => {
                    this.show = false
                }, params.dismiss)
            }
        },
        ok: function() {
            if (typeof this.okCallback == "function") this.okCallback()
            this.show = false
        },
        cancel: function() {
            if (typeof this.cancelCallback == "function") this.cancelCallback()
            this.show = false
        }
    },
    watch: {
        show: function (show) {
            let [ok_button, cancel_button] = document.querySelectorAll('.dialog-button')

            if (show) {
                if (this.okCallback || this.cancelCallback) {
                    // focus the first button
                    let first_button = (this.cancelCallback) ? cancel_button : ok_button
                    Vue.nextTick(() => first_button.focus())
                } else if ("activeElement" in document) {
                    // nothing to focus
                    document.activeElement.blur()
                }
            }

            // add keydown event listener when the first showing
            if (this.isFirst && show) {
                ok_button.addEventListener('keydown', (e) => {
                    if (e.code == "Tab") {
                        cancel_button.focus()
                        e.preventDefault()
                    }
                })
                cancel_button.addEventListener('keydown', (e) => {
                    if (e.code == "Tab") {
                        ok_button.focus()
                        e.preventDefault()
                    }
                })
                this.isFirst = false
            }
        }
    }
}
</script>

<style scoped>
.modal-mask {
    position: fixed;
    z-index: 9998;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, .5);
    display: table;
    transition: opacity .3s ease;
}

.modal-wrapper {
    display: table-cell;
    vertical-align: middle;
}

.modal-container {
    max-width: 400px;
    width: 40%;
    margin: 0px auto;
    padding: 20px 30px;
    background-color: #fff;
    border-radius: 2px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, .33);
    transition: all .3s ease;
    font-family: Helvetica, Arial, sans-serif;
}

.modal-header .title {
    margin-top: 0;
    color: #42b983;
    font-size: 17px;
}

.modal-body {
    margin: 20px 0;
    font-size: 13px;
}

.modal-footer {
    overflow:auto;
    padding: 2px;
}

.dialog-button {
    float: right;
    width: 60px;
    height: 40px;
    margin-left: 10px;
    background-color: white;
    border: 1px solid #ddd;
}

.dialog-button:after {
    clear: right;
}
.modal-enter {
    opacity: 0;
}

.modal-leave-active {
    opacity: 0;
}

.modal-enter .modal-container,
.modal-leave-active .modal-container {
    -webkit-transform: scale(1.1);
    transform: scale(1.1);
}
</style>