27
loading...
This website collects cookies to deliver better user experience
rails new hotwire-search -T && cd hotwire-search
bundle add hotwire-rails
rails hotwire:install
rails g scaffold Player name:string
rails db:migrate
rails s
<p id="notice"><%= notice %></p>
<h1>Players</h1>
<%= form_with(url: search_players_path) do |form| %>
<%= form.label :query, "Search by name:" %>
<%= form.text_field :query, data: { action: "input->form-submission#search" } %>
<% end %>
<table>
<thead>
<tr>
<th>Name</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% @players.each do |player| %>
<tr>
<td><%= player.name %></td>
<td><%= link_to 'Show', player %></td>
<td><%= link_to 'Edit', edit_player_path(player) %></td>
<td><%= link_to 'Destroy', player, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New Player', new_player_path %>
form_with
that expects a route named search_players_path
to exist, we’ll update our routes file to add that path:Rails.application.routes.draw do
resources :players do
collection do
post 'search'
end
end
end
PlayersController
. With the path added, next let’s update the PlayersController
to add our search method:def search
if params[:query].present?
@players = Player.where("name LIKE ?", "%#{params[:query]}%")
else
@players = Player.all
end
render turbo_stream: turbo_stream.replace(
'players',
partial: 'list',
locals: {
players: @players
}
)
end
render
call. players
and replaces the contents of that element with a list
partial.<turbo-stream action="replace" target="players">
<template>
<!-- Content from the rendered partial -->
</template>
</turbo-stream>
<turbo-stream>
element and sent with a header (text/vnd.turbo-stream.html; charset=utf-8
) that indicates the response is a turbo stream.action
and the target
or, newly added, targets
from the <turbo-stream>
and uses that to update only the relevant part(s) of the DOM.touch app/views/players/_list.html.erb
<tbody id="players">
<% players.each do |player| %>
<tr>
<td><%= player.name %></td>
<td><%= link_to 'Show', player %></td>
<td><%= link_to 'Edit', edit_player_path(player) %></td>
<td><%= link_to 'Destroy', player, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
id
attribute on the <tbody>
element. This id must match the target passed to turbo_stream.replace
in our controller, otherwise nothing will happen when we search. The rest of this partial is just boilerplate generated by the Rails scaffold.<!-- Snip -->
<table>
<thead>
<!-- Snip -->
</thead>
<%= render "list", players: @players %>
</table>
<!-- Snip -->
touch app/javascript/controllers/form_submission_controller.js
import { Controller } from "stimulus"
import Rails from "@rails/ujs";
export default class extends Controller {
static targets = [ "form" ]
search() {
clearTimeout(this.timeout)
this.timeout = setTimeout(() => {
Rails.fire(this.formTarget, 'submit')
}, 200)
}
}
search
function waits for the user to stop typing and then submits our form. To trigger the form submission, we’re borrowing a technique outlined in Better Stimulus.<%= form_with(url: search_players_path, data: { controller: 'form-submission', form_submission_target: "form" }) do |form| %>
<%= form.label :query, "Search by name:" %>
<%= form.text_field :query, data: { action: "input->form-submission#search" } %>
<% end %>
<form>
element to connect our controller and set the necessary form target and then the text field is updated with a data-action attribute that triggers the search
function in the form-submission
controller on input.<tbody>
on when form submission starts could be a good starting pointplayers
is empty