// JavaScript Document for all mytype pages
// for IE6 only
if ($.browser.msie && ($.browser.version=='6.0')) {
	document.write('<script type="text/javascript" src="/js/global_IE6.js' + scriptSuffix + '"></scr' + 'ipt>');
}

$.ajaxSetup({
    timeout: 30000 // 30 second timeout on ajax calls
});

$.fn.extend({
    // wrap the load method to allow LOGIN detection
    originalLoad: $.fn.load,
    load: function (url, data, callback) {
        var callbackWrapper = function (responseText, status) {
            if (status == 'success' && $(this).find("form#auth").length)
                location.href = url.replace(/ .*$/, ''); // LOGIN. session expired.
            else if (callback)
                return callback(responseText, status);
        };
		return $(this).originalLoad(url, data, callbackWrapper);
    }
});

// keep a logged in user's session active by calling noOp.mt at 19 minute intervals, session timeout is 20.
$(function () {
	var timeout = 19 * 60 * 1000; // 19 minutes
	var interval;
	var success = function (json) {
		if (!json.authenticated) clearInterval(interval); // stop refreshing the session if the user is not logged in
	};
	// interval to refresh the session. uses the previous success function to check the user is still logged in
	interval = setInterval(function () { jsonCall("/home/noOp.mt" + tomcatSessionArgs, "get", null, success); }, timeout);
});

// 1 enable styling of legends; 2 insert corner image for IE
$(document).ready(function () {
	// for IE, add classes to body, to enable variant styles
	if ($.browser.msie) {
		$("body").addClass("ie v" + parseInt($.browser.version));
	}
	// exclude IE6 from remainder of function, because it doesn't handle png transparency well
	if ($.browser.msie && ($.browser.version=='6.0')) {return;}
	// enable styling of legends (and fieldsets) in all forms with class 'ls' (=legend shift):  
	// a) mark each fieldset found in any form.ls (to remove border), and wrap its content 
	$("form.ls fieldset").addClass('nb').wrapInner("<span class=\"js wrap\"></span>");
	// b) shift each 'legend' up one level and convert to 'strong' tag, 
	$("form.ls legend").each(function() {$(this).prependTo($(this).parent().parent()).replaceWith("<strong class=\"js legend\">" + $(this).html() + "</strong>")});
	// c) shift each '.error ul' up one level
	$("form.ls fieldset.error ul").each(function() {$(this).prependTo($(this).parent().parent())});
	// shift any 'form div .vs' (very short submit button) into previous div, add margin-left, then remove former parent div
	$("form div .vs").each(function() {
		$(this).appendTo($(this).parent().prev()).addClass("ml5");
		$(this).parent().next().remove();
	});
	// insert large numbering with image background in each li in any ol.c (circled numbers) 
	$("ol.c li").each(function() {$(this).prepend("<span class=\"js num\">" + ($("ol.c li").index(this) + 1) +"</span>")});
	// adjust class name to enable specific styling ('ln' = large numbers)
	$("ol.c").addClass("ln");
	// work around IE shortcoming with CSS:
	if ($.browser.msie) {
		// rounded corners - insert corner image
		var cnr = $("<div class=\"js cnr\"></div>");
		cnr.appendTo($(".footer ul.nav"));
		$(".footer ul.nav").addClass("js adj ie7-8");
		// IE7- only
		if ($.browser.version!='8.0') {
			// :before
			$(".error li").prepend("! ");
			// missing margin-bottom on fieldset.error
			$("#dob-date").parent().parent().addClass("mt7");//
		}
	}
	// all a.ext links add target
	$("a.ext").attr("target","_blank");
});

