Skip to content
Snippets Groups Projects
InputArray.vue 6.83 KiB
Newer Older
<template>
    <div class="input-array">
        <span aria-live="assertive" class="sr-only">{{ assistiveLive }}</span>
        <draggable v-model="options" handle=".dragarea" tag="ol" class="clean options">
            <li v-for="(option, index) in options" :key="index">
                <a class="dragarea"
                   v-if="options.length > 1"
                   tabindex="0"
                   :ref="'draghandle_' + index"
                   :title="$gettextInterpolate('Sortierelement für Option %{option}. Drücken Sie die Tasten Pfeil-nach-oben oder Pfeil-nach-unten, um dieses Element in der Liste zu verschieben.', {option: option})"
                   @keydown="keyHandler($event, index)">
                    <span class="drag-handle"></span>
                </a>
                <input type="text"
                       :placeholder="$gettext('Option')"
                       :ref="'option_' + index"
                       @paste="(ev) => onPaste(ev, index)"
                       v-model="options[index]">
                <button class="as-link"
                   :title="$gettext('Option löschen')"
                   @click.prevent="askForDeletingOption(index)">
David Siegfried's avatar
David Siegfried committed
                    <studip-icon shape="trash" :role="options.length > 1 ? 'clickable' : 'inactive'" :size="20" alt=""></studip-icon>
                </button>
            </li>
        </draggable>

David Siegfried's avatar
David Siegfried committed
        <button class="as-link"
                :title="$gettext('Option hinzufügen')"
                @click.prevent="addOption">
            <studip-icon shape="add" :size="20" alt=""></studip-icon>
        </button>

        <studip-dialog
            v-if="askForDeleting"
            :title="$gettext('Bitte bestätigen Sie die Aktion.')"
            :question="$gettext('Wirklich löschen?')"
            :confirmText="$gettext('Ja')"
            :closeText="$gettext('Nein')"
            closeClass="cancel"
            height="180"
            @confirm="deleteOption"
            @close="askForDeleting = false"
        >
        </studip-dialog>
    </div>
</template>

<script>
import StudipIcon from "../StudipIcon.vue";
import StudipDialog from "../StudipDialog.vue";
import draggable from 'vuedraggable';
export default {
    name: 'input-array',
    components: {
        StudipIcon,
        StudipDialog,
        draggable
    },
    props: {
        value: {
            type: Array,
            required: false
        }
    },
    data: function () {
        return {
            options: [],
            askForDeleting: false,
            indexOfDeletingOption: 0,
            unique_id: null,
            assistiveLive: ''
        };
    },
    methods: {
        addOption: function (val, position) {
            let data = this.value;
            if (val.target) {
                val = '';
            }
            if (typeof position === "undefined") {
                data.push(val || '');
                position = this.value.length - 1
            } else {
                data.splice(position, 0, val || '');
            }
            this.$emit('input', data);
            let v = this;
            this.$nextTick(function () {
                v.$refs['option_' + position][0].focus();
            });
        },
        askForDeletingOption: function (index) {
David Siegfried's avatar
David Siegfried committed
            if (this.options.length <= 1) {
                return;
            }

            this.indexOfDeletingOption = index;
            if (this.value[index]) {
                this.askForDeleting = true;
            } else {
                this.deleteOption();
            }
        },
        deleteOption: function () {
            this.$delete(this.value, this.indexOfDeletingOption);
            this.askForDeleting = false;
        },
        onPaste: function (ev, position) {
            let data = ev.clipboardData.getData("text").split("\n");
            for (let i = 0; i < data.length; i++) {
                if (data[i].trim()) {
                    this.addOption(data[i], position + i);
                }
            }
        },
        keyHandler(e, index) {
            switch (e.keyCode) {
                case 38: // up
                    e.preventDefault();
                    if (index > 0) {
                        this.moveUp(index);
                        this.$nextTick(function () {
                            this.$refs['draghandle_' + (index - 1)][0].focus();
                            this.assistiveLive = this.$gettextInterpolate(
                                'Aktuelle Position in der Liste: %{pos} von %{listLength}.'
                                , {pos: index, listLength: this.options.length}
                            );
                        });
                    }
                    break;
                case 40: // down
                    e.preventDefault();
                    if (index < this.options.length - 1) {
                        this.moveDown(index);
                        this.$nextTick(function () {
                            this.$refs['draghandle_' + (index + 1)][0].focus();
                            this.assistiveLive = this.$gettextInterpolate(
                                'Aktuelle Position in der Liste: %{pos} von %{listLength}.'
                                , {pos: index + 2, listLength: this.options.length}
                            );
                        });
                    }
                    break;
            }
        },
        moveDown: function (index) {
            if (index == this.options.length - 1) {
                return;
            }
            let option = this.options[index];
            this.options[index] = this.options[index + 1];
            this.options[index + 1] = option;
            this.$forceUpdate();
        },
        moveUp: function (index) {
            if (index === 0) {
                return;
            }
            let option = this.options[index];
            this.options[index] = this.options[index - 1];
            this.options[index - 1] = option;
            this.$forceUpdate();
        }
    },
    mounted: function () {
        this.options = this.value;
        this.unique_id = 'array_input_' + Math.floor(Math.random() * 100000000);
    },
    watch: {
        options (new_data, old_data) {
            if (typeof old_data === 'undefined' || typeof new_data === 'undefined') {
                return;
            }
            this.$emit('input', new_data);
        },
        value (new_val) {
            this.options = new_val;
        }
    }
}
</script>
David Siegfried's avatar
David Siegfried committed
<style lang="scss" scoped>
.input-array {
    display: grid;
    grid-template-areas:
        "sr sr"
        "options button";
    grid-template-columns: calc(100% - 24px) 24px;
    grid-template-rows: auto;

    > .sr-only {
        grid-area: sr;
    }

    > .options {
        grid-area: options;
    }

    > button.as-link {
        align-self: end;
        grid-area: button;
        justify-self: left;
        margin-bottom: 8px;
    }
}
</style>