// TypeAhead - a javascript auto-complete functionality for web forms. // v1.3 - July 28th 2005 // Copyright (c) 2005 Cédric Savarese // This software is licensed under the CC-GNU LGPL // You may use it as long as you keep this copyright notice intact. // A link back to the formassembly.com is also appreciated. // Usage Notes: // ============ // var typeAhead = new TypeAhead(listName [, groupName]); /* Instanciate the TypeAhead object */ // typeAhead.initInput(inputFieldID); /* Binds the TypeAhead Object to an input field */ // Parameters: // listName: Identifier for the suggestion list. // groupName: (optional) Identifier to keep distinct lists per users, group, date.. or whatever. // inputFieldID: Id of the targeted input field (ex: ) // To bind the same TypeAhead instance to several text inputs, call typeAhead.initInput when the // input field gets the focus: // document.getElementById('inputFieldID').onfocus = function() { typeAhead.initInput(this.id) }; // Change Log // ========== // v1.1 Added Suggestion drop-down ('google suggests' style) // v1.2 Added Private suggestion lists // v1.3 Refactored function TypeAhead(listName /*[,userName]*/ ) { //this.typeAheadServiceURL = "http://formassembly.com/srvc-type-ahead2.php"; this.typeAheadServiceURL = "http://" + window.location.host + "/ajax/popup/list/ListController.jpf"; this.listName = listName // Identifier for the suggestion list. Doesn't have to match the input field id or name attributes. this.userName = ""; // group/user identifier (for private suggestions lists) if(arguments.length>1) this.userName = arguments[1]; this.inputId = null; // id of the currently targeted text input this.inputElement = null; // reference to the currently targeted text input this.suggestions = new Array(); // list of suggestions retrieved for the given input id this.matchingSuggestions = new Array(); // list of suggestions matching the user input this.suggestedText = ""; // latest suggested text this.userText = ""; // latest user typed text this.suggestionDropDown = null; // reference to the Drop Down DIV w/ the list of suggestions this.maxItemInDropDown = 20; // max number of suggestions in the drop-down list var suggestedIndex = 0; // index of the selected suggestion in the Drop Down list (changed w/ up & down arrows) var pressedKeyCount=0; var self = this; // TypeAhead object reference var HTTPReq; // HTTP Request object reference var HTTPReqPost; // HTTP Request object reference (used for POST Requests) var isWaitingForSuggestions = false; // Flag to show updated suggestion list var isThereMoreOnServer = false; // Flag to prevent useless server access // Debug Output var debugOutput = document.getElementById('debugOutput'); function debug(text) { if(typeof debugOutput != "undefined" && debugOutput) debugOutput.innerHTML = debugOutput.innerHTML+"

