/* $Rev: 85 $ */

// pass name, name_popup and name_link are assumed as id's.
function helpbox(name, o)
{
	var popname = '#' + name + '_bubble';
	var linkname = '#' + name + '_link';
	var help_link_id = name + '_anchor';

	o = o || {};

	var def_options = {
		position: 'anchor'
	, 	anchor: 'cover'
	, 	timeout: 8000
	, 	width: 400
	,	minHeight: 50
	,	dialogClass: ' dialog-help '
	};

	var options = jQuery.extend({}, def_options, o);

	// for backwards compatibility, allow lookup by class if no id exists
	var _link = jQuery(linkname);

	if (_link.length == 0)
		_link = jQuery(linkname.replace('#', '.'));


	// if we still don't find a link using class, there's no link. and with no link, no popup
	if (_link.length == 0)
		return false;

	var anchor_html = '<a href="#" class="help ' + help_link_id + '"></a>';

	if (_link.get(0).tagName == 'IMG')
		_link.wrap(anchor_html);
	else _link.wrapInner(anchor_html);

	// need to move helpbox contents to another container since it's default container is hidden;
	var popup = jQuery(popname).addClass('help_bubble');
	popup.pchDialog('a.' + help_link_id, options);

	link = jQuery('a.' + help_link_id);
	link
	.hoverIntent
	(
		function()
		{
			jQuery(this).click();
		}
	, 	function(){}
	)
	.click
	(
		function()
		{
			jQuery('div.ui-dialog.dialog-help div.ui-dialog-content')
			.hover
			(
				function(){}
			,	function()
				{
					jQuery(this).dialog('close');
				}
			);
		}
	);

	return true;
}

jQuery.fn.pchDialog = function(trigger, o)
{
	var popup = this;
	var trigger_ele = jQuery(trigger).addClass('uiTrigger');

	o = o || {};

	var def_options = {
		bgiframe: true
	,	autoOpen: false
	,	trigger_event: 'click'
	,	show_title: false
	,	dialogClass: ''
	,	resizable: false
	,	draggable: false
	,	beforeopen: function()
		{
			// don't hide existing popup if that popup initiated this one
			if (trigger_ele.parents('div.ui-dialog').length == 0)
				jQuery('div.ui-dialog .ui-dialog-content:visible').not(this).dialog('close');

			return true;
		}
	,	timeout: false
	,	anchor: false
	,	pch_open: function(){}
	};


	var options = jQuery.extend({}, def_options, o);

	if (options.show_title === false)
		options.dialogClass += ' no-titlebar ';

	// initialiaze the dialog
	popup.dialog(options);


	trigger_ele
	.bind(options.trigger_event, function(event)
	{
		event.preventDefault();
		event.stopPropagation();

		if (popup.dialog('isOpen'))
			return false;

		// determine coordinates to pass to dialog based on anchor param
		if (options.anchor !== false)
		{
			var new_position = getDialogPosition(popup, jQuery(this), options.anchor, options);
			popup.dialog('option', 'position', new_position);
		}

		var target = jQuery(event.target);
		options.pch_open(target, popup);

		// show current
		popup.dialog('open');

		if (options.timeout !== false)
			setTimeout("jQuery('#" + popup.id + "').dialog('close');", options.timeout);

		//if for some reason the popup doesn't work, allow link to pass
		return ! popup.dialog('isOpen');
	});

	return this;
};

// general dialog close event, if mouse clicked anywhere except an open dialog or it's trigger, close the open dialog
jQuery(document).mouseup(function(event){
	// if no open popups, nothing to worry about
	var allPopups = jQuery('div.ui-dialog .ui-dialog-content:visible');
	if (allPopups.length < 1)
		return;

	var ele = jQuery(event.target);

	var ele_parents = ele.parents();
	var notDialog = ( ! ele_parents.is('div.ui-dialog') && ! ele.is('div.ui-widget-overlay') && ! ele_parents.is('div.ui-widget'));
	var notTrigger = ( ! ele.parents().is('uiTrigger') && ! ele.is('uiTrigger'));

	// if the click is off the dialog, and not clicking the trigger, close any open dialogs
	if (notDialog && notTrigger )
		allPopups.each(function(){jQuery(this).dialog('close');});
});

