3
Copyright 2012 Yahoo! Inc. All rights reserved.
4
Licensed under the BSD License.
5
http://yuilibrary.com/license/
7
YUI.add('uploader-flash', function(Y) {
11
* This module provides a UI for file selection and multiple file upload capability using
12
* Flash as a transport engine.
13
* The supported features include: automatic upload queue management, upload progress
14
* tracking, file filtering, server response retrieval and error reporting.
16
* @module uploader-flash
19
// Shorthands for external modules
20
var substitute = Y.substitute,
21
UploaderQueue = Y.Uploader.Queue,
22
getCN = Y.ClassNameManager.getClassName,
23
UPLOADER = 'uploader',
24
SELECT_FILES = getCN(UPLOADER, 'selectfiles-button');
28
* This module provides a UI for file selection and multiple file upload capability
29
* using Flash as a transport engine.
30
* @class UploaderFlash
33
* @param {Object} config Configuration object.
36
function UploaderFlash(config) {
37
UploaderFlash.superclass.constructor.apply ( this, arguments );
42
Y.UploaderFlash = Y.extend(UploaderFlash, Y.Widget, {
45
* Stored value of the current button state (based on
46
* mouse events dispatched by the Flash player)
47
* @property _buttonState
54
* Stored value of the current button focus state (based
55
* on keyboard and mouse events).
56
* @property _buttonFocus
63
* Stored value of the unique id for the container that holds the
66
* @property _swfContainerId
70
_swfContainerId: null,
73
* Stored reference to the instance of SWF used to host the
76
* @property _swfReference
83
* Stored reference to the instance of Uploader.Queue used to manage
84
* the upload process. This is a read-only property that only exists
85
* during an active upload process. Only one queue can be active at
86
* a time; if an upload start is attempted while a queue is active,
90
* @type {Y.Uploader.Queue}
95
* Stored event bindings for keyboard navigation to and from the uploader.
97
* @property _tabElementBindings
101
_tabElementBindings: null,
105
* Construction logic executed during UploaderFlash instantiation.
107
* @method initializer
110
initializer : function () {
112
// Assign protected variable values
113
this._swfContainerId = Y.guid("uploader");
114
this._swfReference = null;
116
this._buttonState = "up";
117
this._buttonFocus = null;
118
this._tabElementBindings = null;
120
// Publish available events
123
* Signals that files have been selected.
126
* @param event {Event} The event object for the `fileselect` with the
130
* <dd>An `Array` of files selected by the user, encapsulated
131
* in Y.FileFlash objects.</dd>
134
this.publish("fileselect");
137
* Signals that an upload of multiple files has been started.
140
* @param event {Event} The event object for the `uploadstart`.
142
this.publish("uploadstart");
145
* Signals that an upload of a specific file has started.
147
* @event fileuploadstart
148
* @param event {Event} The event object for the `fileuploadstart` with the
152
* <dd>A reference to the Y.File that dispatched the event.</dd>
153
* <dt>originEvent</dt>
154
* <dd>The original event dispatched by Y.File.</dd>
157
this.publish("fileuploadstart");
160
* Reports on upload progress of a specific file.
162
* @event uploadprogress
163
* @param event {Event} The event object for the `uploadprogress` with the
166
* <dt>bytesLoaded</dt>
167
* <dd>The number of bytes of the file that has been uploaded</dd>
168
* <dt>bytesTotal</dt>
169
* <dd>The total number of bytes in the file</dd>
170
* <dt>percentLoaded</dt>
171
* <dd>The fraction of the file that has been uploaded, out of 100</dd>
172
* <dt>originEvent</dt>
173
* <dd>The original event dispatched by the SWF uploader</dd>
176
this.publish("uploadprogress");
179
* Reports on the total upload progress of the file list.
181
* @event totaluploadprogress
182
* @param event {Event} The event object for the `totaluploadprogress` with the
185
* <dt>bytesLoaded</dt>
186
* <dd>The number of bytes of the file list that has been uploaded</dd>
187
* <dt>bytesTotal</dt>
188
* <dd>The total number of bytes in the file list</dd>
189
* <dt>percentLoaded</dt>
190
* <dd>The fraction of the file list that has been uploaded, out of 100</dd>
193
this.publish("totaluploadprogress");
196
* Signals that a single file upload has been completed.
198
* @event uploadcomplete
199
* @param event {Event} The event object for the `uploadcomplete` with the
203
* <dd>The pointer to the instance of `Y.File` whose upload has been completed.</dd>
204
* <dt>originEvent</dt>
205
* <dd>The original event fired by the SWF Uploader</dd>
207
* <dd>Data returned by the server.</dd>
210
this.publish("uploadcomplete");
213
* Signals that the upload process of the entire file list has been completed.
215
* @event alluploadscomplete
216
* @param event {Event} The event object for the `alluploadscomplete`.
218
this.publish("alluploadscomplete");
221
* Signals that a error has occurred in a specific file's upload process.
224
* @param event {Event} The event object for the `uploaderror` with the
227
* <dt>originEvent</dt>
228
* <dd>The original error event fired by the SWF Uploader. </dd>
230
* <dd>The pointer at the instance of Y.FileFlash that returned the error.</dd>
232
* <dd>The source of the upload error, either "io" or "http"</dd>
234
* <dd>The message that accompanied the error. Corresponds to the text of
235
* the error in cases where source is "io", and to the HTTP status for
236
cases where source is "http".</dd>
239
this.publish("uploaderror");
242
* Signals that a mouse has begun hovering over the `Select Files` button.
245
* @param event {Event} The event object for the `mouseenter` event.
247
this.publish("mouseenter");
250
* Signals that a mouse has stopped hovering over the `Select Files` button.
253
* @param event {Event} The event object for the `mouseleave` event.
255
this.publish("mouseleave");
258
* Signals that a mouse button has been pressed over the `Select Files` button.
261
* @param event {Event} The event object for the `mousedown` event.
263
this.publish("mousedown");
266
* Signals that a mouse button has been released over the `Select Files` button.
269
* @param event {Event} The event object for the `mouseup` event.
271
this.publish("mouseup");
274
* Signals that a mouse has been clicked over the `Select Files` button.
277
* @param event {Event} The event object for the `click` event.
279
this.publish("click");
283
* Creates the DOM structure for the UploaderFlash.
284
* UploaderFlash's DOM structure consists of two layers: the base "Select Files"
285
* button that can be replaced by the developer's widget of choice; and a transparent
286
* Flash overlay positoned above the button that captures all input events.
287
* The `position` style attribute of the `boundingBox` of the `Uploader` widget
288
* is forced to be `relative`, in order to accommodate the Flash player overlay
289
* (which is `position`ed `absolute`ly).
294
renderUI : function () {
295
var boundingBox = this.get("boundingBox"),
296
contentBox = this.get('contentBox'),
297
selFilesButton = this.get("selectFilesButton");
299
boundingBox.setStyle("position", "relative");
300
selFilesButton.setStyles({width: "100%", height: "100%"});
301
contentBox.append(selFilesButton);
302
contentBox.append(Y.Node.create(substitute(UploaderFlash.FLASH_CONTAINER,
303
{swfContainerId: this._swfContainerId})));
304
var flashContainer = Y.one("#" + this._swfContainerId);
305
var params = {version: "10.0.45",
306
fixedAttributes: {wmode: "transparent",
307
allowScriptAccess:"always",
308
allowNetworking:"all",
312
this._swfReference = new Y.SWF(flashContainer, this.get("swfURL"), params);
316
* Binds handlers to the UploaderFlash UI events and propagates attribute
317
* values to the Flash player.
318
* The propagation of initial values is set to occur once the Flash player
319
* instance is ready (as indicated by the `swfReady` event.)
324
bindUI : function () {
326
this._swfReference.on("swfReady", function () {
327
this._setMultipleFiles();
328
this._setFileFilters();
329
this._triggerEnabled();
330
this.after("multipleFilesChange", this._setMultipleFiles, this);
331
this.after("fileFiltersChange", this._setFileFilters, this);
332
this.after("enabledChange", this._triggerEnabled, this);
335
this._swfReference.on("fileselect", this._updateFileList, this);
337
this.after("tabElementsChange", this._attachTabElements);
338
this._attachTabElements();
340
// this._swfReference.on("trace", function (ev) {console.log(ev.message);});
342
this._swfReference.on("mouseenter", function () {
343
this.fire("mouseenter");
344
this._setButtonClass("hover", true);
345
if (this._buttonState == "down") {
346
this._setButtonClass("active", true);
349
this._swfReference.on("mouseleave", function () {
350
this.fire("mouseleave");
351
this._setButtonClass("hover", false);
352
this._setButtonClass("active", false);
355
this._swfReference.on("mousedown", function () {
356
this.fire("mousedown");
357
this._buttonState = "down";
358
this._setButtonClass("active", true);
360
this._swfReference.on("mouseup", function () {
361
this.fire("mouseup");
362
this._buttonState = "up";
363
this._setButtonClass("active", false);
365
this._swfReference.on("click", function () {
367
this._buttonFocus = true;
368
this._setButtonClass("focus", true);
369
Y.one("body").focus();
370
this._swfReference._swf.focus();
375
* Attaches keyboard bindings to enabling tabbing to and from the instance of the Flash
376
* player in the Uploader widget. If the previous and next elements are specified, the
377
* keyboard bindings enable the user to tab from the `tabElements["from"]` node to the
378
* Flash-powered "Select Files" button, and to the `tabElements["to"]` node.
380
* @method _attachTabElements
382
* @param ev {Event} Optional event payload if called as a `tabElementsChange` handler.
384
_attachTabElements : function (ev) {
385
if (this.get("tabElements") !== null && this.get("tabElements").from !== null && this.get("tabElements").to !== null) {
387
if (this._tabElementBindings !== null) {
388
this._tabElementBindings.from.detach();
389
this._tabElementBindings.to.detach();
390
this._tabElementBindings.tabback.detach();
391
this._tabElementBindings.tabforward.detach();
392
this._tabElementBindings.focus.detach();
393
this._tabElementBindings.blur.detach();
396
this._tabElementBindings = {};
399
var fromElement = Y.one(this.get("tabElements").from);
400
var toElement = Y.one(this.get("tabElements").to);
403
this._tabElementBindings.from = fromElement.on("keydown", function (ev) {
404
if (ev.keyCode == 9 && !ev.shiftKey) {
406
this._swfReference._swf.setAttribute("tabindex", 0);
407
this._swfReference._swf.setAttribute("role", "button");
408
this._swfReference._swf.setAttribute("aria-label", this.get("selectButtonLabel"));
409
this._swfReference._swf.focus();
412
this._tabElementBindings.to = toElement.on("keydown", function (ev) {
413
if (ev.keyCode == 9 && ev.shiftKey) {
415
this._swfReference._swf.setAttribute("tabindex", 0);
416
this._swfReference._swf.setAttribute("role", "button");
417
this._swfReference._swf.setAttribute("aria-label", this.get("selectButtonLabel"));
418
this._swfReference._swf.focus();
421
this._tabElementBindings.tabback = this._swfReference.on("tabback", function (ev) {this._swfReference._swf.blur(); setTimeout(function () {fromElement.focus();}, 30);}, this);
422
this._tabElementBindings.tabforward = this._swfReference.on("tabforward", function (ev) {this._swfReference._swf.blur(); setTimeout(function () {toElement.focus();}, 30);}, this);
424
this._tabElementBindings.focus = this._swfReference._swf.on("focus", function (ev) {this._buttonFocus = true; this._setButtonClass("focus", true);}, this);
425
this._tabElementBindings.blur = this._swfReference._swf.on("blur", function (ev) {this._buttonFocus = false; this._setButtonClass("focus", false);}, this);
427
else if (this._tabElementBindings !== null) {
428
this._tabElementBindings.from.detach();
429
this._tabElementBindings.to.detach();
430
this._tabElementBindings.tabback.detach();
431
this._tabElementBindings.tabforward.detach();
432
this._tabElementBindings.focus.detach();
433
this._tabElementBindings.blur.detach();
439
* Adds or removes a specified state CSS class to the underlying uploader button.
441
* @method _setButtonClass
443
* @param state {String} The name of the state enumerated in `buttonClassNames` attribute
444
* from which to derive the needed class name.
445
* @param add {Boolean} A Boolean indicating whether to add or remove the class.
447
_setButtonClass : function (state, add) {
449
this.get("selectFilesButton").addClass(this.get("buttonClassNames")[state]);
452
this.get("selectFilesButton").removeClass(this.get("buttonClassNames")[state]);
458
* Syncs the state of the `fileFilters` attribute between the instance of UploaderFlash
459
* and the Flash player.
461
* @method _setFileFilters
464
_setFileFilters : function () {
465
if (this._swfReference && this.get("fileFilters") !== null) {
466
this._swfReference.callSWF("setFileFilters", [this.get("fileFilters")]);
474
* Syncs the state of the `multipleFiles` attribute between this class
475
* and the Flash uploader.
477
* @method _setMultipleFiles
480
_setMultipleFiles : function () {
481
if (this._swfReference) {
482
this._swfReference.callSWF("setAllowMultipleFiles", [this.get("multipleFiles")]);
487
* Syncs the state of the `enabled` attribute between this class
488
* and the Flash uploader.
490
* @method _triggerEnabled
493
_triggerEnabled : function () {
494
if (this.get("enabled")) {
495
this._swfReference.callSWF("enable");
496
this._swfReference._swf.setAttribute("aria-disabled", "false");
497
this._setButtonClass("disabled", false);
500
this._swfReference.callSWF("disable");
501
this._swfReference._swf.setAttribute("aria-disabled", "true");
502
this._setButtonClass("disabled", true);
507
* Adjusts the content of the `fileList` based on the results of file selection
508
* and the `appendNewFiles` attribute. If the `appendNewFiles` attribute is true,
509
* then selected files are appended to the existing list; otherwise, the list is
510
* cleared and populated with the newly selected files.
512
* @method _updateFileList
513
* @param ev {Event} The file selection event received from the uploader.
516
_updateFileList : function (ev) {
518
Y.one("body").focus();
519
this._swfReference._swf.focus();
522
var newfiles = ev.fileList,
523
fileConfObjects = [],
525
swfRef = this._swfReference;
527
Y.each(newfiles, function (value) {
528
var newFileConf = {};
529
newFileConf.id = value.fileId;
530
newFileConf.name = value.fileReference.name;
531
newFileConf.size = value.fileReference.size;
532
newFileConf.type = value.fileReference.type;
533
newFileConf.dateCreated = value.fileReference.creationDate;
534
newFileConf.dateModified = value.fileReference.modificationDate;
535
newFileConf.uploader = swfRef;
537
fileConfObjects.push(newFileConf);
540
Y.each(fileConfObjects, function (value) {
541
parsedFiles.push(new Y.FileFlash(value));
544
this.fire("fileselect", {fileList: parsedFiles});
546
var oldfiles = this.get("fileList");
549
this.get("appendNewFiles") ? oldfiles.concat(parsedFiles) : parsedFiles );
556
* Handles and retransmits events fired by `Y.FileFlash` and `Y.Uploader.Queue`.
558
* @method _uploadEventHandler
559
* @param event The event dispatched during the upload process.
562
_uploadEventHandler : function (event) {
564
switch (event.type) {
565
case "file:uploadstart":
566
this.fire("fileuploadstart", event);
568
case "file:uploadprogress":
569
this.fire("uploadprogress", event);
571
case "uploaderqueue:totaluploadprogress":
572
this.fire("totaluploadprogress", event);
574
case "file:uploadcomplete":
575
this.fire("uploadcomplete", event);
577
case "uploaderqueue:alluploadscomplete":
579
this.fire("alluploadscomplete", event);
581
case "uploaderqueue:uploaderror":
582
this.fire("uploaderror", event);
590
* Starts the upload of a specific file.
593
* @param file {Y.FileFlash} Reference to the instance of the file to be uploaded.
594
* @param url {String} The URL to upload the file to.
595
* @param postVars {Object} (optional) A set of key-value pairs to send as variables along with the file upload HTTP request.
596
* If not specified, the values from the attribute `postVarsPerFile` are used instead.
599
upload : function (file, url, postvars) {
601
var uploadURL = url || this.get("uploadURL"),
602
postVars = postvars || this.get("postVarsPerFile"),
603
fileId = file.get("id");
605
postVars = postVars.hasOwnProperty(fileId) ? postVars[fileId] : postVars;
607
if (file instanceof Y.FileFlash) {
609
file.on("uploadstart", this._uploadStartHandler, this);
610
file.on("uploadprogress", this._uploadProgressHandler, this);
611
file.on("uploadcomplete", this._uploadCompleteHandler, this);
612
file.on("uploaderror", this._uploadErrorHandler, this);
614
file.startUpload(uploadURL, postVars, this.get("fileFieldName"));
619
* Starts the upload of all files on the file list, using an automated queue.
622
* @param url {String} The URL to upload the files to.
623
* @param postVars {Object} (optional) A set of key-value pairs to send as variables along with the file upload HTTP request.
624
* If not specified, the values from the attribute `postVarsPerFile` are used instead.
626
uploadAll : function (url, postvars) {
627
this.uploadThese(this.get("fileList"), url, postvars);
631
* Starts the upload of the files specified in the first argument, using an automated queue.
633
* @method uploadThese
634
* @param files {Array} The list of files to upload.
635
* @param url {String} The URL to upload the files to.
636
* @param postVars {Object} (optional) A set of key-value pairs to send as variables along with the file upload HTTP request.
637
* If not specified, the values from the attribute `postVarsPerFile` are used instead.
639
uploadThese : function (files, url, postvars) {
641
var uploadURL = url || this.get("uploadURL"),
642
postVars = postvars || this.get("postVarsPerFile");
644
this.queue = new UploaderQueue({simUploads: this.get("simLimit"),
645
errorAction: this.get("errorAction"),
646
fileFieldName: this.get("fileFieldName"),
648
uploadURL: uploadURL,
649
perFileParameters: postVars
651
this.queue.on("uploadstart", this._uploadEventHandler, this);
652
this.queue.on("uploadprogress", this._uploadEventHandler, this);
653
this.queue.on("totaluploadprogress", this._uploadEventHandler, this);
654
this.queue.on("uploadcomplete", this._uploadEventHandler, this);
655
this.queue.on("alluploadscomplete", this._uploadEventHandler, this);
656
this.queue.on("alluploadscancelled", function (ev) {this.queue = null;}, this);
657
this.queue.on("uploaderror", this._uploadEventHandler, this);
658
this.queue.startUpload();
660
this.fire("uploadstart");
667
* The template for the Flash player container. Since the Flash player container needs
668
* to completely overlay the &lquot;Select Files&rqot; control, it's positioned absolutely,
669
* with width and height set to 100% of the parent.
671
* @property FLASH_CONTAINER
674
* @default "<div id='{swfContainerId}' style='position:absolute; top:0px; left: 0px; margin: 0; padding: 0; border: 0; width:100%; height:100%'></div>"
676
FLASH_CONTAINER: "<div id='{swfContainerId}' style='position:absolute; top:0px; left: 0px; margin: 0; padding: 0; border: 0; width:100%; height:100%'></div>",
679
* The template for the "Select Files" button.
681
* @property SELECT_FILES_BUTTON
684
* @default "<button type='button' class='yui3-button' tabindex='-1'>{selectButtonLabel}</button>"
686
SELECT_FILES_BUTTON: "<button type='button' class='yui3-button' tabindex='-1'>{selectButtonLabel}</button>",
689
* The static property reflecting the type of uploader that `Y.Uploader`
690
* aliases. The UploaderFlash value is `"flash"`.
699
* The identity of the widget.
703
* @default 'uploader'
711
* Static property used to define the default attribute configuration of
722
* A Boolean indicating whether newly selected files should be appended
723
* to the existing file list, or whether they should replace it.
725
* @attribute appendNewFiles
734
* The names of CSS classes that correspond to different button states
735
* of the "Select Files" control. These classes are assigned to the
736
* "Select Files" control based on the mouse states reported by the
737
* Flash player. The keys for the class names are:
739
* <li> <strong>`hover`</strong>: the class corresponding to mouse hovering over
740
* the "Select Files" button.</li>
741
* <li> <strong>`active`</strong>: the class corresponding to mouse down state of
742
* the "Select Files" button.</li>
743
* <li> <strong>`disabled`</strong>: the class corresponding to the disabled state
744
* of the "Select Files" button.</li>
745
* <li> <strong>`focus`</strong>: the class corresponding to the focused state of
746
* the "Select Files" button.</li>
748
* @attribute buttonClassNames
750
* @default { hover: "yui3-button-hover",
751
* active: "yui3-button-active",
752
* disabled: "yui3-button-disabled",
753
* focus: "yui3-button-selected"
758
"hover": "yui3-button-hover",
759
"active": "yui3-button-active",
760
"disabled": "yui3-button-disabled",
761
"focus": "yui3-button-selected"
766
* A Boolean indicating whether the uploader is enabled or disabled for user input.
777
* The action performed when an upload error occurs for a specific file being uploaded.
778
* The possible values are:
780
* <li> <strong>`UploaderQueue.CONTINUE`</strong>: the error is ignored and the upload process is continued.</li>
781
* <li> <strong>`UploaderQueue.STOP`</strong>: the upload process is stopped as soon as any other parallel file
782
* uploads are finished.</li>
783
* <li> <strong>`UploaderQueue.RESTART_ASAP`</strong>: the file is added back to the front of the queue.</li>
784
* <li> <strong>`UploaderQueue.RESTART_AFTER`</strong>: the file is added to the back of the queue.</li>
786
* @attribute errorAction
788
* @default UploaderQueue.CONTINUE
792
validator: function (val, name) {
793
return (val === UploaderQueue.CONTINUE || val === UploaderQueue.STOP || val === UploaderQueue.RESTART_ASAP || val === UploaderQueue.RESTART_AFTER);
798
* An array indicating what fileFilters should be applied to the file
799
* selection dialog. Each element in the array should be an object with
800
* the following key-value pairs:
802
* description : String
803
extensions: String of the form &lquot;*.ext1;*.ext2;*.ext3;...&rquot;
805
* @attribute fileFilters
814
* A String specifying what should be the POST field name for the file
815
* content in the upload request.
817
* @attribute fileFieldName
826
* The array of files to be uploaded. All elements in the array
827
* must be instances of `Y.FileFlash` and be instantiated with a `fileId`
828
* retrieved from an instance of the uploader.
830
* @attribute fileList
839
* A Boolean indicating whether multiple file selection is enabled.
841
* @attribute multipleFiles
850
* An object, keyed by `fileId`, containing sets of key-value pairs
851
* that should be passed as POST variables along with each corresponding
852
* file. This attribute is only used if no POST variables are specifed
853
* in the upload method call.
855
* @attribute postVarsPerFile
864
* The label for the "Select Files" widget. This is the value that replaces the
865
* `{selectButtonLabel}` token in the `SELECT_FILES_BUTTON` template.
867
* @attribute selectButtonLabel
869
* @default "Select Files"
872
value: "Select Files"
876
* The widget that serves as the "Select Files" control for the file uploader
879
* @attribute selectFilesButton
880
* @type {Node | Widget}
881
* @default A standard HTML button with YUI CSS Button skin.
883
selectFilesButton : {
884
valueFn: function () {
885
return Y.Node.create(substitute(Y.UploaderFlash.SELECT_FILES_BUTTON, {selectButtonLabel: this.get("selectButtonLabel")}));
890
* The number of files that can be uploaded
891
* simultaneously if the automatic queue management
892
* is used. This value can be in the range between 2
895
* @attribute simLimit
901
validator: function (val, name) {
902
return (val >= 2 && val <= 5);
907
* The URL to the SWF file of the flash uploader. A copy local to
908
* the server that hosts the page on which the uploader appears is
913
* @default "CDN Prefix + uploader/assets/flashuploader.swf" with a
914
* random GET parameter for IE (to prevent buggy behavior when the SWF
918
valueFn: function () {
919
var prefix = Y.Env.cdn + "uploader/assets/flashuploader.swf";
922
return (prefix + "?t=" + Y.guid("uploader"));
929
* The id's or `Node` references of the DOM elements that precede
930
* and follow the `Select Files` button in the tab order. Specifying
931
* these allows keyboard navigation to and from the Flash player
932
* layer of the uploader.
933
* The two keys corresponding to the DOM elements are:
935
* <li> `from`: the id or the `Node` reference corresponding to the
936
* DOM element that precedes the `Select Files` button in the tab order.</li>
937
* <li> `to`: the id or the `Node` reference corresponding to the
938
* DOM element that follows the `Select Files` button in the tab order.</li>
940
* @attribute tabElements
949
* The URL to which file upload requested are POSTed. Only used if a different url is not passed to the upload method call.
951
* @attribute uploadURL
961
Y.UploaderFlash.Queue = UploaderQueue;
966
}, '3.5.1' ,{requires:['swf', 'widget', 'substitute', 'base', 'cssbutton', 'node', 'event-custom', 'file-flash', 'uploader-queue']});