// set primary and secondary titles & highlight corresponding menu tab(s)
$(document).ready(function() {
  var h1 = $(".content h1");
	if (h1) {
		var primaryTitle = '';
		var secondaryTitle = '';
		// if h1 has a title attribute
		if (h1.attr("title")) {
			var title = h1.attr("title");
			// if the title has a colon
			if (title.indexOf(':')!=-1) {
				// primaryTitle is the text string left of the colon; secondaryTitle is the text string right of the colon
				primaryTitle = title.split(' : ')[0];
				secondaryTitle = title.split(' : ')[1];
			}
			else {
				// otherwise, primaryTitle is the whole title
				primaryTitle = title;
			}
		}
		// if h1 has NO title attribute
		else {
			// if there's a code.site-section, its title is primaryTitle
			var ssCode = $(".content code.site-section");
			if (ssCode) {
				primaryTitle = ssCode.attr("title");
			}
			else {
				// otherwise, primaryTitle is in the body tag's id, left of underscore, & secondaryTitle is h1's text
				primaryTitle = document.body.id.split('_')[0];
				secondaryTitle = h1.html();
			}
		}
		// highlight the menu tab which correspond to primary title
		var currNav = $(".header .nav a:contains(" + primaryTitle + ")");
		currNav.addClass('current');
		// insert a marker inside the parent 'li' and position it at half way point
		$("<span class=\"js marker\"></span>").appendTo(currNav.parent());
		var currLeft = (currNav.parent().width()/2)-12 + 'px';
		$(".header span.marker").css("left",currLeft);
		// highlight the menu tab which corresponds to secondary title
		if (secondaryTitle != '') {
			$(".header .nav a:contains(" + secondaryTitle + ")").addClass('current');
		}
	}
});

// create, open and close ### LIGHTBOX ###
// mandatory argument is the content; optional arguments are 1 - width and 2 - height of lightbox;
// width of lightbox will be 645 px, unless specified otherwise (including padding);
function createLightbox(userOptions) {
    var options = { // defaults
			content: null, // optional content for lightbox, if not given then must be passed to the open method
			width: 645,
			height: null, // do not set a height; allow content to dictate this; otherwise positioning will be askew
			lightboxClass: null, // optional extra classes for lightbox
			maskClass: null, // optional extra classes for mask
			fadeIn: 1000, // time to fade in lightbox on open in millis
			fadeOut: 400, // time to fade out lightbox on close in millis
			buttons: [] // must be a list or null
    };
    $.extend(options, userOptions);
	// if #lightbox and #mask exist, kill them
	$("#lightbox,#mask").remove();
	// create semi-transparent mask (to cover page) and lightbox (to sit above mask, at centre)
	var mask = $('<div id="mask" class="js"/>');
	var box = $('<div id="lightbox" class="js"/>');
	// add the optional classes
	if (options.maskClass)
			mask.addClass(options.maskClass);
	if (options.lightboxClass)
			box.addClass(options.lightboxClass);
	// create 2 'close' spans for lightbox - 1 at top right corner, and 2 at bottom
	// NB - 'close' is a reserved word in js; so, avoid that as a var name:
	var closeX = $('<span class="close x">X</span>');
	// insert content and close spans in lightbox
	if (options.content) box.append(options.content);
	box.append(closeX);
	if (options.buttons) {
		$.each(options.buttons, function (i, v) {
			box.append(v);
		});
	}
	// for IE, set initial opacity to 0 (setting though CSS has no effect)
	if ($.browser.msie) {
		mask.css("filter","alpha(opacity=0)");
		box.css("filter","alpha(opacity=0)");
	}
	// insert mask and lightbox in body, and hide both
	$("body").append(mask.hide(),box.hide());
	// define onclick function which reveals the lightbox:
	var openlightbox = function (content) {
		if (content) {
			box.contents().filter(":not(.close)").remove();
			box.prepend(content);
		}
		// CSS 'height:100%' doesn't cover whole window, so set mask height through js:
		mask.height(Math.max($("body").height(), $(window).height()));
		// set top position of box as half the window height less half the lightbox height, plus window scrollTop
		// if lightbox height is not specified as a parameter, calculate it
		var bHeight = options.height ? options.height : box.height();
		var bTop = ($(window).height()/2 - bHeight/2) + $(window).scrollTop();
		// set left position of box as half the window width less half the lightbox width
		var bLeft = ($(window).width()/2) - ((options.width)/2);
		// apply width, height, top and left to lightbox
		box.css({top:bTop + 'px', left:bLeft + 'px', width:options.width + 'px'});
		// show and fade in, both mask and lightbox
		mask.animate({ opacity: .5 }, options.fadeIn).show();
		box.animate({ opacity: 1 }, options.fadeIn).show();
	};
	// define onclick function which conceals the lightbox:
	var closelightbox = function (e) {
		if (e) e.preventDefault();
		mask.animate({ opacity: 0 }, options.fadeOut, function () { mask.hide(); });
		box.animate({ opacity: 0 }, options.fadeOut, function () { box.hide(); });
	};
	// attach close function to mask and both close spans, on click
	mask.click(closelightbox);
	closeX.click(closelightbox);
	// map properties 'open' and 'close' to the 2 functions above
	// these functions can then be called from within another function, like so:
	// var lightbox = createLightbox(content); lightbox.close();
	return {
		open: openlightbox,
		close: closelightbox,
		remove: function () {
			mask.remove();
			box.remove();
		}
	};
}

