Subversion-Projekte lars-tiefland.cienc

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
5 lars 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 start
145
@param {plupload.Uploader} uploader Uploader instance sending the event.
146
*/
147
 
148
/**
149
Dispatched when upload is stopped.
150
 
151
@event stop
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
		'<div class="plupload_wrapper">' +
212
			'<div class="ui-widget-content plupload_container">' +
213
				'<div class="ui-state-default ui-widget-header plupload_header">' +
214
					'<div class="plupload_header_content">' +
215
						'<div class="plupload_logo"> </div>' +
216
						'<div class="plupload_header_title">' + _("Select files") + '</div>' +
217
						'<div class="plupload_header_text">' + _("Add files to the upload queue and click the start button.") + '</div>' +
218
						'<div class="plupload_view_switch">' +
219
							'<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>' +
220
							'<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>' +
221
						'</div>' +
222
					'</div>' +
223
				'</div>' +
224
 
225
				'<table class="plupload_filelist plupload_filelist_header ui-widget-header">' +
226
				'<tr>' +
227
					'<td class="plupload_cell plupload_file_name">' + _('Filename') + '</td>' +
228
					'<td class="plupload_cell plupload_file_status">' + _('Status') + '</td>' +
229
					'<td class="plupload_cell plupload_file_size">' + _('Size') + '</td>' +
230
					'<td class="plupload_cell plupload_file_action">&nbsp;</td>' +
231
				'</tr>' +
232
				'</table>' +
233
 
234
				'<div class="plupload_content">' +
235
					'<div class="plupload_droptext">' + _("Drag files here.") + '</div>' +
236
					'<ul class="plupload_filelist_content"> </ul>' +
237
					'<div class="plupload_clearer">&nbsp;</div>' +
238
				'</div>' +
239
 
240
				'<table class="plupload_filelist plupload_filelist_footer ui-widget-header">' +
241
				'<tr>' +
242
					'<td class="plupload_cell plupload_file_name">' +
243
						'<div class="plupload_buttons"><!-- Visible -->' +
244
							'<a class="plupload_button plupload_add">' + _("Add Files") + '</a>&nbsp;' +
245
							'<a class="plupload_button plupload_start">' + _("Start Upload") + '</a>&nbsp;' +
246
							'<a class="plupload_button plupload_stop plupload_hidden">'+_("Stop Upload") + '</a>&nbsp;' +
247
						'</div>' +
248
 
249
						'<div class="plupload_started plupload_hidden"><!-- Hidden -->' +
250
							'<div class="plupload_progress plupload_right">' +
251
								'<div class="plupload_progress_container"></div>' +
252
							'</div>' +
253
 
254
							'<div class="plupload_cell plupload_upload_status"></div>' +
255
 
256
							'<div class="plupload_clearer">&nbsp;</div>' +
257
						'</div>' +
258
					'</td>' +
259
					'<td class="plupload_file_status"><span class="plupload_total_status">0%</span></td>' +
260
					'<td class="plupload_file_size"><span class="plupload_total_file_size">0 kb</span></td>' +
261
					'<td class="plupload_file_action"></td>' +
262
				'</tr>' +
263
				'</table>' +
264
 
265
			'</div>' +
266
			'<input class="plupload_count" value="0" type="hidden">' +
267
		'</div>'
268
	);
269
}
270
 
271
 
272
$.widget("ui.plupload", {
273
 
274
	widgetEventPrefix: '',
275
 
276
	contents_bak: '',
277
 
278
	options: {
279
		browse_button_hover: 'ui-state-hover',
280
		browse_button_active: 'ui-state-active',
281
 
282
		filters: {},
283
 
284
		// widget specific
285
		buttons: {
286
			browse: true,
287
			start: true,
288
			stop: true
289
		},
290
 
291
		views: {
292
			list: true,
293
			thumbs: false,
294
			active: 'list',
295
			remember: true // requires: https://github.com/carhartl/jquery-cookie, otherwise disabled even if set to true
296
		},
297
 
298
		thumb_width: 100,
299
		thumb_height: 60,
300
 
301
		multiple_queues: true, // re-use widget by default
302
		dragdrop : true,
303
		autostart: false,
304
		sortable: false,
305
		rename: false
306
	},
307
 
308
	FILE_COUNT_ERROR: -9001,
309
 
310
	_create: function() {
311
		var id = this.element.attr('id');
312
		if (!id) {
313
			id = plupload.guid();
314
			this.element.attr('id', id);
315
		}
316
		this.id = id;
317
 
318
		// backup the elements initial state
319
		this.contents_bak = this.element.html();
320
		renderUI(this.element);
321
 
322
		// container, just in case
323
		this.container = $('.plupload_container', this.element).attr('id', id + '_container');
324
 
325
		this.content = $('.plupload_content', this.element);
326
 
327
		if ($.fn.resizable) {
328
			this.container.resizable({
329
				handles: 's',
330
				minHeight: 300
331
			});
332
		}
333
 
334
		// list of files, may become sortable
335
		this.filelist = $('.plupload_filelist_content', this.container)
336
			.attr({
337
				id: id + '_filelist',
338
				unselectable: 'on'
339
			});
340
 
341
 
342
		// buttons
343
		this.browse_button = $('.plupload_add', this.container).attr('id', id + '_browse');
344
		this.start_button = $('.plupload_start', this.container).attr('id', id + '_start');
345
		this.stop_button = $('.plupload_stop', this.container).attr('id', id + '_stop');
346
		this.thumbs_switcher = $('#' + id + '_view_thumbs');
347
		this.list_switcher = $('#' + id + '_view_list');
348
 
349
		if ($.ui.button) {
350
			this.browse_button.button({
351
				icons: { primary: 'ui-icon-circle-plus' },
352
				disabled: true
353
			});
354
 
355
			this.start_button.button({
356
				icons: { primary: 'ui-icon-circle-arrow-e' },
357
				disabled: true
358
			});
359
 
360
			this.stop_button.button({
361
				icons: { primary: 'ui-icon-circle-close' }
362
			});
363
 
364
			this.list_switcher.button({
365
				text: false,
366
				icons: { secondary: "ui-icon-grip-dotted-horizontal" }
367
			});
368
 
369
			this.thumbs_switcher.button({
370
				text: false,
371
				icons: { secondary: "ui-icon-image" }
372
			});
373
		}
374
 
375
		// progressbar
376
		this.progressbar = $('.plupload_progress_container', this.container);
377
 
378
		if ($.ui.progressbar) {
379
			this.progressbar.progressbar();
380
		}
381
 
382
		// counter
383
		this.counter = $('.plupload_count', this.element)
384
			.attr({
385
				id: id + '_count',
386
				name: id + '_count'
387
			});
388
 
389
		// initialize uploader instance
390
		this._initUploader();
391
	},
392
 
393
	_initUploader: function() {
394
		var self = this
395
		, id = this.id
396
		, uploader
397
		, options = {
398
			container: id + '_buttons',
399
			browse_button: id + '_browse'
400
		}
401
		;
402
 
403
		$('.plupload_buttons', this.element).attr('id', id + '_buttons');
404
 
405
		if (self.options.dragdrop) {
406
			this.filelist.parent().attr('id', this.id + '_dropbox');
407
			options.drop_element = this.id + '_dropbox';
408
		}
409
 
410
		this.filelist.on('click', function(e) {
411
			if ($(e.target).hasClass('plupload_action_icon')) {
412
				self.removeFile($(e.target).closest('.plupload_file').attr('id'));
413
				e.preventDefault();
414
			}
415
		});
416
 
417
		uploader = this.uploader = uploaders[id] = new plupload.Uploader($.extend(this.options, options));
418
 
419
		if (self.options.views.thumbs) {
420
			uploader.settings.required_features.display_media = true;
421
		}
422
 
423
		// for backward compatibility
424
		if (self.options.max_file_count) {
425
			plupload.extend(uploader.getOption('filters'), {
426
				max_file_count: self.options.max_file_count
427
			});
428
		}
429
 
430
		plupload.addFileFilter('max_file_count', function(maxCount, file, cb) {
431
			if (maxCount <= this.files.length - (this.total.uploaded + this.total.failed)) {
432
				self.browse_button.button('disable');
433
				this.disableBrowse();
434
 
435
				this.trigger('Error', {
436
					code : self.FILE_COUNT_ERROR,
437
					message : _("File count error."),
438
					file : file
439
				});
440
				cb(false);
441
			} else {
442
				cb(true);
443
			}
444
		});
445
 
446
 
447
		uploader.bind('Error', function(up, err) {
448
			var message, details = "";
449
 
450
			message = '<strong>' + err.message + '</strong>';
451
 
452
			switch (err.code) {
453
				case plupload.FILE_EXTENSION_ERROR:
454
					details = o.sprintf(_("File: %s"), err.file.name);
455
					break;
456
 
457
				case plupload.FILE_SIZE_ERROR:
458
					details = o.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)));
459
					break;
460
 
461
				case plupload.FILE_DUPLICATE_ERROR:
462
					details = o.sprintf(_("%s already present in the queue."), err.file.name);
463
					break;
464
 
465
				case self.FILE_COUNT_ERROR:
466
					details = o.sprintf(_("Upload element accepts only %d file(s) at a time. Extra files were stripped."), up.getOption('filters').max_file_count || 0);
467
					break;
468
 
469
				case plupload.IMAGE_FORMAT_ERROR :
470
					details = _("Image format either wrong or not supported.");
471
					break;
472
 
473
				case plupload.IMAGE_MEMORY_ERROR :
474
					details = _("Runtime ran out of available memory.");
475
					break;
476
 
477
				/* // This needs a review
478
				case plupload.IMAGE_DIMENSIONS_ERROR :
479
					details = o.sprintf(_('Resoultion out of boundaries! <b>%s</b> runtime supports images only up to %wx%hpx.'), up.runtime, up.features.maxWidth, up.features.maxHeight);
480
					break;	*/
