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" >
</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();
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);
}
let elem = document.getElementById("tmp_files");
elem.files = new DataTransfer().files;
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;
}
let uniqueSet = new Set(file_name_list);
if (file_count != uniqueSet.size) {
alert("同じファイル名が既に存在します");
errflg = true;
}
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をセットすれば、実現できる。
補足
もっとよい方法があれば教えて。