058893e0b88259f1edb7503a6c80b512b247a607
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Resources / Public / JavaScript / DragUploader.js
1 /*
2 * This file is part of the TYPO3 CMS project.
3 *
4 * It is free software; you can redistribute it and/or modify it under
5 * the terms of the GNU General Public License, either version 2
6 * of the License, or any later version.
7 *
8 * For the full copyright and license information, please read the
9 * LICENSE.txt file that was distributed with this source code.
10 *
11 * The TYPO3 project - inspiring people to share!
12 */
13
14 /**
15 * JavaScript RequireJS module called "TYPO3/CMS/Backend/DragUploader"
16 *
17 */
18 define('TYPO3/CMS/Backend/DragUploader', ['jquery', 'moment', 'nprogress', 'TYPO3/CMS/Lang/Lang', 'TYPO3/CMS/Backend/Modal'], function($, moment, NProgress) {
19
20 /**
21 * Array of files which are asked for being overridden
22 *
23 * @type {Array}
24 */
25 var askForOverride = [],
26 percentagePerFile = 1;
27
28 /**
29 * File actions
30 */
31 var actions = {
32 OVERRIDE: 'replace',
33 RENAME: 'changeName',
34 SKIP: 'cancel'
35 };
36
37 /*
38 * part 1: a generic jQuery plugin "$.dragUploader"
39 */
40
41 // register the constructor
42 var DragUploaderPlugin = function(element) {
43 var me = this;
44 me.$body = $('body');
45 me.$element = $(element);
46 me.$trigger = $(me.$element.data('dropzone-trigger'));
47 me.$dropzone = $('<div />').addClass('dropzone').hide();
48 if (me.$element.data('file-irre-object') && me.$element.nextAll(me.$element.data('dropzone-target')).length !== 0) {
49 me.dropZoneInsertBefore = true;
50 me.$dropzone.insertBefore(me.$element.data('dropzone-target'));
51 } else {
52 me.dropZoneInsertBefore = false;
53 me.$dropzone.insertAfter(me.$element.data('dropzone-target'));
54 }
55 me.$dropzoneMask = $('<div />').addClass('dropzone-mask').appendTo(me.$dropzone);
56 me.$fileInput = $('<input type="file" multiple name="files[]" />').addClass('upload-file-picker').appendTo(me.$body);
57 me.$fileList = $(me.$element.data('progress-container'));
58 me.fileListColumnCount = $('thead tr:first th', me.$fileList).length;
59 me.filesExtensionsAllowed = me.$element.data('file-allowed');
60 me.fileDenyPattern = me.$element.data('file-deny-pattern') ? new RegExp(me.$element.data('file-deny-pattern'), 'i') : false;
61 me.maxFileSize = parseInt(me.$element.data('max-file-size'));
62 me.target = me.$element.data('target-folder');
63
64 me.browserCapabilities = {
65 fileReader: typeof FileReader !== 'undefined',
66 DnD: 'draggable' in document.createElement('span'),
67 FormData: !!window.FormData,
68 Progress: "upload" in new XMLHttpRequest
69 };
70
71 me.showDropzone = function() {
72 me.$dropzone.show();
73 };
74
75 me.hideDropzone = function(event) {
76 event.stopPropagation();
77 event.preventDefault();
78 me.$dropzone.hide();
79 };
80
81 me.dragFileIntoDocument = function(event) {
82 event.stopPropagation();
83 event.preventDefault();
84 me.$body.addClass('drop-in-progress');
85 me.showDropzone();
86 return false;
87 };
88
89 me.dragAborted = function(event) {
90 event.stopPropagation();
91 event.preventDefault();
92 me.$body.removeClass('drop-in-progress');
93 return false;
94 };
95
96 me.ignoreDrop = function(event) {
97 // stops the browser from redirecting.
98 event.stopPropagation();
99 event.preventDefault();
100 me.dragAborted(event);
101 return false;
102 };
103
104 me.handleDrop = function(event) {
105 me.ignoreDrop(event);
106 me.processFiles(event.originalEvent.dataTransfer.files);
107 me.$dropzone.removeClass('drop-status-ok');
108 };
109
110 me.processFiles = function(files) {
111 me.queueLength = files.length;
112
113 // ask user if we should override files
114 var override = false;
115 if (!me.$fileList.is(':visible')) {
116 me.$fileList.show();
117 }
118
119 NProgress.start();
120 percentagePerFile = 1 / files.length;
121
122 // Add each file to queue and start upload
123 var ajaxCalls = [];
124 $.each(files, function(i, file) {
125 if (typeof askForOverride[i] === 'undefined') {
126 // check if the file exists
127 ajaxCalls[i] = $.ajax({
128 url: TYPO3.settings.ajaxUrls['TYPO3_tcefile::fileExists'],
129 data: {
130 fileName: file.name,
131 fileTarget: me.target
132 },
133 cache: false,
134 success: function(response) {
135 var fileExists = typeof response.result !== 'undefined';
136 if (fileExists) {
137 askForOverride[i] = {
138 original: response.result,
139 uploaded: file,
140 action: actions.SKIP
141 };
142 NProgress.inc(percentagePerFile);
143 } else {
144 new FileQueueItem(me, file, 'cancel');
145 }
146 }
147 });
148 } else {
149 if (askForOverride[i].action !== actions.SKIP) {
150 new FileQueueItem(me, askForOverride[i].uploaded, askForOverride[i].action);
151 }
152 }
153 });
154 if (ajaxCalls.length) {
155 $.when.apply($, ajaxCalls).done(function () {
156 me.drawOverrideModal();
157 NProgress.done();
158 });
159 }
160 delete ajaxCalls;
161 me.$fileInput.val('');
162 };
163
164 me.fileInDropzone = function(event) {
165 me.$dropzone.addClass('drop-status-ok');
166 };
167
168 me.fileOutOfDropzone = function(event) {
169 me.$dropzone.removeClass('drop-status-ok');
170 };
171
172 // bind file picker to default upload button
173 me.bindUploadButton = function(button) {
174 button.click(function(event) {
175 event.preventDefault();
176 me.$fileInput.click();
177 me.showDropzone();
178 });
179 };
180
181 if (me.browserCapabilities.DnD) {
182 me.$body.on('dragover', me.dragFileIntoDocument);
183 me.$body.on('dragend', me.dragAborted);
184 me.$body.on('drop', me.ignoreDrop);
185
186 me.$dropzone.on('dragenter', me.fileInDropzone);
187 me.$dropzoneMask.on('dragenter', me.fileInDropzone);
188 me.$dropzoneMask.on('dragleave', me.fileOutOfDropzone);
189 me.$dropzoneMask.on('drop', me.handleDrop);
190
191 me.$dropzone.prepend(
192 '<div class="dropzone-hint">' +
193 '<div class="dropzone-hint-media">' +
194 '<div class="dropzone-hint-icon"></div>' +
195 '</div>' +
196 '<div class="dropzone-hint-body">' +
197 '<h3 class="dropzone-hint-title">' +
198 TYPO3.lang['file_upload.dropzonehint.title'] +
199 '</h3>' +
200 '<p class="dropzone-hint-message">' +
201 TYPO3.lang['file_upload.dropzonehint.message'] +
202 '</p>' +
203 '</div>' +
204 '</div>').click(function() {
205 me.$fileInput.click()
206 });
207 $('<span />').addClass('dropzone-close').click(me.hideDropzone).appendTo(me.$dropzone);
208
209 // no filelist then create own progress table
210 if (me.$fileList.length === 0) {
211 me.$fileList = $('<table />').attr('id', 'typo3-filelist').addClass('table table-striped table-hover upload-queue').html('<tbody></tbody>').hide();
212 if (me.dropZoneInsertBefore) {
213 me.$fileList.insertAfter(me.$dropzone);
214 } else {
215 me.$fileList.insertBefore(me.$dropzone);
216 }
217 me.fileListColumnCount = 7;
218 }
219
220 me.$fileInput.on('change', function() {
221 me.processFiles(this.files);
222 });
223
224 me.bindUploadButton(me.$trigger.length ? me.$trigger : me.$element);
225 }
226
227 me.decrementQueueLength = function() {
228 if (me.queueLength > 0) {
229 me.queueLength--;
230 if (me.queueLength === 0) {
231 NProgress.done();
232 $.ajax({
233 url: TYPO3.settings.ajaxUrls['DocumentTemplate::getFlashMessages'],
234 cache: false,
235 success: function(data) {
236 var messages = $('#typo3-messages');
237 if (messages.length == 0) {
238 $('#typo3-inner-docbody').prepend(data);
239 } else {
240 messages.replaceWith(data);
241 }
242 }
243 });
244 } else {
245 NProgress.inc(percentagePerFile);
246 }
247 }
248 };
249
250 me.drawOverrideModal = function() {
251 var amountOfItems = Object.keys(askForOverride).length;
252 if (amountOfItems === 0) {
253 return;
254 }
255 var $records = $('<p/>').text(TYPO3.lang['file_upload.existingfiles.description']).append(
256 $('<table/>', {class: 'table table-striped table-fixed'}).append(
257 $('<tr />').append(
258 $('<th/>'),
259 $('<th/>').text(TYPO3.lang['file_upload.header.originalFile']),
260 $('<th/>').text(TYPO3.lang['file_upload.header.uploadedFile']),
261 $('<th/>').text(TYPO3.lang['file_upload.header.action'])
262 ),
263 $('<tr/>').append(
264 $('<td/>', {colspan: 3}).append(
265 $('<label/>').text(TYPO3.lang['file_upload.actions.all.label'])
266 ),
267 $('<td/>').append(
268 $('<select/>', {class: 'form-control t3js-actions-all'}).append(
269 $('<option/>').val('').text(TYPO3.lang['file_upload.actions.all.empty']),
270 $('<option/>').val(actions.SKIP).text(TYPO3.lang['file_upload.actions.all.skip']),
271 $('<option/>').val(actions.RENAME).text(TYPO3.lang['file_upload.actions.all.rename']),
272 $('<option/>').val(actions.OVERRIDE).text(TYPO3.lang['file_upload.actions.all.override'])
273 )
274 )
275 )
276 )
277 );
278
279 for (var i = 0; i < amountOfItems; ++i) {
280 var $record = $('<tr />').append(
281 $('<td />').append(
282 (askForOverride[i].original.thumbUrl !== ''
283 ? $('<img />', {src: askForOverride[i].original.thumbUrl})
284 : $('<span />', {class: askForOverride[i].original.iconClasses})
285 )
286 ),
287 $('<td />').html(
288 askForOverride[i].uploaded.name + ' (' + (DragUploader.fileSizeAsString(askForOverride[i].uploaded.size)) + ')' +
289 '<br>' + moment(askForOverride[i].uploaded.lastModified, 'x').format('YYYY-MM-DD HH:mm')
290 ),
291 $('<td />').html(
292 askForOverride[i].uploaded.name + ' (' + (DragUploader.fileSizeAsString(askForOverride[i].original.size)) + ')' +
293 '<br>' + moment(askForOverride[i].original.mtime, 'X').format('YYYY-MM-DD HH:mm')
294 ),
295 $('<td />').append(
296 $('<select />', {class: 'form-control t3js-actions', 'data-override': i}).append(
297 $('<option />').val(actions.SKIP).text(TYPO3.lang['file_upload.actions.skip']),
298 $('<option />').val(actions.RENAME).text(TYPO3.lang['file_upload.actions.rename']),
299 $('<option />').val(actions.OVERRIDE).text(TYPO3.lang['file_upload.actions.override'])
300 )
301 )
302 );
303 $records.find('tr:last-child').before($record);
304 }
305
306 var $modal = top.TYPO3.Modal.confirm(TYPO3.lang['file_upload.existingfiles.title'], $records, top.TYPO3.Severity.warning, [
307 {
308 text: $(this).data('button-close-text') || TYPO3.lang['file_upload.button.cancel'] || 'Cancel',
309 active: true,
310 name: 'cancel'
311 },
312 {
313 text: $(this).data('button-ok-text') || TYPO3.lang['file_upload.button.continue'] || 'Continue with selected actions',
314 btnClass: 'btn-warning',
315 name: 'continue'
316 }
317 ]);
318 $modal.on('change', '.t3js-actions-all', function() {
319 var $me = $(this),
320 value = $me.val();
321
322 if (value !== '') {
323 // mass action was selected, apply action to every file
324 $modal.find('.t3js-actions').attr('disabled', 'disabled');
325 for (var i = 0; i < amountOfItems; ++i) {
326 askForOverride[i].action = value;
327 }
328 } else {
329 $modal.find('.t3js-actions').each(function (index, select) {
330 var $select = $(select);
331 $select.removeAttr('disabled');
332 if (typeof askForOverride[index] !== 'undefined') {
333 askForOverride[index].action = $select.val();
334 }
335 });
336 }
337 }).on('change', '.t3js-actions', function() {
338 var $me = $(this),
339 index = parseInt($me.data('override'));
340 askForOverride[index].action = $me.val();
341 }).on('button.clicked', function(e) {
342 if (e.target.name === 'cancel') {
343 top.TYPO3.Modal.dismiss();
344 } else if (e.target.name === 'continue') {
345 var files = [];
346 for (var i = 0; i < amountOfItems; ++i) {
347 if (askForOverride[i].action !== actions.SKIP) {
348 files.push(askForOverride[i].item);
349 }
350 }
351 me.processFiles(files);
352 top.TYPO3.Modal.dismiss();
353 }
354 }).on('hidden.bs.modal', function() {
355 askForOverride = [];
356 });
357 }
358 };
359
360 var FileQueueItem = function(dragUploader, file, override) {
361 var me = this;
362 me.dragUploader = dragUploader;
363 me.file = file;
364 me.override = override;
365
366 me.$row = $('<tr />').addClass('upload-queue-item uploading');
367 me.$iconCol = $('<td />').addClass('col-icon').appendTo(me.$row);
368 me.$fileName = $('<td />').text(file.name).appendTo(me.$row);
369 me.$progress = $('<td />').attr('colspan', me.dragUploader.fileListColumnCount-2).appendTo(me.$row);
370 me.$progressContainer = $('<div />').addClass('upload-queue-progress').appendTo(me.$progress);
371 me.$progressBar = $('<div />').addClass('upload-queue-progress-bar').appendTo(me.$progressContainer);
372 me.$progressPercentage = $('<span />').addClass('upload-queue-progress-percentage').appendTo(me.$progressContainer);
373 me.$progressMessage = $('<span />').addClass('upload-queue-progress-message').appendTo(me.$progressContainer);
374
375 me.updateMessage = function(message) {
376 me.$progressMessage.text(message);
377 };
378
379 me.removeProgress = function() {
380 if (me.$progress) {
381 me.$progress.remove();
382 }
383 };
384
385 me.uploadStart = function() {
386 me.$progressPercentage.text('(0%)');
387 me.$progressBar.width('1%');
388 me.dragUploader.$trigger.trigger('uploadStart', [me]);
389 };
390
391 me.uploadError = function(response) {
392 me.updateMessage(TYPO3.lang['file_upload.uploadFailed'].replace(/\{0\}/g, me.file.name));
393 var error = $(response.responseText);
394 if (error.is('t3err')) {
395 me.$progressPercentage.text(error.text());
396 } else {
397 me.$progressPercentage.text('(' + response.statusText + ')');
398 }
399 me.$row.addClass('error');
400 me.dragUploader.decrementQueueLength();
401 me.dragUploader.$trigger.trigger('uploadError', [me, response]);
402 };
403
404 me.updateProgress = function(event) {
405 var percentage = Math.round((event.loaded / event.total) * 100) + '%';
406 me.$progressBar.outerWidth(percentage);
407 me.$progressPercentage.text(percentage);
408 me.dragUploader.$trigger.trigger('updateProgress', [me, percentage, event]);
409 };
410
411 me.uploadSuccess = function(data) {
412 if (data.result.upload) {
413 me.dragUploader.decrementQueueLength();
414 me.$row.removeClass('uploading');
415 me.$fileName.text(data.result.upload[0].name);
416 me.$progressPercentage.text('');
417 me.$progressMessage.text('100%');
418 me.$progressBar.outerWidth('100%');
419
420 // replace file icon
421 if (data.result.upload[0].iconClasses) {
422 me.$iconCol.html('<span class="' + data.result.upload[0].iconClasses + '">&nbsp;</span>');
423 }
424
425 if (me.dragUploader.$element.data('file-irre-object')) {
426 inline.delayedImportElement(
427 me.dragUploader.$element.data('file-irre-object'),
428 'sys_file',
429 data.result.upload[0].uid,
430 'file'
431 );
432 setTimeout(function() {
433 me.$row.remove();
434 if ($('tr', me.dragUploader.$fileList).length === 0) {
435 me.dragUploader.$fileList.hide();
436 me.dragUploader.$trigger.trigger('uploadSuccess', [me, data]);
437 }
438 }, 3000);
439 } else {
440 setTimeout(function() {
441 me.showFileInfo(data.result.upload[0]);
442 me.dragUploader.$trigger.trigger('uploadSuccess', [me, data]);
443 }, 3000);
444 }
445 }
446 };
447
448 me.showFileInfo = function(fileInfo) {
449 me.removeProgress();
450 // add spacing cells when clibboard and/or extended view is enabled
451 for (i = 7; i < me.dragUploader.fileListColumnCount; i++) {
452 $('<td />').text('').appendTo(me.$row);
453 }
454 $('<td />').text(fileInfo.extension.toUpperCase()).appendTo(me.$row);
455 $('<td />').text(fileInfo.date).appendTo(me.$row);
456 $('<td />').text(DragUploader.fileSizeAsString(fileInfo.size)).appendTo(me.$row);
457 var permissions = '';
458 if (fileInfo.permissions.read) {
459 permissions += '<strong class="text-danger">R</strong>';
460 }
461 if (fileInfo.permissions.write) {
462 permissions += '<strong class="text-danger">W</strong>';
463 }
464 $('<td />').html(permissions).appendTo(me.$row);
465 $('<td />').text('-').appendTo(me.$row);
466 };
467
468 me.checkAllowedExtensions = function() {
469 if (!me.dragUploader.filesExtensionsAllowed) {
470 return true;
471 }
472 var extension = me.file.name.split('.').pop();
473 var allowed = me.dragUploader.filesExtensionsAllowed.split(',');
474 if ($.inArray(extension.toLowerCase(), allowed) !== -1) {
475 return true;
476 }
477 return false;
478 };
479
480 // position queue item in filelist
481 if ($('tbody tr.upload-queue-item', me.dragUploader.$fileList).length === 0) {
482 me.$row.prependTo($('tbody', me.dragUploader.$fileList));
483 me.$row.addClass('last');
484 } else {
485 me.$row.insertBefore($('tbody tr.upload-queue-item:first', me.dragUploader.$fileList));
486 }
487
488 // set dummy file icon
489 me.$iconCol.html('<span class="t3-icon t3-icon-mimetypes t3-icon-other-other">&nbsp;</span>')
490
491 // check file size
492 if (me.file.size > me.dragUploader.maxFileSize) {
493 me.updateMessage(TYPO3.lang['file_upload.maxFileSizeExceeded']
494 .replace(/\{0\}/g, me.file.name)
495 .replace(/\{1\}/g, DragUploader.fileSizeAsString(me.dragUploader.maxFileSize)));
496 me.$row.addClass('error');
497
498 // check filename/extension against deny pattern
499 } else if (me.dragUploader.fileDenyPattern && me.file.name.match(me.dragUploader.fileDenyPattern)) {
500 me.updateMessage(TYPO3.lang['file_upload.fileNotAllowed'].replace(/\{0\}/g, me.file.name));
501 me.$row.addClass('error');
502
503 } else if (!me.checkAllowedExtensions()) {
504 me.updateMessage(TYPO3.lang['file_upload.fileExtensionExpected']
505 .replace(/\{0\}/g, me.dragUploader.filesExtensionsAllowed)
506 );
507 me.$row.addClass('error');
508 } else {
509 me.updateMessage('- ' + DragUploader.fileSizeAsString(me.file.size));
510
511 var formData = new FormData();
512 formData.append('file[upload][1][target]', me.dragUploader.target);
513 formData.append('file[upload][1][data]', '1');
514 formData.append('overwriteExistingFiles', me.override);
515 formData.append('redirect', '');
516 formData.append('upload_1', me.file);
517
518 var s = $.extend(true, {}, $.ajaxSettings, {
519 url: TYPO3.settings.ajaxUrls['TYPO3_tcefile::process'],
520 contentType: false,
521 processData: false,
522 data: formData,
523 cache: false,
524 type: 'POST',
525 success: me.uploadSuccess,
526 error: me.uploadError
527 });
528
529 s.xhr = function() {
530 var xhr = $.ajaxSettings.xhr();
531 xhr.upload.addEventListener('progress', me.updateProgress);
532 return xhr;
533 };
534
535 // start upload
536 me.upload = $.ajax(s);
537 }
538 };
539
540 /**
541 * part 2: The main module of this file
542 * - initialize the DragUploader module and register
543 * the jQuery plugin in the jQuery global object
544 * when initializing the DragUploader module
545 */
546 var DragUploader = {};
547
548 DragUploader.options = {
549 };
550
551 DragUploader.fileSizeAsString = function(size) {
552 var string = '',
553 sizeKB = size / 1024;
554
555 if (parseInt(sizeKB) > 1024) {
556 var sizeMB = sizeKB / 1024;
557 string = sizeMB.toFixed(1) + ' MB';
558 } else {
559 string = sizeKB.toFixed(1) + ' KB';
560 }
561 return string;
562 };
563
564 DragUploader.initialize = function() {
565 var me = this,
566 opts = me.options;
567
568 // register the jQuery plugin "DragUploaderPlugin"
569 $.fn.dragUploader = function(option) {
570 return this.each(function() {
571 var $this = $(this),
572 data = $this.data('DragUploaderPlugin');
573 if (!data) {
574 $this.data('DragUploaderPlugin', (data = new DragUploaderPlugin(this)));
575 }
576 if (typeof option === 'string') {
577 data[option]();
578 }
579 })
580 };
581
582 $('.t3js-drag-uploader').dragUploader();
583 };
584
585
586
587 /**
588 * part 3: initialize the RequireJS module, require possible post-initialize hooks,
589 * and return the main object
590 */
591 var initialize = function() {
592
593 DragUploader.initialize();
594
595 // load required modules to hook in the post initialize function
596 if (
597 'undefined' !== typeof TYPO3.settings
598 && 'undefined' !== typeof TYPO3.settings.RequireJS
599 && 'undefined' !== typeof TYPO3.settings.RequireJS.PostInitializationModules
600 && 'undefined' !== typeof TYPO3.settings.RequireJS.PostInitializationModules['TYPO3/CMS/Backend/DragUploader']
601 ) {
602 $.each(TYPO3.settings.RequireJS.PostInitializationModules['TYPO3/CMS/Backend/DragUploader'], function(pos, moduleName) {
603 require([moduleName]);
604 });
605 }
606
607 // return the object in the global space
608 return DragUploader;
609 };
610
611 // call the main initialize function and execute the hooks
612 return initialize();
613
614 });