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.
@@ -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
- };