function getDialogPosition(dialog, trigger_ele, anchor, options)
{
	var top = false, left = false;
	var offset = trigger_ele.offset();

	top = offset.top + trigger_ele.height();

	var dialog_size = false, dialog_width = false, dialog_height = false;

	switch(anchor)
	{
		case 'left': // popup and anchor's left edge line up
			left = offset.left;
		break;

		case 'right': // popup and anchor's right edge line up
			left = offset.left + trigger_ele.width();

			dialog_size = get_element_size(dialog, options);
			dialog_height = dialog_size.height;

			top = (wnd.height() / 2) - (dialog_height / 2);
		break;

		case 'cover': // popup appears centered over anchor

			dialog_size = get_element_size(dialog, options);
			dialog_width = dialog_size.width;
			dialog_height = dialog_size.height;

			left = offset.left - dialog_width/2 + trigger_ele.width()/2;
			top = offset.top - dialog_height/2 + trigger_ele.height()/2;
		break;

		case 'tr': // popup top-right edge meets anchor's bottom-left edge

			dialog_size = get_element_size(dialog, options);
			dialog_width = dialog_size.width;

			left = offset.left - dialog_width;
		break;

		case'listproduct_ordering': // special for dash listproduct ordering popout
			top = offset.top - 20; // account for popup header
			left = offset.left - 5; // so rounded corner doesn't line up exactly with corner of image
		break;

		case 'subcat':
			left = offset.left - 2;
			top = top - 2;
		break;

		case 'listorder_notice':
			var table = jQuery('#listorder_tabs');
			offset = table.offset();

			top = offset.top + 67 ;
			left = offset.left + 60;
		break;

		case 'center': // popup is centered just below anchor
		default:

			dialog_size = get_element_size(dialog, options);
			dialog_width = dialog_size.width;

			left = offset.left + (trigger_ele.width() / 2) - (dialog_width / 2);
		break;
	}

	// also need to take into account the viewport positioning, account for IE being a fucking retard
	var yPage = window.pageYOffset || document.documentElement.scrollTop || 0;
	var xPage = window.pageXOffset || document.documentElement.scrollLeft || 0;

	// get viewport dimensions
	// the more standards compliant browsers (mozilla/netscape/opera/IE7) use window.innerWidth and window.innerHeight

	 if (typeof window.innerWidth != 'undefined')
	 {
		  viewportwidth = window.innerWidth,
		  viewportheight = window.innerHeight
	 }
	 else if (typeof document.documentElement != 'undefined'
		 && typeof document.documentElement.clientWidth !=
		 'undefined' && document.documentElement.clientWidth != 0) // IE6 in standards compliant mode (i.e. with a valid doctype as the first line in the document)
	 {
		   viewportwidth = document.documentElement.clientWidth,
		   viewportheight = document.documentElement.clientHeight
	 }
	 else // older versions of IE
	 {
		   viewportwidth = document.getElementsByTagName('body')[0].clientWidth,
		   viewportheight = document.getElementsByTagName('body')[0].clientHeight
	 }

	// account for viewport position, apparently the new dialog goes from there...
	left = left - xPage;
	top = top - yPage;

	// don't let dialog go off left side of screen
	if (left < 0)
		left = 0;

	// don't let dialog go off top of screen
	if (top < 0)
		top = 0;

	if (dialog_width !== false)
	{
		if (left + dialog_width > xPage + viewportwidth)
			left = (xPage + viewport_width) - dialog_width;
	}

	var pos = [left, top];
	return [left, top];
}

function get_element_size(element, options)
{
	var element_width = options.width || element.width() || element.css('width');
	var element_height = options.height || options.minHeight || element.height() || element.css('height');

	if (element_width == 'auto')
		element_width = 0;
	if (element_height == 'auto')
		element_height = 0;

	// we have to use .css() for some dimensions, since the dialog is still hidden when we're determining position
	// this can cause problems because the value will have 'px' or 'em' or 'ex', or at the worst case a percentage...
	// lets do the best we can with this, if it's in pixels, parseInt, if em or ex, parseInt*fontsize conversion? percentages...whatever
	element_widthFloat = parseFloat(element_width);
	if (element_width != element_widthFloat)
	{
		element_widthInt = parseInt(element_width);
		if (element_width.indexOf('%') != -1)
			element_width = dialog.parent().width() * element_widthInt / 100; // take percentage of parent
		else if (element_width.indexOf('ex') != -1)
			element_width = element_widthInt*6; // at 100%, 1ex ~= 6px
		else if (element_width.indexOf('em') != -1)
			element_width = element_widthInt*12; // at 100%, 1em ~= 12px
		else
			element_width = parseInt(element_width); // either in px, or something else. if we get to this, we'll probably just adjust the css to be usable
	}

	// do the same processing for element_height value as element_width
	element_heightFloat = parseFloat(element_height);
	if (element_height != element_heightFloat)
	{
		element_heightInt = parseInt(element_height);
		if (element_height.indexOf('%') != -1)
			element_height = dialog.parent().height() * element_heightInt / 100; // take percentage of parent
		else if (element_height.indexOf('ex') != -1)
			element_height = element_heightInt*6; // at 100%, 1ex ~= 6px
		else if (element_height.indexOf('em') != -1)
			element_height = element_heightInt*12; // at 100%, 1em ~= 12px
		else
			element_height = parseInt(element_height); // either in px, or something else. if we get to this, we'll probably just adjust the css to be usable
	}

	// if for some reason that processing didn't work, just set them to 0...
	if (isNaN(element_width))
		element_width = 0;
	if (isNaN(element_height))
		element_height = 0;

	var element_size = {width: element_width, height: element_height};

	return element_size;
}

