[JS][Rails]プレビュー付き画像アップロードで画像枚数に制限をかけよう!!


枚数制限の記事無くない?

複数画像アップロード方法は過去の記事で記載しているのですが(良かったら見てください。こちら)、例えば画像を複数選択したいけど、5枚までしか登録できない様にしたいんだよなー...という時ないですか?
私も色々記事をあさったのですが中々良い記事が見つかりませんでした。めちゃくちゃ需要ありそうやのに。
なので、今回その方法をお伝えします!!
あくまで一個人の方法なのでもっと良い方法があれば教えてください!!

完成品

HTMLの用意

haml
     = form_with(model:@item, locale: true , id: 'new_item') do |f|
        .item-image-container
          = f.label :"出品画像"
          %span.form-require 必須
          %p.item-image-container__lead
            最大5枚までアップロードできます
          .item-image-container__unit
            = f.fields_for :item_images do |i|
              %ul

              .item-image-container__unit--guide
                %label(for="image-label")
                  = i.file_field :image, multiple: true, class: 'image-upload-dropfile-hidden', id:"image-label",type: 'file', name: "item_images[image][]"
                  %div.image-upload-dropfile-entire
                  .have-image
                    = icon('fas','camera')
                    %p#d-d-delete ドラッグ&ドロップ
                    %p#click-delete またはクリックしてファイルをアップロード

まず何でもよいのですが、適当にこの様なViewを作ってください。
プレビューの際、画像がアップロードされるとulタグの下にJavaScript側で用意したhtmlを挿入していく形で進めていきます。

JavaScript,jQueryの用意

upload.js
$(function(){
  //DataTransferオブジェクトで、データを格納する箱を作る
  var dataBox = new DataTransfer();
  //querySelectorでfile_fieldを取得
  var file_field = document.querySelector('input[type=file]')
   //fileが選択された時に発火するイベント
   $('#image-label').change(function(){
    //選択したfileのオブジェクトをpropで取得
    var files = $('input[type="file"]').prop('files')[0];

    $.each(this.files, function(i, file){
      //FileReaderのreadAsDataURLで指定したFileオブジェクトを読み込む
      var fileReader = new FileReader();
      //DataTransferオブジェクトに対して、fileを追加
      dataBox.items.add(file)
      //DataTransferオブジェクトに入ったfile一覧をfile_fieldの中に代入
      file_field.files = dataBox.files

      //Fileオブジェクトを読み込む
      fileReader.readAsDataURL(file);

      //読み込みが完了すると、srcにfileのURLを格納
      fileReader.onloadend = function() {
        var src = fileReader.result
        var html =  `<li class="item-image-container__unit--preview" >
                      <div class="item-image-container__unit--caption">
                        <img src="${src}">
                      </div>
                      <div class="image-option">
                        <div  class="image-option__list">
                          <div class="image-option__list--tag">編集</div>
                        </div>
                        <div class="image-option__list">
                          <a class="image-option__list--tag">削除</a>
                        </div>
                      </div>
                    </li>`
        //ulタグの下にhtmlをappendしています。
        $(html).appendTo(".item-image-container__unit ul").trigger("build");

      };
      //DataTransfer構造のデバッグ
      console.log(dataBox);
    });

  });
});


複数アップロードでプレビューをする際に1つ以上の画像を保持し、データを格納しておく為にDataTransferクラスを使用することが画像枚数制限をかける上で大変重要になります。
他はコメントアウトしているので読んで理解していってもらえるとありがたいです。

upload.js
if(dataBox.items.length > 4){
   return false;
}

こちらが枚数制限をかける記述です。
意外と単純でしたね。
console.log(dataBox);の下にこの記述を追加してください。
ではなぜこの様な記述をしたのかを解説していきます。

引数にDataTransferオブジェクトを入れたconsole.logでデバッグを行なった結果この様な構造がコンソール上で取得できました。
lengthに注目してください。データを格納する配列に制限をかけてやれば良いのです。
つまりDataTransferのlengthを取得してそれに対して関係演算子を組み込めば、好きな枚数で制限をかけることが可能になります。

これでもし6枚選択してしまってもちゃんと配列には5枚しか登録されません。
当然DBにも5枚で登録されます。
文字がおかしくなるのはお許しください。
今回の理論を使えば文字を消すことだってできるはずです。