補完付きのテキストボックス

デモ

var Suggest = function(input, suggest, dic) {
    this.idInput = document.getElementById(input);
    this.idSuggest = document.getElementById(suggest);
    this.dic   =  dic
    this.idx = -1;
    this.serchWord = "";
    this.init();
};

Suggest.Key = {
    UP: 38,
    DOWN: 40,
    RETURN: 13,
    TAB: 9,
    ESC: 27
};

Suggest.forEach = function(array, func, obj) {
    for (var i = 0; i < array.length; i++) 
	func.call(obj, array[i], i, array);
};

Suggest.addEvent = function(obj, type, func) {
    obj.addEventListener ?
	obj.addEventListener(type, func, false) :
	obj.attachEvent("on" + type, func);
};

Suggest.escapeHTML = function(value) {
    return v.replace(/&/g, "&amp;").replace(/</g,"&lt;").replace(/>/g, "&gt;");
};

Suggest.prototype.select = function() {
    var i = this.idx;
    if (i != -1) {
	var area = this.idSuggest.firstChild;
	this.idInput.value = area.childNodes[i].innerHTML;
    }
    this.removeView();				      
};

Suggest.prototype.keyEvent = function(e) {
    var idx = this.idx;
    
    switch (e.keyCode) {
    case Suggest.Key.TAB: case Suggest.Key.ESC:
	this.removeView();				      
	break;
	
    case Suggest.Key.RETURN:
	this.select();
	break;
	
    case Suggest.Key.UP:
	if (this.idSuggest.firstChild == null) return;
	var list = this.idSuggest.firstChild.childNodes;
	if (idx == -1) {
	    list[list.length - 1].className = "select";
	    this.idInput.value = list[list.length -1].innerHTML;
	    this.idx = list.length - 1;
	} else if (idx - 1 >= 0) {
	    if (idx >= 0) list[idx].className = "over";
	    list[idx - 1].className = "select";
	    this.idInput.value = list[idx -1].innerHTML;
	    this.idx -= 1;					      
	} else {
	    list[idx].className = "over";
	    this.idx = -1;
	    this.idInput.value = this.serchWord;
	}
	break;
	
    case Suggest.Key.DOWN:
	if (this.idSuggest.firstChild == null) return;
	var list = this.idSuggest.firstChild.childNodes;
	if (idx + 1 < list.length) {
	    if (idx != -1) list[idx].className = "over";
	    list[idx + 1].className = "select";
	    this.idInput.value = list[idx + 1].innerHTML;
	    this.idx += 1;
	} else {
	    list[idx].className = "over";
	    this.idInput.value = this.serchWord;
	    this.idx = -1;
	}
	break;
	
    default:
	setTimeout(this.bind(this.serch), 500);
	break;
    }
};

Suggest.prototype.init = function() {
    Suggest.addEvent(this.idInput, "blur", this.bind(this.select));
    Suggest.addEvent(this.idInput, "keydown", this.bindEvent(this.keyEvent));
};

Suggest.prototype.serch = function() {
    var result = [];
    var value = this.idInput.value;
    
    this.removeView();
    if (value == "") return;
    this.serchWord = value;
    value = Suggest.escapeHTML(value);
    Suggest.forEach(this.dic, function (word) {
	if (word.match(value)) result.push(word);
    }, this);
    if (result.length == 0) return;
    this.createView(result);
};

Suggest.prototype.removeView = function() {
    if (this.idSuggest.firstChild != null) {
	this.idSuggest.removeChild(this.idSuggest.firstChild);
	this.idx = -1;
    }
};

Suggest.prototype.changeSelect = function(i) {
    var list = this.idSuggest.firstChild.childNodes;
    if (this.idx != -1) list[this.idx].className = "over";
    list[i].className = "select";
    this.idx = i;
};

Suggest.prototype.createView = function(result) {
    var area = document.createElement("div");
    area.id = "suggest_area";
    Suggest.forEach(result, function(r, i) {
	var div = document.createElement("div");
	Suggest.addEvent(div, "mouseover", this.bind(this.changeSelect, i));
	div.innerHTML = r;
	div.className = "over";
	area.appendChild(div);
    }, this);
    this.idSuggest.appendChild(area);
};

Suggest.prototype.bind = function(func) {
    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);
    return function() {
	func.apply(self, args);
    };
};

Suggest.prototype.bindEvent = function(func) {
    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);
    return function(e) {
	func.apply(self, [e].concat(args));
    };
};