/**
	like jQuery.post() but with a few methods for inserting returned content
 */
jQuery.pchPost = function(action, values)
{
	values += '&ajax=true';

	jQuery.post(
		action
	,	values
	,	function(data)
		{
			//returned info should either be 'failed' or <div.message><div.content>
			if (data != 'failed')
			{
				//won't be recognized as traversable html otherwise
				var retval = jQuery('<div id="container">' + data + '</div>');

				var message = jQuery('div.message', retval).html();

				//content needs to be set up so jquery understands it(e.g. if it's a tr, needs to be wrapped in a <table><tbody></tbody></table>
				var content = jQuery('.content', retval).children();

				// message always contained in div#message
				if (message)
				{
					jQuery('#message').html(jQuery(message)); // should there be a timeout which clears the message div?

					setTimeout("jQuery('#message').slideUp().html('').show();", 30000);
				}

				if (content.length > 0)
				{
					content.each(
						function()
						{
							var $this = jQuery(this);
							var thisid = $this.attr('id');
							var thiscontent = $this.html();

							jQuery('#' + thisid).html(thiscontent);
						});
				}

				dsrInit();
			}
		}
	,	"html"
	);
}

/**
	takes input objects, and jumps through them as though they were 1 input
 */
jQuery.pchInputLink = function(inputList)
{
	var max = inputList.length - 1;

	jQuery.each(
		inputList
	, 	function(i, v)
		{
			var input = inputList[i];
			var prev = (i > 0) ? inputList[i-1] : false;
			var next = (i < max) ? inputList[i+1] : false;

			input.keyup(
				function(e)
				{
					var $this = jQuery(this);
					var keyCode = e.keyCode;

					// only jump through boxes if keyCode in whitelist
					// 0-9(48-57), numpad 0-9(96-105), aA-zZ(65-90), bkspc(8)
					if ((keyCode > 57 && keyCode < 65) || (keyCode > 90 && keyCode < 96) || keyCode > 105 || (keyCode < 48 && keyCode != 8))
						return;

					//37 = left arrow, 39 = right, incorporate these at some point

					if ($this.val().length == $this.attr('maxlength') && next !== false) // forward to next box
					{
						if (next.val().length > 0)
							next.focus().select();
						else next.focus();
					}
					else if ((keyCode == '8' || keyCode == '46') && $this.val().length < 1 && prev !== false) // go to prev box
					{
						if (prev.val().length > 0)
							prev.focus().val(prev.val());
						else prev.focus();
					}
				});
		}
	);
}

/**
 * used on date object, will return given date as a string in a particular format MM/dd/yy
 */
jQuery.pchDate = function(d)
{
	var month = (d.getMonth() + 1).toString();
	if( month.length == 1 )
		month = '0' + month;

	var day = d.getDate().toString();
	if( day.length == 1 )
		day = '0' + day;

	var year = d.getYear().toString().substring(1);

	var dateString = month + '/' + day + '/' + year;

	return dateString;
}

jQuery.fn.defaultText = function(defText)
{
	var inputs = this;

	// in case there's no input
	if (inputs.length == 0)
		return false;

	inputs.each
	(
		function()
		{
			var input = jQuery(this);

			if (input.val().length > 0) 
				return;

			var maxLen = parseInt(input.attr('maxlength'));
			if (isNaN(maxLen) || maxLen < 1)
				maxLen = 40;

			input
			.attr('maxlength', defText.length)
			.val(defText)
			.focus(
				function()
				{
					if (input.val() == defText)
						input.attr('maxlength', maxLen).val('');
				}
			).blur(
				function()
				{
					if (input.val() == '')
						input.attr('maxlength', defText.length).val(defText);
				}
			);

			// avoid sending default text on submit, might cause errors
			jQuery('input[type=submit]').mousedown(
				function()
				{
					if (input.val() == defText) input.val('');
				});
		}
	);

	return this;
};

/**
	Disables submit button(s)

	condition 	string 	button disabled if eval'd to true
	o			object	options
						watchTrigger (DOM elements that will trigger condition check)
						triggerEvent (array of events that will trigger condition check, default is 'change')
						noticeText	 (explanation for disabled button, accepts html)
						spanclass	 (class added to wrapping span, popup event fires onclick of this)
	oPrompt		object	popup options
						see jquery.impromptu.js
 */
