Index: branches/5.3.x/core/admin_templates/js/uploader/uploader.js =================================================================== diff -u -N -r15902 -r16002 --- branches/5.3.x/core/admin_templates/js/uploader/uploader.js (.../uploader.js) (revision 15902) +++ branches/5.3.x/core/admin_templates/js/uploader/uploader.js (.../uploader.js) (revision 16002) @@ -1,9 +1,6 @@ -// this js class name is hardcoded in flash object :( -var SWFUpload = function () {}; -SWFUpload.instances = {}; - function Uploader(id, params) { this.id = id; + this.instance = null; // normalize params if (isNaN(parseInt(params.multiple))) { @@ -14,9 +11,7 @@ params.allowedFilesize = this._normalizeFilesize(params.allowedFilesize); // set params to uploader - this._eventQueue = []; - this.uploadCancelled = false; - this.flashReady = false; + this.ready = false; this.params = params; this._ensureDefaultValues(); @@ -29,6 +24,7 @@ this.deleteURL = params.deleteURL; this.enableUploadButton(); + this._fixFileExtensions(); this._attachEventHandler(); var $me = this; @@ -46,6 +42,10 @@ } /* ==== Private methods ==== */ +Uploader.prototype._fixFileExtensions = function() { + this.params.allowedFiletypes = this.params.allowedFiletypes.replace(/\*\./g, '').replace(/;/g, ','); +}; + Uploader.prototype._attachEventHandler = function() { var $me = this; @@ -56,46 +56,31 @@ Uploader.prototype._ensureDefaultValues = function() { // Upload backend settings - var $defaults = { - baseUrl : '', - uploadURL : '', - deleteURL : '', - previewURL : '', - useQueryString : false, - requeueOnError : false, - httpSuccess : '', - filePostName : 'Filedata', - allowedFiletypes : '*.*', - allowedFiletypesDescription : 'All Files', - allowedFilesize : 0, // Default zero means "unlimited" - multiple : 0, - field : '', - thumb_format: '', - urls : '', - names : '', - sizes : '', - fileQueueLimit : 0, - buttonImageURL : '', - buttonWidth : 1, - buttonHeight : 1, - buttonText : '', - buttonTextTopPadding : 0, - buttonTextLeftPadding : 0, - buttonTextStyle : 'color: #000000; font-size: 16pt;', - buttonAction : parseInt(this.params.multiple) == 1 ? -100 : -110, // SELECT_FILE : -100, SELECT_FILES : -110 - buttonDisabled : true, //false, - buttonCursor : -1, // ARROW : -1, HAND : -2 - wmode : 'transparent', // "window", "transparent", "opaque" - buttonPlaceholderId: false, - ajax: false - }; - for (var $param_name in $defaults) { - if (this.params[$param_name] == null) { -// console.log('setting default value [', $defaults[$param_name], '] for missing parameter [', $param_name, '] instead of [', this.params[$param_name], ']'); - this.params[$param_name] = $defaults[$param_name]; + var $me = this, + $defaults = { + baseUrl : '', + uploadURL : '', + deleteURL : '', + previewURL : '', + allowedFiletypes : '*.*', + allowedFiletypesDescription : 'All Files', + allowedFilesize : 0, // Default zero means "unlimited" + multiple : 0, + field : '', + thumb_format: '', + urls : '', + names : '', + sizes : '', + ajax: false + }; + + $.each($defaults, function ($param_name, $param_value) { + if ($me.params[$param_name] == null) { +// console.log('setting default value [', $param_value, '] for missing parameter [', $param_name, '] instead of [', $me.params[$param_name], ']'); + $me.params[$param_name] = $param_value; } - } + }); }; Uploader.prototype._normalizeFilesize = function($file_size) { @@ -109,8 +94,8 @@ }; Uploader.prototype._prepareFiles = function() { - var ids = ''; - var names = ''; + var ids = '', + names = ''; // process uploaded files for (var f = 0; f < this.files.length; f++) { @@ -122,8 +107,8 @@ names += this.files[f].name + '|'; } - ids = ids.replace(/\|$/, '', ids); - names = names.replace(/\|$/, '', names); + ids = ids.replace(/\|$/, ''); + names = names.replace(/\|$/, ''); document.getElementById(this.id+'[tmp_ids]').value = ids; document.getElementById(this.id+'[tmp_names]').value = names; @@ -142,71 +127,78 @@ return mb + ' MB'; }; -Uploader.prototype._executeNextEvent = function () { - var f = this._eventQueue ? this._eventQueue.shift() : null; - if (typeof(f) === 'function') { - f.apply(this); - } -}; - /* ==== Public methods ==== */ Uploader.prototype.init = function() { + var $me = this, + $uploader_options = { + runtimes : 'flash,html4', // html5 + chunk_size: '1mb', + browse_button : this.id + '_browse_button', + container: this.id + '_container', + url : this.params.uploadURL, + flash_swf_url : this.params.baseUrl + '/Moxie.swf', + multi_selection: this.params.multiple > 1, + filters : {}, + init: {} + }; + + if ( this.params.allowedFilesize > 0 ) { + $uploader_options.filters.max_file_size = this.params.allowedFilesize + 'kb'; + } + + if ( this.params.allowedFiletypes != '*' ) { + $uploader_options.filters.mime_types = [ + {title : this.params.allowedFiletypesDescription, extensions : this.params.allowedFiletypes} + ]; + } + this.IconPath = this.params.IconPath ? this.params.IconPath : '../admin_templates/img/browser/icons'; - // initialize flash object - this.flash_id = UploadsManager._nextFlashId(); + $uploader_options.init['Init'] = function(uploader) { + $me.onReady(); + }; - // add callbacks for every event, because none of callbacks will work in other case (see swfupload documentation) - SWFUpload.instances[this.flash_id] = this; - SWFUpload.instances[this.flash_id].flashReady = function () { UploadsManager.onFlashReady(this.id); }; - SWFUpload.instances[this.flash_id].fileDialogStart = UploadsManager.onHandleEverything; - SWFUpload.instances[this.flash_id].fileQueued = UploadsManager.onFileQueued; - SWFUpload.instances[this.flash_id].fileQueueError = UploadsManager.onFileQueueError; - SWFUpload.instances[this.flash_id].fileDialogComplete = UploadsManager.onHandleEverything; + $uploader_options.init['FilesAdded'] = function(uploader, files) { + $.each(files, function (index, file) { + $me.onFileQueued(file); + }); - SWFUpload.instances[this.flash_id].uploadStart = UploadsManager.onUploadStart; - SWFUpload.instances[this.flash_id].uploadProgress = UploadsManager.onUploadProgress; - SWFUpload.instances[this.flash_id].uploadError = UploadsManager.onUploadError; - SWFUpload.instances[this.flash_id].uploadSuccess = UploadsManager.onUploadSuccess; - SWFUpload.instances[this.flash_id].uploadComplete = UploadsManager.onUploadComplete; - SWFUpload.instances[this.flash_id].debug = UploadsManager.onDebug; + $me.startUpload(); + }; - this.swf = new SWFObject(this.params.baseUrl + '/swfupload.swf', this.flash_id, this.params.buttonWidth, this.params.buttonHeight, '9', '#FFFFFF'); - this.swf.setAttribute('style', ''); - this.swf.addParam('wmode', encodeURIComponent(this.params.wmode)); + $uploader_options.init['FilesRemoved'] = function(uploader, files) { + $.each(files, function (index, file) { + if ( file.status != plupload.QUEUED ) { + uploader.stop(); + uploader.start(); + } + }); + }; - this.swf.addVariable('movieName', encodeURIComponent(this.flash_id)); - this.swf.addVariable('fileUploadLimit', 0); - this.swf.addVariable('fileQueueLimit', encodeURIComponent(this.params.fileQueueLimit)); - this.swf.addVariable('fileSizeLimit', encodeURIComponent(this.params.allowedFilesize)); // in kilobytes - this.swf.addVariable('fileTypes', encodeURIComponent(this.params.allowedFiletypes)); - this.swf.addVariable('fileTypesDescription', encodeURIComponent(this.params.allowedFiletypesDescription)); - this.swf.addVariable('uploadURL', encodeURIComponent(this.params.uploadURL)); + $uploader_options.init['Error'] = function(uploader, error) { + $me.onError(error); + }; - // upload button appearance - this.swf.addVariable('buttonImageURL', encodeURIComponent(this.params.buttonImageURL)); - this.swf.addVariable('buttonWidth', encodeURIComponent(this.params.buttonWidth)); - this.swf.addVariable('buttonHeight', encodeURIComponent(this.params.buttonHeight)); - this.swf.addVariable('buttonText', encodeURIComponent(this.params.buttonText)); - this.swf.addVariable('buttonTextTopPadding', encodeURIComponent(this.params.buttonTextTopPadding)); - this.swf.addVariable('buttonTextLeftPadding', encodeURIComponent(this.params.buttonTextLeftPadding)); - this.swf.addVariable('buttonTextStyle', encodeURIComponent(this.params.buttonTextStyle)); - this.swf.addVariable('buttonAction', encodeURIComponent(this.params.buttonAction)); - this.swf.addVariable('buttonDisabled', encodeURIComponent(this.params.buttonDisabled)); - this.swf.addVariable('buttonCursor', encodeURIComponent(this.params.buttonCursor)); + $uploader_options.init['BeforeUpload'] = function(uploader, file) { + return $me.onUploadFileStart(uploader, file); + }; - if (UploadsManager._debugMode) { - this.swf.addVariable('debugEnabled', encodeURIComponent('true')); // flash var - } + $uploader_options.init['UploadProgress'] = function(uploader, file) { + $me.onUploadProgress(file); + }; - var $me = this; + $uploader_options.init['FileUploaded'] = function(uploader, file, response) { + $me.onUploadFinished(file, response); + }; + this.instance = new plupload.Uploader($uploader_options); + Application.setHook( 'm:OnAfterFormInit', function () { $me.renderBrowseButton(); } - ) + ); this.refreshQueue(); }; @@ -239,12 +231,15 @@ for (var i = 0; i < urls.length; i++) { var a_file = { + // original properties from Uploader id : this.getUploadedFileId(names[i]), name : names[i], - url : urls[i], size: sizes[i], - uploaded : 1, - progress: 100 + percent: 100, + + // custom properties + url : urls[i], + uploaded : 1 }; this.files_count++; @@ -278,67 +273,18 @@ }; Uploader.prototype.enableUploadButton = function() { - var $me = this; - - // enable upload button, when flash is fully loaded - this.queueEvent( - function() { - setTimeout( - function () { - $me.callFlash('SetButtonDisabled', [false]); - }, 0 - ) - } - ); + // enable upload button, when plupload runtime is fully loaded + $('#' + jq(this.id + '_browse_button')).prop('disabled', false).removeClass('button-disabled'); }; Uploader.prototype.renderBrowseButton = function() { - var holder = document.getElementById(this.params.buttonPlaceholderId); - this.swf.write(holder); - - this.flash = document.getElementById(this.flash_id); + this.instance.init(); }; Uploader.prototype.remove = function() { - var id = this.params.buttonPlaceholderId; - - var obj = document.getElementById(id); - - if (obj/* && obj.nodeName == "OBJECT"*/) { - var u = navigator.userAgent.toLowerCase(); - var p = navigator.platform.toLowerCase(); - var windows = p ? /win/.test(p) : /win/.test(u); - var $me = this; - - if (document.all && windows) { - obj.style.display = "none"; - (function(){ - if (obj.readyState == 4) { - $me.removeObjectInIE(id); - } - else { - setTimeout(arguments.callee, 10); - } - })(); - } - else { - obj.parentNode.removeChild(obj); - } - } + this.instance.destroy(); }; -Uploader.prototype.removeObjectInIE = function(id) { - var obj = document.getElementById(id); - if (obj) { - for (var i in obj) { - if (typeof obj[i] == 'function') { - obj[i] = null; - } - } - obj.parentNode.removeChild(obj); - } -}; - Uploader.prototype.isImage = function($filename) { this.removeTempExtension($filename).match(/\.([^.]*)$/); @@ -379,10 +325,11 @@ }; Uploader.prototype.getQueueElement = function($file) { - var $ret = ''; - var $icon_image = this.getFileIcon($file.name); - var $file_label = this.removeTempExtension($file.name) + ' (' + this._formatSize($file.size) + ')'; - var $need_preview = false; + var $me = this, + $ret = '', + $icon_image = this.getFileIcon($file.name), + $file_label = this.removeTempExtension($file.name) + ' (' + this._formatSize($file.size) + ')', + $need_preview = false; if (isset($file.uploaded)) { // add deletion checkbox @@ -393,7 +340,7 @@ $ret += '
'; if ($need_preview) { - $ret += ''; + $ret += ''; } else { $ret += ''; @@ -422,20 +369,15 @@ $ret = $('
' + $ret + '
'); // set click events - var $me = this; + $('.delete-file-btn', $ret).click(function ($e) { + $(this).prop('checked', !$me.deleteFile($file)); + }); - $('.delete-file-btn', $ret).click( - function ($e) { - $(this).prop('checked', !UploadsManager.DeleteFile($me.id, $file.name)); - } - ); + $('.cancel-upload-btn', $ret).click(function ($e) { + $me.removeFile($file); - $('.cancel-upload-btn', $ret).click( - function ($e) { - UploadsManager.CancelFile(UploadsManager._getUploader($file).id, $file.id); - return false; - } - ); + $e.preventDefault(); + }); // prepare auto-loading preview var $image = $('img.thumbnail-image', $ret); @@ -509,7 +451,7 @@ }; Uploader.prototype.updateProgressOnly = function ($file_index) { - var $progress_code = '
'; + var $progress_code = '
'; $('#' + this.files[$file_index].id + '_progress').html($progress_code); }; @@ -519,15 +461,19 @@ n_files = [], $to_delete = []; - for (var f = 0; f < this.files.length; f++) { - if (this.files[f].id != file.id && this.files[f].name != file.id) { - n_files.push(this.files[f]); - count++; + if (!isset(file.uploaded)) { + this.instance.removeFile(file); + } + + $.each(this.files, function (f, current_file) { + if ( current_file.id == file.id || current_file.name == file.name ) { + $to_delete.push(f); } else { - $to_delete.push(f); + n_files.push(current_file); + count++; } - } + }); for (var $i = 0; $i < $to_delete.length; $i++) { this.updateQueueFile($to_delete[$i], true); @@ -551,206 +497,192 @@ }; Uploader.prototype.startUpload = function() { - this.uploadCancelled = false; - - if (!this.hasQueue()) { - return; + if ( this.hasQueue() ) { + this.instance.start(); } - - this.callFlash('StartUpload'); }; -Uploader.prototype.cancelUpload = function() { - this.callFlash('StopUpload'); - var $stats = this.callFlash('GetStats'); - - while ($stats.files_queued > 0) { - this.callFlash('CancelUpload'); - $stats = this.callFlash('GetStats'); +Uploader.prototype.deleteFile = function(file, confirmed) { + if (!confirmed && !confirm('Are you sure you want to delete "' + file.name + '" file?')) { + return false; } - this.uploadCancelled = true; + var $me = this; + + $.get( + this.getDeleteUrl(file), + function ($data) { + $me.removeFile(file); + $me.deleted.push(file.name); + $me.updateInfo(undefined, true); + } + ); + + return true; }; -Uploader.prototype.UploadFileStart = function(file) { - var $file_index = this.getFileIndex(file); - this.files[$file_index].progress = 0; +Uploader.prototype.onUploadFileStart = function(uploader, file) { + var $upload_url = this.params.uploadURL, + $file_index = this.getFileIndex(file), + $extra_params = { + field: this.params.field, + id: file.id, + flashsid: this.params.flashsid + }; + + this.files[$file_index].percent = file.percent; this.updateProgressOnly($file_index); - this.callFlash('AddFileParam', [file.id, 'field', this.params.field]); - this.callFlash('AddFileParam', [file.id, 'id', file.id]); - this.callFlash('AddFileParam', [file.id, 'flashsid', this.params.flashsid]); + $upload_url += ($upload_url.indexOf('?') ? '&' : '?'); - // we can prevent user from adding any files here :) - this.callFlash('ReturnUploadStart', [true]); + $.each($extra_params, function ($param_name, $param_value) { + $upload_url += $param_name + '=' + encodeURIComponent($param_value) + '&'; + }); + + uploader.settings.url = $upload_url; + + return true; }; -Uploader.prototype.UploadProgress = function(file, bytesLoaded, bytesTotal) { +Uploader.prototype.onUploadProgress = function(file) { var $file_index = this.getFileIndex(file); - this.files[$file_index].progress = Math.round(bytesLoaded / bytesTotal * 100); + + this.files[$file_index].percent = file.percent; this.updateProgressOnly($file_index); }; -Uploader.prototype.UploadSuccess = function(file, serverData, receivedResponse) { - if (!receivedResponse) { - return ; - } +Uploader.prototype.onFileQueued = function(file) { + if (this.files_count >= this.params.multiple) { + // new file can exceed allowed file number + if (this.params.multiple > 1) { + // it definitely exceed it + var $error = { + 'file': file, 'code': 'ERROR_1', 'message': 'Files count exceeds allowed limit.' + }; - for (var f = 0; f < this.files.length; f++) { - if (this.files[f].id == file.id) { - // new uploaded file name returned by OnUploadFile event - this.files[f].name = serverData; + this.instance.trigger('Error', $error); } + else { + // delete file added + this.files_count++; + this.files.push(file); + + if (this.files[0].uploaded) { + this.deleteFile(this.files[0], true); + } + else { + this.instance.removeFile(file); + } + } } + else { + // new file will not exceed allowed file number + this.files_count++; + this.files.push(file); + } + + this.updateInfo(this.files.length - 1); }; -Uploader.prototype.UploadFileComplete = function(file) { - // file was uploaded OR file upload was cancelled - var $file_index = this.getFileIndex(file); +Uploader.prototype.onError = function(error) { + this.removeFile(error.file); - if ($file_index !== false) { - // in case if file upload was cancelled, then no info here - this.files[$file_index].uploaded = 1; - this.files[$file_index].progress = 100; - this.files[$file_index].temp = 1; - this.files[$file_index].url = this.getUrl(this.files[$file_index]); - this.updateInfo($file_index); + if ( error.code == plupload.FILE_SIZE_ERROR ) { + error.message = 'File size exceeds allowed limit.'; } - - // upload next file in queue - var $stats = this.callFlash('GetStats'); - - if ($stats.files_queued > 0) { - this.callFlash('StartUpload'); + else if ( error.code == plupload.FILE_EXTENSION_ERROR ) { + error.message = 'File is not an allowed file type.'; } - else { - UploadsManager.UploadQueueComplete(this); - } + + setTimeout(function () { + alert('Error: ' + error.message + "\n" + 'Occurred on file ' + error.file.name); + }, 0); }; -Uploader.prototype.getUrl = function($file, $preview) { - var $url = this.params.previewURL.replace('#FILE#', encodeURIComponent($file.name)).replace('#FIELD#', this.params.field); +Uploader.prototype.onUploadFinished = function(file, response) { + var $json_response = eval('(' + response.response + ')'); - if ( $file.temp !== undefined && $file.temp ) { - $url += '&tmp=1&id=' + $file.id; + if (response.status != 200) { + return ; } - if ( $preview !== undefined && $preview === true ) { - $url += '&thumb=1'; - } + if ( $json_response.status == 'error' ) { + var $error = { + 'file': file, 'code': $json_response.error.code, 'message': $json_response.error.message + }; - return $url; -}; + this.instance.trigger('Error', $error); -Uploader.prototype.getFileIndex = function(file) { - for (var f = 0; f < this.files.length; f++) { - if (this.files[f].id == file.id) { - return f; - } + return ; } - return false; + // new uploaded file name returned by OnUploadFile event + file.name = $json_response.result; + + this.onUploadFileComplete(file); }; -Uploader.prototype.queueEvent = function (function_body) { - // Warning: Don't call this.debug inside here or you'll create an infinite loop - var self = this; +Uploader.prototype.onUploadFileComplete = function(file) { + // file was uploaded OR file upload was cancelled + var $file_index = this.getFileIndex(file); - // Queue the event - this._eventQueue.push(function_body); + if ($file_index !== false) { + // in case if file upload was cancelled, then no info here + this.files[$file_index].name = file.name; + this.files[$file_index].percent = file.percent; - if (!this.flashReady) { - // don't execute any flash-related events, while it's not completely loaded - return ; + this.files[$file_index].temp = 1; + this.files[$file_index].uploaded = 1; + this.files[$file_index].url = this.getPreviewUrl(this.files[$file_index]); + this.updateInfo($file_index); } - - // Execute the next queued event - setTimeout( - function () { - self._executeNextEvent(); - }, 0 - ); }; -Uploader.prototype._executeQueuedEvents = function() { - var $me = this; +Uploader.prototype.getPreviewUrl = function($file, $preview) { + var $url = this.getUrl(this.params.previewURL, $file); - setTimeout( - function () { - $me._executeNextEvent(); + if ( $preview !== undefined && $preview === true ) { + $url += '&thumb=1'; + } - if ($me._eventQueue.length > 0) { - $me._executeQueuedEvents(); - } + return $url; +}; - }, 0 - ); +Uploader.prototype.getDeleteUrl = function($file) { + return this.getUrl(this.params.deleteURL, $file); }; -// Private: callFlash handles function calls made to the Flash element. -// Calls are made with a setTimeout for some functions to work around -// bugs in the ExternalInterface library. -Uploader.prototype.callFlash = function (functionName, argumentArray) { - argumentArray = argumentArray || []; +Uploader.prototype.getUrl = function($base_url, $file) { + var $replacements = { + '#FILE#': $file.name, + '#FIELD#': this.params.field, + '#FIELD_ID#': this.id + }; - var returnValue; + var $url = $base_url; - if (typeof this.flash[functionName] === 'function') { - // We have to go through all this if/else stuff because the Flash functions don't have apply() and only accept the exact number of arguments. - if (argumentArray.length === 0) { - returnValue = this.flash[functionName](); - } else if (argumentArray.length === 1) { - returnValue = this.flash[functionName](argumentArray[0]); - } else if (argumentArray.length === 2) { - returnValue = this.flash[functionName](argumentArray[0], argumentArray[1]); - } else if (argumentArray.length === 3) { - returnValue = this.flash[functionName](argumentArray[0], argumentArray[1], argumentArray[2]); - } else { - throw 'Too many arguments'; - } + $.each($replacements, function ($replace_from, $replace_to) { + $url = $url.replace($replace_from, encodeURIComponent($replace_to)); + }); - // Unescape file post param values - if (returnValue != undefined && typeof returnValue.post === 'object') { - returnValue = this.unescapeFilePostParams(returnValue); - } - - return returnValue; - } else { -// alert('invalid function name: ' + functionName); - throw "Invalid function name: " + functionName; + if ( $file.temp !== undefined && $file.temp ) { + $url += '&tmp=1&id=' + $file.id; } + + return $url; }; -// Private: unescapeFileParams is part of a workaround for a flash bug where objects passed through ExternalInterface cannot have -// properties that contain characters that are not valid for JavaScript identifiers. To work around this -// the Flash Component escapes the parameter names and we must unescape again before passing them along. -Uploader.prototype.unescapeFilePostParams = function (file) { - var reg = /[$]([0-9a-f]{4})/i; - var unescapedPost = {}; - var uk; - - if (file != undefined) { - for (var k in file.post) { - if (file.post.hasOwnProperty(k)) { - uk = k; - var match; - while ((match = reg.exec(uk)) !== null) { - uk = uk.replace(match[0], String.fromCharCode(parseInt("0x" + match[1], 16))); - } - unescapedPost[uk] = file.post[k]; - } +Uploader.prototype.getFileIndex = function(file) { + for (var f = 0; f < this.files.length; f++) { + if (this.files[f].id == file.id) { + return f; } - - file.post = unescapedPost; } - return file; + return false; }; -Uploader.prototype.onFlashReady = function() { - var $me = this; - this.flashReady = true; - - // process events, queued before flash load - this._executeQueuedEvents(); +Uploader.prototype.onReady = function() { + this.ready = true; + UploadsManager.onReady(); }; \ No newline at end of file