481
 
482
				case plupload.HTTP_ERROR:
483
					details = _("Upload URL might be wrong or doesn't exist.");
484
					break;
485
			}
486
 
487
			message += " <br /><i>" + details + "</i>";
488
 
489
			self._trigger('error', null, { up: up, error: err } );
490
 
491
			// do not show UI if no runtime can be initialized
492
			if (err.code === plupload.INIT_ERROR) {
493
				setTimeout(function() {
494
					self.destroy();
495
				}, 1);
496
			} else {
497
				self.notify('error', message);
498
			}
499
		});
500
 
501
 
502
		uploader.bind('PostInit', function(up) {
503
			// all buttons are optional, so they can be disabled and hidden
504
			if (!self.options.buttons.browse) {
505
				self.browse_button.button('disable').hide();
506
				up.disableBrowse(true);
507
			} else {
508
				self.browse_button.button('enable');
509
			}
510
 
511
			if (!self.options.buttons.start) {
512
				self.start_button.button('disable').hide();
513
			}
514
 
515
			if (!self.options.buttons.stop) {
516
				self.stop_button.button('disable').hide();
517
			}
518
 
519
			if (!self.options.unique_names && self.options.rename) {
520
				self._enableRenaming();
521
			}
522
 
523
			if (self.options.dragdrop && up.features.dragdrop) {
524
				self.filelist.parent().addClass('plupload_dropbox');
525
			}
526
 
527
			self._enableViewSwitcher();
528
 
529
			self.start_button.click(function(e) {
530
				if (!$(this).button('option', 'disabled')) {
531
					self.start();
532
				}
533
				e.preventDefault();
534
			});
535
 
536
			self.stop_button.click(function(e) {
537
				self.stop();
538
				e.preventDefault();
539
			});
540
 
541
			self._trigger('ready', null, { up: up });
542
		});
