rlm@3: /** rlm@3: * Swiff.Uploader - Flash FileReference Control rlm@3: * rlm@3: * @version 3.0 rc1 rlm@3: * rlm@3: * @license MIT License rlm@3: * rlm@3: * @author Harald Kirschner rlm@3: * @copyright Authors rlm@3: */ rlm@3: rlm@3: Swiff.Uploader = new Class({ rlm@3: rlm@3: Extends: Swiff, rlm@3: rlm@3: Implements: Events, rlm@3: rlm@3: options: { rlm@3: path: 'Swiff.Uploader.swf', rlm@3: rlm@3: target: null, rlm@3: zIndex: 9999, rlm@3: rlm@3: height: 30, rlm@3: width: 100, rlm@3: callBacks: null, rlm@3: params: { rlm@3: wMode: 'opaque', rlm@3: menu: 'false', rlm@3: allowScriptAccess: 'always' rlm@3: }, rlm@3: rlm@3: typeFilter: null, rlm@3: multiple: true, rlm@3: queued: true, rlm@3: verbose: false, rlm@3: rlm@3: url: null, rlm@3: method: null, rlm@3: data: null, rlm@3: mergeData: true, rlm@3: fieldName: null, rlm@3: rlm@3: fileSizeMin: 1, rlm@3: fileSizeMax: null, // Official limit is 100 MB for FileReference! rlm@3: allowDuplicates: false, rlm@3: rlm@3: buttonImage: null, rlm@3: rlm@3: fileListMax: 0, rlm@3: fileListSizeMax: 0, rlm@3: rlm@3: instantStart: false, rlm@3: appendCookieData: false, rlm@3: rlm@3: fileClass: null rlm@3: /* rlm@3: onLoad: $empty, rlm@3: onFail: $empty, rlm@3: onStart: $empty, rlm@3: onQueue: $empty, rlm@3: onComplete: $empty, rlm@3: onBrowse: $empty, rlm@3: onDisabledBrowse: $empty, rlm@3: onCancel: $empty, rlm@3: onSelect: $empty, rlm@3: onSelectSuccess: $empty, rlm@3: onSelectFail: $empty, rlm@3: rlm@3: onButtonEnter: $empty, rlm@3: onButtonLeave: $empty, rlm@3: onButtonDown: $empty, rlm@3: onButtonDisable: $empty, rlm@3: rlm@3: onFileStart: $empty, rlm@3: onFileStop: $empty, rlm@3: onFileRequeue: $empty, rlm@3: onFileOpen: $empty, rlm@3: onFileProgress: $empty, rlm@3: onFileComplete: $empty, rlm@3: onFileRemove: $empty rlm@3: */ rlm@3: }, rlm@3: rlm@3: initialize: function(options) { rlm@3: // protected events to control the class, added rlm@3: // before setting options (which adds own events) rlm@3: this.addEvent('load', this.initializeSwiff, true) rlm@3: .addEvent('select', this.processFiles, true) rlm@3: .addEvent('complete', this.update, true) rlm@3: .addEvent('fileRemove', function(file) { rlm@3: this.fileList.erase(file); rlm@3: }.bind(this), true); rlm@3: rlm@3: this.setOptions(options); rlm@3: rlm@3: // callbacks are no longer in the options, every callback rlm@3: // is fired as event, this is just compat rlm@3: if (this.options.callBacks) { rlm@3: Hash.each(this.options.callBacks, function(fn, name) { rlm@3: this.addEvent(name, fn); rlm@3: }, this); rlm@3: } rlm@3: rlm@3: this.options.callBacks = { rlm@3: fireCallback: this.fireCallback.bind(this) rlm@3: }; rlm@3: rlm@3: var path = this.options.path; rlm@3: if (!path.contains('?')) path += '?noCache=' + $time(); // cache in IE rlm@3: rlm@3: // container options for Swiff class rlm@3: this.options.container = this.box = new Element('span', {'class': 'swiff-uploader-box'}).inject($(this.options.container) || document.body); rlm@3: rlm@3: // target rlm@3: this.target = $(this.options.target); rlm@3: if (this.target) { rlm@3: var scroll = window.getScroll(); rlm@3: this.box.setStyles({ rlm@3: position: 'absolute', rlm@3: visibility: 'visible', rlm@3: zIndex: this.options.zIndex, rlm@3: overflow: 'hidden', rlm@3: height: 1, width: 1, rlm@3: top: scroll.y, left: scroll.x rlm@3: }); rlm@3: rlm@3: // we force wMode to transparent for the overlay effect rlm@3: this.parent(path, { rlm@3: params: { rlm@3: wMode: 'transparent' rlm@3: }, rlm@3: height: '100%', rlm@3: width: '100%' rlm@3: }); rlm@3: rlm@3: this.target.addEvent('mouseenter', this.reposition.bind(this, [])); rlm@3: rlm@3: // button interactions, relayed to to the target rlm@3: this.addEvents({ rlm@3: buttonEnter: this.targetRelay.bind(this, ['mouseenter']), rlm@3: buttonLeave: this.targetRelay.bind(this, ['mouseleave']), rlm@3: buttonDown: this.targetRelay.bind(this, ['mousedown']), rlm@3: buttonDisable: this.targetRelay.bind(this, ['disable']) rlm@3: }); rlm@3: rlm@3: this.reposition(); rlm@3: window.addEvent('resize', this.reposition.bind(this, [])); rlm@3: } else { rlm@3: this.parent(path); rlm@3: } rlm@3: rlm@3: this.inject(this.box); rlm@3: rlm@3: this.fileList = []; rlm@3: rlm@3: this.size = this.uploading = this.bytesLoaded = this.percentLoaded = 0; rlm@3: rlm@3: if (Browser.Plugins.Flash.version < 9) { rlm@3: this.fireEvent('fail', ['flash']); rlm@3: } else { rlm@3: this.verifyLoad.delay(500, this); rlm@3: } rlm@3: }, rlm@3: rlm@3: verifyLoad: function() { rlm@3: if (this.loaded) return; rlm@3: if (!this.object.parentNode) { rlm@3: this.fireEvent('fail', ['disabled']); rlm@3: } else if (this.object.style.display == 'none') { rlm@3: this.fireEvent('fail', ['hidden']); rlm@3: } else if (!this.object.offsetWidth) { rlm@3: this.fireEvent('fail', ['empty']); rlm@3: } rlm@3: }, rlm@3: rlm@3: fireCallback: function(name, args) { rlm@3: // file* callbacks are relayed to the specific file rlm@3: if (name.substr(0, 4) == 'file') { rlm@3: // updated queue data is the second argument rlm@3: if (args.length > 1) this.update(args[1]); rlm@3: var data = args[0]; rlm@3: rlm@3: var file = this.findFile(data.id); rlm@3: this.fireEvent(name, file || data, 5); rlm@3: if (file) { rlm@3: var fire = name.replace(/^file([A-Z])/, function($0, $1) { rlm@3: return $1.toLowerCase(); rlm@3: }); rlm@3: file.update(data).fireEvent(fire, [data], 10); rlm@3: } rlm@3: } else { rlm@3: this.fireEvent(name, args, 5); rlm@3: } rlm@3: }, rlm@3: rlm@3: update: function(data) { rlm@3: // the data is saved right to the instance rlm@3: $extend(this, data); rlm@3: this.fireEvent('queue', [this], 10); rlm@3: return this; rlm@3: }, rlm@3: rlm@3: findFile: function(id) { rlm@3: for (var i = 0; i < this.fileList.length; i++) { rlm@3: if (this.fileList[i].id == id) return this.fileList[i]; rlm@3: } rlm@3: return null; rlm@3: }, rlm@3: rlm@3: initializeSwiff: function() { rlm@3: // extracted options for the swf rlm@3: this.remote('initialize', { rlm@3: width: this.options.width, rlm@3: height: this.options.height, rlm@3: typeFilter: this.options.typeFilter, rlm@3: multiple: this.options.multiple, rlm@3: queued: this.options.queued, rlm@3: url: this.options.url, rlm@3: method: this.options.method, rlm@3: data: this.options.data, rlm@3: mergeData: this.options.mergeData, rlm@3: fieldName: this.options.fieldName, rlm@3: verbose: this.options.verbose, rlm@3: fileSizeMin: this.options.fileSizeMin, rlm@3: fileSizeMax: this.options.fileSizeMax, rlm@3: allowDuplicates: this.options.allowDuplicates, rlm@3: buttonImage: this.options.buttonImage rlm@3: }); rlm@3: rlm@3: this.loaded = true; rlm@3: rlm@3: this.appendCookieData(); rlm@3: }, rlm@3: rlm@3: targetRelay: function(name) { rlm@3: if (this.target) this.target.fireEvent(name); rlm@3: }, rlm@3: rlm@3: reposition: function(coords) { rlm@3: // update coordinates, manual or automatically rlm@3: coords = coords || (this.target && this.target.offsetHeight) rlm@3: ? this.target.getCoordinates(this.box.getOffsetParent()) rlm@3: : {top: window.getScrollTop(), left: 0, width: 40, height: 40} rlm@3: this.box.setStyles(coords); rlm@3: this.fireEvent('reposition', [coords, this.box, this.target]); rlm@3: }, rlm@3: rlm@3: setOptions: function(options) { rlm@3: if (options) { rlm@3: if (options.url) options.url = Swiff.Uploader.qualifyPath(options.url); rlm@3: if (options.buttonImage) options.buttonImage = Swiff.Uploader.qualifyPath(options.buttonImage); rlm@3: this.parent(options); rlm@3: if (this.loaded) this.remote('setOptions', options); rlm@3: } rlm@3: return this; rlm@3: }, rlm@3: rlm@3: setEnabled: function(status) { rlm@3: this.remote('setEnabled', status); rlm@3: }, rlm@3: rlm@3: start: function() { rlm@3: this.remote('start'); rlm@3: }, rlm@3: rlm@3: stop: function() { rlm@3: this.remote('stop'); rlm@3: }, rlm@3: rlm@3: remove: function() { rlm@3: this.remote('remove'); rlm@3: }, rlm@3: rlm@3: fileStart: function(file) { rlm@3: this.remote('fileStart', file.id); rlm@3: }, rlm@3: rlm@3: fileStop: function(file) { rlm@3: this.remote('fileStop', file.id); rlm@3: }, rlm@3: rlm@3: fileRemove: function(file) { rlm@3: this.remote('fileRemove', file.id); rlm@3: }, rlm@3: rlm@3: fileRequeue: function(file) { rlm@3: this.remote('fileRequeue', file.id); rlm@3: }, rlm@3: rlm@3: appendCookieData: function() { rlm@3: var append = this.options.appendCookieData; rlm@3: if (!append) return; rlm@3: rlm@3: var hash = {}; rlm@3: document.cookie.split(/;\s*/).each(function(cookie) { rlm@3: cookie = cookie.split('='); rlm@3: if (cookie.length == 2) { rlm@3: hash[decodeURIComponent(cookie[0])] = decodeURIComponent(cookie[1]); rlm@3: } rlm@3: }); rlm@3: rlm@3: var data = this.options.data || {}; rlm@3: if ($type(append) == 'string') data[append] = hash; rlm@3: else $extend(data, hash); rlm@3: rlm@3: this.setOptions({data: data}); rlm@3: }, rlm@3: rlm@3: processFiles: function(successraw, failraw, queue) { rlm@3: var cls = this.options.fileClass || Swiff.Uploader.File; rlm@3: rlm@3: var fail = [], success = []; rlm@3: rlm@3: if (successraw) { rlm@3: successraw.each(function(data) { rlm@3: var ret = new cls(this, data); rlm@3: if (!ret.validate()) { rlm@3: ret.remove.delay(10, ret); rlm@3: fail.push(ret); rlm@3: } else { rlm@3: this.size += data.size; rlm@3: this.fileList.push(ret); rlm@3: success.push(ret); rlm@3: ret.render(); rlm@3: } rlm@3: }, this); rlm@3: rlm@3: this.fireEvent('selectSuccess', [success], 10); rlm@3: } rlm@3: rlm@3: if (failraw || fail.length) { rlm@3: fail.extend((failraw) ? failraw.map(function(data) { rlm@3: return new cls(this, data); rlm@3: }, this) : []).each(function(file) { rlm@3: file.invalidate().render(); rlm@3: }); rlm@3: rlm@3: this.fireEvent('selectFail', [fail], 10); rlm@3: } rlm@3: rlm@3: this.update(queue); rlm@3: rlm@3: if (this.options.instantStart && success.length) this.start(); rlm@3: } rlm@3: rlm@3: }); rlm@3: rlm@3: $extend(Swiff.Uploader, { rlm@3: rlm@3: STATUS_QUEUED: 0, rlm@3: STATUS_RUNNING: 1, rlm@3: STATUS_ERROR: 2, rlm@3: STATUS_COMPLETE: 3, rlm@3: STATUS_STOPPED: 4, rlm@3: rlm@3: log: function() { rlm@3: if (window.console && console.info) console.info.apply(console, arguments); rlm@3: }, rlm@3: rlm@3: unitLabels: { rlm@3: b: [{min: 1, unit: 'B'}, {min: 1024, unit: 'kB'}, {min: 1048576, unit: 'MB'}, {min: 1073741824, unit: 'GB'}], rlm@3: s: [{min: 1, unit: 's'}, {min: 60, unit: 'm'}, {min: 3600, unit: 'h'}, {min: 86400, unit: 'd'}] rlm@3: }, rlm@3: rlm@3: formatUnit: function(base, type, join) { rlm@3: var labels = Swiff.Uploader.unitLabels[(type == 'bps') ? 'b' : type]; rlm@3: var append = (type == 'bps') ? '/s' : ''; rlm@3: var i, l = labels.length, value; rlm@3: rlm@3: if (base < 1) return '0 ' + labels[0].unit + append; rlm@3: rlm@3: if (type == 's') { rlm@3: var units = []; rlm@3: rlm@3: for (i = l - 1; i >= 0; i--) { rlm@3: value = Math.floor(base / labels[i].min); rlm@3: if (value) { rlm@3: units.push(value + ' ' + labels[i].unit); rlm@3: base -= value * labels[i].min; rlm@3: if (!base) break; rlm@3: } rlm@3: } rlm@3: rlm@3: return (join === false) ? units : units.join(join || ', '); rlm@3: } rlm@3: rlm@3: for (i = l - 1; i >= 0; i--) { rlm@3: value = labels[i].min; rlm@3: if (base >= value) break; rlm@3: } rlm@3: rlm@3: return (base / value).toFixed(1) + ' ' + labels[i].unit + append; rlm@3: } rlm@3: rlm@3: }); rlm@3: rlm@3: Swiff.Uploader.qualifyPath = (function() { rlm@3: rlm@3: var anchor; rlm@3: rlm@3: return function(path) { rlm@3: (anchor || (anchor = new Element('a'))).href = path; rlm@3: return anchor.href; rlm@3: }; rlm@3: rlm@3: })(); rlm@3: rlm@3: Swiff.Uploader.File = new Class({ rlm@3: rlm@3: Implements: Events, rlm@3: rlm@3: initialize: function(base, data) { rlm@3: this.base = base; rlm@3: this.update(data); rlm@3: }, rlm@3: rlm@3: update: function(data) { rlm@3: return $extend(this, data); rlm@3: }, rlm@3: rlm@3: validate: function() { rlm@3: var options = this.base.options; rlm@3: rlm@3: if (options.fileListMax && this.base.fileList.length >= options.fileListMax) { rlm@3: this.validationError = 'fileListMax'; rlm@3: return false; rlm@3: } rlm@3: rlm@3: if (options.fileListSizeMax && (this.base.size + this.size) > options.fileListSizeMax) { rlm@3: this.validationError = 'fileListSizeMax'; rlm@3: return false; rlm@3: } rlm@3: rlm@3: return true; rlm@3: }, rlm@3: rlm@3: invalidate: function() { rlm@3: this.invalid = true; rlm@3: this.base.fireEvent('fileInvalid', this, 10); rlm@3: return this.fireEvent('invalid', this, 10); rlm@3: }, rlm@3: rlm@3: render: function() { rlm@3: return this; rlm@3: }, rlm@3: rlm@3: setOptions: function(options) { rlm@3: if (options) { rlm@3: if (options.url) options.url = Swiff.Uploader.qualifyPath(options.url); rlm@3: this.base.remote('fileSetOptions', this.id, options); rlm@3: this.options = $merge(this.options, options); rlm@3: } rlm@3: return this; rlm@3: }, rlm@3: rlm@3: start: function() { rlm@3: this.base.fileStart(this); rlm@3: return this; rlm@3: }, rlm@3: rlm@3: stop: function() { rlm@3: this.base.fileStop(this); rlm@3: return this; rlm@3: }, rlm@3: rlm@3: remove: function() { rlm@3: this.base.fileRemove(this); rlm@3: return this; rlm@3: }, rlm@3: rlm@3: requeue: function() { rlm@3: this.base.fileRequeue(this); rlm@3: } rlm@3: rlm@3: });