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