/**

 * Ajax upload

 * Project page - http://valums.com/ajax-upload/

 * Copyright (c) 2008 Andris Valums, http://valums.com

 * Licensed under the MIT license (http://valums.com/mit-license/)

 * Version 3.5 (23.06.2009)

 */



/**

 * Changes from the previous version:

 * 1. Added better JSON handling that allows to use 'application/javascript' as a response

 * 2. Added demo for usage with jQuery UI dialog

 * 3. Fixed IE "mixed content" issue when used with secure connections

 * 

 * For the full changelog please visit: 

 * http://valums.com/ajax-upload-changelog/

 */



(function(){

	

var d = document, w = window;



/**

 * Get element by id

 */	

function get(element){

	if (typeof element == "string")

		element = d.getElementById(element);

	return element;

}



/**

 * Attaches event to a dom element

 */

function addEvent(el, type, fn){

	if (w.addEventListener){

		el.addEventListener(type, fn, false);

	} else if (w.attachEvent){

		var f = function(){

		  fn.call(el, w.event);

		};			

		el.attachEvent('on' + type, f)

	}

}





/**

 * Creates and returns element from html chunk

 */

var toElement = function(){

	var div = d.createElement('div');

	return function(html){

		div.innerHTML = html;

		var el = div.childNodes[0];

		div.removeChild(el);

		return el;

	}

}();



function hasClass(ele,cls){

	return ele.className.match(new RegExp('(\\s|^)'+cls+'(\\s|$)'));

}

function addClass(ele,cls) {

	if (!hasClass(ele,cls)) ele.className += " "+cls;

}

function removeClass(ele,cls) {

	var reg = new RegExp('(\\s|^)'+cls+'(\\s|$)');

	ele.className=ele.className.replace(reg,' ');

}



// getOffset function copied from jQuery lib (http://jquery.com/)

if (document.documentElement["getBoundingClientRect"]){

	// Get Offset using getBoundingClientRect

	// http://ejohn.org/blog/getboundingclientrect-is-awesome/

	var getOffset = function(el){

		var box = el.getBoundingClientRect(),

		doc = el.ownerDocument,

		body = doc.body,

		docElem = doc.documentElement,

		

		// for ie 

		clientTop = docElem.clientTop || body.clientTop || 0,

		clientLeft = docElem.clientLeft || body.clientLeft || 0,

		

		// In Internet Explorer 7 getBoundingClientRect property is treated as physical,

		// while others are logical. Make all logical, like in IE8.

		

		

		zoom = 1;

		if (body.getBoundingClientRect) {

			var bound = body.getBoundingClientRect();

			zoom = (bound.right - bound.left)/body.clientWidth;

		}

		if (zoom > 1){

			clientTop = 0;

			clientLeft = 0;

		}

		var top = box.top/zoom + (window.pageYOffset || docElem && docElem.scrollTop/zoom || body.scrollTop/zoom) - clientTop,

		left = box.left/zoom + (window.pageXOffset|| docElem && docElem.scrollLeft/zoom || body.scrollLeft/zoom) - clientLeft;

				

		return {

			top: top,

			left: left

		};

	}

	

} else {

	// Get offset adding all offsets 

	var getOffset = function(el){

		if (w.jQuery){

			return jQuery(el).offset();

		}		

			

		var top = 0, left = 0;

		do {

			top += el.offsetTop || 0;

			left += el.offsetLeft || 0;

		}

		while (el = el.offsetParent);

		

		return {

			left: left,

			top: top

		};

	}

}



function getBox(el){

	var left, right, top, bottom;	

	var offset = getOffset(el);

	left = offset.left;

	top = offset.top;

						

	right = left + el.offsetWidth;

	bottom = top + el.offsetHeight;		

		

	return {

		left: left,

		right: right,

		top: top,

		bottom: bottom

	};

}



/**

 * Crossbrowser mouse coordinates

 */

function getMouseCoords(e){		

	// pageX/Y is not supported in IE

	// http://www.quirksmode.org/dom/w3c_cssom.html			

	if (!e.pageX && e.clientX){

		// In Internet Explorer 7 some properties (mouse coordinates) are treated as physical,

		// while others are logical (offset).

		var zoom = 1;	

		var body = document.body;

		

		if (body.getBoundingClientRect) {

			var bound = body.getBoundingClientRect();

			zoom = (bound.right - bound.left)/body.clientWidth;

		}



		return {

			x: e.clientX / zoom + d.body.scrollLeft + d.documentElement.scrollLeft,

			y: e.clientY / zoom + d.body.scrollTop + d.documentElement.scrollTop

		};

	}

	

	return {

		x: e.pageX,

		y: e.pageY

	};		



}

/**

 * Function generates unique id

 */		

var getUID = function(){

	var id = 0;

	return function(){

		return 'ValumsAjaxUpload' + id++;

	}

}();



function fileFromPath(file){

	return file.replace(/.*(\/|\\)/, "");			

}



function getExt(file){

	return (/[.]/.exec(file)) ? /[^.]+$/.exec(file.toLowerCase()) : '';

}			



// Please use AjaxUpload , Ajax_upload will be removed in the next version

Ajax_upload = AjaxUpload = function(button, options){

	if (button.jquery){

		// jquery object was passed

		button = button[0];

	} else if (typeof button == "string" && /^#.*/.test(button)){					

		button = button.slice(1);				

	}

	button = get(button);	

	

	this._input = null;

	this._button = button;

	this._disabled = false;

	this._submitting = false;

	// Variable changes to true if the button was clicked

	// 3 seconds ago (requred to fix Safari on Mac error)

	this._justClicked = false;

	this._parentDialog = d.body;

	

	if (window.jQuery && jQuery.ui && jQuery.ui.dialog){

		var parentDialog = jQuery(this._button).parents('.ui-dialog');

		if (parentDialog.length){

			this._parentDialog = parentDialog[0];

		}

	}			

					

	this._settings = {

		// Location of the server-side upload script

		action: 'upload.php',			

		// File upload name

		name: 'userfile',

		// Additional data to send

		data: {},

		// Submit file as soon as it's selected

		autoSubmit: true,

		// The type of data that you're expecting back from the server.

		// Html and xml are detected automatically.

		// Only useful when you are using json data as a response.

		// Set to "json" in that case. 

		responseType: false,

		// When user selects a file, useful with autoSubmit disabled			

		onChange: function(file, extension){},					

		// Callback to fire before file is uploaded

		// You can return false to cancel upload

		onSubmit: function(file, extension){},

		// Fired when file upload is completed

		// WARNING! DO NOT USE "FALSE" STRING AS A RESPONSE!

		onComplete: function(file, response) {}

	};



	// Merge the users options with our defaults

	for (var i in options) {

		this._settings[i] = options[i];

	}

	

	this._createInput();

	this._rerouteClicks();

}

			

// assigning methods to our class

AjaxUpload.prototype = {

	setData : function(data){

		this._settings.data = data;

	},

	disable : function(){

		this._disabled = true;

	},

	enable : function(){

		this._disabled = false;

	},

	// removes ajaxupload

	destroy : function(){

		if(this._input){

			if(this._input.parentNode){

				this._input.parentNode.removeChild(this._input);

			}

			this._input = null;

		}

	},				

	/**

	 * Creates invisible file input above the button 

	 */

	_createInput : function(){

		var self = this;

		var input = d.createElement("input");

		input.setAttribute('type', 'file');

		input.setAttribute('name', this._settings.name);

		var styles = {

			'position' : 'absolute'

			,'margin': '-5px 0 0 -175px'

			,'padding': 0

			,'width': '220px'

			,'height': '30px'

			,'fontSize': '14px'								

			,'opacity': 0

			,'cursor': 'pointer'

			,'display' : 'none'

			,'zIndex' :  2147483583 //Max zIndex supported by Opera 9.0-9.2x 

			// Strange, I expected 2147483647					

		};

		for (var i in styles){

			input.style[i] = styles[i];

		}

		

		// Make sure that element opacity exists

		// (IE uses filter instead)

		if ( ! (input.style.opacity === "0")){

			input.style.filter = "alpha(opacity=0)";

		}

							

		this._parentDialog.appendChild(input);



		addEvent(input, 'change', function(){

			// get filename from input

			var file = fileFromPath(this.value);	

			if(self._settings.onChange.call(self, file, getExt(file)) == false ){

				return;				

			}														

			// Submit form when value is changed

			if (self._settings.autoSubmit){

				self.submit();						

			}						

		});

		

		// Fixing problem with Safari

		// The problem is that if you leave input before the file select dialog opens

		// it does not upload the file.

		// As dialog opens slowly (it is a sheet dialog which takes some time to open)

		// there is some time while you can leave the button.

		// So we should not change display to none immediately

		addEvent(input, 'click', function(){

			self.justClicked = true;

			setTimeout(function(){

				// we will wait 3 seconds for dialog to open

				self.justClicked = false;

			}, 3000);			

		});		

		

		this._input = input;

	},

	_rerouteClicks : function (){

		var self = this;

	

		// IE displays 'access denied' error when using this method

		// other browsers just ignore click()

		// addEvent(this._button, 'click', function(e){

		//   self._input.click();

		// });

				

		var box, dialogOffset = {top:0, left:0}, over = false;							

		addEvent(self._button, 'mouseover', function(e){

			if (!self._input || over) return;

			over = true;

			box = getBox(self._button);

					

			if (self._parentDialog != d.body){

				dialogOffset = getOffset(self._parentDialog);

			}	

		});

		

	

		// we can't use mouseout on the button,

		// because invisible input is over it

		addEvent(document, 'mousemove', function(e){

			var input = self._input;			

			if (!input || !over) return;

			

			if (self._disabled){

				removeClass(self._button, 'hover');

				input.style.display = 'none';

				return;

			}	

										

			var c = getMouseCoords(e);



			if ((c.x >= box.left) && (c.x <= box.right) && 

			(c.y >= box.top) && (c.y <= box.bottom)){			

				input.style.top = c.y - dialogOffset.top + 'px';

				input.style.left = c.x - dialogOffset.left + 'px';

				input.style.display = 'block';

				addClass(self._button, 'hover');				

			} else {		

				// mouse left the button

				over = false;

				if (!self.justClicked){

					input.style.display = 'none';

				}

				removeClass(self._button, 'hover');

			}			

		});			

			

	},

	/**

	 * Creates iframe with unique name

	 */

	_createIframe : function(){

		// unique name

		// We cannot use getTime, because it sometimes return

		// same value in safari :(

		var id = getUID();

		

		// Remove ie6 "This page contains both secure and nonsecure items" prompt 

		// http://tinyurl.com/77w9wh

		var iframe = toElement('<iframe src="javascript:false;" name="' + id + '" />');

		iframe.id = id;

		iframe.style.display = 'none';

		d.body.appendChild(iframe);			

		return iframe;						

	},

	/**

	 * Upload file without refreshing the page

	 */

	submit : function(){

		var self = this, settings = this._settings;	

					

		if (this._input.value === ''){

			// there is no file

			return;

		}

										

		// get filename from input

		var file = fileFromPath(this._input.value);			



		// execute user event

		if (! (settings.onSubmit.call(this, file, getExt(file)) == false)) {

			// Create new iframe for this submission

			var iframe = this._createIframe();

			

			// Do not submit if user function returns false										

			var form = this._createForm(iframe);

			form.appendChild(this._input);

			

			form.submit();

			

			d.body.removeChild(form);				

			form = null;

			this._input = null;

			

			// create new input

			this._createInput();

			

			var toDeleteFlag = false;

			

			addEvent(iframe, 'load', function(e){

					

				if (// For Safari

					iframe.src == "javascript:'%3Chtml%3E%3C/html%3E';" ||

					// For FF, IE

					iframe.src == "javascript:'<html></html>';"){						

					

					// First time around, do not delete.

					if( toDeleteFlag ){

						// Fix busy state in FF3

						setTimeout( function() {

							d.body.removeChild(iframe);

						}, 0);

					}

					return;

				}				

				

				var doc = iframe.contentDocument ? iframe.contentDocument : frames[iframe.id].document;



				// fixing Opera 9.26

				if (doc.readyState && doc.readyState != 'complete'){

					// Opera fires load event multiple times

					// Even when the DOM is not ready yet

					// this fix should not affect other browsers

					return;

				}

				

				// fixing Opera 9.64

				if (doc.body && doc.body.innerHTML == "false"){

					// In Opera 9.64 event was fired second time

					// when body.innerHTML changed from false 

					// to server response approx. after 1 sec

					return;				

				}

				

				var response;

									

				if (doc.XMLDocument){

					// response is a xml document IE property

					response = doc.XMLDocument;

				} else if (doc.body){

					// response is html document or plain text

					response = doc.body.innerHTML;

					if (settings.responseType && settings.responseType.toLowerCase() == 'json'){

						// If the document was sent as 'application/javascript' or

						// 'text/javascript', then the browser wraps the text in a <pre>

						// tag and performs html encoding on the contents.  In this case,

						// we need to pull the original text content from the text node's

						// nodeValue property to retrieve the unmangled content.

						// Note that IE6 only understands text/html

						if (doc.body.firstChild && doc.body.firstChild.nodeName.toUpperCase() == 'PRE'){

							response = doc.body.firstChild.firstChild.nodeValue;

						}

						if (response) {

							response = window["eval"]("(" + response + ")");

						} else {

							response = {};

						}

					}

				} else {

					// response is a xml document

					var response = doc;

				}

																			

				settings.onComplete.call(self, file, response);

						

				// Reload blank page, so that reloading main page

				// does not re-submit the post. Also, remember to

				// delete the frame

				toDeleteFlag = true;

				

				// Fix IE mixed content issue

				iframe.src = "javascript:'<html></html>';";		 								

			});

	

		} else {

			// clear input to allow user to select same file

			// Doesn't work in IE6

			// this._input.value = '';

			d.body.removeChild(this._input);				

			this._input = null;

			

			// create new input

			this._createInput();						

		}

	},		

	/**

	 * Creates form, that will be submitted to iframe

	 */

	_createForm : function(iframe){

		var settings = this._settings;

		

		// method, enctype must be specified here

		// because changing this attr on the fly is not allowed in IE 6/7		

		var form = toElement('<form method="post" enctype="multipart/form-data"></form>');

		form.style.display = 'none';

		form.action = settings.action;

		form.target = iframe.name;

		d.body.appendChild(form);

		

		// Create hidden input element for each data key

		for (var prop in settings.data){

			var el = d.createElement("input");

			el.type = 'hidden';

			el.name = prop;

			el.value = settings.data[prop];

			form.appendChild(el);

		}			

		return form;

	}	

};

})();