/* // a simpler function to use: create and close a ### LIGHTBOX ### with specified content and width
function doLightbox(myContent,myWidth) {
	// create semi-transparent mask (to cover page) and lightbox (to sit above mask, at centre)
	var mask = $('<div id="mask" class="js"/>');
  var box = $('<div id="lightbox" class="js"/>');
  var inner = $('<div class="inner"/>');
  // create 2 'close' spans for lightbox - 1 at top right corner, and 1 at bottom
	var closeX = $('<span class="close x" href="#">X</span>');
	var closeC = $('<span class="action" href="#">Close</span>');
	// insert content in .inner, then .inner and .close spans in #lightbox
	inner.append(myContent);
	box.append(inner,closeX, closeC);
  // for IE, set initial opacity to 0 (setting though CSS has no effect) & shift .close.x up one level
	if ($.browser.msie) {
		mask.css("filter","alpha(opacity=0)");
		box.css("filter","alpha(opacity=0)");
	}
	// insert mask and lightbox in body, and hide both
	$("body").append(mask.hide(),box.hide());
	// CSS 'height:100%' doesn't cover whole window, so set mask height through js:
	mask.height(Math.max($("body").height(), $(window).height()));
	// set top position of box as half the window height less half the lightbox height, plus window scrollTop
	var bTop = ($(window).height()/2 - box.height()/2) + $(window).scrollTop();
	// set left position of box as half the window width less half the lightbox width
	var bLeft = ($(window).width()/2) - (myWidth)/2);
	// apply width, height, top and left to lightbox
	box.css({'top':bTop + 'px', 'left':bLeft + 'px', 'width':myWidth + 'px'});
	// show and fade in, both mask and lightbox
	mask.animate({ opacity: .5 }, options.fadeIn).show();
	box.animate({ opacity: 1 }, options.fadeIn).show();
	// define onclick function which conceals the lightbox, then attach to mask and both close spans:
	var closelightbox = function (e) {
		if (e) e.preventDefault();
		mask.animate({ opacity: 0 }, options.fadeOut, function () { mask.remove(); });
		box.animate({ opacity: 0 }, options.fadeOut, function () { box.remove(); });
	};
	mask.click(closelightbox);
	closeX.click(closelightbox);
	closeC.click(closelightbox);
}*/

/* //a simple ajax load using function above:	
// on clicking 'Cancel membershop' link, create a lightbox and insert child nodes of .content in hyperlinked page
$(".content:first a:contains('Cancel membership')").click(function () {
	var temp = $("<div class='temp'/>");
	temp.load($(this).attr("href") + " .content > *", null, function () {
		doLightbox(temp.html(),645);
	});
	return false;
}); */

function quickLightbox(userOptions) {
    var options = {
        type: 'confirm', // confirm / alert
	    title: "Are you sure",
	    message: "Are you sure?",

	    // for type=='confirm' only
        confirm: "Confirm",
        deny: "Cancel",
        confirmCallback: function () {},
        denyCallback: function () {}
    };
    $.extend(options, userOptions);
    var textcontent = document.createTextNode(options.message);
	var content = $('<div class="inner"/>').append($("<h1 class=\"s\"/>").text(options.title), textcontent);

	var buttons = null;
	var lightboxCloser = new Object();
	if (options.type == 'confirm') {
		// create
		var yesButton = $('<span class="action a"/>').text(options.confirm).click(function () {
			lightboxCloser.close();
			options.confirmCallback();
		});
		var noButton = $('<span class="action"/>').text(options.deny).click(function () {
			lightboxCloser.close();
			options.denyCallback();
		});
		buttons = [yesButton, noButton];
	}
	else {
		var okButton = $('<span class="action a"/>').text(options.deny).click(function () {
			lightboxCloser.close();
			options.denyCallback();
		});
		buttons = [okButton];	
	}

	var lightbox = createLightbox({
		content: content,
		width:360,
		height:160,
		buttons: buttons
	});

	lightboxCloser.close = function () { lightbox.close(); };

    setTimeout(function() { lightbox.open(); }, 1);
}

