Total Complexity | 269 |
Complexity/F | 2.28 |
Lines of Code | 2485 |
Function Count | 118 |
Duplicated Lines | 0 |
Ratio | 0 % |
Changes | 0 |
Complex classes like web/public/yt/jsbin/plupload.dev.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
1 | /** |
||
13 | ;(function (global, factory) { |
||
14 | var extract = function() { |
||
15 | var ctx = {}; |
||
16 | factory.apply(ctx, arguments); |
||
17 | return ctx.plupload; |
||
18 | }; |
||
19 | |||
20 | if (typeof define === "function" && define.amd) { |
||
1 ignored issue
–
show
|
|||
21 | define("plupload", ['./moxie'], extract); |
||
22 | } else if (typeof module === "object" && module.exports) { |
||
23 | module.exports = extract(require('./moxie')); |
||
24 | } else { |
||
25 | global.plupload = extract(global.moxie); |
||
26 | } |
||
27 | }(this || window, function(moxie) { |
||
28 | /** |
||
29 | * Plupload.js |
||
30 | * |
||
31 | * Copyright 2013, Moxiecode Systems AB |
||
32 | * Released under GPL License. |
||
33 | * |
||
34 | * License: http://www.plupload.com/license |
||
35 | * Contributing: http://www.plupload.com/contributing |
||
36 | */ |
||
37 | |||
38 | ;(function(exports, o, undef) { |
||
39 | |||
40 | var delay = window.setTimeout; |
||
41 | var fileFilters = {}; |
||
42 | var u = o.core.utils; |
||
43 | var Runtime = o.runtime.Runtime; |
||
44 | |||
45 | // convert plupload features to caps acceptable by mOxie |
||
46 | function normalizeCaps(settings) { |
||
47 | var features = settings.required_features, caps = {}; |
||
48 | |||
49 | function resolve(feature, value, strict) { |
||
50 | // Feature notation is deprecated, use caps (this thing here is required for backward compatibility) |
||
51 | var map = { |
||
52 | chunks: 'slice_blob', |
||
53 | jpgresize: 'send_binary_string', |
||
54 | pngresize: 'send_binary_string', |
||
55 | progress: 'report_upload_progress', |
||
56 | multi_selection: 'select_multiple', |
||
57 | dragdrop: 'drag_and_drop', |
||
58 | drop_element: 'drag_and_drop', |
||
59 | headers: 'send_custom_headers', |
||
60 | urlstream_upload: 'send_binary_string', |
||
61 | canSendBinary: 'send_binary', |
||
62 | triggerDialog: 'summon_file_dialog' |
||
63 | }; |
||
64 | |||
65 | if (map[feature]) { |
||
66 | caps[map[feature]] = value; |
||
67 | } else if (!strict) { |
||
68 | caps[feature] = value; |
||
69 | } |
||
70 | } |
||
71 | |||
72 | if (typeof(features) === 'string') { |
||
73 | plupload.each(features.split(/\s*,\s*/), function(feature) { |
||
74 | resolve(feature, true); |
||
75 | }); |
||
76 | } else if (typeof(features) === 'object') { |
||
77 | plupload.each(features, function(value, feature) { |
||
78 | resolve(feature, value); |
||
79 | }); |
||
80 | } else if (features === true) { |
||
81 | // check settings for required features |
||
82 | if (settings.chunk_size && settings.chunk_size > 0) { |
||
83 | caps.slice_blob = true; |
||
84 | } |
||
85 | |||
86 | if (!plupload.isEmptyObj(settings.resize) || settings.multipart === false) { |
||
87 | caps.send_binary_string = true; |
||
88 | } |
||
89 | |||
90 | if (settings.http_method) { |
||
91 | caps.use_http_method = settings.http_method; |
||
92 | } |
||
93 | |||
94 | plupload.each(settings, function(value, feature) { |
||
95 | resolve(feature, !!value, true); // strict check |
||
96 | }); |
||
97 | } |
||
98 | |||
99 | return caps; |
||
100 | } |
||
101 | |||
102 | /** |
||
103 | * @module plupload |
||
104 | * @static |
||
105 | */ |
||
106 | var plupload = { |
||
107 | /** |
||
108 | * Plupload version will be replaced on build. |
||
109 | * |
||
110 | * @property VERSION |
||
111 | * @for Plupload |
||
112 | * @static |
||
113 | * @final |
||
114 | */ |
||
115 | VERSION : '2.3.6', |
||
116 | |||
117 | /** |
||
118 | * The state of the queue before it has started and after it has finished |
||
119 | * |
||
120 | * @property STOPPED |
||
121 | * @static |
||
122 | * @final |
||
123 | */ |
||
124 | STOPPED : 1, |
||
125 | |||
126 | /** |
||
127 | * Upload process is running |
||
128 | * |
||
129 | * @property STARTED |
||
130 | * @static |
||
131 | * @final |
||
132 | */ |
||
133 | STARTED : 2, |
||
134 | |||
135 | /** |
||
136 | * File is queued for upload |
||
137 | * |
||
138 | * @property QUEUED |
||
139 | * @static |
||
140 | * @final |
||
141 | */ |
||
142 | QUEUED : 1, |
||
143 | |||
144 | /** |
||
145 | * File is being uploaded |
||
146 | * |
||
147 | * @property UPLOADING |
||
148 | * @static |
||
149 | * @final |
||
150 | */ |
||
151 | UPLOADING : 2, |
||
152 | |||
153 | /** |
||
154 | * File has failed to be uploaded |
||
155 | * |
||
156 | * @property FAILED |
||
157 | * @static |
||
158 | * @final |
||
159 | */ |
||
160 | FAILED : 4, |
||
161 | |||
162 | /** |
||
163 | * File has been uploaded successfully |
||
164 | * |
||
165 | * @property DONE |
||
166 | * @static |
||
167 | * @final |
||
168 | */ |
||
169 | DONE : 5, |
||
170 | |||
171 | // Error constants used by the Error event |
||
172 | |||
173 | /** |
||
174 | * Generic error for example if an exception is thrown inside Silverlight. |
||
175 | * |
||
176 | * @property GENERIC_ERROR |
||
177 | * @static |
||
178 | * @final |
||
179 | */ |
||
180 | GENERIC_ERROR : -100, |
||
181 | |||
182 | /** |
||
183 | * HTTP transport error. For example if the server produces a HTTP status other than 200. |
||
184 | * |
||
185 | * @property HTTP_ERROR |
||
186 | * @static |
||
187 | * @final |
||
188 | */ |
||
189 | HTTP_ERROR : -200, |
||
190 | |||
191 | /** |
||
192 | * Generic I/O error. For example if it wasn't possible to open the file stream on local machine. |
||
193 | * |
||
194 | * @property IO_ERROR |
||
195 | * @static |
||
196 | * @final |
||
197 | */ |
||
198 | IO_ERROR : -300, |
||
199 | |||
200 | /** |
||
201 | * @property SECURITY_ERROR |
||
202 | * @static |
||
203 | * @final |
||
204 | */ |
||
205 | SECURITY_ERROR : -400, |
||
206 | |||
207 | /** |
||
208 | * Initialization error. Will be triggered if no runtime was initialized. |
||
209 | * |
||
210 | * @property INIT_ERROR |
||
211 | * @static |
||
212 | * @final |
||
213 | */ |
||
214 | INIT_ERROR : -500, |
||
215 | |||
216 | /** |
||
217 | * File size error. If the user selects a file that is too large or is empty it will be blocked and |
||
218 | * an error of this type will be triggered. |
||
219 | * |
||
220 | * @property FILE_SIZE_ERROR |
||
221 | * @static |
||
222 | * @final |
||
223 | */ |
||
224 | FILE_SIZE_ERROR : -600, |
||
225 | |||
226 | /** |
||
227 | * File extension error. If the user selects a file that isn't valid according to the filters setting. |
||
228 | * |
||
229 | * @property FILE_EXTENSION_ERROR |
||
230 | * @static |
||
231 | * @final |
||
232 | */ |
||
233 | FILE_EXTENSION_ERROR : -601, |
||
234 | |||
235 | /** |
||
236 | * Duplicate file error. If prevent_duplicates is set to true and user selects the same file again. |
||
237 | * |
||
238 | * @property FILE_DUPLICATE_ERROR |
||
239 | * @static |
||
240 | * @final |
||
241 | */ |
||
242 | FILE_DUPLICATE_ERROR : -602, |
||
243 | |||
244 | /** |
||
245 | * Runtime will try to detect if image is proper one. Otherwise will throw this error. |
||
246 | * |
||
247 | * @property IMAGE_FORMAT_ERROR |
||
248 | * @static |
||
249 | * @final |
||
250 | */ |
||
251 | IMAGE_FORMAT_ERROR : -700, |
||
252 | |||
253 | /** |
||
254 | * While working on files runtime may run out of memory and will throw this error. |
||
255 | * |
||
256 | * @since 2.1.2 |
||
257 | * @property MEMORY_ERROR |
||
258 | * @static |
||
259 | * @final |
||
260 | */ |
||
261 | MEMORY_ERROR : -701, |
||
262 | |||
263 | /** |
||
264 | * Each runtime has an upper limit on a dimension of the image it can handle. If bigger, will throw this error. |
||
265 | * |
||
266 | * @property IMAGE_DIMENSIONS_ERROR |
||
267 | * @static |
||
268 | * @final |
||
269 | */ |
||
270 | IMAGE_DIMENSIONS_ERROR : -702, |
||
271 | |||
272 | /** |
||
273 | * Expose whole moxie (#1469). |
||
274 | * |
||
275 | * @property moxie |
||
276 | * @type Object |
||
277 | * @final |
||
278 | */ |
||
279 | moxie: o, |
||
280 | |||
281 | /** |
||
282 | * Mime type lookup table. |
||
283 | * |
||
284 | * @property mimeTypes |
||
285 | * @type Object |
||
286 | * @final |
||
287 | */ |
||
288 | mimeTypes : u.Mime.mimes, |
||
289 | |||
290 | /** |
||
291 | * In some cases sniffing is the only way around :( |
||
292 | */ |
||
293 | ua: u.Env, |
||
294 | |||
295 | /** |
||
296 | * Gets the true type of the built-in object (better version of typeof). |
||
297 | * @credits Angus Croll (http://javascriptweblog.wordpress.com/) |
||
298 | * |
||
299 | * @method typeOf |
||
300 | * @static |
||
301 | * @param {Object} o Object to check. |
||
302 | * @return {String} Object [[Class]] |
||
303 | */ |
||
304 | typeOf: u.Basic.typeOf, |
||
305 | |||
306 | /** |
||
307 | * Extends the specified object with another object. |
||
308 | * |
||
309 | * @method extend |
||
310 | * @static |
||
311 | * @param {Object} target Object to extend. |
||
312 | * @param {Object..} obj Multiple objects to extend with. |
||
313 | * @return {Object} Same as target, the extended object. |
||
314 | */ |
||
315 | extend : u.Basic.extend, |
||
316 | |||
317 | /** |
||
318 | * Generates an unique ID. This is 99.99% unique since it takes the current time and 5 random numbers. |
||
319 | * The only way a user would be able to get the same ID is if the two persons at the same exact millisecond manages |
||
320 | * to get 5 the same random numbers between 0-65535 it also uses a counter so each call will be guaranteed to be page unique. |
||
321 | * It's more probable for the earth to be hit with an asteriod. You can also if you want to be 100% sure set the plupload.guidPrefix property |
||
322 | * to an user unique key. |
||
323 | * |
||
324 | * @method guid |
||
325 | * @static |
||
326 | * @return {String} Virtually unique id. |
||
327 | */ |
||
328 | guid : u.Basic.guid, |
||
329 | |||
330 | /** |
||
331 | * Get array of DOM Elements by their ids. |
||
332 | * |
||
333 | * @method get |
||
334 | * @param {String} id Identifier of the DOM Element |
||
335 | * @return {Array} |
||
336 | */ |
||
337 | getAll : function get(ids) { |
||
338 | var els = [], el; |
||
339 | |||
340 | if (plupload.typeOf(ids) !== 'array') { |
||
341 | ids = [ids]; |
||
342 | } |
||
343 | |||
344 | var i = ids.length; |
||
345 | while (i--) { |
||
346 | el = plupload.get(ids[i]); |
||
347 | if (el) { |
||
348 | els.push(el); |
||
349 | } |
||
350 | } |
||
351 | |||
352 | return els.length ? els : null; |
||
353 | }, |
||
354 | |||
355 | /** |
||
356 | Get DOM element by id |
||
357 | |||
358 | @method get |
||
359 | @param {String} id Identifier of the DOM Element |
||
360 | @return {Node} |
||
361 | */ |
||
362 | get: u.Dom.get, |
||
363 | |||
364 | /** |
||
365 | * Executes the callback function for each item in array/object. If you return false in the |
||
366 | * callback it will break the loop. |
||
367 | * |
||
368 | * @method each |
||
369 | * @static |
||
370 | * @param {Object} obj Object to iterate. |
||
371 | * @param {function} callback Callback function to execute for each item. |
||
372 | */ |
||
373 | each : u.Basic.each, |
||
374 | |||
375 | /** |
||
376 | * Returns the absolute x, y position of an Element. The position will be returned in a object with x, y fields. |
||
377 | * |
||
378 | * @method getPos |
||
379 | * @static |
||
380 | * @param {Element} node HTML element or element id to get x, y position from. |
||
381 | * @param {Element} root Optional root element to stop calculations at. |
||
382 | * @return {object} Absolute position of the specified element object with x, y fields. |
||
383 | */ |
||
384 | getPos : u.Dom.getPos, |
||
385 | |||
386 | /** |
||
387 | * Returns the size of the specified node in pixels. |
||
388 | * |
||
389 | * @method getSize |
||
390 | * @static |
||
391 | * @param {Node} node Node to get the size of. |
||
392 | * @return {Object} Object with a w and h property. |
||
393 | */ |
||
394 | getSize : u.Dom.getSize, |
||
395 | |||
396 | /** |
||
397 | * Encodes the specified string. |
||
398 | * |
||
399 | * @method xmlEncode |
||
400 | * @static |
||
401 | * @param {String} s String to encode. |
||
402 | * @return {String} Encoded string. |
||
403 | */ |
||
404 | xmlEncode : function(str) { |
||
405 | var xmlEncodeChars = {'<' : 'lt', '>' : 'gt', '&' : 'amp', '"' : 'quot', '\'' : '#39'}, xmlEncodeRegExp = /[<>&\"\']/g; |
||
406 | |||
407 | return str ? ('' + str).replace(xmlEncodeRegExp, function(chr) { |
||
408 | return xmlEncodeChars[chr] ? '&' + xmlEncodeChars[chr] + ';' : chr; |
||
409 | }) : str; |
||
410 | }, |
||
411 | |||
412 | /** |
||
413 | * Forces anything into an array. |
||
414 | * |
||
415 | * @method toArray |
||
416 | * @static |
||
417 | * @param {Object} obj Object with length field. |
||
418 | * @return {Array} Array object containing all items. |
||
419 | */ |
||
420 | toArray : u.Basic.toArray, |
||
421 | |||
422 | /** |
||
423 | * Find an element in array and return its index if present, otherwise return -1. |
||
424 | * |
||
425 | * @method inArray |
||
426 | * @static |
||
427 | * @param {mixed} needle Element to find |
||
428 | * @param {Array} array |
||
429 | * @return {Int} Index of the element, or -1 if not found |
||
430 | */ |
||
431 | inArray : u.Basic.inArray, |
||
432 | |||
433 | /** |
||
434 | Recieve an array of functions (usually async) to call in sequence, each function |
||
435 | receives a callback as first argument that it should call, when it completes. Finally, |
||
436 | after everything is complete, main callback is called. Passing truthy value to the |
||
437 | callback as a first argument will interrupt the sequence and invoke main callback |
||
438 | immediately. |
||
439 | |||
440 | @method inSeries |
||
441 | @static |
||
442 | @param {Array} queue Array of functions to call in sequence |
||
443 | @param {Function} cb Main callback that is called in the end, or in case of error |
||
444 | */ |
||
445 | inSeries: u.Basic.inSeries, |
||
446 | |||
447 | /** |
||
448 | * Extends the language pack object with new items. |
||
449 | * |
||
450 | * @method addI18n |
||
451 | * @static |
||
452 | * @param {Object} pack Language pack items to add. |
||
453 | * @return {Object} Extended language pack object. |
||
454 | */ |
||
455 | addI18n : o.core.I18n.addI18n, |
||
456 | |||
457 | /** |
||
458 | * Translates the specified string by checking for the english string in the language pack lookup. |
||
459 | * |
||
460 | * @method translate |
||
461 | * @static |
||
462 | * @param {String} str String to look for. |
||
463 | * @return {String} Translated string or the input string if it wasn't found. |
||
464 | */ |
||
465 | translate : o.core.I18n.translate, |
||
466 | |||
467 | /** |
||
468 | * Pseudo sprintf implementation - simple way to replace tokens with specified values. |
||
469 | * |
||
470 | * @param {String} str String with tokens |
||
471 | * @return {String} String with replaced tokens |
||
472 | */ |
||
473 | sprintf : u.Basic.sprintf, |
||
474 | |||
475 | /** |
||
476 | * Checks if object is empty. |
||
477 | * |
||
478 | * @method isEmptyObj |
||
479 | * @static |
||
480 | * @param {Object} obj Object to check. |
||
481 | * @return {Boolean} |
||
482 | */ |
||
483 | isEmptyObj : u.Basic.isEmptyObj, |
||
484 | |||
485 | /** |
||
486 | * Checks if specified DOM element has specified class. |
||
487 | * |
||
488 | * @method hasClass |
||
489 | * @static |
||
490 | * @param {Object} obj DOM element like object to add handler to. |
||
491 | * @param {String} name Class name |
||
492 | */ |
||
493 | hasClass : u.Dom.hasClass, |
||
494 | |||
495 | /** |
||
496 | * Adds specified className to specified DOM element. |
||
497 | * |
||
498 | * @method addClass |
||
499 | * @static |
||
500 | * @param {Object} obj DOM element like object to add handler to. |
||
501 | * @param {String} name Class name |
||
502 | */ |
||
503 | addClass : u.Dom.addClass, |
||
504 | |||
505 | /** |
||
506 | * Removes specified className from specified DOM element. |
||
507 | * |
||
508 | * @method removeClass |
||
509 | * @static |
||
510 | * @param {Object} obj DOM element like object to add handler to. |
||
511 | * @param {String} name Class name |
||
512 | */ |
||
513 | removeClass : u.Dom.removeClass, |
||
514 | |||
515 | /** |
||
516 | * Returns a given computed style of a DOM element. |
||
517 | * |
||
518 | * @method getStyle |
||
519 | * @static |
||
520 | * @param {Object} obj DOM element like object. |
||
521 | * @param {String} name Style you want to get from the DOM element |
||
522 | */ |
||
523 | getStyle : u.Dom.getStyle, |
||
524 | |||
525 | /** |
||
526 | * Adds an event handler to the specified object and store reference to the handler |
||
527 | * in objects internal Plupload registry (@see removeEvent). |
||
528 | * |
||
529 | * @method addEvent |
||
530 | * @static |
||
531 | * @param {Object} obj DOM element like object to add handler to. |
||
532 | * @param {String} name Name to add event listener to. |
||
533 | * @param {Function} callback Function to call when event occurs. |
||
534 | * @param {String} (optional) key that might be used to add specifity to the event record. |
||
535 | */ |
||
536 | addEvent : u.Events.addEvent, |
||
537 | |||
538 | /** |
||
539 | * Remove event handler from the specified object. If third argument (callback) |
||
540 | * is not specified remove all events with the specified name. |
||
541 | * |
||
542 | * @method removeEvent |
||
543 | * @static |
||
544 | * @param {Object} obj DOM element to remove event listener(s) from. |
||
545 | * @param {String} name Name of event listener to remove. |
||
546 | * @param {Function|String} (optional) might be a callback or unique key to match. |
||
547 | */ |
||
548 | removeEvent: u.Events.removeEvent, |
||
549 | |||
550 | /** |
||
551 | * Remove all kind of events from the specified object |
||
552 | * |
||
553 | * @method removeAllEvents |
||
554 | * @static |
||
555 | * @param {Object} obj DOM element to remove event listeners from. |
||
556 | * @param {String} (optional) unique key to match, when removing events. |
||
557 | */ |
||
558 | removeAllEvents: u.Events.removeAllEvents, |
||
559 | |||
560 | /** |
||
561 | * Cleans the specified name from national characters (diacritics). The result will be a name with only a-z, 0-9 and _. |
||
562 | * |
||
563 | * @method cleanName |
||
564 | * @static |
||
565 | * @param {String} s String to clean up. |
||
566 | * @return {String} Cleaned string. |
||
567 | */ |
||
568 | cleanName : function(name) { |
||
569 | var i, lookup; |
||
570 | |||
571 | // Replace diacritics |
||
572 | lookup = [ |
||
573 | /[\300-\306]/g, 'A', /[\340-\346]/g, 'a', |
||
574 | /\307/g, 'C', /\347/g, 'c', |
||
575 | /[\310-\313]/g, 'E', /[\350-\353]/g, 'e', |
||
576 | /[\314-\317]/g, 'I', /[\354-\357]/g, 'i', |
||
577 | /\321/g, 'N', /\361/g, 'n', |
||
578 | /[\322-\330]/g, 'O', /[\362-\370]/g, 'o', |
||
579 | /[\331-\334]/g, 'U', /[\371-\374]/g, 'u' |
||
580 | ]; |
||
581 | |||
582 | for (i = 0; i < lookup.length; i += 2) { |
||
583 | name = name.replace(lookup[i], lookup[i + 1]); |
||
584 | } |
||
585 | |||
586 | // Replace whitespace |
||
587 | name = name.replace(/\s+/g, '_'); |
||
588 | |||
589 | // Remove anything else |
||
590 | name = name.replace(/[^a-z0-9_\-\.]+/gi, ''); |
||
591 | |||
592 | return name; |
||
593 | }, |
||
594 | |||
595 | /** |
||
596 | * Builds a full url out of a base URL and an object with items to append as query string items. |
||
597 | * |
||
598 | * @method buildUrl |
||
599 | * @static |
||
600 | * @param {String} url Base URL to append query string items to. |
||
601 | * @param {Object} items Name/value object to serialize as a querystring. |
||
602 | * @return {String} String with url + serialized query string items. |
||
603 | */ |
||
604 | buildUrl: function(url, items) { |
||
605 | var query = ''; |
||
606 | |||
607 | plupload.each(items, function(value, name) { |
||
608 | query += (query ? '&' : '') + encodeURIComponent(name) + '=' + encodeURIComponent(value); |
||
609 | }); |
||
610 | |||
611 | if (query) { |
||
612 | url += (url.indexOf('?') > 0 ? '&' : '?') + query; |
||
613 | } |
||
614 | |||
615 | return url; |
||
616 | }, |
||
617 | |||
618 | /** |
||
619 | * Formats the specified number as a size string for example 1024 becomes 1 KB. |
||
620 | * |
||
621 | * @method formatSize |
||
622 | * @static |
||
623 | * @param {Number} size Size to format as string. |
||
624 | * @return {String} Formatted size string. |
||
625 | */ |
||
626 | formatSize : function(size) { |
||
627 | |||
628 | if (size === undef || /\D/.test(size)) { |
||
629 | return plupload.translate('N/A'); |
||
630 | } |
||
631 | |||
632 | function round(num, precision) { |
||
633 | return Math.round(num * Math.pow(10, precision)) / Math.pow(10, precision); |
||
634 | } |
||
635 | |||
636 | var boundary = Math.pow(1024, 4); |
||
637 | |||
638 | // TB |
||
639 | if (size > boundary) { |
||
640 | return round(size / boundary, 1) + " " + plupload.translate('tb'); |
||
641 | } |
||
642 | |||
643 | // GB |
||
644 | if (size > (boundary/=1024)) { |
||
645 | return round(size / boundary, 1) + " " + plupload.translate('gb'); |
||
646 | } |
||
647 | |||
648 | // MB |
||
649 | if (size > (boundary/=1024)) { |
||
650 | return round(size / boundary, 1) + " " + plupload.translate('mb'); |
||
651 | } |
||
652 | |||
653 | // KB |
||
654 | if (size > 1024) { |
||
655 | return Math.round(size / 1024) + " " + plupload.translate('kb'); |
||
656 | } |
||
657 | |||
658 | return size + " " + plupload.translate('b'); |
||
659 | }, |
||
660 | |||
661 | |||
662 | /** |
||
663 | * Parses the specified size string into a byte value. For example 10kb becomes 10240. |
||
664 | * |
||
665 | * @method parseSize |
||
666 | * @static |
||
667 | * @param {String|Number} size String to parse or number to just pass through. |
||
668 | * @return {Number} Size in bytes. |
||
669 | */ |
||
670 | parseSize : u.Basic.parseSizeStr, |
||
671 | |||
672 | |||
673 | /** |
||
674 | * A way to predict what runtime will be choosen in the current environment with the |
||
675 | * specified settings. |
||
676 | * |
||
677 | * @method predictRuntime |
||
678 | * @static |
||
679 | * @param {Object|String} config Plupload settings to check |
||
680 | * @param {String} [runtimes] Comma-separated list of runtimes to check against |
||
681 | * @return {String} Type of compatible runtime |
||
682 | */ |
||
683 | predictRuntime : function(config, runtimes) { |
||
684 | var up, runtime; |
||
685 | |||
686 | up = new plupload.Uploader(config); |
||
687 | runtime = Runtime.thatCan(up.getOption().required_features, runtimes || config.runtimes); |
||
688 | up.destroy(); |
||
689 | return runtime; |
||
690 | }, |
||
691 | |||
692 | /** |
||
693 | * Registers a filter that will be executed for each file added to the queue. |
||
694 | * If callback returns false, file will not be added. |
||
695 | * |
||
696 | * Callback receives two arguments: a value for the filter as it was specified in settings.filters |
||
697 | * and a file to be filtered. Callback is executed in the context of uploader instance. |
||
698 | * |
||
699 | * @method addFileFilter |
||
700 | * @static |
||
701 | * @param {String} name Name of the filter by which it can be referenced in settings.filters |
||
702 | * @param {String} cb Callback - the actual routine that every added file must pass |
||
703 | */ |
||
704 | addFileFilter: function(name, cb) { |
||
705 | fileFilters[name] = cb; |
||
706 | } |
||
707 | }; |
||
708 | |||
709 | |||
710 | plupload.addFileFilter('mime_types', function(filters, file, cb) { |
||
711 | if (filters.length && !filters.regexp.test(file.name)) { |
||
712 | this.trigger('Error', { |
||
713 | code : plupload.FILE_EXTENSION_ERROR, |
||
714 | message : plupload.translate('File extension error.'), |
||
715 | file : file |
||
716 | }); |
||
717 | cb(false); |
||
718 | } else { |
||
719 | cb(true); |
||
720 | } |
||
721 | }); |
||
722 | |||
723 | |||
724 | plupload.addFileFilter('max_file_size', function(maxSize, file, cb) { |
||
725 | var undef; |
||
726 | |||
727 | maxSize = plupload.parseSize(maxSize); |
||
728 | |||
729 | // Invalid file size |
||
730 | if (file.size !== undef && maxSize && file.size > maxSize) { |
||
1 ignored issue
–
show
|
|||
731 | this.trigger('Error', { |
||
732 | code : plupload.FILE_SIZE_ERROR, |
||
733 | message : plupload.translate('File size error.'), |
||
734 | file : file |
||
735 | }); |
||
736 | cb(false); |
||
737 | } else { |
||
738 | cb(true); |
||
739 | } |
||
740 | }); |
||
741 | |||
742 | |||
743 | plupload.addFileFilter('prevent_duplicates', function(value, file, cb) { |
||
744 | if (value) { |
||
745 | var ii = this.files.length; |
||
746 | while (ii--) { |
||
747 | // Compare by name and size (size might be 0 or undefined, but still equivalent for both) |
||
748 | if (file.name === this.files[ii].name && file.size === this.files[ii].size) { |
||
749 | this.trigger('Error', { |
||
750 | code : plupload.FILE_DUPLICATE_ERROR, |
||
751 | message : plupload.translate('Duplicate file error.'), |
||
752 | file : file |
||
753 | }); |
||
754 | cb(false); |
||
755 | return; |
||
756 | } |
||
757 | } |
||
758 | } |
||
759 | cb(true); |
||
760 | }); |
||
761 | |||
762 | plupload.addFileFilter('prevent_empty', function(value, file, cb) { |
||
763 | if (value && !file.size && file.size !== undef) { |
||
764 | this.trigger('Error', { |
||
765 | code : plupload.FILE_SIZE_ERROR, |
||
766 | message : plupload.translate('File size error.'), |
||
767 | file : file |
||
768 | }); |
||
769 | cb(false); |
||
770 | } else { |
||
771 | cb(true); |
||
772 | } |
||
773 | }); |
||
774 | |||
775 | |||
776 | /** |
||
777 | @class Uploader |
||
778 | @constructor |
||
779 | |||
780 | @param {Object} settings For detailed information about each option check documentation. |
||
781 | @param {String|DOMElement} settings.browse_button id of the DOM element or DOM element itself to use as file dialog trigger. |
||
782 | @param {Number|String} [settings.chunk_size=0] Chunk size in bytes to slice the file into. Shorcuts with b, kb, mb, gb, tb suffixes also supported. `e.g. 204800 or "204800b" or "200kb"`. By default - disabled. |
||
783 | @param {String|DOMElement} [settings.container] id of the DOM element or DOM element itself that will be used to wrap uploader structures. Defaults to immediate parent of the `browse_button` element. |
||
784 | @param {String|DOMElement} [settings.drop_element] id of the DOM element or DOM element itself to use as a drop zone for Drag-n-Drop. |
||
785 | @param {String} [settings.file_data_name="file"] Name for the file field in Multipart formated message. |
||
786 | @param {Object} [settings.filters={}] Set of file type filters. |
||
787 | @param {String|Number} [settings.filters.max_file_size=0] Maximum file size that the user can pick, in bytes. Optionally supports b, kb, mb, gb, tb suffixes. `e.g. "10mb" or "1gb"`. By default - not set. Dispatches `plupload.FILE_SIZE_ERROR`. |
||
788 | @param {Array} [settings.filters.mime_types=[]] List of file types to accept, each one defined by title and list of extensions. `e.g. {title : "Image files", extensions : "jpg,jpeg,gif,png"}`. Dispatches `plupload.FILE_EXTENSION_ERROR` |
||
789 | @param {Boolean} [settings.filters.prevent_duplicates=false] Do not let duplicates into the queue. Dispatches `plupload.FILE_DUPLICATE_ERROR`. |
||
790 | @param {Boolean} [settings.filters.prevent_empty=true] Do not let empty files into the queue (IE10 is known to hang for example when trying to upload such). Dispatches `plupload.FILE_SIZE_ERROR`. |
||
791 | @param {String} [settings.flash_swf_url] URL of the Flash swf. |
||
792 | @param {Object} [settings.headers] Custom headers to send with the upload. Hash of name/value pairs. |
||
793 | @param {String} [settings.http_method="POST"] HTTP method to use during upload (only PUT or POST allowed). |
||
794 | @param {Number} [settings.max_retries=0] How many times to retry the chunk or file, before triggering Error event. |
||
795 | @param {Boolean} [settings.multipart=true] Whether to send file and additional parameters as Multipart formated message. |
||
796 | @param {Object} [settings.multipart_params] Hash of key/value pairs to send with every file upload. |
||
797 | @param {Boolean} [settings.multi_selection=true] Enable ability to select multiple files at once in file dialog. |
||
798 | @param {String|Object} [settings.required_features] Either comma-separated list or hash of required features that chosen runtime should absolutely possess. |
||
799 | @param {Object} [settings.resize] Enable resizng of images on client-side. Applies to `image/jpeg` and `image/png` only. `e.g. {width : 200, height : 200, quality : 90, crop: true}` |
||
800 | @param {Number} [settings.resize.width] If image is bigger, it will be resized. |
||
801 | @param {Number} [settings.resize.height] If image is bigger, it will be resized. |
||
802 | @param {Number} [settings.resize.quality=90] Compression quality for jpegs (1-100). |
||
803 | @param {Boolean} [settings.resize.crop=false] Whether to crop images to exact dimensions. By default they will be resized proportionally. |
||
804 | @param {String} [settings.runtimes="html5,flash,silverlight,html4"] Comma separated list of runtimes, that Plupload will try in turn, moving to the next if previous fails. |
||
805 | @param {String} [settings.silverlight_xap_url] URL of the Silverlight xap. |
||
806 | @param {Boolean} [settings.send_chunk_number=true] Whether to send chunks and chunk numbers, or total and offset bytes. |
||
807 | @param {Boolean} [settings.send_file_name=true] Whether to send file name as additional argument - 'name' (required for chunked uploads and some other cases where file name cannot be sent via normal ways). |
||
808 | @param {String} settings.url URL of the server-side upload handler. |
||
809 | @param {Boolean} [settings.unique_names=false] If true will generate unique filenames for uploaded files. |
||
810 | |||
811 | */ |
||
812 | plupload.Uploader = function(options) { |
||
813 | /** |
||
814 | Fires when the current RunTime has been initialized. |
||
815 | |||
816 | @event Init |
||
817 | @param {plupload.Uploader} uploader Uploader instance sending the event. |
||
818 | */ |
||
819 | |||
820 | /** |
||
821 | Fires after the init event incase you need to perform actions there. |
||
822 | |||
823 | @event PostInit |
||
824 | @param {plupload.Uploader} uploader Uploader instance sending the event. |
||
825 | */ |
||
826 | |||
827 | /** |
||
828 | Fires when the option is changed in via uploader.setOption(). |
||
829 | |||
830 | @event OptionChanged |
||
831 | @since 2.1 |
||
832 | @param {plupload.Uploader} uploader Uploader instance sending the event. |
||
833 | @param {String} name Name of the option that was changed |
||
834 | @param {Mixed} value New value for the specified option |
||
835 | @param {Mixed} oldValue Previous value of the option |
||
836 | */ |
||
837 | |||
838 | /** |
||
839 | Fires when the silverlight/flash or other shim needs to move. |
||
840 | |||
841 | @event Refresh |
||
842 | @param {plupload.Uploader} uploader Uploader instance sending the event. |
||
843 | */ |
||
844 | |||
845 | /** |
||
846 | Fires when the overall state is being changed for the upload queue. |
||
847 | |||
848 | @event StateChanged |
||
849 | @param {plupload.Uploader} uploader Uploader instance sending the event. |
||
850 | */ |
||
851 | |||
852 | /** |
||
853 | Fires when browse_button is clicked and browse dialog shows. |
||
854 | |||
855 | @event Browse |
||
856 | @since 2.1.2 |
||
857 | @param {plupload.Uploader} uploader Uploader instance sending the event. |
||
858 | */ |
||
859 | |||
860 | /** |
||
861 | Fires for every filtered file before it is added to the queue. |
||
862 | |||
863 | @event FileFiltered |
||
864 | @since 2.1 |
||
865 | @param {plupload.Uploader} uploader Uploader instance sending the event. |
||
866 | @param {plupload.File} file Another file that has to be added to the queue. |
||
867 | */ |
||
868 | |||
869 | /** |
||
870 | Fires when the file queue is changed. In other words when files are added/removed to the files array of the uploader instance. |
||
871 | |||
872 | @event QueueChanged |
||
873 | @param {plupload.Uploader} uploader Uploader instance sending the event. |
||
874 | */ |
||
875 | |||
876 | /** |
||
877 | Fires after files were filtered and added to the queue. |
||
878 | |||
879 | @event FilesAdded |
||
880 | @param {plupload.Uploader} uploader Uploader instance sending the event. |
||
881 | @param {Array} files Array of file objects that were added to queue by the user. |
||
882 | */ |
||
883 | |||
884 | /** |
||
885 | Fires when file is removed from the queue. |
||
886 | |||
887 | @event FilesRemoved |
||
888 | @param {plupload.Uploader} uploader Uploader instance sending the event. |
||
889 | @param {Array} files Array of files that got removed. |
||
890 | */ |
||
891 | |||
892 | /** |
||
893 | Fires just before a file is uploaded. Can be used to cancel the upload for the specified file |
||
894 | by returning false from the handler. |
||
895 | |||
896 | @event BeforeUpload |
||
897 | @param {plupload.Uploader} uploader Uploader instance sending the event. |
||
898 | @param {plupload.File} file File to be uploaded. |
||
899 | */ |
||
900 | |||
901 | /** |
||
902 | Fires when a file is to be uploaded by the runtime. |
||
903 | |||
904 | @event UploadFile |
||
905 | @param {plupload.Uploader} uploader Uploader instance sending the event. |
||
906 | @param {plupload.File} file File to be uploaded. |
||
907 | */ |
||
908 | |||
909 | /** |
||
910 | Fires while a file is being uploaded. Use this event to update the current file upload progress. |
||
911 | |||
912 | @event UploadProgress |
||
913 | @param {plupload.Uploader} uploader Uploader instance sending the event. |
||
914 | @param {plupload.File} file File that is currently being uploaded. |
||
915 | */ |
||
916 | |||
917 | /** |
||
918 | * Fires just before a chunk is uploaded. This event enables you to override settings |
||
919 | * on the uploader instance before the chunk is uploaded. |
||
920 | * |
||
921 | * @event BeforeChunkUpload |
||
922 | * @param {plupload.Uploader} uploader Uploader instance sending the event. |
||
923 | * @param {plupload.File} file File to be uploaded. |
||
924 | * @param {Object} args POST params to be sent. |
||
925 | * @param {Blob} chunkBlob Current blob. |
||
926 | * @param {offset} offset Current offset. |
||
927 | */ |
||
928 | |||
929 | /** |
||
930 | Fires when file chunk is uploaded. |
||
931 | |||
932 | @event ChunkUploaded |
||
933 | @param {plupload.Uploader} uploader Uploader instance sending the event. |
||
934 | @param {plupload.File} file File that the chunk was uploaded for. |
||
935 | @param {Object} result Object with response properties. |
||
936 | @param {Number} result.offset The amount of bytes the server has received so far, including this chunk. |
||
937 | @param {Number} result.total The size of the file. |
||
938 | @param {String} result.response The response body sent by the server. |
||
939 | @param {Number} result.status The HTTP status code sent by the server. |
||
940 | @param {String} result.responseHeaders All the response headers as a single string. |
||
941 | */ |
||
942 | |||
943 | /** |
||
944 | Fires when a file is successfully uploaded. |
||
945 | |||
946 | @event FileUploaded |
||
947 | @param {plupload.Uploader} uploader Uploader instance sending the event. |
||
948 | @param {plupload.File} file File that was uploaded. |
||
949 | @param {Object} result Object with response properties. |
||
950 | @param {String} result.response The response body sent by the server. |
||
951 | @param {Number} result.status The HTTP status code sent by the server. |
||
952 | @param {String} result.responseHeaders All the response headers as a single string. |
||
953 | */ |
||
954 | |||
955 | /** |
||
956 | Fires when all files in a queue are uploaded. |
||
957 | |||
958 | @event UploadComplete |
||
959 | @param {plupload.Uploader} uploader Uploader instance sending the event. |
||
960 | @param {Array} files Array of file objects that was added to queue/selected by the user. |
||
961 | */ |
||
962 | |||
963 | /** |
||
964 | Fires when a error occurs. |
||
965 | |||
966 | @event Error |
||
967 | @param {plupload.Uploader} uploader Uploader instance sending the event. |
||
968 | @param {Object} error Contains code, message and sometimes file and other details. |
||
969 | @param {Number} error.code The plupload error code. |
||
970 | @param {String} error.message Description of the error (uses i18n). |
||
971 | */ |
||
972 | |||
973 | /** |
||
974 | Fires when destroy method is called. |
||
975 | |||
976 | @event Destroy |
||
977 | @param {plupload.Uploader} uploader Uploader instance sending the event. |
||
978 | */ |
||
979 | var uid = plupload.guid() |
||
980 | , settings |
||
981 | , files = [] |
||
982 | , preferred_caps = {} |
||
983 | , fileInputs = [] |
||
984 | , fileDrops = [] |
||
985 | , startTime |
||
986 | , total |
||
987 | , disabled = false |
||
988 | , xhr |
||
989 | ; |
||
990 | |||
991 | |||
992 | // Private methods |
||
993 | function uploadNext() { |
||
994 | var file, count = 0, i; |
||
995 | |||
996 | if (this.state == plupload.STARTED) { |
||
997 | // Find first QUEUED file |
||
998 | for (i = 0; i < files.length; i++) { |
||
999 | if (!file && files[i].status == plupload.QUEUED) { |
||
1000 | file = files[i]; |
||
1001 | if (this.trigger("BeforeUpload", file)) { |
||
1002 | file.status = plupload.UPLOADING; |
||
1003 | this.trigger("UploadFile", file); |
||
1004 | } |
||
1005 | } else { |
||
1006 | count++; |
||
1007 | } |
||
1008 | } |
||
1009 | |||
1010 | // All files are DONE or FAILED |
||
1011 | if (count == files.length) { |
||
1012 | if (this.state !== plupload.STOPPED) { |
||
1013 | this.state = plupload.STOPPED; |
||
1014 | this.trigger("StateChanged"); |
||
1015 | } |
||
1016 | this.trigger("UploadComplete", files); |
||
1017 | } |
||
1018 | } |
||
1019 | } |
||
1020 | |||
1021 | |||
1022 | function calcFile(file) { |
||
1023 | file.percent = file.size > 0 ? Math.ceil(file.loaded / file.size * 100) : 100; |
||
1024 | calc(); |
||
1025 | } |
||
1026 | |||
1027 | |||
1028 | function calc() { |
||
1029 | var i, file; |
||
1030 | var loaded; |
||
1031 | var loadedDuringCurrentSession = 0; |
||
1032 | |||
1033 | // Reset stats |
||
1034 | total.reset(); |
||
1035 | |||
1036 | // Check status, size, loaded etc on all files |
||
1037 | for (i = 0; i < files.length; i++) { |
||
1038 | file = files[i]; |
||
1039 | |||
1040 | if (file.size !== undef) { |
||
1041 | // We calculate totals based on original file size |
||
1042 | total.size += file.origSize; |
||
1043 | |||
1044 | // Since we cannot predict file size after resize, we do opposite and |
||
1045 | // interpolate loaded amount to match magnitude of total |
||
1046 | loaded = file.loaded * file.origSize / file.size; |
||
1047 | |||
1048 | if (!file.completeTimestamp || file.completeTimestamp > startTime) { |
||
1049 | loadedDuringCurrentSession += loaded; |
||
1050 | } |
||
1051 | |||
1052 | total.loaded += loaded; |
||
1053 | } else { |
||
1054 | total.size = undef; |
||
1055 | } |
||
1056 | |||
1057 | if (file.status == plupload.DONE) { |
||
1058 | total.uploaded++; |
||
1059 | } else if (file.status == plupload.FAILED) { |
||
1060 | total.failed++; |
||
1061 | } else { |
||
1062 | total.queued++; |
||
1063 | } |
||
1064 | } |
||
1065 | |||
1066 | // If we couldn't calculate a total file size then use the number of files to calc percent |
||
1067 | if (total.size === undef) { |
||
1068 | total.percent = files.length > 0 ? Math.ceil(total.uploaded / files.length * 100) : 0; |
||
1069 | } else { |
||
1070 | total.bytesPerSec = Math.ceil(loadedDuringCurrentSession / ((+new Date() - startTime || 1) / 1000.0)); |
||
1071 | total.percent = total.size > 0 ? Math.ceil(total.loaded / total.size * 100) : 0; |
||
1072 | } |
||
1073 | } |
||
1074 | |||
1075 | |||
1076 | function getRUID() { |
||
1077 | var ctrl = fileInputs[0] || fileDrops[0]; |
||
1078 | if (ctrl) { |
||
1079 | return ctrl.getRuntime().uid; |
||
1080 | } |
||
1081 | return false; |
||
1082 | } |
||
1083 | |||
1084 | |||
1085 | function bindEventListeners() { |
||
1086 | this.bind('FilesAdded FilesRemoved', function(up) { |
||
1087 | up.trigger('QueueChanged'); |
||
1088 | up.refresh(); |
||
1089 | }); |
||
1090 | |||
1091 | this.bind('CancelUpload', onCancelUpload); |
||
1092 | |||
1093 | this.bind('BeforeUpload', onBeforeUpload); |
||
1094 | |||
1095 | this.bind('UploadFile', onUploadFile); |
||
1096 | |||
1097 | this.bind('UploadProgress', onUploadProgress); |
||
1098 | |||
1099 | this.bind('StateChanged', onStateChanged); |
||
1100 | |||
1101 | this.bind('QueueChanged', calc); |
||
1102 | |||
1103 | this.bind('Error', onError); |
||
1104 | |||
1105 | this.bind('FileUploaded', onFileUploaded); |
||
1106 | |||
1107 | this.bind('Destroy', onDestroy); |
||
1108 | } |
||
1109 | |||
1110 | |||
1111 | function initControls(settings, cb) { |
||
1112 | var self = this, inited = 0, queue = []; |
||
1113 | |||
1114 | // common settings |
||
1115 | var options = { |
||
1116 | runtime_order: settings.runtimes, |
||
1117 | required_caps: settings.required_features, |
||
1118 | preferred_caps: preferred_caps, |
||
1119 | swf_url: settings.flash_swf_url, |
||
1120 | xap_url: settings.silverlight_xap_url |
||
1121 | }; |
||
1122 | |||
1123 | // add runtime specific options if any |
||
1124 | plupload.each(settings.runtimes.split(/\s*,\s*/), function(runtime) { |
||
1125 | if (settings[runtime]) { |
||
1126 | options[runtime] = settings[runtime]; |
||
1127 | } |
||
1128 | }); |
||
1129 | |||
1130 | // initialize file pickers - there can be many |
||
1131 | if (settings.browse_button) { |
||
1132 | plupload.each(settings.browse_button, function(el) { |
||
1133 | queue.push(function(cb) { |
||
1134 | var fileInput = new o.file.FileInput(plupload.extend({}, options, { |
||
1135 | accept: settings.filters.mime_types, |
||
1136 | name: settings.file_data_name, |
||
1137 | multiple: settings.multi_selection, |
||
1138 | container: settings.container, |
||
1139 | browse_button: el |
||
1140 | })); |
||
1141 | |||
1142 | fileInput.onready = function() { |
||
1143 | var info = Runtime.getInfo(this.ruid); |
||
1144 | |||
1145 | // for backward compatibility |
||
1146 | plupload.extend(self.features, { |
||
1147 | chunks: info.can('slice_blob'), |
||
1148 | multipart: info.can('send_multipart'), |
||
1149 | multi_selection: info.can('select_multiple') |
||
1150 | }); |
||
1151 | |||
1152 | inited++; |
||
1153 | fileInputs.push(this); |
||
1154 | cb(); |
||
1155 | }; |
||
1156 | |||
1157 | fileInput.onchange = function() { |
||
1158 | self.addFile(this.files); |
||
1159 | }; |
||
1160 | |||
1161 | fileInput.bind('mouseenter mouseleave mousedown mouseup', function(e) { |
||
1162 | if (!disabled) { |
||
1163 | if (settings.browse_button_hover) { |
||
1164 | if ('mouseenter' === e.type) { |
||
1165 | plupload.addClass(el, settings.browse_button_hover); |
||
1166 | } else if ('mouseleave' === e.type) { |
||
1167 | plupload.removeClass(el, settings.browse_button_hover); |
||
1168 | } |
||
1169 | } |
||
1170 | |||
1171 | if (settings.browse_button_active) { |
||
1172 | if ('mousedown' === e.type) { |
||
1173 | plupload.addClass(el, settings.browse_button_active); |
||
1174 | } else if ('mouseup' === e.type) { |
||
1175 | plupload.removeClass(el, settings.browse_button_active); |
||
1176 | } |
||
1177 | } |
||
1178 | } |
||
1179 | }); |
||
1180 | |||
1181 | fileInput.bind('mousedown', function() { |
||
1182 | self.trigger('Browse'); |
||
1183 | }); |
||
1184 | |||
1185 | fileInput.bind('error runtimeerror', function() { |
||
1186 | fileInput = null; |
||
1187 | cb(); |
||
1188 | }); |
||
1189 | |||
1190 | fileInput.init(); |
||
1191 | }); |
||
1192 | }); |
||
1193 | } |
||
1194 | |||
1195 | // initialize drop zones |
||
1196 | if (settings.drop_element) { |
||
1197 | plupload.each(settings.drop_element, function(el) { |
||
1198 | queue.push(function(cb) { |
||
1199 | var fileDrop = new o.file.FileDrop(plupload.extend({}, options, { |
||
1200 | drop_zone: el |
||
1201 | })); |
||
1202 | |||
1203 | fileDrop.onready = function() { |
||
1204 | var info = Runtime.getInfo(this.ruid); |
||
1205 | |||
1206 | // for backward compatibility |
||
1207 | plupload.extend(self.features, { |
||
1208 | chunks: info.can('slice_blob'), |
||
1209 | multipart: info.can('send_multipart'), |
||
1210 | dragdrop: info.can('drag_and_drop') |
||
1211 | }); |
||
1212 | |||
1213 | inited++; |
||
1214 | fileDrops.push(this); |
||
1215 | cb(); |
||
1216 | }; |
||
1217 | |||
1218 | fileDrop.ondrop = function() { |
||
1219 | self.addFile(this.files); |
||
1220 | }; |
||
1221 | |||
1222 | fileDrop.bind('error runtimeerror', function() { |
||
1223 | fileDrop = null; |
||
1224 | cb(); |
||
1225 | }); |
||
1226 | |||
1227 | fileDrop.init(); |
||
1228 | }); |
||
1229 | }); |
||
1230 | } |
||
1231 | |||
1232 | |||
1233 | plupload.inSeries(queue, function() { |
||
1234 | if (typeof(cb) === 'function') { |
||
1235 | cb(inited); |
||
1236 | } |
||
1237 | }); |
||
1238 | } |
||
1239 | |||
1240 | |||
1241 | function resizeImage(blob, params, runtimeOptions, cb) { |
||
1242 | var img = new o.image.Image(); |
||
1243 | |||
1244 | try { |
||
1245 | img.onload = function() { |
||
1246 | // no manipulation required if... |
||
1247 | if (params.width > this.width && |
||
1248 | params.height > this.height && |
||
1249 | params.quality === undef && |
||
1250 | params.preserve_headers && |
||
1251 | !params.crop |
||
1252 | ) { |
||
1253 | this.destroy(); |
||
1254 | cb(blob); |
||
1255 | } else { |
||
1256 | // otherwise downsize |
||
1257 | img.downsize(params.width, params.height, params.crop, params.preserve_headers); |
||
1258 | } |
||
1259 | }; |
||
1260 | |||
1261 | img.onresize = function() { |
||
1262 | var resizedBlob = this.getAsBlob(blob.type, params.quality); |
||
1263 | this.destroy(); |
||
1264 | cb(resizedBlob); |
||
1265 | }; |
||
1266 | |||
1267 | img.bind('error runtimeerror', function() { |
||
1268 | this.destroy(); |
||
1269 | cb(blob); |
||
1270 | }); |
||
1271 | |||
1272 | img.load(blob, runtimeOptions); |
||
1273 | } catch(ex) { |
||
1274 | cb(blob); |
||
1275 | } |
||
1276 | } |
||
1277 | |||
1278 | |||
1279 | function setOption(option, value, init) { |
||
1280 | var self = this, reinitRequired = false; |
||
1281 | |||
1282 | function _setOption(option, value, init) { |
||
1283 | var oldValue = settings[option]; |
||
1284 | |||
1285 | switch (option) { |
||
1286 | case 'max_file_size': |
||
1287 | if (option === 'max_file_size') { |
||
1288 | settings.max_file_size = settings.filters.max_file_size = value; |
||
1289 | } |
||
1290 | break; |
||
1291 | |||
1292 | case 'chunk_size': |
||
1293 | if (value = plupload.parseSize(value)) { |
||
1294 | settings[option] = value; |
||
1295 | settings.send_file_name = true; |
||
1296 | } |
||
1297 | break; |
||
1298 | |||
1299 | case 'multipart': |
||
1300 | settings[option] = value; |
||
1301 | if (!value) { |
||
1302 | settings.send_file_name = true; |
||
1303 | } |
||
1304 | break; |
||
1305 | |||
1306 | case 'http_method': |
||
1307 | settings[option] = value.toUpperCase() === 'PUT' ? 'PUT' : 'POST'; |
||
1308 | break; |
||
1309 | |||
1310 | case 'unique_names': |
||
1311 | settings[option] = value; |
||
1312 | if (value) { |
||
1313 | settings.send_file_name = true; |
||
1314 | } |
||
1315 | break; |
||
1316 | |||
1317 | case 'filters': |
||
1318 | // for sake of backward compatibility |
||
1319 | if (plupload.typeOf(value) === 'array') { |
||
1320 | value = { |
||
1321 | mime_types: value |
||
1322 | }; |
||
1323 | } |
||
1324 | |||
1325 | if (init) { |
||
1326 | plupload.extend(settings.filters, value); |
||
1327 | } else { |
||
1328 | settings.filters = value; |
||
1329 | } |
||
1330 | |||
1331 | // if file format filters are being updated, regenerate the matching expressions |
||
1332 | if (value.mime_types) { |
||
1333 | if (plupload.typeOf(value.mime_types) === 'string') { |
||
1334 | value.mime_types = o.core.utils.Mime.mimes2extList(value.mime_types); |
||
1335 | } |
||
1336 | |||
1337 | value.mime_types.regexp = (function(filters) { |
||
1338 | var extensionsRegExp = []; |
||
1339 | |||
1340 | plupload.each(filters, function(filter) { |
||
1341 | plupload.each(filter.extensions.split(/,/), function(ext) { |
||
1342 | if (/^\s*\*\s*$/.test(ext)) { |
||
1343 | extensionsRegExp.push('\\.*'); |
||
1344 | } else { |
||
1345 | extensionsRegExp.push('\\.' + ext.replace(new RegExp('[' + ('/^$.*+?|()[]{}\\'.replace(/./g, '\\$&')) + ']', 'g'), '\\$&')); |
||
1346 | } |
||
1347 | }); |
||
1348 | }); |
||
1349 | |||
1350 | return new RegExp('(' + extensionsRegExp.join('|') + ')$', 'i'); |
||
1351 | }(value.mime_types)); |
||
1352 | |||
1353 | settings.filters.mime_types = value.mime_types; |
||
1354 | } |
||
1355 | break; |
||
1356 | |||
1357 | case 'resize': |
||
1358 | if (value) { |
||
1359 | settings.resize = plupload.extend({ |
||
1360 | preserve_headers: true, |
||
1361 | crop: false |
||
1362 | }, value); |
||
1363 | } else { |
||
1364 | settings.resize = false; |
||
1365 | } |
||
1366 | break; |
||
1367 | |||
1368 | case 'prevent_duplicates': |
||
1369 | settings.prevent_duplicates = settings.filters.prevent_duplicates = !!value; |
||
1370 | break; |
||
1371 | |||
1372 | // options that require reinitialisation |
||
1373 | case 'container': |
||
1374 | case 'browse_button': |
||
1375 | case 'drop_element': |
||
1376 | value = 'container' === option |
||
1377 | ? plupload.get(value) |
||
1378 | : plupload.getAll(value) |
||
1379 | ; |
||
1380 | |||
1381 | case 'runtimes': |
||
1382 | case 'multi_selection': |
||
1383 | case 'flash_swf_url': |
||
1384 | case 'silverlight_xap_url': |
||
1385 | settings[option] = value; |
||
1386 | if (!init) { |
||
1387 | reinitRequired = true; |
||
1388 | } |
||
1389 | break; |
||
1390 | |||
1391 | default: |
||
1392 | settings[option] = value; |
||
1393 | } |
||
1394 | |||
1395 | if (!init) { |
||
1396 | self.trigger('OptionChanged', option, value, oldValue); |
||
1397 | } |
||
1398 | } |
||
1399 | |||
1400 | if (typeof(option) === 'object') { |
||
1401 | plupload.each(option, function(value, option) { |
||
1402 | _setOption(option, value, init); |
||
1403 | }); |
||
1404 | } else { |
||
1405 | _setOption(option, value, init); |
||
1406 | } |
||
1407 | |||
1408 | if (init) { |
||
1409 | // Normalize the list of required capabilities |
||
1410 | settings.required_features = normalizeCaps(plupload.extend({}, settings)); |
||
1411 | |||
1412 | // Come up with the list of capabilities that can affect default mode in a multi-mode runtimes |
||
1413 | preferred_caps = normalizeCaps(plupload.extend({}, settings, { |
||
1414 | required_features: true |
||
1415 | })); |
||
1416 | } else if (reinitRequired) { |
||
1417 | self.trigger('Destroy'); |
||
1418 | |||
1419 | initControls.call(self, settings, function(inited) { |
||
1420 | if (inited) { |
||
1421 | self.runtime = Runtime.getInfo(getRUID()).type; |
||
1422 | self.trigger('Init', { runtime: self.runtime }); |
||
1423 | self.trigger('PostInit'); |
||
1424 | } else { |
||
1425 | self.trigger('Error', { |
||
1426 | code : plupload.INIT_ERROR, |
||
1427 | message : plupload.translate('Init error.') |
||
1428 | }); |
||
1429 | } |
||
1430 | }); |
||
1431 | } |
||
1432 | } |
||
1433 | |||
1434 | |||
1435 | // Internal event handlers |
||
1436 | function onBeforeUpload(up, file) { |
||
1437 | // Generate unique target filenames |
||
1438 | if (up.settings.unique_names) { |
||
1439 | var matches = file.name.match(/\.([^.]+)$/), ext = "part"; |
||
1440 | if (matches) { |
||
1441 | ext = matches[1]; |
||
1442 | } |
||
1443 | file.target_name = file.id + '.' + ext; |
||
1444 | } |
||
1445 | } |
||
1446 | |||
1447 | |||
1448 | function onUploadFile(up, file) { |
||
1449 | var url = up.settings.url; |
||
1450 | var chunkSize = up.settings.chunk_size; |
||
1451 | var retries = up.settings.max_retries; |
||
1452 | var features = up.features; |
||
1453 | var offset = 0; |
||
1454 | var blob; |
||
1455 | |||
1456 | var runtimeOptions = { |
||
1457 | runtime_order: up.settings.runtimes, |
||
1458 | required_caps: up.settings.required_features, |
||
1459 | preferred_caps: preferred_caps, |
||
1460 | swf_url: up.settings.flash_swf_url, |
||
1461 | xap_url: up.settings.silverlight_xap_url |
||
1462 | }; |
||
1463 | |||
1464 | // make sure we start at a predictable offset |
||
1465 | if (file.loaded) { |
||
1466 | offset = file.loaded = chunkSize ? chunkSize * Math.floor(file.loaded / chunkSize) : 0; |
||
1467 | } |
||
1468 | |||
1469 | function handleError() { |
||
1470 | if (retries-- > 0) { |
||
1471 | delay(uploadNextChunk, 1000); |
||
1472 | } else { |
||
1473 | file.loaded = offset; // reset all progress |
||
1474 | |||
1475 | up.trigger('Error', { |
||
1476 | code : plupload.HTTP_ERROR, |
||
1477 | message : plupload.translate('HTTP Error.'), |
||
1478 | file : file, |
||
1479 | response : xhr.responseText, |
||
1480 | status : xhr.status, |
||
1481 | responseHeaders: xhr.getAllResponseHeaders() |
||
1482 | }); |
||
1483 | } |
||
1484 | } |
||
1485 | |||
1486 | function uploadNextChunk() { |
||
1487 | var chunkBlob, args = {}, curChunkSize; |
||
1488 | |||
1489 | // make sure that file wasn't cancelled and upload is not stopped in general |
||
1490 | if (file.status !== plupload.UPLOADING || up.state === plupload.STOPPED) { |
||
1491 | return; |
||
1492 | } |
||
1493 | |||
1494 | // send additional 'name' parameter only if required |
||
1495 | if (up.settings.send_file_name) { |
||
1496 | args.name = file.target_name || file.name; |
||
1497 | } |
||
1498 | |||
1499 | if (chunkSize && features.chunks && blob.size > chunkSize) { // blob will be of type string if it was loaded in memory |
||
1500 | curChunkSize = Math.min(chunkSize, blob.size - offset); |
||
1501 | chunkBlob = blob.slice(offset, offset + curChunkSize); |
||
1502 | } else { |
||
1503 | curChunkSize = blob.size; |
||
1504 | chunkBlob = blob; |
||
1505 | } |
||
1506 | |||
1507 | // If chunking is enabled add corresponding args, no matter if file is bigger than chunk or smaller |
||
1508 | if (chunkSize && features.chunks) { |
||
1509 | // Setup query string arguments |
||
1510 | if (up.settings.send_chunk_number) { |
||
1511 | args.chunk = Math.ceil(offset / chunkSize); |
||
1512 | args.chunks = Math.ceil(blob.size / chunkSize); |
||
1513 | } else { // keep support for experimental chunk format, just in case |
||
1514 | args.offset = offset; |
||
1515 | args.total = blob.size; |
||
1516 | } |
||
1517 | } |
||
1518 | |||
1519 | if (up.trigger('BeforeChunkUpload', file, args, chunkBlob, offset)) { |
||
1520 | uploadChunk(args, chunkBlob, curChunkSize); |
||
1521 | } |
||
1522 | } |
||
1523 | |||
1524 | function uploadChunk(args, chunkBlob, curChunkSize) { |
||
1525 | var formData; |
||
1526 | |||
1527 | xhr = new o.xhr.XMLHttpRequest(); |
||
1528 | |||
1529 | // Do we have upload progress support |
||
1530 | if (xhr.upload) { |
||
1531 | xhr.upload.onprogress = function(e) { |
||
1532 | file.loaded = Math.min(file.size, offset + e.loaded); |
||
1533 | up.trigger('UploadProgress', file); |
||
1534 | }; |
||
1535 | } |
||
1536 | |||
1537 | xhr.onload = function() { |
||
1538 | // check if upload made itself through |
||
1539 | if (xhr.status < 200 || xhr.status >= 400) { |
||
1540 | handleError(); |
||
1541 | return; |
||
1542 | } |
||
1543 | |||
1544 | retries = up.settings.max_retries; // reset the counter |
||
1545 | |||
1546 | // Handle chunk response |
||
1547 | if (curChunkSize < blob.size) { |
||
1548 | chunkBlob.destroy(); |
||
1549 | |||
1550 | offset += curChunkSize; |
||
1551 | file.loaded = Math.min(offset, blob.size); |
||
1552 | |||
1553 | up.trigger('ChunkUploaded', file, { |
||
1554 | offset : file.loaded, |
||
1555 | total : blob.size, |
||
1556 | response : xhr.responseText, |
||
1557 | status : xhr.status, |
||
1558 | responseHeaders: xhr.getAllResponseHeaders() |
||
1559 | }); |
||
1560 | |||
1561 | // stock Android browser doesn't fire upload progress events, but in chunking mode we can fake them |
||
1562 | if (plupload.ua.browser === 'Android Browser') { |
||
1563 | // doesn't harm in general, but is not required anywhere else |
||
1564 | up.trigger('UploadProgress', file); |
||
1565 | } |
||
1566 | } else { |
||
1567 | file.loaded = file.size; |
||
1568 | } |
||
1569 | |||
1570 | chunkBlob = formData = null; // Free memory |
||
1571 | |||
1572 | // Check if file is uploaded |
||
1573 | if (!offset || offset >= blob.size) { |
||
1574 | // If file was modified, destory the copy |
||
1575 | if (file.size != file.origSize) { |
||
1576 | blob.destroy(); |
||
1577 | blob = null; |
||
1578 | } |
||
1579 | |||
1580 | up.trigger('UploadProgress', file); |
||
1581 | |||
1582 | file.status = plupload.DONE; |
||
1583 | file.completeTimestamp = +new Date(); |
||
1584 | |||
1585 | up.trigger('FileUploaded', file, { |
||
1586 | response : xhr.responseText, |
||
1587 | status : xhr.status, |
||
1588 | responseHeaders: xhr.getAllResponseHeaders() |
||
1589 | }); |
||
1590 | } else { |
||
1591 | // Still chunks left |
||
1592 | delay(uploadNextChunk, 1); // run detached, otherwise event handlers interfere |
||
1593 | } |
||
1594 | }; |
||
1595 | |||
1596 | xhr.onerror = function() { |
||
1597 | handleError(); |
||
1598 | }; |
||
1599 | |||
1600 | xhr.onloadend = function() { |
||
1601 | this.destroy(); |
||
1602 | }; |
||
1603 | |||
1604 | // Build multipart request |
||
1605 | if (up.settings.multipart && features.multipart) { |
||
1606 | xhr.open(up.settings.http_method, url, true); |
||
1607 | |||
1608 | // Set custom headers |
||
1609 | plupload.each(up.settings.headers, function(value, name) { |
||
1610 | xhr.setRequestHeader(name, value); |
||
1611 | }); |
||
1612 | |||
1613 | formData = new o.xhr.FormData(); |
||
1614 | |||
1615 | // Add multipart params |
||
1616 | plupload.each(plupload.extend(args, up.settings.multipart_params), function(value, name) { |
||
1617 | formData.append(name, value); |
||
1618 | }); |
||
1619 | |||
1620 | // Add file and send it |
||
1621 | formData.append(up.settings.file_data_name, chunkBlob); |
||
1622 | xhr.send(formData, runtimeOptions); |
||
1623 | } else { |
||
1624 | // if no multipart, send as binary stream |
||
1625 | url = plupload.buildUrl(up.settings.url, plupload.extend(args, up.settings.multipart_params)); |
||
1626 | |||
1627 | xhr.open(up.settings.http_method, url, true); |
||
1628 | |||
1629 | // Set custom headers |
||
1630 | plupload.each(up.settings.headers, function(value, name) { |
||
1631 | xhr.setRequestHeader(name, value); |
||
1632 | }); |
||
1633 | |||
1634 | // do not set Content-Type, if it was defined previously (see #1203) |
||
1635 | if (!xhr.hasRequestHeader('Content-Type')) { |
||
1636 | xhr.setRequestHeader('Content-Type', 'application/octet-stream'); // Binary stream header |
||
1637 | } |
||
1638 | |||
1639 | xhr.send(chunkBlob, runtimeOptions); |
||
1640 | } |
||
1641 | } |
||
1642 | |||
1643 | |||
1644 | blob = file.getSource(); |
||
1645 | |||
1646 | // Start uploading chunks |
||
1647 | if (!plupload.isEmptyObj(up.settings.resize) && plupload.inArray(blob.type, ['image/jpeg', 'image/png']) !== -1) { |
||
1648 | // Resize if required |
||
1649 | resizeImage(blob, up.settings.resize, runtimeOptions, function(resizedBlob) { |
||
1650 | blob = resizedBlob; |
||
1651 | file.size = resizedBlob.size; |
||
1652 | uploadNextChunk(); |
||
1653 | }); |
||
1654 | } else { |
||
1655 | uploadNextChunk(); |
||
1656 | } |
||
1657 | } |
||
1658 | |||
1659 | |||
1660 | function onUploadProgress(up, file) { |
||
1661 | calcFile(file); |
||
1662 | } |
||
1663 | |||
1664 | |||
1665 | function onStateChanged(up) { |
||
1666 | if (up.state == plupload.STARTED) { |
||
1667 | // Get start time to calculate bps |
||
1668 | startTime = (+new Date()); |
||
1669 | } else if (up.state == plupload.STOPPED) { |
||
1670 | // Reset currently uploading files |
||
1671 | for (var i = up.files.length - 1; i >= 0; i--) { |
||
1672 | if (up.files[i].status == plupload.UPLOADING) { |
||
1673 | up.files[i].status = plupload.QUEUED; |
||
1674 | calc(); |
||
1675 | } |
||
1676 | } |
||
1677 | } |
||
1678 | } |
||
1679 | |||
1680 | |||
1681 | function onCancelUpload() { |
||
1682 | if (xhr) { |
||
1683 | xhr.abort(); |
||
1684 | } |
||
1685 | } |
||
1686 | |||
1687 | |||
1688 | function onFileUploaded(up) { |
||
1689 | calc(); |
||
1690 | |||
1691 | // Upload next file but detach it from the error event |
||
1692 | // since other custom listeners might want to stop the queue |
||
1693 | delay(function() { |
||
1694 | uploadNext.call(up); |
||
1695 | }, 1); |
||
1696 | } |
||
1697 | |||
1698 | |||
1699 | function onError(up, err) { |
||
1700 | if (err.code === plupload.INIT_ERROR) { |
||
1701 | up.destroy(); |
||
1702 | } |
||
1703 | // Set failed status if an error occured on a file |
||
1704 | else if (err.code === plupload.HTTP_ERROR) { |
||
1705 | err.file.status = plupload.FAILED; |
||
1706 | err.file.completeTimestamp = +new Date(); |
||
1707 | calcFile(err.file); |
||
1708 | |||
1709 | // Upload next file but detach it from the error event |
||
1710 | // since other custom listeners might want to stop the queue |
||
1711 | if (up.state == plupload.STARTED) { // upload in progress |
||
1712 | up.trigger('CancelUpload'); |
||
1713 | delay(function() { |
||
1714 | uploadNext.call(up); |
||
1715 | }, 1); |
||
1716 | } |
||
1717 | } |
||
1718 | } |
||
1719 | |||
1720 | |||
1721 | function onDestroy(up) { |
||
1722 | up.stop(); |
||
1723 | |||
1724 | // Purge the queue |
||
1725 | plupload.each(files, function(file) { |
||
1726 | file.destroy(); |
||
1727 | }); |
||
1728 | files = []; |
||
1729 | |||
1730 | if (fileInputs.length) { |
||
1731 | plupload.each(fileInputs, function(fileInput) { |
||
1732 | fileInput.destroy(); |
||
1733 | }); |
||
1734 | fileInputs = []; |
||
1735 | } |
||
1736 | |||
1737 | if (fileDrops.length) { |
||
1738 | plupload.each(fileDrops, function(fileDrop) { |
||
1739 | fileDrop.destroy(); |
||
1740 | }); |
||
1741 | fileDrops = []; |
||
1742 | } |
||
1743 | |||
1744 | preferred_caps = {}; |
||
1745 | disabled = false; |
||
1746 | startTime = xhr = null; |
||
1747 | total.reset(); |
||
1748 | } |
||
1749 | |||
1750 | |||
1751 | // Default settings |
||
1752 | settings = { |
||
1753 | chunk_size: 0, |
||
1754 | file_data_name: 'file', |
||
1755 | filters: { |
||
1756 | mime_types: [], |
||
1757 | max_file_size: 0, |
||
1758 | prevent_duplicates: false, |
||
1759 | prevent_empty: true |
||
1760 | }, |
||
1761 | flash_swf_url: 'js/Moxie.swf', |
||
1762 | http_method: 'POST', |
||
1763 | max_retries: 0, |
||
1764 | multipart: true, |
||
1765 | multi_selection: true, |
||
1766 | resize: false, |
||
1767 | runtimes: Runtime.order, |
||
1768 | send_file_name: true, |
||
1769 | send_chunk_number: true, |
||
1770 | silverlight_xap_url: 'js/Moxie.xap' |
||
1771 | }; |
||
1772 | |||
1773 | |||
1774 | setOption.call(this, options, null, true); |
||
1775 | |||
1776 | // Inital total state |
||
1777 | total = new plupload.QueueProgress(); |
||
1778 | |||
1779 | // Add public methods |
||
1780 | plupload.extend(this, { |
||
1781 | |||
1782 | /** |
||
1783 | * Unique id for the Uploader instance. |
||
1784 | * |
||
1785 | * @property id |
||
1786 | * @type String |
||
1787 | */ |
||
1788 | id : uid, |
||
1789 | uid : uid, // mOxie uses this to differentiate between event targets |
||
1790 | |||
1791 | /** |
||
1792 | * Current state of the total uploading progress. This one can either be plupload.STARTED or plupload.STOPPED. |
||
1793 | * These states are controlled by the stop/start methods. The default value is STOPPED. |
||
1794 | * |
||
1795 | * @property state |
||
1796 | * @type Number |
||
1797 | */ |
||
1798 | state : plupload.STOPPED, |
||
1799 | |||
1800 | /** |
||
1801 | * Map of features that are available for the uploader runtime. Features will be filled |
||
1802 | * before the init event is called, these features can then be used to alter the UI for the end user. |
||
1803 | * Some of the current features that might be in this map is: dragdrop, chunks, jpgresize, pngresize. |
||
1804 | * |
||
1805 | * @property features |
||
1806 | * @type Object |
||
1807 | */ |
||
1808 | features : {}, |
||
1809 | |||
1810 | /** |
||
1811 | * Current runtime name. |
||
1812 | * |
||
1813 | * @property runtime |
||
1814 | * @type String |
||
1815 | */ |
||
1816 | runtime : null, |
||
1817 | |||
1818 | /** |
||
1819 | * Current upload queue, an array of File instances. |
||
1820 | * |
||
1821 | * @property files |
||
1822 | * @type Array |
||
1823 | * @see plupload.File |
||
1824 | */ |
||
1825 | files : files, |
||
1826 | |||
1827 | /** |
||
1828 | * Object with name/value settings. |
||
1829 | * |
||
1830 | * @property settings |
||
1831 | * @type Object |
||
1832 | */ |
||
1833 | settings : settings, |
||
1834 | |||
1835 | /** |
||
1836 | * Total progess information. How many files has been uploaded, total percent etc. |
||
1837 | * |
||
1838 | * @property total |
||
1839 | * @type plupload.QueueProgress |
||
1840 | */ |
||
1841 | total : total, |
||
1842 | |||
1843 | |||
1844 | /** |
||
1845 | * Initializes the Uploader instance and adds internal event listeners. |
||
1846 | * |
||
1847 | * @method init |
||
1848 | */ |
||
1849 | init : function() { |
||
1850 | var self = this, opt, preinitOpt, err; |
||
1 ignored issue
–
show
|
|||
1851 | |||
1852 | preinitOpt = self.getOption('preinit'); |
||
1853 | if (typeof(preinitOpt) == "function") { |
||
1854 | preinitOpt(self); |
||
1855 | } else { |
||
1856 | plupload.each(preinitOpt, function(func, name) { |
||
1857 | self.bind(name, func); |
||
1858 | }); |
||
1859 | } |
||
1860 | |||
1861 | bindEventListeners.call(self); |
||
1862 | |||
1863 | // Check for required options |
||
1864 | plupload.each(['container', 'browse_button', 'drop_element'], function(el) { |
||
1865 | if (self.getOption(el) === null) { |
||
1 ignored issue
–
show
|
|||
1866 | err = { |
||
1867 | code : plupload.INIT_ERROR, |
||
1868 | message : plupload.sprintf(plupload.translate("%s specified, but cannot be found."), el) |
||
1869 | } |
||
1870 | return false; |
||
1871 | } |
||
1872 | }); |
||
1873 | |||
1874 | if (err) { |
||
1875 | return self.trigger('Error', err); |
||
1876 | } |
||
1877 | |||
1878 | |||
1879 | if (!settings.browse_button && !settings.drop_element) { |
||
1880 | return self.trigger('Error', { |
||
1881 | code : plupload.INIT_ERROR, |
||
1882 | message : plupload.translate("You must specify either browse_button or drop_element.") |
||
1883 | }); |
||
1884 | } |
||
1885 | |||
1886 | |||
1887 | initControls.call(self, settings, function(inited) { |
||
1888 | var initOpt = self.getOption('init'); |
||
1889 | if (typeof(initOpt) == "function") { |
||
1890 | initOpt(self); |
||
1891 | } else { |
||
1892 | plupload.each(initOpt, function(func, name) { |
||
1893 | self.bind(name, func); |
||
1894 | }); |
||
1895 | } |
||
1896 | |||
1897 | if (inited) { |
||
1898 | self.runtime = Runtime.getInfo(getRUID()).type; |
||
1899 | self.trigger('Init', { runtime: self.runtime }); |
||
1900 | self.trigger('PostInit'); |
||
1901 | } else { |
||
1902 | self.trigger('Error', { |
||
1903 | code : plupload.INIT_ERROR, |
||
1904 | message : plupload.translate('Init error.') |
||
1905 | }); |
||
1906 | } |
||
1907 | }); |
||
1 ignored issue
–
show
|
|||
1908 | }, |
||
1909 | |||
1910 | /** |
||
1911 | * Set the value for the specified option(s). |
||
1912 | * |
||
1913 | * @method setOption |
||
1914 | * @since 2.1 |
||
1915 | * @param {String|Object} option Name of the option to change or the set of key/value pairs |
||
1916 | * @param {Mixed} [value] Value for the option (is ignored, if first argument is object) |
||
1917 | */ |
||
1918 | setOption: function(option, value) { |
||
1919 | setOption.call(this, option, value, !this.runtime); // until runtime not set we do not need to reinitialize |
||
1920 | }, |
||
1921 | |||
1922 | /** |
||
1923 | * Get the value for the specified option or the whole configuration, if not specified. |
||
1924 | * |
||
1925 | * @method getOption |
||
1926 | * @since 2.1 |
||
1927 | * @param {String} [option] Name of the option to get |
||
1928 | * @return {Mixed} Value for the option or the whole set |
||
1929 | */ |
||
1930 | getOption: function(option) { |
||
1931 | if (!option) { |
||
1932 | return settings; |
||
1933 | } |
||
1934 | return settings[option]; |
||
1935 | }, |
||
1936 | |||
1937 | /** |
||
1938 | * Refreshes the upload instance by dispatching out a refresh event to all runtimes. |
||
1939 | * This would for example reposition flash/silverlight shims on the page. |
||
1940 | * |
||
1941 | * @method refresh |
||
1942 | */ |
||
1943 | refresh : function() { |
||
1944 | if (fileInputs.length) { |
||
1945 | plupload.each(fileInputs, function(fileInput) { |
||
1946 | fileInput.trigger('Refresh'); |
||
1947 | }); |
||
1948 | } |
||
1949 | this.trigger('Refresh'); |
||
1950 | }, |
||
1951 | |||
1952 | /** |
||
1953 | * Starts uploading the queued files. |
||
1954 | * |
||
1955 | * @method start |
||
1956 | */ |
||
1957 | start : function() { |
||
1958 | if (this.state != plupload.STARTED) { |
||
1959 | this.state = plupload.STARTED; |
||
1960 | this.trigger('StateChanged'); |
||
1961 | |||
1962 | uploadNext.call(this); |
||
1963 | } |
||
1964 | }, |
||
1965 | |||
1966 | /** |
||
1967 | * Stops the upload of the queued files. |
||
1968 | * |
||
1969 | * @method stop |
||
1970 | */ |
||
1971 | stop : function() { |
||
1972 | if (this.state != plupload.STOPPED) { |
||
1973 | this.state = plupload.STOPPED; |
||
1974 | this.trigger('StateChanged'); |
||
1975 | this.trigger('CancelUpload'); |
||
1976 | } |
||
1977 | }, |
||
1978 | |||
1979 | |||
1980 | /** |
||
1981 | * Disables/enables browse button on request. |
||
1982 | * |
||
1983 | * @method disableBrowse |
||
1984 | * @param {Boolean} disable Whether to disable or enable (default: true) |
||
1985 | */ |
||
1986 | disableBrowse : function() { |
||
1987 | disabled = arguments[0] !== undef ? arguments[0] : true; |
||
1988 | |||
1989 | if (fileInputs.length) { |
||
1990 | plupload.each(fileInputs, function(fileInput) { |
||
1991 | fileInput.disable(disabled); |
||
1992 | }); |
||
1993 | } |
||
1994 | |||
1995 | this.trigger('DisableBrowse', disabled); |
||
1996 | }, |
||
1997 | |||
1998 | /** |
||
1999 | * Returns the specified file object by id. |
||
2000 | * |
||
2001 | * @method getFile |
||
2002 | * @param {String} id File id to look for. |
||
2003 | * @return {plupload.File} File object or undefined if it wasn't found; |
||
2004 | */ |
||
2005 | getFile : function(id) { |
||
2006 | var i; |
||
2007 | for (i = files.length - 1; i >= 0; i--) { |
||
2008 | if (files[i].id === id) { |
||
2009 | return files[i]; |
||
2010 | } |
||
2011 | } |
||
1 ignored issue
–
show
|
|||
2012 | }, |
||
2013 | |||
2014 | /** |
||
2015 | * Adds file to the queue programmatically. Can be native file, instance of Plupload.File, |
||
2016 | * instance of mOxie.File, input[type="file"] element, or array of these. Fires FilesAdded, |
||
2017 | * if any files were added to the queue. Otherwise nothing happens. |
||
2018 | * |
||
2019 | * @method addFile |
||
2020 | * @since 2.0 |
||
2021 | * @param {plupload.File|mOxie.File|File|Node|Array} file File or files to add to the queue. |
||
2022 | * @param {String} [fileName] If specified, will be used as a name for the file |
||
2023 | */ |
||
2024 | addFile : function(file, fileName) { |
||
2025 | var self = this |
||
2026 | , queue = [] |
||
2027 | , filesAdded = [] |
||
2028 | , ruid |
||
2029 | ; |
||
2030 | |||
2031 | function filterFile(file, cb) { |
||
2032 | var queue = []; |
||
2033 | plupload.each(self.settings.filters, function(rule, name) { |
||
2034 | if (fileFilters[name]) { |
||
2035 | queue.push(function(cb) { |
||
2036 | fileFilters[name].call(self, rule, file, function(res) { |
||
2037 | cb(!res); |
||
2038 | }); |
||
2039 | }); |
||
2040 | } |
||
2041 | }); |
||
2042 | plupload.inSeries(queue, cb); |
||
2043 | } |
||
2044 | |||
2045 | /** |
||
2046 | * @method resolveFile |
||
2047 | * @private |
||
2048 | * @param {moxie.file.File|moxie.file.Blob|plupload.File|File|Blob|input[type="file"]} file |
||
2049 | */ |
||
2050 | function resolveFile(file) { |
||
2051 | var type = plupload.typeOf(file); |
||
2052 | |||
2053 | // moxie.file.File |
||
2054 | if (file instanceof o.file.File) { |
||
2055 | if (!file.ruid && !file.isDetached()) { |
||
2056 | if (!ruid) { // weird case |
||
2057 | return false; |
||
2058 | } |
||
2059 | file.ruid = ruid; |
||
2060 | file.connectRuntime(ruid); |
||
2061 | } |
||
2062 | resolveFile(new plupload.File(file)); |
||
1 ignored issue
–
show
|
|||
2063 | } |
||
2064 | // moxie.file.Blob |
||
2065 | else if (file instanceof o.file.Blob) { |
||
2066 | resolveFile(file.getSource()); |
||
2067 | file.destroy(); |
||
1 ignored issue
–
show
|
|||
2068 | } |
||
2069 | // plupload.File - final step for other branches |
||
2070 | else if (file instanceof plupload.File) { |
||
2071 | if (fileName) { |
||
2072 | file.name = fileName; |
||
2073 | } |
||
2074 | |||
2075 | queue.push(function(cb) { |
||
2076 | // run through the internal and user-defined filters, if any |
||
2077 | filterFile(file, function(err) { |
||
2078 | if (!err) { |
||
2079 | // make files available for the filters by updating the main queue directly |
||
2080 | files.push(file); |
||
2081 | // collect the files that will be passed to FilesAdded event |
||
2082 | filesAdded.push(file); |
||
2083 | |||
2084 | self.trigger("FileFiltered", file); |
||
2085 | } |
||
2086 | delay(cb, 1); // do not build up recursions or eventually we might hit the limits |
||
2087 | }); |
||
2088 | }); |
||
1 ignored issue
–
show
|
|||
2089 | } |
||
2090 | // native File or blob |
||
2091 | else if (plupload.inArray(type, ['file', 'blob']) !== -1) { |
||
2092 | resolveFile(new o.file.File(null, file)); |
||
1 ignored issue
–
show
|
|||
2093 | } |
||
2094 | // input[type="file"] |
||
2095 | else if (type === 'node' && plupload.typeOf(file.files) === 'filelist') { |
||
2096 | // if we are dealing with input[type="file"] |
||
2097 | plupload.each(file.files, resolveFile); |
||
1 ignored issue
–
show
|
|||
2098 | } |
||
2099 | // mixed array of any supported types (see above) |
||
2100 | else if (type === 'array') { |
||
1 ignored issue
–
show
|
|||
2101 | fileName = null; // should never happen, but unset anyway to avoid funny situations |
||
2102 | plupload.each(file, resolveFile); |
||
1 ignored issue
–
show
|
|||
2103 | } |
||
2104 | } |
||
2105 | |||
2106 | ruid = getRUID(); |
||
2107 | |||
2108 | resolveFile(file); |
||
2109 | |||
2110 | if (queue.length) { |
||
2111 | plupload.inSeries(queue, function() { |
||
2112 | // if any files left after filtration, trigger FilesAdded |
||
2113 | if (filesAdded.length) { |
||
2114 | self.trigger("FilesAdded", filesAdded); |
||
2115 | } |
||
2116 | }); |
||
2117 | } |
||
2118 | }, |
||
2119 | |||
2120 | /** |
||
2121 | * Removes a specific file. |
||
2122 | * |
||
2123 | * @method removeFile |
||
2124 | * @param {plupload.File|String} file File to remove from queue. |
||
2125 | */ |
||
2126 | removeFile : function(file) { |
||
2127 | var id = typeof(file) === 'string' ? file : file.id; |
||
2128 | |||
2129 | for (var i = files.length - 1; i >= 0; i--) { |
||
2130 | if (files[i].id === id) { |
||
2131 | return this.splice(i, 1)[0]; |
||
2132 | } |
||
2133 | } |
||
1 ignored issue
–
show
|
|||
2134 | }, |
||
2135 | |||
2136 | /** |
||
2137 | * Removes part of the queue and returns the files removed. This will also trigger the |
||
2138 | * FilesRemoved and QueueChanged events. |
||
2139 | * |
||
2140 | * @method splice |
||
2141 | * @param {Number} [start=0] Start index to remove from. |
||
2142 | * @param {Number} [length] Number of files to remove (defaults to number of files in the queue). |
||
2143 | * @return {Array} Array of files that was removed. |
||
2144 | */ |
||
2145 | splice : function(start, length) { |
||
2146 | // Splice and trigger events |
||
2147 | var removed = files.splice(start === undef ? 0 : start, length === undef ? files.length : length); |
||
2148 | |||
2149 | // if upload is in progress we need to stop it and restart after files are removed |
||
2150 | var restartRequired = false; |
||
2151 | if (this.state == plupload.STARTED) { // upload in progress |
||
2152 | plupload.each(removed, function(file) { |
||
2153 | if (file.status === plupload.UPLOADING) { |
||
1 ignored issue
–
show
|
|||
2154 | restartRequired = true; // do not restart, unless file that is being removed is uploading |
||
2155 | return false; |
||
2156 | } |
||
2157 | }); |
||
2158 | |||
2159 | if (restartRequired) { |
||
2160 | this.stop(); |
||
2161 | } |
||
2162 | } |
||
2163 | |||
2164 | this.trigger("FilesRemoved", removed); |
||
2165 | |||
2166 | // Dispose any resources allocated by those files |
||
2167 | plupload.each(removed, function(file) { |
||
2168 | file.destroy(); |
||
2169 | }); |
||
2170 | |||
2171 | if (restartRequired) { |
||
2172 | this.start(); |
||
2173 | } |
||
2174 | |||
2175 | return removed; |
||
2176 | }, |
||
2177 | |||
2178 | /** |
||
2179 | Dispatches the specified event name and its arguments to all listeners. |
||
2180 | |||
2181 | @method trigger |
||
2182 | @param {String} name Event name to fire. |
||
2183 | @param {Object..} Multiple arguments to pass along to the listener functions. |
||
2184 | */ |
||
2185 | |||
2186 | // override the parent method to match Plupload-like event logic |
||
2187 | dispatchEvent: function(type) { |
||
2188 | var list, args, result; |
||
1 ignored issue
–
show
|
|||
2189 | |||
2190 | type = type.toLowerCase(); |
||
2191 | |||
2192 | list = this.hasEventListener(type); |
||
2193 | |||
2194 | if (list) { |
||
2195 | // sort event list by priority |
||
2196 | list.sort(function(a, b) { return b.priority - a.priority; }); |
||
2197 | |||
2198 | // first argument should be current plupload.Uploader instance |
||
2199 | args = [].slice.call(arguments); |
||
2200 | args.shift(); |
||
2201 | args.unshift(this); |
||
2202 | |||
2203 | for (var i = 0; i < list.length; i++) { |
||
2204 | // Fire event, break chain if false is returned |
||
2205 | if (list[i].fn.apply(list[i].scope, args) === false) { |
||
2206 | return false; |
||
2207 | } |
||
2208 | } |
||
2209 | } |
||
2210 | return true; |
||
2211 | }, |
||
2212 | |||
2213 | /** |
||
2214 | Check whether uploader has any listeners to the specified event. |
||
2215 | |||
2216 | @method hasEventListener |
||
2217 | @param {String} name Event name to check for. |
||
2218 | */ |
||
2219 | |||
2220 | |||
2221 | /** |
||
2222 | Adds an event listener by name. |
||
2223 | |||
2224 | @method bind |
||
2225 | @param {String} name Event name to listen for. |
||
2226 | @param {function} fn Function to call ones the event gets fired. |
||
2227 | @param {Object} [scope] Optional scope to execute the specified function in. |
||
2228 | @param {Number} [priority=0] Priority of the event handler - handlers with higher priorities will be called first |
||
2229 | */ |
||
2230 | bind: function(name, fn, scope, priority) { |
||
2231 | // adapt moxie EventTarget style to Plupload-like |
||
2232 | plupload.Uploader.prototype.bind.call(this, name, fn, priority, scope); |
||
2233 | }, |
||
2234 | |||
2235 | /** |
||
2236 | Removes the specified event listener. |
||
2237 | |||
2238 | @method unbind |
||
2239 | @param {String} name Name of event to remove. |
||
2240 | @param {function} fn Function to remove from listener. |
||
2241 | */ |
||
2242 | |||
2243 | /** |
||
2244 | Removes all event listeners. |
||
2245 | |||
2246 | @method unbindAll |
||
2247 | */ |
||
2248 | |||
2249 | |||
2250 | /** |
||
2251 | * Destroys Plupload instance and cleans after itself. |
||
2252 | * |
||
2253 | * @method destroy |
||
2254 | */ |
||
2255 | destroy : function() { |
||
2256 | this.trigger('Destroy'); |
||
2257 | settings = total = null; // purge these exclusively |
||
2258 | this.unbindAll(); |
||
2259 | } |
||
2260 | }); |
||
2261 | }; |
||
2262 | |||
2263 | plupload.Uploader.prototype = o.core.EventTarget.instance; |
||
2264 | |||
2265 | /** |
||
2266 | * Constructs a new file instance. |
||
2267 | * |
||
2268 | * @class File |
||
2269 | * @constructor |
||
2270 | * |
||
2271 | * @param {Object} file Object containing file properties |
||
2272 | * @param {String} file.name Name of the file. |
||
2273 | * @param {Number} file.size File size. |
||
2274 | */ |
||
2275 | plupload.File = (function() { |
||
2276 | var filepool = {}; |
||
2277 | |||
2278 | function PluploadFile(file) { |
||
2279 | |||
2280 | plupload.extend(this, { |
||
2281 | |||
2282 | /** |
||
2283 | * File id this is a globally unique id for the specific file. |
||
2284 | * |
||
2285 | * @property id |
||
2286 | * @type String |
||
2287 | */ |
||
2288 | id: plupload.guid(), |
||
2289 | |||
2290 | /** |
||
2291 | * File name for example "myfile.gif". |
||
2292 | * |
||
2293 | * @property name |
||
2294 | * @type String |
||
2295 | */ |
||
2296 | name: file.name || file.fileName, |
||
2297 | |||
2298 | /** |
||
2299 | * File type, `e.g image/jpeg` |
||
2300 | * |
||
2301 | * @property type |
||
2302 | * @type String |
||
2303 | */ |
||
2304 | type: file.type || '', |
||
2305 | |||
2306 | /** |
||
2307 | * Relative path to the file inside a directory |
||
2308 | * |
||
2309 | * @property relativePath |
||
2310 | * @type String |
||
2311 | * @default '' |
||
2312 | */ |
||
2313 | relativePath: file.relativePath || '', |
||
2314 | |||
2315 | /** |
||
2316 | * File size in bytes (may change after client-side manupilation). |
||
2317 | * |
||
2318 | * @property size |
||
2319 | * @type Number |
||
2320 | */ |
||
2321 | size: file.fileSize || file.size, |
||
2322 | |||
2323 | /** |
||
2324 | * Original file size in bytes. |
||
2325 | * |
||
2326 | * @property origSize |
||
2327 | * @type Number |
||
2328 | */ |
||
2329 | origSize: file.fileSize || file.size, |
||
2330 | |||
2331 | /** |
||
2332 | * Number of bytes uploaded of the files total size. |
||
2333 | * |
||
2334 | * @property loaded |
||
2335 | * @type Number |
||
2336 | */ |
||
2337 | loaded: 0, |
||
2338 | |||
2339 | /** |
||
2340 | * Number of percentage uploaded of the file. |
||
2341 | * |
||
2342 | * @property percent |
||
2343 | * @type Number |
||
2344 | */ |
||
2345 | percent: 0, |
||
2346 | |||
2347 | /** |
||
2348 | * Status constant matching the plupload states QUEUED, UPLOADING, FAILED, DONE. |
||
2349 | * |
||
2350 | * @property status |
||
2351 | * @type Number |
||
2352 | * @see plupload |
||
2353 | */ |
||
2354 | status: plupload.QUEUED, |
||
2355 | |||
2356 | /** |
||
2357 | * Date of last modification. |
||
2358 | * |
||
2359 | * @property lastModifiedDate |
||
2360 | * @type {String} |
||
2361 | */ |
||
2362 | lastModifiedDate: file.lastModifiedDate || (new Date()).toLocaleString(), // Thu Aug 23 2012 19:40:00 GMT+0400 (GET) |
||
2363 | |||
2364 | |||
2365 | /** |
||
2366 | * Set when file becomes plupload.DONE or plupload.FAILED. Is used to calculate proper plupload.QueueProgress.bytesPerSec. |
||
2367 | * @private |
||
2368 | * @property completeTimestamp |
||
2369 | * @type {Number} |
||
2370 | */ |
||
2371 | completeTimestamp: 0, |
||
2372 | |||
2373 | /** |
||
2374 | * Returns native window.File object, when it's available. |
||
2375 | * |
||
2376 | * @method getNative |
||
2377 | * @return {window.File} or null, if plupload.File is of different origin |
||
2378 | */ |
||
2379 | getNative: function() { |
||
2380 | var file = this.getSource().getSource(); |
||
2381 | return plupload.inArray(plupload.typeOf(file), ['blob', 'file']) !== -1 ? file : null; |
||
2382 | }, |
||
2383 | |||
2384 | /** |
||
2385 | * Returns mOxie.File - unified wrapper object that can be used across runtimes. |
||
2386 | * |
||
2387 | * @method getSource |
||
2388 | * @return {mOxie.File} or null |
||
2389 | */ |
||
2390 | getSource: function() { |
||
2391 | if (!filepool[this.id]) { |
||
2392 | return null; |
||
2393 | } |
||
2394 | return filepool[this.id]; |
||
2395 | }, |
||
2396 | |||
2397 | /** |
||
2398 | * Destroys plupload.File object. |
||
2399 | * |
||
2400 | * @method destroy |
||
2401 | */ |
||
2402 | destroy: function() { |
||
2403 | var src = this.getSource(); |
||
2404 | if (src) { |
||
2405 | src.destroy(); |
||
2406 | delete filepool[this.id]; |
||
2407 | } |
||
2408 | } |
||
2409 | }); |
||
2410 | |||
2411 | filepool[this.id] = file; |
||
2412 | } |
||
2413 | |||
2414 | return PluploadFile; |
||
2415 | }()); |
||
2416 | |||
2417 | |||
2418 | /** |
||
2419 | * Constructs a queue progress. |
||
2420 | * |
||
2421 | * @class QueueProgress |
||
2422 | * @constructor |
||
2423 | */ |
||
2424 | plupload.QueueProgress = function() { |
||
2425 | var self = this; // Setup alias for self to reduce code size when it's compressed |
||
2426 | |||
2427 | /** |
||
2428 | * Total queue file size. |
||
2429 | * |
||
2430 | * @property size |
||
2431 | * @type Number |
||
2432 | */ |
||
2433 | self.size = 0; |
||
2434 | |||
2435 | /** |
||
2436 | * Total bytes uploaded. |
||
2437 | * |
||
2438 | * @property loaded |
||
2439 | * @type Number |
||
2440 | */ |
||
2441 | self.loaded = 0; |
||
2442 | |||
2443 | /** |
||
2444 | * Number of files uploaded. |
||
2445 | * |
||
2446 | * @property uploaded |
||
2447 | * @type Number |
||
2448 | */ |
||
2449 | self.uploaded = 0; |
||
2450 | |||
2451 | /** |
||
2452 | * Number of files failed to upload. |
||
2453 | * |
||
2454 | * @property failed |
||
2455 | * @type Number |
||
2456 | */ |
||
2457 | self.failed = 0; |
||
2458 | |||
2459 | /** |
||
2460 | * Number of files yet to be uploaded. |
||
2461 | * |
||
2462 | * @property queued |
||
2463 | * @type Number |
||
2464 | */ |
||
2465 | self.queued = 0; |
||
2466 | |||
2467 | /** |
||
2468 | * Total percent of the uploaded bytes. |
||
2469 | * |
||
2470 | * @property percent |
||
2471 | * @type Number |
||
2472 | */ |
||
2473 | self.percent = 0; |
||
2474 | |||
2475 | /** |
||
2476 | * Bytes uploaded per second. |
||
2477 | * |
||
2478 | * @property bytesPerSec |
||
2479 | * @type Number |
||
2480 | */ |
||
2481 | self.bytesPerSec = 0; |
||
2482 | |||
2483 | /** |
||
2484 | * Resets the progress to its initial values. |
||
2485 | * |
||
2486 | * @method reset |
||
2487 | */ |
||
2488 | self.reset = function() { |
||
2489 | self.size = self.loaded = self.uploaded = self.failed = self.queued = self.percent = self.bytesPerSec = 0; |
||
2490 | }; |
||
2491 | }; |
||
2492 | |||
2493 | exports.plupload = plupload; |
||
2494 | |||
2495 | }(this, moxie)); |
||
2496 | |||
2497 | })); |
This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.
To learn more about declaring variables in Javascript, see the MDN.