/*

JQuery Hijax plugin:
~~~~~~~~~~~~~~~~~~~~
@product     JQuery Hijax plugin
@version     0.9.8
@copyright   Copyright (c) 2010 Yaron Tadmor
@site        http://www.yarontadmor.co.cc/hijax
@license     GPL license (http://www.gnu.org/licenses/gpl.html)
@requires    jquery.history.js


Revision History:
~~~~~~~~~~~~~~~~
0.9.5 - Fixed bug on IE (extra comma in default option list).
0.9.6 - Added support for changing title on Ajax
      - Added support for proper JS handling in Hijaxed links via _hijax_ready.
      - Minor bug fixes for end-cases of IE.
0.9.7 - Changed title handling so title is in "title" tag (instead of a div)
      - Use the content of the source element instead of the element itself
0.9.8 - Slight modification to hash saving code. Solves bug of going back and forward
        to the last page
      - Fixed a bug of .live() for [hijax*=] on FireFox

TODO: - Find a way to change title only once with ajax

License and Disclaimer:
~~~~~~~~~~~~~~~~~~~~~~
This software is licensed under the GPL open source license. You may distribute it freely, and
use it in your own software. 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPLICIT OR IMPLIED, INCLUDING BUT 
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 
OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.



* - internal use only

trg : {[startCB: <start load function>, ]
		[endCB: <end load callback>, ]
		url: <url to load>, - 
		src: <source ID in returned html>,
		
		[formHref: <alternative URL for history. without it from submision doesn't appear in history]
		*[formData: <if form a serialized version of form's data>]

		*[force: <force data reload even if url/src didn't change>,]
		}


targets: { <array of trg objects> }

options: { 	trgs: { <array of trg objects> },
			defaults: { src: <default source>, (used for targets with no source reference)
						url: <default url>, (used for targets with no URL)
						startCB: <default start load function>,
						endCB: <default end load function>,
					},
			defaultTrg: <default target>, (used for links/forms with no target reference at all)
			selectionClass: <class to add for selected objects> }
			
*/



