Node.jsでカスタマイズボタンからファイルアップロードする

以下を実現する方法のメモ

  1. カスタマイズボタンからファイルダイアログを表示する
  2. 複数ファイルをAjaxでアップロードする
  3. サーバ側はNode.js Express multerで複数ファイルを受け取る

カスタマイズボタンからファイルダイアログを表示する

基本は、type=file属性のinputタグを使うのだが、この方法だとbootstrapで概観を変更できないし、いろんな方法でファイル選択したいケースはあるが、融通が利かない。方法は、formタグごと非表示にして、カスタムボタンからファイル選択ボタンへクリックイベントを発火させる。

<form id="upload_form" enctype="multipart/form-data" method="post" style="display:none;">
    <input id="file_dialog" type="file" name="select_files" multiple="multiple"
         accept="image/*,application/pdf">
</form>
<!-- カスタムボタン -->
<div class="panel-body">
    <button id="select_button" class="btn btn-warning btn-default" style="margin: 0px 5px;">
        select file
    </button>
    <button id="upload_button" class="btn btn-danger btn-default">upload</button>
</div>

jQueryを使用した例。カスタムボタンのクリックイベントで、ファイルダイアログボタンのクリックイベントを発火させる

<div class="code"> $("#select_button").on("click",function(e){ $("#file_dialog").click(); });

複数ファイルをAjaxでアップロードする

uploadボタンを押下して、Ajaxで送信する方法が以下。送信先のURLは/file_uploadとしている。javascriptのFormDataクラスのインスタンスを生成して、jQueryのAjax関数で送信する。

$("#upload_button").on("click",function(e){
    //upload
    var fd = new FormData( $('#upload_form')[0] );
    $.ajax({
        url: '/file_upload',
        type: 'post',
        data: fd,
        processData: false,
        contentType: false,
    }).done( function( res, status, xhr ){
        if( resobj.status == 0 ){
            alert("file uploading done.");
        }else{
            alert("file uploading failed.");
        }
    });
});

サーバ側をNode.js+Express+multerで実装する

Node.jsを使ったサーバー側の実装例。express4以降の場合、ファイルアップロード機能はmulterモジュールに切り離されているので、両モジュールをあらかじめnpmインストールしておく。 以下の場合、サーバールート直下にuploadsフォルダが作成されて、これがアップロードファイルのキャッシュディレクトリになる。ここにサーバーが付与した名称でファイルが一時保管される。

app.postの第2引数で、array()を使うことによって、複数ファイルを受けることができる。単一ファイルのみの場合はsingle()を使う。引数にinputタグのname属性値を指定する。

var multer = require( 'multer' );
var express = require('express');
var app = express();
 
var upload = multer({ dest: './uploads/' });
app.post( '/file_upload', upload.array( 'select_files' ),
  function( request, response ) {
    console.log( request.files );
    response.json({status:0});
});

実装が正しければ、request.filesにアップロードしたファイル情報が格納される。(恐らくこれをmulterがやっており、multerが機能してないとfilesプロパティが存在しない)ダンプの内容は以下のとおり。

例では、pdf、jpg、pngファイルを同時に送信している。filesプロパティは、json配列になっている。filenameの名称でuploadsフォルダにデータが格納されているので、これ以降の処理は、Node.js上でファイルIO処理を行うことになる。

[ { fieldname: 'select_files',
    originalname: 'sample1.pdf',
    encoding: '7bit',
    mimetype: 'application/pdf',
    destination: './uploads/',
    filename: '25c259b45062f54339d6e59a5b13dc2c',
    path: 'uploads\\25c259b45062f54339d6e59a5b13dc2c',
    size: 57980 },
  { fieldname: 'select_files',
    originalname: 'sample2.jpg',
    encoding: '7bit',
    mimetype: 'image/jpeg',
    destination: './uploads/',
    filename: '4e8fee2055acfaaacef4b5fb3044ecd1',
    path: 'uploads\\4e8fee2055acfaaacef4b5fb3044ecd1',
    size: 60647 },
  { fieldname: 'select_files',
    originalname: 'sample3.png',
    encoding: '7bit',
    mimetype: 'image/png',
    destination: './uploads/',
    filename: '9899a4d4d25abf65163d73d4b792ecab',
    path: 'uploads\\9899a4d4d25abf65163d73d4b792ecab',
    size: 36384 } ]

処理が終わった後は、response.json()に戻り値を追加すると、$.ajax.done()がコールバックされる。

[補足] bootstrapのプログレスバーを使って進捗を表示する

ポイントは、

  1. $.ajax()で処理中の進捗をコールバックすること
  2. コールバック内で、bootstrapのプログレスバーをアニメーションさせること

上記のコードを追加で修正すると以下になる。

  • プログレスバーの追加

概観のバリエーションは幾つかあるが、ストライプのアニメーションタイプを例とする。ポイントはwidth属性とテキストの0%である。width属性に0~100%の文字列を設定するとバーが割合に応じた長さになる。テキストはバーの中の文字列になる。これらを進捗コールバック内でjQueryを使って値を変えればよい。

<div class="progress">
    <div id="prog_var" role="progressbar"
        class="progress-bar progress-bar-success progress-bar-striped active" style="width: 0%;">
        0%
    </div>
</div>
  • $.ajax()処理の修正

ポイントは引数にxhrプロパティを追加し、コールバックを実装する。正しく動作すると、適宜function(ev)がコールバックされる。ev.loadedが進捗、ev.totalが母数になる。

$.ajax({
    url: '/file_upload',
    type: 'post',
    data: form,
    processData: false,
    contentType: false,
    xhr:function(){
        var xhr = $.ajaxSettings.xhr();
        if( xhr.upload ){
            xhr.upload.addEventListener("progress", function(ev){
                var progress_var = $("#prog_var");
                var prog = parseInt( (ev.loaded / ev.total) * 100 );
                progress_var.css({"width":prog.toString()+"%"});
                progress_var.text(prog.toString()+"%");
            }, false);
        }
        return xhr;
    }
}).done( function( res, status, xhr ){
    if( resobj.status == 0 ){
        alert("file uploading done.");
    }else{
        alert("file uploading failed.");
    }
});

lightbox風パネル上に上記の処理を実装して、実際にファイルアップロードを行ったところ。