こんな感じになりました

はじめに

複数のファイルをアップロードする件でググっていたところ,SWFUpload なるライブラリの存在を知りました.

公開自体はもう 2年くらい前みたいですね.私は昨日初めて知りました ><

Flickr30days Album を使って,複数ファイルのアップロードやその際のプログレス表示がかなり快いことを身をもって知りましたが,そんなことをこのライブラリで実現できるっぽいです.

使ってみようと思って ドキュメント を眺めましたが,導入のフローのイメージをつかめなかったので,サンプルをベースに組み立てて行くことにしました.

Simple Demo - SWFUpload

サンプルには数パタンありますが,待機リストやプログレス表示のある 「Simple Demo」 が個人的に望むものに近かったので,これを選びました.

This page demonstrates a simple usage of SWFUpload. It uses the Queue Plugin to simplify uploading or cancelling all queued files.
SWFUpload Demos – Simple Demo

ただ,そのまま使うのはあまりおもしろくないので,いじれる部分を少しいじって次のような特徴を加えてみました.

  • ファイルリストの要素を横並びにする
  • アップロード完了後,その画像のサムネイルを表示する.また,フェイドアウトさせない

では以下,その辺の手順についてだらだらと綴っていきます.

なお,現時点で最新である ver.2.2.0 を使っています.

目次

  • はじめに
  • 目次
  • ライブラリ・サンプルの入手
  • クライアントサイドの準備
  • サーバサイドの準備
  • クライアントサイドの調整 (DHTML)
  • クライアントサイドの調整 (CSS)
  • クライアントサイドの調整 (JavaScript)
  • サーバサイドの調整
  • こんな感じ
  • おわりに

ライブラリ・サンプルの入手

ライブラリやサンプルをダウンロードします.Google Code から入手できます.

クライアントサイドの準備

HTML

index.php の次の部分を,設置したい場所にペーストします.

<div class="fieldset flash clearfix" id="fsUploadProgress">
	<span class="legend">Upload Queue</span>
</div>
<div id="divStatus">0 Files Uploaded</div>
<div>
	<span id="spanButtonPlaceHolder"></span>
	<input id="btnCancel" type="button" value="Cancel All Uploads" onclick="swfu.cancelQueue();" disabled="disabled" style="margin-left: 2px; font-size: 8pt; height: 29px;" />
</div>

SWF

swfupload.swf を,ブラウザから参照できる任意の場所に置きます.

JavaScript

ライブラリ swfupload.js と,このデモに含まれている swfupload.queue.jsfileprogress.jshandler.js を,ブラウザから参照できる任意の場所に置き,scriptタグですべて読み込みます.

<script type="text/javascript" src="js/swfupload/swfupload.js"></script>
<script type="text/javascript" src="js/swfupload/simpledemo/js/swfupload.queue.js"></script>
<script type="text/javascript" src="js/swfupload/simpledemo/js/fileprogress.js"></script>
<script type="text/javascript" src="js/swfupload/simpledemo/js/handler.js"></script>

そして,index.php の次の部分を,設置したい場所の周辺(?)にペーストします.

<script type="text/javascript">
var swfu;
 
window.onload = function() {
  var settings = {
    flash_url : "../swfupload/swfupload.swf",
    upload_url: "../simpledemo/upload.php",	// Relative to the SWF file
    post_params: {"PHPSESSID" : "<?php echo session_id(); ?>"},
    file_size_limit : "100 MB",
    file_types : "*.*",
    file_types_description : "All Files",
    file_upload_limit : 100,
    file_queue_limit : 0,
    custom_settings : {
      progressTarget : "fsUploadProgress",
      cancelButtonId : "btnCancel"
    },
    debug: false,
 
    // Button settings
    button_image_url: "../simpledemo/images/TestImageNoText_65x29.png",	// Relative to the Flash file
    button_width: "65",
    button_height: "29",
    button_placeholder_id: "spanButtonPlaceHolder",
    button_text: '<span class="theFont">Hello</span>',
    button_text_style: ".theFont { font-size: 16; }",
    button_text_left_padding: 12,
    button_text_top_padding: 3,
 
    // The event handler functions are defined in handlers.js
    file_queued_handler : fileQueued,
    file_queue_error_handler : fileQueueError,
    file_dialog_complete_handler : fileDialogComplete,
    upload_start_handler : uploadStart,
    upload_progress_handler : uploadProgress,
    upload_error_handler : uploadError,
    upload_success_handler : uploadSuccess,
    upload_complete_handler : uploadComplete,
    queue_complete_handler : queueComplete	// Queue plugin event
  };
 
  swfu = new SWFUpload(settings);
};
</script>