"+text; } // INITIALIZATION ROUTINES ------------------------------------------------------------------------------------------------ // init: runs when the object is instanciated. this.init = function() { debug("TypeAhead Object Initialization"); if(window.ActiveXObject) HTTPReq = new ActiveXObject("Microsoft.XMLHTTP"); else HTTPReq = new XMLHttpRequest(); //init suggestions array with something //self.getSuggestions(); } // initInput: to be run when the 'inputId' field is displayed or get the focus. this.initInput = function() { /*[inputId]*/ // Make sure we have a valid id and a valid reference to the text input if(arguments.length>0) self.inputId = arguments[0]; if(self.inputId) self.inputElement = document.getElementById(self.inputId); if(self.inputElement && !self.inputId) { if(!self.inputElement.id) self.inputElement.id = randomId(); self.inputId = self.inputElement.id; } if(!self.inputId) return false; self.userText = self.inputElement.value; debug("TypeAhead Field Initialization: "+ self.inputId); // Create markup for drop-down list of suggestions self.suggestionDropDown = document.getElementById("THDropDown-" + self.inputId); if(!self.suggestionDropDown) { self.suggestionDropDown = document.createElement('DIV'); self.suggestionDropDown.id = "THDropDown-" + self.inputId; self.suggestionDropDown.className = "THHideDropDown"; self.suggestionDropDown = self.inputElement.parentNode.insertBefore(self.suggestionDropDown, self.inputElement.nextSibling); self.suggestionDropDown.style.top = (getTop(self.inputElement) + self.inputElement.offsetHeight + 2).toString() + "px"; self.suggestionDropDown.style.left = getLeft(self.inputElement).toString() + "px"; self.suggestionDropDown.style.width = self.inputElement.offsetWidth.toString() + "px"; } // Handle user input self.inputElement.onkeyup = function (evt) { if(!evt) evt = window.event; switch(evt.keyCode) { case 8: // backspace self.userText = self.inputElement.value; //self.getSuggestions(); break; case 46: // delete self.userText = self.inputElement.value; //self.getSuggestions(); break; case 40: // arrow down if(suggestedIndex==0) { self.updateSuggestionList(); self.showSuggestionList(); } if(suggestedIndex < self.matchingSuggestions.length) suggestedIndex++; self.suggest(); break; case 38: // arrow up if(suggestedIndex > 1) suggestedIndex --; self.suggest(); break; default: pressedKeyCount--; if (self.userText != self.inputElement.value) { self.userText = self.inputElement.value suggestedIndex = 0; self.getSuggestions(); } } } self.inputElement.onkeydown = function (evt) { if(!evt) evt = window.event; if(evt.keyCode != 38 && evt.keyCode != 40 && evt.keyCode != 46 && evt.keyCode != 8) pressedKeyCount++; } self.inputElement.onfocus = function(evt) { //self.updateSuggestionList(); //self.showSuggestionList(); } // hides the suggestion drop-down when input field not in focus self.inputElement.onblur = function(evt) { window.setTimeout(function() { self.suggestionDropDown.className = self.suggestionDropDown.className.replace("THShowDropDown","THHideDropDown"); },500); } } // SUGGESTION LIST IN/OUT ------------------------------------------------------------------------------------------------ this.getSuggestions = function(text) { if(text) self.userText = text; HTTPReq.abort(); HTTPReq.onreadystatechange = self.populateSuggestionsFromService; HTTPReq.open("GET", self.typeAheadServiceURL + "?inputid="+encodeURIComponent(self.listName)+"&inputtxt="+encodeURIComponent(self.userText)+"&user="+encodeURIComponent(self.userName), true); HTTPReq.send(null); debug("request sent: "+"inputid="+encodeURIComponent(self.listName)+"&inputtxt="+encodeURIComponent(self.userText)); } this.populateSuggestionsFromService = function(evt) { if (HTTPReq.readyState == 4) { /* Esto es en caso de que se quiera regresar un texto separado por algún tipo de tabulador. ----------------------------- var string = trim(HTTPReq.responseText); self.suggestions = string.length > 0 ? string.split('&') : []; debug("response: "+ HTTPReq.responseText); ----------------------------- */ var nombres = HTTPReq.responseXML.getElementsByTagName('nombre'); var resultsArray = new Array(); for (loop = 0; loop < nombres.length; loop++) { resultsArray[loop] = trim(nombres[loop].childNodes[0].nodeValue); } self.suggestions = resultsArray; self.suggest(); } } // USER INPUT OPERATIONS ------------------------------------------------------------------------------------------------ // suggest: replace input value with suggested text. this.suggest = function(text) { if(text) self.userText = text; self.suggestedText = ""; if(suggestedIndex==0 && pressedKeyCount==0) { // can't suggest if more than one key is pressed at the same time self.updateSuggestionList(); if(self.matchingSuggestions.length>0) { self.suggestedText = self.matchingSuggestions[0]; var startIndex = self.inputElement.value.length; if(self.suggestedText != ""){ self.inputElement.value = self.suggestedText; self.selectText(startIndex, self.suggestedText.length); } } } if(suggestedIndex>0) { // used up/down arrow key to select in the drop-down list if(self.matchingSuggestions.length>0) { self.suggestedText = self.matchingSuggestions[suggestedIndex-1]; var startIndex = self.inputElement.value.length; self.inputElement.value = self.suggestedText; self.selectText(startIndex, self.suggestedText.length); } } self.showSuggestionList(); } this.updateSuggestionList = function() { self.matchingSuggestions = self.suggestions; } this.showSuggestionList = function() { var htmlList=""; for (var i=0; i < self.matchingSuggestions.length; i++) { if(suggestedIndex-1 == i) htmlList += "
  • "+self.matchingSuggestions[i]+"
  • "; else htmlList += "
  • "+self.matchingSuggestions[i]+"
  • "; } if(htmlList=="") if(isWaitingForSuggestions) self.suggestionDropDown.innerHTML = "loading more suggestions..."; else self.suggestionDropDown.innerHTML = "no suggestion available"; else self.suggestionDropDown.innerHTML = ""; self.suggestionDropDown.className = self.suggestionDropDown.className.replace("THHideDropDown","THShowDropDown"); } // UTILITY FUNCTIONS ------------------------------------------------------------------------------------------------ this.selectText = function(startIndex, nbChars) { if (self.inputElement.createTextRange) { // for Internet Explorer var txtRange = self.inputElement.createTextRange(); txtRange.moveStart("character", startIndex); txtRange.moveEnd("character", nbChars - self.inputElement.value.length); txtRange.select(); } else if (self.inputElement.setSelectionRange) { // for Mozilla self.inputElement.setSelectionRange(startIndex, nbChars); } //set focus back to the textbox self.inputElement.focus(); } function getTop(obj) { var cur = 0; if(obj.offsetParent) { while(obj.offsetParent) { cur+=obj.offsetTop; obj = obj.offsetParent; } } return cur; } function getLeft(obj) { var cur = 0; if(obj.offsetParent) { while(obj.offsetParent) { cur+=obj.offsetLeft; obj = obj.offsetParent; } } return cur; } function randomId() { var rId = ""; for (var i=0; i<6;i++) rId += String.fromCharCode(97 + Math.floor((Math.random()*24))) return rId; } function trim(s) { while (s.substring(0,1) == ' ') { s = s.substring(1,s.length); } while (s.substring(s.length-1,s.length) == ' ') { s = s.substring(0,s.length-1); } return s; } // INITIALIZE INSTANCE ------------------------------------------------------------------------------------------------ this.init(); } // Utility functions var XBrowserAddHandler = function (target,eventName,handlerName) { if(!target) return; if (target.addEventListener) { target.addEventListener(eventName, function(e){eval(handlerName)(e);}, false); } else if (target.attachEvent) { target.attachEvent("on" + eventName, function(e){eval(handlerName)(e);}); } else { // THIS CODE NOT TESTED var originalHandler = target["on" + eventName]; if (originalHandler) { target["on" + eventName] = function(e){originalHandler(e);eval(handlerName)(e);}; } else { target["on" + eventName] = eval(handlerName); } } }