/*
		// ### typical $ load method
		// 4. load the imported page content (inside body) into the temp div
    inner.load(url, null, function (responseText, status) { //url+" .content"
		// if load is successful, shift #lightbox .content up one level & add class, then remove temp div,
				lBox.append($("#lightbox .content"));
				$("#lightbox .content").addClass('st'); // short text
				$("#lightbox .inner").remove();
				// and append buttons to lightbox
        lBox.append(agree,decline);
        if (status != "success")
        {
            // should do something here...
        }
    });
*/

function ajaxSubmit(form, successCallback) {
    if (!form || form.is(":not(form)"))
        throw "call this with a real form";

    // if more than one form provided, ajaxify all of them
    if (form.size() > 1) {
        form.each(function() { ajaxSubmit($(this), successCallback); });
	    return;
    }

    var setClass = function (n) {
        toggleClass(form, ["progress", "success", "failure"], n, 500);
    };

    function clearFieldErrors() {
        var errorDivs = form.find("div.error:has(input, select, textarea)");
        errorDivs.removeClass("error");
        errorDivs.each(function () {
            var ul = $(this).find("ul");
            ul.hide("fast", function () {
                ul.remove();
            });
        });
    };

    var getFormContainer = function(e) {
        var container = e;
        do {
            container = container.parent();
        } while (container != null && container.parent().nodeName != form.nodeName);
        return container;
    };

    var addFieldErrors = function (fieldErrors) {
        clearFieldErrors();

        // add new errors
        $.each(fieldErrors, function (n, v) {
            var field = form.find("[name='" + n + "']");

            // this should rarely happen. only for hidden fields, stuff on the form actions querystring
            if (field.size() == 0) {
                log("no field for field error: " + n + ": " + inspect(v, null, true));
            }
            if (field.size() > 1) {
                // take the first, this could happen for fields with the same name, ie. checkbox lists, radios, dates, etc...
                field = field.eq(0);
            }

            var container = getFormContainer(field);

            container.addClass("error");
            var errorUl = $("<ul/>");
            $.each(v, function (i, v) {
                var error = $("<li/>");
                error.html(v);
                errorUl.append(error);
            });
            errorUl.css("display", "none");
            container.prepend(errorUl);
            errorUl.show("fast");
        });
    };

    var disableFormFields = function () {
        form.find(":input").attr("disabled", "disabled");
    };

    var enableFormFields = function () {
        form.find(":input").removeAttr("disabled");
    };

    var progress = $('<div class="ajax progress">Working...</div>');
    var failure = $('<div class="ajax error"/>'); // action error, server failure or couldn't contact server
    var success = $('<div class="ajax success"/>');
    var timeout = $('<div class="ajax failure timeout"/>'); // servers busy, very slow connection...
    var clearStatusDivs = function () {
        $.each([success, failure, progress, timeout], function (i, v) {
            v.contents().remove();
            v.remove();
        });
    };

	form.find(":submit").click(function (e) {
		e.preventDefault();

		var extra = {};
		extra[$(this).attr("name")] = $(this).val();
		formSubmitter(extra);
	});

	var formSubmitter = function (extra) {

        var values = form.serialize();

		var urlencode = function (s) {
			var hex = "0123456789abcdef";
			return s.replace(/[^a-zA-Z0-9_]/g, function (g1) {
				var code = g1.charCodeAt(0);
				var finalDigit = code % 16;
				var firstDigit = (code - finalDigit) / 16;
				return '%' + hex.charAt(firstDigit) + hex.charAt(finalDigit);
			});
		};

		// extend with extra
		for (var n in extra) {
			if (values != null && values.length > 0)
				values += "&";
			values += urlencode(n) + "=" + urlencode(extra[n]);
		}

        form.addClass("ajax");

        var error = function (xmlHttpRequest, textStatus, errorThrown) {
            setClass("failure");
            enableFormFields();

            clearStatusDivs();
            if (textStatus == "timeout") {
                form.prepend(timeout);
            }
        };

        var callback = function (json, textStatus) {
            if (json.resultCode != "error" && !json.hasErrors) {
                setClass("success");

                clearStatusDivs();
                if (json.hasActionMessages) {
	                var ul = $("<ul/>");
	                $.each(json.actionMessages, function (i, v) {
		                ul.append($("<li/>").text(v));
	                });
	                success.append(ul);
                    form.prepend(success);
                }

                if (successCallback) {
                    // give the callback access to some stuff
                    var exposed = {
                        form: form, // the $(form) object
                        enable: enableFormFields, // function
                        disable: disableFormFields, // function
                        json: json // returned by the action
                    };
                    successCallback.apply(form.get(0), [exposed]);
                }
            }
            else {
                if (json.hasFieldErrors)
                    addFieldErrors(json.fieldErrors);

                error(null, null, null);
                if (json.hasActionErrors) {
                    failure.html("<strong>Error</strong><ul><li>" + json.actionErrors.join("</li><li>") + "</li></ul>");
	                form.prepend(failure);
                }
            }
        };

        jsonCall(form.attr("action"), form.attr("method"), values, callback, error);

        clearFieldErrors();
        setClass("progress");
        disableFormFields();

        clearStatusDivs();
        form.append(progress);
    };

    form.submit(function (e) {
	    e.preventDefault();
	    formSubmitter();
    });
}