settings が示すオブジェクト(ハッシュ)の内容が SWFUpload の基本設定となるので,ここを適宜修正します.

主なプロパティ (私が実際に修正してみたもの) について簡単に解説しておきます.

flash_url
swfupload.swf を置いた場所です.http://から始めた方が無難っぽいです.
upload_url
アップロード先のURLです.formaction 属性のようなものですね.http://から始めた方が無難っぽいです.
post_params
アップロード時に送信するクエリパラメータを指定できます.<input type="hidden" /> とかの namevalue のペアですね.
file_types
ファイル選択ダイアログで表示するファイルの絞り込みを行うために指定します. *.jpg とすれば,jpgファイル (拡張子が jpg であるファイル) のみが表示されます.(あとフォルダも.)
file_types_description
custom_settings.progressTarget
この値をIDに持つ要素の中で,アップロードファイル情報の待ちリスト(以下,キュー) やアップロードのプログレス表示のためのHTMLが動的に生成されます.
custom_settings.cancelButtonId
アップロードをキャンセルするボタンのIDです.
button_image_url
ファイル選択ボタンに画像を使いたい場合,この値を指定します.デフォルト状態,マウスオーバー状態,マウスダウン状態,???の 4パタンの画像を,順に下に重ねた画像を準備する必要があります.
button_width
ファイル選択ボタンの幅です.
button_height
ファイル選択ボタンの高さです.
button_placeholder_id
ファイル選択ボタンのIDです.
button_text
ファイル選択ボタンに表示する文字列です.HTMLもOKです.
button_text_style
button_text プロパティが HTML な値のとき,その部分に適用するスタイルを指定することができます.

ひとまずOK

ここまでやると,次のようなボタンが表示されます.

とりあえず設置 1

ファイル選択ボタンをクリックすると,複数選択が可能なファイルダイアログが現れます.

とりあえず設置 2

このファイル選択ボタンが flash になっているんですね.

サーバサイドの準備

次に,アップロードの処理を行う側の準備をします.

perl cgi な書き方をしていますので,適宜読み換えてください.

まずは

いきなりファイルのアップロード処理を行うのもメンドイので,まずは,次のようなものを準備します.

#!/usr/bin/perl
 
print << "...";
Content-type: text/javascript; charset=utf8;
 
({
  response: true
})
...

先の基本設定の upload_url プロパティで指定したURLに直接アクセスしてみて,テキストが表示されればOKです.

この状態であれば,複数ファイルアップロードの操作を 「見かけ上」 では再現できるかと思います.(当然ながら,ファイルはサーバ側に保存されません.)

クライアントサイドの調整 (DHTML)

プログレス表示関連部分の HTML は,アップロードの状態によって,その都度動的生成・制御されます.

サンプルを少し試したところ,次の 5つの状態に分けられそうです.

  • ファイル選択前
  • ファイル選択後・アップロード待機中
  • アップロード中
  • アップロード完了
  • 消滅

ここでは,これらの各状態における HTML の構造を見てみます.なお,HTML構造の解析には Firebug を使っています.

ファイル選択前

<div id="fsUploadProgress" class="fieldset flash">
	<span class="legend">Upload Queue</span>
</div>
<div id="divStatus">0 Files Uploaded</div>

ファイル選択後・アップロード待機中

プログレス表示:最初
<div id="fsUploadProgress" class="fieldset flash">
	<span class="legend">Upload Queue</span>
	<div id="SWFUpload_0_0" class="progressWrapper">
		<div class="progressContainer">
			<a class="progressCancel" href="#" style="visibility: visible;"> </a>
			<div class="progressName">filename</div>
			<div class="progressBarStatus">Pending...</div>
			<div class="progressBarInProgress"/>
		</div>
	</div>
</div>
<div id="divStatus">0 Files Uploaded</div>

アップロード中

