/**
 * Client-side partial view rendering
 *
 * @copyright  BliXem Internet Services
 *
 * @package    Core
 * @subpackage Controller
 * @filesource
 */

// Make sure the prototype library is loaded
if (typeof Prototype == 'undefined') {
	throw('PartialView requires the Prototype JavaScript framework');
}

/**
 * This object enables support for partial updating of the webpage.
 */
var PartialView = {
	/**
	 * Optional callback functions called before and after an update
	 */
	preUpdateCallback: null,
	updateCallback: null,
	postUpdateCallback: null,

	/**
	 * Initialize the PartialView object. This function should be called right 
	 * after the Validation object is created.
	 */
	init: function() {
		// When the onload event of the window fires, call the apply function
		Event.observe(window, 'load', PartialView.apply);
	},

	/**
	 * Apply the partial view special behaviour to all appropriate objects.
	 */
	apply: function()
	{
		// Search for elements with the "partialview" class
		$$('.partialview').each(function(partial) {
			PartialView.applyBehaviour(partial);
		});
	},

	/**
	 * Apply the correct behaviour to the elements of a partialview element.
	 *
	 * @param HTMLElement partial An element with the partial class assigned.
	 */
	applyBehaviour: function(partial)
	{
		// Convert the <a>, <form> and partialview-linking elements
		PartialView.convertLinks(partial);
		PartialView.convertForms(partial);

		partial.load = function(url) {
			PartialView.replace(this, url);
		}
	},
		
	/**
	 * Replace the contents 
	 *
	 * @param HTMLElement el The element which is to be replaced.
	 * @param string url The URL from which the replacement data is retrieved.
	 */
	replace: function(el, url) {
		// Send an AJAX request and use the result (if successfull) to update the
		// contents of the element
		new Ajax.Request(url, {
			method: 'get',
			onSuccess: function(xml, json) {
				PartialView.update(el, xml.responseText, json, url);
			}
		});
	},

	/**
	 * Convert links with the partial class to support the partialview behaviour.
	 *
	 * @param HTMLElement partial An element with the partial class assigned.
	 */
	convertLinks: function(partial)
	{
		// Find all anchor elements in the partial class
		$A(
			partial.getElementsByTagName('a')
		).findAll(
			function(a) {
				return Element.hasClassName(a, 'partial');
			}
		).each(
			function(a) {
				// Only try this if a pvref override wasn't set before
				if (!a.pvref) {
					if (a.listener) {
						Event.stopObserving(a, 'click', a.listener);
					}
					a.partial  = partial;
					a.listener = PartialView.handleLink.bindAsEventListener(a);

					Event.observe(a, 'click', a.listener);
				}
			}
		);

		// Find all anchor elements referencing this partial
		if (partial.id) {
			$A(
				document.getElementsByTagName('a')
			).findAll(
				function(a) {
					return Element.hasClassName(a, 'pvref_' + partial.id);
				}
			).each(
				function(a) {
					if (a.listener) {
						Event.stopObserving(a, 'click', a.listener);
					}
					a.partial  = partial;
					a.pvref    = partial;
					a.listener = PartialView.handleLink.bindAsEventListener(a);

					Event.observe(a, 'click', a.listener);
				}
			);
		}
	},

	/**
	 * Convert forms with the partial class to support the partialview behaviour.
	 *
	 * @param HTMLElement partial An element with the partial class assigned.
	 */
	convertForms: function(partial)
	{
		// Find all form elements with the partial class
		$A(
			partial.getElementsByTagName('form')
		).findAll(
			function(form) {
				return Element.hasClassName(form, 'partial');
			}
		).each(
			function(form) {
				if (form.listener) {
					Event.stopObserving(form, 'submit', form.listener);
				}
				form.listener = PartialView.handleForm.bindAsEventListener(form);

				// Check if the form should submit to another partial view
				var referenceClassName = Element.classNames(form).find( 
					function(className) {
						return (className.indexOf('pvref_') == 0);
					}
				);

				// The form should submit to another partial, check if it is a valid reference
				if (referenceClassName != null && $(referenceClassName.substr(6)) != null) {
					form.partial = $(referenceClassName.substr(6));

				// No valid reference
				} else {
					form.partial = partial;
				}

				Event.observe(form, 'submit', form.listener);
			}
		);
	},

	/**
	 * Handle the event of clicking on a partialview link/anchor (an <a> item).
	 *
	 * @param HTMLEvent The event that occured.
	 */
	handleLink: function(evt)
	{
		// Send an AJAX request using the "href" attribute specified in the 
		// link/anchor element and update the contents of this.partial with the
		// response of the AJAX request
 		new Ajax.Request(this.href, {
 			method: 'get',
			onSuccess: function(xml, json) {
				PartialView.update(this.partial, xml.responseText, json, this.href);
			}.bind(this)
		});

		// Stop handling the event
		Event.stop(evt);
	},

	/**
	 * Handle the event of submitting a partialview form.
	 *
	 * @param HTMLEvent The event that occured.
	 */
	handleForm: function(evt)
	{
		// Get the form method, action and data
		var method = (this.method) ? this.method : 'post';
		var url    = this.action;
		var data   = Form.serialize(this);

		// Dynamically build the AJAX request options
		var options = {};
		
		if (method == 'get') {
			options.parameters = data;
		} else {
			options.postBody = data;
		}
		options.method    = method;
		options.onSuccess = function(xml, json) {
			PartialView.update(this.partial, xml.responseText, json, url);
		}.bind(this);

		// Send an AJAX request using the "method" attribute specified in the 
		// form element and update the contents of this.partial with the
		// response of the AJAX request
		new Ajax.Request(url, options);
		Event.stop(evt);
	},

	/**
	 * Replace the old partial element with the new element
	 *
	 * @param HTMLElement partial An element with the partial class assigned.
	 * @param string HTML The HTML
	 */
	update: function(partial, html, json, url)
	{
		// Determine if there is a json callback override specified
		var jsonCallbackOverride = (json != null && json.callbackOverride != undefined);
		
		// Call user-defined pre-update callback function if there is one
		if (typeof PartialView.preUpdateCallback == 'function' && !jsonCallbackOverride) {
			
			var returnHTML = PartialView.preUpdateCallback(partial, html, json, url);

			// PreUpdate function can change the HTML
			if (typeof returnHTML == 'string') {
				html = returnHTML;
			}
		}

		// Call user-defined update callback function if there is one
		if (typeof PartialView.updateCallback == 'function' && !jsonCallbackOverride) {
			PartialView.updateCallback(partial, html, json, url);
			
		// Or use the built-in update code
		} else {
			// Partial override from code (sent with JSON)?
			if (json != null && json.partialOverride != null) {
				partial = $(json.partialOverride);
			}

			// Replace the partial element with the HTML data
			partial.replace(html);
		}

		// Reapply the partialview behaviour to the entire page
		PartialView.apply();

		// Check if the Validation object is defined, if so call the apply function
		// as new elements might have been introduced
		if (typeof Validation != 'undefined') {
			Validation.apply();
		}
		
		// Call user-defined post-update callback function if there is one
		if (typeof PartialView.postUpdateCallback == 'function' && !jsonCallbackOverride) {
			PartialView.postUpdateCallback(partial, html, json, url);
		}
	},	

	/**
	 * Public function to request an update
	 *
	 * @param string url The URL to load into the partial
	 * @param HTMLElement partial An element with the partial class assigned.
	 * @param function callback function
	 * @param FORMElement form which should be posted when requesting the update
	 */
	requestUpdate: function(url, partial, callback, postform)
	{
		// Dynamically build the AJAX request options
		var options = {};
		
		options.onSuccess = function(xml, json) {
			PartialView.update(partial, xml.responseText, json, url);

			if (typeof callback == 'function') {
				callback(partial, xml.responseText, json, url);
			}
		}.bind(this);

		// If a form should be posted, add some extra options
		if (typeof postform == 'object') {
			options.method = (postform.method) ? postform.method : 'post';
			if (options.method == 'get') {
				options.parameters = postform.serialize();
			} else {
				options.postBody = postform.serialize();
			}
		} else {
			options.method = 'get';
		}
		
		// Should always be an object
		partial = $(partial);

		// Call for an update
 		new Ajax.Request(url, options);
	},

	/**
	 * Public function to set user defined callback functions which are called
	 * before and after an (automatic) update
	 *
	 * @param function callback function called BEFORE partialview update
	 * @param function callback function called AFTER partialview update
	 */
	setUpdateCallbacks: function(preUpdateCallback, updateCallback, postUpdateCallback)
	{				
		if (typeof preUpdateCallback == 'function') {
			PartialView.preUpdateCallback = preUpdateCallback;
		}
		if (typeof updateCallback == 'function') {
			PartialView.updateCallback = updateCallback;
		}
		if (typeof postUpdateCallback == 'function') {
			PartialView.postUpdateCallback = postUpdateCallback;
		}
	}
}

// Initialize the PartialView object
PartialView.init();