clipbucket/upload/modules/uploader/plupload/plupload.js
2012-08-16 20:10:07 +00:00

1771 lines
No EOL
43 KiB
JavaScript

/**
* plupload.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
// JSLint defined globals
/*global window:false, escape:false */
/*!@@version@@*/
(function() {
var count = 0, runtimes = [], i18n = {}, mimes = {},
xmlEncodeChars = {'<' : 'lt', '>' : 'gt', '&' : 'amp', '"' : 'quot', '\'' : '#39'},
xmlEncodeRegExp = /[<>&\"\']/g, undef, delay = window.setTimeout,
// A place to store references to event handlers
eventhash = {},
uid;
// IE W3C like event funcs
function preventDefault() {
this.returnValue = false;
}
function stopPropagation() {
this.cancelBubble = true;
}
// Parses the default mime types string into a mimes lookup map
(function(mime_data) {
var items = mime_data.split(/,/), i, y, ext;
for (i = 0; i < items.length; i += 2) {
ext = items[i + 1].split(/ /);
for (y = 0; y < ext.length; y++) {
mimes[ext[y]] = items[i];
}
}
})(
"application/msword,doc dot," +
"application/pdf,pdf," +
"application/pgp-signature,pgp," +
"application/postscript,ps ai eps," +
"application/rtf,rtf," +
"application/vnd.ms-excel,xls xlb," +
"application/vnd.ms-powerpoint,ppt pps pot," +
"application/zip,zip," +
"application/x-shockwave-flash,swf swfl," +
"application/vnd.openxmlformats-officedocument.wordprocessingml.document,docx," +
"application/vnd.openxmlformats-officedocument.wordprocessingml.template,dotx," +
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,xlsx," +
"application/vnd.openxmlformats-officedocument.presentationml.presentation,pptx," +
"application/vnd.openxmlformats-officedocument.presentationml.template,potx," +
"application/vnd.openxmlformats-officedocument.presentationml.slideshow,ppsx," +
"application/x-javascript,js," +
"application/json,json," +
"audio/mpeg,mpga mpega mp2 mp3," +
"audio/x-wav,wav," +
"audio/mp4,m4a," +
"image/bmp,bmp," +
"image/gif,gif," +
"image/jpeg,jpeg jpg jpe," +
"image/photoshop,psd," +
"image/png,png," +
"image/svg+xml,svg svgz," +
"image/tiff,tiff tif," +
"text/plain,asc txt text diff log," +
"text/html,htm html xhtml," +
"text/css,css," +
"text/csv,csv," +
"text/rtf,rtf," +
"video/mpeg,mpeg mpg mpe," +
"video/quicktime,qt mov," +
"video/mp4,mp4," +
"video/x-m4v,m4v," +
"video/x-flv,flv," +
"video/x-ms-wmv,wmv," +
"video/avi,avi," +
"video/webm,webm," +
"video/vnd.rn-realvideo,rv," +
"application/vnd.oasis.opendocument.formula-template,otf," +
"application/octet-stream,exe"
);
/**
* Plupload class with some global constants and functions.
*
* @example
* // Encode entities
* console.log(plupload.xmlEncode("My string &lt;&gt;"));
*
* // Generate unique id
* console.log(plupload.guid());
*
* @static
* @class plupload
*/
var plupload = {
/**
* Plupload version will be replaced on build.
*/
VERSION : '@@version@@',
/**
* Inital state of the queue and also the state ones it's finished all it's uploads.
*
* @property STOPPED
* @final
*/
STOPPED : 1,
/**
* Upload process is running
*
* @property STARTED
* @final
*/
STARTED : 2,
/**
* File is queued for upload
*
* @property QUEUED
* @final
*/
QUEUED : 1,
/**
* File is being uploaded
*
* @property UPLOADING
* @final
*/
UPLOADING : 2,
/**
* File has failed to be uploaded
*
* @property FAILED
* @final
*/
FAILED : 4,
/**
* File has been uploaded successfully
*
* @property DONE
* @final
*/
DONE : 5,
// Error constants used by the Error event
/**
* Generic error for example if an exception is thrown inside Silverlight.
*
* @property GENERIC_ERROR
* @final
*/
GENERIC_ERROR : -100,
/**
* HTTP transport error. For example if the server produces a HTTP status other than 200.
*
* @property HTTP_ERROR
* @final
*/
HTTP_ERROR : -200,
/**
* Generic I/O error. For exampe if it wasn't possible to open the file stream on local machine.
*
* @property IO_ERROR
* @final
*/
IO_ERROR : -300,
/**
* Generic I/O error. For exampe if it wasn't possible to open the file stream on local machine.
*
* @property SECURITY_ERROR
* @final
*/
SECURITY_ERROR : -400,
/**
* Initialization error. Will be triggered if no runtime was initialized.
*
* @property INIT_ERROR
* @final
*/
INIT_ERROR : -500,
/**
* File size error. If the user selects a file that is too large it will be blocked and an error of this type will be triggered.
*
* @property FILE_SIZE_ERROR
* @final
*/
FILE_SIZE_ERROR : -600,
/**
* File extension error. If the user selects a file that isn't valid according to the filters setting.
*
* @property FILE_EXTENSION_ERROR
* @final
*/
FILE_EXTENSION_ERROR : -601,
/**
* Runtime will try to detect if image is proper one. Otherwise will throw this error.
*
* @property IMAGE_FORMAT_ERROR
* @final
*/
IMAGE_FORMAT_ERROR : -700,
/**
* While working on the image runtime will try to detect if the operation may potentially run out of memeory and will throw this error.
*
* @property IMAGE_MEMORY_ERROR
* @final
*/
IMAGE_MEMORY_ERROR : -701,
/**
* Each runtime has an upper limit on a dimension of the image it can handle. If bigger, will throw this error.
*
* @property IMAGE_DIMENSIONS_ERROR
* @final
*/
IMAGE_DIMENSIONS_ERROR : -702,
/**
* Mime type lookup table.
*
* @property mimeTypes
* @type Object
* @final
*/
mimeTypes : mimes,
/**
* In some cases sniffing is the only way around :(
*/
ua: (function() {
var nav = navigator, userAgent = nav.userAgent, vendor = nav.vendor, webkit, opera, safari;
webkit = /WebKit/.test(userAgent);
safari = webkit && vendor.indexOf('Apple') !== -1;
opera = window.opera && window.opera.buildNumber;
return {
windows: navigator.platform.indexOf('Win') !== -1,
ie: !webkit && !opera && (/MSIE/gi).test(userAgent) && (/Explorer/gi).test(nav.appName),
webkit: webkit,
gecko: !webkit && /Gecko/.test(userAgent),
safari: safari,
opera: !!opera
};
}()),
/**
* Gets the true type of the built-in object (better version of typeof).
* @credits Angus Croll (http://javascriptweblog.wordpress.com/)
*
* @param {Object} o Object to check.
* @return {String} Object [[Class]]
*/
typeOf: function(o) {
return ({}).toString.call(o).match(/\s([a-z|A-Z]+)/)[1].toLowerCase();
},
/**
* Extends the specified object with another object.
*
* @method extend
* @param {Object} target Object to extend.
* @param {Object..} obj Multiple objects to extend with.
* @return {Object} Same as target, the extended object.
*/
extend : function(target) {
plupload.each(arguments, function(arg, i) {
if (i > 0) {
plupload.each(arg, function(value, key) {
target[key] = value;
});
}
});
return target;
},
/**
* Cleans the specified name from national characters (diacritics). The result will be a name with only a-z, 0-9 and _.
*
* @method cleanName
* @param {String} s String to clean up.
* @return {String} Cleaned string.
*/
cleanName : function(name) {
var i, lookup;
// Replace diacritics
lookup = [
/[\300-\306]/g, 'A', /[\340-\346]/g, 'a',
/\307/g, 'C', /\347/g, 'c',
/[\310-\313]/g, 'E', /[\350-\353]/g, 'e',
/[\314-\317]/g, 'I', /[\354-\357]/g, 'i',
/\321/g, 'N', /\361/g, 'n',
/[\322-\330]/g, 'O', /[\362-\370]/g, 'o',
/[\331-\334]/g, 'U', /[\371-\374]/g, 'u'
];
for (i = 0; i < lookup.length; i += 2) {
name = name.replace(lookup[i], lookup[i + 1]);
}
// Replace whitespace
name = name.replace(/\s+/g, '_');
// Remove anything else
name = name.replace(/[^a-z0-9_\-\.]+/gi, '');
return name;
},
/**
* Adds a specific upload runtime like for example flash or gears.
*
* @method addRuntime
* @param {String} name Runtime name for example flash.
* @param {Object} obj Object containing init/destroy method.
*/
addRuntime : function(name, runtime) {
runtime.name = name;
runtimes[name] = runtime;
runtimes.push(runtime);
return runtime;
},
/**
* Generates an unique ID. This is 99.99% unique since it takes the current time and 5 random numbers.
* The only way a user would be able to get the same ID is if the two persons at the same exact milisecond manages
* to get 5 the same random numbers between 0-65535 it also uses a counter so each call will be guaranteed to be page unique.
* It's more probable for the earth to be hit with an ansteriod. You can also if you want to be 100% sure set the plupload.guidPrefix property
* to an user unique key.
*
* @method guid
* @return {String} Virtually unique id.
*/
guid : function() {
var guid = new Date().getTime().toString(32), i;
for (i = 0; i < 5; i++) {
guid += Math.floor(Math.random() * 65535).toString(32);
}
return (plupload.guidPrefix || 'p') + guid + (count++).toString(32);
},
/**
* Builds a full url out of a base URL and an object with items to append as query string items.
*
* @param {String} url Base URL to append query string items to.
* @param {Object} items Name/value object to serialize as a querystring.
* @return {String} String with url + serialized query string items.
*/
buildUrl : function(url, items) {
var query = '';
plupload.each(items, function(value, name) {
query += (query ? '&' : '') + encodeURIComponent(name) + '=' + encodeURIComponent(value);
});
if (query) {
url += (url.indexOf('?') > 0 ? '&' : '?') + query;
}
return url;
},
/**
* Executes the callback function for each item in array/object. If you return false in the
* callback it will break the loop.
*
* @param {Object} obj Object to iterate.
* @param {function} callback Callback function to execute for each item.
*/
each : function(obj, callback) {
var length, key, i;
if (obj) {
length = obj.length;
if (length === undef) {
// Loop object items
for (key in obj) {
if (obj.hasOwnProperty(key)) {
if (callback(obj[key], key) === false) {
return;
}
}
}
} else {
// Loop array items
for (i = 0; i < length; i++) {
if (callback(obj[i], i) === false) {
return;
}
}
}
}
},
/**
* Formats the specified number as a size string for example 1024 becomes 1 KB.
*
* @method formatSize
* @param {Number} size Size to format as string.
* @return {String} Formatted size string.
*/
formatSize : function(size) {
if (size === undef || /\D/.test(size)) {
return plupload.translate('N/A');
}
// GB
if (size > 1073741824) {
return Math.round(size / 1073741824, 1) + " GB";
}
// MB
if (size > 1048576) {
return Math.round(size / 1048576, 1) + " MB";
}
// KB
if (size > 1024) {
return Math.round(size / 1024, 1) + " KB";
}
return size + " b";
},
/**
* Returns the absolute x, y position of an Element. The position will be returned in a object with x, y fields.
*
* @method getPos
* @param {Element} node HTML element or element id to get x, y position from.
* @param {Element} root Optional root element to stop calculations at.
* @return {object} Absolute position of the specified element object with x, y fields.
*/
getPos : function(node, root) {
var x = 0, y = 0, parent, doc = document, nodeRect, rootRect;
node = node;
root = root || doc.body;
// Returns the x, y cordinate for an element on IE 6 and IE 7
function getIEPos(node) {
var bodyElm, rect, x = 0, y = 0;
if (node) {
rect = node.getBoundingClientRect();
bodyElm = doc.compatMode === "CSS1Compat" ? doc.documentElement : doc.body;
x = rect.left + bodyElm.scrollLeft;
y = rect.top + bodyElm.scrollTop;
}
return {
x : x,
y : y
};
}
// Use getBoundingClientRect on IE 6 and IE 7 but not on IE 8 in standards mode
if (node && node.getBoundingClientRect && ((navigator.userAgent.indexOf('MSIE') > 0) && (doc.documentMode < 8))) {
nodeRect = getIEPos(node);
rootRect = getIEPos(root);
return {
x : nodeRect.x - rootRect.x,
y : nodeRect.y - rootRect.y
};
}
parent = node;
while (parent && parent != root && parent.nodeType) {
x += parent.offsetLeft || 0;
y += parent.offsetTop || 0;
parent = parent.offsetParent;
}
parent = node.parentNode;
while (parent && parent != root && parent.nodeType) {
x -= parent.scrollLeft || 0;
y -= parent.scrollTop || 0;
parent = parent.parentNode;
}
return {
x : x,
y : y
};
},
/**
* Returns the size of the specified node in pixels.
*
* @param {Node} node Node to get the size of.
* @return {Object} Object with a w and h property.
*/
getSize : function(node) {
return {
w : node.offsetWidth || node.clientWidth,
h : node.offsetHeight || node.clientHeight
};
},
/**
* Parses the specified size string into a byte value. For example 10kb becomes 10240.
*
* @method parseSize
* @param {String/Number} size String to parse or number to just pass through.
* @return {Number} Size in bytes.
*/
parseSize : function(size) {
var mul;
if (typeof(size) == 'string') {
size = /^([0-9]+)([mgk]?)$/.exec(size.toLowerCase().replace(/[^0-9mkg]/g, ''));
mul = size[2];
size = +size[1];
if (mul == 'g') {
size *= 1073741824;
}
if (mul == 'm') {
size *= 1048576;
}
if (mul == 'k') {
size *= 1024;
}
}
return size;
},
/**
* Encodes the specified string.
*
* @method xmlEncode
* @param {String} s String to encode.
* @return {String} Encoded string.
*/
xmlEncode : function(str) {
return str ? ('' + str).replace(xmlEncodeRegExp, function(chr) {
return xmlEncodeChars[chr] ? '&' + xmlEncodeChars[chr] + ';' : chr;
}) : str;
},
/**
* Forces anything into an array.
*
* @method toArray
* @param {Object} obj Object with length field.
* @return {Array} Array object containing all items.
*/
toArray : function(obj) {
var i, arr = [];
for (i = 0; i < obj.length; i++) {
arr[i] = obj[i];
}
return arr;
},
/**
* Find an element in array and return it's index if present, otherwise return -1.
*
* @method inArray
* @param {mixed} needle Element to find
* @param {Array} array
* @return {Int} Index of the element, or -1 if not found
*/
inArray : function(needle, array) {
if (array) {
if (Array.prototype.indexOf) {
return Array.prototype.indexOf.call(array, needle);
}
for (var i = 0, length = array.length; i < length; i++) {
if (array[i] === needle) {
return i;
}
}
}
return -1;
},
/**
* Extends the language pack object with new items.
*
* @param {Object} pack Language pack items to add.
* @return {Object} Extended language pack object.
*/
addI18n : function(pack) {
return plupload.extend(i18n, pack);
},
/**
* Translates the specified string by checking for the english string in the language pack lookup.
*
* @param {String} str String to look for.
* @return {String} Translated string or the input string if it wasn't found.
*/
translate : function(str) {
return i18n[str] || str;
},
/**
* Checks if object is empty.
*
* @param {Object} obj Object to check.
* @return {Boolean}
*/
isEmptyObj : function(obj) {
if (obj === undef) return true;
for (var prop in obj) {
return false;
}
return true;
},
/**
* Checks if specified DOM element has specified class.
*
* @param {Object} obj DOM element like object to add handler to.
* @param {String} name Class name
*/
hasClass : function(obj, name) {
var regExp;
if (obj.className == '') {
return false;
}
regExp = new RegExp("(^|\\s+)"+name+"(\\s+|$)");
return regExp.test(obj.className);
},
/**
* Adds specified className to specified DOM element.
*
* @param {Object} obj DOM element like object to add handler to.
* @param {String} name Class name
*/
addClass : function(obj, name) {
if (!plupload.hasClass(obj, name)) {
obj.className = obj.className == '' ? name : obj.className.replace(/\s+$/, '')+' '+name;
}
},
/**
* Removes specified className from specified DOM element.
*
* @param {Object} obj DOM element like object to add handler to.
* @param {String} name Class name
*/
removeClass : function(obj, name) {
var regExp = new RegExp("(^|\\s+)"+name+"(\\s+|$)");
obj.className = obj.className.replace(regExp, function($0, $1, $2) {
return $1 === ' ' && $2 === ' ' ? ' ' : '';
});
},
/**
* Returns a given computed style of a DOM element.
*
* @param {Object} obj DOM element like object.
* @param {String} name Style you want to get from the DOM element
*/
getStyle : function(obj, name) {
if (obj.currentStyle) {
return obj.currentStyle[name];
} else if (window.getComputedStyle) {
return window.getComputedStyle(obj, null)[name];
}
},
/**
* Adds an event handler to the specified object and store reference to the handler
* in objects internal Plupload registry (@see removeEvent).
*
* @param {Object} obj DOM element like object to add handler to.
* @param {String} name Name to add event listener to.
* @param {Function} callback Function to call when event occurs.
* @param {String} (optional) key that might be used to add specifity to the event record.
*/
addEvent : function(obj, name, callback) {
var func, events, types, key;
// if passed in, event will be locked with this key - one would need to provide it to removeEvent
key = arguments[3];
name = name.toLowerCase();
// Initialize unique identifier if needed
if (uid === undef) {
uid = 'Plupload_' + plupload.guid();
}
// Add event listener
if (obj.addEventListener) {
func = callback;
obj.addEventListener(name, func, false);
} else if (obj.attachEvent) {
func = function() {
var evt = window.event;
if (!evt.target) {
evt.target = evt.srcElement;
}
evt.preventDefault = preventDefault;
evt.stopPropagation = stopPropagation;
callback(evt);
};
obj.attachEvent('on' + name, func);
}
// Log event handler to objects internal Plupload registry
if (obj[uid] === undef) {
obj[uid] = plupload.guid();
}
if (!eventhash.hasOwnProperty(obj[uid])) {
eventhash[obj[uid]] = {};
}
events = eventhash[obj[uid]];
if (!events.hasOwnProperty(name)) {
events[name] = [];
}
events[name].push({
func: func,
orig: callback, // store original callback for IE
key: key
});
},
/**
* Remove event handler from the specified object. If third argument (callback)
* is not specified remove all events with the specified name.
*
* @param {Object} obj DOM element to remove event listener(s) from.
* @param {String} name Name of event listener to remove.
* @param {Function|String} (optional) might be a callback or unique key to match.
*/
removeEvent: function(obj, name) {
var type, callback, key;
// match the handler either by callback or by key
if (typeof(arguments[2]) == "function") {
callback = arguments[2];
} else {
key = arguments[2];
}
name = name.toLowerCase();
if (obj[uid] && eventhash[obj[uid]] && eventhash[obj[uid]][name]) {
type = eventhash[obj[uid]][name];
} else {
return;
}
for (var i=type.length-1; i>=0; i--) {
// undefined or not, key should match
if (type[i].key === key || type[i].orig === callback) {
if (obj.removeEventListener) {
obj.removeEventListener(name, type[i].func, false);
} else if (obj.detachEvent) {
obj.detachEvent('on'+name, type[i].func);
}
type[i].orig = null;
type[i].func = null;
type.splice(i, 1);
// If callback was passed we are done here, otherwise proceed
if (callback !== undef) {
break;
}
}
}
// If event array got empty, remove it
if (!type.length) {
delete eventhash[obj[uid]][name];
}
// If Plupload registry has become empty, remove it
if (plupload.isEmptyObj(eventhash[obj[uid]])) {
delete eventhash[obj[uid]];
// IE doesn't let you remove DOM object property with - delete
try {
delete obj[uid];
} catch(e) {
obj[uid] = undef;
}
}
},
/**
* Remove all kind of events from the specified object
*
* @param {Object} obj DOM element to remove event listeners from.
* @param {String} (optional) unique key to match, when removing events.
*/
removeAllEvents: function(obj) {
var key = arguments[1];
if (obj[uid] === undef || !obj[uid]) {
return;
}
plupload.each(eventhash[obj[uid]], function(events, name) {
plupload.removeEvent(obj, name, key);
});
}
};
/**
* Uploader class, an instance of this class will be created for each upload field.
*
* @example
* var uploader = new plupload.Uploader({
* runtimes : 'gears,html5,flash',
* browse_button : 'button_id'
* });
*
* uploader.bind('Init', function(up) {
* alert('Supports drag/drop: ' + (!!up.features.dragdrop));
* });
*
* uploader.bind('FilesAdded', function(up, files) {
* alert('Selected files: ' + files.length);
* });
*
* uploader.bind('QueueChanged', function(up) {
* alert('Queued files: ' + uploader.files.length);
* });
*
* uploader.init();
*
* @class plupload.Uploader
*/
/**
* Constructs a new uploader instance.
*
* @constructor
* @method Uploader
* @param {Object} settings Initialization settings, to be used by the uploader instance and runtimes.
*/
plupload.Uploader = function(settings) {
var events = {}, total, files = [], startTime, disabled = false;
// Inital total state
total = new plupload.QueueProgress();
// Default settings
settings = plupload.extend({
chunk_size : 0,
multipart : true,
multi_selection : true,
file_data_name : 'file',
filters : []
}, settings);
// Private methods
function uploadNext() {
var file, count = 0, i;
if (this.state == plupload.STARTED) {
// Find first QUEUED file
for (i = 0; i < files.length; i++) {
if (!file && files[i].status == plupload.QUEUED) {
file = files[i];
file.status = plupload.UPLOADING;
if (this.trigger("BeforeUpload", file)) {
this.trigger("UploadFile", file);
}
} else {
count++;
}
}
// All files are DONE or FAILED
if (count == files.length) {
this.stop();
this.trigger("UploadComplete", files);
}
}
}
function calc() {
var i, file;
// Reset stats
total.reset();
// Check status, size, loaded etc on all files
for (i = 0; i < files.length; i++) {
file = files[i];
if (file.size !== undef) {
total.size += file.size;
total.loaded += file.loaded;
} else {
total.size = undef;
}
if (file.status == plupload.DONE) {
total.uploaded++;
} else if (file.status == plupload.FAILED) {
total.failed++;
} else {
total.queued++;
}
}
// If we couldn't calculate a total file size then use the number of files to calc percent
if (total.size === undef) {
total.percent = files.length > 0 ? Math.ceil(total.uploaded / files.length * 100) : 0;
} else {
total.bytesPerSec = Math.ceil(total.loaded / ((+new Date() - startTime || 1) / 1000.0));
total.percent = total.size > 0 ? Math.ceil(total.loaded / total.size * 100) : 0;
}
}
// Add public methods
plupload.extend(this, {
/**
* Current state of the total uploading progress. This one can either be plupload.STARTED or plupload.STOPPED.
* These states are controlled by the stop/start methods. The default value is STOPPED.
*
* @property state
* @type Number
*/
state : plupload.STOPPED,
/**
* Current runtime name.
*
* @property runtime
* @type String
*/
runtime: '',
/**
* Map of features that are available for the uploader runtime. Features will be filled
* before the init event is called, these features can then be used to alter the UI for the end user.
* Some of the current features that might be in this map is: dragdrop, chunks, jpgresize, pngresize.
*
* @property features
* @type Object
*/
features : {},
/**
* Current upload queue, an array of File instances.
*
* @property files
* @type Array
* @see plupload.File
*/
files : files,
/**
* Object with name/value settings.
*
* @property settings
* @type Object
*/
settings : settings,
/**
* Total progess information. How many files has been uploaded, total percent etc.
*
* @property total
* @type plupload.QueueProgress
*/
total : total,
/**
* Unique id for the Uploader instance.
*
* @property id
* @type String
*/
id : plupload.guid(),
/**
* Initializes the Uploader instance and adds internal event listeners.
*
* @method init
*/
init : function() {
var self = this, i, runtimeList, a, runTimeIndex = 0, items;
if (typeof(settings.preinit) == "function") {
settings.preinit(self);
} else {
plupload.each(settings.preinit, function(func, name) {
self.bind(name, func);
});
}
settings.page_url = settings.page_url || document.location.pathname.replace(/\/[^\/]+$/g, '/');
// If url is relative force it absolute to the current page
if (!/^(\w+:\/\/|\/)/.test(settings.url)) {
settings.url = settings.page_url + settings.url;
}
// Convert settings
settings.chunk_size = plupload.parseSize(settings.chunk_size);
settings.max_file_size = plupload.parseSize(settings.max_file_size);
// Add files to queue
self.bind('FilesAdded', function(up, selected_files) {
var i, file, count = 0, extensionsRegExp, filters = settings.filters;
// Convert extensions to regexp
if (filters && filters.length) {
extensionsRegExp = [];
plupload.each(filters, function(filter) {
plupload.each(filter.extensions.split(/,/), function(ext) {
if (/^\s*\*\s*$/.test(ext)) {
extensionsRegExp.push('\\.*');
} else {
extensionsRegExp.push('\\.' + ext.replace(new RegExp('[' + ('/^$.*+?|()[]{}\\'.replace(/./g, '\\$&')) + ']', 'g'), '\\$&'));
}
});
});
extensionsRegExp = new RegExp(extensionsRegExp.join('|') + '$', 'i');
}
for (i = 0; i < selected_files.length; i++) {
file = selected_files[i];
file.loaded = 0;
file.percent = 0;
file.status = plupload.QUEUED;
// Invalid file extension
if (extensionsRegExp && !extensionsRegExp.test(file.name)) {
up.trigger('Error', {
code : plupload.FILE_EXTENSION_ERROR,
message : plupload.translate('File extension error.'),
file : file
});
continue;
}
// Invalid file size
if (file.size !== undef && file.size > settings.max_file_size) {
up.trigger('Error', {
code : plupload.FILE_SIZE_ERROR,
message : plupload.translate('File size error.'),
file : file
});
continue;
}
// Add valid file to list
files.push(file);
count++;
}
// Only trigger QueueChanged event if any files where added
if (count) {
delay(function() {
self.trigger("QueueChanged");
self.refresh();
}, 1);
} else {
return false; // Stop the FilesAdded event from immediate propagation
}
});
// Generate unique target filenames
if (settings.unique_names) {
self.bind("UploadFile", function(up, file) {
var matches = file.name.match(/\.([^.]+)$/), ext = "tmp";
if (matches) {
ext = matches[1];
}
file.target_name = file.id + '.' + ext;
});
}
self.bind('UploadProgress', function(up, file) {
file.percent = file.size > 0 ? Math.ceil(file.loaded / file.size * 100) : 100;
calc();
});
self.bind('StateChanged', function(up) {
if (up.state == plupload.STARTED) {
// Get start time to calculate bps
startTime = (+new Date());
} else if (up.state == plupload.STOPPED) {
// Reset currently uploading files
for (i = up.files.length - 1; i >= 0; i--) {
if (up.files[i].status == plupload.UPLOADING) {
up.files[i].status = plupload.QUEUED;
calc();
}
}
}
});
self.bind('QueueChanged', calc);
self.bind("Error", function(up, err) {
// Set failed status if an error occured on a file
if (err.file) {
err.file.status = plupload.FAILED;
calc();
// Upload next file but detach it from the error event
// since other custom listeners might want to stop the queue
if (up.state == plupload.STARTED) {
delay(function() {
uploadNext.call(self);
}, 1);
}
}
});
self.bind("FileUploaded", function(up, file) {
file.status = plupload.DONE;
file.loaded = file.size;
up.trigger('UploadProgress', file);
// Upload next file but detach it from the error event
// since other custom listeners might want to stop the queue
delay(function() {
uploadNext.call(self);
}, 1);
});
// Setup runtimeList
if (settings.runtimes) {
runtimeList = [];
items = settings.runtimes.split(/\s?,\s?/);
for (i = 0; i < items.length; i++) {
if (runtimes[items[i]]) {
runtimeList.push(runtimes[items[i]]);
}
}
} else {
runtimeList = runtimes;
}
// Call init on each runtime in sequence
function callNextInit() {
var runtime = runtimeList[runTimeIndex++], features, requiredFeatures, i;
if (runtime) {
features = runtime.getFeatures();
// Check if runtime supports required features
requiredFeatures = self.settings.required_features;
if (requiredFeatures) {
requiredFeatures = requiredFeatures.split(',');
for (i = 0; i < requiredFeatures.length; i++) {
// Specified feature doesn't exist
if (!features[requiredFeatures[i]]) {
callNextInit();
return;
}
}
}
// Try initializing the runtime
runtime.init(self, function(res) {
if (res && res.success) {
// Successful initialization
self.features = features;
self.runtime = runtime.name;
self.trigger('Init', {runtime : runtime.name});
self.trigger('PostInit');
self.refresh();
} else {
callNextInit();
}
});
} else {
// Trigger an init error if we run out of runtimes
self.trigger('Error', {
code : plupload.INIT_ERROR,
message : plupload.translate('Init error.')
});
}
}
callNextInit();
if (typeof(settings.init) == "function") {
settings.init(self);
} else {
plupload.each(settings.init, function(func, name) {
self.bind(name, func);
});
}
},
/**
* Refreshes the upload instance by dispatching out a refresh event to all runtimes.
* This would for example reposition flash/silverlight shims on the page.
*
* @method refresh
*/
refresh : function() {
this.trigger("Refresh");
},
/**
* Starts uploading the queued files.
*
* @method start
*/
start : function() {
if (files.length && this.state != plupload.STARTED) {
this.state = plupload.STARTED;
this.trigger("StateChanged");
uploadNext.call(this);
}
},
/**
* Stops the upload of the queued files.
*
* @method stop
*/
stop : function() {
if (this.state != plupload.STOPPED) {
this.state = plupload.STOPPED;
this.trigger("CancelUpload");
this.trigger("StateChanged");
}
},
/**
* Disables/enables browse button on request.
*
* @method disableBrowse
* @param {Boolean} disable Whether to disable or enable (default: true)
*/
disableBrowse : function() {
disabled = arguments[0] !== undef ? arguments[0] : true;
this.trigger("DisableBrowse", disabled);
},
/**
* Returns the specified file object by id.
*
* @method getFile
* @param {String} id File id to look for.
* @return {plupload.File} File object or undefined if it wasn't found;
*/
getFile : function(id) {
var i;
for (i = files.length - 1; i >= 0; i--) {
if (files[i].id === id) {
return files[i];
}
}
},
/**
* Removes a specific file.
*
* @method removeFile
* @param {plupload.File} file File to remove from queue.
*/
removeFile : function(file) {
var i;
for (i = files.length - 1; i >= 0; i--) {
if (files[i].id === file.id) {
return this.splice(i, 1)[0];
}
}
},
/**
* Removes part of the queue and returns the files removed. This will also trigger the FilesRemoved and QueueChanged events.
*
* @method splice
* @param {Number} start (Optional) Start index to remove from.
* @param {Number} length (Optional) Lengh of items to remove.
* @return {Array} Array of files that was removed.
*/
splice : function(start, length) {
var removed;
// Splice and trigger events
removed = files.splice(start === undef ? 0 : start, length === undef ? files.length : length);
this.trigger("FilesRemoved", removed);
this.trigger("QueueChanged");
return removed;
},
/**
* Dispatches the specified event name and it's arguments to all listeners.
*
*
* @method trigger
* @param {String} name Event name to fire.
* @param {Object..} Multiple arguments to pass along to the listener functions.
*/
trigger : function(name) {
var list = events[name.toLowerCase()], i, args;
// console.log(name, arguments);
if (list) {
// Replace name with sender in args
args = Array.prototype.slice.call(arguments);
args[0] = this;
// Dispatch event to all listeners
for (i = 0; i < list.length; i++) {
// Fire event, break chain if false is returned
if (list[i].func.apply(list[i].scope, args) === false) {
return false;
}
}
}
return true;
},
/**
* Check whether uploader has any listeners to the specified event.
*
* @method hasEventListener
* @param {String} name Event name to check for.
*/
hasEventListener : function(name) {
return !!events[name.toLowerCase()];
},
/**
* Adds an event listener by name.
*
* @method bind
* @param {String} name Event name to listen for.
* @param {function} func Function to call ones the event gets fired.
* @param {Object} scope Optional scope to execute the specified function in.
*/
bind : function(name, func, scope) {
var list;
name = name.toLowerCase();
list = events[name] || [];
list.push({func : func, scope : scope || this});
events[name] = list;
},
/**
* Removes the specified event listener.
*
* @method unbind
* @param {String} name Name of event to remove.
* @param {function} func Function to remove from listener.
*/
unbind : function(name) {
name = name.toLowerCase();
var list = events[name], i, func = arguments[1];
if (list) {
if (func !== undef) {
for (i = list.length - 1; i >= 0; i--) {
if (list[i].func === func) {
list.splice(i, 1);
break;
}
}
} else {
list = [];
}
// delete event list if it has become empty
if (!list.length) {
delete events[name];
}
}
},
/**
* Removes all event listeners.
*
* @method unbindAll
*/
unbindAll : function() {
var self = this;
plupload.each(events, function(list, name) {
self.unbind(name);
});
},
/**
* Destroys Plupload instance and cleans after itself.
*
* @method destroy
*/
destroy : function() {
this.stop();
this.trigger('Destroy');
// Clean-up after uploader itself
this.unbindAll();
}
/**
* Fires when the current RunTime has been initialized.
*
* @event Init
* @param {plupload.Uploader} uploader Uploader instance sending the event.
*/
/**
* Fires after the init event incase you need to perform actions there.
*
* @event PostInit
* @param {plupload.Uploader} uploader Uploader instance sending the event.
*/
/**
* Fires when the silverlight/flash or other shim needs to move.
*
* @event Refresh
* @param {plupload.Uploader} uploader Uploader instance sending the event.
*/
/**
* Fires when the overall state is being changed for the upload queue.
*
* @event StateChanged
* @param {plupload.Uploader} uploader Uploader instance sending the event.
*/
/**
* Fires when a file is to be uploaded by the runtime.
*
* @event UploadFile
* @param {plupload.Uploader} uploader Uploader instance sending the event.
* @param {plupload.File} file File to be uploaded.
*/
/**
* Fires when just before a file is uploaded. This event enables you to override settings
* on the uploader instance before the file is uploaded.
*
* @event BeforeUpload
* @param {plupload.Uploader} uploader Uploader instance sending the event.
* @param {plupload.File} file File to be uploaded.
*/
/**
* Fires when the file queue is changed. In other words when files are added/removed to the files array of the uploader instance.
*
* @event QueueChanged
* @param {plupload.Uploader} uploader Uploader instance sending the event.
*/
/**
* Fires while a file is being uploaded. Use this event to update the current file upload progress.
*
* @event UploadProgress
* @param {plupload.Uploader} uploader Uploader instance sending the event.
* @param {plupload.File} file File that is currently being uploaded.
*/
/**
* Fires while a file was removed from queue.
*
* @event FilesRemoved
* @param {plupload.Uploader} uploader Uploader instance sending the event.
* @param {Array} files Array of files that got removed.
*/
/**
* Fires while when the user selects files to upload.
*
* @event FilesAdded
* @param {plupload.Uploader} uploader Uploader instance sending the event.
* @param {Array} files Array of file objects that was added to queue/selected by the user.
*/
/**
* Fires when a file is successfully uploaded.
*
* @event FileUploaded
* @param {plupload.Uploader} uploader Uploader instance sending the event.
* @param {plupload.File} file File that was uploaded.
* @param {Object} response Object with response properties.
*/
/**
* Fires when file chunk is uploaded.
*
* @event ChunkUploaded
* @param {plupload.Uploader} uploader Uploader instance sending the event.
* @param {plupload.File} file File that the chunk was uploaded for.
* @param {Object} response Object with response properties.
*/
/**
* Fires when all files in a queue are uploaded.
*
* @event UploadComplete
* @param {plupload.Uploader} uploader Uploader instance sending the event.
* @param {Array} files Array of file objects that was added to queue/selected by the user.
*/
/**
* Fires when a error occurs.
*
* @event Error
* @param {plupload.Uploader} uploader Uploader instance sending the event.
* @param {Object} error Contains code, message and sometimes file and other details.
*/
/**
* Fires when destroy method is called.
*
* @event Destroy
* @param {plupload.Uploader} uploader Uploader instance sending the event.
*/
});
};
/**
* File instance.
*
* @class plupload.File
* @param {String} name Name of the file.
* @param {Number} size File size.
*/
/**
* Constructs a new file instance.
*
* @constructor
* @method File
* @param {String} id Unique file id.
* @param {String} name File name.
* @param {Number} size File size in bytes.
*/
plupload.File = function(id, name, size) {
var self = this; // Setup alias for self to reduce code size when it's compressed
/**
* File id this is a globally unique id for the specific file.
*
* @property id
* @type String
*/
self.id = id;
/**
* File name for example "myfile.gif".
*
* @property name
* @type String
*/
self.name = name;
/**
* File size in bytes.
*
* @property size
* @type Number
*/
self.size = size;
/**
* Number of bytes uploaded of the files total size.
*
* @property loaded
* @type Number
*/
self.loaded = 0;
/**
* Number of percentage uploaded of the file.
*
* @property percent
* @type Number
*/
self.percent = 0;
/**
* Status constant matching the plupload states QUEUED, UPLOADING, FAILED, DONE.
*
* @property status
* @type Number
* @see plupload
*/
self.status = 0;
};
/**
* Runtime class gets implemented by each upload runtime.
*
* @class plupload.Runtime
* @static
*/
plupload.Runtime = function() {
/**
* Returns a list of supported features for the runtime.
*
* @return {Object} Name/value object with supported features.
*/
this.getFeatures = function() {
};
/**
* Initializes the upload runtime. This method should add necessary items to the DOM and register events needed for operation.
*
* @method init
* @param {plupload.Uploader} uploader Uploader instance that needs to be initialized.
* @param {function} callback Callback function to execute when the runtime initializes or fails to initialize. If it succeeds an object with a parameter name success will be set to true.
*/
this.init = function(uploader, callback) {
};
};
/**
* Runtime class gets implemented by each upload runtime.
*
* @class plupload.QueueProgress
*/
/**
* Constructs a queue progress.
*
* @constructor
* @method QueueProgress
*/
plupload.QueueProgress = function() {
var self = this; // Setup alias for self to reduce code size when it's compressed
/**
* Total queue file size.
*
* @property size
* @type Number
*/
self.size = 0;
/**
* Total bytes uploaded.
*
* @property loaded
* @type Number
*/
self.loaded = 0;
/**
* Number of files uploaded.
*
* @property uploaded
* @type Number
*/
self.uploaded = 0;
/**
* Number of files failed to upload.
*
* @property failed
* @type Number
*/
self.failed = 0;
/**
* Number of files yet to be uploaded.
*
* @property queued
* @type Number
*/
self.queued = 0;
/**
* Total percent of the uploaded bytes.
*
* @property percent
* @type Number
*/
self.percent = 0;
/**
* Bytes uploaded per second.
*
* @property bytesPerSec
* @type Number
*/
self.bytesPerSec = 0;
/**
* Resets the progress to it's initial values.
*
* @method reset
*/
self.reset = function() {
self.size = self.loaded = self.uploaded = self.failed = self.queued = self.percent = self.bytesPerSec = 0;
};
};
// Create runtimes namespace
plupload.runtimes = {};
// Expose plupload namespace
window.plupload = plupload;
})();