Livewire и Laravel. Делаем интерактивный поиск.

Интерактивный поиск с пагинацией на Livewire и Laravel

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

Интерактивный поиск с пагинацией на Livewire и Laravel

Если вы смотрели Laracon 2019, то, возможно, видели удивительный новый пакет от Caleb Porzio под названием Livewire. Вот как оно описано на сайте:

Livewire — это полнофункциональный фреймворк для Laravel, который делает создание динамических интерфейсов таким же простым, как написание ванильного PHP (буквально).

Я начал писать на Laravel два года назад, и одной из моих самых больших проблем было создание интерактивных страниц. Laravel позволил мне легко перейти к веб-разработке, но я обнаружил, что использование Vue.js или React чересчур запутано и усложнено. Когда Калеб показал простой счетчик, я сразу подумал — Livewire будет идеален для поиска. И сейчас я расскажу вам как использовать Livewire для интерактивного поиска записей Eloquent с пагинацией.

Настройка Livewire ( документация )

composer require calebporzio/livewire

Добавьте его на все страницы, где вы хотите выполнять поиск, прямо перед закрывающим тегом body:

@livewireAssets 

</body> 
</html>

Livewire работает так — он подключая данные от компонентов Livewire на фронтенде напрямую к бэкендовому контроллеру Livewire. Эти контроллеры затем могут перерендерить компонент без необходимости обновления всей страницы. Наша цель — передать поисковый запрос из формы ввода на бэкенд и использовать его для поиска в нашей модели Eloquent. Результаты обновляются в реальном времени!

Наполняем базу данных (необязательно)

Для этого примера я использую свежеустановленное приложение Laravel. Если вы используете существующее приложение, то можете пропустить этот шаг. В противном случае, чтобы получить данные для поиска, вы можете заполнить таблицу пользователей дефолтной фабрикой пользователей Laravel (UserFactory). Добавьте это в свой файл DatabaseSeeder.php:

public function run()
{
    factory(App\User::class, 500)->create();
}

Затем сделайте в консоли php artisan db:seed и у вас появится 500 пользователей для поиска!

Создаем Поисковый Компонент

Прежде всего, давайте создадим контроллер поиска и шаблон Livewire:

php artisan make:livewire search

Это создаст новый контроллер в App\Http\Livewire под названием Search.php. И шаблон с именем search.blade.php в папке шаблонов Livewire.

Создаем Поле поиска + переменная

Давайте добавим в search.blade.php форму ввода и забиндим ее к Livewire:

<input type="text" wire:model="searchTerm" />

Установив wire:model="searchTerm" Livewire автоматически обновит переменную $searchTerm в контроллере Search.php. Давайте её добавим:

class Search extends Component
{
    public $searchTerm;
    
    public function render()
    {
        return view('livewire.search')
    }
}

Я разместил всё на дефолтной главной странице Laravel, но вы можете добавить на любую страницу, какую захотите. Просто добавьте @livewire('search') в blade-шаблон там, где он вам нужен.У меня сейчас так:

Интерактивный поиск с пагинацией на Livewire и Laravel

Вывод записей Eloquent

Давайте начнем с передачи всех пользователей компоненту и отображения их просто списком (без поиска). Поскольку мы собираемся сделать это интерактивным, нам нужно сделать всё это в файле search.blade.php. Просто сделайте еще одну переменную под именем $users и присвойте ей User::all().

class Search extends Component
{
    public $searchTerm;
    public $users;
    
    public function render()
    {
        $this->users = User::all();
        return view('livewire.search')
    }
}

Теперь, когда страница будет рендерится, она передаст переменную $users в шаблон поиска.
Мы можем использовать её в Blade как обычно в цикле вывести всех пользователей:

<div>
    <input type="text" wire:model="searchTerm">
    
    <ul>
        @foreach($users as $user)
            <li>
                <p>
                    {{ $user->name }}
                </p>
            </li>
        @endforeach
    </ul>
</div>

Теперь мы видим всех наших пользователей:

Интерактивный поиск с пагинацией на Livewire и Laravel

В поисках Eloquent

