jquery.ui.plupload.js ➔ onLast   F
last analyzed

Complexity

Conditions 89

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 11
c 0
b 0
f 0
rs 0
cc 89

How to fix   Complexity   

Complexity

Complex classes like jquery.ui.plupload.js ➔ onLast often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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
		'<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
			var me = $(e.target), fileContainer;
412
			if (me.hasClass('plupload_action_icon')) {
413
				fileContainer = me.closest('.plupload_file');
414
				if (fileContainer.hasClass('plupload_delete')) {
415
					self.removeFile(fileContainer.attr('id'));
416
					e.preventDefault();
417
				}
418
			}
419
		});
420
421
		uploader = this.uploader = uploaders[id] = new plupload.Uploader($.extend(this.options, options));
422
423
		// retrieve full normalized set of options
424
		this.options = uploader.getOption();
425
426
		if (self.options.views.thumbs) {
427
			uploader.settings.required_features.display_media = true;
428
		}
429
430
		// for backward compatibility
431
		if (self.options.max_file_count) {
432
			plupload.extend(uploader.getOption('filters'), {
433
				max_file_count: self.options.max_file_count
434
			});
435
		}
436
437
		plupload.addFileFilter('max_file_count', function(maxCount, file, cb) {
438
			if (maxCount <= this.files.length - (this.total.uploaded + this.total.failed)) {
439
				self.browse_button.button('disable');
440
				this.disableBrowse();
441
442
				this.trigger('Error', {
443
					code : self.FILE_COUNT_ERROR,
444
					message : _("File count error."),
445
					file : file
446
				});
447
				cb(false);
448
			} else {
449
				cb(true);
450
			}
451
		});
452
453
454
		uploader.bind('Error', function(up, err) {
455
			var message, details = "";
456
457
			message = '<strong>' + err.message + '</strong>';
458
459
			switch (err.code) {
460
				case plupload.FILE_EXTENSION_ERROR:
461
					details = plupload.sprintf(_("File: %s"), err.file.name);
462
					break;
463
464
				case plupload.FILE_SIZE_ERROR:
465
					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)));
466
					break;
467
468
				case plupload.FILE_DUPLICATE_ERROR:
469
					details = plupload.sprintf(_("%s already present in the queue."), err.file.name);
470
					break;
471
472
				case self.FILE_COUNT_ERROR:
473
					details = plupload.sprintf(_("Upload element accepts only %d file(s) at a time. Extra files were stripped."), up.getOption('filters').max_file_count || 0);
474
					break;
475
476
				case plupload.IMAGE_FORMAT_ERROR :
477
					details = _("Image format either wrong or not supported.");
478
					break;
479
480
				case plupload.IMAGE_MEMORY_ERROR :
481
					details = _("Runtime ran out of available memory.");
482
					break;
483
484
				/* // This needs a review
485
				case plupload.IMAGE_DIMENSIONS_ERROR :
486
					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);
487
					break;	*/
488
489
				case plupload.HTTP_ERROR:
490
					details = _("Upload URL might be wrong or doesn't exist.");
491
					break;
492
			}
493
494
			message += " <br /><i>" + details + "</i>";
495
496
			self._trigger('error', null, { up: up, error: err } );
497
498
			// do not show UI if no runtime can be initialized
499
			if (err.code === plupload.INIT_ERROR) {
500
				setTimeout(function() {
501
					self.destroy();
502
				}, 1);
503
			} else {
504
				self.notify('error', message);
505
			}
506
		});
