JSのライブラリを利用せずに、モダンなファイルアップロード処理を書こうとすると 割と色々コーディングしないといけないということが分かったのでメモ。
ネットに転がっているのは、
- D&DでUPする場合と、ファイルダイアログからUPする場合を共存させる考慮
- ファイルを追加でUPする場合の考慮
- 特定のファイルを削除する場合の考慮
- UPしたファイルを一覧で見たい場合の考慮
などが漏れているサンプルが多かったので、参考になれば幸い。
前提
- JQueryは使わない
- ライブラリは使わない(いわゆるバニラJS)Vueとか使ったほうが本当はきれいなんだけどね。
- IEなんて考慮しない
- 以下では、ある程度JavaScriptを分かっている人向けの適当な解説しかしない(メモなので)
サンプルページ
ファイルUPすると、リストでファイルが表示される。
https://gologius.github.io/test/file_upload/upload.html
ソース
下記参照。
gologius.github.io/test/file_upload at master · gologius/gologius.github.io · GitHub
ポイント1
input タグを二つ用意する。 片方がファイルを仮UPする用で、チェックが通ったものを、もう一方のinputにセットする。
HTML(抜粋
<form action="./upload.php" method="post" enctype="multipart/form-data"> <div id="dd_area" class="drop_area_off"> <div> ここにファイルをドラッグ&ドロップ or <input type="button" value="手動でファイル追加" onclick="clickUpload();" > </div> <input type="file" id="upload_files" name="upload_files[]"> <input type="file" id="tmp_files" name="tmp_files" multiple> </div> <div>アップロードしたファイル↓</div> <table class="file_list"> <tbody id="upload_files_list" > <!--実際の要素はJS側から挿入されるで--> </tbody> </table> </form>
Javascript(抜粋
function upload_files(new_files) { //変数準備 let sum_size = 0; let file_count = 0; let file_name_list = []; let work_transfer = new DataTransfer(); //通常の配列ではなく、datatransferでファイル管理する必要あり。 //現時点でUPされているファイルを走査する → 配列に格納 let now_files = document.getElementById("upload_files").files; for (var i = 0; i < now_files.length; i++) { var file = now_files[i]; work_transfer.items.add(file); sum_size += file.size; file_count += 1; file_name_list.push(file.name); } //アップロード対象のファイル群を走査 → 配列に格納 for (var i = 0; i < new_files.length; i++) { var file = new_files[i]; work_transfer.items.add(file); sum_size += file.size; file_count += 1; file_name_list.push(file.name); } //対象ファイルの走査が完了して用済みのため、tmp_filesはリセットする let elem = document.getElementById("tmp_files"); elem.files = new DataTransfer().files; //UPしてよいか判定 let errflg = false; if (sum_size > MAX_FILE_SIZE_MB * 1048576){ alert(`ファイルサイズの合計値は ${MAX_FILE_SIZE_MB}MB です`); errflg = true; } if (file_count > MAX_FILE_COUNT){ alert(`ファイルをUPできる最大個数は ${MAX_FILE_COUNT}個 です`); errflg = true; } //重複を削除したSetを生成。ファイル名が重複していると、Setの要素数がファイル数よりも減るので、それを利用する let uniqueSet = new Set(file_name_list); if (file_count != uniqueSet.size) { alert("同じファイル名が既に存在します"); errflg = true; } //UPしても問題ないと判断できれば、実際にファイルアップロードする if (errflg == false) { document.getElementById("upload_files").files = work_transfer.files; } //表示更新 update_file_list_html(); return; }
なんでこんなことをしているのかというと、 inputタグからファイルダイアログを開くと、そこにセットされていたファイルが上書きされてしまう。 上書きされてしまうので、追加でのファイルUPができないのである。
仮UPすることで、ファイル全数のサイズや個数のチェックもできるので、 仮UPしたほうがいろいろ都合もよい。
ポイント2
inputタグが持つfiles要素は、インデックスを指定して参照はできるが、 「直接の加工はできない」
できない例
let now_files = document.getElementById("upload_files").files; for (var i = 0; i < now_files.length; i++) { now_files[i] = tmp_file;// ←これはできない }
よって、datatransferを丸ごとfiles にセットしてあげる必要がある。
できる例
let now_files = document.getElementById("upload_files").files; let work_transfer = new DataTransfer(); for (var i = 0; i < now_files.length; i++) { work_transfer.items.add(tmp_file); } document.getElementById("upload_files").files = work_transfer;
特定のファイルを削除する場合には、「特定のファイルを除いた」datatransferをセットすれば、実現できる。
補足
もっとよい方法があれば教えて。