function AutoCompleteSearch(textFieldSelector) {
	// Code at this level is the same as in a constructor

	// Store a reference to this to allow access to this object's properties
	// and methods in callbacks (where 'this' is another object).
	var self = this;
	// Store the text field element/object
	this.textField = $(textFieldSelector);
	// The AutoComplete DIV.
	this.autoCompleteDiv = '';
	// The UL for category matches in the AutoComplete DIV.
	this.categoryMatchesList = '';
	// The UL for product matches in the AutoComplete DIV.
	this.productMatchesList = '';
	// The UL for FAQ matches in the AutoComplete DIV.
	this.faqMatchesList = '';
	// A constant to indicate no timer is currently set.
	var NO_TIMER_SET = -1;
	// Intialise the timer ID variable.
	this.timerId = NO_TIMER_SET;
	// The configuration
	this.configuration = {};
	// A showing indicator
	this.showing = false;

	/**
	 * Make the AJAX requests to get the auto-complete entries. One request is to the server, for the FAQ
	 * entries, and the other is to Fredhopper, for products and categories.
	 */
	this.update = function(enteredText) {
		if (typeof(enteredText) == 'undefined') {
			return;
		}
		
		// Query the local server for FAQ matches
		$.ajax({
			url : '/autoCompleteSearch.action',
			type : 'POST',
			dataType : 'json',
			data : {
				enteredText : enteredText
			},
			success : function(data) {
				self.faqMatchesList.empty();
				
				// Process FAQ matches
				$.each(data.matchingFAQs, function(index, item) {
					if (index >= 4) {
						// Break out of loop
						return false;
					}
					// Create an element for each match
					var link = $('<a href="' + item.url + '">' + item.text + '<strong class="number">(' + item.matchCount + ')</strong></a>');
					var option = $(document.createElement('li'));
					option.append(link);
					
					option.click(function() {
						// Set the timeout ID to something recognisable as 'no timer set'.
						self.timerId = NO_TIMER_SET;
						// Update the flag
						self.showing = false;
						// Hide the auto-complete DIV
						self.autoCompleteDiv.fadeOut();
					});

					self.faqMatchesList.append(option);
				});

				//alert("self.faqMatchesList.html().trim() = " + self.faqMatchesList.html().trim());
				// If no FAQ entries matched, show an informational message
				// When no matching results for either categories, products or FAQ, then the section should not be visible ->https://jira.unic.com/browse/STANDAARD-441. 
				if (self.faqMatchesList.html().trim() == '') {
					document.getElementById("faqResultsTitle").style.display = 'none';
					//	self.faqMatchesList.append($('<li>' + self.configuration.noResultsFoundLabel + '</li>'));
				}else{
					document.getElementById("faqResultsTitle").style.display = 'block';
				} 
				
				// Show the auto-complete DIV
				if (self.showing == false) {
					self.showing = true;
					self.autoCompleteDiv.fadeIn();
				}
			},
			error : function(xmlHttpRequest, textStatus, errorThrown) {
				//alert(errorThrown);
			}
		});
		
		// Query fredhopper for product and category matches
		var fhUrl = self.configuration.fredhopperUrl + '?scope=' + self.configuration.searchScope + '&search=' + enteredText + '&callback=?';
		$.getJSON(fhUrl, function(data) {
			self.categoryMatchesList.empty();
			self.productMatchesList.empty();
			
			if (typeof(data.suggestionGroups) != 'undefined') {
				$.each(data.suggestionGroups, function(index, suggestion) {
					var ul = self.productMatchesList;
					
					var categories = false;
					if (suggestion.indexName == '3categories') {
						categories = true;
						ul = self.categoryMatchesList;
					}
					
					$.each(suggestion.suggestions, function(index, suggestion) {
						if ((categories == true && index >= 4) || (categories != true && index >= 10)) {
							// Break out of loop
							return false;
						}
						var entry;
						var option = $(document.createElement('li'));
						if (categories == true) {
							entry = "<a href='/products.action?fh_location=" + suggestion.fhLocation + "'>" 
										+ suggestion.mlValue +"<strong class='number'>(" + suggestion.nrResults + ")</strong></a>";
						} else {
							// There is a (temporary) inconsistency between the data returned by the live1 and test1 servers,
							// one is returning 'secondid' and the other 'secondId'. Make sure we pick up one or the other.
							// This should be removed when both servers are returning consistent data structures, expected
							// to happen around the 15th October 2011.
							var id = suggestion.secondid;
							if (typeof(id) == 'undefined') {
								id = suggestion.secondId;
							}
							var categoryName = "";
							if (suggestion.category_name){
								categoryName = " ("+suggestion.category_name+")";
							}
							entry = $("<a href='" + suggestion.seourl + "'>" + updateHaystack(suggestion.name,enteredText)+categoryName+"</a>");
						}
						option.append(entry);
						
						ul.append(option);
					});					
				});
				
				// If no Categories entries matched, show an informational message
				if (self.categoryMatchesList.html().trim() == '') {
					//self.categoryMatchesList.append($('<li>' + self.configuration.noResultsFoundLabel + '</li>'));
					document.getElementById("categoryResultsTitle").style.display = 'none';
				}else{
					document.getElementById("categoryResultsTitle").style.display = 'block';
				}
				// If no Product entries matched, show an informational message
				if (self.productMatchesList.html().trim() == '') {
					//self.productMatchesList.append($('<li>' + self.configuration.noResultsFoundLabel + '</li>'));
					document.getElementById("productResultsTitle").style.display = 'none';
				}else{
					document.getElementById("productResultsTitle").style.display = 'block';
				}
				
				// Show the auto-complete DIV
				if (self.showing == false) {
					self.showing = true;
					self.autoCompleteDiv.fadeIn();
				}
			}
		});
	};
	
	function updateHaystack(input, needle) {
	    return input.replace(new RegExp('(^'+needle+')(|$)','gi'), '<b>$1</b>$2');
	}

	
	/**
	 * Load the configuration via an AJAX call to the server and initialise the GUI on response received.
	 */
	this.initialise = function() {
		$.getJSON("/autoCompleteConfiguration.action", function(responseData) {
			// Store the configuration
			self.configuration = responseData;
			// Initialise the drop-down etc..
			self.initialiseGUI();
		});		
	};

	/**
	 * Initialises the auto-completer.
	 */
	this.initialiseGUI = function() {		
		// Create the DIV to float below the text field
		this.createAutoCompleteDiv();

		// Add the onKeyUp listener to the text field
		this.textField.keyup(function() {
			self.update(self.textField.attr('value'));
		});

		// Add the onExit listener to the text field to hide the drop-down
		this.textField.blur(function() {
			// Set a timer to hide the auto-complete DIV after a delay
			self.timerId = setTimeout(function() {
				// Update the flag
				self.showing = false;
				// Hide the div
				self.autoCompleteDiv.fadeOut();
				// Set the timeout ID to something recognisable as 'no timer set'.
				self.timerId = NO_TIMER_SET;
			}, 400);
		});
	};

	/**
	 * Create the auto-complete div.
	 */
	this.createAutoCompleteDiv = function() {
		// Create the lists to contain the search results
		this.categoryMatchesList = $(document.createElement('ul'));
		this.categoryMatchesList.attr('id', 'categoryMatchesList');
		this.productMatchesList = $(document.createElement('ul'));
		this.productMatchesList.attr('id', 'productMatchesList');
		this.faqMatchesList = $(document.createElement('ul'));
		this.faqMatchesList.attr('id', 'faqMatchesList');
		
		// Create the auto-complete div itself
		this.autoCompleteDiv = $(document.createElement('div'));
		this.autoCompleteDiv.attr('id', 'autoCompleteDiv');
		this.autoCompleteDiv.attr('class', 'result-list');
		
		// Append the headers and lists to the DIV		
		this.autoCompleteDiv.append($("<div class='heading' id='categoryResultsTitle'><em class='title'>" + this.configuration.categoryResultsTitle + "</em></div>"));
		this.autoCompleteDiv.append(this.categoryMatchesList);
		this.autoCompleteDiv.append($("<div class='heading' id='productResultsTitle'><em class='title'>" + this.configuration.productResultsTitle + "</em></div>"));
		this.autoCompleteDiv.append(this.productMatchesList);
		this.autoCompleteDiv.append($("<div class='heading' id='faqResultsTitle'><em class='title'>" + this.configuration.faqResultsTitle + "</em></div>"));
		this.autoCompleteDiv.append(this.faqMatchesList);
		
		// Prevent hiding of drop-down if mouse goes over drop-down while text-box loses focus 
		this.autoCompleteDiv.mouseover(function() {
			// Only if a timer is currently set 
			if (self.timerId != NO_TIMER_SET) {
				// Clear timer to prevent hiding of auto-complete DIV
				clearTimeout(self.timerId);
				// Set the timeout ID to something recognisable as 'no timer set'.
				self.timerId = NO_TIMER_SET;
			}
		});

		this.autoCompleteDiv.hide();

		this.textField.parent().append(this.autoCompleteDiv);
	};

	
	/**
	 * Setter for the contents of the text box. Does not trigger a search.
	 * 
	 * @param text the text to set in the text box. 
	 */
	this.setText = function(text) {
		if (typeof(text) == 'undefined') {
			return;
		}
		
		this.textField.attr('value', text);
	};
	
	// Initialise the auto-completer.
	this.initialise();
};