Чтобы получить желаемые результаты, нужно отфильтровать списки по тому, что ищет пользователь. Вместо того, чтобы возвращать User::all(), мы используем SQL команду ilike для поиска соответствующих записей в базе данных. Поскольку некоторые пользователи могут искать только фамилию или отчество, мы можем окружить наш поисковый запрос знаками % обозначающими любое количество символов с любой стороны нашего запроса. Все вместе это выглядит так:

class Search extends Component
{
    public $searchTerm;
    public $users;
    
    public function render()
    {
        $searchTerm = '%' . $this->searchTerm . '%';
        $this->users = User::where('name', 'ilike', $searchTerm)->get();
        return view('livewire.search')
    }
}

Вот и всё! Если мы перейдем на нашу страницу, мы сможем искать и мгновенно получать результат по всем пользователм. И мы не написали ни одной строки на javascript!

Интерактивный поиск с пагинацией на Livewire и Laravel

Что насчет пагинации?

У некоторых из наших пользователей есть сотни страниц контента, нам нужен был простой способ интерактивного поиска и пагинации всех результатов. Ниже я расскажу как сделать это, используя стандартную нумерацию страниц Laravel.

Пагинация + Вывод

Делаем в search.blade.php пагинацию результатов и её вывод через {{ $users->onEachSide(1)->links() }}. Чтобы разбить на страницы с помощью Eloquent, замените запрос на такой:

$this->users = User::where('name', 'ilike', $searchTerm)->paginate(10);

Интерактивный поиск с пагинацией на Livewire и Laravel

Теперь видна навигация по результатам. Однако вы можете заметить, что каждый клик перезагружает всю страницу и выдает ошибку при попытке поиска и переключения страниц. Нам нужно будет обновить дефолтный шаблон пагинации для работы с Livewire.

Делаем кастомную пагинацию

Вместо того, чтобы писать совершенно новую систему пагинации, давайте используем дефолтную из Laravel и модифицируем ее новыми функциями Livewire. Вы можете найти Bootstrap пагинацию в папке resources/views/vendor/pagination/ в файле bootstrap-4.blade.php. Мы видим, что каждая кнопка и номер страницы являются ссылкой, указывающие на предыдущий или следующий URL:

Интерактивный поиск с пагинацией на Livewire и Laravel

Давайте скопипастим этот файл в livewire-pagination.blade.php. Заменим все ссылки href="url" (их должно быть три) на href="#", чтобы страница не перезагружалась. И добавим Livewire метод клик каждой ссылке:

wire:click="setPage('{{$paginator->previousPageURL()}}')"

Обновленная пагинация теперь выглядит так:

Интерактивный поиск с пагинацией на Livewire и Laravel

Нам также нужно обновить наш контроллер добавив метод setPage(url):

public function setPage($url)
{
    //пока пусто
}

Чтобы отобразить эту кастомную пагинацию, нужно указать её в параметрах вызова из шаблона

{{ $users->onEachSide(1)->links('livewire-pagination') }}

Обновление пагинации в контроллере

Теперь нам нужен способ обновить результаты пагинации и компонент на основе текущей выбранной страницы. К счастью, в Laravel Paginator есть метод currentPageResolver(), который позволяет нам предварительно настроить отображаемую страницу. Мы можем передать текущий номер страницы этому методу, и он автоматически обновит результаты.

Чтобы получить номер выбранной страницы, нам нужно разобрать URL-адрес, переданный в метод setPage($url). Мы также должны объявить переменную $currentPage. Все вместе это выглядит так:

namespace App\Http\LiveWire;

use App\User;
use Livewire\Component;
use Illuminate\Pagination\Paginator;

class Search extends Component
{
    public $searchTerm;
    public $users;
    public $currentPage = 1;
    
    public function render()
    {
        $searchTerm = '%' . $this->searchTerm . '%';
        $this->users = User::where('name', 'ilike', $searchTerm)->get();
        return view('livewire.search')
    }
    
    public function setPage($url)
    {
        $this->currentPage = explode('page=', $url)[1];
        
        Paginator::currentPageResolver(function ()) {
            return $this->currentPage;
        });
    }
}

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

Интерактивный поиск с пагинацией на Livewire и Laravel

Надеюсь, что вам это было полезно!

Автор: Branick Weix
Перевод: Demiurge Ash