// Show or hide an element by removing or adding the 'hidden' class
function showit(elemOrId) {return YAHOO.util.Dom.removeClass(elemOrId,'hidden');}
function hideit(elemOrId) {return YAHOO.util.Dom.addClass(elemOrId,'hidden');}
function isVisible(elemOrId) {return !YAHOO.util.Dom.hasClass(elemOrId,'hidden');} // Check if it has the noscript class, return the opposite result...
function switchVisibility(theObject) { // hide if shown, show if hidden
    if(isVisible(theObject)) hideit(theObject);
    else showit(theObject);
}
function showHide(elemOrId,showBoolean) { // Show depending on a boolean value
    if(showBoolean) return showit(elemOrId);
    else return hideit(elemOrId);
}

// Checks if object is an element
function isElemOrId(elemOrId) {
    // Make sure it's an element
    var elem;
    if(typeof(elemOrId) == 'string') elem = document.getElementById(elemOrId);
    else elem = elemOrId;
    // If it's an element
    if(elem && typeof(elem.nodeType) == 'number' && elem.nodeType == 1) return true;
    else return false;
}

/* Yahoo Tooltips - help pop-up */
// Pops up a tooltip from the yahoo library
var endsleighPopups = new Object(); // global object/associative array to hold all tooltip objects

function endsleighPopup(anchorElemId,htmlText,showAction,hideAction,positioning,openTimeout,closeTimeout,conditionFunction) {
    window.addListener(
        'load',
        function() {
            if(!yui.get(anchorElemId)) {return false;}
            
            // Store variables
            var custom = {
                anchorElemId: anchorElemId,
                htmlText: htmlText,
                showAction: showAction,
                hideAction: hideAction,
                positioning: positioning,
                openTimeout: openTimeout,
                closeTimeout: closeTimeout,
                conditionFunction: conditionFunction,
                showPopup: function(popupObj) {
                    if(!yui.isObject(popupObj)) {popupObj = endsleighPopups[this.anchorElemId];}
                    if(yui.isNumber(popupObj.custom.positioning[0]) && yui.isNumber(popupObj.custom.positioning[1])) {
                        popupObj.cfg.setProperty('xy',popupCoords(popupObj,popupObj.custom.anchorElemId,popupObj.custom.positioning));
                    }
                    window.setTimeout(function() {popupObj.show()},popupObj.custom.openTimeout);
                },
                hidePopup: function(popupObj) {
                    if(!yui.isObject(popupObj)) {popupObj = endsleighPopups[this.anchorElemId];}
                    window.setTimeout(function() {popupObj.hide()},popupObj.custom.closeTimeout);
                }
            };
            
            // Set default values
            if(!yui.isString(custom.showAction)) {custom.showAction = 'mouseover'}
            if(!yui.isString(custom.hideAction)) {custom.hideAction = 'mouseout'}
            if(!yui.isArray(custom.positioning)) {custom.positioning = [0,0]}
            if(!yui.isNumber(custom.openTimeout)) {custom.openTimeout = 200}
            if(!yui.isNumber(custom.closeTimeout)) {custom.closeTimeout = 250}
            
            // Create the new overlay
            endsleighPopups[custom.anchorElemId] = new YAHOO.widget.Overlay(
                anchorElemId + "_popup", // Set it's ID
                {
                    context: [custom.anchorElemId,'tl','br'], // Make it display to the bottom right of the element
                    visible: false, // Invisible to start
                    constraintoviewport: true
                }
            );
            
            // Add HTML text
            endsleighPopups[custom.anchorElemId].setBody(custom.htmlText);
            endsleighPopups[custom.anchorElemId].render(document.body);
            YAHOO.util.Dom.addClass(endsleighPopups[custom.anchorElemId].element,"helpPopup");
            
            // Setup event listeners
            endsleighPopups[custom.anchorElemId].custom = custom; // Add custom to object for use within event listeners
            var anObj = endsleighPopups[custom.anchorElemId];
            
            // If they're the same, we only need one listener to switch
            if(custom.showAction == custom.hideAction) {
                // show hide based on visibility or condition function
                YAHOO.util.Event.addListener(
                    custom.anchorElemId,
                    custom.showAction,
                    function(evt) {
                        if(!evt) {evt = window.event}
                        
                        // If no conditionFunction or it returns true, show (otherwise, do nothing)
                        if(yui.isFunction(this.custom.conditionFunction)) {
                            if(this.custom.conditionFunction(evt)) {this.custom.showPopup(this)} // Show if condition function says yes
                            else {this.custom.hidePopup(this)} // Hide otherwise
                        } else {
                            if(!this.cfg.getProperty('visible')) {this.custom.showPopup(this)} // Show if not visible
                            else {this.custom.hidePopup(this)} // Hide otherwise
                        }
                    },
                    endsleighPopups[custom.anchorElemId],
                    true
                );
            } else {
                // Show event
                YAHOO.util.Event.addListener(
                    custom.anchorElemId,
                    custom.showAction,
                    function(evt) {
                        if(!evt) {evt = window.event}
                        // If no conditionFunction or it returns false, show (otherwise, do nothing)
                        if(!yui.isFunction(this.custom.conditionFunction) || !this.custom.conditionFunction(evt)) {this.custom.showPopup(this)}
                    },
                    endsleighPopups[custom.anchorElemId],
                    true
                );
                // Hide event
                YAHOO.util.Event.addListener(
                    custom.anchorElemId,
                    custom.hideAction,
                    function(evt) {
                        if(!evt) {evt = window.event}
                        // If no conditionFunction or it returns false, hide (otherwise, do nothing)
                        if(!yui.isFunction(this.custom.conditionFunction) || !this.custom.conditionFunction(evt)) {this.custom.hidePopup(this)}
                    },
                    endsleighPopups[custom.anchorElemId],
                    true
                );
            }
            
            return true;
        }
    );
    
    return true;
}

