PHP Profi

Мгновенный ajax-поиск на Laravel и Vue Перевод

Мгновенный поиск является довольно популярной фичей на сайтах и в приложениях. В этом посте мы постараемся охватить ключевые моменты реализации поиска в реальном времени с такими функциями, как подавление "дребезга" или подсветка результатов.

Пролог: официальный пакет для реальной работы

Если вам нужен аккуратный и хорошо поддерживаемый вариант поиска Eloquent-моделей, то Laravel предоставляет для этого официальный пакет. Laravel Scout является пакетом основанным на технике драйверов и реализующий полнотекстовый поиск по Eloquent-моделям. В настоящее время в качестве драйвера он поддерживает только Algolia, но вы можете легко использовать собственный драйвер поиска.

Приступая к работе

В результате должно получиться следующее: мы набираем что-то в поле ввода и отправляем на сервер AJAX-запрос с заданными ключевыми словами. На back-end-е мы получаем ключевые слова и достаём модели, которые соответствуют данному запросу.

Т.к. то, что мы хотим описать в статье - это не более чем демонстрация, то back-end и front-end будут достаточно простыми. Что мы действительно хотим здесь подчеркнуть - ключевые моменты и фичи, которые делают поиск более удобным.

Реализация back-end

Зачастую мы хотим реализовать более сложные вещи, чем просто фильтрация результатов только по одному параметру. Если при этом мы не хотим использовать Scout, мы можем написать наши собственные реализацию поиска по запросу. Мы не будем рассматривать такую реализацию в этом посте, мы просто хотим отметить этот способ работы с несколькими параметрами запроса, которые мы хотим применять и использовать.

Теперь давайте двигаться дальше и создадим контроллер.

// SearchController.php

public function search(Request $request)
{
    $posts = Post::where('name', $request->keywords)->get();

    return response()->json($posts);
}

Как вы видите, тут всё очень просто, но мы должны заметить две вещи:

Первая - мы возвращаем ответ в формате json, потому что мы хотим получить его на front-end-е. Это также означает, что здесь мы должны использовать API-роут, а не обычный веб-роут, но это сейчас второстепенно.

Вторая - так как мы используем $request->keywords, строка запроса должна выглядеть примерно так ?keywords=Some+search+query.

В результате, мы получаем Eloquent-коллекцию с соответствующими моделями, которую мы преобразовали в json и сделали удобной для обработки на front-end-е.

Реализация поиска с помощью Vue

Чтобы упростить задачу, у нас будет только поле ввода и список результатов. В качестве первого шага, мы создаем экземпляр Vue и привязываем модель к полю ввода. Затем мы выполняем некие действия, когда значение инпута изменилось. Давайте посмотрим, как это должно выглядеть:

<template>
    <div>
        <input type="text" v-model="keywords">
        <ul v-if="results.length > 0">
            <li v-for="result in results" :key="result.id" v-text="result.name"></li>
        </ul>
    </div>
</template>

<script>
export default {
    data() {
        return {
            keywords: null,
            results: []
        };
    },

    watch: {
        keywords(after, before) {
            this.fetch();
        }
    },

    methods: {
        fetch() {
            axios.get('/api/search', { params: { keywords: this.keywords } })
                .then(response => this.results = reponse.data)
                .catch(error => {});
        }
    }
}
</script>

Итак, что здесь происходит? В шаблонной части этого кода мы привязываем модель Vue и пробегаем по результатам. В скриптовой части мы задаём данные, которые мы хотим использовать, и определяем метод fetch(), который является оберткой для axios-запроса.

Также мы прописываем "наблюдателя" за изменениями данных. И когда в data() меняется значение свойства keywords, мы снова вызываем метод fetch() с новыми ключевыми словами и  выводим новые результаты.

В следующих шагах мы постараемся пройтись по нескольким моментам, которые сделают наш компонент немного аккуратнее.

Подавление "дребезга" для v-model

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

Первый способ - это модификатор lazy. С этим модификатором синхронизация будет происходить не по соботию input, а по событю change. Это означает, что модель будет обновляться только по событию blur или по нажатию enter. Вы можете посмотреть документацию здесь. Если вам и не нужно большего, то это простое в реализации и использовании решение.

Другой способ - реализовать debouncer нашей v-модели. На самом деле, в Vue v1 у нас было подавление дребезга, но оно было удалено в версии 2. Как описано в руководстве по миграции, мы можем использовать библиотеку debounce из lodash (_), но, на мой взгляд, если мы можем использовать наше собственное решение, это как раз тот случай, когда мы должны это сделать.

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

Чтобы заставить его работать, вы должны добавить модификатор .lazy к модели! Не забывайте это!

Итак, допустим, мы интегрировали debounce. Теперь, если мы хотим, мы можем отложить какие-либо изменения в нашей Vue-модели. Представьте, что если нет никаких изменений в заданном интервале, мы передаём последнее состояние в модель. Вызывается метод watch() и мы можем получить новые данные. Но мы делаем это только один раз, вместо 4-5 раз.

Если мы хотим добавить задержку в 300мс в нашей v-модели, мы можем сделать это так:

<input type="text" v-model.lazy="keywords" v-debounce="300">

Вот и всё! В конце мы покажем полный пример с этим решением.

Подсветка результатов

С точки зрения UX, эта часть является значительной. Если бы мы могли выделить совпадения с ключевыми словами, это был бы хороший способ помочь пользователю найти быстрее то, что он(а) хочет.

Это можно реализовать простым одностроковым методом, который находит соответствия и оборачивает их в подсвечивающую разметку. Мы можем положить его в раздел с методами и легко использовать.

highlight(text) {
    return text.replace(new RegExp(this.keywords, 'gi'), '<span class="highlighted">$&</span>');
}

Как видите, мы возвращаем HTML, а не простую строку. Это означает, что мы должны заменить v-text на v-html.

Заключение

Как мы видим, это не особо сложно написать простой AJAX-поиск по введённому тексту. Самым тяжелым, как всегда, было найти и интегрировать небольшие, но полезные фичи - такие, как подавление дребезга или подсветка. Нам кажется, что уделяя внимание таким мелочам, как эти, вы сделаете ваше приложение намного лучше и более отзывчивее, чем вы думаете.

Как и обещали, вы можете найти полностью рабочее решение (без back-end) здесь: https://jsfiddle.net/hej7L1jy/2/.

Оно немного изменено, т.к. нам нужна немного другая логика, если мы работаем со статическими данными. Мы надеемся, что вы сможете использовать любую из этих фич!

2017-11-07 оригинал

Последние посты

Комментарии

авторизуйтесь или зарегистрируйтесь, чтобы оставить комментарий