function Grasshopper() {
	var gh = this;
	
	function BrowserDetect() {
		this.searchString = function (data) {
			for (var i=0;i<data.length;i++)	{
				var dataString = data[i].string;
				var dataProp = data[i].prop;
				this.versionSearchString = data[i].versionSearch || data[i].identity;
				if (dataString) {
					if (dataString.indexOf(data[i].subString) != -1)
						return data[i].identity;
				}
				else if (dataProp)
					return data[i].identity;
			}
		};
		
		this.searchVersion = function (dataString) {
			var index = dataString.indexOf(this.versionSearchString);
			if (index == -1) return;
			return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
		};
		
		this.dataBrowser = [
			{
				string: navigator.userAgent,
				subString: "Chrome",
				identity: "Chrome"
			},
			{ 	string: navigator.userAgent,
				subString: "OmniWeb",
				versionSearch: "OmniWeb/",
				identity: "OmniWeb"
			},
			{
				string: navigator.vendor,
				subString: "Apple",
				identity: "Safari",
				versionSearch: "Version"
			},
			{
				prop: window.opera,
				identity: "Opera"
			},
			{
				string: navigator.vendor,
				subString: "iCab",
				identity: "iCab"
			},
			{
				string: navigator.vendor,
				subString: "KDE",
				identity: "Konqueror"
			},
			{
				string: navigator.userAgent,
				subString: "Firefox",
				identity: "Firefox"
			},
			{
				string: navigator.vendor,
				subString: "Camino",
				identity: "Camino"
			},
			{		// for newer Netscapes (6+)
				string: navigator.userAgent,
				subString: "Netscape",
				identity: "Netscape"
			},
			{
				string: navigator.userAgent,
				subString: "MSIE",
				identity: "Explorer",
				versionSearch: "MSIE"
			},
			{
				string: navigator.userAgent,
				subString: "Gecko",
				identity: "Mozilla",
				versionSearch: "rv"
			},
			{ 		// for older Netscapes (4-)
				string: navigator.userAgent,
				subString: "Mozilla",
				identity: "Netscape",
				versionSearch: "Mozilla"
			}
		];
		this.dataOS = [
			{
				string: navigator.platform,
				subString: "Win",
				identity: "Windows"
			},
			{
				string: navigator.platform,
				subString: "Mac",
				identity: "Mac"
			},
			{
				   string: navigator.userAgent,
				   subString: "iPhone",
				   identity: "iPhone/iPod"
		    },
			{
				string: navigator.platform,
				subString: "Linux",
				identity: "Linux"
			}
		];

		this.browser = this.searchString(this.dataBrowser) || "An unknown browser";
		this.version = this.searchVersion(navigator.userAgent)
			|| this.searchVersion(navigator.appVersion)
			|| "an unknown version";
		this.OS = this.searchString(this.dataOS) || "an unknown OS";
	};

	function ServerMessaging(interval) {
		this.__messageData = null;
		this.__lastMessageSerial = 0;
		this.__numMessageErrors = 0;
		this.__intervalTimer = -1;
		this.__extraDataToSend = null;
		this.__messages = [];
		this.__loadedMessagesCookie = false;
		this.__numSuccess = 0;
		this.__disconnected = false;
		
		var self = this;
		
		/**
		 * Loads messages data from the cookie; can be called multiple times, it only
		 * gets the data on the first call
		 * @return
		 */
		this._loadMessagesCookie = function() {
			if (this.__loadedMessagesCookie)
				return;
			if ($.cookie) {
				var data = $.cookie("messagesData");
				if (data) {
					this.__lastMessageSerial = data.lastMessageSerial;
					if ($.isArray(data.messages))
						this.__messages = data.messages;
				}
			}
			this.__loadedMessagesCookie = true;
		};
		
		/**
		 * Saves the messages data
		 * @return
		 */
		this._saveMessagesCookie = function() {
			if ($.cookie)
				$.cookie("messagesData", { lastMessageSerial: self.__lastMessageSerial, messages: this.__messages }, { expires: 7, path: '/' });
		};
		
		/**
		 * Deletes the messages data
		 * @return
		 */
		this._deleteMessagesCookie = function() {
			if ($.cookie)
				$.cookie("messagesData", null);
		};
		
		/**
		 * Updates the time when the user last edited a form
		 */
		this.setExtraDataToSend = function(data) {
			this.__extraDataToSend = data;
		};
		
		
		/**
		 * Called to check for messages; invoked by a timer or manually
		 * @return
		 */
		this.checkForMessages = function() {
			if (this.__disconnected)
				return;
			var data = {
					lastMessageSerial: this.__lastMessageSerial,
					url: window.location.pathname + window.location.search
				};
			var ed = this.__extraDataToSend;
			if (ed)
				for (var name in ed)
					data[name] = ed[name];
			if ($.idleTimer)
				data.lastActivity = new Date().getTime() - $.idleTimer("getElapsedTime");
			data.messagesAcknowledged = this.__messages.length == 0;
			$.ajax({
				url: "/grasshopper/server-messaging.json",
				type: "POST",
				data: data,
				success: function(data, textStatus, xhr) {
					self.__messageData = data;
				},
				error: function(xhr, textStatus, errorThrown) {
					self.__messageData = null;
				},
				complete: self.__checkForMessagesComplete 
			});
		};
		
		/**
		 * AJAX callback returned from the keep-alive
		 * @param xhr
		 * @param status
		 * @return
		 */
		this.__checkForMessagesComplete = function(xhr, status) {
			var data = self.__messageData;
			//$.log("received status " + xhr.status + ": data = " + data);
			
			// Found new data
			if (xhr.status == 200) {
				self.__numMessageErrors = 0;
				self.__numSuccess++;
				try {
					if (!data)
						return;
					data = $.trim(data);
					if (!data.length)
						return;
					var data = eval("(" + data + ")");
					if (!data || !data.messageSerial || data.messageSerial == self.__lastMessageSerial) {
						self._hideMessageWindow(true);
						return;
					}
					
					self.__lastMessageSerial = data.messageSerial;
					if (data.disconnect) {
						self.__disconnected = true;
						clearInterval(self.__intervalTimer);
						self.__intervalTimer = null;
					}
					self.addMessages(data.messages);
				} catch(e) {
					$.log("Error while generating message view: " + e.name + ": " + e.message);
				}
				
			// No Data means that no data is applicable - stop polling
			} else if (xhr.status == 204) {
				self._hideMessageWindow(true);
				clearInterval(self.__intervalTimer);
				self.__intervalTimer = null;
				
			// ...and it's not "not modified"
			} else if (xhr.status != 304) {
				$.log("Error while retrieving messages: " + status + " " + xhr.status);
				self.__numMessageErrors++;
				if (self.__numMessageErrors >= 5) {
					$.log("Cancelling message timer because of too many errors (" + self.__numMessageErrors + ")");
					clearInterval(self.__intervalTimer);
					self.__intervalTimer = null;
				}
			}
		};
		
		/**
		 * Returns the message window DIV, creating it if necessary
		 * @param doNotCreate if true, the window is not created if it does not already exist
		 * @return null if doNotCreate is true and the window does not exist, otherwise the jQuery result 
		 */
		this._getMessageWindow = function(doNotCreate) {
			var $msgWindow = this.$__msgWindow = $("#g_messageWindow");
			if ($msgWindow.length)
				return $msgWindow;
			if (doNotCreate)
				return null;
			
			$('body').prepend('<div id="g_messageWindow"><div class="g_messages"></div><p><a href="#"><button class="submitButton"><span>Close this window</span></button></a></div></p>');
			$msgWindow = $("#g_messageWindow");
			$("p > a", $msgWindow).click(function(e) {
				gh.killEvent(e);
				self._hideMessageWindow(true);
				if (self.__disconnected)
					window.location = "/index.html";
			});
			var $wrapper = $("#wrapper");
			if ($wrapper.length) {
				var pos = $wrapper.offset();
				$msgWindow.offset({ left: pos.left + 20, top: pos.top + 20 });
			}
			
			return $msgWindow;
		};
	
		/**
		 * Hides the message window
		 * @param clearDown if true, the messages are disposed of
		 * @return
		 */
		this._hideMessageWindow = function(clearDown) {
			var $msgWindow = this._getMessageWindow(true);
			if (!$msgWindow)
				return;
			$msgWindow.hide();
			gh.hideModalMask();
			if (clearDown == undefined || clearDown != false) {
				self.__messages = [];
				self._deleteMessagesCookie();
				$("div.g_messages", $msgWindow).html("");
			}
		};
	
		/**
		 * Adds messages to the display
		 * @param msgs {Map[]} array of messages, each map looks like:
		 * 	sender {String}: Name of the sender
		 * 	kind {String [ "information", "warning", "error", "success" ]}: type of message
		 * 	when {long} date the message was sent
		 *  modal {boolean}: if true, the window is displayed as modal
		 * @return
		 */
		this.addMessages = function(msgs) {
			var $msgWindow = this._getMessageWindow();
			
			this.__messages = this.__messages.concat(msgs);
			if (!this.__messages.length) {
				$msgWindow.hide();
				return;
			}
			
			var modal = false;
			for (var i = 0; !modal && i < this.__messages.length; i++)
				modal = this.__messages[i].modal;
			
			var content = [];
			for (var i = 0; i < msgs.length; i++) {
				var msg = msgs[i];
				var kind = msg.kind;
				kind = kind.substring(0, 1).toUpperCase() + kind.substring(1);
				content.push('<div class="g_message g_message');
				content.push(kind);
				content.push('"><p class="g_messageFrom">From: ');
				content.push(msg.sender);
				content.push('</p><p class="g_messageBody">');
				content.push(msg.message);
				content.push('</p></div>\n');
			}
			
			var str = content.join("");
			$("div.g_messages", $msgWindow).append(str);
			if (modal) {
				$msgWindow.addClass("g_messageModal");
				gh.showModalMask();
			} else {
				$msgWindow.removeClass("g_messageModal");
				gh.hideModalMask();
			}
			
			$msgWindow.show();
			this._saveMessagesCookie();
		};
		
		$(function() {
			self.__intervalTimer = setInterval("gh.getServerMessaging().checkForMessages()", interval);
		});
	};

	
	/* ************************************************************************
	 * 
	 * Browser Detection
	 * 
	 */
	
	this.__browserDetector = null;
	
	/**
	 * Returns the BrowserDetector, creating one if necessary
	 * @return {BrowserDetector}
	 */
	this.getBrowserDetector = function() {
		if (!this.__browserDetector)
			this.__browserDetector = new BrowserDetector();
		return this.__browserDetector;
	};
	
	
	
	/* ***********************************************************************
	 * 
	 * Misc functions
	 * 
	 */
	this.trim = function(str) {
		return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
	};

	/**
	 * Positions the named element on the right hand side of its parent
	 * 
	 * @param linkId
	 */
	this.positionRHS = function (linkId, scope) {
		$(function() {
			var link = $(linkId, scope);
			var parent = link.parent();
			
			link.css("position", "absolute");
			var pt = parent.position();
			var left = parent.width() - link.width();
			var top = (parent.height() - link.height()) / 2;
			link.css("left", pt.left + left);
		});
	};

	/**
	 * Prevents an event from activating completely (specifically, it is needed
	 * to prevent overlapping links in overlapping div's triggering two events)
	 */
	this.killEvent = function(e) {
		e.preventDefault();
		e = e || window.event;
		if (e.stopPropagation) 
			e.stopPropagation();
		e.cancelBubble = true;
	};

	/**
	 * Adds a link with a properties image to the RHS of an element and makes it
	 * trigger the admin toolbar
	 * 
	 * @param elementId
	 * @param toolbarId
	 */
	this.addAdminToolbarPopup = function (elementId, toolbarId) {
		$(function() {
			var elem = $(elementId);
			if (typeof elem == "undefined" || !elem)
				return;
			elem.html(elem.html() + '<a id="anonToolbarLink" class="adminToolbar" href="#"><img src="/skin/images/propertiesedit.gif" border="0" /></a>');
			var link = $("#anonToolbarLink", elem);
			link.ready(function() {
				var pt = elem.position();
				if (typeof pt == "undefined" || !pt) {
					$.log("cannot find position of element " + elementId);
					return;
				}
				var left = elem.width() - link.width();
				var top = (elem.height() - link.height()) / 2;
				
				link.
					css("position", "absolute").
					css("left", pt.left + left).
					click(function(e) {
						gh.killEvent(e);
	
						var toolbar = $(toolbarId);
						var ptLink = link.position();
						var top;
						if (ptLink.top < toolbar.outerHeight() + 10)
							top = ptLink.top + link.outerHeight();
						else
							top = ptLink.top - toolbar.outerHeight();
						$(".popupAdminToolbar").css("visibility", "hidden");
						toolbar.
							css("visibility", "visible").
							css("left", (ptLink.left + link.outerWidth()) - toolbar.outerWidth()).
							css("top", top);
					});
			});
		});
	};

	/**
	 * Links a popup admin toolbar to a hyperlink
	 * 
	 * @param linkId
	 * @param toolbarId
	 */
	this.setAdminToolbarPopup = function (linkId, toolbarId, callback, context) {
		$(function() {
			$(linkId).click(function(e) {
				gh.killEvent(e);
				
				var toolbar = $(toolbarId);
				var link = $(this);
				var ptLink = link.position();
				var top;
				if (ptLink.top < toolbar.outerHeight() + 10)
					top = ptLink.top + link.outerHeight();
				else
					top = ptLink.top - toolbar.outerHeight();
				toolbar.
					css("visibility", "visible").
					css("left", (ptLink.left + link.outerWidth()) - toolbar.outerWidth()).
					css("top", top);
				var index = 0;
				toolbar.bind("mouseleave", function() {
					index--;
					if (index < 1)
						toolbar.css("visibility", "hidden");
				}).bind("mouseenter", function() {
					index++;
				});
				if (callback)
					callback.call(context, this, toolbar);
			});
		});
	};

	/**
	 * Adds a confirmation message to a link
	 * 
	 * @param linkId
	 * @param url
	 */
	this.addDeleteDocumentConfirmation = function (linkId, url) {
		$(function() {
			$(linkId).click(function(e) {
				e.preventDefault();
				if (confirm("Are you sure that you want to delete this document? (your action cannot be undone)"))
					window.location = url;
			});
		});
	};
	
	/**
	 * Locates the first child node with a given selector
	 * @param startFrom
	 * @param selector
	 * @return null if none found
	 */
	this.findFirst = function(startFrom, selector) {
		var result = $(startFrom).find(selector);
		if (!result || !result.length)
			return null;
		return result[0];
	};
	
	/**
	 * Opens the specified URL
	 */
	this.openLocation = function(url, options) {
		if (!options)
			options = {};
		if (!options.target || options.target != "_blank")
			window.location = url;
		else if (options.features != undefined)
			window.open(url, options.name, options.features);
		else
			window.open(url, options.name);
	};

	/**
	 * Adds hover bars to selectable table rows
	 */
	this.configureSelectableRow = function(i) {
		var $this = $(this);
		var link = gh.findFirst($this, ".selectableRowLink");
		if (!link)
			link = gh.findFirst($this, "a");
		$this.
			hover(function(i) {
					$(this).addClass("g_hoverBlock");
				}, function() {
					$(this).removeClass("g_hoverBlock");
				});
		if (!link)
			return;
		var href = $(link).attr("href");
		if (!href)
			return;
		$this.
			click(function(e) {
					gh.killEvent(e);
					gh.openLocation(href, { target: $(link).attr("target") });
				});
		
		// All Anchor tags need to have their own click handler otherwise
		// our on-click handler
		// will fire and then the default anchor tag. This handler will
		// block our on-click
		// and do the anchor tag's own firing
		$this.find("a:not(.selectableRowLink)").click(function(e) {
            e = e || window.event;
            e.preventDefault();
            if (e.stopPropagation)
                e.stopPropagation();
            e.cancelBubble = true;
            var href = $(this).attr("href");
            if (href && href != "#" && href.length > 0) {
            	var options = { };
            	options.target = $(this).attr("target");
				gh.openLocation($(this).attr("href"), options);
            }
        });
	};
	
	
	/**
	 * Makes the entire element clickable and have a hand pointer when hovered.
	 * The link is either a child link with a class of "selectableElementLink"
	 * or is the first anchor tag found.
	 * 
	 * The hovering effect is done by adding the "hover" class to the element
	 */
	$(function() {
		$(".selectableElement").each(function(i) {
			var links = $(this).find(".selectableElementLink");
			var link = null;
			if (links && links.length && links.length > 0)
				link = links[0];
			if (!link) {
				link = $(this).find("a");
				if (links && links.length && links.length > 0)
					link = links[0];
			}
			if (!link)
				return;
			var href = $(link).attr("href");
			$(this).
				hover(function(i) {
						$(this).addClass("hover");
					}, function() {
						$(this).removeClass("hover");
					}).
				click(function() {
						window.location = href;
					});
		});
	});

	
	/**
	 * Adds a close button to popupAdminToolbars
	 */
	$(function() {
		$("div.popupAdminToolbar").each(function() {
				var t = $(this);
				t.html(t.html() + "<a href='#' class='popupAdminToolbarCloseLink'><img src='/skin/images/closeWindow.gif' border='0'></a>");
				var close = $(".popupAdminToolbarCloseLink", t);
				var paddingTop = t.css("padding-top").replace("px", "");
				var paddingRight = t.css("padding-right").replace("px", "");
				close.
					css("padding-left", "10px").
					css("position", "relative").
					css("top", (0 - t.height() + close.height() - paddingTop) + "px").
					css("left", paddingRight + "px").
					click(function(e) {
						gh.killEvent(e);
						
						t.css("visibility", "hidden");
					});
			});
		$(".adminToolbar a.deleteDocumentLink").click(function(e) {
				gh.killEvent(e);
				if (confirm("Are you sure that you want to delete this document? (your action cannot be undone)"))
					window.location = this.href;
			});
	});
	
	this._getModalMask = function() {
		var $mask = $("#g_modalMask");
		if (!$mask.length) {
			$('body').prepend('<div id="g_modalMask"></div>');
			$mask = $("#g_modalMask");
			$mask.css({ position: "absolute", top: "0", left: "0" });
		}
		return $mask;
	};

	this.showModalMask = function() {
		var $mask = this._getModalMask();
		
        var height = $(document).height();  
        var width = $(window).width();  
      
        $mask.css({ 'width': width, 'height': height })
        	.show()
        	.fadeIn(1000)
        	.fadeTo("slow", 0.7);    
	};

	this.hideModalMask = function() {
		var $mask = this._getModalMask();
		$mask.hide();
	};

	this.loadingBlockerDiv = null;
	this.loadingDiv = null;
	
	this.showLoadingBlocker = function() {
		var width;
		var height;
		if (gh.BrowserDetect.browser == "Explorer") {
			width = document.documentElement.clientWidth;
			height = document.documentElement.clientHeight;
		} else {
			width = document.documentElement.clientWidth;
			height = document.documentElement.clientHeight;
		}
		
		if (this.loadingBlockerDiv) {
			this.__setVisible(this.loadingBlockerDiv, true);
			this.__setVisible(this.loadingDiv, true);
			return;
		}
		
		$(function() {
			var isIE6 = gh.BrowserDetect.browser == "Explorer" && gh.BrowserDetect.version == 6;
			
			var div = document.createElement("div");
			var css = "position: absolute; width: " + width + "px; height: " + height + "px; left: 0px; top: 0px; background-color: #bcbcbc; filter: alpha(opacity=50); -moz-opacity: 0.5; opacity: 0.5; background-image: url(/skin/images/firtree_blurred.png); background-position: center center; background-repeat: no-repeat";
			if (isIE6)
				css += "; behavior: url(/public/resources/iepngfix/iepngfix.htc)";
			div.style.cssText = css;
			document.body.appendChild(div);
			
			var div2 = document.createElement("div");
			div2.style.cssText = "width: 400px; margin-left: auto; margin-right: auto; padding-top: 200px; font-size: 16pt; font-weight: bold; text-align: center";
			div2.appendChild(document.createElement("br"));
			var img = document.createElement("img");
			img.src = "/skin/images/please_be_patient.png";
			if (isIE6)
				img.style.cssText = "; behavior: url(/public/resources/iepngfix/iepngfix.htc)";
			div2.appendChild(img);
			var img = document.createElement("img");
			img.src = "/public/resources/images/animated-loading-large.gif";
			div2.appendChild(img);
			document.body.appendChild(div2);
			
			gh.loadingBlockerDiv = div;
			gh.loadingDiv = div2;
		});
	};
	
	this.hideLoadingBlocker = function() {
		if (!this.loadingBlockerDiv)
			return;
		this.__setVisible(this.loadingBlockerDiv, false);
		this.__setVisible(this.loadingDiv, false);
	};
	
	this.__setVisible = function(div, visible) {
		var css = div.style.cssText.replace("display\s*:\s*\w+", "");
		if (!visible)
			css += "; display: none";
		div.style.cssText = css;
	};
	
	this.loadForm = function(url) {
		gh.showLoadingBlocker();
		setTimeout(function() { gh.__loadForm(url); }, 250);
	};
	
	this.__loadForm = function(url) {
		$(function() {
			var elemForm = document.createElement("form");
			var formContent = document.getElementById("formContent");
			formContent.appendChild(elemForm);
			
			var elemScript;
			
			qxsettings = new Object();
			qxsettings['qx.enableApplicationLayout'] = false;
			/*
			 * elemScript = document.createElement("script");
			 * elemForm.appendChild(elemScript); var txt =
			 * document.createTextNode("qxsettings = new Object();
			 * qxsettings['qx.enableApplicationLayout'] = false;");
			 * elemScript.appendChild(txt); elemScript.type = "text/javascript";
			 */
			
			elemScript = document.createElement("script");
			elemScript.type = "text/javascript";
			elemScript.src = url;
			elemForm.appendChild(elemScript);
			
			_editor_url="/public/xinha/"; 
			_editor_lang="en"; 
			_editor_skin="silva";
			/*
			 * elemScript = document.createElement("script"); elemScript.type =
			 * "text/javascript"; txt =
			 * document.createTextNode('_editor_url="/public/xinha/";
			 * _editor_lang="en"; _editor_skin="silva";');
			 * elemScript.appendChild(txt); elemForm.appendChild(elemScript);
			 */
			
			elemScript = document.createElement("script");
			elemScript.type = "text/javascript";
			elemScript.src = "/public/xinha/XinhaCore.js";
			elemForm.appendChild(elemScript);
			
			elemScript = document.createElement("script");
			elemScript.type = "text/javascript";
			elemScript.src = "/public/autoforms/xinha_fix.js";
			elemForm.appendChild(elemScript);
			
		});
	};
	
	/* *****************************************************************************
	 * 
	 * Server callback and message processing
	 * 
	 */
	this.__serverMessaging = null;
	
	/**
	 * Enables the periodic checking for messages and keep alives
	 */
	this.enableServerMessaging = function() {
		if (this.__serverMessaging)
			return;
		this.__serverMessaging = new ServerMessaging(15000);
		if ($.idleTimer)
			$.idleTimer(3000);
	};
	
	/**
	 * Gets the messaging object, or null if not enabled
	 */
	this.getServerMessaging = function() {
		return this.__serverMessaging;
	};
	
	
	/* ****************************************************************************************
	 * 
	 * CAPTCHA forms
	 * 
	 */
	
	/**
	 * Configures a form with a CAPTCHA image
	 * @param container {DOM|String} the element or selector which contains the form
	 * @param captchaCategory {String} the category for the CAPTCHA
	 * @param options {Map?} map of options:
	 * 		placeholders {Map?} map of placeholders for inputs, indexed by ID
	 * 		magnifyingGlass {Boolean?false} if true, then a magnifying glass is provided when 
	 * 					hovering over the CAPTCHA image
	 */
	this.setCaptchaForm = function(container, captchaCategory, opts) {
		if (!opts)
			opts = {};
		var placeholders = opts.placeholders||{};
		
		var isVisible = false;
		var imageUrl = null;
		var $magnifyingGlass = null;
		var $captchaImage = $(container).find("img.g_captchaImage");
		var $captchaCodeInput = $(container).find("input.g_captchaCodeInput");
		var $submitButton = $(container).find("input[type=submit]");
		if (!$submitButton.length)
			$submitButton = $(container).find("input[type=image]");
		if (!placeholders[$captchaCodeInput.attr("id")])
			placeholders[$captchaCodeInput.attr("id")] = "Security Code";
		
		// Generates a new captcha image
		function newCaptcha() {
			imageUrl = "/grasshopper/captcha.jpg?category=" + captchaCategory + "&nocache=" + new Date().getTime();
			$captchaImage.attr("src", imageUrl);
			$captchaCodeInput.addClass("g_captchaInputWithPlaceholder").val(placeholders[$captchaCodeInput.attr("id")]);
		}
		
		// Shows the captcha image for the first time
		function showCaptcha() {
			if (isVisible)
				return;
			newCaptcha();
			$(container).find(".g_captchaImageContainer").show();
			isVisible = true;
		}
		
		$captchaImage
			// Update the magnified version
			.load(function() {
				if ($magnifyingGlass)
					$magnifyingGlass.attr("src", imageUrl + "&refresh=false");
			})
			// Generate a new captcha every time the image is clicked
			.click(newCaptcha);
		
		$(container).find("input[type!=submit]")
			// For all of the input fields, either put a placeholder in
			//	or if it already has a value show the captcha
			.each(function(evt) {
	    		var val = $(this).val()||"";
				var id = $(this).attr("id");
				var placeholder = placeholders[id];
	    		if (val == "" || val == placeholder)
					$(this).addClass("g_captchaInputWithPlaceholder").val(placeholder);
	    		else {
	    			$(this).removeClass("g_captchaInputWithPlaceholder");
	    			showCaptcha();
	    		}
    		})
    		// Whan a field is focused, remove any placeholder
    		.focus(function(evt) {
	    		var val = $(this).val()||"";
				var id = $(this).attr("id");
				var placeholder = placeholders[id];
	    		if (val == placeholder) {
	    			$(this).removeClass("g_captchaInputWithPlaceholder").val("");
	    			showCaptcha();
	    		}
    		})
	    	// When a field looses focus, if it has no text put the placeholder back
    		.blur(function(evt) {
    			var val = $(this).val()||"";
    			if (val == "") {
   					var id = $(this).attr("id");
   					var placeholder = placeholders[id];
   					$(this).addClass("g_captchaInputWithPlaceholder").val(placeholder);
    			}
    		});
		
		$(container).find("form").submit(function(evt) {
			// Validate email
			var result = true;
			
			$(container).find(".g_captchaEmail").each(function() {
				var email = $(this).val();
				if (email.indexOf('@') < 1)
					result = false;
			});
			
			if (!result) {
				alert("Please enter your email address");
				gh.killEvent(evt);
				return false; 
			}

			// Initialise
			var $input = $(container).find("input.g_captchaCodeInput");
			if ($input.length) {
				var captchaVal = $input.val();
				var url = "/grasshopper/checkCaptcha.html?category=" + captchaCategory + "&remove=false&captcha=" + captchaVal;
				
				// Send to the server to check if the captcha code is valid
				$.ajax({
					url: url,
					async: false,
					success: function(data, status, xhr) {
						result = eval(data);
					},
					error: function(xhr, status, errorThrown) {
					}
				});
	
				if (!result) {
					alert("That wasn't quite right - please try again and we'll generate a different picture (or you can email info@debtadmincentre.com for help)");
					newCaptcha();
					gh.killEvent(evt);
				}
			}
			
			return result;
		});
		
		// Magnifying glass
		if (opts.magnifyingGlass) {
			var loadedGlass = false;
			$captchaImage
				.hover(function(){
					if (!loadedGlass) {
						$('body').prepend('<img id="g_captchaMagnify" style="z-index: 1000; position: absolute;" src="' + imageUrl + '&refresh=false" alt="Image CAPTCHA"/>');
						loadedGlass = true;
					}
				 
					$magnifyingGlass = $('#g_captchaMagnify');
					var elem = this;
					var position = $submitButton.offset();
		
					$magnifyingGlass
						.css({'top' : position.top + $submitButton.height(), 'left' : position.left - 10, 'border': '5px solid #8583b0'})
						.fadeIn()
						.click(function() { 
							$(this).hide(); 
						})
						.hover(function() {
							$magnifyingGlass.stopTime("zoomOut");
						}, function() {
							$magnifyingGlass.oneTime(3000, 'zoomOut', function() {
								$magnifyingGlass.fadeOut();
							});
						});
				},function(e){
				});
			
		// Zoom into the CAPTCHA image
		} else {
			var width = $captchaImage.attr("width")||$captchaImage.css("width");
			var height = $captchaImage.attr("height")||$captchaImage.css("height");
			$captchaImage
				.hover(
					function() {
						$captchaImage
							.removeAttr("width")
							.removeAttr("height")
							.css("width", "")
							.css("height", "");					
					},
					function() {
						$captchaImage
							.css("width", width)
							.css("height", height);					
					});
		}
	};
	
	this.initRollovers = function() {
		var x = $(".g_rolloverImage");
		$(".g_rolloverImage").each(function() {
			var src = $(this).attr('src');
			if (src) {
				var regex = /^(.+)(\.[a-z]+)$/;
				var match = regex.exec(src);
				if (match) {
					this.g_normalImage = src;
					this.g_rolloverImage = match[1] + '_o' + match[2];
				}
			}
		}).hover(
			function() {
				if (this.g_rolloverImage)
					$(this).attr('src', this.g_rolloverImage);
			}, function() {
				if (this.g_normalImage)
					$(this).attr('src', this.g_normalImage);
			});
	};
	
	$(function() {
		$(".g_telephonePopover")
			.wrap('<span class="g_telephonePopoverWrapper" />')
			.each(function() {
				var $popover = $(this);
				var $wrapper = $popover.parent();
				$wrapper.prepend('<a class="g_telephoneMessage">View telephone number</a>');
				$("a.g_telephoneMessage", $wrapper).click(function() {
					$.ajax({
						async: true,
						cache: false,
						url: "/grasshopper/clickthru-counter.gh?url=text:telephone=" + encodeURIComponent($popover.text())
					});
					$(this).hide();
					$popover.show();
				});
			})
			.hide();
		
	});
}
gh = new Grasshopper();

$(function() {
	gh.initRollovers();
	$(".selectableRow").each(gh.configureSelectableRow);
});


if (jQuery) {
	(function($){
		$.extend({
			"log": function(){
				try {
					if (arguments.length < 1)
						return false;
					
					// join for graceful degregation
					var args = (arguments.length > 1) ? Array.prototype.join.call(arguments, " ") : arguments[0];
		
					// this is the standard; firebug and newer webkit browsers support this
					try {
						console.log(args);
						return true;
					} catch(e) {
					}
					
					// newer opera browsers support posting errors to their consoles (typo)
					try {
						opera.postError(args);
						return true;
					} catch(e2) { 
					}
		
					if ($("#jlogger").length > 0) {	
						$("#jlogger").append("	" + args);
					}
					return false;
				} catch(ex) {
					// Suppress it
				}
			}
		});
	})(jQuery);
}


