/**
 *
 *  $Id: pjautocomplete.js,v 1.14 2008/04/17 16:58:51 danielg Exp $
 *
 *
 * @author Daniel G.
 * @module Widgets
 * Copyright 2006, 2007 Nethrom Software
 */

if (typeof Projapi == "undefined") Projapi = {};
if (!Projapi.Widgets) Projapi.Widgets = {};


//
//
// ------------------------------------- PJAutoComplete
//
//

/**
 *  PJAutoComplete
 *
 *  @class
 *  @extends Projapi.Base.iPJVisualObject
 *  @constructor
 */
Projapi.Widgets.PJAutoComplete = function(_objectName, _objectProperties) {
	Projapi.Widgets.PJAutoComplete.superclass.constructor.call(this, _objectName, _objectProperties);
	
	/**
	 * Container class prefix string.
	 * @private
	 * @type String
	 */
	this._DOMClassPrefix = "pjAutoComplete";
	
	
	/** The type of this particular SmartObject
	 * @private
	 * @type String
	 */
	this._objectType = "AutoComplete";
	
	this._staticMessageBundle = {
		no_match: '(no match)'
	}
	
	this._lastText = null;
	this._firstText = null;
	this.__interval = null;		// ugly but works better than alternatives
	this.__hideTimeout = null;
	
	this._canFocus = true;
	this._hasFocus = false;
	
	this._containerSource = null;
	this.ContainerSourceEvents = Projapi.Links.Container.SourceEvents.without(Projapi.Links.Container.ViewObject);
	this.ContainerTargetEvents = Projapi.Links.Container.TargetEvents;
	
	this._processObjectProperties();
	if (typeof this._properties.minChars != "number")
		this._properties.minChars = 1;
}

Projapi.extend(Projapi.Widgets.PJAutoComplete, Projapi.Base.iPJVisualObject);

Projapi.Widgets.PJAutoComplete.prototype.createObjects = function() {
	if (this._objectCreated) return;
	Projapi.Widgets.PJAutoComplete.superclass.createObjects.call(this);
	
	this._buildUI();
	this._initHandlers();
	
	this._objectCreated = true;
}

Projapi.Widgets.PJAutoComplete.prototype.initializeObject = function() {
	if (this._objectInitialized) return;
	if (!Projapi.Request) Projapi.Request = Ajax.Request;
	if (!this._objectCreated)
		this.createObjects();
	this._objectInitialized = true;
	
	if (!this._properties.values)
		if (this._properties.valueOptions) {
			
		} else if (this._properties.valuesDO) {
			var name, val, dO, i;
			dO = Projapi.Register.get(this._properties.valuesDO);
			val = dO.collectColumn(this._properties.valuesDOValueColumn);
			if (this._properties.valuesDONameColum)
				name = dO.collectColumn(this._properties.valuesDONameColumn);
			else
				name = val;
			this._properties.values = [];
			for (i = 0; i < val.length; i++)
				this._properties.values.push({label: name[i], value: val[i]});
		}
	Projapi.Widgets.PJAutoComplete.superclass.initializeObject.call(this);
	
	this._bindHandlers();
	this.hideObject();
}

Projapi.Widgets.PJAutoComplete.prototype._buildUI = function() {
	this._domNode = Projapi.UI.Element.fromString('<div id="' + this._objectName + '" class="' + this._DOMClassPrefix + '" style="display: none;"></div>');
}

Projapi.Widgets.PJAutoComplete.prototype._initHandlers = function() {
	this._activate = this.__activate.bind(this);
	this._deactivate = this.__deactivate.bind(this);
	this._updateChoices = this.__updateChoices.bind(this);
	this._hostKeyDown = this.__hostKeyDown.bind(this);
	this._actions = {};
	this._actions.keydown = Projapi.Widgets.PJAutoComplete._actions.keydown.bindAsEventListener(this);
	this._actions.blur = Projapi.Widgets.PJAutoComplete._actions.blur.bindAsEventListener(this);
	this._actions.feed = Projapi.Widgets.PJAutoComplete._actions.feed.bind(this);
	this._domNode.domNode.feed = this._actions.feed;
	Event.observe(this._domNode.domNode, "keydown", this._actions.keydown, false);
	Event.observe(this._domNode.domNode, "blur", this._actions.blur, false);
}

Projapi.Widgets.PJAutoComplete.prototype._bindHandlers = function() {
	this._unbindHandlers();
	if (!this._containerSource)
		return;
	if (!this._objectInitialized)
		return;
	
	var vec = this._containerSource.getDOMNode().domNode.getElementsByTagName("input");
	if (vec.length == 0) return;
	this.__linkedObject = vec[0];
	this.__linkedObject.parentNode.appendChild(this.getDOMNode().domNode);
	Event.observe(this.__linkedObject, "focus", this._activate, false);
	Event.observe(this.__linkedObject, "blur", this._deactivate, false);
	Event.observe(this.__linkedObject, "keydown", this._hostKeyDown, false);
}