543
 
544
		// uploader internal events must run first
545
		uploader.init();
546
 
547
		uploader.bind('FileFiltered', function(up, file) {
548
			self._addFiles(file);
549
		});
550
 
551
		uploader.bind('FilesAdded', function(up, files) {
552
			self._trigger('selected', null, { up: up, files: files } );
553
 
554
			// re-enable sortable
555
			if (self.options.sortable && $.ui.sortable) {
556
				self._enableSortingList();
557
			}
558
 
559
			self._trigger('updatelist', null, { filelist: self.filelist });
560
 
561
			if (self.options.autostart) {
562
				// set a little delay to make sure that QueueChanged triggered by the core has time to complete
563
				setTimeout(function() {
564
					self.start();
565
				}, 10);
566
			}
567
		});
568
 
569
		uploader.bind('FilesRemoved', function(up, files) {
570
			// destroy sortable if enabled
571
			if ($.ui.sortable && self.options.sortable) {
572
				$('tbody', self.filelist).sortable('destroy');
573
			}
574
 
575
			$.each(files, function(i, file) {
576
				$('#' + file.id).toggle("highlight", function() {
577
					$(this).remove();
578
				});
579
			});
580
 
581
			if (up.files.length) {
582
				// re-initialize sortable
583
				if (self.options.sortable && $.ui.sortable) {
584
					self._enableSortingList();
585
				}
586
			}
587
 
588
			self._trigger('updatelist', null, { filelist: self.filelist });
589
			self._trigger('removed', null, { up: up, files: files } );
590
		});
591
 
592
		uploader.bind('QueueChanged StateChanged', function() {
593
			self._handleState();
594
		});
595
 
596
		uploader.bind('UploadFile', function(up, file) {
597
			self._handleFileStatus(file);
598
		});
599
 
600
		uploader.bind('FileUploaded', function(up, file) {
601
			self._handleFileStatus(file);
602
			self._trigger('uploaded', null, { up: up, file: file } );
603
		});
604
 
605
		uploader.bind('UploadProgress', function(up, file) {
606
			self._handleFileStatus(file);
607
			self._updateTotalProgress();
608
			self._trigger('progress', null, { up: up, file: file } );
609
		});
610
 
611
		uploader.bind('UploadComplete', function(up, files) {
612
			self._addFormFields();
613
			self._trigger('complete', null, { up: up, files: files } );
614
		});
