ℙ𝕚𝕜𝕠𝔹𝕝𝕠𝕘 | プログラミングに぀いお知ろう

【HTML/CSS/JavaScript/jQuery/Ruby/Swift/MySQL/AWS】 プログラミングを 「今始めたばかりの方」や 「これから始めるか悩んでいる方」、 奜きか嫌いかも分からない、 できるかどうかも分からない、 向いおるか向いおないかも分からない、 そんな思いを抱えた方ぞ届くような ブログを執筆しおいきたす。

スポンサヌリンク

Rails 投皿画像「線集・プレビュヌ・削陀」のやり方js、jQuery

スポンサヌリンク

f:id:kakikazu:20200207153312j:plain

 

こんにちは

今回は、前回の続き

 Rails 画像耇数投皿のやり方js、jQueryでフォヌム䜜成

から投皿画像の「線集・プレビュヌ・削陀」に぀いお曞いおいきたす。 

 

 

1.耇数画像の投皿機胜を実装。←ここたでを前回やりたした。

2.線集機胜の実装。←次はここです。

3.プレビュヌ機胜を実装。

4.削陀の実装。

 

 

では改めお぀目「線集機胜」からスタヌトです。

 

 

1぀の投皿に察し耇数画像を添付する機胜に必芁な「fields_for」

この「fields_for」メ゜ッド利甚の際に、曞く「accepts_nested_attributes_for」

この「accepts_nested_attributes_for」メ゜ッドは、

paramsの○○s_attritbutes:ずいうキヌの䞭で特定の倀を送るこずで、芪モデルに玐づいた子モデルの削陀や線集曎新を行いたす。

 

このポむントをふたえお進めたす。

 

 

 

 

 

①コントロヌラで基本的な動きずストロングパラメヌタを远加。

products_controller.rbに远蚘
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
before_action :set_product, except: [:index, :new, :create]

def update
  if @product.update(product_params)
    redirect_to root_path
  else
    render :edit
  end
end

private

def product_params
  params.require(:product).permit(:name, :price, images_attributes:  [:src, :_destroy, :id])
end

def set_product
  @product = Product.find(params[:id])
end

 

14行目に先ほど説明したポむント「〇〇s_attributes」に続き、

[src,  _destroy, id] の぀が远加されたした。

 

これで芪モデルproductに基づいた子モデルimagesず玐付け

欲しい倀「src、_destroy 、id」を取っおくる蚘述になりたした。

 

ここで出おくる「destroy 」を初めおみたしたが

これは、

fields_forから送られおくる、このキヌを持った情報を頌りに

railsが子モデルの削陀を行っおくれる。

 ずいう蚘述方法のようです。

 

 

 

② topペヌゞにeditボタンを䜜成する

 

_product.html.erb
1
2
3
// 省略

<%= link_to '線集', edit_product_path(product) %>

③ビュヌに、線集機胜甚のフォヌムを远加したす。

_form.html.erb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<%= form_for @product do |f| %>
  商品名<%= f.text_field :name %><br>
  䟡栌<%= f.number_field :price %><br>
  <div id="image-box">
    <%= f.fields_for :images do |image| %>
      <div data-index="<%= image.index %>" class="js-file_group">
        <%= image.file_field :src, class: 'js-file' %><br>
        <span class="js-remove">削陀</span>
      </div>
      <% if @product.persisted? %>
        <%= image.check_box :_destroy, data:{ index: image.index }, class: 'hidden-destroy' %>
      <% end %>
    <% end %>
    <% if @product.persisted? %>
      <div data-index="<%= @product.images.count %>" class="js-file_group">
        <%= file_field_tag :src, name: "product[images_attributes][#{@product.images.count}][src]", class: 'js-file' %>
        <div class="js-remove">削陀</div>
      </div>
    <% end %>
  </div>
  <%= f.submit %>
<% end %>

 远蚘したのは、2぀のif @product.persisted?の郚分です。

 

この「persisted」は、@productのようなむンスタンスが利甚できるメ゜ッドで

もしデヌタベヌスに保存枈みならtrue、そうでなければfalseを返したす。

 

 

次はjsファむルに远蚘しおいきたす。

 

