Proyectos de Subversion LeadersLinked - Antes de SPA

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
694 steven 1
/**
2
 * jquery.ui.plupload.js
3
 *
4
 * Copyright 2013, Moxiecode Systems AB
5
 * Released under GPL License.
6
 *
7
 * License: http://www.plupload.com/license
8
 * Contributing: http://www.plupload.com/contributing
9
 *
10
 * Depends:
11
 *	jquery.ui.core.js
12
 *	jquery.ui.widget.js
13
 *	jquery.ui.button.js
14
 *	jquery.ui.progressbar.js
15
 *
16
 * Optionally:
17
 *	jquery.ui.sortable.js
18
 */
19
 
20
/* global jQuery:true */
21
 
22
/**
23
 jQuery UI based implementation of the Plupload API - multi-runtime file uploading API.
24
 
25
 To use the widget you must include _jQuery_ and _jQuery UI_ bundle (including `ui.core`, `ui.widget`, `ui.button`,
26
 `ui.progressbar` and `ui.sortable`).
27
 
28
 In general the widget is designed the way that you do not usually need to do anything to it after you instantiate it.
29
 But! You still can intervenue, to some extent, in case you need to. Although, due to the fact that widget is based on
30
 _jQuery UI_ widget factory, there are some specifics. See examples below for more details.
31
 
32
 @example
33
 <!-- Instantiating: -->
34
 <div id="uploader">
35
 <p>Your browser doesn't have Flash, Silverlight or HTML5 support.</p>
36
 </div>
37
 
38
 <script>
39
 $('#uploader').plupload({
40
			url : '../upload.php',
41
			filters : [
42
				{title : "Image files", extensions : "jpg,gif,png"}
43
			],
44
			rename: true,
45
			sortable: true,
46
			flash_swf_url : '../../js/Moxie.swf',
47
			silverlight_xap_url : '../../js/Moxie.xap',
48
		});
49
 </script>
50
 
51
 @example
52
 // Invoking methods:
53
 $('#uploader').plupload(options);
54
 
55
 // Display welcome message in the notification area
56
 $('#uploader').plupload('notify', 'info', "This might be obvious, but you need to click 'Add Files' to add some files.");
57
 
58
 @example
59
 // Subscribing to the events...
60
 // ... on initialization:
61
 $('#uploader').plupload({
62
		...
63
		viewchanged: function(event, args) {
64
			// stuff ...
65
		}
66
	});
67
 // ... or after initialization
68
 $('#uploader').on("viewchanged", function(event, args) {
69
		// stuff ...
70
	});
71
 
72
 @class UI.Plupload
73
 @constructor
74
 @param {Object} settings For detailed information about each option check documentation.
75
 @param {String} settings.url URL of the server-side upload handler.
76
 @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.
77
 @param {String} [settings.file_data_name="file"] Name for the file field in Multipart formated message.
78
 @param {Object} [settings.filters={}] Set of file type filters.
79
 @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`
80
 @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`.
81
 @param {Boolean} [settings.filters.prevent_duplicates=false] Do not let duplicates into the queue. Dispatches `plupload.FILE_DUPLICATE_ERROR`.
82
 @param {Number} [settings.filters.max_file_count=0] Limit the number of files that can reside in the queue at the same time (default is 0 - no limit).
83
 @param {String} [settings.flash_swf_url] URL of the Flash swf.
84
 @param {Object} [settings.headers] Custom headers to send with the upload. Hash of name/value pairs.
85
 @param {Number|String} [settings.max_file_size] 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`.
86
 @param {Number} [settings.max_retries=0] How many times to retry the chunk or file, before triggering Error event.
87
 @param {Boolean} [settings.multipart=true] Whether to send file and additional parameters as Multipart formated message.
88
 @param {Object} [settings.multipart_params] Hash of key/value pairs to send with every file upload.
89
 @param {Boolean} [settings.multi_selection=true] Enable ability to select multiple files at once in file dialog.
90
 @param {Boolean} [settings.prevent_duplicates=false] Do not let duplicates into the queue. Dispatches `plupload.FILE_DUPLICATE_ERROR`.
91
 @param {String|Object} [settings.required_features] Either comma-separated list or hash of required features that chosen runtime should absolutely possess.
92
 @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}`
93
 @param {Number} [settings.resize.width] If image is bigger, it will be resized.
94
 @param {Number} [settings.resize.height] If image is bigger, it will be resized.
95
 @param {Number} [settings.resize.quality=90] Compression quality for jpegs (1-100).
96
 @param {Boolean} [settings.resize.crop=false] Whether to crop images to exact dimensions. By default they will be resized proportionally.
97
 @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.
98
 @param {String} [settings.silverlight_xap_url] URL of the Silverlight xap.
99
 @param {Boolean} [settings.unique_names=false] If true will generate unique filenames for uploaded files.
100
 
101
 @param {Boolean} [settings.autostart=false] Whether to auto start uploading right after file selection.
102
 @param {Boolean} [settings.dragdrop=true] Enable ability to add file to the queue by drag'n'dropping them from the desktop.
103
 @param {Boolean} [settings.rename=false] Enable ability to rename files in the queue.
104
 @param {Boolean} [settings.sortable=false] Enable ability to sort files in the queue, changing their uploading priority.
105
 @param {Object} [settings.buttons] Control the visibility of functional buttons.
106
 @param {Boolean} [settings.buttons.browse=true] Display browse button.
107
 @param {Boolean} [settings.buttons.start=true] Display start button.
108
 @param {Boolean} [settings.buttons.stop=true] Display stop button.
109
 @param {Object} [settings.views] Control various views of the file queue.
110
 @param {Boolean} [settings.views.list=true] Enable list view.
111
 @param {Boolean} [settings.views.thumbs=false] Enable thumbs view.
112
 @param {String} [settings.views.default='list'] Default view.
113
 @param {Boolean} [settings.views.remember=true] Whether to remember the current view (requires jQuery Cookie plugin).
114
 @param {Boolean} [settings.multiple_queues=true] Re-activate the widget after each upload procedure.
115
 */
