// ClientUILib base.js v1.0.0, Fri Jan 19 19:16:36 CET 2007

// TODO: Copyright (c) 2007, Denis Morozov (dmorozov@exadel.com)
// ...

if(!ClientUILib) {

var ClientUILib = {
	Version: '1.0.0',
	Name: 'ClientUILib',
	LibraryPath: './',	
	packages: [],
	load: function(showLog) {
	  // Check for Prototype JavaScript framework
	  
	  if((typeof Prototype=='undefined') || 
	     (typeof Element == 'undefined') || 
	     (typeof Element.Methods=='undefined') ||
	     parseFloat(Prototype.Version.split(".")[0] + "." +
	                Prototype.Version.split(".")[1]) < 1.5)
	     throw("ClientUILib requires the Prototype JavaScript framework >= 1.5.0");
	  	
	  // Check for Extend JavaScript library
//	  if((typeof Extend=='undefined') ||
//	  	Extend.VERSION < 1.1)
//	     throw("ClientUILib requires the Extend JavaScript library >= 1.1");

	  $A(document.getElementsByTagName("script")).findAll( function(s) {
	    return (s.src && s.src.match(/ClientUILib\.js(\?.*)?$/))
	  }).each( function(s) {
	    LibraryPath = s.src.replace(/ClientUILib\.js(\?.*)?$/,'');
	  });
	  
	  if(showLog) {
		  ClientUILogger.create("ClientUILogger");
		  this.startTime = (new Date()).getTime();
	  }
	  
	  this.initBrowser();
	},
 	include: function(libraryPackageName) {
		if(!this.packages)
			this.packages=[];
		if(!this.packages[libraryPackageName]) {
			this.packages[libraryPackageName] = true;
			var re = /\./g; // Replace all '.' in package name
			var packagePath = LibraryPath + libraryPackageName.replace(re, "/");
			document.write('<script type="text/javascript" src="' + packagePath + '.js"></script>');
		}
	},
	include2: function(libraryPackageName) {
		if(!this.packages)
			this.packages=[];
		if(!this.packages[libraryPackageName]) {
			this.packages[libraryPackageName] = true;
			var re = /\./g; // Replace all '.' in package name
			var packagePath = LibraryPath + libraryPackageName.replace(re, "/");
			var e = document.createElement("script");
		   	e.src = packagePath+".js";
		   	e.type="text/javascript";
		   	document.getElementsByTagName("head")[0].appendChild(e);		
		}
	},
	requireClass: function(libName) {
		// required class not included before
		if(!this.packages[libName]) {
			//this.include2(libName);
			ClientUILib.log(ClientUILogger.ERROR, "Library '" + libName + "' required!!!");
			throw("Package '" + libName + "' is required!");
		}
	},
	declarePackage: function(libName) {
		var pckg = null;
		var packages = $A(libName.split("."));
		packages.each( function(s) {
			if(pckg == null)
				pckg = eval(s);
			else {
				if(!pckg[s]) pckg[s] = {};
				pckg = pckg[s];
			}
	  	});
	  	this.packages[libName] = true;
	},
	log: function(level, infoText) {
		if(ClientUILogger.isCreated){
			ClientUILogger.log(level, infoText);
		} else {
			switch(level) {
				case ClientUILogger.INFO: LOG.info(infoText); break; 	
				case ClientUILogger.ERROR: LOG.error(infoText); break; 	
				case ClientUILogger.WARNING: LOG.warn(infoText); break; 	
				default: LOG.a4jDebug(infoText);; 	
			}
		}
	},
	
	initBrowser: function() {
		var ua = navigator.userAgent.toLowerCase();
		/** @type Boolean */
		this.isOpera = (ua.indexOf('opera') > -1);
	   	/** @type Boolean */
		this.isSafari = (ua.indexOf('webkit') > -1);
	   	/** @type Boolean */
		this.isIE = (window.ActiveXObject);
	   	/** @type Boolean */
		this.isIE7 = (ua.indexOf('msie 7') > -1);
	   	/** @type Boolean */
		this.isGecko = !this.isSafari && (ua.indexOf('gecko') > -1);
		
		if(ua.indexOf("windows") != -1 || ua.indexOf("win32") != -1){
		    /** @type Boolean */
		    this.isWindows = true;
		}else if(ua.indexOf("macintosh") != -1){
			/** @type Boolean */
		    this.isMac = true;
		}
		if(this.isIE && !this.isIE7){
	        try{
	            document.execCommand("BackgroundImageCache", false, true);
	        }catch(e){}
	    }
	}	
};

var ClientUILogger = {
	// log level
	INFO: 		1,
	WARNING: 	2,
	ERROR: 		3,
	
	// flag logger is initialized
	isCreated: false,
	width: 460,
	height: 90,
	create: function() {
		this.logElement = $(document.createElement("div"));
		this.logElement.setStyle({position: 'absolute'});
		this.logElement.setStyle({overflow: 'auto'});
		this.logElement.setStyle({whiteSpace: 'nowrap'});
		
		Event.observe(window, 'load', ClientUILogger.init);
		Event.observe(window, 'resize', ClientUILogger.resizeWindow);
		this.isCreated = true;
	},
	init: function() {
		if(ClientUILogger.logElement)
			document.body.appendChild(ClientUILogger.logElement);
		ClientUILogger.show();
	},
	resizeWindow: function() {
		ClientUILogger.show();
	},
	show: function() {
		if(this.logElement) {
			Element.show(this.logElement);
			this.logElement.setStyle({width: this.width + 'px'});
			this.logElement.setStyle({height: this.height + 'px'});
			//this.logElement.setStyle({top: (this.getWindowHeight() - this.height - 10) + 'px'});
			//this.logElement.setStyle({top: 10 + 'px'});
			//this.logElement.setStyle({left: (this.getWindowWidth() - this.width - 10) + 'px'});
			this.logElement.setStyle({top: '0px'});
			this.logElement.setStyle({left: '250px'});
			this.logElement.setStyle({zIndex: '1000'});
		}
	},
	log: function(level, infoText) {
		var msg = $(document.createElement("div"));
		this.logElement.appendChild(msg);
		msg.setStyle({width: '100%'});
		
		var font = "bold normal bold 10pt Arial";
		var fontColor = "red";
		
		switch(level) {
			case ClientUILogger.INFO: 
				fontColor = "black";
				font = "normal normal normal 10pt Arial";
				break;
			case ClientUILogger.WARNING: 
				fontColor = "blue";
				font = "italic normal normal 10pt Arial";
				break;
			case ClientUILogger.ERROR: 
				fontColor = "red";
				font = "normal normal bold 10pt Arial";
				break;
			default:
				infoText = "UNRESOLVED: level=" + level + ", msg=" + infoText;
		}
		msg.setStyle({font: font});
		msg.setStyle({color: fontColor});
		msg.appendChild(document.createTextNode("> " + infoText));
		
		this.logElement.scrollTop = this.logElement.scrollHeight;
	},
	getWindowWidth: function(){
	    var innerWidth;
		  if (navigator.appVersion.indexOf('MSIE')>0) {
			  innerWidth = document.body.clientWidth;
	    } else {
			  innerWidth = window.innerWidth;
	    }
	    return innerWidth;	
	},
	getWindowHeight: function(){
	    var innerHeight;
		  if (navigator.appVersion.indexOf('MSIE')>0) {
			  innerHeight = document.body.clientHeight;
	    } else {
			  innerHeight = window.innerHeight;
	    }
	    return innerHeight;	
	}	
};

ClientUILib.load();

// declare predefined packages
var ClientUI = {
	controls: {},
	layouts: {}
};

// Some helper functions\
if(!ClientUILib.isIE){
	HTMLElement.prototype.click = function() {
		var evt = this.ownerDocument.createEvent('MouseEvents');
		evt.initMouseEvent('click', true, true, this.ownerDocument.defaultView, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
		this.dispatchEvent(evt);
	}
};

// Usage: Event.onReady(callbackFunction);
Object.extend(Event, {
	_domReady : function() {
		if (arguments.callee.done) return;
		arguments.callee.done = true;
		 
		if (Event._timer) clearInterval(Event._timer);
		
		Event._readyCallbacks.each(function(f) { f() });
		Event._readyCallbacks = null;
	},
	onReady : function(f) {
		if (!this._readyCallbacks) {
			var domReady = this._domReady;
		
			if (domReady.done) return f();
		
			if (document.addEventListener)
				document.addEventListener("DOMContentLoaded", domReady, false);
			if (/WebKit/i.test(navigator.userAgent)) {
				this._timer = setInterval(function() {
					if (/loaded|complete/.test(document.readyState)) domReady();
				}, 10);
			}
			Event.observe(window, 'load', domReady);
			Event._readyCallbacks = [];
		}
		Event._readyCallbacks.push(f);
	}
});

};
var Utils = {
	
	DOM: {
		copyAttributes : function(target, source, opts) {

			//LOG.debug("copyAttributes");
			var attrs = source.attributes;
			
			var exclusions = (opts && opts.exclude) ? opts.exclude : [];
			
			for(var i = 0 ; i < attrs.length; i++) {
			
				var attributeNode = attrs[i];
				var nodeName = attributeNode.nodeName;
				var nodeValue = attributeNode.nodeValue;
				
				var shouldCheckAttribute = 
					nodeValue && 
					nodeValue.length > 0 && 
					exclusions.indexOf(nodeName) < 0;
				
				if (ClientUILib.isIE) {
					shouldCheckAttribute &= attributeNode.specified;
				}
				
				if(shouldCheckAttribute) {
					
					//LOG.debug("Copying attribute " + attributeNode.nodeName + "=" + attributeNode.nodeValue);
					
					var newAttributeNode = 
						document.createAttribute(nodeName);
					
					newAttributeNode.nodeValue = attributeNode.nodeValue;
					target.setAttributeNode(newAttributeNode);
				}
				
			}
			//LOG.debug("/copyAttributes");
		},
		
		replaceNode : function(id, request) {
			var target = document.getElementById(id);
			var src = request.getElementById(id);
	
			if(target && src) {
				var cells = target.cells;
				for(var i = 0; i < cells.length; i++) {
					Utils.DOM.Event.removeListeners(cells[i]);
				}
				
				if (ClientUILib.isIE) {
					
					var s = String();
					var newOuterXml = "<table><tbody>" + src.xml + "</tbody></table>";
					var newNode = document.createElement("DIV");

					newNode.innerHTML = newOuterXml;

					var imported = newNode.firstChild.firstChild.firstChild;
					target.parentNode.replaceChild(imported, target);;
					return imported;
					
				} else if (ClientUILib.isGecko){
					//Mozill family
					var theDoc = document;
					
					Utils.DOM._clearAttributes(target);
					Utils.DOM.copyAttributes(target, src);

					target.innerHTML = src.innerHTML;//nnerHTML.join("");
					return target;
				} else {
					//Fall back to DOM, and cross the fingers
					src = document.importNode(src, true);
					target.parentNode.replaceChild(src, target);
				}
			}
			else {
				if(!target)
					ClientUILib.log(ClientUILogger.ERROR, "DOM Element with id " + id + " not found for update.");
				if(!src) {
					ClientUILib.log(ClientUILogger.ERROR, "RESPONSE Element with id " + id + " not found for update.");
					
					// cleanup destination
					if(target) {
						for(var i=0; i<target.cells.length; i++) {
							target.cells[i].innerHTML = "";
						}
					}
				}
			}
		},
		
		_clearAttributes : function(node) {

			var attrs = node.attributes;
			if (node.clearAttributes) {
				node.clearAttributes();
			} else {
				while(node.attributes.length > 0) {
					node.removeAttributeNode(node.attributes[0]);
				}
			}
		},
		
		_formatNode : function(node) {
			
			var sb = new StringBuilder();
			
			sb.append("<").append(node.nodeName);
			for (var i = 0; i < node.attributes.length; i++) {
				var attr = node.attributes[i];
				if (attr.specified) {
					sb
						.append(" ")
						.append(attr.nodeName)
						.append("=\"")
						.append(attr.nodeValue)
						.append("\" ");
				}
			}
			
			sb.append("/>");
			
			return sb.toString();
			
		},
		
		Event: {
			
			/**
			 * cache listeners in element to kill them all on element ajax replace
			 * @param {Object} element
			 * @param {Object} event
			 * @param {Object} handler
			 * @param {Object} useCapture
			 */
			observe : function (element, event, handler, useCapture) {
				if (true) {
					if (!element._listeners) {
						element._listeners = [];
					}
					
					element._listeners[element._listeners.length] = 
						{
							event: event,
							handler: handler,
							useCapture: useCapture
						};
				}
				Event.observe(element, event, handler, useCapture);
			},
			
			stopObserving : function(element, event, handler, useCapture) {
				
				if(element._listeners) {
					element._listeners = 
						element._listeners.reject(
							function(obj) {
								return obj.event == event 
									&& obj.handler == handler 
									&& obj.useCapture == useCapture;
							}
						);
				}
				
				Event.stopObserving(element, event, handler, useCapture);
				
			}, 
			
			removeListeners : function(element) {
				if (element._listeners) {
					var l = element._listeners.length;
					for(var i = 0; i < l; i++) {
						var listener = element._listeners[i];
						Event.stopObserving(
							element, 
							listener.event, 
							listener.handler, 
							listener.useCapture);
					}
					
					element._listeners = null;
				}
			}	
		}
	},
	
	AJAX : {
		updateRows: function(options,request,grid,clientid, callbacks, callbacksPost){
			var localOptions = options;
			var rowCount = grid.getBody().templFrozen.getElement().rows.length;
			var startRow = localOptions.startRow;
			var count = localOptions.count;
			var rowindex, i, el;
			var dataModel = grid.dataModel;
			var baseid = clientid;
			
			var countForUpdate = 0;
			var rowsForUpdate = [];
			
			for(i=0; i<count; i++) {
				rowindex = startRow + i;
				if(rowindex >= rowCount){
					 rowindex -= rowCount;
				}
				[":f:", ":n:"].unbreakableEach(
					function(suffix) {
						var id = [baseid,suffix,rowindex].join("");
						var row = Utils.DOM.replaceNode(id, request);
						
						if (callbacks) {
							// just suspend operation for future
							if(!rowsForUpdate[i]) rowsForUpdate[i] = {};
							rowsForUpdate[i][suffix] = {index : rowindex, row : row};
							countForUpdate++;
						}
					} 
				);
			}
			if (ClientUILib.isIE7) {
				setTimeout(function()  {
						var body = grid.getBody();
						["fTable", "nTable"].unbreakableEach(
							function(prop) {
								body[prop].hide();
								body[prop].show();
							}
						);
					}
					,50);
			} 
			
			if (callbacks && countForUpdate>0) {
				// process suspended processing
				setTimeout(function(){
					for(var i=0; i<count; i++) {
						callbacks.unbreakableEach(
							function(callback) {
								if(rowsForUpdate[i]) {
									if(rowsForUpdate[i][":f:"].row) callback.call(grid, rowsForUpdate[i][":f:"]);
									if(rowsForUpdate[i][":n:"].row) callback.call(grid, rowsForUpdate[i][":n:"]);
								}
							}
						);
					}
					
					if(callbacksPost) {
						callbacksPost.unbreakableEach(
							function(callback) {
								callback.call(grid);
							}
						);
					}
				}, 100);
			}
	
			dataModel.eventDataReady.fire(localOptions);
		}	
	}
};
/*
var _cAtt = Utils.DOM._clearAttributes;
Utils.DOM._clearAttributes = function() {
	return;
	var d1 = new Date().getTime();
	_cAtt.apply(Utils.DOM, arguments);
	var d2 = new Date().getTime();
	ClientUILib.log(ClientUILogger.INFO, "Utils.DOM._clearAttributes " + (d2 - d1) + "ms");
};
var cAtt = Utils.DOM._clearAttributes;
Utils.DOM.copyAttributes = function() {
	return;
	var d1 = new Date().getTime();
	cAtt.apply(Utils.DOM, arguments);
	var d2 = new Date().getTime();
	ClientUILib.log(ClientUILogger.INFO, "Utils.DOM.copyAttributes " + (d2 - d1) + "ms");
};
*/
Utils.execOnLoad = function(func, condition, timeout) {
	
	if (condition()) {
		func();		
	} else {
		window.setTimeout(
			function() {
				Utils.execOnLoad(func, condition, timeout);
			},
			timeout
		);
	}
};
Utils.Condition = {
	ElementPresent : function(element) {
		return function () {
			var el = $(element);
			return el && el.offsetHeight > 0;
		};
	}
};

Array.prototype.unbreakableEach = function(f) {
	for (var i = 0; i < this.length; i++) {
		f(this[i], i);
	}
};/*
 * TODO: Copyright (c) 2007 Denis Morozov <dmorozov@exadel.com>
 *
 * ...
 */
ClientUILib.declarePackage("ClientUI.common.utils.StringBuilder");

/*
/* sbuilder.js - Helper class to improve strings concatenation perfomance 
 * by Denis Morozov <dmorozov@exadel.com> distributed under the BSD license. 
 *
 * Usage:
 * var sb = new StringBuilder();
 * sb.append("String 1").append("String 2");
 * sb.append("String 3");
 * var str = sb.toString();
 */
StringBuilder = Class.create({
	
	initialize: function(str) {
		this._string = null;
		this._current = 0;
		this._parts = [];
		this.length = 0;
		
		if(str != null)
			this.append(str);
	},
	append: function (str) {
		// append argument
		//this.length += (this._parts[this._current++] = String(str)).length;
		this._parts.push(String(str));
		
		// reset cache
		this._string = null;
		return this;
	},
	
	toString: function () {
		if (this._string != null)
			return this._string;
		
		var s = this._parts.join("");
		this._parts = [s];
		this._current = 1;
		this.length = s.length;
		
		return this._string = s;
	},
	
	clean: function(str) {
		this.initialize();
	}
});

Object.extend(StringBuilder.prototype, {
	length: 	0,
	
	// private
	_current:	0,
	_parts:		[],
	_string: 	null	// used to cache the string
});
/**
 * Validators.js		Date created: 14.04.2007
 * Copyright (c) 2007 Exadel Inc.
 * @author Denis Morozov <dmorozov@exadel.com>
 */
 
var Validators = {
	/**
	 * Internet Explorer holds references to objects that are not actually javascript objects. So if we use the 
	 * objects in javascript it will give error. But the typeof operator identifies them as javascript objects( problem!!!). 
	 * Here we can use the isIEObject() function to identify those objects.
	 */
	isIEObject: function(a) {
		return this.isObject(a) && typeof a.constructor != 'function';
	},

	/**
	 * This function returns true if a is an array, meaning that it was produced by the Array constructor or by 
	 * the [ ] array literal notation.
	 */
	isArray: function(a) {
		return this.isObject(a) && a.constructor == Array;
	},
 
	/**
	 * This function returns true if a is one of the Boolean values, true or false.
	 */
	isBoolean: function(a) {
		return typeof a == 'boolean';
	},
	
	getBoolean: function(val, defVal) {
		if(this.isBoolean(val))
			return val;
		if(val == "true") return true;
		else if(val == "false") return false;
		
		return defVal;
	},

	/**
	 * This function returns true if a is an object or array or function containing no enumerable members.
	 */
	isEmpty: function(o) {
		if (this.isObject(o)) {
           for (var i in o) {
                return false;
           }
		}
		else if(this.isString(o) && o.length > 0) {
			return false;
		}
		
		return !this.IsNumber(o);
	},

	/**
	 * This function returns true if a is a function. Beware that some native functions in IE were made to look 
	 * like objects instead of functions. This function does not detect that.Netscape is better behaved in this regard.
	 */
	isFunction: function(a) {
		return typeof a == 'function';
	},

	/**
	 * This function returns true if a is the null value.
	 */
	isNull: function(a) {
		return typeof a == 'object' && !a;
	},

	/**
	 * This function returns true if <code>data</code> is a finite number. It returns false if <code>data</code> is NaN or Infinite.
	 */
	IsNumber: function(data) {
		if(typeof data == 'number' && isFinite(data)) {
			return true;
		}
		
		// if it is a string that contains a number
		var re = /(^-?[1-9](\d{1,2}(\,\d{3})*|\d*)|^0{1})$/;
		if ( re.test(data) ) {
			return true;
		}
		return false;
	},
	IsFormattedNumber: function(data) {
		// Regular expression should match number with commas or not
		//1. ^-? <-- '-' is optional at the beginning
		//2. \d{1,3} <-- with or without comma, first 3 digits
		//3. \d{1,3}(\,\d{3})* <-- with comma, at least one digit with max of three before repeating like ',ddd'
		//4. \d+ <-- without comma, match any number of integer(shouldn't be though)
		var re = /^-?(\d{1,3}|\d{1,3}(\,\d{3})*|\d*)$/g;
		if ( ! re.test(data) ) {
			return false;
		}
		return true;		
	},

	/**
	 * This function returns true if a is an object, array, or function. It returns false if a is a string, 
	 * number, Boolean, null, or undefined.
	 */
	isObject: function(a) {
		return (typeof a == 'object' && !!a) || this.isFunction(a);
	},

	/**
	 * This function returns true if a is a string.
	 */
	isString: function(a) {
		return typeof a == 'string';
	},

	/**
	 * This function returns true if a is the undefined value. You can get the undefined value from an uninitialized 
	 * variable or from an object's missing member.
	 */
	isUndefined: function(a) {
		return typeof a == 'undefined';
	}
};/**
 * Box.js		Date created: 6.04.2007
 * Copyright (c) 2007 Exadel Inc.
 * @author Denis Morozov <dmorozov@exadel.com>
 */
ClientUILib.declarePackage("ClientUI.common.box.Box");


/*
 * Base class for all ui controls 
 *
 * TODO: description of control 
 *
 * TODO: usage description
 * Usage: 
 *  ClientUILib.declarePackage("ClientUI.common");
 *  ClientUILib.requireClass("ClientUI.common.box.Box");
 * 	var ClientUI.MyControl = Class.create({
 * 		CLASSDEF : {
 * 			name:  'ClientUI.MyControl',
 * 			parent: ClientUI.common.box.Box
 * 		}, 		
 *		initialize:function() {
 *			this.parentClass().constructor().call(this)
 *			alert("A new " + this.getClass().className + " was created")
 *		}				
 *	})
 */
ClientUI.common.box.Box = Class.create({

	initialize: function(element, parentElement, dontUpdateStyles) {
		this.element = $(element);
		if(!this.element) {
			this.element = $(document.createElement("div"));
			if($(parentElement)) {
      			$(parentElement).appendChild(this.element);
			}
      		else {
	      		document.body.appendChild(this.element);
      		}
      	}
		this.element.wrapper = this;
		if(!this.element.parentNode && $(parentElement)) {
			$(parentElement).appendChild(this.element);
		}

      	if(!this.element.id) {
			this.element.id = "ClientUI_Box" + ClientUI_common_box_Box_idGenerator++;
		}
		if(!dontUpdateStyles) {
	      	this.element.setStyle({overflow: 'hidden'});
	      	this.element.setStyle({whiteSpace: 'nowrap'});
	      	
			// if the element isn't positioned, make it relative
		    var position = this.element.getStyle('position');
		    if(position != 'absolute' && position != 'fixed'){
		        this.element.setStyle({position: 'relative'});
		    }			
		}
	},
	
	setParent: function(newParent) {
		if(this.element.parentNode) {
			this.element.parentNode.removeChild(this.element);
		}
		if(newParent) {
			if(newParent.getElement) {
				newParent = newParent.getElement();
			}
			$(newParent).appendChild(this.element);			
		}
		return this;
	},
	getElement: function() {
		return this.element;
	},
	getHeight: function() {
		if(this.getElement().tagName.toLowerCase() != "body") {
			var h = Element.getHeight(this.element);
			return h>0 ? h : (this.element.boxHeight ? parseInt(this.element.boxHeight) : 0);
		}

		if (self.innerHeight) { // all except Explorer
			return self.innerHeight;
		}
		else if (document.documentElement && document.documentElement.clientHeight) {
			// Explorer 6 Strict Mode
			return document.documentElement.clientHeight;
		}
		else if (document.body) { // other Explorers
			return document.body.clientHeight;
		}
	},
	isModified: false,
	setHeight: function(newHeight) {
		this.element.boxHeight = newHeight;
		if(Validators.IsNumber(newHeight)) {
			if(newHeight<0) newHeight = 0;
			newHeight += "px";	
		}
		this.element.setStyle({height: newHeight});
		isModified = true;
		return this;
	},
	getWidth: function() {
		if(this.getElement().tagName.toLowerCase() != "body") {
			var w = Element.getWidth(this.element);
			return w>0 ? w : (this.element.boxWidth ? parseInt(this.element.boxWidth) : 0);
		}
			
		if (self.innerHeight) {// all except Explorer
			return self.innerWidth;
		}
		else if (document.documentElement && document.documentElement.clientHeight) {
			// Explorer 6 Strict Mode
			return document.documentElement.clientWidth;
		}
		else if (document.body) { // other Explorers
			return document.body.clientWidth;
		}			
	},
	setWidth: function(newWidth) {
		this.element.boxWidth = newWidth;
		if(Validators.IsNumber(newWidth)) {
			if(newWidth<0) newWidth = 0;
			newWidth += "px";	
		}
		this.element.setStyle({width: newWidth});
		isModified = true;
		return this;
	},
	moveToX: function(x) {
		if(Validators.IsNumber(x)) {x += "px";}
		this.getElement().setStyle({left: x});
		isModified = true;
		return this;		
	},
	moveToY: function(y) {
		if(Validators.IsNumber(y)) {y += "px";}
		this.getElement().setStyle({top: y});
		isModified = true;
		return this;
	},
	moveTo: function(x, y) {
		this.moveToX(x);
		this.moveToY(y);
		return this;
	},
	hide: function() {
		Element.hide(this.element);
		isModified = true;
		return this;
	},
	show: function() {
		Element.show(this.element);
		isModified = true;
		return this;
	},
	updateLayout: function() {
		isModified = false;
		return this;
	},
	getViewportWidth: function() {
		if(this.getElement().tagName.toLowerCase() != "body") {
			var width = 0;
			if( this.getElement().clientWidth ) {
			    width = this.getElement().clientWidth;
			}
			else if( this.getElement().innerWidth ) {
			    width = this.getElement().innerWidth - getScrollerWidth();
			}
			  
			if(ClientUILib.isGecko) {
			  	width -= this.getPadding("lr");
			}
			return width;
		}
		
		return this.getWidth();
	},
	getViewportHeight: function() {
		if(this.getElement().tagName.toLowerCase() != "body") {
			var height = 0;
			if( this.getElement().clientHeight ) {
			    height = this.getElement().clientHeight;
			}
			else if( this.getElement().innerHeight ) {
			    height = this.getElement().innerHeight - getScrollerWidth();
			}
			  
			if(ClientUILib.isGecko) {
			  	height -= this.getPadding("tb");
			}
			return height;
		}
		return this.getHeight();
	},	
	/**
     * Gets the width of the border(s) for the specified side(s)
     * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example, 
     * passing lr would get the border (l)eft width + the border (r)ight width.
     * @return {Number} The width of the sides passed added together
     */
    getBorderWidth : function(side){
        return this.getStyles(side, this.borders);
    },
    
    /**
     * Gets the width of the padding(s) for the specified side(s)
     * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example, 
     * passing lr would get the padding (l)eft + the padding (r)ight.
     * @return {Number} The padding of the sides passed added together
     */
    getPadding : function(side){
        return this.getStyles(side, this.paddings);
    },	
	getStyles : function(sides, styles){
        var val = 0;
        for(var i = 0, len = sides.length; i < len; i++){
             var w = parseInt(this.getElement().getStyle(styles[sides.charAt(i)]), 10);
             if(!isNaN(w)) val += w;
        }
        return val;
    },
	makeAbsolute: function(keepPos) {
		if(keepPos) {
			Position.absolutize(this.getElement());	
		}
		else {
			this.getElement().setStyle({position: 'absolute'});
		}
		return this;
	},
	getX: function() {
		return this.getElement().offsetLeft;
	},
	getY: function() {
		return this.getElement().offsetTop;
	},
	setStyle: function(style) {
		this.getElement().setStyle(style);
		return this;
	},
	
	borders: {l: 'border-left-width', r: 'border-right-width', t: 'border-top-width', b: 'border-bottom-width'},
	paddings: {l: 'padding-left', r: 'padding-right', t: 'padding-top', b: 'padding-bottom'},
	margins: {l: 'margin-left', r: 'margin-right', t: 'margin-top', b: 'margin-bottom'}
	
});

if(!ClientUI_common_box_Box_idGenerator) {
var ClientUI_common_box_Box_idGenerator = 0;
};/**
 * InlineBox.js		Date created: 6.04.2007
 * Copyright (c) 2007 Exadel Inc.
 * @author Denis Morozov <dmorozov@exadel.com>
 */
ClientUILib.declarePackage("ClientUI.common.box.InlineBox");

ClientUILib.requireClass("ClientUI.common.box.Box");

/**
 * Base class that wrap work with inline blocks like span
 */
ClientUI.common.box.InlineBox = Class.create(ClientUI.common.box.Box, {

	initialize: function($super, element, parentElement, dontUpdateStyles) {
		if(!element) {
			element = $(document.createElement("span"));
			if($(parentElement)) {
      			$(parentElement).appendChild(element);
			}
      		else {
	      		document.body.appendChild(element);
      		}
      	}		
      	if(!element.id) {
			element.id = "ClientUI_InlineBox" + ClientUI_common_box_InlineBox_idGenerator++;
		}
		
		$super(element, parentElement, dontUpdateStyles);
		
		// additional styles
		if(!dontUpdateStyles) {
			this.element.setStyle({display: 'block'});	
		}
	}
});

if(!ClientUI_common_box_InlineBox_idGenerator) {
	var ClientUI_common_box_InlineBox_idGenerator = 0;
};/**
 * CustomEvent.js		Date created: 6.04.2007
 * Copyright (c) 2007 Exadel Inc.
 * @author Denis Morozov <dmorozov@exadel.com>
 */
ClientUILib.declarePackage("ClientUI.common.utils");

if(!ClientUI.common.utils.CustomEvent) {
ClientUILib.declarePackage("ClientUI.common.utils.CustomEvent");

ClientUI.common.utils.CustomEvent = Class.create({
	// Constructor
	initialize: function(event) {
		this._eventName = event;
		// WARNING: If too many events will be in system with dynamic 
		// observation/stop observation than integer idGenerator can be overflowed
		this._id = ClientUI_common_utils_CustomEvent_idGenerator++;
	},
	getName: function() {
		return this._eventName;
	},
	getId: function() {
		return this._id;
	},
	
	// Fire this event and notifies the subscribers.
	fire: function() {
        if (!Event.subscribers || !Event.subscribers.length) {
            return true;
        }

		var id = this.getId();
		var subscribers = Event.subscribers;
		var subscribersToNotify = Event.subscribers.findAll( 
			function(subscriber) {
				return subscriber.event.getId()===id; 
			});

		var args = $A(arguments);
		subscribersToNotify.unbreakableEach(function(subscriber) {
			subscriber.callback.apply(subscriber.callback, args);
		});
	}
});

Object.extend(ClientUI.common.utils.CustomEvent, {
	_eventName: 'undefined',
	_id: -1	
});

Event._observe = Event.observe;
Event.observe  = function( element, name, observer, useCapture ) {
	if(element && element.getName && element.getId) {
		var customEvent = element;
		if (!this.subscribers) {
			this.subscribers = [];	
		}
		
		this.subscribers.push({
			event: customEvent,
			desc: name,
			callback: observer
		});
	}
	else {
		Event._observe(element, name, observer, useCapture);	
	}
};

Event._stopObserving = Event.stopObserving;
Event.stopObserving  = function( element, name, observer, useCapture ) {
	if(element && element.getName && element.getId) {
		var customEvent = element;
		if (this.subscribers && this.subscribers.length && this.subscribers.length!==0) {
			var id = customEvent.getId();
			this.subscribers = this.subscribers.select(
				function(subscriber) {
					return subscriber.event.getId()!==id; 
				});
		}
	}
	else {
		Event._stopObserving(element, name, observer, useCapture);	
	}
};

Event._unloadCache = Event.unloadCache; 
Event.unloadCache = function() {
    if (Event.subscribers) {
	    for (var i = 0, length = Event.subscribers.length; i < length; i++) {
	      Event.stopObserving.apply(this, Event.subscribers[i]);
	      Event.subscribers[i][0] = null;
	    }
	    Event.subscribers = false;
	}
	Event._unloadCache();
};
  
var ClientUI_common_utils_CustomEvent_idGenerator = 0;

};
 /**
 * ScrollableBox.js		Date created: 6.04.2007
 * Copyright (c) 2007 Exadel Inc.
 * @author Denis Morozov <dmorozov@exadel.com>
 */
ClientUILib.declarePackage("ClientUI.common.box.ScrollableBox");

ClientUILib.requireClass("ClientUI.common.box.Box");
ClientUILib.requireClass("ClientUI.common.utils.CustomEvent");

/**
 * This class target to manage scrollable box object.
 */
ClientUI.common.box.ScrollableBox = Class.create(ClientUI.common.box.Box, {

	//Constructor
	initialize: function($super, element, parentElement) {
		$super(element, parentElement);
		this.element.setStyle({overflow: 'auto'});
		
		// Create custom event producers
		this.eventHScroll = new ClientUI.common.utils.CustomEvent('OnHScroll');
		this.eventVScroll = new ClientUI.common.utils.CustomEvent('OnVScroll');

		this.eventOnScroll = this.scrollContent.bindAsEventListener(this);
		Event.observe(this.element, 'scroll', this.eventOnScroll);
	},
	scrollContent: function(event) {
		this.updateScrollPos();
	},
	updateScrollPos: function() {
		this.timer = null;
		
		// process horizontal scrolling
		if(this.scrollLeft!==this.getViewportScrollX()) {
			this.scrollLeft = this.getViewportScrollX();
			this.eventHScroll.fire(this.getViewportScrollX());
		}
		
		// process vertical scrolling		
		if(this.scrollTop!==this.getViewportScrollY()) {
			this.scrollTop = this.getViewportScrollY();
			this.eventVScroll.fire(this.getViewportScrollY());
		}
	},
	updateLayout: function($super) {
		// NOTE: not implemented in this class
		$super();
	},
	getViewportScrollX: function() {
		var scrollX = 0;
		if( this.getElement().scrollLeft ) {
			scrollX = this.getElement().scrollLeft;
		}
		else if( this.getElement().pageXOffset ) {
			scrollX = this.getElement().pageXOffset;
		}
		else if( this.getElement().scrollX ) {
			scrollX = this.getElement().scrollX;
		}
		return scrollX;
	},
	getViewportScrollY: function() {
		var scrollY = 0;
		if( this.getElement().scrollTop ) {
			scrollY = this.getElement().scrollTop;
		}
		else if( this.getElement().pageYOffset ) {
			scrollY = this.getElement().pageYOffset;
		}
		else if( this.getElement().scrollY ) {
			scrollY = this.getElement().scrollY;
		}
		return scrollY;
	},
	getScrollerWidth: function() {
		if(this.scrollerWidth && this.scrollerWidth > 0)
			return this.scrollerWidth;
			
	    var scr = null;
	    var inn = null;
	    var wNoScroll = 0;
	    var wScroll = 0;
	
	    // Outer scrolling div
	    scr = document.createElement('div');
	    scr.style.position = 'absolute';
	    scr.style.top = '-1000px';
	    scr.style.left = '-1000px';
	    scr.style.width = '100px';
	    scr.style.height = '50px';
	    // Start with no scrollbar
	    scr.style.overflow = 'hidden';
	
	    // Inner content div
	    inn = document.createElement('div');
	    inn.style.width = '100%';
	    inn.style.height = '200px';
	
	    // Put the inner div in the scrolling div
	    scr.appendChild(inn);
	    // Append the scrolling div to the doc
	    document.body.appendChild(scr);
	
	    // Width of the inner div sans scrollbar
	    wNoScroll = inn.offsetWidth;
	    // Add the scrollbar
	    scr.style.overflow = 'auto';
	    // Width of the inner div width scrollbar
	    wScroll = inn.offsetWidth;
	
	    // Remove the scrolling div from the doc
	    document.body.removeChild(
	        document.body.lastChild);
	
	    // Pixel width of the scroller
	    this.scrollerWidth = (wNoScroll - wScroll);
	    return this.scrollerWidth || 0;
	}	
});

Object.extend(ClientUI.common.box.ScrollableBox.prototype, {
	// Custom events
	/**
	 * Occured when content scrolled in horizontal
	 */
	eventHScroll: {},
	/**
	 * Occured when content scrolled in vertical
	 */
	eventVScroll: {}	
});/*
 * TODO: Copyright (c) 2007 Denis Morozov <dmorozov@exadel.com>
 *
 * ...
 */
ClientUILib.declarePackage("ClientUI.controls.grid.DataModel");

ClientUILib.requireClass("ClientUI.common.utils.CustomEvent");

/*
 * DataModel.js - Base datamodel class for grid control 
 * by Denis Morozov <dmorozov@exadel.com> distributed under the BSD license. 
 *
 */
ClientUI.controls.grid.DataModel = Class.create({
	
	initialize: function() {
		// constructor
		this.eventDataReady = new ClientUI.common.utils.CustomEvent('DataModel::OnDataReady');
	},	
	
	// interface method
	getRow: function(index) {
		return [];
	},
	
	// count of all data rows
	getCount: function() {
		return 0;
	},
		
	// start rows loading
	loadRows: function(options) {
		this.eventDataReady.fire(options);
	},
	
	// regulate requsts frequency
	getRequestDelay: function() {
		return 1000;
	}
});

Object.extend(ClientUI.controls.grid.DataModel.prototype, {
	eventDataReady: {}
});
			
/*
 * TODO: Copyright (c) 2007 Denis Morozov <dmorozov@exadel.com>
 *
 * ...
 */
ClientUILib.declarePackage("ClientUI.controls.grid.ArrayDataModel");

ClientUILib.requireClass("ClientUI.controls.grid.DataModel");
/*
 * ArrayDataModel.js - Array datamodel for grid control 
 * by Denis Morozov <dmorozov@exadel.com> distributed under the BSD license. 
 *
 */
ClientUI.controls.grid.ArrayDataModel = Class.create(ClientUI.controls.grid.DataModel, {
	initialize: function($super, data) {
		$super();
		this.data = $A(data || []);
	},
	getRow: function(index) {
		return this.data[index];
	},
	getCount: function() {
		return this.data.length;
	},
	getRequestDelay: function() {
		return 50;
	}
});ClientUILib.declarePackage("ClientUI.controls.grid.FakeArrayDataModel ");
ClientUI.controls.grid.FakeArrayDataModel = Class.create(ClientUI.controls.grid.DataModel, {
			initialize: function($super, rows_count, columns_count, grid_id) {
				$super();
				this.data = [];
				this.count = rows_count;
				this.columns = columns_count;
				this.gridId = grid_id;
				this.curr_options;
			},
			
			getRow: function(index) {
				if(!this.data[index]) {
				  this.data[index] = [];
				  for (var index2 = 0; index2 < 7; index2++) {
				  	this.data[index][index2] = index2 + " : " + index; 
				  }
				  this.data[index][6] = index%2 ? "value 1" : "value 2";					
				}
				
				return this.data[index];
			},
			getCount: function() {
				return this.count;
			},
			getRequestDelay: function() {
				return 150;
			},
			
			getCurrentOptions: function() {
				 if(!this.curr_options) {
					this.curr_options = {
						count: grid.getBody().templFrozen.getElement().rows.length,
						index: 0,
						startRow: 0
					}
			}
				return this.curr_options;
			},
			
			loadRows: function(options) {
				window.loadingStartTime = (new Date()).getTime();
				var state_options = options;
				var state_input = $(this.gridId + "_state_input");
				var submit_input = $(this.gridId + "_submit_input");
//						var options_input = $(this.gridId + "_options_input");
				var submit_values = state_options.count + "," + state_options.index + "," + state_options.startRow;
				state_input.value = submit_values; 
//	 			options_input.value = options;
				this.curr_options = options;
				submit_input.click();
				
			}
			
		});
/*
 * TODO: Copyright (c) 2007 Denis Morozov <dmorozov@exadel.com>
 *
 * ...
 */
ClientUILib.declarePackage("ClientUI.layouts.LayoutManager");

/*
/* LayoutManager.js - Base class for all layout managers
 * by Denis Morozov <dmorozov@exadel.com> distributed under the BSD license. 
 *
 * TODO: description of control 
 */
ClientUI.layouts.LayoutManager = Class.create(ClientUI.common.box.Box, {	
	initialize: function($super, element, parentElement) {
		$super(element, parentElement);
		
		// store container element to look after
		this.container = parentElement;
		if(this.container)
			this.container = new ClientUI.common.box.Box($(this.container));

		// declare event listeners
		this.eventContainerResize = this.containerResize.bindAsEventListener(this);
		
		// Create custom event producers
		this.eventOnBeforeResize = new ClientUI.common.utils.CustomEvent('OnBeforeResize');
		this.eventOnAfterResize = new ClientUI.common.utils.CustomEvent('OnAfterResize');
		
		this.registerEvents();
	},
	registerEvents: function() {
			Event.observe(window, "resize", this.eventContainerResize);
	},
	destroy: function() {
			Event.stopObserving(window, "resize", this.eventContainerResize);
	},
   	containerResize: function(event) {
	  	//Event.stop(event);
	  	this.eventOnBeforeResize.fire();
	  	this.updateLayout();
	  	this.eventOnAfterResize.fire();
	},
	updateLayout: function($super) {
		if(this.container) {
			var w = this.container.getWidth();
			var h = this.container.getHeight();
			if(ClientUILib.isGecko) {
				w -= this.container.getBorderWidth("lr") + this.container.getPadding("lr");
				h -= this.container.getBorderWidth("tb") + this.container.getPadding("tb");
			}			
			this.setWidth(w);
			this.setHeight(h);
		}
		$super();		
	},
	getContainer: function() {
		return this.container;
	}
});

Object.extend(ClientUI.layouts.LayoutManager.prototype, {
	
	// Custom events
	/**
	 * Occured before resizing
	 */
	eventOnBeforeResize: {},
	/**
	 * Occured after resizing
	 */
	eventOnAfterResize: {}	

});/**
 * Substrate.js		Date created: 21.04.2007
 * Copyright (c) 2007 Exadel Inc.
 * @author Denis Morozov <dmorozov@exadel.com>
 */
ClientUILib.declarePackage("ClientUI.common.box.Substrate");

ClientUILib.requireClass("ClientUI.common.box.Box");

/**
 * Base class that wrap work with inline blocks like span
 */
ClientUI.common.box.Substrate = Class.create(ClientUI.common.box.Box, {

	initialize: function($super, element, parentElement, dontUpdateStyles) {
		if(!element) {			
			var fakeElement = $(document.createElement("div"));
			fakeElement.innerHTML = '<iframe id="'+'ClientUI_Substrate' + (ClientUI_common_box_Substrate_idGenerator++) +'" src="javascript:\'\'" scrolling="no" frameborder="0" style="filter:Alpha(opacity=0);position:absolute;top:0px;left:0px;display:block"></iframe>';
			element = $(fakeElement.getElementsByTagName("iframe")[0]);
			fakeElement.removeChild(element);
      	}		
		
		$super(element, parentElement, dontUpdateStyles);
		
		// additional styles
		if(!dontUpdateStyles) {
		}
	}
});

if(!ClientUI_common_box_Substrate_idGenerator) {
	var ClientUI_common_box_Substrate_idGenerator = 0;
};/*
 * TODO: Copyright (c) 2007 Denis Morozov <dmorozov@exadel.com>
 *
 * ...
 */
ClientUILib.declarePackage("ClientUI.layouts.VLayoutManager");

ClientUILib.requireClass("ClientUI.common.box.Box");
ClientUILib.requireClass("ClientUI.layouts.LayoutManager");

var GridLayout_Enum = {
	HEADER: 	1,
	BODY: 		2,
	FOOTER: 	3
};

/*
 * LayoutManager.js - Base class for all layout managers
 * by Denis Morozov <dmorozov@exadel.com> distributed under the BSD license. 
 *
 * TODO: description of control 
 */
ClientUI.layouts.VLayoutManager = Class.create(ClientUI.layouts.LayoutManager, {	
	initialize: function($super, element, parentElement, config) {
		$super(element, parentElement);		
		if(!element || !element.id) {
			this.element.id = "ClientUI_VLayoutManager" + ClientUI_layouts_VLayoutManager_idGenerator++;
		}

		this.registerEvents();
	},
	registerEvents: function($super) {
		$super();
	},
	destroy: function($super) {
		$super();
	},
   	/*containerResize: function(event) {
   		ClientUI.layouts.VLayoutManager.parentClass.method("containerResize").call(this, event);
   		this.updateLayout();
	},*/
	addPane: function(align, pane) {
		if(!this.panels) { this.panels = []; }

		this.panels[align] = pane;
		this.panels[align].makeAbsolute();
		//this.panels[align].setParent(this);
	},
	getPane: function(align) {
		return this.panels[align];
	},
	updateLayout: function($super) {
		$super();
		
		var parentBox = this.getContainer();
		if(!parentBox) parentBox = this;
		var height = parentBox.getHeight();
		var width = parentBox.getViewportWidth();
		if(ClientUILib.isGecko) {
			width -= parentBox.getBorderWidth("lr") + parentBox.getPadding("lr");
			height -= parentBox.getBorderWidth("tb") + parentBox.getPadding("tb");
		}			
		
		// NOTE: not implemented in this class
	  	if(this.panels) {
	  		var headerHeight = 0;
	  		var footerHeight = 0;
	  		
	  		// first get size of header
	  		if(this.panels[GridLayout_Enum.HEADER]) {
	  			headerHeight = this.panels[GridLayout_Enum.HEADER].getHeight();
	  			this.panels[GridLayout_Enum.HEADER].moveTo(0, 0);
				this.panels[GridLayout_Enum.HEADER].setWidth(width);
	  			this.panels[GridLayout_Enum.HEADER].updateLayout();
	  		}
	  		if(this.panels[GridLayout_Enum.FOOTER]) {
	  			footerHeight = this.panels[GridLayout_Enum.FOOTER].getHeight();
	  			this.panels[GridLayout_Enum.FOOTER].moveTo(0, height - footerHeight);
				this.panels[GridLayout_Enum.FOOTER].setWidth(width);
	  			this.panels[GridLayout_Enum.FOOTER].updateLayout();
	  		}
	  		// than calculate size of body
	  		if(this.panels[GridLayout_Enum.BODY]) {
				var body = this.panels[GridLayout_Enum.BODY];
	  			body.setWidth(width);
	  			var bodyHeight = height - (headerHeight + footerHeight);
	  			body.setHeight(bodyHeight);
	  			body.moveTo(0, headerHeight);
	  			body.updateLayout();
	  		}			
	  	}
	}	
});

if(!ClientUI_layouts_VLayoutManager_idGenerator) {
	var ClientUI_layouts_VLayoutManager_idGenerator = 0;
};/*
 * TODO: Copyright (c) 2007 Denis Morozov <dmorozov@exadel.com>
 *
 * ...
 */
ClientUILib.declarePackage("ClientUI.layouts.GridLayoutManager");

ClientUILib.requireClass("ClientUI.common.box.Box");
ClientUILib.requireClass("ClientUI.layouts.VLayoutManager");
ClientUILib.requireClass("ClientUI.layouts.LayoutManager");

var GridLayout_Enum = {
	HEADER: 	1,
	BODY: 		2,
	FOOTER: 	3
};

/*
 * LayoutManager.js - Base class for all layout managers
 * by Denis Morozov <dmorozov@exadel.com> distributed under the BSD license. 
 *
 * TODO: description of control 
 */
ClientUI.layouts.GridLayoutManager = Class.create(ClientUI.layouts.VLayoutManager, {
	initialize: function($super, element, parentElement, config) {
		$super(element, parentElement);
	},
	updateLayout: function() {
		ClientUI.layouts.LayoutManager.prototype.updateLayout.call(this);
		
		var parentBox = this.getContainer();
		if(parentBox==null) parentBox = this;
		var height = parentBox.getHeight();
		var width = parentBox.getViewportWidth();
		
		// NOTE: not implemented in this class
	  	if(this.panels) {
	  		var headerHeight = 0;
	  		var footerHeight = 0;
			var bodyBottom = 0;
	  		
	  		// first get size of header
	  		if(this.panels[GridLayout_Enum.HEADER]) {
	  			headerHeight = this.panels[GridLayout_Enum.HEADER].getHeight();
	  			this.panels[GridLayout_Enum.HEADER].moveTo(0, 0);
				this.panels[GridLayout_Enum.HEADER].setWidth(width);
	  			this.panels[GridLayout_Enum.HEADER].updateLayout();
	  		}	  		
	  		// than calculate size of body
	  		if(this.panels[GridLayout_Enum.BODY]) {
				var body = this.panels[GridLayout_Enum.BODY];
	  			body.setWidth(width);
	  			var bodyHeight = height - headerHeight;
	  			body.setHeight(bodyHeight);
	  			body.moveTo(0, headerHeight);
	  			body.updateLayout();
				bodyBottom = body.getY() + body.contentBox.getY() + body.scrollBox.getViewportHeight();
	  		}
			
	  		if(this.panels[GridLayout_Enum.FOOTER]) {
	  			footerHeight = this.panels[GridLayout_Enum.FOOTER].getHeight();
	  			this.panels[GridLayout_Enum.FOOTER].moveTo(0, bodyBottom - footerHeight);
				this.panels[GridLayout_Enum.FOOTER].setWidth(width);
	  			this.panels[GridLayout_Enum.FOOTER].updateLayout();
	  		}
			
	  	}
	}
});/*
 * TODO: Copyright (c) 2007 Denis Morozov <dmorozov@exadel.com>
 *
 * ...
 */
ClientUILib.declarePackage("ClientUI.controls.grid.GridHeader");

ClientUILib.requireClass("ClientUI.common.box.Box");
ClientUILib.requireClass("ClientUI.common.box.InlineBox");

/*
 * GridHeader.js - Grid control header pane
 * TODO: add comments
 */
ClientUI.controls.grid.GridHeader = Class.create(ClientUI.common.box.Box, {
	// constructor
	initialize: function($super, template, grid) {
		this.grid = grid;
		this.gridId = this.grid.getElement().id;
		$super(template);

		// register event handlers
		this.eventSepDblClick = this.OnSepDblClick.bindAsEventListener(this);
		this.eventSepMouseDown = this.OnSepMouseDown.bindAsEventListener(this);
		this.eventSepMouseUp = this.OnSepMouseUp.bindAsEventListener(this);
		this.eventSepMouseMove = this.OnSepMouseMove.bindAsEventListener(this);
		this.eventCellMouseDown = this.OnCellMouseDown.bindAsEventListener(this);
		Event.observe(document, 'mousemove', this.eventSepMouseMove, true);
		Event.observe(document, 'mouseup', this.eventSepMouseUp, true);

		this.createControl(template);
	},
	
	// create grid header control
	createControl: function(template) {
		var errMsg = "";
		if(!template) {
			errMsg = "Invalid template specified for GridHeader.";
			ClientUILib.log(ClientUILogger.ERROR, errMsg);
			throw(errMsg);
		}

		if(!this.parseTemplate(template)) {
			errMsg = "Unable to parse template. GridHeader requires template specified over table element with one row.";
			ClientUILib.log(ClientUILogger.ERROR, errMsg);
			throw(errMsg);
		}

		// Set dimensions
		this.setHeight(this.defaultHeight);
		this.setWidth(this.defaultWidth);
		this.controlCreated = true;
		this.agjustSeparators();
	},
	parseTemplate: function(template) {
		if(!template) {
			return false;
		}

		var childs = template.childNodes;
		for(var i=0; i<childs.length; i++) {
			if(childs[i].tagName && childs[i].tagName.toLowerCase() == "div") {
				this.container = $(childs[i]);
				this.container.setStyle({"z-index": 100});
				break;
			}
		}

		var normal = null, frozen = null;
		var childs = this.container.childNodes;
		for(var i=0; i<childs.length; i++) {
			if(childs[i].id && childs[i].id.indexOf("FrozenBox")>=0) {
				frozen = childs[i];
			}
			else if(childs[i].id && childs[i].id.indexOf("NormalBox")>=0){
				normal = childs[i];
			}
		}

		if(!normal || !frozen) {
			errMsg = "Unable to parse template for GridHeader. Unable to find FrozenBox or NormalBox.";
			ClientUILib.log(ClientUILogger.ERROR, errMsg);
			throw(errMsg);
		}
		this.contentBox = new ClientUI.common.box.Box(normal);
		this.contentBox.makeAbsolute();
		this.frozenContentBox = new ClientUI.common.box.Box(frozen);
		this.frozenContentBox.makeAbsolute();

		// create row template
		var ch = this.contentBox.getElement().firstChild;
		while(ch) {
			if(ch.tagName && ch.tagName.toLowerCase()=="table") {
				this.headerRow = new ClientUI.common.box.Box($(ch), null, true);
				break;
			}
			ch = ch.nextSibling;
		}
		ch = this.frozenContentBox.getElement().firstChild;
		while(ch) {
			if(ch.tagName && ch.tagName.toLowerCase()=="table") {
				this.headerFrozenRow = new ClientUI.common.box.Box($(ch), null, true);
				break;
			}
			ch = ch.nextSibling;
		}		
		
		this.helpObj = new ClientUI.common.box.Box(this.frozenContentBox.getElement(), null, true);

		var fcols = this.headerFrozenRow.getElement().getElementsByTagName("col");
		this.fcount = fcols.length;
		var cols = this.headerRow.getElement().getElementsByTagName("col");
		this.ncount = cols.length;
		var columns = new Array(this.fcount + this.ncount);
		var defaultWidth = 0;
		var defaultHeight = 0;
		
		var eventCellMouseDown = this.eventCellMouseDown.bind(this);
		
		// Get columns information
		var i = 0, h, j=0, cell;
		var cells = this.headerFrozenRow.getElement().rows[0].cells;
		var ids = this.grid.options.ids;
		for(i=0; i<this.fcount; i++) {
			cell = cells[i];
			columns[j] = {
				columnId: ids[i],
				col: fcols[i],
				width: parseInt(fcols[i].width),
				styleClass: cell.className,
				id: cell.id,
				align: cell.align,
				valign: cell.vAlign,
				title: cell.title,
				minWidth: 10,
				frozen: true,
				fixedWidth: Validators.getBoolean(cell.getAttribute("fixedWidth"), false),
				sortable: Validators.getBoolean(cell.getAttribute("sortable"), false),
				sorted: Validators.getBoolean(cell.getAttribute("sorted"), "desc")
			};
			
			if(columns[j].sortable)
				Event.observe(cell, 'click',  eventCellMouseDown);
			h = Element.getHeight(cell);
			if(h > defaultHeight) defaultHeight = h;
			defaultWidth += columns[j].width;
			columns[j].object = new ClientUI.common.box.InlineBox(cell, null, true);
			
			var details = this._getCellElements(j);
			// separator
			columns[j].sep = new ClientUI.common.box.InlineBox(details[0], null, true);
			columns[j].sep.getElement().columnIndex = j;
			if(!columns[j].fixedWidth) {
				Event.observe(columns[j].sep.getElement(), 'dblclick',  this.eventSepDblClick);
				Event.observe(columns[j].sep.getElement(), 'mousedown', this.eventSepMouseDown);
			}			
			else {
				sep.setStyle({cursor: 'auto'});
			}
			// sort icons
			//columns[j].sortIcon = new ClientUI.common.box.Box(details[1], null, true);
			//columns[j].sortDesc = new ClientUI.common.box.Box(details[1], null, true);
			//columns[j].sortAsc = new ClientUI.common.box.Box(details[2], null, true);
			j++;
		}

		cells = this.headerRow.getElement().rows[0].cells;
		for(i=0; i<this.ncount; i++) {
			cell = cells[i];

			columns[j] = {
				columnId: ids[i],
				col: cols[i],
				width: parseInt(cols[i].width),
				styleClass: cell.className,
				id: cell.id,
				align: cell.align,
				valign: cell.vAlign,
				title: cell.title,
				minWidth: 10,
				frozen: false,
				fixedWidth: Validators.getBoolean(cell.getAttribute("fixedWidth"), false),
				sortable: Validators.getBoolean(cell.getAttribute("sortable"), false),
				sorted: Validators.getBoolean(cell.getAttribute("sorted"), "desc")
			};
			
			if(columns[j].sortable)
				Event.observe(cell, 'click',  eventCellMouseDown);
			h = Element.getHeight(cell);
			if(h > defaultHeight) defaultHeight = h;
			defaultWidth += columns[j].width;
			columns[j].object = new ClientUI.common.box.InlineBox(cell, null, true);
			
			var details = this._getCellElements(j);
			// separator
			if (details[0]) {
				columns[j].sep = new ClientUI.common.box.InlineBox(details[0], null, true);
				columns[j].sep.getElement().columnIndex = j;
				if(!columns[j].fixedWidth) {
					Event.observe(columns[j].sep.getElement(), 'dblclick',  this.eventSepDblClick);
					Event.observe(columns[j].sep.getElement(), 'mousedown', this.eventSepMouseDown);
				}			
				else {
					sep.setStyle({cursor: 'auto'});
				}
				// sort icons
				//columns[j].sortIcon = new ClientUI.common.box.Box(details[1], null, true);
				//columns[j].sortDesc = new ClientUI.common.box.Box(details[1], null, true);
				//columns[j].sortAsc = new ClientUI.common.box.Box(details[2], null, true);
			}
			j++;
		}
		
		this._columns = columns;
		this.defaultHeight = defaultHeight;
		this.defaultWidth = defaultWidth;
		if(ClientUILib.isGecko) {
			this.defaultWidth -= this.getBorderWidth("lr") + this.getPadding("lr");
			this.defaultHeight -= this.getBorderWidth("tb") + this.getPadding("tb");
		}

		this.frozenSubstrate = new ClientUI.common.box.Box(this.gridId + ":hs", this.getElement());
		this.frozenSubstrate.getElement().name = this.getElement().id + "HRFrm";
		this.frozenSubstrate.setHeight(this.headerRow.getHeight());

		return true;
	},
	_getCellElements: function(column) {
		var details = new Array(3);
		var clientId = this.grid.getElement().id;
		details[0] = $(clientId + ":hsep_" + column);
		details[1] = $(clientId + ":hs_" + column);
		//details[1] = document.getElementById(clientId + ":hsortd_" + column);
		//details[2] = document.getElementById(clientId + ":hsorta_" + column);
		return details;
	},
	agjustSeparators: function() {
		var i=0;
		var fcells = this.headerFrozenRow.getElement().rows[0].cells;
		var ncells = this.headerRow.getElement().rows[0].cells;
		var delta = 4;
		if (ncells[0].offsetWidth == ncells[0].clientWidth) {
			delta--;
		}
		for(var j=0; j<this.fcount; i++,j++) {
			this._columns[i].sep.moveToX(fcells[j].offsetLeft + fcells[j].offsetWidth - delta);
		}
		var length = this.ncount - 1;
		for(var j=0; j<length; i++,j++) {
			this._columns[i].sep.moveToX(ncells[j].offsetLeft + ncells[j].offsetWidth - delta);
		}
	},
	updateSize: function() {
		var defaultWidth = 0, defaultHeight = 0;
		var i = 0, h, j=0;
		var columns = this._columns;
		var cols = $A(this.headerFrozenRow.getElement().getElementsByTagName("col"));
		var cells = $A(this.headerFrozenRow.getElement().rows[0].cells);		
		cells.each(function(cell) {
			if(i<columns.length) {
				columns[i].width = parseInt(cols[j].width);				
				h = Element.getHeight(cell);
				if(h > defaultHeight) defaultHeight = h;
				defaultWidth += columns[i].width;
			}
			i++;j++;
		});

		j=0;		
		cols = $A(this.headerRow.getElement().getElementsByTagName("col"));
		cells = $A(this.headerRow.getElement().rows[0].cells);		
		cells.each(function(cell) {
			if(i<columns.length) {
				columns[i].width = parseInt(cols[j].width);
				
				h = Element.getHeight(cell);
				if(h > defaultHeight) defaultHeight = h;
				defaultWidth += columns[i].width;
			}
			i++;j++;
		});
		
		this.defaultHeight = defaultHeight;
		this.defaultWidth = defaultWidth;
		
		this.setHeight(this.defaultHeight);
		this.setWidth(this.defaultWidth);		
		this.agjustSeparators();
		this.updateHeaders();
	},
	updateLayout: function($super) {
		if(!this.controlCreated || !this.grid.controlCreated) {
			return;
		}
		$super();
		var height = this.getHeight();
		var totalWidth = this.grid.getColumnsTotalWidth();
		var frozenContentWidth = this.grid.getColumnsFrozenWidth();

		this.contentBox.setWidth(Math.max(this.getWidth(), totalWidth));
		this.contentBox.setHeight(height);		
		this.contentBox.moveTo(frozenContentWidth - this.grid.getScrollOffset(), 0);
		this.headerRow.setHeight(height);
		this.frozenContentBox.setWidth(frozenContentWidth);
		this.frozenContentBox.setHeight(height);
		this.frozenContentBox.moveTo(0, 0);
		this.headerFrozenRow.setHeight(height);
		this.frozenSubstrate.setWidth(frozenContentWidth);
		this.updateHeaders();
	},
	getColumns: function() {
		return this._columns;
	},
	// lets implement column resizer
	OnSepMouseDown: function(event) {
		this.dragColumnInfo = {
			srcElement: Event.element(event),
			dragStarted: false,
			mouseDown: true,
			startX: Event.pointerX(event),
			originalX: 0
		};		
		this.dragColumnInfo.object = this.getColumns()[this.dragColumnInfo.srcElement.columnIndex].object;
		this.dragColumnInfo.sep = this.getColumns()[this.dragColumnInfo.srcElement.columnIndex].sep;
		this.dragColumnInfo.minWidth = this.getColumns()[this.dragColumnInfo.srcElement.columnIndex].minWidth;
		
		Event.stop(event);
	},
	OnSepMouseUp: function(event) {
		if(this.dragColumnInfo && this.dragColumnInfo.dragStarted) {
			this.dragColumnInfo.dragStarted = false;
			this.dragColumnInfo.mouseDown = false;
			var delta = Event.pointerX(event) - this.dragColumnInfo.startX;
			var newWidth = this.dragColumnInfo.object.getWidth() + delta;
			if(newWidth < this.dragColumnInfo.minWidth) {
				newWidth = this.dragColumnInfo.minWidth;
			}
			setTimeout(function() {
				this.grid.adjustColumnWidth(this.dragColumnInfo.srcElement.columnIndex, newWidth); 
			}.bind(this), 10);
		}
		this._hideSplitter();
	},
	OnSepMouseMove: function(event) {
		if(this.dragColumnInfo && this.dragColumnInfo.mouseDown) {
			if(!this.dragColumnInfo.dragStarted) {
				this.dragColumnInfo.dragStarted = true;
				this._showSplitter(this.dragColumnInfo.srcElement.columnIndex);
			}
			else {
				var delta = Event.pointerX(event) - this.dragColumnInfo.startX;
				var minColumnWidth = this.dragColumnInfo.object.getWidth() - this.dragColumnInfo.minWidth;
				if(delta >= -minColumnWidth) {
					var x = this.dragColumnInfo.originalX + delta;
					this.columnSplitter.moveToX(x - 6);
				}
			}
			Event.stop(event);
		}
	},
	OnSepDblClick: function(event) {
		ClientUILib.log(ClientUILogger.INFO, "OnSepDblClick");
	},
	_showSplitter: function(index) {
		if(!this.columnSplitter) {
			this._createSplitter();
		}

		var pos = this.dragColumnInfo.sep.getX();
		if(!this.getColumns()[index].frozen) {
			pos += this.grid.getColumnsFrozenWidth() - this.grid.getScrollOffset();
		}
		this.dragColumnInfo.originalX = pos;
		this.columnSplitter.show();
		this.columnSplitter.setHeight(this.defaultHeight + this.grid.getBody().contentBox.getHeight());
		this.columnSplitter.moveTo(pos, 0);
	},
	_hideSplitter: function() {
		if(this.columnSplitter) {
			this.columnSplitter.hide();
		}
	},
	_createSplitter: function() {
		this.columnSplitter = new ClientUI.common.box.Box(this.gridId +":cs", this.grid.getElement());
		this.columnSplitter.makeAbsolute();
		this.columnSplitter.setWidth(10);
	},
	adjustScrollPosition: function(pos) {
		this.contentBox.moveToX(this.grid.getColumnsFrozenWidth()-pos);
	},
	OnCellMouseDown: function(event) {
		var el = Event.element(event);
		while(el && !Element.hasClassName(el, "dr-sdt-hc")) {
			el = el.parentNode;
		}
		
		if(el) {
			var index = parseInt(el.getAttribute("columnindex"));
			if(index>=0) {
				/*
				var dir = this.getColumns()[index].sorted;
				dir = (dir == "asc") ? "desc" : "asc";
				this.getColumns()[index].sorted = dir;
				
		        for(var i = 0, len = this.getColumns().length; i < len; i++) {
		            var h = this.getColumns()[i];
		            if(h.sortDesc && h.sortAsc) {
			            if(i != index) {
							Element.setStyle(h.sortDesc.getElement(), {display: 'none'});
							Element.setStyle(h.sortAsc.getElement(), {display: 'none'});
			            } else{
			            	h.sortDesc.moveTo(h.object.getWidth() - 16, 4);
			            	h.sortAsc.moveTo(h.object.getWidth() - 16, 4);
							Element.setStyle(h.sortDesc.getElement(), {display: (dir == 'desc' ? 'block' : 'none')});
							Element.setStyle(h.sortAsc.getElement(), {display: (dir == 'asc' ? 'block' : 'none')});
			            }
		            }
		        }
				*/
				var rows = this.grid.getBody().templFrozen.getElement().rows;
				var startRow = rows && rows.length>0 ? this.grid.getBody()._getRowIndex(rows[0].id) : 0;
				this.grid.eventOnSort.fire({
					column: index,
					/*order: dir,*/
					startRow: startRow,
					index: this.grid.getBody().currRange.start
				});
				Event.stop(event);
			}
		}		
	},
	
	updateHeaders : function() {
		var cols = this.getColumns();
		for(var i = 0; i < cols.length; i++) {
			var col = cols[i];
			var th = col.object;
			var icon = this._getCellElements(i)[1];
			
			if (icon) {
				var newPosX = th.getWidth() - icon.getWidth();
				var newPosY = (th.getHeight() - icon.getHeight())/2;
				
				newPosX = Math.floor(newPosX);
				newPosY = Math.floor(newPosY);
				
				icon.setStyle({left: newPosX + "px",top: newPosY + "px", visibility : "inherit"});
			}
		}
	},
	
	adjustColumnWidth: function(column, width) {
		this._columns[column].col.width = width>0 ? width : 1;
		if(width<=0) this.getColumns()[column].sep.hide();
		this._columns[column].width = width;
	},
	
	setFakeColumnWidth: function() {
		this._columns.last().col.width = 1;
		var width = this.grid.getElement().offsetWidth - this.headerRow.getElement().offsetWidth;
		if (width < 1) {
			width = 1;
		}
		this._columns.last().col.width = width;
	},
	
	hideColumn: function(index, frozen) {
		var column = this._columns.splice(index,1)[0];
		var input = $(this.grid.getElement().id + "_hc")
		input.value = input.value + column.columnId + ",";
		column.col.parentNode.removeChild(column.col);
		column.bodyCol.parentNode.removeChild(column.bodyCol);
		column.footerCol.parentNode.removeChild(column.footerCol);
		var rows;
		if(frozen) {
			rows = this.headerFrozenRow.getElement().rows;
		} else {
			rows = this.headerRow.getElement().rows;
		}
		for(var i=0; i<rows.length; i++) {
			rows[i].removeChild(rows[i].cells[index]);
		}
	}	
});

Object.extend(ClientUI.controls.grid.GridHeader.prototype, {
	sepStyleClass: "dr-sdt-hsep", 
	// internal variables
	_columns: []	
});/**
 * InlineBox.js		Date created: 6.04.2007
 * Copyright (c) 2007 Exadel Inc.
 * @author Denis Morozov <dmorozov@exadel.com>
 */
ClientUILib.declarePackage("ClientUI.common.box.SplashBox");

ClientUILib.requireClass("ClientUI.common.box.Box");
ClientUILib.requireClass("ClientUI.common.box.Substrate");

/**
 * Base class that wrap work with inline blocks like span
 */
ClientUI.common.box.SplashBox = Class.create(ClientUI.common.box.Box, {

	initialize: function($super, text, parentElement, zIndex, supressSplash) {
		$super(null, parentElement);
		this.moveTo(-1000, -1000);
		this.zIndex = zIndex || 500;
		this.supressSplash = supressSplash || false;
		this.createControl(text);
		this.hide();
	},
	createControl: function(text) {
		this.element.setStyle({zIndex: this.zIndex});
		this.makeAbsolute();
		
		this.frame = new ClientUI.common.box.Substrate(null, this.getElement());
		this.frame.getElement().setStyle({zIndex: this.zIndex + 1});
		this.frame.getElement().setStyle({backgroundColor: "#FFFFFF"});
		this.frame.getElement().setStyle({border: "medium none"});
		this.frame.getElement().setStyle({opacity: "0"});
		this.frame.moveTo(0, 0);
		this.frame.setWidth(this.getWidth());
		this.frame.setHeight(this.getHeight());
		
		if(!this.supressSplash) {			
			this.div = new ClientUI.common.box.Box(null, this.getElement());
			this.div.getElement().setStyle({zIndex: this.zIndex + 2});
			this.div.getElement().setStyle({backgroundColor: "#000000"});
			this.div.getElement().setStyle({opacity: "0.6"});
			this.div.makeAbsolute();
			this.div.moveTo(0, 0);
			this.div.setWidth(this.getWidth());
			this.div.setHeight(this.getHeight());
			this.div.show();
		}

		this.text = new ClientUI.common.box.Box(text, this.getElement());
		this.text.setParent(this.getElement());
		this.text.getElement().setStyle({zIndex: this.zIndex + 100});
		if(!text)
			this.text.getElement().innerHTML = "<font size='4' color='white'>Loading...</font>";
		this.text.makeAbsolute();
		this.text.show();
		
	},
	setSize: function(width, height) {
		this.setWidth(width);
		this.setHeight(height);
		this.containerWidth = width;
		this.containerHeight = height;
	},
	setImage: function(image) {
		if(!this.img) {
			this.img = new ClientUI.common.box.Box($(document.createElement("img")), this.div.getElement());
			this.img.makeAbsolute();
		}		
		this.img.getElement().src = image;
		this.text.hide();
	},
	updateLayout: function() {
		var width = this.containerWidth||this.getWidth();
		var height = this.containerHeight||this.getHeight();

		this.frame.setWidth(width);
		this.frame.setHeight(height);
		
		if(!this.supressSplash) {
			this.div.setWidth(width);
			this.div.setHeight(height);		
		}
		
		if(this.img) {
			var x = width/2 - this.img.getWidth()/2;
			var y = height/2 - this.img.getHeight()/2;		
			this.img.moveTo(x, y);
		}
		else {
			var x = width/2 - this.text.getWidth()/2;
			var y = height/2 - this.text.getHeight()/2;		
			this.text.moveTo(x, y);
		}
	}
});
/*
 * TODO: Copyright (c) 2007 Denis Morozov <dmorozov@exadel.com>
 *
 * ...
 */
ClientUILib.declarePackage("ClientUI.controls.grid.DataCash");

/*
 * DataCash.js - Implements client side data cashing 
 * by Denis Morozov <dmorozov@exadel.com> distributed under the BSD license. 
 *
 */
ClientUI.controls.grid.DataCash = Class.create({
	
	initialize: function(cashSize) {
		var count = parseInt(cashSize/this.PKG_SIZE + 1);
		this.cash = new Array(count);
		for(var i=0; i<count; i++) {
			this.cash[i] = new Array(this.PKG_SIZE);
		}
	},
	getRow: function(index) {
		var i = parseInt(index/this.PKG_SIZE), j = index%this.PKG_SIZE;
		return this.cash[i][j];
	},
	setRow: function(index, row) {
		var i = parseInt(index/this.PKG_SIZE), j = index%this.PKG_SIZE;
		this.cash[i][j] = row;
	}
});

Object.extend(ClientUI.controls.grid.DataCash.prototype, {
	PKG_SIZE: 20
});/*
 * TODO: Copyright (c) 2007 Denis Morozov <dmorozov@exadel.com>
 *
 * ...
 */
ClientUILib.declarePackage("ClientUI.controls.grid.GridBody");

ClientUILib.requireClass("ClientUI.common.box.Box");
//ClientUILib.requireClass("ClientUI.controls.grid.DataCash");

/*
 * GridHeader.js - Grid control header pane
 * TODO: add comments
 */
ClientUI.controls.grid.GridBody = Class.create(ClientUI.common.box.Box, {
	/**
	 * Constructor
	 * @param {Object} template for Grid body row
	 * @param {Object} grid parent grid object
	 */
	initialize: function($super, template, grid) {
		this.grid = grid;
		this.gridId = grid.getElement().id;
		$super(template);

		// declare event listeners
		this._eventOnHScroll = this._onContentHScroll.bind(this);
		this._eventOnVScroll = this._onContentVScroll.bind(this);
		this._eventOnDataReady = this._onDataReady.bind(this);
		
		this.createControl(template);
		this.registerEvents();
	},
	registerEvents: function() {
		Event.observe(this.scrollBox.eventHScroll, "grid body hscroll", this._eventOnHScroll);
		Event.observe(this.scrollBox.eventVScroll, "grid body vscroll", this._eventOnVScroll);
		Event.observe(this.grid.dataModel.eventDataReady, "grid data is loaded", this._eventOnDataReady);
	},
	destroy: function() {
		Event.stopObserving(this.scrollBox.eventHScroll, "grid body hscroll", this._eventOnHScroll);
		Event.stopObserving(this.scrollBox.eventVScroll, "grid body vscroll", this._eventOnVScroll);
		Event.stopObserving(this.grid.dataModel.eventDataReady, "grid data is loaded", this._eventOnDataReady);
	},
	// event listeners
	_onContentHScroll: function(xpos) {
		this.grid.adjustScrollPosition(xpos);
	},
	_onDataReady: function(options) {
		// load rows data		
		window.loadingUpdateTime = (new Date()).getTime();

		this.invalidate(options);
		
		window.loadingInvalidateTime = (new Date()).getTime();
	},
	_onContentVScroll: function(ypos) {
		this.helpObject1.moveToY(this.sizeBox.getHeight()+ this.defaultRowHeight + 5);
		this.helpObject2.moveToY(this.sizeBox.getHeight()+ this.defaultRowHeight + 5);
		this.setScrollPos(ypos);
		this.adjustDataPosition(ypos);
	},
	createControl: function(template) {

		var childs = template.childNodes;
		for(var i=0; i<childs.length; i++) {
			if(childs[i].id == this.gridId + ":bc") {
				this.container = new ClientUI.common.box.Box(childs[i], null, true);
				this.container.makeAbsolute();
				this.container.setStyle({'z-index' : 20});
				if(!ClientUILib.isIE) this.container.setStyle({overflow: 'hidden'});
				break;
			}
		}
		
		// create scroll box
		this.scrollBox = new ClientUI.common.box.ScrollableBox(this.gridId + ":scb", this.getElement());
		this.scrollBox.setWidth(this.getWidth());
		this.scrollBox.setHeight(this.getHeight());		
		this.sizeBox = new ClientUI.common.box.Box(this.gridId + ":sb", this.scrollBox.getElement());
		
		var normal = null, frozen = null;
		var childs = this.container.getElement().childNodes;
		for(var i=0; i<childs.length; i++) {
			if(childs[i].id && childs[i].id.indexOf("FrozenBox")>=0) {
				frozen = childs[i];
			}
			else if(childs[i].id && childs[i].id.indexOf("NormalBox")>=0){
				normal = childs[i];
			}
		}

		if(!normal || !frozen) {
			errMsg = "Unable to parse template for GridBody. Unable to find FrozenBox or NormalBox.";
			ClientUILib.log(ClientUILogger.ERROR, errMsg);
			throw(errMsg);
		}

		this.contentBox = new ClientUI.common.box.Box(normal);
		this.contentBox.makeAbsolute();
		this.frozenContentBox = new ClientUI.common.box.Box(frozen);
		this.frozenContentBox.makeAbsolute();
		
		this.helpObject1 = new ClientUI.common.box.Box(this.gridId + ":nho", this.contentBox.getElement());
		this.helpObject2 = new ClientUI.common.box.Box(this.gridId + ":fho", this.frozenContentBox.getElement());
		
		// create row template
		var ch = this.frozenContentBox.getElement().firstChild;
		while(ch) {
			if(ch.tagName && ch.tagName.toLowerCase()=="table") {
				this.templFrozen = new ClientUI.common.box.Box($(ch), null, true);
				this.templFrozen.makeAbsolute();
				break;
			}
			ch = ch.nextSibling;
		}
		ch = this.contentBox.getElement().firstChild;
		while(ch) {
			if(ch.tagName && ch.tagName.toLowerCase()=="table") {
				this.templNormal = new ClientUI.common.box.Box($(ch), null, true);
				this.templNormal.makeAbsolute();
				break;
			}
			ch = ch.nextSibling;
		}

		this.parseTemplate(this.templFrozen.getElement(), this.templNormal.getElement());

		// init cash with initial data		
		/*var cash = this.getCash();
		var frows = this.templFrozen.getElement().rows;
		var nrows = this.templNormal.getElement().rows;
		var count = this.rowsCount;
		for(var i=0; i<count; i++) {
			cash.setRow(i, {f: frows[i].innerHTML, n: nrows[i].innerHTML});
		}*/
		var gridId = this.grid.getElement().id;
		this.fTable = $(gridId + ":f")
		this.nTable = $(gridId + ":n")
		this.controlCreated = true;		
		this.sizeBox.setHeight(this.templNormal.getElement().offsetHeight);
	},
	parseTemplate: function(templFrozen, templNormal) {
		if(!templNormal || !templNormal.rows || templNormal.rows.length===0) {
			return false;
		}
		
		var columns = this.grid.getHeader().getColumns();
		var i=0, j=0;
		var cols = this.templFrozen.getElement().getElementsByTagName("col");
		for(i=0; i<cols.length; i++) {
			columns[j].bodyCol = $(cols[i]);
			j++;
		}
		cols = this.templNormal.getElement().getElementsByTagName("col");
		for(i=0; i<cols.length; i++) {
			columns[j].bodyCol = $(cols[i]);
			j++;
		}
				
		this.rowsCount = Math.min(templNormal.rows.length, this.grid.dataModel.getCount());

		//if(ClientUILib.isGecko) {
		//	this.defaultRowHeight -= this.getBorderWidth("tb") + this.getPadding("tb");
		//}
		this.helpObj = new ClientUI.common.box.Box(templFrozen, null, true);
		this.countToLoad = 0;
		this.startRow = 0;
		this.startIndex = 0;
		this.currRange = $R(0, this.rowsCount);	
		
		return true;		
	},
	setScrollPos: function(pos) {
		this.contentBox.getElement().scrollTop = pos;
		this.frozenContentBox.getElement().scrollTop = pos;
		if(ClientUILib.isIE && !ClientUILib.isIE7) {
			this.contentBox.getElement().scrollTop = pos;
			this.frozenContentBox.getElement().scrollTop = pos;
		}
	},
	updateSize: function() {
		
		var defHeight = this._calcDefaultRowHeight();
		
		if (isFinite(defHeight)) {
			this.defaultRowHeight = defHeight;
		}
		
		var row = this.templNormal.getElement().rows[0];
		if(row) {
			this.defaultRowHeight = Element.getHeight(row.cells[0]);
		}
		//if(ClientUILib.isGecko) {
		//	this.defaultRowHeight -= this.getBorderWidth("tb") + this.getPadding("tb");
		//}	
	},
	updateLayout: function($super) {
		if(!this.controlCreated || !this.grid.controlCreated) {
			return;
		}
		$super();
		if(!this.scrollBox || !this.contentBox || !this.sizeBox) {
			return;			
		}

		var totalWidth = this.grid.getColumnsTotalWidth();
		this.scrollBox.moveTo(0, 0);
		this.scrollBox.setWidth(this.getWidth());
		this.scrollBox.setHeight(this.getHeight());		
		
		var scrollLeft = this.grid.getScrollOffset();
		var height = this.scrollBox.getHeight();
		var fixH = this.grid.getFooter() ? this.grid.getFooter().getHeight() : 0;
		if(fixH > height) fixH = 0;
		
		var frozenContentWidth = this.grid.getColumnsFrozenWidth();
		
		this.frozenContentBox.moveTo(0, 0);
		this.contentBox.moveTo(frozenContentWidth, 0);		
		this.sizeBox.moveTo(0, 0);
		this.sizeBox.setWidth(totalWidth);

		this.scrollBox.setWidth(this.getWidth()+1);
		this.scrollBox.setHeight(this.getHeight()+1);
		this.scrollBox.setWidth(this.getWidth());
		this.scrollBox.setHeight(this.getHeight());
		var viewWidth = this.scrollBox.getViewportWidth();
		this.container.setWidth(viewWidth);

		if(ClientUILib.isIE) {
			this.contentBox.setWidth(viewWidth - frozenContentWidth);
		}
		else {
			this.contentBox.setWidth(Math.max(this.getWidth(), totalWidth));
		}
		
		height = this.scrollBox.getElement().clientHeight;
				
		this.contentBox.setHeight(height - fixH);		
		this.frozenContentBox.setWidth(frozenContentWidth);
		this.frozenContentBox.setHeight(height - fixH);
		this.container.setHeight(height - fixH);
		
		this.defaultRowHeight = this._calcDefaultRowHeight();
		this.sizeBox.setHeight(this.defaultRowHeight * this.grid.dataModel.getCount() + fixH);
		this.helpObject1.moveToY(this.sizeBox.getHeight()+ this.defaultRowHeight + 5);
		this.helpObject2.moveToY(this.sizeBox.getHeight()+ this.defaultRowHeight + 5);

		var scrollPos = Math.min(totalWidth - viewWidth, scrollLeft);
		this.grid.adjustScrollPosition(scrollPos);
		
		this.dataVisible = parseInt(this.contentBox.getHeight() / this.defaultRowHeight, 10) + 1;
		this.dataVisible = Math.min(this.dataVisible, this.rowsCount);
		if(height > 0) {
			this.adjustDataPosition(this.currentPos);	
		}
	},
	adjustScrollPosition: function(pos) {
		this.templNormal.moveToX(-pos);
	},
	getScrollYPosition: function() {
		return this.contentBox.getElement().scrollTop;
	},
	adjustDataPosition: function (pos) {		
		if(this.currentPos == pos) { 
			return;
		}
		
		// 1. calculate direction and range to load next data
		this.processedPos = pos;
		var forwardDir = (this.currentPos <= pos) ? true : false;
		
		// first visible row index
		var first = parseInt(pos / this.defaultRowHeight) - 1;
		if(first < 0) first = 0;
		
		// check direction and predict some next rows
		var from = Math.max(first - (forwardDir ? 1 : (this.rowsCount - this.dataVisible - 1)), 0);
		var to = Math.min(first + (forwardDir ? this.rowsCount-1 : this.dataVisible + 1), this.grid.dataModel.getCount());

		if(from == 0) {
			to = this.rowsCount;
		}
		else if(to == this.grid.dataModel.getCount()) {
			from = to - this.rowsCount;
		}

		var range = $R(from, to);
		
		if(this.currRange.start == from && this.currRange.end == to) {
			return;
		}
		
		if(from >= to) {
			ClientUILib.log(ClientUILogger.WARNING, "!!! GridBody: adjustDataPosition. Pos: " + pos + ", From:" + from + ", To:" + to);
			return;			
		}

		var task = this._getPendingTask();		
		if(to - from > 0) {
			task.timer = null;
			task.from = from;
			task.to = to;
			task.first = first;
			task.pos = pos;
			this._setPendingTask(task);
		}
	},	
	_getPendingTask: function() {
		if(!this.pendingTask) {
			this.pendingTask = {
				timer: null,
				rowsToLoad: [],
				rowsToLoadIdx: [],
				from: 0,
				to: 0,
				first: 0,
				pos: 0
			};
		}
		return this.pendingTask;
	},	
	_setPendingTask: function(task) {
		clearTimeout(this.pendingTask.timer);
		this.pendingTask.timer = null;
		this.pendingTask = task;
	
		// and plan other agjusting over the time
		task.timer = setTimeout(function() {
			this.startLoadData();
		}.bind(this), this.grid.dataModel.getRequestDelay());
	},
	adjustColumnWidth: function(column, width) {
		var columns = this.grid.getHeader().getColumns();
		columns[column].bodyCol.width = width>0 ? width : 1;
	},
	
	setFakeColumnWidth: function() {
		var columns = this.grid.getHeader().getColumns();
		var bodyCell = columns.last().bodyCol;
		if(bodyCell) bodyCell.width = 1;
		var width = this.grid.getElement().offsetWidth - this.fTable.offsetWidth - this.nTable.offsetWidth;
		if (width < 1) {
			width = 1;
		}
		if(bodyCell) bodyCell.width = width;
	},
	
	startLoadData: function() {
		if(this.updateStarted) {
			this._setPendingTask(this._getPendingTask());
			return;
		}

		this.updateStarted = true;
		var task = this._getPendingTask();
		var range = $R(task.from, task.to);
		var switchType = 5;
		var startIndex = 0;
		var startRowIndx = 0;
		var countToLoad = 0;
		
		// if we have intersepted ranges than rearrange rows
		// in other case just move rows table to first position
		if(this.currRange.end < range.start 
			|| this.currRange.start > range.end) {
			switchType = 0;
		}
					
		if(switchType === 0) {
			startRowIndx = this._getRowIndex(this.templFrozen.getElement().rows[0].id);
			startIndex = range.start;
			countToLoad = range.end - range.start;
		}
		else {
			var i, row, rownew, cloned;
			countToLoad = 0;
			var frozenTbl = this.templFrozen.getElement();
			var normalTbl = this.templNormal.getElement();
			if(range.start > this.currRange.start 
				&& range.start < this.currRange.end) {					
				switchType = 1;
				countToLoad = range.start - this.currRange.start;
				if(countToLoad > 0) {
					startRowIndx = this._getRowIndex(frozenTbl.rows[0].id);
					startIndex = this.currRange.end;
				}
			}
			else if(range.start == this.currRange.start) {
				switchType = 3;
				countToLoad = range.end - this.currRange.end;
				if(countToLoad > 0) {
					startIndex = this.currRange.end;
					var restCount = this.rowsCount - countToLoad;
					startRowIndx = this._getRowIndex(frozenTbl.rows[restCount].id);
				}
			}
			else {
				switchType = 2;
				countToLoad = this.currRange.start - range.start;
				if(countToLoad > 0) {
					startIndex = range.start;
					var restCount = this.rowsCount - countToLoad;
					startRowIndx = this._getRowIndex(frozenTbl.rows[restCount].id);
				}
			}
		}

		var process = true;
		if(startIndex > (task.first + this.dataVisible) ||
			(startIndex + countToLoad) < task.first) {
			process = false;
		}
		if(countToLoad > 0 && process) {
			this.updateStarted = true;
			ClientUILib.log(ClientUILogger.WARNING, "Start loading: index: " + startIndex + ", and startRow: " + startRowIndx + ", and count: " + countToLoad);
			this.currRange = range;
			this.currentPos = task.pos;
			
			
			if (this.grid.options.hideWhenScrolling) {
				this.container.hide();
			}
			
			var options = {
					index: startIndex,
					count: countToLoad,
					startRow: startRowIndx,
					switchType: switchType
			};
			var opt = {
					index: options.index,
					count: options.count,
					startRow: options.startRow,
					switchType: options.switchType
			};
			//options = this.processCashedValues(options);
			if(options.count > 0) {
				// Make timer to handle quick clicks on scrollbar arrows
				setTimeout(function() {
					
					// 4. start data loading				
					this.updateInterval = screen.updateInterval;
					screen.updateInterval = 1000;
									
					this.grid.dataModel.loadRows(options);
				}.bind(this), 10);
			}
			/*else {
				this.invalidate(opt);
				this.updateStarted = false;
			}*/			
		}
		else {
			this.updateStarted = false;
		}
	},
	forceReRender: function() {
		if(ClientUILib.isIE && !ClientUILib.isIE7) {
			var frozenTbl = this.templFrozen.getElement();
			var normalTbl = this.templNormal.getElement();
			// force to rerender table !!!
			var tr = frozenTbl.insertRow();
			frozenTbl.deleteRow(tr.rowIndex);
			tr = normalTbl.insertRow();
			normalTbl.deleteRow(tr.rowIndex);
		}
	},
	rearrangeRows: function(options, updateCash, showContainer) {
		var frozenTbl = this.templFrozen.getElement();
		var normalTbl = this.templNormal.getElement();
		//var cash = this.getCash();
	
		if(options.switchType === 0) {
			var visibleRowPos = this.defaultRowHeight * options.index;
			var test = this.contentBox.getElement().scrollTop;
			if(showContainer) this._showContainer();
			this.templFrozen.moveToY(visibleRowPos);
			this.templNormal.moveToY(visibleRowPos);
			
			/*if(updateCash) {
				var frows = frozenTbl.rows;
				var nrows = normalTbl.rows;
				var count = frows.length;
				var index = options.index;
				for(var i=0; i<count; i++) {
					cash.setRow(index+i, {f: frows[i].innerHTML, n: nrows[i].innerHTML});
				}
			}*/
			
			this.forceReRender();
		}
		else if(options.switchType === 1 || options.switchType === 2) {
			// store visible row pos to restore after rows reerrange
			var count = frozenTbl.rows.length;
			var frows = new Array(count), nrows = new Array(count);
			var j = 0, i;
			var index = options.index;

			if(options.switchType === 1) {
				for(i=options.count; i<this.rowsCount; i++) {
					frows[j] = frozenTbl.rows[i];
					nrows[j] = normalTbl.rows[i];
					j++;
				}
				for(i=0; i<options.count; i++) {
					frows[j] = frozenTbl.rows[i];
					nrows[j] = normalTbl.rows[i];
					/*if(updateCash) {
						cash.setRow(index+i, {f: frows[j].innerHTML, n: nrows[j].innerHTML});
					}*/
					j++;
				}
			}
			else {
				for(i=this.rowsCount - options.count; i<this.rowsCount; i++) {
					frows[j] = frozenTbl.rows[i];
					nrows[j] = normalTbl.rows[i];
					/*if(updateCash) {
						cash.setRow(index+j, {f: frows[j].innerHTML, n: nrows[j].innerHTML});
					}*/
					j++;
				}
				for(i=0; i<this.rowsCount - options.count; i++) {
					frows[j] = frozenTbl.rows[i];
					nrows[j] = normalTbl.rows[i];
					j++;
				}
			}
			
			
			// Mozilla is faster when doing the DOM manipulations on
			// an orphaned element. MSIE is not	
			var removeChilds = navigator.product == "Gecko";
			var fbody = frozenTbl.tBodies[0];
			var nbody = normalTbl.tBodies[0];
			var fnextSibling = fbody.nextSibling;
			var nnextSibling = nbody.nextSibling;
			
			if (removeChilds) { // remove all rows
				fp = fbody.parentNode;
				fp.removeChild(fbody);
				np = nbody.parentNode;
				np.removeChild(nbody);
			}
				
			// insert in the new order
			for (i = 0; i < count; i++) {
				fbody.appendChild(frows[i]);
				nbody.appendChild(nrows[i]);
			}
		
			if(removeChilds) {
				fp.insertBefore(fbody, fnextSibling);
				np.insertBefore(nbody, nnextSibling);
			}

			var visibleRowPos = (options.switchType == 1) ? this.currRange.start * this.defaultRowHeight : options.index * this.defaultRowHeight;
			if(showContainer) this._showContainer();
			this.templFrozen.moveToY(visibleRowPos);
			this.templNormal.moveToY(visibleRowPos);
		}
		else {
			/*if(updateCash) {
				var frows = frozenTbl.rows;
				var nrows = normalTbl.rows;
				var count = frows.length;
				var index = options.index;
				for(var i=0; i<count; i++) {
					cash.setRow(index+i, {f: frows[i].innerHTML, n: nrows[i].innerHTML});
				}
			}*/
			var visibleRowPos = this.currRange.start * this.defaultRowHeight;
			if(showContainer) this._showContainer();
			this.templFrozen.moveToY(visibleRowPos);
			this.templNormal.moveToY(visibleRowPos);
		}
	},
	_showContainer: function() {
		this.container.show();
		if (ClientUILib.isIE) {
			this.setScrollPos(this.currentPos);	
		}
	},
	/**
	 * show hiden rows after loading them from datasource
	 * @param {Object} options
	 */
	invalidate: function(options) {
		screen.updateInterval = this.updateInterval;
		this.rearrangeRows(options, true, true);
		this.container.show();
		this.updateStarted = false;
		/*if(this.processedPos != this.currentPos) {
			this.currentPos = this.processedPos;
			setTimeout(function (){
				this.pendedUpdate();
			}.bind(this), this.grid.dataModel.getRequestDelay());
		}*/
	},
	/*pendedUpdate: function() {
		if(this.processedPos != this.currentPos) {
			this.currentPos = this.processedPos;
			setTimeout(function (){
				this.pendedUpdate();
			}.bind(this), this.grid.dataModel.getRequestDelay());
		}
		else {
			this.adjustDataPosition(this.processedPos);
		}
	},*/
	/*getCash: function() {
		if(!this.cash) {
			this.cash = new ClientUI.controls.grid.DataCash(this.grid.dataModel.getCount());
		}
		return this.cash;
	},
	_restoreFromCash: function(options) {
		var count = options.count;
		var index = options.index;
		var startRow = options.startRow;
		var cash = this.getCash();
		var frows = this.templFrozen.getElement().rows;
		var nrows = this.templNormal.getElement().rows;
	
		var row, rowU, i=0;
		var rowC = cash.getRow(index);
		do {
			row = frows[startRow];
			rowU = row.cloneNode(true);
			rowU.innerHTML = rowC.f;
			row.parentNode.replaceChild(rowU, row);

			row = nrows[startRow];
			rowU = row.cloneNode(true);
			rowU.innerHTML = rowC.n;
			row.parentNode.replaceChild(rowU, row);
		
			i++;
			startRow++;
			if(startRow >= this.rowsCount) startRow = 0;
			rowC = cash.getRow(index + i);
		} while(i<count && rowC);	
		
		setTimeout(function () {
			this.rearrangeRows(options);
			this.container.show();
			this.splash.hide();
			this.updateStarted = false;
			
		}.bind(this), 10);
	},*/
	processCashedValues: function(options) {
		return options;
		
		var opt = {switchType: options.switchType };
		var cash = this.getCash();
		var count = options.count;
		var index = options.index;
		var startRow = options.startRow;

		var i = 0;
		var rowC;
		
		while(i<count && (rowC = cash.getRow(index + i))!=null) i++;
		if(i>0) { // there are cashed rows from start
			opt.count = i;
			opt.index = index;
			opt.startRow = startRow;
			this._restoreFromCash(opt);
			
			options.count -= i;
			options.index = index+i;
			options.startRow = startRow+i;
			if(options.startRow >= this.rowsCount) options.startRow -= this.rowsCount;
		}
		
		var cnt = 0;
		while(i<count && !(rowC = cash.getRow(index + i))) { i++; cnt++; }
		if(i<count) { // there are cashed rows at the end of range
			opt.count = options.count - cnt;
			opt.index = index+i;
			opt.startRow = startRow+i;
			if(opt.startRow >= this.rowsCount) opt.startRow -= this.rowsCount;
			this._restoreFromCash(opt);
			
			options.count = cnt;
			options.index = index+(i-cnt);
			options.startRow = startRow+(i-cnt);
			if(options.startRow >= this.rowsCount) options.startRow -= this.rowsCount;
		}
		
		return options;
	},
	getCellText: function(row, column) {		
		var tbl = this.templFrozen.getElement().rows[row].cells;
		var fcount = tbl.length;
		var index = column;
		if(index >= fcount) {
			index -= fcount;
			tbl = this.templNormal.getElement().rows[row].cells;
		}
		
		var cell = document.getElementsByClassName("dr-sdt-bcbody", tbl[index]);
		return (cell && cell.length && cell.length>0) ? cell[0].innerHTML : "";
	},
	ensureVisible: function (index) {
		if(index>=0 && index<this.grid.dataModel.getCount()) {
			
			var visibleRows = parseInt(this.contentBox.getHeight() / this.defaultRowHeight, 10) + 1;
			if(this.grid.dataModel.getCount() > visibleRows) {
				var y = index*this.defaultRowHeight;
				this.scrollBox.getElement().scrollTop = y;
				this.currentPos = 0;
				this._onContentVScroll(this.scrollBox.getElement().scrollTop);
			}
		}
	},
	reloadData: function() {
		this.currentPos = -(this.rowsCount*this.defaultRowHeight);
		this.scrollBox.getElement().scrollTop = 0;
		this.currRange.start = -this.rowsCount;
		this.currRange.end = -1;
		this._onContentVScroll(0);
	},	
	_getRowIndex: function(rowId) {
      	return Number(rowId.split(this.grid.getElement().id)[1].split(":")[2]);
	},
	
	hideColumn: function(index, frozen) {
		var rows;
		if(frozen) {
			rows = this.templFrozen.getElement().rows;
		} else {
			rows = this.templNormal.getElement().rows;
		}
		for(var i=0; i<rows.length; i++) {
			rows[i].removeChild(rows[i].cells[index]);
		}
	},
	
	showRow: function(rowIndex) {
		if(rowIndex == "up") {
			this.scrollBox.getElement().scrollTop = this.scrollBox.getElement().scrollTop - this.fTable.rows[1].offsetTop;
		} else if(rowIndex == "down") {
			this.scrollBox.getElement().scrollTop = this.scrollBox.getElement().scrollTop + this.fTable.rows[1].offsetTop;
		} else {
			var row = $(this.gridId + ":n:" + rowIndex);
			var offsetTop = this.fTable.offsetTop + row.offsetTop;
			if(this.frozenContentBox.getElement().scrollTop > offsetTop) {
				this.scrollBox.getElement().scrollTop = offsetTop;
			} else {
				offsetTop += row.offsetHeight;
				offsetTop -= this.frozenContentBox.getElement().clientHeight;
				if (this.frozenContentBox.getElement().scrollTop < offsetTop){
					this.scrollBox.getElement().scrollTop = offsetTop;
				}
			}
		}
		this.scrollBox.updateScrollPos();
	},
	
	_calcDefaultRowHeight: function() {
		var templNormal = this.templNormal.getElement();
		var length = templNormal.rows.length;
		if (length) {
			return Math.ceil(templNormal.offsetHeight / length);
		} else {
			return 16;
		}
	}
});

Object.extend(ClientUI.controls.grid.GridBody.prototype, {
	/**
	 * Count of rows can be viewed in the same time in grid
	 */
	dataVisible: 50,
	
	/**
	 * Count of rows loaded additianally to dataVisible rows
	 */
	dataDelta: 5,

	/**
	 * Current data position
	 */
	currentPos: 0
	
});
/*
 * TODO: Copyright (c) 2007 Denis Morozov <dmorozov@exadel.com>
 *
 * ...
 */
ClientUILib.declarePackage("ClientUI.controls.grid.GridFooter");

ClientUILib.requireClass("ClientUI.common.box.Box");

/*
/* GridHeader.js - Grid control header pane
 * TODO: add comments
 */
ClientUI.controls.grid.GridFooter = Class.create(ClientUI.common.box.Box, {
	initialize: function($super, template, grid) {
		this.grid = grid;
		$super(template);
		this.createControl(template);
	},
	createControl: function(template) {
		var errMsg = "";
		if(!template) {
			errMsg = "Invalid template specified for GridFooter.";
			ClientUILib.log(ClientUILogger.ERROR, errMsg);
			throw(errMsg);
		}
		
		if(!this.parseTemplate(template)) {
			errMsg = "Unable to parse template. GridFooter requires template specified over table element with one row.";
			ClientUILib.log(ClientUILogger.ERROR, errMsg);
			throw(errMsg);
		}

		// Set dimensions
		this.setHeight(this.defaultHeight);
		this.setWidth(this.defaultWidth);
		this.controlCreated = true;
	},
	parseTemplate: function(template) {
		if(!template) {
			return false;
		}

		var childs = template.childNodes;
		for(var i=0; i<childs.length; i++) {
			if(childs[i].tagName && childs[i].tagName.toLowerCase() == "div") {
				this.container = new ClientUI.common.box.Box(childs[i], null, true);
				this.container.setStyle({"z-index": 100});
				break;
			}
		}
		
		var normal = null, frozen = null;
		var childs = this.container.getElement().childNodes;
		for(var i=0; i<childs.length; i++) {
			if(childs[i].id && childs[i].id.indexOf("FrozenBox")>=0) {
				frozen = childs[i];
			}
			else if(childs[i].id && childs[i].id.indexOf("NormalBox")>=0){
				normal = childs[i];
			}
		}

		if(!normal || !frozen) {
			errMsg = "Unable to parse template for GridFooter. Unable to find FrozenBox or NormalBox.";
			ClientUILib.log(ClientUILogger.ERROR, errMsg);
			throw(errMsg);
		}
		this.contentBox = new ClientUI.common.box.Box(normal);
		this.contentBox.makeAbsolute();
		this.frozenContentBox = new ClientUI.common.box.Box(frozen);
		this.frozenContentBox.makeAbsolute();

		// create row template
		var ch = this.contentBox.getElement().firstChild;
		while(ch) {
			if(ch.tagName && ch.tagName.toLowerCase()=="table") {
				this.headerRow = new ClientUI.common.box.Box($(ch), null, true);
				break;
			}
			ch = ch.nextSibling;
		}
		ch = this.frozenContentBox.getElement().firstChild;
		while(ch) {
			if(ch.tagName && ch.tagName.toLowerCase()=="table") {
				this.headerFrozenRow = new ClientUI.common.box.Box($(ch), null, true);
				break;
			}
			ch = ch.nextSibling;
		}		

		var columns = this.grid.getHeader().getColumns();
		var i=0;
		var cols = $A(this.headerFrozenRow.getElement().getElementsByTagName("col"));
		cols.each(function(col) {
			columns[i].footerCol = $(col);
			i++;
		});
		cols = $A(this.headerRow.getElement().getElementsByTagName("col"));
		cols.each(function(col) {
			columns[i].footerCol = $(col);
			i++;
		});

		this.helpObj = new ClientUI.common.box.Box(this.frozenContentBox.getElement(), null, true);
		this.defaultWidth = this.grid.getHeader().defaultWidth;
		this.defaultHeight = Element.getHeight(this.headerRow.getElement().rows[0].cells[0]);
		if(ClientUILib.isGecko) {
			this.defaultHeight -= this.getBorderWidth("tb") + this.getPadding("tb");
			//this.defaultWidth -= this.getBorderWidth("lr") + this.getPadding("lr");
		}
				
		this.frozenSubstrate = new ClientUI.common.box.Box(this.grid.getElement().id + ":fs", this.getElement());
		this.frozenSubstrate.getElement().name = this.getElement().id + "FRFrm";
		this.frozenSubstrate.setHeight(this.defaultHeight);			
		return true;
	},
	updateSize: function() {
		this.defaultWidth = this.grid.getHeader().defaultWidth;
		this.defaultHeight = Element.getHeight(this.headerRow.getElement().rows[0].cells[0]);
		if(ClientUILib.isGecko) {
			this.defaultHeight -= this.getBorderWidth("tb") + this.getPadding("tb");
			//this.defaultWidth -= this.getBorderWidth("lr") + this.getPadding("lr");
		}
		this.setHeight(this.defaultHeight);
		this.setWidth(this.defaultWidth);
	},
	updateLayout: function($super) {
		if(!this.controlCreated || !this.grid.controlCreated) {
			return;
		}
		$super();
	
		var height = this.getHeight();
		var totalWidth = this.grid.getColumnsTotalWidth();
		var frozenContentWidth = this.grid.getColumnsFrozenWidth();

		this.contentBox.setWidth(Math.max(this.getWidth(), totalWidth));
		this.contentBox.setHeight(height);
		this.contentBox.moveTo(frozenContentWidth - this.grid.getScrollOffset(), 0);
		this.frozenContentBox.setWidth(frozenContentWidth);
		this.frozenContentBox.setHeight(height);
		this.frozenContentBox.moveTo(0, 0);
		var frozenContentWidth = this.grid.getBody().frozenContentBox.getWidth();
		
		var viewWidth = this.grid.getBody().scrollBox.getViewportWidth();
		this.container.setWidth(viewWidth);
		this.setWidth(viewWidth);
		this.frozenSubstrate.setWidth(frozenContentWidth);
	},
	adjustScrollPosition: function(pos) {
		this.contentBox.moveToX(this.grid.getColumnsFrozenWidth()-pos);	
	},
	adjustColumnWidth: function(column, width) {
		var columns = this.grid.getHeader().getColumns();
		columns[column].footerCol.width = width>0 ? width : 1;
	},
	
	setFakeColumnWidth: function() {
		var columns = this.grid.getHeader().getColumns();
		columns.last().footerCol.width = 1;
		var width = this.grid.getElement().offsetWidth - this.headerRow.getElement().offsetWidth;
		if (width < 1) {
			width = 1;
		}
		columns.last().footerCol.width = width;
	},
	
	hideColumn: function(index, frozen) {
		var rows;
		if(frozen) {
			rows = this.headerFrozenRow.getElement().rows;
		} else {
			rows = this.headerRow.getElement().rows;
		}
		for(var i=0; i<rows.length; i++) {
			rows[i].removeChild(rows[i].cells[index]);
		}
	}	
});
/**
 * Grid.js		Date created: 6.04.2007
 * Copyright (c) 2007 Exadel Inc.
 * @author Denis Morozov <dmorozov@exadel.com>
 */
ClientUILib.declarePackage("ClientUI.controls.grid.Grid");

ClientUILib.requireClass("ClientUI.common.box.Box");
ClientUILib.requireClass("ClientUI.controls.grid.GridHeader");
ClientUILib.requireClass("ClientUI.controls.grid.GridBody");
ClientUILib.requireClass("ClientUI.controls.grid.GridFooter");

/*
 * grid.js - Grid library on top of Prototype 
 * by Denis Morozov <dmorozov@exadel.com> distributed under the BSD license. 
 *
 * TODO: description of control 
 *
 * TODO: usage description
 * Usage: 
 *   <script src="/javascripts/prototype.js" type="text/javascript"></script>
 *   <script src="/javascripts/extend.js" type="text/javascript"></script>
 *   <script src="/javascripts/grid.js" type="text/javascript"></script>
 *   <script type="text/javascript">
 *     // with valid DOM id
 *     var grid = new ClientUI.controls.grid.Grid('id_of_trigger_element', 'id_of_tooltip_to_show_element')
 *
 *     // with text
 *     var grid = new ClientUI.controls.grid.Grid('id_of_trigger_element', 'a nice description')
 *   </script>
 */
ClientUI.controls.grid.Grid = Class.create(ClientUI.common.box.Box, {
		
	initialize: function($super, element, dataModel, templates) {
		$super(element);
		if(!this.element || !this.element.id)
			this.element.id = "ClientUI_Grid" + ClientUI_controls_grid_Grid_idGenerator++;
		
		this.dataModel = dataModel;
		this.templates = $A(templates);
		
		this.eventOnSort = new ClientUI.common.utils.CustomEvent('OnSort');
		this.eventOnResizeColumn = new ClientUI.common.utils.CustomEvent('OnResizeColumn');
		
		this.createControl();
		
	},
	createControl: function() {
		var grid = this;
		this.layout = new ClientUI.layouts.GridLayoutManager(this.getElement(), null);
		
		var pagePart, item;
		for(var i=0; i<this.templates.length; i++) {
			item = this.templates[i];
			switch(item.pane) {
				case GridLayout_Enum.HEADER: {
					pagePart = new ClientUI.controls.grid.GridHeader($(item.ref), grid);
					this.layout.addPane(GridLayout_Enum.HEADER, pagePart);
					break;
					}
				case GridLayout_Enum.BODY: {
					pagePart = new ClientUI.controls.grid.GridBody($(item.ref), grid);
					this.layout.addPane(GridLayout_Enum.BODY, pagePart);
					break;
					}
				case GridLayout_Enum.FOOTER: {
					pagePart = new ClientUI.controls.grid.GridFooter($(item.ref), grid);
					this.layout.addPane(GridLayout_Enum.FOOTER, pagePart);
					break;
					}
			}			
		}
		
		this.currentScrollPos = 0;
		this.controlCreated = true;
		this.updateLayout();
		
		if (ClientUILib.isIE && !ClientUILib.isIE7) {
			var grid = this;
			setTimeout(function() {grid.updateLayout()}, 50);
		}
	},
	updateLayout: function($super) {
		if(!this.controlCreated || this.getHeight()==0) {
			return;
		}
		$super();
		
		var header = this.getHeader();
		if(header.getHeight() == 0) {
			header.updateSize();
			this.getBody().updateSize();
			if(this.getFooter()) {this.getFooter().updateSize();}
		} 

		if(this.layout) {
			this.layout.updateLayout();
		}
		header.updateLayout();
		header.setFakeColumnWidth();
		this.getBody().setFakeColumnWidth();
		if(this.getFooter()) {this.getFooter().setFakeColumnWidth();}		
	},
	getHeader: function() {
		return this.layout.getPane(GridLayout_Enum.HEADER);
	},
	getFooter: function() {
		return this.layout.getPane(GridLayout_Enum.FOOTER);
	},
	getBody: function() {
		return this.layout.getPane(GridLayout_Enum.BODY);		
	},
	adjustColumnWidth: function(index, width) {
		this.getHeader().adjustColumnWidth(index, width);
		this.getBody().adjustColumnWidth(index, width);
		if(this.getFooter()) this.getFooter().adjustColumnWidth(index, width);
		this.updateLayout();
		this.getHeader().agjustSeparators();
		this.eventOnResizeColumn.fire(index, width);		
	},
	adjustScrollPosition: function(pos) {
		if(pos<0) {pos = 0;}
		this.currentScrollPos = pos;
		this.getHeader().adjustScrollPosition(pos);
		this.getBody().adjustScrollPosition(pos);
		if(this.getFooter()) {this.getFooter().adjustScrollPosition(pos);}
	},
	getScrollOffset: function() {
		return this.currentScrollPos ? this.currentScrollPos : 0;
	},
	setColumnMinWidth: function(index, width) {
		if(index<0 || index>=this.getHeader().getColumns().length)
			return false;
		this.getHeader().getColumns()[index].minWidth = width;
		return true;		
	},
	getColumnsTotalWidth: function() {
		var totalWidth = 0;
		var columns = this.getHeader().getColumns();
		for(var i=0; i<columns.length; i++) {
			totalWidth += columns[i].width;
		}
		return totalWidth;
	},
	getColumnsFrozenWidth: function() {
		var totalWidth = 0;
		var columns = this.getHeader().getColumns();
		var i = 0;
		while(i<columns.length && columns[i].frozen) {
			totalWidth += columns[i++].width;
		}
		return totalWidth;
	},
	invalidate: function(params) {
		this.getBody().invalidate(params);
	},
	adjustColumnsWidth: function() {
		var columns = this.getHeader().getColumns();
		for(var i=0; i<columns.length; i++) {
			this.adjustColumnWidth(i, columns[i].width);
		}
	},
	quickFind: function(column, text, startRow) {
		var start = startRow || 0;
	    var searchText = "*" + text + "*";
	    searchText = searchText.replace(/\*/g, ".*");
	    var searchReg = new RegExp(searchText, 'i');
		var rowIndex = -1;
		var body = this.getBody();
		var rowsCount = body.rowsCount;
        for (var i = start; i < rowsCount; i++) {
            var currentTextInGrid = body.getCellText(i, column);
            currentTextInGrid = currentTextInGrid.replace(/,/g,'');
            if (currentTextInGrid.search(searchReg) != -1) {
                rowIndex = i;
                break;
            }
        }
        
        return rowIndex;
	},
	ensureVisible: function(index) {
		this.getBody().ensureVisible(index);
	},
	getDataIndex: function(rowIndex) {
		var body = this.getBody();
		return body.currRange.start+rowIndex;
	},
	getRowIndex: function(dataIndex) {
		var body = this.getBody();
		return (dataIndex>=body.currRange.start && dataIndex<body.currRange.start+body.rowsCount) ?
			dataIndex - body.currRange.start : -1;
	},
	hideColumn: function(column) {
		this.adjustColumnWidth(column, 0);
	},
	reloadData: function() {
		this.getBody().reloadData();
	},
	updateRowCount: function(newCount) {
		var rowCount = parseInt(newCount);
		if(rowCount>=0) {
			this.dataModel.count = rowCount;
			this.updateLayout();
		}
	}
});

Object.extend(ClientUI.controls.grid.Grid.prototype, {
	// Custom events
	/**
	 * Occured when content header clicked
	 */
	eventOnSort: {},
	/**
	 * Occured when column width adjusted
	 */
	eventOnResizeColumn: {}
});

if(!ClientUI_controls_grid_Grid_idGenerator) {
var ClientUI_controls_grid_Grid_idGenerator = 0;
};
			
ClientUI.controls.grid.Selection = Class.create({
	initialize: function() {
		this.ranges = [];
	},

	addId: function(id) {
		id = parseInt(id);
		if(this.isSelectedId(id))
			return;
		var i = 0;
		while(i < this.ranges.length && id >= this.ranges[i++].indexes[1]);
		i--;
		if(this.ranges[i-1] && id==(this.ranges[i-1].indexes[1]+1) ) {
			if(id==(this.ranges[i].indexes[0]-1)) {
				this.ranges[i-1].indexes[1] = this.ranges[i].indexes[1];
				this.removeRange(i);			
			} else {
				this.ranges[i-1].indexes[1]++;			
			}
		} else {
			if(this.ranges[i]){
				if(this.ranges[i] && id==(this.ranges[i].indexes[0]-1)) {
					this.ranges[i].indexes[0]--;			
				} else {
					if(id==(this.ranges[i].indexes[1]+1)){
						this.ranges[i].indexes[1]++;			
					} else {
						if(id<this.ranges[i].indexes[1]){
							this.addRange(i, new ClientUI.controls.grid.Range(id, id));					
						} else {
							this.addRange(i + 1, new ClientUI.controls.grid.Range(id, id));					
						}
					}
				}	
			} else {
				this.addRange(i, new ClientUI.controls.grid.Range(id, id));					
			}	
		} 			
	},

	addRange: function(index, range) {
		var i = this.ranges.push(range) - 2;
		if(index >= 0) {
			while(i>=index)
				this.ranges[i+1] = this.ranges[i--];
			this.ranges[i+1] = range;
		}
	},

	removeRange: function(index) {
		var i = index + 1;
		while(i!=this.ranges.length)
			this.ranges[i-1] = this.ranges[i++];
		this.ranges.pop();
	},

	isSelectedId: function(id) {
		var i = 0;
		while(i < this.ranges.length && id >= this.ranges[i].indexes[0]) {
			if(id >= this.ranges[i].indexes[0] && id <= this.ranges[i].indexes[1]) {
				return true;
			} else {
				i++;
			}
		}
		return false;
	},

	getSelectedIdsQuantity: function() {
		var number = 0;
		for (var i = 0; i < this.ranges.length; i++) {
			number+= this.ranges[i].size();
		}
		return number;
	},
	
	size: function () {
		return this.getSelectedIdsQuantity();
	},
	
	removeId: function(id) {
		id = parseInt(id);
		if(!this.isSelectedId(id))
			return;
		var i = 0;
		while(i < this.ranges.length && id > this.ranges[i++].indexes[1]);
		i--;
		if(this.ranges[i]) {
			if(id==(this.ranges[i].indexes[1]) ) {
				if(id==(this.ranges[i].indexes[0])){
					this.removeRange(i);			
				} else {
					this.ranges[i].indexes[1]--;			
				}
			} else {
				if(id==(this.ranges[i].indexes[0])){
					this.ranges[i].indexes[0]++;			
				} else {
				this.addRange(i+1, new ClientUI.controls.grid.Range(id+1, this.ranges[i].indexes[1]));			
				this.ranges[i].indexes[1] = id-1;
				}
			}
		}		
	},

	getState: function() {
		var s = this.clone();
		return {
			size: function() {
					return s.size();
			},
			
			each: function(iterator) {
				s.each(iterator);
  			},
			
			isSelected: function(id) {
				return s.isSelectedId(id);
  			}
		};
	},

	clone: function() {
		var ret =  Object.extend(new Object(),this);
		ret.ranges = new Array(ret.ranges.length);
		for (var i = 0; i < ret.ranges.length; i++) {
			ret.ranges[i] = this.ranges[i].clone();
		}		
 		return ret;
 	},

	each: function(iterator) {
		for (var i = 0; i < this.ranges.length; i++) {
			this.ranges[i].each(iterator);					
		}
 	},
  	
  	getRanges: function() {
		return this.ranges;
	},

	setRanges: function(ranges) {
		this.ranges = ranges;
	},
	
	initRanges: function(rangesStr) {
		if(!rangesStr) {
			this.ranges = [];
			return;
		}
		var rangeStrRArray = rangesStr.split(";");
		this.ranges = new Array(rangeStrRArray.length -1);
		var indexStrRArray;
		for(var i = 0; i < this.ranges.length; i++) {
			indexStrRArray = rangeStrRArray[i].split(",");
			this.ranges[i] = new ClientUI.controls.grid.Range(parseInt(indexStrRArray[0]), parseInt(indexStrRArray[1]));
		}
		
	}, 

	inspectRanges: function() {
		var ranges = this.getRanges();
		var ret = "";
		ranges.each( function(r) { ret += r.inspect(); } );
		return ret;
	} 
});

ClientUI.controls.grid.Range = Class.create({
	initialize: function(startIndex, endIndex) {
		this.indexes = [startIndex, endIndex];
	},

	inspect: function() {
		return this.indexes[0] + "," + this.indexes[1] + ";";
	},
	toString: function() {
		return this.inspect();
	},
	
	size: function() {
		return this.indexes[1] - this.indexes[0] + 1;;
	},
	
	each: function(iterator) {
		var j = this.indexes[0];
		while(j <= this.indexes[1]) {
      		iterator(j++);					
		}
  	},
	
	clone: function() {
		var ret = Object.extend(new Object(),this);
		ret.indexes = this.indexes.clone();
		return ret;
  	}
});

ClientUI.controls.grid.SelectionManager = Class.create({
	initialize: function(grid) {
		this.grid = grid;
		this.selectionFlag;
		this.firstIndex;
		var gridElement = grid.getElement();
		this.prefix = gridElement.id;
		this.selection = new ClientUI.controls.grid.Selection();

		this.inputElement = grid.options.selectionInput;
		this.onselectionchange = grid.options.onselectionchange;
		this.selectedClass = grid.options.selectedClass;
		this.activeClass = grid.options.activeClass;

		this.restoreState();
		this.setListeners();
		this.eventKeyPress = this.processKeyDown.bindAsEventListener(this);
		Event.observe(document, "keypress", this.eventKeyPress);
		if (document.selection) {
			Event.observe(gridElement, "click", this.resetSelection.bindAsEventListener(this));
		}

		this.eventLostFocus = this.processLostFocus.bindAsEventListener(this);
		Event.observe(document, "click", this.eventLostFocus);

		this.eventPreventLostFocus = this.processPreventLostFocus.bindAsEventListener(this);
		Event.observe(gridElement, "click", this.eventPreventLostFocus);
		
		
		
//		var selChangeHandler = this.grid.options.onselectionchange;		
//		if (selChangeHandler) {
//			IL.Event.observe(this.grid.element, "selectionchange", selChangeHandler);
//		}
//		var deleteHandler = this.grid.options.onDeleted;		
//		if (deleteHandler) {
//			IL.Event.observe(this.grid.element, "delete", deleteHandler);
//		}

	},

	restoreState: function() {
		this.selectionFlag = null;
		this.selection.initRanges($(this.inputElement).value);
	//	this.firstIndex = Number($(this.prefix + ":f").rows[0].id.split(this.prefix)[1].split(":")[2]);;
		var i = 0;
		var j;
		while(i < this.selection.ranges.length) {
			j = this.selection.ranges[i].indexes[0];
			while(j <= this.selection.ranges[i].indexes[1]) {
				var fElement = $(this.prefix + ":f:" + j);
				var nElement = $(this.prefix + ":n:" + j);
				Element.addClassName(fElement, "dr-sdt-row-selected");
				Element.addClassName(nElement, "dr-sdt-row-selected");
				Element.addClassName(fElement, "rich-sdt-row-selected");
				Element.addClassName(nElement, "rich-sdt-row-selected");
				Element.addClassName(fElement, this.selectedClass);
				Element.addClassName(nElement, this.selectedClass);
				j++;
			}
			i++;
		}
		this.oldState = this.selection.getState();
	},
	
	setListeners: function() {
		var frows = $(this.prefix + ":f").rows;
		var nrows = $(this.prefix + ":n").rows;
		this.rowCount = frows.length;
		var rowIndex;
		for(var i = 0; i < this.rowCount; i++) {
			rowIndex = Number(frows[i].id.split(this.prefix)[1].split(":")[2]);
			this.addListener(frows[i], rowIndex);
			this.addListener(nrows[i], rowIndex);
		}
	},
	
	addListener: function(element, rowIndex) {
		var listener = this.processClick.bindAsEventListener(this, rowIndex);
		var cells = element.cells;
		for(var i = 0; i < cells.length; i++) {
			Utils.DOM.Event.observe(cells[i], "click", listener);	
		}
	},
	
/*	getGridSelection: function() {
		return this.selection.getRanges();
	},*/

	processPreventLostFocus: function() {
		this.inFocus = true;
		this.preventLostFocus = true;
	},

	processLostFocus: function() {
		if (!this.preventLostFocus) {
			this.lostFocus();
		} else {
			this.preventLostFocus = false;
		}
	},

	lostFocus: function() {
		this.inFocus = false;
	},

	processKeyDown: function(event) {
		if(!event.shiftKey) {
			this.shiftRow = null;
		}		
		var range, rowIndex;
		var activeRow = this.activeRow;
		var noDefault = false;
		this.firstIndex = Number($(this.prefix + ":n").rows[0].id.split(this.prefix)[1].split(":")[2]);;
		switch (event.keyCode || event.charCode) {
			case Event.KEY_UP:
				if (this.inFocus && activeRow != null) {
					if(this.firstIndex != activeRow) {
						rowIndex = (this.rowCount + activeRow - 1) % this.rowCount;		
						if (!event.ctrlKey && !event.shiftKey) {
							this.selectionFlag = "x";
							range = [rowIndex, rowIndex];
							this.setSelection(range);		
						} else if (!event.ctrlKey && event.shiftKey) {
							if(!this.shiftRow) {
								this.shiftRow = this.activeRow;
							}
							if(this.shiftRow >= this.activeRow) {
								this.addRowToSelection(rowIndex);						
							} else {
								this.removeRowFromSelection(activeRow);						
							}
						}
						noDefault = true;
						this.setActiveRow(rowIndex);
					} else {
						this.grid.getBody().showRow("up");					
					}
				}
				break;
			case Event.KEY_DOWN:
				if (this.inFocus && activeRow != null) {
					rowIndex = (activeRow + 1) % this.rowCount;		
					if(this.firstIndex != rowIndex) {
						if (!event.ctrlKey && !event.shiftKey) {
							this.selectionFlag = "x";
							range = [rowIndex, rowIndex];
							this.setSelection(range);		
						} else if (!event.ctrlKey && event.shiftKey) {
							if(!this.shiftRow) {
								this.shiftRow = this.activeRow;
							}
							if(this.shiftRow <= this.activeRow) {
								this.addRowToSelection(rowIndex);						
							} else {
								this.removeRowFromSelection(activeRow);						
							}
						}
						noDefault = true;
						this.setActiveRow(rowIndex);
					} else {
						this.grid.getBody().showRow("down");					
					}
				}
				break;
			case 65: case 97:								// Ctrl-A
				if (this.inFocus && event.ctrlKey) {
					this.selectionFlag = "a";
					range = [0, this.rowCount];
					this.setSelection(range);		
					noDefault = true;
				}
				break;
			case Event.KEY_TAB:
				this.lostFocus();
		}
		if (noDefault) {
			this.grid.getBody().showRow(this.activeRow);
			this.selectionChanged(event);			
			if (event.preventBubble) event.preventBubble();
			Event.stop(event);
		}
	},

	processClick: function(event, rowIndex) {
		if(!event.shiftKey) {
			this.shiftRow = null;
		}		
		var range;
		if ( event.shiftKey && !event.ctrlKey && !event.altKey) {
			this.firstIndex = Number($(this.prefix + ":n").rows[0].id.split(this.prefix)[1].split(":")[2]);;
			this.selectionFlag = "x";
			if(!this.shiftRow) {
				this.shiftRow = this.activeRow;
			}
			this.startRow = this.shiftRow;
			if (((this.startRow <= rowIndex) && (this.firstIndex <= this.startRow || rowIndex < this.firstIndex))
				|| (this.startRow > rowIndex && this.firstIndex < this.startRow && rowIndex <= this.firstIndex)) {
				this.endRow = rowIndex;
			} else {
				this.endRow = this.startRow;
				this.startRow = rowIndex;
			}
			range = [this.startRow, this.endRow];
			this.setSelection(range);		
		} else if (!event.shiftKey &&  event.ctrlKey && !event.altKey) {
			if (this.selection.isSelectedId(rowIndex)) {
				this.removeRowFromSelection(rowIndex);
			} else {
				this.addRowToSelection(rowIndex);
			}
		} else  if (!event.shiftKey && !event.ctrlKey && !event.altKey) {
			this.selectionFlag = "x";
			range = [rowIndex, rowIndex];
			this.setSelection(range);		
		}
		this.setActiveRow(rowIndex);
		if (event.shiftKey) {
			if (window.getSelection) {
				window.getSelection().removeAllRanges();
			} else if (document.selection) {
				document.selection.empty();
			}
		}
		this.selectionChanged(event);			
	},
	
	selectionChanged: function(event) {
		$(this.inputElement).value = this.selection.inspectRanges()+ (this.selectionFlag ? this.selectionFlag : "") ;
		var state = this.selection.getState();			
		event.oldSelection = this.oldState;
		event.newSelection = state;
		if(this.onselectionchange) this.onselectionchange(event);
		this.oldState = state;
	},

	setShiftRow: function(event) {
		if(event.shiftKey) {
			if(!this.shiftRow) {
				this.shiftRow = this.activeRow;
			}
		} else {
			this.shiftRow = null;		
		}	
	},
	
	setSelection: function(range) {
		var i = range[0];
		range[1] = (range[1] + 1) % this.rowCount;		
		while (i != range[1]) {
			this.addRowToSelection(i);
			i = (i + 1) % this.rowCount;		
		}
		while (i != range[0]) {
			this.removeRowFromSelection(i);
			i = (i + 1) % this.rowCount;				
		}
	},
	
	resetSelection: function(e) {
		if(e.shiftKey) {
			document.selection.empty();
		}
	},

	addRowToSelection: function(rowIndex) {
		this.selection.addId(rowIndex);
		var fElement = $(this.prefix + ":f:" + rowIndex);
		var nElement = $(this.prefix + ":n:" + rowIndex);
		Element.addClassName(fElement, "dr-sdt-row-selected");
		Element.addClassName(nElement, "dr-sdt-row-selected");
		Element.addClassName(fElement, "rich-sdt-row-selected");
		Element.addClassName(nElement, "rich-sdt-row-selected");
		Element.addClassName(fElement, this.selectedClass);
		Element.addClassName(nElement, this.selectedClass);
	},

	removeRowFromSelection: function(rowIndex) {
		this.selection.removeId(rowIndex);
		var fElement = $(this.prefix + ":f:" + rowIndex);
		var nElement = $(this.prefix + ":n:" + rowIndex);
		Element.removeClassName(fElement, "dr-sdt-row-selected");
		Element.removeClassName(nElement, "dr-sdt-row-selected");
		Element.removeClassName(fElement, "rich-sdt-row-selected");
		Element.removeClassName(nElement, "rich-sdt-row-selected");
		Element.removeClassName(fElement, this.selectedClass);
		Element.removeClassName(nElement, this.selectedClass);
	},

	setActiveRow: function(rowIndex) {
		var fElement, nElement;
		if(this.activeRow != null) {
			fElement = $(this.prefix + ":f:" + this.activeRow);
			nElement = $(this.prefix + ":n:" + this.activeRow);
		Element.removeClassName(fElement, "dr-sdt-row-active");
		Element.removeClassName(nElement, "dr-sdt-row-active");
		Element.removeClassName(fElement, "rich-sdt-row-active");
		Element.removeClassName(nElement, "rich-sdt-row-active");
		Element.removeClassName(fElement, this.activeClass);
		Element.removeClassName(nElement, this.activeClass);
		}
		fElement = $(this.prefix + ":f:" + rowIndex);
		nElement = $(this.prefix + ":n:" + rowIndex);
		Element.addClassName(fElement, "dr-sdt-row-active");
		Element.addClassName(nElement, "dr-sdt-row-active");
		Element.addClassName(fElement, "rich-sdt-row-active");
		Element.addClassName(nElement, "rich-sdt-row-active");
		Element.addClassName(fElement, this.activeClass);
		Element.addClassName(nElement, this.activeClass);
		this.activeRow = rowIndex;
	}
});
ClientUILib.declarePackage("ClientUI.controls.grid.ScrollableGrid");

ClientUI.controls.grid.ScrollableGrid = Class.create(ClientUI.controls.grid.Grid, {
	initialize: function($super, options) {
		this.startInitTime = (new Date()).getTime();
		
		this.options = options;
		this.client_id = this.options.client_id;
		this.rows_count = $(this.client_id + "_rows_input").value;
		this.scroll_si = $(this.client_id + ":si");
		this.columns_count = this.options.columnsCount;
		this.splash_id = this.options.splash_id; 
		this.dataModel = new ClientUI.controls.grid.FakeArrayDataModel(this.rows_count, this.columns_count, this.client_id);
		
		this.templates = [
			{pane: GridLayout_Enum.HEADER,	ref: this.client_id +"_" + "GridHeaderTemplate"},
			{pane: GridLayout_Enum.BODY, 	ref: this.client_id +"_" + "GridBodyTemplate"},
			{pane: GridLayout_Enum.FOOTER,	ref: this.client_id +"_" +  "GridFooterTemplate"}
		];			
		var grid = this;
		var s = $super;
		
		Event.onReady(function(){
			grid.init(s);
		});		
		Utils.execOnLoad(
			function(){
				grid.init(s);
			},
			Utils.Condition.ElementPresent(grid.client_id), 100);
				
		this.endInitTime = (new Date()).getTime();
		this.rowCallbacks = [];
	},

// initialize parent Grid	
	
	init: function(superCtor){
		// mark that grid control initialized
		if(!this.isInitialized) {
			this.isInitialized = true;
			this.startCreateTime = (new Date()).getTime();
			
			superCtor(this.client_id, this.dataModel, this.templates);
						
			this.endCreateTime = (new Date()).getTime();
						
			// suspend some processing
			setTimeout(function() {
				this.startPostProcessTime = (new Date()).getTime();
				
				this.eventOnPostSort = new ClientUI.common.utils.CustomEvent('OnSort');
				this.eventOnPostScroll = new ClientUI.common.utils.CustomEvent('OnScroll');
							
				//var progress = new ClientUI.common.box.SplashBox(this.splash_id, null, 300, true);
				//this.setProgressCtrl(progress);
				Event.observe(this.eventOnSort, "on sort",  this.onSorted.bindAsEventListener(this));
				if (this.options.selectionInput) {
					this.selectionManager = new ClientUI.controls.grid.SelectionManager(this);
				}
				
				this.endPostProcessTime = (new Date()).getTime();
			}.bind(this), 500);
			
			if(this.scroll_si.value !=''){
				var options = this.scroll_si.value.split(',');
				this.restoreScrollState(options[0],options[1],options[2]);  		
			}
			
			var form = this.getForm($(this.client_id));						
			Event.observe(form, "submit",this.restoreScrollPos.bindAsEventListener(this));
		}
	},	
	
	getForm: function(element){
		var node = element;
		if(node){
			while(node.tagName.toUpperCase()  != 'FORM'){
				node = node.parentNode;	
			}
		}
		
		return node;	
	},
	
	onSortComplete : function(request, event, data){
		var options = request.getJSON("options");				
		Utils.AJAX.updateRows(options,request,
								this,this.client_id, 
								[this.updateSelectionCallBack], 
								[function(){
									this.selectionManager.restoreState();
									this.eventOnPostSort.fire(options.column, options.order);
								}]);
		this.updateLayout();
	},
	onScrollComplete : function(request, event, data){
		var options = this.dataModel.getCurrentOptions();
		window.loadingServerTime = (new Date()).getTime();
		Utils.AJAX.updateRows(options,request,
								this,this.client_id, 
								[this.updateSelectionCallBack],
								[function(){
									this.selectionManager.restoreState();
									this.eventOnPostScroll.fire(this.getBody().currRange.start);
								}]);								
								
		window.loadingEndTime = (new Date()).getTime();
		
		// TODO: remove this time statistic logging
		ClientUILib.log(ClientUILogger.ERROR, "Total time of data loading of "+options.count+" rows is: " + (window.loadingEndTime - window.loadingStartTime) + " miliseconds.");
		ClientUILib.log(ClientUILogger.WARNING, "...Server load time: " + (window.loadingServerTime - window.loadingStartTime));
		ClientUILib.log(ClientUILogger.WARNING, "...DOM updated time: " + (window.loadingUpdateTime - window.loadingServerTime));
		ClientUILib.log(ClientUILogger.WARNING, "...Grid invalidation time: " + (window.loadingInvalidateTime - window.loadingUpdateTime));
		ClientUILib.log(ClientUILogger.WARNING, "...Selection mng time: " + (window.loadingEndTime - window.loadingInvalidateTime));
	},
	
	onSorted: function(sortEvent) {
		this.options.onSortAjaxUpdate(sortEvent);
	},
	
	updateSelectionCallBack: function(argMap) {
		if (this.selectionManager) {
			this.selectionManager.addListener(argMap.row, argMap.index);
		}
	},
	
	setSizes: function(width, height) {
		var style = this.element.style;
		style.width = width +"px";
		style.height = height +"px";
		this.updateLayout();
	},
	
	doCollapse: function(index) {
		var header = this.getHeader();
		var flength = header.headerFrozenRow.getElement().rows[0].cells.length;
		var nlength = header.headerRow.getElement().rows[0].cells.length;
		if(index < flength + nlength - 1) {
			var frozen = true;
			if(index >= flength) {
				index -= flength;
				frozen = false;			
			}
			this.hideColumn(index, frozen);
		}
	},
	
	hideColumn: function(index, frozen) {
		this.getHeader().hideColumn(index, frozen);
		this.getBody().hideColumn(index, frozen);
		if(this.getFooter()) {this.getFooter().hideColumn(index, frozen);}				
		this.updateLayout();
	},
	
	restoreScrollState: function(scrollPos, start, end) {
		
		var body = this.getBody(); 
		body.scrollBox.getElement().scrollTop = scrollPos;
		body.currentPos = scrollPos;
		body.currRange.start = start;
		body.currRange.end = end;
		body._onContentVScroll(scrollPos);
		var visibleRowPos = body.currRange.start * body.defaultRowHeight;
		//setTimeout(function(){
			body.templFrozen.moveToY(visibleRowPos);
			body.templNormal.moveToY(visibleRowPos);
		//}, 500);
	},
	
	getScrollPos: function() {
		return this.getBody().currentPos;
	},
	
	restoreScrollPos: function(){
		var body = this.getBody();
		var start = body.currRange.start;
		var end = body.currRange.end;
		var index = this.getScrollPos();
		this.scroll_si.value = index + "," + start + "," + end; 
	}
});

Object.extend(ClientUI.controls.grid.ScrollableGrid.prototype, {
	/**
	* Occured when scroll position adjusted
	*/
	eventOnPostScroll: {},
	
	/**
	 * Occured when sorting adjusted
	 */
	eventOnPostSort: {}
	
});