jQuery.fn.disableButton = function(condition, o, oPrompt)
{
	var trigger = o.watchTrigger || false;
	var triggerEvent = o.triggerEvent || ['change'];
	if (typeof triggerEvent != "object") triggerEvent = [triggerEvent];
	var noticeText = o.noticeText || false;
	var spanClass = o.spanclass || 'disabled_button';
	var safari = false; //jQuery.browser.safari;
	var callback = jQuery.isFunction(o.callback);

	var promptDef = {
		overlayspeed: 0.01
	,	promptspeed: 0.01
	,	show: 'fadeIn'
	,	submit: function()
		{
			if (callback)
				o.callback();

			return true;
		}
	};

	var oPrompt = jQuery.extend({}, promptDef, oPrompt);

	var button = this;

	button.mousedown(
		function(event)
		{
			//console.log(condition + ': "' + eval(condition) + '"');
			var disable_button = eval(condition);
			if (disable_button)
			{
				event.preventDefault();

				if (noticeText.length > 0)
					jQuery.prompt(noticeText, oPrompt);

				return false;
			}
			else
			{
				button.removeClass('disabled_button');
				return true;
			}
		}
	).click(function(){if (eval(condition)) return false;});


	//check condition, and assign to trigger
	if (eval(condition))
	{
		button.addClass('disabled_button');

		if (safari)
			button.attr('disabled', true);
	}
	//console.log(condition + ': "' + eval(condition) + '"');

	if (trigger)
	{
		jQuery.each(triggerEvent,
			function(i, tEvent)
			{
				jQuery(trigger).bind(tEvent,
					function()
					{
						//console.log(condition + ': "' + eval(condition) + '"');
						var disable_button = eval(condition);
						if (disable_button)
						{
							button.addClass('disabled_button');

							if (safari)
								button.attr('disabled', true);
						} else {
							if (safari)
								button.attr('disabled', false);

							button.removeClass('disabled_button');
						}
					});
			});
	}
}

jQuery.fn.hoverClass = function(c, onIntent) {
	var action = onIntent === true ? 'hoverIntent' : 'hover';

    return this.each(function(){
        jQuery(this)[action](
            function() {jQuery(this).addClass(c);},
            function() {jQuery(this).removeClass(c);}
        );
    });
};

// allow thumbnails to update one main image
// main - pass main beauty shot object
// thumbs - pass thumbs object, usually links wrapping thumbs
// attr_map - define attributes to copy from thumb to beauty, {main attr: thumb attr, main attr2: thumb attr2 ... }
// options - event to trigger swap, etc.
// TODO: add animation for different widths as well as height
jQuery.fn.beautySwap = function(thumbs, attr_map, o)
{
	var main = this;

	// if no map passed, thumb href becomes beauty src
	attr_map_defaults = {
		"src": "href"
	,	"alt": "alt"
	}

	options_defaults = {
		swapEvent: 'click'
	,	callback: function(){}
	}

	attributes = attr_map || attr_map_defaults;
	var options = jQuery.extend({}, options_defaults, o);

	// if no main image or no thumbs, no reason to go on
	if (thumbs.length == 0)
		return false;

	var main_parent = main.parent();
	var main_parent_height = main.height();
	main_parent.attr('size_fix', 0);

	thumbs.bind(options.swapEvent,
		function(e)
		{
			e.preventDefault();
			e.stopPropagation();

			var $thumb = jQuery(this);

			main_parent.addClass('loading');

			if ( main_parent.attr('size_fix') == 0)
			{
				if (main_parent_height != 0)
					main_parent.height(main_parent_height);

				main_parent.attr('size_fix', 1);
			}

			main.stop().fadeTo
			(
				'fast'
			,	0.01
			,	function()
				{
					var same_image = true;
					jQuery.each
						(
							attributes
						,	function(main_attr, thumb_attr)
							{
								if (main.attr(main_attr) != $thumb.attr(thumb_attr) && same_image === true)
									same_image = false;

								main.attr(main_attr, $thumb.attr(thumb_attr));
							}
						);

					// if all attrs match up, just fade back in
					if (same_image === true)
					{
						main.fadeTo(500, 1.0);
						return false;
					}

					var newImg = new Image();

					jQuery(newImg)
					.load(function(){
						var $this = jQuery(this);

						// have to insert image into dom to get height
						jQuery('body').append($this.hide());
						var newHeight = $this.height();
						$this.remove();

						if (newHeight != main_parent.height())
						{
							main_parent.animate
							(
								{height: newHeight}
							, 	400
							,	null
							,	function()
								{
									main.fadeTo(500, 1.0);
									main_parent.removeClass('loading');
								}
							);
						}
						else
						{
							main.fadeTo(500, 1.0);
							main_parent.removeClass('loading');
						}

					}).attr('src', $thumb.attr(attributes['src']));
				}
			);

			options.callback($thumb);

			return false;
		});
}

/*
 * inspired by Alen Grakalic (http://cssglobe.com)
 */

