Skip to content

Instantly share code, notes, and snippets.

@DarrenSem
Created October 31, 2022 18:01
Show Gist options
  • Save DarrenSem/178257a44ca1b036e7e989f30ae40a4f to your computer and use it in GitHub Desktop.
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
/////// 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