1333 lines
35 KiB
JavaScript
1333 lines
35 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 */
|
|
|
|
(function() {
|
|
var count = 0, runtimes = [], i18n = {}, mimes = {},
|
|
xmlEncodeChars = {'<' : 'lt', '>' : 'gt', '&' : 'amp', '"' : 'quot', '\'' : '#39'},
|
|
xmlEncodeRegExp = /[<>&\"\']/g, undef;
|
|
|
|
// 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,docx pptx xlsx," +
|
|
"audio/mpeg,mpga mpega mp2 mp3," +
|
|
"audio/x-wav,wav," +
|
|
"image/bmp,bmp," +
|
|
"image/gif,gif," +
|
|
"image/jpeg,jpeg jpg jpe," +
|
|
"image/png,png," +
|
|
"image/svg+xml,svg svgz," +
|
|
"image/tiff,tiff tif," +
|
|
"text/html,htm html xhtml," +
|
|
"text/rtf,rtf," +
|
|
"video/mpeg,mpeg mpg mpe," +
|
|
"video/quicktime,qt mov," +
|
|
"video/x-flv,flv," +
|
|
"video/vnd.rn-realvideo,rv," +
|
|
"text/plain,asc txt text diff log," +
|
|
"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 = {
|
|
/**
|
|
* 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 to 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 : -700,
|
|
|
|
/**
|
|
* Mime type lookup table.
|
|
*
|
|
* @property mimeTypes
|
|
* @type Object
|
|
* @final
|
|
*/
|
|
mimeTypes : mimes,
|
|
|
|
/**
|
|
* 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) {
|
|
return plupload.translate('N/A');
|
|
}
|
|
|
|
// 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.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.clientWidth || node.offsetWidth,
|
|
h : node.clientHeight || node.offsetHeight
|
|
};
|
|
},
|
|
|
|
/**
|
|
* 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;
|
|
},
|
|
|
|
/**
|
|
* 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.
|
|
* @reutrn {String} Translated string or the input string if it wasn't found.
|
|
*/
|
|
translate : function(str) {
|
|
return i18n[str] || str;
|
|
},
|
|
|
|
/**
|
|
* Adds an event handler to the specified object.
|
|
*
|
|
* @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.
|
|
*/
|
|
addEvent : function(obj, name, callback) {
|
|
if (obj.attachEvent) {
|
|
obj.attachEvent('on' + name, function() {
|
|
var evt = window.event;
|
|
|
|
if (!evt.target) {
|
|
evt.target = evt.srcElement;
|
|
}
|
|
|
|
evt.preventDefault = preventDefault;
|
|
evt.stopPropagation = stopPropagation;
|
|
|
|
callback(evt);
|
|
});
|
|
} else if (obj.addEventListener) {
|
|
obj.addEventListener(name, callback, false);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 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 = [], fileIndex, startTime;
|
|
|
|
// 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;
|
|
|
|
if (this.state == plupload.STARTED && fileIndex < files.length) {
|
|
file = files[fileIndex++];
|
|
|
|
if (file.status == plupload.QUEUED) {
|
|
this.trigger('BeforeUpload', file);
|
|
this.trigger("UploadFile", file);
|
|
} else {
|
|
uploadNext.call(this);
|
|
}
|
|
} else {
|
|
this.stop();
|
|
}
|
|
}
|
|
|
|
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,
|
|
|
|
/**
|
|
* 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;
|
|
|
|
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) {
|
|
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 : '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 : '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) {
|
|
self.trigger("QueueChanged");
|
|
self.refresh();
|
|
}
|
|
});
|
|
|
|
// 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) {
|
|
if (file.status == plupload.QUEUED) {
|
|
file.status = plupload.UPLOADING;
|
|
}
|
|
|
|
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());
|
|
}
|
|
});
|
|
|
|
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
|
|
window.setTimeout(function() {
|
|
uploadNext.call(self);
|
|
});
|
|
}
|
|
});
|
|
|
|
self.bind("FileUploaded", function(up, file) {
|
|
file.status = plupload.DONE;
|
|
file.loaded = file.size;
|
|
up.trigger('UploadProgress', file);
|
|
uploadNext.call(self);
|
|
});
|
|
|
|
// 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.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 : 'Init error.'
|
|
});
|
|
}
|
|
}
|
|
|
|
callNextInit();
|
|
},
|
|
|
|
/**
|
|
* 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 (this.state != plupload.STARTED) {
|
|
fileIndex = 0;
|
|
|
|
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("StateChanged");
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 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, 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;
|
|
},
|
|
|
|
/**
|
|
* 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, func) {
|
|
var list = events[name.toLowerCase()], i;
|
|
|
|
if (list) {
|
|
for (i = list.length - 1; i >= 0; i--) {
|
|
if (list[i].func === func) {
|
|
list.splice(i, 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 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.
|
|
*/
|
|
});
|
|
};
|
|
|
|
/**
|
|
* 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;
|
|
})(); |