116
;(function(window, document, plupload, o, $) {
117
 
118
    /**
119
     Dispatched when the widget is initialized and ready.
120
 
121
     @event ready
122
     @param {plupload.Uploader} uploader Uploader instance sending the event.
123
     */
124
 
125
    /**
126
     Dispatched when file dialog is closed.
127
 
128
     @event selected
129
     @param {plupload.Uploader} uploader Uploader instance sending the event.
130
     @param {Array} files Array of selected files represented by plupload.File objects
131
     */
132
 
133
    /**
134
     Dispatched when file dialog is closed.
135
 
136
     @event removed
137
     @param {plupload.Uploader} uploader Uploader instance sending the event.
138
     @param {Array} files Array of removed files represented by plupload.File objects
139
     */
140
 
141
    /**
142
     Dispatched when upload is started.
143
 
144
     @event started
145
     @param {plupload.Uploader} uploader Uploader instance sending the event.
146
     */
147
 
148
    /**
149
     Dispatched when upload is stopped.
150
 
151
     @event stopped
152
     @param {plupload.Uploader} uploader Uploader instance sending the event.
153
     */
154
 
155
    /**
156
     Dispatched during the upload process.
157
 
158
     @event progress
159
     @param {plupload.Uploader} uploader Uploader instance sending the event.
160
     @param {plupload.File} file File that is being uploaded (includes loaded and percent properties among others).
161
     @param {Number} size Total file size in bytes.
162
     @param {Number} loaded Number of bytes uploaded of the files total size.
163
     @param {Number} percent Number of percentage uploaded of the file.
164
     */
165
 
166
    /**
167
     Dispatched when file is uploaded.
168
 
169
     @event uploaded
170
     @param {plupload.Uploader} uploader Uploader instance sending the event.
171
     @param {plupload.File} file File that was uploaded.
172
     @param {Enum} status Status constant matching the plupload states QUEUED, UPLOADING, FAILED, DONE.
173
     */
174
 
175
    /**
176
     Dispatched when upload of the whole queue is complete.
177
 
178
     @event complete
179
     @param {plupload.Uploader} uploader Uploader instance sending the event.
180
     @param {Array} files Array of uploaded files represented by plupload.File objects
181
     */
182
 
183
    /**
184
     Dispatched when the view is changed, e.g. from `list` to `thumbs` or vice versa.
185
 
186
     @event viewchanged
187
     @param {plupload.Uploader} uploader Uploader instance sending the event.
188
     @param {String} type Current view type.
189
     */
190
 
191
    /**
192
     Dispatched when error of some kind is detected.
193
 
194
     @event error
195
     @param {plupload.Uploader} uploader Uploader instance sending the event.
196
     @param {String} error Error message.
197
     @param {plupload.File} file File that was uploaded.
198
     @param {Enum} status Status constant matching the plupload states QUEUED, UPLOADING, FAILED, DONE.
199
     */
200
 
201
    var uploaders = {};
202
 
203
    function _(str) {
204
        return plupload.translate(str) || str;
205
    }
206
 
207
    function renderUI(obj) {
208
        obj.id = obj.attr('id');
209
 
210
        obj.html(
211
            '<span class="flow-drawer-container">' +
212
            '<div class="drawer drawer-media">' +
213
            '<div class="plupload_wrapper">' +
214
            '<div class="ui-widget-content plupload_container">' +
215
            '<div class="ui-state-default ui-widget-header plupload_header theme-bg-color">' +
216
            '<div class="plupload_header_content">' +
217
            '<div class="plupload_logo" id="close_uploadFile"><i class="ti-close"></i> </div>' +
218
            '<div class="plupload_header_title" style="font-size:15px;width:100px;">' + _("Vista Previa") + '</div>' +
219
            '<div class="plupload_header_text hidden">' + _("Add files to the upload queue and click the start button.") + '</div>' +
220
            '<div class="plupload_view_switch">' +
221
            '<input type="radio" id="'+obj.id+'_view_list" name="view_mode_'+obj.id+'" checked="checked" /><label class="plupload_button" for="'+obj.id+'_view_list" data-view="list">' + _('List') + '</label>' +
222
            '<input type="radio" id="'+obj.id+'_view_thumbs" name="view_mode_'+obj.id+'" /><label class="plupload_button"  for="'+obj.id+'_view_thumbs" data-view="thumbs">' + _('Thumbnails') + '</label>' +
223
            '</div>' +
224
            '</div>' +
225
            '</div>' +
226
 
227
            '<table class="plupload_filelist plupload_filelist_header ui-widget-header" style="display:none;">' +
228
            '<tr>' +
229
            '<td class="plupload_cell plupload_file_name">' + _('Filename') + '</td>' +
230
            '<td class="plupload_cell plupload_file_status">' + _('Status') + '</td>' +
231
            '<td class="plupload_cell plupload_file_size">' + _('Size') + '</td>' +
232
            '<td class="plupload_cell plupload_file_action">&nbsp;</td>' +
233
            '</tr>' +
234
            '</table>' +
235
 
236
            '<div class="plupload_content">' +
237
            '<div class="plupload_droptext hidden-xs">' + _("Arrastra tus archivos aqui.") + '</div>' +
238
            '<ul class="plupload_filelist_content"> </ul>' +
239
            '<div class="plupload_clearer">&nbsp;</div>' +
240
            '</div>' +
241
 
242
            '<table class="plupload_filelist plupload_filelist_footer ui-widget-header">' +
243
            '<tr>' +
244
            '<td class="plupload_cell plupload_file_name" colspan="3">' +
245
            '<div class="plupload_buttons"><!-- Visible -->' +
246
            '<a class="plupload_button plupload_add" style="font-size:14px;">' + _("Agregar") + '</a>&nbsp;' +
247
            '<a class="plupload_button plupload_start" style="font-size:14px;">' + _("Enviar") + '</a>&nbsp;' +
248
            '<a class="plupload_button plupload_stop plupload_hidden">'+_("Cancelar") + '</a>&nbsp;' +
249
            '</div>' +
250
 
251
            '<div class="plupload_started plupload_hidden"><!-- Hidden -->' +
252
            '<div class="plupload_progress plupload_right">' +
253
            '<div class="plupload_progress_container"></div>' +
254
            '</div>' +
255
 
256
            '<div class="plupload_cell plupload_upload_status"></div>' +
257
 
258
            '<div class="plupload_clearer">&nbsp;</div>' +
259
            '</div>' +
260
            '</td>' +
261
            '</tr>' +
262
            '<tr style="display:none;">' +
263
            '<td class="plupload_file_size"><span class="plupload_total_file_size">0 kb</span></td>' +
264
            '<td class="plupload_file_status"><span class="plupload_total_status">0%</span></td>' +
265
            '<td class="plupload_file_action"></td>' +
266
            '</tr>' +
267
            '</table>' +
268
 
269
            '</div>' +
270
            '<input class="plupload_count" value="0" type="hidden">' +
271
            '</div>'+
272
            '</div>' +
273
            '</span>'
274
        );
275
    }
276
 
277
 
278
    $.widget("ui.plupload", {
279
 
280
        widgetEventPrefix: '',
281
 
282
        contents_bak: '',
283
 
284
        options: {
285
            browse_button_hover: 'ui-state-hover',
286
            browse_button_active: 'ui-state-active',
287
 
288
            filters: {},
289
 
290
            // widget specific
291
            buttons: {
292
                browse: true,
293
                start: true,
294
                stop: true
295
            },
296
 
297
            views: {
298
                list: true,
299
                thumbs: false,
300
                active: 'list',
301
                remember: true // requires: https://github.com/carhartl/jquery-cookie, otherwise disabled even if set to true
302
            },
303
 
304
            thumb_width: 100,
305
            thumb_height: 60,
306
 
307
            multiple_queues: true, // re-use widget by default
308
            dragdrop : true,
309
            autostart: false,
310
            sortable: false,
311
            rename: false
312
        },
313
 
314
        FILE_COUNT_ERROR: -9001,
315
 
316
        _create: function() {
317
            var id = this.element.attr('id');
318
            if (!id) {
319
                id = plupload.guid();
320
                this.element.attr('id', id);
321
            }
322
            this.id = id;
323
 
324
            // backup the elements initial state
325
            this.contents_bak = this.element.html();
326
            renderUI(this.element);
327
 
328
            // container, just in case
329
            this.container = $('.plupload_container', this.element).attr('id', id + '_container');
330
 
331
            this.content = $('.plupload_content', this.element);
332
 
333
            if ($.fn.resizable) {
334
                this.container.resizable({
335
                    handles: 's',
336
                    minHeight: 300
337
                });
338
            }
339
 
340
            // list of files, may become sortable
341
            this.filelist = $('.plupload_filelist_content', this.container)
342
                .attr({
343
                    id: id + '_filelist',
344
                    unselectable: 'on'
345
                });
346
 
347
 
348
            // buttons
349
            this.browse_button = $('.plupload_add', this.container).attr('id', id + '_browse');
350
            this.start_button = $('.plupload_start', this.container).attr('id', id + '_start');
351
            this.stop_button = $('.plupload_stop', this.container).attr('id', id + '_stop');
352
            this.thumbs_switcher = $('#' + id + '_view_thumbs');
353
            this.list_switcher = $('#' + id + '_view_list');
354
 
355
            if ($.ui.button) {
356
                this.browse_button.button({
357
                    icons: { primary: 'ui-icon-circle-plus' },
358
                    disabled: true
359
                });
360
 
361
                this.start_button.button({
362
                    icons: { primary: 'ui-icon-circle-arrow-e' },
363
                    disabled: true
364
                });
365
 
366
                this.stop_button.button({
367
                    icons: { primary: 'ui-icon-circle-close' }
368
                });
369
 
370
                this.list_switcher.button({
371
                    text: false,
372
                    icons: { secondary: "ui-icon-grip-dotted-horizontal" }
373
                });
374
 
375
                this.thumbs_switcher.button({
376
                    text: false,
377
                    icons: { secondary: "ui-icon-image" }
378
                });
379
            }
380
 
381
            // progressbar
382
            this.progressbar = $('.plupload_progress_container', this.container);
383
 
384
            if ($.ui.progressbar) {
385
                this.progressbar.progressbar();
386
            }
387
 
388
            // counter
389
            this.counter = $('.plupload_count', this.element)
390
                .attr({
391
                    id: id + '_count',
392
                    name: id + '_count'
393
                });
394
 
395
            // initialize uploader instance
396
            this._initUploader();
397
        },
398
 
399
        _initUploader: function() {
400
            var self = this
401
                , id = this.id
402
                , uploader
403
                , options = {
404
                    container: id + '_buttons',
405
                    browse_button: id + '_browse'
406
                }
407
                ;
408
 
409
            $('.plupload_buttons', this.element).attr('id', id + '_buttons');
410
 
411
            if (self.options.dragdrop) {
412
                this.filelist.parent().attr('id', this.id + '_dropbox');
413
                options.drop_element = this.id + '_dropbox';
414
            }
415
 
416
            this.filelist.on('click', function(e) {
417
                if ($(e.target).hasClass('plupload_action_icon')) {
418
                    self.removeFile($(e.target).closest('.plupload_file').attr('id'));
419
                    e.preventDefault();
420
                }
421
            });
422
 
423
            uploader = this.uploader = uploaders[id] = new plupload.Uploader($.extend(this.options, options));
424
 
425
            // retrieve full normalized set of options
426
            this.options = uploader.getOption();
427
 
428
            if (self.options.views.thumbs) {
429
                uploader.settings.required_features.display_media = true;
430
            }
431
 
432
            // for backward compatibility
433
            if (self.options.max_file_count) {
434
                plupload.extend(uploader.getOption('filters'), {
435
                    max_file_count: self.options.max_file_count
436
                });
437
            }
438
 
439
            plupload.addFileFilter('max_file_count', function(maxCount, file, cb) {
440
                if (maxCount <= this.files.length - (this.total.uploaded + this.total.failed)) {
441
                    self.browse_button.button('disable');
442
                    this.disableBrowse();
443
 
444
                    this.trigger('Error', {
445
                        code : self.FILE_COUNT_ERROR,
446
                        message : _("File count error."),
447
                        file : file
448
                    });
449
                    cb(false);
450
                } else {
451
                    cb(true);
452
                }
453
            });
454
 
455
 
456
            uploader.bind('Error', function(up, err) {
457
                var message, details = "";
458
 
459
                message = '<strong>' + err.message + '</strong>';
460
 
461
                switch (err.code) {
462
                    case plupload.FILE_EXTENSION_ERROR:
463
                        details = plupload.sprintf(_("File: %s"), err.file.name);
464
                        break;
465
 
466
                    case plupload.FILE_SIZE_ERROR:
467
                        details = plupload.sprintf(_("File: %s, size: %d, max file size: %d"), err.file.name,  plupload.formatSize(err.file.size), plupload.formatSize(plupload.parseSize(up.getOption('filters').max_file_size)));
468
                        break;
469
 
470
                    case plupload.FILE_DUPLICATE_ERROR:
471
                        details = plupload.sprintf(_("%s already present in the queue."), err.file.name);
472
                        break;
473
 
474
                    case self.FILE_COUNT_ERROR:
475
                        details = plupload.sprintf(_("Upload element accepts only %d file(s) at a time. Extra files were stripped."), up.getOption('filters').max_file_count || 0);
476
                        break;
477
 
478
                    case plupload.IMAGE_FORMAT_ERROR :
479
                        details = _("Image format either wrong or not supported.");
480
                        break;
481
 
482
                    case plupload.IMAGE_MEMORY_ERROR :
483
                        details = _("Runtime ran out of available memory.");
484
                        break;
485
 
486
                    /* // This needs a review
487
                     case plupload.IMAGE_DIMENSIONS_ERROR :
488
                     details = plupload.sprintf(_('Resoultion out of boundaries! <b>%s</b> runtime supports images only up to %wx%hpx.'), up.runtime, up.features.maxWidth, up.features.maxHeight);
489
                     break;	*/
490
 
491
                    case plupload.HTTP_ERROR:
492
                        details = _("Upload URL might be wrong or doesn't exist.");
493
                        break;
494
                }
495
 
496
                message += " <br /><i>" + details + "</i>";
497
 
498
                self._trigger('error', null, { up: up, error: err } );
499
 
500
                // do not show UI if no runtime can be initialized
501
                if (err.code === plupload.INIT_ERROR) {
502
                    setTimeout(function() {
503
                        self.destroy();
504
                    }, 1);
505
                } else {
506
                    self.notify('error', message);
507
                }
508
            });
509
 
510
 
511
            uploader.bind('PostInit', function(up) {
512
                // all buttons are optional, so they can be disabled and hidden
513
                if (!self.options.buttons.browse) {
514
                    self.browse_button.button('disable').hide();
515
                    up.disableBrowse(true);
516
                } else {
517
                    self.browse_button.button('enable');
518
                }
519
 
520
                if (!self.options.buttons.start) {
521
                    self.start_button.button('disable').hide();
522
                }
523
 
524
                if (!self.options.buttons.stop) {
525
                    self.stop_button.button('disable').hide();
526
                }
527
 
528
                if (!self.options.unique_names && self.options.rename) {
529
                    self._enableRenaming();
530
                }
531
 
532
                if (self.options.dragdrop && up.features.dragdrop) {
533
                    self.filelist.parent().addClass('plupload_dropbox');
534
                }
535
 
536
                self._enableViewSwitcher();
537
 
538
                self.start_button.click(function(e) {
539
                    if (!$(this).button('option', 'disabled')) {
540
                        self.start();
541
                    }
542
                    e.preventDefault();
543
                });
544
 
545
                self.stop_button.click(function(e) {
546
                    self.stop();
547
                    e.preventDefault();
548
                });
549
 
550
                self._trigger('ready', null, { up: up });
551
            });
552
 
553
            // uploader internal events must run first
554
            uploader.init();
555
 
556
            uploader.bind('FileFiltered', function(up, file) {
557
                self._addFiles(file);
558
            });
559
 
560
            uploader.bind('FilesAdded', function(up, files) {
561
                self._trigger('selected', null, { up: up, files: files } );
562
 
563
                // re-enable sortable
564
                if (self.options.sortable && $.ui.sortable) {
565
                    self._enableSortingList();
566
                }
567
 
568
                self._trigger('updatelist', null, { filelist: self.filelist });
569
 
570
                if (self.options.autostart) {
571
                    // set a little delay to make sure that QueueChanged triggered by the core has time to complete
572
                    setTimeout(function() {
573
                        self.start();
574
                    }, 10);
575
                }
576
            });
577
 
578
            uploader.bind('FilesRemoved', function(up, files) {
579
                // destroy sortable if enabled
580
                if ($.ui.sortable && self.options.sortable) {
581
                    $('tbody', self.filelist).sortable('destroy');
582
                }
583
 
584
                $.each(files, function(i, file) {
585
                    $('#' + file.id).toggle("highlight", function() {
586
                        $(this).remove();
587
                    });
588
                });
589
 
590
                if (up.files.length) {
591
                    // re-initialize sortable
592
                    if (self.options.sortable && $.ui.sortable) {
593
                        self._enableSortingList();
594
                    }
595
                }
596
 
597
                self._trigger('updatelist', null, { filelist: self.filelist });
598
                self._trigger('removed', null, { up: up, files: files } );
599
            });
600
 
601
            uploader.bind('QueueChanged', function() {
602
                self._handleState();
603
            });
604
 
605
            uploader.bind('StateChanged', function(up) {
606
                self._handleState();
607
                if (plupload.STARTED === up.state) {
608
                    self._trigger('started', null, { up: this.uploader });
609
                } else if (plupload.STOPPED === up.state) {
610
                    self._trigger('stopped', null, { up: this.uploader });
611
                }
612
            });
613
 
614
            uploader.bind('UploadFile', function(up, file) {
615
                self._handleFileStatus(file);
616
            });
617
 
618
            uploader.bind('FileUploaded', function(up, file, result) {
619
                self._handleFileStatus(file);
620
                self._trigger('uploaded', null, { up: up, file: file, result: result } );
621
            });
622
 
623
            uploader.bind('UploadProgress', function(up, file) {
624
                self._handleFileStatus(file);
625
                self._updateTotalProgress();
626
                self._trigger('progress', null, { up: up, file: file } );
627
            });
628
 
629
            uploader.bind('UploadComplete', function(up, files) {
630
                self._addFormFields();
631
                self._trigger('complete', null, { up: up, files: files } );
632
            });
633
        },
634
 
635
 
636
        _setOption: function(key, value) {
637
            var self = this;
638
 
639
            if (key == 'buttons' && typeof(value) == 'object') {
640
                value = $.extend(self.options.buttons, value);
641
 
642
                if (!value.browse) {
643
                    self.browse_button.button('disable').hide();
644
                    self.uploader.disableBrowse(true);
645
                } else {
646
                    self.browse_button.button('enable').show();
647
                    self.uploader.disableBrowse(false);
648
                }
649
 
650
                if (!value.start) {
651
                    self.start_button.button('disable').hide();
652
                } else {
653
                    self.start_button.button('enable').show();
654
                }
655
 
656
                if (!value.stop) {
657
                    self.stop_button.button('disable').hide();
658
                } else {
659
                    self.start_button.button('enable').show();
660
                }
661
            }
662
 
663
            self.uploader.setOption(key, value);
664
        },
665
 
666
 
667
        /**
668
         Start upload. Triggers `start` event.
669
 
670
         @method start
671
         */
672
        start: function() {
673
            this.uploader.start();
674
        },
675
 
676
 
677
        /**
678
         Stop upload. Triggers `stop` event.
679
 
680
         @method stop
681
         */
682
        stop: function() {
683
            this.uploader.stop();
684
        },
685
 
686
 
687
        /**
688
         Enable browse button.
689
 
690
         @method enable
691
         */
692
        enable: function() {
693
            this.browse_button.button('enable');
694
            this.uploader.disableBrowse(false);
695
        },
696
 
697
 
698
        /**
699
         Disable browse button.
700
 
701
         @method disable
702
         */
703
        disable: function() {
704
            this.browse_button.button('disable');
705
            this.uploader.disableBrowse(true);
706
        },
707
 
708
 
709
        /**
710
         Retrieve file by its unique id.
711
 
712
         @method getFile
713
         @param {String} id Unique id of the file
714
         @return {plupload.File}
715
         */
716
        getFile: function(id) {
717
            var file;
718
 
719
            if (typeof id === 'number') {
720
                file = this.uploader.files[id];
721
            } else {
722
                file = this.uploader.getFile(id);
723
            }
724
            return file;
725
        },
726
 
727
        /**
728
         Return array of files currently in the queue.
729
 
730
         @method getFiles
731
         @return {Array} Array of files in the queue represented by plupload.File objects
732
         */
733
        getFiles: function() {
734
            return this.uploader.files;
735
        },
736
 
737
 
738
        /**
739
         Remove the file from the queue.
740
 
741
         @method removeFile
742
         @param {plupload.File|String} file File to remove, might be specified directly or by its unique id
743
         */
744
        removeFile: function(file) {
745
            if (plupload.typeOf(file) === 'string') {
746
                file = this.getFile(file);
747
            }
748
            this.uploader.removeFile(file);
749
        },
750
 
751
 
752
        /**
753
         Clear the file queue.
754
 
755
         @method clearQueue
756
         */
757
        clearQueue: function() {
758
            this.uploader.splice();
759
        },
760
 
761
 
762
        /**
763
         Retrieve internal plupload.Uploader object (usually not required).
764
 
765
         @method getUploader
766
         @return {plupload.Uploader}
767
         */
768
        getUploader: function() {
769
            return this.uploader;
770
        },
771
 
772
 
773
        /**
774
         Trigger refresh procedure, specifically browse_button re-measure and re-position operations.
775
         Might get handy, when UI Widget is placed within the popup, that is constantly hidden and shown
776
         again - without calling this method after each show operation, dialog trigger might get displaced
777
         and disfunctional.
778
 
779
         @method refresh
780
         */
781
        refresh: function() {
782
            this.uploader.refresh();
783
        },
784
 
785
 
786
        /**
787
         Display a message in notification area.
788
 
789
         @method notify
790
         @param {Enum} type Type of the message, either `error` or `info`
791
         @param {String} message The text message to display.
792
         */
793
        notify: function(type, message) {
794
            var popup = $(
795
                '<div class="plupload_message">' +
796
                '<span class="plupload_message_close ui-icon ui-icon-circle-close" title="'+_('Close')+'"></span>' +
797
                '<p><span class="ui-icon"></span>' + message + '</p>' +
798
                '</div>'
799
            );
800
 
801
            popup
802
                .addClass('ui-state-' + (type === 'error' ? 'error' : 'highlight'))
803
                .find('p .ui-icon')
804
                .addClass('ui-icon-' + (type === 'error' ? 'alert' : 'info'))
805
                .end()
806
                .find('.plupload_message_close')
807
                .click(function() {
808
                    popup.remove();
809
                })
810
                .end();
811
 
812
            $('.plupload_header', this.container).append(popup);
813
        },
814
 
815
 
816
        /**
817
         Destroy the widget, the uploader, free associated resources and bring back original html.
818
 
819
         @method destroy
820
         */
821
        destroy: function() {
822
            // destroy uploader instance
823
            this.uploader.destroy();
824
 
825
            // unbind all button events
826
            $('.plupload_button', this.element).unbind();
827
 
828
            // destroy buttons
829
            if ($.ui.button) {
830
                $('.plupload_add, .plupload_start, .plupload_stop', this.container)
831
                    .button('destroy');
832
            }
833
 
834
            // destroy progressbar
835
            if ($.ui.progressbar) {
836
                this.progressbar.progressbar('destroy');
837
            }
838
 
839
            // destroy sortable behavior
840
            if ($.ui.sortable && this.options.sortable) {
841
                $('tbody', this.filelist).sortable('destroy');
842
            }
843
 
844
            // restore the elements initial state
845
            this.element
846
                .empty()
847
                .html(this.contents_bak);
848
            this.contents_bak = '';
849
 
850
            $.Widget.prototype.destroy.apply(this);
851
        },
852
 
853
 
854
        _handleState: function() {
855
            var up = this.uploader
856
                , filesPending = up.files.length - (up.total.uploaded + up.total.failed)
857
                , maxCount = up.getOption('filters').max_file_count || 0
858
                ;
859
 
860
            if (plupload.STARTED === up.state) {
861
                $([])
862
                    .add(this.stop_button)
863
                    .add('.plupload_started')
864
                    .removeClass('plupload_hidden');
865
 
866
                this.start_button.button('disable');
867
 
868
                if (!this.options.multiple_queues) {
869
                    this.browse_button.button('disable');
870
                    up.disableBrowse();
871
                }
872
 
873
                $('.plupload_upload_status', this.element).html(plupload.sprintf(_('Uploaded %d/%d files'), up.total.uploaded, up.files.length));
874
                $('.plupload_header_content', this.element).addClass('plupload_header_content_bw');
875
            }
876
            else if (plupload.STOPPED === up.state) {
877
                $([])
878
                    .add(this.stop_button)
879
                    .add('.plupload_started')
880
                    .addClass('plupload_hidden');
881
 
882
                if (filesPending) {
883
                    this.start_button.button('enable');
884
                } else {
885
                    this.start_button.button('disable');
886
                }
887
 
888
                if (this.options.multiple_queues) {
889
                    $('.plupload_header_content', this.element).removeClass('plupload_header_content_bw');
890
                }
891
 
892
                // if max_file_count defined, only that many files can be queued at once
893
                if (this.options.multiple_queues && maxCount && maxCount > filesPending) {
894
                    this.browse_button.button('enable');
895
                    up.disableBrowse(false);
896
                }
897
 
898
                this._updateTotalProgress();
899
            }
900
 
901
            if (up.total.queued === 0) {
902
                $('.ui-button-text', this.browse_button).html(_('Add Files'));
903
            } else {
904
                $('.ui-button-text', this.browse_button).html(plupload.sprintf(_('%d files'), up.total.queued));
905
            }
906
 
907
            up.refresh();
908
        },
909
 
910
 
911
        _handleFileStatus: function(file) {
912
            var $file = $('#' + file.id), actionClass, iconClass;
913
 
914
            // since this method might be called asynchronously, file row might not yet be rendered
915
            if (!$file.length) {
916
                return;
917
            }
918
 
919
            switch (file.status) {
920
                case plupload.DONE:
921
                    actionClass = 'plupload_done';
922
                    iconClass = 'plupload_action_icon ui-icon ui-icon-circle-check';
923
                    break;
924
 
925
                case plupload.FAILED:
926
                    actionClass = 'ui-state-error plupload_failed';
927
                    iconClass = 'plupload_action_icon ui-icon ui-icon-alert';
928
                    break;
929
 
930
                case plupload.QUEUED:
931
                    actionClass = 'plupload_delete';
932
                    iconClass = 'plupload_action_icon ui-icon ui-icon-circle-minus';
933
                    break;
934
 
935
                case plupload.UPLOADING:
936
                    actionClass = 'ui-state-highlight plupload_uploading';
937
                    iconClass = 'plupload_action_icon ui-icon ui-icon-circle-arrow-w';
938
 
939
                    // scroll uploading file into the view if its bottom boundary is out of it
940
                    var scroller = $('.plupload_scroll', this.container)
941
                        , scrollTop = scroller.scrollTop()
942
                        , scrollerHeight = scroller.height()
943
                        , rowOffset = $file.position().top + $file.height()
944
                        ;
945
 
946
                    if (scrollerHeight < rowOffset) {
947
                        scroller.scrollTop(scrollTop + rowOffset - scrollerHeight);
948
                    }
949
 
950
                    // Set file specific progress
951
                    $file
952
                        .find('.plupload_file_percent')
953
                        .html(file.percent + '%')
954
                        .end()
955
                        .find('.plupload_file_progress')
956
                        .css('width', file.percent + '%')
957
                        .end()
958
                        .find('.plupload_file_size')
959
                        .html(plupload.formatSize(file.size));
960
                    break;
961
            }
962
            actionClass += ' ui-state-default plupload_file';
963
 
964
            $file
965
                .attr('class', actionClass)
966
                .find('.plupload_action_icon')
967
                .attr('class', iconClass);
968
        },
969
 
970
 
971
        _updateTotalProgress: function() {
972
            var up = this.uploader;
973
 
974
            // Scroll to end of file list
975
            this.filelist[0].scrollTop = this.filelist[0].scrollHeight;
976
 
977
            this.progressbar.progressbar('value', up.total.percent);
978
 
979
            this.element
980
                .find('.plupload_total_status')
981
                .html(up.total.percent + '%')
982
                .end()
983
                .find('.plupload_total_file_size')
984
                .html(plupload.formatSize(up.total.size))
985
                .end()
986
                .find('.plupload_upload_status')
987
                .html(plupload.sprintf(_('Uploaded %d/%d files'), up.total.uploaded, up.files.length));
988
        },
989
 
990
 
991
        _displayThumbs: function() {
992
            var self = this
993
                , tw, th // thumb width/height
994
                , cols
995
                , num = 0 // number of simultaneously visible thumbs
996
                , thumbs = [] // array of thumbs to preload at any given moment
997
                , loading = false
998
                ;
999
 
1000
            if (!this.options.views.thumbs) {
1001
                return;
1002
            }
1003
 
1004
 
1005
            function onLast(el, eventName, cb) {
1006
                var timer;
1007
 
1008
                el.on(eventName, function() {
1009
                    clearTimeout(timer);
1010
                    timer = setTimeout(function() {
1011
                        clearTimeout(timer);
1012
                        cb();
1013
                    }, 300);
1014
                });
1015
            }
1016
 
1017
 
1018
            // calculate number of simultaneously visible thumbs
1019
            function measure() {
1020
                if (!tw || !th) {
1021
                    var wrapper = $('.plupload_file:eq(0)', self.filelist);
1022
                    tw = wrapper.outerWidth(true);
1023
                    th = wrapper.outerHeight(true);
1024
                }
1025
 
1026
                var aw = self.content.width(), ah = self.content.height();
1027
                cols = Math.floor(aw / tw);
1028
                num =  cols * (Math.ceil(ah / th) + 1);
1029
            }
1030
 
1031
 
1032
            function pickThumbsToLoad() {
1033
                // calculate index of virst visible thumb
1034
                var startIdx = Math.floor(self.content.scrollTop() / th) * cols;
1035
                // get potentially visible thumbs that are not yet visible
1036
                thumbs = $('.plupload_file .plupload_file_thumb', self.filelist)
1037
                    .slice(startIdx, startIdx + num)
1038
                    .filter('.plupload_thumb_toload')
1039
                    .get();
1040
            }
1041
 
1042
 
1043
            function init() {
1044
                function mpl() { // measure, pick, load
1045
                    if (self.view_mode !== 'thumbs') {
1046
                        return;
1047
                    }
1048
                    measure();
1049
                    pickThumbsToLoad();
1050
                    lazyLoad();
1051
                }
1052
 
1053
                if ($.fn.resizable) {
1054
                    onLast(self.container, 'resize', mpl);
1055
                }
1056
 
1057
                onLast(self.window, 'resize', mpl);
1058
                onLast(self.content, 'scroll',  mpl);
1059
 
1060
                self.element.on('viewchanged selected', mpl);
1061
 
1062
                mpl();
1063
            }
1064
 
1065
 
1066
            function preloadThumb(file, cb) {
1067
                var img = new o.image.Image();
1068
                var resolveUrl = o.core.utils.Url.resolveUrl;
1069
 
1070
                img.onload = function() {
1071
                    var thumb = $('#' + file.id + ' .plupload_file_thumb', self.filelist);
1072
                    this.embed(thumb[0], {
1073
                        width: self.options.thumb_width,
1074
                        height: self.options.thumb_height,
1075
                        crop: true,
1076
                        preserveHeaders: false,
1077
                        swf_url: resolveUrl(self.options.flash_swf_url),
1078
                        xap_url: resolveUrl(self.options.silverlight_xap_url)
1079
                    });
1080
                };
1081
 
1082
                img.bind("embedded error", function(e) {
1083
                    $('#' + file.id, self.filelist)
1084
                        .find('.plupload_file_thumb')
1085
                        .removeClass('plupload_thumb_loading')
1086
                        .addClass('plupload_thumb_' + e.type)
1087
                    ;
1088
                    this.destroy();
1089
                    setTimeout(cb, 1); // detach, otherwise ui might hang (in SilverLight for example)
1090
                });
1091
 
1092
                $('#' + file.id, self.filelist)
1093
                    .find('.plupload_file_thumb')
1094
                    .removeClass('plupload_thumb_toload')
1095
                    .addClass('plupload_thumb_loading')
1096
                ;
1097
                img.load(file.getSource());
1098
            }
1099
 
1100
 
1101
            function lazyLoad() {
1102
                if (self.view_mode !== 'thumbs' || loading) {
1103
                    return;
1104
                }
1105
 
1106
                pickThumbsToLoad();
1107
                if (!thumbs.length) {
1108
                    return;
1109
                }
1110
 
1111
                loading = true;
1112
 
1113
                preloadThumb(self.getFile($(thumbs.shift()).closest('.plupload_file').attr('id')), function() {
1114
                    loading = false;
1115
                    lazyLoad();
1116
                });
1117
            }
1118
 
1119
            // this has to run only once to measure structures and bind listeners
1120
            this.element.on('selected', function onselected() {
1121
                self.element.off('selected', onselected);
1122
                init();
1123
            });
1124
        },
1125
 
1126
 
1127
        _addFiles: function(files) {
1128
            var self = this, file_html, html = '';
1129
 
1130
            file_html = '<li class="plupload_file ui-state-default plupload_delete" id="{id}" style="width:{thumb_width}px;">' +
1131
            '<div class="plupload_file_thumb plupload_thumb_toload" style="width: {thumb_width}px; height: {thumb_height}px;">' +
1132
            '<div class="plupload_file_dummy ui-widget-content" style="line-height: {thumb_height}px;"><span class="ui-state-disabled">{ext} </span></div>' +
1133
            '</div>' +
1134
            '<div class="plupload_file_status">' +
1135
            '<div class="plupload_file_progress ui-widget-header" style="width: 0%"> </div>' +
1136
            '<span class="plupload_file_percent">{percent} </span>' +
1137
            '</div>' +
1138
            '<div class="plupload_file_name" title="{name}">' +
1139
            '<span class="plupload_file_name_wrapper">{name} </span>' +
1140
            '</div>' +
1141
            '<div class="plupload_file_action">' +
1142
            '<div class="plupload_action_icon ui-icon ui-icon-circle-minus"> </div>' +
1143
            '</div>' +
1144
            '<div class="plupload_file_size">{size} </div>' +
1145
            '<div class="plupload_file_fields"> </div>' +
1146
            '</li>';
1147
 
1148
            if (plupload.typeOf(files) !== 'array') {
1149
                files = [files];
1150
            }
1151
 
1152
            $.each(files, function(i, file) {
1153
                var ext = o.core.utils.Mime.getFileExtension(file.name) || 'none';
1154
 
1155
                html += file_html.replace(/\{(\w+)\}/g, function($0, $1) {
1156
                    switch ($1) {
1157
                        case 'thumb_width':
1158
                        case 'thumb_height':
1159
                            return self.options[$1];
1160
 
1161
                        case 'size':
1162
                            return plupload.formatSize(file.size);
1163
 
1164
                        case 'ext':
1165
                            return ext;
1166
 
1167
                        default:
1168
                            return file[$1] || '';
1169
                    }
1170
                });
1171
            });
1172
 
1173
            self.filelist.append(html);
1174
        },
1175
 
1176
 
1177
        _addFormFields: function() {
1178
            var self = this;
1179
 
1180
            // re-add from fresh
1181
            $('.plupload_file_fields', this.filelist).html('');
1182
 
1183
            plupload.each(this.uploader.files, function(file, count) {
1184
                var fields = '',
1185
                    id = self.id + '_' + count;
1186
                var getfilename = plupload.xmlEncode(file.name);
1187
                var fileExt = getfilename.split('.').pop();
1188
 
1189
                if (file.target_name) {
1190
                    fields += '<input type="hidden" name="' + id + '_tmpname" value="'+plupload.xmlEncode(file.target_name)+'" />';
1191
                }
1192
                fields += '<input type="hidden" name="' + id + '_name" value="'+plupload.xmlEncode(file.name)+'" />';
1193
                fields += '<input type="hidden" name="' + id + '_status" value="' + (file.status === plupload.DONE ? 'done' : 'failed') + '" />';
1194
 
1195
 
1196
                $('#' + file.id).find('.plupload_file_fields').html(fields);
1197
 
1198
            });
1199
 
1200
            //$('#uploader').css({'display':'none'});
1201
            //$('#uploader').html('');
1202
            this.counter.val(this.uploader.files.length);
1203
        },
1204
 
1205
 
1206
        _viewChanged: function(view) {
1207
            // update or write a new cookie
1208
            if (this.options.views.remember && $.cookie) {
1209
                $.cookie('plupload_ui_view', view, { expires: 7, path: '/' });
1210
            }
1211
 
1212
            // ugly fix for IE6 - make content area stretchable
1213
            if (plupload.ua.browser === 'IE' && plupload.ua.version < 7) {
1214
                this.content.attr('style', 'height:expression(document.getElementById("' + this.id + '_container' + '").clientHeight - ' + (view === 'list' ? 132 : 102) + ')');
1215
            }
1216
 
1217
            this.container.removeClass('plupload_view_list plupload_view_thumbs').addClass('plupload_view_' + view);
1218
            this.view_mode = view;
1219
            this._trigger('viewchanged', null, { view: view });
1220
        },
1221
 
1222
 
1223
        _enableViewSwitcher: function() {
1224
            var self = this
1225
                , view
1226
                , switcher = $('.plupload_view_switch', this.container)
1227
                , buttons
1228
                , button
1229
                ;
1230
 
1231
            plupload.each(['list', 'thumbs'], function(view) {
1232
                if (!self.options.views[view]) {
1233
                    switcher.find('[for="' + self.id + '_view_' + view + '"], #'+ self.id +'_view_' + view).remove();
1234
                }
1235
            });
1236
 
1237
            // check if any visible left
1238
            buttons = switcher.find('.plupload_button');
1239
 
1240
            if (buttons.length === 1) {
1241
                switcher.hide();
1242
                view = buttons.eq(0).data('view');
1243
                this._viewChanged(view);
1244
            } else if ($.ui.button && buttons.length > 1) {
1245
                if (this.options.views.remember && $.cookie) {
1246
                    view = $.cookie('plupload_ui_view');
1247
                }
1248
 
1249
                // if wierd case, bail out to default
1250
                if (!~plupload.inArray(view, ['list', 'thumbs'])) {
1251
                    view = this.options.views.active;
1252
                }
1253
 
1254
                switcher
1255
                    .show()
1256
                    .buttonset()
1257
                    .find('.ui-button')
1258
                    .click(function(e) {
1259
                        view = $(this).data('view');
1260
                        self._viewChanged(view);
1261
                        e.preventDefault(); // avoid auto scrolling to widget in IE and FF (see #850)
1262
                    });
1263
 
1264
                // if view not active - happens when switcher wasn't clicked manually
1265
                button = switcher.find('[for="' + self.id + '_view_'+view+'"]');
1266
                if (button.length) {
1267
                    button.trigger('click');
1268
                }
1269
            } else {
1270
                switcher.show();
1271
                this._viewChanged(this.options.views.active);
1272
            }
1273
 
1274
            // initialize thumb viewer if requested
1275
            if (this.options.views.thumbs) {
1276
                this._displayThumbs();
1277
            }
1278
        },
1279
 
1280
 
1281
        _enableRenaming: function() {
1282
            var self = this;
1283
 
1284
            this.filelist.dblclick(function(e) {
1285
                var nameSpan = $(e.target), nameInput, file, parts, name, ext = "";
1286
 
1287
                if (!nameSpan.hasClass('plupload_file_name_wrapper')) {
1288
                    return;
1289
                }
1290
 
1291
                // Get file name and split out name and extension
1292
                file = self.uploader.getFile(nameSpan.closest('.plupload_file')[0].id);
1293
                name = file.name;
1294
                parts = /^(.+)(\.[^.]+)$/.exec(name);
1295
                if (parts) {
1296
                    name = parts[1];
1297
                    ext = parts[2];
1298
                }
1299
 
1300
                // Display input element
1301
                nameInput = $('<input class="plupload_file_rename" type="text" />').width(nameSpan.width()).insertAfter(nameSpan.hide());
1302
                nameInput.val(name).blur(function() {
1303
                    nameSpan.show().parent().scrollLeft(0).end().next().remove();
1304
                }).keydown(function(e) {
1305
                    var nameInput = $(this);
1306
 
1307
                    if ($.inArray(e.keyCode, [13, 27]) !== -1) {
1308
                        e.preventDefault();
1309
 
1310
                        // Rename file and glue extension back on
1311
                        if (e.keyCode === 13) {
1312
                            file.name = nameInput.val() + ext;
1313
                            nameSpan.html(file.name);
1314
                        }
1315
                        nameInput.blur();
1316
                    }
1317
                })[0].focus();
1318
            });
1319
        },
1320
 
1321
 
1322
        _enableSortingList: function() {
1323
            var self = this;
1324
 
1325
            if ($('.plupload_file', this.filelist).length < 2) {
1326
                return;
1327
            }
1328
 
1329
            // destroy sortable if enabled
1330
            $('tbody', this.filelist).sortable('destroy');
1331
 
1332
            // enable
1333
            this.filelist.sortable({
1334
                items: '.plupload_delete',
1335
 
1336
                cancel: 'object, .plupload_clearer',
1337
 
1338
                stop: function() {
1339
                    var files = [];
1340
 
1341
                    $.each($(this).sortable('toArray'), function(i, id) {
1342
                        files[files.length] = self.uploader.getFile(id);
1343
                    });
1344
 
1345
                    files.unshift(files.length);
1346
                    files.unshift(0);
1347
 
1348
                    // re-populate files array
1349
                    Array.prototype.splice.apply(self.uploader.files, files);
1350
                }
1351
            });
1352
        }
1353
    });
1354
 
1355
} (window, document, plupload, moxie, jQuery));