615
	},
616
 
617
 
618
	_setOption: function(key, value) {
619
		var self = this;
620
 
621
		if (key == 'buttons' && typeof(value) == 'object') {
622
			value = $.extend(self.options.buttons, value);
623
 
624
			if (!value.browse) {
625
				self.browse_button.button('disable').hide();
626
				self.uploader.disableBrowse(true);
627
			} else {
628
				self.browse_button.button('enable').show();
629
				self.uploader.disableBrowse(false);
630
			}
631
 
632
			if (!value.start) {
633
				self.start_button.button('disable').hide();
634
			} else {
635
				self.start_button.button('enable').show();
636
			}
637
 
638
			if (!value.stop) {
639
				self.stop_button.button('disable').hide();
640
			} else {
641
				self.start_button.button('enable').show();
642
			}
643
		}
644
 
645
		self.uploader.settings[key] = value;
646
	},
647
 
648
 
649
	/**
650
	Start upload. Triggers `start` event.
651
 
652
	@method start
653
	*/
654
	start: function() {
655
		this.uploader.start();
656
		this._trigger('start', null, { up: this.uploader });
657
	},
658
 
659
 
660
	/**
661
	Stop upload. Triggers `stop` event.
662
 
663
	@method stop
664
	*/
665
	stop: function() {
666
		this.uploader.stop();
667
		this._trigger('stop', null, { up: this.uploader });
668
	},
669
 
670
 
671
	/**
672
	Enable browse button.
673
 
674
	@method enable
675
	*/
676
	enable: function() {
677
		this.browse_button.button('enable');
678
		this.uploader.disableBrowse(false);
679
	},
680
 
681
 
682
	/**
683
	Disable browse button.
684
 
685
	@method disable
686
	*/
687
	disable: function() {
688
		this.browse_button.button('disable');
689
		this.uploader.disableBrowse(true);
690
	},
691
 
692
 
693
	/**
694
	Retrieve file by it's unique id.
695
 
696
	@method getFile
697
	@param {String} id Unique id of the file
698
	@return {plupload.File}
699
	*/
700
	getFile: function(id) {
701
		var file;
702
 
703
		if (typeof id === 'number') {
704
			file = this.uploader.files[id];
705
		} else {
706
			file = this.uploader.getFile(id);
707
		}
708
		return file;
709
	},
710
 
711
	/**
712
	Return array of files currently in the queue.
713
 
714
	@method getFiles
715
	@return {Array} Array of files in the queue represented by plupload.File objects
716
	*/
717
	getFiles: function() {
718
		return this.uploader.files;
719
	},
720
 
721
 
722
	/**
723
	Remove the file from the queue.
724
 
725
	@method removeFile
726
	@param {plupload.File|String} file File to remove, might be specified directly or by it's unique id
727
	*/
728
	removeFile: function(file) {
729
		if (plupload.typeOf(file) === 'string') {
730
			file = this.getFile(file);
731
		}
732
		this.uploader.removeFile(file);
733
	},
734
 
735
 
736
	/**
737
	Clear the file queue.
738
 
739
	@method clearQueue
740
	*/
741
	clearQueue: function() {
742
		this.uploader.splice();
743
	},
744
 
745
 
746
	/**
747
	Retrieve internal plupload.Uploader object (usually not required).
748
 
749
	@method getUploader
750
	@return {plupload.Uploader}
751
	*/
752
	getUploader: function() {
753
		return this.uploader;
754
	},
755
 
756
 
757
	/**
758
	Trigger refresh procedure, specifically browse_button re-measure and re-position operations.
759
	Might get handy, when UI Widget is placed within the popup, that is constantly hidden and shown
760
	again - without calling this method after each show operation, dialog trigger might get displaced
761
	and disfunctional.
762
 
763
	@method refresh
764
	*/
765
	refresh: function() {
766
		this.uploader.refresh();
767
	},
768
 
769
 
770
	/**
771
	Display a message in notification area.
772
 
773
	@method notify
774
	@param {Enum} type Type of the message, either `error` or `info`
775
	@param {String} message The text message to display.
776
	*/
777
	notify: function(type, message) {
778
		var popup = $(
779
			'<div class="plupload_message">' +
780
				'<span class="plupload_message_close ui-icon ui-icon-circle-close" title="'+_('Close')+'"></span>' +
781
				'<p><span class="ui-icon"></span>' + message + '</p>' +
782
			'</div>'
783
		);
784
 
785
		popup
786
			.addClass('ui-state-' + (type === 'error' ? 'error' : 'highlight'))
787
			.find('p .ui-icon')
788
				.addClass('ui-icon-' + (type === 'error' ? 'alert' : 'info'))
789
				.end()
790
			.find('.plupload_message_close')
791
				.click(function() {
792
					popup.remove();
793
				})
794
				.end();
795
 
796
		$('.plupload_header', this.container).append(popup);
797
	},
