1771 lines
43 KiB
JavaScript
1771 lines
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 <>"));
|
||
|
*
|
||
|
* // 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;
|
||
|
})();
|