Допустим, хотим мы добавить в формы нашего фронт-энда такой популярный плагин как 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>
в коде наших форм можно будет не менять.