gists/2012.js.autocomplete.js

291 lines
8.2 KiB
JavaScript

/*
By Anders Ytterström 2012.
Example use:
<script src="autocomplete.js"></script>
<script>
var values = ["Hansi", "Andre", "The Omen", "Marcus", "Prince Charles"],
callback = function (k) {
console.log(k);
};
autocomplete("bg", values, callback);
</script>
*/
/*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();
};