1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

「1つの投稿に対し複数の画像を紐付ける機能の実装」(Label:forとidの紐付編)

Last updated at Posted at 2020-08-21

目的

某スクールの複数投稿の実装の仕方がとても苦労したので、メモとして残して置こうと思います。
今回は、label:forとidを使った紐付けについてです。




カーソルが出るので分かりやすい動画だと思います!
Shimokita.php
カメラのアイコンをクリックしたら、input[type=file]がクリックされて、
次にもう一度、カメラのアイコンをクリックすると新しく形成されたinput[type=file]に、
カーソルが移動してそれがクリックされているというのが分かると思います!
なので、3枚目、4枚目、5枚目っとこれが繰り返されるという事になります。


余談ですが、画像が挿入される度に、新しくinput[type=file]が形成されているのは、
javascriptsで画像が挿入されれば、新しいinput[type=file]が形成されるというようなコードを書いている為です。



ステップ

※前回の「1つの投稿に対し複数の画像を紐付ける機能の実装」 の投稿の続きになりますので
こちらの実装には、前回の記事の全てコード使用しますので前回の記事を参照ください

①. viewにlabelダグとfor属性を使用して、フォーム部品と項目名(ラベル)を関連付ける
②. javascriptsのattr()メソッドを使用して、属性を変更させる
の2のステップで行っていきます。


1つずつ行っていきましょう!




ステップ①. viewにlabelダグとfor属性を使用して、フォーム部品と項目名(ラベル)を関連付ける

出品画面のnew.html.hamlにlabelダグとfor属性を使用して、idと関連付ける値を入れていきましょう!
追加するコードはこちらです
↓↓↓

%label{for: "item_images_attributes_0_item_image"}
new.html.haml
   .contents__body__input-list
      %p.theme 出品情報
      = form_with(model: @item, local: true, class: "contents__body__input-list__form") do |f|
        .input-list__images
          %p.input-list__images--notice 最大5枚までアップロードできます
          #image-box
            #image-box-1
              .item-num-0#image-box__container
                %label{for: "item_images_attributes_0_item_image"}
                  %i.fas.fa-camera#image-box-icon
            #image-box-2
              = f.fields_for :images do |i|
                .input-field__contents{"data-index" => "#{i.index}"}
                  = i.file_field :item_image, type: 'file'
        .product-name
          %p.letter--1 商品名
          %p.letter--2 ※必須
          = f.text_field :name, placeholder: "商品名(必須 40文字まで)", class: "form--text"
        .introduction
          %p.introduction--letter1 商品説明
          %p.introduction--letter2 ※必須
          = f.text_area :introduction, placeholder: "商品の説明(必須 1,000文字以内)(色、素材、重さ、定価、注意点など)", class: "introduction__form-area"
        .form-separator
        .form-post
          = f.submit "確認する", class: "form-post--send"

forの後の「item_images_attributes_0_item_image」は、付与されているidです。
どこで、このidが付与されているかと言いますと、
これは、fields_forで生成されたフォームには、
name属性についてはitem[images_attributes][0][item_image]という値が、
id属性についてはitem_images_attributes_0_item_imageという値が付与されます。
このように最初から0という添え字が当てられるという訳です。

さらに言うと、fields_forを利用して作成されたフォームから来る値は、○○s_attributes: [:××]という形でparamsに入ります。
○○は関連付く側のモデルの名前、××にはフォームに対応するカラムの名前が入ります。
是非覚えておいて下さい!


これで、最初に形成されたinput[type=file]とカメラのアイコンが紐付けられました!
画像で確認をして見ましょう!
先ずは、検証画面を開いて、、、
ファイル名

アップの画像を見てみると、、、
ファイル名
labe forとinput[type=file]のidの値が一緒になっていますね!
これで紐付け完了です!


カメラアイコンとinput[type=file]の紐付けされる理論は理解出来たかと思います。
次はこれを、どうやって新しく追加されたinput[type=file]と紐付けていくのかを説明していきます。






ステップ②. javascriptsのattr()メソッドを使用して、属性を変更させる

続いて、ステップ②に入りますが、その前に新しく形成されたinput[type=file]が
どういう値なのか見てみましょう!


先ずは、検証画面を開いて、、、
ファイル名

アップの画像を見てみると、、、
ファイル名
値の中の数字がになっているのが分かると思います!
次にまた新しく追加されれば、今度は2という数字になります。
このように、別々の数字が導入される事で、別々のinput[type=file]が形成されるので、
カメラのアイコンとそれぞれのinput[type=file]の紐づけが可能になるのです!
勿論これは、只追加していけばこのようになる訳ではなく、JSのコードの記述でこのようになります。


ちなみに、もし異なる数字を導入するJS記述がなかったら、只item_images_attributes_0_item_imageという値のinput[type=file]がたくさん形成されるだけになります。
このなると、DBに保存した時に最後に投稿した画像のデータのみが保存されるだけになってしまいます。




