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¶m2=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.