2
1

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 1 year has passed since last update.

【jQuery】Ajaxでmultipleのファイルを送信する

Posted at

概要

ActiveStorageでやればいいところ、

  • 別モデルのフォーム内
  • ファイル選択と同時に登録処理

と言う条件だったためどうせJS制御になるしと言うことで
Ajaxを使って複数ファイルを送ることにしたけど、
複数ファイル選択だったところにつまずいたので備忘録です。

環境

  • Ruby: 3.1.0
  • Rails: 6.1.4.4

実装

model
class User < ApplicationRecord
  has_many :user_avatar_files
end

class UserAvatarFiles < ApplicationRecord
  belongs_to :user
  has_one_attached :avatar
end
html
= form_with model: @user, url: users_path, multipart: true, method: :post do |f|
  = hidden_field_tag "authenticity_token", form_authenticity_toke

  = hidden_field_tag "user_id", @user.id
  = file_field_tag 'avatars[]', multiple: true, id: 'upload', class: 'js-avatar', hidden: true
  label for="upload" class="btn"
    | ファイルをアップロード


#avatar-template.is-hidden
  .avatar-row
    = link_to '', "#", class: 'avatar-link'
    = link_to '削除', "#", class: 'avatar-delete', method: :delete

js
$(document).on('change', '.js-avatar', function (e) {
  const files = $(this).prop('files');
  const form_data = new FormData();

  form_data.append('authenticity_token', $("#authenticity_token").val());
  form_data.append('user_id', $('#user_id').val());
  $.each(files, function(i, val) {
    form_data.append('avatars[]', val);
  });

  $.ajax({
    url: '/user_avater_files',
    type: 'post',
    data: form_data,
    contentType: false,
    processData: false
  })
  .done(function(data)
    $.each(data.created_avatars,function(i, val) {
        $('#avatar-template').children().clone(true).appendTo('.avatar-lists')
          .removeClass('is-hidden')
          .find('.avatar-delete').each(function() {
            $(this).attr('href', val.delete_url);
          }).end().find('.avatar-link').each(function() {
            $(this).text(val.filename).attr('href', val.url);
          })
    })
  })
});
controller
def create
  @user = User.find(params[:user_id])
  created_avatars = []
  params[:avatars].each do |avatar|
    user_avater_file = @user.user_avater_file.create!
    user_avater_file.avatar.attach(avatar)
    avatar = user_avater_file.avatar
    created_avatars.push({filename: avatar.filename, url: rails_blob_path(avatar), delete_url: users_avatar_path(user_avater_file.id)} )
  end

  return render json: {created_avatars: created_avatars}
end

解説

ファイルの複数選択

ファイルの複数選択はinputmultipleのオプションをつけることで可能です。
今回の場合はfile_field_tagなのでmultiple: trueの部分になります。

html
= file_field_tag 'avatars[]', multiple: true, id: 'upload', class: 'js-avatar', hidden: true
複数選択されたファイルの送信

選択済みのファイルが1つの時には$('セレクタ').prop('files')[0]
でアクセスすることができ、
送信時にもこれをFormDataに入れて渡してあげれば処理できます。
しかし今回は複数なので、一つ一つFormDataに入れてあげる必要があります。

js
const files = $(this).prop('files');
const form_data = new FormData();

$.each(files, function(i, val) {
  form_data.append('avatars[]', val);
});

この部分で格納前にわざわざ配列を作って渡したりとかして無駄に詰まりました。
ファイルデータは一つ一つappendすれば良いようです。


あとは返ってきた値を使って
必要な表示をしてあげればOKです。

番外

postが送れない

Can't verify CSRF token authenticity.
こんな感じのエラーが出てコケました。
以下の部分が対策です。

html
= hidden_field_tag "authenticity_token", form_authenticity_toke
js
form_data.append('authenticity_token', $("#authenticity_token").val());

その他にcontrollerで制御するやり方や、
'meta[name="csrf-token"]から取得するやり方もあります。

送られてきたデータが変

controllerに送られてきたデータがなんかおかしく、

------WebKitFormBoundaryGkAId8S6WOXgIw06
15:28:51 web.1     | Content-Disposition: form-data; name="resumes[]"; filename="ミッキー.jpeg"
15:28:51 web.1     | Content-Type: image/jpeg
15:28:51 web.1     | 
15:28:51 web.1     | ����JFIF���         ( %!3!%)+...383.7(-.+
15:28:51 web.1     | 
...

こんな感じで想定していたものになっていなかったのですが、
FormDataを使用する場合は

js
  $.ajax({
    url: '/user_avater_files',
    type: 'post',
    data: form_data,
    contentType: false,
    processData: false
  })

このcontentType: falseprocessData: falseがいるようです。
contentTypeをfalseにすることで、Content-Typeの指定をFormDataに任せるのと、
processDataをfalseにするのは、クエリ文字列への変換を無効化してくれるらしいです。

参考にさせていただきました記事の作者様、
ありがとうございました。

2
1
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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?