// TOGGLE TABBED CONTENT (and load via Ajax if it doesn't already exist)
// call this function on DOM ready (i.e. when page loads) and on clicking certain nav links
// within the specified parent node (param = container) (and targeting child nodes only),
// remove class 'active' from current nav and tab (which will hide the tab)
// attach class 'active' to the specified nav and tab (.nav a.activeName and .tab div#activeName)
// note - the tab is the parent node of the div.section which has id #activeName
// note - adding .active to a tab displays the child div.section, while removing it hides the child
// if the specified tab doesn't yet exist, load the content via Ajax into a new div.tab
function toggleTab(container,activeName,self,callback) {
	try{
	// remove .active class (from currently active nav and tab)
	$(container + " > .nav .active").removeClass("active");
	$(container + " > .tab.active").removeClass("active");
	// find nav with class name = activeName and mark it as 'active'
	var activeNav = $(container + " > .nav ." + activeName);
	activeNav.addClass('active');
	// if section with id = activeName exists, mark its parent div.tab as 'active'
	if ($("#" + activeName).length!=0) {
		if (!self)
			$("#" + activeName).parent().addClass('active');
		else
			$("#" + activeName).addClass('active');
	}
	// otherwise load the content from the appropriate page into a new div.tab
	// NB - an element with id = activeName is assumed in page being loaded
	else if (activeNav.is("a")) {
		var tabDiv = $("<div class=\"js tab active\">Loading...</div>");
		$(container).append(tabDiv);
		tabDiv.load(activeNav.attr("href") + " .content #" + activeName,null,function (textResponse,textStatus) {
				// if there's an error or timeout, send user to the hyperlinked page
				if ($.inArray(textStatus,["timeout","error","notmodified","parsererror"])!=-1) {
					// TODO - lightbox error message
					location.href = activeNav.attr("href");
				}
				var re = /<script type="text\/javascript" src="(\/js\/[^"]+.js)\?v=[^&"]*"><\/script>/gi;
				textResponse.replace(re, function(match, script) {
					if (script.search("_") != -1) getScript(script);
					return '';
				});
				if (callback) {
					callback(textStatus);
				}
		});
	}
	}catch(e){alert(e);}
}

