Skip to content
Snippets Groups Projects
StudipDialog.vue 8.99 KiB
Newer Older
<template>
    <MountingPortal mountTo="body" append>
        <focus-trap v-model="trap" :initial-focus="() => $refs.buttonB">
            <div class="studip-dialog" @keydown.esc="closeDialog">
                <transition name="dialog-fade">
                    <div class="studip-dialog-backdrop">
                        <vue-resizeable
                            class="resizable"
                            style="position: absolute"
                            ref="resizableComponent"
                            :dragSelector="dragSelector"
                            :active="handlers"
                            :fit-parent="fit"
                            :left="left"
                            :top="top"
                            :width="currentWidth"
                            :height="currentHeight"
                            :min-width="minW | checkEmpty"
                            :min-height="minH | checkEmpty"
                            @mount="initSize"
                            @resize:move="resizeHandler"
                            @resize:start="resizeHandler"
                            @resize:end="resizeHandler"
                            @drag:move="resizeHandler"
                            @drag:start="resizeHandler"
                            @drag:end="resizeHandler"
                        >
                            <div
                                :style="{ width: dialogWidth, height: dialogHeight, top: top, left: left }"
                                :class="{ 'studip-dialog-warning': question, 'studip-dialog-alert': alert }"
                                class="studip-dialog-body"
                                role="dialog"
                                :aria-modal="'true'"
                                :aria-labelledby="dialogTitleId"
                                :aria-describedby="dialogDescId"
                                ref="dialog"
                            >
                                <header
                                    class="studip-dialog-header"
                                >
                                    <span :id="dialogTitleId" class="studip-dialog-title" :title="dialogTitle">
                                        {{ dialogTitle }}
                                    </span>
                                    <slot name="dialogHeader"></slot>
                                    <span
                                        :aria-label="$gettext('Diesen Dialog schließen')"
                                        class="studip-dialog-close-button"
                                        :title="$gettext('Schließen')"
                                        @click="closeDialog"
                                    >
                                    </span>
                                </header>
                                <section
                                    :id="dialogDescId"
                                    :style="{ height: contentHeight }"
                                    class="studip-dialog-content"
                                >
                                    <slot name="dialogContent"></slot>
                                    <div v-if="message">{{ message }}</div>
                                    <div v-if="question">{{ question }}</div>
                                    <div v-if="alert">{{ alert }}</div>
                                </section>
                                <footer class="studip-dialog-footer" ref="footer">
                                    <button
                                        v-if="buttonA"
                                        :title="buttonA.text"
                                        :class="[buttonA.class]"
                                        class="button"
                                        type="button"
                                        @click="confirmDialog"
                                    >
                                        {{ buttonA.text }}
                                    </button>
                                    <slot name="dialogButtons"></slot>
                                    <button
                                        v-if="buttonB"
                                        :title="buttonB.text"
                                        :class="[buttonB.class]"
                                        class="button"
                                        type="button"
                                        ref="buttonB"
                                        @click="closeDialog"
                                    >
                                        {{ buttonB.text }}
                                    </button>
                                </footer>
                            </div>
                        </vue-resizeable>
                    </div>
                </transition>
            </div>
        </focus-trap>
    </MountingPortal>
</template>

<script>
import { FocusTrap } from 'focus-trap-vue';
import VueResizeable from 'vrp-vue-resizable';
let uuid = 0;
Ron Lucke's avatar
Ron Lucke committed
const dialogPadding = 3;

export default {
    name: 'studip-dialog',
    components: {
        FocusTrap,
        VueResizeable,
    },
    props: {
        height: {type: String, default: '300'},
        width: {type: String, default: '450'},
        title: String,
        confirmText: String,
        closeText: String,
        confirmClass: String,
        closeClass: String,
        question: String,
        alert: String,
        message: String,
    },
    data() {
        const dialogId = uuid++;

        return {
            trap: true,
            dialogTitleId: `studip-dialog-title-${dialogId}`,
            dialogDescId: `studip-dialog-desc-${dialogId}`,

            currentWidth: 450,
            currentHeight: 300,
            minW: 100,
            minH: 100,
            left: 0,
            top: 0,
            dragSelector: ".studip-dialog-header",
            handlers: ["r", "rb", "b", "lb", "l", "lt", "t", "rt"],
            fit: false,
            footerHeight: 68,
        };
    },
    computed: {
        buttonA() {
            let button = false;
            if (this.message) {
                return false;
            }
            if (this.question || this.alert) {
                button = {};
                button.text = this.$gettext('Ja');
                button.class = 'accept';
            }
            if (this.confirmText) {
                button = {};
                button.text = this.confirmText;
                button.class = this.confirmClass;
            }

            return button;
        },
        buttonB() {
            let button = false;
            if (this.message) {
                button = {};
                button.text = this.$gettext('Ok');
                button.class = '';
            }
            if (this.question || this.alert) {
                button = {};
                button.text = this.$gettext('Nein');
                button.class = 'cancel';
            }
            if (this.closeText) {
                button = {};
                button.text = this.closeText;
                if (this.closeClass) {
                    button.class = this.closeClass;
                } else {
                    button.class = 'cancel';
                }
            }

            return button;
        },
        dialogTitle() {
            if (this.title) {
                return this.title;
            }
            if (this.alert || this.question) {
                return this.$gettext('Bitte bestätigen Sie die Aktion');
            }
            if (this.message) {
                return this.$gettext('Information');
            }
        },
        dialogWidth() {
Ron Lucke's avatar
Ron Lucke committed
            return this.currentWidth ? (this.currentWidth - dialogPadding * 4) + 'px' : 'unset';
Ron Lucke's avatar
Ron Lucke committed
            return this.currentHeight ? (this.currentHeight - dialogPadding * 4) + 'px' : 'unset';
        },
        contentHeight() {
            return this.currentHeight ? this.currentHeight - this.footerHeight + 'px' : 'unset';
        }
    },
    methods: {
        closeDialog() {
            this.$emit('close');
        },
        confirmDialog() {
            this.$emit('confirm');
        },
        initSize() {
            this.currentWidth = parseInt(this.width, 10);
            this.currentHeight = parseInt(this.height, 10);
            if (window.outerWidth > this.currentWidth) {
                this.left = (window.outerWidth - this.currentWidth) / 2;
            } else {
                this.left = 5;
                this.currentWidth = window.outerWidth - 16;
            }
            
            this.top = (window.outerHeight - this.currentHeight) / 2;
            this.footerHeight = this.$refs.footer.offsetHeight;
        },
        resizeHandler(data) {
            this.currentWidth = data.width;
            this.currentHeight = data.height;
            this.left = data.left;
            this.top = data.top;
        },
    },
    filters: {
        checkEmpty(value) {
            return typeof value !== "number" ? 0 : value;
        }
    },
};
</script>