/* By Anders Ytterström 2012. Example use: */ /*jslint indent: 2, maxlen: 90, browser: true */ var autocomplete = function (id, values, callback) { "use strict"; var autoCompleteTimer, stopDefaultAction, getPosition, field, initAutoComplete, keydownAutoComplete, generateDropdown, autoComplete, mouseoverDropdown, mouseoutDropdown, mousedownDropdown, assignMouseListeners, blurAutoComplete, closeDropdown; stopDefaultAction = function (event) { event.returnValue = false; if (typeof event.preventDefault !== "undefined") { event.preventDefault(); } return true; }; getPosition = function (theElement) { var positionX = 0, positionY = 0; while (theElement !== null) { positionX += theElement.offsetLeft; positionY += theElement.offsetTop; theElement = theElement.offsetParent; } return [positionX, positionY]; }; initAutoComplete = function () { field = document.getElementById(id); field.setAttribute("autocomplete", "off"); if (window.attachEvent) { field.attachEvent("onkeydown", keydownAutoComplete); } else { field.addEventListener("keydown", keydownAutoComplete); } field.onblur = function () { blurAutoComplete(); }; }; keydownAutoComplete = function (event) { var target = event.target || event.srcElement, autoCompleteDropdown, childLis, selected, i, max, inputRanges; if (typeof event === "undefined") { event = window.event; } switch (event.keyCode) { case 9: // tab case 13: // enter case 16: // shift case 17: // ctrl case 18: // alt case 20: // caps lock case 27: // esc case 33: // page up case 34: // page down case 35: // end case 36: // home case 37: // left arrow case 39: // right arrow break; case 38: // up arrow autoCompleteDropdown = document.getElementById("autoCompleteDropdown"); if (autoCompleteDropdown !== null) { childLis = autoCompleteDropdown.childNodes; selected = false; for (i = 0, max = childLis.length; i < max; i += 1) { if (childLis[i].className === "hover") { selected = true; if (i > 0) { childLis[i].className = ""; childLis[i - 1].className = "hover"; target.value = childLis[i - 1].firstChild.nodeValue; } break; } } if (!selected) { childLis[0].className = "hover"; target.value = childLis[0].firstChild.nodeValue; } } stopDefaultAction(event); break; case 40: // down arrow autoCompleteDropdown = document.getElementById("autoCompleteDropdown"); if (autoCompleteDropdown !== null) { childLis = autoCompleteDropdown.childNodes; selected = false; for (i = 0, max = childLis.length; i < max; i += 1) { if (childLis[i].className === "hover") { selected = true; if (i < childLis.length - 1) { childLis[i].className = ""; childLis[i + 1].className = "hover"; target.value = childLis[i + 1].firstChild.nodeValue; } break; } } if (!selected) { childLis[0].className = "hover"; target.value = childLis[0].firstChild.nodeValue; } } stopDefaultAction(event); break; case 8: // backspace case 46: // delete if (typeof autoCompleteTimer !== "undefined") { clearTimeout(autoCompleteTimer); } autoCompleteTimer = setTimeout(function () { generateDropdown(false); }, 500); break; default: if (typeof autoCompleteTimer !== "undefined") { clearTimeout(autoCompleteTimer); } target = event.target || event.srcElement; inputRanges = "false"; if (typeof target.createTextRange !== "undefined" || typeof target.setSelectionRange !== "undefined") { inputRanges = "true"; } autoCompleteTimer = setTimeout(function () { generateDropdown(inputRanges); }, 500); } return true; }; assignMouseListeners = function (e) { e.onmouseover = function () { mouseoverDropdown(this); }; e.mouseout = function () { mouseoutDropdown(this); }; e.mousedown = function () { mousedownDropdown(this); }; }; generateDropdown = function (doAutoComplete) { closeDropdown(); var input = document.getElementById(id), newUl = document.createElement("div"), newLi, i, max = values.length; newUl.setAttribute("id", "autoCompleteDropdown"); newUl.autoCompleteInput = input; newUl.style.position = "absolute"; newUl.style.left = getPosition(input)[0] + "px"; newUl.style.top = getPosition(input)[1] + input.offsetHeight - 2 + "px"; newUl.style.width = input.offsetWidth - 3 + "px"; for (i = 0; i < max; i += 1) { if (values[i].indexOf(input.value) === 0) { newLi = document.createElement("button"); newLi.innerHTML = values[i]; assignMouseListeners(newLi); newUl.appendChild(newLi); } } if (newUl.firstChild !== null) { document.getElementsByTagName("body")[0].appendChild(newUl); } if (typeof doAutoComplete !== "undefined" && doAutoComplete) { autoComplete(); } return true; }; autoComplete = function () { var input = document.getElementById(id), cursorMidway = false, range, originalValue, autoCompleteDropdown; if (typeof document.selection !== "undefined") { range = document.selection.createRange(); if (range.move("character", 1) !== 0) { cursorMidway = true; } } else if (typeof input.selectionStart !== "undefined" && input.selectionStart < input.value.length) { cursorMidway = true; } originalValue = input.value; autoCompleteDropdown = document.getElementById("autoCompleteDropdown"); if (autoCompleteDropdown !== null && !cursorMidway) { autoCompleteDropdown.firstChild.className = "hover"; input.value = autoCompleteDropdown.firstChild.firstChild.nodeValue; if (typeof input.createTextRange !== "undefined") { range = input.createTextRange(); range.moveStart("character", originalValue.length); range.select(); } else if (typeof input.setSelectionRange !== "undefined") { input.setSelectionRange(originalValue.length, input.value.length); } if (autoCompleteDropdown.childNodes.length === 1) { setTimeout(function () { closeDropdown(); }, 10); } } return true; }; mouseoverDropdown = function (target) { var childLis, i, max; while (target.nodeName.toLowerCase() !== "button") { target = target.parentNode; } childLis = target.parentNode.childNodes; max = childLis.length; for (i = 0; i < max; i += 1) { childLis[i].className = ""; } target.className = "hover"; return true; }; mouseoutDropdown = function (target) { while (target.nodeName.toLowerCase() !== "button") { target = target.parentNode; } target.className = ""; return true; }; mousedownDropdown = function (target) { while (target.nodeName.toLowerCase() !== "button") { target = target.parentNode; } target.parentNode.autoCompleteInput.value = target.firstChild.nodeValue; closeDropdown(); return true; }; blurAutoComplete = function () { if (typeof autoCompleteTimer !== "undefined") { clearTimeout(autoCompleteTimer); } closeDropdown(); callback(field.value); return true; }; closeDropdown = function () { var autoCompleteDropdown = document.getElementById("autoCompleteDropdown"); if (autoCompleteDropdown !== null) { autoCompleteDropdown.parentNode.removeChild(autoCompleteDropdown); } return true; }; initAutoComplete(); };