Projapi.Widgets.PJAutoComplete.prototype._unbindHandlers = function() {
	if (this.__linkedObject) {
		Event.stopObserving(this.__linkedObject, "focus", this._activate, false);
		Event.stopObserving(this.__linkedObject, "blur", this._deactivate, false);
		this.hideObject();
		this.__linkedObject = null;
	}
	this._lastText = null;
}

Projapi.Widgets.PJAutoComplete.prototype.__activate = function() {
	if (!this._objectEnabled)
		return;
	this._hasFocus = false;
	this.__interval = setInterval(this._updateChoices, 500);
	this._firstText = this.__linkedObject.value;
	if (this.__hideTimeout) {
		clearTimeout(this.__hideTimeout);
		this.__hideTimeout = null;
	}
}

Projapi.Widgets.PJAutoComplete.prototype.__deactivate = function() {
	if (this._hasFocus) return;
	if (this.__interval)
		clearInterval(this.__interval);
	var oThis = this;
	this.__hideTimeout = setTimeout(function() { oThis.hideObject(); }, 50);	// 50ms so we can catch the click if the user clicked an item in the list
	//this._lastText = null;
}

Projapi.Widgets.PJAutoComplete.prototype.__hostKeyDown = function(event) {
	var key = event.keyCode || event.charCode;
	switch (key) {
		case 13: /////	ENTER
			break;
		case 27: /////	ESC
			break;
		case 38: /////	UP
			if (this._lastChoices && this._lastChoices.length > 0) {
				this._hasFocus = true;
				this._domNode.domNode.focus();
				var el = this._domNode.domNode.lastChild;
				el.className = "selected";
				if (el.offsetTop + el.offsetHeight > this._domNode.domNode.scrollTop + this._domNode.domNode.clientHeight)
					this._domNode.domNode.scrollTop = el.offsetTop + el.offsetHeight - this._domNode.domNode.clientHeight;
				Event.stop(event);
				return false;
			}
			break;
		case 40: /////	DOWN
			if (this._lastChoices && this._lastChoices.length > 0) {
				this._hasFocus = true;
				this._domNode.domNode.focus();
				this._domNode.domNode.firstChild.className = "selected";
				this._domNode.domNode.scrollTop = 0;
				Event.stop(event);
				return false;
			}
			break;
		default:
			if (String.fromCharCode(key).match(/[-a-zA-Z0-9.,+*/()[]= ]/))
				this._domNode.domNode.className += ' old';
			break;
	}
}

/*
**	Data retrieval/manip
*/

Projapi.Widgets.PJAutoComplete.prototype.__updateChoices = function(event) {
	var text = this.__linkedObject.value;
	if (text == this._lastText) {
		this.viewObject(true);
		return;
	}
	this._lastText = text;
	if (text.length < this._properties.minChars) {
		this.hideObject(true);
		return;
	}
	this.viewObject();
	if (this._properties.url) {
		this._domNode.domNode.style.innerHTML = "Loading...";
		new Projapi.Request(this._properties.url, {method: "post", asynchronous: true, domain: this,
			postBody: "column=" + encodeURIComponent(this._properties.doColumnName || this._objectName) + "&text=" + text, onComplete: (function(request) {
				if (!request || request.status != 200) return;
				
				this._setChoices(request.responseText.parseJSON().values);
			}).bind(this)});
	} else if (this._properties.values)
		this._setChoices(this._properties.values);
}

