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