function jx_load_tooltips() {
	$(".jx_contextual_help").each(function() {
        var parent = this;
        var tooltip = document.createElement("div");
        var message = $(parent).attr("title");
        if (parent.id && document.getElementById(parent.id+"_text")) {
            message = $("#"+parent.id+"_text").html();
        } else {
            message = "<p>" + message + "</p>";
        }
        $(parent).attr("title", "");
        $(tooltip).addClass("jx_contextual_help_tooltip");
        $(tooltip).html("<div class='inner'>"+message+"</div>");
        $(tooltip).insertAfter("#canvas");
        var fadeOutTimer;
		var fadeInTimer;

		var pointer_offset = $(this).offset();
		var container_offset = $("#content").offset();
		var container_width = $("#content").width();
		var container_height = $("#content").height();
		var tooltip_width = $(tooltip).width();
		var tooltip_height = $(tooltip).height();

		var difference = 0;
		var tooltip_top = pointer_offset.top + 16;
		var tooltip_left = pointer_offset.left - 150;

		// If the tooltip will expand past the right edge of the container, bump it back over
		var tooltip_right_edge = pointer_offset.left + tooltip_width - 150;
		var container_right_edge = container_offset.left + container_width;
		if (tooltip_right_edge >= container_right_edge) {
			tooltip_left = tooltip_right_edge - container_right_edge + 10;
		}
		// If the tooltip is too large for the container, move to the immediate left and closer to the container's top
		var tooltip_bottom_edge = tooltip_top + tooltip_height;
		var container_bottom_edge = container_offset.top + container_height;
		if (tooltip_bottom_edge >= container_bottom_edge) {
			tooltip_left = pointer_offset.left - tooltip_width - 10;
			tooltip_top = container_offset.top + 10;
		}
		
		$(tooltip).css({ "top": tooltip_top+"px", "left": tooltip_left+"px" });
		if ($.browser.msie && $.browser.version <= 6) {
			$(tooltip).css({ "position": "absolute" });
		}

        $(parent).bind("mouseenter", function() {
            $(".jx_contextual_help_tooltip").each(function() {
                if ($(tooltip).html() != $(this).html()) { $(this).fadeOut(); }
            });
            fadeInTimer = setTimeout(function(){
				$(tooltip).fadeIn();
			}, 1000);
            clearTimeout(fadeOutTimer);
        });
        $(parent).bind("mouseleave", function() {
			clearTimeout(fadeInTimer);
            fadeOutTimer = setTimeout(function() { $(tooltip).fadeOut() }, 1000);
        });
        $(tooltip).bind("mouseenter", function() {
            clearTimeout(fadeOutTimer);
        });
        $(tooltip).bind("mouseleave", function() {
			clearTimeout(fadeInTimer);
            fadeOutTimer = setTimeout(function() { $(tooltip).fadeOut(); }, 1000);
        });
	});
}
jQuery.jx_clear_notifications = function(opts) {
	var defaults = {
		'target': ".jx_notifications_target"
	};
	var options = $.extend(defaults, opts);
	$(options.target).each(function() {
		$(this).find("div").fadeOut();
	});
};
jQuery.jx_flash_notification = function(opts) {
	var defaults = {
		'type'    : "success",
		'message' : "This message should never be shown, please fix me.",
		'timeout' : 5 * 1000, // Timeout till notification fadeout
		'target'  : ".jx_notifications_target", // message target element
		'clear'   : true // Clear message target between notifications
	};
	var options = $.extend(defaults, opts);
	var message = options.message.replace(/&lt;/g, '<');
		message = options.message.replace(/&gt;/g, '>');

	var notification = document.createElement("div");
	$(notification).addClass("jx_notification_"+options.type);
	$(notification).html("<p>"+message+"</p>");
	$(notification).hide();

	// Clear existing notifications
	if (options.clear) {
		$.jx_clear_notifications(options);
	}
	$(options.target).prepend($(notification));
	$(notification).fadeIn();
	if (options.timeout > 0) {
		setTimeout(function() {
			$(notification).fadeOut();
		}, options.timeout);
	}
};
/**
 *	Monitor max length plugin
 *
 *	Given a wrapper around a textarea, attachs a monitoring function to key
 *	input events
 *
 *	@param	maxlength	The maximum amount of characters allowed in the textarea
 *	@param	message		If defined, updates a counter element within the wrapper 
 *						with the current amount of characters typed
 */