798
 
799
 
800
	/**
801
	Destroy the widget, the uploader, free associated resources and bring back original html.
802
 
803
	@method destroy
804
	*/
805
	destroy: function() {
806
		// destroy uploader instance
807
		this.uploader.destroy();
808
 
809
		// unbind all button events
810
		$('.plupload_button', this.element).unbind();
811
 
812
		// destroy buttons
813
		if ($.ui.button) {
814
			$('.plupload_add, .plupload_start, .plupload_stop', this.container)
815
				.button('destroy');
816
		}
817
 
818
		// destroy progressbar
819
		if ($.ui.progressbar) {
820
			this.progressbar.progressbar('destroy');
821
		}
822
 
823
		// destroy sortable behavior
824
		if ($.ui.sortable && this.options.sortable) {
825
			$('tbody', this.filelist).sortable('destroy');
826
		}
827
 
828
		// restore the elements initial state
829
		this.element
830
			.empty()
831
			.html(this.contents_bak);
832
		this.contents_bak = '';
833
 
834
		$.Widget.prototype.destroy.apply(this);
835
	},
836
 
837
 
838
	_handleState: function() {
839
		var up = this.uploader
840
		, filesPending = up.files.length - (up.total.uploaded + up.total.failed)
841
		, maxCount = up.getOption('filters').max_file_count || 0
842
		;
843
 
844
		if (plupload.STARTED === up.state) {
845
			$([])
846
				.add(this.stop_button)
847
				.add('.plupload_started')
848
					.removeClass('plupload_hidden');
849
 
850
			this.start_button.button('disable');
851
 
852
			if (!this.options.multiple_queues) {
853
				this.browse_button.button('disable');
854
				up.disableBrowse();
855
			}
856
 
857
			$('.plupload_upload_status', this.element).html(o.sprintf(_('Uploaded %d/%d files'), up.total.uploaded, up.files.length));
858
			$('.plupload_header_content', this.element).addClass('plupload_header_content_bw');
859
		}
860
		else if (plupload.STOPPED === up.state) {
861
			$([])
862
				.add(this.stop_button)
863
				.add('.plupload_started')
864
					.addClass('plupload_hidden');
865
 
866
			if (filesPending) {
867
				this.start_button.button('enable');
868
			} else {
869
				this.start_button.button('disable');
870
			}
871
 
872
			if (this.options.multiple_queues) {
873
				$('.plupload_header_content', this.element).removeClass('plupload_header_content_bw');
874
			}
875
 
876
			// if max_file_count defined, only that many files can be queued at once
877
			if (this.options.multiple_queues && maxCount && maxCount > filesPending) {
878
				this.browse_button.button('enable');
879
				up.disableBrowse(false);
880
			}
881
 
882
			this._updateTotalProgress();
883
		}
884
 
885
		if (up.total.queued === 0) {
886
			$('.ui-button-text', this.browse_button).html(_('Add Files'));
887
		} else {
888
			$('.ui-button-text', this.browse_button).html(o.sprintf(_('%d files queued'), up.total.queued));
889
		}
890
 
891
		up.refresh();
892
	},
893
 
894
 
895
	_handleFileStatus: function(file) {
896
		var $file = $('#' + file.id), actionClass, iconClass;
897
 
898
		// since this method might be called asynchronously, file row might not yet be rendered
899
		if (!$file.length) {
900
			return;
901
		}
902
 
903
		switch (file.status) {
904
			case plupload.DONE:
905
				actionClass = 'plupload_done';
906
				iconClass = 'plupload_action_icon ui-icon ui-icon-circle-check';
907
				break;
908
 
909
			case plupload.FAILED:
910
				actionClass = 'ui-state-error plupload_failed';
911
				iconClass = 'plupload_action_icon ui-icon ui-icon-alert';
912
				break;
913
 
914
			case plupload.QUEUED:
915
				actionClass = 'plupload_delete';
916
				iconClass = 'plupload_action_icon ui-icon ui-icon-circle-minus';
917
				break;
918
 
919
			case plupload.UPLOADING:
920
				actionClass = 'ui-state-highlight plupload_uploading';
921
				iconClass = 'plupload_action_icon ui-icon ui-icon-circle-arrow-w';
922
 
923
				// scroll uploading file into the view if its bottom boundary is out of it
924
				var scroller = $('.plupload_scroll', this.container)
925
				, scrollTop = scroller.scrollTop()
926
				, scrollerHeight = scroller.height()
927
				, rowOffset = $file.position().top + $file.height()
928
				;
929
 
930
				if (scrollerHeight < rowOffset) {
931
					scroller.scrollTop(scrollTop + rowOffset - scrollerHeight);
932
				}
933
 
934
				// Set file specific progress
935
				$file
936
					.find('.plupload_file_percent')
937
						.html(file.percent + '%')
938
						.end()
939
					.find('.plupload_file_progress')
940
						.css('width', file.percent + '%')
941
						.end()
942
					.find('.plupload_file_size')
943
						.html(plupload.formatSize(file.size));
944
				break;
945
		}
946
		actionClass += ' ui-state-default plupload_file';
947
 
948
		$file
949
			.attr('class', actionClass)
950
			.find('.plupload_action_icon')
951
				.attr('class', iconClass);
952
	},