プログレス表示:アップロード中
<div id="fsUploadProgress" class="fieldset flash">
	<span class="legend">Upload Queue</span>
	<div id="SWFUpload_0_0" class="progressWrapper" style="opacity: 1;">
		<div class="progressContainer green">
			<a class="progressCancel" href="#" style="visibility: visible;"> </a>
			<div class="progressName">filename</div>
			<div class="progressBarStatus">Uploading...</div>
			<div class="progressBarInProgress" style="width: 100%;"/>
		</div>
	</div>
</div>
<div id="divStatus">0 Files Uploaded</div>

アップロード完了

プログレス表示:アップロード完了
<div id="fsUploadProgress" class="fieldset flash">
	<span class="legend">Upload Queue</span>
	<div id="SWFUpload_0_0" class="progressWrapper" style="opacity: 1;">
		<div class="progressContainer blue">
			<a class="progressCancel" href="#" style="visibility: hidden;"> </a>
			<div class="progressName">filename</div>
			<div class="progressBarStatus">Complete.</div>
			<div class="progressBarComplete" style=""/>
		</div>
	</div>
</div>
<div id="divStatus">1 file uploaded.</div>
改造

アップロード完了後にその画像のサムネイルを表示させることを考えると,そのための領域を準備するのがよさげです.

ということで,progressThumbnail なクラスの div 要素を,progressContainer なクラスの要素の中に追加します.

<div id="fsUploadProgress" class="fieldset flash">
	<span class="legend">Upload Queue</span>
	<div id="SWFUpload_0_0" class="progressWrapper" style="opacity: 1;">
		<div class="progressContainer blue">
			<a class="progressCancel" href="#" style="visibility: hidden;"> </a>
			<div class="progressName">filename</div>
			<div class="progressBarStatus">Complete.</div>
			<div class="progressBarComplete" style=""/>
			<div class="progressThumbnail"></div> <!-- ← これを追加 -->
		</div>
	</div>
</div>
<div id="divStatus">1 file uploaded.</div>

消滅

プログレス表示:消滅
<div id="fsUploadProgress" class="fieldset flash">
	<span class="legend">Upload Queue</span>
	<div id="SWFUpload_0_0" class="progressWrapper" style="opacity: 0; height: 0px; display: none;">
		<div class="progressContainer blue">
			<a class="progressCancel" href="#" style="visibility: hidden;"> </a>
			<div class="progressName">filename</div>
			<div class="progressBarStatus">Complete.</div>
			<div class="progressBarComplete" style=""/>
		</div>
	</div>
</div>
<div id="divStatus">1 file uploaded.</div>

クライアントサイドの調整 (CSS)

どのようなID,クラス名が現れるか,どのようなHTML構造になるかが先でわかったので,それらを基に CSS を組んでいきます.

とりあえず次のようにしてみました.

 
#fsUploadProgress {
	margin-bottom: 8px;
	font-size: 12px;
	line-height: 133%;
}
#fsUploadProgress .progressWrapper {
	margin: 0 4px 4px 0;
	padding: 4px;
	border: 1px solid #72dbff;
	width: 138px;
	height: 138px;
	height: 162px;
	float: left;
	background: #dff7ff;
}
#fsUploadProgress .progressWrapper .progressContainer {
	position: relative;
}
#fsUploadProgress .progressWrapper .progressContainer a.progressCancel {
	position: absolute;
	top: 0;
	right: 0;
	display: block;
	width: 16px;
	height: 16px;
	background: #ff9999;
	cursor: pointer;
}
#fsUploadProgress .progressWrapper .progressContainer .progressName {
	padding: 2px 4px 4px;
	font-weight: bolder;
	line-height: 100%;
}
#fsUploadProgress .progressWrapper .progressContainer .progressBarStatus {
}
#fsUploadProgress .progressWrapper .progressContainer .progressBarInProgress {
	width: 0;
	height: 8px;
	background: #9999ff;
}
#fsUploadProgress .progressWrapper .progressContainer .progressBarComplete {
}
 
/* 追加 */
#fsUploadProgress .progressWrapper .progressContainer .progressThumbnail {
	width: 138px;
	height: 138px;
	background-color: #000000;
	background-position: center center;
	background-repeat: no-repeat;
}

クライアントサイドの調整 (JavaScript)

アップロード完了後の動作を変更するには,基本設定オブジェクトの upload_success_handler プロパティが参照する関数の動作を変更すればOKIです.

before

