/**
 * Kaizen Framework - JavaScript
 * 
 * @author Vasil Georgiev Dinkov - Kaizen Web-Productions (http://www.kaizen-web.com)
 * @version 1.0
 * @copyright Copyright(C), Kaizen Web-Productions, 2004-2008, All Rights Reserved.
 * @package KaizenFramework
 */

/**
 * DOM Functionality
 * Define general DOM helper functions.
 * 
 * @author Vasil Georgiev Dinkov - Kaizen Web-Productions (http://www.kaizen-web.com)
 * @version 1.0
 * @copyright Copyright(C), Kaizen Web-Productions, 2004-2008, All Rights Reserved.
 * @package KaizenFramework
 * @subpackage DOM
 */

Kaizen.DOM = {

	/**
	 * Creates a new element
	 *
	 * @param object	def	Definition e.g. {'tag':'table', 'class':'myClass', 'text':'Some text', 'style':'border:0;', 'anyAttribute':'value'}
	 */
	createElement: function(def) {
		var ns = (document.documentElement || "").namespaceURI;
		var element;
		element = ns ? document.createElementNS(ns, def['tag']) : document.createElement(def['tag']);
		for (var i in def) {
			if (i == 'tag')
				continue;
			if (i == 'class') {
				element.className = def['class'];
			} else if (i == 'text') {
				element.appendChild(document.createTextNode(def['text']));
			} else if (i == 'style') {
				element.style.cssText = def['style'];
			} else if (i == 'for') {
				element.htmlFor = def['for'];
			} else {
				if (typeof def[i] == 'function')
					Kaizen.DOM.IEDOMleaks.push([element, i]);
				element.setAttribute(i, def[i]);
				element[i] = def[i];
			}
		}
		return element;
	},

	// stores new elements that have registered closures (used to fix memory leaks in IE/Windows)
	IEDOMleaks: [],
	IEDOMEventleaks: [],

	/**
	 * Appends multiple child nodes
	 *
	 * @param element	element		The parent element
	 * @param element			child1
	 * @param element			child2
	 * ...					...
	 */
	appendChildren: function(element) {
		for (var i = 1, l = arguments.length; i < l; i++)
			element.appendChild(arguments[i]);
	},

	// Empty the contents of an element
	removeChildren: function(element, useInnerHTML) {
		if (useInnerHTML) {
			element.innerHTML = '';
		} else {
			var child;
			while (child = element.firstChild)
				element.removeChild(child);
		}
	},

	// Returns the element's position on the page - {x:position_x, y:position_y}
	getPosition: function(element) {
		var c = {x:element.offsetLeft, y:element.offsetTop};
		while (element = element.offsetParent) {
			c.x += element.offsetLeft;
			c.y += element.offsetTop;
		}
		return c
	},

	/**
	 * Returns an array with all elements that have a given CSS class
	 *
	 * @param string	className	The CSS class
	 * @param string	tag		(optional) The tag of the elements to lookup (used for speed optimization)
	 * @param element	scope		(optional) The parent element whose children to lookup (used for speed optimization)
	 */
	getElementsByClassName: function(className, tag, scope) {
		if (!tag)
			tag = '*';
		if (!scope)
			scope = document;
		var testClass, testA = [], resultA = [], cur, curClass;
		testClass = ' ' + className + ' ';
		testA = scope.getElementsByTagName(tag);
		for (var i = 0, l = testA.length; i < l; i++) {
			cur = testA[i];
			curClass = ' ' + cur.className + ' ';
			if (curClass.indexOf(testClass) > -1) // do not call Kaizen.DOM.hasClass for max speed
				resultA.push(cur);
		}
		return resultA;
	},

	// Checks whether an element currently has a CSS class
	hasClass: function(element, className) {
		return (' ' + element.className + ' ').indexOf(' ' + className + ' ') > -1;
	},

	// Adds a CSS class to an element
	addClass: function(element, className) {
		if (this.hasClass(element, className))
			return;
		var c = element.className;
		element.className = (c ? c + ' ' : '') + className;
	},

	// Removes a CSS class from an element
	removeClass: function(element, className) {
		if (!this.hasClass(element, className))
			return;
		element.className = (' ' + element.className).replace(' ' + className, '').substring(1);
	},

	// Add event handler
	addEvent: function(obj, type, func) {
		if (obj.addEventListener) {
			obj.addEventListener(type, func, false);
		} else if (obj.attachEvent) {
			obj['_' + type + func] = function(e){func.call(obj, e)};
			obj.attachEvent('on' + type, obj['_' + type + func]);
			Kaizen.DOM.IEDOMEventleaks.push([obj, type, func]);
		}
	},

	// Remove event handler
	removeEvent: function(obj, type, func) {
		if (obj.removeEventListener) {
			obj.removeEventListener(type, func, false);
		} else if (obj.attachEvent) {
			obj.detachEvent('on' + type, obj['_' + type + func]);
			obj['_' + type + func] = null;
		}
	},

	// Kill event (i.e. stop bubbling)
	killEvent: function(e) {
		if (!e)
			e = window.event;
		if (e.stopPropagation)
			e.stopPropagation();
		else
			e.cancelBubble = true;
	},

	// Prevent default event function
	eventPreventDefault: function(e) {
		if (!e)
			e = window.event;
		if (e.preventDefault)
			e.preventDefault();
		else
			e.returnValue = false;
	},

	// stores iframe shims
	IFRAMEshims: [],

	// get available iframe shim - if none available, create a new one and store it in this.IFRAMEshims
	getIFRAMEshim: function() {
		var cur = null;
		for (var i = 0, l = this.IFRAMEshims.length; i < l; i++) {
			cur = this.IFRAMEshims[i];
			if(!cur._beingUsed)
				return cur;
		}
		this.IFRAMEshims[l] = new Kaizen.Element('iframe', {'src': (Kaizen.Browser.IE ? 'javascript:0' : navigator.productSub < 20070309 ? 'data:' : document.location.protocol), 'tabindex':'-1', 'style':'position:absolute;border:0;filter:alpha(opacity=0);opacity:0;'});
		this.IFRAMEshims[l]._index = l;
		return this.IFRAMEshims[l];
	},

	// Adds an IFRAME shim for an element (tooltip) to allow it to overlap OS controls in IE/Win and Java applets in Firefox
	addIFRAMEshim: function(element) {
		if(!Kaizen.Browser.IE && !Kaizen.Browser.Gecko)
			return;
		var ifr = this.getIFRAMEshim();
		ifr._beingUsed = true;
		element._IFRAMEshimIndex = ifr._index;
		var ifrs = ifr.style, elms = element.style;
		ifrs.left = elms.left;
		ifrs.top = elms.top;
		ifrs.width = element.offsetWidth + 'px';
		ifrs.height = element.offsetHeight + 'px';
		element.parentNode.insertBefore(ifr, element);
	},

	// Removes the IFRAME shim for an element
	removeIFRAMEshim: function(element) {
		if(!Kaizen.Browser.IE && !Kaizen.Browser.Gecko)
			return;
		var ifr = this.IFRAMEshims[element._IFRAMEshimIndex];
		element.parentNode.removeChild(ifr);
		ifr._beingUsed = false;
	},

	// Hides/shows special elements in an element - e.g. useful when displaying an overlay layer above the element
	hideShowSpecialElements: function(element, showFlag) {
		if (!element)
			element = document;
		var tags = ['select'];
		var vis = showFlag ? 'inherit' : 'hidden';
		var elms;
		for (var i = 0, l = tags.length; i < l; i++) {
			elms = element.getElementsByTagName(tags[i]);
			for (var j = 0, m = elms.length; j < m; j++)
				elms[j].style.visibility = vis;
		}
	},

	// init link icons in IE6
	initLinkIconsIE6: function() {
		// if we don't have the separate CSS file with the link icons rules for IE6 linked, quit
		if (!document.getElementById('ie6-link-icons'))
			return;
		var links = document.links;
		var cur, ext;
		for (var i = 0, l = links.length; i < l; i++) {
			cur = links[i];
			if ((ext = cur.href.match(/\.([^\/\\#\?]+)$/)))
				this.addClass(cur, 'link-icons-' + ext[1]);
			else if (/(\s|^)external(\s|$)/.test(cur.rel))
				this.addClass(cur, 'link-icons-external');
		}
	}
};

/**
 * Class for creating new elements
 *
 * @param string	type	The tag of the new element - use 'text' for text
 * @param object/string	prop	Properties OR class OR text - e.g. {'text':'Some text', 'class':'myClass', 'style':'border:0;', 'anyAttribute':'value'} OR 'myClass' OR 'some text for a text node'
 */
Kaizen.Element = function(type, prop) {
	var ref = this;
	this.object = { };

	if (typeof prop == "object") {
		this.object = prop;
		this.object.tag = type;
	} else {
		this.object.tag = type;
		this.object['class'] = prop;
	}

	// create the element
	if(type == 'table') {
		this.element = this.createElement({'tag':'tbody'});
		if('cellpadding' in this.object) {
			this.object.cellPadding = this.object.cellpadding;
			delete this.object.cellpadding;
		}
		if('cellspacing' in this.object) {
			this.object.cellSpacing = this.object.cellspacing;
			delete this.object.cellspacing;
		}
	} else if (type == 'text') {
		this.element = document.createTextNode(prop);
		// return the text node to avoid exception in IE if we try to define any methods for it
		return this.element;
	} else {
		this.element = this.createElement(this.object);
	}

	// add methods
	this.element.append = Kaizen.DOM.appendChildren.applyAsMethod();
	if(type == 'table') {
		this.element.end = function() {
			var table = ref.createElement(ref.object);
			table.appendChild(this);
			return table;
		}
		Kaizen.DOM.IEDOMleaks.push([this.element, 'end']);
	}
	if(type == "select"){
		this.element.add = function(text, value, selected) { // for adding options
			var prop = {'tag':'option', 'text':text, 'value':value};
			if (selected)
				prop.selected = 'selected';
			this.appendChild(ref.createElement(prop));
		}
		Kaizen.DOM.IEDOMleaks.push([this.element, 'add']);
	}

	return this.element;
}

Kaizen.Element.prototype.createElement = Kaizen.DOM.createElement;

// fixing DOM memory leaks in IE
if(window.attachEvent && Kaizen.Browser.IE){
	window.attachEvent('onunload', function() {
		var arr = Kaizen.DOM.IEDOMleaks;
		var cur;
		var j;
		for (var i = 0, l = arr.length; i < l; i++) {
			cur = arr[i];
			j=1;
			while (cur[j])
				cur[0][cur[j++]] = null;
		}
		arr = Kaizen.DOM.IEDOMEventleaks;
		for (i = 0, l = arr.length; i < l; i++)
			Kaizen.DOM.removeEvent.apply(null, arr[i]);
	});
}

/**
 * Class for creating an accordion effect
 *
 * @param string	container	The id of the container DIV element
 * @param object	options		Any custom options to override the default options - e.g. {'height': 200}
 */
Kaizen.Accordion = function(container, options) {
	this.options = {
		onShow: null,					// function that will be executed when a content element starts to show
								// 'this' in the function will refer to the content element that is being shown
		onHide: null,					// function that will be executed when a content element starts to hide
								// 'this' in the function will refer to the content element that is being hidden
		height: 0,					// The height of the individual content elements - use 0 for auto height
		initialIndex: 0,				// The index of the tab to show initially - starts from 0
		animate: true,					// Use animation or display the new content immediately
		animationDuration: 100,				// Animation duration in milliseconds
		animationSteps: 30,				// Animation steps
		titleMasterClass: 'AccordionTitleMaster',	// CSS class to use for the title container DIV
		titleClass: 'AccordionTitle',			// CSS class to use for the title inner DIV
		titleOverClass: 'KaizenAccordionTitleOver',	// CSS class to use for the title bar onmouseover
		titleExtraClass: '',				// CSS class to use for the some titles to avoid border doubling
		contentClass: 'AccordionContent',		// CSS class to use for the content container DIV
		containerExtraClass: ''				// CSS class to use for when the last content area is active (last tab)
	};
	if (options)
		Kaizen.extendObject(this.options, options);

	this.container = document.getElementById(container);
	this.lastIndex = this.options.initialIndex;
	this.inProgress = false;
	this.containerExtra = false;
	this.titleElms = [];
	this.titleInnerElms = [];
	this.contentElms = [];
	var cur = this.container.firstChild, cur2;
	while (cur) {
		if (cur.nodeName.toUpperCase() == 'DIV' && cur.className) {
			if (Kaizen.DOM.hasClass(cur, this.options.titleMasterClass)) {
				this.titleElms[this.titleElms.length] = cur;

				cur2 = cur.firstChild;
				while (cur2) {
					if (cur2.nodeName.toUpperCase() == 'DIV' && cur2.className) {
						if (Kaizen.DOM.hasClass(cur2, this.options.titleClass)) 
							this.titleInnerElms[this.titleInnerElms.length] = cur2;
					}
					cur2 = cur2.nextSibling;
				}
			}
			else if (Kaizen.DOM.hasClass(cur, this.options.contentClass)) {
				this.contentElms[this.contentElms.length] = cur;
			}
		}
		cur = cur.nextSibling;
	}
	for (var i = 0, l = this.contentElms.length; i < l; i++) {
		cur = this.contentElms[i].style;
		if (this.options.height) {
			cur.overflow = 'hidden';
			cur.height = this.options.height + 'px';
		}
		cur.display = i != this.options.initialIndex ? 'none' : 'block';
		if (i > 0 && i != this.options.initialIndex + 1)
			this.titleSetExtraClass(i);
	}
	this.bindHandlers();
	this.contentTotal = this.contentElms.length - 1;
}

Kaizen.Accordion.prototype.bindHandlers = function() {
	var cur;
	for (var i = 0, l = this.titleElms.length; i < l; i++) {
		cur = this.titleElms[i];
		Kaizen.DOM.addEvent(cur, 'click', this.showContent.bind(this, i));
		Kaizen.DOM.addEvent(cur, 'mouseover', this.titleHover.bind(this, i));
		Kaizen.DOM.addEvent(cur, 'mouseout', this.titleHover.bind(this, i, true));
	}
}

Kaizen.Accordion.prototype.showContent = function(index) {
	if (this.lastIndex == index || this.inProgress) 
		return;
	//Check if its the last content area and the custom class is set, if change it
	if (index == this.contentTotal && this.options.containerExtraClass) 
		this.containerSetExtraClass();
	
	this.index = index;

	this.last = this.contentElms[this.lastIndex];
	this.cur = this.contentElms[this.index];

	if (this.options.onHide)
		this.options.onHide.apply(this.last);
	if (this.options.onShow)
		this.options.onShow.apply(this.cur);

	if (this.options.animate) {
		this.inProgress = true;

		// setup initial properties for last
		this.lastH = this.options.height || this.last.offsetHeight; // original height
		this.lastD = this.lastH / this.options.animationSteps; // difference to subtract on each step
		this.lastC = this.lastH; // current height
		this.last.style.overflow = 'hidden';

		// setup initial properties for current
		var curs = this.cur.style;
		if (!this.options.height) {
			curs.position = 'absolute';
			curs.visibility = 'hidden';
			curs.display = 'block';
		}
		this.curH = this.options.height || this.cur.offsetHeight;
		this.curD = this.curH / this.options.animationSteps;
		this.curC = 0;
		curs.overflow = 'hidden';
		curs.height = '1px';
		curs.position = 'static';
		curs.visibility = 'inherit';
		curs.display = 'block';
		this.titleSetExtraClass(this.index + 1, true);
		this.animate();
	} else {
		if (this.index != 0)
			this.titleSetExtraClass(this.index);
		this.titleSetExtraClass(this.index + 1, true);
		this.titleSetExtraClass(this.lastIndex + 1);
		this.last.style.display = 'none';
		this.cur.style.display = 'block';
		this.lastIndex = this.index;
	}
}

Kaizen.Accordion.prototype.animate = function() {
	this.lastC = parseInt(this.lastC - this.lastD);
	if (this.lastC <= 0) {
		this.last.style.display = 'none';
		if (this.options.height) {
			this.last.style.height = this.options.height + 'px';
		} else {
			this.last.style.height = 'auto';
			this.last.style.overflow = 'visible';
		}
		this.lastR = true; // last completed
		this.titleSetExtraClass(this.lastIndex + 1);
	} else {
		this.last.style.height = this.lastC + 'px';
	}

	this.curC = parseInt(this.curC + this.curD);
	if (this.curC >= this.curH) {
		this.cur.style.height = this.curH + 'px';
		if (!this.options.height)
			this.cur.style.overflow = 'visible';
		this.curR = true; // current completed
		if (this.index != 0)
			this.titleSetExtraClass(this.index);
	} else {
		this.cur.style.height = this.curC + 'px';
	}

	if (this.lastR && this.curR) { // both has completed so end
		//If it is not the last content area and the custom class was set, change it back
		if (this.containerExtra && this.index != this.contentTotal) 
			this.containerSetExtraClass(true);
		this.lastIndex = this.index;
		this.lastR = false;
		this.curR = false;
		this.inProgress = false;
		return;
	}
	setTimeout(this.animate.bind(this), this.options.animationDuration / this.options.animationSteps);
}

Kaizen.Accordion.prototype.titleHover = function(index, out) {
	var title = this.titleElms[index];
	if (!out)
		Kaizen.DOM.addClass(title, this.options.titleOverClass);
	else
		Kaizen.DOM.removeClass(title, this.options.titleOverClass);
}

Kaizen.Accordion.prototype.titleSetExtraClass = function(index, unset) {
	if (!this.options.titleExtraClass)
		return;
	var title = this.titleInnerElms[index];
	if (!title)
		return;
	if (!unset)
		Kaizen.DOM.addClass(title, this.options.titleExtraClass);
	else
		Kaizen.DOM.removeClass(title, this.options.titleExtraClass);
}

Kaizen.Accordion.prototype.containerSetExtraClass = function(unset) {
	if (!unset) {
		Kaizen.DOM.addClass(this.container, this.options.containerExtraClass);
		this.containerExtra = true;
	} else {
		Kaizen.DOM.removeClass(this.container, this.options.containerExtraClass);
		this.containerExtra = false;
	}
}

// Code for creating a popup
Kaizen.Popup = {

	// stores created elements for reuse
	elements: [],

	// if this.elements contains available return it, else create a new one and store it in this.elements
	getElement: function() {
		var cur = null;
		for(var i = 0, l = this.elements.length; i < l; i++){
			cur = this.elements[i];
			if (!cur._inUse)
				return cur;
		}
		this.elements[l] = new Kaizen.Element('div', {'style':'display:none;visibility:hidden;'});
		document.body.appendChild(this.elements[l]);
		return this.elements[l];
	},

	// Class for creating a popup
	Box: function(options) {
		this.options = {
			useOverlay: false,			// Use an overlay for this popup
			popupClass: 'KaizenPopup',		// CSS class for the popup element
			overlayClass: 'KaizenPopupOverlay'	// CSS class for the overlay element
		};
		if (options)
			Kaizen.extendObject(this.options, options);
	}

}

/**
 * Show a popup
 *
 * @param string/element	content		Content to display in the popup - can be element OR string (HTML)
 */
Kaizen.Popup.Box.prototype.show = function(content) {
	this.elm = Kaizen.Popup.getElement();
	this.elm._inUse = true;
	this.elm.className = this.options.popupClass;
	if (this.options.useOverlay) {
		this.overlay = Kaizen.Popup.getElement();
		this.overlay._inUse = true;
		this.overlay.className = this.options.overlayClass;
	}

	if (content) {
		if (typeof content == 'string')
			this.elm.innerHTML = content;
		else
			this.elm.appendChild(content);
	}
	this.elm.style.display = 'block';

	var viewportSize = Kaizen.Viewport.getSize();
	var scrollOffsets = Kaizen.Viewport.getScrollOffsets();
	var popupX = parseInt((viewportSize.w - this.elm.offsetWidth) / 2) + scrollOffsets.x;
	var popupY = parseInt((viewportSize.h - this.elm.offsetHeight) / 2) + scrollOffsets.y;
	if (popupY < scrollOffsets.y)
		popupY = scrollOffsets.y;
	var docW = viewportSize.w;
	var dB = document.body;
	var dE = document.documentElement;
	var docH = Math.max(viewportSize.h, dB.offsetHeight, dB.scrollHeight, dE.offsetHeight, dE.scrollHeight);

	var css;
	if (this.options.useOverlay) {
		Kaizen.DOM.hideShowSpecialElements(null);
		css = 'left:0;top:0;width:' + docW + 'px;height:' + docH + 'px;display:block;visibility:visible;';
		this.overlay.style.cssText = css;
	}
	css = 'left:' + popupX + 'px;top:' + popupY + 'px;display:block;';
	this.elm.style.cssText = css;
	if (!this.options.useOverlay)
		Kaizen.DOM.addIFRAMEshim(this.elm);
	this.elm.style.visibility = 'visible';

	// send focus to the close link/button or (if not found) to the first link/input in the popup
	try {
		Kaizen.DOM.getElementsByClassName('closePopup', 'a', this.elm)[0].focus();
	} catch (e) {
		try {
			Kaizen.DOM.getElementsByClassName('closePopup', 'input', this.elm)[0].focus();
		} catch (e) {
			try {
				this.elm.getElementsByTagName('a')[0].focus();
			} catch (e) {
				try {
					this.elm.getElementsByTagName('input')[0].focus();
				} catch (e) {};
			}
		}
	}
}

Kaizen.Popup.Box.prototype.hide = function() {
	this.elm.style.cssText = 'display:none;visibility:hidden;';
	if (!this.options.useOverlay)
		Kaizen.DOM.removeIFRAMEshim(this.elm);
	Kaizen.DOM.removeChildren(this.elm);
	this.elm._inUse = false;
	if (this.options.useOverlay) {
		this.overlay.style.cssText = 'display:none;visibility:hidden;';
		Kaizen.DOM.hideShowSpecialElements(null, true);
		Kaizen.DOM.removeChildren(this.overlay);
		this.overlay._inUse = false;
	}
}

// Code for creating an alert popup
Kaizen.Alert = {

	// stores created elements for reuse
	elements: [],

	// if this.elements contains available return it, else create a new one and store it in this.elements
	getElement: function() {
		var cur = null;
		for(var i = 0, l = this.elements.length; i < l; i++){
			cur = this.elements[i];
			if (!cur._inUse)
				return cur;
		}
		this.elements[l] = new Kaizen.Element('div');
		var textHolder = new Kaizen.Element('div');
		var buttonHolder = new Kaizen.Element('div');
		var button = new Kaizen.Element('input', {'type':'button', 'class':'closePopup'});
		buttonHolder.append(button);
		this.elements[l].append(textHolder, buttonHolder);
		return this.elements[l];
	},

	// Class for creating an alert popup
	Box: function(options) {
		this.options = {
			useOverlay: false,				// Use an overlay for this popup
			popupClass: 'KaizenAlert',			// CSS class for the alert holder DIV
			innerClass: 'KaizenAlertInner',			// CSS class for the alert inner DIV (first child of the holder DIV)
			textHolderClass: 'KaizenAlertTextHolder',	// CSS class for the alert text holder DIV
			buttonHolderClass: 'KaizenAlertButtonHolder',	// CSS class for the alert button holder DIV
			overlayClass: 'KaizenAlertOverlay'		// CSS class for the overlay element
		};
		if (options)
			Kaizen.extendObject(this.options, options);

		this.popup = new Kaizen.Popup.Box(this.options);
	}

}

/**
 * Show an alert popup
 *
 * @param string	text		Text to display inside the alert box 
 * @param string	buttonText	Text for the close button in the alert box 
 */
Kaizen.Alert.Box.prototype.show = function(text, buttonText) {
	var ref = this;

	this.elm = Kaizen.Alert.getElement();
	this.elm._inUse = true;
	this.elm.className = this.options.innerClass;
	this.elm.firstChild.className = this.options.textHolderClass;
	this.elm.firstChild.innerHTML = text;
	this.elm.lastChild.className = this.options.buttonHolderClass;
	Kaizen.DOM.addEvent(this.elm.lastChild.firstChild, 'click', function(){this.blur();ref.hide()});
	this.elm.lastChild.firstChild.value = buttonText;

	this.popup.show(this.elm);
}

Kaizen.Alert.Box.prototype.hide = function() {
	this.popup.hide();
	this.elm._inUse = false;
}

// Code for creating a progress indicator popup
Kaizen.Progress = {

	// stores created popups for reuse
	popups: [],

	// get available popup index from the popups array
	getPopupIndex: function(ref) {
		var cur = null;
		for (var i = 0, l = this.popups.length; i < l; i++){
			cur = this.popups[i];
			if (!cur.inUse)
				return i;
		}
		this.popups[l] = new Kaizen.Popup.Box(ref.options);
		return l;
	},

	// Class for creating a progress bar popup
	Box: function(options) {
		this.options = {
			useOverlay: true,				// Use an overlay for this popup
			popupClass: 'KaizenProgress',			// CSS class for the progress bar holder DIV
			overlayClass: 'KaizenProgressOverlay'		// CSS class for the overlay element
		};
		if (options)
			Kaizen.extendObject(this.options, options);
	}

}

/**
 * Shows a progress indicator popup
 *
 * @param string/null	id		ID of the element on the page for which to show the indicator OR null if we want to show it for the whole page
 * @param string	loadingText	Text to display in the progress bar (e.g. 'Loading...')
 * @param int		offsetX		(optional) X offset from the element's left edge (if showing the progress bar for an element and not the whole page)
 * @param int		offsetY		(optional) Y offset from the element's top edge
 */
Kaizen.Progress.Box.prototype.show = function(id, loadingText, offsetX, offsetY) {
	var index = Kaizen.Progress.getPopupIndex(this);
	var popup = Kaizen.Progress.popups[index];
	popup.inUse = true;

	var elm;
	if (!id)
		elm = document.body;
	else
		elm = document.getElementById(id);
	elm._progressPopupIndex = index;

	if (!id) {
		popup.show(loadingText);
	} else {
		popup.elm = Kaizen.Popup.getElement();
		popup.elm._inUse = true;
		popup.elm.className = this.options.popupClass;
		if (this.options.useOverlay) {
			popup.overlay = Kaizen.Popup.getElement();
			popup.overlay._inUse = true;
			popup.overlay.className = this.options.overlayClass;
		}
		popup.elm.innerHTML = loadingText;
		popup.elm.style.display = 'block';
		var elmX, elmY, elmW, elmH, popupX, popupY;
		var coords = Kaizen.DOM.getPosition(elm);
		elmX = coords.x;
		elmY = coords.y;
		elmW = elm.offsetWidth;
		elmH = elm.offsetHeight;
		popupX = elmX + (typeof offsetX == 'number' ? offsetX : 6);
		popupY = elmY + (typeof offsetY == 'number' ? offsetY : 6);
		var css;
		if (this.options.useOverlay) {
			Kaizen.DOM.hideShowSpecialElements(null);
			css = 'left:' + elmX + 'px;top:' + elmY + 'px;width:' + elmW + 'px;height:' + elmH + 'px;display:block;visibility:visible;';
			popup.overlay.style.cssText = css;
		}
		css = 'left:' + popupX + 'px;top:' + popupY + 'px;display:block;';
		popup.elm.style.cssText = css;
		if (!this.options.useOverlay)
			Kaizen.DOM.addIFRAMEshim(popup.elm);
		popup.elm.style.visibility = 'visible';
	}
}

Kaizen.Progress.Box.prototype.hide = function(id) {
	var elm;
	if (!id)
		elm = document.body;
	else
		elm = document.getElementById(id);

	if (typeof elm._progressPopupIndex == 'number') {
		var popup = Kaizen.Progress.popups[elm._progressPopupIndex];
		popup.hide();
		popup.inUse = false;
	}
}

/**
 * Class for creating a slideshow
 *
 * @param string	container	The id of the container DIV element
 * @param object	options		Any custom options to override the default options - e.g. {'height': 200}
 */
Kaizen.Slideshow = function(container, options) {
	this.options = {
		height: 0,					// [required] The height of the individual image frames in the image
		autoNext: true,					// Proceed to next automatically?
		stopOnclick: true,				// Set 'autoNext' to false when a link from the list is clicked?
		slideTimeout: 2000,				// The timeout to wait before moving to the next frames
		initialIndex: 0,				// The index of the image frame to show initially - starts from 0
		imageLinkClass: 'KaizenSlideshowImageLink'	// CSS class for the transparent link displayed above the image
	};
	if (options)
		Kaizen.extendObject(this.options, options);

	this.container = document.getElementById(container);
	this.lastIndex = this.options.initialIndex;
	this.links = [];
	var links = this.container.getElementsByTagName('a');
	var cur;
	for (var i = 0, l = links.length; i < l; i++) {
		cur = links[i];
		cur.onclick = this.linkOnclick.bind(this, i);
		Kaizen.DOM.IEDOMleaks.push([cur, 'onclick']);
		this.links[i] = cur;
	}
	this.imageLink = new Kaizen.Element('a', this.options.imageLinkClass);
	this.container.appendChild(this.imageLink);

	this.showNext(this.options.initialIndex);
}

Kaizen.Slideshow.prototype.showNext = function(index) {
	if (typeof index == 'undefined')
		index = this.lastIndex < this.links.length - 1 ? this.lastIndex + 1 : 0;
	this.container.style.backgroundPosition = '0 -' + (this.options.height * index) + 'px';
	this.imageLink.href = this.links[index].href;
	this.imageLink.target = this.links[index].target;
	Kaizen.DOM.removeClass(this.links[this.lastIndex], 'selected');
	Kaizen.DOM.addClass(this.links[index], 'selected');
	this.lastIndex = index;
	if (this.options.autoNext)
		this.timer = setTimeout(function(){this.showNext()}.bind(this), this.options.slideTimeout);
}

Kaizen.Slideshow.prototype.linkOnclick = function(index) {
	if (this.timer)
		clearTimeout(this.timer);
	if (this.options.stopOnclick)
		this.options.autoNext = false;
	this.showNext(index);
	return false;
}

/**
 * Class for creating reveal effect for some container
 *
 * @param string		container	The id of the container DIV element
 * @param object		options		Any custom options to override the default options - e.g. {'animate': false}
 */
Kaizen.Reveal = function(container, options) {
	var ref = this;
	this.container = document.getElementById(container);
	this.options = {
		onShow: null,					// function that will be executed when the container element starts to be revealed
								// 'this' in the function will refer to the container element
		onHide: null,					// function that will be executed when the container element starts to be hidden
								// 'this' in the function will refer to the container element
		showText: '',					// Text for the link - i.e. 'show'
		hideText: '',					// Text for the link - i.e. 'hide'
		showHideLink: '',				// If desired, use some existing link on the page instead of creating a new one - this could be string/element - i.e. the id of the link on the page OR the link element on the page
		initiallyHidden: true,				// The default state of the container is hidden
		animate: true,					// Use animation or display the new content immediately
		animationDuration: 100,				// Animation duration in milliseconds
		animationSteps: 15,				// Animation steps
		linkClass: 'KaizenRevealLink'			// CSS class to use for the link that shows/hides the container
	};
	if (options)
		Kaizen.extendObject(this.options, options);

	if (this.options.showHideLink) {
		this.link = typeof this.options.showHideLink == 'string' ? document.getElementById(this.options.showHideLink) : this.options.showHideLink;
	} else {
		this.link = new Kaizen.Element('a', {'href':'#'});
		this.container.parentNode.insertBefore(this.link, this.container);
	}
	if (this.options.linkClass)
		Kaizen.DOM.addClass(this.link, this.options.linkClass);
	this.link.onclick = function() {
		ref.startShowHide();
		return false;
	};
	Kaizen.DOM.IEDOMleaks.push([this.link, 'onclick']);
	if (this.options.initiallyHidden) {
		this.container.style.display = 'none';
		this.changeLinkText();
	} else {
		this.changeLinkText(true);
	}

	this.inProgress = false;
}

Kaizen.Reveal.prototype.changeLinkText = function(hideText) {
	var text = hideText ? this.options.hideText : this.options.showText;
	if (text == '')
		return;
	if (typeof this.link.textContent != 'undefined')
		this.link.textContent = text;
	else
		this.link.innerText = text;
}

Kaizen.Reveal.prototype.startShowHide = function() {
	if (this.inProgress)
		return;

	var show = this.container.style.display == 'none';

	if (show) {
		if (this.options.onShow)
			this.options.onShow.apply(this.container);
	} else {
		if (this.options.onHide)
			this.options.onHide.apply(this.container);
	}

	if (this.options.animate) {
		this.inProgress = true;

		this.changeLinkText(show);

		// setup initial properties for container
		var cons = this.container.style;
		if (show) {
			cons.position = 'absolute';
			cons.visibility = 'hidden';
			cons.display = 'block';
		}
		this.conH = this.container.offsetHeight;
		this.conD = this.conH / this.options.animationSteps;
		this.conC = show ? 0 : this.conH;
		cons.overflow = 'hidden';
		if (show) {
			cons.height = '1px';
			cons.position = 'static';
			cons.visibility = 'inherit';
		}
		this.animate(show);
	} else {
		this.container.style.display = show ? '' : 'none';
		this.changeLinkText(show);
	}
}

Kaizen.Reveal.prototype.animate = function(show) {
	if (show) {
		this.conC = parseInt(this.conC + this.conD);
		if (this.conC >= this.conH) {
			this.container.style.height = 'auto';
			this.container.style.overflow = 'visible';
			this.conR = true; // completed
		} else {
			this.container.style.height = this.conC + 'px';
		}
	} else {
		this.conC = parseInt(this.conC - this.conD);
		if (this.conC <= 0) {
			this.container.style.display = 'none';
			this.container.style.height = 'auto';
			this.container.style.overflow = 'visible';
			this.conR = true; // completed
		} else {
			this.container.style.height = this.conC + 'px';
		}
	}

	if (this.conR) { // completed so end
		this.conR = false;
		this.inProgress = false;
		return;
	}
	setTimeout(this.animate.bind(this, show), this.options.animationDuration / this.options.animationSteps);
}

/**
 * Class for adding action buttons for objects on the page
 *
 * @param string	container	The id of the container DIV element that holds all objects that have actions applied
 * @param object	actions		An object defining all available actions. Valid syntax is:
{
    'action1Name': ['iconClass', 'Title for the icon', handlerFunction],
    'action2Name': ['iconClass', 'Title for the icon', handlerFunction],
    ...
}
 * @param object	options		Any custom options to override the default options
 */
Kaizen.Actions = function(container, actions, options) {
	var ref = this;
	this.options = {
		tooltipDisplayTimeout: 500,				// Timeout before displaying the tooltip for a given icon (milliseconds)
		objectContainerHoverClass: 'KaizenActionsObjectOver',	// CSS class for the hover state of the objects
		iconsContainerClass: 'KaizenActionsIcons',		// CSS class for the container of the icons
		iconClass: 'KaizenActionIcon'				// CSS class for the icons
	};
	if (options)
		Kaizen.extendObject(this.options, options);

	this.container = document.getElementById(container);
	this.actions = actions;
	this.tooltipTimeout = 0;

	// remove accessibility links
	var linksContainers = Kaizen.DOM.getElementsByClassName(this.options.iconsContainerClass, 'div', this.container);
	var cur;
	for (var i = 0, l = linksContainers.length; i < l; i++) {
		cur = linksContainers[i];
		cur.parentNode.removeChild(cur);
	}

	this.cursorPosition = {};
	Kaizen.DOM.addEvent(document, 'mousemove', function(e) {
		ref.updateCursorPosition(e);
	});
}

/**
 * Add actions for given container(s) - get the containers by id
 *
 * @param string/array		keys		The id of the container for which to add the actions OR multiple ids e.g. ['id1', 'id2'...]
 * @param string/array		actions		The action which to add for the given container(s) OR multiple actions e.g. ['open', 'edit'...]
 */
Kaizen.Actions.prototype.addById = function(keys, actions) {
	this.add(keys, actions, true);
}

/**
 * Add actions for given container(s) - get the containers by class name
 *
 * @param string/array		keys		The class of the containers for which to add the actions OR multiple classes e.g. ['class1', 'class2'...]
 * @param string/array		actions		The action which to add for the given container(s) OR multiple actions e.g. ['open', 'edit'...]
 */
Kaizen.Actions.prototype.addByClassName = function(keys, actions) {
	this.add(keys, actions);
}

/**
 * Disable actions for given container(s) - get the containers by id
 *
 * @param string/array		keys		The id of the container for which to disable the actions OR multiple ids e.g. ['id1', 'id2'...]
 * @param string/array		actions		The action which to disable for the given container(s) OR multiple actions e.g. ['open', 'edit'...]
 */
Kaizen.Actions.prototype.disableById = function(keys, actions) {
	this.handleActionState(keys, actions, true);
}

/**
 * Disable actions for given container(s) - get the containers by class name
 *
 * @param string/array		keys		The class of the containers for which to disable the actions OR multiple classes e.g. ['class1', 'class2'...]
 * @param string/array		actions		The action which to disable for the given container(s) OR multiple actions e.g. ['open', 'edit'...]
 */
Kaizen.Actions.prototype.disableByClassName = function(keys, actions) {
	this.handleActionState(keys, actions);
}

/**
 * Enable actions for given container(s) - get the containers by id
 *
 * @param string/array		keys		The id of the container for which to enable the actions OR multiple ids e.g. ['id1', 'id2'...]
 * @param string/array		actions		The action which to enable for the given container(s) OR multiple actions e.g. ['open', 'edit'...]
 */
Kaizen.Actions.prototype.enableById = function(keys, actions) {
	this.handleActionState(keys, actions, true, true);
}

/**
 * Enable actions for given container(s) - get the containers by class name
 *
 * @param string/array		keys		The class of the containers for which to enable the actions OR multiple classes e.g. ['class1', 'class2'...]
 * @param string/array		actions		The action which to enable for the given container(s) OR multiple actions e.g. ['open', 'edit'...]
 */
Kaizen.Actions.prototype.enableByClassName = function(keys, actions) {
	this.handleActionState(keys, actions, false, true);
}

Kaizen.Actions.prototype.add = function(keys, actions, byId) {
	var ref = this;

	if (typeof actions == 'string')
		actions = [actions];

	var objectContainers = this.getObjectsByKeys(keys, byId);

	var cur, actionsContainer, j, m, tr, td, action, link;
	for (i = 0, l = objectContainers.length; i < l; i++) {
		cur = objectContainers[i];
		//actionsContainer = new Kaizen.Element('div', this.options.iconsContainerClass);
		actionsContainer = new Kaizen.Element('table', {'border':'0', 'cellpadding':'0', 'cellspacing':'0', 'class':this.options.iconsContainerClass});
		tr = new Kaizen.Element('tr');
		td = new Kaizen.Element('td');
		for (j = 0, m = actions.length; j < m; j++) {
			action = this.actions[actions[j]];
			link = new Kaizen.Element('a', {'href':'#', 'class':this.options.iconClass + ' ' + action[0], 'onclick':function(){
				this._actionHandler(this._objectContainerId.substring(this._objectContainerId.lastIndexOf('_') + 1));
				return false;
			}, 'onmouseover':function(){
				ref.actionIconHover(this);
			}, 'onmouseout':function(){
				ref.actionIconHover(this, true);
			}});
			link._objectContainerId = cur.id;
			link._title = action[1];
			link._actionHandler = action[2];
			//actionsContainer.append(link);
			td.appendChild(link);
		}
		tr.append(td);
		actionsContainer.append(tr);
		actionsContainer = actionsContainer.end();
		cur.appendChild(actionsContainer);
		Kaizen.DOM.addEvent(cur, 'mouseover', this.objectContainerHover.bind(this, cur));
		Kaizen.DOM.addEvent(cur, 'mouseout', this.objectContainerHover.bind(this, cur, true));
	}
}

Kaizen.Actions.prototype.handleActionState = function(keys, actions, byId, enable) {
	if (typeof actions == 'string')
		actions = [actions];

	var objectContainers = this.getObjectsByKeys(keys, byId);

	var cur, action, link, j, m;
	for (var i = 0, l = objectContainers.length; i < l; i++) {
		cur = objectContainers[i];
		for (j = 0, m = actions.length; j < m; j++) {
			action = this.actions[actions[j]];
			link = Kaizen.DOM.getElementsByClassName(action[0], 'a', cur);
			// if this action is not added for this container, skip without and error
			if (link.length == 0)
				continue;
			link[0].style.display = enable ? '' : 'none';
		}
	}
}

Kaizen.Actions.prototype.getObjectsByKeys = function(keys, byId) {
	if (typeof keys == 'string')
		keys = [keys];

	var objectContainers = [];

	var cur, cur2, j, m;
	for (var i = 0, l = keys.length; i < l; i++) {
		cur = keys[i];
		if (byId) {
			objectContainers.push(document.getElementById(cur));
		} else {
			cur2 = Kaizen.DOM.getElementsByClassName(cur, 'div', this.container);
			for (j = 0, m = cur2.length; j < m; j++)
				objectContainers.push(cur2[j]);
		}
	}

	return objectContainers;
}

Kaizen.Actions.prototype.objectContainerHover = function(elm, out) {
	if (!out)
		Kaizen.DOM.addClass(elm, this.options.objectContainerHoverClass);
	else
		Kaizen.DOM.removeClass(elm, this.options.objectContainerHoverClass);
}

Kaizen.Actions.prototype.actionIconHover = function(elm, out) {
	var ref = this;

	if (this.tooltipTimeout) {
		clearTimeout(this.tooltipTimeout);
		this.tooltipTimeout = 0;
	}
	if (elm._title) {
		if (!out) {
			this.cursorPosition.target = elm;
			this.tooltipTimeout = setTimeout(function(){
				Kaizen.Tip.show(elm._title, ref.cursorPosition);
			}, this.options.tooltipDisplayTimeout);
		} else {
			Kaizen.Tip.hide();
		}
	}
}

Kaizen.Actions.prototype.updateCursorPosition = function(e) {
	if (!e)
		e = window.event;
	this.cursorPosition.clientX = e.clientX;
	this.cursorPosition.clientY = e.clientY;
	this.cursorPosition.pageX = e.pageX;
	this.cursorPosition.pageY = e.pageY;
}

/**
 * Class for creating a countdown
 *
 * @param string	container	The id of the container element (could be SPAN, DIV, etc...)
 * @param string	targetDate	The target date in '2010-03-21 14:00:00' format. PHP syntax is 'Y-m-d  H:i:s'
 * @param string	currentDate	The current date in '2010-03-21 14:00:00' format. PHP syntax is 'Y-m-d  H:i:s'
 * @param object	options		Any custom options to override the default options
 */
Kaizen.Countdown = function(container, targetDate, currentDate, options) {
	this.options = {
		leadingUnit: 'days',				// The leading unit to display - 'days', 'hours', 'minutes' or 'seconds'
		outputFormat: '[D] days, [H] hours, [M] minutes, [S] seconds',	// The format of the output - syntax:
										// [D] - days, [H] - hours, [M] - minutes, [S] - seconds
		offsetMinutesFromServerTime: 0,			// Offset (in minutes from server time)
		lauchMessage: 'Lauch time reached!', 		// Message to display inside the container when the target date is reached
		updateInterval: 1000,				// Update interval (milliseconds)
		daysClass: 'KaizenCountdownDays',		// CSS class to use for the days SPAN
		hoursClass: 'KaizenCountdownHours',		// CSS class to use for the hours SPAN
		minutesClass: 'KaizenCountdownMinutes',		// CSS class to use for the minutes SPAN
		secondsClass: 'KaizenCountdownSeconds'		// CSS class to use for the seconds SPAN
	};
	if (options)
		Kaizen.extendObject(this.options, options);

	targetDate = this.convertDate(targetDate);
	currentDate = this.convertDate(currentDate);

	this.container = document.getElementById(container);
	this.targetDateTime = new Date(targetDate).getTime();
	this.currentDateTime = new Date(currentDate);
	if (this.options.offsetMinutesFromServerTime != 0)
		this.currentDate.setTime(this.currentDate.getTime() + this.options.offsetMinutesFromServerTime * 60 * 1000);
	this.currentDateTime = this.currentDateTime.getTime();
	this.startPointTime = new Date().getTime();
	this.updateCountdown();
	this.updateInt = setInterval(this.updateCountdown.bind(this), this.options.updateInterval);
}

// Converts date string from 2009-03-08 14:00:00 to Mar 8, 2009 14:00:00
Kaizen.Countdown.prototype.convertDate = function(date) {
	var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
	var tmpArr, day, month;
	tmpArr = date.substring(0, 10).split('-');
	month = months[parseInt(tmpArr[1].replace(/^0/,'')) - 1];
	day = tmpArr[2].replace(/^0/,'');
	date = month + ' ' + day + ', ' + tmpArr[0] + ' ' + date.substring(11);
	return date;
}

Kaizen.Countdown.prototype.updateCountdown = function() {
	var now = new Date().getTime();
	var sinceStartPointTime = now - this.startPointTime;
	var remainingMilliseconds = this.targetDateTime - (this.currentDateTime + sinceStartPointTime);
	if (remainingMilliseconds <= 0) { // reached lauch time
		clearInterval(this.updateInt);
		this.container.innerHTML = this.options.lauchMessage;
	} else {
		var second, minute, hour, day, days, hours, minutes, seconds;
		second = 1000;
		minute = second * 60;
		hour = minute * 60;
		day = hour * 24;
		days = Math.floor(remainingMilliseconds / day);
		hours = Math.floor((remainingMilliseconds - day * days) / hour);
		minutes = Math.floor((remainingMilliseconds - day * days - hour * hours) / minute);
		seconds = Math.floor((remainingMilliseconds - day * days - hour * hours - minute * minutes) / second);
		if (this.options.leadingUnit == 'hours') {
			hours = hours + days * 24;
		} else if (this.options.leadingUnit == 'minutes') {
			minutes = minutes + days * 24 * 60 + hours * 60;
		} else if (this.options.leadingUnit == 'seconds') {
			seconds = seconds + days * 24 * 60 * 60 + hours * 60 * 60 + minutes * 60;
		}
		var content = this.options.outputFormat.replace('[D]', '<span class="' + this.options.daysClass + '">' + days + '</span>').
							replace('[H]', '<span class="' + this.options.hoursClass + '">' + hours + '</span>').
							replace('[M]', '<span class="' + this.options.minutesClass + '">' + minutes + '</span>').
							replace('[S]', '<span class="' + this.options.secondsClass + '">' + seconds + '</span>');
		this.container.innerHTML = content;
	}
}

/**
 * Class for initializing a couple of select boxes with options that can be moved between them
 *
 * @param string	selectLeft	The id of the left-hand side select box
 * @param string	selectRight	The id of the right-hand side select box
 * @param string	moveRightBtn	The id of the button to move the options to the right hand-side select box
 * @param string	moveLeftBtn	The id of the button to move the options to the left hand-side select box
 * @param object	options		Any custom options to override the default options
 */
Kaizen.MovableOptionsSelectBoxes = function(selectLeft, selectRight, moveRightBtn, moveLeftBtn, options) {
	var ref = this;
	this.options = {
		tooltipDelay: 500,		// Delay before showing the tooltips for the options (milliseconds)
		tooltipOffsetX: 20,		// X offset from mouse event when displaying the tooltip
		tooltipOffsetY: 20,		// Y offset from mouse event when displaying the tooltip
		tooltipFamily: 'default'	// The tooltip family to use for the tooltips of the options
	};
	if (options)
		Kaizen.extendObject(this.options, options);

	this.selects = {left: document.getElementById(selectLeft), right: document.getElementById(selectRight)};
	this.initOptionTitles(this.selects.left);
	this.initOptionTitles(this.selects.right);

	this.moveRightBtn = document.getElementById(moveRightBtn);
	this.moveLeftBtn = document.getElementById(moveLeftBtn);
	this.moveRightBtn.onclick = Kaizen.returnFalse;
	this.moveLeftBtn.onclick = Kaizen.returnFalse;
	Kaizen.DOM.addEvent(this.moveRightBtn, 'click', function(){ref.moveOption(this)});
	Kaizen.DOM.addEvent(this.moveLeftBtn, 'click', function(){ref.moveOption(this)});

	this.tooltipTimeout = 0;
	this.tooltipMousemoveRegistered = false;

	// these browsers do not support mouseover/mouseout events for OPTION elements and we will use a workaround for them
	this.optionEventsWorkaround = Kaizen.Browser.IE && typeof document.documentElement.currentStyle.minWidth != 'undefined' || Kaizen.Browser.Webkit && Kaizen.Browser.Webkit >= 420 && Kaizen.Browser.Webkit < 530;
}

Kaizen.MovableOptionsSelectBoxes.prototype = {

	// Method for adding a tooltip for a given option
	addTooltip: function(optionValue, tooltipContent) {
		var ref = this;
		var option = this.getOptionByValue(optionValue);
		option._tooltip = tooltipContent;
		Kaizen.DOM.addEvent(option, 'mouseover', this.tooltipShow.bind(this, option));

		if (!this.selectTooltipEventsRegistered) {
			var select;
			for (var i in this.selects) {
				select = this.selects[i];
				Kaizen.DOM.addEvent(select, 'mousemove', function(e){
					if (!e)
						e = event;
					ref.selectsMousemove(this, e);
				});
				Kaizen.DOM.addEvent(select, 'mouseout', this.tooltipHide.bind(this));
				Kaizen.DOM.addEvent(select, 'mousewheel', this.tooltipHide.bind(this));
			}
			this.selectTooltipEventsRegistered = true;
		}
	},

	tooltipShow: function(option) {
		if (option._tooltip && option.value != 'nomove' && option.value != 'domove') {
			var ref = this;
			if (this.tooltipTimeout) {
				clearTimeout(this.tooltipTimeout);
				this.tooltipTimeout = 0;
			}
			this.tooltipTimeout = setTimeout(function() {
				Kaizen.Tip.show(option._tooltip, null, ref.tooltipX, ref.tooltipY, ref.options.tooltipFamily);
			}, this.options.tooltipDelay);
		}
	},

	tooltipHide: function() {
		if (this.tooltipTimeout) {
			clearTimeout(this.tooltipTimeout);
			this.tooltipTimeout = 0;
		}
		Kaizen.Tip.hide(this.options.tooltipFamily);
		this.currentOptionIndex = -1;
	},

	selectsMousemove: function(select, e) {
		var vScroll = Kaizen.Viewport.getScrollOffsets();
		this.tooltipX = (e.pageX || e.clientX + vScroll.x) + this.options.tooltipOffsetX;
		this.tooltipY = (e.pageY || e.clientY + vScroll.y) + this.options.tooltipOffsetY;

		if (this.optionEventsWorkaround) {
			var selectHeight = select.clientHeight;
			var optionHeight = Math.round(selectHeight / select.size);
			var scrollTop = Kaizen.Browser.IE && !document.querySelector ? 0 : select.scrollTop;
			var eventY = e.offsetY;
			if (eventY < 0)
				eventY = 0;

			var currentOptionIndex = Math.floor((eventY + scrollTop) / optionHeight);
			if (this.currentOptionIndex != currentOptionIndex) {
				this.tooltipHide();
				var option = select.options[currentOptionIndex];
				if (option)
					this.tooltipShow(option);
				this.currentOptionIndex = currentOptionIndex;
			}
			//document.getElementsByTagName('p')[0].innerHTML = 'y = ' + e.offsetY + '; scrollTop = ' + scrollTop;
		}
	},

	initOptionTitles: function(select) {
		var options = select.options;
		var cur, curTitle;
		for (var i = 0, l = options.length; i < l; i++) {
			cur = options[i];
			if (cur.value == 'nomove' || cur.value == 'domove') {
				curTitle = cur;
				cur._isTitle = true;
			} else if (curTitle) {
				cur._title = curTitle.innerHTML;
			}
		}
	},

	moveOption: function(btn) {
		var moveLeft = btn == this.moveLeftBtn;
		var selectedIndex, option;
		var selectFrom = moveLeft ? this.selects.right : this.selects.left;
		var selectTo = moveLeft ? this.selects.left : this.selects.right;
		if ((selectedIndex = selectFrom.selectedIndex) >= 0) {
			option = selectFrom.options[selectedIndex];
			// if title is selected and we should move the whole group
			if (option.value == 'domove') {
				var titleTo = this.getTitleByInnerHTML(selectTo, option.innerHTML);
				// if we don't have this title in selectTo, create it and append it
				if (!titleTo.elm) {
					titleTo.elm = option.cloneNode(true);
					titleTo.elm._isTitle = true;
					selectTo.appendChild(titleTo.elm);
				}
				if (titleTo.index == -1)
					titleTo.index = selectTo.length - 1;
				var insertPos = titleTo.index + 1;
				var cur;
				while ((cur = selectFrom.options[selectedIndex + 1]) && !cur._isTitle)
					selectTo.insertBefore(cur, selectTo.options[insertPos++]);
				selectFrom.removeChild(option);

				selectTo.selectedIndex = titleTo.index;
				selectFrom.selectedIndex = selectFrom.options[selectedIndex] ? selectedIndex : selectedIndex - 1;
			// regular option is selected and we should move just it
			} else if (option.value != 'nomove') {
				// if we don't have titles
				if (!option._title) {
					selectTo.appendChild(option);

					selectTo.selectedIndex = selectTo.options.length - 1;
					selectFrom.selectedIndex = selectFrom.options[selectedIndex] ? selectedIndex : selectedIndex - 1;
				} else {
					var titleTo = this.getTitleByInnerHTML(selectTo, option._title);
					// if we don't have this title in selectTo, create it and append it
					if (!titleTo.elm) {
						titleTo.elm = this.getTitleByInnerHTML(selectFrom, option._title).elm.cloneNode(true);
						titleTo.elm._isTitle = true;
						selectTo.appendChild(titleTo.elm);
					}
					if (titleTo.index == -1)
						titleTo.index = selectTo.length - 1;
					var insertPos = titleTo.index;
					var cur;
					while ((cur = selectTo.options[++insertPos]) && !cur._isTitle)
						;
					selectTo.insertBefore(option, selectTo.options[insertPos]);

					selectTo.selectedIndex = insertPos;
					// if there are no other options in this group, remove the group title
					if (selectFrom.options[selectedIndex - 1]._isTitle && (!selectFrom.options[selectedIndex] || selectFrom.options[selectedIndex]._isTitle)) {
						selectFrom.removeChild(selectFrom.options[selectedIndex - 1]);
						selectFrom.selectedIndex = selectFrom.options[selectedIndex - 1] ? selectedIndex - 1 : selectedIndex - 2;
					} else {
						selectFrom.selectedIndex = selectFrom.options[selectedIndex] ? selectedIndex : selectedIndex - 1;
					}
				}
			}
		}
	},

	getOptionByValue: function(value) {
		var option = null;
		var cur;
		for (var i = 0, l = this.selects.left.options.length; i < l; i++) {
			cur = this.selects.left.options[i];
			if (cur.value == value) {
				option = cur;
				break;
			}
		}
		if (!option) {
			for (i = 0, l = this.selects.right.options.length; i < l; i++) {
				cur = this.selects.right.options[i];
				if (cur.value == value) {
					option = cur;
					break;
				}
			}
		}
		return option;
	},

	// finds a title option in the target select box and returns it
	// it might seem a bit weird to use innerHTML but it's convenient (and saves resources) as we don't need to add another attribute
	getTitleByInnerHTML: function(select, innerHTML) {
		var title = {elm:null, index:-1};
		var cur;
		for (var i = 0, l = select.options.length; i < l; i++) {
			cur = select.options[i];
			if (cur.innerHTML == innerHTML) {
				title.elm = cur;
				title.index = i;
				break;
			}
		}
		return title;
	}

}