507
508
509
		uploader.bind('PostInit', function(up) {
510
			// all buttons are optional, so they can be disabled and hidden
511
			if (!self.options.buttons.browse) {
512
				self.browse_button.button('disable').hide();
513
				up.disableBrowse(true);
514
			} else {
515
				self.browse_button.button('enable');
516
			}
517
518
			if (!self.options.buttons.start) {
519
				self.start_button.button('disable').hide();
520
			}
521
522
			if (!self.options.buttons.stop) {
523
				self.stop_button.button('disable').hide();
524
			}
525
526
			if (!self.options.unique_names && self.options.rename) {
527
				self._enableRenaming();
528
			}
529
530
			if (self.options.dragdrop && up.features.dragdrop) {
531
				self.filelist.parent().addClass('plupload_dropbox');
532
			}
533
534
			self._enableViewSwitcher();
535
536
			self.start_button.click(function(e) {
537
				if (!$(this).button('option', 'disabled')) {
538
					self.start();
539
				}
540
				e.preventDefault();
541
			});
542
543
			self.stop_button.click(function(e) {
544
				self.stop();
545
				e.preventDefault();
546
			});
547
548
			self._trigger('ready', null, { up: up });
549
		});
550
551
		// uploader internal events must run first
552
		uploader.init();
553
554
		uploader.bind('FileFiltered', function(up, file) {
555
			self._addFiles(file);
556
		});
557
558
		uploader.bind('FilesAdded', function(up, files) {
559
			self._trigger('selected', null, { up: up, files: files } );
560
561
			// re-enable sortable
562
			if (self.options.sortable && $.ui.sortable) {
563
				self._enableSortingList();
564
			}
565
566
			self._trigger('updatelist', null, { filelist: self.filelist });
567
568
			if (self.options.autostart) {
569
				// set a little delay to make sure that QueueChanged triggered by the core has time to complete
570
				setTimeout(function() {
571
					self.start();
572
				}, 10);
573
			}
574
		});
575
576
		uploader.bind('FilesRemoved', function(up, files) {
577
			// destroy sortable if enabled
578
			if ($.ui.sortable && self.options.sortable) {
579
				$('tbody', self.filelist).sortable('destroy');
580
			}
581
582
			$.each(files, function(i, file) {
583
				$('#' + file.id).toggle("highlight", function() {
584
					$(this).remove();
585
				});
586
			});
587
588
			if (up.files.length) {
589
				// re-initialize sortable
590
				if (self.options.sortable && $.ui.sortable) {
591
					self._enableSortingList();
592
				}
593
			}
594
595
			self._trigger('updatelist', null, { filelist: self.filelist });
596
			self._trigger('removed', null, { up: up, files: files } );
597
		});
598
599
		uploader.bind('QueueChanged', function() {
600
			self._handleState();
601
		});
602
603
		uploader.bind('StateChanged', function(up) {
604
			self._handleState();
605
			if (plupload.STARTED === up.state) {
606
				self._trigger('started', null, { up: this.uploader });
607
			} else if (plupload.STOPPED === up.state) {
608
				self._trigger('stopped', null, { up: this.uploader });
609
			}
610
		});
611
612
		uploader.bind('UploadFile', function(up, file) {
613
			self._handleFileStatus(file);
614
		});
615
616
		uploader.bind('FileUploaded', function(up, file, result) {
617
			self._handleFileStatus(file);
618
			self._trigger('uploaded', null, { up: up, file: file, result: result } );
619
		});
620
621
		uploader.bind('UploadProgress', function(up, file) {
622
			self._handleFileStatus(file);
623
			self._updateTotalProgress();
624
			self._trigger('progress', null, { up: up, file: file } );
625
		});
626
627
		uploader.bind('UploadComplete', function(up, files) {
628
			self._addFormFields();
629
			self._trigger('complete', null, { up: up, files: files } );
630
		});
631
	},
632
633
634
	_setOption: function(key, value) {
635
		var self = this;
636
637
		if (key == 'buttons' && typeof(value) == 'object') {
638
			value = $.extend(self.options.buttons, value);
639
640
			if (!value.browse) {
641
				self.browse_button.button('disable').hide();
642
				self.uploader.disableBrowse(true);
643
			} else {
644
				self.browse_button.button('enable').show();
645
				self.uploader.disableBrowse(false);
646
			}
647
648
			if (!value.start) {
649
				self.start_button.button('disable').hide();
650
			} else {
651
				self.start_button.button('enable').show();
652
			}
653
654
			if (!value.stop) {
655
				self.stop_button.button('disable').hide();
656
			} else {
657
				self.start_button.button('enable').show();
658
			}
659
		}
660
661
		self.uploader.setOption(key, value);
662
	},
