個人メモ：hotwire_comboboxの挫折

## 記事概要
以下は個人開発で ["hotwire_combobox"](https://github.com/josefarias/hotwire_combobox) というgemを使って問題点の解消をうまくできず諦めた内容です。

正確には機能自体を実装できはしたものの、使うにあたって気になる使用感の問題を解決できませんでした。

:::note warn
もし解決方法があったり、記事の間違いなどがありましたらコメントしていただけるとありがたいです。
:::

基本的には自分のための備忘録ですが、hotwire_comboboxを使おうと考えている人が見ることもあると思うので個人的な結論から述べると、
### 日本語のアプリケーションでは"hotwire_combobox"を使用することは推奨しません。
<br/>

その理由はこれから紹介する、自分の[解決できなかった問題](#解決できなかった問題) は **「かな入力」** に関わるものがほとんどだからです。

そもそも["hotwire_combobox"を使ったサンプルサイト](https://hotwirecombobox.com)でもかな入力ではおかしな挙動をしているので、日本語入力を前提とする検索機能にはあまり対応していないのかもしれません。(2025/ 1月時点)

https://hotwirecombobox.com

※自分はform-withに含める形で使用していたので、それに近い形である「A free-text combobox」のサンプルでかな入力を試したところ、文字入力後にEnterキーを押下した時、入力していた文字が連続するなどという挙動をします。

作成前に気づきたかった。

:::note warn
以降どんな問題があったのか気になる人、それでも使ってみようと思う人以外はあまり見る意味ないです。
:::

## 記事の目次
* [hotwire_comboboxの用途](#hotwire_comboboxの用途)
* [解決できなかった問題](#解決できなかった問題)
* [実装内容](#実装内容)

## hotwire_comboboxの用途
"hotwire_combobox"自体はオートコンプリート機能のためのgemです。

簡単にいうと、アプリ内の検索で、対象テーブルに登録してあるデータが予測変換候補に表示されるような機能をつけるために使おうとしていました。

例えば、、、
「Items」というテーブルに「時計」、「りんご」、「ネクタイ」...などというデータを多数登録させます。

そして「ネクタイ」を検索しようというときに「ネ」や「ネク」など途中までの検索キーワードと一致するItemsのデータを予測候補として表示してくれるというものです。

|<font color="gray">入力前</font>　　　　　　　　  　　　　　　　　   　　　　　　　　　　　　　　　　　　　　　|<font color="gray">入力後</font>　　　　　　　　  　　　　　　　　   　　　　　　　　　　　　　　　　　　　　　|
|:-:|:--:|
|<img src="https://i.gyazo.com/292cb08643bab67672317f0eb45fe46e.png" width="206">|<img src="https://i.gyazo.com/7dd9c3564150222a6bf4fd0b3d9cab8f.png" width="200">


## 解決できなかった問題
まず前提として、hotwire_comboboxでは予測候補の表示形式を「autocomplete」というオプションにより、以下のように指定することができます。

 **autocomplete オプション**
:list → 入力フィールド下にリストで候補を表示
:inline → 予測候補を直接入力欄に補完
:both → 両方を使用（デフォルト）

そして、
これから提示する 問題 ① と ② は list形式 に関わる問題点、
問題 ③ は inline形式 に関わる問題点となります。

もちろん both にすれば、基本的に list形式 と inline形式 両方の問題が起こります。


### 問題①： 検索時にEnterキーを押下すると、選択された候補が入力欄に反映され、入力途中だった文字も残ってしまう。

|<font color="gray">入力</font>　　　　　　　　  　　　　　　　　   　　　　　　　　　　　　　　　　　　　　　|<font color="gray">Enter押下</font>　　　　　　　　  　　　　　　　　   　　　　　　　　　　　　　　　　　　　　　|
|:-:|:-:|
|<img src="https://i.gyazo.com/4663e77709068d5a2d09eb335967d9aa.png"  width="154"/>|<img src="https://i.gyazo.com/dcb2d5596313fa065b57c0a9a5e6c7ca.png" width="154"/>|

写真のように入力途中だった「香水」が、候補選択後も入力欄に表示されてしまいます。
もちろん消せばいいのですが、気持ち悪いです。

また厳密には **「まだ変換できる状態」**（入力文字に水色の下線がある状態）の時に、Enterを押すことで起きてしまうので、変換可能な状態でEnterを押した時に候補が入力欄に反映されてしまうことが問題であると考えるべきなのかもしれません。

そしてつまりはかな文字入力モード特有なので、日本語の記事が一つもないようなこのgemでは想定していないものなのかもしれない。
（正確には英文記事を[日本語に翻訳した記事](https://techracho.bpsinc.jp/hachi8833/2024_10_10/145306)が一つある）

どちらにせよ解決できませんでした。

※表示形式が list や both の時に起きた問題。

### 問題②： 標準の予測変換が消えない
|<font color="gray">デフォルトの予測変換が消えない</font>　　　　　　　　  　　　　　　　　   　　　　　　　　　　　　　　　　　　　　　|
|:-:|
|<img src="https://i.gyazo.com/4d3b9673fbb85216822df7f369c029ea.png" width="172"/>|

指定先が悪いのか autocomplete = "off" など試したものの消えず。
※inline形式では表示されないので、これはどうにかなりそう。

※表示形式が list, both の時に起きた問題。


### 問題③： かな入力モードで、 Shiftキーとローマ字の同時押しによる大文字入力をして文字を続けると、 最初の大文字が連続してしまう
|<font color="gray">かな入力で「Shift + l」→「a」を入力</font>　　　　　　　　  　　　　　　　　   　　　　　　　　　　　　　　　　　　　　　|
|:-:|
|<img src="https://i.gyazo.com/3f209b0c247f9318c9ec37434cc60a2e.png" width="172"/>|

上記の写真は「La」と入力したもの。

「L<INS>Lあ</INS>」となっているものの、「L」と「L」と「A」を入力した訳ではなく、かな入力モードで「Shift + l」→「a」を入力した写真。

※タイミングによっては「L<INS>La</INS>」と認識、表示される。

※表示形式が inline, both の時に起きた問題。


あとは問題というほどでもないですが、
list形式では「部分一致検索」ができるものの、inline形式にすると「部分一致検索」ができなかったり、大文字小文字を別物として識別したりというところが気になりました。


## 実装内容
ItemsとCitiesのデータの検索でcomboboxを使いましたが、
重複した内容になるので本記事ではItemsの内容に絞って記述します。

また検索機能自体についても記述していません。
あくまでもオートコンプリート機能である"hotwire_combobox"に関するコードです。


### 設定
設定は [README](https://github.com/josefarias/hotwire_combobox/blob/main/README.md) 参照
```ruby:Gemfile
# 追記して"budle install"
gem "hotwire_combobox"
```
```:JS 部分をインストールするコマンド
# 自分はesbuildを使ってyarnでインストールしていたので以下のコマンドを実行

$ yarn add @josefarias/hotwire_combobox
```
```js:app/javascript/controllers/application.js
// 追記
application.register("hw-combobox", HwComboboxController)
import HwComboboxController from "@josefarias/hotwire_combobox"
```
```ruby:app/views/layouts/application.html.erb
# 追記（スタイルのため）
<%= combobox_style_tag %>
```
:::note warn
※READMEに記載がありますが、インポートマップを使っていない場合HotwireCombobox の新しいバージョンがリリースされるたびに、npm パッケージと gem の両方を更新する必要があります。
:::
### 機能追加

```ruby:app/views/prices/index.html.erb
# form_with部分はあまり関係ないですが、この中でform.comboboxを使っていたことを示すため記述
<%= form_with model: @search_form, html: { data: { turbo_frame: "prices-list" } }, method: :get, url: prices_path do |form| %>
  ...
  # ここでautocomplete: :listなどとしてオプション指定可能
  <%= form.combobox :item_name, searches_items_path, label: "商品：", include_blank: true, input: { placeholder: "商品名を入力" } %>
  ...
<% end %>
```

```ruby:app/controllers/searches/items_controller.rb
# Citiesのデータ検索もオートコンプリートをつけようとしていたためsearchesに入れているだけ
class Searches::ItemsController < ApplicationController
  def index
    @items = Item.where("name like :query", query: "%#{params[:q]}%")
    # ransackも使っていたので以下の形式でもOK
    # @q = Item.ransack(name_cont: params[:q])
    # @items = @q.result
  end
end
```

```ruby:app/views/searches/items/index.turbo_stream.erb
# Citiesのデータ検索もオートコンプリートをつけようとしていたためsearchesに入れているだけ
# <%= async_combobox_options @items.map { |item| [item.name, item.id] } %>
# 以下の記述にする場合にはapp/models/item.rbにto_combobox_displayの記述が必要
<%= async_combobox_options @items %>
```
```ruby:app/models/item.rb
# 追記
def to_combobox_display
  name
end
```
```ruby:config/routes.rb
# 追記
namespace :searches do
  resources :items, only: :index
  resources :cities, only: :index
end
```

### データ構造（こんなデータで使っていました）
```ruby:config/db/schema.rb
ActiveRecord::Schema[7.2].define(version: 2025_01_17_050937) do
  enable_extension "plpgsql"

  create_table "cities", force: :cascade do |t|
    t.string "name"
  end

  create_table "items", force: :cascade do |t|
    t.string "name"
  end

  create_table "prices", force: :cascade do |t|
    t.bigint "city_id", null: false
    t.bigint "item_id", null: false
    t.integer "price_percentage", null: false
    t.boolean "trend", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["city_id"], name: "index_prices_on_city_id"
    t.index ["item_id"], name: "index_prices_on_item_id"
  end

  add_foreign_key "prices", "cities"
  add_foreign_key "prices", "items"
end
```


## まとめ
オートコンプリート機能をどのように実装するか考えた時に、あまり最近のものがなかったのですが、以下の記事に"hotwire_combobox"を勧めるものがあったため今回使ってみました。

https://discuss.rubyonrails.org/t/text-field-auto-complete-in-rails-7/85925

しかし新しいものを使う際に考慮しなくてはいけないのは、参考資料の関係だけでないということがよくわかったので、選ぶ際にはこれまで以上に慎重に考えたいです。