(function ($) {

	$.fn.hijax = function (targets) {
		if (!targets)
			targets = {};
			
		return this.each (function() {		
	 		var $this = $(this);		
	 		
	 		// save the targets passed to us
	 		var locTargets = $.extend (true, {}, targets);
	 		this.hijaxTargets = locTargets;
	 		
			// register the proper event handler.
			if (!$this.hasClass ("hijax")) {
				if ($this.is ("a"))
					$this.click ($.fn.hijax.event);
				else if ($this.is ("form"))
					$this.submit ($.fn.hijax.event)
			}
		});		
	}
	
	
	$.fn.hijax.event = function (e) {
		//prevent default action  
		e.preventDefault();  

		// get targets of object, and override current state with object targets
		var targets = $.fn.hijax.buildTargets.call (this); //e.target);
		var curStateTargets = $.fn.hijax.parseHash ($.fn.hijax._curState);
		curStateTargets = $.extend (true, curStateTargets, targets);

		// setup history with our state
		var hash = $.fn.hijax.buildHash (curStateTargets);
		if (hash)
			$.history.load (hash);
			
		// load the state
		$.fn.hijax.doAjax (curStateTargets);
		
		// change state 
		if (hash)
			$.fn.hijax._curState = hash;		
		$.fn.hijax.doSelection (curStateTargets);	

	}
	
	
	$.fn.hijax.buildTargets = function () {
		$this = $(this);
		
		var targets = this.hijaxTargets;
		if (!targets)
			targets = {};
	
		// get target=source pairs and update (or add) them
		var connections = $this.attr("hijax");
		if (connections != undefined && connections.length == 0)
				connections = undefined;
		if (connections != undefined)
			connections = connections.split ("&");
		for (connection in connections) {
			connection = connections[connection];
			var split = connection.split ("=");
			var trg = split[0];
			var src = split[1];				
			var newTarget = {};
			newTarget[trg] = { src: src/*, force: true */}
			$.extend (true, targets, newTarget);
		}
		
		// if no targets, use default target
		var noTargets = true;
		for (t in targets) { noTargets = false; break; }
		if (noTargets && $.fn.hijax.options.defaultTrg)
			targets[$.fn.hijax.options.defaultTrg] = {};

		// Call tag specific code to handle targets
		if ($this.is ("a"))
			targets = $.fn.hijax.buildTargets.a.call (this, targets);
		else if ($this.is ("form"))
			targets = $.fn.hijax.buildTargets.form.call (this, targets);

		// add defaults:
		for (t in targets) 
			targets[t] = $.extend (true, {}, $.fn.hijax.options.trgs[t], targets[t]);
		
		
		return (targets);
	}


	$.fn.hijax.buildTargets.a = function (targets) {
		$this = $(this);
	
		// get url and set it to all requested targets
		var url = $this.attr("href");
		for (trg in targets)
			$.extend (true, targets[trg], { url: url });
//			targets[trg].url = url;
			
		return (targets);
	}

	
	$.fn.hijax.buildTargets.form = function (targets) {
		$this = $(this);
				
		// url, alternative href and form data and set to targets
		var url = $this.attr("action");			
		var formHref = $this.attr("href");			
		var formData = $this.serialize();
		for (trg in targets)
			$.extend (true, targets[trg], { url: url, /*force: true,*/ formData: formData, formHref: formHref });
		
		return (targets);
	}
	
	
	$.fn.hijax.buildHash = function (targets) {
		var hash = "";
		for (trg in targets) {
			var trgData = targets[trg];

			// forms without alternative URLs do not create history setting.
			if (trgData.formData != undefined && trgData.formHref == undefined)
				return (null);
			hash += "::" + trg;
			hash += "/" + (trgData.src ? trgData.src : "*");
			if (trgData.formHref == undefined)
				hash += "/" + (trgData.url ? trgData.url : "*");
			else
				hash += "/" + trgData.formHref;
		}
		
		return (hash);
	}
	
	$.fn.hijax.parseHash = function (hash) {
		// load the default state
		var targets = $.extend (true, {}, $.fn.hijax.options.trgs);
		if (hash == "*") {
			// handle the initial state as empty
			for (trg in targets) {
				targets[trg].src = "*";
				targets[trg].url = "*";
			}
		}
		
		// parse pars of hash
		parts = hash.split ("::");
		parts.shift(); // will always be empty
		for (part in parts) {
			part = parts[part];
			subParts = part.split ("/", 3);
			var trg = subParts[0];
			var src = (subParts[1] == "*" ? undefined : subParts[1]);
			var url = (subParts[2] == "*" ? undefined : subParts[2]);
			
			var newTarget = {};
			newTarget[trg] = {url: url, src: src};
			$.extend (true, targets, newTarget);
		}
		
		return (targets);
	}
	
	
	$.fn.hijax.doSelection = function (curTargets) {
		$("[hijax*=], [hijaxTargets*=]").each (function() {
			var objTargets = $.fn.hijax.buildTargets.call (this);
			var match = true; 
			var hasTargets = false;
			for (trg in objTargets) {
				hasTargets = true;
				if (objTargets[trg].src != curTargets[trg].src ||
					objTargets[trg].url != curTargets[trg].url ||
					objTargets[trg].formData) {
					match = false;
					break;
				}
			}
			
			// get object to change
			$obj = $(this);
			var selectionTarget = $obj.attr("hijaxSelectionTarget");
			if (selectionTarget)
				var $obj = $obj.closest(selectionTarget);
				
			// change selection
			if (match && hasTargets)
				$obj.addClass ($.fn.hijax.options.selectionClass);
			else
				$obj.removeClass ($.fn.hijax.options.selectionClass);
		});
		
	}
	
	$.fn.hijax.doTitle = function () {	
		var $title = $("title");

		// If we have hijax title on, parse all targets for the text.
		var titleText = $title.attr ("hijaxTitle");
		if (titleText) {
			for (trg in $.fn.hijax.options.trgs) {
				// get text for target
				var trgTitleAtr = "hijax"+trg;
				var trgTitleTxt = $title.attr (trgTitleAtr);
				if (!trgTitleTxt)
					continue;
					
				// put target's text in title
				titleText = titleText.replace ("#"+trg, trgTitleTxt);
			}
			
			// if we have text for ALL titles, we can use it.
			if (titleText.search ("#") == -1)
				document.title = titleText;
		}
		
	}
	
	
	$.fn.hijax.doAjax = function (targets) {
		var curTargets = $.fn.hijax.parseHash ($.fn.hijax._curState);
	
		// loop and update all targets
		for (trg in targets) {
			var trgData = targets[trg];
			
			// if target hasn't changed and was not forced, ignore it
			var curTrgData = curTargets[trg];
			if (trgData.force != true &&
				trgData.src == curTrgData.src &&
				trgData.url == curTrgData.url &&
				!trgData.formData)
				continue;
				
			if (!trgData.src || !trgData.url)
				continue;
				
			// add "ajax" var to URL
			var url = trgData.url;
			if (url.search (/\?/) == -1)
				url += "?ajax";
			else
				url += "&ajax";
			
			$.fn.hijax.doAjaxHelper (url, trg, trgData);
		}	
	}

	$.fn.hijax.doAjaxHelper = function (url, trg, trgData) {
		var trgDataLoc = $.extend (true, {}, trgData);
		var trgLocal = trg;
		var $trgLocal = $("#"+trgLocal);
		trgDataLoc.startCB.call ($trgLocal, function () {
			$.post(url, trgDataLoc.formData, function (data, status, res) {
				// append result to DOM.
				// (Script are added as is).
				var regex_script = /<script(.|\s)*?\/script>/gi;
				var regex_head = /<head(.|\s)*?\/head>/gi;
				var regex_title = /<title(.|\s)*?\/title>/gi;

				data = data.replace (regex_head, "");
				//var scripts = data.match (regex_script);
				//var non_script = data;//.replace(regex_script, "");
				if (trgDataLoc.src && trgDataLoc.src.length) {
					var $tmpDiv = $("<div />").append(data);
					$trgLocal.html ($tmpDiv.find("#"+trgDataLoc.src).contents());
				}
				else
					$trgLocal.html (data);
				
				
				// copy title
				var trgTitleAttr = "hijax"+trgLocal;
				var titleData = res.responseText.match (regex_title);
				if (titleData) {
					titleData = titleData[0];
					var trgTitleText = $(titleData).attr (trgTitleAttr);
					if (trgTitleText)
						$("title").attr (trgTitleAttr, trgTitleText);
				}



				// call _hijax_ready functions
				$.fn.hijax.doReadyCB();
				
				// do title
				$.fn.hijax.doTitle();

				// end callback
				trgDataLoc.endCB.call($trgLocal);
			});
		});	
	}
	
	

	$.fn.hijax.doReadyCB = function() {
		// call _hijax_ready functions
		if (typeof _hijax_ready == "function")
			_hijax_ready = [_hijax_ready];
		for (f in _hijax_ready) {
			f = _hijax_ready[f];
			f();
		}
		_hijax_ready = [];
	}
	

	$.fn.hijax.historyCB = function (hash) {
		// if nothing changed, do nothing
		if (hash == $.fn.hijax._curState)
			return;

		// get targets by hash
		var targets = $.fn.hijax.parseHash (hash);

		// unless specifically requeted we ignore empty hash (default state)
		if (hash.length || $.fn.hijax._ajaxEmptyHash) 
			$.fn.hijax.doAjax (targets);
		
		// change state
		$.fn.hijax._curState = hash;		
		$.fn.hijax.doSelection(targets);
		$.fn.hijax.doTitle();
		$.fn.hijax.doReadyCB();
	}


	$.fn.hijax.init = function (options, dontAjaxInitialPage) {
		if ($.fn.hijax_init)
			return;
			
		// update options
		$.extend (true, $.fn.hijax.options, options || {});
		
		// if we have a default target, make sure it's in the trgs.
		if ($.fn.hijax.options.defaultTrg) {
			var newTarget = {};
			newTarget[$.fn.hijax.options.defaultTrg] = { }
			$.extend (true, $.fn.hijax.options.trgs, newTarget);
		}
			
		// init targets with default values if needed
		for (trg in $.fn.hijax.options.trgs) {
			$.fn.hijax.options.trgs[trg] = $.extend (true, {}, $.fn.hijax.options.defaults, $.fn.hijax.options.trgs[trg])
		}

		// init the history module
		if (!dontAjaxInitialPage)
			dontAjaxInitialPage = false;
		$.fn.hijax._ajaxEmptyHash = !dontAjaxInitialPage;
		$.history.init($.fn.hijax.historyCB, { unescape: true });
		$.fn.hijax._ajaxEmptyHash = true;
		
		$.fn.hijax_init = true;
	}

	 
	 
	$.fn.hijax.defaultStartCB = function (cb) {
		cb();
	}

	$.fn.hijax.defaultEndCB = function () {
	}


	// basic options so we have call backs.
	$.fn.hijax.options = {trgs: {},
							defaults: { src: "",
										//url: "",
										startCB: $.fn.hijax.defaultStartCB,
										endCB: $.fn.hijax.defaultEndCB },
							defaultTrg: undefined,
							selectionClass: "selected" };
							
	$.fn.hijax._curState = "*"; // This causes empty hash (initial page) to load default state (since ""!="*")
	$.fn.hijax._ajaxEmptyHash = true;
	$.fn.hijax_init = false;
	
	
	// Install handler to catch all hijax HTML elements (the *= is to allow empty attribute)
	if ($.browser.msie) {
		$("a[hijax*=]").live ("click", $.fn.hijax.event);
		$("form[hijax*=]").live ("submit", $.fn.hijax.event);
	}
	else {
		$("a[hijax]").live ("click", $.fn.hijax.event);
		$("form[hijax]").live ("submit", $.fn.hijax.event);
	}
	_hijax_ready = [];

}) (jQuery);

