rails7 Hotwire로 자동완성 셀렉트폼 구현하는 방법

Posted by negabaro kim on Sunday, December 3, 2023 Tags: rails/hotwire   4 minute read

hotwire(turbo-stream,turbo-frame,stimuls)를 이용해 자동완성 셀렉트폼을 구현하는 방법을 알아보자.

77696d049e762090792ac6f9bb187ab5

12cc8417d65f1eeb1915cb39e29474f6

전체 코드는rails7-turbo-autocomplete-select-box을 참고

크게 아래 4가지 챕터로 구현했다.

  1. ransack으로 검색기능 구현
  2. input이벤트시 -> turbo_frame 사용(search_results_22222을 갱신)
  3. 자동완성된 내용을 선택시 update(patch) 발생 -> turbo_stream으로 search_form_11111을 갱신
  4. 존재하지 않는 키워드 검색시 신규추가 버튼 클릭 -> create(post) 발생 -> turbo_stream으로 search_form_11111갱신

텍스트 입력시 자동완성 부분만 turbo_frame으로 바꿔주고 자동완성된 내용중 선택시 update경유해서 turbo_stream으로 자동완성 + 검색 폼을 갱신 신규추가시에도 create경유해서 turbo_stream으로 동일하게 갱신해줬다.

각 챕터별 핵심 구현 코드는 아래를 참고

1.

gem 'ransack' -> bundle i
@qq = params[:q]
@q = Person.ransack(params[:q])
@persons = @q.result.order(id: :desc)
@assign_person = Post.first.person

2.

<%= turbo_frame_tag 'search_form_11111' do %>
<%= search_form_for @q, url: root_path,
    data: {
      turbo_frame: "search_results_22222",
      controller: "search-form",
      action: "input->search-form#submit"
    } do |f| %>

<div class="input-group">
  <%= f.search_field :name_cont, class: "shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md h-10", placeholder: "Search…", value: @assign_person.present? ? @assign_person.name : nil  %>
</div>

<%= turbo_frame_tag 'search_results_22222' do %>
 autocomplete되는 부분
<% end %>
<% end %>
import { Controller } from "@hotwired/stimulus";

// Connects to data-controller="search-form"
export default class extends Controller {
  submit(event) {
    event.preventDefault();
    clearTimeout(this.timeout);
    this.timeout = setTimeout(() => {
      this.element.requestSubmit();
    }, 200);
  }
}

3.

<%= button_to person.name, post_assign_people_update_path(person), method: :patch, form: {data: {turbo_frame: "search_form_tQPUuzjH"}}  %>
  def update
    new_assign_person = Person.find(params[:person_id])

    @qq = params[:q]
    @q = Person.ransack(params[:q])
    @persons = @q.result.order(id: :desc)

    if Post.first.update(person: new_assign_person)
      respond_to do |format|
        format.turbo_stream do
          @qq = nil
          @assign_person = new_assign_person
          render turbo_stream: turbo_stream.replace("search_form_11111", partial: "search/search_form_turbo", locals: { person: @person })
        end
        format.html { redirect_to root_path }
      end
    else
    end
  end

4.

<%= button_to post_assign_people_create_path, method: :post, params: { new_person: @qq[:name_cont] }, form: {data: {turbo_frame: "search_form_tQPUuzjH"}}, class: "flex items-center justify-center" do %>
+
<% end %>
  def create
    @qq = params[:q]
    @q = Person.ransack(params[:q])
    @persons = @q.result.order(id: :desc)

    new_person_name = params[:new_person]
    new_person = Person.create(name: new_person_name)

    if Post.first.update(person: new_person)
      respond_to do |format|
        format.turbo_stream do
          @qq = nil
          @assign_person = new_person
          render turbo_stream: turbo_stream.replace("search_form_11111", partial: "search/search_form_turbo", locals: { person: @person })
        end
        format.html { redirect_to root_path }
      end
    else
      # エラーハンドリング
    end
  end

@qq가 nil이면 자동완성폼을 안보여주므로 create성공시 @qq = nil을 넣어주는게 포인트