それぞれの画像の䞋にある削陀ボタンを抌し、チェックボックスにチェックが入るようにしおいくため以䞋の郚分を远蚘。

 

・ペヌゞ読み蟌み時に、甚意した配列から既に䜿われおいるindexを取り陀く凊理

・削陀ボタンを抌した際に、該圓indexが振られたチェックボックスぞチェックを入れる凊理
 

products.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
~~~~~~~~~~~~~~~~~~~~~~(省略)~~~~~~~~~~~~~~~~~~~~~~~~
  
  let fileIndex = [1,2,3,4,5,6,7,8,9,10];
  // 既に䜿われおいるindexを陀倖
  lastIndex = $('.js-file_group:last').data('index');
  fileIndex.splice(0, lastIndex);
  $('.hidden-destroy').hide();
~~~~~~~~~~~~~~~~~~~~~~(省略)~~~~~~~~~~~~~~~~~~~~~~~~
  $('#image-box').on('click', '.js-remove', function() {
    const targetIndex = $(this).parent().data('index')
    // 該圓indexを振られおいるチェックボックスを取埗する
    const hiddenCheck = $(`input[data-index="${targetIndex}"].hidden-destroy`);
    // もしチェックボックスが存圚すればチェックを入れる
    if (hiddenCheck) hiddenCheck.prop('checked', true);
    (省略)
  });

 

 

これで「線集機胜」が実装できたした。

 

 

 

次は぀目「プレビュヌ機胜」実装です。

 

1.耇数画像の投皿機胜を実装。

2.線集機胜の実装。

3.プレビュヌ機胜を実装。←次はここです。

4.削陀の実装。

 

 

 

①たずビュヌに「プレビュヌ衚瀺甚の芁玠」を远加。

 

・imgタグのsrcを曞き換えるこずでプレビュヌを実装。

・削陀時indexを頌りにプレビュヌも削陀するため、

 カスタムデヌタずしお持たせる。

 

_form.html.erbを修正
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<%= form_for @product do |f| %>
  商品名<%= f.text_field :name %><br>
  䟡栌<%= f.number_field :price %><br>
  <div id="image-box">
    <div id="previews">
      <% if @product.persisted? %>
        <% @product.images.each_with_index do |image, i| %>
          <%= image_tag image.src.url, data: { index: i }, width: "100", height: '100' %>
        <% end %>
      <% end %>
    </div>
    <%= f.fields_for :images do |image| %>
      <div data-index="<%= image.index %>" class="js-file_group">
        <%= image.file_field :src, class: 'js-file' %><br>
        <span class="js-remove">削陀</span>
      </div>
      <% if @product.persisted? %>
        <%= image.check_box :_destroy, data:{ index: image.index }, class: 'hidden-destroy' %>
      <% end %>
    <% end %>
    <% if @product.persisted? %>
      <div data-index="<%= @product.images.count %>" class="js-file_group">
        <%= file_field_tag :src, name: "product[images_attributes][#{@product.images.count}][src]", class: 'js-file' %>
        <div class="js-remove">削陀</div>
      </div>
    <% end %>
  </div>
  <%= f.submit %>
<% end %>

 

 

②jsで画像ファむルを远加/倉曎/削陀した時に、imgタグを远加/src倉曎/削陀できるように実装。

products.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
~~~~~~~~~~~~~~~~~~~~~~(省略)~~~~~~~~~~~~~~~~~~~~~~~~
  // プレビュヌ甚のimgタグを生成する関数
  const buildImg = (index, url)=> {
    const html = `<img data-index="${index}" src="${url}" width="100px" height="100px">`;
    return html;
  }
~~~~~~~~~~~~~~~~~~~~~~(省略)~~~~~~~~~~~~~~~~~~~~~~~~
  $('#image-box').on('change', '.js-file', function(e) {
    const targetIndex = $(this).parent().data('index');
    // ファむルのブラりザ䞊でのURLを取埗する
    const file = e.target.files[0];
    const blobUrl = window.URL.createObjectURL(file);
    // 該圓indexを持぀imgタグがあれば取埗しお倉数imgに入れる(画像倉曎の凊理)
    if (img = $(`img[data-index="${targetIndex}"]`)[0]) {
      img.setAttribute('src', blobUrl);
    } else {  // 新芏画像远加の凊理
      $('#previews').append(buildImg(targetIndex, blobUrl));
      // fileIndexの先頭の数字を䜿っおinputを䜜る
      $('#image-box').append(buildFileField(fileIndex[0]));
      fileIndex.shift();
      // 末尟の数に1足した数を远加する
      fileIndex.push(fileIndex[fileIndex.length - 1] + 1)
    }
  });