/*// initial TOGGLE TAB function - now superseded by function above
// for users with js enabled, pull the content of the hyperlinked page into a new div.tabbed
// and make the approrpriate nav and div.tabbed active
function tabinate(userOptions) {
    var options = {
        element: $(document), // element below which to look for tabs
        success: function (tab, tabSelect) {} // called after each tab has been loaded, with the newly loaded tab as the parameter
    };
    var undefined;
    if (userOptions !== undefined)
        $.extend(options, userOptions); // overwrite defaults


    // set up each tab group
    $(".tabbed", options.element).each(function () {
        var tabbed = $(this);
        var selects = tabbed.find("> a.tab-select");
        var tabs = tabbed.find("> div.tab");

        // initialise the map of tab buttons to existing tabs
        var tabmap = {};
        var activeTab = tabs.eq(0);
        tabs.each(function (i) {
            var selectIndex = i;
            if ((/^tab\-[a-z]+\-[0-9]+$/).test($(this).attr("id"))) {
                selectIndex = parseInt($(this).attr("id").replace(/[^0-9]/g, '')) - 1;
            }
            var baseId = selects.eq(selectIndex).attr("href");
            tabmap["tab-" + baseId] = $(this);
            $(this).data("id", "tab-" + baseId);

            options.success($(this), selects.eq(selectIndex));
        });

        // function to switch the active tab
        var activate = function (select, tab) {
            var activeClass = "active-tab";
            selects.removeClass(activeClass);
            select.addClass(activeClass);
            tabs.removeClass(activeClass);
            tab.addClass(activeClass);
            activeTab = tab;
        };

        // add the tabbing event handler
        selects.click(function (e) {
            e.preventDefault(); // the tab select button is an <a> element
            var select = $(this);
            var baseId = select.attr("href");
            var desiredTabId = "tab-" + baseId;
            if (activeTab.data("id") != desiredTabId) {
                var tab = tabmap[desiredTabId];
                if (tab == null) {
                    // populate the tabmap with a fake loading tab to prevent double loading
                    tabmap[desiredTabId] = $('<div class="tab loading"/>');

                    // create the tab element to load the content into
                    tab = $('<div class="tab"/>').data("id", desiredTabId);
                    tabs = tabs.add(tab);
                    select.addClass("loading");

                    // get the tab content
                    tab.load(select.attr("href"), { fragment: true }, function (rt, textStatus) {
                        if (textStatus == "success") {
                            tabbed.append(tab);
                            tabmap[desiredTabId] = tab;

                            // make sure the loaded tab has any internal tabs tabinated
                            tabinate({ element: tab, success: options.success });
                            options.success(tab, select);

                            activate(select, tab);
                        }
                        else {
                            // ought to do something better than just log this, i guess
                            log("failure: " + textStatus);
                            tabmap[desiredTabId] = null;
                        }
                        select.removeClass("loading");
                    });
                }
                else {
                    // tab already exists in the page
                    activate(select, tab);
                }
            }
        });
    });
}*/

function toggleClass(element, classList, className, duration) {
    $.each(classList, function (i, v) {
        if (v != className && element.hasClass(v))
            element.removeClass(v, duration);
    });

    if (!element.hasClass(className))
        element.addClass(className, duration);
}

// ### ZEBRA STRIPING FOR DATA TABLES ###
function doZebraStripes() {
	$(".content table.data").each(function() {
		$(this).find("tr:even").addClass("x");
	});
}

function ajaxOmnitureTracking(siteTrackingParameters) {
	var s = s_gi(s_account);

	//set all tracking prams
	var n;
	for (n in siteTrackingParameters) {
		log(n + ";" + siteTrackingParameters[n]);
		s[n] = siteTrackingParameters[n];
	}

	s.t();
	//rsvp use s.tl(this, 'o', name);
}

function jsonCall(url, method, data, success, error) {
    // make a copy of the data map so we can add a value to it
    var copy;
    if (data == null || typeof data == "string") {
        copy = data == null ? "" : data;
        if (copy.length > 0)
            copy += "&";
        copy += "ajax=true";
    }
    else {
        copy = { ajax : true };
        $.each(data, function (n, v) { copy[n] = v; });
    }

    // provide a generic handler for a login resultCode
    var successWrapper = function (data, textStatus) {
        if (data.resultCode == "login") {
            // no valid session - popup dialog but don't redirect page
            alert(data.actionErrors);
        }
        else if (success)
            success(data, textStatus);
    };

    // check for JSON parsererrors, and provide a generic error handler for connection resets / etc. if
    // none has otherwise been defined.
    var errorWrapper = function (data, textStatus) {
        if (textStatus == "parsererror")
            alert('Failed to parse response');
        else if (error)
            error(data, textStatus);
        else {
            var message = data.hasActionErrors ? data.actionErrors.join("\n") : 'Unexpected error, please try again later.';
            alert(message);
        }
    };

    $.ajax({
        url: url,
        data: copy,
        success: successWrapper,
        error: errorWrapper,
        dataType: "json",
        type: method
    });
}

