file_fieldのデザインを変更する

### きっかけ
file_fieldのデザインをデフォルトのものから変更したいと思いましたが、
UIkitでは単数のfile_fieldのclassは用意されているものの、
複数用のfile_fieldは用意されていなかったので、
自分で変更する方法を調べて変更してみました。

初学者のため、誤りがありましたらご指導いただけると幸いです。

### 環境
Rails7.1
UIkit
ActiveStorage
stimulus

前提として
すでにmodelやdb等は作成してあるとして進めさせていただきます。

### 完成系のイメージ
送信ボタンにはUIkitのデザインをあててます。
![スクリーンショット 2024-05-11 14.06.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3624777/43b74c9b-30ee-d72a-c07f-0f0ab5220e00.png)

file選択ボタンの部分をデフォルトから
![スクリーンショット 2024-05-11 14.08.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3624777/66c09549-dd6b-f864-2788-b3e61c151bff.png)
少しUIkitのデザインに寄せた感じにします。

### 実装
要点としては、下記になります。
1. `file_field`に`display: none;`を設定する (`class: 'hidden'`で設定)
2. `file_field`を`label`で囲み、`label`の`class`でcssをあてる
3. `file_field`に表示したい文字は`label`と`file_field`の間に記載する


```erb:new.html.erb
<%= form_with(model: @photo, url: photos_path, local: true) do |f| %>

  <label class='file_field'>
    <span class="ml-5">画像を選択する</span>
    <%= f.file_field :images, multiple: true, class: 'hidden'%>
  </label>
  <div class='uk-margin'>
    <%= f.submit '保存', class: 'uk-button uk-button-default' %>
  </div>
<% end %>
```

`file_field`のボタンの中に表示する文字は`span`で囲った部分になっています。

参考にさせていただいたサイトでは、`file_field`に`class: 'hidden'`を設定すると
デフォルトのデザインは消えるとあったのですが、
私の場合Tailwindを使用していなかったから？か消えなかったため、cssに`hidden`を設定します。

また、新しく充てたいデザインは`class='file_field'`としてcssに記載しています。

```css:photo.scss
.hidden {
  display: none !important;
}

.file_field {
  background-color: rgb(253, 253, 253);
  border: 1px solid #ced4da;
  border-radius: 0.25rem;
  padding: 0.75rem 1rem;
}
.file_field:hover {
  border-color: #80858b;
}
```
`display: none`だけでも消えたのですが、もし消えないようなら`!important`もつけてみてください
`!important`をつけるとスタイルの宣言の優先度を上げて適用されます。

`file_field`と`hover`については、UIkitでの送信ボタンの方にデザインを寄せた感じです。


### javascriptで選択したfile数を表示する

上記の設定までで完成系のイメージのボタンにはなり、送信もできますが、
このままだとファイルを選択した後も「画像を選択する」が表示されていて、
ユーザーが本当に選択されたのか？と戸惑ってしまいます。

file_fieldアイコン内の文字を選択した数を表示できるようにします。

![スクリーンショット 2024-05-11 14.56.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3624777/23bf6330-6308-e856-a31b-3f679f902d95.png)

### 追加する部分
1. 選択された画像数を取得するjsを`photo_contoroller.js`で定義する
2. `photo_controller.js`を`label`部分で適用する
3. `new.html.erb`の`span`と`file_field`にjsで定義した`target`を記載する

```js:photo_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["fileField", "fileLabel"]

  connect() {
    this.fileFieldTarget.addEventListener('change', this.updateFileLabel.bind(this));
  }

  disconnect() {
    this.fileFieldTarget.removeEventListener('change', this.updateFileLabel.bind(this));
  }

  updateFileLabel() {
    let selectedFileCount = this.fileFieldTarget.files.length;
    this.fileLabelTarget.textContent = selectedFileCount + " 個の画像を選択しています";
  }
}
```

`fileField`targetをhtmlのformの`file_field`に設定し、`file_field`でファイルが選択されたら、
chengeイベントが発生し、`updateFileLabel()`が実行されます。

`updateFileLabel()`では、選択されたファイル数(length)を取得して、表示する文字を組み、`fileLabel`をtargetで設定したhtmlの部分の文字が変更されます。

```erb:new.html.erb
<%= form_with(model: @photo, url: photos_path, local: true) do |f| %>

  <%# 変更箇所 data-controller='photo'とすることでphoto_controller.jsの内容が使える %>
  <label class='file_field' data-controller='photo'>
    <%# 変更箇所 表示文字を変える部分にfileLabel targetを設定 %>
    <%# 画像を選択する　　の部分はデフォルトとして表示される %>
    <span class='ml-5' data-photo-target='fileLabel'>画像を選択する</span>
    <%# 変更箇所 ファイル数のchangeをチェックする部分にfileField targetを設定 %>
    <%= f.file_field :images, multiple: true, class: 'hidden', data: { photo_target: 'fileField' } %>
  </label>
  <div class='uk-margin'>
    <%= f.submit '保存', class: 'uk-button uk-button-default' %>
  </div>
<% end %>
```

デザイン変更の手順は以上となります。

### 参考
https://blog.to-ko-s.com/rails-file-field-design/