かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

jQuery Ajaxでファイルを送りたい!

input[type="file"]をAjaxで送信したい時
普通にAjaxを使って送ると、ファイルが文字列になってしまい
form.submit()した時と違うのでハマったのでメモ。

どうやら FormData を使い
Ajaxが自動的に送信データを整形する機能をOFFにすれば
form.submit()と同じデータで送れるようです。

■ フォーム

<form id="myForm" name="myForm" method="post" action="">
  <input type="file" name="file" id="formFile"/>
  <input type="text" name="text" id="formText"/>
  <textarea name="textarea" id="formTextarea"></textarea>
  <input type="submit" id="submitBtn" value="そうしん!" />
</form>

jQuery.ajaxで送信

$(function() {
  $('#submitBtn').on('click', function(evt) {
    var form = $('#myForm').get()[0];
   
    // FormData オブジェクトを作成
    var formData = new FormData( form );
    
    // Ajaxで送信
    $.ajax({
      url: '/api'
      method: 'post',
      dataType: 'json',
      // dataに FormDataを指定
      data: formData,
      // Ajaxがdataを整形しない指定
      processData: false,
      // contentTypeもfalseに指定
      contentType: false
    }).done(function( res ) {
      // 送信せいこう!
      console.log( 'SUCCESS', res );
    }).fail(function( jqXHR, textStatus, errorThrown ) {
      // しっぱい!
      console.log( 'ERROR', jqXHR, textStatus, errorThrown );
    });

    return false;
  });
});

processData: falsecontentType: falseがポイントの様です。

new FormData()する時にformを渡してやると
form.submit();した時と同じ様に
フォーム内のデータがnameとvalueのセットでそのまま送信されます。

nameを変更して送りたい時は次のように指定することが出来ます。

$(function() {
  $('#submitBtn').on('click', function(evt) {
    // FormData オブジェクトを作成
    var formData = new FormData();
    var $text = $('#formText');
    var $textarea = $('#formTextarea');
    var $file = $('#formFile');
    
    // appendを使いnameとデータをセット
    formData.append( 'form-text', $text.val() );
    
    // attr('name')を使えばHTMLで指定されているnameのまま送れる
    formData.append( $textarea.attr('name'), $textarea.val() );

    // fileは .prop() で files を取ってきてセットする
    formData.append( $file.attr('name'), $file.prop("files")[0] );
    
    // Ajaxで送信
    $.ajax({
      url: '/api'
      method: 'post',
      dataType: 'json',
      data: formData,
      processData: false,
      contentType: false
    }).done(function( res ) {
      console.log( 'SUCCESS', res );
    }).fail(function( jqXHR, textStatus, errorThrown ) {
      console.log( 'ERROR', jqXHR, textStatus, errorThrown );
    });

    return false;
  });
});

フォームに入力されてないならAjaxでデータを送らない。
などといった処理を入れる場合はこちらの方が使うかも?

[参考にしました]
jQuery.ajax() | jQuery API Documentation
jQuery + ajax で ファイルアップロードするには - 犬ターネット
y.okano blog: jQuery.ajax() でファイルを送る


少しズレるのだけれど、
AjaxはContent-Typeが指定できるっぽいです。
contentType falseで送るのが気持ち悪いって時に使えるかも。

$(function() {
    /* 中略 */
    $.ajax({
      url:  '/api'
      method: 'post',
      dataType: 'json',
      data: formData,
      processData: false,
      // headers を使ってContent-Typeを指定
      headers: {  
        'Content-Type': 'multipart/form-data'
      }
    }).done(function( res ) {
      console.log( 'SUCCESS', res );
    }).fail(function( jqXHR, textStatus, errorThrown ) {
      console.log( 'ERROR', jqXHR, textStatus, errorThrown );
    });
    /* 中略 */
});

contentTypeプロパティもあるので、headersを使わずに
contentType: 'multipart/form-data' みたいな指定でもOKっぽい!

[参考にしました]
jQuery: ajax メソッドのヘッダーを変更する - Sarabande.jp



ただし、
このFormDataってのがHTML5のものらしく、
IE9以前、Android2.3系以前のバージョンでは使えないらしい。

これらに対応せざるをえない時はFormData判定して処理分けるしかなさそう。。。

// FormData に対応しているか判定
if(window.FormData) {
  // FormData 使えるのでAjaxで送信
} else {
  // FormData 使えない子なので、form.submit()で対応?
}


[参考にしました]
JavaScriptプログラミング講座【FormData クラスについて】
Ajaxによるmultipart/postでの画像ファイルアップロード : アシアルブログ のコメント欄

jQuery入門道場

jQuery入門道場