Поле ввода тегов
На проектах встречается задача сделать поле для ввода тегов. Рассмотрим, как можно решить эту задачу, используя компонент vs-vue3-select
. Приведем пример условий задачи:
- По мере ввода пользователем, поле должно отображать популярные теги, начинающиеся с введенной строки, с отображением статистики их использования.
- Поле должно поддерживать ввод произвольных тегов.
- Пользователь должен иметь возможность вставлять заранее подготовленный список тегов из буфера обмена.
- Высота поля должна регулироваться автоматически для отображения всех введенных тегов.
- Значение поля должно быть массивом строк, содержащих выбранные теги.
Для начала настроим компонент. Подробности параметров описаны в разделе параметров:
taggable
включаем возможность создания опций пользователемmultimpe
включаем режим множественного выбораfiltrable
отключаем, т.к. фильтрацией будем управлять самостоятельноselect-on-key-codes
устанавливаем равным[188,13]
, что бы выбор происходил при нажатии Enter и запятойpaste-separator
задаем значение,
чтобы при вставке из буфера строка разбивалась по этому символу
Затем передаем следующие функции:
create-option
- функцию, создающую новую опцию из строки. Так как опции от бэкенда представляют собой объекты с тегом и статистикой использования.@search"
- функцию для запроса к API с целью получения списка опций, соответствующих введенной пользователем строке. В примере функция эмулирует асинхронное обращение к серверу.reduce
- по условиям задачи, компонент должен возвращать массив простых строк, содержащих теги. Мы извлекаем только необходимую информацию из объекта, описывающего опцию.
Через слот option настраиваем отображение каждой опции, чтобы видны были теги и статистика их использования.
Кроме того, добавляем CSS-класс stretch
компоненту, чтобы обеспечить автоматическое изменение высоты поля при добавлении тегов.
<script setup>
import {computed, ref} from "vue";
import tags from '../data/tags';
const getTags = async function (search) {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve(tags.filter(tag => ~tag.label.indexOf(search))), 100)
});
options.value = await promise;
}
const onSearch = search => {
if (search.length) {
getTags(search);
}
}
const displayUses = value => value < 1000 ? value : (Math.ceil(value / 100) / 10) + 'K';
const options = ref([]);
const value = ref([]);
const demoTags = computed(() => {
return value.value.join(', ')
})
const createTag = value => {
return {label: value, uses: 0}
}
</script>
<template>
<div class="custom-result">Assigned Tags: {{ demoTags }}</div>
<v-select
v-model="value"
:options="tags"
taggable
multiple
:filtrable="false"
:select-on-key-codes="[188, 13]"
paste-separator=","
:create-option="createTag"
@search="onSearch"
:reduce="option => option.label"
class="stretch"
>
<template #option="{label, uses}">
<span class="custom-option__label">{{ label }}</span>
<span class="custom-option__uses">{{ displayUses(uses) }} video</span>
</template>
</v-select>
</template>
<style>
.stretch .vs__selected-options {
flex-wrap: wrap;
}
.custom-result {
margin-bottom: 24px;
opacity: 0.5;
}
.custom-option__label {
display: block;
}
.custom-option__uses {
display: block;
opacity: 0.6;
font-size: 0.8em;
}
</style>
Давайте рассмотрим получившийся компонент в действии. Для удобства под полем ввода отображается отформатированное значение. В качестве примера существующих тегов используются названия стран.
Также, попробуйте скопировать строку в буфер обмена и вставить ее в поле ввода.