Help us understand the problem. What is going on with this article?

Ajaxを用いてテキストと画像ファイルを投稿する

More than 1 year has passed since last update.

したいこと

簡易チャットアプリ作成中。
Ajaxを用いてテキストと画像データを投稿できるようにしたい。
JavaScriptが難しかったので以下メモを残します。理解が間違っている場合ご指摘いただけると幸いです。

ポイントは、
・フォームの情報をいかに取得するか
・processDataとcontentTypeがfalseって、何?
です。

環境

Ruby: 2.3.1
Rails: 5.0.1
rmagick 2.16.0
imagemagick 6.9.7-7

コード

message.js
$(function() {

  function buildHTML(data) {
    (中略)
    return html;
  }

  $('#new_message').on('submit', function(e) {
    e.preventDefault();
    var formData = new FormData($(this).get(0));
    var group_id = $('.group_id').attr('value');
    var url = '/groups/' + group_id + '/messages.json'
    $.ajax({
      type: 'POST',
      url: url,
      data: formData,
      processData: false,
      contentType: false
    })
    .done(function(data) {
      var html = buildHTML(data);
      $('.chat-messages').append(html);
      $('.input-area__text').val('');
    })
    .fail(function() {
      alert('error');
    });
  });
});

これで、テキストとファイルを非同期通信で投稿できる。
解説を加えると以下のようになる。

message.js
$(function() {

  function buildHTML(data) {
    (中略)
    return html;
  }

  $('#new_message').on('submit', function(e) {
  // #new_messageは、formタグにつけたid。formがsubmitされた際、以下が行われる。
    e.preventDefault();
    // 非同期通信でメッセージの投稿を行いたいため、通常の動作を停止。
    // 通常の動作: submit押される→create controllerでformの内容をDB保存→viewにredirect
    var formData = new FormData($(this).get(0));
    // ① new FormData()でFormDataオブジェクトを新規に作成。引数にフォームの情報を入れられる。
    // ② this:イベントを発火させた要素、つまり#new_message、すなわちフォームのこと。
    // ③-1 .get(0): JavaScriptのgetメソッド。このメソッドは、Mapオブジェクトである$(this)から要素を取り出すことができる。
    //      今回の場合、$(this)からキーが0の要素を取得している。
    // ③-2 Mapオブジェクト:「キーと値のペアのコレクション」とのことらしい。配列の中にkeyと値のペアがいくつか入ったオブジェクト、と言うふうにとりあえず理解。
    // ③-3 Viewから取得したformのデータは、Mapオブジェクトになっている。詳細は、下のスクショを参照。

    var group_id = $('.group_id').attr('value');
    // group idをviewより取得。inputタグをhiddenにして、そこにgroup idを仕込んである。
    var url = '/groups/' + group_id + '/messages.json'
    // urlを定義
    $.ajax({
      type: 'POST',
      url: url,
      data: formData,
      processData: false,
      contentType: false
      // この2つを何故falseにするのかが、ざっくりとしかわからない。
      // dataをajaxで送信する際に、データに手を加えずにそのままま送る、みたいな感じ。詳細は下記参照。
    })
    .done(function(data) {
      var html = buildHTML(data);
      $('.chat-messages').append(html);
      $('.input-area__text').val('');
      // .doneからここまでで、テキストとファイルを投稿&テキスト入力欄をクリア。
    })
    .fail(function() {
      alert('error');
      // ajaxミスったらアラート。
    });
  });
});

Point1 フォームデータの取得に関して
Viewから取得するフォームのデータは、Mapオブジェクト(≒配列の中にキーと値のセットが入ってる)の形になっている。
スクリーンショット 2017-02-09 12.57.36.png

Point2 processDataとcontentTypeに関して

①processData: false
→Dataに余計な手を加えずそのまま送る

processData / boolean
dataに指定したオブジェクトをクエリ文字列に変換するかどうかを設定します。
初期値はtrueで、自動的に "application/x-www-form-urlencoded" 形式に変換します。
DOMDocumentそのものなど、他の形式でデータを送るために自動変換を行いたくない場合はfalseを指定します。(引用:Jquery日本語リファレンス

なるほど。えーとクエリ文字列ってなんでしたっけ...

クエリ文字列とは、WebブラウザなどがWebサーバに送信するデータをURLの末尾に特定の形式で表記したもの。
Webアプリケーションなどでクライアントからサーバにパラメータを渡すのに使われる表記法で、URLの末尾に「?」マークを付け、続けて「名前=値」の形式で記述する。
値が複数あるときは「&」で区切り、例えば http://example.com/foo/var.php?name1=value1&name2=value2 のように記述する。(引用:IT用語辞典e-Words

ざっくりイメージだと、Googleで検索するとアドレスバーにぐわ〜〜って出てくるアレですね。

理解が正しいか微妙ですが、processDataをfalseにしている理由は、
・今回はGETではなくPOSTで送るのでクエリ文字列にする必要ない?
・フォームのデータはHTML(DOMDocument)なので、クエリ文字列に自動変換させたくない
ため?なんでしょうか。

②contentType: false
→contentTypeはデフォルトじゃないよ、と伝えてる?

contentType / string

サーバにデータを送信する際に用いるcontent-typeヘッダの値です。初期値は"application/x-www-form-urlencoded"で、殆どの場合はこの設定のままで問題ないはずです。(引用:Jquery日本語リファレンス

なるほど。他のサイトによると、

ファイルやデータの「本当の種類」。
MIMEによって定義された、ファイルの種類を示すための方法。
 
たとえば、インターネットでファイルのダウンロードを行う時等、ファイルに拡張子がなかったり、拡張子からファイルの種類が分からない場合がある。
その場合、「これから返すファイルの種類」をContent-Typeによって示すことで、ファイルの種類を受け取る側が知ることができる。またその種類に応じて、Webブラウザがそのまま表示したり、ファイルとして保存したり、他のアプリケーションを起動してそれを用いて出力したり、と処理を変更する。(引用:JavaAzZ

と言うものらしい。これから送るDataはこんなものだよ!っていうのを教えてあげる感じでしょうか。
またデフォルトのapplication/x-www-form-urlencodedというのはContent-Typeの一つで、

データはid=dataの形式で、formが複数ある場合は&で区切られる(form1=data1&form2=data2)。
dataはURLエンコードされる。
英数字と3種類の記号 _ . - はそのまま、スペースは+に変換され、それ以外の文字は%xxの16進形式になる。(引用:通信用語の基礎知識

ものらしい。なんかクエリ文字列っぽい??

正直、正確に理解できませんでした。とりあえずざっくり理解に留めます。

...
ちなみに、console.log(formData)だと、フォームの中身が見えません。

for (var [key, value] of formData.entries()) { 
  console.log(key, value);
}

こちらを使うとフォームの情報がこんな感じで見れます。

スクリーンショット 2017-02-09 13.49.46.png

参考サイト

・Jquery日本語リファレンス
jQuery.ajax

・MDN
FormData
this

・Microsoft Developer Network
Mapオブジェクト
getメソッド

・その他
JavaScript入門講座「DOMとは」
小粋空間「JavaScriptのFormDataの使い方」
あんだぐろ「Javascript FormData(form)の落とし穴」
「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典「Content-Type【メールヘッダ】」
IT用語辞典e-Words「クエリ文字列とは」
JavaAzZ「Content-Type」
通信用語の基礎知識「application/x-www-form-urlencoded」

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away