今回の場合,uploadSuccess 関数ということになるわけで,これは handlers.js の中で定義されています.その中でさらに,FileProgress オブジェクトの setComplete メソッドが呼ばれています.(fileprogress.js の中で定義されています.)

//
// handlers.js
//
function uploadSuccess(file, serverData) {
	try {
		var progress = new FileProgress(file, this.customSettings.progressTarget);
		progress.setComplete();
		progress.setStatus("Complete.");
		progress.toggleCancel(false);
 
	} catch (ex) {
		this.debug(ex);
	}
}
 
//
// fileprogress.js
//
FileProgress.prototype.setComplete = function () {
	this.fileProgressElement.className = "progressContainer blue";
	this.fileProgressElement.childNodes[3].className = "progressBarComplete";
	this.fileProgressElement.childNodes[3].style.width = "";
 
	var oSelf = this;
	this.setTimer(setTimeout(function () {
		oSelf.disappear();
	}, 10000));
};

after

なんだか説明文がメンドくなってきたので,先の関数・メソッドを変更したものを以下に示しておきます.

サーバから返ったデータ (後述します) を setComplete メソッドまで渡し,そのデータに含まれるアップロード画像のサムネイルへのURLを参照することで,アップロード完了後にサムネイルを読み込む,などしています.

//
// handler.js
//
function uploadSuccess(file, serverData) {
    try {
        var progress = new FileProgress(file, this.customSettings.progressTarget);
        progress.setComplete( serverData );
        progress.setStatus("Complete.");
        progress.toggleCancel(false);
    } catch (ex) {
        alert( serverData );
        this.debug(ex);
    }
}
 
//
// fileprogress.js
//
FileProgress.prototype.setComplete = function ( data ) {
    var o = eval( data );
    this.fileProgressElement.className = "progressContainer blue";
    this.fileProgressElement.childNodes[3].className = "progressBarComplete";
    this.fileProgressElement.childNodes[3].style.width = "";
 
    var oSelf = this;
    this.setTimer( setTimeout( function() {
        oSelf.loadThumbnail( o );
    }, 0 ) );
};
 
/* 追加 */
FileProgress.prototype.loadThumbnail = function( data ) {
    var container = this.fileProgressElement;
    container.removeChild( container.childNodes[2] );  // ステータス
 
    var thumbnail = document.createElement( 'div' );
    thumbnail.className = 'progressThumbnail';
    thumbnail.style.backgroundImage = 'url('+ data.photo_url +')';
    container.appendChild( thumbnail );
};

サーバサイドの調整

JSONを返す

クライアントサイドでの処理が楽なので,JSONで返します.ここにサムネイルのURLを含めます.

#!/usr/bin/perl
 
...
 
#
#  アップロードファイルの出力処理
#
 
...
 
print << "...";
Content-type: text/javascript; charset=utf-8;
 
({
  photo_url: 'サムネイルのURL',
});
...

アップロードファイルの出力処理

アップロードファイルを出力するために,そのファイルへのファイルハンドルを取得する必要がありますが,どのパラメータにアクセスすればいいのかがわかりませんでした.

で,次のコードです.

use CGI;
use Data::Dumper;
my $q = CGI->new;
my $params = $q->Vars;
print Dumper $params;
$VAR1 = {
          'Filedata' => 'filename',
          'Filename' => 'filename',
          'Upload' => 'Submit Query',
          'param1' => 'foo',  # 基本設定オブジェクトの post_params プロパティでセットされたパラメータ
          'param2' => 'bar',  # 同じく
        };

ということで(?), Filedata パラメータが正解です.

my $fh_upload;
$fh_upload = $q->param( 'Filedata' );
# もしくは
$fh_upload = $q->upload( 'Filedata' );

あとは,取得したファイルハンドルでデータを読み,適当な場所へ出力するだけですね.

こんな感じ

といった感じで,冒頭のようなファイルアップロードのインタフェイスができました.

こんな感じになりました

おわりに

以上,複数ファイルのアップロードを可能にしてくれるライブラリ SWFUpload を使った,ファイルアップロードインタフェイスを,サンプルデモを基につくっていく過程を綴りました.後になるほど文章がいい加減になっている気がしますが...

このライブラリがとても重宝するのはもうわかっているつもりなので,ドキュメントを読むなどして,より深い使い方ができるように努めますかね.

こちらもあわせてどうぞ

コメントをどうぞ