663
664
665
	/**
666
	Start upload. Triggers `start` event.
667
668
	@method start
669
	*/
670
	start: function() {
671
		this.uploader.start();
672
	},
673
674
675
	/**
676
	Stop upload. Triggers `stop` event.
677
678
	@method stop
679
	*/
680
	stop: function() {
681
		this.uploader.stop();
682
	},
683
684
685
	/**
686
	Enable browse button.
687
688
	@method enable
689
	*/
690
	enable: function() {
691
		this.browse_button.button('enable');
692
		this.uploader.disableBrowse(false);
693
	},
694
695
696
	/**
697
	Disable browse button.
698
699
	@method disable
700
	*/
701
	disable: function() {
702
		this.browse_button.button('disable');
703
		this.uploader.disableBrowse(true);
704
	},
705
706
707
	/**
708
	Retrieve file by its unique id.
709
710
	@method getFile
711
	@param {String} id Unique id of the file
712
	@return {plupload.File}
713
	*/
714
	getFile: function(id) {
715
		var file;
716
717
		if (typeof id === 'number') {
718
			file = this.uploader.files[id];
719
		} else {
720
			file = this.uploader.getFile(id);
721
		}
722
		return file;
723
	},
724
725
	/**
726
	Return array of files currently in the queue.
727
728
	@method getFiles
729
	@return {Array} Array of files in the queue represented by plupload.File objects
730
	*/
731
	getFiles: function() {
732
		return this.uploader.files;
733
	},
734
735
736
	/**
737
	Remove the file from the queue.
738
739
	@method removeFile
740
	@param {plupload.File|String} file File to remove, might be specified directly or by its unique id
741
	*/
742
	removeFile: function(file) {
743
		if (plupload.typeOf(file) === 'string') {
744
			file = this.getFile(file);
745
		}
746
		this.uploader.removeFile(file);
747
	},
748
749
750
	/**
751
	Clear the file queue.
752
753
	@method clearQueue
754
	*/
755
	clearQueue: function() {
756
		this.uploader.splice();
757
	},
758
759
760
	/**
761
	Retrieve internal plupload.Uploader object (usually not required).
762
763
	@method getUploader
764
	@return {plupload.Uploader}
765
	*/
766
	getUploader: function() {
767
		return this.uploader;
768
	},
769
770
771
	/**
772
	Trigger refresh procedure, specifically browse_button re-measure and re-position operations.
773
	Might get handy, when UI Widget is placed within the popup, that is constantly hidden and shown
774
	again - without calling this method after each show operation, dialog trigger might get displaced
775
	and disfunctional.
776
777
	@method refresh
778
	*/
779
	refresh: function() {
780
		this.uploader.refresh();
781
	},
782
783
784
	/**
785
	Display a message in notification area.
786
787
	@method notify
788
	@param {Enum} type Type of the message, either `error` or `info`
789
	@param {String} message The text message to display.
790
	*/
791
	notify: function(type, message) {
792
		var popup = $(
793
			'<div class="plupload_message">' +
794
				'<span class="plupload_message_close ui-icon ui-icon-circle-close" title="'+_('Close')+'"></span>' +
795
				'<p><span class="ui-icon"></span>' + message + '</p>' +
796
			'</div>'
797
		);
798
799
		popup
800
			.addClass('ui-state-' + (type === 'error' ? 'error' : 'highlight'))
801
			.find('p .ui-icon')
802
				.addClass('ui-icon-' + (type === 'error' ? 'alert' : 'info'))
803
				.end()
804
			.find('.plupload_message_close')
805
				.click(function() {
806
					popup.remove();
807
				})
808
				.end();
809
810
		$('.plupload_header', this.container).append(popup);
811
	},