// Calculate it's position
function popupCoords(theTT,anchorEl,nudge) {
    anchorEl = yui.get(anchorEl);
    if(!yui.isArray(nudge)) {nudge = [0,0];}
    // Decide where x and y are going to be
    var coordsX = YAHOO.util.Dom.getX(anchorEl) + anchorEl.offsetWidth + nudge[0];
    var coordsY = YAHOO.util.Dom.getY(anchorEl) + anchorEl.offsetHeight + nudge[1];
    return [coordsX, coordsY];
}

// Show the noscript error message box
function showErrorBox(theError) {
    YAHOO.util.Dom.removeClass('noscript_message','noscript');
}

/**
 * Submits form data as GET data via Ajax
 * @param {Object} theForm The form elements to submit the data of
 * @param {String} url [optional] The url to submit the form data to - if missing, will use the form's "action" by default
 * @param {Object} extraArgs [optional] Extra CGI arguments to add to the url (e.g: {action: 'ajax_submit'} // will add 'action=ajax_submit;' to url)
 * @param {Object} callBackObj [optional] The object to handle Ajax response - should have 'success' function, and preferably 'failure' as well
 * @return the request transaction object, or false
 * @type {Object}
 */
function ajaxSubmitForm(theForm,url,extraArgs,callBackObj) {
    var evt = window.event;
    
    var parameters = "";
    
    // Get the form
    var theForm = yui.get(theForm);
    if(!lib.isElement(theForm,'form')) {return false;}
    
    // If we haven't been given a URL, use the form action
    if(!yui.isString(url)) {
        if(yui.isString(theForm.action) && theForm.action.length > 0) {url = theForm.action;}
        else {url = window.location.pathname} // No form action? Fail - we have nowhere to submit to
    }
    
    // If we have extra arguments add them to the array
    if(yui.isObject(extraArgs)) {parameters += objectToCGIParams(extraArgs)}
    
    // Add the form data to the url
    var formData = lib.getFormData(theForm);
    if(yui.isObject(formData)) {parameters += objectToCGIParams(formData);}
    else {
        // No form data - report it if we can
        if(yui.isObject(console) && yui.isFunction(console.log)) {console.log('ajaxSubmitForm: Warning! No form data found')}
    }
    
    // Make sure the callBackObj is present and correct
    if(!yui.isObject(callBackObj)) {callBackObj = new Object()}
    if(!yui.isFunction(callBackObj.success)) {
        callBackObj.success = function() {
            if(yui.isObject(console) && yui.isFunction(console.log)) {
                console.log('ajaxSubmitForm: XMLHttpRequest to '+url+' completed successfully.');
            }
        }
    }
    if(!yui.isFunction(callBackObj.failure)) {
        callBackObj.failure = function() {
            if(yui.isObject(console) && yui.isFunction(console.log)) {
                console.log('ajaxSubmitForm: XMLHttpRequest to '+url+' failed.');
            }
        }
    }
    
    // Send request to server
    // Post by default
    var method = 'POST';
    // If method is "get" then get
    if(lib.exists(theForm.method) && yui.isString(theForm.method) && theForm.method.match(/^get$/i)) {
        method = 'GET'; // Set the method
        url = addCGIArgs(url,parameters); // Add the parameters to the url string
        parameters = null; // Set parameters to null
    }
    
    if(typeof(console) == 'object' && yui.isObject(console) && yui.isFunction(console.log)) {console.log("Arguments to asyncRequest:",method,url,callBackObj,parameters);}
    
    var transaction = YAHOO.util.Connect.asyncRequest(method, url, callBackObj, parameters);
    
    return transaction;
}
/**
 * An email address format checker based on a Regular Expressions sent through by Daai
 * @param {String} emailString The email string to check
 * @returns True if valid, false otherwise
 * @type {Boolean}
 */
