PageSourceSearch

https://nottsgirlscan.co.uk/packages/formidable/libraries/3rdparty/ajaxupload/js/ajaxupload.js

js nottsgirlscan.co.uk collected 2026-10-06 11:17:36 UTC 88,986 bytes, 2,596 lines download raw bytes

1/**
2 * jQuery Real Ajax Uploader 3.0
3 * http://www.albanx.com/
4 *
5 * Copyright 2010-2013, Alban Xhaferllari
6 *
7 * Date: 03-11-2013
8 */
9
10
11//==================================================================CLASS DEFINITION IN JS========================================================\\
12/* Simple JavaScript Inheritance
13 * By John Resig http://ejohn.org/
14 * MIT Licensed.
15 */
16// Inspired by base2 and Prototype
17(function(){
18  var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
19  this.Class = function(){};
20  Class.extend = function(prop) {
21    var _super = this.prototype;
22    initializing = true;
23    var prototype = new this();
24    initializing = false;
25   
26    for (var name in prop) {
27      prototype[name] = typeof prop[name] == "function" &&
28        typeof _super[name] == "function" && fnTest.test(prop[name]) ?
29        (function(name, fn){
30          return function() {
31            var tmp 	= this._super;
32            this._super = _super[name];
33            var ret 	= fn.apply(this, arguments);        
34            this._super = tmp;
35           
36            return ret;
37          };
38        })(name, prop[name]) :
39        prop[name];
40    }
41   
42    function Class() {
43      if ( !initializing && this.init )
44        this.init.apply(this, arguments);
45    }
46    Class.prototype = prototype;
47    Class.prototype.constructor = Class;
48    Class.extend = arguments.callee;
49    return Class;
50  };
51})();
52//==========================================================================================================================================\\
53
54
55
56/**
57 * jQuery Real Ajax Uploader 3.0
58 * http://www.albanx.com/
59 *
60 * Copyright 2010-2013, Alban Xhaferllari
61 *
62 * Date: 03-11-2013
63 */
64
65(function($, undefined){
66	'use strict'; 
67    
68	//=============================================================Simple i18 system============================================================\\
69	//String used in the uploader : STRING: POSITION
70	var strings = {
71			'Add files':0,
72			'Start upload':1,
73			'Remove all':2,
74			'Close':3,
75			'Select Files':4,
76			'Preview':5,
77			'Remove file':6,
78			'Bytes':7,
79			'KB':8,
80			'MB':9,
81			'GB':10,
82			'Upload aborted':11,
83			'Upload all files':12,
84			'Select Files or Drag&Drop Files':13,
85			'File uploaded 100%':14,
86			'Max files number reached':15,
87			'Extension not allowed':16,
88			'File size now allowed':17
89	};
90	
91	//translations for STRINGS in array for each language
92	var I18N = {
93			'it_IT':[
94				'Aggiungi file',
95				'Inizia caricamento',
96				'Rimuvi tutti',
97				'Chiudi',
98				'Seleziona',
99				'Anteprima',
100				'Rimuovi file',
101				'Bytes',
102				'KB',
103				'MB',
104				'GB',
105				'Interroto',
106				'Carica tutto',
107				'Seleziona o Trascina qui i file',
108				'File caricato 100%',
109				'Numero massimo di file superato',
110				'Estensione file non permessa',
111				'Dimensione file non permessa'
112			],
113			'sq_AL':[
114				'Shto file',
115				'Fillo karikimin',
116				'Hiqi te gjithë',
117				'Mbyll',
118				'Zgjith filet',
119				'Miniaturë',
120				'Hiqe file-in',
121				'Bytes',
122				'KB',
123				'MB',
124				'GB',
125				'Karikimi u ndërpre',
126				'Kariko të gjithë',
127				'Zgjith ose Zvarrit dokumentat këtu',
128				'File u karikua 100%'
129			],
130			'nl_NL':[
131			    'Bestanden toevoegen',
132			    'Start uploaden',
133			    'Verwijder alles',
134			    'Sluiten',
135			    'Selecteer bestanden',
136			    'Voorbeeld',
137			    'Verwijder bestand',
138			    'Bytes',
139			    'KB',
140			    'MB',
141			    'GB',
142			    'Upload afgebroken',
143			    'Upload alle bestanden',
144			    'Selecteer bestanden of  Drag&Drop bestanden',
145			    'Bestand geüpload 100%'
146			   ],
147			'de_DE':[
148                'Dateien hinzufügen',
149                'Hochladen',
150                'Alle entfernen',
151                'Schliessen',
152                'Dateien wählen',
153                'Vorschau',
154                'Datei entfernen',
155                'Bytes',
156                'KB',
157                'MB',
158                'GB',
159                'Upload abgebrochen',
160                'Alle hochgeladen',
161                'Wählen Sie Dateien oder fügen Sie sie mit Drag & Drop hinzu.',
162                'Upload 100%'
163          ],
164		   'fr_FR':[
165               'Ajouter',
166               'Envoyer',
167               'Tout supprimer',
168               'Fermer',
169               'Parcourir',
170               'Visualiser',
171               'Supprimer fichier',
172               'Bytes',
173               'Ko',
174               'Mo',
175               'Go',
176               'Envoi annulé',
177               'Tout envoyer',
178               'Parcourir ou Glisser/Déposer',
179               'Fichier envoyé 100%'],
180			'ja':[
181			   'ファイルを追加',
182			   'アップロードを開始',
183			   '全てを削除',
184			   '閉じる',
185			   'ファイルを選択',
186			   'プレビュー',
187			   'ファイルを削除',
188			   'バイト',
189			   'KB',
190			   'MB',
191			   'GB',
192			   'アップロードに失敗しました',
193			   '全てのファイルをアップロード',
194			   'ファイルを選択またはドラッグ&ドロップ',
195			   'ファイル100%アップロード済み',
196			   'ファイルの最大数に達しました',
197			   '許可されていない拡張子です',
198			   '許可されていないファイルサイズです'
199			]
200	};
201	
202	//current loaded translation
203	var AX_I18N = {};
204	function load18(lang){
205		if(I18N[lang] != undefined) {
206			AX_I18N = I18N[lang];
207		}
208	}
209	
210	//the translation function
211	function _(s) {
212		return AX_I18N ? (AX_I18N[strings[s]] || s) : s;
213	}
214	//===========================================================================================================================================\\
215	
216	
217	
218	//===============================================File object for uploader=====================================================================\\
219	//contains information about file, the upload status of file and some callback functions
220	var fileObject = Class.extend({
221		init: function(file, name, size, ext, AU)
222		{
223			//File properties
224			this.file 		= file; 			//real dom file object
225			this.status 	= 0; 				//status -1 error, 0 idle 1 done, 2 uploading
226			this.name		= name; 			//name of file
227			this.size		= size; 			//size of file
228			this.xhr		= null; 			//xmlhttprequest object or form 
229			this.info		= null; 			//info about upload status
230			this.ext 		= ext; 				//file extension
231			this.pos		= AU.files.length; 	//position of file in the array needed for file remove
232			this.AU 		= AU; 				//AjaxUploader object
233			this.settings 	= AU.settings;
234			this.exifData	= null;
235			//temp variables
236			this.currentByte= 0; 	//current uploaded byte
237			
238			this.afterInit();
239		},
240		afterInit: function()
241		{
242			//visual part
243			this.renderHtml();
244	    	
245	    	//bind events
246	    	this.bindEvents();
247	    	
248	    	//create the small preview
249	    	this.doPreview();
250	
251			if(this.settings.hideUploadForm && this.AU.form!==null && this.AU.form!==undefined)
252			{
253				this.uploadButton.hide();
254			}
255			
256			var AU = this.AU;
257	    	//if ajax upload is not supported then add a form around the file to upload it
258	    	if(AU.hasHtml4)
259	    	{
260	    		var params = AU.getParams(this.name, 0, false);   		
261	    		//create the upload form
262	    		var form = $('<form action="'+this.settings.url+'" method="post" target="ax-main-frame" encType="multipart/form-data" />').hide().appendTo(this.li);
263	    		form.append(this.file);	
264	    		//append to the form eventually the changed name of the uploaded file
265	        	form.append('<input type="hidden" value="'+this.name+'" name="ax-file-name" />');//input for re-name of file
266				for(var i=0; i<params.length;i++)
267				{
268					var d = params[i].split('=');
269					form.append('<input type="hidden" value="'+d[1]+'" name="'+d[0]+'" />');
270				}
271				
272	        	this.xhr = form;
273	    	}
274		},
275		renderHtml: function()
276		{
277			var settings = this.settings;
278			//create visual part
279			var size			= this.AU.formatSize(this.size);
280		    this.li				= $('<li />').appendTo(this.AU.fileList).attr('title', name);//li element container
281		    if(settings.bootstrap)
282		    {
283		    	this.li = $('<a />').appendTo(this.li);
284		    }
285		    
286		    this.prevContainer	= $('<a class="ax-prev-container" />').appendTo( this.li );//preview container
287		    this.details		= $('<div class="ax-details" />').appendTo( this.li ); //div containing details of files
288		    this.progressInfo	= $('<div class="ax-progress" />').appendTo( this.li ); //progress infomation container
289		    this.buttons 		= $('<div class="ax-toolbar" />').appendTo( this.li ); //button container
290
291		    this.prevImage		= $('<img class="ax-preview" src="" alt="' + _('Preview') + '" />').appendTo( this.prevContainer ); //preview image
292		    this.nameContainer	= $('<div class="ax-file-name">'+this.name+'</div>').appendTo( this.details ); //name container
293		    this.sizeContainer	= $('<div class="ax-file-size">'+size+'</div>').appendTo( this.details ); //size container
294		    this.progressBar	= $('<div class="ax-progress-bar" />').appendTo( this.progressInfo ); //animated progress bar
295		    this.progressPer	= $('<div class="ax-progress-info">0%</div>').appendTo( this.progressInfo ); //progress percentual
296	    	this.uploadButton 	= $('<a title="' + _('Start upload') + '" class="ax-upload ax-button" />').appendTo( this.buttons ).append('<span class="ax-upload-icon ax-icon"></span>');//upload button
297	    	this.removeButton 	= $('<a title="Remove file" class="ax-remove ax-button" />').appendTo( this.buttons ).append('<span class="ax-clear-icon ax-icon"></span>'); //remove button
298	    	
299	    	if(settings.bootstrap)
300	    	{
301	    		this.li.addClass('media thumbnail label-info');
302	    		this.prevContainer.addClass('pull-left');
303	    		this.prevImage.addClass('img-rounded media-object');
304	    		
305	    		this.details.addClass('label label-info').css({ 'border-bottom-left-radius':0});
306
307	    		this.progressInfo.addClass('progress progress-striped active').css({'margin-bottom':0});
308	    		this.progressBar.addClass('bar');
309	    		
310	    		this.buttons.css({ 'border-top-left-radius':0, 'border-top-right-radius':0});
311	    		this.uploadButton.addClass('btn btn-success btn-small').find('span').addClass('icon-play');
312	    		this.removeButton.addClass('btn btn-danger btn-small').find('span').addClass('icon-minus-sign');
313	    	}
314		},
315
316		//====================== Bind action events, upload, remove, preview =======================\\
317		bindEvents: function()
318		{
319		   	//bind start upload
320			this.uploadButton.bind('click', this, function(e){
321				if(e.data.AU.settings.enable)
322				{
323					if(e.data.status!=2)//start upload
324					{
325						e.data.startUpload();
326					}
327					else//if is uploading then stop on reclick
328					{
329						e.data.stopUpload();
330					}
331				}
332			});
333			   
334			//bind remove file
335			this.removeButton.bind('click', this, function(e){ 
336				if(e.data.AU.settings.enable) e.data.AU.removeFile(e.data.pos);	
337			});
338			
339		    if(this.settings.editFilename)
340		    {
341		    	//on double click bind the edit file name
342		    	this.nameContainer.bind('dblclick', this, function(e){
343		    		if(e.data.AU.settings.enable)
344		    		{
345				    	e.stopPropagation();
346				    	var file_name = e.data.name;
347				    	var file_ext = e.data.ext;
348				    	//get file name without extension
349				    	var file_name_no_ext = file_name.replace('.'+file_ext, '');
350				    	$(this).html('<input type="text" value="'+file_name_no_ext+'" />.'+file_ext);
351		    		}
352			    	
353			    }).bind('blur focusout', this, function(e){
354		    		e.stopPropagation();
355		    		var new_fn = $(this).children('input').val();
356		    		if( typeof(new_fn) != 'undefined' )
357		    		{
358		    			var cleanString = new_fn.replace(/[|&;$%@"<>()+,]/g, '');//remove bad filename chars
359		    			var final_fn = cleanString+'.'+e.data.ext;
360		    			$(this).html(final_fn);
361		    			e.data.name = final_fn;
362		    			if(!e.data.AU.hasAjaxUpload)//on form upload also rename input hidden input
363		    			{
364		    				//change the name of hidden input in the uploader form
365		    				e.data.xhr.children('input[name="ax-file-name"]').val(final_fn);
366		    			}
367		    		}
368			    });
369		    }
370		},
371		
372		
373		//=================================== function that creates the preview of images ===============================\\
374		doPreview: function()
375		{
376			//if filereader html5 preview is supported that make the preview on the fly
377	   		if (this.AU.settings.previews && this.AU.hasAjaxUpload && this.file.type.match(/image.*/) && (this.ext=='jpg' || this.ext=='gif' || this.ext=='png') && typeof (FileReader) !== "undefined")
378		    {
379	   			var name = this.name;
380	   			var me = this;
381	   			//remove the background image of the preview container
382	   			this.prevContainer.css('background','none');
383	   			
384	   			//the image that will contain the preview
385	   			var img = this.prevImage;
386	   			
387	   			//file reader object for reading and loading image
388			    var reader = new FileReader();  
389			    reader.onload =function(e) {
390			    	//set the image cursort to pointer for indicating
391			    	img.css('cursor','pointer').attr('src', e.target.result).click(function(){
392			    		
393			    		//create a image loader for getting image size
394			   			var imgloader = new Image();
395			   			imgloader.onload = function()
396			   			{
397			   				//resize image to fit the user window size
398			   			    var ratio = Math.min( $(window).width() / this.width, ($(window).height()-100) / this.height);
399			   			    var newWidth = (ratio<1)?this.width * ratio:this.width;
400			   			    var newHeight = (ratio<1)?this.height * ratio:this.height;
401
402			   			    var axtop = ($(window).scrollTop()-20+($(window).height()-newHeight)/2);
403			   			    var axleft= ($(window).width()-newWidth)/2;
404			   			    
405			   			    //set preview box position and dimension accordin to screen
406			   			    var axbox = $('#ax-box').css({ top:  axtop, height:newHeight, width:newWidth, left: axleft});
407			   			    
408			   			    //set the preview image
409			   			    axbox.children('img').attr({ width: newWidth, height:newHeight, src:e.target.result });
410			   			    
411			   			    //set the name of the file
412			   			    $('#ax-box-fn').find('span').html(name + ' ('+me.AU.formatSize(me.size)+')');
413			   			    
414			   			    //then show in the preview
415			   			    axbox.fadeIn(500);			
416			   			    
417			   			    //expand blocking overlay
418				    		$('#ax-box-shadow').css('height', $(document).height()).show();
419			   			};
420			   			//load the image
421			   			imgloader.src = e.target.result;
422			   			
423						$('#ax-box-shadow').css('z-index', 10000);
424						$('#ax-box').css('z-index', 10001);
425			    	});
426			    };  
427			    
428			    //read file from fs
429			    reader.readAsDataURL(this.file); 
430		    }
431		    else
432		    {
433		    	//if not supported or is not image file that load the icon of file type
434		    	this.prevContainer.addClass('ax-filetype-'+this.ext).children('img:first').remove();
435		    }
436		},
437		
438		askUser: function(callback, msg)
439		{
440			if(this.askDiv) this.askDiv.remove();
441		    this.askDiv		= $('<div class="ax-ask-div"></div>').appendTo(this.li); // div with more action and infos
442		    var askDivInner	= $('<div class="ax-ask-inner"><div class="ax-ask-quest">'+msg+'</div> </div>').appendTo(this.askDiv); // div with more action and infos
443	    	var askYes 		= $('<a title="Yes" class="ax-button ax-ask-yes"><span class="ax-upload-icon ax-icon"></span> <span>Yes</span></a>').appendTo(askDivInner);
444	    	var askNo 		= $('<a title="No" class="ax-button ax-ask-no"><span class="ax-clear-icon ax-icon"></span> <span>No</span></a>').appendTo(askDivInner);
445	    	
446	    	if(this.settings.bootstrap)
447	    	{
448	    		this.askDiv.addClass('alert');
449	    		askYes.addClass('btn btn-success btn-small').find('.ax-icon').addClass('icon-ok');
450	    		askNo.addClass('btn btn-danger btn-small').find('.ax-icon').addClass('icon-remove');
451	    	}
452	    	
453	    	askYes.on('click', this, function(e){
454	    		callback.call(e.data);
455	    		e.data.askDiv.remove();
456	    		e.data.askDiv = null;
457	    	});
458	    	
459	    	askNo.on('click', this, function(e){
460	    		e.data.askDiv.remove();
461	    		e.data.askDiv = null;
462	    	});
463		},
464    
465	    //Check if a file exits before upload, and ask user if want to ovveride or stop upload
466	    //params: fileobjct, and callback to call (normally upload start)
467	    checkFileExists: function(callback)
468	    {
469	    	var fileobj = this;
470			if(this.settings.checkFileExists)
471			{
472		    	var params = this.AU.getParams(fileobj.name, fileobj.size, false);
473		    	params.push('ax-check-file=1');
474		    	$.post(this.settings.url, params.join('&'), function(msg){
475		    		if(msg=='yes')
476		    		{
477		    			fileobj.askUser(callback, _('File exits on server. Override?'));
478		    		}
479		    		else
480		    		{
481		    			callback.call(fileobj);
482		    		}
483		    	});
484			}
485			else
486			{
487				callback.call(fileobj);
488			}
489	    },
490	    
491		/**
492		 * Start upload method
493		 */
494		startUpload: function()
495		{
496			//check if the before upload returns true, from user validation event
497			var valid = this.settings.beforeUpload.call(this, this.name, this.file);
498			if(valid)
499			{
500				this.status = 3;//check status
501				this.checkFileExists(function(){
502					//upload code as callback of check file exits
503					this.progressBar.css('width','0%');
504					this.progressPer.html('0%');
505					this.uploadButton.addClass('ax-abort');
506					this.status = 2;//uploading status
507					if(this.AU.hasAjaxUpload)//html5 upload
508					{
509						this.uploadAjax();
510					}
511					else if(this.AU.hasFlash) //flash upload
512					{
513						
514						if(!this.AU.uploading)
515						{
516							this.AU.uploading = true;
517							this.AU.flashObj.uploadFile(this.pos);
518						}
519					}
520					else //standard html4 upload
521					{
522						this.uploadStandard();
523					}
524					
525				});
526			}
527			else
528			{
529				this.status = -1;//error validation
530				this.onError('File validation failed');
531			}
532			return valid;
533		},
534		
535		/**
536		 * Main upload ajax html5 method, uses xmlhttprequest object for uploading file
537		 * Runs in recrusive mode for uploading files by chunk
538		 */
539		uploadAjax: function()
540		{
541			var settings 	= this.settings;
542			var file		= this.file;
543	    	var currentByte	= this.currentByte;
544	    	var name		= this.name;
545	    	var size		= this.size;
546	    	var chunkSize	= settings.chunkSize;	//chunk size
547			var endByte		= chunkSize + currentByte;
548			var isLast		= (size - endByte <= 0);
549	    	var chunk		= file;
550	    	var chunkNum	= chunkSize!=0 ? endByte / chunkSize: 1;
551	    	this.xhr 		= new XMLHttpRequest();//prepare xhr for upload
552	    	
553	    	if(chunkSize == 0)//no divide
554	    	{
555	    		chunk	= file;
556	    		isLast	= true;
557	    	}
558	    	else if(file.slice) // old slice, there are two version of the same
559	    	{
560	    		chunk	= file.slice(currentByte, endByte);
561	    	}
562	    	else if(file.mozSlice) // moz slice
563	    	{
564	    		chunk	= file.mozSlice(currentByte, endByte);
565	    	}
566	    	else if(file.webkitSlice) //webkit slice
567	    	{
568	    		chunk	= file.webkitSlice(currentByte, endByte);
569	    	}
570	    	else//no slice
571	    	{
572	    		chunk	= file;
573	    		isLast	= true;
574	    	}
575	    	
576	    	var me = this;
577	    	//abort event, (nothing to do for the moment)
578	    	this.xhr.upload.addEventListener('abort', function(e){
579	    		me.AU.slots--;
580	    	}, false); 
581	    	
582	    	//progress function, with ajax upload progress can be monitored
583	    	this.xhr.upload.addEventListener('progress', function(e)
584			{
585				if (e.lengthComputable) 
586				{
587					var perc = Math.round((e.loaded + chunkNum * chunkSize - chunkSize) * 100 / size);
588					me.onProgress(perc);
589				}  
590			}, false); 
591	    	    	
592	    	this.xhr.upload.addEventListener('error', function(e){
593	    		me.onError(this.responseText);
594	    	}, false);  
595	    	
596	    	this.xhr.onreadystatechange=function()
597			{
598				if(this.readyState == 4 && this.status == 200)
599				{
600					try
601					{
602						var ret	= JSON.parse( this.responseText );
603						//get the name returned by the server (it renames eventually)
604						if(currentByte == 0)
605						{
606							me.name	= ret.name;
607							me.nameContainer.html(ret.name);
608						}
609						
610						if(parseInt(ret.status) == -1)
611						{
612							throw ret.info;
613						}
614						else if(isLast)
615						{
616							//exec finish event of the file
617							me.onFinishUpload(ret.name, ret.size, ret.status, ret.info);
618						}
619						else
620						{
621							me.currentByte = endByte;
622							me.uploadAjax();
623						}
624					}
625					catch(err)
626					{
627						me.onError(err);
628					}
629				}
630			};
631		
632			var params = this.AU.getParams(name, size, !this.AU.useFormData);
633			params.push('ax-start-byte='+ currentByte);
634			params.push('ax-last-chunk='+ isLast);
635
636			if(this.AU.useFormData) //firefox 5 formdata does not work correctly
637			{
638				var data = new FormData();
639				data.append('ax_file_input', chunk);
640				for(var i=0; i<params.length; i++)
641				{
642					var d = params[i].split('=');
643					data.append(d[0], d[1] );
644				}
645				this.xhr.open('POST', settings.url, settings.async);
646				this.xhr.send(data);
647			}
648			else//else we use a old trick upload with php::/input ajax, FF3.6+, Chrome, Safari
649			{
650				var c =  settings.url.indexOf('?')==-1 ?'?':'&';			
651				this.xhr.open('POST', settings.url+c+params.join('&'), settings.async);
652				this.xhr.setRequestHeader('Cache-Control', 'no-cache');
653				this.xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');//header
654				this.xhr.setRequestHeader('Content-Type', 'application/octet-stream');//generic stream header
655				this.xhr.send(chunk);//send peice of file
656			}
657		},
658		
659		/**
660		 * starndard upload function for normal browsers
661		 */
662		uploadStandard: function()
663		{
664			//just fake upload in standard forms upload
665			this.progressBar.css('width','50%');
666			this.progressPer.html('50%');
667			$('#ax-main-frame').unbind('load').bind('load', this, function(e){
668				//get iframe content
669				var frameDoc = null;
670				if ( this.contentDocument ) 
671				{ // FF
672					frameDoc = this.contentDocument;
673				}
674				else if ( this.contentWindow ) 
675				{ // IE
676					frameDoc = this.contentWindow.document;
677				}
678	    		
679				//hope is a json, from which we get file information
680				try{
681					var ret	= $.parseJSON(frameDoc.body.innerHTML);
682		    		//set progress to 100%
683		    		e.data.onProgress(100);
684		    		
685		    		//get file info
686		    		e.data.onFinishUpload(ret.name, ret.size, ret.status, ret.info);
687				}catch(err){
688					e.data.onError(frameDoc.body.innerHTML);
689				}
690	   		
691	    		//if upload all flag is set then try to upload the next file
692	    		if(e.data.AU.upload_all && e.data.AU.files[e.data.pos+1]!==undefined)
693	    		{
694	    			e.data.AU.files[e.data.pos+1].startUpload();
695	    		}
696			});
697			
698			//submit the form of the standard upload
699			this.xhr.submit();
700		},
701	    /**
702	     * Stop upload function. it reset visual information and if the upload is xhr it calls the abort
703	     * method, or if upload is, iframe standard based it stops the iframe request
704	     */
705		stopUpload: function()
706		{
707			if(this.AU.hasAjaxUpload)
708			{
709				if(this.xhr!==null)//if is current uploading 
710				{
711					this.xhr.abort();	//abort xmlhttprequest request
712					this.xhr 	= null;//remove the xhr / form object
713				}
714			}
715			else if(this.AU.hasFlash)
716			{
717				this.AU.flashObj.stopUpload(this.pos);//call flash stop upload method
718			}
719			else
720			{
721				var iframe	= document.getElementById('ax-main-frame');
722				//stop iframe from uploading
723				try{
724					iframe.contentWindow.document.execCommand('Stop');
725				}
726				catch(ex){
727					iframe.contentWindow.stop();
728				}
729			}
730			
731			this.uploadButton.removeClass('ax-abort');//show upload button
732			this.currentByte = 0; //reset current byte
733			this.status = 0;//set status to idle
734			this.progressBar.css('width', 0);// reset progress bar
735			this.progressPer.html( _('Upload aborted') );//print abort info
736		},
737		/**
738		 * Runs on error
739		 * @param err error return from the server
740		 */
741		onError : function(err)
742		{
743			this.AU.slots--;
744	    	this.currentByte = 0;
745	    	this.status	= -1;
746	    	this.info	= err;
747	    	this.progressPer.html(err);
748	    	this.progressBar.css('width','0%');
749	    	this.uploadButton.removeClass('ax-abort');
750
751	    	//trigger the error event
752			this.settings.error.call(this, err, this.name);
753			
754			if(this.settings.removeOnError)
755	    	{
756	    		this.AU.removeFile(this.pos);
757	    	}
758		},
759		onFinishUpload : function(name, size, status, info)
760		{
761			this.AU.slots--;
762	    	this.name	= name;
763	    	this.status	= parseInt(status);
764	    	this.info	= info;
765
766
767	    	//in html5 file and flash api we read size from browser, in standard upload we get it from the server
768	    	if(!this.AU.hasAjaxUpload && !this.AU.hasFlash)
769	    	{
770	    		var size = this.AU.formatSize(this.size);
771	    		this.sizeContainer.html(size);
772	    	}
773	    	
774	    	this.currentByte = 0;
775	    	this.nameContainer.html(name);
775//get new name of file from server
776	    	this.li.attr('title', name);
777	    	this.onProgress(100);
778	    	this.uploadButton.removeClass('ax-abort');//remove abort button
779	    	this.progressBar.width(0);
780	    	this.progressPer.html(_('File uploaded 100%')); 
781	    	this.settings.success.call(this, name);//call success method
782	    	
783	    	//if all files had been uploaded then exec finish event
784	    	var runFinish = true;
785	    	for(var i = 0; i<this.AU.files.length; i++)
786	    	{
787	    		//so if we have any file still at idle do not run finish event
788	    		if(this.AU.files[i].status!=1 && this.AU.files[i].status!=-1) runFinish = false;
789	    	}
790
791	    	//if upload all was pressed, try to upload next files
792	    	if(this.AU.upload_all)
793	    	{
794	    		this.AU.uploadAll();
795	    	}
796	    	
797	    	if(runFinish)
798	    	{
799	    		this.AU.finish();
800	    	}
801	    	
802	    	
803	    	if(this.settings.removeOnSuccess)
804	    	{
805	    		this.AU.removeFile(this.pos);
806	    	}
807		},
808		/**
809		 * Function that is trigger on the progress event of the upload
810		 * updates progress bar and percentual
811		 */
812		onProgress: function(p){
813			this.progressBar.css('width',p+'%');
814			this.progressPer.html(p+'%');
815		}
816	});
817 	
818	//3.0 Premium feature: exif info
819	fileObject = fileObject.extend({
820		renderHtml: function()
821		{
822			this._super();
823			if(this.AU.hasAjaxUpload && this.ext.toLowerCase()=='jpg' && $.fileExif)//TODO only for jpg now
824			{
825		    	this.infoButton = $('<a title="File Exif data" class="ax-info ax-button" />').appendTo( this.buttons ).append('<span class="ax-info-icon ax-icon"></span>');
826		    	if(this.settings.bootstrap)
827		    	{
828		    		this.infoButton.addClass('btn btn-primary btn-small').find('span').addClass('icon-info-sign');
829		    	}
830			}
831		},
832		afterInit: function()
833		{
834			this._super();
835			var AU = this.AU;
836			if(AU.hasAjaxUpload && $.fileExif && this.ext.toLowerCase()=='jpg')//extrac axif only on html5 api browsers
837	    	{
838	    		var me = this;
839	    		$.fileExif(this.file, function(data){
840	    			me.exifData = data;
841	    		});
842	    	}
843		},
844		bindEvents: function()
845		{
846		    this._super();
847		    if(this.AU.hasAjaxUpload && $.fileExif && this.ext.toLowerCase()=='jpg')
848		    {
849			    this.infoButton.bind('click', this, function(e){
850			    	e.data.settings.fileInfo.call(e.data, e.data.exifData);
851			    });
852		    }
853		}
854	});
855	
856	
857	//allow delete file
858	fileObject = fileObject.extend({
859		renderHtml: function()
860		{
861			this._super();
862			if(this.settings.allowDelete)
863			{
864				this.deleteButt = $('<a title="Delete the file from server" class="ax-delete ax-button ax-disabled" />').appendTo( this.buttons ).append('<span class="ax-delete-icon ax-icon"></span>');
865		    	if(this.settings.bootstrap)
866		    	{
867		    		this.deleteButt.addClass('btn btn-warning btn-small').find('span').addClass('icon-remove');
868		    	}
869			}
870		},
871		bindEvents: function()
872		{
873		    this._super();
874		    if(this.settings.allowDelete)
875		    {
876			    this.deleteButt.bind('click', this, function(e){
877			    	var ofile = e.data;
878			    	if(ofile.status==1 && !$(this).hasClass('ax-disabled')) //we can delete file only if has been uploaded
879			    	{
880				    	ofile.askUser(function(){
881				    		ofile.deleteFile();
882				    	}, _('Delete uploaded file?'));
883			    	}
884			    });
885		    }
886		},
887		deleteFile: function()
888		{
889			if(this.settings.allowDelete)
890			{
891		    	var params = this.AU.getParams(this.name, this.size, false);
892		    	params.push('ax-delete-file=1');
893		    	$.post(this.settings.url, params.join('&'));
894		    	this.status = 0;//reset status as file not uploaded
895		    	this.deleteButt.addClass('ax-disabled');
896			}
897		},
898		onFinishUpload : function(name, size, status, info)
899		{
900			this._super(name, size, status, info);
901			if(this.status==1 && this.settings.allowDelete)
902			{
903				this.deleteButt.removeClass('ax-disabled');
904			}
905		}
906	});
907	
908	
909	/**
910	 * Ajax Uploader class
911	 */
912	var AjaxUploader = function($this, settings)
913	{
914		//support variables
915		this.hasFlash   	= false;
916		this.hasAjaxUpload 	= false;
917		this.useFormData 	= false;
918		this.hasHtml4 		= true;
919		
920		this.settings = this.preCheckSettings(settings);
921		this._init($this);
922		this.checkUploadSupport();//check upload support
923		
924		//properties
925		this.container 	= $this; 		// main container
926		this.files 		= [];			// array with the fileObjects
927		this.slots 		= 0;			// trace slots (parallel uploads), for limiting
928		
929		this.form 		= null; 		//if form integration
930		this.form_submit_event = null;	
931		this.flashObj	= null;
932		
933		//runtime vars
934		this.upload_all	= false;
935		this.uploading	= false;
936		
937		//render html
938		this.renderHtml();
939	    
940	    //run the init call back
941		settings.onInit.call(this);
942	    
943		//bind click mouse event and other events
944	    this.bindEvents();
945	};
946
947	AjaxUploader.prototype = 
948	{		
949		preCheckSettings: function(settings)
950		{
951			settings.allowDelete 		= settings.allowDelete || false; 
952			settings.checkFileExists 	= settings.checkFileExists || false;
953			settings.allowExt 			= $.map(settings.allowExt, function(n, i){ return n.toLowerCase();  });
954			
955			//=============IOS7 is bugged on multiple upload of video files
956			var is_bugged_ios7 =  navigator.userAgent.indexOf(" OS 7_") !== -1;
957			if(is_bugged_ios7) settings.maxFiles = 1;
958			//==================================================
959			
960			if(settings.language == 'auto')
961			{
962				var language = window.navigator.userLanguage || window.navigator.language;
963				settings.language = language.replace('-', '_');
964			}
965			
966			return settings;
967		},
968		_init: function($this)
969		{
970			var settings = this.settings;
971			//load language
972			load18(settings.language);
973			
974			$this.addClass('ax-uploader').data('author','http://www.albanx.com/');
975			
976			//create a iframe for standard uploads
977			if($('#ax-main-frame').length==0) 	$('<iframe name="ax-main-frame" id="ax-main-frame" />').hide().appendTo('body');
978			
979			//lightbox preview for images
980			if($('#ax-box').length==0)			$('<div id="ax-box"><div id="ax-box-fn"><span></span></div><img /><a id="ax-box-close" title="'+_('Close')+'"></a></div>').appendTo('body');//preview box
981			if($('#ax-box-shadow').length==0)	$('<div id="ax-box-shadow"/>').appendTo('body');//preview shadow, overlay
982
983			    $('#ax-box-close, #ax-box-shadow').click(function(e){
984    			e.preventDefault();
985    			$('#ax-box').fadeOut(500);
986    			$('#ax-box-shadow').hide();
987    		});
988			    
989		    if(settings.bootstrap)
990		    {
991		    	$('#ax-box-close').addClass('btn btn-danger').html('<span class="ax-clear-icon ax-icon icon-remove-sign"></span>');
992		    }
993		    
994		    //generate an id if it has no one, so to point the uploader from flash
995		    var unique_id = 'AX_'+Math.floor(Math.random()*100001); 
996		    while($('#'+unique_id).length>0)
997		    {
998		    	unique_id = 'AX_'+Math.floor(Math.random()*100001); 
999		    }  			     
1000		    $this.attr('id', $this.attr('id') ? $this.attr('id') : unique_id ); 
1001		},
1002
1003		//check upload support
1004		checkUploadSupport: function()
1005		{
1006			//--------------Test if support pure ajax upload and create browse file input-------
1007			var axtest 			= document.createElement('input');
1008			axtest.type 		= 'file';
1009			this.hasAjaxUpload 	= ('multiple' in axtest &&  typeof File != "undefined" &&  typeof (new XMLHttpRequest()).upload != "undefined" );
1010			this.hasFlash 		= false;
1011			axtest 				= null; //avoid memory leak IE
1012			//----------------------------------------------------------------------------------
1013			
1014			//this.hasAjaxUpload = false;
1015			
1016			//--------safari<5.1.7 is bugged in file api so we force using flash upload---------
1017			var is_bugged_safari =  /Safari/.test(navigator.userAgent) && /Apple Computer/.test(navigator.vendor) &&  /Version\/5\./.test(navigator.userAgent) && /Win/.test(navigator.platform);
1018			if(is_bugged_safari) this.hasAjaxUpload=false;
1019			//----------------------------------------------------------------------------------
1020
1021			
1022			//if does not support html5 upload, test if supports flash upload
1023			if(!this.hasAjaxUpload)
1024			{
1025				try 
1026				{
1027					var fo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash');
1028					if(fo) this.hasFlash = true;
1029				}catch(e){
1030					if(navigator.mimeTypes ["application/x-shockwave-flash"] != undefined) this.hasFlash = true;
1031				}
1032				
1033				this.settings.maxConnections = 0; // no parallel uploads on old browsers
1034			}
1035			
1036			//at the end test if supports only html4 standard upload
1037			this.hasHtml4 = (!this.hasFlash && !this.hasAjaxUpload);
1038			
1039			this.useFormData = window.FormData !== undefined;
1040			
1041			//if formData is supported we use that, better in general FF4+, Chrome, Safari
1042			var isfirefox5 =  (navigator.userAgent).match(/Firefox\/(\d+)?/);
1043			if(isfirefox5!==null)
1044			{
1045				var fire_ver = isfirefox5!==null && isfirefox5[1]!==undefined && !isNaN(isfirefox5[1]) ? parseFloat(isfirefox5[1]) : 7;
1046				if(fire_ver<=6) this.useFormData = false;
1047			}
1048			
1049			//same for some version of opera
1050			var is_opera =  (navigator.userAgent).match(/Opera\/(\d+)?/);
1051			if(is_opera!==null)
1052			{
1053				var ver = (navigator.userAgent).match(/Version\/(\d+)?/);
1054				var opera_ver = ver[1]!==undefined && !isNaN(ver[1]) ? parseFloat(ver[1]) : 0;
1055				if(opera_ver<12.10) this.useFormData = false;
1056			}
1057		},
1058		renderHtml: function()
1059		{
1060			var settings 	= this.settings;
1061			this.mainWrapper  	= $('<div class="ax-main-container" />').append('<h5 class="ax-main-title">' + _('Select Files') + '</h5>').appendTo(this.container);
1062			
1063			//get max size bytes in real format
1064			this.max_size 	= settings.maxFileSize;
1065			var mult 		= settings.maxFileSize.slice(-1);
1066			if(isNaN(mult))
1067			{
1068				this.max_size = this.max_size.replace(mult, '');//remove the last char
1069				switch (mult)//1024 or 1000??
1070				{
1071					case 'P': this.max_size = this.max_size*1024;
1072					case 'T': this.max_size = this.max_size*1024;
1073					case 'G': this.max_size = this.max_size*1024;
1074					case 'M': this.max_size = this.max_size*1024;
1075					case 'K': this.max_size = this.max_size*1024;
1076				}
1077			}
1078			
1079			var bs_browse = 'ax-browse-c ax-button';
1080			var bs_upload = 'ax-upload-all ax-button';
1081			var bs_remove = 'ax-clear ax-button';
1082			
1083			var bs_b_icon	= 'ax-plus-icon ax-icon';
1084			var bs_u_icon	= 'ax-upload-icon ax-icon';
1085			var bs_r_icon	= 'ax-clear-icon ax-icon';
1086			
1087			if(settings.bootstrap)
1088			{
1089				bs_browse += ' btn btn-primary';
1090				bs_upload += ' btn btn-success';
1091				bs_remove += ' btn btn-danger';
1092				
1093				bs_b_icon += ' icon-plus-sign';
1094				bs_u_icon += ' icon-play';
1095				bs_r_icon += ' icon-remove-sign';
1096			}
1097			
1098			this.browse_c = $('<a class="'+bs_browse+'" title="' + _('Add files') + '" />').append('<span class="'+bs_b_icon+'"></span> <span class="ax-text">' + _('Add files') + '</span>').appendTo(this.mainWrapper);
1099			
1100			//Browser control
1101			this.browseFiles = $('<input type="file" class="ax-browse" name="ax_file_input" />').prop('multiple', (this.hasAjaxUpload && this.settings.maxFiles!=1) ).appendTo(this.browse_c);
1102			
1103			//experimental feature, works only on google chrome, has some perfomance issue, upload directory
1104			if(settings.uploadDir)
1105			{
1106				this.browseFiles.prop({'directory':'directory', 'webkitdirectory':'webkitdirectory', 'mozdirectory':'mozdirectory'});
1107			}
1108			
1109			//Browse container for the browse control
1110			if(this.hasFlash)
1111			{
1112				//remove the normal html browse in this case to add the flash one
1113				this.browse_c.children('.ax-browse').remove();
1114				
1115				//give to the flash element an id
1116				var flash_id = this.container.attr('id')+'_flash';
1117				
1118				//standard cross-browser flash html code
1119				settings.flash = settings.flash+'?test='+Math.random(10, 100000);
1120				var flash_html = 	'<!--[if !IE]> -->'+
1121				'<object style="position:absolute;width:150px;height:100px;left:0px;top:0px;z-index:1000;" id="'+flash_id+'" type="application/x-shockwave-flash" data="'+settings.flash+'" width="150" height="100">'+
1122				'<!-- <![endif]-->'+
1123				'<!--[if IE]>'+
1124				'<object style="position:absolute;width:150px;height:100px;left:0px;top:0px;z-index:1000;" id="'+flash_id+'" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"  codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0" width="150" height="100">'+
1125				'<param name="movie" value="'+settings.flash+'" />'+
1126				'<!--><!--dgx-->'+
1127				'<param name="flashvars" value="instance_id='+this.container.attr('id')+'">'+
1128				'<param name="allowScriptAccess" value="always" />'+
1129				'<param value="transparent" name="wmode">'+
1130				'</object>'+
1131				'<!-- <![endif]-->';
1132									
1133				
1134				this.browse_c.append('<div style="position:absolute;overflow:hidden;width:150px;height:100px;left:0px;top:0px;z-index:0;">'+flash_html+'</div>');
1135
1136				//keep a reference to the flash object for calling actionscript methods from javascript
1137				this.flashObj = document.getElementById(flash_id);
1138			}
1139					
1140			//Upload all button
1141		    this.uploadFiles = $('<a class="'+bs_upload+'" title="' + _('Upload all files') + '" />').append('<span class="'+bs_u_icon+'"></span> <span class="ax-text">' + _('Start upload') + '</span>').appendTo(this.mainWrapper);
1142		    
1143		    //remove files button
1144		    this.removeFiles = $('<a class="'+bs_remove+'" title="' + _('Remove all') + '" />').append('<span class="'+bs_r_icon+'"></span> <span class="ax-text">' + _('Remove all') + '</span>').appendTo(this.mainWrapper);
1145
1146		    //file list container
1147		    this.fileList	= $('<ul class="ax-file-list" />').appendTo(this.mainWrapper);
1148		    if(settings.bootstrap)
1149		    {
1150		    	this.fileList.addClass('media-list');
1151		    }
1152		},
1153		//============Bind the events method===============\\
1154		bindEvents: function()
1155		{
1156		    //Add browse event
1157			var settings = this.settings;
1158			
1159		    this.browseFiles.bind('change', this, function(e){
1160		    	var AU = e.data;
1161		    	//do not open on flash support, flash has his own browse file
1162		    	if(AU.settings.enable && !AU.hasFlash)
1163		    	{
1164					//if is supported ajaxupload then we have an array of files, if not we have a simple input element with one file
1165					var files = (AU.hasAjaxUpload) ? this.files : new Array(this);
1166					AU.addFiles(files);
1167			    	
1168			    	if(!AU.hasAjaxUpload)
1169			    	{
1170			    		//clone element for next file select
1171			    		$(this).clone(true).val('').appendTo(AU.browse_c);
1172			    	}
1173			    	else
1174			    	{
1175			    		//chrome fix change event
1176			    		this.value = '';
1177			    	}
1178		    	}
1179			});
1180		    
1181		    
1182		    //upload files
1183		    this.uploadFiles.bind('click', this, function(e){
1184		    	if(e.data.settings.enable) e.data.uploadAll();
1185		    	return false;
1186		    });
1187		    
1188		    //remove all files from list
1189		    this.removeFiles.bind('click', this, function(e){
1190		    	if(e.data.settings.enable) e.data.clearQueue();
1191		    	return false;
1192		    });
1193		    
1194		    //external form integration
1195			//If used with form combination, the bind upload on form submit
1196			if($(settings.form).length>0)
1197			{
1198				this.form = $(settings.form);
1199			}
1200			else if(settings.form=='parent')
1201			{
1202				this.form = this.container.parents('form:first');
1203			}   
1204			
1205			if(this.form !==null && this.form!==undefined)
1206			{
1207				//hide upload buttons on form
1208				if(settings.hideUploadForm){
1209					this.uploadFiles.hide();
1210				}
1211				
1212				//if form has any binded any submit event by the user store it and execute at the end
1213				var events = this.form.data("events");
1214				if(events!==null && events!==undefined)
1215				{
1216					if(events.submit!==null && events.submit!==undefined)
1217					{
1218						this.form_submit_event = events.submit;
1219					}
1220				}
1221		        
1222				//now unbind form events
1223				this.form.unbind('submit');
1224				
1225				//before submit form first upload the files
1226				this.form.bind('submit.ax', this, function(e){
1227					if(e.data.files.length>0)
1228					{
1229						e.data.uploadAll();
1230						return false;
1231					}
1232				});
1233			}
1234			
1235		    //create drop area if is setted and if is supported
1236		    if(this.hasAjaxUpload)
1237		    {
1238		    	var dropArea = (settings.dropArea=='self')? this.container[0]: $(settings.dropArea)[0];
1239		    	var me = this;
1240		    	//change the text to drag&drop
1241		    	if(settings.dropArea == 'self')
1242		    	{
1243		    		this.mainWrapper.find('.ax-main-title').html(_('Select Files or Drag&Drop Files'));
1244		    	}
1245		    	
1246			    //Prevent default and stop propagation on dragenter
1247		    	dropArea.addEventListener('dragenter',function(e){
1248		    		e.stopPropagation();  
1249		    		e.preventDefault(); 
1250		    	},false);
1251		    	
1252		    	//on drag over change background color
1253		    	dropArea.addEventListener('dragover', function(e){    		
1254		    		e.stopPropagation();  
1255		    		e.preventDefault(); 
1256		    		if(me.settings.enable)
1257		    		{
1258		    			if(settings.dropClass) 
1259		    				$(this).addClass(settings.dropClass);
1260		    			else
1261		    				this.style.backgroundColor=settings.dropColor; 
1262		    			
1263		    		}
1264		    	},false);
1265		    	
1266		    	//on drag leave reset background color
1267		    	dropArea.addEventListener('dragleave', function(e){
1268		    		e.stopPropagation();  
1269		    		e.preventDefault(); 
1270		    		if(me.settings.enable)
1271		    		{
1272		    			if(settings.dropClass) 
1273		    				$(this).removeClass(settings.dropClass);
1274		    			else
1275		    				this.style.backgroundColor = '';   
1276		    		}
1277		    	},false);
1278		    	
1279		    	//on drop add files to list
1280		    	
1281		    	dropArea.addEventListener('drop', function(e)
1282			    {
1283		    		if(me.settings.enable)
1284		    		{
1285				    	e.stopPropagation();  
1286				    	e.preventDefault();
1287		
1288				    	//add files
1289				    	me.addFiles(e.dataTransfer.files);
1290		
1291				    	//reset background color
1292						this.style.backgroundColor = '';
1293						
1294						//if autostart is enabled then start upload
1295				    	if(settings.autoStart)
1296				    	{
1297				    		me.uploadAll();
1298				    	}
1299		    		}
1300				},false);	
1301		    	
1302		    	//bind the ESC button to close the preview of image, unbind to avoid multiple call when have multiple instance of the uploader
1303	    		$(document).unbind('.ax').bind('keyup.ax',function(e){
1304	    			if (e.keyCode == 27) {
1305	    				$('#ax-box-shadow, #ax-box').fadeOut(500);
1306	    			}  
1307	    		});
1308		    }
1309		    
1310		    //load enable option
1311		    this.enable(this.settings.enable);	
1312		},
1313		
1314		//========================Finish function, runs when all files get uploaded====================\\
1315		finish: function()
1316		{
1317			this.upload_all = false;
1318			//collect file names in a array
1319			var fileNames 	= []; //file names
1320			
1321			for(var i = 0; i < this.files.length; i++)
1322			{
1323				fileNames.push(this.files[i].name);
1324			}
1325			
1326			//to the finish event return the file names and files object
1327			this.settings.finish.call(this, fileNames, this.files);
1328	
1329			//run the before submit event, in case of form integration
1330			this.settings.beforeSubmit.call(this, fileNames, this.files, function(){
1331				//if there is a form integrated then submit the form and append files informations
1332				if(this.form!==null && this.form!==undefined)
1333				{
1334					//add to the form the file paths
1335					var basepath = (typeof(this.settings.remotePath)=='function')?this.settings.remotePath():this.settings.remotePath;
1336					
1337					for(var i=0;i<fileNames.length;i++)
1338					{
1339						var filepath = basepath+fileNames[i];
1340						this.form.append('<input name="ax-uploaded-files[]" type="hidden" value="'+filepath+'" />');
1341					}
1342					
1343					this.form.unbind('submit.ax');//remove ajax uploader event
1344					
1345					//bind his original submit event
1346					if(this.form_submit_event!==null && this.form_submit_event!==undefined)
1347					{
1348						this.form.bind('submit', this.form_submit_event);
1349					}
1350					
1351					var has_submit_button = this.form.find('[type="submit"]');//strange if form has submit button cannot call .submit()
1352	
1353					if(has_submit_button.length>0)
1354						has_submit_button.trigger('click');//trigger click on the submit button of the form
1355					else 
1356						this.form.submit();//submit the form normally now
1357				}
1358			});
1359		},
1360		
1361		//======================= Add selected files to list ====================\\
1362		addFiles: function(files)
1363		{
1364			var current_selected_files = [];//store this just for the on select event
1365			
1366			//add selected files to the queue
1367			for (var i = 0; i < files.length; i++) 
1368			{
1369				var ext, name, size;
1370				
1371				//get file name and file extenstion
1372				if(this.hasAjaxUpload || this.hasFlash)
1373				{
1374					name	= files[i].name;
1375					size	= files[i].size;
1376				}
1377				else
1378				{
1379					name	= files[i].value.replace(/^.*\\/, '');
1380					size	= 0;
1381				}
1382	
1383				//normalizze extension
1384				ext	= name.split('.').pop().toLowerCase();
1385	
1386				//check if extension is allowed to be uploaded 
1387				//if we have reach the max number of files allowed
1388				//if file size is allowed
1389				var err = this.checkFile(name, size);
1390				
1391				//if no errors add file to list
1392				if(err == '')
1393				{
1394					//create the file object
1395					var fileObj = new fileObject(files[i], name, size, ext, this );
1396					//put in queue
1397					this.files.push( fileObj );
1398					current_selected_files.push( fileObj );
1399				}
1400				else
1401				{
1402					//if there are errors call the error event (if defined from the user)
1403					this.settings.error.call(this, err, name);
1404				}
1405			}
1406			
1407			//call the onSelect event, on the selected files
1408			this.settings.onSelect.call(this, current_selected_files);
1409	
1410			//if autostart is enabled then start upload
1411	    	if(this.settings.autoStart)
1412	    	{
1413	    		this.uploadAll();
1414	    	}
1415		},
1416		
1417		//=================== Check file method on file select action ======================\\
1418		checkFile: function(name, size)
1419		{
1420			var ext	= name.split('.').pop().toLowerCase();
1421			
1422			//check max file number
1423			var max_num_f 	= !!(this.files.length < this.settings.maxFiles);
1424			
1425			//check extension
1426			var allow_ext 	= !!($.inArray(ext, this.settings.allowExt)>=0 || this.settings.allowExt.length==0);
1427			
1428			//check file size
1429			var max_size 	= !!(size<=this.max_size);
1430			
1431			//check user validate file function
1432			var user_error = typeof(this.settings.validateFile)==='function' ? this.settings.validateFile.call(this, name, ext, size):'';
1433			
1434			var error = '';
1435			if(!max_num_f)	error=error+_('Max files number reached')+':' + max_num_f +"\n";
1436			if(!allow_ext)	error=error+_('Extension not allowed')+':' + ext +"\n";
1437			if(!max_size)	error=error+_('File size now allowed')+':' + size + "\n";
1438			if(user_error!='' && user_error!==undefined && user_error!==null) error = error+user_error;
1439			
1440			return error;
1441		},
1442		getPendingFiles: function()
1443		{
1444			var arr = [];
1445			for(var i = 0;i < this.files.length; i++)
1446			{
1447				if(this.files[i].status == 0 && this.slots <= this.settings.maxConnections)
1448				{
1449					arr.push(this.files[i]);
1450					this.slots++;
1451				}
1452			}
1453			return arr;
1454		},
1455		//=================== Start the upload of all files======================\\
1456		uploadAll: function()
1457		{
1458			this.upload_all = true;
1459			//call the beforeUploadAll event, if return false do not upload
1460			var valid = this.settings.beforeUploadAll.call(this, this.files);
1461			if(valid!==false)
1462			{
1463				var pending = this.getPendingFiles();
1464				for(var i = 0; i<pending.length;i++)
1465				{
1466					pending[i].startUpload();
1467				}
1468			}
1469		},
1470		
1471		//=================== Remove files from the list==========================\\
1472		clearQueue: function()
1473	    {
1474	    	while(this.files.length>0){
1475	    		this.removeFile(0);
1476	    	}
1477	    },
1478	    
1479	    //=================== This method formats the params to be passed to the server script==========================\\
1480	    // encode option is for sending data encoded in case data are sended by GET method, (old Ajax Upload without form data, FF3.6)
1481	    getParams: function(file_name, size, encode)
1482	    {
1483	    	//NOTE: all internal params of Real Ajax Uploader starts with ax-
1484			var settings = this.settings;
1485			var getpath	= (typeof(settings.remotePath)=='function')?settings.remotePath():settings.remotePath;
1486			var params	= [];
1487			
1488			//file data
1489			params.push('ax-file-path=' + (encode ? encodeURIComponent(getpath): getpath) );
1490			params.push('ax-allow-ext=' + (encode ? encodeURIComponent( settings.allowExt.join('|')) : settings.allowExt.join('|')) );
1491			params.push('ax-file-name=' + (encode ? encodeURIComponent(file_name) : file_name) );
1492			params.push('ax-max-file-size=' + settings.maxFileSize);
1493			params.push('ax-file-size=' + size);
1494			
1495			//thumb data, for generation of thumbs in the server side
1496			params.push('ax-thumbPostfix=' + (encode ? encodeURIComponent(settings.thumbPostfix) : settings.thumbPostfix) );
1497			params.push('ax-thumbPath=' + (encode ? encodeURIComponent(settings.thumbPath) : settings.thumbPath) );
1498			params.push('ax-thumbFormat=' + (encode ? encodeURIComponent(settings.thumbFormat) : settings.thumbFormat) );
1499			params.push('ax-thumbHeight=' + settings.thumbHeight);
1500			params.push('ax-thumbWidth=' + settings.thumbWidth);
1501			params.push('ax-random='+ (Math.random()*10001) );
1502			//override or not
1503			if( this.settings.checkFileExists || this.settings.overrideFile){
1504				params.push('ax-override=1');
1505			}
1506			//user send data, maybe a string like param=value&param2=value2 or json object: {param: value}
1507			var otherdata	= (typeof(settings.data)=='function')?settings.data():settings.data;
1508			if(typeof(otherdata)=='object')
1509			{
1510				for(var i in otherdata)
1511				{
1512					params.push( i + '=' + (encode ? encodeURIComponent(otherdata[i]) : otherdata[i]) );
1513				}
1514			}
1515			else if(typeof(otherdata)=='string' && otherdata!='')
1516			{
1517				var pp = otherdata.split('&');
1518				for(var i = 0; i<pp.length;i++)
1519				{
1520					params.push(pp[i]);
1521				}
1522			}
1523			
1524			return params;
1525	    },
1526
1527	    //============================== Remove a file from the list =====================\\
1528	    //pos the index position of the file
1529	    removeFile: function(pos)
1530		{
1531			var fileobj = this.files[pos];//get the file to remove
1532			fileobj.stopUpload();//stop upload if the files is being upload
1533			fileobj.li.remove();//remove the visual LI
1534			fileobj.file = null;//remove file dom object
1535			this.files.splice(pos, 1);//remove the file from the array (same is done in flash internal list)
1536			
1537			//remove the file from flash list
1538			if(this.hasFlash)
1539			{
1540				this.flashObj.removeFile(pos);
1541			}
1542			//re-calculate file positions
1543			for(var i=0; i<this.files.length; i++)
1544			{
1545				this.files[i].pos = i;
1546			}
1547		},
1548		
1549		//============================= Stop all uploads========================\\
1550		stopUpload: function()
1551		{
1552			for(var i = 0; i<this.files.lenght; i++){
1553				this.files[i].stopUpload();
1554			}
1555		},
1556
1557		//============================= Format size of files=====================\\
1558		formatSize: function(size)
1559		{
1560			var precision = this.settings.precision;
1561			
1562			if (typeof(precision) =='undefined' ) precision = 2;
1563			
1564			var suffix = new Array(_('Bytes'), _('KB'), _('MB'), _('GB'));
1565			var i=0;
1566			
1567		    while (size >= 1024 && (i < (suffix.length - 1))) {
1568		        size /= 1024;
1569		        i++;
1570		    }
1571		    var intVal = Math.round(size);
1572		    var multp = Math.pow(10, precision);
1573		    var floor = Math.round((size*multp) % multp);
1574		    return intVal+'.'+floor+' '+suffix[i];
1575		},
1576		//============================== Method for getting/setting options =====================\\
1577		options: function(opt, val)
1578		{
1579			if(val!==undefined && val!==null)//if val is defined then set the option
1580			{
1581				this.settings[opt] = val;
1582				if(opt == 'enable')
1583				{
1584					this.enable(val);
1585				}
1586			}
1587			else//if not return the value of that option
1588			{
1589				return this.settings[opt];
1590			}
1591		},
1592		
1593		//============================== Method for enable/disable the uploader =====================\\
1594		enable: function(bool)
1595		{
1596			this.settings.enable = bool;
1597			if(bool)
1598			{
1599				this.container.removeClass('ax-disabled').find('input').attr('disabled',false);
1600			}
1601			else
1602			{
1603				this.container.addClass('ax-disabled').find('input').attr('disabled',true);
1604			}
1605		}
1606	};
1607	
1608
1609	
1610	//==============================Jquery Bind==============================================\\
1611	
1612    var globalSettings = 
1613    {
1614    	remotePath : 	'uploads/',					//remote upload path, can be set also in the php upload script
1615    	url:			'upload.php',				//php/asp/jsp upload script
1616    	flash:			'uploader.swf',				//flash uploader url for not html5 browsers
1617    	data:			'',							//other user data to send in GET to the php script
1618    	async:			true,						//set asyncron upload or not
1619    	maxFiles:		9999,						//max number of files can be selected
1620    	allowExt:		[],							//array of allowed upload extesion, can be set also in php script
1621    	success:		function(fn){ },				//function that triggers every time a file is uploaded
1622    	finish:			function(file_names, file_obj){ },			//function that triggers when all files are uploaded
1623    	error:			function(err, fn){ },		//function that triggers if an error occuors during upload,
1624    	enable:			true,						//start plugin enable or disabled
1625    	chunkSize:		1048576,					//default 1Mb,	//if supported send file to server by chunks, not at once
1626    	maxConnections:	3,							//max parallel connection on multiupload recomended 3, firefox support 6, only for browsers that support file api
1627    	dropColor:		'red',						//back color of drag & drop area, hex or rgb
1628    	dropClass:		'ax-drop',					//class to add to the drop area when dropping files
1629    	dropArea:		'self',						//set the id or element of area where to drop files. default self
1630    	autoStart:		false,						//if true upload will start immediately after drop of files or select of files
1631    	thumbHeight:	0,							//max thumbnial height if set generate thumbnial of images on server side
1632    	thumbWidth:		0,							//max thumbnial width if set generate thumbnial of images on server side
1633    	thumbPostfix:	'_thumb',					//set the post fix of generated thumbs, default filename_thumb.ext,
1634    	thumbPath:		'',							//set the path where thumbs should be saved, if empty path setted as remotePath
1635    	thumbFormat:	'',							//default same as image, set thumb output format, jpg, png, gif
1636    	maxFileSize:	'10M',						//max file size of single file,
1637    	form:			null,						//integration with some form, set the form selector or object, and upload will start on form submit
1638    	hideUploadForm:	true,						//hide upload button on form integration, upload starts on form submit
1639    	beforeSubmit: 	function(file_names, file_obj, formsubmitcall){
1640    		formsubmitcall.call(this);
1641    	},				//event that runs before submiting a form
1642    	editFilename:	false,						//if true allow edit file names before upload, by dblclick
1643		beforeUpload:	function(filename, file){return true;}, //this function runs before upload start for each file, if return false the upload does not start
1644    	beforeUploadAll:function(files){return true;}, //this function runs before upload all start, can be good for validation
1645    	onSelect: 		function(files){},			//function that trigger after a file select has been made, paramter total files in the queue
1646    	onInit:			function(AU){},				//function that trigger on uploader initialization. Usefull if need to hide any button before uploader set up, without using css
1647    	language:		'auto',							//set regional language, default is english, avaiables: sq_AL, it_IT
1648    	uploadDir:		false,						//experimental feature, works on google chrome, for upload an entire folder content
1649    	removeOnSuccess:false,						//if true remove the file from the list after has been uploaded successfully
1650    	removeOnError:	false,						//if true remove the file from the list if it has errors during upload
1651    	bootstrap:		false,						//tell if to use bootstrap for theming buttons
1652    	previews:		true,						//disable previews of images . to avoid memory problem with browsers
1653    	
1654    	validateFile: 	function(name, extension, size){ //user define function to validate a file, must return a string with error
1655    		
1656    	},
1657    	
1658    	//3.0 added
1659    	overrideFile: 	false,						//false=> do not ovveride on server side, true==> over, function let user decide
1660    	checkFileExists: false,						//false=> do not ask user for file exits, true=> ask user to override or not the file
1661    	fileInfo: function(oData)
1662		{
1663		    var strPretty = "";
1664		    for (var a in oData) {
1665		        if (oData.hasOwnProperty(a)) {
1666		            if (typeof oData[a] == "object") {
1667		                strPretty += a + " : [" + oData[a].length + " values]\r\n";
1668		            } else {
1669		                strPretty += a + " : " + oData[a] + "\r\n";
1670		            }
1671		        }
1672		    }
1673		    alert(strPretty);
1674		},											//function that get bind on info button of file, return file exif data (for images)
1675    	allowDelete:	false						//if enabled allow user to delete file after upload NOTE: should also be enabled from server side for security reason
1676    };
1677    
1678	var methods =
1679	{
1680		init : function(options)
1681		{
1682    	    return this.each(function() 
1683    	    {
1684				var settings = $.extend({}, globalSettings, options);
1685				//for avoiding two times call errors
1686				var $this 	= $(this).html('');
1687				var AU		= $this.data('AU');
1688				if( AU!==undefined && AU!==null)
1689				{
1690					return;
1691				}
1692				//create the uploader object
1693				$this.data('AU', new AjaxUploader($this, settings));
1694    	    });
1695		},
1696		clear:function()
1697		{
1698			return this.each(function()
1699			{
1700				var $this = $(this);
1701				var AU = $this.data('AU');
1702				AU.clearQueue();
1703			});
1704		},
1705		start:function()
1706		{
1707			return this.each(function()
1708			{
1709				var $this = $(this);
1710				var AU = $this.data('AU');
1711				AU.uploadAll();
1712			});
1713		},
1714		addFlash:function(files)
1715		{
1716			var $this = $(this);
1717			var AU = $this.data('AU');
1718			AU.addFiles(files);
1719		},
1720		progressFlash: function(p, filepos)
1721		{
1722			var $this = $(this);
1723			var AU = $this.data('AU');
1724			AU.files[filepos].onProgress(p);
1725		},
1726		onFinishFlash: function(json, pos)
1727		{
1728			var $this = $(this);
1729			var AU = $this.data('AU');
1730			AU.uploading = false;
1731			try
1732			{
1733				var json_ret = jQuery.parseJSON(json);
1734				if(parseInt(json_ret.status) == -1)
1735				{
1736					throw json_ret.info;
1737				}
1738				else
1739				{
1740					AU.files[pos].onFinishUpload(json_ret.name, json_ret.size, json_ret.status, json_ret.info);
1741				}
1742			}
1743			catch(err)
1744			{
1745				AU.files[pos].onError(err);
1746			}
1747		},
1748		getUrl: function(name, size)
1749		{
1750			var $this 	= $(this);
1751			var AU 		= $this.data('AU');
1752			return AU.settings.url;
1753		},
1754		getParams: function(name, size)
1755		{
1756			var $this 	= $(this);
1757			var AU 		= $this.data('AU');
1758			var params	= AU.getParams(name, size, true);
1759			return params.join('&');
1760		},
1761		getAllowedExt: function(asArray)
1762		{
1763			var $this = $(this);
1764			var AU = $this.data('AU');
1765			var allowedExt = AU.settings.allowExt;
1766			
1767			return (asArray===true)?allowedExt:allowedExt.join('|');
1768		},
1769		getMaxFileNum: function(asArray)
1770		{
1771			var AU = $(this).data('AU');
1772			return AU.settings.maxFiles;
1773		},
1774		checkFile: function(name, size)
1775		{
1776			var AU = $(this).data('AU');
1777			return AU.checkFile(name, size) == '';
1778		},
1779		checkEnable: function(){
1780			return $(this).data('AU').settings.enable;
1781		},
1782		getFiles: function(){
1783			var AU = $(this).data('AU');
1784            if (AU) return AU.files;
1785            return 0;
1786		},
1787		enable:function()
1788		{
1789			return this.each(function()
1790			{
1791				var AU = $(this).data('AU');
1792				AU.enable(true);
1793			});
1794		},
1795		disable:function()
1796		{
1797			return this.each(function()
1798			{
1799				var AU = $(this).data('AU');
1800				AU.enable(false);
1801			});
1802		},
1803		destroy : function()
1804		{
1805			return this.each(function()
1806			{
1807				var $this = $(this);
1808				var AU = $this.data('AU');//get ajax uploader object
1809				AU.clearQueue();//remove files in queue
1810				$this.removeData('AU').html('');//remove object and empty element
1811			});
1812		},
1813		option : function(option, value)
1814		{
1815			return this.each(function(){
1816				var AU = $(this).data('AU');
1817				return AU.options(option, value);
1818			});
1819		},
1820		debug: function(msg){
1821			//console.log(msg);
1822		}
1823	};
1824
1825
1826	//jquery standard recomendation write of plugins
1827	$.fn.ajaxupload = function(method, options)
1828	{
1829		if(methods[method])
1830		{
1831			return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
1832		}
1833		else if(typeof method === 'object' || !method)
1834		{
1835			return methods.init.apply(this, arguments);
1836		}
1837		else
1838		{
1839			$.error('Method ' + method + ' does not exist on jQuery.AjaxUploader');
1840		}
1841	};
1842
1843})(jQuery);
1844
1845
1846
1847///////EXTERNAL PLUGINSSS
1848
1849/*
1850 * Javascript EXIF Reader - jQuery plugin 0.1.4
1851 * Copyright (c) 2008 Jacob Seidelin, [email protected], http://blog.nihilogic.dk/
1852 * Licensed under the MPL License [http://www.nihilogic.dk/licenses/mpl-license.txt]
1853 * Edit from Alban Xhaferllari file reader http://www.albanx.com
1854 */
1855
1856(function($) {
1857	
1858	function _arrayBufferToBase64( buffer ) {
1859	    var binary = '';
1860	    var bytes = new Uint8Array( buffer );
1861	    var len = bytes.byteLength;
1862	    for (var i = 0; i < len; i++) {
1863	        binary += String.fromCharCode( bytes[ i ] );
1864	    }
1865	    return binary;
1866	}
1867	
1868	
1869	var BinaryFile = function(strData, iDataOffset, iDataLength) {
1870		if (typeof strData == "object") {
1871	    	strData = _arrayBufferToBase64(strData); 
1872		}
1873	    var data = strData;
1874	    var dataOffset = iDataOffset || 0;
1875	    var dataLength = 0;
1876	
1877	    this.getRawData = function() {
1878	        return data;
1879	    };
1880	
1881	    if (typeof strData == "string") {
1882	        dataLength = iDataLength || data.length;
1883	
1884	        this.getByteAt = function(iOffset) {
1885	            return data.charCodeAt(iOffset + dataOffset) & 0xFF;
1886	        };
1887	    }else if (typeof strData == "unknown") {
1888	        dataLength = iDataLength || IEBinary_getLength(data);
1889	
1890	        this.getByteAt = function(iOffset) {
1891	            return IEBinary_getByteAt(data, iOffset + dataOffset);
1892	        };
1893	    }
1894	
1895	    this.getLength = function() {
1896	        return dataLength;
1897	    };
1898	
1899	    this.getSByteAt = function(iOffset) {
1900	        var iByte = this.getByteAt(iOffset);
1901	        if (iByte > 127)
1902	            return iByte - 256;
1903	        else
1904	            return iByte;
1905	    };
1906	
1907	    this.getShortAt = function(iOffset, bBigEndian) {
1908	        var iShort = bBigEndian ?
1909	            (this.getByteAt(iOffset) << 8) + this.getByteAt(iOffset + 1)
1910	            : (this.getByteAt(iOffset + 1) << 8) + this.getByteAt(iOffset);
1911	        if (iShort < 0) iShort += 65536;
1912	        return iShort;
1913	    };
1914	    this.getSShortAt = function(iOffset, bBigEndian) {
1915	        var iUShort = this.getShortAt(iOffset, bBigEndian);
1916	        if (iUShort > 32767)
1917	            return iUShort - 65536;
1918	        else
1919	            return iUShort;
1920	    };
1921	    this.getLongAt = function(iOffset, bBigEndian) {
1922	        var iByte1 = this.getByteAt(iOffset),
1923	            iByte2 = this.getByteAt(iOffset + 1),
1924	            iByte3 = this.getByteAt(iOffset + 2),
1925	            iByte4 = this.getByteAt(iOffset + 3);
1926	
1927	        var iLong = bBigEndian ?
1928	            (((((iByte1 << 8) + iByte2) << 8) + iByte3) << 8) + iByte4
1929	            : (((((iByte4 << 8) + iByte3) << 8) + iByte2) << 8) + iByte1;
1930	        if (iLong < 0) iLong += 4294967296;
1931	        return iLong;
1932	    };
1933	    this.getSLongAt = function(iOffset, bBigEndian) {
1934	        var iULong = this.getLongAt(iOffset, bBigEndian);
1935	        if (iULong > 2147483647)
1936	            return iULong - 4294967296;
1937	        else
1938	            return iULong;
1939	    };
1940	    this.getStringAt = function(iOffset, iLength) {
1941	        var aStr = [];
1942	        for (var i=iOffset,j=0;i<iOffset+iLength;i++,j++) {
1943	            aStr[j] = String.fromCharCode(this.getByteAt(i));
1944	        }
1945	        return aStr.join("");
1946	    };
1947	
1948	    this.getCharAt = function(iOffset) {
1949	        return String.fromCharCode(this.getByteAt(iOffset));
1950	    };
1951	    this.toBase64 = function() {
1952	        return window.btoa(data);
1953	    };
1954	    this.fromBase64 = function(strBase64) {
1955	        data = window.atob(strBase64);
1956	    };
1957	};
1958	
1959	var EXIF = {};
1960	
1961	(function() {
1962	var bDebug = false;
1963	
1964	EXIF.Tags = {
1965	
1966	    // version tags
1967	    0x9000 : "ExifVersion",         // EXIF version
1968	    0xA000 : "FlashpixVersion",     // Flashpix format version
1969	
1970	    // colorspace tags
1971	    0xA001 : "ColorSpace",          // Color space information tag
1972	
1973	    // image configuration
1974	    0xA002 : "PixelXDimension",     // Valid width of meaningful image
1975	    0xA003 : "PixelYDimension",     // Valid height of meaningful image
1976	    0x9101 : "ComponentsConfiguration", // Information about channels
1977	    0x9102 : "CompressedBitsPerPixel",  // Compressed bits per pixel
1978	
1979	    // user information
1980	    0x927C : "MakerNote",           // Any desired information written by the manufacturer
1981	    0x9286 : "UserComment",         // Comments by user
1982	
1983	    // related file
1984	    0xA004 : "RelatedSoundFile",        // Name of related sound file
1985	
1986	    // date and time
1987	    0x9003 : "DateTimeOriginal",        // Date and time when the original image was generated
1988	    0x9004 : "DateTimeDigitized",       // Date and time when the image was stored digitally
1989	    0x9290 : "SubsecTime",          // Fractions of seconds for DateTime
1990	    0x9291 : "SubsecTimeOriginal",      // Fractions of seconds for DateTimeOriginal
1991	    0x9292 : "SubsecTimeDigitized",     // Fractions of seconds for DateTimeDigitized
1992	
1993	    // picture-taking conditions
1994	    0x829A : "ExposureTime",        // Exposure time (in seconds)
1995	    0x829D : "FNumber",         // F number
1996	    0x8822 : "ExposureProgram",     // Exposure program
1997	    0x8824 : "SpectralSensitivity",     // Spectral sensitivity
1998	    0x8827 : "ISOSpeedRatings",     // ISO speed rating
1999	    0x8828 : "OECF",            // Optoelectric conversion factor
2000	    0x9201 : "ShutterSpeedValue",       // Shutter speed
2001	    0x9202 : "ApertureValue",       // Lens aperture
2002	    0x9203 : "BrightnessValue",     // Value of brightness
2003	    0x9204 : "ExposureBias",        // Exposure bias
2004	    0x9205 : "MaxApertureValue",        // Smallest F number of lens
2005	    0x9206 : "SubjectDistance",     // Distance to subject in meters
2006	    0x9207 : "MeteringMode",        // Metering mode
2007	    0x9208 : "LightSource",         // Kind of light source
2008	    0x9209 : "Flash",           // Flash status
2009	    0x9214 : "SubjectArea",         // Location and area of main subject
2010	    0x920A : "FocalLength",         // Focal length of the lens in mm
2011	    0xA20B : "FlashEnergy",         // Strobe energy in BCPS
2012	    0xA20C : "SpatialFrequencyResponse",    //
2013	    0xA20E : "FocalPlaneXResolution",   // Number of pixels in width direction per FocalPlaneResolutionUnit
2014	    0xA20F : "FocalPlaneYResolution",   // Number of pixels in height direction per FocalPlaneResolutionUnit
2015	    0xA210 : "FocalPlaneResolutionUnit",    // Unit for measuring FocalPlaneXResolution and FocalPlaneYResolution
2016	    0xA214 : "SubjectLocation",     // Location of subject in image
2017	    0xA215 : "ExposureIndex",       // Exposure index selected on camera
2018	    0xA217 : "SensingMethod",       // Image sensor type
2019	    0xA300 : "FileSource",          // Image source (3 == DSC)
2020	    0xA301 : "SceneType",           // Scene type (1 == directly photographed)
2021	    0xA302 : "CFAPattern",          // Color filter array geometric pattern
2022	    0xA401 : "CustomRendered",      // Special processing
2023	    0xA402 : "ExposureMode",        // Exposure mode
2024	    0xA403 : "WhiteBalance",        // 1 = auto white balance, 2 = manual
2025	    0xA404 : "DigitalZoomRation",       // Digital zoom ratio
2026	    0xA405 : "FocalLengthIn35mmFilm",   // Equivalent foacl length assuming 35mm film camera (in mm)
2027	    0xA406 : "SceneCaptureType",        // Type of scene
2028	    0xA407 : "GainControl",         // Degree of overall image gain adjustment
2029	    0xA408 : "Contrast",            // Direction of contrast processing applied by camera
2030	    0xA409 : "Saturation",          // Direction of saturation processing applied by camera
2031	    0xA40A : "Sharpness",           // Direction of sharpness processing applied by camera
2032	    0xA40B : "DeviceSettingDescription",    //
2033	    0xA40C : "SubjectDistanceRange",    // Distance to subject
2034	
2035	    // other tags
2036	    0xA005 : "InteroperabilityIFDPointer",
2037	    0xA420 : "ImageUniqueID"        // Identifier assigned uniquely to each image
2038	};
2039	
2040	EXIF.TiffTags = {
2041	    0x0100 : "ImageWidth",
2042	    0x0101 : "ImageHeight",
2043	    0x8769 : "ExifIFDPointer",
2044	    0x8825 : "GPSInfoIFDPointer",
2045	    0xA005 : "InteroperabilityIFDPointer",
2046	    0x0102 : "BitsPerSample",
2047	    0x0103 : "Compression",
2048	    0x0106 : "PhotometricInterpretation",
2049	    0x0112 : "Orientation",
2050	    0x0115 : "SamplesPerPixel",
2051	    0x011C : "PlanarConfiguration",
2052	    0x0212 : "YCbCrSubSampling",
2053	    0x0213 : "YCbCrPositioning",
2054	    0x011A : "XResolution",
2055	    0x011B : "YResolution",
2056	    0x0128 : "ResolutionUnit",
2057	    0x0111 : "StripOffsets",
2058	    0x0116 : "RowsPerStrip",
2059	    0x0117 : "StripByteCounts",
2060	    0x0201 : "JPEGInterchangeFormat",
2061	    0x0202 : "JPEGInterchangeFormatLength",
2062	    0x012D : "TransferFunction",
2063	    0x013E : "WhitePoint",
2064	    0x013F : "PrimaryChromaticities",
2065	    0x0211 : "YCbCrCoefficients",
2066	    0x0214 : "ReferenceBlackWhite",
2067	    0x0132 : "DateTime",
2068	    0x010E : "ImageDescription",
2069	    0x010F : "Make",
2070	    0x0110 : "Model",
2071	    0x0131 : "Software",
2072	    0x013B : "Artist",
2073	    0x8298 : "Copyright"
2074	};
2075	
2076	EXIF.GPSTags = {
2077	    0x0000 : "GPSVersionID",
2078	    0x0001 : "GPSLatitudeRef",
2079	    0x0002 : "GPSLatitude",
2080	    0x0003 : "GPSLongitudeRef",
2081	    0x0004 : "GPSLongitude",
2082	    0x0005 : "GPSAltitudeRef",
2083	    0x0006 : "GPSAltitude",
2084	    0x0007 : "GPSTimeStamp",
2085	    0x0008 : "GPSSatellites",
2086	    0x0009 : "GPSStatus",
2087	    0x000A : "GPSMeasureMode",
2088	    0x000B : "GPSDOP",
2089	    0x000C : "GPSSpeedRef",
2090	    0x000D : "GPSSpeed",
2091	    0x000E : "GPSTrackRef",
2092	    0x000F : "GPSTrack",
2093	    0x0010 : "GPSImgDirectionRef",
2094	    0x0011 : "GPSImgDirection",
2095	    0x0012 : "GPSMapDatum",
2096	    0x0013 : "GPSDestLatitudeRef",
2097	    0x0014 : "GPSDestLatitude",
2098	    0x0015 : "GPSDestLongitudeRef",
2099	    0x0016 : "GPSDestLongitude",
2100	    0x0017 : "GPSDestBearingRef",
2101	    0x0018 : "GPSDestBearing",
2102	    0x0019 : "GPSDestDistanceRef",
2103	    0x001A : "GPSDestDistance",
2104	    0x001B : "GPSProcessingMethod",
2105	    0x001C : "GPSAreaInformation",
2106	    0x001D : "GPSDateStamp",
2107	    0x001E : "GPSDifferential"
2108	};
2109	
2110	EXIF.StringValues = {
2111	    ExposureProgram : {
2112	        0 : "Not defined",
2113	        1 : "Manual",
2114	        2 : "Normal program",
2115	        3 : "Aperture priority",
2116	        4 : "Shutter priority",
2117	        5 : "Creative program",
2118	        6 : "Action program",
2119	        7 : "Portrait mode",
2120	        8 : "Landscape mode"
2121	    },
2122	    MeteringMode : {
2123	        0 : "Unknown",
2124	        1 : "Average",
2125	        2 : "CenterWeightedAverage",
2126	        3 : "Spot",
2127	        4 : "MultiSpot",
2128	        5 : "Pattern",
2129	        6 : "Partial",
2130	        255 : "Other"
2131	    },
2132	    LightSource : {
2133	        0 : "Unknown",
2134	        1 : "Daylight",
2135	        2 : "Fluorescent",
2136	        3 : "Tungsten (incandescent light)",
2137	        4 : "Flash",
2138	        9 : "Fine weather",
2139	        10 : "Cloudy weather",
2140	        11 : "Shade",
2141	        12 : "Daylight fluorescent (D 5700 - 7100K)",
2142	        13 : "Day white fluorescent (N 4600 - 5400K)",
2143	        14 : "Cool white fluorescent (W 3900 - 4500K)",
2144	        15 : "White fluorescent (WW 3200 - 3700K)",
2145	        17 : "Standard light A",
2146	        18 : "Standard light B",
2147	        19 : "Standard light C",
2148	        20 : "D55",
2149	        21 : "D65",
2150	        22 : "D75",
2151	        23 : "D50",
2152	        24 : "ISO studio tungsten",
2153	        255 : "Other"
2154	    },
2155	    Flash : {
2156	        0x0000 : "Flash did not fire",
2157	        0x0001 : "Flash fired",
2158	        0x0005 : "Strobe return light not detected",
2159	        0x0007 : "Strobe return light detected",
2160	        0x0009 : "Flash fired, compulsory flash mode",
2161	        0x000D : "Flash fired, compulsory flash mode, return light not detected",
2162	        0x000F : "Flash fired, compulsory flash mode, return light detected",
2163	        0x0010 : "Flash did not fire, compulsory flash mode",
2164	        0x0018 : "Flash did not fire, auto mode",
2165	        0x0019 : "Flash fired, auto mode",
2166	        0x001D : "Flash fired, auto mode, return light not detected",
2167	        0x001F : "Flash fired, auto mode, return light detected",
2168	        0x0020 : "No flash function",
2169	        0x0041 : "Flash fired, red-eye reduction mode",
2170	        0x0045 : "Flash fired, red-eye reduction mode, return light not detected",
2171	        0x0047 : "Flash fired, red-eye reduction mode, return light detected",
2172	        0x0049 : "Flash fired, compulsory flash mode, red-eye reduction mode",
2173	        0x004D : "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected",
2174	        0x004F : "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected",
2175	        0x0059 : "Flash fired, auto mode, red-eye reduction mode",
2176	        0x005D : "Flash fired, auto mode, return light not detected, red-eye reduction mode",
2177	        0x005F : "Flash fired, auto mode, return light detected, red-eye reduction mode"
2178	    },
2179	    SensingMethod : {
2180	        1 : "Not defined",
2181	        2 : "One-chip color area sensor",
2182	        3 : "Two-chip color area sensor",
2183	        4 : "Three-chip color area sensor",
2184	        5 : "Color sequential area sensor",
2185	        7 : "Trilinear sensor",
2186	        8 : "Color sequential linear sensor"
2187	    },
2188	    SceneCaptureType : {
2189	        0 : "Standard",
2190	        1 : "Landscape",
2191	        2 : "Portrait",
2192	        3 : "Night scene"
2193	    },
2194	    SceneType : {
2195	        1 : "Directly photographed"
2196	    },
2197	    CustomRendered : {
2198	        0 : "Normal process",
2199	        1 : "Custom process"
2200	    },
2201	    WhiteBalance : {
2202	        0 : "Auto white balance",
2203	        1 : "Manual white balance"
2204	    },
2205	    GainControl : {
2206	        0 : "None",
2207	        1 : "Low gain up",
2208	        2 : "High gain up",
2209	        3 : "Low gain down",
2210	        4 : "High gain down"
2211	    },
2212	    Contrast : {
2213	        0 : "Normal",
2214	        1 : "Soft",
2215	        2 : "Hard"
2216	    },
2217	    Saturation : {
2218	        0 : "Normal",
2219	        1 : "Low saturation",
2220	        2 : "High saturation"
2221	    },
2222	    Sharpness : {
2223	        0 : "Normal",
2224	        1 : "Soft",
2225	        2 : "Hard"
2226	    },
2227	    SubjectDistanceRange : {
2228	        0 : "Unknown",
2229	        1 : "Macro",
2230	        2 : "Close view",
2231	        3 : "Distant view"
2232	    },
2233	    FileSource : {
2234	        3 : "DSC"
2235	    },
2236	
2237	    Components : {
2238	        0 : "",
2239	        1 : "Y",
2240	        2 : "Cb",
2241	        3 : "Cr",
2242	        4 : "R",
2243	        5 : "G",
2244	        6 : "B"
2245	    }
2246	};
2247	
2248	function addEvent(oElement, strEvent, fncHandler)
2249	{
2250	    if (oElement.addEventListener) {
2251	        oElement.addEventListener(strEvent, fncHandler, false);
2252	    } else if (oElement.attachEvent) {
2253	        oElement.attachEvent("on" + strEvent, fncHandler);
2254	    }
2255	}
2256	
2257	
2258	function imageHasData(oImg)
2259	{
2260	    return !!(oImg.exifdata);
2261	}
2262
2263	
2264	function findEXIFinJPEG(oFile) {
2265	    if (oFile.getByteAt(0) != 0xFF || oFile.getByteAt(1) != 0xD8) {
2266	        return false; // not a valid jpeg
2267	    }
2268	
2269	    var iOffset = 2;
2270	    var iLength = oFile.getLength();
2271	    while (iOffset < iLength) {
2272	        if (oFile.getByteAt(iOffset) != 0xFF) {
2273	            if (bDebug) console.log("Not a valid marker at offset " + iOffset + ", found: " + oFile.getByteAt(iOffset));
2274	            return false; // not a valid marker, something is wrong
2275	        }
2276	
2277	        var iMarker = oFile.getByteAt(iOffset+1);
2278	
2279	        // we could implement handling for other markers here,
2280	        // but we're only looking for 0xFFE1 for EXIF data
2281	
2282	        if (iMarker == 22400) {
2283	            if (bDebug) console.log("Found 0xFFE1 marker");
2284	            return readEXIFData(oFile, iOffset + 4, oFile.getShortAt(iOffset+2, true)-2);
2285	            // iOffset += 2 + oFile.getShortAt(iOffset+2, true);
2286	            // WTF?
2287	
2288	        } else if (iMarker == 225) {
2289	            // 0xE1 = Application-specific 1 (for EXIF)
2290	            if (bDebug) console.log("Found 0xFFE1 marker");
2291	            return readEXIFData(oFile, iOffset + 4, oFile.getShortAt(iOffset+2, true)-2);
2292	
2293	        } else {
2294	            iOffset += 2 + oFile.getShortAt(iOffset+2, true);
2295	        }
2296	
2297	    }
2298	
2299	}
2300	
2301	
2302	function readTags(oFile, iTIFFStart, iDirStart, oStrings, bBigEnd)
2303	{
2304	    var iEntries = oFile.getShortAt(iDirStart, bBigEnd);
2305	    var oTags = {};
2306	    for (var i=0;i<iEntries;i++) {
2307	        var iEntryOffset = iDirStart + i*12 + 2;
2308	        var strTag = oStrings[oFile.getShortAt(iEntryOffset, bBigEnd)];
2309	        if (!strTag && bDebug) console.log("Unknown tag: " + oFile.getShortAt(iEntryOffset, bBigEnd));
2310	        oTags[strTag] = readTagValue(oFile, iEntryOffset, iTIFFStart, iDirStart, bBigEnd);
2311	    }
2312	    return oTags;
2313	}
2314	
2315	
2316	function readTagValue(oFile, iEntryOffset, iTIFFStart, iDirStart, bBigEnd)
2317	{
2318	    var iType = oFile.getShortAt(iEntryOffset+2, bBigEnd);
2319	    var iNumValues = oFile.getLongAt(iEntryOffset+4, bBigEnd);
2320	    var iValueOffset = oFile.getLongAt(iEntryOffset+8, bBigEnd) + iTIFFStart;
2321	
2322	    switch (iType) {
2323	        case 1: // byte, 8-bit unsigned int
2324	        case 7: // undefined, 8-bit byte, value depending on field
2325	            if (iNumValues == 1) {
2326	                return oFile.getByteAt(iEntryOffset + 8, bBigEnd);
2327	            } else {
2328	                var iValOffset = iNumValues > 4 ? iValueOffset : (iEntryOffset + 8);
2329	                var aVals = [];
2330	                for (var n=0;n<iNumValues;n++) {
2331	                    aVals[n] = oFile.getByteAt(iValOffset + n);
2332	                }
2333	                return aVals;
2334	            }
2335	            break;
2336	
2337	        case 2: // ascii, 8-bit byte
2338	            var iStringOffset = iNumValues > 4 ? iValueOffset : (iEntryOffset + 8);
2339	            return oFile.getStringAt(iStringOffset, iNumValues-1);
2340	            // break;
2341	
2342	        case 3: // short, 16 bit int
2343	            if (iNumValues == 1) {
2344	                return oFile.getShortAt(iEntryOffset + 8, bBigEnd);
2345	            } else {
2346	                var iValOffset = iNumValues > 2 ? iValueOffset : (iEntryOffset + 8);
2347	                var aVals = [];
2348	                for (var n=0;n<iNumValues;n++) {
2349	                    aVals[n] = oFile.getShortAt(iValOffset + 2*n, bBigEnd);
2350	                }
2351	                return aVals;
2352	            }
2353	            // break;
2354	
2355	        case 4: // long, 32 bit int
2356	            if (iNumValues == 1) {
2357	                return oFile.getLongAt(iEntryOffset + 8, bBigEnd);
2358	            } else {
2359	                var aVals = [];
2360	                for (var n=0;n<iNumValues;n++) {
2361	                    aVals[n] = oFile.getLongAt(iValueOffset + 4*n, bBigEnd);
2362	                }
2363	                return aVals;
2364	            }
2365	            break;
2366	        case 5: // rational = two long values, first is numerator, second is denominator
2367	            if (iNumValues == 1) {
2368	                return oFile.getLongAt(iValueOffset, bBigEnd) / oFile.getLongAt(iValueOffset+4, bBigEnd);
2369	            } else {
2370	                var aVals = [];
2371	                for (var n=0;n<iNumValues;n++) {
2372	                    aVals[n] = oFile.getLongAt(iValueOffset + 8*n, bBigEnd) / oFile.getLongAt(iValueOffset+4 + 8*n, bBigEnd);
2373	                }
2374	                return aVals;
2375	            }
2376	            break;
2377	        case 9: // slong, 32 bit signed int
2378	            if (iNumValues == 1) {
2379	                return oFile.getSLongAt(iEntryOffset + 8, bBigEnd);
2380	            } else {
2381	                var aVals = [];
2382	                for (var n=0;n<iNumValues;n++) {
2383	                    aVals[n] = oFile.getSLongAt(iValueOffset + 4*n, bBigEnd);
2384	                }
2385	                return aVals;
2386	            }
2387	            break;
2388	        case 10: // signed rational, two slongs, first is numerator, second is denominator
2389	            if (iNumValues == 1) {
2390	                return oFile.getSLongAt(iValueOffset, bBigEnd) / oFile.getSLongAt(iValueOffset+4, bBigEnd);
2391	            } else {
2392	                var aVals = [];
2393	                for (var n=0;n<iNumValues;n++) {
2394	                    aVals[n] = oFile.getSLongAt(iValueOffset + 8*n, bBigEnd) / oFile.getSLongAt(iValueOffset+4 + 8*n, bBigEnd);
2395	                }
2396	                return aVals;
2397	            }
2398	            break;
2399	    }
2400	}
2401	
2402	
2403	function readEXIFData(oFile, iStart, iLength)
2404	{
2405	    if (oFile.getStringAt(iStart, 4) != "Exif") {
2406	        if (bDebug) console.log("Not valid EXIF data! " + oFile.getStringAt(iStart, 4));
2407	        return false;
2408	    }
2409	
2410	    var bBigEnd;
2411	
2412	    var iTIFFOffset = iStart + 6;
2413	
2414	    // test for TIFF validity and endianness
2415	    if (oFile.getShortAt(iTIFFOffset) == 0x4949) {
2416	        bBigEnd = false;
2417	    } else if (oFile.getShortAt(iTIFFOffset) == 0x4D4D) {
2418	        bBigEnd = true;
2419	    } else {
2420	        if (bDebug) console.log("Not valid TIFF data! (no 0x4949 or 0x4D4D)");
2421	        return false;
2422	    }
2423	
2424	    if (oFile.getShortAt(iTIFFOffset+2, bBigEnd) != 0x002A) {
2425	        if (bDebug) console.log("Not valid TIFF data! (no 0x002A)");
2426	        return false;
2427	    }
2428	
2429	    if (oFile.getLongAt(iTIFFOffset+4, bBigEnd) != 0x00000008) {
2430	        if (bDebug) console.log("Not valid TIFF data! (First offset not 8)", oFile.getShortAt(iTIFFOffset+4, bBigEnd));
2431	        return false;
2432	    }
2433	
2434	    var oTags = readTags(oFile, iTIFFOffset, iTIFFOffset+8, EXIF.TiffTags, bBigEnd);
2435	
2436	    if (oTags.ExifIFDPointer) {
2437	        var oEXIFTags = readTags(oFile, iTIFFOffset, iTIFFOffset + oTags.ExifIFDPointer, EXIF.Tags, bBigEnd);
2438	        for (var strTag in oEXIFTags) {
2439	            switch (strTag) {
2440	                case "LightSource" :
2441	                case "Flash" :
2442	                case "MeteringMode" :
2443	                case "ExposureProgram" :
2444	                case "SensingMethod" :
2445	                case "SceneCaptureType" :
2446	                case "SceneType" :
2447	                case "CustomRendered" :
2448	                case "WhiteBalance" :
2449	                case "GainControl" :
2450	                case "Contrast" :
2451	                case "Saturation" :
2452	                case "Sharpness" :
2453	                case "SubjectDistanceRange" :
2454	                case "FileSource" :
2455	                    oEXIFTags[strTag] = EXIF.StringValues[strTag][oEXIFTags[strTag]];
2456	                    break;
2457	
2458	                case "ExifVersion" :
2459	                case "FlashpixVersion" :
2460	                    oEXIFTags[strTag] = String.fromCharCode(oEXIFTags[strTag][0], oEXIFTags[strTag][1], oEXIFTags[strTag][2], oEXIFTags[strTag][3]);
2461	                    break;
2462	
2463	                case "ComponentsConfiguration" :
2464	                    oEXIFTags[strTag] =
2465	                        EXIF.StringValues.Components[oEXIFTags[strTag][0]]
2466	                        + EXIF.StringValues.Components[oEXIFTags[strTag][1]]
2467	                        + EXIF.StringValues.Components[oEXIFTags[strTag][2]]
2468	                        + EXIF.StringValues.Components[oEXIFTags[strTag][3]];
2469	                    break;
2470	            }
2471	            oTags[strTag] = oEXIFTags[strTag];
2472	        }
2473	    }
2474	
2475	    if (oTags.GPSInfoIFDPointer) {
2476	        var oGPSTags = readTags(oFile, iTIFFOffset, iTIFFOffset + oTags.GPSInfoIFDPointer, EXIF.GPSTags, bBigEnd);
2477	        for (var strTag in oGPSTags) {
2478	            switch (strTag) {
2479	                case "GPSVersionID" :
2480	                    oGPSTags[strTag] = oGPSTags[strTag][0]
2481	                        + "." + oGPSTags[strTag][1]
2482	                        + "." + oGPSTags[strTag][2]
2483	                        + "." + oGPSTags[strTag][3];
2484	                    break;
2485	            }
2486	            oTags[strTag] = oGPSTags[strTag];
2487	        }
2488	    }
2489	
2490	    return oTags;
2491	}
2492	
2493	EXIF.getTag = function(oImg, strTag)
2494	{
2495	    if (!imageHasData(oImg)) return;
2496	    return oImg.exifdata[strTag];
2497	};
2498	
2499	EXIF.getAllTags = function(oImg)
2500	{
2501	    if (!imageHasData(oImg)) return {};
2502	    var oData = oImg.exifdata;
2503	    var oAllTags = {};
2504	    for (var a in oData) {
2505	        if (oData.hasOwnProperty(a)) {
2506	            oAllTags[a] = oData[a];
2507	        }
2508	    }
2509	    return oAllTags;
2510	};
2511	
2512	EXIF.pretty = function(oImg)
2513	{
2514	    if (!imageHasData(oImg)) return "";
2515	    var oData = oImg.exifdata;
2516	    var strPretty = "";
2517	    for (var a in oData) {
2518	        if (oData.hasOwnProperty(a)) {
2519	            if (typeof oData[a] == "object") {
2520	                strPretty += a + " : [" + oData[a].length + " values]\r\n";
2521	            } else {
2522	                strPretty += a + " : " + oData[a] + "\r\n";
2523	            }
2524	        }
2525	    }
2526	    return strPretty;
2527	};
2528	
2529	EXIF.readFromBinaryFile = function(oFile) {
2530	    return findEXIFinJPEG(oFile);
2531	};
2532	
2533	EXIF.getFilePart = function(file) {
2534	    if (file.slice) {
2535	        filePart = file.slice(0, 131072);
2536	    } else if (file.webkitSlice) {
2537	        filePart = file.webkitSlice(0, 131072);
2538	    } else if (file.mozSlice) {
2539	        filePart = file.mozSlice(0, 131072);
2540	    } else {
2541	        filePart = file;
2542	    }
2543	
2544	    return filePart;
2545	};
2546	
2547	// load data for images manually
2548	$.fn.exif = function(strTag) {
2549	    var aStrings = [];
2550	    this.each(function() {
2551	        aStrings.push(EXIF.getTag(this, strTag));
2552	    });
2553	    return aStrings;
2554	};
2555	
2556	$.fn.exifAll = function() {
2557	    var aStrings = [];
2558	    this.each(function() {
2559	        aStrings.push(EXIF.getAllTags(this));
2560	    });
2561	    return aStrings;
2562	};
2563	
2564	$.fn.exifPretty = function() {
2565	    var aStrings = [];
2566	    this.each(function() {
2567	        aStrings.push(EXIF.pretty(this));
2568	    });
2569	    return aStrings;
2570	};
2571	
2572	$.fn.fileExif = function(callback) 
2573	{
2574	    var reader = new FileReader();
2575	    reader.onload = function(event) {
2576	        var binaryResponse = new BinaryFile(event.target.result);
2577	        
2578	        callback(EXIF.readFromBinaryFile(binaryResponse));
2579	    };
2580	    
2581	    reader.readAsArrayBuffer(EXIF.getFilePart(this[0].files[0]));
2582	};
2583
2584	$.fileExif = function(file, callback)
2585	{       
2586		var reader = new FileReader();
2587	    reader.onload = function(event){
2588	    	var binaryResponse = new BinaryFile(event.target.result); 
2589	    	callback(EXIF.readFromBinaryFile(binaryResponse));
2590	    };
2591	    reader.readAsArrayBuffer(EXIF.getFilePart(file));	
2592	};
2593
2594})();
2595
2596})(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.