fileuploader-rails 2.1.2 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,319 @@
1
+ var qq = function(element) {
2
+ "use strict";
3
+
4
+ return {
5
+ hide: function() {
6
+ element.style.display = 'none';
7
+ return this;
8
+ },
9
+
10
+ /** Returns the function which detaches attached event */
11
+ attach: function(type, fn) {
12
+ if (element.addEventListener){
13
+ element.addEventListener(type, fn, false);
14
+ } else if (element.attachEvent){
15
+ element.attachEvent('on' + type, fn);
16
+ }
17
+ return function() {
18
+ qq(element).detach(type, fn);
19
+ };
20
+ },
21
+
22
+ detach: function(type, fn) {
23
+ if (element.removeEventListener){
24
+ element.removeEventListener(type, fn, false);
25
+ } else if (element.attachEvent){
26
+ element.detachEvent('on' + type, fn);
27
+ }
28
+ return this;
29
+ },
30
+
31
+ contains: function(descendant) {
32
+ // compareposition returns false in this case
33
+ if (element == descendant) {
34
+ return true;
35
+ }
36
+
37
+ if (element.contains){
38
+ return element.contains(descendant);
39
+ } else {
40
+ return !!(descendant.compareDocumentPosition(element) & 8);
41
+ }
42
+ },
43
+
44
+ /**
45
+ * Insert this element before elementB.
46
+ */
47
+ insertBefore: function(elementB) {
48
+ elementB.parentNode.insertBefore(element, elementB);
49
+ return this;
50
+ },
51
+
52
+ remove: function() {
53
+ element.parentNode.removeChild(element);
54
+ return this;
55
+ },
56
+
57
+ /**
58
+ * Sets styles for an element.
59
+ * Fixes opacity in IE6-8.
60
+ */
61
+ css: function(styles) {
62
+ if (styles.opacity != null){
63
+ if (typeof element.style.opacity != 'string' && typeof(element.filters) != 'undefined'){
64
+ styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
65
+ }
66
+ }
67
+ qq.extend(element.style, styles);
68
+
69
+ return this;
70
+ },
71
+
72
+ hasClass: function(name) {
73
+ var re = new RegExp('(^| )' + name + '( |$)');
74
+ return re.test(element.className);
75
+ },
76
+
77
+ addClass: function(name) {
78
+ if (!qq(element).hasClass(name)){
79
+ element.className += ' ' + name;
80
+ }
81
+ return this;
82
+ },
83
+
84
+ removeClass: function(name) {
85
+ var re = new RegExp('(^| )' + name + '( |$)');
86
+ element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
87
+ return this;
88
+ },
89
+
90
+ getByClass: function(className) {
91
+ if (element.querySelectorAll){
92
+ return element.querySelectorAll('.' + className);
93
+ }
94
+
95
+ var result = [];
96
+ var candidates = element.getElementsByTagName("*");
97
+ var len = candidates.length;
98
+
99
+ for (var i = 0; i < len; i++){
100
+ if (qq(candidates[i]).hasClass(className)){
101
+ result.push(candidates[i]);
102
+ }
103
+ }
104
+ return result;
105
+ },
106
+
107
+ children: function() {
108
+ var children = [],
109
+ child = element.firstChild;
110
+
111
+ while (child){
112
+ if (child.nodeType == 1){
113
+ children.push(child);
114
+ }
115
+ child = child.nextSibling;
116
+ }
117
+
118
+ return children;
119
+ },
120
+
121
+ setText: function(text) {
122
+ element.innerText = text;
123
+ element.textContent = text;
124
+ return this;
125
+ },
126
+
127
+ clearText: function() {
128
+ return qq(element).setText("");
129
+ }
130
+ };
131
+ };
132
+
133
+ qq.log = function(message, level) {
134
+ if (window.console) {
135
+ if (!level || level === 'info') {
136
+ window.console.log(message);
137
+ }
138
+ else
139
+ {
140
+ if (window.console[level]) {
141
+ window.console[level](message);
142
+ }
143
+ else {
144
+ window.console.log('<' + level + '> ' + message);
145
+ }
146
+ }
147
+ }
148
+ };
149
+
150
+ qq.isObject = function(variable) {
151
+ "use strict";
152
+ return variable !== null && variable && typeof(variable) === "object" && variable.constructor === Object;
153
+ };
154
+
155
+ qq.extend = function (first, second, extendNested) {
156
+ "use strict";
157
+ var prop;
158
+ for (prop in second) {
159
+ if (second.hasOwnProperty(prop)) {
160
+ if (extendNested && qq.isObject(second[prop])) {
161
+ if (first[prop] === undefined) {
162
+ first[prop] = {};
163
+ }
164
+ qq.extend(first[prop], second[prop], true);
165
+ }
166
+ else {
167
+ first[prop] = second[prop];
168
+ }
169
+ }
170
+ }
171
+ };
172
+
173
+ /**
174
+ * Searches for a given element in the array, returns -1 if it is not present.
175
+ * @param {Number} [from] The index at which to begin the search
176
+ */
177
+ qq.indexOf = function(arr, elt, from){
178
+ if (arr.indexOf) return arr.indexOf(elt, from);
179
+
180
+ from = from || 0;
181
+ var len = arr.length;
182
+
183
+ if (from < 0) from += len;
184
+
185
+ for (; from < len; from++){
186
+ if (from in arr && arr[from] === elt){
187
+ return from;
188
+ }
189
+ }
190
+ return -1;
191
+ };
192
+
193
+ qq.getUniqueId = (function(){
194
+ var id = 0;
195
+ return function(){ return id++; };
196
+ })();
197
+
198
+ //
199
+ // Browsers and platforms detection
200
+
201
+ qq.ie = function(){ return navigator.userAgent.indexOf('MSIE') != -1; }
202
+ qq.ie10 = function(){ return navigator.userAgent.indexOf('MSIE 10') != -1; }
203
+ qq.safari = function(){ return navigator.vendor != undefined && navigator.vendor.indexOf("Apple") != -1; }
204
+ qq.chrome = function(){ return navigator.vendor != undefined && navigator.vendor.indexOf('Google') != -1; }
205
+ qq.firefox = function(){ return (navigator.userAgent.indexOf('Mozilla') != -1 && navigator.vendor != undefined && navigator.vendor == ''); }
206
+ qq.windows = function(){ return navigator.platform == "Win32"; }
207
+
208
+ //
209
+ // Events
210
+
211
+ qq.preventDefault = function(e){
212
+ if (e.preventDefault){
213
+ e.preventDefault();
214
+ } else{
215
+ e.returnValue = false;
216
+ }
217
+ };
218
+
219
+ /**
220
+ * Creates and returns element from html string
221
+ * Uses innerHTML to create an element
222
+ */
223
+ qq.toElement = (function(){
224
+ var div = document.createElement('div');
225
+ return function(html){
226
+ div.innerHTML = html;
227
+ var element = div.firstChild;
228
+ div.removeChild(element);
229
+ return element;
230
+ };
231
+ })();
232
+
233
+ /**
234
+ * obj2url() takes a json-object as argument and generates
235
+ * a querystring. pretty much like jQuery.param()
236
+ *
237
+ * how to use:
238
+ *
239
+ * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
240
+ *
241
+ * will result in:
242
+ *
243
+ * `http://any.url/upload?otherParam=value&a=b&c=d`
244
+ *
245
+ * @param Object JSON-Object
246
+ * @param String current querystring-part
247
+ * @return String encoded querystring
248
+ */
249
+ qq.obj2url = function(obj, temp, prefixDone){
250
+ var uristrings = [],
251
+ prefix = '&',
252
+ add = function(nextObj, i){
253
+ var nextTemp = temp
254
+ ? (/\[\]$/.test(temp)) // prevent double-encoding
255
+ ? temp
256
+ : temp+'['+i+']'
257
+ : i;
258
+ if ((nextTemp != 'undefined') && (i != 'undefined')) {
259
+ uristrings.push(
260
+ (typeof nextObj === 'object')
261
+ ? qq.obj2url(nextObj, nextTemp, true)
262
+ : (Object.prototype.toString.call(nextObj) === '[object Function]')
263
+ ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
264
+ : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
265
+ );
266
+ }
267
+ };
268
+
269
+ if (!prefixDone && temp) {
270
+ prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
271
+ uristrings.push(temp);
272
+ uristrings.push(qq.obj2url(obj));
273
+ } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj != 'undefined') ) {
274
+ // we wont use a for-in-loop on an array (performance)
275
+ for (var i = 0, len = obj.length; i < len; ++i){
276
+ add(obj[i], i);
277
+ }
278
+ } else if ((typeof obj != 'undefined') && (obj !== null) && (typeof obj === "object")){
279
+ // for anything else but a scalar, we will use for-in-loop
280
+ for (var i in obj){
281
+ add(obj[i], i);
282
+ }
283
+ } else {
284
+ uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
285
+ }
286
+
287
+ if (temp) {
288
+ return uristrings.join(prefix);
289
+ } else {
290
+ return uristrings.join(prefix)
291
+ .replace(/^&/, '')
292
+ .replace(/%20/g, '+');
293
+ }
294
+ };
295
+
296
+ /**
297
+ * A generic module which supports object disposing in dispose() method.
298
+ * */
299
+ qq.DisposeSupport = {
300
+ _disposers: [],
301
+
302
+ /** Run all registered disposers */
303
+ dispose: function() {
304
+ var disposer;
305
+ while (disposer = this._disposers.shift()) {
306
+ disposer();
307
+ }
308
+ },
309
+
310
+ /** Add disposer to the collection */
311
+ addDisposer: function(disposeFunction) {
312
+ this._disposers.push(disposeFunction);
313
+ },
314
+
315
+ /** Attach event handler and register de-attacher as a disposer */
316
+ _attach: function() {
317
+ this.addDisposer(qq(arguments[0]).attach.apply(this, Array.prototype.slice.call(arguments, 1)));
318
+ }
319
+ };
@@ -1,5 +1,4 @@
1
1
  /*
2
- * VERSION 2.1.2
3
2
  * Original version: 1.0 © 2010 Andrew Valums ( andrew(at)valums.com )
4
3
  * Current Maintainer (2.0+): 2012, Ray Nicholus ( fineuploader(at)garstasio.com )
5
4
  *
@@ -67,7 +66,7 @@
67
66
  font-size: 16px;
68
67
  background-color: #FFF0BD;
69
68
  }
70
- .qq-upload-file, .qq-upload-spinner, .qq-upload-size, .qq-upload-cancel, .qq-upload-failed-text, .qq-upload-finished {
69
+ .qq-upload-file, .qq-upload-spinner, .qq-upload-size, .qq-upload-cancel, .qq-upload-retry, .qq-upload-failed-text, .qq-upload-finished {
71
70
  margin-right: 12px;
72
71
  }
73
72
  .qq-upload-file {
@@ -85,7 +84,17 @@
85
84
  height:15px;
86
85
  vertical-align:text-bottom;
87
86
  }
88
- .qq-upload-size, .qq-upload-cancel {
87
+ .qq-upload-retry {
88
+ display: none;
89
+ color: #000000;
90
+ }
91
+ .qq-upload-cancel {
92
+ color: #000000;
93
+ }
94
+ .qq-upload-retryable .qq-upload-retry {
95
+ display: inline;
96
+ }
97
+ .qq-upload-size, .qq-upload-cancel, .qq-upload-retry {
89
98
  font-size: 12px;
90
99
  font-weight: normal;
91
100
  }
@@ -103,6 +112,10 @@
103
112
  .qq-upload-fail .qq-upload-failed-text {
104
113
  display: inline;
105
114
  }
115
+ .qq-upload-retrying .qq-upload-failed-text {
116
+ display: inline;
117
+ color: #D60000;
118
+ }
106
119
  .qq-upload-list li.qq-upload-success {
107
120
  background-color: #5DA30C;
108
121
  color: #FFFFFF;
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fileuploader-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.2
4
+ version: 3.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-14 00:00:00.000000000 Z
12
+ date: 2012-11-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: railties
@@ -69,7 +69,15 @@ files:
69
69
  - lib/fileuploader-rails/version.rb
70
70
  - lib/fileuploader-rails.rb
71
71
  - vendor/assets/images/loading.gif
72
- - vendor/assets/javascripts/fineuploader.js
72
+ - vendor/assets/javascripts/fineuploader/button.js
73
+ - vendor/assets/javascripts/fineuploader/handler.base.js
74
+ - vendor/assets/javascripts/fineuploader/handler.form.js
75
+ - vendor/assets/javascripts/fineuploader/handler.xhr.js
76
+ - vendor/assets/javascripts/fineuploader/header.js
77
+ - vendor/assets/javascripts/fineuploader/jquery-plugin.js
78
+ - vendor/assets/javascripts/fineuploader/uploader.basic.js
79
+ - vendor/assets/javascripts/fineuploader/uploader.js
80
+ - vendor/assets/javascripts/fineuploader/util.js
73
81
  - vendor/assets/stylesheets/fineuploader.css
74
82
  - MIT-LICENSE
75
83
  - Rakefile
@@ -86,12 +94,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
86
94
  - - ! '>='
87
95
  - !ruby/object:Gem::Version
88
96
  version: '0'
97
+ segments:
98
+ - 0
99
+ hash: 3723958291510347633
89
100
  required_rubygems_version: !ruby/object:Gem::Requirement
90
101
  none: false
91
102
  requirements:
92
103
  - - ! '>='
93
104
  - !ruby/object:Gem::Version
94
105
  version: '0'
106
+ segments:
107
+ - 0
108
+ hash: 3723958291510347633
95
109
  requirements: []
96
110
  rubyforge_project: fileuploader-rails
97
111
  rubygems_version: 1.8.24
@@ -1,1642 +0,0 @@
1
- /**
2
- * http://github.com/Valums-File-Uploader/file-uploader
3
- *
4
- * Multiple file upload component with progress-bar, drag-and-drop.
5
- *
6
- * Have ideas for improving this JS for the general community?
7
- * Submit your changes at: https://github.com/Valums-File-Uploader/file-uploader
8
- * Readme at https://github.com/valums/file-uploader/blob/2.1.2/readme.md
9
- *
10
- * VERSION 2.1.2
11
- * Original version: 1.0 © 2010 Andrew Valums ( andrew(at)valums.com )
12
- * Current Maintainer (2.0+): © 2012, Ray Nicholus ( fineuploader(at)garstasio.com )
13
- *
14
- * Licensed under MIT license, GNU GPL 2 or later, GNU LGPL 2 or later, see license.txt.
15
- */
16
-
17
- //
18
- // Helper functions
19
- //
20
-
21
- var qq = qq || {};
22
-
23
- /**
24
- * Adds all missing properties from second obj to first obj
25
- */
26
- qq.extend = function(first, second){
27
- for (var prop in second){
28
- first[prop] = second[prop];
29
- }
30
- };
31
-
32
- /**
33
- * Searches for a given element in the array, returns -1 if it is not present.
34
- * @param {Number} [from] The index at which to begin the search
35
- */
36
- qq.indexOf = function(arr, elt, from){
37
- if (arr.indexOf) return arr.indexOf(elt, from);
38
-
39
- from = from || 0;
40
- var len = arr.length;
41
-
42
- if (from < 0) from += len;
43
-
44
- for (; from < len; from++){
45
- if (from in arr && arr[from] === elt){
46
- return from;
47
- }
48
- }
49
- return -1;
50
- };
51
-
52
- qq.getUniqueId = (function(){
53
- var id = 0;
54
- return function(){ return id++; };
55
- })();
56
-
57
- //
58
- // Browsers and platforms detection
59
-
60
- qq.ie = function(){ return navigator.userAgent.indexOf('MSIE') != -1; }
61
- qq.safari = function(){ return navigator.vendor != undefined && navigator.vendor.indexOf("Apple") != -1; }
62
- qq.chrome = function(){ return navigator.vendor != undefined && navigator.vendor.indexOf('Google') != -1; }
63
- qq.firefox = function(){ return (navigator.userAgent.indexOf('Mozilla') != -1 && navigator.vendor != undefined && navigator.vendor == ''); }
64
- qq.windows = function(){ return navigator.platform == "Win32"; }
65
-
66
- //
67
- // Events
68
-
69
- /** Returns the function which detaches attached event */
70
- qq.attach = function(element, type, fn){
71
- if (element.addEventListener){
72
- element.addEventListener(type, fn, false);
73
- } else if (element.attachEvent){
74
- element.attachEvent('on' + type, fn);
75
- }
76
- return function() {
77
- qq.detach(element, type, fn)
78
- }
79
- };
80
- qq.detach = function(element, type, fn){
81
- if (element.removeEventListener){
82
- element.removeEventListener(type, fn, false);
83
- } else if (element.attachEvent){
84
- element.detachEvent('on' + type, fn);
85
- }
86
- };
87
-
88
- qq.preventDefault = function(e){
89
- if (e.preventDefault){
90
- e.preventDefault();
91
- } else{
92
- e.returnValue = false;
93
- }
94
- };
95
-
96
- //
97
- // Node manipulations
98
-
99
- /**
100
- * Insert node a before node b.
101
- */
102
- qq.insertBefore = function(a, b){
103
- b.parentNode.insertBefore(a, b);
104
- };
105
- qq.remove = function(element){
106
- element.parentNode.removeChild(element);
107
- };
108
-
109
- qq.contains = function(parent, descendant){
110
- // compareposition returns false in this case
111
- if (parent == descendant) return true;
112
-
113
- if (parent.contains){
114
- return parent.contains(descendant);
115
- } else {
116
- return !!(descendant.compareDocumentPosition(parent) & 8);
117
- }
118
- };
119
-
120
- /**
121
- * Creates and returns element from html string
122
- * Uses innerHTML to create an element
123
- */
124
- qq.toElement = (function(){
125
- var div = document.createElement('div');
126
- return function(html){
127
- div.innerHTML = html;
128
- var element = div.firstChild;
129
- div.removeChild(element);
130
- return element;
131
- };
132
- })();
133
-
134
- //
135
- // Node properties and attributes
136
-
137
- /**
138
- * Sets styles for an element.
139
- * Fixes opacity in IE6-8.
140
- */
141
- qq.css = function(element, styles){
142
- if (styles.opacity != null){
143
- if (typeof element.style.opacity != 'string' && typeof(element.filters) != 'undefined'){
144
- styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
145
- }
146
- }
147
- qq.extend(element.style, styles);
148
- };
149
- qq.hasClass = function(element, name){
150
- var re = new RegExp('(^| )' + name + '( |$)');
151
- return re.test(element.className);
152
- };
153
- qq.addClass = function(element, name){
154
- if (!qq.hasClass(element, name)){
155
- element.className += ' ' + name;
156
- }
157
- };
158
- qq.removeClass = function(element, name){
159
- var re = new RegExp('(^| )' + name + '( |$)');
160
- element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
161
- };
162
- qq.setText = function(element, text){
163
- element.innerText = text;
164
- element.textContent = text;
165
- };
166
-
167
- //
168
- // Selecting elements
169
-
170
- qq.children = function(element){
171
- var children = [],
172
- child = element.firstChild;
173
-
174
- while (child){
175
- if (child.nodeType == 1){
176
- children.push(child);
177
- }
178
- child = child.nextSibling;
179
- }
180
-
181
- return children;
182
- };
183
-
184
- qq.getByClass = function(element, className){
185
- if (element.querySelectorAll){
186
- return element.querySelectorAll('.' + className);
187
- }
188
-
189
- var result = [];
190
- var candidates = element.getElementsByTagName("*");
191
- var len = candidates.length;
192
-
193
- for (var i = 0; i < len; i++){
194
- if (qq.hasClass(candidates[i], className)){
195
- result.push(candidates[i]);
196
- }
197
- }
198
- return result;
199
- };
200
-
201
- /**
202
- * obj2url() takes a json-object as argument and generates
203
- * a querystring. pretty much like jQuery.param()
204
- *
205
- * how to use:
206
- *
207
- * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
208
- *
209
- * will result in:
210
- *
211
- * `http://any.url/upload?otherParam=value&a=b&c=d`
212
- *
213
- * @param Object JSON-Object
214
- * @param String current querystring-part
215
- * @return String encoded querystring
216
- */
217
- qq.obj2url = function(obj, temp, prefixDone){
218
- var uristrings = [],
219
- prefix = '&',
220
- add = function(nextObj, i){
221
- var nextTemp = temp
222
- ? (/\[\]$/.test(temp)) // prevent double-encoding
223
- ? temp
224
- : temp+'['+i+']'
225
- : i;
226
- if ((nextTemp != 'undefined') && (i != 'undefined')) {
227
- uristrings.push(
228
- (typeof nextObj === 'object')
229
- ? qq.obj2url(nextObj, nextTemp, true)
230
- : (Object.prototype.toString.call(nextObj) === '[object Function]')
231
- ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
232
- : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
233
- );
234
- }
235
- };
236
-
237
- if (!prefixDone && temp) {
238
- prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
239
- uristrings.push(temp);
240
- uristrings.push(qq.obj2url(obj));
241
- } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj != 'undefined') ) {
242
- // we wont use a for-in-loop on an array (performance)
243
- for (var i = 0, len = obj.length; i < len; ++i){
244
- add(obj[i], i);
245
- }
246
- } else if ((typeof obj != 'undefined') && (obj !== null) && (typeof obj === "object")){
247
- // for anything else but a scalar, we will use for-in-loop
248
- for (var i in obj){
249
- add(obj[i], i);
250
- }
251
- } else {
252
- uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
253
- }
254
-
255
- if (temp) {
256
- return uristrings.join(prefix);
257
- } else {
258
- return uristrings.join(prefix)
259
- .replace(/^&/, '')
260
- .replace(/%20/g, '+');
261
- }
262
- };
263
-
264
- //
265
- //
266
- // Uploader Classes
267
- //
268
- //
269
-
270
- var qq = qq || {};
271
-
272
- /**
273
- * Creates upload button, validates upload, but doesn't create file list or dd.
274
- */
275
- qq.FileUploaderBasic = function(o){
276
- var that = this;
277
- this._options = {
278
- // set to true to see the server response
279
- debug: false,
280
- action: '/server/upload',
281
- params: {},
282
- customHeaders: {},
283
- button: null,
284
- multiple: true,
285
- maxConnections: 3,
286
- disableCancelForFormUploads: false,
287
- autoUpload: true,
288
- forceMultipart: false,
289
- // validation
290
- allowedExtensions: [],
291
- acceptFiles: null, // comma separated string of mime-types for browser to display in browse dialog
292
- sizeLimit: 0,
293
- minSizeLimit: 0,
294
- stopOnFirstInvalidFile: true,
295
- // events
296
- // return false to cancel submit
297
- onSubmit: function(id, fileName){},
298
- onComplete: function(id, fileName, responseJSON){},
299
- onCancel: function(id, fileName){},
300
- onUpload: function(id, fileName, xhr){},
301
- onProgress: function(id, fileName, loaded, total){},
302
- onError: function(id, fileName, reason) {},
303
- // messages
304
- messages: {
305
- typeError: "{file} has an invalid extension. Valid extension(s): {extensions}.",
306
- sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
307
- minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
308
- emptyError: "{file} is empty, please select files again without it.",
309
- noFilesError: "No files to upload.",
310
- onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
311
- },
312
- showMessage: function(message){
313
- alert(message);
314
- },
315
- inputName: 'qqfile'
316
- };
317
- qq.extend(this._options, o);
318
- this._wrapCallbacks();
319
- qq.extend(this, qq.DisposeSupport);
320
-
321
- // number of files being uploaded
322
- this._filesInProgress = 0;
323
-
324
- this._storedFileIds = [];
325
-
326
- this._handler = this._createUploadHandler();
327
-
328
- if (this._options.button){
329
- this._button = this._createUploadButton(this._options.button);
330
- }
331
-
332
- this._preventLeaveInProgress();
333
- };
334
-
335
- qq.FileUploaderBasic.prototype = {
336
- log: function(str){
337
- if (this._options.debug && window.console) console.log('[uploader] ' + str);
338
- },
339
- setParams: function(params){
340
- this._options.params = params;
341
- },
342
- getInProgress: function(){
343
- return this._filesInProgress;
344
- },
345
- uploadStoredFiles: function(){
346
- while(this._storedFileIds.length) {
347
- this._filesInProgress++;
348
- this._handler.upload(this._storedFileIds.shift(), this._options.params);
349
- }
350
- },
351
- clearStoredFiles: function(){
352
- this._storedFileIds = [];
353
- },
354
- _createUploadButton: function(element){
355
- var self = this;
356
-
357
- var button = new qq.UploadButton({
358
- element: element,
359
- multiple: this._options.multiple && qq.UploadHandlerXhr.isSupported(),
360
- acceptFiles: this._options.acceptFiles,
361
- onChange: function(input){
362
- self._onInputChange(input);
363
- }
364
- });
365
-
366
- this.addDisposer(function() { button.dispose(); });
367
- return button;
368
- },
369
- _createUploadHandler: function(){
370
- var self = this,
371
- handlerClass;
372
-
373
- if(qq.UploadHandlerXhr.isSupported()){
374
- handlerClass = 'UploadHandlerXhr';
375
- } else {
376
- handlerClass = 'UploadHandlerForm';
377
- }
378
-
379
- var handler = new qq[handlerClass]({
380
- debug: this._options.debug,
381
- action: this._options.action,
382
- forceMultipart: this._options.forceMultipart,
383
- maxConnections: this._options.maxConnections,
384
- customHeaders: this._options.customHeaders,
385
- inputName: this._options.inputName,
386
- demoMode: this._options.demoMode,
387
- onProgress: function(id, fileName, loaded, total){
388
- self._onProgress(id, fileName, loaded, total);
389
- self._options.onProgress(id, fileName, loaded, total);
390
- },
391
- onComplete: function(id, fileName, result){
392
- self._onComplete(id, fileName, result);
393
- self._options.onComplete(id, fileName, result);
394
- },
395
- onCancel: function(id, fileName){
396
- self._onCancel(id, fileName);
397
- self._options.onCancel(id, fileName);
398
- },
399
- onError: self._options.onError,
400
- onUpload: function(id, fileName, xhr){
401
- self._onUpload(id, fileName, xhr);
402
- self._options.onUpload(id, fileName, xhr);
403
- }
404
- });
405
-
406
- return handler;
407
- },
408
- _preventLeaveInProgress: function(){
409
- var self = this;
410
-
411
- this._attach(window, 'beforeunload', function(e){
412
- if (!self._filesInProgress){return;}
413
-
414
- var e = e || window.event;
415
- // for ie, ff
416
- e.returnValue = self._options.messages.onLeave;
417
- // for webkit
418
- return self._options.messages.onLeave;
419
- });
420
- },
421
- _onSubmit: function(id, fileName){
422
- if (this._options.autoUpload) {
423
- this._filesInProgress++;
424
- }
425
- },
426
- _onProgress: function(id, fileName, loaded, total){
427
- },
428
- _onComplete: function(id, fileName, result){
429
- this._filesInProgress--;
430
-
431
- if (!result.success){
432
- var errorReason = result.error ? result.error : "Upload failure reason unknown";
433
- this._options.onError(id, fileName, errorReason);
434
- }
435
- },
436
- _onCancel: function(id, fileName){
437
- var storedFileIndex = qq.indexOf(this._storedFileIds, id);
438
- if (this._options.autoUpload || storedFileIndex < 0) {
439
- this._filesInProgress--;
440
- }
441
- else if (!this._options.autoUpload) {
442
- this._storedFileIds.splice(storedFileIndex, 1);
443
- }
444
- },
445
- _onUpload: function(id, fileName, xhr){
446
- },
447
- _onInputChange: function(input){
448
- if (this._handler instanceof qq.UploadHandlerXhr){
449
- this._uploadFileList(input.files);
450
- } else {
451
- if (this._validateFile(input)){
452
- this._uploadFile(input);
453
- }
454
- }
455
- this._button.reset();
456
- },
457
- _uploadFileList: function(files){
458
- if (files.length > 0) {
459
- for (var i=0; i<files.length; i++){
460
- if (this._validateFile(files[i])){
461
- this._uploadFile(files[i]);
462
- } else {
463
- if (this._options.stopOnFirstInvalidFile){
464
- return;
465
- }
466
- }
467
- }
468
- }
469
- else {
470
- this._error('noFilesError', "");
471
- }
472
- },
473
- _uploadFile: function(fileContainer){
474
- var id = this._handler.add(fileContainer);
475
- var fileName = this._handler.getName(id);
476
-
477
- if (this._options.onSubmit(id, fileName) !== false){
478
- this._onSubmit(id, fileName);
479
- if (this._options.autoUpload) {
480
- this._handler.upload(id, this._options.params);
481
- }
482
- else {
483
- this._storeFileForLater(id);
484
- }
485
- }
486
- },
487
- _storeFileForLater: function(id) {
488
- this._storedFileIds.push(id);
489
- },
490
- _validateFile: function(file){
491
- var name, size;
492
-
493
- if (file.value){
494
- // it is a file input
495
- // get input value and remove path to normalize
496
- name = file.value.replace(/.*(\/|\\)/, "");
497
- } else {
498
- // fix missing properties in Safari 4 and firefox 11.0a2
499
- name = (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
500
- size = (file.fileSize !== null && file.fileSize !== undefined) ? file.fileSize : file.size;
501
- }
502
-
503
- if (! this._isAllowedExtension(name)){
504
- this._error('typeError', name);
505
- return false;
506
-
507
- } else if (size === 0){
508
- this._error('emptyError', name);
509
- return false;
510
-
511
- } else if (size && this._options.sizeLimit && size > this._options.sizeLimit){
512
- this._error('sizeError', name);
513
- return false;
514
-
515
- } else if (size && size < this._options.minSizeLimit){
516
- this._error('minSizeError', name);
517
- return false;
518
- }
519
-
520
- return true;
521
- },
522
- _error: function(code, fileName){
523
- var message = this._options.messages[code];
524
- function r(name, replacement){ message = message.replace(name, replacement); }
525
-
526
- var extensions = this._options.allowedExtensions.join(', ');
527
-
528
- r('{file}', this._formatFileName(fileName));
529
- r('{extensions}', extensions);
530
- r('{sizeLimit}', this._formatSize(this._options.sizeLimit));
531
- r('{minSizeLimit}', this._formatSize(this._options.minSizeLimit));
532
-
533
- this._options.onError(null, fileName, message);
534
- this._options.showMessage(message);
535
- },
536
- _formatFileName: function(name){
537
- if (name.length > 33){
538
- name = name.slice(0, 19) + '...' + name.slice(-13);
539
- }
540
- return name;
541
- },
542
- _isAllowedExtension: function(fileName){
543
- var ext = (-1 !== fileName.indexOf('.'))
544
- ? fileName.replace(/.*[.]/, '').toLowerCase()
545
- : '';
546
- var allowed = this._options.allowedExtensions;
547
-
548
- if (!allowed.length){return true;}
549
-
550
- for (var i=0; i<allowed.length; i++){
551
- if (allowed[i].toLowerCase() == ext){ return true;}
552
- }
553
-
554
- return false;
555
- },
556
- _formatSize: function(bytes){
557
- var i = -1;
558
- do {
559
- bytes = bytes / 1024;
560
- i++;
561
- } while (bytes > 99);
562
-
563
- return Math.max(bytes, 0.1).toFixed(1) + ['kB', 'MB', 'GB', 'TB', 'PB', 'EB'][i];
564
- },
565
- _wrapCallbacks: function() {
566
- var self, safeCallback;
567
-
568
- self = this;
569
-
570
- safeCallback = function(callback, args) {
571
- try {
572
- return callback.apply(self, args);
573
- }
574
- catch (exception) {
575
- self.log("Caught " + exception + " in callback: " + callback);
576
- }
577
- }
578
-
579
- for (var prop in this._options) {
580
- if (/^on[A-Z]/.test(prop)) {
581
- (function() {
582
- var oldCallback = self._options[prop];
583
- self._options[prop] = function() {
584
- return safeCallback(oldCallback, arguments);
585
- }
586
- }());
587
- }
588
- }
589
- }
590
- };
591
-
592
-
593
- /**
594
- * Class that creates upload widget with drag-and-drop and file list
595
- * @inherits qq.FileUploaderBasic
596
- */
597
- qq.FileUploader = function(o){
598
- // call parent constructor
599
- qq.FileUploaderBasic.apply(this, arguments);
600
-
601
- // additional options
602
- qq.extend(this._options, {
603
- element: null,
604
- // if set, will be used instead of qq-upload-list in template
605
- listElement: null,
606
- dragText: 'Drop files here to upload',
607
- extraDropzones : [],
608
- hideDropzones : true,
609
- disableDefaultDropzone: false,
610
- uploadButtonText: 'Upload a file',
611
- cancelButtonText: 'Cancel',
612
- failUploadText: 'Upload failed',
613
-
614
- template: '<div class="qq-uploader">' +
615
- (!this._options.disableDefaultDropzone ? '<div class="qq-upload-drop-area"><span>{dragText}</span></div>' : '') +
616
- (!this._options.button ? '<div class="qq-upload-button">{uploadButtonText}</div>' : '') +
617
- (!this._options.listElement ? '<ul class="qq-upload-list"></ul>' : '') +
618
- '</div>',
619
-
620
- // template for one item in file list
621
- fileTemplate: '<li>' +
622
- '<div class="qq-progress-bar"></div>' +
623
- '<span class="qq-upload-spinner"></span>' +
624
- '<span class="qq-upload-finished"></span>' +
625
- '<span class="qq-upload-file"></span>' +
626
- '<span class="qq-upload-size"></span>' +
627
- '<a class="qq-upload-cancel" href="#">{cancelButtonText}</a>' +
628
- '<span class="qq-upload-failed-text">{failUploadtext}</span>' +
629
- '</li>',
630
-
631
- classes: {
632
- // used to get elements from templates
633
- button: 'qq-upload-button',
634
- drop: 'qq-upload-drop-area',
635
- dropActive: 'qq-upload-drop-area-active',
636
- dropDisabled: 'qq-upload-drop-area-disabled',
637
- list: 'qq-upload-list',
638
- progressBar: 'qq-progress-bar',
639
- file: 'qq-upload-file',
640
- spinner: 'qq-upload-spinner',
641
- finished: 'qq-upload-finished',
642
- size: 'qq-upload-size',
643
- cancel: 'qq-upload-cancel',
644
- failText: 'qq-upload-failed-text',
645
-
646
- // added to list item <li> when upload completes
647
- // used in css to hide progress spinner
648
- success: 'qq-upload-success',
649
- fail: 'qq-upload-fail',
650
-
651
- successIcon: null,
652
- failIcon: null
653
- },
654
- extraMessages: {
655
- formatProgress: "{percent}% of {total_size}",
656
- tooManyFilesError: "You may only drop one file"
657
- },
658
- failedUploadTextDisplay: {
659
- mode: 'default', //default, custom, or none
660
- maxChars: 50,
661
- responseProperty: 'error',
662
- enableTooltip: true
663
- }
664
- });
665
- // overwrite options with user supplied
666
- qq.extend(this._options, o);
667
- this._wrapCallbacks();
668
-
669
- qq.extend(this._options.messages, this._options.extraMessages);
670
-
671
- // overwrite the upload button text if any
672
- // same for the Cancel button and Fail message text
673
- this._options.template = this._options.template.replace(/\{dragText\}/g, this._options.dragText);
674
- this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.uploadButtonText);
675
- this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.cancelButtonText);
676
- this._options.fileTemplate = this._options.fileTemplate.replace(/\{failUploadtext\}/g, this._options.failUploadText);
677
-
678
- this._element = this._options.element;
679
- this._element.innerHTML = this._options.template;
680
- this._listElement = this._options.listElement || this._find(this._element, 'list');
681
-
682
- this._classes = this._options.classes;
683
-
684
- if (!this._button) {
685
- this._button = this._createUploadButton(this._find(this._element, 'button'));
686
- }
687
-
688
- this._bindCancelEvent();
689
- this._setupDragDrop();
690
- };
691
-
692
- // inherit from Basic Uploader
693
- qq.extend(qq.FileUploader.prototype, qq.FileUploaderBasic.prototype);
694
-
695
- qq.extend(qq.FileUploader.prototype, {
696
- clearStoredFiles: function() {
697
- qq.FileUploaderBasic.prototype.clearStoredFiles.apply(this, arguments);
698
- this._listElement.innerHTML = "";
699
- },
700
- addExtraDropzone: function(element){
701
- this._setupExtraDropzone(element);
702
- },
703
- removeExtraDropzone: function(element){
704
- var dzs = this._options.extraDropzones;
705
- for(var i in dzs) if (dzs[i] === element) return this._options.extraDropzones.splice(i,1);
706
- },
707
- _leaving_document_out: function(e){
708
- return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
709
- || (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
710
- },
711
- _storeFileForLater: function(id) {
712
- qq.FileUploaderBasic.prototype._storeFileForLater.apply(this, arguments);
713
- var item = this._getItemByFileId(id);
714
- this._find(item, 'spinner').style.display = "none";
715
- },
716
- /**
717
- * Gets one of the elements listed in this._options.classes
718
- **/
719
- _find: function(parent, type){
720
- var element = qq.getByClass(parent, this._options.classes[type])[0];
721
- if (!element){
722
- throw new Error('element not found ' + type);
723
- }
724
-
725
- return element;
726
- },
727
- _setupExtraDropzone: function(element){
728
- this._options.extraDropzones.push(element);
729
- this._setupDropzone(element);
730
- },
731
- _setupDropzone: function(dropArea){
732
- var self = this;
733
-
734
- var dz = new qq.UploadDropZone({
735
- element: dropArea,
736
- onEnter: function(e){
737
- qq.addClass(dropArea, self._classes.dropActive);
738
- e.stopPropagation();
739
- },
740
- onLeave: function(e){
741
- //e.stopPropagation();
742
- },
743
- onLeaveNotDescendants: function(e){
744
- qq.removeClass(dropArea, self._classes.dropActive);
745
- },
746
- onDrop: function(e){
747
- if (self._options.hideDropzones) {
748
- dropArea.style.display = 'none';
749
- }
750
- qq.removeClass(dropArea, self._classes.dropActive);
751
- if (e.dataTransfer.files.length > 1 && !self._options.multiple) {
752
- self._error('tooManyFilesError', "");
753
- }
754
- else {
755
- self._uploadFileList(e.dataTransfer.files);
756
- }
757
- }
758
- });
759
-
760
- this.addDisposer(function() { dz.dispose(); });
761
-
762
- if (this._options.hideDropzones) {
763
- dropArea.style.display = 'none';
764
- }
765
- },
766
- _setupDragDrop: function(){
767
- var self = this;
768
-
769
- if (!this._options.disableDefaultDropzone) {
770
- var dropArea = this._find(this._element, 'drop');
771
- this._options.extraDropzones.push(dropArea);
772
- }
773
-
774
- var dropzones = this._options.extraDropzones;
775
- var i;
776
- for (i=0; i < dropzones.length; i++){
777
- this._setupDropzone(dropzones[i]);
778
- }
779
-
780
- // IE <= 9 does not support the File API used for drag+drop uploads
781
- // Any volunteers to enable & test this for IE10?
782
- if (!this._options.disableDefaultDropzone && !qq.ie()) {
783
- this._attach(document, 'dragenter', function(e){
784
- if (qq.hasClass(dropArea, self._classes.dropDisabled)) return;
785
-
786
- dropArea.style.display = 'block';
787
- for (i=0; i < dropzones.length; i++){ dropzones[i].style.display = 'block'; }
788
-
789
- });
790
- }
791
- this._attach(document, 'dragleave', function(e){
792
- // only fire when leaving document out
793
- if (self._options.hideDropzones && qq.FileUploader.prototype._leaving_document_out(e)) {
794
- for (i=0; i < dropzones.length; i++) {
795
- dropzones[i].style.display = 'none';
796
- }
797
- }
798
- });
799
- qq.attach(document, 'drop', function(e){
800
- if (self._options.hideDropzones) {
801
- for (i=0; i < dropzones.length; i++){
802
- dropzones[i].style.display = 'none';
803
- }
804
- }
805
- e.preventDefault();
806
- });
807
- },
808
- _onSubmit: function(id, fileName){
809
- qq.FileUploaderBasic.prototype._onSubmit.apply(this, arguments);
810
- this._addToList(id, fileName);
811
- },
812
- // Update the progress bar & percentage as the file is uploaded
813
- _onProgress: function(id, fileName, loaded, total){
814
- qq.FileUploaderBasic.prototype._onProgress.apply(this, arguments);
815
-
816
- var item = this._getItemByFileId(id);
817
-
818
- if (loaded === total) {
819
- var cancelLink = this._find(item, 'cancel');
820
- cancelLink.style.display = 'none';
821
- }
822
-
823
- var size = this._find(item, 'size');
824
- size.style.display = 'inline';
825
-
826
- var text;
827
- var percent = Math.round(loaded / total * 100);
828
-
829
- if (loaded != total) {
830
- // If still uploading, display percentage
831
- text = this._formatProgress(loaded, total);
832
- } else {
833
- // If complete, just display final size
834
- text = this._formatSize(total);
835
- }
836
-
837
- // Update progress bar <span> tag
838
- this._find(item, 'progressBar').style.width = percent + '%';
839
-
840
- qq.setText(size, text);
841
- },
842
- _onComplete: function(id, fileName, result){
843
- qq.FileUploaderBasic.prototype._onComplete.apply(this, arguments);
844
-
845
- var item = this._getItemByFileId(id);
846
-
847
- qq.remove(this._find(item, 'progressBar'));
848
-
849
- if (!this._options.disableCancelForFormUploads || qq.UploadHandlerXhr.isSupported()) {
850
- qq.remove(this._find(item, 'cancel'));
851
- }
852
- qq.remove(this._find(item, 'spinner'));
853
-
854
- if (result.success){
855
- qq.addClass(item, this._classes.success);
856
- if (this._classes.successIcon) {
857
- this._find(item, 'finished').style.display = "inline-block";
858
- qq.addClass(item, this._classes.successIcon)
859
- }
860
- } else {
861
- qq.addClass(item, this._classes.fail);
862
- if (this._classes.failIcon) {
863
- this._find(item, 'finished').style.display = "inline-block";
864
- qq.addClass(item, this._classes.failIcon)
865
- }
866
- this._controlFailureTextDisplay(item, result);
867
- }
868
- },
869
- _onUpload: function(id, fileName, xhr){
870
- qq.FileUploaderBasic.prototype._onUpload.apply(this, arguments);
871
-
872
- var item = this._getItemByFileId(id);
873
-
874
- if (qq.UploadHandlerXhr.isSupported()) {
875
- this._find(item, 'progressBar').style.display = "block";
876
- }
877
-
878
- var spinnerEl = this._find(item, 'spinner');
879
- if (spinnerEl.style.display == "none") {
880
- spinnerEl.style.display = "inline-block";
881
- }
882
- },
883
- _addToList: function(id, fileName){
884
- var item = qq.toElement(this._options.fileTemplate);
885
- if (this._options.disableCancelForFormUploads && !qq.UploadHandlerXhr.isSupported()) {
886
- var cancelLink = this._find(item, 'cancel');
887
- qq.remove(cancelLink);
888
- }
889
-
890
- item.qqFileId = id;
891
-
892
- var fileElement = this._find(item, 'file');
893
- qq.setText(fileElement, this._formatFileName(fileName));
894
- this._find(item, 'size').style.display = 'none';
895
- if (!this._options.multiple) this._clearList();
896
- this._listElement.appendChild(item);
897
- },
898
- _clearList: function(){
899
- this._listElement.innerHTML = '';
900
- this.clearStoredFiles();
901
- },
902
- _getItemByFileId: function(id){
903
- var item = this._listElement.firstChild;
904
-
905
- // there can't be txt nodes in dynamically created list
906
- // and we can use nextSibling
907
- while (item){
908
- if (item.qqFileId == id) return item;
909
- item = item.nextSibling;
910
- }
911
- },
912
- /**
913
- * delegate click event for cancel link
914
- **/
915
- _bindCancelEvent: function(){
916
- var self = this,
917
- list = this._listElement;
918
-
919
- this._attach(list, 'click', function(e){
920
- e = e || window.event;
921
- var target = e.target || e.srcElement;
922
-
923
- if (qq.hasClass(target, self._classes.cancel)){
924
- qq.preventDefault(e);
925
-
926
- var item = target.parentNode;
927
- while(item.qqFileId == undefined) {
928
- item = target = target.parentNode;
929
- }
930
-
931
- self._handler.cancel(item.qqFileId);
932
- qq.remove(item);
933
- }
934
- });
935
- },
936
- _formatProgress: function (uploadedSize, totalSize) {
937
- var message = this._options.messages.formatProgress;
938
- function r(name, replacement) { message = message.replace(name, replacement); }
939
-
940
- r('{percent}', Math.round(uploadedSize / totalSize * 100));
941
- r('{total_size}', this._formatSize(totalSize));
942
- return message;
943
- },
944
- _controlFailureTextDisplay: function(item, response) {
945
- var mode, maxChars, responseProperty, failureReason, shortFailureReason;
946
-
947
- mode = this._options.failedUploadTextDisplay.mode;
948
- maxChars = this._options.failedUploadTextDisplay.maxChars;
949
- responseProperty = this._options.failedUploadTextDisplay.responseProperty;
950
-
951
- if (mode === 'custom') {
952
- var failureReason = response[responseProperty];
953
- if (failureReason) {
954
- if (failureReason.length > maxChars) {
955
- shortFailureReason = failureReason.substring(0, maxChars) + '...';
956
- }
957
- qq.setText(this._find(item, 'failText'), shortFailureReason || failureReason);
958
-
959
- if (this._options.failedUploadTextDisplay.enableTooltip) {
960
- this._showTooltip(item, failureReason);
961
- }
962
- }
963
- else {
964
- this.log("'" + responseProperty + "' is not a valid property on the server response.");
965
- }
966
- }
967
- else if (mode === 'none') {
968
- qq.remove(this._find(item, 'failText'));
969
- }
970
- else if (mode !== 'default') {
971
- this.log("failedUploadTextDisplay.mode value of '" + mode + "' is not valid");
972
- }
973
- },
974
- //TODO turn this into a real tooltip, with click trigger (so it is usable on mobile devices). See case #355 for details.
975
- _showTooltip: function(item, text) {
976
- item.title = text;
977
- }
978
- });
979
-
980
- qq.UploadDropZone = function(o){
981
- this._options = {
982
- element: null,
983
- onEnter: function(e){},
984
- onLeave: function(e){},
985
- // is not fired when leaving element by hovering descendants
986
- onLeaveNotDescendants: function(e){},
987
- onDrop: function(e){}
988
- };
989
- qq.extend(this._options, o);
990
- qq.extend(this, qq.DisposeSupport);
991
-
992
- this._element = this._options.element;
993
-
994
- this._disableDropOutside();
995
- this._attachEvents();
996
- };
997
-
998
- qq.UploadDropZone.prototype = {
999
- _dragover_should_be_canceled: function(){
1000
- return qq.safari() || (qq.firefox() && qq.windows());
1001
- },
1002
- _disableDropOutside: function(e){
1003
- // run only once for all instances
1004
- if (!qq.UploadDropZone.dropOutsideDisabled ){
1005
-
1006
- // for these cases we need to catch onDrop to reset dropArea
1007
- if (this._dragover_should_be_canceled){
1008
- qq.attach(document, 'dragover', function(e){
1009
- e.preventDefault();
1010
- });
1011
- } else {
1012
- qq.attach(document, 'dragover', function(e){
1013
- if (e.dataTransfer){
1014
- e.dataTransfer.dropEffect = 'none';
1015
- e.preventDefault();
1016
- }
1017
- });
1018
- }
1019
-
1020
- qq.UploadDropZone.dropOutsideDisabled = true;
1021
- }
1022
- },
1023
- _attachEvents: function(){
1024
- var self = this;
1025
-
1026
- self._attach(self._element, 'dragover', function(e){
1027
- if (!self._isValidFileDrag(e)) return;
1028
-
1029
- var effect = qq.ie() ? null : e.dataTransfer.effectAllowed;
1030
- if (effect == 'move' || effect == 'linkMove'){
1031
- e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
1032
- } else {
1033
- e.dataTransfer.dropEffect = 'copy'; // for Chrome
1034
- }
1035
-
1036
- e.stopPropagation();
1037
- e.preventDefault();
1038
- });
1039
-
1040
- self._attach(self._element, 'dragenter', function(e){
1041
- if (!self._isValidFileDrag(e)) return;
1042
-
1043
- self._options.onEnter(e);
1044
- });
1045
-
1046
- self._attach(self._element, 'dragleave', function(e){
1047
- if (!self._isValidFileDrag(e)) return;
1048
-
1049
- self._options.onLeave(e);
1050
-
1051
- var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
1052
- // do not fire when moving a mouse over a descendant
1053
- if (qq.contains(this, relatedTarget)) return;
1054
-
1055
- self._options.onLeaveNotDescendants(e);
1056
- });
1057
-
1058
- self._attach(self._element, 'drop', function(e){
1059
- if (!self._isValidFileDrag(e)) return;
1060
-
1061
- e.preventDefault();
1062
- self._options.onDrop(e);
1063
- });
1064
- },
1065
- _isValidFileDrag: function(e){
1066
- // e.dataTransfer currently causing IE errors
1067
- // IE9 does NOT support file API, so drag-and-drop is not possible
1068
- // IE10 should work, but currently has not been tested - any volunteers?
1069
- if (qq.ie()) return false;
1070
-
1071
- var dt = e.dataTransfer,
1072
- // do not check dt.types.contains in webkit, because it crashes safari 4
1073
- isSafari = qq.safari();
1074
-
1075
- // dt.effectAllowed is none in Safari 5
1076
- // dt.types.contains check is for firefox
1077
- return dt && dt.effectAllowed != 'none' &&
1078
- (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files')));
1079
-
1080
- }
1081
- };
1082
-
1083
- qq.UploadButton = function(o){
1084
- this._options = {
1085
- element: null,
1086
- // if set to true adds multiple attribute to file input
1087
- multiple: false,
1088
- acceptFiles: null,
1089
- // name attribute of file input
1090
- name: 'file',
1091
- onChange: function(input){},
1092
- hoverClass: 'qq-upload-button-hover',
1093
- focusClass: 'qq-upload-button-focus'
1094
- };
1095
-
1096
- qq.extend(this._options, o);
1097
- qq.extend(this, qq.DisposeSupport);
1098
-
1099
- this._element = this._options.element;
1100
-
1101
- // make button suitable container for input
1102
- qq.css(this._element, {
1103
- position: 'relative',
1104
- overflow: 'hidden',
1105
- // Make sure browse button is in the right side
1106
- // in Internet Explorer
1107
- direction: 'ltr'
1108
- });
1109
-
1110
- this._input = this._createInput();
1111
- };
1112
-
1113
- qq.UploadButton.prototype = {
1114
- /* returns file input element */
1115
- getInput: function(){
1116
- return this._input;
1117
- },
1118
- /* cleans/recreates the file input */
1119
- reset: function(){
1120
- if (this._input.parentNode){
1121
- qq.remove(this._input);
1122
- }
1123
-
1124
- qq.removeClass(this._element, this._options.focusClass);
1125
- this._input = this._createInput();
1126
- },
1127
- _createInput: function(){
1128
- var input = document.createElement("input");
1129
-
1130
- if (this._options.multiple){
1131
- input.setAttribute("multiple", "multiple");
1132
- }
1133
-
1134
- if (this._options.acceptFiles) input.setAttribute("accept", this._options.acceptFiles);
1135
-
1136
- input.setAttribute("type", "file");
1137
- input.setAttribute("name", this._options.name);
1138
-
1139
- qq.css(input, {
1140
- position: 'absolute',
1141
- // in Opera only 'browse' button
1142
- // is clickable and it is located at
1143
- // the right side of the input
1144
- right: 0,
1145
- top: 0,
1146
- fontFamily: 'Arial',
1147
- // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
1148
- fontSize: '118px',
1149
- margin: 0,
1150
- padding: 0,
1151
- cursor: 'pointer',
1152
- opacity: 0
1153
- });
1154
-
1155
- this._element.appendChild(input);
1156
-
1157
- var self = this;
1158
- this._attach(input, 'change', function(){
1159
- self._options.onChange(input);
1160
- });
1161
-
1162
- this._attach(input, 'mouseover', function(){
1163
- qq.addClass(self._element, self._options.hoverClass);
1164
- });
1165
- this._attach(input, 'mouseout', function(){
1166
- qq.removeClass(self._element, self._options.hoverClass);
1167
- });
1168
- this._attach(input, 'focus', function(){
1169
- qq.addClass(self._element, self._options.focusClass);
1170
- });
1171
- this._attach(input, 'blur', function(){
1172
- qq.removeClass(self._element, self._options.focusClass);
1173
- });
1174
-
1175
- // IE and Opera, unfortunately have 2 tab stops on file input
1176
- // which is unacceptable in our case, disable keyboard access
1177
- if (window.attachEvent){
1178
- // it is IE or Opera
1179
- input.setAttribute('tabIndex', "-1");
1180
- }
1181
-
1182
- return input;
1183
- }
1184
- };
1185
-
1186
- /**
1187
- * Class for uploading files, uploading itself is handled by child classes
1188
- */
1189
- qq.UploadHandlerAbstract = function(o){
1190
- // Default options, can be overridden by the user
1191
- this._options = {
1192
- debug: false,
1193
- action: '/upload.php',
1194
- // maximum number of concurrent uploads
1195
- maxConnections: 999,
1196
- onProgress: function(id, fileName, loaded, total){},
1197
- onComplete: function(id, fileName, response){},
1198
- onCancel: function(id, fileName){},
1199
- onUpload: function(id, fileName, xhr){}
1200
- };
1201
- qq.extend(this._options, o);
1202
-
1203
- this._queue = [];
1204
- // params for files in queue
1205
- this._params = [];
1206
- };
1207
- qq.UploadHandlerAbstract.prototype = {
1208
- log: function(str){
1209
- if (this._options.debug && window.console) console.log('[uploader] ' + str);
1210
- },
1211
- /**
1212
- * Adds file or file input to the queue
1213
- * @returns id
1214
- **/
1215
- add: function(file){},
1216
- /**
1217
- * Sends the file identified by id and additional query params to the server
1218
- */
1219
- upload: function(id, params){
1220
- var len = this._queue.push(id);
1221
-
1222
- var copy = {};
1223
- qq.extend(copy, params);
1224
- this._params[id] = copy;
1225
-
1226
- // if too many active uploads, wait...
1227
- if (len <= this._options.maxConnections){
1228
- this._upload(id, this._params[id]);
1229
- }
1230
- },
1231
- /**
1232
- * Cancels file upload by id
1233
- */
1234
- cancel: function(id){
1235
- this._cancel(id);
1236
- this._dequeue(id);
1237
- },
1238
- /**
1239
- * Cancells all uploads
1240
- */
1241
- cancelAll: function(){
1242
- for (var i=0; i<this._queue.length; i++){
1243
- this._cancel(this._queue[i]);
1244
- }
1245
- this._queue = [];
1246
- },
1247
- /**
1248
- * Returns name of the file identified by id
1249
- */
1250
- getName: function(id){},
1251
- /**
1252
- * Returns size of the file identified by id
1253
- */
1254
- getSize: function(id){},
1255
- /**
1256
- * Returns id of files being uploaded or
1257
- * waiting for their turn
1258
- */
1259
- getQueue: function(){
1260
- return this._queue;
1261
- },
1262
- /**
1263
- * Actual upload method
1264
- */
1265
- _upload: function(id){},
1266
- /**
1267
- * Actual cancel method
1268
- */
1269
- _cancel: function(id){},
1270
- /**
1271
- * Removes element from queue, starts upload of next
1272
- */
1273
- _dequeue: function(id){
1274
- var i = qq.indexOf(this._queue, id);
1275
- this._queue.splice(i, 1);
1276
-
1277
- var max = this._options.maxConnections;
1278
-
1279
- if (this._queue.length >= max && i < max){
1280
- var nextId = this._queue[max-1];
1281
- this._upload(nextId, this._params[nextId]);
1282
- }
1283
- }
1284
- };
1285
-
1286
- /**
1287
- * Class for uploading files using form and iframe
1288
- * @inherits qq.UploadHandlerAbstract
1289
- */
1290
- qq.UploadHandlerForm = function(o){
1291
- qq.UploadHandlerAbstract.apply(this, arguments);
1292
-
1293
- this._inputs = {};
1294
- this._detach_load_events = {};
1295
- };
1296
- // @inherits qq.UploadHandlerAbstract
1297
- qq.extend(qq.UploadHandlerForm.prototype, qq.UploadHandlerAbstract.prototype);
1298
-
1299
- qq.extend(qq.UploadHandlerForm.prototype, {
1300
- add: function(fileInput){
1301
- fileInput.setAttribute('name', this._options.inputName);
1302
- var id = 'qq-upload-handler-iframe' + qq.getUniqueId();
1303
-
1304
- this._inputs[id] = fileInput;
1305
-
1306
- // remove file input from DOM
1307
- if (fileInput.parentNode){
1308
- qq.remove(fileInput);
1309
- }
1310
-
1311
- return id;
1312
- },
1313
- getName: function(id){
1314
- // get input value and remove path to normalize
1315
- return this._inputs[id].value.replace(/.*(\/|\\)/, "");
1316
- },
1317
- _cancel: function(id){
1318
- this._options.onCancel(id, this.getName(id));
1319
-
1320
- delete this._inputs[id];
1321
- delete this._detach_load_events[id];
1322
-
1323
- var iframe = document.getElementById(id);
1324
- if (iframe){
1325
- // to cancel request set src to something else
1326
- // we use src="javascript:false;" because it doesn't
1327
- // trigger ie6 prompt on https
1328
- iframe.setAttribute('src', 'javascript:false;');
1329
-
1330
- qq.remove(iframe);
1331
- }
1332
- },
1333
- _upload: function(id, params){
1334
- this._options.onUpload(id, this.getName(id), false);
1335
- var input = this._inputs[id];
1336
-
1337
- if (!input){
1338
- throw new Error('file with passed id was not added, or already uploaded or cancelled');
1339
- }
1340
-
1341
- var fileName = this.getName(id);
1342
- params[this._options.inputName] = fileName;
1343
-
1344
- var iframe = this._createIframe(id);
1345
- var form = this._createForm(iframe, params);
1346
- form.appendChild(input);
1347
-
1348
- var self = this;
1349
- this._attachLoadEvent(iframe, function(){
1350
- self.log('iframe loaded');
1351
-
1352
- var response = self._getIframeContentJSON(iframe);
1353
-
1354
- self._options.onComplete(id, fileName, response);
1355
- self._dequeue(id);
1356
-
1357
- delete self._inputs[id];
1358
- // timeout added to fix busy state in FF3.6
1359
- setTimeout(function(){
1360
- self._detach_load_events[id]();
1361
- delete self._detach_load_events[id];
1362
- qq.remove(iframe);
1363
- }, 1);
1364
- });
1365
-
1366
- form.submit();
1367
- qq.remove(form);
1368
-
1369
- return id;
1370
- },
1371
- _attachLoadEvent: function(iframe, callback){
1372
- this._detach_load_events[iframe.id] = qq.attach(iframe, 'load', function(){
1373
- // when we remove iframe from dom
1374
- // the request stops, but in IE load
1375
- // event fires
1376
- if (!iframe.parentNode){
1377
- return;
1378
- }
1379
-
1380
- try {
1381
- // fixing Opera 10.53
1382
- if (iframe.contentDocument &&
1383
- iframe.contentDocument.body &&
1384
- iframe.contentDocument.body.innerHTML == "false"){
1385
- // In Opera event is fired second time
1386
- // when body.innerHTML changed from false
1387
- // to server response approx. after 1 sec
1388
- // when we upload file with iframe
1389
- return;
1390
- }
1391
- }
1392
- catch (error) {
1393
- //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
1394
- }
1395
-
1396
- callback();
1397
- });
1398
- },
1399
- /**
1400
- * Returns json object received by iframe from server.
1401
- */
1402
- _getIframeContentJSON: function(iframe){
1403
- //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
1404
- try {
1405
- // iframe.contentWindow.document - for IE<7
1406
- var doc = iframe.contentDocument ? iframe.contentDocument: iframe.contentWindow.document,
1407
- response;
1408
-
1409
- var innerHTML = doc.body.innerHTML;
1410
- this.log("converting iframe's innerHTML to JSON");
1411
- this.log("innerHTML = " + innerHTML);
1412
- //plain text response may be wrapped in <pre> tag
1413
- if (innerHTML.slice(0, 5).toLowerCase() == '<pre>' && innerHTML.slice(-6).toLowerCase() == '</pre>') {
1414
- innerHTML = doc.body.firstChild.firstChild.nodeValue;
1415
- }
1416
- response = eval("(" + innerHTML + ")");
1417
- } catch(err){
1418
- response = {success: false};
1419
- }
1420
-
1421
- return response;
1422
- },
1423
- /**
1424
- * Creates iframe with unique name
1425
- */
1426
- _createIframe: function(id){
1427
- // We can't use following code as the name attribute
1428
- // won't be properly registered in IE6, and new window
1429
- // on form submit will open
1430
- // var iframe = document.createElement('iframe');
1431
- // iframe.setAttribute('name', id);
1432
-
1433
- var iframe = qq.toElement('<iframe src="javascript:false;" name="' + id + '" />');
1434
- // src="javascript:false;" removes ie6 prompt on https
1435
-
1436
- iframe.setAttribute('id', id);
1437
-
1438
- iframe.style.display = 'none';
1439
- document.body.appendChild(iframe);
1440
-
1441
- return iframe;
1442
- },
1443
- /**
1444
- * Creates form, that will be submitted to iframe
1445
- */
1446
- _createForm: function(iframe, params){
1447
- // We can't use the following code in IE6
1448
- // var form = document.createElement('form');
1449
- // form.setAttribute('method', 'post');
1450
- // form.setAttribute('enctype', 'multipart/form-data');
1451
- // Because in this case file won't be attached to request
1452
- var protocol = this._options.demoMode ? "GET" : "POST"
1453
- var form = qq.toElement('<form method="' + protocol + '" enctype="multipart/form-data"></form>');
1454
-
1455
- var queryString = qq.obj2url(params, this._options.action);
1456
-
1457
- form.setAttribute('action', queryString);
1458
- form.setAttribute('target', iframe.name);
1459
- form.style.display = 'none';
1460
- document.body.appendChild(form);
1461
-
1462
- return form;
1463
- }
1464
- });
1465
-
1466
- /**
1467
- * Class for uploading files using xhr
1468
- * @inherits qq.UploadHandlerAbstract
1469
- */
1470
- qq.UploadHandlerXhr = function(o){
1471
- qq.UploadHandlerAbstract.apply(this, arguments);
1472
-
1473
- this._files = [];
1474
- this._xhrs = [];
1475
-
1476
- // current loaded size in bytes for each file
1477
- this._loaded = [];
1478
- };
1479
-
1480
- // static method
1481
- qq.UploadHandlerXhr.isSupported = function(){
1482
- var input = document.createElement('input');
1483
- input.type = 'file';
1484
-
1485
- return (
1486
- 'multiple' in input &&
1487
- typeof File != "undefined" &&
1488
- typeof FormData != "undefined" &&
1489
- typeof (new XMLHttpRequest()).upload != "undefined" );
1490
- };
1491
-
1492
- // @inherits qq.UploadHandlerAbstract
1493
- qq.extend(qq.UploadHandlerXhr.prototype, qq.UploadHandlerAbstract.prototype)
1494
-
1495
- qq.extend(qq.UploadHandlerXhr.prototype, {
1496
- /**
1497
- * Adds file to the queue
1498
- * Returns id to use with upload, cancel
1499
- **/
1500
- add: function(file){
1501
- if (!(file instanceof File)){
1502
- throw new Error('Passed obj in not a File (in qq.UploadHandlerXhr)');
1503
- }
1504
-
1505
- return this._files.push(file) - 1;
1506
- },
1507
- getName: function(id){
1508
- var file = this._files[id];
1509
- // fix missing name in Safari 4
1510
- //NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
1511
- return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
1512
- },
1513
- getSize: function(id){
1514
- var file = this._files[id];
1515
- return file.fileSize != null ? file.fileSize : file.size;
1516
- },
1517
- /**
1518
- * Returns uploaded bytes for file identified by id
1519
- */
1520
- getLoaded: function(id){
1521
- return this._loaded[id] || 0;
1522
- },
1523
- /**
1524
- * Sends the file identified by id and additional query params to the server
1525
- * @param {Object} params name-value string pairs
1526
- */
1527
- _upload: function(id, params){
1528
- this._options.onUpload(id, this.getName(id), true);
1529
-
1530
- var file = this._files[id],
1531
- name = this.getName(id),
1532
- size = this.getSize(id);
1533
-
1534
- this._loaded[id] = 0;
1535
-
1536
- var xhr = this._xhrs[id] = new XMLHttpRequest();
1537
- var self = this;
1538
-
1539
- xhr.upload.onprogress = function(e){
1540
- if (e.lengthComputable){
1541
- self._loaded[id] = e.loaded;
1542
- self._options.onProgress(id, name, e.loaded, e.total);
1543
- }
1544
- };
1545
-
1546
- xhr.onreadystatechange = function(){
1547
- if (xhr.readyState == 4){
1548
- self._onComplete(id, xhr);
1549
- }
1550
- };
1551
-
1552
- // build query string
1553
- params = params || {};
1554
- params[this._options.inputName] = name;
1555
- var queryString = qq.obj2url(params, this._options.action);
1556
-
1557
- var protocol = this._options.demoMode ? "GET" : "POST";
1558
- xhr.open(protocol, queryString, true);
1559
- xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
1560
- xhr.setRequestHeader("X-File-Name", encodeURIComponent(name));
1561
- xhr.setRequestHeader("Cache-Control", "no-cache");
1562
- if (this._options.forceMultipart) {
1563
- var formData = new FormData();
1564
- formData.append(this._options.inputName, file);
1565
- file = formData;
1566
- } else {
1567
- xhr.setRequestHeader("Content-Type", "application/octet-stream");
1568
- //NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
1569
- xhr.setRequestHeader("X-Mime-Type",file.type );
1570
- }
1571
- for (key in this._options.customHeaders){
1572
- xhr.setRequestHeader(key, this._options.customHeaders[key]);
1573
- };
1574
- xhr.send(file);
1575
- },
1576
- _onComplete: function(id, xhr){
1577
- "use strict";
1578
- // the request was aborted/cancelled
1579
- if (!this._files[id]) { return; }
1580
-
1581
- var name = this.getName(id);
1582
- var size = this.getSize(id);
1583
- var response; //the parsed JSON response from the server, or the empty object if parsing failed.
1584
-
1585
- this._options.onProgress(id, name, size, size);
1586
-
1587
- this.log("xhr - server response received");
1588
- this.log("responseText = " + xhr.responseText);
1589
-
1590
- try {
1591
- if (typeof JSON.parse === "function") {
1592
- response = JSON.parse(xhr.responseText);
1593
- } else {
1594
- response = eval("(" + xhr.responseText + ")");
1595
- }
1596
- } catch(err){
1597
- response = {};
1598
- }
1599
- if (xhr.status !== 200){
1600
- this._options.onError(id, name, "XHR returned response code " + xhr.status);
1601
- }
1602
- this._options.onComplete(id, name, response);
1603
-
1604
- this._xhrs[id] = null;
1605
- this._dequeue(id);
1606
- },
1607
- _cancel: function(id){
1608
- this._options.onCancel(id, this.getName(id));
1609
-
1610
- this._files[id] = null;
1611
-
1612
- if (this._xhrs[id]){
1613
- this._xhrs[id].abort();
1614
- this._xhrs[id] = null;
1615
- }
1616
- }
1617
- });
1618
-
1619
- /**
1620
- * A generic module which supports object disposing in dispose() method.
1621
- * */
1622
- qq.DisposeSupport = {
1623
- _disposers: [],
1624
-
1625
- /** Run all registered disposers */
1626
- dispose: function() {
1627
- var disposer;
1628
- while (disposer = this._disposers.shift()) {
1629
- disposer();
1630
- }
1631
- },
1632
-
1633
- /** Add disposer to the collection */
1634
- addDisposer: function(disposeFunction) {
1635
- this._disposers.push(disposeFunction);
1636
- },
1637
-
1638
- /** Attach event handler and register de-attacher as a disposer */
1639
- _attach: function() {
1640
- this.addDisposer(qq.attach.apply(this, arguments));
1641
- }
1642
- };