(function($) {
	$.fn.monitorMaxLength = function(options) {
		var defaults = {
			'maxlength': 120,
			'message': "{current}/{maxlength} characters"
		};
		var options = $.extend(defaults, options);
		return this.each(function() {
	   		var parent = this;
			function monitor(textarea) {
				var length = $(textarea).val().length;
				var message = options.message;
				if (message != "") {
					message = message.replace("{current}", length);
					message = message.replace("{maxlength}", options.maxlength);
					$(parent).find(".counter").html(message);
				}
				if (length > options.maxlength) {
					if (! $(textarea).hasClass("jx_field_exceeded")) { $(textarea).addClass("jx_field_exceeded"); }
					$(parent).find(".counter").addClass("jx_field_exceeded");
				} else {
					$(textarea).removeClass("jx_field_exceeded");
					$(parent).find(".counter").removeClass("jx_field_exceeded");
				}
			}
			// Simulate load event
			monitor($(parent).find("textarea"));
			$(parent).find("textarea").bind("keydown change input", function() {
				monitor(this);
			});
		});
	};
})(jQuery);

/**
 * Common behaviors for phone input elements
 *
 * Given a wrapper around common phone elements, try to predict the country
 * code that the user types into the phone field.
 */
(function($) {
	$.fn.attachPhoneBehaviors = function() {
		return this.each(function() {
			var parent = this;

			// Regexes for filtering input
			var reValidChars = /\d/;
			var reAllowedChars = /[\(\)\.\-\ \,\\\/\+]/;
			var reFilteredInput = /[^0-9\(\)\.\-\ \,\\\/\+]/mgi;
			var reClipboardChars = /[acvxz]/i;

			function selectCountry(iso) {
				$(parent).find(".countries option").removeAttr("selected");
				$(parent).find(".countries option[value="+iso.toUpperCase()+"]").attr("selected","selected");

				var country = countries[iso.toLowerCase()];
				if (country) {
					var code = countries[iso.toLowerCase()].code;
					$(parent).find(".country_code").val(code);
					// If the phone number already has the country code in it, parse it out
					var phone = $.trim($(parent).find(".phone").val());
					var code_pos = 0;
					var scrubbed = "";
					// Simple regex and substrings don't account for all cases, so we
					// filter character by character
					for (var i = 0; i <= phone.length; i++) {
						// We've filtered the country code already
						if (code_pos == code.length) {
							scrubbed += phone.charAt(i);
						// Skip this character if it matches the current pointer in the country code
						} else if (code.charAt(code_pos) == phone.charAt(i)) {
							code_pos++;
						} else {
							scrubbed += phone.charAt(i);
						}
					}
					$(parent).find(".phone").val(scrubbed);
				} else {
					$(parent).find(".country_code").val('');
				}
			}
			function predictCountry() {
				// Scrub phone number of everything except digits and country code indicator (+)
				var phone = $.trim($(parent).find(".phone").val());
				var stripped = phone.replace(/[^\d\+]/mgi, "");
				if (stripped.indexOf("+") === 0) {
					var match = '';
					// If the phone number leads with a +, assume that the user has entered a country code
					// directly, and match against our list of country codes
					for (var i in country_codes) {
						if (stripped.indexOf(i) === 0) {
							match = country_codes[i];
						}
					}
					// Some area codes are different countries entirely
					if (match == "us") {
						if (stripped.indexOf("684") === 2) { match = "as"; }
						if (stripped.indexOf("264") === 2) { match = "ai"; }
						if (stripped.indexOf("268") === 2) { match = "ag"; }
						if (stripped.indexOf("242") === 2) { match = "bs"; }
						if (stripped.indexOf("246") === 2) { match = "bb"; }
						if (stripped.indexOf("441") === 2) { match = "bm"; }
						if (stripped.indexOf("345") === 2) { match = "ky"; }
						if (stripped.indexOf("767") === 2) { match = "dm"; }
						if (stripped.indexOf("809") === 2) { match = "do"; }
						if (stripped.indexOf("829") === 2) { match = "do"; }
						if (stripped.indexOf("473") === 2) { match = "gd"; }
						if (stripped.indexOf("671") === 2) { match = "gu"; }
						if (stripped.indexOf("876") === 2) { match = "jm"; }
						if (stripped.indexOf("664") === 2) { match = "ms"; }
						if (stripped.indexOf("670") === 2) { match = "mp"; }
						if (stripped.indexOf("787") === 2) { match = "pr"; }
						if (stripped.indexOf("939") === 2) { match = "pr"; }
						if (stripped.indexOf("869") === 2) { match = "kn"; }
						if (stripped.indexOf("758") === 2) { match = "lc"; }
						if (stripped.indexOf("784") === 2) { match = "vc"; }
						if (stripped.indexOf("868") === 2) { match = "tt"; }
						if (stripped.indexOf("340") === 2) { match = "vg"; }
						if (stripped.indexOf("284") === 2) { match = "vi"; }
						if (stripped.indexOf("649") === 2) { match = "tc"; }
					}
					// If the user has typed a valid country code, strip out the country code and
					// show the country as selected
					if (match) {
						selectCountry(match);
					}
				}
			}
			// Country dropdown selection
			$(parent).find(".countries").change(function() {
				selectCountry($(this).find("option:selected").val());
			});
			// Typing a country code directly should automatically select the country
			$(parent).find(".country_code").bind("change keyup", function(evt) {
				if (evt.type == "keyup") {
					var key = evt.charCode || evt.keyCode || 0;
					if (key < 47) {
						return true;
					}
				}
				var code = $(this).val().replace(/[^\d]/mgi, "");
				var iso  = country_codes["+"+code];
				if (iso) {
					selectCountry(iso);
					predictCountry();
					$(parent).find(".phone").focus();
				} else {
					$(parent).find(".countries").val("selectACountry");
				}
			});
			// As the user is typing, filter valid input:
			// 01234567890-+.,()\/
			$(parent).find(".phone").bind("keypress", function(evt) {
				// Determine which key/character was pressed
				var key  = evt.charCode || evt.keyCode || 0;
				var char = String.fromCharCode(key);
				
				// Valid input by default is digits, allowed whitespace, and any control keystrokes
				var valid = reValidChars.test(char) || reAllowedChars.test(char);

				// Check for clipboard events
				if (evt.ctrlKey && reClipboardChars.test(char)) {
					valid = true;
				}
				// Check for control keys (space, enter, tab, etc)
				if (key < 47) {
					valid = true;
					predictCountry(); // Try to predict the country code as soon as whitespace is entered
				}

				// Trim leading whitespace and certain punctuation
				$(this).val($(this).val().replace(/^[\ \-\\\/\,\.]/, ""));

				// Block non-valid input
				if (valid) {
					return true;
				} else {
					return false;
				}
			});
			$(parent).find(".phone").bind("change input", function(evt) {
				// Backup input filter
				$(this).val($(this).val().replace(reFilteredInput, ""));
			});
			// Return false for browsers that support this
			$(parent).find(".phone").bind("beforepaste", function(evt) {
				return false;
			});
			// Predict the country code after a user pastes in a phone number
			$(parent).find(".phone").bind("paste", function(evt) {
				// If the browser allows us, prefilter the user's clipboard data
				if (window.clipboardData && window.clipboardData.getData && window.clipboardData.setData) {
					var clipboard = window.clipboardData.getData("text");
					var filtered  = clipboard.replace(reFilteredInput, "");
					window.clipboardData.setData("text", filtered);
				}
				// After a paste has happened, try parsing out the country code.
				setTimeout(function() {
					predictCountry();
					selectCountry($(parent).find(".countries option:selected").val());
				}, 100);
		    });
			// Predict the country code after a user moves away from the field
			$(parent).find(".phone").bind("blur change", function(evt) {
				predictCountry();
				// If the user has already selected a country then puts in a phone
				// number that includes the country code, have the selectCountry
				// function parse it out.
				selectCountry($(parent).find(".countries option:selected").val());
			});
		});
	};
})(jQuery);