では、JSのどの記述が別々の数字を導入させているのかと言いますと、

image-post.js
$(document).on('turbolinks:load', function(){
  $(function(){
      //DataTransferオブジェクトで、データを格納する箱を作る
      var dataBox = new DataTransfer();
      //querySelectorでfile_fieldを取得し変数に入れている
      var file_field = document.querySelector('input[type=file]')
      
      //file(#image-box)が変化した時に発火するイベント
      $('#image-box').on("change",`input[type="file"]`,function(){
      //選択したファイル情報を取得し変数に格納 - 最後の[0]は最初のファイルという意味
      var files = $('input[type=file]').prop('files')[0];
      
      //$.each()メソッドで、配列やハッシュに対して繰り返し処理を行う
      $.each(this.files, function(i,file){
      
        //FileReaderのreadAsDataURLで指定したFileオブジェクトを読み込む
        var fileReader = new FileReader();
      
        //DataTransferオブジェクトに対して、fileを追加
        dataBox.items.add(file)

        // file_fieldのnameに動的なindexをつける為の配列
        let num = [1,2,3,4,5,6,7,8];
        let img = [0,1,2,3,4,5,6,7];
        // data()メソッドでindex番号を取得、lastを使って最後のinput[type="file"]を取得して変数に入れる
        lastIndex = $('.input-field__contents:last').data('index');
        anotherIndex = $('.input-field__contents:last').data('index');
        //splice()メソッドを使い配列から要素を削除・追加して組み替える
        num.splice(0, lastIndex);
        img.splice(0, anotherIndex);

        // 画像用のinputにそれぞれ異なる番号付与する記述
        const buildFileField = (index)=> {
          const html = `<div data-index="${index}" class="input-field__contents">
                          <input id:"img-file" type="file"
                          name="item[images_attributes][${index}][item_image]"
                          id="item_images_attributes_${index}_item_image"><br>
                        </div>`;
          return html;
        }
      
        // buildFileFieldの変数に配列の0番目の番号入れて、image-box-2に加える
        $('#image-box-2').append(buildFileField(num[0]));


        //fileReader.readAsDataURL(file)で画像の読み込み。
        fileReader.readAsDataURL(file);
      
        //読み込みが完了すると、srcにfileのURLを格納
        fileReader.onloadend = function() {
          //resultプロパティで、読み込み成功後に、中身のデータを取得する
          var src = fileReader.result

          // 画像のプレビュー作成
          var html = `<div class='item-image' data-image="${file.name}" data-index="${img[0]}">
                        <div class=' item-image__content'>
                          <div class='item-image__content--icon'>
                            <img src=${src} width="140" height="150" >
                          </div>
                        </div>
                        <div class='item-image__operetion'>
                          <div class='item-image__operetion--delete' >削除</div>
                        </div>
                      </div>`
          //image_box__container要素の前にhtmlを差し込む
          $('#image-box__container').before(html);
        };
      });
    });
  });
});

この部分で、行っています。
// file_fieldのnameに動的なindexをつける為の配列
let num = [1,2,3,4,5,6,7,8];
let img = [0,1,2,3,4,5,6,7];
// data()メソッドでindex番号を取得、lastを使って最後のinput[type="file"]を取得して変数に入れる
lastIndex = $('.input-field__contents:last').data('index');
anotherIndex = $('.input-field__contents:last').data('index');
//splice()メソッドを使い配列から要素を削除・追加して組み替える
num.splice(0, lastIndex);
img.splice(0, anotherIndex);

// 画像用のinputにそれぞれ異なる番号付与する記述
const buildFileField = (index)=> {
  const html = `<div data-index="${index}" class="input-field__contents">
                  <input id:"img-file" type="file"
                  name="item[images_attributes][${index}][item_image]"
                  id="item_images_attributes_${index}_item_image"><br>
                </div>`;
  return html;
}

// buildFileFieldの変数に配列の0番目の番号入れて、image-box-2に加える
$('#image-box-2').append(buildFileField(num[0]));

これを説明すると、気づけば2021年になっていましたなんて事になりかねないので
またの機会にでもご説明致します。




では、前置きがとても長くなってしまいましたが、attr()メソッドを使用してJSに
属性を変更させる記述していきましょう!
image-post.jsに下記のコードを追加しましょう!

image-post.js
//画像を追加した時に、labelのfor属性の値を新しいinputのidに変える記述
  $(document).on("click", '#item_images_attributes_0_item_image', function(){
    $('label').attr('for', 'item_images_attributes_1_item_image');
  })
  $(document).on("click", '#item_images_attributes_1_item_image', function(){
    $('label').attr('for', 'item_images_attributes_2_item_image');
  })
  $(document).on("click", '#item_images_attributes_2_item_image', function(){
    $('label').attr('for', 'item_images_attributes_3_item_image');
  })
  $(document).on("click", '#item_images_attributes_3_item_image', function(){
    $('label').attr('for', 'item_images_attributes_4_item_image');
  })
  $(document).on("click", '#item_images_attributes_4_item_image', function(){
    $('label').attr('for', 'item_images_attributes_5_item_image');
  })

