Index: branches/RC/core/admin_templates/js/uploader/uploader.js =================================================================== diff -u -r11203 -r11281 --- branches/RC/core/admin_templates/js/uploader/uploader.js (.../uploader.js) (revision 11203) +++ branches/RC/core/admin_templates/js/uploader/uploader.js (.../uploader.js) (revision 11281) @@ -4,6 +4,7 @@ function Uploader(id, params) { this.id = id; + this._moved = false; // flash was moved outside scroll container // normalize params if (isNaN(parseInt(params.multiple))) { @@ -18,15 +19,55 @@ this.uploadCancelled = false; this.params = params; + this._ensureDefaultValues(); + this.files_count = 0; this.files = new Array(); this.deleted = new Array() this.uploadURL = params.uploadURL; this.deleteURL = params.deleteURL; + + this._resetCounters(); } /* ==== Private methods ==== */ +Uploader.prototype._ensureDefaultValues = function() { + // Upload backend settings + var $defaults = { + baseUrl: '', + uploadURL : '', + useQueryString : false, + requeueOnError : false, + httpSuccess : '', + filePostName : 'Filedata', + allowedFiletypes : '*.*', + allowedFiletypesDescription : 'All Files', + allowedFilesize : 0, // Default zero means "unlimited" + multiple : 0, + 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 + } + + 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]; + } + } +} + Uploader.prototype._normalizeFilesize = function($file_size) { var $normalize_size = parseInt($file_size); if (isNaN($normalize_size)) { @@ -92,8 +133,15 @@ /* ==== Public methods ==== */ Uploader.prototype.init = function() { - var holder = document.createElement('DIV'); - document.body.appendChild(holder); + if (this.params.buttonPlaceholderId !== false) { + // use given container + var holder = document.getElementById(this.params.buttonPlaceholderId); + } + else { + // create container on the fly + var holder = document.createElement('DIV'); + document.body.appendChild(holder); + } if (UploadsManager.useTransparency) { document.getElementById($form_name).style.display = 'block'; @@ -112,15 +160,13 @@ this.remaining = document.getElementById(this.id+'_progress_remaining'); this.percent = document.getElementById(this.id+'_percent'); this.done = document.getElementById(this.id+'_done'); - this.total = 0; - this.uploaded = 0; // initialize flash object this.flash_id = UploadsManager._nextFlashId(); // 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 = UploadsManager.onHandleEverything; + 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; @@ -133,32 +179,39 @@ SWFUpload.instances[this.flash_id].uploadComplete = UploadsManager.onUploadComplete; SWFUpload.instances[this.flash_id].debug = UploadsManager.onDebug; - this.swf = new SWFObject('swfupload.swf', this.flash_id, '0', '0', '8', '#FFFFFF'); -// this.swf = new SWFObject('swfupload_f9.swf', this.flash_id, '0', '0', '9', '#FFFFFF'); + 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', escape(this.params.wmode)); this.swf.addVariable('movieName', escape(this.flash_id)); this.swf.addVariable('fileUploadLimit', escape(this.params.multiple)); + this.swf.addVariable('fileQueueLimit', escape(this.params.fileQueueLimit)); this.swf.addVariable('fileSizeLimit', escape(this.params.allowedFilesize)); // in kilobytes this.swf.addVariable('fileTypes', escape(this.params.allowedFiletypes)); this.swf.addVariable('fileTypesDescription', escape(this.params.allowedFiletypesDescription)); this.swf.addVariable('uploadURL', escape(this.params.uploadURL)); + // upload button appearance + this.swf.addVariable('buttonImageURL', escape(this.params.buttonImageURL)); + this.swf.addVariable('buttonWidth', escape(this.params.buttonWidth)); + this.swf.addVariable('buttonHeight', escape(this.params.buttonHeight)); + this.swf.addVariable('buttonText', escape(this.params.buttonText)); + this.swf.addVariable('buttonTextTopPadding', escape(this.params.buttonTextTopPadding)); + this.swf.addVariable('buttonTextLeftPadding', escape(this.params.buttonTextLeftPadding)); + this.swf.addVariable('buttonTextStyle', escape(this.params.buttonTextStyle)); + this.swf.addVariable('buttonAction', escape(this.params.buttonAction)); + this.swf.addVariable('buttonDisabled', escape(this.params.buttonDisabled)); + this.swf.addVariable('buttonCursor', escape(this.params.buttonCursor)); + if (UploadsManager._debugMode) { this.swf.addVariable('debugEnabled', escape('true')); // flash var } - this.swf.write(holder); - - this.flash = document.getElementById(this.flash_id); - /*if (this.flash != null) { - if(this.flash.PercentLoaded() == 100) { - alert('done movie: '+this.flash.PercentLoaded()); - } + if (this.params.buttonPlaceholderId === false) { + // only write flash, when button placeholder is not used + this.swf.write(holder); + this.flash = document.getElementById(this.flash_id); } - else { - alert('this.flash is null') - }*/ if (this.params.urls != '') { var urls = this.params.urls.split('|'); @@ -179,40 +232,122 @@ } } +Uploader.prototype.moveOutside = function() { + // move flash outside scroll_container, but keeps it's position on screen + if (!UploadsManager.useTransparency) { + // moving only needed when transparency us used (e.g. in admin) + return ; + } + + var $new_container = document.createElement('DIV'); + $new_container.id = this.params.buttonPlaceholderId + '_outside'; + $new_container.style.position = 'absolute'; + + var $old_container = document.getElementById(this.params.buttonPlaceholderId); + $new_container.style.top = getRealTop($old_container) + 'px'; + $new_container.style.left = getRealLeft($old_container) + 'px'; + + var $holder_dimensions = getDimensions($old_container); + $new_container.style.width = $holder_dimensions.innerWidth + 'px'; + $new_container.style.height = $holder_dimensions.innerHeight + 'px'; + + document.body.appendChild($new_container); + + this.swf.write($new_container); // write flash outside scroll_container + this.flash = document.getElementById(this.flash_id); // fix reference to flash object + + this._moved = true; +} + +Uploader.prototype.syncBrowseButton = function() { + // when flash is moved outside scroll_container, keeps it's position on screen during scroll operations + if (!this._moved) { + return ; + } + + var $scroll_container = UploadsManager._getScrollContainer(); + var $scroll_container_top = getRealTop($scroll_container); + var $scroll_container_left = getRealLeft($scroll_container); + + var $scroll_top = $scroll_container.scrollTop; + var $scroll_left = $scroll_container.scrollLeft; + + var $old_container = document.getElementById(this.params.buttonPlaceholderId); + var $new_container = document.getElementById(this.params.buttonPlaceholderId + '_outside'); + + var $old_container_top = getRealTop($old_container); + var $old_container_left = getRealLeft($old_container); + + if ($scroll_container_top <= $old_container_top - $scroll_top) { + // prevents moving outside $scroll_container + $new_container.style.top = ($old_container_top - $scroll_top) + 'px'; + } + else { + // move browse button outside visible area + $new_container.style.top = -this.params.buttonHeight + 'px'; + } + + if ($scroll_container_left <= $old_container_left - $scroll_left) { + // prevents moving outside $scroll_container + $new_container.style.left = ($old_container_left - $scroll_left) + 'px'; + } + else { + // move browse button outside visible area + $new_container.style.left = -this.params.buttonWidth + 'px'; + } +} + Uploader.prototype.updateInfo = function() { - var o = ''; + var $o = ''; + var $icon = ''; + var $filename = ''; + var $delete_code = ''; + for (var f in this.files) { this.files[f].name.match(/\.([^.]*)$/); var ext = RegExp.$1.toLowerCase(); + $icon = ext.match(/^(ai|avi|bmp|cs|dll|doc|dot|exe|fla|gif|htm|html|jpg|js|mdb|mp3|pdf|ppt|rdp|swf|swt|txt|vsd|xls|xml|zip)$/) ? ext : 'default.icon'; + $icon = ' '; - var icon = ext.match(/^(ai|avi|bmp|cs|dll|doc|dot|exe|fla|gif|htm|html|jpg|js|mdb|mp3|pdf|ppt|rdp|swf|swt|txt|vsd|xls|xml|zip)$/) ? ext : 'default.icon'; - o += ' '; if (isset(this.files[f].uploaded)) { - o += ''+this.files[f].name + ' ('+this._formatSize(this.files[f].size)+') [Delete]
'; + $filename = '' + this.files[f].name + ' (' + this._formatSize(this.files[f].size) + ')'; + $delete_code = 'UploadsManager.DeleteFile(\'' + this.id + '\', \'' + this.files[f].name + '\')'; } else { - o += this.files[f].name + ' ('+this._formatSize(this.files[f].size)+') [Delete]
'; + $filename = this.files[f].name + ' (' + this._formatSize(this.files[f].size) + ')'; + $delete_code = 'UploadsManager.CancelFile(\'' + UploadsManager._getUploader(this.files[f]).id + '\', \'' + this.files[f].id + '\')'; } + + $o += '' + $icon + '' + $filename + ' [Delete]'; } - document.getElementById(this.id+'_queueinfo').innerHTML = o; - this._prepareFiles() + + document.getElementById(this.id+'_queueinfo').innerHTML = '' + $o + '
'; + this._prepareFiles(); + + // sync position of all uploaders below current, because file queue height change will not affect their positions + UploadsManager.iterate('syncBrowseButton'); } Uploader.prototype.removeFile = function (file) { var n_files = new Array(); var count = 0; - this.total = 0; + var $new_total = 0; for (var f in this.files) { if (this.files[f].id != file.id && this.files[f].name != file.id) { n_files.push(this.files[f]); if (!isset(this.files[f].uploaded)) { - this.total += file.size; + $new_total += file.size; } count++; } } + if (this.StartTime == 0) { + // don't update total during upload, because that breaks progress bar + this.total = $new_total; + } + this.files = n_files; this.files_count = count; this.updateInfo(); @@ -238,7 +373,7 @@ } if (UploadsManager.useTransparency) { - Request.setOpacity(30, UploadsManager._getFromContainer()); + Request.setOpacity(30, UploadsManager._getFormContainer()); } if (!document.all) { @@ -268,54 +403,56 @@ this.uploaded = 0; this.total = 0; for (var f in this.files) { - if (isset(this.files[f].uploaded)) continue; + if (isset(this.files[f].uploaded)) { + // get total bytes of non-uploaded files + continue; + } this.total += this.files[f].size; } - this.flash.StartUpload(); + this.callFlash('StartUpload'); } Uploader.prototype.cancelUpload = function() { - this.flash.StopUpload(); + this.callFlash('StopUpload'); - var $stats = this.flash.GetStats(); + var $stats = this.callFlash('GetStats'); while ($stats.files_queued > 0) { - this.flash.CancelUpload(); - $stats = this.flash.GetStats(); + this.callFlash('CancelUpload'); + + $stats = this.callFlash('GetStats'); } UploadsManager.uploadCancelled = this.uploadCancelled = true; } -Uploader.prototype.browse = function() { - if (parseInt(this.params.multiple) > 1) { - this.flash.SelectFiles(); - } else { - this.flash.SelectFile(); - } -} - Uploader.prototype.UploadFileStart = function(file) { this.filename.innerHTML = file.name; + 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]); + + UploadsManager.iterate('disableBrowse', true); // disable all "Browse" buttons (not just for current uploader)! + // we can prevent user from adding any files here :) - this.flash.ReturnUploadStart(true); + this.callFlash('ReturnUploadStart', [true]); } Uploader.prototype.UploadProgress = function(file, bytesLoaded, bytesTotal) { this.cur_file_uploaded = bytesLoaded; - var uploaded = this.uploaded+this.cur_file_uploaded; + var uploaded = this.uploaded + this.cur_file_uploaded; this.ProgressTime = this._getMicroTime() - this.StartTime; var speed = 0; if (this.ProgressTime > 0) { - speed = Math.round(uploaded/this.ProgressTime*100)/100; + speed = Math.round(uploaded / this.ProgressTime * 100) / 100; } - this.progress.innerHTML = this._formatSize(uploaded)+' / '+this._formatSize(this.total) + ' ('+this._formatSize(speed)+'/s)'; - this.ProgressPercent = Math.round(uploaded/this.total*100); - this.done.style.width = this.ProgressPercent+'%'; - this.percent.innerHTML = this.ProgressPercent+'%'; + this.progress.innerHTML = this._formatSize(uploaded) + ' / ' + this._formatSize(this.total) + ' (' + this._formatSize(speed) + '/s)'; + this.ProgressPercent = Math.round(uploaded / this.total * 100); + this.done.style.width = this.ProgressPercent + '%'; + this.percent.innerHTML = this.ProgressPercent + '%'; this.elapsed.innerHTML = this._formatTime(this.ProgressTime ); this.remaining.innerHTML = this._formatTime( this._getEstimatedTime() ); @@ -333,9 +470,9 @@ this.updateInfo(); // upload next file in queue - var $stats = this.flash.GetStats(); + var $stats = this.callFlash('GetStats'); if ($stats.files_queued > 0 && !UploadsManager.uploadCancelled) { - this.flash.StartUpload(); + this.callFlash('StartUpload'); } else { // all files in queue are uploaded OR upload was cancelled globally if (UploadsManager.uploadCancelled) { @@ -359,4 +496,94 @@ self._executeNextEvent(); }, 0 ); -}; \ No newline at end of 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 || []; + + var returnValue; + + 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'; + } + + // 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; + } +}; + +// 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]; + } + } + + file.post = unescapedPost; + } + + return file; +}; + +Uploader.prototype.onFlashReady = function() { + this.disableBrowse(false); +} + +Uploader.prototype.disableBrowse = function($disabled) { + if (!isset($disabled)) { + $disabled = true; + } + + this.queueEvent( + function() { + this.callFlash('SetButtonDisabled', [$disabled]); + } + ); +} + +Uploader.prototype._resetCounters = function() { + this.StartTime = 0; // time, when upload was started + this.ProgressPercent = 0; // upload progress in percents + this.ProgressTime = 0; // flash upload process callback times + this.total = 0; // total bytes to upload (from all queued files) + this.uploaded = 0; // total uploaded bytes (from all queued files) +} + +Uploader.prototype.finalizeUpload = function() { + // hide progress bar only of uploader, that completed it's queue + this.div.style.display = 'none'; + this._resetCounters(); +} \ No newline at end of file