function endsleighCheckEmail(emailString) {
    var emailRegExp = /^([\w])([\w!#.%'\*\+-\/=\?\^{}\|~])*@([\w][-\w]*[\w]\.)+([a-zA-Z]{2,9})$/;
    
    if(yui.isString(emailString)) { // If it's a string
        if(emailRegExp.test(lib.trim(emailString))) { // And it matches
            return true;
        }
    }
    return false; // We've got this far so it can't be valid
}
/**
 * A postcode format checker based on a Regular Expressions sent through by Daai
 * @param {String} postcodeString The postcode string to check
 * @returns True if valid, false otherwise
 * @type {Boolean}
 */
function endsleighCheckPostcode(postcodeString) {
    var postcodeRegExp = /\b[a-zA-Z]([a-zA-Z]|[0-9])([a-zA-Z]|[0-9])?([a-zA-Z]|[0-9])?[ ]*[0-9][a-zA-Z][a-zA-Z]|[a-zA-Z][0-9]\b/;
    
    if(yui.isString(postcodeString)) { // If it's a string
        if(postcodeRegExp.test(lib.trim(postcodeString))) { // And it matches
            return true;
        }
    }
    return false; // We've got this far so it can't be valid
}
/**
 * A postcode format checker based on a Regular Expressions sent through by Daai
 * @param {String} postcodeString The postcode string to check
 * @returns True if valid, false otherwise
 * @type {Boolean}
 */
function endsleighCheckPhoneNumber(phoneNumberString) {
    var phoneNumberRegExp = /^(\(?[0-9\s\)]{9,16})$|[Nn][Oo][Nn][Ee]\s[Aa][Vv][Aa][Ii][Ll][Aa][Bb][Ll][Ee]|[Ee][Xx]\s[Dd][Ii][Rr][Ee][Cc][Tt][Oo][Rr][Yy]/;
    
    if(yui.isString(phoneNumberString)) { // If it's a string
        if(phoneNumberRegExp.test(lib.trim(phoneNumberString))) { // And it matches
            return true;
        }
    }
    return false; // We've got this far so it can't be valid
}
/**
 * Check card number format against the LUHN algorithm - all credit card numbers use this format, to allow for simple error checking
 * @param {Number|String} numberString The number to check against
 * @returns true if valid, false otherwise
 * @type {Boolean}
 */
function LUHNCardCheck(numberString) {
    numberString = numberString.replace(/\s/g,''); // remove spaces
    var numberArray = numberString.split(''); // turn into array

    var sum = 0;
    var alt = false;
    
    for(var i = numberArray.length - 2; i >= 0; i -= 2) { // for every other digit starting from the right
        numberArray[i] *= 2;
        if(numberArray[i] > 9) numberArray[i] -= 9; // equivalent to adding the value of digits
    }
    
    var sum;
    for(var i=0; i<numberArray.length; i++) sum += Number(numberArray[i]);
    
    return sum % 10 == 0;
}
/**
 * <p>Checks the contents of a form element against it's "submitTests" variable to decide whether to submit the form</p>
 * <p>Must be called as a "submit" event on said form</p>
 * <p>Form.submitTests must have the format:</p>
 * <p><code>
 * [{test: <Function returns Boolean>, showFields: [<1stIDtoshow>,<2ndIDtoshow>...]},...]
 * </code></p>
 * @param {Event} evt The submit event on the form.
 * @returns True of the event was allowed, false otherwise
 * @type {boolean}
 */
function checkSubmitTests(evt) {
    if(!lib.exists(evt,'object')) evt = window.event; // if no evt object, it's probably ie's silly JS model, so retrieve the IE event object
    if(!lib.exists(evt,'object')) return false; // If still no evt object, fail.
    if(!lib.exists(this.submitTests,'object') || !lib.exists(this.submitTests.length)) return true; // If no mandatory fields, submit form like normal
    
    var errors = false; // If errors, this gets set to true
    
    // Iterate through "this.submitTests" - the array of form error checks
    for(var i=0; i<this.submitTests.length; i++) {
        var thisTest = this.submitTests[i];
        
        // Now check the test...
        if(yui.isFunction(thisTest.test)) {
            // If we just have one string of error field, turn it into an array
            if(yui.isString(thisTest.showFields)) {thisTest.showFields = new Array(thisTest.showFields)}; // If string
            
            if(thisTest.test() == false) {
                // If it's false 
                // show fields 
                if(lib.exists(thisTest.showFields,'object') && lib.exists(thisTest.showFields[0],'string')) { // if it's an array an array
                    for(var j=0; j<thisTest.showFields.length; j++) {showit(thisTest.showFields[j])}; // Show each field in turn
                }
                // set errors to true
                errors = true;
            } else {
                // Otherwise, if it's true, hide all the fields
                if(lib.exists(thisTest.showFields,'object') && lib.exists(thisTest.showFields[0],'string')) { // Or an array
                    for(var j=0; j<thisTest.showFields.length; j++) {hideit(thisTest.showFields[j])}; // Show each field in turn
                }
            }
        }
    }
    
    if(errors) {
        // If we have errors we need to do stuff
        // If we have an error box, show it
        if(lib.exists(this.errorBoxId,'string')) {showit(this.errorBoxId)};
        if(yui.isFunction(this.submitTestsErrorFunction)) {this.submitTestsErrorFunction()};
        // Now stop the form submitting
        return lib.cancelEvent(evt);
    } else {
        // If we don't have errors, clear up
        // If we have an error box, hide it
        if(lib.exists(this.errorBoxId,'string')) {hideit(this.errorBoxId)};
        return true; // Allow the form to submit
    }
}

/**
 * Add CGI arguments to a URL string from an object
 * @param {String} url The url string to add the args to
 * @param {Object} argumentsObj The object containing the arguments
 * @returns The url with the arguments added
 * @type {String}
 */
function addCGIArgs(url,parameters) {
    // Check arguments
    if(!yui.isString(url) || !(yui.isObject(parameters) || yui.isString(parameters))) {return false;}
    // Test for form of query string
    var hasQueries = /^[^\?]+\?[^\?]*$/;
    var needsSemicolon = /^[^\?]+\?[^\?]*[^;&]$/;
    if(!hasQueries.test(url)) {url += '?'} // add a question mark if needed
    if(needsSemicolon.test(url)) {url += ';'} // add a semicolon to the end if needed
    
    // Now add the arguments
    if(yui.isObject(parameters)) {arguments = objectToCGIParams(parameters)} // If we have an object convert it to a string of arguments
    
    url += parameters; // Add the arguments to the URL
    
    return url;
}
/**
 * Converts an object to a list of CGI parameters
 * @param {Object} theObject The object containing the parameters to convert
 * @returns the string of cgi parameters
 * @type {String}
 */
function objectToCGIParams(theObject) {
    if(!yui.isObject(theObject)) {return false;}
    
    var paramsString = ''; // The resulting string
    // Iterate through arguments
    for(var argumentName in theObject) {
        if(yui.hasOwnProperty(theObject,argumentName)) {
            // Convert the value to string if possible
            var argVal = theObject[argumentName];
            if(lib.exists(argVal) && yui.isFunction(argVal.toString)) {argVal = argVal.toString()}
            
            // Encode the variable, and put it on the string
            paramsString += argumentName+'='+encodeURIComponent(argVal)+';';
        }
    }
    
    return paramsString;
}

/** 
 * Filters out all keypresses apart from digits and other unrelated keys
 * @param {Element} anElement The element on which to filter out key presses
 * @returns True
 * @type {Boolean}
 */
function numbersFilter(anElement) {

    function numberFilterEvent(evt) {
        // Get the windows event
        if(!evt) {evt = window.event;}
        
        if(evt.keyCode) {
            if(evt.altKey || evt.ctrlKey) {return true;} // Always allow alt and control key commands
            if(evt.keyCode.toString().match(/^(8|9|1[6-8]|3[7-9]|40|46)$/)) {return true;} // Always allow backspace (8), tab (9), shift,alt,ctrl (16=18), delete (46) and arrow keys (37-40)
            if(evt.keyCode.toString().match(/^(4[89]|5[0-7]|9[6-9]|10[0-5])$/) && !evt.shiftKey) {return true;} // Allow numbers (48-57) only if less than 4 are already entered
        }
        // In all other cases, disallow
        if(evt.preventDefault) {evt.preventDefault();}
        else {evt.returnValue = false;}
        return false;
    }
        
    anElement.onkeydown = numberFilterEvent;

    return true;
}

/**
 * Retrieved the text content of an element the firefox and ie way
 * @param {Element} theElement the element to get content of
 * @returns the text content if success, false otherwise
 * @type {String}
 */
function getTextContent(theElement) {
    theElement = yui.get(theElement);
    if(!(lib.exists(theElement) && yui.isObject(theElement))) {
        if(lib.exists(console) && yui.isObject(console) && yui.isFunction(console.log)) {console.log("getTextContent - No element passed: "+theElement)}
        return false;
    }
    if(yui.isString(theElement.textContent)) {
        // The gecko way
        return theElement.textContent;
    } else if(yui.isString(theElement.innerHTML)) {
        // The internet explorer way
        return theElement.innerHTML;
    } else {
        // Can't find either method, fail
        return false;
    }
}

/*
Genuine Objects for forms:
*/

/**
 * @class
 * @constructor
 * <p>Create searchable box from a text input field and two select boxes</p>
 * <p>The effect is that when you type in the text field the contents of the top select box will be filtered accordingly</p>
 * <h3>Essential HTMl format:</h3>
 * <code>
 * <fieldset id="<propertiesListId>">
 *   <input class="searchInput" type="text" />
 *   <select class="visibleSelect"></select><!-- this should be visible, it's where the filtered options are -->
 *   <select class="selectAll hidden"></select><!-- this should be hidden - it just stores all the options -->
 * </fieldset>
 * </code>
 * <h3>Notes</h3>
 * <ul>
 *   <li>You can optionally populate the select boxes via Ajax. The response must be in the following format: [{<valueVar>:<value>,<contentVar>:<content>},...]</li>
 * </ul>
 * 
 * @param {Element} propertiesList The fieldset element containing the fields for the searchableBox. Can be element or id.
 * @param {String} url [optional] The url to retrieve contents from via ajax
 * @param {String} valueVar [optional] The name of the variable holding the value of each option to be added via Ajax - in the ajax response (see format above).
 * @param {String} contentVar [optional] The name of the variable holding the content of each option to be added via Ajax - in the ajax response (see format above).
 * @param {Object} callBackObject [optional] Extra functions to be run on Ajax success or failure. Format: {success: function() {}, failure: function() {}}
 * @returns this
 * @type {Object}
 */
function searchableBox(boxContainer,url,valueVar,contentVar,callBackObject) {
    // Get initial objects from ids
    this.boxContainer = yui.get(boxContainer);
    this.url = url;
    this.valueVar = valueVar || 'value';
    this.contentVar = contentVar || 'content';
    this.doAjax = false;
    
    // check parameters
    if(!lib.isElement(this.boxContainer,'fieldset')) {return false;}
    if(lib.exists(this.url) && lib.exists(this.valueVar) && lib.exists(this.contentVar)) {
        // Do checks
        if(!yui.isString(this.url)) {return false;}
        else if(!yui.isString(this.valueVar)) {return false;}
        else if(!yui.isString(this.contentVar)) {return false;}
        else {this.doAjax = true} // All checks done, now set 
    }
    
    // Get other important objects
    this.searchInput = yui.getElementsByClassName(this.boxContainer,'searchInput','input')[0];
    this.visibleSelect = yui.getElementsByClassName(this.boxContainer,'visibleSelect','select')[0];
    this.selectAll = yui.getElementsByClassName(this.boxContainer,'selectAll','select')[0];
    this.notListed = yui.getElementsByClassName(this.boxContainer,'notListed','input')[0];
    
    // Check we managed to get them properly
    if(!lib.isElement(this.searchInput,'input') && this.searchInput.type == 'text') {return false;}
    if(!lib.isElement(this.visibleSelect,'select')) {return false;}
    if(!lib.isElement(this.selectAll,'select')) {return false;}
    
    /* Start doing stuff */
        
    // If doAjax is true, then we can fetch stuff from ajax
    if(this.doAjax == true) {
    
        // Make sure the fields are empty to start with
        this.searchInput.value = '';
        if(lib.isElement(this.notListed)) {this.notListed.checked = false};
        lib.empty(this.visibleSelect);
        lib.empty(this.selectAll);
        
        // Add "loading list" to the select box
        var firstOption = document.createElement('option');
        firstOption.appendChild(document.createTextNode("loading list..."));
        this.visibleSelect.appendChild(firstOption);
                
        // Create callback function to populate the visibleSelect from the populated selectAll
        var resetSelectsCallBack = {
            success: function() {
                this.searchableBox.reset();
                //args['searchableBox'].ajaxLoaded = true;
                // now run the real callback function
                if(yui.isObject(this.oldCallBack) && yui.isFunction(this.oldCallBack.success)) {this.oldCallBack.success()};
            },
            failure: function() {
                if(yui.isObject(this.oldCallBack) && yui.isFunction(this.oldCallBack.failure)) {this.oldCallBack.failure()};
            },
            searchableBox: this,
            oldCallBack: callBackObject
        }
        // Now populate selectAll
        this.populateSelectListFromUrl(this.selectAll,this.url,this.valueVar,this.contentVar,resetSelectsCallBack);
    }
    
    // Link to this object from within the elements
    this.searchInput.searchableBoxObj = this;
    this.visibleSelect.searchableBoxObj = this;
    
    // Add event listeners
    // When key is lifted on input box, filter the select box contents
    yui.addListener(
        this.searchInput,
        'keyup',
        function() {
            if(this.searchableBoxObj.selectAll.options.length > 0) {
                this.searchableBoxObj.filterList(this,this.searchableBoxObj.selectAll,this.searchableBoxObj.visibleSelect);
            }
        }
    );
    
    // When you leave the input box, see if we can guess which university you mean
    yui.addListener(
        this.searchInput,
        'blur',
        function(evt) {
            if(this.searchableBoxObj.selectAll.options.length > 0) {
                var matches = [];
                var valueMatch = new RegExp('^'+this.searchableBoxObj.searchInput.value+'$','i');
                for(var i=0; i<this.searchableBoxObj.visibleSelect.options.length; i++) {
                    if(valueMatch.test(getTextContent(this.searchableBoxObj.visibleSelect.options[i]))) {
                        matches.push(this.searchableBoxObj.visibleSelect.options[i]);
                    }
                }
                if(matches.length == 1) {this.searchableBoxObj.filterList(this,this.searchableBoxObj.selectAll,this.searchableBoxObj.visibleSelect);}
                this.searchableBoxObj.selectFromList(this,this.searchableBoxObj.visibleSelect);
                this.searchableBoxObj.updateSearchInput(this.searchableBoxObj.visibleSelect, this);
            }
            return true;
        }
    );
    // When the value in the select box change, update the input field
    yui.addListener(
        this.visibleSelect,
        'change',
        function() {
            this.searchableBoxObj.updateSearchInput(this, this.searchableBoxObj.searchInput);
        }
    );
    
    // Not listed check box
    if(lib.isElement(this.notListed)) {
        this.notListed.searchableBoxObj = this;
        function notListedDisable() {
            if(this.checked) {
                // disable both boxes & remove values
                this.searchableBoxObj.searchInput.disabled = true;
                this.searchableBoxObj.searchInput.value = "not listed";
                this.searchableBoxObj.visibleSelect.disabled = true;
                this.searchableBoxObj.visibleSelect.selectedIndex = -1;
            } else {
                // enable both boxes & remove values
                this.searchableBoxObj.searchInput.disabled = false;
                this.searchableBoxObj.searchInput.value = "";
                this.searchableBoxObj.visibleSelect.disabled = false;
                this.searchableBoxObj.visibleSelect.selectedIndex = -1;
            }
        }
        this.notListed.addListener('click',notListedDisable);
        this.notListed.addListener('change',notListedDisable);
    }
    
    return this;
}
/**
 * Populate a select box via Ajax (server-request) from a remote location
 * @param {String} selectBoxId The ID of the select box element to fill
 * @param {String} url The URL to get the data from, via Ajax - the response of this URL needs to be in the following JSON format: [{<valueVar>:<value>,<contentVar>:<content>},...]
 * @param {String} valueVar [optional] The name of the variable in each object containing the value for each 'option' element. Defaults to 'value'.
 * @param {String} contentVar [optional] The name of the variable in each object containing the content for each 'option' element. Defaults to 'content'.
 * @returns True
 * @type {Boolean}
 */
searchableBox.prototype.populateSelectListFromUrl = function(selectBoxOrID,url,valueVar,contentVar,callBackObject) {
    var selectBox = yui.get(selectBoxOrID); // The multiple select box holding the university names
    if(!lib.isElement(selectBox,'select')) {selectBox = this.selectBox}
    if(!lib.exists(url,'string')) {url = this.url || 'value'}
    if(!lib.exists(valueVar,'string')) {valueVar = this.valueVar || 'value'}
    if(!lib.exists(contentVar,'string')) {contentVar = this.contentVar || 'content'}
    
    // Empty select box
    lib.empty(selectBox);
    
    var populateSelect = {
        // If we have success
        success:function(results) {
            // Check we have response text
            if(typeof(results.responseText) == 'undefined') {return this.failure(results)};
            // Parse the response text
            var valuesArray;
            if(lib.exists(results.responseText.parseJSON,'function')) {valuesArray = results.responseText.parseJSON()} // Convert results string back into variable
            else {valuesArray = eval(results.responseText)};
            
            // Now do the stuff
            if(!(typeof(valuesArray) == 'object' && typeof(valuesArray.length) != 'undefined')) {return false}; //  Check it's an array
            if(valuesArray.length > 0) {
                // empty the select box
                lib.empty(selectBox);
                
                // Create and add each of the unis
                for(var i=0; i<valuesArray.length; i++) {
                    if(!(valuesArray[i][valueVar] && valuesArray[i][contentVar])) {return false};
                    // Set up the new option
                    var thisOption = document.createElement('option');
                    thisOption.setAttribute('value',valuesArray[i][valueVar]);
                    thisOption.appendChild(document.createTextNode(valuesArray[i][contentVar]));
                    // Add it to the list
                    selectBox.appendChild(thisOption);
                }
            }
            
            // Run the callback function if we have one
            if(yui.isObject(this.callBack) && yui.isFunction(this.callBack.success)) {this.callBack.success(this);}
        }, 
        // If we have failure, throw an error
        failure:function(results) {
            // Run the failure function, if we were given one
            if(yui.isObject(this.callBack) && yui.isFunction(this.callBack.failure)) {this.callBack.failure(this);}
            return false;
        },
        callBack: callBackObject
    }
    
    // Send request to server
    YAHOO.util.Connect.initHeader('Content-type', 'text/plain'); // We are expecting a plaintext response
    var transaction = YAHOO.util.Connect.asyncRequest('GET',url,populateSelect, null);
    
    return true;
}
/**
 * Filters a select box based on the contents of another select box and a search string
 * To be called within an event listener
 * @param {String} textEntered The string to filter contents by
 * @param {Element} originalList The select box holding all possible options - this is usually hidden
 * @param {Element} filteredList The visible select box to add filtered contents to
 * @returns true
 * @type {Boolean}
 */
searchableBox.prototype.filterList = function(textEntered, originalList, filteredList) {
    var originalSelect = yui.get(originalList);
    var filteredSelect = yui.get(filteredList);
    
    if (filteredSelect.options.length != originalSelect.options.length || textEntered.value.length > 2) {
        for ( var i = (filteredSelect.options.length - 1); i > -1; i--) {
            filteredSelect.options[i] = null;
        }
          
        if (textEntered.value.length > 2) {
            for ( var i=0; i < originalSelect.options.length; i++ ) {
                if (originalSelect.options[i].text.toUpperCase().indexOf(textEntered.value.toUpperCase()) != -1) {       
                    var y=document.createElement('option');
                    y.value = originalSelect.options[i].value;
                    y.text = originalSelect.options[i].text;
                    try {
                        filteredSelect.add(y, null);
                    } catch(ex) {
                        filteredSelect.add(y);
                    }
                }
            }
        } else {
            for ( var i=0; i < originalSelect.options.length; i++ ) {
                var y=document.createElement('option');
                y.value = originalSelect.options[i].value;
                y.text = originalSelect.options[i].text;
                try {
                    filteredSelect.add(y, null);
                }
                catch(ex) {
                    filteredSelect.add(y);
                }
            }
        }
    }
    
    return true;
}
/**
 * Selects the appropriate value from a select box based on the contents of a text input field
 * If there is just one value in selectBox then that is selected.
 * Otherwise, all options in selectBox are searched to see if one matches the contents of the inputBox and then that value is selected
 * @param {Element} inputBox The input field that determines what is selected
 * @param {Element} selectBox The select element that holds the options to select one of
 * @returns true if we managed to decide which option to select, false otherwise
 * @type {Boolean}
 */
searchableBox.prototype.selectFromList = function(inputBox,selectBox) {
    if(lib.exists(selectBox,'string')) {selectBox = document.getElementById(selectBox)} // If select is a string assume it's an id
    if(lib.exists(inputBox,'string')) {inputBox = document.getElementById(inputBox)} // If input is a string assume it's an id
    if(!(lib.isElement(selectBox,'select') && lib.isElement(inputBox,'input') && inputBox.type == 'text')) {return false} // Check we have the elements
    
    
    
    // If we only have input in the input box and there is nothing selected in the select box
    if(selectBox.selectedIndex < 0 && lib.exists(inputBox.value,'string') && inputBox.value.length > 2) {
        if(selectBox.length == 1) {
            selectBox.selectedIndex = 0;
            return true;
        } else {
            var inputContent = inputBox.value; // The value of the input box
            var inputMatch = new RegExp(inputContent,'i');
            for(var i=0; i<selectBox.length; i++) {
                var optionContent = selectBox.options[i].textContent || selectBox.options[i].innerText;
                if(lib.exists(optionContent,'string') && inputMatch.testoptionContent == inputContent) {
                    selectBox.selectedIndex = i;
                    return true;
                }
            }
        }
    }
    
    return false; // We couldn't find one to select
}
/**
 * Updates the value of a text field based on the selected option of a select element
 * @param {Element|String} selectElem - can be element or element id
 * @param {Element|String} searchInput The input field to update the value of - can be element or element id
 * @returns true
 * @type {Boolean}
 */
searchableBox.prototype.updateSearchInput = function(selectElem, searchInput) {
    window.setTimeout(function() {
        selectElem = yui.get(selectElem);
        if(selectElem.selectedIndex >= 0) {
            var selectedUniversity = selectElem[selectElem.selectedIndex].text;
            if (selectedUniversity != null) {
                yui.get(searchInput).value = selectedUniversity;
            }
        }
    },15);
    
    return true;
}
/**
 * Resets the fields in the searchableBox to the original state
 * @params {Element} inputBox
 * @params {Element} selectAll
 * @params {Element} visibleSelect
 * @params {Element} notListed
 */
searchableBox.prototype.reset = function(inputBox,selectAll,visibleSelect,notListed) {
    if(!lib.isElement(inputBox,'input')) {inputBox = this.searchInput}
    if(!lib.isElement(selectAll,'select')) {selectAll = this.selectAll}
    if(!lib.isElement(visibleSelect,'select')) {visibleSelect = this.visibleSelect}
    if(!lib.isElement(notListed,'select')) {notListed = this.notListed}
    // empty fields
    inputBox.value = '';
    visibleSelect.selectedIndex = -1;
    if(lib.isElement(notListed)) {notListed.checked = false}
    // Repopulate visibleSelect
    lib.copyChildrenTo(selectAll,visibleSelect);
    
    return true;
}



//~ /**
 //~ * @constructor
 //~ * <p>Adds items to a &lt;ul&gt; based on input elements</p>
 //~ * <p>
 //~ * Requires:
 //~ * <ul>
 //~ * <li>YAHOO libraries.</li>
 //~ * <li>{@link YahooShortcuts}.</li>
 //~ * <li>{@link TamarLibrary}.</li>
 //~ * </ul>
 //~ * </p>
 //~ * <p><strong>Usage:</strong></p>
 //~ * <p>HTML code:</p>
 //~ * <code>
 //~ *   <input type="text" id="input1" value="" />
 //~ *   <input type="text" id="input2" value="" />
 //~ *   <button type="button" id="addInput">add</button>
 //~ *   <ul id="dynamicItems"></ul>
 //~ * </code>
 //~ * <p>JavaScript code:</p>
 //~ * <code>
 //~ *   window.addListener('load',function() {
 //~ *     function errorFunction() {if(document.getElementById('input1').value == '') {return false;} else {return true}}
 //~ *     var myList = new dynamicFormList({input1: 'dynamicItemDesc', input2: 'dynamicItemAmount'},'dynamicItems','addInput','delete',errorFunction);
 //~ *   });
 //~ * </code>
 //~ * <p>Now if you press "add", it will check (errorFunction) to see if input1 is empty. If it is, don't do anything. Otherwise generate the following:</p>
 //~ * <code>
 //~ *   <ul id="dynamicItems">
 //~ *       <li>
 //~ *           <input type="hidden" name="dynamicItemDesc" value="thing1"/>
 //~ *           <span class="input1">thing1</span>
 //~ *           <input type="hidden" name="dynamicItemAmount" value="thin2"/>
 //~ *           <span class="input2">thin2</span>
 //~ *           <button>delete</button>
 //~ *       </li>
 //~ *   </ul>
 //~ * </code>
 //~ * <p>And pressing the "delete" button will delete the &lt;li&gt;</p>
 //~ * @param {Object} inputElements An object containing input element ids, mapped onto generated input names, e.g.: {input1: 'dynamicInput1Name', input2: 'dynamicInput2Name'}
 //~ * @param {Object|String} targetList The element or ID of the &lt;ul&gt; to add items to.
 //~ * @param {Object|String} addButton [optional] The element or ID to use as the add button. Or you can create this yourself using the "add" method
 //~ * @param {String} deleteButtonText [optional] The text to add to the "delete" button on each item. Otherwise just style it yourself, with "background-image" or perhaps "display: none"
 //~ * @param {Function} testFunction [optional] The test function to perform before adding an item. Will only add if the result is "true". Function will be passed "this" as an argument.
 //~ * @param {Function} onAddFunction [optional] A function to perform after adding an item. Gets passed the added list item as an argument.
 //~ * @param {Function} onDeleteFunction [optional] A function to perform after deleting an item. Gets passed the deleted list item as an argument.
 //~ * @returns This
 //~ * @type {Object}
 //~ */
//~ function dynamicFormList(inputElements,targetList,addButton,deleteButtonText,testFunction,onAddFunction,onDeleteFunction) {
    //~ /* Check and store all arguments as properties */
    //~ /* Mandatory arguments */
    //~ // Check inputElements is an object
    //~ if(typeof(inputElements) == 'object') {this.inputElements = inputElements}
    //~ else {
        //~ if(lib.canReport('error')) {console.error('dynamicFormList constructor: Expected object as 1st argument: ' + inputElements)}
        //~ return false;
    //~ }
    //~ // Check targetList is a list or id of list element
    //~ if(lib.isElement(yui.get(targetList),['ul','ol'])) {this.targetList = yui.get(targetList)}
    //~ else {
        //~ if(lib.canReport('error')) {console.error('dynamicFormList constructor: Expected list element or id as 2nd argument: ' + targetList)}
        //~ return false;
    //~ }
    //~ /* optional arguments */
    //~ // If addButton is supplied, check it's a button or id
    //~ if(!lib.exists(addButton)) {this.addButton == false;} // No add button passed, but that's fine
    //~ else if(lib.isElement(yui.get(addButton),'button')) {this.addButton = yui.get(addButton);} // We have an add button
    //~ else { // If it was passed but isn't an add button
        //~ if(lib.canReport('error')) {console.error('dynamicFormList constructor: Expected button element or id as 3rd argument: ' + addButton)}
        //~ return false;        
    //~ }
    //~ // Check for string as deleteButtonText
    //~ if(!lib.exists(deleteButtonText)) {this.deleteButtonText = false;} // Nothing passed, that's fine
    //~ else if(yui.isString(deleteButtonText)) {this.deleteButtonText = deleteButtonText} // Yes, string, cool
    //~ else { // If it was passed but isn't a string, error
        //~ if(lib.canReport('error')) {console.error('dynamicFormList constructor: Expected string as 4th argument: ' + deleteButtonText)}
        //~ return false;     
    //~ }
    //~ // Check for testFunction
    //~ if(!lib.exists(testFunction)) {this.testFunction = false;} // Nothing passed, that's fine
    //~ else if(yui.isFunction(testFunction)) {this.testFunction = testFunction} // Yes, Function, cool
    //~ else { // If it was passed but isn't a Function, error
        //~ if(lib.canReport('error')) {console.error('dynamicFormList constructor: Expected Function string as 5th argument: ' + testFunction)}
        //~ return false;     
    //~ }
    //~ // Check for onAddFunction
    //~ if(!lib.exists(onAddFunction)) {this.onAddFunction = false;} // Nothing passed, that's fine
    //~ else if(yui.isFunction(onAddFunction)) {this.onAddFunction = testFunction} // Yes, Function, cool
    //~ else { // If it was passed but isn't a Function, error
        //~ if(lib.canReport('error')) {console.error('dynamicFormList constructor: Expected Function string as 6th argument: ' + onAddFunction)}
        //~ return false;     
    //~ }
    //~ // Check for sonDeleteFunction
    //~ if(!lib.exists(onDeleteFunction)) {this.onDeleteFunction = false;} // Nothing passed, that's fine
    //~ else if(yui.isFunction(onDeleteFunction)) {this.onDeleteFunction = testFunction} // Yes, Function, cool
    //~ else { // If it was passed but isn't a Function, error
        //~ if(lib.canReport('error')) {console.error('dynamicFormList constructor: Expected Function string as 7th argument: ' + onDeleteFunction)}
        //~ return false;     
    //~ }
    
    //~ // If we have an add button, set it up
    //~ if(lib.isElement(this.addButton,'button')) {
        //~ this.setupAddButton();
    //~ }
//~ }
//~ /* Properties */
//~ /**
 //~ * Contains the input elements object (see constructor description)
 //~ * @type {Object}
 //~ */
//~ dynamicFormList.prototype.inputElements = new Object();
//~ /**
 //~ * Contains the "add" button - to be given a "click" listener to add stuff (see constructor description)
 //~ * @type {Object}
 //~ */
//~ dynamicFormList.prototype.addButton = new Object();
//~ /**
 //~ * Contains the &lt;ul&gt; to add items to (see constructor description)
 //~ * @type {Object}
 //~ */
//~ dynamicFormList.prototype.targetList = new Object();
//~ /**
 //~ * Contains the text for the delete button (see constructor description)
 //~ * @type {String}
 //~ */
//~ dynamicFormList.prototype.deleteButtonText = new String();
//~ /**
 //~ * Contains a boolean function to determine whether to add new item or not (see constructor description)
 //~ * @type {Function}
 //~ */
//~ dynamicFormList.prototype.testFunction = new Function();
//~ /**
 //~ * Contains a function to be run after a new item is added
 //~ * @type {Function}
 //~ */
//~ dynamicFormList.prototype.onAddFunction = new Function();
//~ /**
 //~ * Contains a function to be run after an item is deleted
 //~ * @type {Function}
 //~ */
//~ dynamicFormList.prototype.onDeleteFunction = new Function();
//~ /* methods */
//~ /**
 //~ * Setup the add button to run "add()" on "click", depending on testFunction
 //~ * @param {Element|String} addButton [optional] The element or ID of the button to use. If nothing supplied will use this.addButton.
 //~ * @param {Function} testFunction [optional] The Boolean function to determine whether to add or not. If nothing supplied will use this.testFunction.
 //~ * @returns True if success, false otherwise
 //~ * @type {Boolean}
 //~ */
//~ dynamicFormList.prototype.setupAddButton = function(addButton) {
    //~ if(lib.exists(addButton)) {this.addButton = yui.get(addButton)} // Get the button if it was passed
    //~ // Check we have a button element
    //~ if(!lib.isElement(this.addButton,'button')) {
        //~ if(lib.canReport('error')) {console.error('dynamicFormList.setupAddButton: Cannot find button element.')}
        //~ return false;
    //~ }
    //~ // Now setup onclick
    //~ function addButtonOnClick(evt) {
        //~ if(!evt) {window.event} // Get IE event
        //~ this.dynamicFormList.add(); // Add an item
    //~ }
    //~ this.addButton.dynamicFormList = this;
    //~ this.addButton.addListener('click',addButtonOnClick);
    //~ return true;
//~ }
//~ /**
 //~ * Add an item to this.targetElement
 //~ * @returns True if success, false otherwise
 //~ * @type {Boolean}
 //~ */
//~ dynamicFormList.prototype.add = function() {
    //~ // Check for stuff
    //~ if(!yui.isObject(this.inputElements)) {
        //~ if(lib.canReport('error')) {console.error('dynamicFormList.add: Cannot find object: '+this.inputElements)}
        //~ return false;
    //~ }
    //~ if(!lib.isElement(this.targetList,['ul','ol'])) {
        //~ if(lib.canReport('error')) {console.error('dynamicFormList.add: Cannot find list: '+this.targetList)}
        //~ return false;
    //~ }
    //~ // Now check test function
    //~ if(yui.isFunction(this.testFunction) && this.testFunction(this) != true) {
        //~ if(lib.canReport('log')) {console.log('dynamicFormList.add: Testfunction did not return true: ' + this.testFunction)}
        //~ return false;
    //~ }
    
    //~ /* Now set up list item */
    //~ var listItem = document.createElement('li'); // parent list item
    //~ // Add the input fields and "span" fields to the li
    //~ for(var inputName in this.inputElements) {
        //~ var inputTargetName = this.inputElements[inputName];
        //~ if(!yui.isString(inputTargetName)) {continue;} // If the value isn't a string, skip it
        
        //~ var theInput = yui.get(inputName); // The source input element
        //~ if(!(lib.isElement(theInput,'input') && yui.isString(theInput.value))) {continue;} // If input doesn't exist or has no value
        
        //~ // The new input element
        //~ var newInput = document.createElement('input');
        //~ newInput.type = 'hidden';
        //~ newInput.name = inputTargetName;
        //~ newInput.value = theInput.value;
        
        //~ // The new span
        //~ var newSpan = document.createElement('span');
        //~ newSpan.addClass(inputName);
        //~ lib.updateTextContent(newSpan,theInput.value);
        
        //~ // Add items to list item
        //~ listItem.appendChild(newInput);
        //~ listItem.appendChild(newSpan);
    //~ }
    
    //~ // The delete button
    //~ var deleteButton = document.createElement('button');
    //~ if(yui.isString(this.deleteButtonText)) {lib.updateTextContent(deleteButton,this.deleteButtonText)}
    //~ // Set up to delete item onclick
    //~ function deleteItem(evt) {
        //~ if(!evt) {evt = window.event} // Get IE event
        //~ var ancestorLI = this.getAncestorByTagName('li');
        //~ if(lib.isElement(ancestorLI)) {ancestorLI.remove();}
        //~ // Now run "on delete function"
        //~ if(yui.isFunction(this.onDeleteFunction)) {this.onDeleteFunction(ancestorLI)}
    //~ }
    //~ deleteButton.addListener('click',deleteItem);
    
    //~ // Add the delete button
    //~ listItem.appendChild(deleteButton);
    
    //~ // Add list item to list
    //~ this.targetList.appendChild(listItem);
    
    //~ // Now run "on add function"
    //~ if(yui.isFunction(this.onAddFunction)) {this.onAddFunction(listItem)}
    
    //~ return true;
//~ }
