Created
October 31, 2022 18:01
-
-
Save DarrenSem/178257a44ca1b036e7e989f30ae40a4f to your computer and use it in GitHub Desktop.
upload.js: LOAD data FROM files (aka 'import/upload') including handling drag-and-drop
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/////// upload.js: LOAD data FROM files (aka 'import/upload')... | |
// const buildFileSelector=(a,m)=>{const c=null==a?"":a+"";return Object.assign(document.createElement("input"),{type:"file"},m&&{multiple:m},c.trim().length&&{accept:c})}; | |
// const resetFileSelector=e=>e&&(e.value=null,e); | |
// const getFileSelector=(e,c,a,m,r)=>resetFileSelector(e)&&e.readAs===r?e:Object.assign(e||buildFileSelector(a,m),{onchange:c,readAs:r}); | |
// SEE BELOW: handleUploadOrDropExample = event => { | |
// Warning: "File chooser dialog can only be shown with a user activation." (just like clipboard functionality) | |
// const buildFileSelector=(a,m)=>{const c=null==a?"":a+"";return Object.assign(document.createElement("input"),{type:"file"},m&&{multiple:m},c.trim().length&&{accept:c})}; | |
const buildFileSelector = (acceptHint, multiple) => { | |
const accept = acceptHint == null ? "" : String(acceptHint); // cf. https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#accept cf. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept | |
return Object.assign( | |
document.createElement("input") | |
, { type: "file" } // AKA newlyCreatedInputElement.setAttribute("type", "file"); | |
, multiple && { multiple } // NOTE: true if ANYTHING other than false OR 0 OR "" (even string "0" enables it) | |
, accept.trim().length && { accept } | |
); | |
}; // via https://www.richardkotze.com/top-tips/how-to-open-file-dialogue-just-using-javascript | |
// call resetFileSelector(fileSelector) AFTER processing file, otherwise fileSelector.onChange event won't be triggered on 2nd+ click of SAME filename | |
// const resetFileSelector=e=>e&&(e.value=null,e); | |
const resetFileSelector = e => e && (e.value = null, e); | |
// const getFileSelector=(e,c,a,m,r)=>resetFileSelector(e)&&e.readAs===r?e:Object.assign(e||buildFileSelector(a,m),{onchange:c,readAs:r}); | |
const getFileSelector = (elToReuse, onchange, acceptList, multiple, readAs) => ( | |
resetFileSelector(elToReuse) && elToReuse.readAs === readAs ? elToReuse : Object.assign( | |
elToReuse || buildFileSelector(acceptList, multiple) | |
, { onchange, readAs } | |
) | |
); | |
// accept LIST: ["audio/*", "image/*", "video/*"] or "image/png" or ".png" or ".pdf" or "application/json" etc. (even MULTIPLE: "video/*,audio/*,.json") via https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept#unique_file_type_specifiers | |
const ACCEPT_VIDEO = "video/*"; | |
const ACCEPT_MARKDOWN = "*.md"; | |
const ACCEPT_TXT = "text/plain"; | |
const ACCEPT_JSON = "application/json"; | |
const ACCEPT_PNG = "image/png"; | |
const fileReaderExample = (file, onload, readAs = "Text", encodingIfText) => { | |
console.warn(`fileReaderExample: readAs${readAs} "${file.name}" (${file.type}, ${file.size} bytes) ${file.lastModifiedDate}`); // file.lastModified = +lastModifiedDate, .webkitRelativePath = ? | |
try { | |
const reader = Object.assign(new FileReader(), { onload }); // https://developer.mozilla.org/en-US/docs/Web/API/FileReader , cf. https://stackoverflow.com/questions/66487921/react-load-file-from-input | |
// reader.onprogress = reader.onloadstart = reader.onloadend = event => console.log(event, event.lengthComputable ? `${event.loaded} / ${event.total}` : ""); | |
reader[`readAs${readAs || ['Text', 'DataURL', 'ArrayBuffer'][0]}`](file, encodingIfText); | |
} catch (e) { return e; }; | |
}; | |
const handleUploadOrDropExample = event => { | |
const { files, readAs } = !event ? {} : event.dataTransfer || event.target; // .dataTransfer via onDrop | |
const fileFirst = files && files.item(0); // recommended to use files.item(n) instead of files[n] | |
resetFileSelector(el) // *CRITICAL* otherwise fileSelector.onChange event won't be triggered on 2nd+ click of SAME filename | |
const processFile = event => { | |
const uploadedData = event && event.target.result; | |
const type = uploadedData !== undefined && uploadedData.constructor; | |
console.warn(`Loaded '${fileFirst.name}' (${(uploadedData || {})[type === ArrayBuffer ? "byteLength" : "length"]} bytes).`); | |
console.log(`TODO: Processing uploadedData: (${type.name})`, uploadedData); | |
if (type === ArrayBuffer) { | |
// console.log(new Uint8Array(uploadedData).map(b => String.fromCharCode(b)).join("")); // FAILS, because Uint8Array can't store as String | |
console.log([...new Uint8Array(uploadedData)].map(b => String.fromCharCode(b)).join("")); // creates new Array, so CAN store String elements | |
console.log(Array.from(new Uint8Array(uploadedData), b => String.fromCharCode(b)).join(""));// different syntax (but arguably more clear) | |
}; | |
}; | |
if (!fileFirst) return console.warn("Cancelled, no files in event:", { event }); | |
fileReaderExample(fileFirst, processFile, readAs); | |
}; | |
// console.clear(); | |
let el; | |
// "SELECT FILE(S) TO UPLOAD..." examples of setting/overwriting fileSelector element inside main loop, render, etc... | |
if ("XYZreadAsText" === "readAsText") { // existing el.onchange will be overwritten | |
el = getFileSelector(el, handleUploadOrDropExample); | |
el.click(); // call resetFileSelector(fileSelector) AFTER processing file, otherwise fileSelector.onChange event won't be triggered on 2nd+ click of SAME filename | |
}; | |
if ("readAsArrayBuffer" === "readAsArrayBuffer") { // existing el.onchange and el.readAs will be overwritten | |
const multi = false; | |
el = getFileSelector(el, handleUploadOrDropExample, [ACCEPT_JSON, ACCEPT_TXT], multi, "ArrayBuffer"); | |
el.click(); // call resetFileSelector(fileSelector) AFTER processing file, otherwise fileSelector.onChange event won't be triggered on 2nd+ click of SAME filename | |
}; | |
if ("readAsDataURL" === "readAsDataURL") { // diffferent fileSelector so won't overwrite el.onchange and el.readAs | |
const multi = true; | |
let el2 = getFileSelector("", handleUploadOrDropExample, ` ${ACCEPT_PNG} , , ${ACCEPT_JSON} `, multi, "DataURL"); | |
el2.click(); // call resetFileSelector(fileSelector) AFTER processing file, otherwise fileSelector.onChange event won't be triggered on 2nd+ click of SAME filename | |
}; | |
if ("XYZdrag and drop example" === "drag and drop example") { | |
document.body.insertAdjacentHTML("afterbegin", ` | |
<p id="drag" draggable="true">-_- drag (non-file) from here -_-</p> | |
<p id="drop" | |
ondragover="event.preventDefault();" | |
ondrop="event.preventDefault(); | |
event.dataTransfer.readAs = ['Text', 'DataURL', 'ArrayBuffer'][1]; | |
debugger; handleUploadOrDropExample(event);" | |
>...<b> [ drop into here ] </b>...</p>`); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment