/*
 * Key class
 * =========
 * Author: Barak Ulmann
 * Description: create a cross-browser designed combo box
 * Compatibility: IE5+ & NS6+ compatible
 * based on the DynLayer technology. learn more about the DynDuo and cross-browser dhtml in:
 * http://www.dansteinman.com/dynduo/
 * Last Updated: Januar 11th 2004
 *
 * Notes:
 * 1. to create the ComoBox object a select object must be first created.
 * 2. the select object is not displayed and behaves same as a hidden input field.
 * 3. the select object's onchange attribute, if specified, is evaluated in similar behaviour
 * 4. object supports a new propert that sets the number of visible options in the options
 *    div (when opened). the property's name is 'opensize' and should be added to the
 *    select object either by specifiying it in the select html tag (IE), or by specifying
 *    it as a property of the select object (IE or NS)
 * 5. style parameters must be added to the select object, either by specifying a 'params' 
 * 	  attribute in the select html tag (IE) or by specifying a 'params' property 
 *    of the select object (IE or NS). the 'params' property is a comma separeted string 
 *    in the format of: attribute=value,attribute=value (spaces allowed). some style
 *    attributes are mandatory. the following attributes are used:
 *		options-width, options-height, shown-option-width - in pixels. defines the width and height
 *			of each option in the options table and the width of the shown option
 *		button, button-off, button-on, button-pushed, button-disabled - button images urls
 *			either 'button' or 'button-off' are mandatory and taken as default if other are 
 *			not specified
 *		font, font-off, font-on, font-selected, font-disabled - a font css string
 *			either 'font' or 'font-off' are mandatory and taken as default if other are 
 *			not specified
 *		fgcolor, fgcolor-off, fgcolor-on, fgcolor-selected, fgcolor-disabled - a color css string
 *			either 'fgcolor' or 'fgcolor-off' are mandatory and taken as default if other are 
 *			not specified
 *		bgcolor, bgcolor-off, bgcolor-on, bgcolor-selected, bgcolor-disabled - a background-color css string
 *			either 'bgcolor' or 'bgcolor-off' are mandatory and taken as default if other are 
 *			not specified
 *		outer-border-style - a border css string for the outer table
 *		options-border-style - a border css string for the options layer
 *		options-padding-style - a padding css string.
 *		options-scroll-style - a scrollbar css string for the options div
 */

// static variables
// ================

ComboBox.Count = 0
ComboBox.DEBUG = false

// constructor
function ComboBox(
	selectObj 	// a select object (the object is not displyed)
	,inline		// an optional boolean indicating whether object is to be
				// built and written automatically after constructed 
	)
{
	this.name = 'ComboBox' + ComboBox.Count++
	this.obj = this.name + 'Object'
	eval(this.obj + '=this')
	this.errcode = 0
	this.errmsg = 'No Error'
	
	if (!selectObj) {
		this.errcode = 1
		this.errmsg = 'NULL Argument - selectObj'
		return
	}
	this.selectObj = selectObj
	if (!ComboBox.DEBUG) this.selectObj.style.display = 'none'
	this.selectObj.lastSelectedIndex = this.selectObj.selectedIndex
	
	this.size = 1//this.selectObj.size
	//this.size = !this.size || isNaN(this.size)? 1: parseInt(this.size)
	this.opensize = this.selectObj.opensize
	this.opensize = !this.opensize || isNaN(this.opensize)? Math.min(10,this.selectObj.options.length): parseInt(this.opensize)
	
	// parse params string
	this.params = new Array()
	var tmp = this.selectObj.params.split(/\s*,\s*/)
	var attr, val
	for (var i = 0; i < tmp.length; i++) {
		if (/^.+\s*=\s*.+$/.test(tmp[i])) {
			attr = tmp[i].split(/\s*=\s*/)[0]
			val = tmp[i].split(/\s*=\s*/)[1]
			if (val.length && !isNaN(val)) val = parseInt(val)
			this.params[attr] = val
		}
	}
	
	// create style objects
	this.font = this._prCreateStyleObject('font')
	this.fgcolor = this._prCreateStyleObject('fgcolor')
	this.bgcolor = this._prCreateStyleObject('bgcolor')
	
	// create button objects and preload button images
	this.tmp = this._prCreateStyleObject('button')
	if (!this.tmp) return
	this.button = new Object()
	this.button.off = new Image()
	this.button.on = new Image()
	this.button.psh = new Image()
	this.button.dis = new Image()
	this.button.off.src = this.tmp.off
	this.button.on.src = this.tmp.on
	this.button.psh.src = this.tmp.psh
	this.button.dis.src = this.tmp.dis
	delete this.tmp
	
	// get options width and height
	this.shownOptionW = this.params['shown-option-width']
	this.optionsH = this.params['options-height']
	this.optionsW = this.params['options-width']
	this.outerBorderWidth = this._prParseBorderWidth(this.params['options-border-style'])
	this.shownOptionW = !this.shownOptionW || isNaN(this.shownOptionW)? 0: parseInt(this.shownOptionW)
	this.optionsW = !this.optionsW || isNaN(this.optionsW)? 0: parseInt(this.optionsW)
	this.optionsH = !this.optionsH || isNaN(this.optionsH)? 18: parseInt(this.optionsH)
	
	this.css = ''
	this.html = ''
	this.lyr = null
	this.built = false
	this.active = false
	this.opened = false
	this.focused = false
	
	if (inline) {
		this.build()
		this.write()
	}
}

