/************************************************************************

Confirmer is a jQuery plugin which provides an alternative to
confirmation dialog boxes and "check this checkbox to confirm action"
widgets. It acts by modifying a button to require two clicks within
a certain time, with the second click acting as a confirmation of
the first. If the second click does not come within a specified
timeout then the action is not confirmed.

Usage:

<button id='MyButton'></button>

$('#MyButton').initConfirmer({options});

Options:

	.initialText = initial text of the element.
	There is no default value.

	.confirmText = text to show when in "confirm mode".
	Default=("Confirm: "+initialText), or something similar.

	.timeout = Number of milliseconds to wait for confirmation.
	Default=3000.

	.onconfirm = function to call when clicked in confirm mode.
	Default = null.

	.ontimeout = function to call when confirm is not issued.
	Default = null.

Due to the nature of multi-threaded code, it is potentially possible
that confirmation and timeout actions BOTH happen if the user triggers
the associated action at "just the right millisecond" before the timeout
is triggered. One potential/theoretical way around this would be to add
an intenral buffer of, say, 200ms, and internally cancel the Confirm
action at the requested time and trigger the ontimeout action only after
that extra 200ms has passed and only if the user did not confirm the action.

Potential TODOs:

- Add support for non-BUTTON elements. In theory, any element for which
a click(handler) and text("some text") is valid can be used, but in
practice some elements (e.g. IMG) won't work gracefully with the current
code.

- Add onclick handler which gets called before the timer is set. This
would allow the user to change the appearance of the item, e.g. highlight
it, while waiting for confirmation.

- Add optional generic support for UI effects like blinking the button.
This requires additional plugins, though.

- This thing probably has far more code than it really needs. See what
we can strip out.

////////////////////////////////////////////////////////////////////////
   Confirmer home page:

   http://wanderinghorse.net/computing/javascript/jquery/confirmer/

   License: Public Domain

   Author: stephan beal (http://wanderinghorse.net/home/stephan/)

   Terse revision history (newest at the top):

   20070717: initial release
************************************************************************/

jQuery.fn.initConfirmer = function(opts) {
	if( ! opts ) { opts = []; }
	var self = this;
	self.opts = jQuery.extend({
		initialText:"PLEASE SET .initialText='Button Label'",
		confirmText:"Confirm: "+opts.initialText,
		timeout:3000,
		onconfirm:null,
		ontimeout:null,
		debuggering:false
	}, opts);
	/** Internal debuggering function. */
	self.dbgdiv = null;
	function dbg(msg) { if( self.dbgdiv ) self.dbgdiv.prepend("Confirmer debug: "+msg+"<br/>"); };
	if( self.opts.debuggering ) {
		self.after("<div id='ConfirmerDebugDiv'>Confirmer debugging area</div>");
		self.dbgdiv = jQuery('#ConfirmerDebugDiv');
		self.dbgdiv.css('border','1px dashed #000');
		dbg("debugging activated.");
	}

	/* Internal data holder class. */
	function ConfirmHolder(target,opts) {
		var me = this;
		me.target = target;
		me.opts = opts;
		var states = { initial:0,waiting:1 };
		me.state = states.initial;
		me.target.html(me.opts.initialText);
		me.doTimeout = function() {
			if( me.state != states.waiting ) {
				// it was already confirmed
				return;
			}
			me.state = states.initial;
			dbg("Timeout triggered.");
			me.target.html(me.opts.initialText);
			if( me.opts.ontimeout ) {
				me.opts.ontimeout();
			}
		};

		me.target.click( function() {
			switch( me.state ) {
				case( states.waiting ):
					me.state = states.initial;
					dbg("Confirmed");
					me.target.html(me.opts.initialText);
					if( me.opts.onconfirm ) me.opts.onconfirm();
					break;
				case( states.initial ):
					me.state = states.waiting;
					dbg("Waiting on confirmation...");
					me.target.html( me.opts.confirmText );
					var id = jQuery.fn.initConfirmer.currentID++;
					jQuery.fn.initConfirmer.ids[id] = me;
					setTimeout( "jQuery.fn.initConfirmer.bogusTimeoutHandler("+id+")", me.opts.timeout );
					break;
				default: // can't happen.
					break;
			};
		});
	};
	var holder = new ConfirmHolder(this,self.opts);
	return self;
}; // initConfirmer()
/**
	The jQuery.fn.initConfirmer.XXX members are all part of a conspiratorial kludge designed
	to work around a significant limitation of setTimeout(), namely the fact that code
	passed to it has absolutely no knowledge of scoping (as closures do). Whoever designed
	setTimeout() to NOT take a Function as an argument is a real jackass.

	Here's how we work around it: each time we want to call setTimeout() we generate a unique
	integer (a request ID, of sorts). Then we assign jQuery.fn.initConfirmer.ids[requestID] = ourObj.
	The setTimeout() code calls jQuery.fn.initConfirmer.bogusTimeoutHandler(requestID), which then uses
	the requestID to find ourObj and call ourObj.doTimeout(). The requestID is deleted from
	jQuery.fn.initConfirmer.ids as part of this process, so the array does not get too cluttered.

	DO NOT USE THESE MEMBERS IN CLIENT CODE! Use ONLY initConfirmer(). Violating this rule leads to
	undefined behaviour.
*/
jQuery.fn.initConfirmer.ids = [];
jQuery.fn.initConfirmer.currentID = 0;
jQuery.fn.initConfirmer.bogusTimeoutHandler = function(objid){
	var obj = jQuery.fn.initConfirmer.ids[objid];
	delete jQuery.fn.initConfirmer.ids[objid];
	obj.doTimeout();
};

