プロフィール

髭山髭人(ひげひと)

自分の書いた記事が、一人でも誰かの役に立てば...
活動信条の一つとして「貴方のメモは、誰かのヒント」というのがあります。

このサイトについて

本家HP packetroom.net から切り離した いわゆる技術メモ用のブログで、無料レンタルサーバーにて運用しています。広告表示はその義務なのでご容赦。
XREA さんには長年お世話になっています

fileスキーマでも動くD&Dでのファイル読み

概要

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);
})();

その他リンク