jQuery.fn.imagePreview = function(o){

	var default_opt = {
		xOffset: 10
	,	yOffset: 30
	,	anchor: false
	,	disableLink: true
	,	attr: 'href'
	,	width: 250
	,	id: 'preview'
	,	alt_text: '&nbsp;'
	,	preload: false
	}

	var options = jQuery.extend({}, default_opt, o);
	var $this = this;

	if (options.preload === true)
	{
		jQuery(function()
		{
			$this.each(function()
			{
				var anchor = jQuery(this);
				var src = jQuery(this).attr(options.attr);
				jQuery('<img />')
				.attr('src', src) // once we set the src, it will preload the image
				.load(function(){anchor.attr('img_loaded', '1');}); // flag the image as loaded so we don't try to use the load() event
			});
		});
	}

	// $("a.preview") // example target, a.preview with thumbnail inside
	$this.hoverIntent(
		function(e)
		{
			var anchor = jQuery(this);

			var title = anchor.attr('title') || '';
			anchor.attr('t', title);
			anchor.attr('title', ''); // unset this on hover so it doesn't look shitty

			// popup caption (can I make this take html?)
			var c = (title != "" && title != 'undefined') ? '<div class="caption">' + title + '</div>': "";
			var img = "<img src='"+ anchor.attr(options.attr) +"' alt='"+ options.alt_text +"' />";
			var preview = jQuery("<div id=" + options.id + ">" + img + c +"</div>");

			preview.add(jQuery('img', preview)).width(options.width);

			// wait until the image is loaded to show div so it's not empty
			// but once it's loaded once, we can't use the load event anymore, so just show it since the img is cached
			var img_loaded = anchor.attr('img_loaded') || false;
			if (parseInt(img_loaded) !== 1)
			{
				jQuery('img', preview).load(
					function()
					{
						showImagePreview(anchor, preview, options);

						anchor.attr('img_loaded', '1');
					});
			}
			else showImagePreview(anchor, preview, options);
	    },
		function()
		{
			var anchor = jQuery(this);

			var title = anchor.attr('t');
			anchor.attr('title', title);

			jQuery("#" + options.id).remove();
	    });

    if (options.anchor === false)
    {
		$this.mousemove(
			function(e)
			{
				var preview = jQuery("#" + options.id);

				// TODO: add bounding so preview won't go offscreen
				var top = (e.pageY - parseInt(options.yOffset));
				var left = (e.pageX + parseInt(options.xOffset));

				preview.css({"top": top + "px", "left": left + "px"});
			});
	}

	// we don't want this link to open up image
	if (options.disableLink === true || options.attr == 'href')
	{
		$this.click(
			function(e)
			{
				e.preventDefault();
				e.stopPropagation();

				return false;
			});
	}
};

function showImagePreview(anchor, preview, options)
{
	// remove any lingering popups
	jQuery("#" + options.id).remove();

	jQuery("body").append(preview);

	var top = 0;
	var left = 0;

	if (options.anchor === false)
	{
		top = (e.pageY - parseInt(options.yOffset));
		left = (e.pageX + parseInt(options.xOffset));
	}
	else
	{
		top = (parseInt(anchor.offset().top) - parseInt(options.yOffset));
		left = (parseInt(anchor.offset().left));

		var previewWidth = parseInt(jQuery('img', preview).css('width')) + 2;
		var previewHeight = parseInt(jQuery('img', preview).height()) + 2;

		var anchorWidth = anchor.width();

		var wnd = jQuery(window);
		var yPage = window.pageYOffset;

		// // popup rests on right side of thumb, unless it'd go offscreen to right
		if (left > jQuery('body').width()/2)
			left = left - previewWidth - parseInt(options.xOffset);
		else // then popup rests on right side of thumb
			left = left + anchorWidth + parseInt(options.xOffset);


		// if bottom of image below viewport, rest on bottom of viewport
		if ((top + previewHeight) > (wnd.height() +  yPage) && previewHeight < wnd.height())
			top = yPage + wnd.height() - previewHeight - 10;
		else if (previewHeight >wnd.height()) // then top of image at top of viewport
			top = yPage;
	}

	preview.css({"top": top + "px", "left": left + "px"}).fadeIn("normal");
}