簡単に説明しますと、
先ず、document(html要素)の中の、id名=item_images_attributes_0_item_imageが、
クリックされた時に発火するイベントを組みました。
そして、クリックされれば、labelタグのfor属性がitem_images_attributes_1_item_imageに変更
されるというコードになっています。


またまた、画像で確認しましょう!
先ずは、検証画面を開いて、、、
ファイル名

アップの画像を見てみると、、、
ファイル名
labelタグのfor属性がitem_images_attributes_1_item_imageに変更されていますね!
これで完成しました!




最後に格好悪いのでinput[type=file]を消しましょう!
下記のコードをSCSSの最後の行に記述しておきましょう。

#image-box-2{
  display: none;
}


一応JSの完成図を載せておきます。
image-post.js
$(document).on('turbolinks:load', function(){
  $(function(){
      //DataTransferオブジェクトで、データを格納する箱を作る
      var dataBox = new DataTransfer();
      //querySelectorでfile_fieldを取得し変数に入れている
      var file_field = document.querySelector('input[type=file]')

      //file(#image-box)が変化した時に発火するイベント
      $('#image-box').on("change",`input[type="file"]`,function(){
      //選択したファイル情報を取得し変数に格納 - 最後の[0]は最初のファイルという意味
      var files = $('input[type=file]').prop('files')[0];

      //$.each()メソッドで、配列やハッシュに対して繰り返し処理を行う
      $.each(this.files, function(i,file){

        //FileReaderのreadAsDataURLで指定したFileオブジェクトを読み込む
        var fileReader = new FileReader();

        //DataTransferオブジェクトに対して、fileを追加
        dataBox.items.add(file)

        // file_fieldのnameに動的なindexをつける為の配列
        let num = [1,2,3,4,5,6,7,8];
        let img = [0,1,2,3,4,5,6,7];
        // data()メソッドでindex番号を取得、lastを使って最後のinput[type="file"]を取得して変数に入れる
        lastIndex = $('.input-field__contents:last').data('index');
        anotherIndex = $('.input-field__contents:last').data('index');
        //splice()メソッドを使い配列から要素を削除・追加して組み替える
        num.splice(0, lastIndex);
        img.splice(0, anotherIndex);

        // 画像用のinputにそれぞれ異なる番号付与する記述
        const buildFileField = (index)=> {
          const html = `<div data-index="${index}" class="input-field__contents">
                          <input id:"img-file" type="file"
                          name="item[images_attributes][${index}][item_image]"
                          id="item_images_attributes_${index}_item_image"><br>
                        </div>`;
          return html;
        }

        // buildFileFieldの変数に配列の0番目の番号入れて、image-box-2に加える
        $('#image-box-2').append(buildFileField(num[0]));


        //fileReader.readAsDataURL(file)で画像の読み込み。
        fileReader.readAsDataURL(file);

        //読み込みが完了すると、srcにfileのURLを格納
        fileReader.onloadend = function() {
          //resultプロパティで、読み込み成功後に、中身のデータを取得する
          var src = fileReader.result

          // 画像のプレビュー作成
          var html = `<div class='item-image' data-image="${file.name}" data-index="${img[0]}">
                        <div class=' item-image__content'>
                          <div class='item-image__content--icon'>
                            <img src=${src} width="140" height="150" >
                          </div>
                        </div>
                        <div class='item-image__operetion'>
                          <div class='item-image__operetion--delete' >削除</div>
                        </div>
                      </div>`
          //image_box__container要素の前にhtmlを差し込む
          $('#image-box__container').before(html);
        };
      });
    });
  });
  //画像を追加した時に、labelのfor属性の値を新しいinputのidに変える記述
  $(document).on("click", '#item_images_attributes_0_item_image', function(){
    $('label').attr('for', 'item_images_attributes_1_item_image');
  })
  $(document).on("click", '#item_images_attributes_1_item_image', function(){
    $('label').attr('for', 'item_images_attributes_2_item_image');
  })
  $(document).on("click", '#item_images_attributes_2_item_image', function(){
    $('label').attr('for', 'item_images_attributes_3_item_image');
  })
  $(document).on("click", '#item_images_attributes_3_item_image', function(){
    $('label').attr('for', 'item_images_attributes_4_item_image');
  })
  $(document).on("click", '#item_images_attributes_4_item_image', function(){
    $('label').attr('for', 'item_images_attributes_5_item_image');
  })
});


完成形 - ![Shimokita.php](https://i.gyazo.com/3ef25619dcb6f4bdcdd33eb45214e9a5.gif)

まとめ - 以上、長々と書きましたが、お読み頂きありがとうございます! 何かご不明点や分かりづらいとの事が有ればご連絡下さい。 次は、違いやり方での紐付けの仕方を書きたいと思います。

使用しているrailsのver - Rails 6.0.3.2
1
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?