// public methods
// ==============

// build component's html
function ComboBoxBuild()
{
	if (this.errcode != 0 || this.built)
		return
	
	this.css = '<style type="text/css">\n' + 
		'#' + this.name + 'Div {position:absolute;top:0;left:0;visibility:hidden;width:100%;height:' + (this.optionsH * this.opensize + this.outerBorderWidth.top + this.outerBorderWidth.bottom) + ';background-color:' + this.bgcolor.off + ';z-index:1;overflow-y:auto;' +  this.params['options-border-style'] + ';' + this.params['options-scroll-style'] + ';}\n' + 
		'.' + this.name + 'OptionOff {font:' + this.font.off + ';color:' + this.fgcolor.off + ';background-color:' + this.bgcolor.off + ';}\n' + 
		'.' + this.name + 'OptionOn {font:' + this.font.on + ';color:' + this.fgcolor.on + ';background-color:' + this.bgcolor.on + ';}\n' + 
		'.' + this.name + 'OptionSel {font:' + this.font.sel + ';color:' + this.fgcolor.sel + ';background-color:' + this.bgcolor.sel + ';}\n' + 
		'.' + this.name + 'OptionDis {font:' + this.font.dis + ';color:' + this.fgcolor.dis + ';background-color:' + this.bgcolor.dis + ';}\n' + 
		'.' + this.name + 'OuterTable {' +  this.params['outer-border-style'] + ';}\n' + 
		'</style>'
	
	var i, optionClass
	var div = '' + 
		'<div id="' + this.name + 'Div">\n' + 
		'	<table border="0" cellpadding="0" cellspacing="0" width="100%">\n'
	for (i = 0; i < this.selectObj.options.length; i++) {
		optionClass = this.name + 'Option' + (this.selectObj.disabled? 'Dis': this.selectObj.options[i].selected? 'Sel': 'Off')
		div += '' +
		'		<tr>\n' + 
		'			<td id="' + this.name + 'Option' + i + '" height="' + this.optionsH + '" width="100%" ' + 
					'class="' + optionClass + '" classOff="' + optionClass + '" style="' +  this.params['options-padding-style'] + '"' + 
					'onmouseover="this.className = \'' + this.name + 'OptionOn\'" onmouseout="this.className = this.classOff" ' + 
					'onclick="event.cancelBubble = true;' + this.obj + '.select(' + i + ')" !onfocus="' + this.obj + '.select(' + i + ')">' + 
						this.selectObj.options[i].text + 
					'</td>\n' + 
		'		</tr>\n'
		if (browser.ns)
			div += '<script language="JavaScript">ComboBox.getElementById(\'' + this.name + 'Option' + i + '\').classOff = \'' + optionClass + '\'</script>'
	}
	div += '' + 
		'	</table>\n' + 
		'</div>'
	
	var buttonImg = this.selectObj.disabled? '<img src="' + this.button.sel.src + '">': '<img src="' + this.button.off.src + '" onmouseover="this.src = \'' + this.button.on.src + '\'" onmouseout="this.src = \'' + this.button.off.src + '\'" onclick="event.cancelBubble = true;this.src = \'' + this.button.psh.src + '\';' + this.obj + '.onImgClick()">'
	this.html = '' + 
		'<table border="0" cellpadding="0" cellspacing="0" class="' + this.name + 'OuterTable">\n' + 
		'	<tr>\n' + 
		'		<td id="' + this.name + 'Option" width="' + this.shownOptionW + '"  height="' + this.optionsH + '" class="' + this.name + 'OptionOff" style="' +  this.params['options-padding-style'] + '" onclick="event.cancelBubble = true;' + this.obj + '.open()">' + 
					this.selectObj.options[this.selectObj.selectedIndex].text + 
				'</td>\n' + 
		'		<td valign="top">' + buttonImg + '</td>\n' + 
		'	</tr>\n' +
		'</table>\n' + 
		'<div style="position:relative;top:1;width:' + this.optionsW + '">\n' + div + '\n</div>'
	
	this.built = true
	
	// debug lines
	if (ComboBox.DEBUG) {
		var win = window.open('','DEBUG')
		win.document.open()
		win.document.write(this.css + '\n' + this.html)
		win.document.close()
	}
}
ComboBox.prototype.build = ComboBoxBuild

// write component's html
function ComboBoxWrite(i)
{
	if (!this.built || this.active) return
	document.write(this.css + '\n' + this.html)
	this.lyr = new Layer(this.name + 'Div')
	if (browser.ns) this.lyr.css.overflow = 'auto'
	this.selectedTdObj = ComboBox.getElementById(this.name + 'Option' + this.selectObj.selectedIndex)
	document.onclick = eventAppend(document.onclick,this.obj + '.close();' + this.obj + '.focused = false')
	document.onkeypress = eventAppend(document.onclick,this.obj + '.onkeypress(e)','e')
	document.onkeydown = eventAppend(document.onclick,this.obj + '.onkeydown(e)','e')
	// NOTE: document.body.onkeypress & document.body.onclick doesn't work in ns
	this.active = true
}
ComboBox.prototype.write = ComboBoxWrite

// implement an option selection
function ComboBoxSelect(i,closeOnSelect)
{
	if (!this.active) return
	if (i < 0 || i > this.selectObj.options.length) return
	
	if (this.selectObj.selectedIndex != i) {
		this.selectObj.lastSelectedIndex = this.selectObj.selectedIndex
		this.selectObj.selectedIndex = i
		
		this.selectedTdObj.className = this.selectedTdObj.classOff = this.name + 'OptionOff'
		this.selectedTdObj = ComboBox.getElementById(this.name + 'Option' + i)
		this.selectedTdObj.className = this.selectedTdObj.classOff = this.name + 'OptionSel'
		
		var shownOption = ComboBox.getElementById(this.name + 'Option')
		shownOption.innerHTML = this.selectedTdObj.innerHTML
	}
	
	this.focused = true
	
	closeOnSelect = (closeOnSelect != false) // true by default
	if (closeOnSelect) { // close immidiately
		this.close()
		if (this.selectObj.onchange)
			this.selectObj.onchange()
	}
	else { // stay opened
		if (this.selectedTdObj.scrollIntoView) // ie5 or ns7
			this.selectedTdObj.scrollIntoView()
	}
}
ComboBox.prototype.select = ComboBoxSelect

// open options div
function ComboBoxOpen()
{
	if (!this.active || this.opened) return
	this.lyr.show()
	if (this.selectedTdObj.scrollIntoView) // ie5 or ns7
		this.selectedTdObj.scrollIntoView()
	this.opened = true
	this.focused = true
}
ComboBox.prototype.open = ComboBoxOpen

// close options div
function ComboBoxClose()
{
	if (!this.active || !this.opened) return
	this.lyr.hide()
	this.opened = false
}
ComboBox.prototype.close = ComboBoxClose

