ProcessWire Upload Formular mit Drag and Drop
Dies ist die Fortsetzung von ProcessWire Upload Formular mit AJAX und damit der dritte Teil der Upload Formular-Serie.
Links
https://www.smashingmagazine.com/2018/01/drag-drop-file-uploader-vanilla-js/ Anleitung für Dieses Tutorial https://www.ab-heute-programmieren.de/drag-and-drop-upload-mit-html5/ Schönes Beispiel mit einfacherem aber kompatibleren Code.
Dieses mal möchten wir das Upload Formular um eine Drag and Drop Area erweitern.
Drag and Drop Events
Einführung
Es gibt acht Events die der Browser abfeuert:
drag, dragend, dragenter, dragexit, dragleave, dragover, dragstart, drop
Wobei
drag, dragend, dragexit und dragstart
Nur für Seitenelemente wichtig sind die gezogen werden. Wir ziehen Elemente aus dem Dateisystem, daher brauchen wir diese nicht.
Damit man die Events nutzen kann, registriert man ganz normal Handler.
// DROP area
dropArea = document.getElementById('drop-area');
dropArea.addEventListener('dragenter', handlerFunction, false);
dropArea.addEventListener('dragleave', handlerFunction, false);
dropArea.addEventListener('dragover', handlerFunction, false);
dropArea.addEventListener('drop', handlerFunction, false);
Interessant hier. Wenn man über ein Kindelement der #drop-area zieht wird für die drop-area dragleave gefeuert und für das Kindelement dragenter. Wen man jetzt den Mausbutton losläßt wird trotzdem das dropevent für die drop-area gefeuert, weil sich der drag Event nach oben durchpropagiert.
Damit der Browser nachher auch die Datei hochlädt und nicht im öffnet, muss man auch noch mit event.preventDefault() in jedem Listener das Standardverhalten unterbinden.
Handler für die Drop Area
let dropArea = document.getElementById("drop-area")
// Prevent default drag behaviors
;['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, preventDefaults, false)
document.body.addEventListener(eventName, preventDefaults, false)
})
HTML Drop Area
Wir ergänzen einen Bereich #drop-area in die der User seine Dateien ziehen kann.
Außerdem bauen wir noch ein onchange Event ein. Dazu später mehr.
Das file Input Feld wird über CSS versteckt und das label stlen wir wie einen Button.
<div id="drop-area">
<input id="fileElem" type="file" onchange="handleFiles(this.files)" name="images[]" id="images" multiple="multiple" size="40" accept="image/jpg,image/jpeg,image/gif,image/png"/>
<label for="fileElem" id="images-label" class="upload-button">Select Images</label>
</div>
Styling für die Drop Area
#drop-area{
border: 2px dashed #ddd;
border-radius: 12px;
min-width: 200px;
margin: 50px 0;
padding: 12px;
background: #FFF;
}
#drop-area.highlight{
border-color: purple;
}
.upload-button {
display: inline-block;
padding: 10px;
background: #ccc;
cursor: pointer;
border-radius: 5px;
border: 1px solid #ccc;
}
.upload-button:hover {
background: #ddd;
}
Drag and Drop Funktionalität
Event Listener
let dropArea = document.getElementById('drop-area') // Referenz zur Drop Area
// Events hinzufügen und default Verhalten des Browsers unterbinden
;['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, preventDefaults, false)
})
function preventDefaults (e) {
e.preventDefault()
e.stopPropagation()
}
// Klassen für das Hovern setzen. Damit können wir dem User visualisieren "Hier bist du richtig"
;['dragenter', 'dragover'].forEach(eventName => { // dragenter UND dragover um bei dragleave der Kindelemente das Entfernen der Klasse zu verhindern
dropArea.addEventListener(eventName, highlight, false)
})
;['dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, unhighlight, false)
})
function highlight(e) {
dropArea.classList.add('highlight')
}
function unhighlight(e) {
dropArea.classList.remove('highlight')
}
// Jetzt die Drop Funktionalität (User läßt Dateien los)
dropArea.addEventListener('drop', handleDrop, false) // Listener
function handleDrop(e) {
let dt = e.dataTransfer
let files = dt.files // unsere Dateien als FileList Objekt (zum besseren Iterieren wird es in handleFiles() in ein Array konvertiert)
handleFiles(files)
}
function handleFiles(files) {
([...files]).forEach(uploadFile) // In Array umwandeln und jedes File uploaden.
}
function uploadFile(file) {
let url = 'YOUR URL HERE' // Zielskript
let formData = new FormData() // FormData Objekt
formData.append('file', file)
fetch(url, { // Daten senden -> geht nicht mit IE (siehe unten Alternative)
method: 'POST',
body: formData
})
.then(() => { /* Done. Inform the user */ })
.catch(() => { /* Error. Inform the user */ })
}
Alternativer Upload mit XML HttpRequest
// XML HttpRequest (vanilla)
function uploadFile(file) {
var url = 'YOUR URL HERE'
var xhr = new XMLHttpRequest()
var formData = new FormData()
xhr.open('POST', url, true)
xhr.addEventListener('readystatechange', function(e) {
if (xhr.readyState == 4 && xhr.status == 200) {
// Done. Inform the user
}
else if (xhr.readyState == 4 && xhr.status != 200) {
// Error. Inform the user
}
})
formData.append('file', file)
xhr.send(formData)
}
// Oder mit jQuery
Zusätzliche Funktionalität
Vorschau Bilder
Dazu nutzen wir die FileReader API. Sie arbeitet asnchron, so blockieren wir nicht den Thread
function previewFile(file) {
let reader = new FileReader() // FileReader Objekt
reader.readAsDataURL(file)
reader.onloadend = function() { // Event wenn Bild fertig geladen
let img = document.createElement('img') // img Element erzeugen
img.src = reader.result // Src Attribut setzen
document.getElementById('gallery').appendChild(img) // Bild an die Gallerie hängen
}
}
Und im HTML an passender Stelle noch den Galeriebereich einsetzen
Und etwas Styling dazu.
#gallery{
margin-top: 12px;
}
#gallery img{
width: 150px;
margin: 5px;
}
Progress Bar
JS
// ************************ Drag and drop ***************** //
let dropArea = document.getElementById("drop-area")
// Prevent default drag behaviors
;['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, preventDefaults, false)
document.body.addEventListener(eventName, preventDefaults, false)
})
// Highlight drop area when item is dragged over it
;['dragenter', 'dragover'].forEach(eventName => {
dropArea.addEventListener(eventName, highlight, false)
})
;['dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, unhighlight, false)
})
// Handle dropped files
dropArea.addEventListener('drop', handleDrop, false)
function preventDefaults (e) {
e.preventDefault()
e.stopPropagation()
}
function highlight(e) {
dropArea.classList.add('highlight')
}
function unhighlight(e) {
dropArea.classList.remove('active')
}
function handleDrop(e) {
var dt = e.dataTransfer
var files = dt.files
handleFiles(files)
}
let uploadProgress = []
let progressBar = document.getElementById('progress-bar')
function initializeProgress(numFiles) {
progressBar.value = 0
uploadProgress = []
for(let i = numFiles; i > 0; i--) {
uploadProgress.push(0)
}
}
function updateProgress(fileNumber, percent) {
uploadProgress[fileNumber] = percent
let total = uploadProgress.reduce((tot, curr) => tot + curr, 0) / uploadProgress.length
console.debug('update', fileNumber, percent, total)
progressBar.value = total
}
function handleFiles(files) {
files = [...files]
initializeProgress(files.length)
files.forEach(uploadFile)
files.forEach(previewFile)
}
function previewFile(file) {
let reader = new FileReader()
reader.readAsDataURL(file)
reader.onloadend = function() {
let img = document.createElement('img')
img.src = reader.result
document.getElementById('gallery').appendChild(img)
}
}
function uploadFile(file, i) {
var url = 'https://api.cloudinary.com/v1_1/joezimim007/image/upload'
var xhr = new XMLHttpRequest()
var formData = new FormData()
xhr.open('POST', url, true)
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest')
// Update progress (can be used to show progress indicator)
xhr.upload.addEventListener("progress", function(e) {
updateProgress(i, (e.loaded * 100.0 / e.total) || 100)
})
xhr.addEventListener('readystatechange', function(e) {
if (xhr.readyState == 4 && xhr.status == 200) {
updateProgress(i, 100) // <- Add this
}
else if (xhr.readyState == 4 && xhr.status != 200) {
// Error. Inform the user
}
})
formData.append('upload_preset', 'ujpu6gyk')
formData.append('file', file)
xhr.send(formData)
}
CSS
/* Drop Area */
#drop-area{
border: 2px dashed #ddd;
border-radius: 12px;
min-width: 200px;
margin: 50px 0;
padding: 12px;
background: #FFF;
}
#drop-area.highlight{
border-color: purple;
}
#gallery{
margin-top: 12px;
}
#gallery img{
width: 150px;
margin: 5px;
}