LoginSignup
1
2

More than 3 years have passed since last update.

【Rails】テーブルを分けて複数の画像をアップロードする

Last updated at Posted at 2020-05-15

複数枚の写真を一度に保存する機能の実装において、
【itemテーブル】と【item_imageテーブル】の二つに
分けて複数枚の写真を保存する機能を実装した際の手順をまとめたのでご紹介します。

完成形

○ 商品写真は最大で10枚まで投稿可能。
○ 一つのファイルフィールドに一つの写真でアップロードしていく。
○ 5枚投稿時点で下段に切り替わる。
Image from Gyazo

1. HTMLの用意

sample.haml

~ 省略 ~

.image-container
  .image-container_box
    .form-title
      %span.box-form-explanaion
        商品画像
      %span.indispensable
        必須
    %p.image-container_box_message
      最大10枚までアップロードできます
    .image-container_box_alart-10
      ※ 1枚目は必須です
  .image-container_unit-man    【写真が順にプレビューされていく箱】
    .item-image-container__unit.preview-0  【写真一枚が入る箱 ※投稿ごとに生成されていく】
      = f.fields_for :item_images do |i|
        .image-container__unit--guide
          = i.file_field :image, class: 'img-man', id:"image-label-0",type: 'file'
          .have-image
            %i.fas.fa-camera

~ 省略 ~

◉【写真一枚が入る箱】に2つクラスがあるのは、2枚目以降では毎回クラス名を
変えていくためです。
◉この仕様では、1つのinputに写真は1つなので、multiple属性は付けていません。

2. CSSの用意

sample.scss
  .image-container {
    padding: 40px;
    border-bottom: solid 1px rgb(235, 235, 235);
    &_box {
      &_message {
        height: 19px;
        margin: 16px 0 5px;
        font-size: 14px;
        line-height: 1.4em;
        display: block;
      }
      &_alart-10 {
        margin-bottom: 4px;
        font-size: 14px;
        font-weight: bold;
        color:red;
      }
    }
    &_unit-man {
      min-height: 152px;
      display: flex;
      flex-direction: row;
      flex-wrap: wrap;
      .item-image-container__unit {
        align-content: center;
        align-items: center;
        background-color: rgb(245, 245, 245);
        display: flex;
        justify-content: flex-start;
        height: 150px;
        width: 118px;
        margin-left: 5px;
        margin-bottom: 2px;
        justify-content: center;
        position: relative;
        border-width: 1px;
        border-style: dashed;
        border-color: rgb(204, 204, 204);
        border-image: initial;

        .have-image {
          position: absolute;
            left: 32px;
            top: 40px;
          z-index: 0;
          cursor: pointer;
          font-size: 16px;
          .fas.fa-camera {
            margin-left: 16px;
            font-size: 1.2rem;
          }
          .fas.fa-camera:hover {
            cursor: pointer;
            transform: scale(1.3, 1.3);
            transition: all 0.1s ease 0s;
          }
        }
        .item-image-container__unit__image {
          z-index: 1;
          height: 145px;
          width: 100%;
          margin: 0 3px;
          background-color: #ffffff;
          position: relative;
          img {
            width: 100%;
            height: auto;
          }
          .image-option__list--tag {
            width: 100%;
            background-color: lightblue;
            text-align: center;
            position: absolute;
              bottom: 0;
              left: 0;
          }
          .image-option__list--tag:hover {
            cursor: pointer;
            transform: scale(1.0, 1.0);
            transition: all 0.1s ease 0s;
            background-color: #ea352d;
            color:#ffffff;
          }

        }
      }
      .item-image-container__unit {
        input{
          display: none;
        }
      }
    }
  }

「.unit-man」に適用されている、[flex-direction: row;]と[flex-wrap: wrap;]により、
写真が既定の幅まで投稿されたら下段に折り返してくれます。

3. JSでプレビューさせる

sample.js
$(function(){


~ 省略 ~


  var dataBox = new DataTransfer();  //データ用の箱を作る
  $(document).on('change', '.img-man', function(){    //inputの中身が変化したら発火する
    $.each(this.files, function(i, file){
      var fileReader = new FileReader();
      dataBox.items.add(file)    
      fileReader.readAsDataURL(file);     //ファイルの読み込み
      fileReader.onloadend = function() {     //読み込み完了すると発火
        var num = $('.item-image-container__unit').length  //写真の枚数をnumに代入
        var src = fileReader.result   //写真のデータをsrcに代入
        var html =  `<div class="item-image-container__unit__image">
                        <img src="${src}">
                      <div class="image-option__list--tag btn-${num}">削除</div>
                    </div>`

        var field = `<div class="item-image-container__unit preview-${num}">
                      <div class="image-container__unit--guide">
                        <label for="image-label-${num}">
                          <input class="img-man" id="image-label-${num}" type="file" name="item[item_images_attributes][${num}][image]">
                          <div class="have-image">
                            <i class="fas fa-camera"></i>
                          </div>
                        </label>
                      </div>
                    </div>`
        $(html).appendTo(`.preview-${num - 1}`)  //枚数で該当するクラスに写真を追加する
                      
        if (num < 10 ) {     //10枚分まで新しいinputの生成を行う
          $(field).appendTo('.image-container_unit-man')
        }
      };
    });
  });
  //削除機能 
  $(document).on("click", ".image-option__list--tag", function(){  //削除ボタンクリックで発火
    var num = $('.item-image-container__unit').length
    var field_2 = `<div class="item-image-container__unit preview-0">
                    <div class="image-container__unit--guide">
                      <label for="image-label">
                        <input class="img-man" id="image-label-0" type="file">
                        <div class="have-image">
                          <i class="fas fa-camera"></i>
                        </div>
                      </label>
                    </div>
                  </div>`
    $(this).parent().parent().remove();  //写真が入っている箱ごと削除
    $(".item-image-container__unit").removeClass(`preview-${num - 1}`)
    $(".item-image-container__unit").addClass(`preview-${num - 2}`)
    if(num == 1) {   //全部削除した時に新たに1つフィールドを生成する
      $(field_2).appendTo('.image-container_unit-man')
    }
  });

~ 省略 ~

◉変数【num】は、写真の読み込みごとに代入され、その度に横に生成される
【preview-${num}】クラスが書き換わって横に生成されるようになっています。

◉ 下部の10枚制限の記述によって、10枚までアップされるとinputの生成が止まります。
また、multiple属性が付いていないので写真の一括選択はできなくなります。

◉削除機能に関しても、クラス名の書き換えを行う必要があります。


登録枚数に関しては、モデルにも別でバリーデーションは書いてあります。

sample.rb
  def  item_images_number
    errors.add(:item_images, "は10枚までです") if item_images.size > 10
  end

以上で終了です。
ご覧いただきありがとうございました。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2