Поле ввода тегов

На проектах встречается задача сделать поле для ввода тегов. Рассмотрим, как можно решить эту задачу, используя компонент 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>

Давайте рассмотрим получившийся компонент в действии. Для удобства под полем ввода отображается отформатированное значение. В качестве примера существующих тегов используются названия стран.

Assigned Tags:

Также, попробуйте скопировать строку в буфер обмена и вставить ее в поле ввода.