jQuery.fn.hoverIntent = function(f,g) {
	// default configuration options
	var cfg = {
		sensitivity: 7,
		interval: 100,
		timeout: 0
	};
	// override configuration options with user supplied object
	cfg = jQuery.extend(cfg, g ? {over: f, out: g} : f );

	// instantiate variables
	// cX, cY = current X and Y position of mouse, updated by mousemove event
	// pX, pY = previous X and Y position of mouse, set by mouseover and polling interval
	var cX, cY, pX, pY;

	// A private function for getting mouse position
	var track = function(ev) {
		cX = ev.pageX;
		cY = ev.pageY;
	};

	// A private function for comparing current and previous mouse position
	var compare = function(ev,ob) {
		ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
		// compare mouse positions to see if they've crossed the threshold
		if ( ( Math.abs(pX-cX) + Math.abs(pY-cY) ) < cfg.sensitivity ) {
			jQuery(ob).unbind("mousemove",track);
			// set hoverIntent state to true (so mouseOut can be called)
			ob.hoverIntent_s = 1;
			return cfg.over.apply(ob,[ev]);
		} else {
			// set previous coordinates for next time
			pX = cX;pY = cY;
			// use self-calling timeout, guarantees intervals are spaced out properly (avoids JavaScript timer bugs)
			ob.hoverIntent_t = setTimeout( function(){compare(ev, ob);} , cfg.interval );
		}
	};

	// A private function for delaying the mouseOut function
	var delay = function(ev,ob) {
		ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
		ob.hoverIntent_s = 0;
		return cfg.out.apply(ob,[ev]);
	};

	// A private function for handling mouse 'hovering'
	var handleHover = function(e) {
		// next three lines copied from jQuery.hover, ignore children onMouseOver/onMouseOut
		var p = (e.type == "mouseover" ? e.fromElement : e.toElement) || e.relatedTarget;
		while ( p && p != this ) {try {p = p.parentNode;} catch(e) {p = this;}}
		if ( p == this ) {return false;}

		// copy objects to be passed into t (required for event object to be passed in IE)
		var ev = jQuery.extend({},e);
		var ob = this;

		// cancel hoverIntent timer if it exists
		if (ob.hoverIntent_t) {ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);}

		// else e.type == "onmouseover"
		if (e.type == "mouseover") {
			// set "previous" X and Y position based on initial entry point
			pX = ev.pageX;pY = ev.pageY;
			// update "current" X and Y position based on mousemove
			jQuery(ob).bind("mousemove",track);
			// start polling interval (self-calling timeout) to compare mouse coordinates over time
			if (ob.hoverIntent_s != 1) {ob.hoverIntent_t = setTimeout( function(){compare(ev,ob);} , cfg.interval );}

		// else e.type == "onmouseout"
		} else {
			// unbind expensive mousemove event
			jQuery(ob).unbind("mousemove",track);
			// if hoverIntent state is true, then call the mouseOut function after the specified delay
			if (ob.hoverIntent_s == 1) {ob.hoverIntent_t = setTimeout( function(){delay(ev,ob);} , cfg.timeout );}
		}
	};

	// bind the function to the two event listeners
	return this.mouseover(handleHover).mouseout(handleHover);
};

jQuery.fn.confirm = function(msg, o)
{
	if ( ! msg || msg == '')
		msg = 'Are you sure?';

	o = o || {};

	var def_options =
		{
			buttons:
			{
				Cancel: false
			,	Ok: true
			}
		, 	overlayspeed:'fast'
		,	submit: finishClick
		, 	promptspeed:'fast'
		, 	show:'fadeIn'
		};

	var options = jQuery.extend({}, def_options, o);

	var buttons = jQuery(this);
	buttons.mousedown(
		function(e)
		{
			e.preventDefault();
			e.stopPropagation();

			var $_this = jQuery(this); // only the button that was clicked, not the group of buttons passed to .confirm();

			// add selected flag so callback can trigger it
			$_this.addClass('click_flag');

			jQuery.prompt(msg, options);

			return false;
		}
	);
}

function finishClick(v,m)
{
	var clicked = jQuery('.click_flag');

	if (v == 'true')
	{
		if (clicked.is('a'))
			document.location = clicked.attr('href');
		else clicked.click(); // if submit button or something
	}
	else clicked.removeClass('click_flag');

	return true;
}

jQuery.fn.clickClass = function(class_name)
{
	var $this = this;

	// if use mouses off before finishing click, but then goes back, keep class, use this as a placeholder
	var mouseoff_class = class_name + '_mouseoff';

	$this.mousedown // add when clicked
	(
		function()
		{
			jQuery(this).addClass(class_name);
			jQuery('.' + class_name).removeClass(class_name);
		}
	).mouseup // remove when click is done
	(
		function()
		{
			jQuery('.' + class_name + ', .' + mouseoff_class).removeClass(class_name).removeClass(mouseoff_class);
		}
	).mouseout // or remove if use mouses off before finishing click
	(
		function()
		{
			var _$this = jQuery(this);

			if(_$this.hasClass(class_name))
			{
				_$this.removeClass(class_name).addClass(mouseoff_class);
				setTimeout("jQuery('." + mouseoff_class + "').removeClass('" + mouseoff_class + "');", 5000);
			}
		}
	).mouseout // and add it back if use mouses back on
	(
		function()
		{
			var _$this = jQuery(this);
			if (_$this.hasClass(mouseoff_class))
				_$this.addClass(class_name).removeClass(mouseoff_class);
		}
	);
}

