fine_uploader 3.1.1 → 3.4.1

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.
@@ -1,161 +1,143 @@
1
- /**
2
- * Class for uploading files using form and iframe
3
- * @inherits qq.UploadHandlerAbstract
4
- */
5
- qq.UploadHandlerForm = function(o){
6
- qq.UploadHandlerAbstract.apply(this, arguments);
7
-
8
- this._inputs = {};
9
- this._detach_load_events = {};
10
- };
11
- // @inherits qq.UploadHandlerAbstract
12
- qq.extend(qq.UploadHandlerForm.prototype, qq.UploadHandlerAbstract.prototype);
1
+ /*globals qq, document, setTimeout*/
2
+ /*globals clearTimeout*/
3
+ qq.UploadHandlerForm = function(o, uploadCompleteCallback, logCallback) {
4
+ "use strict";
13
5
 
14
- qq.extend(qq.UploadHandlerForm.prototype, {
15
- add: function(fileInput){
16
- fileInput.setAttribute('name', this._options.inputName);
17
- var id = qq.getUniqueId();
6
+ var options = o,
7
+ inputs = [],
8
+ uuids = [],
9
+ detachLoadEvents = {},
10
+ postMessageCallbackTimers = {},
11
+ uploadComplete = uploadCompleteCallback,
12
+ log = logCallback,
13
+ corsMessageReceiver = new qq.WindowReceiveMessage({log: log}),
14
+ onloadCallbacks = {},
15
+ api;
18
16
 
19
- this._inputs[id] = fileInput;
20
17
 
21
- // remove file input from DOM
22
- if (fileInput.parentNode){
23
- qq(fileInput).remove();
18
+ function detachLoadEvent(id) {
19
+ if (detachLoadEvents[id] !== undefined) {
20
+ detachLoadEvents[id]();
21
+ delete detachLoadEvents[id];
24
22
  }
23
+ }
25
24
 
26
- return id;
27
- },
28
- getName: function(id){
29
- // get input value and remove path to normalize
30
- return this._inputs[id].value.replace(/.*(\/|\\)/, "");
31
- },
32
- isValid: function(id) {
33
- return this._inputs[id] !== undefined;
34
- },
35
- reset: function() {
36
- qq.UploadHandlerAbstract.prototype.reset.apply(this, arguments);
37
- this._inputs = {};
38
- this._detach_load_events = {};
39
- },
40
- _cancel: function(id){
41
- this._options.onCancel(id, this.getName(id));
42
-
43
- delete this._inputs[id];
44
- delete this._detach_load_events[id];
45
-
46
- var iframe = document.getElementById(id);
47
- if (iframe){
48
- // to cancel request set src to something else
49
- // we use src="javascript:false;" because it doesn't
50
- // trigger ie6 prompt on https
51
- iframe.setAttribute('src', 'javascript:false;');
52
-
53
- qq(iframe).remove();
54
- }
55
- },
56
- _upload: function(id){
57
- this._options.onUpload(id, this.getName(id), false);
58
- var input = this._inputs[id];
25
+ function registerPostMessageCallback(iframe, callback) {
26
+ var id = iframe.id;
59
27
 
60
- if (!input){
61
- throw new Error('file with passed id was not added, or already uploaded or cancelled');
62
- }
28
+ onloadCallbacks[uuids[id]] = callback;
63
29
 
64
- var fileName = this.getName(id);
30
+ detachLoadEvents[id] = qq(iframe).attach('load', function() {
31
+ if (inputs[id]) {
32
+ log("Received iframe load event for CORS upload request (file id " + id + ")");
65
33
 
66
- var iframe = this._createIframe(id);
67
- var form = this._createForm(iframe, this._options.paramsStore.getParams(id));
68
- form.appendChild(input);
34
+ postMessageCallbackTimers[id] = setTimeout(function() {
35
+ var errorMessage = "No valid message received from loaded iframe for file id " + id;
36
+ log(errorMessage, "error");
37
+ callback({
38
+ error: errorMessage
39
+ });
40
+ }, 1000);
41
+ }
42
+ });
69
43
 
70
- var self = this;
71
- this._attachLoadEvent(iframe, function(){
72
- self.log('iframe loaded');
44
+ corsMessageReceiver.receiveMessage(id, function(message) {
45
+ log("Received the following window message: '" + message + "'");
46
+ var response = qq.parseJson(message),
47
+ uuid = response.uuid,
48
+ onloadCallback;
73
49
 
74
- var response = self._getIframeContentJSON(iframe);
50
+ if (uuid && onloadCallbacks[uuid]) {
51
+ clearTimeout(postMessageCallbackTimers[id]);
52
+ delete postMessageCallbackTimers[id];
75
53
 
76
- // timeout added to fix busy state in FF3.6
77
- setTimeout(function(){
78
- self._detach_load_events[id]();
79
- delete self._detach_load_events[id];
80
- qq(iframe).remove();
81
- }, 1);
54
+ detachLoadEvent(id);
82
55
 
83
- if (!response.success) {
84
- if (self._options.onAutoRetry(id, fileName, response)) {
85
- return;
86
- }
56
+ onloadCallback = onloadCallbacks[uuid];
57
+
58
+ delete onloadCallbacks[uuid];
59
+ corsMessageReceiver.stopReceivingMessages(id);
60
+ onloadCallback(response);
61
+ }
62
+ else if (!uuid) {
63
+ log("'" + message + "' does not contain a UUID - ignoring.");
87
64
  }
88
- self._options.onComplete(id, fileName, response);
89
- self._dequeue(id);
90
65
  });
66
+ }
91
67
 
92
- this.log('Sending upload request for ' + id);
93
- form.submit();
94
- qq(form).remove();
95
-
96
- return id;
97
- },
98
- _attachLoadEvent: function(iframe, callback){
99
- var self = this;
100
- this._detach_load_events[iframe.id] = qq(iframe).attach('load', function(){
101
- self.log('Received response for ' + iframe.id);
102
-
103
- // when we remove iframe from dom
104
- // the request stops, but in IE load
105
- // event fires
106
- if (!iframe.parentNode){
107
- return;
108
- }
68
+ function attachLoadEvent(iframe, callback) {
69
+ /*jslint eqeq: true*/
109
70
 
110
- try {
111
- // fixing Opera 10.53
112
- if (iframe.contentDocument &&
113
- iframe.contentDocument.body &&
114
- iframe.contentDocument.body.innerHTML == "false"){
115
- // In Opera event is fired second time
116
- // when body.innerHTML changed from false
117
- // to server response approx. after 1 sec
118
- // when we upload file with iframe
71
+ if (options.cors.expected) {
72
+ registerPostMessageCallback(iframe, callback);
73
+ }
74
+ else {
75
+ detachLoadEvents[iframe.id] = qq(iframe).attach('load', function(){
76
+ log('Received response for ' + iframe.id);
77
+
78
+ // when we remove iframe from dom
79
+ // the request stops, but in IE load
80
+ // event fires
81
+ if (!iframe.parentNode){
119
82
  return;
120
83
  }
121
- }
122
- catch (error) {
123
- //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
124
- self.log('Error when attempting to access iframe during handling of upload response (' + error + ")", 'error');
125
- }
126
84
 
127
- callback();
128
- });
129
- },
85
+ try {
86
+ // fixing Opera 10.53
87
+ if (iframe.contentDocument &&
88
+ iframe.contentDocument.body &&
89
+ iframe.contentDocument.body.innerHTML == "false"){
90
+ // In Opera event is fired second time
91
+ // when body.innerHTML changed from false
92
+ // to server response approx. after 1 sec
93
+ // when we upload file with iframe
94
+ return;
95
+ }
96
+ }
97
+ catch (error) {
98
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
99
+ log('Error when attempting to access iframe during handling of upload response (' + error + ")", 'error');
100
+ }
101
+
102
+ callback();
103
+ });
104
+ }
105
+ }
106
+
130
107
  /**
131
108
  * Returns json object received by iframe from server.
132
109
  */
133
- _getIframeContentJSON: function(iframe){
110
+ function getIframeContentJson(iframe) {
111
+ /*jshint evil: true*/
112
+
113
+ var response;
114
+
134
115
  //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
135
116
  try {
136
117
  // iframe.contentWindow.document - for IE<7
137
- var doc = iframe.contentDocument ? iframe.contentDocument: iframe.contentWindow.document,
138
- response;
118
+ var doc = iframe.contentDocument || iframe.contentWindow.document,
119
+ innerHTML = doc.body.innerHTML;
139
120
 
140
- var innerHTML = doc.body.innerHTML;
141
- this.log("converting iframe's innerHTML to JSON");
142
- this.log("innerHTML = " + innerHTML);
121
+ log("converting iframe's innerHTML to JSON");
122
+ log("innerHTML = " + innerHTML);
143
123
  //plain text response may be wrapped in <pre> tag
144
124
  if (innerHTML && innerHTML.match(/^<pre/i)) {
145
125
  innerHTML = doc.body.firstChild.firstChild.nodeValue;
146
126
  }
147
- response = eval("(" + innerHTML + ")");
127
+
128
+ response = qq.parseJson(innerHTML);
148
129
  } catch(error){
149
- this.log('Error when attempting to parse form upload response (' + error + ")", 'error');
130
+ log('Error when attempting to parse form upload response (' + error + ")", 'error');
150
131
  response = {success: false};
151
132
  }
152
133
 
153
134
  return response;
154
- },
135
+ }
136
+
155
137
  /**
156
138
  * Creates iframe with unique name
157
139
  */
158
- _createIframe: function(id){
140
+ function createIframe(id){
159
141
  // We can't use following code as the name attribute
160
142
  // won't be properly registered in IE6, and new window
161
143
  // on form submit will open
@@ -163,7 +145,6 @@ qq.extend(qq.UploadHandlerForm.prototype, {
163
145
  // iframe.setAttribute('name', id);
164
146
 
165
147
  var iframe = qq.toElement('<iframe src="javascript:false;" name="' + id + '" />');
166
- // src="javascript:false;" removes ie6 prompt on https
167
148
 
168
149
  iframe.setAttribute('id', id);
169
150
 
@@ -171,22 +152,22 @@ qq.extend(qq.UploadHandlerForm.prototype, {
171
152
  document.body.appendChild(iframe);
172
153
 
173
154
  return iframe;
174
- },
155
+ }
156
+
175
157
  /**
176
158
  * Creates form, that will be submitted to iframe
177
159
  */
178
- _createForm: function(iframe, params){
179
- // We can't use the following code in IE6
180
- // var form = document.createElement('form');
181
- // form.setAttribute('method', 'post');
182
- // form.setAttribute('enctype', 'multipart/form-data');
183
- // Because in this case file won't be attached to request
184
- var protocol = this._options.demoMode ? "GET" : "POST",
160
+ function createForm(id, iframe){
161
+ var params = options.paramsStore.getParams(id),
162
+ protocol = options.demoMode ? "GET" : "POST",
185
163
  form = qq.toElement('<form method="' + protocol + '" enctype="multipart/form-data"></form>'),
186
- url = this._options.endpoint;
164
+ endpoint = options.endpointStore.getEndpoint(id),
165
+ url = endpoint;
166
+
167
+ params[options.uuidParamName] = uuids[id];
187
168
 
188
- if (!this._options.paramsInBody) {
189
- url = qq.obj2url(params, this._options.endpoint);
169
+ if (!options.paramsInBody) {
170
+ url = qq.obj2url(params, endpoint);
190
171
  }
191
172
  else {
192
173
  qq.obj2Inputs(params, form);
@@ -199,4 +180,110 @@ qq.extend(qq.UploadHandlerForm.prototype, {
199
180
 
200
181
  return form;
201
182
  }
202
- });
183
+
184
+
185
+ api = {
186
+ add: function(fileInput) {
187
+ fileInput.setAttribute('name', options.inputName);
188
+
189
+ var id = inputs.push(fileInput) - 1;
190
+ uuids[id] = qq.getUniqueId();
191
+
192
+ // remove file input from DOM
193
+ if (fileInput.parentNode){
194
+ qq(fileInput).remove();
195
+ }
196
+
197
+ return id;
198
+ },
199
+ getName: function(id) {
200
+ /*jslint regexp: true*/
201
+
202
+ if (api.isValid(id)) {
203
+ // get input value and remove path to normalize
204
+ return inputs[id].value.replace(/.*(\/|\\)/, "");
205
+ }
206
+ else {
207
+ log(id + " is not a valid item ID.", "error");
208
+ }
209
+ },
210
+ isValid: function(id) {
211
+ return inputs[id] !== undefined;
212
+ },
213
+ reset: function() {
214
+ inputs = [];
215
+ uuids = [];
216
+ detachLoadEvents = {};
217
+ },
218
+ getUuid: function(id) {
219
+ return uuids[id];
220
+ },
221
+ cancel: function(id) {
222
+ options.onCancel(id, this.getName(id));
223
+
224
+ delete inputs[id];
225
+ delete uuids[id];
226
+ delete detachLoadEvents[id];
227
+
228
+ if (options.cors.expected) {
229
+ clearTimeout(postMessageCallbackTimers[id]);
230
+ delete postMessageCallbackTimers[id];
231
+ corsMessageReceiver.stopReceivingMessages(id);
232
+ }
233
+
234
+ var iframe = document.getElementById(id);
235
+ if (iframe) {
236
+ // to cancel request set src to something else
237
+ // we use src="javascript:false;" because it doesn't
238
+ // trigger ie6 prompt on https
239
+ iframe.setAttribute('src', 'java' + String.fromCharCode(115) + 'cript:false;'); //deal with "JSLint: javascript URL" warning, which apparently cannot be turned off
240
+
241
+ qq(iframe).remove();
242
+ }
243
+ },
244
+ upload: function(id){
245
+ var input = inputs[id],
246
+ fileName = api.getName(id),
247
+ iframe = createIframe(id),
248
+ form;
249
+
250
+ if (!input){
251
+ throw new Error('file with passed id was not added, or already uploaded or cancelled');
252
+ }
253
+
254
+ options.onUpload(id, this.getName(id));
255
+
256
+ form = createForm(id, iframe);
257
+ form.appendChild(input);
258
+
259
+ attachLoadEvent(iframe, function(responseFromMessage){
260
+ log('iframe loaded');
261
+
262
+ var response = responseFromMessage ? responseFromMessage : getIframeContentJson(iframe);
263
+
264
+ detachLoadEvent(id);
265
+
266
+ //we can't remove an iframe if the iframe doesn't belong to the same domain
267
+ if (!options.cors.expected) {
268
+ qq(iframe).remove();
269
+ }
270
+
271
+ if (!response.success) {
272
+ if (options.onAutoRetry(id, fileName, response)) {
273
+ return;
274
+ }
275
+ }
276
+ options.onComplete(id, fileName, response);
277
+ uploadComplete(id);
278
+ });
279
+
280
+ log('Sending upload request for ' + id);
281
+ form.submit();
282
+ qq(form).remove();
283
+
284
+ return id;
285
+ }
286
+ };
287
+
288
+ return api;
289
+ };