953
 
954
 
955
	_updateTotalProgress: function() {
956
		var up = this.uploader;
957
 
958
		// Scroll to end of file list
959
		this.filelist[0].scrollTop = this.filelist[0].scrollHeight;
960
 
961
		this.progressbar.progressbar('value', up.total.percent);
962
 
963
		this.element
964
			.find('.plupload_total_status')
965
				.html(up.total.percent + '%')
966
				.end()
967
			.find('.plupload_total_file_size')
968
				.html(plupload.formatSize(up.total.size))
969
				.end()
970
			.find('.plupload_upload_status')
971
				.html(o.sprintf(_('Uploaded %d/%d files'), up.total.uploaded, up.files.length));
972
	},
973
 
974
 
975
	_displayThumbs: function() {
976
		var self = this
977
		, tw, th // thumb width/height
978
		, cols
979
		, num = 0 // number of simultaneously visible thumbs
980
		, thumbs = [] // array of thumbs to preload at any given moment
981
		, loading = false
982
		;
983
 
984
		if (!this.options.views.thumbs) {
985
			return;
986
		}
987
 
988
 
989
		function onLast(el, eventName, cb) {
990
			var timer;
991
 
992
			el.on(eventName, function() {
993
				clearTimeout(timer);
994
				timer = setTimeout(function() {
995
					clearTimeout(timer);
996
					cb();
997
				}, 300);
998
			});
999
		}
1000
 
1001
 
1002
		// calculate number of simultaneously visible thumbs
1003
		function measure() {
1004
			if (!tw || !th) {
1005
				var wrapper = $('.plupload_file:eq(0)', self.filelist);
1006
				tw = wrapper.outerWidth(true);
1007
				th = wrapper.outerHeight(true);
1008
			}
1009
 
1010
			var aw = self.content.width(), ah = self.content.height();
1011
			cols = Math.floor(aw / tw);
1012
			num =  cols * (Math.ceil(ah / th) + 1);
1013
		}
1014
 
1015
 
1016
		function pickThumbsToLoad() {
1017
			// calculate index of virst visible thumb
1018
			var startIdx = Math.floor(self.content.scrollTop() / th) * cols;
1019
			// get potentially visible thumbs that are not yet visible
1020
			thumbs = $('.plupload_file', self.filelist)
1021
				.slice(startIdx, startIdx + num)
1022
				.filter('.plupload_file_loading')
1023
				.get();
1024
		}
1025
 
1026
 
1027
		function init() {
1028
			function mpl() { // measure, pick, load
1029
				if (self.view_mode !== 'thumbs') {
1030
					return;
1031
				}
1032
				measure();
1033
				pickThumbsToLoad();
1034
				lazyLoad();
1035
			}
1036
 
1037
			if ($.fn.resizable) {
1038
				onLast(self.container, 'resize', mpl);
1039
			}
1040
 
1041
			onLast(self.window, 'resize', mpl);
1042
			onLast(self.content, 'scroll',  mpl);
1043
 
1044
			self.element.on('viewchanged selected', mpl);
1045
 
1046
			mpl();
1047
		}
1048
 
1049
 
1050
		function preloadThumb(file, cb) {
1051
			var img = new o.Image();
1052
 
1053
			img.onload = function() {
1054
				var thumb = $('#' + file.id + ' .plupload_file_thumb', self.filelist).html('');
1055
				this.embed(thumb[0], {
1056
					width:  self.options.thumb_width,
1057
					height: self.options.thumb_height,
1058
					crop: true,
1059
					swf_url: o.resolveUrl(self.options.flash_swf_url),
1060
					xap_url: o.resolveUrl(self.options.silverlight_xap_url)
1061
				});
1062
			};
1063
 
1064
			img.bind("embedded error", function() {
1065
				$('#' + file.id, self.filelist).removeClass('plupload_file_loading');
1066
				this.destroy();
1067
				setTimeout(cb, 1); // detach, otherwise ui might hang (in SilverLight for example)
1068
			});
1069
 
1070
			img.load(file.getSource());
1071
		}
1072
 
1073
 
1074
		function lazyLoad() {
1075
			if (self.view_mode !== 'thumbs' || loading) {
1076
				return;
1077
			}
1078
 
1079
			pickThumbsToLoad();
1080
			if (!thumbs.length) {
1081
				return;
1082
			}
1083
 
1084
			loading = true;
1085
 
1086
			preloadThumb(self.getFile($(thumbs.shift()).attr('id')), function() {
1087
				loading = false;
1088
				lazyLoad();
1089
			});
1090
		}
1091
 
1092
		// this has to run only once to measure structures and bind listeners
1093
		this.element.on('selected', function onselected() {
1094
			self.element.off('selected', onselected);
1095
			init();
1096
		});
1097
	},
