1/** 2 * Ajax upload 3 * Project page - http://valums.com/ajax-upload/ 4 * Copyright (c) 2008 Andris Valums, http://valums.com 5 * Licensed under the MIT license (http://valums.com/mit-license/) 6 * Version 3.5 (23.06.2009) 7 */ 8 9/** 10 * Changes from the previous version: 11 * 1. Added better JSON handling that allows to use 'application/javascript' as a response 12 * 2. Added demo for usage with jQuery UI dialog 13 * 3. Fixed IE "mixed content" issue when used with secure connections 14 * 15 * For the full changelog please visit: 16 * http://valums.com/ajax-upload-changelog/ 17 */ 18 19(function(){ 20 21var d = document, w = window; 22 23/** 24 * Get element by id 25 */ 26function get(element){ 27 if (typeof element == "string") 28 element = d.getElementById(element); 29 return element; 30} 31 32/** 33 * Attaches event to a dom element 34 */ 35function addEvent(el, type, fn){ 36 if (w.addEventListener){ 37 el.addEventListener(type, fn, false); 38 } else if (w.attachEvent){ 39 var f = function(){ 40 fn.call(el, w.event); 41 }; 42 el.attachEvent('on' + type, f) 43 } 44} 45 46 47/** 48 * Creates and returns element from html chunk 49 */ 50var toElement = function(){ 51 var div = d.createElement('div'); 52 return function(html){ 53 div.innerHTML = html; 54 var el = div.childNodes[0]; 55 div.removeChild(el); 56 return el; 57 } 58}(); 59 60function hasClass(ele,cls){ 61 return ele.className.match(new RegExp('(\\s|^)'+cls+'(\\s|$)')); 62} 63function addClass(ele,cls) { 64 if (!hasClass(ele,cls)) ele.className += " "+cls; 65} 66function removeClass(ele,cls) { 67 var reg = new RegExp('(\\s|^)'+cls+'(\\s|$)'); 68 ele.className=ele.className.replace(reg,' '); 69} 70 71// getOffset function copied from jQuery lib (http://jquery.com/) 72if (document.documentElement["getBoundingClientRect"]){ 73 // Get Offset using getBoundingClientRect 74 // http://ejohn.org/blog/getboundingclientrect-is-awesome/ 75 var getOffset = function(el){ 76 var box = el.getBoundingClientRect(), 77 doc = el.ownerDocument, 78 body = doc.body, 79 docElem = doc.documentElement, 80 81 // for ie 82 clientTop = docElem.clientTop || body.clientTop || 0, 83 clientLeft = docElem.clientLeft || body.clientLeft || 0, 84 85 // In Internet Explorer 7 getBoundingClientRect property is treated as physical, 86 // while others are logical. Make all logical, like in IE8. 87 88 89 zoom = 1; 90 if (body.getBoundingClientRect) { 91 var bound = body.getBoundingClientRect(); 92 zoom = (bound.right - bound.left)/body.clientWidth; 93 } 94 if (zoom > 1){ 95 clientTop = 0; 96 clientLeft = 0; 97 } 98 var top = box.top/zoom + (window.pageYOffset || docElem && docElem.scrollTop/zoom || body.scrollTop/zoom) - clientTop, 99 left = box.left/zoom + (window.pageXOffset|| docElem && docElem.scrollLeft/zoom || body.scrollLeft/zoom) - clientLeft; 100 101 return { 102 top: top, 103 left: left 104 }; 105 } 106 107} else { 108 // Get offset adding all offsets 109 var getOffset = function(el){ 110 if (w.jQuery){ 111 return jQuery(el).offset(); 112 } 113 114 var top = 0, left = 0; 115 do { 116 top += el.offsetTop || 0; 117 left += el.offsetLeft || 0; 118 } 119 while (el = el.offsetParent); 120 121 return { 122 left: left, 123 top: top 124 }; 125 } 126} 127 128function getBox(el){ 129 var left, right, top, bottom; 130 var offset = getOffset(el); 131 left = offset.left; 132 top = offset.top; 133 134 right = left + el.offsetWidth; 135 bottom = top + el.offsetHeight; 136 137 return { 138 left: left, 139 right: right, 140 top: top, 141 bottom: bottom 142 }; 143} 144 145/** 146 * Crossbrowser mouse coordinates 147 */ 148function getMouseCoords(e){ 149 // pageX/Y is not supported in IE 150 // http://www.quirksmode.org/dom/w3c_cssom.html 151 if (!e.pageX && e.clientX){ 152 // In Internet Explorer 7 some properties (mouse coordinates) are treated as physical, 153 // while others are logical (offset). 154 var zoom = 1; 155 var body = document.body; 156 157 if (body.getBoundingClientRect) { 158 var bound = body.getBoundingClientRect(); 159 zoom = (bound.right - bound.left)/body.clientWidth; 160 } 161 162 return { 163 x: e.clientX / zoom + d.body.scrollLeft + d.documentElement.scrollLeft, 164 y: e.clientY / zoom + d.body.scrollTop + d.documentElement.scrollTop 165 }; 166 } 167 168 return { 169 x: e.pageX, 170 y: e.pageY 171 }; 172 173} 174/** 175 * Function generates unique id 176 */ 177var getUID = function(){ 178 var id = 0; 179 return function(){ 180 return 'ValumsAjaxUpload' + id++; 181 } 182}(); 183 184function fileFromPath(file){ 185 return file.replace(/.*(\/|\\)/, ""); 186} 187 188function getExt(file){ 189 return (/[.]/.exec(file)) ? /[^.]+$/.exec(file.toLowerCase()) : ''; 190} 191 192// Please use AjaxUpload , Ajax_upload will be removed in the next version 193Ajax_upload = AjaxUpload = function(button, options){ 194 if (button.jquery){ 195 // jquery object was passed 196 button = button[0]; 197 } else if (typeof button == "string" && /^#.*/.test(button)){ 198 button = button.slice(1); 199 } 200 button = get(button); 201 202 this._input = null; 203 this._button = button; 204 this._disabled = false;
205 this._submitting = false; 206 // Variable changes to true if the button was clicked 207 // 3 seconds ago (requred to fix Safari on Mac error) 208 this._justClicked = false; 209 this._parentDialog = d.body; 210 211 if (window.jQuery && jQuery.ui && jQuery.ui.dialog){ 212 var parentDialog = jQuery(this._button).parents('.ui-dialog'); 213 if (parentDialog.length){ 214 this._parentDialog = parentDialog[0]; 215 } 216 } 217 218 this._settings = { 219 // Location of the server-side upload script 220 action: 'upload.php', 221 // File upload name 222 name: 'userfile', 223 // Additional data to send 224 data: {}, 225 // Submit file as soon as it's selected 226 autoSubmit: true, 227 // The type of data that you're expecting back from the server. 228 // Html and xml are detected automatically. 229 // Only useful when you are using json data as a response. 230 // Set to "json" in that case. 231 responseType: false, 232 // When user selects a file, useful with autoSubmit disabled 233 onChange: function(file, extension){}, 234 // Callback to fire before file is uploaded 235 // You can return false to cancel upload 236 onSubmit: function(file, extension){}, 237 // Fired when file upload is completed 238 // WARNING! DO NOT USE "FALSE" STRING AS A RESPONSE! 239 onComplete: function(file, response) {} 240 }; 241 242 // Merge the users options with our defaults 243 for (var i in options) { 244 this._settings[i] = options[i]; 245 } 246 247 this._createInput(); 248 this._rerouteClicks(); 249} 250 251// assigning methods to our class 252AjaxUpload.prototype = { 253 setData : function(data){ 254 this._settings.data = data; 255 }, 256 disable : function(){ 257 this._disabled = true; 258 }, 259 enable : function(){ 260 this._disabled = false; 261 }, 262 // removes ajaxupload 263 destroy : function(){ 264 if(this._input){ 265 if(this._input.parentNode){ 266 this._input.parentNode.removeChild(this._input); 267 } 268 this._input = null; 269 } 270 }, 271 /** 272 * Creates invisible file input above the button 273 */ 274 _createInput : function(){ 275 var self = this; 276 var input = d.createElement("input"); 277 input.setAttribute('type', 'file'); 278 input.setAttribute('name', this._settings.name); 279 var styles = { 280 'position' : 'absolute'
281 ,'margin': '-5px 0 0 -175px' 282 ,'padding': 0 283 ,'width': '220px' 284 ,'height': '30px' 285 ,'fontSize': '14px' 286 ,'opacity': 0 287 ,'cursor': 'pointer' 288 ,'display' : 'none' 289 ,'zIndex' : 2147483583 //Max zIndex supported by Opera 9.0-9.2x 290 // Strange, I expected 2147483647 291 }; 292 for (var i in styles){ 293 input.style[i] = styles[i]; 294 } 295 296 // Make sure that element opacity exists 297 // (IE uses filter instead) 298 if ( ! (input.style.opacity === "0")){ 299 input.style.filter = "alpha(opacity=0)"; 300 } 301 302 this._parentDialog.appendChild(input); 303 304 addEvent(input, 'change', function(){ 305 // get filename from input 306 var file = fileFromPath(this.value); 307 if(self._settings.onChange.call(self, file, getExt(file)) == false ){ 308 return; 309 } 310 // Submit form when value is changed 311 if (self._settings.autoSubmit){ 312 self.submit(); 313 } 314 }); 315 316 // Fixing problem with Safari 317 // The problem is that if you leave input before the file select dialog opens 318 // it does not upload the file. 319 // As dialog opens slowly (it is a sheet dialog which takes some time to open) 320 // there is some time while you can leave the button. 321 // So we should not change display to none immediately 322 addEvent(input, 'click', function(){ 323 self.justClicked = true; 324 setTimeout(function(){ 325 // we will wait 3 seconds for dialog to open 326 self.justClicked = false; 327 }, 3000); 328 }); 329 330 this._input = input; 331 }, 332 _rerouteClicks : function (){ 333 var self = this; 334 335 // IE displays 'access denied' error when using this method 336 // other browsers just ignore click() 337 // addEvent(this._button, 'click', function(e){ 338 // self._input.click(); 339 // }); 340 341 var box, dialogOffset = {top:0, left:0}, over = false; 342 addEvent(self._button, 'mouseover', function(e){ 343 if (!self._input || over) return; 344 over = true; 345 box = getBox(self._button); 346 347 if (self._parentDialog != d.body){ 348 dialogOffset = getOffset(self._parentDialog); 349 } 350 }); 351 352 353 // we can't use mouseout on the button, 354 // because invisible input is over it 355 addEvent(document, 'mousemove', function(e){ 356 var input = self._input; 357 if (!input || !over) return; 358 359 if (self._disabled){ 360 removeClass(self._button, 'hover'); 361 input.style.display = 'none'; 362 return; 363 } 364 365 var c = getMouseCoords(e); 366 367 if ((c.x >= box.left) && (c.x <= box.right) && 368 (c.y >= box.top) && (c.y <= box.bottom)){ 369 input.style.top = c.y - dialogOffset.top + 'px'; 370 input.style.left = c.x - dialogOffset.left + 'px'; 371 input.style.display = 'block'; 372 addClass(self._button, 'hover'); 373 } else { 374 // mouse left the button 375 over = false; 376 if (!self.justClicked){ 377 input.style.display = 'none'; 378 } 379 removeClass(self._button, 'hover'); 380 } 381 }); 382 383 }, 384 /** 385 * Creates iframe with unique name 386 */ 387 _createIframe : function(){ 388 // unique name 389 // We cannot use getTime, because it sometimes return 390 // same value in safari :( 391 var id = getUID(); 392 393 // Remove ie6 "This page contains both secure and nonsecure items" prompt 394 // http://tinyurl.com/77w9wh 395 var iframe = toElement('<iframe src="javascript:false;" name="' + id + '" />'); 396 iframe.id = id; 397 iframe.style.display = 'none'; 398 d.body.appendChild(iframe); 399 return iframe; 400 }, 401 /** 402 * Upload file without refreshing the page 403 */ 404 submit : function(){ 405 var self = this, settings = this._settings; 406 407 if (this._input.value === ''){ 408 // there is no file 409 return; 410 } 411 412 // get filename from input 413 var file = fileFromPath(this._input.value); 414 415 // execute user event 416 if (! (settings.onSubmit.call(this, file, getExt(file)) == false)) { 417 // Create new iframe for this submission 418 var iframe = this._createIframe(); 419 420 // Do not submit if user function returns false 421 var form = this._createForm(iframe); 422 form.appendChild(this._input); 423 424 form.submit(); 425 426 d.body.removeChild(form); 427 form = null; 428 this._input = null; 429 430 // create new input 431 this._createInput(); 432 433 var toDeleteFlag = false;
434 435 addEvent(iframe, 'load', function(e){ 436 437 if (// For Safari 438 iframe.src == "javascript:'%3Chtml%3E%3C/html%3E';" || 439 // For FF, IE 440 iframe.src == "javascript:'<html></html>';"){ 441 442 // First time around, do not delete. 443 if( toDeleteFlag ){ 444 // Fix busy state in FF3 445 setTimeout( function() { 446 d.body.removeChild(iframe); 447 }, 0); 448 } 449 return; 450 } 451 452 var doc = iframe.contentDocument ? iframe.contentDocument : frames[iframe.id].document; 453 454 // fixing Opera 9.26 455 if (doc.readyState && doc.readyState != 'complete'){ 456 // Opera fires load event multiple times 457 // Even when the DOM is not ready yet 458 // this fix should not affect other browsers 459 return; 460 } 461 462 // fixing Opera 9.64 463 if (doc.body && doc.body.innerHTML == "false"){ 464 // In Opera 9.64 event was fired second time 465 // when body.innerHTML changed from false 466 // to server response approx. after 1 sec 467 return; 468 } 469 470 var response; 471 472 if (doc.XMLDocument){ 473 // response is a xml document IE property 474 response = doc.XMLDocument; 475 } else if (doc.body){ 476 // response is html document or plain text 477 response = doc.body.innerHTML; 478 if (settings.responseType && settings.responseType.toLowerCase() == 'json'){ 479 // If the document was sent as 'application/javascript' or 480 // 'text/javascript', then the browser wraps the text in a <pre> 481 // tag and performs html encoding on the contents. In this case, 482 // we need to pull the original text content from the text node's 483 // nodeValue property to retrieve the unmangled content. 484 // Note that IE6 only understands text/html 485 if (doc.body.firstChild && doc.body.firstChild.nodeName.toUpperCase() == 'PRE'){ 486 response = doc.body.firstChild.firstChild.nodeValue; 487 } 488 if (response) { 489 response = window["eval"]("(" + response + ")"); 490 } else { 491 response = {}; 492 } 493 } 494 } else { 495 // response is a xml document 496 var response = doc; 497 } 498 499 settings.onComplete.call(self, file, response); 500 501 // Reload blank page, so that reloading main page 502 // does not re-submit the post. Also, remember to 503 // delete the frame 504 toDeleteFlag = true; 505 506 // Fix IE mixed content issue 507 iframe.src = "javascript:'<html></html>';"; 508 }); 509 510 } else { 511 // clear input to allow user to select same file 512 // Doesn't work in IE6 513 // this._input.value = ''; 514 d.body.removeChild(this._input); 515 this._input = null; 516 517 // create new input 518 this._createInput(); 519 } 520 }, 521 /** 522 * Creates form, that will be submitted to iframe 523 */ 524 _createForm : function(iframe){ 525 var settings = this._settings; 526 527 // method, enctype must be specified here 528 // because changing this attr on the fly is not allowed in IE 6/7 529 var form = toElement('<form method="post" enctype="multipart/form-data"></form>'); 530 form.style.display = 'none'; 531 form.action = settings.action; 532 form.target = iframe.name; 533 d.body.appendChild(form); 534 535 // Create hidden input element for each data key 536 for (var prop in settings.data){ 537 var el = d.createElement("input"); 538 el.type = 'hidden'; 539 el.name = prop; 540 el.value = settings.data[prop]; 541 form.appendChild(el); 542 } 543 return form; 544 } 545}; 546})();
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.