Railsで画像のプレビュー機能を実装

##前提
- ruby on rails 6.0.0 を使用。
- ユーザー機能はdeviseにより導入されているものとする。
- viewファイルは全てhaml形式とする。
- ちなみに使っているのはMacBook Air(Retina, 13-inch, 2020)です。

##はじめに
<a href="https://qiita.com/silvercrow222/items/516d424a59ffe9fe8fb5">前回(part4)</a>のあらすじです。複数画像を含んだ商品情報の登録、編集機能までが実装できました。ひと通りの作業は終わった感じですね。

ちなみに前置きや手順などは <a href="https://qiita.com/silvercrow222/items/55aeb5d13ec2bde305db"">part1</a> に詳しく記載してあるので気になったら是非見てやってください。

さぁ！残るはプレビューのみだ！

##プレビューします
それではまずjsから記述していきます。
やることしてはフォームのときと似たような感じです。

html生成用の関数を作って、新しいフォーム同時に表示させます。画像を削除した場合はプレビューも消えるようにしましょう。

```javascript:app/assets/javascripts/product.js
$(function() {
  // ~省略~
  const buildImg = (index, url)=> {
    const html = `<img data-index="${index}" src="${url}" width="100px" height="100px">`;
    return html;
  }
  // ~省略~
  $('#image-box').on('change', '.file', function(e) {
    const targetIndex = $(this).parent().data('index');
    const file = e.target.files[0];
    const blobUrl = window.URL.createObjectURL(file);

    if (img = $(`img[data-index="${targetIndex}"]`)[0]) {
      img.setAttribute('src', blobUrl);
    } else {
      $('#image-box').append(buildFileField(fileIndex[0]));
      fileIndex.shift();
      fileIndex.push(fileIndex[fileIndex.length - 1] + 1)
    }
  });
  // ~省略~
  $('#image-box').on('click', '.remove', function() {
    // ~省略~
    $(`img[data-index="${targetIndex}"]`).remove();
  });
});
```
長くなってしまいましたがこんなところです。複雑なのは真ん中の部分だけなのでそう身構えることもないと思います。

それでは順番に解説をつけていきましょう。

```javascript
const buildImg = (index, url)=> {
    const html = `<img data-index="${index}" src="${url}" width="100px" height="100px">`;
    return html;
```
まずはhtmlを生成するための関数ですが、特に難しいことは何もないので大丈夫だと思います。引数として渡されたurlで、大きさを指定しながら画像を表示しています。

```javascript
$('#image-box').on('change', '.file', function(e) {
    // フォームに割り振られた固有のインデックスを取得。
    const targetIndex = $(this).parent().data('index');
    // 画像ファイルのweb上におけるURLを取得。
    const file = e.target.files[0];
    const blobUrl = window.URL.createObjectURL(file);
    // 該当するインデックスを持つ画像の有無で条件分岐
    if (img = $(`img[data-index="${targetIndex}"]`)[0]) {
      // 前行で取得した画像のURLを差し替える。
      img.setAttribute('src', blobUrl);
    } else {
      $('#previews').append(buildImg(targetIndex, blobUrl));
      $('#image-box').append(buildFileField(fileIndex[0]));
      fileIndex.shift();
      fileIndex.push(fileIndex[fileIndex.length - 1] + 1)
```
次に画像を選択した際の処理です。内容としてはこんな感じ。

難しいのは5,6行目のURLの取得ですが、ここに関してはいまいち自分でも理解できていないので後ほど詳しく調べようと思っています。動作自体はこれでうまくいくのでひとまず安心してください。

ちなみにelse以降の部分は元々あった記述にプレビュー表示を追加しただけなので詳しくは前のpartをご覧くださいな。

こいつらをelse以下に移動した理由としては、今までの状態では画像を差し替えるだけでも新しいフォームが表示されてしまっていたためです。なのでこいつらをelse以下に置くことで、画像追加時のみフォームが追加されるようにしてます。

```javascript
  $(`img[data-index="${targetIndex}"]`).remove();
```
最後はこいつですが、ただ削除ボタンに合わせてプレビューを削除しているだけです。ほんとにそれだけ。

これでjsの処理が完成したので、今度は編集画面にあらかじめ表示されるべきプレビューを追加していきます。

```haml:app/views/products/_form.html.haml
= form_with model: @product, local: true do |f|
  = f.text_field :name, placeholder: 'name'
  #image-box
    #previews
      - if @product.persisted?
        - @product.images.each_with_index do |image, i|
          = image_tag image.src.url, data: { index: i }, width: '100', height: '100'
　　 = f.fields_for :images do |i|
      // ~省略~
  = f.submit 'SEND'
```
追記したのは *@product.persisted?* の部分です。productに紐づいた画像をurlで1枚ごと取り出し、image_tagを用いて表示させています。

ちなみに.each_with_index とは、引数を二つ設定することで、.eachと同時に一つずつ番号を割り振るメソッドです。

さて、これでプレビュー機能も実装完了となります。
ということは、

##最後に
遂に完成です！！結構な時間がかかりましたが、なんとか終えることができました。ふぅ。

完成とは言いつつもできたのは機能面だけなので、あとはゆっくりマークアップをしていく感じになります。

とはいえ仕様書の内容は全て実装することができました。実際のところ大して難しいことをしてるわけでもないのに長々と書いてしまってすみません。

いないとは思いますが、最後まで読んだ方がいらっしゃいましたら長らくどうもありがとうございました。何かの参考になれば幸いです。