﻿/// <reference path="../../../vendor/jquery/1.4.4/jquery.js"/>

/*
* A jQuery plugin that makes an element clickable.
* The API is designed along the lines of the jQuery.ui interface 
* When an item is made clickable it gets decorated with hover and active behaviors wrapped around the click function
*
* Usage:
*	$(".button").clickable();					// makes the button elements clickable
*
*	$(".button").clickable(options);			// makes the button elements clickable with the supplied options
*
*	The full set of options and their defaults are:
*		addLoader:			false,					// Adds a span element at the start of a click
*		loaderClass:		"loader",				// The class to use for the loader added if addLoader is true
*		loaderAddCallback:	undefined,				// A callback to add the loader in a custom location, without it the loader gets put after the clickable elements. 
*													// the function takes the form function(clickable, loader) where clickable is the element the plugin is being applied to
*		clickableClass:		"cn-clickable",			// The css class to apply to all elements
*		hoverClass:			"cn-clickable-hover",	// The css class to apply to the clickable elements when they are hovered or have the focus
*		activeClass:		"cn-clickable-active",	// The css class to apply at the start of a click when the click is 'active', mimic browser active links
*		doClick:			elements default click	// A function to call when the element is clicked
*		beforeClick:		undefined				// A function to call before the main doClick method, if the function returns false then the click behavior is stopped
*		afterClick:			undefined				// A function to call after the main doClick method
*		enterIsClick:		true					// If the element has the focus then the enter key will trigger a click
*
*	The click function takes the form function(event, callback) with arguments:
*		event:		the javascript click event 
*		callback:	a callback function with no parameters that removes the loader and active styles. Clickable elements that result in a page refresh can ignore this
*					elements that perform some form of ajax request should call the callback as part of the handlre of the ajax response
*			
*	$(".button").clickable("disable")			// disable all the clickable features (including the underlying click behavior of the button elements) and add the css class 'cn-clickable-disabled'
*
*	$(".button").clickable("enable")			// re-enable all the clickable features and remove the css class 'cn-clickable-disabled'
*
*	$(".button").clickable("removeLoader")		// removes the loader element. Typically used if you only want to show the loader for the first click.
*
*	It is also possible to bind additional events to the doClick, afterClick, and beforeClick handlers:	
*	
*	$(".button").bind("clickabledoClick", function() { alert("Clicking"); })
*		.bind("clickablebeforeClick", function() { alert("before click"); })
*		.bind("clickableafterClick", function() { alert("after click"); })
*
*/

(function($)
{
	$.widget("cn.clickable", {
		options: 
		{
			addLoader: false,
			clickableClass: "cn-clickable",
			hoverClass: "cn-clickable-hover",
			activeClass: "cn-clickable-active",
			loaderClass: "loader",
			enterIsClick: true
		},
		_init: function()
		{
			var self = this;
			this._hasFocus = false;
			this._hasHover = false;

			//if we have no click then default to the click on ourself
			if (!this.options.doClick)
				this.options.doClick = function(e, callback) { self.element.click(); callback(); };

			if (this.options.addLoader)
			{
				this.loader = $('<span></span>')
						.addClass(this.options.loaderClass)
						.hide();
				if (this.options.loaderAddCallback)
				{
					this.options.loaderAddCallback(this.element, this.loader);
				}
				else
				{
					this.element.after(this.loader);
				}
			}

			self.element
				.addClass(this.options.clickableClass)
				.focus(
					function()
					{
						self._hasFocus = true;
						self._addHoverClassIf();
					})
				.blur(
					function()
					{
						self._hasFocus = false;
						self._removeHoverClassIf();
					})
				.mouseover(
					function()
					{
						self._hasHover = true;
						self._addHoverClassIf();
					})
				.mouseout(
					function()
					{
						self._hasHover = false;
						self._removeHoverClassIf();
					})
				.keydown(function(e)
				{
					if (eventIsEnterKey(e) && !self.options.disabled && self.options.enterIsClick)
					{
						self.element.click();
					}
				})
				.click(function(e)
				{
					if (!self.clickInProgress && !self.options.disabled)
					{
						self.clickInProgress = true;
						if (false === self._trigger('beforeClick'))
						{
							self.clickInProgress = false;
							return false;
						}

						self.element.addClass(self.options.activeClass);
						if (self.loader)
                        {
                            // Awful hack for FF 3.x, which thinks our flowers should have style 'block' as of jQuery 1.4.4
                            // jQuery 1.3.2 implemented show() by removing the old element and replacing it with an identical one
                            // whose 'display' was unset - that might be better.  Or would it work to just unset display?
                            self.loader.each(function() { jQuery.data(this, "olddisplay", "inline-block"); });
							self.loader.show();
                        }

						var returnValue = self._trigger('doClick', e, function() { self._clickCallback(); });

						self._trigger('afterClick');
						return returnValue;
					}
					else
					{
						//make sure we don't trigger the underlying behavior if the click is in progress or disabled
						return false;
					}
				});

		},
		_clickCallback: function()
		{
			this.element.removeClass(this.options.activeClass);
			if (this.loader)
				this.loader.hide();
			this.clickInProgress = false;
		},
		removeLoader: function()
		{
			this.loader.remove();
			this.loader = undefined;
		},
		destroy: function()
		{
			this.element
				.removeClass(this.options.clickableClass)
				.removeData("clickable")
				.unbind(".clickable");
			if (this.element.loader)
			{
				this.element.loader.remove();
			}
		},
		_removeHoverClassIf: function()
		{
			if (!this.options.disabled && !(this._hasHover || this._hasFocus) && this.options.hoverClass)
			{
				this.element.removeClass(this.options.hoverClass);
			}
		},
		_addHoverClassIf: function()
		{
			if (!this.options.disabled)
			{
				this.element.addClass(this.options.hoverClass);
			}
		}
	});

	$.fn.clickableButton = function(options)
	{
		// get a set of options that are common to button elements
		var buttonOptions = $.extend({}, {
			hoverClass: "button-hover",
			activeClass: "button-active",
			addLoader: true
		}, options);

		this.clickable(buttonOptions);
		return this; //continue the jquery chain
	}

})(jQuery);

