1rails new dynamic_search --css=bootstrap -j=esbuild
esbuild
, which is straightforward and simple to use. You could also opt for using webpacker
or the more modern import_maps
approach, however, for now it's easiest to stick with esbuild
due to its straightforward integration of Bootstrap.1bin/dev
localhost:3000
.app/javascript/application.js
:1import "@hotwired/turbo-rails"
name
and description
attributes. Use Rails' scaffolding to quickly set up the model and its CRUD operations:1rails g scaffold project name description
index
action of the ProjectsController. Therefore I've added a search form to the corresponding view template:1<%= form_with url: projects_path, method: :get, data: { turbo_frame: 'project_listings' } do |form| %>
2 <%= form.search_field :q, class: 'form-control', placeholder: "Search for Name or Description" %>
3<% end %>
GET
search request against the projects#index
action. The search query is provided via the q
parameter. I've adapted the index
action as follows:1def index
2@projects = Project.all
3@search = params[:q]
4@results = if @search.blank?
5 []
6else
7 Project.where('name LIKE :search OR description LIKE :search', search: "%#{@search}%")
8end
9end
index
view. @search
holds the search query and @results
holds an array of found Project models. For the sake of demonstration, I've implemented the database search using a simple LIKE
based search query. This can and should be improved by using better implementations such as pg_search.app/views/projects/index.html.erb
also needs some changes:1<%= turbo_frame_tag 'project_listings' do %>
2 <% unless @results.empty? %>
3 <% @results&.each do |project| %>
4 <div class="p-2 border">
5 <div class="fs-6">Name: <%= project.name %> - Description: <%= project.description %></div>
6 </div>
7 <% end %>
8 <% end %>
9<% end %>
turbo_frame_tag
with the same ID as the form submits to. Therefore, Turbo is smart enough to figure out which part of the DOM needs to change and only updates the project_listings
turbo_frame_tag
to avoid a full page refresh.turbo_frame_tags
. This allows me to trigger / load the source action of a TurboFrame as soon as the frame is visible in the viewport.turbo_frame_tag
within the first search result TurboFrame s.t. it's always reloaded when the first TurboFrame is reloaded. Ensure that the src
option is pointing to the external search action, which we are now going to add. For this, add the following route to your routes.rb
file:1get 'projects/external_search_project' => 'projects#external_search_project', as: :load_external_search_project
1def external_search_project
2 return if params[:q].blank? || params[:avoid_search] == "true"
3
4 sleep 1.0 # simulate external request
5 @external_search_result = [Project.new(name: "External Project", description: "Loaded from wherever")]
6end
sleep
. Also, the action is skipped if no search query is passed or an avoid_search
param is given.avoid_search
parameter depending on whether local search results are available or not, the external search is only triggered accordingly. This allows for only executing and rendering external search results if local search didn't provide any results.1<%= turbo_frame_tag 'project_listings' do %>
2 <% unless @results.empty? %>
3 <% @results&.each do |project| %>
4 <div class="p-2 border">
5 <div class="fs-6">Name: <%= project.name %> - Description: <%= project.description %></div>
6 </div>
7 <% end %>
8 <% end %>
9 <%= turbo_frame_tag 'external_search_result', src: load_external_search_project_path(q: @search, avoid_search: @results.any?), loading: :lazy do %>
10 <% end %>
11<% end %>
external_search_result
TurboFrame, add this view template to app/views/projects/external_search_project.html.erb
. This will be rendered on external search and lists the external search results if they are available.1<%= turbo_frame_tag "external_search_result" do %>
2<% @external_search_result&.each do |project| %>
3 <div class="p-2 border">
4 <div class="fs-6">Name: <%= project.name %> - Description: <%= project.description %></div>
5 </div>
6<% end %>
7<% end %>
1import { Controller } from "@hotwired/stimulus"
2
3export default class extends Controller {
4
5search() {
6 clearTimeout(this.timeout)
7 this.timeout = setTimeout(() => {
8 this.element.requestSubmit()
9 }, 300)
10}
11}
1<%= form_with url: projects_path, method: :get, data: { controller: "formsubmission", turbo_frame: 'project_listings' } do |form| %>
2 <%= form.search_field :q, class: 'form-control', placeholder: "Search for Name or Description", data: { action: "input->formsubmission#search" } %>
3<% end %>
Abonnieren Sie unseren Newsletter und erhalten Sie jede Woche neue Artikel, Experteneinblicke und Branchen-Updates direkt in Ihr Postfach.