    /**
    * Global vars
    */
    __AutoComplete = new Array();

    // Basic UA detection
    isIE = document.all ? true : false;
    isGecko = navigator.userAgent.toLowerCase().indexOf('gecko') != -1;
    isOpera = navigator.userAgent.toLowerCase().indexOf('opera') != -1;


    /**
    * Attachs the autocomplete object to a form element. Sets
    * onkeypress event on the form element.
    * 
    * @param string formElement Name of form element to attach to
    * @param array  data        Array of strings of which to use as the autocomplete data
    */
    function AutoComplete_Create (id, resultsLimit, ajaxUrl, minCharCount)
    {
        __AutoComplete[id] = {'data':null,
                              'isVisible':false,
                              'element':document.getElementById(id),
                              'dropdown':null,
                              'highlighted':null,
                              'history':new Ext.util.MixedCollection(),
                              'ajaxRequestId':null,
                              'maxitems': resultsLimit,
                              'ajaxUrl': ajaxUrl,
                              'delayTimer': null,
                              'historyLength': 15,
                              'minCharCount':minCharCount,
                              'extraParam':arguments[4] || function(){return ''},
                              'disabled':false};

        __AutoComplete[id]['element'].setAttribute('autocomplete', 'off');
        
        Ext.get(__AutoComplete[id]['element']).on('keydown', function(e) {return AutoComplete_KeyDown(this.getAttribute('id'), e)}, __AutoComplete[id]['element']);
        Ext.get(__AutoComplete[id]['element']).on('keyup', function(e) {return AutoComplete_KeyUp(this.getAttribute('id'), e)}, __AutoComplete[id]['element']);
        Ext.get(__AutoComplete[id]['element']).on('keypress', function(e) {if (e.getKey() == 13) e.stopEvent();});
        
        
        //__AutoComplete[id]['element'].onkeydown  = function(e) {return AutoComplete_KeyDown(this.getAttribute('id'), e);}
        //__AutoComplete[id]['element'].onkeyup    = function(e) {return AutoComplete_KeyUp(this.getAttribute('id'), e);}
        //__AutoComplete[id]['element'].onkeypress = function(e) {if (!e) e = window.event; if (e.keyCode == 13 || isOpera) return false;}
        
        __AutoComplete[id]['element'].ondblclick = function() {AutoComplete_ShowDropdown(this.getAttribute('id'));}
        __AutoComplete[id]['element'].onclick    = function(e) {if (!e) e = window.event; e.cancelBubble = true; e.returnValue = false;}

        // Hides the dropdowns when document clicked
        var docClick = function()
        {
           for (id in __AutoComplete) {
        	   if((typeof __AutoComplete[id]) != 'function'){
           			AutoComplete_HideDropdown(id);
        	   }
           }
           
        }

        if (document.addEventListener) {
            document.addEventListener('click', docClick, false);
        } else if (document.attachEvent) {
            document.attachEvent('onclick', docClick, false);
        }


        // Max number of items shown at once
        /*if (arguments[2] != null) {
            __AutoComplete[id]['maxitems'] = arguments[2];
            __AutoComplete[id]['firstItemShowing'] = 0;
            __AutoComplete[id]['lastItemShowing']  = arguments[2] - 1;
        }*/
        
        AutoComplete_CreateDropdown(id);
        
        // Prevent select dropdowns showing thru
        if (isIE) {
            __AutoComplete[id]['iframe'] = document.createElement('iframe');
            __AutoComplete[id]['iframe'].id = id +'_iframe';
            __AutoComplete[id]['iframe'].style.position = 'absolute';
            __AutoComplete[id]['iframe'].style.top = '0';
            __AutoComplete[id]['iframe'].style.left = '0';
            __AutoComplete[id]['iframe'].style.width = '0px';
            __AutoComplete[id]['iframe'].style.height = '0px';
            __AutoComplete[id]['iframe'].style.zIndex = '98';
            __AutoComplete[id]['iframe'].style.visibility = 'hidden';
            
            __AutoComplete[id]['element'].parentNode.insertBefore(__AutoComplete[id]['iframe'], __AutoComplete[id]['element']);
        }
    }


    /**
    * Creates the dropdown layer
    * 
    * @param string id The form elements id. Used to identify the correct dropdown.
    */
    function AutoComplete_CreateDropdown(id)
    {
        var left  = Ext.get(__AutoComplete[id]['element']).getX();
        var top   = Ext.get(__AutoComplete[id]['element']).getY();
        var width = Ext.get(__AutoComplete[id]['element']).getWidth();
    
        __AutoComplete[id]['dropdown'] = document.createElement('div');
        __AutoComplete[id]['dropdown'].className = 'autocomplete'; // Don't use setAttribute()
    
        __AutoComplete[id]['element'].parentNode.insertBefore(__AutoComplete[id]['dropdown'], __AutoComplete[id]['element']);
        
        // Position it
        Ext.get(__AutoComplete[id]['dropdown']).setX(left);
        Ext.get(__AutoComplete[id]['dropdown']).setY(top);
        Ext.get(__AutoComplete[id]['dropdown']).setWidth(width);
        //__AutoComplete[id]['dropdown'].style.width      = width + 'px';
        __AutoComplete[id]['dropdown'].style.zIndex     = '99';
        __AutoComplete[id]['dropdown'].style.visibility = 'hidden';
    }
    
    function AutoComplete_RenderList(id, data){
    	var toDisplay = new Array();
        var newDiv    = null;
        var text      = null;
        var numItems  = __AutoComplete[id]['dropdown'].childNodes.length;
	
		// Go thru data searching for matches
        for (i=0; i<data.length; ++i) {
            toDisplay[toDisplay.length] = data[i].n;
        }
        
        // No matches?
        if (toDisplay.length == 0) {
            AutoComplete_HideDropdown(id);
            return;
        }


        // Add data to the dropdown layer
        for (i=0; i<toDisplay.length; ++i) {
            newDiv = document.createElement('div');
            newDiv.className = 'autocomplete_item'; // Don't use setAttribute()
            newDiv.setAttribute('id', 'autocomplete_item_' + i);
            newDiv.setAttribute('index', i);
            newDiv.style.zIndex = '99';
            
             // Scrollbars are on display ?
            if (toDisplay.length > __AutoComplete[id]['maxitems'] && navigator.userAgent.indexOf('MSIE') == -1) {
                newDiv.style.width = __AutoComplete[id]['element'].offsetWidth - 22 + 'px';
            }

            newDiv.onmouseover = function() {AutoComplete_HighlightItem(__AutoComplete[id]['element'].getAttribute('id'), this.getAttribute('index'));};
            newDiv.onclick     = function() {AutoComplete_SetValue(__AutoComplete[id]['element'].getAttribute('id')); AutoComplete_HideDropdown(__AutoComplete[id]['element'].getAttribute('id'));}
            
            text   = document.createTextNode(toDisplay[i]);
            newDiv.appendChild(text);
            
            __AutoComplete[id]['dropdown'].appendChild(newDiv);
        }
        
        
        // Too many items?
        if (toDisplay.length > __AutoComplete[id]['maxitems']) {
            __AutoComplete[id]['dropdown'].style.height = (__AutoComplete[id]['maxitems'] * 15) + 2 + 'px';
        
        } else {
            __AutoComplete[id]['dropdown'].style.height = '';
        }

        
        /**
        * Set left/top in case of document movement/scroll/window resize etc
        */
        var top   = Ext.get(__AutoComplete[id]['element']).getY() + Ext.get(__AutoComplete[id]['element']).getHeight();
        Ext.get(__AutoComplete[id]['dropdown']).setY(top);
        
        var left   = Ext.get(__AutoComplete[id]['element']).getX();
        Ext.get(__AutoComplete[id]['dropdown']).setX(left);
        
        var width = Ext.get(__AutoComplete[id]['element']).getWidth();
        Ext.get(__AutoComplete[id]['dropdown']).setWidth(width);
        //__AutoComplete[id]['dropdown'].style.left = AutoComplete_GetLeft(__AutoComplete[id]['element']);
        //__AutoComplete[id]['dropdown'].style.top  = AutoComplete_GetTop(__AutoComplete[id]['element']) + __AutoComplete[id]['element'].offsetHeight;


        // Show the iframe for IE
        if (isIE) {
            __AutoComplete[id]['iframe'].style.top    = __AutoComplete[id]['dropdown'].style.top;
            __AutoComplete[id]['iframe'].style.left   = __AutoComplete[id]['dropdown'].style.left;
            __AutoComplete[id]['iframe'].style.width  = __AutoComplete[id]['dropdown'].offsetWidth;
            __AutoComplete[id]['iframe'].style.height = __AutoComplete[id]['dropdown'].offsetHeight;
            
            __AutoComplete[id]['iframe'].style.visibility = 'visible';
        }


        // Show dropdown
        if (!__AutoComplete[id]['isVisible']) {
            __AutoComplete[id]['dropdown'].style.visibility = 'visible';
            __AutoComplete[id]['isVisible'] = true;
        }

        
        // If now showing less items than before, reset the highlighted value
        if (__AutoComplete[id]['dropdown'].childNodes.length != numItems) {
            __AutoComplete[id]['highlighted'] = null;
        }
    	
    }
    
    
    function AutoComplete_AjaxSuccess(id, term, extra){
    	return function(response, options){
    		var res = Ext.decode(response.responseText);
    		if(res.success==true){
    			AutoComplete_RenderList(id, res.items);
    			AutoComplete_HistoryAdd(id, term+extra, res.items);
    		}
    	};
    }
    
    function AutoComplete_AjaxRequest(id, data, extra){
    	if (__AutoComplete[id]['ajaxRequestId']) Ext.Ajax.abort(__AutoComplete[id]['ajaxRequestId']);
    	
    	var result = __AutoComplete[id]['history'].key(data+extra);
    	if (result){ 
    		AutoComplete_RenderList(id, result);
    	}else{
	    	__AutoComplete[id]['ajaxRequestId'] = Ext.Ajax.request({
	    		url: __AutoComplete[id]['ajaxUrl'],
	    		success: AutoComplete_AjaxSuccess(id, data, extra),
	    		failure: function(){},
	    		headers: {},
	    		params: {
	    			data: data,
	    			extra: extra,
	    			limit: __AutoComplete[id]['maxitems']
	    		}
	    	});
    	}
    }
    
    
    /**
    * Shows the dropdown layer
    * 
    * @param string id The form elements id. Used to identify the correct dropdown.
    */
    function AutoComplete_ShowDropdown(id)
    {
        
    	__AutoComplete[id]['delayTimer'] = null;
    	
    	AutoComplete_HideAll();

        var value = __AutoComplete[id]['element'].value;
        
        if (value.length < __AutoComplete[id]['minCharCount']) return;

        // Remove all child nodes from dropdown
        while (__AutoComplete[id]['dropdown'].childNodes.length > 0) {
            __AutoComplete[id]['dropdown'].removeChild(__AutoComplete[id]['dropdown'].childNodes[0]);
        }

        if (__AutoComplete[id]['disabled']) return;
        
        AutoComplete_AjaxRequest(id, value, __AutoComplete[id]['extraParam'].call());
        
        
    }
    
    
    /**
    * Hides the dropdown layer
    * 
    * @param string id The form elements id. Used to identify the correct dropdown.
    */
    function AutoComplete_HideDropdown(id)
    {
        if (__AutoComplete[id]['iframe']) {
            __AutoComplete[id]['iframe'].style.visibility = 'hidden';
        }

        __AutoComplete[id]['dropdown'].style.visibility = 'hidden';
        __AutoComplete[id]['highlighted'] = null;
        __AutoComplete[id]['isVisible']   = false;
    }
    
    
    /**
    * Hides all dropdowns
    */
    function AutoComplete_HideAll()
    {
        for (id in __AutoComplete) {
        	
        	if((typeof __AutoComplete[id]) != 'function'){
        		AutoComplete_HideDropdown(id);
        	}
        }
    }
    
    
    /**
    * Highlights a specific item
    * 
    * @param string id    The form elements id. Used to identify the correct dropdown.
    * @param int    index The index of the element in the dropdown to highlight
    */
    function AutoComplete_HighlightItem(id, index)
    {
        if (__AutoComplete[id]['dropdown'].childNodes[index]) {
            for (var i=0; i<__AutoComplete[id]['dropdown'].childNodes.length; ++i) {
                if (__AutoComplete[id]['dropdown'].childNodes[i].className == 'autocomplete_item_highlighted') {
                    __AutoComplete[id]['dropdown'].childNodes[i].className = 'autocomplete_item';
                }
            }
            
            __AutoComplete[id]['dropdown'].childNodes[index].className = 'autocomplete_item_highlighted';
            __AutoComplete[id]['highlighted'] = index;
        }
    }


    /**
    * Highlights the menu item with the given index
    * 
    * @param string id    The form elements id. Used to identify the correct dropdown.
    * @param int    index The index of the element in the dropdown to highlight
    */
    function AutoComplete_Highlight(id, index)
    {
        // Out of bounds checking
        if (index == 1 && __AutoComplete[id]['highlighted'] == __AutoComplete[id]['dropdown'].childNodes.length - 1) {
            __AutoComplete[id]['dropdown'].childNodes[__AutoComplete[id]['highlighted']].className = 'autocomplete_item';
            __AutoComplete[id]['highlighted'] = null;
        
        } else if (index == -1 && __AutoComplete[id]['highlighted'] == 0) {
            __AutoComplete[id]['dropdown'].childNodes[0].className = 'autocomplete_item';
            __AutoComplete[id]['highlighted'] = __AutoComplete[id]['dropdown'].childNodes.length;
        }

        // Nothing highlighted at the moment
        if (__AutoComplete[id]['highlighted'] == null) {
            __AutoComplete[id]['dropdown'].childNodes[0].className = 'autocomplete_item_highlighted';
            __AutoComplete[id]['highlighted'] = 0;

        } else {
            if (__AutoComplete[id]['dropdown'].childNodes[__AutoComplete[id]['highlighted']]) {
                __AutoComplete[id]['dropdown'].childNodes[__AutoComplete[id]['highlighted']].className = 'autocomplete_item';
            }

            var newIndex = __AutoComplete[id]['highlighted'] + index;

            if (__AutoComplete[id]['dropdown'].childNodes[newIndex]) {
                __AutoComplete[id]['dropdown'].childNodes[newIndex].className = 'autocomplete_item_highlighted';
                
                __AutoComplete[id]['highlighted'] = newIndex;
            }
        }
    }


    /**
    * Sets the input to a given value
    * 
    * @param string id    The form elements id. Used to identify the correct dropdown.
    */
    function AutoComplete_SetValue(id)
    {
        __AutoComplete[id]['element'].value = __AutoComplete[id]['dropdown'].childNodes[__AutoComplete[id]['highlighted']].innerHTML;
    }
    
    
    /**
    * Checks if the dropdown needs scrolling
    * 
    * @param string id    The form elements id. Used to identify the correct dropdown.
    */
    function AutoComplete_ScrollCheck(id)
    {
        // Scroll down, or wrapping around from scroll up
        if (__AutoComplete[id]['highlighted'] > __AutoComplete[id]['lastItemShowing']) {
            __AutoComplete[id]['firstItemShowing'] = __AutoComplete[id]['highlighted'] - (__AutoComplete[id]['maxitems'] - 1);
            __AutoComplete[id]['lastItemShowing']  = __AutoComplete[id]['highlighted'];
        }
        
        // Scroll up, or wrapping around from scroll down
        if (__AutoComplete[id]['highlighted'] < __AutoComplete[id]['firstItemShowing']) {
            __AutoComplete[id]['firstItemShowing'] = __AutoComplete[id]['highlighted'];
            __AutoComplete[id]['lastItemShowing']  = __AutoComplete[id]['highlighted'] + (__AutoComplete[id]['maxitems'] - 1);
        }
        
        __AutoComplete[id]['dropdown'].scrollTop = __AutoComplete[id]['firstItemShowing'] * 15;
    }


    /**
    * Function which handles the keypress event
    * 
    * @param string id    The form elements id. Used to identify the correct dropdown.
    */
    function AutoComplete_KeyDown(id)
    {
    	
        // Mozilla
        //if (arguments[1] != null) {
            event = arguments[1];
        //}
        
        var keyCode = event.getKey();
        //alert(keyCode);
        switch (keyCode) {

            // Return/Enter
            case 13:
                if (__AutoComplete[id]['highlighted'] != null) {
                    AutoComplete_SetValue(id);
                    AutoComplete_HideDropdown(id);
                }
                
                event.stopEvent();
                //event.returnValue = false;
                //event.cancelBubble = true;
                break;

            // Escape
            case 27:
                AutoComplete_HideDropdown(id);
                //event.returnValue = false;
                //event.cancelBubble = true;
                event.stopEvent();
                break;
            
            // Up arrow
            case 38:
                if (!__AutoComplete[id]['isVisible']) {
                	if (__AutoComplete[id]['delayTimer']) clearTimeout(__AutoComplete[id]['delayTimer']);
                	__AutoComplete[id]['delayTimer'] = setTimeout("AutoComplete_ShowDropdown('"+id+"')", 500);
                    //AutoComplete_ShowDropdown(id);
                }
                
                AutoComplete_Highlight(id, -1);
                AutoComplete_ScrollCheck(id, -1);
                return false;
                break;
            
            // Tab
            case 9:
            	//if (__AutoComplete[id]['element'].value.trim().length < 2) return;
                if (__AutoComplete[id]['isVisible']) {
                    AutoComplete_HideDropdown(id);
                }
                return true;
            
            // Down arrow
            case 40:
            	if (__AutoComplete[id]['element'].value.trim().length < __AutoComplete[id]['minCharCount']) return;
            	 if (!__AutoComplete[id]['isVisible']) {
                 	if (__AutoComplete[id]['delayTimer']) clearTimeout(__AutoComplete[id]['delayTimer']);
                 	__AutoComplete[id]['delayTimer'] = setTimeout("AutoComplete_ShowDropdown('"+id+"')", 500);
                     //AutoComplete_ShowDropdown(id);
                 }
                
                AutoComplete_Highlight(id, 1);
                AutoComplete_ScrollCheck(id, 1);
                return false;
                break;
        }
        return true;
    }


    /**
    * Function which handles the keyup event
    * 
    * @param string id    The form elements id. Used to identify the correct dropdown.
    */
    function AutoComplete_KeyUp(id)
    {
    	
    	//if (__AutoComplete[id]['element'].value.trim().length < 2) return;
        // Mozilla
        //if (arguments[1] != null) {
        //    event = arguments[1];
        //}
            event = arguments[1];
        var keyCode = event.getKey();
        //alert(keyCode);
        //
        switch (keyCode) {
            case 13:
            	event.stopEvent();
                //event.returnValue = false;
                //event.cancelBubble = true;
                break;

            case 27:
                AutoComplete_HideDropdown(id);
                //event.returnValue = false;
                //event.cancelBubble = true;
                event.stopEvent();
                break;
            
            case 16:
            case 17:
            case 37:
            case 38:
            case 39:
            case 40:
                return false;
                break;

            default:
            	if (__AutoComplete[id]['element'].value.trim().length < __AutoComplete[id]['minCharCount']){
            		AutoComplete_HideDropdown(id);
            		return true;
            	}
            
            	if (__AutoComplete[id]['delayTimer']) clearTimeout(__AutoComplete[id]['delayTimer']);
            	__AutoComplete[id]['delayTimer'] = setTimeout("AutoComplete_ShowDropdown('"+id+"')", 500);
                //AutoComplete_ShowDropdown(id);
            
                break;
        }
        return true;
    }
    
    /**
    * Returns whether the dropdown is visible
    * 
    * @param string id    The form elements id. Used to identify the correct dropdown.
    */
    function AutoComplete_isVisible(id)
    {
        return __AutoComplete[id]['dropdown'].style.visibility == 'visible';
    }
    
    
    /*function AutoComplete_HistoryFind(id, term){
    	if (!__AutoComplete[id]['history']) return null;
    	for (var i=0; i<=__AutoComplete[id]['history'].length; i++){
    		if ()
    	}
    }*/
    
    function AutoComplete_HistoryAdd(id, term, data){
    	var h = __AutoComplete[id]['history'];
    	if (h.containsKey(term)){
    		h.removeKey(term);
    		h.add(term, data);
    	}else{ 
    		if (h.getCount() == __AutoComplete[id]['historyLength']){
    			h.remove(h.first())
    		}
    		h.add(term, data);
    	}
    	
    }
    
    function AutoComplete_Disable(id){
    	__AutoComplete[id]['disabled'] = true;
    }
    
    function AutoComplete_Enable(id){
    	__AutoComplete[id]['disabled'] = false;
    }
