Skip to content

Instantly share code, notes, and snippets.

@gtindo
Last active June 10, 2022 21:55
Show Gist options
  • Save gtindo/32a59f8a2fd530973ef3619349a6d25d to your computer and use it in GitHub Desktop.
Save gtindo/32a59f8a2fd530973ef3619349a6d25d to your computer and use it in GitHub Desktop.
File Upload Web Component
class FileUpload extends HTMLElement {
constructor(){
super();
this.handleDropEvent = this.handleDropEvent.bind(this);
this._root = this.attachShadow({mode: "open"});
this._root.addEventListener("drop", this.handleDropEvent);
this._root.addEventListener("dragover", this.handleDragOver);
this.maxSize = 0;
this.maxFiles = 1;
this.filesTypes = [];
this.render();
this.error = this._root.querySelector("#error");
this.dropZone = this._root.querySelector("#drop-zone");
this.dropZone.addEventListener("click", () => {
const input = document.createElement("input");
input.type = "file";
input.multiple = true;
input.click();
input.onchange = () => {
this.handleFilesSelection(input.files);
}
});
}
static get observedAttributes(){
return [
"max-files",
"max-size",
"files-types"
]
}
attributeChangedCallback(name, oldValue, newValue){
switch(name){
case "max-files":
this.maxFiles = parseInt(newValue ?? "1");
break;
case "max-size":
this.maxSize = parseInt(newValue ?? "0");
break;
case "files-types":
this.filesTypes = newValue ? newValue.split(",") : [];
break;
}
}
/**
*
* @param {FileList} files
*/
validateFiles(files){
let status = {
err: false,
msg: ""
}
const nbFiles = files.length;
if(nbFiles > this.maxFiles) {
status.err = true;
status.msg += `There are ${nbFiles - this.maxFiles} more files than required <br />`;
}
for(let i = 0; i < nbFiles; i++){
const file = files.item(i);
if(file.size > this.maxSize){
status.err = true;
status.msg += `${file.name} size exceed ${this.maxSize} limit <br />`;
}
if(this.filesTypes.length > 0 && !this.filesTypes.includes(file.type)){
status.err = true;
status.msg += `${file.name} mime type is not supported <br />`;
}
}
return status;
}
/**
*
* @param {DragEvent} e
*/
handleDropEvent(e){
e.preventDefault();
const files = e.dataTransfer.files;
this.handleFilesSelection(files);
}
/**
*
* @param {FileList} files
*/
handleFilesSelection(files) {
const validation = this.validateFiles(files);
if(validation.err) {
this.error.innerHTML = validation.msg;
return;
}
this.error.innerHTML = "";
this.dispatchEvent(new CustomEvent("files", {
detail: {
files
}
}));
}
handleDragOver(e) {
e.preventDefault();
}
render(){
this._root.innerHTML = `
<style>
:host {
display: block;
width: 350px;
height: 350px;
border: solid 1px black;
text-align: center;
background: #eee;
}
.drop-zone {
display: flex;
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
cursor: pointer;
}
.drop-zone:hover {
background: #ddd;
}
#error {
font-size: 12px;
color: #aaa;
}
</style>
<div class="drop-zone" id="drop-zone">
<div>
Drag and Drop your files here or click to upload! <br />
<span id="error"></span>
</div>
</div>`
;
}
}
customElements.define("file-upload", FileUpload);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>File Upload Demo</title>
<script src="file-upload.js" defer></script>
<style>
</style>
</head>
<body>
<file-upload id="file-upload"
max-files="2"
max-size="5000000"
files-types="image/jpeg,image/jpg,image/png"
></file-upload>
<script>
const fileUpload = document.querySelector("file-upload");
fileUpload.addEventListener("files", (e) => {
console.log(e.detail);
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment