/** * # 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 = $('').attr('type', 'text'); var $removeFileLink = $(''); $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 = $('').attr('id', fileId).data('fileId', fileId); $('').addClass('filepreview').html(' ').appendTo($row); $('').addClass('filename').html(fileObject.name).appendTo($row); $('').addClass('filesize').html(fileSize).appendTo($row); $('').addClass('filetitle').html($inputTitleTemplate).appendTo($row); $('').addClass('filekeyword').html($selectKeywordTemplate).appendTo($row); $('').addClass('filestatus').html('Bereit zum Hochladen').appendTo($row); $('').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('' + errorMessage + ''); }, success: function (data) { var successMessage = data.hasOwnProperty('message') ? data.message : 'Hochgeladen'; $statusCell.html('' + successMessage + ''); 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('Upload abgebrochen'); 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 = $('
'); $dropzone.addClass('dropzone'); $dropzone.html('Dateien hier ablegen'); $dropzone.on('dragover dragleave', me.onDragEventHandler); $dropzone.on('drop', me.onDropFilesEventHandler); $dropzone.on('click', me.onClickDropboxEventHandler); $dropzone.appendTo(me.storage.$container); var $fileInput = $(''); $fileInput.attr('type', 'file'); $fileInput.prop('multiple', true); $fileInput.on('change', me.onSelectFilesEventHandler); $('
').addClass('hidden-upload').html($fileInput).appendTo(me.storage.$container); me.storage.$dropzone = $dropzone; }, /** * Datei-Tabelle erzeugen */ createFilesContainer: function () { var tableTemplate = '' + '' + '' + '' + '
DateinameGrößeTitelStichwortStatusAktionen
'; var $files = $('
').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 = $('
').addClass('buttons'); $buttons.appendTo(me.storage.$container).hide(); var $uploadButton = $(''); $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 = $(''); $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 = $(''); $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 = $(''); $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 = $('').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 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('.', ',') + ' ' + 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));