// jump to first option begining with the pressed key
function ComboBoxOnKeyPress(e)
{
	if (!this.active || !this.focused) return
	var key = new Key(e)
	// search first option that begins with same key
	for (var i = 0; i < this.selectObj.options.length; i++) {
		if (this.selectObj.options[i].text.charCodeAt(0) == key.value) {
			this.select(i)
			break
		}
	}
}
ComboBox.prototype.onkeypress = ComboBoxOnKeyPress

// catch up and down arrows key press
function ComboBoxOnKeyDown(e)
{
	if (!this.active || !this.focused) return
	var key = new Key(e)
	if (key.value == 38) // up arrow
		this.scroll(-1) // scroll one up
	else if (key.value == 40) // down arrow
		this.scroll(1) // scroll one down
}
ComboBox.prototype.onkeydown = ComboBoxOnKeyDown

// scroll options
function ComboBoxScroll(by)
{
	if (!this.active || !this.focused) return
	if (typeof(by) != 'number' || this.selectObj.options.length == 0) return
	var selectedIndex = this.selectObj.selectedIndex
	var targetIndex = by < 0? Math.max(selectedIndex + by,0): Math.min(selectedIndex + by,this.selectObj.options.length - 1)
	this.select(targetIndex,!this.opened)
}
ComboBox.prototype.scroll = ComboBoxScroll

// open/close options div when image clicked
function ComboBoxOnImgClick()
{
	if (!this.active) return
	this.opened? this.close(): this.open()
}
ComboBox.prototype.onImgClick = ComboBoxOnImgClick
// private methods
// ===============

function ComboBoxCreateStyleObject(prefix)
{
	var property = new Object()
	property.dflt = this.params[prefix + '-off']? this.params[prefix + '-off']: this.params[prefix]
	if (!property.dflt) {
		this.errcode = 2
		this.errmsg = prefix + ' parameter not specified'
		delete property
		return
	}
	property.off = this.params[prefix + '-off']? this.params[prefix + '-off']: property.dflt
	property.on = this.params[prefix + '-on']? this.params[prefix + '-on']: property.dflt
	property.dis = this.params[prefix + '-disabled']? this.params[prefix + '-disabled']: property.dflt
	if (prefix == 'button')
		property.psh = this.params[prefix + '-pushed']? this.params[prefix + '-pushed']: property.dflt
	else
		property.sel = this.params[prefix + '-selected']? this.params[prefix + '-selected']: property.dflt
	
	return property
}
ComboBox.prototype._prCreateStyleObject = ComboBoxCreateStyleObject

function ComboBoxParseBorderWidth(borderCssString)
{
	var w
	var borderWidth = new Object()
	borderWidth.top = 0
	borderWidth.bottom = 0
	if (borderCssString.match(/^\s*border:/)) {
		borderCssString = borderCssString.replace(/^(\s*border:[^;\d]*)(\d)(.*)$/,'$2$3')
		w = parseInt(borderCssString)
		if (isNaN(w)) w = 0
		borderWidth.top = borderWidth.bottom = w
	}
	else {
		if (borderCssString.match(/^\s*border-top:/)) {
			borderCssString = borderCssString.replace(/^(\s*border-top:[^;\d]*)(\d)(.*)$/,'$2$3')
			w = parseInt(borderCssString)
			if (isNaN(w)) w = 0
			borderWidth.top = w
		}
		if (borderCssString.match(/^\s*border-bottom:/)) {
			borderCssString = borderCssString.replace(/^(\s*border-bottom:[^;\d]*)(\d)(.*)$/,'$2$3')
			w = parseInt(borderCssString)
			if (isNaN(w)) w = 0
			borderWidth.bottom = w
		}
	}
	
	return borderWidth
}
ComboBox.prototype._prParseBorderWidth = ComboBoxParseBorderWidth

// global functions
// ================

function ComboBoxGetElementById(id)
{
	if (document.all)
		return document.all(id)
	else if (document.getElementById)
		return document.getElementById(id)
	else
		return eval('document.' + id)
}
ComboBox.getElementById = ComboBoxGetElementById