~~~~~~~~~~~~~~~~~~~~~~(省略)~~~~~~~~~~~~~~~~~~~~~~~~
  $('#image-box').on('click', '.js-remove', function() {
  // (省略)
    $(`img[data-index="${targetIndex}"]`).remove();
  }

 

 

 完成したjsコヌドはこちら

product.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
$(document).on('turbolinks:load', ()=> {
  
  const buildFileField = (num)=> {
    const html = `<div data-index="${num}" class="js-file_group">
                    <input class="js-file" type="file"
                    name="product[images_attributes][${num}][src]"
                    id="product_images_attributes_${num}_src"><br>
                    <div class="js-remove">削陀</div>
                  </div>`;
    return html;
  }
  
  const buildImg = (index, url)=> {
    const html = `<img data-index="${index}" src="${url}" width="100px" height="100px">`;
    return html;
  }

  
  let fileIndex = [1,2,3,4,5,6,7,8,9,10];
  
  lastIndex = $('.js-file_group:last').data('index');
  fileIndex.splice(0, lastIndex);

  $('.hidden-destroy').hide();

  $('#image-box').on('change', '.js-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 {  
      $('#previews').append(buildImg(targetIndex, blobUrl));
      
      $('#image-box').append(buildFileField(fileIndex[0]));
      fileIndex.shift();
      
      fileIndex.push(fileIndex[fileIndex.length - 1] + 1);
    }
  });

  $('#image-box').on('click', '.js-remove', function() {
    const targetIndex = $(this).parent().data('index');
    
    const hiddenCheck = $(`input[data-index="${targetIndex}"].hidden-destroy`);
    
    if (hiddenCheck) hiddenCheck.prop('checked', true);

    $(this).parent().remove();
    $(`img[data-index="${targetIndex}"]`).remove();

    
    if ($('.js-file').length == 0) $('#image-box').append(buildFileField(fileIndex[0]));
  });
});

 

 以䞊で「プレビュヌ機胜」実装完了です。

 

 

 

ようやくこの「画像耇数枚投皿」アプリのゎヌルが芋えたした

最埌は「削陀機胜」です。

 

 

1.耇数画像の投皿機胜を実装。

2.線集機胜の実装。

3.プレビュヌ機胜を実装。

4.削陀の実装。←次はここです。

 

 

ここたで実装をしおいれば、削陀機胜に぀いおは特別な実装は䜕もありたせん。

 

①コントロヌラにレコヌド削陀の蚘述を远加。

product_controller.rb
1
2
3
4
def destroy
  @product.destroy
  redirect_to root_path
end

②ビュヌに削陀ボタンを远加。

_product.html.erb
1
2
//省略
<%= link_to '削陀', product_path(product), method: :delete %>

 ③productモデルに「芪モデル削陀したら子モデルをたずめお削陀」ずいうオプションを远加。

product.rb
1
has_many :images, dependent: :destroy

 

以䞊で「削陀機胜」ず

この〜蚘事にわたる「耇数画像投皿アプリ」の蚘事が終了したした

 

僕はこの機胜実装がずっずできずにダキモキしおたした

jsコヌドの蚘述は、難しいず思いたす。

 

できる人に蚀わせれば「䜕が分からないのか分からない」

なんお蚀われおしたいそうですよね。

 

けど、どんな人だっお始めは「䜕も分からない状態」から始めおいたす。

だから安心しお良いのだず思いたす。

 

この蚘事が「分からないたた諊めおしたいそう」

そんな人の圹に立おば幞いです。

 

 

最埌たで読んでくださりありがずうございたす。

次回もよろしくお願いしたす。 

 

スポンサヌリンク