812
813
814
	/**
815
	Destroy the widget, the uploader, free associated resources and bring back original html.
816
817
	@method destroy
818
	*/
819
	destroy: function() {
820
		// destroy uploader instance
821
		this.uploader.destroy();
822
823
		// unbind all button events
824
		$('.plupload_button', this.element).unbind();
825
826
		// destroy buttons
827
		if ($.ui.button) {
828
			$('.plupload_add, .plupload_start, .plupload_stop', this.container)
829
				.button('destroy');
830
		}
831
832
		// destroy progressbar
833
		if ($.ui.progressbar) {
834
			this.progressbar.progressbar('destroy');
835
		}
836
837
		// destroy sortable behavior
838
		if ($.ui.sortable && this.options.sortable) {
839
			$('tbody', this.filelist).sortable('destroy');
840
		}
841
842
		// restore the elements initial state
843
		this.element
844
			.empty()
845
			.html(this.contents_bak);
846
		this.contents_bak = '';
847
848
		$.Widget.prototype.destroy.apply(this);
849
	},
850
851
852
	_handleState: function() {
853
		var up = this.uploader
854
		, filesPending = up.files.length - (up.total.uploaded + up.total.failed)
855
		, maxCount = up.getOption('filters').max_file_count || 0
856
		;
857
858
		if (plupload.STARTED === up.state) {
859
			$([])
860
				.add(this.stop_button)
861
				.add('.plupload_started')
862
					.removeClass('plupload_hidden');
863
864
			this.start_button.button('disable');
865
866
			if (!this.options.multiple_queues) {
867
				this.browse_button.button('disable');
868
				up.disableBrowse();
869
			}
870
871
			$('.plupload_upload_status', this.element).html(plupload.sprintf(_('Uploaded %d/%d files'), up.total.uploaded, up.files.length));
872
			$('.plupload_header_content', this.element).addClass('plupload_header_content_bw');
873
		}
874
		else if (plupload.STOPPED === up.state) {
875
			$([])
876
				.add(this.stop_button)
877
				.add('.plupload_started')
878
					.addClass('plupload_hidden');
879
880
			if (filesPending) {
881
				this.start_button.button('enable');
882
			} else {
883
				this.start_button.button('disable');
884
			}
885
886
			if (this.options.multiple_queues) {
887
				$('.plupload_header_content', this.element).removeClass('plupload_header_content_bw');
888
			}
889
890
			// if max_file_count defined, only that many files can be queued at once
891
			if (this.options.multiple_queues && maxCount && maxCount > filesPending) {
892
				this.browse_button.button('enable');
893
				up.disableBrowse(false);
894
			}
895
896
			this._updateTotalProgress();
897
		}
898
899
		if (up.total.queued === 0) {
900
			$('.ui-button-text', this.browse_button).html(_('Add Files'));
901
		} else {
902
			$('.ui-button-text', this.browse_button).html(plupload.sprintf(_('%d files queued'), up.total.queued));
903
		}
904
905
		// have a helper class on a container expressing whether it has files queued or not
906
		this.container.toggleClass('plupload_files_queued', up.files.length);
907
908
		up.refresh();
909
	},
