Студия разработки сайтов и приложений

Netspark.ru

Vue.js и инкапсуляция компонентов

Допустим, хотим мы добавить в формы нашего фронт-энда такой популярный плагин как datetimepicker. То есть красивый элемент для выбора даты и времени.

У Vue.js есть для этого несколько разных библиотек с компонентами, вот одна из них: Vue Bootstrap Datetimepicker. Данная библиотека при подключении позволяет использовать компонент <date-picker>. Компонент достаточно гибкий, с разнообразными настройками, можно выбирать дату, можно дату и время, поддерживаются форматы, используется moment.js, всё хорошо.

Что нужно, чтобы его подключить. Сначала — добавим импорт компонента и стилей, например так:

import datePicker from 'vue-bootstrap-datetimepicker';
import 'pc-bootstrap4-datetimepicker/build/css/bootstrap-datetimepicker.css';
Vue.use(datePicker);

Теперь для вставки компонента нужно написать:

<date-picker v-model="myDate" :config="options"></date-picker>

где options — это объект с конфигурацией компонента. Причем, инлайнить его прямо внутрь пропа :config — нельзя, иначе плагин будет дико тормозить (см. тут). То есть конфиг нужно определять где-то в коде. Вот пример такого конфига:

options: {
    format: 'DD.MM.YYYY HH:mm',
    icons: {
        time: 'far fa-clock',
        date: 'far fa-calendar',
        up: 'fas fa-arrow-up',
        down: 'fas fa-arrow-down',
        previous: 'fas fa-chevron-left',
        next: 'fas fa-chevron-right',
        today: 'fas fa-calendar-check',
        clear: 'far fa-trash-alt',
        close: 'far fa-times-circle'
    },
    showClear: true,
    showClose: true
}

Часть с иконками нужна, потому что компонент универсальный и не знает, какой иконический шрифт у нас установлен. Поэтому нужно прописывать классы вручную.

Соответственно, в каждую форму, в которую мы хотим добавить дейтпикер, нам придется притащить строчки импорта и здоровенное описание конфигурации. То есть засорить описание формы ненужными (для понимания её работы) деталями.

Со строками импорта понятно — мы можем добавить эти строки глобально в app.js один раз. Но засорять файл, пихая туда все подряд, как-то не хочется. Он и так засорится, можно этому хотя бы осознанно не способствовать.

Но и таскать здоровенный конфиг (а возможных настроек там гораздо больше) из формы в форму — не хочется. Тем более, в общем случае мы скорее всего захотим, чтобы большинство наших дейтпикеров выглядело одинаково. То есть и настройки у них будут одинаковые.

К счастью, именно эта проблема во Vue.js не проблема вообще, потому что во Вью есть однофайловые компоненты. Мы просто пишем компонент-обертку, и всю повторяющуюся логику инкапсулируем в нём.

Создадим компонент DatePick.vue. В первом приближении HTML будет вот такой:

<template>
    <date-picker :config="options" v-model="localValue"></date-picker>
</template>

В options мы передаем весь конфиг для дейт-пикера, а localValue — это модель данных, изменяемых компонентом. Скриптовая часть будет для начала такая:

<script>
    import moment from 'moment';
    import datePicker from 'vue-bootstrap-datetimepicker';
    import 'pc-bootstrap4-datetimepicker/build/css/bootstrap-datetimepicker.css';

    export default {
        data() {
            return {
                // внутренняя переменная для изменения даты через компонент
                localValue: null,
                // объект настроек компонента
                options: {
                    format: 'DD.MM.YYYY HH:mm',
                    icons: {
                        time: 'far fa-clock',
                        date: 'far fa-calendar',
                        up: 'fas fa-arrow-up',
                        down: 'fas fa-arrow-down',
                        previous: 'fas fa-chevron-left',
                        next: 'fas fa-chevron-right',
                        today: 'fas fa-calendar-check',
                        clear: 'far fa-trash-alt',
                        close: 'far fa-times-circle'
                    }
                }
            }
        },

        props: {
            // проп для передачи значения внутрь нашего компонента-обертки
            value: String
        },

        mounted: function () {
            // при установке компонента скопируем 
            // переданное значение в локальную переменную
            this.localValue = this.value;
        },

        watch: {
            // если локальное значение было изменено
            // генерируется событие input и передается изменение
            // (так реализуется поддержка v-model нашим компонентом)
            localValue: function () {
                this.$emit("input", this.localValue);
            },
            // если проп value изменился, обновляем локальную переменную
            value: function () {
                this.localValue = this.value;
            }
        },

        // объявляем импортированный компонент datePicker
        components: {
            datePicker
        }
    };
