
В шапку страницы или другое нужное место добавляем форму поиска:
<div class="search-block"> <h1>@lang('search.enter-search')</h1> <form action="{{route('searchSimple')}}" method="GET" class="search-simple"> <div class="row"> <div class="col-xs-10"> <div class="form-group"> <input type="text" class="form-control" name="q" value="{{ old('q') }}" required> </div> </div> <div class="col-xs-2"> <div class="form-group"> <input class="btn btn-info" type="submit" value="Искать"> </div> </div> </div> </form> </div>
Как видно, запрос будет отправлен методом GET на маршрут searchSimple. Поэтому в файле routes/web.php добавляем данный маршрут:
Route::match(['get','post'],'searchSimple',['uses'=>'SearchController@index','as'=>'searchSimple']);
Указываем в нем так же и метод POST, т.к. AJAX запрос будет обрабатывать тот же самый метод контроллера (index).
В своих примерах я буду использовать модель(таблицу) Student, содержащую данные по студентам (ФИО, email и тд.).
Создаем SearchController:
<?php namespace App\Http\Controllers; use App\Student; use Illuminate\Http\Request; use Illuminate\Support\Facades\Input; class SearchController extends Controller { public $qQeury; public function index(Request $request){ $q = $request->input('q'); $max_page = 30; //Полнотекстовый поиск с пагинацией $results = $this->searchSimple($q, $max_page); //AJAX-запрос if($request->ajax() && $request->input('page')){ return view('search.table-ajax', [ 'qQeury' => $this->qQeury, 'students' => $results, ]); } return view('search.index', [ 'qQeury' => $this->qQeury, 'include' => 'search.table', 'students' => $results, ])->render();; } /** * Полнотекстовый поиск (простая форма) * * @param string $q * @param integer $count * @return mixed */ public function searchSimple($q, $count){ $page = Input::get('page'); if(!$page) $page =1; $query = mb_strtolower($q, 'UTF-8'); $tmp = explode(" ", $query); $query = []; foreach ($tmp as $word) { $len = mb_strlen($word, 'UTF-8'); switch (true) { case ($len <= 3): { $query[] = $word . "*"; break; } case ($len > 3 && $len <= 6): { $query[] = mb_substr($word, 0, -1, 'UTF-8') . "*"; break; } case ($len > 6 && $len <= 9): { $query[] = mb_substr($word, 0, -2, 'UTF-8') . "*"; break; } case ($len > 9): { $query[] = mb_substr($word, 0, -3, 'UTF-8') . "*"; break; } default: { break; } } } $query = array_unique($query, SORT_STRING); $qQeury = implode(" ", $query); $this->qQeury = $qQeury; // Таблица для поиска $results = Student::whereRaw( "MATCH(name,email) AGAINST(? IN BOOLEAN MODE)", // name,email - поля, по которым нужно искать array($qQeury))->paginate($count) ; return $results; } }
В методе index указываем кол-во записей которые нужно получить и вывести за 1 раз, тут «30». Метод содержит проверку: если это AJAX - подключаем шаблон «search.table-ajax», который выводит только сами строки без другого контента и заголовков таблицы для добавления их в конец таблицы.
У меня данный шаблон выглядит так (файл resources/views/search/table-ajax.blade.php):
@foreach($students as $student) <tr> <td class="align-left">{{$student->id}}</td> <td>{{$student->name}}</td> <td>{{$student->age}}</td> <td>{{$student->email }}</td> <td>{{$student->country->name_ru}}</td> <td>{{$student->city->name_ru}}</td> <td>{{$student->user->name}}</td> </tr> @endforeach
Ну а если был GET запрос, то выполнение кода пойдет дальше и будет подключен шаблон 'search.index', файл resources/views/search/index.blade.php:
@extends('layouts.layout') @section('content') @include($include) @endsection {{--JS только для страниц поиска--}} @section('individualJS') <script type="text/javascript" src="{{ asset('js/pagination.js') }}"></script> @endsection
Тут, у меня, подключается макет layout.blade.php из папки layouts, соответственно не забудьте прописать в своем макете 2 директивы для подключения указанных секций:
- @yield('content') – шаблон таблицы которая будет выводить результаты поиска;
- в конце блока body подключение js скрипта - @yield('individualJS').
Я специально создал отдельную секцию для подключения js скрипта, теперь он будет подключен только на странице вывода результатов поиска, а не на всех.
Шаблон таблицы выводящий результаты поиска (файл resources/views/search/table.blade.php):
<div class="students-table"> {{--token--}} <div id="id-token" data-token="{{csrf_token()}}" data-last-page="{{$students->lastPage()}}" style="display: none"></div> <table> <thead> <tr> <th class="align-left">id</th> <th>@lang('students.name')</th> <th>@lang('students.age')</th> <th>email</th> <th>@lang('students.country')</th> <th>@lang('students.city')</th> <th>@lang('students.curator')</th> </tr> </thead> <tbody> @foreach($students as $student) <tr> <td class="align-left">{{$student->id}}</td> <td class="align-left">{!! isset($qQeury) ? \App\Http\Controllers\SearchController::search_backlight($student->name, $qQeury) : $student->name !!}</td> <td>{{$student->age}}</td> <td>{!! isset($qQeury) ? \App\Http\Controllers\SearchController::search_backlight($student->email, $qQeury) : $student->email !!}</td> <td>{{$student->country->name_ru}}</td> <td>{{$student->city->name_ru}}</td> <td>{{$student->user->name}}</td> </tr> @endforeach </tbody> </table> </div>
В блоке
<div id="id-token" data-token="{{csrf_token()}}" data-last-page="{{$students->lastPage()}}" style="display: none"></div>в атрибуте data-token я указываю токен, который понадобится для осуществления POST запроса из JS скрипта. Так же, в атрибуте data-last-page я прописываю общее количество частей на которые был разделен результат запроса (пагинация). Данные атрибуты будут считаны js скриптом.
Второй метод контроллера «searchSimple» непосредственно осуществляет поиск в базе данных. У меня это полнотекстовый поиск, который использует FULLTEXT индексы для увеличения скорости поиска и снижения нагрузки на сервер. В своей заметке я писал как создать такие индексы для нужных полей таблицы. Вы же можете и не использовать полнотекстовый поиск, главное, чтобы метод searchSimple возвращал объект LengthAwarePaginator, который создается при вызове метода paginate().
Я использую пагинацию для загрузки результатов поиска частями. Только вместо стандартных кнопок пагинации у меня контент будет добавляться в конец блока при скролле (прокрутке) вниз.
Осталось только привести пример javascript (jquery) который мы подключили в шаблоне search/index.blade.php.
Файл public/js/pagination.js:
$(document).ready(function() { var inProgress = false; //флаг для отслеживания того, происходит ли в данный момент ajax-запрос var count = 2; //начинать пагинацию со 2 страницы, т.к. первая выводится сразу var countPage = $('#id-token').attr("data-last-page"); //токен для проверки запроса var table = $(".students-table").get(0); //таблица, в которую будут добавляться строки /* Обработчик скролла страницы */ $('.students-table').on('scroll', function() { /* Если скролл в блоке(таблице) прокручен вниз до конца и ajax-запрос в настоящий момент не выполняется и есть страницы для пагинации */ if((table.scrollHeight - table.scrollTop === table.clientHeight) && !inProgress && count<=countPage ){ var data = {}; data["token"] = $('#id-token').attr("data-token"); data["page"] = count; $.ajax({ url: '', data: data, type: 'POST', headers: { 'X-CSRF-TOKEN': data["token"] }, /* выполнить до отправки запрса */ beforeSend: function() { inProgress = true; }, // Ответ от сервера success: function(html){ $('.students-table tbody').append(html); }, // Ошибка AJAX error: function(result){ console.log(result); } /* сразу после выполнения запроса */ }).done(function(data){ inProgress = false; count++; }); } }); });
Данный код так же хорошо прокомментарирован. При скролле блока с результатами поиска вниз вызывается событие, которое запускает выполнение анонимной функции:
$('.students-table').on('scroll', function(){...}
В ней прописана проверка, которая выполняет AJAX запрос только если посетитель прокрутил блок вниз до конца. Так же проверяется не осуществляется ли в данное время AJAX запрос и есть ли еще данные для вывода.
При получении успешного ответа от сервера, а это у нас строки из шаблона search/table-ajax.blade.php, он вставляется в конец таблицы. Затем пользователь прокручивает вниз чтобы просмотреть появившиеся данные и подгружается следующая порция строк…
Еще важный момент – т.к. контент подгружается при скроллинге, необходимо ограничить высоту блока в который будет подгружаться контент. Например указать такие CSS стили для блока:
.students-table{ max-height: 500px; overflow: auto; }
При этом, порция строк, которую вы указываете для получения (у меня 30), не должна полностью помещаться по высоте, т.е. у вас должна появиться полоска скролла в правой части блока. Таким образом пользователь будет видеть, что не все поместилось в видимой области блока, прокручивать его и таким образом вызывать js событие с подгрузкой нового контента.