OpenXE/classes/Modules/FileBrowser/www/js/jquery.dropzoneUpload.js
2021-05-21 08:49:41 +02:00

706 lines
26 KiB
JavaScript

/**
* # DropzoneUpload
*
* ## Initialisierung
*
* `$('#dropzone').dropzoneUpload(options);`
*
* ## Initialisierungsoptionen
*
* * debug (bool):
* Debugging aktivieren/deaktivieren
* Default: `false`
*
* * upload.url (string):
* URL die zum Hochladen von Dateien verwendet wird.
* Default: `'index.php?module=dateibrowser&action=upload&cmd=upload-file'`
*
* * upload.formData (object):
* Zusätzliche POST-Daten für den Datei-Upload.
* Default: `{}`
*
* * upload.simultaneous (number)
* Anzahl der simultanen Upload-Requests.
* Default: `5`
*
* ## DropzoneUpload-API
*
* API-Objekt holen: `$('#dropzone').DropZoneUploadApi()`
*
* ### API-Methoden
*
* * `startUploads()`
* Startet alle offenen Uploads
*
* * `cancelUploads()`
* Bricht alle laufenden und wartenden Uploads ab
*
* * `reset()`
* Setzt alles auf den Ausgangszustand zurück
*/
(function ($) {
'use strict';
var DropZoneUpload = function ($elem, options) {
var STATUS = {
INITAL: 'inital', // Ausgangszustand: Dateiliste leer
WAITING: 'waiting', // Uploads wurden hinzugefügt; Es wird gewartet dass Benutzer den Upload startet
UPLOADING: 'uploading', // Upload werden gerade hochgeladen
FINISHED: 'finished' // Alle Uploads verarbeitet
};
var me = {
/** @property {Object} me.options Default configuration */
options: {
debug: false,
upload: {
url: 'index.php?module=dateibrowser&action=upload&cmd=upload-file',
formData: {},
simultaneous: 5
}
},
/** @property {Object} me.storage Runtime storage */
storage: {
$container: null,
$dropzone: null,
$filesContainer: null,
$filesList: null,
$statusInfo: null,
buttons: {
$container: null,
$start: null,
$cancel: null,
$reset: null
},
uploadLoop: null,
currentStatus: STATUS.INITAL,
waiting: [], // Wartende Uploads
uploads: [], // Laufende Uploads
canceled: [] // Abgebrochene Uploads
},
/**
* @param {HTMLElement} element
* @param {Object} options
*/
init: function (element, options) {
// Prüfen ob HTML5-File-API verfügbar
if (!me.isFileApiAvailable()) {
alert(
'Die HTML5 File-API wird von ihrem Browser nicht unterstützt. ' +
'Bitte verwenden sie einen moderneren Browser.'
);
return;
}
// Optionen mit Defaults mergen
me.options = $.extend({}, me.options, options);
if (typeof me.options.upload.simultaneous !== 'number') {
throw 'Invalid option "upload.simultaneous".';
}
var $container = $(element);
if ($container.length !== 1) {
throw 'Container-Elemente wurde nicht gefunden.';
}
$container.addClass('dropzone-upload');
me.storage.$container = $container;
me.createDropzone();
me.createButtons();
me.createFilesContainer();
},
/**
* @param {string} status
*/
changeStatus: function (status) {
switch (status) {
case STATUS.INITAL:
me.storage.$filesList.html('');
me.storage.$filesContainer.hide();
me.storage.buttons.$container.hide();
me.storage.buttons.$start.hide();
me.storage.buttons.$cancel.hide();
me.storage.buttons.$reset.hide();
break;
case STATUS.WAITING:
me.storage.$filesContainer.show();
me.storage.buttons.$container.show();
me.storage.buttons.$start.show();
me.storage.buttons.$cancel.hide();
me.storage.buttons.$reset.show();
break;
case STATUS.UPLOADING:
me.storage.$statusInfo.html('Dateien werden hochgeladen');
me.storage.$filesContainer.show();
me.storage.buttons.$container.show();
me.storage.buttons.$start.show();
me.storage.buttons.$cancel.show();
me.storage.buttons.$reset.hide();
break;
case STATUS.FINISHED:
me.storage.$statusInfo.html('Upload abgeschlossen');
me.storage.$filesContainer.show();
me.storage.buttons.$container.show();
me.storage.buttons.$start.hide();
me.storage.buttons.$cancel.hide();
me.storage.buttons.$reset.show();
break;
default:
status = STATUS.INITAL;
me.changeStatus(status);
break;
}
me.storage.currentStatus = status;
},
/**
* @param {Event} event
*/
onClickDropboxEventHandler: function (event) {
event.stopPropagation();
var $fileInput = me.storage.$container.find('input[type=file]');
$fileInput.trigger('click');
},
/**
* @param {Event} event
*/
onDragEventHandler: function (event) {
event.preventDefault();
event.stopPropagation();
if (event.type === 'dragover') {
$(this).addClass('dragging');
}
if (event.type === 'dragleave') {
$(this).removeClass('dragging');
}
},
/**
* @param {Event} event
*/
onDropFilesEventHandler: function (event) {
event.preventDefault();
event.stopPropagation();
var files = event.originalEvent.dataTransfer.files;
$.each(files, function (index, file) {
me.addFileUpload(file);
});
me.storage.$dropzone.removeClass('dragging');
},
/**
* @param {Event} event
*/
onSelectFilesEventHandler: function (event) {
/** @var {FileList} files */
var files = event.target.files;
$.each(files, function (index, file) {
me.addFileUpload(file);
});
},
/**
* @param {File} fileObject
*/
addFileUpload: function (fileObject) {
var fileId = me.generateRandomId();
var fileSize = me.formatBytes(fileObject.size);
me.storage.waiting.push({
id: fileId,
file: fileObject,
reader: new FileReader()
});
var $selectKeywordTemplate = $('#select-keyword-template').clone();
var selectedKeyword = $('#select-keyword-template').val();
$selectKeywordTemplate.removeAttr('id');
$selectKeywordTemplate.val(selectedKeyword);
$selectKeywordTemplate.trigger('change');
var $inputTitleTemplate = $('<input>').attr('type', 'text');
var $removeFileLink = $('<a>');
$removeFileLink.attr('href', '#').text('Entfernen');
$removeFileLink.addClass('dropzone-removefile-trigger').addClass('btn');
$removeFileLink.on('click', function (event) {
event.preventDefault();
var $link = $(this);
var $row = $link.parents('tr');
var fileId = $row.data('fileId');
me.removeFile(fileId);
});
var $row = $('<tr>').attr('id', fileId).data('fileId', fileId);
$('<td>').addClass('filepreview').html('&nbsp;').appendTo($row);
$('<td>').addClass('filename').html(fileObject.name).appendTo($row);
$('<td>').addClass('filesize').html(fileSize).appendTo($row);
$('<td>').addClass('filetitle').html($inputTitleTemplate).appendTo($row);
$('<td>').addClass('filekeyword').html($selectKeywordTemplate).appendTo($row);
$('<td>').addClass('filestatus').html('Bereit zum Hochladen').appendTo($row);
$('<td>').addClass('fileaction').html($removeFileLink).appendTo($row);
$row.appendTo(me.storage.$filesList);
if (me.storage.waiting.length > 0) {
if (me.storage.uploads.length > 0) {
me.changeStatus(STATUS.UPLOADING);
} else {
me.changeStatus(STATUS.WAITING);
}
}
},
/**
* @param {String} uploadId
*/
removeFile: function (uploadId) {
var $row = $('#' + uploadId);
if ($row.length === 0) {
alert('Can not remove file upload. Element "#' + uploadId + '" not found.');
return;
}
// Tabellenzeile entfernen
$row.remove();
var uploads = me.storage.uploads;
$.each(uploads, function (index, upload) {
if (typeof upload === 'undefined' || upload === null) {
return; // Upload wurde bereits verarbeitet
}
if (uploadId === upload.id) {
me.storage.uploads.splice(index, 1);
me.storage.canceled.push(upload);
me.cancelSingleUpload(upload);
}
});
var waiting = me.storage.waiting;
$.each(waiting, function (index, upload) {
if (typeof upload === 'undefined' || upload === null) {
return; // Upload wurde bereits verarbeitet
}
if (uploadId === upload.id) {
me.storage.waiting.splice(index, 1);
me.storage.canceled.push(upload);
me.cancelSingleUpload(upload);
}
});
},
/**
* Alle offenen (unverarbeiteten) Uploads starten
*/
startUploads: function () {
me.storage.uploadLoop = window.setInterval(me.processUploadQueue, 100);
},
/**
* Warteschlange mit unverarbeiteten Uploads abarbeiten
*/
processUploadQueue: function () {
if (me.options.debug === true) {
console.log('processUploadQueue', {
waitingCount: me.storage.waiting.length,
uploadsCount: me.storage.uploads.length,
canceledCount: me.storage.canceled.length,
simultaneous: me.options.upload.simultaneous
});
}
if (me.storage.uploads.length <= 0 && me.storage.waiting.length <= 0) {
window.clearInterval(me.storage.uploadLoop);
return; // Queue leer; keine unverarbeiteten oder laufenden Uploads
}
// Starte einzelnen Upload, wenn Platz frei
if (me.storage.uploads.length < me.options.upload.simultaneous) {
var uploadObject = me.storage.waiting.shift();
if (typeof uploadObject !== 'undefined' && uploadObject !== null) {
me.startSingleUpload(uploadObject);
}
}
},
/**
* @param {Object} upload Einzelner Wert aus me.storage.waiting
*/
startSingleUpload: function (upload) {
if (upload === null) {
return;
}
var fileId = upload.id;
if (typeof fileId === 'undefined') {
throw 'Upload fehlgeschlagen. Unique-ID is missing.';
}
// Upload-Queue füllen
me.storage.uploads.push(upload);
var $tableRow = $('#' + fileId);
var $inputTitle = $tableRow.find('.filetitle input');
var $selectKeyword = $tableRow.find('.filekeyword select');
var $statusCell = $tableRow.find('.filestatus');
$statusCell.html('Bitte warten...');
if (me.options.debug === true) {
console.log('startSingleUpload', {
waitingCount: me.storage.waiting.length,
uploadsCount: me.storage.uploads.length,
canceledCount: me.storage.canceled.length,
simultaneous: me.options.upload.simultaneous
});
}
// Datei-Inhalt fertig eingelesen => Upload starten
upload.reader.onloadend = function (event) {
if (event.target.readyState !== FileReader.DONE) {
return;
}
var ajaxData = me.options.upload.formData;
ajaxData.file_data = event.target.result;
ajaxData.file_name = upload.file.name;
ajaxData.file_type = upload.file.type;
ajaxData.file_size = upload.file.size;
ajaxData.file_title = $inputTitle.val();
ajaxData.file_keyword = $selectKeyword.val();
upload.xhr = $.ajax({
url: me.options.upload.url,
type: 'POST',
dataType: 'json',
cache: false,
data: ajaxData,
beforeSend: function () {
$statusCell.html('Hochladen...');
$inputTitle.prop('disabled', true);
$selectKeyword.prop('disabled', true);
},
error: function (jqXHR, textStatus, errorThrown) {
var errorMessage = 'Unbekannter Fehler #21: ' + errorThrown;
// User hat "Abbrechen" geklickt
if (textStatus === 'abort') {
errorMessage = 'Upload abgebrochen';
}
// PHP-Skript hat Fehler geliefert (z.b. 404)
if (textStatus === 'error') {
errorMessage =
jqXHR.hasOwnProperty('responseJSON') &&
jqXHR.responseJSON.hasOwnProperty('message')
? 'Fehler: ' + jqXHR.responseJSON.message
: 'Unbekannter Fehler';
}
$statusCell.html('<span class="message-failure">' + errorMessage + '</span>');
},
success: function (data) {
var successMessage = data.hasOwnProperty('message') ? data.message : 'Hochgeladen';
$statusCell.html('<span class="message-success">' + successMessage + '</span>');
if (data.hasOwnProperty('file') && data.file.hasOwnProperty('preview')) {
me.createImagePreview(fileId, data.file.preview);
}
},
complete: function () {
me.finishUpload(fileId);
}
});
};
// Datei einlesen starten
upload.reader.readAsDataURL(upload.file);
me.changeStatus(STATUS.UPLOADING);
},
/**
* Erfolgreichen Upload aus der Upload-Queue entfernen
*
* @param {String} uploadId
*/
finishUpload: function (uploadId) {
$.each(me.storage.uploads, function (index, upload) {
if (typeof upload === 'undefined') {
return;
}
if (upload.id === uploadId) {
me.storage.uploads.splice(index, 1);
}
});
if (me.storage.uploads.length <= 0 &&
me.storage.waiting.length <= 0) {
me.changeStatus(STATUS.FINISHED);
}
},
/**
* Bricht alle laufenden und wartenden Uploads ab
*/
cancelUploads: function () {
var index;
// Erstmal Alles (offene und wartende Uploads) in Canceled-Queue schieben
for (index = 0; index < me.storage.waiting.length; index++) {
me.storage.canceled.push(me.storage.waiting[index]);
}
for (index = 0; index < me.storage.uploads.length; index++) {
me.storage.canceled.push(me.storage.uploads[index]);
}
// Warteschlangen leeren
me.storage.waiting = [];
me.storage.uploads = [];
if (me.options.debug === true) {
console.log('cancelUploads', {
waitingCount: me.storage.waiting.length,
uploadsCount: me.storage.uploads.length,
canceledCount: me.storage.canceled.length,
simultaneous: me.options.upload.simultaneous
});
}
// Uploads abbrechen
$.each(me.storage.canceled, function (index, upload) {
me.cancelSingleUpload(upload);
});
me.changeStatus(STATUS.FINISHED);
},
/**
* Bricht einzelnen (laufenden) Uploads ab
*
* @param {object} uploadObject
*/
cancelSingleUpload: function (uploadObject) {
if (typeof uploadObject === 'undefined' || uploadObject === null) {
return;
}
var fileId = uploadObject.id;
var $tableRow = $('#' + fileId);
var $statusCell = $tableRow.find('.filestatus');
$statusCell.html('<span class="message-failure">Upload abgebrochen</span>');
if (uploadObject.hasOwnProperty('reader')) {
uploadObject.reader.abort();
}
if (uploadObject.hasOwnProperty('xhr')) {
uploadObject.xhr.abort();
}
},
/**
* Dateiliste leeren
*/
reset: function () {
me.cancelUploads();
me.storage.uploads = [];
me.storage.waiting = [];
me.storage.canceled = [];
me.changeStatus(STATUS.INITAL);
},
/**
* Dropzone-Element erzeugen
*/
createDropzone: function () {
var $dropzone = $('<div>');
$dropzone.addClass('dropzone');
$dropzone.html('<span>Dateien hier ablegen</span>');
$dropzone.on('dragover dragleave', me.onDragEventHandler);
$dropzone.on('drop', me.onDropFilesEventHandler);
$dropzone.on('click', me.onClickDropboxEventHandler);
$dropzone.appendTo(me.storage.$container);
var $fileInput = $('<input>');
$fileInput.attr('type', 'file');
$fileInput.prop('multiple', true);
$fileInput.on('change', me.onSelectFilesEventHandler);
$('<div>').addClass('hidden-upload').html($fileInput).appendTo(me.storage.$container);
me.storage.$dropzone = $dropzone;
},
/**
* Datei-Tabelle erzeugen
*/
createFilesContainer: function () {
var tableTemplate =
'<table>' +
'<thead><th></th><th>Dateiname</th><th>Gr&ouml;&szlig;e</th>' +
'<th>Titel</th><th>Stichwort</th><th>Status</th><th>Aktionen</th></tr></thead>' +
'<tbody></tbody>' +
'</table>';
var $files = $('<div>').addClass('files');
var $list = $(tableTemplate);
$files.appendTo(me.storage.$container).hide();
$list.appendTo($files);
me.storage.$filesContainer = $files;
me.storage.$filesList = $list.find('tbody');
},
/**
* Buttons erzeugen
*/
createButtons: function () {
var $buttons = $('<div>').addClass('buttons');
$buttons.appendTo(me.storage.$container).hide();
var $uploadButton = $('<input>');
$uploadButton.attr('type', 'button');
$uploadButton.addClass('upload-files-trigger');
$uploadButton.addClass('btnGreen');
$uploadButton.val('Upload starten');
$uploadButton.on('click', function (e) {
e.preventDefault();
me.startUploads();
});
$uploadButton.appendTo($buttons);
var $cancelButton = $('<input>');
$cancelButton.attr('type', 'button');
$cancelButton.addClass('stop-upload-trigger');
$cancelButton.addClass('btnBlue');
$cancelButton.val('Upload abbrechen');
$cancelButton.on('click', function (e) {
e.preventDefault();
me.cancelUploads();
});
$cancelButton.appendTo($buttons);
var $resetButton = $('<input>');
$resetButton.attr('type', 'button');
$resetButton.addClass('clear-files-trigger');
$resetButton.addClass('btnBlue');
$resetButton.val('Liste leeren');
$resetButton.on('click', function (e) {
e.preventDefault();
me.reset();
});
$resetButton.appendTo($buttons);
var $statusInfo = $('<span class="status-info"></span>');
$statusInfo.appendTo($buttons);
// Referenzen wegspeichern
me.storage.buttons.$container = $buttons;
me.storage.buttons.$start = $uploadButton;
me.storage.buttons.$cancel = $cancelButton;
me.storage.buttons.$reset = $resetButton;
me.storage.$statusInfo = $statusInfo;
},
/**
* @param {String} fileId
* @param {String} previewUrl
*/
createImagePreview: function (fileId, previewUrl) {
var $row = me.storage.$filesList.find('#' + fileId);
if ($row.length === 0) {
return;
}
var $img = $('<img src="" alt="">').attr({
'alt': 'Vorschau',
'src': previewUrl
});
$row.find('.filepreview').html($img);
},
/**
* HTML5 File-API vorhanden? Oder Uralt-Browser?
*
* @return {boolean}
*/
isFileApiAvailable: function () {
return typeof window.File !== 'undefined' &&
typeof window.FileList !== 'undefined' &&
typeof window.FileReader !== 'undefined';
},
/**
* Zufällige ID generieren
*
* @return {string}
*/
generateRandomId: function () {
return 'upload_' + Math.floor(Math.random() * Math.floor(9999999999));
},
/**
* @param {string} value
*
* @return {string}
*/
formatBytes: function (value) {
var bytes = parseInt(value, 10);
if (bytes === 0) {
return '0&nbsp;Bytes';
}
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
var exponent = Math.floor(Math.log(bytes) / Math.log(1024));
var decimalString = (bytes / Math.pow(1024, exponent)).toFixed(1) + '';
return decimalString.replace('.', ',') + '&nbsp;' + sizes[exponent];
}
};
me.init($elem, options);
/**
* Return public api
*/
return {
startUploads: me.startUploads,
cancelUploads: me.cancelUploads,
reset: me.reset
};
};
// Dokumentation: Siehe Dateianfang
$.fn.dropzoneUpload = function (options) {
return this.each(function () {
var $elem = $(this);
if (!$elem.data('dropzoneUpload')) {
var api = new DropZoneUpload(this, options);
$elem.init.prototype.DropZoneUploadApi = function () {
return api;
};
$elem.data('dropzoneUpload', api);
}
});
};
}(jQuery));