No he visto ningún ejemplo que haga esto. ¿No está permitido en las especificaciones de la API?
Estoy buscando una solución fácil de arrastrar y soltar para cargar un árbol de carpetas completo de fotos.
html
file-upload
drag-and-drop
Miguel
fuente
fuente
input type=file
: stackoverflow.com/questions/9518335/…Respuestas:
Ahora es posible gracias a Chrome> = 21.
function traverseFileTree(item, path) { path = path || ""; if (item.isFile) { // Get file item.file(function(file) { console.log("File:", path + file.name); }); } else if (item.isDirectory) { // Get folder contents var dirReader = item.createReader(); dirReader.readEntries(function(entries) { for (var i=0; i<entries.length; i++) { traverseFileTree(entries[i], path + item.name + "/"); } }); } } dropArea.addEventListener("drop", function(event) { event.preventDefault(); var items = event.dataTransfer.items; for (var i=0; i<items.length; i++) { // webkitGetAsEntry is where the magic happens var item = items[i].webkitGetAsEntry(); if (item) { traverseFileTree(item); } } }, false);
Más información: https://protonet.info/blog/html5-experiment-drag-drop-of-folders/
fuente
readEntries
que no devolverá todos los enteros en un directorio. Según el enlace de error que proporcionó, escribí una respuesta completa: stackoverflow.com/a/53058574/885922Desafortunadamente, ninguna de las respuestas existentes es completamente correcta porque
readEntries
no necesariamente devolverá TODAS las entradas (archivo o directorio) para un directorio determinado. Esto es parte de la especificación API (consulte la sección de documentación a continuación).Para obtener todos los archivos, necesitaremos llamar
readEntries
repetidamente (para cada directorio que encontremos) hasta que devuelva una matriz vacía. Si no lo hacemos, perderemos algunos archivos / subdirectorios en un directorio, por ejemplo, en Chrome,readEntries
solo devolverá un máximo de 100 entradas a la vez.Usando Promises (
await
/async
) para demostrar más claramente el uso correcto dereadEntries
(ya que es asincrónico) y la búsqueda en amplitud (BFS) para recorrer la estructura del directorio:// Drop handler function to get all files async function getAllFileEntries(dataTransferItemList) { let fileEntries = []; // Use BFS to traverse entire directory/file structure let queue = []; // Unfortunately dataTransferItemList is not iterable i.e. no forEach for (let i = 0; i < dataTransferItemList.length; i++) { queue.push(dataTransferItemList[i].webkitGetAsEntry()); } while (queue.length > 0) { let entry = queue.shift(); if (entry.isFile) { fileEntries.push(entry); } else if (entry.isDirectory) { queue.push(...await readAllDirectoryEntries(entry.createReader())); } } return fileEntries; } // Get all the entries (files or sub-directories) in a directory // by calling readEntries until it returns empty array async function readAllDirectoryEntries(directoryReader) { let entries = []; let readEntries = await readEntriesPromise(directoryReader); while (readEntries.length > 0) { entries.push(...readEntries); readEntries = await readEntriesPromise(directoryReader); } return entries; } // Wrap readEntries in a promise to make working with readEntries easier // readEntries will return only some of the entries in a directory // e.g. Chrome returns at most 100 entries at a time async function readEntriesPromise(directoryReader) { try { return await new Promise((resolve, reject) => { directoryReader.readEntries(resolve, reject); }); } catch (err) { console.log(err); } }
Ejemplo de trabajo completo en Codepen: https://codepen.io/anon/pen/gBJrOP
FWIW Solo recogí esto porque no estaba recuperando todos los archivos que esperaba en un directorio que contiene 40,000 archivos (muchos directorios que contienen más de 100 archivos / subdirectorios) cuando usé la respuesta aceptada.
Documentación:
Este comportamiento está documentado en FileSystemDirectoryReader . Extracto con énfasis agregado:
Pero para ser justos, la documentación de MDN podría aclarar esto en otras secciones. La documentación readEntries () simplemente señala:
Y la única mención / sugerencia de que se necesitan varias llamadas está en la descripción del parámetro successCallback :
Podría decirse que la API también podría ser más intuitiva, pero como señala la documentación: es una característica no estándar / experimental, no está en una pista de estándares y no se puede esperar que funcione para todos los navegadores.
Relacionado:
readEntries
devolverá como máximo 100 entradas para un directorio (verificado como Chrome 64).readEntries
bastante bien en esta respuesta (aunque sin código).readEntries
de forma asincrónica sin BFS. También señala que Firefox devuelve todas las entradas en un directorio (a diferencia de Chrome), pero no podemos confiar en esto dada la especificación.fuente
FileSystemFileEntry
aFile
, a través delfile(successCb, failureCb)
método. Si también necesita la ruta completa, debe tomarla defileEntry.fullPath
(file.webkitRelativePath
será solo el nombre).Esta función le dará una promesa para una matriz de todos los archivos caídos, como
<input type="file"/>.files
:function getFilesWebkitDataTransferItems(dataTransferItems) { function traverseFileTreePromise(item, path='') { return new Promise( resolve => { if (item.isFile) { item.file(file => { file.filepath = path + file.name //save full path files.push(file) resolve(file) }) } else if (item.isDirectory) { let dirReader = item.createReader() dirReader.readEntries(entries => { let entriesPromises = [] for (let entr of entries) entriesPromises.push(traverseFileTreePromise(entr, path + item.name + "/")) resolve(Promise.all(entriesPromises)) }) } }) } let files = [] return new Promise((resolve, reject) => { let entriesPromises = [] for (let it of dataTransferItems) entriesPromises.push(traverseFileTreePromise(it.webkitGetAsEntry())) Promise.all(entriesPromises) .then(entries => { //console.log(entries) resolve(files) }) }) }
Uso:
dropArea.addEventListener("drop", function(event) { event.preventDefault(); var items = event.dataTransfer.items; getFilesFromWebkitDataTransferItems(items) .then(files => { ... }) }, false);
paquete npm
https://www.npmjs.com/package/datatransfer-files-promise
ejemplo de uso: https://github.com/grabantot/datatransfer-files-promise/blob/master/index.html
fuente
function getFilesWebkitDataTransferItems(dataTransfer)
debería serfunction getFilesWebkitDataTransferItems(items)
yfor (entr of entries)
debería serfor (let entr of entries)
.readEntries
repetidamente hasta que devuelve una matriz vacía.En este mensaje a la lista de correo HTML 5, Ian Hickson dice:
(Consulte también la propuesta de función original ). Por lo tanto, es seguro asumir que él considera que cargar carpetas usando arrastrar y soltar también está fuera de alcance. Aparentemente, depende del navegador entregar archivos individuales.
La carga de carpetas también tendría algunas otras dificultades, como lo describe Lars Gunther :
fuente
Ahora puede cargar directorios con arrastrar y soltar e ingresar.
<input type='file' webkitdirectory >
y para arrastrar y soltar (para navegadores webkit).
Manejo de carpetas de arrastrar y soltar.
<div id="dropzone"></div> <script> var dropzone = document.getElementById('dropzone'); dropzone.ondrop = function(e) { var length = e.dataTransfer.items.length; for (var i = 0; i < length; i++) { var entry = e.dataTransfer.items[i].webkitGetAsEntry(); if (entry.isFile) { ... // do whatever you want } else if (entry.isDirectory) { ... // do whatever you want } } }; </script>
Recursos:
http://updates.html5rocks.com/2012/07/Drag-and-drop-a-folder-onto-Chrome-now-available
fuente
Firefox ahora admite la carga de carpetas, a partir del 15 de noviembre de 2016, en la versión 50.0: https://developer.mozilla.org/en-US/Firefox/Releases/50#Files_and_directories
Puede arrastrar y soltar carpetas en Firefox o puede buscar y seleccionar una carpeta local para cargar. También admite carpetas anidadas en subcarpetas.
Eso significa que ahora puede usar Chrome, Firefox, Edge u Opera para cargar carpetas. No puede utilizar Safari o Internet Explorer en este momento.
fuente
A continuación, se muestra un ejemplo completo de cómo utilizar la API de entradas de directorio y archivo :
var dropzone = document.getElementById("dropzone"); var listing = document.getElementById("listing"); function scanAndLogFiles(item, container) { var elem = document.createElement("li"); elem.innerHTML = item.name; container.appendChild(elem); if (item.isDirectory) { var directoryReader = item.createReader(); var directoryContainer = document.createElement("ul"); container.appendChild(directoryContainer); directoryReader.readEntries(function(entries) { entries.forEach(function(entry) { scanAndLogFiles(entry, directoryContainer); }); }); } } dropzone.addEventListener( "dragover", function(event) { event.preventDefault(); }, false ); dropzone.addEventListener( "drop", function(event) { var items = event.dataTransfer.items; event.preventDefault(); listing.innerHTML = ""; for (var i = 0; i < items.length; i++) { var item = items[i].webkitGetAsEntry(); if (item) { scanAndLogFiles(item, listing); } } }, false );
body { font: 14px "Arial", sans-serif; } #dropzone { text-align: center; width: 300px; height: 100px; margin: 10px; padding: 10px; border: 4px dashed red; border-radius: 10px; } #boxtitle { display: table-cell; vertical-align: middle; text-align: center; color: black; font: bold 2em "Arial", sans-serif; width: 300px; height: 100px; }
<p>Drag files and/or directories to the box below!</p> <div id="dropzone"> <div id="boxtitle"> Drop Files Here </div> </div> <h2>Directory tree:</h2> <ul id="listing"></ul>
webkitGetAsEntry
es compatible con Chrome 13+, Firefox 50+ y Edge.Fuente: https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem/webkitGetAsEntry
fuente
Solo Chrome admite esta función. No ha tenido tracción y es probable que se retire.
Ref: https://developer.mozilla.org/en/docs/Web/API/DirectoryReader#readEntries
fuente
readEntries
que no se puede llamar sireadEntries
aún se está ejecutando otra llamada de . El diseño de la API de DirectoryReader no es el mejorACTUALIZACIÓN: Desde 2012 ha cambiado mucho, consulte las respuestas anteriores. Dejo esta respuesta aquí por el bien de la arqueología.
La especificación HTML5 NO dice que al seleccionar una carpeta para cargar, el navegador debe cargar todos los archivos contenidos de forma recursiva.
En realidad, en Chrome / Chromium, puede cargar una carpeta, pero cuando lo hace, simplemente carga un archivo de 4KB sin sentido, que representa el directorio. Algunas aplicaciones del lado del servidor como Alfresco pueden detectar esto y advertir al usuario que las carpetas no se pueden cargar:
fuente
Recientemente me encontré con la necesidad de implementar esto en dos de mis proyectos, así que creé un montón de funciones de utilidad para ayudar con esto.
Uno crea una estructura de datos que representa todas las carpetas, archivos y la relación entre ellos, así 👇
{ folders: [ { name: string, folders: Array, files: Array }, /* ... */ ], files: Array }
Mientras que el otro solo devuelve una matriz de todos los archivos (en todas las carpetas y subcarpetas).
Aquí está el enlace al paquete: https://www.npmjs.com/package/file-system-utils
fuente