Ruby on Rails 複数画像投稿（carrierwave）とプレビュー機能実装　 new edit（新規、編集、削除） 

## 複数画像で困っていませんか？

[![Image from Gyazo](https://i.gyazo.com/1356ea4fc3b023ce0d69d9e93e4522e8.gif)](https://gyazo.com/1356ea4fc3b023ce0d69d9e93e4522e8)


もし内容がよければ、
いいね！とフォローをいただければ幸いです


完全にオリジナルですが、考え方はあっているはず。


## productと同時に画像をformに投稿する。

`accepts_nested_attributes_for `を記述して、productのformでimageを同時投稿できるようにします。

```ruby:product.rb
class Product < ApplicationRecord
  has_many :images, dependent: :destroy
  accepts_nested_attributes_for :images
end
```

一方のimageは``belongs_to``だけで構いません

```ruby:image.rb
class Image < ApplicationRecord
  mount_uploader :image, ImageUploader
  belongs_to :product
end
```
product_controllerのparamsにもimageを含めましょう

```ruby:product_controller.rb
  private
  def product_params
    params.require(:product).permit(
      :name,
      :description,
      :period,
      :price,
      category_ids: []
      images_attributes: [:name, :id],
    )
    .merge(user_id: current_user.id)
  end
```


これにより、productのformにimageを同時に記述できます。
productモデルのformに``fields_for``を記述して、imageのformを追加します。

```haml:new.html.haml(product)
/productのform
= form_with model: @product, class:"form" do |f|
  /imageのform
  = f.fields_for :images do |f_img|
    = f_img.file_field :name, class: "hidden image_upload"

```

さて、これでいけると思うかもしれませんが、実際には``fields_for``の項目が表示されません。
[![Image from Gyazo](https://i.gyazo.com/dca922fde9675bfd1fed3780bd5b56c8.png)](https://gyazo.com/dca922fde9675bfd1fed3780bd5b56c8)

これは、コントローラー上でimageを作成していないからです。
``= f.fields_for :images do |f_img|``に必要な``:images``を用意できていないので、formに表示されないという問題です。
なので、コントローラーにimageを用意します。

```ruby:product_controller.rb
class ProductsController < ApplicationController
  def new
    @product = Product.new
    @product.images.build
  end
end
```
上記の記述により、imageを作成します。
``@product.images.build``を記述することで、productに紐付けた状態でimageを作成しています。これだと楽ですね。

これで、form上に表示されます。

[![Image from Gyazo](https://i.gyazo.com/bd227a0d2973e434a883ebde46d868df.png)](https://gyazo.com/bd227a0d2973e434a883ebde46d868df)


あとは、cssで綺麗にしたり、javascriptでプレビュー機能を実装させます。







# 複数画像投稿 - new編 -
[![Image from Gyazo](https://i.gyazo.com/8eca3dcc5d0abcb74b267ca76fd1487f.gif)](https://gyazo.com/8eca3dcc5d0abcb74b267ca76fd1487f)

[![Image from Gyazo](https://i.gyazo.com/a5cd30502fb8206fca2ad5a1ac233c93.gif)](https://gyazo.com/a5cd30502fb8206fca2ad5a1ac233c93)

### 出品画面のHTML

```haml:new.html.haml
.new-product
  =render "./registrations/sub-header"
  .main
    .head
      %h2 商品の情報を入力
    = form_with model: @product, class: "form", id: "product-form" do |f|
      .form-image
        .form-image__title
          %label 出品画像
          %span 必須
          .form-image__text 最大10枚までアップロードできます
          = f.fields_for :images do |image|
            .clearfix
              // 写真のプレビューとインプットボタンのul
              %ul#previews
                %li.input
                  // 画像を取り込むインプットボタン
                  %label.upload-label
                    .upload-label__text
                      ドラッグアンドドロップ
                      %br
                      またはクリックしてファイルをアップロード
                      .input-area
                        = image.file_field :name, class: "hidden image_upload"
      
```

### js:プレビューとインプットの仕組み

1. inputだけある状態
2. inputに写真が追加される
3. その画像URLを取得
4. imgタグに画像URLを追加
5. そのimgタグをliにに追加＝プレビューが表示
3. inputをdisplay: none;で非表示にする
4. display: none;にしてもhtml上には残るので、removeClassでinputクラスを除去（jQueryで数を数える時に邪魔）
5. プレビューとわかるクラス、`.image-preview`を付与
6. li（プレビュー）のサイズを指定サイズにする
8. 新しいinputを追加（append)する。
9. 新しいinputのサイズを変更する

完了

####1.inputだけある状態

```haml:new.html.haml
= form_with model: @product, class:"form" do |f| 
 = f.fields_for :images do |f_img|
   .clearfix
     %ul#previews
       %li.input
         %label.upload-label
           .upload-label__text
             クリックしてファイルをアップロード
               .input-area
                 = f_img.file_field :name, class: "hidden image_upload"
```
上記のhamlの``= f_img.file_field :name, class: "hidden image_upload"``がinputです

####2. inputをクリックしてイベント発生

```javascript:js
$(document).on("click", ".image_upload", function () {
```

``= f_img.file_field :name, class: "hidden image_upload"``がinputですが、これをクリックした時にイベントを発生させます。
なぜ、inputクリック時にイベんト発生にしたかというと、``ul``や``li``、``input``がthisで簡単に取得できるからです。

```javascript:
$(document).on("click", ".image_upload", function () {
//素材を用意
    $ul = $("#previews");
    $li = $(this).parents("li");
    $label = $(this).parents(".upload-label");
    $inputs = $ul.find(".image_upload");
    //$liに追加するためのプレビュー画面のHTML
    var preview = $(
      `<div class="image-preview__wapper"><img class="preview"></div><div class="image-preview_btn"><div class="image-preview_btn_edit">編集</div><div class="image-preview_btn_delete">削除</div></div>`
    );
    //次の画像を読み込むためのinput。処理の最後にappendで追加する。
    var append_input = $(
      `<li class="input"><label class="upload-label"><div class="upload-label__text">ドラッグアンドドロップ<br>またはクリックしてファイルをアップロード<div class="input-area"><input class="hidden image_upload" type="file"></div></div></label></li>`
    );

```

#### 画像を選択後、イベント発生

素材配置のためのイベントが

```javascript:js
$(document).on("click", ".image_upload", function () {
```
でしたが、今度はプレビュー用の本命イベントを用意します。

まず、画像を選択されたらイベントを発生させます。


```javascript:画像を読み込んだら発生
$(".image_upload").on("change", function (e) {
```
inputの情報がchangeされたら発生します。なので、画像が選択後に処理が動きます。

```javascript
$(".image_upload").on("change", function (e) {
      //inputで選択した画像を読み込む
      var reader = new FileReader();
      // プレビューに追加させるために、inputから画像ファイルを読み込む。
      reader.readAsDataURL(e.target.files[0]);
```


画像を読み込んだら、プレビュー用imgタグに画像URLを埋め込んであげます。
``reader.readAsDataURL(e.target.files[0]);``で画像URLを取得します。
これをimgタグに挿入することで、画像がプレビューされます。

#### プレビュー用の<img>に画像URLを埋め込む

```javascript:プレビュー用のhtml
var preview = $(
      `<div class="image-preview__wapper"><img class="preview"></div><div class="image-preview_btn"><div class="image-preview_btn_edit">編集</div><div class="image-preview_btn_delete">削除</div></div>`
    );
```
プレビュー用のhtml素材``var preview``と定義し、``<img class="preview">``に画像URLのsrcを追加します。



```javascript:素材に画像srcを追加
var preview = $(
      `<div class="image-preview__wapper"><img class="preview"></div><div class="image-preview_btn"><div class="image-preview_btn_edit">編集</div><div class="image-preview_btn_delete">削除</div></div>`
    );

$(preview).find('.preview').attr('src', e.target.result);
//<img class="preview">をfindで指定。
//attrでimgタグにsrcを挿入
```

jsの``attr``を使って、``src``に``reader.readAsDataURL(e.target.files[0])``で取得した画像URLを挿入しています。これで画像が表示されます。

```html:
<img src="画像URLが追加されて表示される" class="preview">
```

imgタグを用意できたので、この素材をliに追加しましょう。

#### inputのliにimgを挿入してプレビュー表示

では、inputのliにプレビュー用の<img>を``append``で追加しましょう。

```haml:inputのliはこちら
                %ul#previews
                  %li.input //こちらを取得
                    %label.upload-label
                      .upload-label__text
                        クリックしてファイルをアップロード
                        .input-area
                          = f_img.file_field :name, class: "hidden image_upload"
```
上記の``%li.input``を``$li``と定義します。

```javascript
$(document).on("click", ".image_upload", function () {
  //inputタグがあるliを取得
  $li = $(this).parents("li");
```

続いて、この``$li``にimgを追加します。
プレビュー画像の``<img>``を``preview``と定義し、``$li``に``preview``をappendで追加します。

```javascript
$li.append(preview);
```

これでプレビュー用の画像が表示されます。




では、ここまでの処理を合算させると、下記になります。

```javascript:js
  $(document).on('click', '.image_upload', function(){
//素材
    $ul = $('#previews')
    $li = $(this).parents('li');
    $label = $(this).parents('.upload-label');
    $inputs = $ul.find('.image_upload');
    //$liに追加するためのプレビュー画面のHTML
    var preview = $('<div class="image-preview__wapper"><img class="preview"></div><div class="image-preview_btn"><div class="image-preview_btn_edit">編集</div><div class="image-preview_btn_delete">削除</div></div>'); 
    //次の画像を読み込むためのinput。処理の最後にappendで追加する。 
    var append_input = $(`<li class="input"><label class="upload-label"><div class="upload-label__text">ドラッグアンドドロップ<br>またはクリックしてファイルをアップロード<div class="input-area"><input class="hidden image_upload" type="file"></div></div></label></li>`)

//処理
    //inputに画像を読み込んだら、"プレビューの追加"と"新しいli追加"処理が動く
    $('.image_upload').on('change', function (e) {
      //画像URLを取得
      var reader = new FileReader();
      reader.readAsDataURL(e.target.files[0]);
      
      //画像URLをimgに追加
      //画像ファイルが読み込んだら、処理が実行される。 
      reader.onload = function(e){
        $(preview).find('.preview').attr('src', e.target.result);
      }
      
      //inputを保有する、liにimgを追加
      $li.append(preview);
  }
```

#### 不要なinputを非表示にする

``$li``に``<img>``を挿入したので、``<input>``は不要です

```haml:これが不要
  %label.upload-label
    .upload-label__text
      クリックしてファイルをアップロード
      .input-area
        = f_img.file_field :name, class: "hidden image_upload"
```
上記を無くして、プレビュー画像の``<img>``だけを``<li>``に残したい。
なので、``%label.upload-label``をdisplay: none;にして非表示にします。

```javascript
$label = $(this).parents(".upload-label");
$label.css("display", "none");
```
これで画面上から消えました。
しかし、html上にクラスは存在します。
不要なクラスを剥奪します。

一つだけあるinputを下記で取得しています。

```javascript:inputを取得

$input = $ul.find(".input");
```

同様の処理を繰り返すので、``.input``クラスを剥奪します



```haml
  %li.input //inputを非表示にしたので、.inputを剥奪

```
``removeClass``を利用して、クラスを剥奪

```javascript

 $li.removeClass("input");
```

これで``.input```クラスはなくなりました。

代わりに新しいクラスを付与して、プレビュー用のliだとわかるようにします。

```javascript
      $label.css("display", "none");
      $li.removeClass("input");
      $li.addClass("image-preview");
      $lis = $ul.find(".image-preview");

      //widthでプレビューのサイズを指定
      $("#previews li").css({
        width: `114px`,
      });
```

あとは新しいinputを追加します。

####新しいinput（li）を追加する

inputタグを非表示にしたので、新しいinputを追加します。

```javascript
//新しいinputタグ（li）
var append_input = $(
      `<li class="input"><label class="upload-label"><div class="upload-label__text">ドラッグアンドドロップ<br>またはクリックしてファイルをアップロード<div class="input-area"><input class="hidden image_upload" type="file"></div></div></label></li>`
    );

//新しいliを追加
$ul.append(append_input);
```

まだ画像を取得していない、新しいinputを用意できました。


これで処理を繰り返せます。
しかし、プレビュー用のliがあるので、inputのサイズを変更したいです。


#### inputのサイズを変化させる。

[![Image from Gyazo](https://i.gyazo.com/8eca3dcc5d0abcb74b267ca76fd1487f.gif)](https://gyazo.com/8eca3dcc5d0abcb74b267ca76fd1487f)

プレビューのサイズは固定ですが、inputのサイズは徐々に小さく成ります。

***だったら、***

```css

width: 100% - (プレビューのサイズ*プレビュー数);
```
で表現すれば大丈夫ではないか？


これをjQueryで実施します

```javascript
$('#previews li:last-child').css({
      'width': `calc(100% - (20% * (${$lis.length}  )))`
 })

// プレビューはliの最後にあるので、last-childを利用してます。

```
こんな感じですね。

5個になるとinputのサイズがwidth: 100%に戻るので、下記にするといいでしょう。

```javascript
$('#previews li:last-child').css({
      'width': `calc(100% - (20% * (${$lis.length} - 5 )))`
 })
```

1. 1〜4個ときの処理
2. 5個のときの処理
3. 6〜9個ときの処理
4. 10個のときの処理

この4つの条件で変わるので、下記のif文が成立します。

```javascript
      $lis = $ul.find(".image-preview");

      if ($lis.length <= 4) {
        $ul.append(append_input);
        $("#previews li:last-child").css({
          width: `calc(100% - (20% * ${$lis.length}))`,
        });
      } else if ($lis.length == 5) {
        $li.addClass("image-preview");
        $ul.append(append_input);
        $("#previews li:last-child").css({
          width: `100%`,
        });
      } else if ($lis.length <= 9) {
        $li.addClass("image-preview");
        $ul.append(append_input);
        $("#previews li:last-child").css({
          width: `calc(100% - (20% * (${$lis.length} - 5 )))`,
        });
      }
```

これでプレビューの数に応じて、inputのサイズは変化します。

#### 最後の画像しか保存されない。

これで完成かと思われるかもしれませんが、実際に試すと最後の画像しか保存されません。
なぜか？
inputを非表示で隠していますが、名前等で区別されていないので、上書きされていきます。ですから、最後の画像のみ保存されます。
最後のinputの情報のみsubmitされてしまうのです。

ですから、区別してあげましょう

htmlを確認すると

```:inputのhtml
<input class="hidden image_upload" type="file" id="product_images_attributes_0_name" name="product[images_attributes][0][name]">
```

`name="product[images_attributes][0][name]"`とあります。
これでinputを区別しています。
ですから、[0]の番号を変えてあげて、nameを区別してあげます。

```html:こんな感じでnameを区別したい。

<input type="file" id="product_images_attributes_0_name" name="product[images_attributes][0][name]">
<input type="file" id="product_images_attributes_1_name" name="product[images_attributes][1][name]">
<input type="file" id="product_images_attributes_2_name" name="product[images_attributes][2][name]">
<input type="file" id="product_images_attributes_3_name" name="product[images_attributes][3][name]">
<input type="file" id="product_images_attributes_4_name" name="product[images_attributes][4][name]">
```
これを行うために、nameを追加します

```javascript:名前を区別する
      $inputs.each(function (num, input) {
        //nameの番号を更新するために、現在の番号を除去
        $(input).removeAttr("name");
        $(input).attr({
          name: "product[images_attributes][" + num + "][name]",
          id: "product_images_attributes_" + num + "_name",
        });
      });
```
idとnameの番号を変えてあげて無事に区別されました。
これで複数画像が無事にsubmitされます。

#### newのときのjavascriptまとめ

これまでのまとめ

```javascript

  $(document).on("click", ".image_upload", function () {
    //$liに追加するためのプレビュー画面のHTML
    var preview = $(
      `<div class="image-preview__wapper"><img class="preview"></div><div class="image-preview_btn"><div class="image-preview_btn_edit">編集</div><div class="image-preview_btn_delete">削除</div></div>`
    );
    //次の画像を読み込むためのinput。処理の最後にappendで追加する。
    var append_input = $(
      `<li class="input"><label class="upload-label"><div class="upload-label__text">ドラッグアンドドロップ<br>またはクリックしてファイルをアップロード<div class="input-area"><input class="hidden image_upload" type="file"></div></div></label></li>`
    );
    $ul = $("#previews");
    $li = $(this).parents("li");
    $label = $(this).parents(".upload-label");
    $inputs = $ul.find(".image_upload");
    //inputに画像を読み込んだら、"プレビューの追加"と"新しいli追加"処理が動く
    $(".image_upload").on("change", function (e) {
      //inputで選択した画像を読み込む
      var reader = new FileReader();

      // プレビューに追加させるために、inputから画像ファイルを読み込む。
      reader.readAsDataURL(e.target.files[0]);

      //画像ファイルが読み込んだら、処理が実行される。
      reader.onload = function (e) {
        //previewをappendで追加する前に、プレビューできるようにinputで選択した画像を<img>に'src'で付与させる
        $(preview).find(".preview").attr("src", e.target.result);
      };

      //inputの画像を付与した,previewを$liに追加。
      $li.append(preview);

      //プレビュー完了後は、inputを非表示にさせる。これによりプレビューだけが残る。
      $label.css("display", "none");
      $li.removeClass("input");
      $li.addClass("image-preview");
      $lis = $ul.find(".image-preview");

      $("#previews li").css({
        width: `114px`,
      });
      //"ul"に新しい"li(inputボタン)"を追加させる。
      if ($lis.length <= 4) {
        $ul.append(append_input);
        $("#previews li:last-child").css({
          width: `calc(100% - (20% * ${$lis.length}))`,
        });
      } else if ($lis.length == 5) {
        $li.addClass("image-preview");
        $ul.append(append_input);
        $("#previews li:last-child").css({
          width: `100%`,
        });
      } else if ($lis.length <= 9) {
        $li.addClass("image-preview");
        $ul.append(append_input);
        $("#previews li:last-child").css({
          width: `calc(100% - (20% * (${$lis.length} - 5 )))`,
        });
      }

      //inputの最後の"data-image"を取得して、input nameの番号を更新させてる。
      $inputs.each(function (num, input) {
        //nameの番号を更新するために、現在の番号を除去
        $(input).removeAttr("name");
        $(input).attr({
          name: "product[images_attributes][" + num + "][name]",
          id: "product_images_attributes_" + num + "_name",
        });
      });
    });
  });
```
### コントローラーの処理


```ruby:product_controller.rb
  def new
    @product = Product.new
    @product.images.build
  end


  def create
    @product = Product.new(product_params)
    if @product.save
      redirect_to users_path, notice: "商品を出品しました"
    else 
      render 'new'
    end
  end


  private

  def product_params
    params.require(:product).permit(
      :name,
      images_attributes: [:name, :id],
    )
    .merge(seller_id: current_user.id)
  end
```

### 具体的に見てみる


```javascript:product-new-images.js


$(document).on('turbolinks:load', function(){
  // 下記はedit用です。できれば別ファイルで作成することを推奨。バグの元
  var append_input = $(`<li class="input"><label class="upload-label"><div class="upload-label__text">ドラッグアンドドロップ<br>またはクリックしてファイルをアップロード<div class="input-area"><input class="hidden image_upload" type="file"></div></div></label></li>`)
  $ul = $('#previews')
  $lis = $ul.find('.image-preview');
  $input = $ul.find('.input');
  if($input.length == 0){
    if($lis.length <= 4 ){
      $ul.append(append_input)
      $('#previews .input').css({
        'width': `calc(100% - (20% * ${$lis.length}))`
      })
    }
    else if($lis.length == 5 ){
      $ul.append(append_input)
      $('#previews .input').css({
        'width': `100%`
      })
    }
    else if($lis.length <= 9 ){
      $ul.append(append_input)
      $('#previews .input').css({
        'width': `calc(100% - (20% * (${$lis.length} - 5 )))`
      })
    }
  }

  //newにおいては、下記が本題

  // プレビュー機能
  //'change'イベントでは$(this)で要素が取得できないため、 'click'イベントを入れた。
  //これにより$(this)で'input'を取得することができ、inputの親要素である'li'まで辿れる。
  
  $(document).on('click', '.image_upload', function(){
    //inputの要素はクリックされておらず、inputの親要素であるdivが押されている。
    //だからdivのクラス名をclickした時にイベントが作動。
    //div（this）から要素を辿ればinputを指定することが可能。

    //$liに追加するためのプレビュー画面のHTML。横長でないとバグる
    var preview = $('<div class="image-preview__wapper"><img class="preview"></div><div class="image-preview_btn"><div class="image-preview_btn_edit">編集</div><div class="image-preview_btn_delete">削除</div></div>'); 
    //次の画像を読み込むためのinput。 
    var append_input = $(`<li class="input"><label class="upload-label"><div class="upload-label__text">ドラッグアンドドロップ<br>またはクリックしてファイルをアップロード<div class="input-area"><input class="hidden image_upload" type="file"></div></div></label></li>`)
    $ul = $('#previews')
    $li = $(this).parents('li');
    $label = $(this).parents('.upload-label');
    $inputs = $ul.find('.image_upload');
    //inputに画像を読み込んだら、"プレビューの追加"と"新しいli追加"処理が動く
    $('.image_upload').on('change', function (e) {
      //inputで選択した画像を読み込む
      var reader = new FileReader();
      
      
      // プレビューに追加させるために、inputから画像ファイルを読み込む。
      reader.readAsDataURL(e.target.files[0]);
      
      //画像ファイルが読み込んだら、処理が実行される。 
      reader.onload = function(e){
        //previewをappendで追加する前に、プレビューできるようにinputで選択した画像を<img>に'src'で付与させる
        // つまり、<img>タグに画像を追加させる
        $(preview).find('.preview').attr('src', e.target.result);
      }
      
      //inputの画像を付与した,previewを$liに追加。
      $li.append(preview);
      
      //プレビュー完了後は、inputを非表示にさせる。これによりプレビューだけが残る。
      $label.css('display','none'); // inputを非表示
      $li.removeClass('input');     // inputのクラスはjQueryで数を数える時に邪魔なので除去
      $li.addClass('image-preview'); // inputのクラスからプレビュー用のクラスに変更した
      $lis = $ul.find('.image-preview'); // クラス変更が完了したところで、プレビューの数を数える。 
      $('#previews li').css({
        'width': `114px`
      })




      //"ul"に新しい"li(inputボタン)"を追加させる。
      if($lis.length <= 4 ){
        $ul.append(append_input)
        $('#previews li:last-child').css({
          'width': `calc(100% - (20% * ${$lis.length}))`
        })
      }
      else if($lis.length == 5 ){
        $li.addClass('image-preview');
        $ul.append(append_input)
        $('#previews li:last-child').css({
          'width': `100%`
        })
      }
      // ９個のプレビューのとき、1個のinputを追加。最後の数は9です。
      else if($lis.length <= 9 ){
        $li.addClass('image-preview');
        $ul.append(append_input)
        $('#previews li:last-child').css({
          'width': `calc(100% - (20% * (${$lis.length} - 5 )))`
        })
      }
      
      //inputの最後の"data-image"を取得して、input nameの番号を更新させてる。
      // これをしないと、それぞれのinputの区別ができず、最後の1枚しかDBに保存されません。
      // 全部のプレビューの番号を更新することで、プレビューを削除して、新しく追加しても番号が1,2,3,4,5,6と綺麗に揃う。だから全部の番号を更新させる
      $inputs.each( function( num, input ){
        //nameの番号を更新するために、現在の番号を除去
        $(input).removeAttr('name');
        $(input).attr({
          name:"product[images_attributes][" + num + "][name]",
          id:"product_images_attributes_" + num + "_name"
        });
      });
    })
  })
});

```


これで完了です。

```:補足
//inputの最後の"data-image"を取得して、input nameの番号を更新させてる。
// これをしないと、それぞれのinputの区別ができず、最後の1枚しかDBに保存されません。
```
上記について補足します

```haml:hamlでformを記述
#Rails方式でname inputを書くと(Product)
= f.text_field :name
```

```html:HTMLで実際に表示されるのは下記
<input type="text" name="product[name]">
```

要点
つまり、inputは``name``で識別されている。
この``name``が全く同じ場合、最後に取得した情報だけparamsに渡る
なので、nameに数字を混ぜて、それぞれ異なる情報としている

```html:実際の例
<input type="file" name="product[images_attributes][0][name]">
<input type="file" name="product[images_attributes][1][name]">
<input type="file" name="product[images_attributes][2][name]">
<input type="file" name="product[images_attributes][3][name]">
<input type="file" name="product[images_attributes][4][name]">
```
images_attributesの中に順番に入る

続いては削除する際の動作にいきましょう！




# 複数画像投稿 - 削除編 -

[![Image from Gyazo](https://i.gyazo.com/9114e72c4e94a143491e995642244ff2.gif)](https://gyazo.com/9114e72c4e94a143491e995642244ff2)


inputに入った画像は、
#### jQueryじゃ更新できない！！！

だから、inputごと削除してやる。

```javascript
$li.remove();
```
これでliを削除できます。
inputごとliを削除したので、新しいli.inputを追加します。

```javascript:product-images-delete.js
//削除ボタンをクリックしたとき、処理が動く。
$(document).on('click','.image-preview_btn_delete',function(){
  var append_input = $(`<li class="input"><label class="upload-label"><div class="upload-label__text">ドラッグアンドドロップ<br>またはクリックしてファイルをアップロード<div class="input-area"><input class="hidden image_upload" type="file"></div></div></label></li>`)
  $ul = $('#previews')
  $lis = $ul.find('.image-preview');
  $input = $ul.find('.input');
  $ul = $('#previews')
  $li = $(this).parents('.image-preview');


  //"li"ごと削除して、previewとinputを削除させる。
  $li.remove();

  // inputボタンのサイズを更新する、または追加させる
  // まずはプレビューの数を数える。
  $lis = $ul.find('.image-preview');
  $label = $ul.find('.input');
  if($lis.length <= 4 ){
    // inputのサイズを変更
    $('#previews li:last-child').css({
      'width': `calc(100% - (20% * ${$lis.length}))`
    })
  }
  else if($lis.length == 5 ){
    // inputのサイズを変更
    $('#previews li:last-child').css({
      'width': `100%`
    })
  }
  else if($lis.length < 9 ){
    // inputのサイズを変更
    $('#previews li:last-child').css({
      'width': `calc(100% - (20% * (${$lis.length} - 5 )))`
    })
  }
  else if($lis.length == 9 ){
    $ul.append(append_input) // 9個の時だけ、新しいinputを追加してやる
    $('#previews li:last-child').css({
      'width': `calc(100% - (20% * (${$lis.length} - 5 )))`
    })
  }
});

```


追記：編集方法


# 複数画像　- edit編 -
[![Image from Gyazo](https://i.gyazo.com/1356ea4fc3b023ce0d69d9e93e4522e8.gif)](https://gyazo.com/1356ea4fc3b023ce0d69d9e93e4522e8)

一番、難しいのはここ

edit画面では、投稿済み画像を削除しても”DB”上には残ります。
だって、HTMLしか消してないんだもん。
そりゃあ、DBは残るよ。
これで、だいぶムッチャ苦しんだ。

ここがハマるポイント。


じゃあ、どうやって消す？？


### 考え方

考えたのが、paramsとDBを比較して、投稿済み画像を消しているようなら、
コントローラー上で画像をdestroyして削除する
paramsのハッシュですが、

- 投稿済み画像： imageハッシュ。これはform上でそう設定してます。
- 新しく追加した画像：images_attributesハッシュ


でハッシュ名が異なります。





imageハッシュの分は、投稿済み画像ですが、削除されていないか確認する。

- image ハッシュがない場合、edit画面で投稿済み画像を全部消している　= いらないようなので、コントローラーで画像を全部削除する
- image ハッシュがある場合、 一部消しているかもしれないので、paramsと比較する。なければ削除。


images_attributesの分は、newと同じでそのまま追加。


### 流れ
1. imageハッシュとimage_attributeハッシュ両方がない場合は、やり直しさせる。
2. 片方どちらかある場合は、画像が少なくとも1枚あるということなので処理を進める
3. 投稿済み画像のハッシュである、imageがあるか？ if文で確認。
4. image ハッシュがある場合は、paramsとDBのIDを比較。一致しない分は削除する
5. image ハッシュがない場合は、画像を全部削除
6. update処理をする。
7. 新しい画像が追加されて、更新完了

完了




### HTML

```haml:edit.html.haml

.new-product
  =render "./registrations/sub-header"
  .main
    .head
      %h2 商品の情報を入力
    = form_with model: @product, class: "form", id: "product-form-edit" do |f|
      .form-image
        .form-image__title
          %label 出品画像
          %span 必須
          .form-image__text 最大10枚までアップロードできます
          .clearfix
            %ul#previews
              = f.fields_for :image do |image|
                - @product.images.each_with_index do |img, i|
                  %li.image-preview
                    %label.upload-label{style:"display: none;"}
                      .upload-label__text
                        ドラッグアンドドロップ
                        %br
                        またはクリックしてファイルをアップロード
                        .input-area
                          = image.file_field :name, value: img.name ,class: "hidden image_upload"
                          = image.hidden_field :id, value: img.id, name:"product[image][#{i}]"
                    .image-preview__wapper
                      = image_tag img.name.to_s, class:"preview"
                    .image-preview_btn
                      .image-preview_btn_edit 編集
                      .image-preview_btn_delete 削除

```


### コントローラー

```ruby:product_controller.rb

  def update
    @parents = Category.where(ancestry: nil)
    # each do で並べた画像が image
    # 新しくinputに追加された画像が image_attributes
    # この二つがない時はupdateしない
    if params[:product].keys.include?("image") || params[:product].keys.include?("images_attributes") 
      if @product.valid?
        if params[:product].keys.include?("image") 
        # dbにある画像がedit画面で一部削除してるか確認
          update_images_ids = params[:product][:image].values #投稿済み画像 
          before_images_ids = @product.images.ids
          #  商品に紐づく投稿済み画像が、投稿済みにない場合は削除する
          # @product.images.ids.each doで、一つずつimageハッシュにあるか確認。なければdestroy
          before_images_ids.each do |before_img_id|
            Image.find(before_img_id).destroy unless update_image_ids.include?("#{before_img_id}") 
            
          end
        else
          # imageハッシュがない = 投稿済みの画像をすべてedit画面で消しているので、商品に紐づく投稿済み画像を削除する。
          # @product.images.destroy = nil と削除されないので、each do で一つずつ削除する
          before_images_ids.each do |before_img_id|
            Image.find(before_img_id).destroy 
          end
        end
        @product.update(product_params)
        @size = @product.categories[1].sizes[0]
        @product.update(size: nil) unless @size
        redirect_to item_product_path(@product), notice: "商品を更新しました"
      else
        render 'edit'
      end
    else
      redirect_back(fallback_location: root_path,flash: {success: '画像がありません'})
    end
  end
```

ポイントは下記になります

```ruby

       if params[:product].keys.include?("image") 
        # dbにある画像がedit画面で一部削除してるか確認
          update_images_ids = params[:product][:image].values #投稿済み画像の残り
          before_images_ids = @product.images.ids
          #  商品に紐づく投稿済み画像が、投稿済みにない場合は削除する
          # before_images_ids.each do doで、一つずつimageハッシュにあるか確認。なければdestroy
          before_images_ids.each do |before_img_id|
            Image.find(before_img_id).destroy unless update_image_ids.include?("#{before_img_id}") 
          end
        else
          # imageハッシュがない = 投稿済みの画像をすべてedit画面で消しているので、商品に紐づく投稿済み画像を削除する。
          # @product.images.destroy = nil と削除されないので、each do で一つずつ削除する
          before_images_ids.each do |before_img_id|
            Image.find(before_img_id).destroy 
          end
        end
```

valueはhtmlで定義をしています

```haml:html
= f.fields_for :image do |image|
  - @product.images.each_with_index do |img, i|
    .input-area
      = image.file_field :name, value: img.name ,class: "hidden image_upload"
      = image.hidden_field :id, value: img.id, name:"product[image][#{i}]"