1098
 
1099
 
1100
	_addFiles: function(files) {
1101
		var self = this, file_html, html = '';
1102
 
1103
		file_html = '<li class="plupload_file ui-state-default plupload_file_loading plupload_delete" id="%id%" style="width:%thumb_width%px;">' +
1104
			'<div class="plupload_file_thumb" style="width:%thumb_width%px;height:%thumb_height%px;">' +
1105
				'<div class="plupload_file_dummy ui-widget-content" style="line-height:%thumb_height%px;"><span class="ui-state-disabled">%ext% </span></div>' +
1106
			'</div>' +
1107
			'<div class="plupload_file_status">' +
1108
				'<div class="plupload_file_progress ui-widget-header" style="width: 0%"> </div>' +
1109
				'<span class="plupload_file_percent">%percent% </span>' +
1110
			'</div>' +
1111
			'<div class="plupload_file_name" title="%name%">' +
1112
				'<span class="plupload_file_name_wrapper">%name% </span>' +
1113
			'</div>' +
1114
			'<div class="plupload_file_action">' +
1115
				'<div class="plupload_action_icon ui-icon ui-icon-circle-minus"> </div>' +
1116
			'</div>' +
1117
			'<div class="plupload_file_size">%size% </div>' +
1118
			'<div class="plupload_file_fields"> </div>' +
1119
		'</li>';
1120
 
1121
		if (plupload.typeOf(files) !== 'array') {
1122
			files = [files];
1123
		}
1124
 
1125
		$.each(files, function(i, file) {
1126
			var ext = o.Mime.getFileExtension(file.name) || 'none';
1127
 
1128
			html += file_html.replace(/%(\w+)%/g, function($0, $1) {
1129
				switch ($1) {
1130
					case 'thumb_width':
1131
					case 'thumb_height':
1132
						return self.options[$1];
1133
 
1134
					case 'size':
1135
						return plupload.formatSize(file.size);
1136
 
1137
					case 'ext':
1138
						return ext;
1139
 
1140
					default:
1141
						return file[$1] || '';
1142
				}
1143
			});
1144
		});
1145
 
1146
		self.filelist.append(html);
1147
	},
1148
 
1149
 
1150
	_addFormFields: function() {
1151
		var self = this;
1152
 
1153
		// re-add from fresh
1154
		$('.plupload_file_fields', this.filelist).html('');
1155
 
1156
		plupload.each(this.uploader.files, function(file, count) {
1157
			var fields = ''
1158
			, id = self.id + '_' + count
1159
			;
1160
 
1161
			if (file.target_name) {
1162
				fields += '<input type="hidden" name="' + id + '_tmpname" value="'+plupload.xmlEncode(file.target_name)+'" />';
1163
			}
1164
			fields += '<input type="hidden" name="' + id + '_name" value="'+plupload.xmlEncode(file.name)+'" />';
1165
			fields += '<input type="hidden" name="' + id + '_status" value="' + (file.status === plupload.DONE ? 'done' : 'failed') + '" />';
1166
 
1167
			$('#' + file.id).find('.plupload_file_fields').html(fields);
1168
		});
1169
 
1170
		this.counter.val(this.uploader.files.length);
1171
	},
1172
 
1173
 
1174
	_viewChanged: function(view) {
1175
		// update or write a new cookie
1176
		if (this.options.views.remember && $.cookie) {
1177
			$.cookie('plupload_ui_view', view, { expires: 7, path: '/' });
1178
		}
1179
 
1180
		// ugly fix for IE6 - make content area stretchable
1181
		if (o.Env.browser === 'IE' && o.Env.version < 7) {
1182
			this.content.attr('style', 'height:expression(document.getElementById("' + this.id + '_container' + '").clientHeight - ' + (view === 'list' ? 132 : 102) + ')');
1183
		}
1184
 
1185
		this.container.removeClass('plupload_view_list plupload_view_thumbs').addClass('plupload_view_' + view);
1186
		this.view_mode = view;
1187
		this._trigger('viewchanged', null, { view: view });
1188
	},