910
911
912
	_handleFileStatus: function(file) {
913
		var $file = $('#' + file.id), actionClass, iconClass;
914
915
		// since this method might be called asynchronously, file row might not yet be rendered
916
		if (!$file.length) {
917
			return;
918
		}
919
920
		switch (file.status) {
921
			case plupload.DONE:
922
				actionClass = 'plupload_done';
923
				iconClass = 'plupload_action_icon ui-icon ui-icon-circle-check';
924
				break;
925
926
			case plupload.FAILED:
927
				actionClass = 'ui-state-error plupload_failed';
928
				iconClass = 'plupload_action_icon ui-icon ui-icon-alert';
929
				break;
930
931
			case plupload.QUEUED:
932
				actionClass = 'plupload_delete';
933
				iconClass = 'plupload_action_icon ui-icon ui-icon-circle-minus';
934
				break;
935
936
			case plupload.UPLOADING:
937
				actionClass = 'ui-state-highlight plupload_uploading';
938
				iconClass = 'plupload_action_icon ui-icon ui-icon-circle-arrow-w';
939
940
				// scroll uploading file into the view if its bottom boundary is out of it
941
				var scroller = $('.plupload_scroll', this.container)
942
				, scrollTop = scroller.scrollTop()
943
				, scrollerHeight = scroller.height()
944
				, rowOffset = $file.position().top + $file.height()
945
				;
946
947
				if (scrollerHeight < rowOffset) {
948
					scroller.scrollTop(scrollTop + rowOffset - scrollerHeight);
949
				}
950
951
				// Set file specific progress
952
				$file
953
					.find('.plupload_file_percent')
954
						.html(file.percent + '%')
955
						.end()
956
					.find('.plupload_file_progress')
957
						.css('width', file.percent + '%')
958
						.end()
959
					.find('.plupload_file_size')
960
						.html(plupload.formatSize(file.size));
961
				break;
962
		}
963
		actionClass += ' ui-state-default plupload_file';
0 ignored issues
show
Bug introduced by
The variable actionClass seems to not be initialized for all possible execution paths.
Loading history...
964
965
		$file
966
			.attr('class', actionClass)
967
			.find('.plupload_action_icon')
968
				.attr('class', iconClass);
0 ignored issues
show
Bug introduced by
The variable iconClass seems to not be initialized for all possible execution paths. Are you sure attr handles undefined variables?
Loading history...
969
	},
970
971
972
	_updateTotalProgress: function() {
973
		var up = this.uploader;
974
975
		// Scroll to end of file list
976
		this.filelist[0].scrollTop = this.filelist[0].scrollHeight;
977
978
		this.progressbar.progressbar('value', up.total.percent);
979
980
		this.element
981
			.find('.plupload_total_status')
982
				.html(up.total.percent + '%')
983
				.end()
984
			.find('.plupload_total_file_size')
985
				.html(plupload.formatSize(up.total.size))
986
				.end()
987
			.find('.plupload_upload_status')
988
				.html(plupload.sprintf(_('Uploaded %d/%d files'), up.total.uploaded, up.files.length));
989
	},
