2009年5月31日日曜日

Ajax + jQueryで超簡単にファイルをアップロードするためのメモ

jQueryはAJAX関連のメソッドが充実しているものの、デフォルトではファイルのアップロードをAJAXで行うことはできません。

AJAXでファイルアップロードを行うためのプラグインは数あれど AjaxFileUpload がよさげなので使ってみることにしました。

AjaxFileUpload
http://www.phpletter.com/Our-Projects/AjaxFileUpload

上のリンクのHow to use it? を参考にすれば簡単にAjaxでファイルのアップロードを行うことが可能なのですが、今回はちょっとひと工夫を。

通常は...
1. ファイルを選択
2. 「アップロードボタン(submitボタン)」をクリック
という流れでファイルのアップロードを行います。

ボタンをクリックするの面倒じゃない?という方のために、ファイルを選択した時点でアップロードを実行するようにしてみます。


HTML

<form action="/profile/icon/upload/" enctype="multipart/form-data" id="upload_form" method="post">
<input type="file" name="icon" id="icon_field"/>
</form>

<div id="icon_container">
<img src="default_icon.gif" alt="icon" />
</div>

Javascript
<script src="/javascripts/ajaxfileupload.js" type="text/javascript"></script>

<script type="text/javascirpt">
$(document).ready(function() {
$('#upload_form').submit(function(){
var $this = $(this);

$.ajaxFileUpload({
url: $this.attr('action'),
secureuri:false,
fileElementId: 'icon_field',
dataType: 'json',
success: function (obj, status) {
if (obj.success) {
$('#icon_container').find('img').attr('src', '/icon/' + obj.key);
} else if(obj.error && obj.message){
alert(obj.message);
}
$this.find('input:file').bind('change', triggerUpload);
},
error: function (data, s, e) {
alert('Sorry, an error occured.');
}
});
return false;
});

$('#icon_field').change(triggerUpload);
});

function triggerUpload() {
$('#upload_form').trigger('submit');
}
</script>

アップロードが成功したら動的に画像を変更します。

ポイントはtriggerUploadという関数を用意しておいて、Ajax成功後にsuccessの中で再バインドしているところでしょうか。

AjaxFileUploadはAjax通信時にファイルフィールド(fileElementIdで指定した要素)のIDを動的に変更するようです。
ですので、$(document).readyで最初にchangeイベントをバインドしていても、一度Ajaxリクエストを行うとイベントが効かなくなりますのでご注意を。

Serverサイド (ここではPython(Django0.96)を使用)
from app import models
from django.http import HttpResponse, Http404
from django.utils import simplejson
from google.appengine.api import images

def upload_icon(request):
res = {}
if request.method != 'POST' or not request.FILES.has_key('icon') or 'content-type' not in request.FILES['icon']:
raise Http404

main, sub = request.FILES['icon']['content-type'].split('/')
if not (main == 'image' and sub in ['jpeg', 'pjpeg', 'gif', 'png']):
res = {'error': 1, 'message': 'JPEG, PNG, GIF only.'}
else:
try:
prof = request.user.get_profile()
except:
prof = models.Profile()
prof.user = request.user

content = request.FILES['icon']['content']

if sub == 'jpeg' or sub == 'pjpeg':
output_encoding = images.JPEG
content_type = 'jpeg'
else:
output_encoding = images.PNG
content_type = 'png'

prof.icon = images.resize(content, 50, 50, output_encoding)
prof.content_type = content_type
prof.save()
res = {'success': 1, 'key': str(prof.key())}

response = simplejson.dumps(res, ensure_ascii=False)
return HttpResponse(response)

ここではアップロードされた画像を50×50にリサイズしてストレージ(Datastore)に保存しています。
(Google App Engineで試したので通常のDjangoにはないモジュールを使用しています)

ポイントは、JSONを返すといってもレスポンスのヘッダーをtext/javascirptなどにしてしまわないこと。
(上の例ではtext/htmlで返しています。)

ヘッダーをtext/javascirptとしてしまうと、JSONが<pre>タグで囲まれてしまいます。
(ファイルをAjaxでアップロードするためにiframeを利用しているため!?)
<pre>{'success': 1, 'key': 'abcdefg'}</pre>



専用のサンプルページは用意していませんが、LibroSpotのプロフィール編集ページが上のようになっています。
LibroSpotにログインした後、「My本ログ」の「プロフィールを編集する」リンクに行ってiconのアップロードをしてみてください。

2 件のコメント:

nogute さんのコメント...

はじめまして野口と申します。

Ajaxでのファイルアップロードサンプルを探していたら、当記事を見つけました。

AjaxFileUploadをダウンロードして、サーバーにアップしました。(この時点では、何もコードをいじっていません。)

動作確認をしましたが、ファイルがどこにアップされているのかわかりません。

ファイルの保存先を指定するような項目がソースの中にあるのでしょうか?

また、当プログラムでは動画ファイルのアップロードは可能なのでしょうか?

開発者様でないのに大変恐縮ですが、ご回答頂けたら幸いに思います。

Youthhr さんのコメント...

コメントありがとうございます。

ご使用のプログラミング言語やフレームワークなどでデバッグ方法は色々あると思うのですが、FireBugを活用されることをオススメします。

若干補足しますと、通常のAJAXではFireBugのコンソールに通信情報が表示されますが、AjaxFileUploadでは表示されません。
「接続」の「すべて」タブを開く必要があるようです。
(AjaxFileUploadが内部的にiframeを使用しているため!?)

アップロード自体が正しく行われていない場合は赤字で表示されるのでそこで判断できるかと思います。
(すでにご存知でしたら申し訳ありません!)

アップロード後の保存先の指定方法に関しては、各言語/フレームワーク別にチュートリアルが充実していると思いますので、検索して調べていただければと思います。

あと、アップロード可能なファイルの種類に制限はないと思います。
動画ファイルでも問題ないはずです。

あまり参考にならない回答で恐縮です。。。