fileスキーマでも動くD&Dでのファイル読み
2023-04-14
概要
html上の所定領域ファイル群やフォルダ等をD&Dする事で、それらを読み込む処理
ファイル一覧をざっとテキストで表示させたい、とか
大量の .json
を読み込んで中身を一括精査しつつHogeHogeしたい、とかを想定。
手動ドロップ経由しているからか、 file:///
スキーマでも動きます。
ファイル一覧をConsole上にリストアップしてみる
js 部分のみ記載しています。
本例では別途 #file_drop_area
を html 上で構築しておいてください ( ※ ドロップ受け入れ用 )
ファイルの中身は読まず、ファイル名(階層付き)を配列で収集して最後に表示しています。
(()=>{
const _dropAreaNode = document.getElementById("file_drop_area");
_dropAreaNode.addEventListener('dragover', (_e)=>{ _e.stopPropagation(); _e.preventDefault(); _e.dataTransfer.dropEffect = 'copy'; }, false);
_dropAreaNode.addEventListener('drop', async(_e)=>{
_e.stopPropagation();
_e.preventDefault();
const _filePathList = [];
/**
* @param {FileSystemDirectoryHandle|FileSystemFileHandle} _fh
* @param {string} _path
* @param {number} _depth
*/
async function* checkFileFromHandle(_fh , _path , _depth){
if(_fh.kind === "directory"){
_path += `${_fh.name}/`;
yield _path;
// そのフォルダ配下を(更に)再帰チェック
for await (const _handle of _fh.values()){
yield* checkFileFromHandle(_handle , _path , (_depth+1));
}
}
else if(_fh.kind === "file"){
/** @type {File} */
const _file = await _fh.getFile();
if(_file !== null){
console.log(`${_file.name}` , _file.size , _file.type);
_filePathList.push(`${_path}${_file.name}`);
}
}
}
const _fileHandlesPromises = [..._e.dataTransfer.items].map((_item) => _item.getAsFileSystemHandle());
// 第一階層でエントリーされた「ファイルorフォルダ」をこのforでチェック
for await (const _entryHandle of _fileHandlesPromises.values()){
console.log(`${_entryHandle.name} (${_entryHandle.kind}) がDROPされました`);
let _path = "/";
// 実際に、ジェネレーター関数で中身をチェックさせる。
// フォルダの場合、階層を掘る。
for await (const _fh of checkFileFromHandle(_entryHandle , _path , 0)) {
//console.log(_fh);
}
}
console.log(_filePathList);
} , false);
})();
.json だけ読んでみる
先程はリストアップするだけでしたが、今度は実際にファイルの中身を読んでみます。
先程のジェネレーター関数にて再帰で一覧を精査する折、
File.type
の値から .json
であると判断したものを、_readTargetList
へ順次格納。
その後、_readTargetList
のファイルを読むための処理を Promise
形式でこさえておき、
それを Promise.all()
で順次実行し終えたタイミングでConsoleに出力。
(()=>{
const _dropAreaNode = document.getElementById("file_drop_area");
_dropAreaNode.addEventListener('dragover', (_e)=>{ _e.stopPropagation(); _e.preventDefault(); _e.dataTransfer.dropEffect = 'copy'; }, false);
_dropAreaNode.addEventListener('drop', async(_e)=>{
_e.stopPropagation();
_e.preventDefault();
const _filePathList = [];
/** @type {Array.<File>} */
const _readTargetList = [];
/**
* @param {FileSystemDirectoryHandle|FileSystemFileHandle} _fh
* @param {string} _path
* @param {number} _depth
*/
async function* checkFileFromHandle(_fh , _path , _depth){
if(_fh.kind === "directory"){
_path += `${_fh.name}/`;
yield _path;
// そのフォルダ配下を(更に)再帰チェック
for await (const _handle of _fh.values()){
yield* checkFileFromHandle(_handle , _path , (_depth+1));
}
}
else if(_fh.kind === "file"){
/** @type {File} */
const _file = await _fh.getFile();
if(_file !== null){
//console.log(`${_file.name}` , _file.size , _file.type);
// json 判断
if(_file.type === "application/json"){
_readTargetList.push(_file);
}
}
}
}
const _fileHandlesPromises = [..._e.dataTransfer.items].map((_item) => _item.getAsFileSystemHandle());
for await (const _entryHandle of _fileHandlesPromises.values()){
console.log(`${_entryHandle.name} (${_entryHandle.kind}) がDROPされました`);
let _path = "/";
for await (const _fh of checkFileFromHandle(_entryHandle , _path , 0)) { }
}
const _jsonList = [];
// ファイルを読みPromise作成
const _promises = _readTargetList.map((_file)=>{
return new Promise((_resolve,_reject)=>{
if(_file instanceof File){
const _reader = new FileReader();
_reader.onload = (_e)=>{
try{
const _obj = JSON.parse(_e.target.result);
_jsonList.push(_obj);
}catch(_err){
console.error(`${_file.name} パース失敗`);
}
_resolve();
}
_reader.readAsText(_file);
return;
}
console.error(`${_file.name} を読み込めませんでした`);
_resolve();
});
});
// ↑でこさえたpromiseを実行
Promise.all(_promises).then(()=>{
// 全部終わったら出力
console.log(_jsonList);
}).catch((_err)=>{
console.error(_err);
});
} , false);
})();