An upload form suitable for Web Accessibility standards. It also provides a fallback solution.
A Pen by Ahmet Kurt on CodePen.
<section class="upload-file"> | |
<h1 class="heading">Upload files</h1> | |
<p class="paragraph">You can upload multiple files.</p> | |
<div class="files hidden" data-files></div> | |
<form class="form" enctype="multipart/formdata"> | |
<div class="dropzone" data-dropzone> | |
<div class="dropzone__field"> | |
<label class="dropzone__label" for="files" data-dropzone-label>Upload files</label> | |
<input class="dropzone__file" id="files" name="files" type="file" multiple data-dropzone-file> | |
</div> | |
<button class="dropzone__upload button" name="uploadFile" type="submit">Upload files</button> | |
</div> | |
<button class="button" name="continue" type="submit">Continue</button> | |
</form> | |
</section> |
function isDragAndDropSupported() { | |
return typeof document.createElement('div').ondrop != 'undefined'; | |
} | |
function isFormDataSupported() { | |
return typeof FormData == 'function'; | |
} | |
function isFileApiSupported() { | |
const input = document.createElement('input'); | |
input.type = 'file'; | |
return typeof input.files != 'undefined'; | |
}; | |
if(isDragAndDropSupported() && isFormDataSupported() && isFileApiSupported()) { | |
var Dropzone = function(container) { | |
this.dropzone = container; | |
this.dropzone.classList.add('dropzone--actual'); | |
this.dropzone.querySelector('[data-dropzone-label]').classList.add('button'); | |
this.setupDropzone(); | |
this.setupFileInput(); | |
this.setupStatusBox(); | |
this.setupFiles(); | |
this.setupFileRemove(); | |
}; | |
Dropzone.prototype.setupDropzone = function() { | |
this.dropzone.addEventListener('dragover', this.onDragOver.bind(this)); | |
this.dropzone.addEventListener('dragleave', this.onDragLeave.bind(this)); | |
this.dropzone.addEventListener('drop', this.onDrop.bind(this)); | |
}; | |
Dropzone.prototype.onDragOver = function(event) { | |
event.preventDefault(); | |
this.dropzone.classList.add('dropzone--dragover'); | |
}; | |
Dropzone.prototype.onDragLeave = function() { | |
this.dropzone.classList.remove('dropzone--dragover'); | |
}; | |
Dropzone.prototype.onDrop = function(event) { | |
event.preventDefault(); | |
this.dropzone.classList.remove('dropzone--dragover'); | |
this.files.classList.remove('hidden'); | |
this.status.innerHTML = 'Uploading files, please wait...'; | |
this.uploadFiles(event.dataTransfer.files); | |
}; | |
Dropzone.prototype.setupFileInput = function() { | |
this.fileInput = document.querySelector('[data-dropzone-file]'); | |
this.fileInput.addEventListener('change', this.onFileChange.bind(this)); | |
this.fileInput.addEventListener('focus', this.onFileFocus.bind(this)); | |
this.fileInput.addEventListener('blur', this.onFileBlur.bind(this)); | |
}; | |
Dropzone.prototype.onFileChange = function(event) { | |
this.files.classList.remove('hidden'); | |
this.status.innerHTML = 'Uploading files, please wait...'; | |
this.uploadFiles(event.currentTarget.files); | |
}; | |
Dropzone.prototype.onFileFocus = function() { | |
this.dropzone.querySelector('[data-dropzone-label]').classList.add('dropzone__label--focused'); | |
}; | |
Dropzone.prototype.onFileBlur = function() { | |
this.dropzone.querySelector('[data-dropzone-label]').classList.remove('dropzone__label--focused'); | |
}; | |
Dropzone.prototype.setupStatusBox = function() { | |
this.status = document.createElement('div'); | |
this.status.className = 'visually-hidden'; | |
this.status.setAttribute('role', 'status'); | |
this.status.setAttribute('aria-live', 'polite'); | |
this.dropzone.appendChild(this.status); | |
}; | |
Dropzone.prototype.setupFiles = function() { | |
this.filesHeading = document.createElement('h2'); | |
this.filesHeading.className = 'heading'; | |
this.filesHeading.innerHTML = 'Files'; | |
this.file = document.createElement('ul'); | |
this.file.className = 'file'; | |
this.files = document.querySelector('[data-files]'); | |
this.files.appendChild(this.filesHeading); | |
this.files.appendChild(this.file); | |
}; | |
Dropzone.prototype.setupFileRemove = function() { | |
document.querySelector('[data-files]').addEventListener('click', this.onFileRemoveClick.bind(this)); | |
}; | |
Dropzone.prototype.onFileRemoveClick = function(event) { | |
const eventTarget = event.target; | |
if(eventTarget.hasAttribute('data-file-remove')) { | |
const listItem = eventTarget.parentNode; | |
listItem.parentNode.removeChild(listItem); | |
} | |
}; | |
Dropzone.prototype.getStatusHtml = function(result, isSuccess) { | |
this.fileName = document.createElement('span'); | |
this.fileStatus = document.createElement('span'); | |
this.fileStatus.className = 'file__status file__status--error'; | |
this.fileStatus.innerHTML = 'Error'; | |
if(isSuccess) { | |
this.fileLink = document.createElement('a'); | |
this.fileLink.className = 'anchor'; | |
this.fileLink.setAttribute('href', '#'); | |
this.fileLink.setAttribute('target', '_blank'); | |
this.fileLink.innerHTML = result.name; | |
this.fileName = document.createElement('div'); | |
this.fileName.appendChild(this.fileLink); | |
this.fileStatus.className = 'file__status file__status--success'; | |
this.fileStatus.innerHTML = 'Success'; | |
} else | |
this.fileName.innerHTML = result.name; | |
this.fileName.className = 'file__name'; | |
this.fileRemove = document.createElement('button'); | |
this.fileRemove.className = 'file__remove button'; | |
this.fileRemove.setAttribute('type', 'button'); | |
this.fileRemove.setAttribute('data-file-remove', ''); | |
this.fileRemove.innerHTML = 'Remove'; | |
this.fileItem = document.createElement('li'); | |
this.fileItem.className = 'file__item'; | |
this.fileItem.appendChild(this.fileName); | |
this.fileItem.appendChild(this.fileStatus); | |
this.fileItem.appendChild(this.fileRemove); | |
return this.fileItem; | |
}; | |
Dropzone.prototype.uploadFiles = function(files) { | |
for(const file of files) | |
this.uploadFile(file); | |
}; | |
Dropzone.prototype.uploadFile = function(file) { | |
const formData = new FormData(); | |
formData.append('documents', file); | |
this.file.appendChild(this.getStatusHtml(file, true)); | |
}; | |
} | |
if(typeof Dropzone != 'undefined') | |
new Dropzone(document.querySelector('[data-dropzone]')); |
body { | |
margin: 1rem; | |
font-family: 'Arial', sans-serif; | |
} | |
:focus { | |
outline: 0; | |
box-shadow: 0 0 .0625rem .25rem #5e9ed6; | |
} | |
.hidden { | |
display: none; | |
} | |
.visually-hidden { | |
position: absolute !important; | |
overflow: hidden !important; | |
width: .0625rem !important; | |
height: .0625rem !important; | |
padding: 0 !important; | |
border: 0 !important; | |
margin: -.0625rem !important; | |
clip: rect(0 0 0 0) !important; | |
} | |
.anchor { | |
border-radius: .125rem; | |
color: #000; | |
} | |
.button { | |
display: block; | |
padding: .375rem .75rem; | |
border: none; | |
border-radius: .125rem; | |
background-color: #000; | |
color: #fff; | |
font-family: inherit; | |
font-size: inherit; | |
text-decoration: none; | |
cursor: pointer; | |
} | |
.upload-file { | |
max-width: 736px; | |
} | |
.file { | |
padding-left: 0; | |
list-style: none; | |
&__item { | |
display: flex; | |
align-items: center; | |
padding: .5rem; | |
border-radius: .125rem; | |
margin-bottom: .0625rem; | |
background-color: #f0f0f0; | |
} | |
&__name { | |
flex-basis: 40%; | |
overflow: hidden; | |
padding: .3125rem; | |
margin-right: .5rem; | |
text-overflow: ellipsis; | |
white-space: nowrap; | |
} | |
&__status { | |
flex-basis: 40%; | |
margin-right: .5rem; | |
&--success { | |
color: #008000; | |
} | |
&--error { | |
color: #800000; | |
} | |
} | |
&__remove { | |
flex-basis: 20%; | |
} | |
} | |
.dropzone { | |
$this: &; | |
margin-bottom: 1rem; | |
&--actual { | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
padding: 3rem; | |
border: .125rem dashed #000; | |
border-radius: .125rem; | |
#{$this}__label { | |
margin-bottom: 0; | |
&::after { | |
content: ''; | |
} | |
&--focused { | |
outline: 0; | |
box-shadow: 0 0 .0625rem .25rem #5e9ed6; | |
} | |
} | |
#{$this}__file { | |
position: absolute; | |
left: -9999em; | |
} | |
#{$this}__upload { | |
display: none; | |
} | |
} | |
&--dragover { | |
background-color: #ddd; | |
} | |
&__label { | |
display: inline-block; | |
margin-bottom: .5rem; | |
&::after { | |
content: ':'; | |
} | |
} | |
&__file { | |
display: block; | |
padding: .375rem .75rem; | |
border: .0625rem solid #000; | |
border-radius: .125rem; | |
font-family: inherit; | |
font-size: inherit; | |
} | |
&__upload { | |
margin-top: .5rem; | |
} | |
} |
An upload form suitable for Web Accessibility standards. It also provides a fallback solution.
A Pen by Ahmet Kurt on CodePen.