fileuploader-rails 2.1.2 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +11 -4
- data/lib/fileuploader-rails/version.rb +1 -1
- data/vendor/assets/javascripts/fineuploader/button.js +102 -0
- data/vendor/assets/javascripts/fineuploader/handler.base.js +120 -0
- data/vendor/assets/javascripts/fineuploader/handler.form.js +197 -0
- data/vendor/assets/javascripts/fineuploader/handler.xhr.js +168 -0
- data/vendor/assets/javascripts/fineuploader/header.js +12 -0
- data/vendor/assets/javascripts/fineuploader/jquery-plugin.js +148 -0
- data/vendor/assets/javascripts/fineuploader/uploader.basic.js +483 -0
- data/vendor/assets/javascripts/fineuploader/uploader.js +575 -0
- data/vendor/assets/javascripts/fineuploader/util.js +319 -0
- data/vendor/assets/stylesheets/fineuploader.css +16 -3
- metadata +17 -3
- data/vendor/assets/javascripts/fineuploader.js +0 -1642
@@ -0,0 +1,168 @@
|
|
1
|
+
/**
|
2
|
+
* Class for uploading files using xhr
|
3
|
+
* @inherits qq.UploadHandlerAbstract
|
4
|
+
*/
|
5
|
+
qq.UploadHandlerXhr = function(o){
|
6
|
+
qq.UploadHandlerAbstract.apply(this, arguments);
|
7
|
+
|
8
|
+
this._files = [];
|
9
|
+
this._xhrs = [];
|
10
|
+
|
11
|
+
// current loaded size in bytes for each file
|
12
|
+
this._loaded = [];
|
13
|
+
};
|
14
|
+
|
15
|
+
// static method
|
16
|
+
qq.UploadHandlerXhr.isSupported = function(){
|
17
|
+
var input = document.createElement('input');
|
18
|
+
input.type = 'file';
|
19
|
+
|
20
|
+
return (
|
21
|
+
'multiple' in input &&
|
22
|
+
typeof File != "undefined" &&
|
23
|
+
typeof FormData != "undefined" &&
|
24
|
+
typeof (new XMLHttpRequest()).upload != "undefined" );
|
25
|
+
};
|
26
|
+
|
27
|
+
// @inherits qq.UploadHandlerAbstract
|
28
|
+
qq.extend(qq.UploadHandlerXhr.prototype, qq.UploadHandlerAbstract.prototype)
|
29
|
+
|
30
|
+
qq.extend(qq.UploadHandlerXhr.prototype, {
|
31
|
+
/**
|
32
|
+
* Adds file to the queue
|
33
|
+
* Returns id to use with upload, cancel
|
34
|
+
**/
|
35
|
+
add: function(file){
|
36
|
+
if (!(file instanceof File)){
|
37
|
+
throw new Error('Passed obj in not a File (in qq.UploadHandlerXhr)');
|
38
|
+
}
|
39
|
+
|
40
|
+
return this._files.push(file) - 1;
|
41
|
+
},
|
42
|
+
getName: function(id){
|
43
|
+
var file = this._files[id];
|
44
|
+
// fix missing name in Safari 4
|
45
|
+
//NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
|
46
|
+
return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
|
47
|
+
},
|
48
|
+
getSize: function(id){
|
49
|
+
var file = this._files[id];
|
50
|
+
return file.fileSize != null ? file.fileSize : file.size;
|
51
|
+
},
|
52
|
+
/**
|
53
|
+
* Returns uploaded bytes for file identified by id
|
54
|
+
*/
|
55
|
+
getLoaded: function(id){
|
56
|
+
return this._loaded[id] || 0;
|
57
|
+
},
|
58
|
+
isValid: function(id) {
|
59
|
+
return this._files[id] !== undefined;
|
60
|
+
},
|
61
|
+
reset: function() {
|
62
|
+
qq.UploadHandlerAbstract.prototype.reset.apply(this, arguments);
|
63
|
+
this._files = [];
|
64
|
+
this._xhrs = [];
|
65
|
+
this._loaded = [];
|
66
|
+
},
|
67
|
+
/**
|
68
|
+
* Sends the file identified by id and additional query params to the server
|
69
|
+
* @param {Object} params name-value string pairs
|
70
|
+
*/
|
71
|
+
_upload: function(id, params){
|
72
|
+
this._options.onUpload(id, this.getName(id), true);
|
73
|
+
|
74
|
+
var file = this._files[id],
|
75
|
+
name = this.getName(id),
|
76
|
+
size = this.getSize(id);
|
77
|
+
|
78
|
+
this._loaded[id] = 0;
|
79
|
+
|
80
|
+
var xhr = this._xhrs[id] = new XMLHttpRequest();
|
81
|
+
var self = this;
|
82
|
+
|
83
|
+
xhr.upload.onprogress = function(e){
|
84
|
+
if (e.lengthComputable){
|
85
|
+
self._loaded[id] = e.loaded;
|
86
|
+
self._options.onProgress(id, name, e.loaded, e.total);
|
87
|
+
}
|
88
|
+
};
|
89
|
+
|
90
|
+
xhr.onreadystatechange = function(){
|
91
|
+
if (xhr.readyState == 4){
|
92
|
+
self._onComplete(id, xhr);
|
93
|
+
}
|
94
|
+
};
|
95
|
+
|
96
|
+
// build query string
|
97
|
+
params = params || {};
|
98
|
+
params[this._options.inputName] = name;
|
99
|
+
var queryString = qq.obj2url(params, this._options.endpoint);
|
100
|
+
|
101
|
+
var protocol = this._options.demoMode ? "GET" : "POST";
|
102
|
+
xhr.open(protocol, queryString, true);
|
103
|
+
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
|
104
|
+
xhr.setRequestHeader("X-File-Name", encodeURIComponent(name));
|
105
|
+
xhr.setRequestHeader("Cache-Control", "no-cache");
|
106
|
+
if (this._options.forceMultipart) {
|
107
|
+
var formData = new FormData();
|
108
|
+
formData.append(this._options.inputName, file);
|
109
|
+
file = formData;
|
110
|
+
} else {
|
111
|
+
xhr.setRequestHeader("Content-Type", "application/octet-stream");
|
112
|
+
//NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
|
113
|
+
xhr.setRequestHeader("X-Mime-Type",file.type );
|
114
|
+
}
|
115
|
+
for (key in this._options.customHeaders){
|
116
|
+
xhr.setRequestHeader(key, this._options.customHeaders[key]);
|
117
|
+
};
|
118
|
+
|
119
|
+
this.log('Sending upload request for ' + id);
|
120
|
+
xhr.send(file);
|
121
|
+
},
|
122
|
+
_onComplete: function(id, xhr){
|
123
|
+
"use strict";
|
124
|
+
// the request was aborted/cancelled
|
125
|
+
if (!this._files[id]) { return; }
|
126
|
+
|
127
|
+
var name = this.getName(id);
|
128
|
+
var size = this.getSize(id);
|
129
|
+
var response; //the parsed JSON response from the server, or the empty object if parsing failed.
|
130
|
+
|
131
|
+
this._options.onProgress(id, name, size, size);
|
132
|
+
|
133
|
+
this.log("xhr - server response received for " + id);
|
134
|
+
this.log("responseText = " + xhr.responseText);
|
135
|
+
|
136
|
+
try {
|
137
|
+
if (typeof JSON.parse === "function") {
|
138
|
+
response = JSON.parse(xhr.responseText);
|
139
|
+
} else {
|
140
|
+
response = eval("(" + xhr.responseText + ")");
|
141
|
+
}
|
142
|
+
} catch(error){
|
143
|
+
this.log('Error when attempting to parse xhr response text (' + error + ')', 'error');
|
144
|
+
response = {};
|
145
|
+
}
|
146
|
+
|
147
|
+
if (xhr.status !== 200 || !response.success){
|
148
|
+
if (this._options.onAutoRetry(id, name, response, xhr)) {
|
149
|
+
return;
|
150
|
+
}
|
151
|
+
}
|
152
|
+
|
153
|
+
this._options.onComplete(id, name, response, xhr);
|
154
|
+
|
155
|
+
this._xhrs[id] = null;
|
156
|
+
this._dequeue(id);
|
157
|
+
},
|
158
|
+
_cancel: function(id){
|
159
|
+
this._options.onCancel(id, this.getName(id));
|
160
|
+
|
161
|
+
this._files[id] = null;
|
162
|
+
|
163
|
+
if (this._xhrs[id]){
|
164
|
+
this._xhrs[id].abort();
|
165
|
+
this._xhrs[id] = null;
|
166
|
+
}
|
167
|
+
}
|
168
|
+
});
|
@@ -0,0 +1,12 @@
|
|
1
|
+
/**
|
2
|
+
* http://github.com/Valums-File-Uploader/file-uploader
|
3
|
+
*
|
4
|
+
* Multiple file upload component with progress-bar, drag-and-drop, support for all modern browsers.
|
5
|
+
*
|
6
|
+
* Original version: 1.0 © 2010 Andrew Valums ( andrew(at)valums.com )
|
7
|
+
* Current Maintainer (2.0+): © 2012, Ray Nicholus ( fineuploader(at)garstasio.com )
|
8
|
+
*
|
9
|
+
* Licensed under MIT license, GNU GPL 2 or later, GNU LGPL 2 or later, see license.txt.
|
10
|
+
*/
|
11
|
+
|
12
|
+
var qq = qq || {};
|
@@ -0,0 +1,148 @@
|
|
1
|
+
(function($) {
|
2
|
+
"use strict";
|
3
|
+
var uploader, $el, init, dataStore, pluginOption, pluginOptions, addCallbacks, transformOptions, isValidCommand,
|
4
|
+
delegateCommand;
|
5
|
+
|
6
|
+
pluginOptions = ['uploaderType'];
|
7
|
+
|
8
|
+
init = function (options) {
|
9
|
+
if (options) {
|
10
|
+
var xformedOpts = transformOptions(options);
|
11
|
+
addCallbacks(xformedOpts);
|
12
|
+
|
13
|
+
if (pluginOption('uploaderType') === 'basic') {
|
14
|
+
uploader(new qq.FineUploaderBasic(xformedOpts));
|
15
|
+
}
|
16
|
+
else {
|
17
|
+
uploader(new qq.FineUploader(xformedOpts));
|
18
|
+
}
|
19
|
+
}
|
20
|
+
|
21
|
+
return $el;
|
22
|
+
};
|
23
|
+
|
24
|
+
dataStore = function(key, val) {
|
25
|
+
var data = $el.data('fineuploader');
|
26
|
+
|
27
|
+
if (val) {
|
28
|
+
if (data === undefined) {
|
29
|
+
data = {};
|
30
|
+
}
|
31
|
+
data[key] = val;
|
32
|
+
$el.data('fineuploader', data);
|
33
|
+
}
|
34
|
+
else {
|
35
|
+
if (data === undefined) {
|
36
|
+
return null;
|
37
|
+
}
|
38
|
+
return data[key];
|
39
|
+
}
|
40
|
+
};
|
41
|
+
|
42
|
+
//the underlying Fine Uploader instance is stored in jQuery's data stored, associated with the element
|
43
|
+
// tied to this instance of the plug-in
|
44
|
+
uploader = function(instanceToStore) {
|
45
|
+
return dataStore('uploader', instanceToStore);
|
46
|
+
};
|
47
|
+
|
48
|
+
pluginOption = function(option, optionVal) {
|
49
|
+
return dataStore(option, optionVal);
|
50
|
+
};
|
51
|
+
|
52
|
+
//implement all callbacks defined in Fine Uploader as functions that trigger appropriately names events and
|
53
|
+
// return the result of executing the bound handler back to Fine Uploader
|
54
|
+
addCallbacks = function(transformedOpts) {
|
55
|
+
var callbacks = transformedOpts.callbacks = {};
|
56
|
+
|
57
|
+
$.each(new qq.FineUploaderBasic()._options.callbacks, function(prop, func) {
|
58
|
+
var name, $callbackEl;
|
59
|
+
|
60
|
+
name = /^on(\w+)/.exec(prop)[1];
|
61
|
+
name = name.substring(0, 1).toLowerCase() + name.substring(1);
|
62
|
+
$callbackEl = $el;
|
63
|
+
|
64
|
+
callbacks[prop] = function() {
|
65
|
+
var args = Array.prototype.slice.call(arguments);
|
66
|
+
return $callbackEl.triggerHandler(name, args);
|
67
|
+
};
|
68
|
+
});
|
69
|
+
};
|
70
|
+
|
71
|
+
//transform jQuery objects into HTMLElements, and pass along all other option properties
|
72
|
+
transformOptions = function(source, dest) {
|
73
|
+
var xformed, arrayVals;
|
74
|
+
|
75
|
+
if (dest === undefined) {
|
76
|
+
if (source.uploaderType !== 'basic') {
|
77
|
+
xformed = { element : $el[0] };
|
78
|
+
}
|
79
|
+
else {
|
80
|
+
xformed = {};
|
81
|
+
}
|
82
|
+
}
|
83
|
+
else {
|
84
|
+
xformed = dest;
|
85
|
+
}
|
86
|
+
|
87
|
+
$.each(source, function(prop, val) {
|
88
|
+
if ($.inArray(prop, pluginOptions) >= 0) {
|
89
|
+
pluginOption(prop, val);
|
90
|
+
}
|
91
|
+
else if (val instanceof $) {
|
92
|
+
xformed[prop] = val[0];
|
93
|
+
}
|
94
|
+
else if ($.isPlainObject(val)) {
|
95
|
+
xformed[prop] = {};
|
96
|
+
transformOptions(val, xformed[prop]);
|
97
|
+
}
|
98
|
+
else if ($.isArray(val)) {
|
99
|
+
arrayVals = [];
|
100
|
+
$.each(val, function(idx, arrayVal) {
|
101
|
+
if (arrayVal instanceof $) {
|
102
|
+
$.merge(arrayVals, arrayVal);
|
103
|
+
}
|
104
|
+
else {
|
105
|
+
arrayVals.push(arrayVal);
|
106
|
+
}
|
107
|
+
});
|
108
|
+
xformed[prop] = arrayVals;
|
109
|
+
}
|
110
|
+
else {
|
111
|
+
xformed[prop] = val;
|
112
|
+
}
|
113
|
+
});
|
114
|
+
|
115
|
+
if (dest === undefined) {
|
116
|
+
return xformed;
|
117
|
+
}
|
118
|
+
};
|
119
|
+
|
120
|
+
isValidCommand = function(command) {
|
121
|
+
return $.type(command) === "string" &&
|
122
|
+
!command.match(/^_/) && //enforce private methods convention
|
123
|
+
uploader()[command] !== undefined;
|
124
|
+
};
|
125
|
+
|
126
|
+
//assuming we have already verified that this is a valid command, call the associated function in the underlying
|
127
|
+
// Fine Uploader instance (passing along the arguments from the caller) and return the result of the call back to the caller
|
128
|
+
delegateCommand = function(command) {
|
129
|
+
return uploader()[command].apply(uploader(), Array.prototype.slice.call(arguments, 1));
|
130
|
+
};
|
131
|
+
|
132
|
+
$.fn.fineUploader = function(optionsOrCommand) {
|
133
|
+
$el = this;
|
134
|
+
|
135
|
+
if (uploader() && isValidCommand(optionsOrCommand)) {
|
136
|
+
return delegateCommand.apply(this, arguments);
|
137
|
+
}
|
138
|
+
else if (typeof optionsOrCommand === 'object' || !optionsOrCommand) {
|
139
|
+
return init.apply(this, arguments);
|
140
|
+
}
|
141
|
+
else {
|
142
|
+
$.error('Method ' + optionsOrCommand + ' does not exist on jQuery.fineUploader');
|
143
|
+
}
|
144
|
+
|
145
|
+
return this;
|
146
|
+
};
|
147
|
+
|
148
|
+
}(jQuery));
|
@@ -0,0 +1,483 @@
|
|
1
|
+
qq.FineUploaderBasic = function(o){
|
2
|
+
var that = this;
|
3
|
+
this._options = {
|
4
|
+
debug: false,
|
5
|
+
button: null,
|
6
|
+
multiple: true,
|
7
|
+
maxConnections: 3,
|
8
|
+
disableCancelForFormUploads: false,
|
9
|
+
autoUpload: true,
|
10
|
+
request: {
|
11
|
+
endpoint: '/server/upload',
|
12
|
+
params: {},
|
13
|
+
customHeaders: {},
|
14
|
+
forceMultipart: false,
|
15
|
+
inputName: 'qqfile'
|
16
|
+
},
|
17
|
+
validation: {
|
18
|
+
allowedExtensions: [],
|
19
|
+
sizeLimit: 0,
|
20
|
+
minSizeLimit: 0,
|
21
|
+
stopOnFirstInvalidFile: true
|
22
|
+
},
|
23
|
+
callbacks: {
|
24
|
+
onSubmit: function(id, fileName){}, // return false to cancel submit
|
25
|
+
onComplete: function(id, fileName, responseJSON){},
|
26
|
+
onCancel: function(id, fileName){},
|
27
|
+
onUpload: function(id, fileName, xhr){},
|
28
|
+
onProgress: function(id, fileName, loaded, total){},
|
29
|
+
onError: function(id, fileName, reason) {},
|
30
|
+
onAutoRetry: function(id, fileName, attemptNumber) {},
|
31
|
+
onManualRetry: function(id, fileName) {},
|
32
|
+
onValidate: function(fileData) {} // return false to prevent upload
|
33
|
+
},
|
34
|
+
messages: {
|
35
|
+
typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.",
|
36
|
+
sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
|
37
|
+
minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
|
38
|
+
emptyError: "{file} is empty, please select files again without it.",
|
39
|
+
noFilesError: "No files to upload.",
|
40
|
+
onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
|
41
|
+
},
|
42
|
+
retry: {
|
43
|
+
enableAuto: false,
|
44
|
+
maxAutoAttempts: 3,
|
45
|
+
autoAttemptDelay: 5,
|
46
|
+
preventRetryResponseProperty: 'preventRetry'
|
47
|
+
}
|
48
|
+
};
|
49
|
+
|
50
|
+
qq.extend(this._options, o, true);
|
51
|
+
this._wrapCallbacks();
|
52
|
+
qq.extend(this, qq.DisposeSupport);
|
53
|
+
|
54
|
+
// number of files being uploaded
|
55
|
+
this._filesInProgress = 0;
|
56
|
+
|
57
|
+
this._storedFileIds = [];
|
58
|
+
|
59
|
+
this._autoRetries = [];
|
60
|
+
this._retryTimeouts = [];
|
61
|
+
this._preventRetries = [];
|
62
|
+
|
63
|
+
this._handler = this._createUploadHandler();
|
64
|
+
|
65
|
+
if (this._options.button){
|
66
|
+
this._button = this._createUploadButton(this._options.button);
|
67
|
+
}
|
68
|
+
|
69
|
+
this._preventLeaveInProgress();
|
70
|
+
};
|
71
|
+
|
72
|
+
qq.FineUploaderBasic.prototype = {
|
73
|
+
log: function(str, level) {
|
74
|
+
if (this._options.debug && (!level || level === 'info')) {
|
75
|
+
qq.log('[FineUploader] ' + str);
|
76
|
+
}
|
77
|
+
else if (level && level !== 'info') {
|
78
|
+
qq.log('[FineUploader] ' + str, level);
|
79
|
+
|
80
|
+
}
|
81
|
+
},
|
82
|
+
setParams: function(params){
|
83
|
+
this._options.request.params = params;
|
84
|
+
},
|
85
|
+
getInProgress: function(){
|
86
|
+
return this._filesInProgress;
|
87
|
+
},
|
88
|
+
uploadStoredFiles: function(){
|
89
|
+
"use strict";
|
90
|
+
while(this._storedFileIds.length) {
|
91
|
+
this._filesInProgress++;
|
92
|
+
this._handler.upload(this._storedFileIds.shift(), this._options.request.params);
|
93
|
+
}
|
94
|
+
},
|
95
|
+
clearStoredFiles: function(){
|
96
|
+
this._storedFileIds = [];
|
97
|
+
},
|
98
|
+
retry: function(id) {
|
99
|
+
if (this._onBeforeManualRetry(id)) {
|
100
|
+
this._handler.retry(id);
|
101
|
+
return true;
|
102
|
+
}
|
103
|
+
else {
|
104
|
+
return false;
|
105
|
+
}
|
106
|
+
},
|
107
|
+
cancel: function(fileId) {
|
108
|
+
this._handler.cancel(fileId);
|
109
|
+
},
|
110
|
+
reset: function() {
|
111
|
+
this.log("Resetting uploader...");
|
112
|
+
this._handler.reset();
|
113
|
+
this._filesInProgress = 0;
|
114
|
+
this._storedFileIds = [];
|
115
|
+
this._autoRetries = [];
|
116
|
+
this._retryTimeouts = [];
|
117
|
+
this._preventRetries = [];
|
118
|
+
this._button.reset();
|
119
|
+
},
|
120
|
+
_createUploadButton: function(element){
|
121
|
+
var self = this;
|
122
|
+
|
123
|
+
var button = new qq.UploadButton({
|
124
|
+
element: element,
|
125
|
+
multiple: this._options.multiple && qq.UploadHandlerXhr.isSupported(),
|
126
|
+
acceptFiles: this._options.validation.acceptFiles,
|
127
|
+
onChange: function(input){
|
128
|
+
self._onInputChange(input);
|
129
|
+
}
|
130
|
+
});
|
131
|
+
|
132
|
+
this.addDisposer(function() { button.dispose(); });
|
133
|
+
return button;
|
134
|
+
},
|
135
|
+
_createUploadHandler: function(){
|
136
|
+
var self = this,
|
137
|
+
handlerClass;
|
138
|
+
|
139
|
+
if(qq.UploadHandlerXhr.isSupported()){
|
140
|
+
handlerClass = 'UploadHandlerXhr';
|
141
|
+
} else {
|
142
|
+
handlerClass = 'UploadHandlerForm';
|
143
|
+
}
|
144
|
+
|
145
|
+
var handler = new qq[handlerClass]({
|
146
|
+
debug: this._options.debug,
|
147
|
+
endpoint: this._options.request.endpoint,
|
148
|
+
forceMultipart: this._options.request.forceMultipart,
|
149
|
+
maxConnections: this._options.maxConnections,
|
150
|
+
customHeaders: this._options.request.customHeaders,
|
151
|
+
inputName: this._options.request.inputName,
|
152
|
+
demoMode: this._options.demoMode,
|
153
|
+
log: this.log,
|
154
|
+
onProgress: function(id, fileName, loaded, total){
|
155
|
+
self._onProgress(id, fileName, loaded, total);
|
156
|
+
self._options.callbacks.onProgress(id, fileName, loaded, total);
|
157
|
+
},
|
158
|
+
onComplete: function(id, fileName, result, xhr){
|
159
|
+
self._onComplete(id, fileName, result, xhr);
|
160
|
+
self._options.callbacks.onComplete(id, fileName, result);
|
161
|
+
},
|
162
|
+
onCancel: function(id, fileName){
|
163
|
+
self._onCancel(id, fileName);
|
164
|
+
self._options.callbacks.onCancel(id, fileName);
|
165
|
+
},
|
166
|
+
onUpload: function(id, fileName, xhr){
|
167
|
+
self._onUpload(id, fileName, xhr);
|
168
|
+
self._options.callbacks.onUpload(id, fileName, xhr);
|
169
|
+
},
|
170
|
+
onAutoRetry: function(id, fileName, responseJSON, xhr) {
|
171
|
+
self._preventRetries[id] = responseJSON[self._options.retry.preventRetryResponseProperty];
|
172
|
+
|
173
|
+
if (self._shouldAutoRetry(id, fileName, responseJSON)) {
|
174
|
+
self._maybeParseAndSendUploadError(id, fileName, responseJSON, xhr);
|
175
|
+
self._options.callbacks.onAutoRetry(id, fileName, self._autoRetries[id] + 1);
|
176
|
+
self._onBeforeAutoRetry(id, fileName);
|
177
|
+
|
178
|
+
self._retryTimeouts[id] = setTimeout(function() {
|
179
|
+
self._onAutoRetry(id, fileName, responseJSON)
|
180
|
+
}, self._options.retry.autoAttemptDelay * 1000);
|
181
|
+
|
182
|
+
return true;
|
183
|
+
}
|
184
|
+
else {
|
185
|
+
return false;
|
186
|
+
}
|
187
|
+
}
|
188
|
+
});
|
189
|
+
|
190
|
+
return handler;
|
191
|
+
},
|
192
|
+
_preventLeaveInProgress: function(){
|
193
|
+
var self = this;
|
194
|
+
|
195
|
+
this._attach(window, 'beforeunload', function(e){
|
196
|
+
if (!self._filesInProgress){return;}
|
197
|
+
|
198
|
+
var e = e || window.event;
|
199
|
+
// for ie, ff
|
200
|
+
e.returnValue = self._options.messages.onLeave;
|
201
|
+
// for webkit
|
202
|
+
return self._options.messages.onLeave;
|
203
|
+
});
|
204
|
+
},
|
205
|
+
_onSubmit: function(id, fileName){
|
206
|
+
if (this._options.autoUpload) {
|
207
|
+
this._filesInProgress++;
|
208
|
+
}
|
209
|
+
},
|
210
|
+
_onProgress: function(id, fileName, loaded, total){
|
211
|
+
},
|
212
|
+
_onComplete: function(id, fileName, result, xhr){
|
213
|
+
this._filesInProgress--;
|
214
|
+
this._maybeParseAndSendUploadError(id, fileName, result, xhr);
|
215
|
+
},
|
216
|
+
_onCancel: function(id, fileName){
|
217
|
+
clearTimeout(this._retryTimeouts[id]);
|
218
|
+
|
219
|
+
var storedFileIndex = qq.indexOf(this._storedFileIds, id);
|
220
|
+
if (this._options.autoUpload || storedFileIndex < 0) {
|
221
|
+
this._filesInProgress--;
|
222
|
+
}
|
223
|
+
else if (!this._options.autoUpload) {
|
224
|
+
this._storedFileIds.splice(storedFileIndex, 1);
|
225
|
+
}
|
226
|
+
},
|
227
|
+
_onUpload: function(id, fileName, xhr){
|
228
|
+
},
|
229
|
+
_onInputChange: function(input){
|
230
|
+
if (this._handler instanceof qq.UploadHandlerXhr){
|
231
|
+
this._uploadFileList(input.files);
|
232
|
+
} else {
|
233
|
+
if (this._validateFile(input)){
|
234
|
+
this._uploadFile(input);
|
235
|
+
}
|
236
|
+
}
|
237
|
+
this._button.reset();
|
238
|
+
},
|
239
|
+
_onBeforeAutoRetry: function(id, fileName) {
|
240
|
+
this.log("Waiting " + this._options.retry.autoAttemptDelay + " seconds before retrying " + fileName + "...");
|
241
|
+
},
|
242
|
+
_onAutoRetry: function(id, fileName, responseJSON) {
|
243
|
+
this.log("Retrying " + fileName + "...");
|
244
|
+
this._autoRetries[id]++;
|
245
|
+
this._handler.retry(id);
|
246
|
+
},
|
247
|
+
_shouldAutoRetry: function(id, fileName, responseJSON) {
|
248
|
+
if (!this._preventRetries[id] && this._options.retry.enableAuto) {
|
249
|
+
if (this._autoRetries[id] === undefined) {
|
250
|
+
this._autoRetries[id] = 0;
|
251
|
+
}
|
252
|
+
|
253
|
+
return this._autoRetries[id] < this._options.retry.maxAutoAttempts
|
254
|
+
}
|
255
|
+
|
256
|
+
return false;
|
257
|
+
},
|
258
|
+
//return false if we should not attempt the requested retry
|
259
|
+
_onBeforeManualRetry: function(id) {
|
260
|
+
if (this._preventRetries[id]) {
|
261
|
+
this.log("Retries are forbidden for id " + id, 'warn');
|
262
|
+
return false;
|
263
|
+
}
|
264
|
+
else if (this._handler.isValid(id)) {
|
265
|
+
var fileName = this._handler.getName(id);
|
266
|
+
|
267
|
+
if (this._options.callbacks.onManualRetry(id, fileName) === false) {
|
268
|
+
return false;
|
269
|
+
}
|
270
|
+
|
271
|
+
this.log("Retrying upload for '" + fileName + "' (id: " + id + ")...");
|
272
|
+
this._filesInProgress++;
|
273
|
+
return true;
|
274
|
+
}
|
275
|
+
else {
|
276
|
+
this.log("'" + id + "' is not a valid file ID", 'error');
|
277
|
+
return false;
|
278
|
+
}
|
279
|
+
},
|
280
|
+
_maybeParseAndSendUploadError: function(id, fileName, response, xhr) {
|
281
|
+
//assuming no one will actually set the response code to something other than 200 and still set 'success' to true
|
282
|
+
if (!response.success){
|
283
|
+
if (xhr && xhr.status !== 200 && !response.error) {
|
284
|
+
this._options.callbacks.onError(id, fileName, "XHR returned response code " + xhr.status);
|
285
|
+
}
|
286
|
+
else {
|
287
|
+
var errorReason = response.error ? response.error : "Upload failure reason unknown";
|
288
|
+
this._options.callbacks.onError(id, fileName, errorReason);
|
289
|
+
}
|
290
|
+
}
|
291
|
+
},
|
292
|
+
_uploadFileList: function(files){
|
293
|
+
var validationDescriptors, index, batchInvalid;
|
294
|
+
|
295
|
+
validationDescriptors = this._getValidationDescriptors(files);
|
296
|
+
if (validationDescriptors.length > 1) {
|
297
|
+
batchInvalid = this._options.callbacks.onValidate(validationDescriptors) === false;
|
298
|
+
}
|
299
|
+
|
300
|
+
if (!batchInvalid) {
|
301
|
+
if (files.length > 0) {
|
302
|
+
for (index = 0; index < files.length; index++){
|
303
|
+
if (this._validateFile(files[index])){
|
304
|
+
this._uploadFile(files[index]);
|
305
|
+
} else {
|
306
|
+
if (this._options.validation.stopOnFirstInvalidFile){
|
307
|
+
return;
|
308
|
+
}
|
309
|
+
}
|
310
|
+
}
|
311
|
+
}
|
312
|
+
else {
|
313
|
+
this._error('noFilesError', "");
|
314
|
+
}
|
315
|
+
}
|
316
|
+
},
|
317
|
+
_uploadFile: function(fileContainer){
|
318
|
+
var id = this._handler.add(fileContainer);
|
319
|
+
var fileName = this._handler.getName(id);
|
320
|
+
|
321
|
+
if (this._options.callbacks.onSubmit(id, fileName) !== false){
|
322
|
+
this._onSubmit(id, fileName);
|
323
|
+
if (this._options.autoUpload) {
|
324
|
+
this._handler.upload(id, this._options.request.params);
|
325
|
+
}
|
326
|
+
else {
|
327
|
+
this._storeFileForLater(id);
|
328
|
+
}
|
329
|
+
}
|
330
|
+
},
|
331
|
+
_storeFileForLater: function(id) {
|
332
|
+
this._storedFileIds.push(id);
|
333
|
+
},
|
334
|
+
_validateFile: function(file){
|
335
|
+
var validationDescriptor, name, size;
|
336
|
+
|
337
|
+
validationDescriptor = this._getValidationDescriptor(file);
|
338
|
+
name = validationDescriptor.name;
|
339
|
+
size = validationDescriptor.size;
|
340
|
+
|
341
|
+
if (this._options.callbacks.onValidate([validationDescriptor]) === false) {
|
342
|
+
return false;
|
343
|
+
}
|
344
|
+
|
345
|
+
if (!this._isAllowedExtension(name)){
|
346
|
+
this._error('typeError', name);
|
347
|
+
return false;
|
348
|
+
|
349
|
+
}
|
350
|
+
else if (size === 0){
|
351
|
+
this._error('emptyError', name);
|
352
|
+
return false;
|
353
|
+
|
354
|
+
}
|
355
|
+
else if (size && this._options.validation.sizeLimit && size > this._options.validation.sizeLimit){
|
356
|
+
this._error('sizeError', name);
|
357
|
+
return false;
|
358
|
+
|
359
|
+
}
|
360
|
+
else if (size && size < this._options.validation.minSizeLimit){
|
361
|
+
this._error('minSizeError', name);
|
362
|
+
return false;
|
363
|
+
}
|
364
|
+
|
365
|
+
return true;
|
366
|
+
},
|
367
|
+
_error: function(code, fileName){
|
368
|
+
var message = this._options.messages[code];
|
369
|
+
function r(name, replacement){ message = message.replace(name, replacement); }
|
370
|
+
|
371
|
+
var extensions = this._options.validation.allowedExtensions.join(', ');
|
372
|
+
|
373
|
+
r('{file}', this._formatFileName(fileName));
|
374
|
+
r('{extensions}', extensions);
|
375
|
+
r('{sizeLimit}', this._formatSize(this._options.validation.sizeLimit));
|
376
|
+
r('{minSizeLimit}', this._formatSize(this._options.validation.minSizeLimit));
|
377
|
+
|
378
|
+
this._options.callbacks.onError(null, fileName, message);
|
379
|
+
|
380
|
+
return message;
|
381
|
+
},
|
382
|
+
_formatFileName: function(name){
|
383
|
+
if (name.length > 33){
|
384
|
+
name = name.slice(0, 19) + '...' + name.slice(-13);
|
385
|
+
}
|
386
|
+
return name;
|
387
|
+
},
|
388
|
+
_isAllowedExtension: function(fileName){
|
389
|
+
var ext = (-1 !== fileName.indexOf('.'))
|
390
|
+
? fileName.replace(/.*[.]/, '').toLowerCase()
|
391
|
+
: '';
|
392
|
+
var allowed = this._options.validation.allowedExtensions;
|
393
|
+
|
394
|
+
if (!allowed.length){return true;}
|
395
|
+
|
396
|
+
for (var i=0; i<allowed.length; i++){
|
397
|
+
if (allowed[i].toLowerCase() == ext){ return true;}
|
398
|
+
}
|
399
|
+
|
400
|
+
return false;
|
401
|
+
},
|
402
|
+
_formatSize: function(bytes){
|
403
|
+
var i = -1;
|
404
|
+
do {
|
405
|
+
bytes = bytes / 1024;
|
406
|
+
i++;
|
407
|
+
} while (bytes > 99);
|
408
|
+
|
409
|
+
return Math.max(bytes, 0.1).toFixed(1) + ['kB', 'MB', 'GB', 'TB', 'PB', 'EB'][i];
|
410
|
+
},
|
411
|
+
_wrapCallbacks: function() {
|
412
|
+
var self, safeCallback;
|
413
|
+
|
414
|
+
self = this;
|
415
|
+
|
416
|
+
safeCallback = function(name, callback, args) {
|
417
|
+
try {
|
418
|
+
return callback.apply(self, args);
|
419
|
+
}
|
420
|
+
catch (exception) {
|
421
|
+
self.log("Caught exception in '" + name + "' callback - " + exception, 'error');
|
422
|
+
}
|
423
|
+
}
|
424
|
+
|
425
|
+
for (var prop in this._options.callbacks) {
|
426
|
+
(function() {
|
427
|
+
var oldCallback = self._options.callbacks[prop];
|
428
|
+
self._options.callbacks[prop] = function() {
|
429
|
+
return safeCallback(prop, oldCallback, arguments);
|
430
|
+
}
|
431
|
+
}());
|
432
|
+
}
|
433
|
+
},
|
434
|
+
_parseFileName: function(file) {
|
435
|
+
var name;
|
436
|
+
|
437
|
+
if (file.value){
|
438
|
+
// it is a file input
|
439
|
+
// get input value and remove path to normalize
|
440
|
+
name = file.value.replace(/.*(\/|\\)/, "");
|
441
|
+
} else {
|
442
|
+
// fix missing properties in Safari 4 and firefox 11.0a2
|
443
|
+
name = (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
|
444
|
+
}
|
445
|
+
|
446
|
+
return name;
|
447
|
+
},
|
448
|
+
_parseFileSize: function(file) {
|
449
|
+
var size;
|
450
|
+
|
451
|
+
if (!file.value){
|
452
|
+
// fix missing properties in Safari 4 and firefox 11.0a2
|
453
|
+
size = (file.fileSize !== null && file.fileSize !== undefined) ? file.fileSize : file.size;
|
454
|
+
}
|
455
|
+
|
456
|
+
return size;
|
457
|
+
},
|
458
|
+
_getValidationDescriptor: function(file) {
|
459
|
+
var name, size, fileDescriptor;
|
460
|
+
|
461
|
+
fileDescriptor = {};
|
462
|
+
name = this._parseFileName(file);
|
463
|
+
size = this._parseFileSize(file);
|
464
|
+
|
465
|
+
fileDescriptor.name = name;
|
466
|
+
if (size) {
|
467
|
+
fileDescriptor.size = size;
|
468
|
+
}
|
469
|
+
|
470
|
+
return fileDescriptor;
|
471
|
+
},
|
472
|
+
_getValidationDescriptors: function(files) {
|
473
|
+
var index, fileDescriptors;
|
474
|
+
|
475
|
+
fileDescriptors = [];
|
476
|
+
|
477
|
+
for (index = 0; index < files.length; index++) {
|
478
|
+
fileDescriptors.push(files[index]);
|
479
|
+
}
|
480
|
+
|
481
|
+
return fileDescriptors;
|
482
|
+
}
|
483
|
+
};
|