function getScript(script, success) {
    if (typeof script != "string" && script.charAt(0) != "/") {
        throw "script must start with '/'";
    }
	// tabs allow us to accidentally load the same script twice.
	if (getScript.loaded == undefined)
		getScript.loaded = [];
	// don't load the same script twice
	if ($.inArray(script, getScript.loaded) != -1)
		return;

	getScript.loaded.push(script);

	if (!$.browser.msie) {
		// $.getScript always bypasses the cache, it adds a parameter _=time-in-millis
		// so we do it manually, with ajax dataType = "text"
		var callback = function (text, status) {
			if (status == "success") {
				eval(text);
				if (success)
					success();
			}
		};
		// scriptSuffix is declared inline in doc-head.jspf
		$.ajax({
			url: script + scriptSuffix,
			success: callback,
			type: "get",
			dataType: "text"
		});
	}
	else {
		$(document).append('<scr' + 'ipt type="text/javascript" src="' + script + scriptSuffix + '"></scr' + 'ipt>');
		if (success) {
			$(document).append('<scr' + 'ipt type="text/javascript">(' + success + ')();</scr' + 'ipt>');
		}
	}
}

// debug methods
function log(s) {
	try {
    // log to firebug or ignore
    if ((typeof console) != 'undefined' && (typeof console.log) != 'undefined') {
        console.log(s);
		}
	}
	catch(e) {}
}

function lightInspect(object) {
	var s = [];
	for (var n in object) {
		var v = object[n];
		if (v === undefined)
			s.push(n + ": " + undefined);
		else if (v.size) {
			for (var i = 0; i < v.size(); i++) {
				var vi = v.eq(i);
				s.push(n + "[" + i + "]: " + vi.attr("nodeName") + "#" + vi.attr("id") + "." + vi.attr('class') + "\t\t" + vi.text());
			}
		}
		else
			s.push(n + ": " + v);
	}
	console.log(s.join("\n"));
}

function inspect(object, msg, silent, stack) {
    if (!stack) stack = [object];
    else {
        stack = $.map(stack, function (v) { return v; });
        stack.push(object);
    }

    var s = [];
    if (typeof object == "string") {
        s.push("'" + object + "'");
    }
    else if ($.inArray(typeof object, ['number', 'boolean']) >= 0) {
        s.push(object);
    }
    else if (typeof object == 'function') {
        s.push(object);
    }
    else if (object !== null && typeof object != 'undefined') {
        for (n in object) {
            var v = 'undefined';
            try {
                v = object[n];
            }
            catch (e) {
            }

            if (v === null || v === undefined)
                s.push("'" + n + "': " + v);
            else if (v.$)
                s.push("'" + n + "': $(" + v.attr("nodeName") + ")");
            else if (v.nodeName)
                s.push("'" + n + "': `" + v.nodeName);
            else if ($.inArray(v, stack) == -1)
                s.push("'" + n + "': (" + inspect(v, null, true, stack) + ")");
            else
                s.push("'" + n + "': [[looping object: " + v + "; position=" + stack.indexOf(v) + "]]");
        }
    }

    if (!silent) {
        log((msg ? msg + ": " : "") + s.join("\n"));
    }
    return s.join("; ");
}

function parseLink(link, paramsOfInterest) {
	// returns the uri part of the link, the query string, and any params of interest
	var href = link.attr("href");
	var queryPos = href.indexOf("?");
	var uri = href.substring(0, queryPos);
	var query = href.substring(queryPos + 1);
	var rval = {"uri": uri, "query": query};
	for (var i = 0; i < paramsOfInterest.length; ++i) {
		var param = paramsOfInterest[i];
		var paramRe = new RegExp(param + "=([^&]*)");
		var matches = paramRe.exec(href);
		if (matches != null && matches.length == 2) {
			var paramVal = matches[1];
			rval[param] = paramVal;
		}
	}
  return rval;
}