// validate email input, pass jquery object of input, and id of url for ajax validation
function validate_email($this, o)
{
	var o = o || {};

	var def_options = {
		'email_url_id': 'validate_email_url'
	,	'pass': function(data)
		{
			$this.attr('valid', '1').attr('last_email', $this.val());
			$this.next('.error_message, .notice_message').hide();
			jQuery('#existing_email_error').hide();
			jQuery('#current_email_error').hide();
		}
	,	'fail': function(data)
		{
			$this.attr('valid', '0');
			$this.siblings('.error_message').show();
			jQuery('#existing_email_error').hide();
			jQuery('#current_email_error').hide();
		}
	,	'exist': function(data)
		{
			$this.attr('valid', '0');
			$this.next('.error_message').hide();

			var error_message = jQuery('#existing_email_error');

			error_message.show();
		}
	,	'params': {}
	};

	var options = jQuery.extend({}, def_options, o);

	var last_email = $this.attr('last_email') || '';
	var email = $this.val();
	var already_valid = $this.attr('valid') == 1 || $this.attr('valid') == '1';

	//console.log('last: "' + last_email + '" - current: "' + email + '" - already_valid: "' + (already_valid ? 'true' : 'false') + '"');

	if (last_email.length > 0 && last_email == email && already_valid)
	{
		options.pass();
		return;
	}

	var emailRegex = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
	if (email.match(emailRegex))
	{
		var validate_email_link = jQuery('#' + options.email_url_id)

		// if for some reason we can't get url for ajax validation, let it pass, action will return dupe error
		if (validate_email_link.length > 0)
			var validate_email_url = validate_email_link.attr('href');
		else
		{
			options.pass();
			return;
		}

		var params = jQuery.extend({}, {"email": email}, options.params);

		jQuery.post
		(
			validate_email_url
		,	params
		,	function(data)
			{
				var fail = (data == 0 || data == '0') || data.length == 0;

				if (fail)
				{
					options.fail(data);
				}
				else if (data == 'exists')
				{
					options.exists(data);
				}
				else
				{
					options.pass(data);
				}
			}
		);
	}
	else
	{
		options.fail();
		return;
	}
}

/*
 * pass jquery object for zip input, can pass zip_url link id, return format (html|json|etc), pass/fail callbacks, and extra params
 */
function validate_zip($this, opt)
{
	// post to validate_zip_url
	// get return, if false, display error message
	// if string, set valid_zip_location.html(ret)
	var valid_zip_location = $this.siblings('.valid_zip_location');

	var def_options = {
		'zip_url_id': 'validate_zip_url'
	,	'ajax_format': 'html'
	,	'pass': function(data)
		{
			$this.attr('last_zip', current_zip).attr('valid', '1').siblings('.error_message').hide();
			valid_zip_location.html(data).css('opacity', '1');

			return;
		}
	,	'fail': function(data)
		{
			$this.attr('valid', '0').siblings('.error_message').show();
			valid_zip_location.css('opacity', '.01'); // in case they change it to an invalid zip

			return;
		}
	,	'params': {}
	};

	// in case nothing extra is passed
	var o = opt || {};

	var options = jQuery.extend({}, def_options, o);

	var validate_zip_url = jQuery('#' + options.zip_url_id).attr('href');

	var last_zip = $this.attr('last_zip') || '';
	var current_zip = $this.val();

	var already_valid = $this.attr('valid') == 1 || $this.attr('valid') == '1';

	// in case they edited and put it back the same, don't validate again
	if (last_zip.length > 0 && last_zip == current_zip && already_valid)
		return;

	// don't bother checking if zip isn't valid length
	if (current_zip.length != $this.attr('maxlength'))
	{
		$this.attr('valid', false).next('.error_message').show();
		valid_zip_location.css('opacity', '.01');

		return;
	}

	// allow params to be passed
	var params = jQuery.extend({}, {"zip": current_zip}, options.params);

	jQuery.post
	(
		validate_zip_url
	, 	params
	, 	function(data, textStatus)
		{
			var fail = options.ajax_format == 'html'
				? (data == 0 || data == '0') || data.length == 0
				: data.length == 0;

			if (fail)
			{
				options.fail(data);
			}
			else
			{
				options.pass(data);
			}
		}
	,	options.ajax_format
	);
}

jQuery.fn.externalLink = function(favicon)
{
	var use_favicon = favicon || false;

	this
	.click(
		function(event)
		{
			event.preventDefault();
			jQuery(this).blur();
			window.open(jQuery(this).attr('href'));
			return false;
		}
	);

	// don't put an external icon next to an image
	var links = this.filter(function(){return jQuery(this).children('img').length < 1;});

	if (use_favicon)
	{
		links.faviconLink();
	}
	else links.addClass("external");
}

