Skip to content

Instantly share code, notes, and snippets.

@tdegrunt
Forked from lazaronixon/_form.html.erb
Created March 23, 2025 10:09
Show Gist options
  • Save tdegrunt/cf03568108e9ae9afd8ce83d5a10eb90 to your computer and use it in GitHub Desktop.
Save tdegrunt/cf03568108e9ae9afd8ce83d5a10eb90 to your computer and use it in GitHub Desktop.
Hotwire Event-Driven Update Pattern
<%= form_with model: citizen, class: "card flex flex-col gap", data: { controller: "form" } do |form| %>
<div class="flex flex-col gap mb-2">
<div class="flex flex-col gap-half">
<% countries = Country.order(:name) %>
<%= label_tag :country_id, "Country", class: "text-sm font-medium leading-none" %>
<%= select_tag :country_id, options_from_collection_for_select(countries, :id, :name, citizen.country_id), include_blank: "Select one", class: "input", data: { action: "form#submit", form_submitter_param: "on_country_change" } %>
</div>
<div class="flex flex-col gap-half">
<% states = State.where(country_id: citizen.country_id).order(:name) %>
<%= label_tag :state_id, "State", class: "text-sm font-medium leading-none" %>
<%= select_tag :state_id, options_from_collection_for_select(states, :id, :name, citizen.state_id), include_blank: "Select one", class: "input", data: { action: "form#submit", form_submitter_param: "on_state_change" } %>
</div>
<div class="flex flex-col gap-half">
<% cities = City.where(state_id: citizen.state_id).order(:name) %>
<%= form.label :city_id, "City", class: "text-sm font-medium leading-none" %>
<%= form.collection_select :city_id, cities, :id, :name, { include_blank: "Select one" }, { class: "input" } %>
</div>
</div>
<div class="flex items-center">
<%= form.submit "Save changes", class: "btn btn--primary" %>
<%= form.submit id: "on_country_change", hidden: true, formaction: on_country_change_citizens_path, formmethod: :post, formnovalidate: true %>
<%= form.submit id: "on_state_change", hidden: true, formaction: on_state_change_citizens_path, formmethod: :post, formnovalidate: true %>
</div>
<% end %>
class Citizen < ApplicationRecord
belongs_to :city
delegate :state, :state_id, to: :city, allow_nil: true
delegate :country, :country_id, to: :state, allow_nil: true
end
class CitizensController < ApplicationController
def new
@citizen = Citizen.new
end
def edit
@citizen = Citizen.first
end
def create
head :created
end
def update
head :ok
end
def on_country_change
@states = State.where(country_id: params[:country_id]).order(:name)
end
def on_state_change
@cities = City.where(state_id: params[:state_id]).order(:name)
end
end
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
submit({ params }) {
if (params.submitter) {
this.element.requestSubmit(this.#find(params.submitter))
} else {
this.element.requestSubmit()
}
}
#find(id) {
return document.getElementById(id) || this.#notFound(id)
}
#notFound(id) {
throw new Error(`Element with ID "${id}" not found`)
}
}
<%= turbo_stream.replace :state_id do %>
<%= select_tag :state_id, options_from_collection_for_select(@states, :id, :name), include_blank: "Select one", class: "input", data: { action: "form#submit", form_submitter_param: "on_state_change" } %>
<% end %>
<%= turbo_stream.replace :citizen_city_id do %>
<%= select_tag :citizen_city_id, nil, name: "citizen[city_id]", include_blank: "Select one", class: "input" %>
<% end %>
<%= turbo_stream.replace :citizen_city_id do %>
<%= select_tag :citizen_city_id, options_from_collection_for_select(@cities, :id, :name), name: "citizen[city_id]", include_blank: "Select one", class: "input" %>
<% end %>
Rails.application.routes.draw do
resources :citizens, only: %i(new edit create update) do
post "on_country_change", on: :collection
post "on_state_change", on: :collection
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment