stimulus-autocompleteを用いてオートコンプリート機能実装(Ruby on Rails)

## はじめに
エンジニア転職を目指しRuby on Railsを中心に学習中の初学者です。
ポートフォリオにオートコンプリート機能を実装したので、その備忘録として書き記しておきます。
誤っている箇所や改善点などありましたらご指摘いただけると幸いです。

## 開発環境
- docker環境
- ruby:3.3.6
- ruby on rails: 7.2
- tailwindcss: 4.0.15
- hotwired/stimulus: 3.2.2
- jsバンドラー(esbuild)を使用

## 前提
- **gem “ransack”を用いてすでに検索機能を実装している状態**とします
- Stimulusがすでにインストールされている状態とします

:::note info
Rails7系では`rails new` を実行した時にデフォルトで、
`@hotwired/stimulus`
`gem "stimulus-rails”` 
がセットアップされています。
:::

念の為実際に、インストールされているか確認します。

```ruby:gemfile
gem "stimulus-rails"
```

```ruby:package.json
{
    "@hotwired/stimulus": "^3.2.2",
    "@hotwired/turbo-rails": "^8.0.13",
    "@tailwindcss/cli": "^4.1.4",
    "tailwindcss": "^4.1.4"
  }
```

## 1.stimulus-autocompleteをインストールする
オートコンプリート機能を実装するために、まず`stimulus-autocomplete`をインストールします。`stimulus-autocomplete`の公式githubは[こちら](https://github.com/afcapel/stimulus-autocomplete)

:::note info
jsバンドラーnode_modules(esbuild、rollup.js、Webpack など)を使用している場合は、npmからパッケージをインストールします。
:::



以下を実行してインストール(docker環境)
```
$ docker compose exec web yarn add stimulus-autocomplete
```
上記を実行後、`package.json`に"stimulus-autocomplete" が追加されているのを確認します
```ruby:package.json
{
    "@hotwired/stimulus": "^3.2.2",
    "@hotwired/turbo-rails": "^8.0.13",
    "@tailwindcss/cli": "^4.1.4",
    "stimulus-autocomplete": "^3.1.0", # 追加される
    "tailwindcss": "^4.1.4"
  }
```

## 2.Autocompleteコントローラを登録する

`app/javascript/controllers/application.js`にAutocompleteコントローラーを登録します。

```ruby:app/javascript/controllers/application.js
import { Application } from '@hotwired/stimulus'
import { Autocomplete } from 'stimulus-autocomplete' // 追加

const application = Application.start()
application.register('autocomplete', Autocomplete) // 追加
```

これによりdata-controller="autocomplete"の属性をHTMLに追加することで、インストールした`stimulus-autocomplete`のAutocompleteコントローラが適用される。

## 3. 検索フォームを編集する
オートコンプリート機能を使用するには、検索フォームに以下の4つの追記が必要です。

```
<div data-controller="autocomplete" data-autocomplete-url-value="<%= autocomplete_〇〇_path %>" role="combobox">


<%= f.search_field :〇〇_cont, data: { autocomplete_target: 'input' } %>


<%= f.hidden_field :〇〇, data: { autocomplete_target: 'hidden' } %>


<ul class="list-group" data-autocomplete-target="results"></ul>
```

### (1) data: { autocomplete_target: 'input' }を追記
まず検索フォームの`<%= f.search_field %>`の中に以下を追記します。
```
data: { autocomplete_target: 'input' }
```

`autocomplete_target`というカスタムデータ属性を追加することでJavaScriptからこの要素を取得しオートコンプリートの機能を適用できます。


### (2) divで囲む
そして`<%= f.search_field %>`を以下の`div`タグで囲みます
```
<div data-controller="autocomplete" data-autocomplete-url-value="<%= autocomplete_〇〇_path %>" role="combobox">
</div>
```

そうすると以下のようになります。

```
<%= search_form_for q, url: url, class: "w-full" do |f| %>
  <div data-controller="autocomplete" data-autocomplete-url-value="<%= autocomplete_〇〇_path %>" role="combobox">
    <%= f.search_field :〇〇_cont data: { autocomplete_target: "input" } %>
  </div>
  <%= f.submit "検索" %>
<% end %>
```
`data-autocomplete-url-value="<%= autocomplete_〇〇_path %>"`ではRailsのビューテンプレートでURLを動的に生成してます。つまり〇〇controller の autocomplete アクションが実行され、オートコンプリートの機能が使われる時にそのアクションが呼び出されます。
のちにautocompleteアクションとルーティングを定義します。

### (3) 隠しフィールドとulを追加する

`<%= f.search_field %>`の下に以下の2つのコードを追加する
```
<%= f.hidden_field :〇〇, data: { autocomplete_target: 'hidden' } %>

<ul class="list-group" data-autocomplete-target="results"></ul>
```

`<%= f.hidden_field %>`は「隠しフィールド」といい実際のページには表示されません。このフィールドは、ユーザーがオートコンプリートの候補から選んだ「値」を保持するために使われます。

`<ul>`には入力内容に応じたオートコンプリートの候補が挿入されます。

最終的にスタイルなどをあてると以下のようになりました。
```
<%= search_form_for q, url: url do |f| %>
  <div class="relative flex rounded-md w-full">
    <div data-controller="autocomplete" data-autocomplete-url-value="<%= autocomplete_books_path %>" role="combobox">
      <%= f.search_field :title_cont, class: 'flex-grow py-3 px-4 rounded-l-md border focus:outline-none w-[400px] h-[50px]', placeholder: "検索ワード", data: { autocomplete_target: 'input' } %>
      <%= f.hidden_field :title, data: { autocomplete_target: 'hidden' } %>
      <ul class="list-group absolute w-[400px]" data-autocomplete-target="results"></ul>
    </div>
    <%= f.submit "検索", class: 'btn bg-[#eed9b6] hover:bg-[#d4b886]' %>
  </div>
<% end %>
```

## 4. autocompleteアクションを定義する
`routes.rb`にautocompleteアクションのルーティングを追記します。
```routes.rb
Rails.application.routes.draw do

  resources :books do
    # 以下を追加する
    collection do
      get :autocomplete
    end
  end
end
```

次にautocompleteアクションを定義します。
```
class booksController < ApplicationController

  # 以下を追加
  def autocomplete
    @books = Book.where("title like ?", "%#{params[:q]}%")
    respond_to do |format|
      format.js
    end
  end
end
```

これにより検索フォームに文字が入力されると、autocomplete_books_pathにリクエストが行われ、オートコンプリートの結果が取得されるようになります。

## 5.オートコンプリートの候補表示のviewを作成する

検索フォームの`<ul>`タグの中に挿入する用の`<li>`
```
<li class="list-group-item" role="option" data-autocomplete-value="〇〇">item</li>
```
を作成する必要がある。
なのでまず`app/views/〇〇/autocomplete.html.erb`を**作成・編集**する
```app/views/books/autocomplete.html.erb
<% @books.each do |book| %>
  <li class="flex w-full items-center text-sm py-2 px-3 hover:bg-amber-100" role="option" data-autocomplete-value="<%= book.id %>" data-autocomplete-label="<%= book.title %>">
    <%= book.title %>
  </li>
<% end %>
```

## 6.完成

これで完成しました
![5116625ca922ef2491cf08e167bf244b.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4072077/61591238-f9dd-48a1-a893-4e6bd0f63e5d.gif)

## 参考記事

https://qiita.com/Yamamoto-Masaya1122/items/879d6eb540ce4e05cfe5

https://github.com/afcapel/stimulus-autocomplete