990
991
992
	_displayThumbs: function() {
993
		var self = this
994
		, tw, th // thumb width/height
995
		, cols
996
		, num = 0 // number of simultaneously visible thumbs
997
		, thumbs = [] // array of thumbs to preload at any given moment
998
		, loading = false
999
		;
1000
1001
		if (!this.options.views.thumbs) {
1002
			return;
1003
		}
1004
1005
1006
		function onLast(el, eventName, cb) {
1007
			var timer;
1008
1009
			el.on(eventName, function() {
1010
				clearTimeout(timer);
1011
				timer = setTimeout(function() {
1012
					clearTimeout(timer);
1013
					cb();
1014
				}, 300);
1015
			});
1016
		}
1017
1018
1019
		// calculate number of simultaneously visible thumbs
1020
		function measure() {
1021
			if (!tw || !th) {
1022
				var wrapper = $('.plupload_file:eq(0)', self.filelist);
1023
				tw = wrapper.outerWidth(true);
1024
				th = wrapper.outerHeight(true);
1025
			}
1026
1027
			var aw = self.content.width(), ah = self.content.height();
1028
			cols = Math.floor(aw / tw);
1029
			num =  cols * (Math.ceil(ah / th) + 1);
1030
		}
1031
1032
1033
		function pickThumbsToLoad() {
1034
			// calculate index of virst visible thumb
1035
			var startIdx = Math.floor(self.content.scrollTop() / th) * cols;
1036
			// get potentially visible thumbs that are not yet visible
1037
			thumbs = $('.plupload_file .plupload_file_thumb', self.filelist)
1038
				.slice(startIdx, startIdx + num)
1039
				.filter('.plupload_thumb_toload')
1040
				.get();
1041
		}
1042
1043
1044
		function init() {
1045
			function mpl() { // measure, pick, load
1046
				if (self.view_mode !== 'thumbs') {
1047
					return;
1048
				}
1049
				measure();
1050
				pickThumbsToLoad();
1051
				lazyLoad();
1052
			}
1053
1054
			if ($.fn.resizable) {
1055
				onLast(self.container, 'resize', mpl);
1056
			}
1057
1058
			onLast(self.window, 'resize', mpl);
1059
			onLast(self.content, 'scroll',  mpl);
1060
1061
			self.element.on('viewchanged selected', mpl);
1062
1063
			mpl();
1064
		}
1065
1066
1067
		function preloadThumb(file, cb) {
1068
			var img = new o.image.Image();
1069
			var resolveUrl = o.core.utils.Url.resolveUrl;
1070
1071
			img.onload = function() {
1072
				var thumb = $('#' + file.id + ' .plupload_file_thumb', self.filelist);
1073
				this.embed(thumb[0], {
1074
					width: self.options.thumb_width,
1075
					height: self.options.thumb_height,
1076
					crop: true,
1077
					fit: true,
1078
					preserveHeaders: false,
1079
					swf_url: resolveUrl(self.options.flash_swf_url),
1080
					xap_url: resolveUrl(self.options.silverlight_xap_url)
1081
				});
1082
			};
1083
1084
			img.bind("embedded error", function(e) {
1085
				$('#' + file.id, self.filelist)
1086
					.find('.plupload_file_thumb')
1087
						.removeClass('plupload_thumb_loading')
1088
						.addClass('plupload_thumb_' + e.type)
1089
					;
1090
				this.destroy();
1091
				setTimeout(cb, 1); // detach, otherwise ui might hang (in SilverLight for example)
1092
			});
1093
1094
			$('#' + file.id, self.filelist)
1095
				.find('.plupload_file_thumb')
1096
					.removeClass('plupload_thumb_toload')
1097
					.addClass('plupload_thumb_loading')
1098
				;
1099
			img.load(file.getSource());
1100
		}
1101
1102
1103
		function lazyLoad() {
1104
			if (self.view_mode !== 'thumbs' || loading) {
1105
				return;
1106
			}
1107
1108
			pickThumbsToLoad();
1109
			if (!thumbs.length) {
1110
				return;
1111
			}
1112
1113
			loading = true;
1114
1115
			preloadThumb(self.getFile($(thumbs.shift()).closest('.plupload_file').attr('id')), function() {
1116
				loading = false;
1117
				lazyLoad();
1118
			});
1119
		}
1120
1121
		// this has to run only once to measure structures and bind listeners
1122
		this.element.on('selected', function onselected() {
1123
			self.element.off('selected', onselected);
1124
			init();
1125
		});
1126
	},
1127
1128
1129
	_addFiles: function(files) {
1130
		var self = this, file_html, html = '';
1131
1132
		file_html = '<li class="plupload_file ui-state-default plupload_delete" id="{id}" style="width:{thumb_width}px;">' +
1133
			'<div class="plupload_file_thumb plupload_thumb_toload" style="width: {thumb_width}px; height: {thumb_height}px;">' +
1134
				'<div class="plupload_file_dummy ui-widget-content" style="line-height: {thumb_height}px;"><span class="ui-state-disabled">{ext} </span></div>' +
1135
			'</div>' +
1136
			'<div class="plupload_file_status">' +
1137
				'<div class="plupload_file_progress ui-widget-header" style="width: 0%"> </div>' +
1138
				'<span class="plupload_file_percent">{percent} </span>' +
1139
			'</div>' +
1140
			'<div class="plupload_file_name" title="{name}">' +
1141
				'<span class="plupload_file_name_wrapper">{name} </span>' +
1142
			'</div>' +
1143
			'<div class="plupload_file_action">' +
1144
				'<div class="plupload_action_icon ui-icon ui-icon-circle-minus"> </div>' +
1145
			'</div>' +
1146
			'<div class="plupload_file_size">{size} </div>' +
1147
			'<div class="plupload_file_fields"> </div>' +
1148
		'</li>';
1149
1150
		if (plupload.typeOf(files) !== 'array') {
1151
			files = [files];
1152
		}
1153
1154
		$.each(files, function(i, file) {
1155
			var ext = o.core.utils.Mime.getFileExtension(file.name) || 'none';
1156
1157
			html += file_html.replace(/\{(\w+)\}/g, function($0, $1) {
1158
				switch ($1) {
1159
					case 'thumb_width':
1160
					case 'thumb_height':
1161
						return self.options[$1];
1162
1163
					case 'size':
1164
						return plupload.formatSize(file.size);
1165
1166
					case 'ext':
1167
						return ext;
1168
1169
					default:
1170
						return file[$1] || '';
1171
				}
1172
			});
1173
		});
1174
1175
		self.filelist.append(html);
1176
	},
