PageSourceSearch

https://semarangkota.go.id/assets/plugins/jquery-fancyfileuploader…/jquery.fancy-fileupload.js

js semarangkota.go.id collected 2026-10-01 11:55:29 UTC 28,550 bytes, 785 lines download raw bytes

1// jQuery plugin to display a custom jQuery File Uploader interface.
2// (C) 2020 CubicleSoft.  All Rights Reserved.
3
4(function($) {
5	var EscapeHTML = function(text) {
6		var map = {
7			'&': '&',
8			'<': '&lt;',
9			'>': '&gt;',
10			'"': '&quot;',
11			"'": '&#039;'
12		};
13
14		return text.replace(/[&<>"']/g, function(m) { return map[m]; });
15	}
16
17	var FormatStr = function(format) {
18		var args = Array.prototype.slice.call(arguments, 1);
19
20		return format.replace(/{(\d+)}/g, function(match, number) {
21			return (typeof args[number] != 'undefined' ? args[number] : match);
22		});
23	};
24
25	var GetDisplayFilesize = function(numbytes, adjustprecision, units) {
26		if (numbytes == 0)  return '0 Bytes';
27		if (numbytes == 1)  return '1 Byte';
28
29		numbytes = Math.abs(numbytes);
30		var magnitude, abbreviations;
31		if (units && units.toLowerCase() === 'iec_formal')
32		{
33			magnitude = Math.pow(2, 10);
34			abbreviations = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
35		}
36		else if (units && units.toLowerCase() === 'si')
37		{
38			magnitude = Math.pow(10, 3);
39			abbreviations = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
40		}
41		else
42		{
43			magnitude = Math.pow(2, 10);
44			abbreviations = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
45		}
46
47		var pos = Math.floor(Math.log(numbytes) / Math.log(magnitude));
48		var result = (numbytes / Math.pow(magnitude, pos));
49
50		return (pos == 0 || (adjustprecision && result >= 99.995) ? result.toFixed(0) : result.toFixed(2)) + ' ' + abbreviations[pos];
51	};
52
53	var DisplayPreviewDialog = function(preview, endelem, inforow, data, settings) {
54		var previewbackground = $('<div>').addClass('ff_fileupload_dialog_background');
55		var previewclone = preview.clone(true, true).click(function(e) {
56			e.stopPropagation();
57		});
58		var previewdialog = $('<div>').addClass('ff_fileupload_dialog_main').append(previewclone);
59
60		var HidePreviewDialog = function() {
61			$(document).off('keyup.fancy_fileupload');
62
63			previewbackground.remove();
64			endelem.focus();
65
66			if (settings.hidepreview)  settings.hidepreview.call(inforow, data, preview, previewclone);
67		};
68
69		$(document).on('keyup.fancy_fileupload', function(e) {
70			if (e.keyCode == 27) {
71				HidePreviewDialog();
72			}
73		});
74
75		previewbackground.append(previewdialog).click(function() {
76			HidePreviewDialog();
77		});
78
79		$('body').append(previewbackground);
80		previewclone.focus();
81
82		if (settings.showpreview)  settings.showpreview.call(inforow, data, preview, previewclone);
83	};
84
85	var InitShowAriaLabelInfo = function(inforow) {
86		inforow.find('button').hover(function() {
87			var val = $(this).attr('aria-label');
88
89			if (val)
90			{
91				inforow.find('.ff_fileupload_buttoninfo').text(val).removeClass('ff_fileupload_hidden');
92				inforow.find('.ff_fileupload_fileinfo').addClass('ff_fileupload_hidden');
93			}
94		}, function() {
95			inforow.find('.ff_fileupload_fileinfo').removeClass('ff_fileupload_hidden');
96			inforow.find('.ff_fileupload_buttoninfo').addClass('ff_fileupload_hidden');
97		});
98	};
99
100	$.fn.FancyFileUpload = function(options) {
101		this.each(function() {
102			var $this = $(this);
103
104			// Remove the previous file uploader.
105			if ($this.data('fancy-fileupload') && typeof($this.data('fancy-fileupload')) === 'object')
106			{
107				$this.removeClass('ff_fileupload_hidden');
108
109				var data = $this.data('fancy-fileupload');
110
111				data.form.find('input[type=file]').fileupload('destroy');
112				data.form.remove();
113				data.fileuploadwrap.remove();
114
115				$this.removeData('fancy-fileupload');
116			}
117		});
118
119		if (!$('.ff_fileupload_hidden').length)
120		{
121			$(document).off('drop.fancy_fileupload dragover.fancy_fileupload');
122			$(window).off('beforeunload.fancy_fileupload');
123		}
124
125		if (typeof(options) === 'string' && options === 'destroy')  return this;
126
127		var settings = $.extend({}, $.fn.FancyFileUpload.defaults, options);
128
129		// Let custom callbacks make last second changes to the finalized settings.
130		if (settings.preinit)  settings.preinit(settings);
131
132		// Prevent default file drag-and-drop operations.
133		$(document).off('drop.fancy_fileupload dragover.fancy_fileupload');
134		$(document).on('drop.fancy_fileupload dragover.fancy_fileupload', function (e) {
135			e.preventDefault();
136		});
137
138		// Some useful functions.
139		var Translate = function(str) {
140			return (settings.langmap[str] ? settings.langmap[str] : str);
141		};
142
143		// Prevent the user from leaving the page if there is an active upload.
144		// Most browsers won't show the custom message.  So make the relevant UI elements bounce using CSS.
145		$(window).on('beforeunload.fancy_fileupload', function(e) {
146			var active = $('.ff_fileupload_uploading, .ff_fileupload_starting');
147			var queued = $('.ff_fileupload_queued');
148
149			if (active.length || queued.length)
150			{
151				active.removeClass('ff_fileupload_bounce');
152				setTimeout(function() { active.addClass('ff_fileupload_bounce') }, 250);
153
154				queued.removeClass('ff_fileupload_bounce');
155				setTimeout(function() { queued.addClass('ff_fileupload_bounce') }, 250);
156
157				if (active.length)  return Translate('There is a file upload still in progress.  Leaving the page will cancel the upload.\n\nAre you sure you want to leave this page?');
158				if (queued.length)  return Translate('There is a file that was added to the queue but the upload has not been started.  Leaving the page will clear the queue and not upload the file.\n\nAre you sure you want to leave this page?');
159			}
160		});
161
162		// Create some extra DOM nodes for preview checking.
163		var audioelem = document.createElement('audio');
164		var videoelem = document.createElement('video');
165
166		var AddFile = function(uploads, e, data) {
167			var inforow = $('<tr><td class="ff_fileupload_preview"><button class="ff_fileupload_preview_image" type="button"><span class="ff_fileupload_preview_text"></span></button><div class="ff_fileupload_actions_mobile"></div></td><td class="ff_fileupload_summary"><div class="ff_fileupload_filename"></div><div class="ff_fileupload_fileinfo"></div><div class="ff_fileupload_buttoninfo ff_fileupload_hidden"></div><div class="ff_fileupload_errors ff_fileupload_hidden"></div><div class="ff_fileupload_progress_background ff_fileupload_hidden"><div class="ff_fileupload_progress_bar"></div></div></td><td class="ff_fileupload_actions"></td></tr>');
168			var pos = data.files[0].name.lastIndexOf('.');
169			var filename = (pos > -1 ? data.files[0].name.substring(0, pos) : data.files[0].name);
170			var fileext = (pos > -1 ? data.files[0].name.substring(pos + 1).toLowerCase() : '');
171			var alphanum = 'abcdefghijklmnopqrstuvwxyz0123456789';
172			pos = (fileext == '' ? -1 : alphanum.indexOf(fileext.charAt(0)));
173			var fileextclass = alphanum.charAt((pos > -1 ? pos : Math.floor(Math.random() * alphanum.length)));
174
175			// Initialize necessary callback options.
176			data.ff_info = {};
177			data.ff_info.errors = [];
178			data.ff_info.retries = 0;
179			data.ff_info.retrydelay = settings.retrydelay;
180			data.ff_info.removewidget = false;
181			data.ff_info.inforow = inforow;
182			data.ff_info.displayfilesize = GetDisplayFilesize(data.files[0].size, settings.adjustprecision, settings.displayunits);
183			data.context = inforow;
184
185			// A couple of functions for handling actions.
186			var StartUpload = function(e) {
187				e.preventDefault();
188
189				// Set filename.
190				if (settings.edit && !data.ff_info.errors.length)
191				{
192					var fileinput = inforow.find('.ff_fileupload_filename input');
193					if (fileinput.length)
194					{
195						var newfilename = fileinput.val();
196						if (fileext != '')  newfilename += '.' + fileext;
197
198						inforow.find('.ff_fileupload_filename').text(newfilename);
199						data.files[0].uploadName = newfilename;
200					}
201				}
202
203				// Remove start upload buttons.
204				inforow.find('button.ff_fileupload_start_upload').remove();
205
206				// Reset hover status.
207				inforow.find('.ff_fileupload_fileinfo').removeClass('ff_fileupload_hidden');
208				inforow.find('.ff_fileupload_buttoninfo').addClass('ff_fileupload_hidden');
209
210				// Set the status.
211				inforow.find('.ff_fileupload_fileinfo').text(data.ff_info.displayfilesize + ' | ' + Translate('Starting upload...'));
212
213				// Display progress bar.
214				inforow.find('.ff_fileupload_progress_background').removeClass('ff_fileupload_hidden');
215
216				// Alter remove buttons.
217				inforow.find('button.ff_fileupload_remove_file').attr('aria-label', Translate('Cancel upload and remove from list'));
218
219				// Begin the actual upload.
220				inforow.removeClass('ff_fileupload_queued');
221				inforow.addClass('ff_fileupload_starting');
222
223				var SubmitUpload = function() {
224					inforow.removeClass('ff_fileupload_starting');
225					inforow.addClass('ff_fileupload_uploading');
226					data.submit();
227				};
228
229				if (settings.startupload)  settings.startupload.call(inforow, SubmitUpload, e, data);
230				else  SubmitUpload();
231			};
232
233			var RemoveFile = function(e) {
234				e.preventDefault();
235
236				if (inforow.hasClass('ff_fileupload_uploading'))
237				{
238					if (!confirm(Translate('This file is currently being uploaded.\n\nStop the upload and remove the file from the list?')))  return;
239
240					data.ff_info.removewidget = true;
241					data.abort();
242				}
243				else
244				{
245					if (inforow.hasClass('ff_fileupload_starting'))
246					{
247						if (!confirm(Translate('This file is waiting to start.\n\nCancel the operation and remove the file from the list?')))  return;
248
249						if (settings.uploadcancelled)  settings.uploadcancelled.call(data.ff_info.inforow, e, data);
250					}
251
252					inforow.remove();
253
254					delete data.ff_info;
255				}
256			};
257
258			data.ff_info.RemoveFile = function() {
259				if (inforow.hasClass('ff_fileupload_uploading'))
260				{
261					data.ff_info.removewidget = true;
262					data.abort();
263				}
264				else
265				{
266					if (inforow.hasClass('ff_fileupload_starting'))
267					{
268						if (settings.uploadcancelled)  settings.uploadcancelled.call(data.ff_info.inforow, e, data);
269					}
270
271					inforow.remove();
272
273					delete data.ff_info;
274				}
275			};
276
277			// Thumbnail preview.
278			var haspreview = false;
279			var preview;
280			var hasimage = false;
281			if (URL && URL.createObjectURL)
282			{
283				var url = URL.createObjectURL(data.files[0]);
284				if (url)
285				{
286					if (data.files[0].type === 'image/gif' || data.files[0].type === 'image/jpeg' || data.files[0].type === 'image/png')
287					{
288						inforow.find('.ff_fileupload_preview_image').css('background-image', 'url("' + url + '")');
289
290						haspreview = true;
291						preview = $('<img>').attr('src', url);
292						hasimage = true;
293					}
294					else if (data.files[0].type.lastIndexOf('audio/', 0) > -1 && audioelem.canPlayType && audioelem.canPlayType(data.files[0].type))
295					{
296						haspreview = true;
297						preview = $('<audio>').attr('src', url).prop('controls', true);
298					}
299					else if (data.files[0].type.lastIndexOf('video/', 0) > -1 && videoelem.canPlayType && videoelem.canPlayType(data.files[0].type))
300					{
301						haspreview = true;
302						preview = $('<video>').attr('src', url).prop('controls', true);
303					}
304				}
305			}
306
307			if (haspreview)
308			{
309				inforow.find('.ff_fileupload_preview_image').addClass('ff_fileupload_preview_image_has_preview').attr('aria-label', Translate('Preview')).click(function(e) {
310					e.preventDefault();
311
312					this.blur();
313					DisplayPreviewDialog(preview, this, inforow, data, settings);
314				});
315			}
316			else
317			{
318				inforow.find('.ff_fileupload_preview_image').prop('disabled', true).attr('aria-label', Translate('No preview available')).click(function(e) {
319					e.preventDefault();
320				});
321			}
322
323			if (!hasimage)  inforow.find('.ff_fileupload_preview_image').addClass('ff_fileupload_preview_text_with_color').addClass('ff_fileupload_preview_text_' + fileextclass).text(fileext);
324
325			// Validate inputs.
326			if (settings.accept)
327			{
328				var found = false;
329				for (var x = 0; x < settings.accept.length && !found; x++)
330				{
331					if (settings.accept[x] === fileext || settings.accept[x] === data.files[0].type)  found = true;
332				}
333
334				if (!found)  data.ff_info.errors.push(Translate('Invalid file extension.'));
335			}
336
337			if (settings.maxfilesize > -1 && data.files[0].size > settings.maxfilesize)  data.ff_info.errors.push(FormatStr(Translate('File is too large.  Maximum file size is {0}.'), GetDisplayFilesize(settings.maxfilesize, settings.adjustprecision, settings.displayunits)));
338
339			// Filename text field/display.
340			if (settings.edit && !data.ff_info.errors.length)
341			{
342				inforow.find('.ff_fileupload_filename').append($('<input>').attr('type', 'text').val(filename).keydown(function(e) {
343					// Start uploading if someone presses enter.
344					if (e.keyCode == 13)  StartUpload(e);
345				}));
346			}
347			else
348			{
349				inforow.find('.ff_fileupload_filename').text(data.files[0].name);
350			}
351
352			// File/Upload information.
353			inforow.find('.ff_fileupload_fileinfo').text(data.ff_info.displayfilesize + (hasimage && settings.edit && !data.ff_info.errors.length ? ' | .' + fileext : ''));
354
355			// Errors.
356			if (data.ff_info.errors.length)  inforow.find('.ff_fileupload_errors').html(data.ff_info.errors.join('<br>')).removeClass('ff_fileupload_hidden');
357
358			// Action buttons.
359			if (!data.ff_info.errors.length)
360			{
361				inforow.find('.ff_fileupload_actions').append($('<button>').addClass('ff_fileupload_start_upload').attr('type', 'button').attr('aria-label', Translate('Start uploading')).click(StartUpload));
362				inforow.find('.ff_fileupload_actions_mobile').append($('<button>').addClass('ff_fileupload_start_upload').attr('type', 'button').attr('aria-label', Translate('Start uploading')).click(StartUpload));
363
364				inforow.addClass('ff_fileupload_queued');
365			}
366
367			inforow.find('.ff_fileupload_actions').append($('<button>').addClass('ff_fileupload_remove_file').attr('type', 'button').attr('aria-label', Translate('Remove from list')).click(RemoveFile));
368			inforow.find('.ff_fileupload_actions_mobile').append($('<button>').addClass('ff_fileupload_remove_file').attr('type', 'button').attr('aria-label', Translate('Remove from list')).click(RemoveFile));
369
370			// Handle button hover.
371			InitShowAriaLabelInfo(inforow);
372
373			// Improve progress bar performance during upload.
374			data.ff_info.fileinfo = inforow.find('.ff_fileupload_fileinfo');
375			data.ff_info.progressbar = inforow.find('.ff_fileupload_progress_bar');
376
377			uploads.append(inforow);
378
379			if (settings.added)  settings.added.call(inforow, e, data);
380		};
381
382		var UploadProgress = function(e, data) {
383			var progress = (data.total < 1 ? 0 : data.loaded / data.total * 100);
384
385			data.ff_info.fileinfo.text(FormatStr(Translate('{0} of {1} | {2}%'), GetDisplayFilesize(data.loaded, settings.adjustprecision, settings.displayunits), data.ff_info.displayfilesize, progress.toFixed(0)));
386			data.ff_info.progressbar.css('width', progress + '%');
387
388			if (settings.continueupload && settings.continueupload.call(data.ff_info.inforow, e, data) === false)  data.abort();
389		};
390
391		var UploadFailed = function(e, data) {
392			// For handling chunked upload termination.
393			if (data.ff_info.lastresult && !data.ff_info.lastresult.success)
394			{
395				data.result = data.ff_info.lastresult;
396				data.errorThrown = 'failed_with_msg';
397			}
398
399			if (data.errorThrown !== 'abort' && data.errorThrown !== 'failed_with_msg' && data.uploadedBytes < data.files[0].size && data.ff_info.retries < settings.retries)
400			{
401				data.ff_info.fileinfo.text(FormatStr(Translate('{0} | Network error, retrying in a moment... ({1})'), data.ff_info.displayfilesize, data.errorThrown));
402
403				data.ff_info.inforow.removeClass('ff_fileupload_uploading');
404				data.ff_info.inforow.addClass('ff_fileupload_starting');
405
406				setTimeout(function() {
407					data.ff_info.inforow.removeClass('ff_fileupload_starting');
408					data.ff_info.inforow.addClass('ff_fileupload_uploading');
409					data.data = null;
410					data.submit();
411				}, data.ff_info.retrydelay);
412
413				data.ff_info.retries++;
414				data.ff_info.retrydelay *= 2;
415
416				return;
417			}
418
419			data.ff_info.inforow.removeClass('ff_fileupload_uploading');
420
421			if (settings.uploadcancelled)  settings.uploadcancelled.call(data.ff_info.inforow, e, data);
422
423			if (data.ff_info.removewidget)
424			{
425				data.ff_info.inforow.remove();
426
427				delete data.ff_info;
428			}
429			else
430			{
431				// Set the error info.
432				if (data.errorThrown === 'abort')  data.ff_info.errors.push(Translate('The upload was cancelled.'));
433				else if (data.errorThrown === 'failed_with_msg')  data.ff_info.errors.push(FormatStr(Translate('The upload failed.  {0} ({1})'), EscapeHTML(data.result.error), EscapeHTML(data.result.errorcode)));
434				else  data.ff_info.errors.push(Translate('The upload failed.'));
435				data.ff_info.inforow.find('.ff_fileupload_errors').html(data.ff_info.errors.join('<br>')).removeClass('ff_fileupload_hidden');
436
437				// Hide the progress bar.
438				data.ff_info.inforow.find('.ff_fileupload_progress_background').addClass('ff_fileupload_hidden');
439
440				// Alter remove buttons.
441				data.ff_info.inforow.find('button.ff_fileupload_remove_file').attr('aria-label', Translate('Remove from list'));
442			}
443		};
444
445		var UploadDone = function(e, data) {
446			if (!data.result.success)
447			{
448				if (typeof(data.result.error) !== 'string')  data.result.error = Translate('The server indicated that the upload was not successful.  No additional information available.');
449				if (typeof(data.result.errorcode) !== 'string')  data.result.errorcode = 'server_response';
450
451				data.errorThrown = 'failed_with_msg';
452				data.ff_info.removewidget = false;
453
454				UploadFailed(e, data);
455
456				return;
457			}
458
459			data.ff_info.inforow.removeClass('ff_fileupload_uploading');
460
461			if (settings.uploadcompleted)  settings.uploadcompleted.call(data.ff_info.inforow, e, data);
462
463			if (data.ff_info.removewidget)
464			{
465				data.ff_info.inforow.remove();
466
467				delete data.ff_info;
468			}
469			else
470			{
471				// Set the status.
472				data.ff_info.inforow.find('.ff_fileupload_fileinfo').text(data.ff_info.displayfilesize + ' | ' + Translate('Upload completed'));
473
474				// Hide the progress bar.
475				data.ff_info.inforow.find('.ff_fileupload_progress_background').addClass('ff_fileupload_hidden');
476
477				// Alter remove buttons.
478				data.ff_info.inforow.find('button.ff_fileupload_remove_file').attr('aria-label', Translate('Remove from list'));
479			}
480		};
481
482		var UploadChunkSend = function(e, data) {
483			if (data.ff_info)
484			{
485				if (settings.continueupload && settings.continueupload.call(data.ff_info.inforow, e, data) === false)
486				{
487					if (!data.ff_info.lastresult || data.ff_info.lastresult.success)
488					{
489						data.ff_info.lastresult = {
490							'success' : false
491						};
492					}
493				}
494
495				if (data.ff_info.lastresult && !data.ff_info.lastresult.success)
496				{
497					data.result = data.ff_info.lastresult;
498
499					if (typeof(data.ff_info.lastresult.error) !== 'string')  data.ff_info.lastresult.error = Translate('The server indicated that the upload was not successful.  No additional information available.');
500					if (typeof(data.ff_info.lastresult.errorcode) !== 'string')  data.ff_info.lastresult.errorcode = 'server_response';
501
502					data.ff_info.removewidget = false;
503
504					return false;
505				}
506			}
507		};
508
509		var UploadChunkDone = function(e, data) {
510			// Reset retries for successful chunked uploads.
511			data.ff_info.retries = 0;
512			data.ff_info.retrydelay = settings.retrydelay;
513
514			// Save for the next UploadChunkSend() call.
515			data.ff_info.lastresult = data.result;
516		};
517
518
519		return this.each(function() {
520			var $this = $(this);
521
522			// Calculate the action URL.
523			if (settings.url === '')
524			{
525				var url = $this.closest('form').attr('action');
526				if (url)  settings.url = url;
527			}
528
529			// Create a separate, hidden form on the page for handling file uploads.
530			var form = $('<form>').addClass('ff_fileupload_hidden').attr({
531				'action' : settings.url,
532				'method' : 'post',
533				'enctype' : 'multipart/form-data'
534			});
535			$('body').append(form);
536
537			// Append hidden input elements.
538			for (var x in settings.params)
539			{
540				if (settings.params.hasOwnProperty(x))
541				{
542					var input = $('<input>').attr({
543						'type' : 'hidden',
544						'name' : x,
545						'value' : settings.params[x]
546					});
547
548					form.append(input);
549				}
550			}
551
552			// Append a file input element.
553			var fileinputname = $this.attr('name');
554			var fileinput = $('<input>').attr({
555				'type' : 'file',
556				'name' : (fileinputname ? fileinputname : 'file')
557			});
558			if ($this.prop('multiple'))  fileinput.prop('multiple', true);
559
560			// Process the accepted file extensions.
561			if ($this.attr('accept'))
562			{
563				fileinput.attr('accept', $this.attr('accept'));
564
565				if (!settings.accept)
566				{
567					var accept = $this.attr('accept').split(',');
568
569					settings.accept = [];
570					for (var x = 0; x < accept.length; x++)
571					{
572						var opt = $.trim(accept[x]).toLowerCase();
573						settings.accept.push(opt.indexOf('/') < 0 && opt.lastIndexOf('.') > -1 ? opt.substring(opt.lastIndexOf('.') + 1) : opt);
574					}
575				}
576			}
577
578			form.append(fileinput);
579
580			// Insert the widget wrapper.
581			var fileuploadwrap = $('<div>').addClass('ff_fileupload_wrap');
582			$this.after(fileuploadwrap);
583
584			// Insert a new dropzone.  Using a button allows for standard keyboard and mouse navigation to the element.  The wrapper is for paste support.
585			var dropzonewrap = $('<div>').addClass('ff_fileupload_dropzone_wrap');
586			var dropzone = $('<button>').addClass('ff_fileupload_dropzone').attr('type', 'button').attr('aria-label', Translate('Browse, drag-and-drop, or paste files to upload'));
587			dropzonewrap.append(dropzone);
588			fileuploadwrap.append(dropzonewrap);
589			dropzone.on('click.fancy_fileupload', function(e) {
590				e.preventDefault();
591
592				form.find('input[type=file]').click();
593			});
594
595			// Add special recording buttons (if enabled).
596			var dropzonetools = $('<div>').addClass('ff_fileupload_dropzone_tools');
597			dropzonewrap.append(dropzonetools);
598
599			// Record audio.
600			if (settings.recordaudio && navigator.mediaDevices && window.MediaRecorder)
601			{
602				var audiobutton = $('<button>').addClass('ff_fileupload_dropzone_tool').addClass('ff_fileupload_recordaudio').attr('type', 'button').attr('aria-label', Translate('Record audio using a microphone'));
603				dropzonetools.append(audiobutton);
604
605				var audiorec = null;
606				var audiochunks = [];
607				audiobutton.click(function(e) {
608					e.preventDefault();
609
610					if (!audiorec)
611					{
612						navigator.mediaDevices.getUserMedia({ audio: true }).then(function(stream) {
613							audiorec = new MediaRecorder(stream, settings.audiosettings);
614
615							audiorec.addEventListener('dataavailable', function(e) {
616								if (e.data.size > 0)  audiochunks.push(e.data);
617
618								if (audiorec.state === 'inactive')
619								{
620									var blob = new Blob(audiochunks, { type: 'audio/mp3' });
621									blob.lastModifiedDate = new Date();
622									blob.lastModified = Math.floor(blob.lastModifiedDate.getTime() / 1000);
623									blob.name = FormatStr(Translate('Audio recording - {0}.mp3'), blob.lastModifiedDate.toLocaleString());
624
625									fileinput.fileupload('add', { files: [blob] });
626
627									stream.getTracks().forEach(function(track) {
628										track.stop();
629									});
630
631									audiobutton.removeClass('ff_fileupload_recording');
632									audiochunks = [];
633									audiorec = null;
634								}
635							});
636
637							audiorec.start();
638							audiobutton.addClass('ff_fileupload_recording');
639						}).catch(function(e) {
640							alert(Translate('Unable to record audio.  Either a microphone was not found or access was denied.'));
641						});
642					}
643					else
644					{
645						audiorec.stop();
646					}
647				});
648			}
649
650			// Record video.
651			if (settings.recordvideo && navigator.mediaDevices && window.MediaRecorder)
652			{
653				var videobutton = $('<button>').addClass('ff_fileupload_dropzone_tool').addClass('ff_fileupload_recordvideo').attr('type', 'button').attr('aria-label', Translate('Record video using a camera'));
654				dropzonetools.append(videobutton);
655
656				var videorecpreview = $('<video>').prop('muted', true).prop('autoplay', true).addClass('ff_fileupload_recordvideo_preview').addClass('ff_fileupload_hidden');
657				dropzonewrap.append(videorecpreview);
658
659				var videorec = null;
660				var videochunks = [];
661				videobutton.click(function(e) {
662					e.preventDefault();
663
664					if (!videorec)
665					{
666						var streamhandler = function(stream) {
667							videorec = new MediaRecorder(stream, settings.videosettings);
668
669							videorec.addEventListener('dataavailable', function(e) {
670								if (e.data.size > 0)  videochunks.push(e.data);
671
672								if (videorec.state === 'inactive')
673								{
674									var blob = new Blob(videochunks, { type: 'video/mp4' });
675									blob.lastModifiedDate = new Date();
676									blob.lastModified = Math.floor(blob.lastModifiedDate.getTime() / 1000);
677									blob.name = FormatStr(Translate('Video recording - {0}.mp4'), blob.lastModifiedDate.toLocaleString());
678
679									fileinput.fileupload('add', { files: [blob] });
680
681									stream.getTracks().forEach(function(track) {
682										track.stop();
683									});
684
685									videobutton.removeClass('ff_fileupload_recording');
686									videorecpreview.addClass('ff_fileupload_hidden');
687									if (videorecpreview[0].src !== '')  videorecpreview[0].src = '';
688									videorecpreview[0].srcObject = null;
689									videochunks = [];
690									videorec = null;
691								}
692							});
693
694							videorec.start();
695							videobutton.addClass('ff_fileupload_recording');
696
697							// Display a preview box with just the video stream.
698							try { videorecpreview[0].src = URL.createObjectURL(stream); } catch(e) { videorecpreview[0].srcObject = stream; }
699
700							videorecpreview.removeClass('ff_fileupload_hidden');
701						};
702
703						// Video with audio (e.g. webcam) with fallback to video only (e.g. some screen recording codecs).
704						navigator.mediaDevices.getUserMedia({ video: true, audio: true }).then(streamhandler).catch(function(e) {
705							navigator.mediaDevices.getUserMedia({ video: true }).then(streamhandler).catch(function(e) {
706								alert(Translate('Unable to record video.  Either a camera was not found or access was denied.'));
707							});
708						});
709					}
710					else
711					{
712						videorec.stop();
713					}
714				});
715			}
716
717			// Add a table to track unprocessed and in-progress uploads.
718			var uploads = $('<table>').addClass('ff_fileupload_uploads');
719			fileuploadwrap.append(uploads);
720
721			// Hide the starting element.
722			$this.addClass('ff_fileupload_hidden');
723
724			// Initialize jQuery File Upload using the hidden form and visible dropzone.
725			var baseoptions = {
726				url: settings.url,
727				dataType: 'json',
728				pasteZone: dropzonewrap,
729				limitConcurrentUploads: 2
730			};
731
732			// Immutable options.
733			var immutableoptions = {
734				singleFileUploads: true,
735				dropZone: dropzone,
736				add: function(e, data) { AddFile(uploads, e, data) },
737				progress: UploadProgress,
738				fail: UploadFailed,
739				done: UploadDone,
740				chunksend: UploadChunkSend,
741				chunkdone: UploadChunkDone
742			};
743
744			// The user interface requires certain options to be set correctly.
745			fileinput.fileupload($.extend(baseoptions, settings.fileupload, immutableoptions));
746
747			// Save necessary information in case the uploader is destroyed later.
748			$this.data('fancy-fileupload', {
749				'fileuploadwrap' : fileuploadwrap,
750				'form' : form,
751				'settings': settings
752			});
753
754			// Post-initialization callback.
755			if (settings.postinit)  settings.postinit.call($this);
756		});
757	}
758
759	$.fn.FancyFileUpload.defaults = {
760		'url' : '',
761		'params' : {},
762		'edit' : true,
763		'maxfilesize' : -1,
764		'accept' : null,
765		'displayunits' : 'iec_windows',
766		'adjustprecision' : true,
767		'retries' : 5,
768		'retrydelay' : 500,
769		'recordaudio' : false,
770		'audiosettings' : {},
771		'recordvideo' : false,
772		'videosettings' : {},
773		'preinit' : null,
774		'postinit' : null,
775		'added' : null,
776		'showpreview' : null,
777		'hidepreview' : null,
778		'startupload' : null,
779		'continueupload' : null,
780		'uploadcancelled' : null,
781		'uploadcompleted' : null,
782		'fileupload' : {},
783		'langmap' : {}
784	};
785}(jQuery));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.