blob: 9969929eaec72b0a18361fb4b981d162c7c20e00 [file] [log] [blame]
/*
---
name: Swiff.Uploader
description: Swiff.Uploader - Flash FileReference Control
requires: [Core/Swiff, Core/Fx, Core/Class, Core/Class.Extras, Core/Browser, Core/Element]
provides: [Swiff.Uploader, Swiff.Uploader.File]
version: 3.0
license: MIT License
author: Harald Kirschner <http://digitarald.de>
author: Valerio Proietti, <http://mad4milk.net>
...
*/
Swiff.Uploader = new Class({
Extends: Swiff,
Implements: Events,
options: {
path: 'Swiff.Uploader.swf',
target: null,
zIndex: 9999,
callBacks: null,
params: {
wMode: 'opaque',
menu: 'false',
allowScriptAccess: 'always'
},
typeFilter: null,
multiple: true,
queued: true,
verbose: false,
height: 30,
width: 100,
passStatus: null,
url: null,
method: null,
data: null,
mergeData: true,
fieldName: null,
fileSizeMin: 1,
fileSizeMax: null, // Official limit is 100 MB for FileReference, but I tested up to 2Gb!
allowDuplicates: false,
timeLimit: (Browser.Platform.linux) ? 0 : 30,
policyFile: null,
buttonImage: null,
fileListMax: 0,
fileListSizeMax: 0,
instantStart: false,
appendCookieData: false,
fileClass: null
/*
onLoad: function(){},
onFail: function(){},
onStart: function(){},
onQueue: function(){},
onComplete: function(){},
onBrowse: function(){},
onDisabledBrowse: function(){},
onCancel: function(){},
onSelect: function(){},
onSelectSuccess: function(){},
onSelectFail: function(){},
onButtonEnter: function(){},
onButtonLeave: function(){},
onButtonDown: function(){},
onButtonDisable: function(){},
onFileStart: function(){},
onFileStop: function(){},
onFileRequeue: function(){},
onFileOpen: function(){},
onFileProgress: function(){},
onFileComplete: function(){},
onFileRemove: function(){},
onBeforeStart: function(){},
onBeforeStop: function(){},
onBeforeRemove: function(){}
*/
},
initialize: function(options) {
// protected events to control the class, added
// before setting options (which adds own events)
this.addEvent('load', this.initializeSwiff, true)
.addEvent('select', this.processFiles, true)
.addEvent('complete', this.update, true)
.addEvent('fileRemove', function(file) {
this.fileList.erase(file);
}.bind(this), true);
this.setOptions(options);
// callbacks are no longer in the options, every callback
// is fired as event, this is just compat
if (this.options.callBacks) {
Object.each(this.options.callBacks, function(fn, name) {
this.addEvent(name, fn);
}, this);
}
this.options.callBacks = {
fireCallback: this.fireCallback.bind(this)
};
var path = this.options.path;
if (!path.contains('?')) path += '?noCache=' + Date.now(); // cache in IE
// container options for Swiff class
this.options.container = this.box = new Element('span', {'class': 'swiff-uploader-box',events: { click: function(e) { e.stopPropagation(); } }}).inject(document.id(this.options.container) || document.body);
// target
this.target = document.id(this.options.target);
if(this.target) {
var scroll = window.getScroll();
this.box.setStyles({
position: 'absolute',
visibility: 'visible',
zIndex: this.options.zIndex,
overflow: 'hidden',
height: 1, width: 1,
top: scroll.y, left: scroll.x
});
// we force wMode to transparent for the overlay effect
this.parent(path, {
params: {
wMode: 'transparent'
},
height: '100%',
width: '100%'
});
this.target.addEvent('mouseenter', this.reposition.bind(this));
// button interactions, relayed to to the target
this.addEvents({
buttonEnter: this.targetRelay.pass('mouseenter',this),
buttonLeave: this.targetRelay.pass('mouseleave',this),
buttonDown: this.targetRelay.pass('mousedown',this),
buttonDisable: this.targetRelay.pass('disable',this)
});
this.reposition();
window.addEvent('resize', this.reposition.bind(this));
} else {
this.parent(path);
}
this.inject(this.box);
this.fileList = [];
this.size = this.uploading = this.bytesLoaded = this.percentLoaded = 0;
if (Browser.Plugins.Flash.version < 9) {
this.fireEvent('fail', ['flash']);
} else {
this.verifyLoad.delay(1000, this);
}
},
verifyLoad: function() {
if (this.loaded) return;
if (!this.object.parentNode) {
this.fireEvent('fail', ['disabled']);
} else if (this.object.style.display == 'none') {
this.fireEvent('fail', ['hidden']);
} else if (!this.object.offsetWidth) {
this.fireEvent('fail', ['empty']);
}
},
fireCallback: function(name, args) {
// file* callbacks are relayed to the specific file
if (name.substr(0, 4) == 'file') {
// updated queue data is the second argument
if (args.length > 1) this.update(args[1]);
var data = args[0];
var file = this.findFile(data.id);
this.fireEvent(name, file || data, 5);
if (file) {
var fire = name.replace(/^file([A-Z])/, function($0, $1) {
return $1.toLowerCase();
});
file.update(data).fireEvent(fire, [data], 10);
}
} else {
this.fireEvent(name, args, 5);
}
},
update: function(data) {
// the data is saved right to the instance
Object.append(this, data);
this.fireEvent('queue', [this], 10);
return this;
},
findFile: function(id) {
for (var i = 0; i < this.fileList.length; i++) {
if (this.fileList[i].id == id) return this.fileList[i];
}
return null;
},
initializeSwiff: function() {
this.appendCookieData(); // looks like there's a bit of trouble with xSetOptions, so we circumvent it by passing it all in one go through xInitialize
// if (typeof console !== 'undefined' && console.log) console.log('initializeSwiff: data count = ' + this.options.data.length + ' : ' + this.options.data);
// extracted options for the swf
this.remote('xInitialize', {
typeFilter: this.options.typeFilter,
multiple: this.options.multiple,
queued: this.options.queued,
verbose: this.options.verbose,
width: this.options.width,
height: this.options.height,
passStatus: this.options.passStatus,
url: this.options.url,
method: this.options.method,
data: this.options.data,
mergeData: this.options.mergeData,
fieldName: this.options.fieldName,
fileSizeMin: this.options.fileSizeMin,
fileSizeMax: this.options.fileSizeMax,
allowDuplicates: this.options.allowDuplicates,
timeLimit: this.options.timeLimit,
policyFile: this.options.policyFile,
buttonImage: this.options.buttonImage
});
this.loaded = true;
},
targetRelay: function(name) {
if (this.target) this.target.fireEvent(name);
},
reposition: function(coords) {
// update coordinates, manual or automatically
coords = coords || (this.target && this.target.offsetHeight) ? this.target.getCoordinates(this.box.getOffsetParent()) : {top: window.getScrollTop(), left: 0, width: 40, height: 40};
this.box.setStyles(coords);
this.fireEvent('reposition', [coords, this.box, this.target]);
},
setOptions: function(options) {
// if (typeof console !== 'undefined' && console.log) console.log('Swiff.Uploader: BASE::setOptions');
if (options) {
if (options.url) options.url = Swiff.Uploader.qualifyPath(options.url);
if (options.buttonImage) options.buttonImage = Swiff.Uploader.qualifyPath(options.buttonImage);
this.parent(options);
if (this.loaded) {
this.remote('xSetOptions', options);
}
}
return this;
},
setEnabled: function(status) {
this.remote('xSetEnabled', status);
},
start: function() {
this.fireEvent('beforeStart');
this.remote('xStart');
},
stop: function() {
this.fireEvent('beforeStop');
this.remote('xStop');
},
remove: function() {
this.fireEvent('beforeRemove');
this.remote('xRemove');
},
fileStart: function(file) {
this.remote('xFileStart', file.id);
},
fileStop: function(file) {
this.remote('xFileStop', file.id);
},
fileRemove: function(file) {
this.remote('xFileRemove', file.id);
},
fileRequeue: function(file) {
this.remote('xFileRequeue', file.id);
},
appendCookieData: function() {
var append = this.options.appendCookieData;
// if (typeof console !== 'undefined' && console.log) console.log('appendCookieData: ' + (1 * append) + ' / ' + append);
if (!append) return;
var hash = {};
//if (typeof console !== 'undefined' && console.log) console.log('appendCookieData: ENTIRE cookie: "' + document.cookie + '"');
document.cookie.split(/;\s*/).each(function(cookie) {
cookie = cookie.split('=');
//if (typeof console !== 'undefined' && console.log) console.log('appendCookieData: cookie: "' + cookie[0] + '"(' + cookie.length + ') = "' + (cookie.length > 1 ? cookie[1] : '???') + '"');
if (cookie.length == 2) {
//hash['\"' + decodeURIComponent(cookie[0]) + '\"'] = decodeURIComponent(cookie[1]); // allow session IDs such as the ASP.NET ones, which come with a dot, etc.
hash[decodeURIComponent(cookie[0])] = decodeURIComponent(cookie[1]);
}
});
var data = this.options.data || {};
if (typeOf(append) == 'string') {
data[append] = hash;
} else {
Object.append(data, hash);
}
this.setOptions({data: data});
},
processFiles: function(successraw, failraw, queue) {
var cls = this.options.fileClass || Swiff.Uploader.File;
var fail = [], success = [];
if (successraw) {
successraw.each(function(data) {
var ret = new cls(this, data);
if (!ret.validate()) {
ret.remove.delay(10, ret);
fail.push(ret);
} else {
this.size += data.size;
this.fileList.push(ret);
success.push(ret);
ret.render();
}
}, this);
this.fireEvent('selectSuccess', [success], 10);
}
if (failraw || fail.length) {
fail.append((failraw) ? failraw.map(function(data) {
return new cls(this, data);
}, this) : []).each(function(file) {
file.invalidate().render();
});
this.fireEvent('selectFail', [fail], 10);
}
this.update(queue);
if (this.options.instantStart && success.length) this.start();
}
});
Object.append(Swiff.Uploader, {
STATUS_QUEUED: 0,
STATUS_RUNNING: 1,
STATUS_ERROR: 2,
STATUS_COMPLETE: 3,
STATUS_STOPPED: 4,
log: function() {
if (window.console && console.info) console.info.apply(console, arguments);
},
unitLabels: {
b: [{min: 1, unit: 'B'}, {min: 1024, unit: 'kB'}, {min: 1048576, unit: 'MB'}, {min: 1073741824, unit: 'GB'}],
s: [{min: 1, unit: 's'}, {min: 60, unit: 'm'}, {min: 3600, unit: 'h'}, {min: 86400, unit: 'd'}]
},
formatUnit: function(base, type, join) {
var labels = Swiff.Uploader.unitLabels[(type == 'bps') ? 'b' : type];
var append = (type == 'bps') ? '/s' : '';
var i, l = labels.length, value;
if (base < 1) return '0 ' + labels[0].unit + append;
if (type == 's') {
var units = [];
for (i = l - 1; i >= 0; i--) {
value = Math.floor(base / labels[i].min);
if (value) {
units.push(value + ' ' + labels[i].unit);
base -= value * labels[i].min;
if (!base) break;
}
}
return (join === false) ? units : units.join(join || ', ');
}
for (i = l - 1; i >= 0; i--) {
value = labels[i].min;
if (base >= value) break;
}
return (base / value).toFixed(1) + ' ' + labels[i].unit + append;
}
});
Swiff.Uploader.qualifyPath = (function() {
var anchor;
return function(path) {
(anchor || (anchor = new Element('a'))).href = path;
return anchor.href;
};
})();
Swiff.Uploader.File = new Class({
Implements: Events,
initialize: function(base, data) {
this.base = base;
this.update(data);
},
update: function(data) {
return Object.append(this, data);
},
validate: function() {
var options = this.base.options;
if (options.fileListMax && this.base.fileList.length >= options.fileListMax) {
this.validationError = 'fileListMax';
return false;
}
if (options.fileListSizeMax && (this.base.size + this.size) > options.fileListSizeMax) {
this.validationError = 'fileListSizeMax';
return false;
}
return true;
},
invalidate: function() {
this.invalid = true;
this.base.fireEvent('fileInvalid', this, 10);
return this.fireEvent('invalid', this, 10);
},
render: function() {
return this;
},
setOptions: function(options) {
//if (typeof console !== 'undefined' && console.log) console.log('Swiff.Uploader: File::setOptions');
if (options) {
if (options.url) {
options.url = Swiff.Uploader.qualifyPath(options.url);
}
this.base.remote('xFileSetOptions', this.id, options);
this.options = Object.merge(this.base.options, options);
}
return this;
},
start: function() {
this.base.fileStart(this);
return this;
},
stop: function() {
this.base.fileStop(this);
return this;
},
remove: function() {
this.base.fileRemove(this);
return this;
},
requeue: function() {
this.base.fileRequeue(this);
}
});