1177
1178
1179
	_addFormFields: function() {
1180
		var self = this;
1181
1182
		// re-add from fresh
1183
		$('.plupload_file_fields', this.filelist).html('');
1184
1185
		plupload.each(this.uploader.files, function(file, count) {
1186
			var fields = ''
1187
			, id = self.id + '_' + count
1188
			;
1189
1190
			if (file.target_name) {
1191
				fields += '<input type="hidden" name="' + id + '_tmpname" value="'+plupload.xmlEncode(file.target_name)+'" />';
1192
			}
1193
			fields += '<input type="hidden" name="' + id + '_name" value="'+plupload.xmlEncode(file.name)+'" />';
1194
			fields += '<input type="hidden" name="' + id + '_status" value="' + (file.status === plupload.DONE ? 'done' : 'failed') + '" />';
1195
1196
			$('#' + file.id).find('.plupload_file_fields').html(fields);
1197
		});
1198
1199
		this.counter.val(this.uploader.files.length);
1200
	},
1201
1202
1203
	_viewChanged: function(view) {
1204
		// update or write a new cookie
1205
		if (this.options.views.remember && $.cookie) {
1206
			$.cookie('plupload_ui_view', view, { expires: 7, path: '/' });
1207
		}
1208
1209
		// ugly fix for IE6 - make content area stretchable
1210
		if (plupload.ua.browser === 'IE' && plupload.ua.version < 7) {
1211
			this.content.attr('style', 'height:expression(document.getElementById("' + this.id + '_container' + '").clientHeight - ' + (view === 'list' ? 132 : 102) + ')');
1212
		}
1213
1214
		this.container.removeClass('plupload_view_list plupload_view_thumbs').addClass('plupload_view_' + view);
1215
		this.view_mode = view;
1216
		this._trigger('viewchanged', null, { view: view });
1217
	},
1218
1219
1220
	_enableViewSwitcher: function() {
1221
		var self = this
1222
		, view
1223
		, switcher = $('.plupload_view_switch', this.container)
1224
		, buttons
1225
		, button
1226
		;
1227
1228
		plupload.each(['list', 'thumbs'], function(view) {
1229
			if (!self.options.views[view]) {
1230
				switcher.find('[for="' + self.id + '_view_' + view + '"], #'+ self.id +'_view_' + view).remove();
1231
			}
1232
		});
1233
1234
		// check if any visible left
1235
		buttons = switcher.find('.plupload_button');
1236
1237
		if (buttons.length === 1) {
1238
			switcher.hide();
1239
			view = buttons.eq(0).data('view');
1240
			this._viewChanged(view);
1241
		} else if ($.ui.button && buttons.length > 1) {
1242
			if (this.options.views.remember && $.cookie) {
1243
				view = $.cookie('plupload_ui_view');
1244
			}
1245
1246
			// if wierd case, bail out to default
1247
			if (!~plupload.inArray(view, ['list', 'thumbs'])) {
0 ignored issues
show
Bug introduced by
The variable view does not seem to be initialized in case this.options.views.remember && $.cookie on line 1242 is false. Are you sure the function inArray handles undefined variables?
Loading history...
1248
				view = this.options.views.active;
1249
			}
1250
1251
			switcher
1252
				.show()
1253
				.buttonset()
1254
				.find('.ui-button')
1255
					.click(function(e) {
1256
						view = $(this).data('view');
1257
						self._viewChanged(view);
1258
						e.preventDefault(); // avoid auto scrolling to widget in IE and FF (see #850)
1259
					});
1260
1261
			// if view not active - happens when switcher wasn't clicked manually
1262
			button = switcher.find('[for="' + self.id + '_view_'+view+'"]');
1263
			if (button.length) {
1264
				button.trigger('click');
1265
			}
1266
		} else {
1267
			switcher.show();
1268
			this._viewChanged(this.options.views.active);
1269
		}
1270
1271
		// initialize thumb viewer if requested
1272
		if (this.options.views.thumbs) {
1273
			this._displayThumbs();
1274
		}
1275
	},
