cattr-app/frontend-application

View on GitHub
app/core/components/MultiSelect.vue

Summary

Maintainability
Test Coverage
<template>
    <div class="at-select-wrapper">
        <at-select
            ref="select"
            v-model="model"
            multiple
            filterable
            placeholder=""
            :size="size"
            @click="onClick"
            @input="onChange"
        >
            <li v-if="showSelectAll" class="at-select__option" @click="selectAll()">
                {{ $t('control.select_all') }}
            </li>
            <slot name="before-options"></slot>
            <at-option
                v-for="option of options"
                :key="option.id"
                :value="option.id"
                :label="option.name"
                @on-select-close="onClose"
            >
            </at-option>
        </at-select>
        <span v-if="showCount" class="at-select__placeholder">
            {{ placeholderText }}
        </span>
        <i v-if="model.length > 0" class="icon icon-x at-select__clear" @click.stop="clearSelect"></i>
    </div>
</template>

<script>
    import ResourceService from '../services/resource.service';

    export default {
        props: {
            service: {
                type: ResourceService,
            },
            selected: {
                type: [String, Number, Array, Object],
                default: Array,
            },
            inputHandler: {
                type: Function,
            },
            prependName: {
                type: String,
                default: '',
            },
            showSelectAll: {
                type: Boolean,
                default: true,
            },
            placeholder: {
                type: String,
                required: true,
            },
            size: {
                type: String,
                default: 'normal',
            },
        },
        data() {
            return {
                model: [],
                showCount: true,
                options: [],
            };
        },
        async created() {
            try {
                const all = await this.service.getAll({ headers: { 'X-Paginate': false } });
                this.options.push(...all);
                this.$emit('onOptionsLoad', this.options);
            } catch ({ response }) {
                if (process.env.NODE_ENV === 'development') {
                    console.warn(response ? response : 'request to projects is canceled');
                }
            }

            if (Array.isArray(this.selected)) {
                this.model = this.selected;
            }

            this.$nextTick(() => {
                this.model.forEach(modelValue => {
                    if (this.$refs.select && Object.prototype.hasOwnProperty.call(this.$refs.select, '$children')) {
                        this.$refs.select.$children.forEach(option => {
                            if (option.value === modelValue) {
                                option.selected = true;
                            }
                        });
                    }
                });
            });

            this.lastQuery = '';
            this.$watch(
                () => {
                    if (this.$refs.select === undefined) {
                        return;
                    }

                    return {
                        query: this.$refs.select.query,
                        visible: this.$refs.select.visible,
                    };
                },
                ({ query, visible }) => {
                    if (visible) {
                        if (query.length) {
                            this.lastQuery = query;
                        } else {
                            if (
                                ['input', 'keypress'].includes(window?.event?.type) ||
                                window?.event?.key === 'Backspace'
                            ) {
                                // If query changed by user typing, save query
                                this.lastQuery = query;
                            } else {
                                // If query changed by clicking option and so on, restore query
                                this.$refs.select.query = this.lastQuery;
                            }
                        }
                    } else {
                        this.lastQuery = query;
                    }
                },
            );
        },
        watch: {
            model(value) {
                if (this.inputHandler) {
                    this.inputHandler(value);
                }
            },
        },
        methods: {
            selectAll(predicate = () => true) {
                console.log(this.$refs.select);
                const query = this.$refs.select.query.toUpperCase();
                this.model = this.options
                    .filter(({ name }) => name.toUpperCase().indexOf(query) !== -1)
                    .filter(predicate)
                    .map(({ id }) => id);
            },
            clearSelect() {
                this.$emit('input', []);
                this.model = [];
            },
            onClick() {
                if (this.showCount) {
                    this.showCount = false;
                } else {
                    setTimeout(() => {
                        this.showCount = true;
                    }, 300);
                }
            },
            onClose() {
                this.$refs.select.query = '';

                if (!this.showCount) {
                    setTimeout(() => {
                        this.showCount = true;
                    }, 300);
                }
            },
            onChange(val) {
                if (this.inputHandler) {
                    this.inputHandler(val);
                }
            },
        },
        computed: {
            selectionAmount() {
                return this.model.length;
            },
            allOptionsSelected() {
                return this.options.length === this.selectionAmount;
            },
            placeholderText() {
                const i18nKey = this.placeholder + (this.allOptionsSelected ? '_all' : '');
                return this.$tc(i18nKey, this.selectionAmount, {
                    count: this.selectionAmount,
                });
            },
        },
    };
</script>

<style lang="scss" scoped>
    .at-select-wrapper {
        position: relative;
    }

    .at-select {
        min-width: 240px;

        &__placeholder {
            left: 0;
            position: absolute;
            z-index: 1;
            font-size: 0.9rem;
        }

        &--small ~ &__placeholder {
            font-size: 11px;
            padding: 5px 24px 0 8px;
        }

        &__clear {
            margin-right: $spacing-05;
            display: block;
            cursor: pointer;
        }
    }

    ::v-deep {
        .at-select {
            &__placeholder {
                color: #3f536d;
                padding: 10px 12px;
            }

            &__input {
                height: 100%;
                z-index: 2;
            }

            &__selection {
                border-radius: 5px;
                color: black;
            }

            &--visible + .at-select__placeholder {
                display: none;
            }

            &__clear {
                z-index: 3;
            }

            &__arrow {
                z-index: 3;
            }
        }

        .at-tag {
            display: none;
        }
    }
</style>