Hotwire Combobox

Episode #459 by Teacher's Avatar David Kimura

Summary

In this episode, we tackle the issue of slow-loading dropdowns with a high volume of options by implementing an asynchronous select using the Hotwire Combobox library.
rails view form hotwire 13:34

Chapters

  • Introduction (0:00)
  • Creating the models (1:27)
  • Adding the dependencies (2:06)
  • Creating the Select Inputs (2:29)
  • Adding the comboboxes (4:01)
  • Setting up the styling (6:16)
  • Setting up the controllers (6:34)
  • Creating the search views (7:51)
  • Adding the Stimulus controller (8:55)
  • Demo (10:44)
  • Fixing when editing the records (11:11)
  • Changing the CSS (12:14)
  • Final Thoughts (13:11)

Resources

Download Source Code

Summary

# Terminal
rails g model category name
rails g model brand name
rails g scaffold products name category:belongs_to brand:belongs_to
bundle add faker
bundle add hotwire_combobox
yarn add @josefarias/hotwire_combobox
rails g controller searches/categories show
rails g controller searches/brands show

# app/views/products/_form.html.erb
<div class="mb-3">
  <%# form.label :category_id, class: 'form-label' %>
  <%# form.select :category_id, Category.all.map { |r| [r.name, r.id] }, {}, class: 'form-control' %>
  <%= form.combobox :category_id, searches_categories_path, label: "Category", placeholder: "Select an option" %>
</div>

<div class="mb-3">
  <%# form.label :brand_id, class: 'form-label' %>
  <%# form.select :brand_id, Brand.all.map { |r| [r.name, r.id] }, {}, class: 'form-control' %>
  <%= form.combobox :brand_id, searches_brands_path, label: "Brand", placeholder: "Select an option" %>
</div>

# db/seeds.rb
1000.times do
  Category.find_or_create_by(name: Faker::Book.genre)
  Brand.find_or_create_by(name: Faker::Company.name)
end

# config/routes.rb
namespace :searches do
  resource :brands, only: :show
  resource :categories, only: :show
end

# app/views/layouts/application.html.erb
<%= combobox_style_tag %>

# app/controllers/searches/brands_controller.rb
class Searches::BrandsController < ApplicationController
  def show
    @brands = Brand.where("name like :query", query: "%#{params[:q]}%").order(:name)
  end
end

# app/controllers/searches/categories_controller.rb
class Searches::CategoriesController < ApplicationController
  def show
    @categories = Category.where("name like :query", query: "%#{params[:q]}%").order(:name)
  end
end

# app/views/searches/brands/show.turbo_stream.erb
<%# async_combobox_options @brands.map { |brand| [brand.name, brand.id] } %>
<%= async_combobox_options @brands %>

# app/views/searches/categories/show.turbo_stream.erb
<%= async_combobox_options @categories %>

# app/javascript/controllers/application.js
import HwComboboxController from "@josefarias/hotwire_combobox"
application.register("hw-combobox", HwComboboxController)

# app/models/brand.rb
class Brand < ApplicationRecord
  def to_combobox_display
    name
  end
end

# app/models/category.rb
class Category < ApplicationRecord
  def to_combobox_display
    name
  end
end

# app/assets/stylesheets/application.bootstrap.scss
.hw-combobox,
.hw-combobox__main__wrapper {
  width: 100% !important;
}