1189
 
1190
 
1191
	_enableViewSwitcher: function() {
1192
		var self = this
1193
		, view
1194
		, switcher = $('.plupload_view_switch', this.container)
1195
		, buttons
1196
		, button
1197
		;
1198
 
1199
		plupload.each(['list', 'thumbs'], function(view) {
1200
			if (!self.options.views[view]) {
1201
				switcher.find('[for="' + self.id + '_view_' + view + '"], #'+ self.id +'_view_' + view).remove();
1202
			}
1203
		});
1204
 
1205
		// check if any visible left
1206
		buttons = switcher.find('.plupload_button');
1207
 
1208
		if (buttons.length === 1) {
1209
			switcher.hide();
1210
			view = buttons.eq(0).data('view');
1211
			this._viewChanged(view);
1212
		} else if ($.ui.button && buttons.length > 1) {
1213
			if (this.options.views.remember && $.cookie) {
1214
				view = $.cookie('plupload_ui_view');
1215
			}
1216
 
1217
			// if wierd case, bail out to default
1218
			if (!~plupload.inArray(view, ['list', 'thumbs'])) {
1219
				view = this.options.views.active;
1220
			}
1221
 
1222
			switcher
1223
				.show()
1224
				.buttonset()
1225
				.find('.ui-button')
1226
					.click(function(e) {
1227
						view = $(this).data('view');
1228
						self._viewChanged(view);
1229
						e.preventDefault(); // avoid auto scrolling to widget in IE and FF (see #850)
1230
					});
1231
 
1232
			// if view not active - happens when switcher wasn't clicked manually
1233
			button = switcher.find('[for="' + self.id + '_view_'+view+'"]');
1234
			if (button.length) {
1235
				button.trigger('click');
1236
			}
1237
		} else {
1238
			switcher.show();
1239
			this._viewChanged(this.options.views.active);
1240
		}
1241
 
1242
		// initialize thumb viewer if requested
1243
		if (this.options.views.thumbs) {
1244
			this._displayThumbs();
1245
		}
1246
	},
1247
 
1248
 
1249
	_enableRenaming: function() {
1250
		var self = this;
1251
 
1252
		this.filelist.dblclick(function(e) {
1253
			var nameSpan = $(e.target), nameInput, file, parts, name, ext = "";
1254
 
1255
			if (!nameSpan.hasClass('plupload_file_name_wrapper')) {
1256
				return;
1257
			}
1258
 
1259
			// Get file name and split out name and extension
1260
			file = self.uploader.getFile(nameSpan.closest('.plupload_file')[0].id);
1261
			name = file.name;
1262
			parts = /^(.+)(\.[^.]+)$/.exec(name);
1263
			if (parts) {
1264
				name = parts[1];
1265
				ext = parts[2];
1266
			}
1267
 
1268
			// Display input element
1269
			nameInput = $('<input class="plupload_file_rename" type="text" />').width(nameSpan.width()).insertAfter(nameSpan.hide());
1270
			nameInput.val(name).blur(function() {
1271
				nameSpan.show().parent().scrollLeft(0).end().next().remove();
1272
			}).keydown(function(e) {
1273
				var nameInput = $(this);
1274
 
1275
				if ($.inArray(e.keyCode, [13, 27]) !== -1) {
1276
					e.preventDefault();
1277
 
1278
					// Rename file and glue extension back on
1279
					if (e.keyCode === 13) {
1280
						file.name = nameInput.val() + ext;
1281
						nameSpan.html(file.name);
1282
					}
1283
					nameInput.blur();
1284
				}
1285
			})[0].focus();
1286
		});
1287
	},
1288
 
1289
 
1290
	_enableSortingList: function() {
1291
		var self = this;
1292
 
1293
		if ($('.plupload_file', this.filelist).length < 2) {
1294
			return;
1295
		}
1296
 
1297
		// destroy sortable if enabled
1298
		$('tbody', this.filelist).sortable('destroy');
1299
 
1300
		// enable
1301
		this.filelist.sortable({
1302
			items: '.plupload_delete',
1303
 
1304
			cancel: 'object, .plupload_clearer',
1305
 
1306
			stop: function() {
1307
				var files = [];
1308
 
1309
				$.each($(this).sortable('toArray'), function(i, id) {
1310
					files[files.length] = self.uploader.getFile(id);
1311
				});
1312
 
1313
				files.unshift(files.length);
1314
				files.unshift(0);
1315
 
1316
				// re-populate files array
1317
				Array.prototype.splice.apply(self.uploader.files, files);
1318
			}
1319
		});
1320
	}
1321
});
1322
 
1323
} (window, document, plupload, mOxie, jQuery));