У веб-розробці важливим аспектом є забезпечення зручного та ефективного пошуку, який позитивно впливає на досвід користувачів. Для мого нещодавнього проєкту на AVIMBU я реалізував функціональність пошуку, яка запитує як локальні дані, так і зовнішні API. Це було досягнуто за допомогою TurboFrames від Hotwire у веб-додатку Ruby on Rails 7.
У цьому блозі я розповім, як реалізувати динамічну панель пошуку, яка отримує дані як локально, так і з зовнішнього джерела (наприклад, API), і все це без перезавантаження сторінки завдяки TurboFrames.
Розділ 1: Початкова настройка проєкту
Оскільки ми будуємо динамічний пошук для проєкту на Ruby on Rails 7, потрібно виконати кілька кроків для налаштування початкового стану проєкту. Для цього виконайте таку команду:
rails new dynamic_search - css=bootstrap -j=esbuild
Ця команда створює проєкт Rails з попередньо налаштованою останньою версією Bootstrap (це необов'язково, але забезпечує кращу стилізацію). Для пакування JavaScript ми використаємо esbuild
, який є простим і зручним у використанні. Ви також можете вибрати webpacker
або більш сучасний підхід import_maps
, але на даний момент найбільш зручним є використання esbuild
через його простоту інтеграції з Bootstrap.
Після створення проєкту перейдіть до директорії проєкту і запустіть його за допомогою:
bin/dev
Тепер ваш додаток має бути доступний за адресою localhost:3000
.
Ця настройка вже включає Turbo, що є ключовим компонентом для динамічних оновлень сторінки. Перевірте його наявність у файлі app/javascript/application.js
:
import "@hotwired/turbo-rails"
Розділ 2: Реалізація функціоналу пошуку
Почнімо з того, що створимо модель Project з атрибутами name
і description
. Для цього використаємо scaffolding в Rails для швидкого налаштування моделі та операцій CRUD:
rails g scaffold project name description
Після цього ви зможете створювати, оновлювати і видаляти моделі проєктів у вашому додатку.
Тепер давайте додамо функціональність пошуку: я вибрав дію index
контролера ProjectsController
. Тому я додав форму пошуку до відповідного шаблону виду:
<%= form_with url: projects_path, method: :get, data: { turbo_frame: 'project_listings' } do |form| %>
<%= form.search_field :q, class: 'form-control', placeholder: "Пошук за назвою або описом" %>
<% end %>
Це додає просту панель пошуку, яка дозволяє надіслати запит типу GET
до дії projects#index
. Параметр пошукового запиту передається через параметр q
. Я адаптував дію index
наступним чином:
def index
@projects = Project.all
@search = params[:q]
@results = if @search.blank?
[]
else
Project.where('name LIKE :search OR description LIKE :search', search: "%#{@search}%")
end
end
Після цієї зміни я отримав дві нові змінні, доступні в поданні index
. @search
містить пошуковий запит, а @results
зберігає масив знайдених моделей Project. Для демонстрації я використав простий запит з LIKE
для пошуку в базі даних. Це можна і потрібно покращити, використовуючи більш ефективні рішення, такі як pg_search.
Файл app/views/projects/index.html.erb
також потребує деяких змін:
<%= turbo_frame_tag 'project_listings' do %>
<% unless @results.empty? %>
<% @results&.each do |project| %>
Name: <%= project.name %> - Description: <%= project.description %>
<% end %> <% end %> <% end %> ``` Я обгорнув результати пошуку в `turbo_frame_tag` з таким самим ID, до якого форма надсилає запит.
Отже, Turbo досить розумний, щоб визначити, яка частина DOM має бути оновлена, і оновлює лише `project_listings` у `turbo_frame_tag`, щоб уникнути повного перезавантаження сторінки.
Тепер, якщо пошук по локальній базі даних повертає результати, ці результати відображаються без необхідності перезавантаження сторінки.
## **Інтеграція зовнішнього пошуку**
Перша частина пошуку тепер реалізована. Тепер давайте додамо функціональність зовнішнього пошуку, щоб інтегрувати зовнішнє API для отримання результатів пошуку на випадок, якщо локальний пошук не дасть бажаних результатів. Для цього я використовую концепцію лінійного завантаження `turbo_frame_tags`. Це дозволяє мені активувати / завантажувати джерело дії TurboFrame, як тільки цей фрейм стає видимим у видимій частині екрану.
Щоб це працювало, потрібно змінити три речі. По-перше, додати `turbo_frame_tag` у перший TurboFrame результатів пошуку так, щоб він завжди перезавантажувався, коли перший TurboFrame буде перезавантажено. Переконайтеся, що опція `src` вказує на зовнішню дію пошуку, яку ми зараз додамо. Для цього додайте наступний маршрут до вашого файлу `routes.rb`:
get 'projects/externalsearchproject' => 'projects#externalsearchproject', as: :loadexternalsearch_project
```
Також додайте цей метод (дію) до контролера проектів:
def external_search_project
return if params[:q].blank? || params[:avoid_search] == "true"
sleep 1.0 # імітація зовнішнього запиту
@external_search_result = [Project.new(name: "External Project", description: "Loaded from wherever")]
end
Це приклад реалізації, де запит до зовнішнього пошуку імітується за допомогою sleep
. Також дія пропускається, якщо не передано пошуковий запит або якщо вказано параметр avoid_search
.
Нарешті, додайте зовнішній пошуковий TurboFrame всередину першого TurboFrame, щоб забезпечити його завантаження разом з регулярним оновленням пошуку. Передаючи пошуковий запит і встановлюючи параметр avoid_search
залежно від того, чи є результати локального пошуку, зовнішній пошук буде запускатися тільки в разі, якщо локальний пошук не дасть результатів.
<%= turbo_frame_tag 'project_listings' do %>
<% unless @results.empty? %>
<% @results&.each do |project| %>
Name: <%= project.name %> - Description: <%= project.description %>
<% end %> <% end %> <%= turbo_frame_tag 'external_search_result', src: load_external_search_project_path(q: @search, avoid_search: @results.any?), loading: :lazy do %> <% end %> <% end %> ``` Також, щоб оновити `external_search_result` TurboFrame, додайте цей шаблон виду до файлу `app/views/projects/external_search_project.html.erb`. Це буде відображено при зовнішньому пошуку і виведе результати пошуку, якщо вони доступні.
<%= turboframetag "externalsearchresult" do %>
<% @externalsearchresult&.each do |project| %>
Name: <%= project.name %> - Description: <%= project.description %>
<% end %> <% end %> ``` Ось і все. Тепер ви маєте контроль над поведінкою пошуку. Це дозволяє адаптувати реалізацію пошуку до ваших потреб. Остаточний продукт має виглядати так: Фінальне рішення
Розділ 4: Покращення: Автоматичне надсилання форми
Покращіть досвід пошуку за допомогою автоматичного надсилання форми. Використовуйте контролер Stimulus для надсилання форми після короткої затримки.
Прикріпіть контролер до форми наступним чином:
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
search() {
console.log("Search Submit")
clearTimeout(this.timeout)
this.timeout = setTimeout(() => {
this.element.requestSubmit()
}, 300)
}
}
Також потрібно прикріпити цей контролер до форми пошуку ось так:
<%= form_with url: projects_path, method: :get, data: { controller: "formsubmission", turbo_frame: 'project_listings' } do |form| %>
<%= form.search_field :q, class: 'form-control', placeholder: "Пошук за назвою чи описом", data: { action: "input->formsubmission#search" } %>
<% end %>
Тепер після цього додавання, форма автоматично надсилається через 300 мс.
Висновок
Використання TurboFrames таким чином є ефективним рішенням для динамічного пошуку в додатках на Rails.
Для додаткової інформації про TurboFrames і Hotwire, відвідайте документацію Turbo.
Якщо хочете дізнатися більше про мене, не соромтеся відвідати мою домашню сторінку або стежити за мною в Twitter тут.
Додаткові ресурси
Код, використаний у цьому посібнику, можна знайти тут.
Декілька додаткових ресурсів:
Перекладено з: Implementing Dynamic Search with Turbo Frames in Ruby on Rails 7