// the text field that we're doing the length check on -- needs to be global because there doesn't seem to be a way to
// determine it in checkTextInputLength() or have it passed in
var lengthCheckTextField = null;

// making these regular expressions global so that they don't need to be compiled each time they're used
var maxOnlyRe = /length\-check\-(\d+)/;
var maxAndMinRe = /length\-check\-(\d+)\-(\d+)/;
var lengthCheckRe = /length\-check\-\d+(?:\-\d+)?/;

function getLengthCheck(s) {
	var reMatches = lengthCheckRe.exec(s);
	if (reMatches != null) {
		return reMatches[0];
	}
	else {
		return null;
	}
}

// string input should be length-check-MIN-MAX or length-check-MAX, where MIN and MAX are the limits for the text input
// field or textarea. Returns {max: MAX, min: MIN}
function parseLengthCheckLimits(s) {
	var rval = {};
	var reMatches = maxAndMinRe.exec(s);
	if (reMatches != null && reMatches.length == 3) {
		rval["minLength"] = reMatches[1];
		rval["maxLength"] = reMatches[2];
	}
	else {
		reMatches = maxOnlyRe.exec(s);
		if (reMatches != null && reMatches.length == 2) {
			rval["minLength"] = 0;
			rval["maxLength"] = reMatches[1];
		}
	}
	return rval;
}

// puts a warning in the div immediately after the text input field if the text input is not according to the limits
// specified in the "length-check..." class
function checkTextInputLength() {
	if (lengthCheckTextField != null) {
		var fieldClass = $(lengthCheckTextField).attr("class");
		var textLength = lengthCheckTextField.value.length;
		var limits = parseLengthCheckLimits(fieldClass);
		if (limits.minLength != null && textLength < limits.minLength) {
			$(lengthCheckTextField).next().replaceWith("<div class='js length-status'>" + (limits.minLength - textLength) + " chars left to satisfy minimum length</div>");
		}
		else if (limits.maxLength != null && textLength > limits.maxLength) {
			lengthCheckTextField.value = lengthCheckTextField.value.substr(0, limits.maxLength);
			$(lengthCheckTextField).next().replaceWith("<div class='js length-status critical st rc'>0 characters remaining</div>")
		}
		else if (limits.maxLength != null && textLength > limits.maxLength * 0.9) {
			$(lengthCheckTextField).next().replaceWith("<div class='js length-status critical st rc'>" + (limits.maxLength - textLength) + " characters remaining</div>")
		}
		else if (limits.maxLength != null && textLength > limits.maxLength * 0.8) {
			$(lengthCheckTextField).next().replaceWith("<div class='js length-status wary oc'>" + (limits.maxLength - textLength) + " characters remaining</div>")
		}
		else if (limits.maxLength != null && textLength > limits.maxLength * 0.5) {
			$(lengthCheckTextField).next().replaceWith("<div class='js length-status'>" + (limits.maxLength - textLength) + " chars remaining</div>")
		}
		else {
			$(lengthCheckTextField).next().replaceWith("<span class='js h length-status'>text is an approved length</span>");
		}
	}
}

var textEntryTimeout = null;
function initTextEntryTimeout(event) {
	if (textEntryTimeout != null) {
		window.clearTimeout(textEntryTimeout);
		textEntryTimeout = null;
	}
	lengthCheckTextField = event.target;
	textEntryTimeout = window.setTimeout(checkTextInputLength, 100);
};

function initLengthCheck() {
	// find all divs that have a "length-check..." class and add the class to the enclosed
	// input or textarea, then add a span which is hidden but will fire the length check on keyup and will be replaced
	// initialise the timeout that checks the text input length
	$("div[class*='length-check']").each(function(i) {
		var theDiv = $(this);
		var lengthCheck = getLengthCheck(theDiv.attr("class"));
		theDiv.removeClass(lengthCheck);
		theDiv.children("textarea, :text").addClass(lengthCheck);
	});
	$("*[class*='length-check']").each(function() {
		if (!$(this).next().is(".length-status")) {
			$(this).after("<span class='js h length-status'>text needs to meet minimum and/or maximum length requirement</span>").bind('keyup', initTextEntryTimeout);
		}
	});
}