Projapi.Widgets.PJAutoComplete.prototype._setChoices = function(choicesA) {
	var i, node = this._domNode.domNode, str = [], choices, metaVal = this.__linkedObject.value;
	if (this._properties.clientFilter) {
		choices = [];
		for (i = 0; i < choicesA.length; i++)
			if (choicesA[i].value.indexOf(metaVal) == 0 ||
				(this._properties.caseSensitive && choicesA[i].value.toUpperCase().indexOf(metaVal.toUpperCase()) == 0))
				choices.push(choicesA[i]);
	} else
		choices = choicesA;
	if (choices.length == 0) {
		str.push("<div class=\"noMatch\">");
		str.push(this.getMessage("no_match"));
		str.push("</div>");
	} else for (i = 0; i < choices.length; i++) {
		str.push("<div onmouseover='this.lastClass = this.className; this.className=\"hover\";' onmouseout='this.className=this.lastClass || \"\";' onclick='this.parentNode.feed(\"");
		str.push(choices[i].value.replace(/\\/, "\\\\").replace(/"/, "\\\"").replace(/'/, "\\\'"));
		str.push("\");'>");
		if (choices[i].image) {
			str.push("<img src='"); str.push(choices[i].image); str.push("'/>");
			str.push(" ");
		}
		str.push(choices[i].label || choices[i].value);
		str.push("</div>");
	}
	if (node.className.match(/\bold\b/))
		node.className = node.className.replace(' old', '');
	node.innerHTML = str.join("");
	this._lastChoices = choices;
}

/*
**	Key handling
*/

Projapi.Widgets.PJAutoComplete._actions = {};

Projapi.Widgets.PJAutoComplete._actions.keydown = function(event) {
	var key = event.keyCode || event.charCode;
	var dv = this._domNode.domNode.firstChild, id = 0, check = false;;
	while (dv && !dv.className.match(/\bselected\b/)) {
		id++; dv = dv.nextSibling;
	}
	if (!dv) return;	// safety net; should never execute
	switch (key) {
		case 13: /////	ENTER
			this._actions.feed(this._lastChoices[id].value);
			break;
		case 27: /////	ESC
			this._actions.feed(this._firstText);
			break;
		case 38: /////	UP
			dv.className = "";
			if (dv.previousSibling) {
				dv = dv.previousSibling; id--;
			} else {
				dv = dv.parentNode.lastChild; id = this._lastChoices.length-1;
			}
			dv.className = "selected";
			check = true;
			break;
		case 40: /////	DOWN
			dv.className = "";
			if (dv.nextSibling) {
				dv = dv.nextSibling; id++;
			} else {
				dv = dv.parentNode.firstChild; id = 0;
			}
			dv.className = "selected";
			check = true;
			break;
		default: break;
	}
	if (check) {
		if (dv.offsetTop + dv.offsetHeight > this._domNode.domNode.scrollTop + this._domNode.domNode.clientHeight)
			this._domNode.domNode.scrollTop = dv.offsetTop + dv.offsetHeight - this._domNode.domNode.clientHeight;
		if (dv.offsetTop < this._domNode.domNode.scrollTop)
			this._domNode.domNode.scrollTop = dv.offsetTop;
	}
	Event.stop(event);
	return false;
};

Projapi.Widgets.PJAutoComplete._actions.blur = function(event) {
	this.hideObject();
};

Projapi.Widgets.PJAutoComplete._actions.feed = function(value) {
	//this._containerSource.setObjectValue(value);
	this._containerSource.valueChanged(this, value);
	this.hideObject();
}

/*
** URL
*/

Projapi.Widgets.PJAutoComplete.prototype.getURL = function() {
	return this._properties.url;
}

Projapi.Widgets.PJAutoComplete.prototype.setURL = function(url) {
	this._properties.url = url;
}

/*
**	Container source
*/

Projapi.Widgets.PJAutoComplete.prototype.setContainerSource = function(newContainerSource) {
	if (newContainerSource == this._containerSource) return;
	this._containerSource = newContainerSource;
	this._bindHandlers();
}

Projapi.Widgets.PJAutoComplete.prototype.removeContainerSource = function() {
	this._containerSource = null;
	this._unbindHandlers();
}

/*
**	Usual publish-subscribe events
*/

Projapi.Widgets.PJAutoComplete.prototype.viewObject = function(skipRepos) {
	if (!this._objectHidden) return;
	if (!skipRepos) {
		var rect = Position.positionedOffset(this.__linkedObject);
		rect.push(this.__linkedObject.offsetWidth);
		rect.push(this.__linkedObject.offsetHeight);
	}
	Projapi.Widgets.PJAutoComplete.superclass.viewObject.call(this);
	if (!skipRepos) {
		this._domNode.domNode.style.left = rect[0] + "px";
		this._domNode.domNode.style.top  = rect[1] + rect[3] -1 + "px";
	}
}

Projapi.Widgets.PJAutoComplete.prototype.hideObject = function(leaveInterval) {
	if (this.__hideTimeout) {
		clearTimeout(this.__hideTimeout);
		this.__hideTimeout = null;
	}
	if (!leaveInterval && this.__interval) {
		clearInterval(this.__interval);
		this.__interval = null;
	}
	if (!this._objectHidden) {
		this._objectHidden = true;
		this._domNode.domNode.style.display = 'none';
		Projapi.Util.Trigger(this, 'onHideObject');
	}
}

Projapi.Widgets.PJAutoComplete.prototype.disableObject = function() {
	this._objectEnabled = false;
	this.hideObject();
}

Projapi.Widgets.PJAutoComplete.prototype.enableObject = function() {
	this._objectEnabled = true;
}

Projapi.Widgets.PJAutoComplete.prototype.destroyObject = function() {
	if (this.__hideTimeout) {
		clearTimeout(this.__hideTimeout);
		this.__hideTimeout = null;
	}
	Projapi.Widgets.PJAutoComplete.superclass.destroyObject.call(this);
}