// use an external website's favicon as the external icon
jQuery.fn.faviconLink = function()
{
	var links = this;
	jQuery.each(links, function()
	{
		var link = this;

		var hoststring = /^http:/;
		var hrefvalue = link.getAttribute("href",2);

		if (hrefvalue.search(hoststring) != -1)
		{
			var domain = hrefvalue.match(/(\w+):\/\/([^/:]+)(:\d*)?([^# ]*)/);
			var domain = RegExp.$2;

			var cue = document.createElement("img");
			cue.className = "faviconimg";
			//var cuesrc = "http://" + domain + "/favicon.ico";
			var cuesrc = "http://ww.google.com/s2/favicons?domain=" + domain
			cue.setAttribute("src", cuesrc);

			// this should default to external gif if no favicon is found
			cue.onerror = function () {
				this.src = "/images/fileicons/external.gif";
			}

			// now append the favicon image
			link.appendChild(cue);
		}
	});
}

jQuery.fn.offHoverDelay = function(opts)
{
	var default_options = {
		duration: 1000
	,	callback: function(){}
	};

	var options = jQuery.extend({}, default_options, opts);

	var $this = this;

	var rename_me = function()
		{
			options.callback();
			$this.css('opacity', 1).show();
		};

	$this.fadeOut(options.duration, rename_me)
		.hoverIntent(function(){$this.stop().animate({opacity: 1}, 200);}, function(){});
};

/**
 * handle toggling of menus, use ajax to save state to session if 'update_session' passed as true
 */
jQuery.fn.handleToggle = function(update_session)
{
	update_session = update_session || true;

	var $this = this;
	var $this_id = $this.attr('id');
	var target = $this.next();
	var target_id = target.attr('id');
	var open_text = jQuery('span.open_text', $this);

	// toggle next element and change class of trigger accordingly
	$this.toggleClass('open').toggleClass('closed');

	target.slideToggle(200, function(){if (open_text.length > 0) open_text.toggle();});

	var ele_state = $this.hasClass('open') ? 'open' : 'closed';

	// post id of element to navMemory action
	// going to use a bitmask, will make this a lot simpler, where should i store the bitmask info? yaml?
	if ( target_id.length > 0 && update_session === true )
		jQuery.post('/nav/navMemorySet', {ele: target_id, state: ele_state});
}

/**
 * create a text input with target element's text as default value
 *
 * cancel button destroys input and leaves original element
 *
 * @param regex string (used for validation input value, prompts user to fix before allowing to save)
 */
jQuery.fn.inlineEdit = function(regex)
{
	// creates a text input with this elements text as default value
	// input name can use element's title attr

	var $this = this;
	//regex = new RegExp(regex, 'gi');

	$this.addClass('mouseover');

	$this.click(function()
	{
		var ele = jQuery(this);
		init_inline_edit(ele, regex);
	});
}

function init_inline_edit(ele, regex)
{
	var title = ele.attr('title');
	var index = jQuery('body').index(ele);
	var input = jQuery('<input>');

	input.attr('name', title);
	input.attr('id', title + '_' + index); // give this a unique id
	input.attr('maxlength', 100);
	input.addClass('inlineEdit');
	input.val(ele.text());

	var cancelLink = jQuery('<span>Cancel</span>');
	cancelLink.addClass('cancelEdit').addClass('mouseover');
	cancelLink.click(function()
	{
		var _$this = jQuery(this);

		ele.show();
		_$this.add(_$this.siblings('input.inlineEdit')).remove();
	});

	// to validate
	input.blur(function()
	{
		var string = input.val();
		var matches = string.match(regex);
		//console.log(matches);

		// don't let them do shit
		if (matches && matches.length > 0)
		{
			jQuery.prompt
			(
				'There are invalid characters in the description. Please remove them.'
			,	{
					callback: function()
					{
						input.focus();
					}
				,	buttons:
					{
						'OK': true
					,	'Undo Changes': false
					}
				,	submit: function(v, m)
					{
						if (v === false)
							cancelLink.click();

						return true
					}
				}
			);
		}
	});

	var inline = input.add(cancelLink);

	//console.log(inline);

	ele.after(inline);
	ele.hide();

	input.focus();
}


/**
 * determine if an element (usually a popup) is part/all outside of viewport
 * if it is, scroll the window to fully display the element
 */
function scroll_to_element(element)
{
	var wnd = jQuery(window);
	var offset = element.offset();
	var yPage = window.pageYOffset;
	var xPage = window.pageXOffset;
	var y = yPage;
	var x = xPage;

	/*
	alert("offTop: " + offset.top +
		  "\noffLeft: " + offset.left +
		  "\nyPage: " + yPage +
		  "\nxPage: " + xPage +
		  "\nwndHt: " + wnd.height() +
		  "\nwndWdth: " + wnd.width());
		  */

	//if popup bottom is below or popup top is above
	if ((offset.top + element.height()) > (wnd.height() +  yPage) || offset.top < yPage)
		y = offset.top - wnd.height()/2;
	//if popup right is to right of popup left is to left
	if ((offset.left + element.width()) > (wnd.width() + xPage) || offset.left < xPage)
		x = offset.left;

	if (y != yPage || x != xPage)
		window.scrollTo(x, y);
}