[FEATURE] Add Drag&Drop Upload to file-list 14/19714/10
authorSteffen Ritter <info@rs-websystems.de>
Fri, 5 Jul 2013 07:53:50 +0000 (09:53 +0200)
committerMarkus Klein <klein.t3@mfc-linz.at>
Sat, 6 Jul 2013 13:53:52 +0000 (15:53 +0200)
In TYPO3 6.1 the Flash-Uploader has been removed and the
upload functionality has been reduced to the basic HTML5
multi-upload field.

In the times of HTML5, CSS3 and modern browser our users
expect a way more advanced experience.

This patch adds the possibility to just drag files into the file
list. The dragged files are asynchronously uploaded to the
currently open folder and an reload of the frame is done.

There are still some minor issues and ideas for a even better
user experience, which have to be discussed if they should
be included in the original patch.

Change-Id: I4221f7ff5a06ee11555906f170b37cc08489d875
Resolves: #47005
Releases: 6.2
Reviewed-on: https://review.typo3.org/19714
Reviewed-by: Steffen Ritter
Tested-by: Steffen Ritter
Reviewed-by: Frans Saris
Tested-by: Frans Saris
Reviewed-by: Markus Klein
Tested-by: Markus Klein
typo3/sysext/backend/Resources/Public/JavaScript/DragUploader.js [new file with mode: 0644]
typo3/sysext/filelist/Classes/Controller/FileListController.php
typo3/sysext/filelist/Resources/Private/Templates/file_list.html
typo3/sysext/lang/locallang_core.xlf
typo3/sysext/t3skin/stylesheets/visual/module_file_list.css

diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/DragUploader.js b/typo3/sysext/backend/Resources/Public/JavaScript/DragUploader.js
new file mode 100644 (file)
index 0000000..1f8a6c0
--- /dev/null
@@ -0,0 +1,250 @@
+/***************************************************************
+ *
+ *  Copyright notice
+ *
+ *  (c) 2013 Steffen Ritter <steffen.ritter@typo3.org>
+ *  All rights reserved
+ *
+ *  Released under GNU/GPL2+ (see license file in the main directory)
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ *  This copyright notice MUST APPEAR in all copies of this script
+ *
+ ***************************************************************/
+/**
+ * JavaScript RequireJS module called "TYPO3/CMS/Backend/DragUploader"
+ *
+ */
+define('TYPO3/CMS/Backend/DragUploader', ['jquery'], function($) {
+
+       /*
+        * part 1: a generic jQuery plugin "$.dragUploader"
+        */
+
+       // register the constructor
+       var DragUploaderPlugin = function() {
+               var me = this;
+
+               me.$body = $('body');
+               me.$element = $('<div />').addClass('DragUpload-DropZone t3-dropzone').appendTo(me.$body);
+               me.$progress = $('<div />').addClass('DragUpload-ProgressInformation').hide().appendTo(me.$body);
+               me.uploadCompletedCount = 0;
+               me.fileQueue = [];
+
+               me.fileDenyPattern = new RegExp($('[data-file-deny-pattern]').attr('data-file-deny-pattern'), 'i');
+               me.maxFileSize = parseInt($('[data-max-file-size]').attr('data-max-file-size'));
+               me.target = $('[data-target-folder]').attr('data-target-folder');
+
+               me.browserCapabilities = {
+                       fileReader: typeof FileReader != 'undefined',
+                       DnD: 'draggable' in document.createElement('span'),
+                       FormData: !!window.FormData,
+                       Progress: "upload" in new XMLHttpRequest
+               };
+
+               me.dragFileIntoDocument = function(event) {
+                       event.preventDefault && event.preventDefault();
+                       event.dataTransfer.dropEffect = 'copy';
+                       me.$body.addClass('dropInProgess');
+                       me.$element.html(TYPO3.l10n.localize('file_upload.dropzonehint'));
+                       return false;
+               };
+
+               me.dragAborted = function(event) {
+                       event.preventDefault && event.preventDefault();
+                       me.$body.removeClass('dropInProgess');
+                       return false;
+               };
+
+               me.ignoreDrop = function(event) {
+                       if (event.stopPropagation) {
+                               event.stopPropagation(); // stops the browser from redirecting.
+                       }
+                       me.dragAborted(event);
+                       return false;
+               };
+
+               me.updateProgress = function() {
+                       var fileCount = me.fileQueue.length;
+                       if (fileCount > 0 && fileCount === me.uploadCompletedCount) {;
+                               me.$progress.html('<p><strong>' + TYPO3.l10n.localize('file_upload.upload-finished') + '</strong></p>').show();
+                               window.location.reload();
+                       } else {
+                               me.$progress.html(
+                                       '<p><strong>' + TYPO3.l10n.localize('file_upload.upload-in-progress') + '</strong></p>' +
+                                       '<p>' + TYPO3.l10n.localize('file_upload.upload-progress-info').replace(/\{0\}/g, me.uploadCompletedCount).replace(/\{1\}/g, fileCount) + '</p>'
+                               ).show();
+                       }
+               };
+
+               me.handleDrop = function (event) {
+
+                       if (event.stopPropagation) {
+                               event.stopPropagation(); // stops the browser from redirecting.
+                       }
+                       me.ignoreDrop(event);
+
+                       // ask user if we should override files
+                       var override = confirm(TYPO3.l10n.localize('file_upload.overwriteExistingFiles'));
+
+                       // Add each file to queue and trigger upload
+                       $.each(event.dataTransfer.files, function(i, file) {
+
+                               // check filesize, fileextension
+                               if (file.size > me.maxFileSize) {
+                                       TYPO3.Flashmessage.display(
+                                               TYPO3.Severity.error,
+                                               'Error',
+                                               TYPO3.l10n.localize('file_upload.maxFilesizeExceeded').replace(/\{0\}/g, file.name).replace(/\{1\}/g, me.maxFileSize)
+                                       );
+                               // check filename/extension
+                               } else if (file.name.match(me.fileDenyPattern)) {
+                                       TYPO3.Flashmessage.display(
+                                               TYPO3.Severity.error,
+                                               'Error',
+                                               TYPO3.l10n.localize('file_upload.fileNotAllowwed').replace(/\{0\}/g, file.name)
+                                       );
+                               } else {
+
+                                       var formData = new FormData();
+                                       formData.append('file[upload][1][target]', me.target);
+                                       formData.append('file[upload][1][data]', '1');
+                                       if(override) {
+                                               formData.append('overwriteExistingFiles', '1');
+                                       }
+                                       formData.append('redirect', '');
+                                       formData.append('upload_1[]', file);
+
+                                       // now post a new XHR request
+                                       var xhr = new XMLHttpRequest();
+                                       xhr.open('POST', 'tce_file.php');
+
+                                       xhr.onload = function () {
+                                               me.uploadCompletedCount++;
+                                               me.updateProgress();
+                                       };
+                                       xhr.onerror = function() {
+                                               TYPO3.Flashmessage.display(
+                                                       TYPO3.Severity.error,
+                                                       'Error',
+                                                       TYPO3.l10n.localize('file_upload.uploadFailed').replace(/\{0\}/g, file.name)
+                                               );
+                                               me.uploadCompletedCount++;
+                                               me.updateProgress();
+                                       };
+                                       xhr.onprogress = function(progressEvent) {
+                                               me.updateProgress();
+                                       };
+                                       me.fileQueue.push(file);
+                                       me.updateProgress();
+
+                                       // start upload
+                                       xhr.send(formData);
+                               }
+                       });
+                       me.$element.removeClass('t3-dropzone-dropReceiveOK');
+                       return false;
+               };
+
+               me.fileInDropzone = function(event) {
+                       me.$element.addClass('t3-dropzone-dropReceiveOK');
+               };
+
+               me.fileOutOfDropzone = function(event) {
+                       me.$element.removeClass('t3-dropzone-dropReceiveOK');
+               };
+
+               if (me.browserCapabilities.DnD) {
+                       var doc = document.documentElement;
+                       me.$body.get(0).ondragover = me.dragFileIntoDocument;
+
+                       me.$body.get(0).ondragend = me.dragAborted;
+                       me.$body.get(0).ondrop = me.ignoreDrop;
+
+                       me.$body.get(0).ondragenter = me.fileInDropzone;
+                       me.$body.get(0).ondragleave = me.dragAborted;
+                       me.$body.get(0).ondrop = me.handleDrop;
+
+                       // if upload button is present, remove it
+                       var $uploadButton = $('#button-upload');
+                       me.flashMessageShown = false;
+                       if ($uploadButton.length > 0) {
+                               $uploadButton.click(function(event) {
+                                       if (!me.flashMessageShown) {
+                                               event.preventDefault();
+                                               TYPO3.Flashmessage.display(
+                                                       TYPO3.Severity.information,
+                                                       TYPO3.l10n.localize('file_upload.draginformation.title'),
+                                                       TYPO3.l10n.localize('file_upload.draginformation.message')
+                                               );
+                                               me.flashMessageShown = true;
+                                               return false;
+                                       }
+                               });
+                       }
+               }
+
+
+       };
+
+       /**
+        * part 2: The main module of this file
+        * - initialize the DragUploader module and register
+        * the jQuery plugin in the jQuery global object
+        * when initializing the DragUploader module
+        */
+       var DragUploader = {};
+
+       DragUploader.options = {
+       };
+
+       DragUploader.initialize = function() {
+               var
+                       me = this
+                       ,opts = me.options;
+
+               // register the jQuery plugin "DragUploaderPlugin"
+               $.fn.dragUploader = function(option) {
+                       return this.each(function() {
+                               var $this = $(this)
+                                       , data = $this.data('DragUploaderPlugin');
+                               if (!data) {
+                                       $this.data('DragUploaderPlugin', (data = new DragUploaderPlugin(this)));
+                               }
+                               if (typeof option == 'string') {
+                                       data[option]();
+                               }
+                       })
+               };
+
+               $('body').dragUploader();
+
+       };
+
+
+
+       /**
+     * part 3: initialize the RequireJS module, require possible post-initialize hooks,
+        * and return the main object
+        */
+       var initialize = function() {
+
+               DragUploader.initialize();
+
+               // load required modules to hook in the post initialize function
+               if (undefined !== TYPO3.settings && undefined !== TYPO3.settings.RequireJS.PostInitializationModules && undefined !== TYPO3.settings.RequireJS.PostInitializationModules['TYPO3/CMS/Backend/DragUploader']) {
+                       $.each(TYPO3.settings.RequireJS.PostInitializationModules['TYPO3/CMS/Backend/DragUploader'], function(pos, moduleName) {
+                               require([moduleName]);
+                       });
+               }
+
+               // return the object in the global space
+               return DragUploader;
+       };
+
+       // call the main initialize function and execute the hooks
+       return initialize();
+});
\ No newline at end of file
index 1fcb053..674c411 100644 (file)
@@ -214,6 +214,12 @@ class FileListController {
                $this->doc->backPath = $GLOBALS['BACK_PATH'];
                $this->doc->setModuleTemplate('EXT:filelist/Resources/Private/Templates/file_list.html');
                $this->doc->getPageRenderer()->loadPrototype();
+               $this->doc->getPageRenderer()->loadJQuery();
+               $this->doc->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/DragUploader');
+               $this->doc->getPageRenderer()->addInlineLanguagelabelFile(
+                       \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath('lang') . 'locallang_core.xlf',
+                       'file_upload'
+               );
                // There there was access to this file path, continue, make the list
                if ($this->folderObject) {
 
@@ -347,7 +353,10 @@ class FileListController {
                        $markerArray = array(
                                'CSH' => $docHeaderButtons['csh'],
                                'FUNC_MENU' => \TYPO3\CMS\Backend\Utility\BackendUtility::getFuncMenu($this->id, 'SET[function]', $this->MOD_SETTINGS['function'], $this->MOD_MENU['function']),
-                               'CONTENT' => ($this->errorMessage ? $this->errorMessage->render() : '') . $pageContent
+                               'CONTENT' => ($this->errorMessage ? $this->errorMessage->render() : '') . $pageContent,
+                               'FOLDER_IDENTIFIER' => $this->folderObject->getCombinedIdentifier(),
+                               'FILEDENYPATERN' => $GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern'],
+                               'MAXFILESIZE' => \TYPO3\CMS\Core\Utility\GeneralUtility::getMaxUploadFileSize() * 1024,
                        );
                        $this->content = $this->doc->moduleBody(array(), $docHeaderButtons, array_merge($markerArray, $otherMarkers));
                        // Renders the module page
index 5a19722..aca3e59 100644 (file)
@@ -1,5 +1,5 @@
 <!-- ###FULLDOC### begin -->
-<div class="typo3-fullDoc">
+<div class="typo3-fullDoc" data-target-folder="###FOLDER_IDENTIFIER###" data-file-deny-pattern="###FILEDENYPATERN###" data-max-file-size="###MAXFILESIZE###">
        <div id="typo3-docheader">
                <div class="typo3-docheader-functions">
                        <div class="left">###CSH### ###FUNC_MENU###</div>
index 4481b97..e4de82f 100644 (file)
@@ -436,6 +436,36 @@ Do you want to continue WITHOUT saving?</source>
                        <trans-unit id="file_upload.php.files" xml:space="preserve">
                                <source>files</source>
                        </trans-unit>
+                       <trans-unit id="file_upload.draginformation.message" xml:space="preserve">
+                               <source>You simply can drag your files into the filelist. Click again for classic mode.</source>
+                       </trans-unit>
+                       <trans-unit id="file_upload.draginformation.title" xml:space="preserve">
+                               <source>Drag &amp; Drop available</source>
+                       </trans-unit>
+                       <trans-unit id="file_upload.dropzonehint" xml:space="preserve">
+                               <source>Drop your files here!</source>
+                       </trans-unit>
+                       <trans-unit id="file_upload.maxFilesizeExceeded" xml:space="preserve">
+                               <source>File &quot;{0}&quot; exceeds maximum filesize of {1} bytes!</source>
+                       </trans-unit>
+                       <trans-unit id="file_upload.fileNotAllowwed" xml:space="preserve">
+                               <source>Filename &quot;{0}&quot; is not allowed!</source>
+                       </trans-unit>
+                       <trans-unit id="file_upload.uploadFailed" xml:space="preserve">
+                               <source>Upload of file &quot;{0}&quot; failed!</source>
+                       </trans-unit>
+                       <trans-unit id="file_upload.upload-in-progress" xml:space="preserve">
+                               <source>File upload in progress...</source>
+                       </trans-unit>
+                       <trans-unit id="file_upload.upload-progress-info" xml:space="preserve">
+                               <source>Uploading file {0} out of {1} files.</source>
+                       </trans-unit>
+                       <trans-unit id="file_upload.upload-finished" xml:space="preserve">
+                               <source>Upload complete! Filelist reloading...</source>
+                       </trans-unit>
+                       <trans-unit id="file_upload.overwriteExistingFiles" xml:space="preserve">
+                               <source>Shall existing files be overwritten?</source>
+                       </trans-unit>
                        <trans-unit id="file_upload.php.number_of_files" xml:space="preserve">
                                <source>Number of files:</source>
                        </trans-unit>
index 3f2770f..6a00730 100644 (file)
@@ -39,4 +39,51 @@ table#typo3-filelist tr td img {
 table#typo3-filelist tr td div.typo3-clipCtrl,
 table#typo3-filelist tr td div.typo3-editCtrl {
        text-align: center;
-}
\ No newline at end of file
+}
+
+
+#typo3-inner-docbody {
+       -webkit-transition:border 0.2s ease;
+       -moz-transition:border 0.2s ease;
+       -ms-transition:border 0.2s ease;
+       -o-transition:border 0.2s ease;
+       transition:border 0.2s ease;
+}
+.dropInProgess #typo3-inner-docbody {
+       border:10px solid #73ab61;
+       padding:-10px !important!;
+       margin-bottom:20px !important;
+       -webkit-transition:border 0.2s ease;
+       -moz-transition:border 0.2s ease;
+       -ms-transition:border 0.2s ease;
+       -o-transition:border 0.2s ease;
+       transition:border 0.2s ease;
+}
+
+.DragUpload-DropZone.t3-dropzone {
+       position: absolute;
+       width:0;
+       display:none;
+       height:0;
+       z-index:9999;
+}
+
+.dropInProgess .DragUpload-DropZone.t3-dropzone,
+.uploadInProgress .DragUpload-DropZone.t3-dropzone,
+.DragUpload-ProgressInformation {
+       width:40%;
+       height:auto;
+       top:50px;
+       left:30%;
+       border:1px solid #73ab61;
+       background:#c8d9c3;
+       padding:8px;
+       display:block;
+       box-shadow:0px 0px 10px 0px #888888;
+}
+.DragUpload-ProgressInformation {
+       position: absolute;
+       top:30px;
+       background-color: #dedede;
+       border: 1px solid #a2aab8;
+}