/**
 * Keep default text
 */
(function($) {
	$.fn.keepDefaultText = function() {
		return this.each(function() {
			if (this.title != "") {
				$(this).focus(function() {
					$(this).removeClass("jx_field_default");
				});
				$(this).blur(function() {
					if ($(this).val() == "" || $(this).val() == this.title) {
						$(this).addClass("jx_field_default");
						$(this).val(this.title);
					}
				});
				if ($(this).val() == "" || $(this).val() == this.title) {
					$(this).addClass("jx_field_default");
					$(this).val(this.title);
				}
			}
		});
	};
})(jQuery);

$(document).ready(function() {
	// Call history tables progressive enhancements
	// This each construct allows us to embed multiple call history tables on a page,
	// if desirable.
	$('.jx_call_history').each(function() {
		var parent = this;
		$(parent).find('.row').mouseover(function() {
    		$(this).addClass('highlight');
			$('tr#' + this.id + '-bottom').addClass('highlight');
		});
		$(parent).find('.row').mouseout(function() {
			$(this).removeClass('highlight');
			$('tr#' + this.id + '-bottom').removeClass('highlight');
		});
		$(parent).find('.row').click(function() {
			$(parent).find('.row').removeClass('selected');
			$(this).addClass('selected');
			$(parent).find('tr.row-bottom').css('display', 'none');
			// Work around an IE bug:
			try {
				document.getElementById(this.id + '-bottom').style.display = 'table-row';
			} catch (e) {
				document.getElementById(this.id + '-bottom').style.display = 'block';
			}
		});
	});

	// Collapsible generic handler
	$(".collapsible").each(function() {
		var parent = this;
		var collapsed = $(parent).find(".collapsible_container").css("display") == "none" ? true : false;
		if (collapsed) { $(parent).find(".collapsible_status").addClass("collapsed"); }
		$(parent).find(".collapse_target").click(function() {
			collapsed = !collapsed;
			if (collapsed) {
				$(parent).find(".collapsible_status").addClass("collapsed");
				$(parent).find(".collapsible_container").slideUp();
			} else {
				$(parent).find(".collapsible_status").removeClass("collapsed");
				$(parent).find(".collapsible_container").slideDown();
			}
		});
	});

	$(".jx_form_elements").each(function() {
		var parent = this;
		var hidden = $(parent).find(".toggle_more").css("display") == "none" ? true : false;
		$(parent).find(".toggle").click(function() {
			if (hidden) {
				$(this).html("cancel");
				$(parent).find(".toggle_more").slideDown();
				hidden = false;
			} else {
				$(this).html("edit");
				$(parent).find(".toggle_more").slideUp();
				hidden = true;
			}
		});
	});

	// Default form fields
	$("input[type=text]").each(function() {
		if (this.title != "") {
			$(this).focus(function() {
				if ($(this).val() == this.title) {
					$(this).val("");
				}
				$(this).removeClass("jx_field_default");
			});
			$(this).blur(function() {
				if (this.value == "") {
					$(this).addClass("jx_field_default");
					this.value = this.title;
				}
			});
			if (this.value == "") {
				$(this).addClass("jx_field_default");
				this.value = this.title;
			}
		}
	});
	jx_load_tooltips();
});
