Cara membuka direktori

Menangani direktori bukanlah sesuatu yang akan Anda tangani setiap hari, tetapi terkadang kasus penggunaan muncul, seperti keinginan untuk memproses semua gambar dalam direktori. Dengan File System Access API, pengguna kini dapat membuka direktori di browser dan memutuskan apakah mereka memerlukan akses tulis atau tidak.

Cara modern

Menggunakan metode showDirectoryPicker() File System Access API

Untuk membuka direktori, panggil showDirectoryPicker(), yang menampilkan promise dengan direktori yang dipilih. Jika memerlukan akses tulis, Anda dapat meneruskan { mode: 'readwrite' } ke metode tersebut.

Dukungan Browser

  • 86
  • 86
  • x
  • x

Sumber

Cara klasik

Menggunakan elemen <input type="file" webkitdirectory>

Elemen <input type="file" webkitdirectory> di halaman memungkinkan pengguna mengkliknya dan membuka direktori. Triknya sekarang terdiri dari menyisipkan elemen yang tidak terlihat ke halaman dengan JavaScript dan mengkliknya secara terprogram.

Dukungan Browser

  • 7
  • 13
  • 50
  • 11,1

Sumber

{i>Progressive enhancement <i}

Metode di bawah ini menggunakan File System Access API jika didukung dan metode lain akan melakukan fallback ke pendekatan klasik. Dalam kedua kasus tersebut, fungsi tersebut menampilkan direktori. Namun, jika File System Access API didukung, setiap objek file juga memiliki FileSystemDirectoryHandle yang tersimpan di properti directoryHandle dan FileSystemFileHandle yang disimpan di properti handle, sehingga Anda dapat secara opsional membuat serialisasi handle ke disk.

const openDirectory = async (mode = "read") => {
  // Feature detection. The API needs to be supported
  // and the app not run in an iframe.
  const supportsFileSystemAccess =
    "showDirectoryPicker" in window &&
    (() => {
      try {
        return window.self === window.top;
      } catch {
        return false;
      }
    })();
  // If the File System Access API is supported…
  if (supportsFileSystemAccess) {
    let directoryStructure = undefined;

    // Recursive function that walks the directory structure.
    const getFiles = async (dirHandle, path = dirHandle.name) => {
      const dirs = [];
      const files = [];
      for await (const entry of dirHandle.values()) {
        const nestedPath = `${path}/${entry.name}`;
        if (entry.kind === "file") {
          files.push(
            entry.getFile().then((file) => {
              file.directoryHandle = dirHandle;
              file.handle = entry;
              return Object.defineProperty(file, "webkitRelativePath", {
                configurable: true,
                enumerable: true,
                get: () => nestedPath,
              });
            })
          );
        } else if (entry.kind === "directory") {
          dirs.push(getFiles(entry, nestedPath));
        }
      }
      return [
        ...(await Promise.all(dirs)).flat(),
        ...(await Promise.all(files)),
      ];
    };

    try {
      // Open the directory.
      const handle = await showDirectoryPicker({
        mode,
      });
      // Get the directory structure.
      directoryStructure = getFiles(handle, undefined);
    } catch (err) {
      if (err.name !== "AbortError") {
        console.error(err.name, err.message);
      }
    }
    return directoryStructure;
  }
  // Fallback if the File System Access API is not supported.
  return new Promise((resolve) => {
    const input = document.createElement('input');
    input.type = 'file';
    input.webkitdirectory = true;

    input.addEventListener('change', () => {
      let files = Array.from(input.files);
      resolve(files);
    });
    if ('showPicker' in HTMLInputElement.prototype) {
      input.showPicker();
    } else {
      input.click();
    }
  });
};

Bacaan lebih lanjut

Demo

HTML

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link
      rel="icon"
      href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>📂</text></svg>"
    />
    <title>How to open a directory</title>
  </head>
  <body>
    <h1>How to open a directory</h1>
    <button type="button">Open directory</button>
    <pre></pre>
  </body>
</html>

CSS


        :root {
  color-scheme: dark light;
}

html {
  box-sizing: border-box;
}

*,
*:before,
*:after {
  box-sizing: inherit;
}

body {
  margin: 1rem;
  font-family: system-ui, sans-serif;
}
        

JS


        const button = document.querySelector('button');
const pre = document.querySelector('pre');

const openDirectory = async (mode = "read") => {
  // Feature detection. The API needs to be supported
  // and the app not run in an iframe.
  const supportsFileSystemAccess =
    "showDirectoryPicker" in window &&
    (() => {
      try {
        return window.self === window.top;
      } catch {
        return false;
      }
    })();
  // If the File System Access API is supported…
  if (supportsFileSystemAccess) {
    let directoryStructure = undefined;

    const getFiles = async (dirHandle, path = dirHandle.name) => {
      const dirs = [];
      const files = [];
      for await (const entry of dirHandle.values()) {
        const nestedPath = `${path}/${entry.name}`;
        if (entry.kind === "file") {
          files.push(
            entry.getFile().then((file) => {
              file.directoryHandle = dirHandle;
              file.handle = entry;
              return Object.defineProperty(file, "webkitRelativePath", {
                configurable: true,
                enumerable: true,
                get: () => nestedPath,
              });
            })
          );
        } else if (entry.kind === "directory") {
          dirs.push(getFiles(entry, nestedPath));
        }
      }
      return [
        ...(await Promise.all(dirs)).flat(),
        ...(await Promise.all(files)),
      ];
    };

    try {
      const handle = await showDirectoryPicker({
        mode,
      });
      directoryStructure = getFiles(handle, undefined);
    } catch (err) {
      if (err.name !== "AbortError") {
        console.error(err.name, err.message);
      }
    }
    return directoryStructure;
  }
  // Fallback if the File System Access API is not supported.
  return new Promise((resolve) => {
    const input = document.createElement('input');
    input.type = 'file';
    input.webkitdirectory = true;

    input.addEventListener('change', () => {
      let files = Array.from(input.files);
      resolve(files);
    });
    if ('showPicker' in HTMLInputElement.prototype) {
      input.showPicker();
    } else {
      input.click();
    }
  });
};

button.addEventListener('click', async () => {
  const filesInDirectory = await openDirectory();
  if (!filesInDirectory) {
    return;
  }
  Array.from(filesInDirectory).forEach((file) => (pre.textContent += `${file.name}\n`));
});