1276
1277
1278
	_enableRenaming: function() {
1279
		var self = this;
1280
1281
		this.filelist.dblclick(function(e) {
1282
			var nameInput, fileContainer, file, parts, name, ext = "";
1283
			var nameSpan = $(e.target);
1284
1285
			if (!nameSpan.hasClass('plupload_file_name_wrapper')) {
1286
				return;
1287
			}
1288
1289
			fileContainer = nameSpan.closest('.plupload_file');
1290
			if (!fileContainer.hasClass('plupload_delete')) {
1291
				return;
1292
			}
1293
1294
			// Get file name and split out name and extension
1295
			file = self.uploader.getFile(fileContainer[0].id);
1296
			name = file.name;
1297
			parts = /^(.+)(\.[^.]+)$/.exec(name);
1298
			if (parts) {
1299
				name = parts[1];
1300
				ext = parts[2];
1301
			}
1302
1303
			// Display input element
1304
			nameInput = $('<input class="plupload_file_rename" type="text" />').width(nameSpan.width()).insertAfter(nameSpan.hide());
1305
			nameInput.val(name).blur(function() {
1306
				nameSpan.show().parent().scrollLeft(0).end().next().remove();
1307
			}).keydown(function(e) {
1308
				var nameInput = $(this);
1309
1310
				if ($.inArray(e.keyCode, [13, 27]) !== -1) {
1311
					e.preventDefault();
1312
1313
					// Rename file and glue extension back on
1314
					if (e.keyCode === 13) {
1315
						file.name = nameInput.val() + ext;
1316
						nameSpan.html(file.name);
1317
					}
1318
					nameInput.blur();
1319
				}
1320
			})[0].focus();
1321
		});
1322
	},
1323
1324
1325
	_enableSortingList: function() {
1326
		var self = this;
1327
1328
		if ($('.plupload_file', this.filelist).length < 2) {
1329
			return;
1330
		}
1331
1332
		// destroy sortable if enabled
1333
		$('tbody', this.filelist).sortable('destroy');
1334
1335
		// enable
1336
		this.filelist.sortable({
1337
			items: '.plupload_delete',
1338
1339
			cancel: 'object, .plupload_clearer',
1340
1341
			stop: function() {
1342
				var files = [];
1343
1344
				$.each($(this).sortable('toArray'), function(i, id) {
1345
					files[files.length] = self.uploader.getFile(id);
1346
				});
1347
1348
				files.unshift(files.length);
1349
				files.unshift(0);
1350
1351
				// re-populate files array
1352
				Array.prototype.splice.apply(self.uploader.files, files);
1353
			}
1354
		});
1355
	}
1356
});
1357
1358
} (window, document, plupload, moxie, jQuery));
0 ignored issues
show
Bug introduced by
The variable moxie seems to be never declared. If this is a global, consider adding a /** global: moxie */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
Bug introduced by
The variable plupload seems to be never declared. If this is a global, consider adding a /** global: plupload */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
1359