</script>    

Теперь нам достаточно подключить данный компонент к нужной форме и просто написать в HTML формы:

<date-pick v-model="myDate"></date-pick>

И дейтпикер будет работать со всеми настройками, скрытыми в компоненте-обертке. Код формы стал гораздо лаконичнее.

Более того, теперь мы можем взаимодействовать с библиотечным компонентом, немного расширяя наш компонент-обертку. Допустим, мы хотим чтобы в каких-то случаях компонент позволял выбирать только дату, а в каких-то других — только время. В дейтпикере это решается изменением формата даты:

data() {
    return {
        localValue: null,
        options: {
            // по умолчанию оставим только дату
            format: 'DD.MM.YYYY',
            // ...
        }
    }
},

props: {
    value: String,
    // добавим логический проп time
    time: Boolean
},

mounted: function () {
    // если проп установлен, добавим к формату время
    if (this.time) {
        this.options.format += ' HH:mm';
    }
    this.localValue = this.value;
},

Теперь если мы напишем:

<date-pick time v-model="myDate"></date-pick>

у компонента появится селектор времени, а если не добавим time — селектора не будет.

Если мы хотим, чтобы справа от поля ввода была кликабельная иконка календаря, немного изменим HTML компонента:

<template>
    <div class="input-group date">
        <date-picker :wrap="true" :config="options" v-model="localValue"></date-picker>
        <div class="input-group-addon">
            <span class="fa fa-calendar"></span>
        </div>
    </div>
</template>

А в options добавим allowInputToggle: true, чтобы по клику тоже появлялся поп-ап.

Наконец, добавим свойства showClear и showClose чтобы были кнопки очистки даты и закрытия календаря. И установим русскую локаль (для примера, а вообще можно и локаль сделать пропом). Финальный код компонента-обертки будет выглядеть так:

<template>
    <div class="input-group date">
        <date-picker :wrap="true" :config="options" v-model="localValue"></date-picker>
        <div class="input-group-addon">
            <span class="fa fa-calendar"></span>
        </div>
    </div>
</template>

<script>
    import moment from 'moment';
    import datePicker from 'vue-bootstrap-datetimepicker';
    import 'pc-bootstrap4-datetimepicker/build/css/bootstrap-datetimepicker.css';

    export default {
        data() {
            return {
                localValue: null,
                options: {
                    format: 'DD.MM.YYYY',
                    icons: {
                        time: 'far fa-clock',
                        date: 'far fa-calendar',
                        up: 'fas fa-arrow-up',
                        down: 'fas fa-arrow-down',
                        previous: 'fas fa-chevron-left',
                        next: 'fas fa-chevron-right',
                        today: 'fas fa-calendar-check',
                        clear: 'far fa-trash-alt',
                        close: 'far fa-times-circle'
                    },
                    showClear: true,
                    showClose: true,
                    allowInputToggle: true,
                    locale: 'ru'
                }
            }
        },

        props: {
            value: String,
            time: Boolean
        },

        mounted: function () {
            if (this.time) {
                this.options.format += ' HH:mm';
            }
            this.localValue = this.value;
        },

        watch: {
            localValue: function () {
                this.$emit("input", this.localValue);
            },
            value: function () {
                this.localValue = this.value;
            }
        },

        components: {
            datePicker
        }
    };
</script>

Кстати, если мы внезапно захотим использовать другой компонент для дейт-пикера, нам нужно будет лишь заменить класс-обертку, а вставки компонентов <date-pick> в коде наших форм можно будет не менять.

Комментарии