/**
 * Runner main object.
 * 
 * JS files include order of inheritance, for example:
 * 1. Runner.js (main functionality)
 * 2. Validate.js (validations utilities)
 * 3. ControlManager.js (global object, for controls manage)
 * 4. Control.js (base abstract class for all controls)
 * 5. All controls in any order.
 */
var Runner = {version: '2.1'};
/**
 * Copies all the properties of config to obj.
 * @param {Object} obj The receiver of the properties
 * @param {Object} config The source of the properties
 * @param {Object} defaults object literal that will be applied first
 * @return {Object} returns obj
 * @member Runner apply
 */
Runner.apply = function(obj, cfg, defaults){
	// third argument passed copy it first
	if(defaults){
		Runner.apply(obj, defaults);
	}
	// copy config and override defaults if they cross
	if(obj && cfg && typeof cfg == 'object'){
		for(var prop in cfg){
			obj[prop] = cfg[prop];
		}
	}
	return obj;
};

/**
 * Reusable empty function
 */
Runner.emptyFn = function(){};

/**
 * Main RunnerJS functionality
 */
(function(){
	
	var idCounter = 0;
	var zIndexMax = 0;
	var userAgent = navigator.userAgent.toLowerCase();

	var isOpera = userAgent.indexOf("opera") > -1; 
	var isIE = (!isOpera && userAgent.indexOf("msie") > -1); 
	var isIE7 = (!isOpera && userAgent.indexOf("msie 7") > -1); 
	var isIE8 = (!isOpera && userAgent.indexOf("msie 8") > -1); 
	var isChrome = userAgent.indexOf("chrome") > -1; 
	var isSafari = (!isChrome && (/webkit|khtml/).test(userAgent)); 
	var isSafari3 = (isSafari && userAgent.indexOf('webkit/5') != -1); 
	var isGecko = (!isSafari && !isChrome && userAgent.indexOf("gecko") > -1); 
	var isGecko3 = (isGecko && userAgent.indexOf("rv:1.9") > -1);
	var isSecure = (window.location.href.toLowerCase().indexOf("https") === 0);

	// copy properties to main object
	Runner.apply(Runner, {
		/**
		 * Implements inheritance, on class-based model. 
		 * Inherites one class from another and optionally overrides properties with third argument - object literal.
		 * Function support three or two arguments call. With two arguments pass superclass as first, and literal with properties to override as second.
		 * In three arguments call pass subclass, parent and object literal to copy properties in subclass
		 * Example of usage

			Runner.controls.TextArea = Runner.extend(Runner.controls.Control,{
				constructor: function(cfg){
					this.addEvent(["change", "keyup"]);
					// call parent
					Runner.controls.TextArea.superclass.constructor.call(this, cfg);
					// change input type, because textarea don't have type attr
					this.inputType = "textarea";
				},
				getForSubmit: function(){
					return [this.valueElem.clone().val(this.getValue())]
				}
			});

		 * @param {Function} subclass The class which inheriting the functionality
		 * @param {Function} superclass The class for extend
		 * @param {Object} overrides (optional) A literal object with properties which are copied into the subclass's prototype
		 * @return {Function} The subclass constructor.
		 * @method extend
		 */
		extend: function(){
			// inline overrides function
			var overrideFunc = function(obj){
				for(var prop in obj){
					this[prop] = obj[prop];
				}
			};
			// constructor of simple Object class
			var baseObjConstr = Object.prototype.constructor;
			// create closure function
			return function(subBase, supPar, overrides){
				// if function called with 2 paramters, superclass and object literal
				if(typeof supPar == 'object'){
					// change vars, because called with 2 params
					overrides = supPar;
					supPar = subBase;
					// if contructor won't overriden, call parent with all passed args
					subBase = (overrides.constructor != baseObjConstr) ? overrides.constructor : function(){supPar.apply(this, arguments);};
				}
				// create temp function and vars with prototypes
				var F = function(){}, subBaseProt, supParProt = supPar.prototype;
				// change temp functiion prototype
				F.prototype = supParProt;
				// create new incstance of prototype, this will solve problem of one prototype chain
				subBaseProt = subBase.prototype = new F();
				// take care of inheritance, reset constructor
				subBaseProt.constructor=subBase;
				// make link to parent contructor
				subBase.superclass=supParProt;
				// reset parent constructor, don't know for what
				if(supParProt.constructor == baseObjConstr){
					supParProt.constructor=supPar;
				}
				// add override function
				subBase.override = function(obj){
					Runner.override(subBase, obj);
				};
				// add override to prototype
				subBaseProt.override = overrideFunc;
				// copy properties
				Runner.override(subBase, overrides);
				// add extend function
				subBase.extend = function(obj){Runner.extend(subBase, obj);};
				// return new class (constructor function)
				return subBase;
			};
		}(),

		/**
		 * Copies and replaces properties of one object with another
		 * @param {Object} baseclass
		 * @param {Object} object literal
		 * @method override
		 */
		override: function(origClass, overrides){
			if(overrides){
				var origProt = origClass.prototype;
				// copy all properties to prototype
				for(var method in overrides){
					origProt[method] = overrides[method];
				}

				if(Runner.isIE && overrides.toString != origClass.toString){
					origProt.toString = overrides.toString;
				}
			}
		},

		/**
		 * Loads javascript from file
		 * fileName {string} name of file to be loaded
		 */
		loadJS: function(src, onload, scope){
			scope = scope || this;
			var js = document.createElement('script');
			js.setAttribute('type', 'text/javascript');
			js.setAttribute('src', src);
			if(onload && Runner.isIE){
				js.onreadystatechange = function(){
					if (js.readyState == 'complete' || js.readyState == 'loaded'){
						onload.call(scope);
					}
				};
			}else if(onload){
				js.onload = function(){
					onload.call(scope);
				}
			}
			document.getElementsByTagName('HEAD')[0].appendChild(js);
		},	

		/**
		 * Creates namesapce
		 * @method
		 */
		namespace: function(name){
			var params = name.split('.'), current = Runner;
			for(var i=1;i<params.length;i++){
				if (!current[params[i]]){
					current[params[i]] = {};
				}
				current = current[params[i]];
			}
			
			return current;
		},

		deepCopy: function(obj, cfg){
			// recursively copy objects
			if(cfg && typeof cfg == 'object'){
				for(var prop in cfg){
					if (typeof cfg[prop] == 'object' && !Runner.isArray(cfg[prop])){
						if (typeof obj[prop] != 'object'){
							obj[prop] = {};
						}
						Runner.deepCopy(obj[prop], cfg[prop]);
					}else{
						obj[prop] = cfg[prop];	
					}
				}
			}
			return obj;
		},

		/**
		 * Get first parent of element wich have absolute position or body tag
		 * @param {object} element
		 * @param {integer} number of call this function
		 * @return {object}
		 */	
		getAbsoluteParent: function(elem, call){
			// for first call
			if(typeof call == 'undefined')
				call = 1;
			if ($(elem).parent().get(0) && $(elem).css('position') != 'absolute'){
				if ($(elem).tagName() != 'body' && $(elem).tagName() != 'html'){
					elem = Runner.getAbsoluteParent($(elem).parent().get(0), call++);
				}
			}
			return elem;
		},
		
		/**
		 * Get coordinate absolute position of element 
		 * @param {object} element
		 * @param {integer} number of call this function
		 * @return {object}
		 */	
		getAbsolutePosition: function(elem, call){
			// for first call
			if(typeof call == 'undefined')
				call = 1;
			//Get borders
			var bLeft = parseInt($.css(elem, 'borderLeftWidth')) || 0,
				bTop = parseInt($.css(elem, 'borderTopWidth'))  || 0,
				tagName = $(elem).tagName(),
				absPos = {top: elem.offsetTop,
						  left: elem.offsetLeft, 
						  width: elem.offsetWidth, 
						  height: elem.offsetHeight};
			//All browses don't add table's cellpadding
			if(tagName=='table'){
				if($(elem).attr('cellpadding')){
					absPos.left -= $(elem).attr('cellpadding');
					absPos.top -= $(elem).attr('cellpadding');
				}
			}	
			// Safari do not add the border for the element
			if($.browser.safari){
				absPos.left += bLeft;
				absPos.top += bTop;
			}
			// Mozilla and IE don't add the border if 'td' has it
			else if ($.browser.mozilla || $.browser.msie){
				if(tagName=='td'){
					absPos.top += bTop;
					absPos.left += bLeft;
				}
				// Mozilla removes the border if the parent has overflow property other than visible
				if ($.browser.mozilla && call>1 && $.css(elem, 'overflow') != 'visible'){
					absPos.left += bLeft;
					absPos.top += bTop;
				}
			}
			if (elem.offsetParent && $(elem).css('position') != 'absolute'){
				if($(elem.offsetParent).css('position') != 'absolute'){
					if (tagName == 'body' || tagName == 'html'){
						//Delete padding
						absPos.left -= parseInt($.css(elem, 'paddingLeft')) || 0;
						absPos.top -= parseInt($.css(elem, 'paddingTop'))  || 0;
					}else{
						var tmpAbsPos = Runner.getAbsolutePosition(elem.offsetParent, call++);
						absPos.left += tmpAbsPos.left;
						absPos.top += tmpAbsPos.top;
					}
				}
			}
			return absPos;
		},

		/**
		 * Get coordinate position of element 
		 * @param {object} element
		 * @param {integer} number of call this function
		 * @return {object}
		 */
		getPosition: function(elem){
			var elemNew = elem,
				curPos = {top: elem.offsetTop,
						  left: elem.offsetLeft,
						  width: elem.offsetWidth,
						  height: elem.offsetHeight}
			
			if(elem.offsetParent){
				while(elem = elem.offsetParent){
					curPos.left += elem.offsetLeft;
					curPos.top += elem.offsetTop;
				}
			}
	
			if($(elemNew).tagName() != 'body')
				while(elemNew = $(elemNew).parent().get(0)){
					if($(elemNew).tagName() != 'body'){
		
						if($(elemNew).scrollLeft())
							curPos.left -= $(elemNew).scrollLeft();
						
						if($(elemNew).scrollTop())
							curPos.top -= $(elemNew).scrollTop();
					}else
						break;
				}
			return curPos;
		},
		
		/**
		 * Gets scrolling for element (consideration div-scrolling)
		 * @param {object} element
		 * @return {object} coordinate of scrolling
		 */
		 getElementScroll: function(element){
			var scroll = {x: 0, y: 0};
			while(element = $(element).parent().get(0)){
				if($(element).tagName() != 'body'  && $(element).css('position') != 'absolute'){
					scroll.x += $(element).scrollLeft();
					scroll.y += $(element).scrollTop();
				}else
					break;
			}
			return scroll;
		},

		/**
		 * Gets cross browser window scrolling
		 * @return {object} coordinate of scrolling
		 */
		 getScrollXY: function(){
			var scroll = {x: 0, y: 0};
			if(typeof(window.pageYOffset) == 'number'){
				//Netscape compliant
				scroll.y = window.pageYOffset;
				scroll.x = window.pageXOffset;
			}else if(document.body && (document.body.scrollLeft || document.body.scrollTop)){
				//DOM compliant
				scroll.y = document.body.scrollTop;
				scroll.x = document.body.scrollLeft;
			}else if(document.documentElement && (document.documentElement.scrollLeft || document.documentElement.scrollTop)){
				//IE6 standards compliant mode
				scroll.y = document.documentElement.scrollTop;
				scroll.x = document.documentElement.scrollLeft;
			}
			return scroll;
		},

		/**
		 * Run loading indicator on page
		 * @param {integer} - id of page
		 * @param {object} - container of page
		 */
		runLoading: function(id,root){
			if(!root){
				return;
			}

			if($(root).tagName()=='table'){
				var parentRoot = $(root).parent();
				$(parentRoot).prepend(Runner.createLoadingForSimplePage());
			}else{
				$(root).prepend(Runner.createLoadingForSimplePage());
			}

			var loadLeft = loadTop = 0,
				elLimitH = elLimitW = 0,
				elFixedH = elFixedW = 0,
				winDim = Runner.getWindowDimensions();

			if ($(root).tagName()=='body'){
				//reload other
				loadLeft = winDim.width/2 - 75;
				loadTop = winDim.height/2 - 25;	
			}else{
				//reload brick grid
				var elAbsPos = Runner.getAbsolutePosition($(root)[0],1),
					loadFon = $(".runner-loading .fon"),
					elPos = [];
				
				if ($(root).closest('div[class=bd]').length){
					//in the popup window
					var divContener = $('div[class=bd]'),
						divAbsPos = Runner.getAbsolutePosition(divContener[0],1);
					
					if (elAbsPos.width < divContener.width()){
						elPos.width = elAbsPos.width;
						elPos.left = elAbsPos.left;
					}else{
						elPos.width = divAbsPos.width - elAbsPos.left + divAbsPos.left;
						elPos.left = elAbsPos.left + divAbsPos.left;
					}
					if (elAbsPos.height < divContener.height()){
						elPos.height = elAbsPos.height;
						elPos.top = elAbsPos.top;
					}else{
						elPos.height = divAbsPos.height-elAbsPos.top + divAbsPos.top;
						elPos.top = elAbsPos.top + divAbsPos.top;
					}
				}else{
					elPos = elAbsPos;
				}
				loadFon.css("width",""+elPos.width+"px");
				loadFon.css("height",""+elPos.height+"px");
				
				elFixedW = winDim.width/2;
				elLimitW = elPos.width/2;
				if (elFixedW < elLimitW){
					loadLeft = elFixedW - 75;
				}else{
					elLimitW = elLimitW + elPos.left;
					if (elLimitW != elPos.left){
						loadLeft = elLimitW - 75;
					}else{ 
						loadLeft = elLimitW + 75;
					}
				}

				elFixedH = winDim.height/2;
				elLimitH = elPos.height/2;
				if (elFixedH < elLimitH){
					loadTop = elFixedH - 25;
				}else{
					elLimitH = elLimitH + elPos.top;
					if(elLimitH != elPos.top){
						loadTop = elLimitH - 25;
					}else{ 
						loadTop = elLimitH + 25;
					}
				}
			}
			$(".runner-loading .main").css({
				"left": ""+loadLeft+"px",
				"top": ""+loadTop+"px"
			});	
			$(".runner-loading").removeClass("hide").addClass("show");
		},

		/**
		 * Stop showing loading indicator 
		 */
		stopLoading: function(){
			$(".runner-loading").remove();
		/*	if(mode == 3){	
				//Show loaded content for details preview
				$("#loaded_content"+id).css('position','static');
				$("#loaded_content"+id).css('left','0px');
			}*/
		},

		/**
		 * Get HTML code for loading indicator on simple mode
		 * @param {object} - container of page
		 * @return {string} html 
		 */
		createLoadingForSimplePage: function(){

			return '<div class="runner-loading hide">'+
					'<div class="main runner-panel">'+
							'<img src="images/loading.gif" align="absmiddle">'+
								'<span class="text">'+Runner.lang.constants.TEXT_LOADING+' .....</span>'+
					'</div>'+
					'<div class="fon"> </div>'+
					'</div>';
		},

		/**
		 * Get client viewport dimensions
		 * @return {object} {width, height}
		 */
		getWindowDimensions: function(){
			var myWidth = 0, 
				myHeight = 0;
			if(typeof(window.innerWidth) == 'number'){
				//Non-IE
				myWidth = window.innerWidth;
				myHeight = window.innerHeight;
			}else if(document.documentElement && (document.documentElement.clientWidth || document.documentElement.clientHeight)){
				//IE 6+ in 'standards compliant mode'
				myWidth = document.documentElement.clientWidth;
				myHeight = document.documentElement.clientHeight;
			}else if(document.body && (document.body.clientWidth || document.body.clientHeight)){
				//IE 4 compatible
				myWidth = document.body.clientWidth;
				myHeight = document.body.clientHeight;
			}
			return {'width': myWidth, 'height': myHeight};
		},

		/**
		 * Decodes after encoded str_replace(array("&","<",">"),array("&amp;","&lt;","&gt;"),$jscode);
		 * @param {string}
		 * @return {string}
		 */
		htmlDecode: function(txt){
			txt = txt.replace(/&gt;/ig,"\>");
			txt = txt.replace(/&lt;/ig,"\<");
			txt = txt.replace(/&amp;/ig,"&");
			return txt;
		},

		setIdCounter: function(num){
			if (typeof num != 'number'){
				return false;
			}
			idCounter += ++num;
		},

		/**
		 * Set z-index css property to element
		 * @param {object} element
		 * @return {integer} new z-index max
		 */
		setZindexMaxToElem: function(elObj){
			if (elObj){
				$(elObj).css("z-index", ++zIndexMax);
			}
			return zIndexMax;
		},

		/**
		 * Set max z-ndex if it smaller than current counter
		 * @param {object} element
		 */
		setZindexMax: function(counter){
			if (zIndexMax < counter){
				zIndexMax = ++counter;
			}
		},

		/**
		 * Generates unique id
		 */
		genId: function(pref){
			return ++idCounter;
		},

		/**
		 * Get control by feild name and row id
		 * @return {object} control
		 */
		getControl: function(rowId, fName){
			return Runner.controls.ControlManager.getAt(false, rowId, fName);
		},

		/**
		 * Replace all except numbers and strings into _
		 */
		goodFieldName: function(fName){
			return fName.replace(/\W/g, '_');
		},

		getSearchController: function(id){
			return window['searchController'+id];
		},

		isArray: function(toCheck){
			return toCheck && typeof toCheck.splice == 'function' && typeof toCheck.length == 'number';
		},

		/**
		 * True if browser is Opera.
		 * @type Boolean
		 */
		isOpera : isOpera,
		/**
		 * True if browser is Mozilla
		 * @type Boolean
		 */
		isGecko : isGecko,
		/**
		 * True if browser is Firefox 2++
		 * @type Boolean
		 */
		isGecko2 : isGecko && !isGecko3,
		/**
		 * True if browser is Firefox 3++
		 * @type Boolean
		 */
		isGecko3 : isGecko3,
		/**
		 * True if browser is Safari.
		 * @type Boolean
		 */
		isSafari : isSafari,
		/**
		 * True if browser is Safari 3++
		 * @type Boolean
		 */
		isSafari3 : isSafari3,
		/**
		 * True if browser is Safari 2++
		 * @type Boolean
		 */
		isSafari2 : isSafari && !isSafari3,
		/**
		 * True if browser is Internet Explorer.
		 * @type Boolean
		 */
		isIE : isIE,
		/**
		 * True if browser is Internet Explorer 7++
		 * @type Boolean
		 */
		isIE7 : isIE7,
		/**
		 * True if browser is Internet Explorer 8++
		 * @type Boolean
		 */
		isIE8 : isIE8,
		/**
		 * True if browser is Chrome.
		 * @type Boolean
		 */
		isChrome : isChrome,  
		isSecure : isSecure,
		/**
		 * Use debug mode for js files or not
		 * @type Boolean
		 */
		debugMode: false
	});
	
	Runner.ns = Runner.namespace;
})();

/**
 * Controls objects package
 * @type {object}
 */
Runner.namespace('Runner.controls');
/**
 * Search objects package
 * @type {object} 
 */
Runner.namespace('Runner.search');
/**
 * Set emty object 
 * if there isn't console in browser
 */
if (!window.console){ 
	console = {};
}
/**
 * Set Tag Name for jQuery
 * @return {string}
 */
$.fn.tagName = function(){
	return this.get(0).tagName.toLowerCase();
}
/**
 * produces a string in which '<', '>', and '&' are replaced with their HTML entity equivalents. 
 * This is essential for placing arbitrary strings into HTML texts. So, "if (a < b && b > c) {".entityify()
 * produces
 * "if (a &lt; b &amp;&amp; b &gt; c) {"
 * @return {string}
 */
String.prototype.entityify = function () {
	return this.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace('"', '&quot;');
};
/**
 * produces a quoted string. 
 * This method returns a string that is like the original string except 
 * that all quote and backslash characters are preceded with backslash.
 * @return {string}
 */
String.prototype.quote = function () {
	return this.replace("\\","\\\\").replace("'","\\'");
};
/**
 * does variable substitution on the string. 
 * It scans through the string looking for expressions enclosed in { } braces. 
 * If an expression is found, use it as a key on the object, and if the key has a string value or number value, 
 * it is substituted for the bracket expression and it repeats. This is useful for automatically fixing URLs. So
 * param = {domain: 'lala.com', media: 'http://zhuzhu.com/'};
 * url = "{media}logo.gif".xTempl(param);
 * produces a url containing "http://zhuzhu.com/logo.gif".
 * @param {object} o
 * @return {string}
 */
String.prototype.xTempl = function (o) {
	return this.replace(/{([^{}]*)}/g,
		function (a, b){
			var r = o[b];
			return typeof r === 'string' || typeof r === 'number' ? r : a;
		}
	);
};
/**
 * method removes whitespace characters from the beginning and end of the string.
 * @return {string}
 */
String.prototype.trim = function () {
	return this.replace(/^\s+|\s+$/g, "");
}; 

String.prototype.slashdecode = function(){
	var out = '';
	var pos = 0;
	for ( var i = 0; i < this.length - 1; i++ )
	{
		var c = this.charAt(i);
		if( c == '\\' )
		{
			out += this.substr(pos,i-pos);
			pos = i + 2;
			var c1 = this.charAt(i+1);
			i++;
			if ( c1 == '\\' ) {
				out += "\\";
			} else if ( c1 == 'r' ) {
				out += "\r";
			} else if ( c1 == 'n') {
				out += "\n";
			} else {
				i--;
				pos-=2;
			}
		}
	}
	if ( pos < this.length )
		out += this.substr(pos);
	
	return out;
};

/**
 * Checks if value exist in array
 * @param {mixed} value val to search
 * @param {bool} caseSensitive
 * @return {Boolean}
 */
Array.prototype.isInArray = function(value, caseSensitive){	
	for (var i=0; i < this.length; i++){
		if (caseSensitive) {
			if(this[i] == value){
				return true;
			}
		}else{
			if(this[i].toString().toLowerCase() == value.toString().toLowerCase()){
				return true;
			}
		}
	}
	return false;
};
/**
 * Counts elements in array
 * @param {mixed} value val to search
 * @param {bool} caseSensitive
 * @return {int}
 */
Array.prototype.countElems = function(value, caseSensitive){
	var count = 0;
	for (var i=0; i < this.length; i++){
		if (caseSensitive) {
			if(this[i] == value){
				count++;
			}
		}else{
			if(this[i].toString().toLowerCase() == value.toString().toLowerCase()){
				count++;
			}
		}
	}
	return count;
};

/**
 * Return index of element in array. If element doesn't exist in array, returns -1
 * @param {mixed} value value to search
 * @param {} callBack link to function that may used to comparison, 
 * accepts arguemnts:
 * 		value {mixed} value to search
 * 		elem {mixed} current array element in loop
 * 
 * @param {bool} caseSensitive doesn't work with callBack function
 * @return {int} index of element if found, or -1 if element doesn't exist in array
 */
Array.prototype.getIndexOfElem = function(value, callBack, caseSensitive){
	for (var i=0; i < this.length; i++){
		if (callBack){
			if(callBack(value, this[i])){
				return i;
			}
		}else if (caseSensitive){
			if (this[i] == value){
				return i; 
			}
		}else{
			if (this[i].toString().toLowerCase() == value.toString().toLowerCase()) {
				return i; 
			}
		}
	}
	return -1;
};

Array.prototype.removeElem = function(value, callBack, caseSensitive){
	for (var i=0; i < this.length; i++) {
		if(callBack){
			if(callBack(value, this[i])){
				this.splice(i, 1);
				return this;
			}
		}else if(caseSensitive) {
			if (this[i] == value) {
				this.splice(i, 1);
				return this;
			}
		}else{
			if (this[i].toString().toLowerCase() == value.toString().toLowerCase()) {
				this.splice(i, 1);
				return this;
			}
		}
	}
	return this;
};

Array.prototype.copy = function(){
	var copy = [];
	for (var i=0; i < this.length; i++) {
		copy[i] = this[i];
	}
	return copy;
};

Runner.apply(Function.prototype, {
	createCallback : function(){
		var args = arguments;
		var func = this;
		return function() {
			return func.apply(window, args);
		};
	},

	createDelegate : function(obj, args, appendArgs){
		var func = this;
		return function() {
			var callArgs = args || arguments;
			if(appendArgs === true){
				callArgs = Array.prototype.slice.call(arguments, 0);
				callArgs = callArgs.concat(args);
			}else if(typeof appendArgs == "number"){
				callArgs = Array.prototype.slice.call(arguments, 0); 
				var applyArgs = [appendArgs, 0].concat(args); 
				Array.prototype.splice.apply(callArgs, applyArgs); 
			}
			return func.apply(obj || window, callArgs);
		};
	}
});
Runner.namespace('Runner.util');

(function(){
	
	var createDelayed = function(hn, obj, scope){
		return function(){
			var argsArr = Array.prototype.slice.call(arguments, 0);
			setTimeout(function(){
				hn.apply(scope, argsArr);
			}, obj.delay || 10);
		};
	};
	
	var createSingle = function(hn, e, fn, scope){
		return function(){
			e.removeListener(fn, scope);
			return hn.apply(scope, arguments);
		};
	};
	
	var createBuffered = function(hn, obj, scope){
		var task = new Runner.util.DelayedTask();
		return function(){
			task.delay(obj.buffer, hn, scope, Array.prototype.slice.call(arguments, 0));
		};
	};
	
	Runner.util.Event = function(obj, name){
		this.name = name;
		this.obj = obj;
		this.listeners = [];
	};
	
	Runner.util.Event.prototype = {
		createListener: function(fn, scope, obj){
			obj = obj || {};
			scope = scope || this.obj;
			var ls = {
				fn: fn, 
				scope: scope,
				options: obj
			};
			var hn = fn;
			if(obj.delay){
				hn = createDelayed(hn, obj, scope);
			}
			if(obj.single){
				hn = createSingle(hn, this, fn, scope);
			}
			if(obj.buffer){
				hn = createBuffered(hn, obj, scope);
			}
			ls.fireFn = hn;
			return ls;
		},
		
		getListenerIndex: function(fn, scope){
			scope = scope || this.obj;
			var length = this.listeners.length,
				ls;
			for(var i = 0; i < length; i++){
				ls = this.listeners[i];
				if(ls.fn == fn && ls.scope == scope){
					return i;
				}
			}
			return -1;
		},
		
		session: null,
		
		fire: function(){
			var scope,
				ls,
				length = this.listeners.length;
			if(this.listeners.length > 0){
				this.firing = true;
				var argsArr = Array.prototype.slice.call(arguments, 0);
				for(var i = 0; i < this.listeners.length; i++){
					ls = this.listeners[i];
					if ( this.session === null || ls.session == this.session){
						if(ls.fireFn.apply(ls.scope || this.obj || window, arguments) === false){
							this.firing = false;
							return false;
						}
					}
				}
				this.firing = false;
			}
			return true;
		},
		
		on: function(fn, scope  , options, session){
			scope = scope || this.obj;
			if(!this.isListening(fn, scope)){
				ls = this.createListener(fn, scope, options);
				ls.session = session;
				if(!this.firing){
					this.listeners.push(ls);
				}else{
					//this.listeners = this.listeners.slice(0);
					this.listeners.push(ls);
				}
			}
		},
		
		isListening: function(fn, scope){
			return this.getListenerIndex(fn, scope) != -1;
		},
		
		removeListener: function(fn, scope){
			var index;
			if((index = this.getListenerIndex(fn, scope)) != -1){
				if(!this.firing){
					this.listeners.splice(index, 1);
				}else{
					this.listeners = this.listeners.slice(0);
					this.listeners.splice(index, 1);
				}
				return true;
			}
			return false;
		},
		
		clearListeners: function(){
			this.listeners = [];
		}
	};
})();


/**
 * @class Runner.util.Observable
 * Observer-subscriber class
 */
Runner.util.Observable = Runner.extend(Runner.emptyFn, {
	
	filterOptRe: /^(?:scope|delay|buffer|single)$/,
	
	addEvents: function(obj){
		if(!this.events){
			this.events = {};
		}
		if(typeof obj == 'string'){
			for(var i = 0, a = arguments, v; v = a[i]; i++){
				if(!this.events[a[i]]){
					this.events[a[i]] = true;
				}
			}
		}else{
			Runner.apply(this.events, obj);
		}
	},
	
	fireEventFilesLoaded: function(){
		if(this.eventsSuspended !== true){
			var ce = this.events[arguments[0].toLowerCase()];// || this.events[arguments[0]];
			if(typeof ce == "object"){
				ce.session = arguments[1];
				var r =  ce.fire.apply(ce, Array.prototype.slice.call(arguments, 1));
				ce.session = null;
				return r;
			}
		}
		return true;
	},
	
	fireEvent: function(){
		if(this.eventsSuspended !== true){
			var ce = this.events[arguments[0].toLowerCase()];// || this.events[arguments[0]];
			if(typeof ce == "object"){
				return ce.fire.apply(ce, Array.prototype.slice.call(arguments, 1));
			}
		}
		return true;
	},
	
	session: 0,
	
	on: function(evName, fn, scope, obj){
		if(typeof evName == "object"){
			obj = evName;
			for(var event in obj){
				if(this.filterOptRe.test(event)){
					continue;
				}
				if(typeof obj[event] == "function"){
					this.on(event, obj[event], obj.scope, obj);
				}else{
					this.on(event, obj[event].fn, obj[event].scope, obj[event]);
				}
			}
			return;
		}
		obj = (!obj || typeof obj == "boolean") ? {} : obj;
		evName = evName.toLowerCase();
		var ce = this.events[evName] || true;
		if(typeof ce == "boolean"){
			ce = new Runner.util.Event(this, evName);
			this.events[evName] = ce;
		}
		ce.on(fn, scope, obj, this.session);
	},
	
	un: function(evName, fn, scope){
		var ce = this.events[evName.toLowerCase()];
		if(typeof ce == "object"){
			ce.removeListener(fn, scope);
		}
	},
	
	purgeListeners: function(){
		for(var event in this.events){
			if(typeof this.events[event] == "object"){
				this.events[event].clearListeners();
			}
		}
	},
	
	suspendEvents: function(){
	this.eventsSuspended = true;
	},
	
	resumeEvents: function(){
		this.eventsSuspended = false;
	}
});


// register new namespace
Runner.namespace('Runner.util');

/**
 * @class Runner.util.DelayedTask
 * method for performing setTimeout where a new timeout cancels the old timeout. 
 * @param {Function} fn (optional) The default function to timeout
 * @param {Object} scope (optional) The default scope of that timeout
 * @param {Array} args (optional) The default Array of arguments
 */
Runner.util.DelayedTask = function(fn, scope, args){
	var id = null, delay, time;
	
	var call = function(){
		var now = new Date().getTime();
		if(now - time >= delay){
			clearInterval(id);
			id = null;
			fn.apply(scope, args || []);
		}
	};
	
	/**
	 * Cancels any pending timeout and queues a new one
	 * @param {Number} delay The milliseconds to delay
	 * @param {Function} newFn (optional) Overrides function passed to constructor
	 * @param {Object} newScope (optional) Overrides scope passed to constructor
	 * @param {Array} newArgs (optional) Overrides args passed to constructor
	 */
	this.delay = function(newDelay, newFn, newScope, newArgs){
		if(id && delay != newDelay){
			this.cancel();
		}
		delay = newDelay;
		time = new Date().getTime();
		fn = newFn || fn;
		scope = newScope || scope;
		args = newArgs || args;
		if(!id){
			id = setInterval(call, delay);
		}
	};
	
	/**
	 * Cancel the last queued timeout
	 */
	this.cancel = function(){
		if(id){
			clearInterval(id);
			id = null;
		}
	};
};

/**
 * Global object for loading scripts and css files
 * @object
 */
Runner.util.ScriptLoader = Runner.extend(Runner.util.Observable, {
	/**
	 * Array of CSS files for loading
	 * @type 
	 */
	cssFiles: [],
	/**
	 * Array of file names for load
	 * @type {array}
	 */
	jsFiles: [],
	
	constructor: function(cfg){
		Runner.util.ScriptLoader.superclass.constructor.call(this, cfg);
		this.addEvents('filesLoaded');
		
		this.on('filesLoaded', function(){
			if (Runner.pages){
				Runner.pages.PageManager.initPages();
			}
		}, this, {single: true});
		
	},
	
	/**
	 * Add js file to load queue
	 * @param {array} files
	 * @param any param except first will be added for requirements array
	 */
	addJS: function(files){
		var self = this,
			isAdded = false;
		// loop through all files to add
		for (var i=0;i<files.length;i++){
			// check if such file was added before
			for (var j=0;j<this.jsFiles.length;j++){
				if (this.jsFiles[j].name == files[i]){
					isAdded = true;
					break;
				}
			}
			// add only new files
			if(!isAdded){
				// add files to array of file names
				var req = [],
					onload = null,
					arg = Array.prototype.slice.call(arguments, 1);
				for(var k=0;k < arg.length; k++){
					if (typeof(arg[k]) == typeof('string')){
						req.push(arg[k]);
					}
					if (typeof(arg[k]) == typeof(function(){})){
						//req.push(arg[k]);
						onload = function(f){ return f; }(arg[k]);
					}
				}
				this.jsFiles.push({
					name: files[i],
					isLoaded: false,
					//	add requirements, all passed arguments, except first
					requirements: Array.prototype.slice.call(arguments, 1),
					'onload': onload,
					session: parseInt(self.session)
				});
			}
			// reinit var
			isAdded = false;
		}
	},
	
	/**
	 * Method for load CSS files
	 * @param files {array}
	 */
	loadCSS: function (files, loadIE){
		for (var i=0;i<files.length; i++){
			// check if file exist in array of CSS files, try to get it's index
			var idx = this.cssFiles.getIndexOfElem(files[i], function(val, arrElem){
				if (val == arrElem.name){
					return true;
				}
			});
			
			// if file already added and it was loaded, than return true
			if (idx!=-1 && this.cssFiles[idx].isLoaded){
				continue;
			}
			this.cssFiles.push({
				name: files[i],
				isLoaded: true
			})
			var existInHead = false;
			$('head link[rel="stylesheet"]').each(function(index, element){
				if($(element).attr('href') == files[i] + ".css"){
					existInHead = true;
					return false;
				}
			});
			if(existInHead)
				continue;
			// load file
			var head = $(document).find('head')[0];
			var css = document.createElement('link');
			css.setAttribute('rel', 'stylesheet');
			css.setAttribute('type', 'text/css');
			css.setAttribute('href', files[i]+".css");
			head.appendChild(css);
			if(loadIE && Runner.isIE){
				css = document.createElement('link');
				css.setAttribute('rel', 'stylesheet');
				css.setAttribute('type', 'text/css');
				css.setAttribute('href', files[i]+"IE.css");
				head.appendChild(css);
			}
		}
	},
	
	load: function(){
		this.session = this.session + 1;
		for(var i=0;i<this.jsFiles.length;i++){
			this.loadJS(i);
		}
	},
	/**
	 * Load file from queue
	 * @param {int} idx file index
	 * @return {bool} true if success
	 * @method
	 * @private
	 */
	loadJS: function(idx){
		// return if no file obj for this file
		if (!this.jsFiles[idx]){
			return false;
		}
		// if loaded, load dependent files
		if(this.jsFiles[idx].isLoaded){
			this.jsFiles[idx].session = this.session - 1;
			this.postLoad(idx);
			return true;
		}
		// check requirements
		if (!this.checkReq(this.jsFiles[idx])){
			return false;
		}
		// file loading started already
		if(this.jsFiles[idx].isStarted){
			return false;
		}
		// load file
		this.jsFiles[idx].isStarted = true;
		var js = document.createElement('script');
		js.setAttribute('type', 'text/javascript');
		js.setAttribute('src', this.jsFiles[idx].name);
		var initFuncName = this.jsFiles[idx].name.replace('.js','') + '_init';
		initFuncName = initFuncName.replace(/[\/\\.]/g, "_");
		//initFuncName = initFuncName.replace("/", "_");
		
		var sl = this;
		
		//	add onload event handler
		if (typeof(this.jsFiles[idx].onload) == typeof(function(){})){
			if(Runner.isIE){
				js.onreadystatechange = function(){
					if (js.readyState == 'complete' || js.readyState == 'loaded'){
						sl.jsFiles[idx].onload.call(sl, idx);
					}
				};
			}else{
				js.onload = function() {
					sl.jsFiles[idx].onload.call(sl, idx);
				};
			}
		} else {
			if(Runner.isIE){
				js.onreadystatechange = function(){
					if (js.readyState == 'complete' || js.readyState == 'loaded'){
						if (typeof(window[initFuncName]) == typeof(function(){})){
							window[initFuncName].call(sl, idx);
						} else {
							sl.postLoad(idx);
						}
					}
				};
			}else{
				js.onload = function() {
					if (typeof(window[initFuncName]) == typeof(function(){})){
						window[initFuncName].call(sl, idx);
					} else {
						sl.postLoad(idx);
					}
				};
			}
		}
		
		document.getElementsByTagName('HEAD')[0].appendChild(js);
		return true;
	},
	
	/**
	 * Checks is required files are loaded
	 * @param {object} fileObj
	 * @return {Boolean}
	 */
	checkReq: function(fileObj){
		// loop through all files
		for(var i=0;i<fileObj.requirements.length;i++){
			// loop through all req
			for(var j=0;j<this.jsFiles.length;j++){
				// if req cotains loaded file, than try to load it
				if (fileObj.requirements[i] == this.jsFiles[j].name && !this.jsFiles[j].isLoaded){ 
					return false;
				}
			}
		}
		return true;
	},
	
	/**
	 * After event handler. Called after file loaded.
	 * @method
	 */
	postLoad: function(idx, session){
		var loadedAll = true;
		if (idx !== undefined){
			this.jsFiles[idx].isLoaded = true;
			this.loadDependent(idx);
			var session = this.jsFiles[idx].session;
		}
		for(var i=0;i<this.jsFiles.length; i++){
			if (!this.jsFiles[i].isLoaded /*&& this.jsFiles[i].session == session*/){
				loadedAll = false;
				break;
			}
		}
		if (loadedAll){
				if ($.inArray(session, this.loadedSes) == -1 ){
					this.fireEventFilesLoaded('filesLoaded', session);
					this.loadedSes.push(session);
				}
				if (idx!==undefined){
					for(var i=0; i < this.session; i++ ){
						this.postLoad(undefined, i);
					}
				}
		}
	},
	
	loadedSes:[],
	/**
	 * Call load for files, which are dependent to file with index = idx
	 * @param {int} idx
	 */
	loadDependent: function(idx){
		// loop through all files
		for(var i=0;i<this.jsFiles.length;i++){
			// loop through all req
			for(var j=0;j<this.jsFiles[i].requirements.length;j++){
				// if req cotains loaded file, than try to load it
				if (i != idx && this.jsFiles[i].requirements[j] == this.jsFiles[idx].name){ 
					this.loadJS(i);
				}
			}
		}
	}	
});
Runner.util.ScriptLoader = new Runner.util.ScriptLoader();
 
// create namespace
Runner.namespace('Runner.bricks');

/**
 * General class for manage bricks and containers
 * @class Runner.bricks.BrickManager
 */
Runner.bricks.BrickManager = Runner.extend(Runner.emptyFn,{
	/**
	 * jQuery element
	 * Block, container or element wich contain brick
	 * @type {object}
	 */
	elem: null,
	/**
	 * Name of container, brick or block
	 * Without contain 'runner-c-', 'runner-b-' or 'runner-'
	 * @type {string}
	 */
	name: '',
	/**
	 * First part of base class
	 * Without contain name
	 * @type {string}
	 */
	baseClass: '',
	/**
	 * Class Name for hide container, brick or block
	 * @type {string}
	 */
	hiddenClass: '',
		
	constructor: function(cfg){
		Runner.apply(this, cfg);
		this.getElemName();
	},
	
	/**
	 * Get element name of brick, container or block 
	 * without base part of class name
	 * @param object element table for container
	 */
	getElemName: function(elem){
		if(this.name){
			return;
		}
		if(typeof elem == 'undefined'){
			elem = this.elem;
		}	
		var cls = elem.attr('class').split(' ');
		for(var i=0; i<cls.length; i++){
			if(cls[i]){
				var pos = cls[i].indexOf(this.baseClass);
				if(pos > -1){
					this.name = cls[i].substr(pos+this.baseClass.length).trim();
					break;
				}
			}	
		}
	},
	
	/**
	 * Hide element 
	 * Add hidden class
	 */
	hide: function(){
		this.elem.addClass(this.hiddenClass);
	},
	/**
	 * Show element 
	 * Remove hidden class
	 */
	show: function(){
		this.elem.removeClass(this.hiddenClass);
	},
	
	/**
	 * Check on visible element
	 * @return {boolean}
	 */
	visible: function(){
		return this.elem.is(':visible');
	}

});

/**
 * @class Runner.bricks.Brick
 */
Runner.bricks.Brick = Runner.extend(Runner.bricks.BrickManager,{
	
	contObj: false,
	
	contentElem: false,
	
	constructor: function(cfg){
		cfg.baseClass = 'runner-b-';
		cfg.hiddenClass = 'runner-hiddenbrick';
		Runner.bricks.Brick.superclass.constructor.call(this, cfg);	
		this.getContainer();
		this.getContentElem();
	},
	
	/**
	 * Get content HTML element 
	 * If there isn't element with class runner-brickcontents in this brick
	 * Then content element it is this brick element
	 * @method
	 */
	getContentElem: function(){
		var brickContents = $('.runner-brickcontents',this.elem);
		if(brickContents.length){
			this.contentElem = brickContents;
		}else{
			this.contentElem = this.elem;
		}	
	},
	
	/**
	 * Get container HTML element 
	 * @method
	 * @return {object}
	 */
	getContainer: function(){
		var contElem = this.elem.closest('div');
		if(contElem.length){
			this.contObj = new Runner.bricks.Container({
				elem: contElem
			});
		}
	},
	
	/**
	 * Hide brick
	 * If this brick the single in the container
	 * Then the container must be hidden too!
	 */
	hide: function(){
		Runner.bricks.Brick.superclass.hide.call(this);
		if(!this.contObj){
			return;
		}
		
		var contBricks = this.contObj.getBricks(),
			visible = false;
			
		for(var i = 0, l = contBricks.length; i<l; i++){
			if(contBricks[i].visible()){
				visible = true;
				break;
			}
		}
		
		//If there isn't any bricks in the container, hide container
		if(!visible){
			this.contObj.hide();
		}
	},
	
	/**
	 * Show brick
	 * If this brick position in the hidden container
	 * Than the container must be shown too!
	 */
	show: function(){
		Runner.bricks.Brick.superclass.show.call(this);
		if(!this.contObj){
			return;
		}
		this.contObj.show();
	},
	
	/**
	 * Check html is allowed to replace 
	 * @param {mixed} 
	 * @return {boolean}
	 */
	allowedToReplaceWith: function(html){
		if(html){
			if(!this.visible()){
				this.show();
			}
			return true;
		}else if(this.visible()){
			this.hide();
		}
		return false;
	},
	
	/**
	 * Replace html for brick
	 * @param {mixed} new html
	 */
	replaceHTMLWith: function(newHTML){
		if(this.allowedToReplaceWith(newHTML)){
			this.elem.empty().html(newHTML);
		}
	},
	
	/**
	 * Replace itself content of brick
	 * @param {mixed} new brick content
	 */
	replaceContentWith: function(newContent){
		if(this.allowedToReplaceWith(newContent)){
			this.contentElem.replaceWith(newContent);
		}
	},
	
	/**
	 * Replace html for brick contents
	 * @param {mixed} new contents
	 */
	replaceContentHTMLWith: function(newHTML){
		if(this.allowedToReplaceWith(newHTML)){
			this.contentElem.empty().html(newHTML);
		}
	}
});

/**
 * @class Runner.bricks.Container
 */
Runner.bricks.Container = Runner.extend(Runner.bricks.BrickManager,{
	
	blockObj: false,
	
	constructor: function(cfg){
		cfg.baseClass = 'runner-c-';
		cfg.hiddenClass = 'runner-hiddencontainer';
		Runner.bricks.Container.superclass.constructor.call(this, cfg);	
		this.getBlock();
	},
	
	getElemName: function(){
		Runner.bricks.Container.superclass.getElemName.call(this, $('table:first',this.elem));
	},
	
	/**
	 * Hide container
	 * If this container the single in the block
	 * Then the block must be hidden too!
	 */
	hide: function(){
		Runner.bricks.Container.superclass.hide.call(this);
		if(!this.blockObj){
			return;
		}
		
		var blockConts = this.blockObj.getContainers(),
			visible = false;
			
		for(var i = 0, l = blockConts.length; i<l; i++){
			if(blockConts[i].visible()){
				visible = true;
				break;
			}
		}
		
		//If there isn't any containers in the block, hide block
		if(!visible){
			this.blockObj.hide();
		}
	},
	
	/**
	 * Show container
	 * If this container position in the hidden block
	 * Than the block must be shown too!
	 */
	show: function(){
		Runner.bricks.Container.superclass.show.call(this);
		if(!this.blockObj){
			return;
		}
		this.blockObj.show();
	},
	
	/**
	 * Get array of bricks for container
	 * @method
	 * @return {array}  
	 */
	getBricks: function(){
		var bricksArr = [];
		$('[class*="runner-b-"]', this.elem).each(function(){
			bricksArr.push(new Runner.bricks.Brick({
				elem: $(this)
			}));
		});
		return bricksArr;
	},
	
	/**
	 * Get block HTML element 
	 * @method
	 * @return {object}  
	 */
	getBlock: function(elem){
		var blockElem;
		if(typeof elem == 'undefined'){
			blockElem = this.elem.closest('td');
		}else{
			blockElem = elem.parent().closest('td');
		}
		if(blockElem.hasClass('runner-wrapper')){
			this.getBlock(blockElem);
		}else if(blockElem.length){
			this.blockObj = new Runner.bricks.Block({
				elem: blockElem
			});
		}
	}
});

/**
 * @class Runner.bricks.Block
 */
Runner.bricks.Block = Runner.extend(Runner.bricks.BrickManager,{
	
	constructor: function(cfg){
		cfg.baseClass = 'runner-';
		cfg.hiddenClass = 'runner-hiddenblock';
		Runner.bricks.Block.superclass.constructor.call(this, cfg);	
	},
	
	/**
	 * Get array of containers for block
	 * @method
	 * @return {array}  
	 */
	getContainers: function(){
		var contsArr = [];
		$('[class*="runner-s-"]', this.elem).each(function(){
			contsArr.push(new Runner.bricks.Container({
				elem: $(this)
			}));
		});
		return contsArr;
	}
});

// create namespace
Runner.namespace('Runner.menu');

/**
 * @class Runner.menu.Manager
 * Abstract base class that provides common menu functionality. 
 */
Runner.menu.Manager = Runner.extend(Runner.emptyFn,{

	/**
	 * Click handler function for menu items
	 * @param {object} event
	 */	
	itemClickHandler: function(event){
		var target = Runner.Event.prototype.getTarget(event),
			link = $('a:first',this)[0];
		
		if($(this).hasClass('Group') && target.nodeName!='IMG' && !link.href){
			$('.groupImg:first', this).click();
			return;
		}
		
		if(target.nodeName == "A" ){
			if(target.rel == 'external'){
				return !window.open(target.href);
			}	
			return;
		}
		
		Runner.Event.prototype.stopEvent(event);	
		if(link.href){
			if(link.rel == 'external'){
				return !window.open(link.href);
			}	
			window.location = link.href;
		}
	}
});

/**
 * @class Runner.menu.QuickJump
 * Abstract base class that provides quick jump menu functionality. 
 */
Runner.menu.QuickJump = Runner.extend(Runner.emptyFn, {
	/**
	 * Current selected item in dropdown
	 * @type Integer
	 */
	selectCurrent: -1,
	/**
	 * Init quickjump menu
	 * Bind events handlers on dropdown
	 */
	init: function(){
		var menuObj = this;
		$(".runner-quickjump:first").prop("initialized","true");
		$(".runner-quickjump").bind({
			focus: function(){
				menuObj.selectCurrent = this.selectedIndex; 
			},
			change: function(){
				if(this.options[this.selectedIndex].value){
					if($(this.options[this.selectedIndex]).attr('link') == 'External'){
						window.open(this.options[this.selectedIndex].value);
					}else{
						window.location.href = this.options[this.selectedIndex].value;
					}
				}else{
					this.selectedIndex = menuObj.selectCurrent;
				}
			}	
		});
	}	
});

/**
 * @class Runner.menu.Horizontal
 * Abstract base class that provides horizontal menu functionality. 
 */
Runner.menu.Horizontal = Runner.extend(Runner.menu.Manager,{
	/**
	 * Max submenu width
	 * @type Integer
	 */
	maxSubMenuWidth: 0, 
	/**
	 * Max group (ul) width
	 * @type Integer
	 */
	maxGroupWidth: 0,
	/**
	 * Max item (li) width
	 * @type Integer
	 */
	maxItemWidth: 0,
	/**
	 * Was top item hovered or not
	 * @type Boolean
	 */
	topItemHovered: false,
	/**
	 * Absolute posution for top item
	 * @type object
	 */
	topItemAbsPos: null,
	/**
	 * Was item hovered or not
	 * @type Boolean
	 */
	itemHovered: false,
	/**
	 * Sub menu for hovered item
	 * @type object
	 */
	subMenu: null,
	
	init: function(){
		this.bindHoverOnItems();
		this.setRaquoToTopItems();
		$(".runner-hmenu:first").prop("initialized","true");
	},
	
	/**
	 * Find sub menu for item
	 * @param {obj} elem
	 */
	findSubMenu: function(elem){
		this.subMenu = $('ul:first:has(li)', elem);
	},
	
	/**
	 * Manage add/remove class Active
	 * @param {obj} elem
	 * @param {boolean} toggle
	 */
	manageActiveClass: function(elem ,toggle){
		
		if($(elem).attr('class')!='Separator'){
			//if top item 
			if($('table:first',elem).length){
				$('.runner-menutab',elem).toggleClass('active',toggle);
			}else{
				$(elem).toggleClass('active', toggle);
			}	
		}	
	},
	
	/**
	 * Bind hover event on every menu item
	 * For hovered items add class 'active'
	 * For items losted hover event remove class 'active'
	 */
	bindHoverOnItems: function(){
		var menuObj = this;
			
		$('.runner-hmenu td, .runner-hmenu ul li').hover(function(){
			menuObj.findSubMenu(this);
			menuObj.manageActiveClass(this, true);	
			
			if(menuObj.subMenu.length){
				menuObj.maxGroupWidth = 0;
				
				if($(this).attr('view')=='topitem'){
					menuObj.topItemAbsPos = Runner.getAbsolutePosition(this,1);
					menuObj.maxItemWidth = menuObj.topItemAbsPos.width;
					menuObj.topItemHovered = false;
				}
				
				menuObj.subMenu.css('display','block');

				$('li[parent='+(menuObj.subMenu.attr('id'))+'] a', menuObj.subMenu).each(function(){
					if(menuObj.maxGroupWidth < this.offsetWidth)
						menuObj.maxGroupWidth = this.offsetWidth
				});
				
				if(!menuObj.topItemHovered && menuObj.maxGroupWidth < menuObj.maxItemWidth){
					menuObj.maxSubMenuWidth = menuObj.maxItemWidth;
				}else{
					menuObj.maxSubMenuWidth = menuObj.maxGroupWidth;
				}	
				
				menuObj.maxSubMenuWidth += 20;	
				menuObj.subMenu.css('width',''+menuObj.maxSubMenuWidth+'px');
				
				if(!menuObj.topItemHovered){
					if(document.dir=='rtl'){
						menuObj.subMenu.css('left',''+(menuObj.topItemAbsPos.left + (menuObj.topItemAbsPos.width - menuObj.subMenu[0].offsetWidth))+'px');
					}else{
						menuObj.subMenu.css('left',''+(menuObj.topItemAbsPos.left)+'px');
					}	
					menuObj.subMenu.css('top',''+(menuObj.topItemAbsPos.top + menuObj.topItemAbsPos.height - 1)+'px');
				}else{
					var raquoElement = $('.raquo',this)[0];
					menuObj.subMenu.css('top',''+this.offsetTop+'px');
					menuObj.subMenu.css('left',''+(Runner.getAbsolutePosition(raquoElement, 1).left + raquoElement.offsetWidth - Runner.getAbsolutePosition(this,1).left + 10)+'px');
				}	
				
				$('li[parent='+(menuObj.subMenu.attr('id'))+']', menuObj.subMenu).each(function(){
					$(this).css('width',''+menuObj.maxSubMenuWidth+'px');
					if(document.dir=='rtl'){
						$('ul:first', this).css('right',''+menuObj.maxSubMenuWidth+'px');
					}else{
						$('ul:first', this).css('left',''+menuObj.maxSubMenuWidth+'px');
					}	
				});
				
				var leftInd = 1, // left indent for submenu and shadow
					topInd = 2; // top indent for submenu and shadow
				
				//if right-to-left orientation in document
				if(document.dir=='rtl'){
					if($.browser.msie && !menuObj.topItemHovered){
						leftInd = -21;
					}else{
						leftInd = -2;
					}	
				}
							
				if($.browser.msie){
					leftInd += document.body.scrollLeft;
					topInd += document.body.scrollTop;
				}
				//init drop shadow for submenu
				menuObj.subMenu.dropShadow({
					left: leftInd, 
					top: topInd, 
					blur: 2, 
					opacity: 0.4
				});
				
				menuObj.topItemHovered = true;
				menuObj.itemHovered = true;
				//redraw shadow
				setTimeout(function(){
					if(Runner.menu.Horizontal.prototype.itemHovered){
						Runner.menu.Horizontal.prototype.subMenu.redrawShadow();
						Runner.menu.Horizontal.prototype.itemHovered = false;
					}	
				},150);
			}	
		},
		function(){
			menuObj.itemHovered = false;
			menuObj.manageActiveClass(this, false);
			$('ul:first', this).css('display','none'); 
			$('ul:first', this).removeShadow();
		});
		
		$('.runner-hmenu td, .runner-hmenu ul li').click(this.itemClickHandler);
	},	
	
	/**
	 * Set raquo for top items which has submenus
	 */	
	setRaquoToTopItems: function(){	
	
		$('a:first', '.runner-hmenu td:has(ul:has(li)), .runner-hmenu li:has(ul:has(li))').each(function(){
			if(!$('b',this).length){	
				$(this).after('<b class="raquo">&nbsp;&raquo;</b>');
				$(".runner-hmenu .raquo").css("color",$(this).css("color"));
			}
		});
		//if submenus of top item has current item, then add current item title to top item after raquo 
		$('b.raquo:first', '.runner-hmenu td[view=topitem]:has(ul:has(li.current))').append("&nbsp;<b class='subcur'>"+$(".runner-hmenu .curlink").attr('title')+"</b>");
		$('.runner-hmenu .subcur').css("color",$("runner-hmenu .curlink").css("color"));
		$('.runner-hmenu ul li ul li ul').css('top','0px');
	}
});

/**
 * @class Runner.menu.SimpleVmenu
 * Abstract base class that provides vertical simple menu functionality. 
 */
Runner.menu.SimpleVmenu = Runner.extend(Runner.menu.Manager,{
	/**
	 * Maximum item width
	 * @type Integer
	 */	
	maxSubMenuWidth: 0,
	/**
	 * Was item hovered or not
	 * @type Boolean
	 */
	itemHovered: false,	
	/**
	 * Was top item hovered or not
	 * @type Boolean
	 */
	topItemHovered: false,
	/**
	 * Absolute posution for top item
	 * @type object
	 */
	topItemAbsPos: null,
	/**
	 * Sub menu for hovered item
	 * @type object
	 */
	subMenu: null,
		
	init: function(){
		this.bindHoverOnItems();
		this.setRaquoToTopItems();
		$(".runner-vmenu.simple:first").prop("initialized","true");
	},
	
	/**
	 * Find sub menu for item
	 * @param {obj} elem
	 */
	findSubMenu: function(elem){
		this.subMenu = $('ul:first:has(li)', elem);
	},
	
	/**
	 * Manage add/remove class Active
	 * @param {obj} elem
	 * @param {boolean} toggle
	 */
	manageActiveClass: function(elem ,toggle){
		if(!$(elem).hasClass('Separator')){
			$(elem).toggleClass('active', toggle);
		}	
	},
	
	/**
	 * Bind hover event on every menu item
	 * For hovered items add class 'active'
	 * For items losted hover event remove class 'active'
	 */
	bindHoverOnItems: function(){
		var menuObj = this;
		
		// hover handlerIn function 
		$('.runner-vmenu.simple, .runner-vmenu.simple ul li').hover(function(){
			menuObj.manageActiveClass(this, true);
			menuObj.findSubMenu(this); 
			var	vScroll = 0, // vertical scrolling 
				leftInd = 1, // left indent for submenu and shadow
				topInd = 2; // top indent for submenu and shadow
					
			if(menuObj.subMenu.length){
				menuObj.subMenu.css('display','block');
				if(document.dir=='rtl' && $.browser.msie){
					$('.runner-vmenu.simple li.Separator[parent='+menuObj.subMenu.attr('id')+']', menuObj.subMenu).each(function(){
						$(this).addClass('runner-hiddenelem');
					});
				}
				var subMenuOfW = menuObj.subMenu[0].offsetWidth;
				
				// if hovered top item
				if($(this).attr('view')=='topitem'){
					//get absolute position for top item
					menuObj.topItemAbsPos = Runner.getAbsolutePosition(this, 1);
					//set max item width
					if(document.dir == 'rtl'){
						menuObj.maxSubMenuWidth = menuObj.topItemAbsPos.left - subMenuOfW;
					}else{
						menuObj.maxSubMenuWidth = menuObj.topItemAbsPos.left + menuObj.topItemAbsPos.width;
					}
					if(!$.browser.msie){
						if(document.dir=='rtl'){
							menuObj.maxSubMenuWidth += 10;
						}else{
							menuObj.maxSubMenuWidth -= 10;
						}	
					}
					menuObj.topItemHovered = false;
				}
				// add scrolling
				if(!$.browser.msie && (menuObj.subMenu[0].offsetHeight + menuObj.topItemAbsPos.top) > document.body.clientHeight){
					vScroll = 10;
				}
				//find raquo element in current menu item and it's left position
				var raquoElement = $('.raquo', this)[0],
					requoAbsPosLeft = Runner.getAbsolutePosition(raquoElement, 1).left;
					
				// set position for submenu
				if(!menuObj.topItemHovered){
					menuObj.subMenu.css('left',''+(requoAbsPosLeft + raquoElement.offsetWidth - vScroll + 10)+'px');	
					menuObj.subMenu.css('top',''+(menuObj.topItemAbsPos.top)+'px');
				}else{
					menuObj.subMenu.css('top',''+this.offsetTop+'px');
					// Need get more specific about "document.dir"! Now this "if" can be working not correct 
					if(document.dir=='rtl'){
						menuObj.subMenu.css('right',''+(this.offsetWidth - vScroll)+'px');
					}else{
						menuObj.subMenu.css('left',''+(requoAbsPosLeft - Runner.getAbsolutePosition(this, 1).left - vScroll + raquoElement.offsetWidth + 10)+'px');
					}	
				}
				//if right-to-left orientation in document
				if(document.dir=='rtl'){
					leftInd = -2;
					if($.browser.msie){
						$('.runner-vmenu.simple li.Separator[parent='+menuObj.subMenu.attr('id')+']', menuObj.subMenu).each(function(){
							$(this).css('width',''+subMenuOfW+'px');
							$(this).show();
						});
					}
				}
				//init draw shadow for submenu
				menuObj.subMenu.dropShadow({
					left: leftInd, 
					top: topInd, 
					blur: 2, 
					opacity: 0.4
				});

				menuObj.itemHovered = true;
				menuObj.topItemHovered = true;
				
				//redraw shadow for item
				setTimeout(function(){
					if(Runner.menu.SimpleVmenu.prototype.itemHovered){
						Runner.menu.SimpleVmenu.prototype.subMenu.redrawShadow();
						Runner.menu.SimpleVmenu.prototype.itemHovered = false;
					}	
				},150);
			}
		},
		// hover handlerInOut function
		function(){            
			menuObj.findSubMenu(this);
			menuObj.itemHovered = false;
			menuObj.manageActiveClass(this, false);
			  
			if(menuObj.subMenu.length){
				menuObj.subMenu.css('display','none'); 
				menuObj.subMenu.removeShadow();
			}
		});
		
		$('.runner-vmenu.simple, .runner-vmenu.simple ul li').click(this.itemClickHandler);
		
	},
	
	/**
	 * Set raquo for top items which has submenus
	 */
	setRaquoToTopItems: function(){	
		
		$('.runner-vmenu.simple:has(ul:has(li)), .runner-vmenu.simple li:has(ul:has(li))').find('a:first').each(function(){
			if(!$('b',this).length){
				$(this).after('<b class="raquo">&nbsp;&raquo;</b>');
				$("b.raquo").css("color",$(this).css("color"));
			}	
		});
		//if submenus of top item has current item, then add current item title to top item after raquo 
		$('.runner-vmenu.simple[view=topitem]:has(ul:has(li.current))').find("b.raquo:first").append("&nbsp;<b class='subcur'>"+$(".runner-vmenu.simple a.curlink").attr('title')+"</b>");
		$(".runner-vmenu.simple .subcur").css("color",$(".runner-vmenu.simple .current").css("color"));
	}

});	

/**
 * @class Runner.menu.TreeLikeVmenu
 * Abstract base class that provides vertical tree-like menu functionality. 
 */
Runner.menu.TreeLikeVmenu = Runner.extend(Runner.menu.Manager,{
	/**
	 * Cookie root
	 */
	cookieRoot: "",	
	/**
	 * Current item
	 */
	curItem: null,
	/**
	 * Id of current item
	 */
	curItemId: "",
	/**
	 * Level of current item
	 */
	curItemLevel: -1,
	
	init: function(){
		if($('.runner-vmenu.tree .curlink').length){
			this.curItem = $('.runner-vmenu.tree .curlink').closest('tr');
			this.curItemId = $(this.curItem).attr('id');
			this.curItemLevel = this.getItemLevel(this.curItem);
		}
		//hide all submenus
		$('.runner-vmenu.tree[parent]').addClass('runner-hiddenelem');
		
		//init menu
		this.bindHoverOnItems();
		this.setCurrentStyle();
		this.toggleMenuGroup();
		this.manageExpandCollapse();
		
		//open menu group saved in cookie
		this.openMenuOnLoad();
		$(".runner-vmenu.tree:first").prop("initialized","true");
	},
	
	/**
	 * Get menu item level
	 * There are 5 levels in tree like menu
	 * Top items have zero level
	 * Default value -1, level is not define
	 * @param {object} jquery item object
	 * @return {number} number of level
	 */
	getItemLevel: function(item){
		var clsItem = $(item).attr('class'),
			pos = clsItem.indexOf("level");
		//if not found level, return zero - top item	
		if(pos == -1){
			return 0;
		}	
		//cut number of level, parse it to int and return number
		return parseInt(clsItem.substr(pos+5, 1), 10);
	},
	
	/**
	 * Bind hover event on every menu item
	 * For hovered items add class 'active'
	 * For items losted hover event remove class 'active'
	 */
	bindHoverOnItems: function(){
		$('.runner-vmenu.tree[id^=item]').hover(function(){
			if($(this).parent().attr('class')!='Separator'){
				$(this).addClass('active');
			}	
		},
		function(){
			if($(this).parent().attr('class')!='Separator'){
				$(this).removeClass('active'); 
			}	
		});

		$('.runner-vmenu.tree[id^=item]').click(this.itemClickHandler);
	},	
	
	/**
	 * Set current style for top group
	 * which has current item in children
	 */
	setCurrentStyle: function(){	
		$('.runner-vmenu.tree.Group[view=topitem]').each(function(){
			var group = this;
			$('.runner-vmenu.tree[topparent='+this.id+']').each(function(){
				if($(this).hasClass('current')){
					$(group).addClass('current');
				}	
			});		
		});
	},	
	
	/**
	 * Bind click event on span to toggle menu group
	 */
	toggleMenuGroup: function(){	
		var menuObj = this;
		
		$('.runner-vmenu.tree.Group span').click(function(){
			
			var spanItem = $(this).closest("tr"),
				spanItemId = spanItem.attr('id');
						
			if ($('.groupImg',this).attr('src') == 'images/plus.gif'){
				//show all children to the current item
				menuObj.showGroupChildren(spanItem, spanItemId);
			}
			else if($('.groupImg',this).attr('src') == 'images/minus.gif'){
				
				//hide all children of closed group
				menuObj.hideGroupChildren(spanItem, spanItemId);
				
				if(spanItemId!=menuObj.curItemId && menuObj.curItemLevel > menuObj.getItemLevel(spanItem) && menuObj.hasCurrentItem(spanItem, spanItemId)){
					spanItem.addClass('current');
				}
			}
			return false;
		});
	},	
	
	/**
	 * Show all children of closed group
	 * If this group has current item,
	 * then show all groups in this group, which has current item 
	 * @param {object}
	 */
	showGroupChildren: function(item, itemId){
		var menuObj = this;
		if(!itemId){
			itemId = $(item).attr('id');
		}

		//set image minus for closed group
		$('.groupImg',item).attr('src', 'images/minus.gif');
		
		if(itemId!=this.curItemId && $(item).hasClass('current'))
			$(item).removeClass('current');
	
		$('.runner-vmenu.tree[parent='+itemId+']').each(function(){
			$(this).removeClass('runner-hiddenelem');
			if($(this).hasClass('Group') && menuObj.curItemLevel > menuObj.getItemLevel(this) && menuObj.hasCurrentItem(this)){
				menuObj.showGroupChildren(this);
			}	
		});

		// add to cookie opened group
		this.addToCookie(itemId);
	},
	
	/**
	 * Hide all children of group
	 * @param {object}
	 */
	hideGroupChildren: function(item, itemId){
		var menuObj = this;
		if(!itemId){
			itemId = $(item).attr('id');
		}	

		//set image plus for closed group
		$('.groupImg',item).attr('src', 'images/plus.gif');
		
		$('.runner-vmenu.tree[parent='+itemId+']').each(function(){
			$(this).addClass('runner-hiddenelem');
			if($(this).hasClass('Group'))
				menuObj.hideGroupChildren(this);
		});
		
		// remove from cookie closed group
		this.removeFromCookie(itemId);
	},
	
	/**
	 * Check has item in children current item or not
	 * @param {object}
	 * @return {boolean}
	 */	
	hasCurrentItem: function(item, itemId){
		if(!itemId){
			itemId = $(item).attr('id');
		}
		if($('.runner-vmenu.tree.[parent='+itemId+']').hasClass('current'))			
			return true;
			
		var colSubGroups = $('.runner-vmenu.tree.Group.[parent='+itemId+']').length;
		for(var i=0; i<colSubGroups;i++){
			if(this.hasCurrentItem($('.runner-vmenu.tree.Group.[parent='+itemId+']').get(i)))			
				return true;
		}
		return false;	
	},
	
	/**
	 * Menage group menu with expand/collapse button control
	 */
	manageExpandCollapse: function(){
		
		var menuObj = this,
			expand = false;
		
		if($('.runner-vmenu.tree.Group[view=topitem]').length){
			$('.manage').css('display','block');
			$('.manage a').click(function(){
				if(expand){
					expand = false;
					
					$('.runner-vmenu.tree[parent]').addClass('runner-hiddenelem');
					$('.manage a').empty();
					$('img.groupImg').attr('src','images/plus.gif');
					$('.manage a').append('<img src=\"images/plus.gif\" border=0> &nbsp;&nbsp;'+Runner.lang.constants.TEXT_EXPAND_ALL);
					
					// on collapse all, remove all ids from cookie
					delete_cookie('openMenuGroupIds', menuObj.cookieRoot, '');
					
					if(menuObj.curItem){
						$('#'+menuObj.curItem.attr('topparent')).addClass('current');
					}	
				}else{
					expand = true;
					
					$('.runner-vmenu.tree[parent]').removeClass('runner-hiddenelem');
					$('.manage a').empty();
					$('img.groupImg').attr('src','images/minus.gif');
					$('.manage a').append('<img src=\"images/minus.gif\" border=0> &nbsp;&nbsp;'+Runner.lang.constants.TEXT_COLLAPSE_ALL);
					
					// on expand all add all group ids to cookie
					$('.runner-vmenu.tree.Group').each(function(){
						menuObj.addToCookie(this.id);
					});
					
					if(menuObj.curItem){
						$('#'+menuObj.curItem.attr('topparent')).removeClass('current');
					}	
				}
				return false; 
			});
		}
	},	
	
	/**
	 * Find cookie root
	 * Use for open menu groups on page load
	 */
	findCookieRoot: function(){
		var cutFrom = document.location['pathname'].indexOf('/', 1);
		this.cookieRoot = document.location['pathname'].substr(0,(cutFrom+1));
	},

	/**
	 * Add opened menu group id to cookie
	 * @param {string} group id
	 */
	addToCookie: function(menuGroupId){
		var openMenuGroupIds = get_cookie('openMenuGroupIds');
		
		if (openMenuGroupIds){
			if (openMenuGroupIds.indexOf(menuGroupId) == -1)
				openMenuGroupIds += ";"+menuGroupId;	
		}else
			openMenuGroupIds = menuGroupId;
				
		set_cookie('openMenuGroupIds', openMenuGroupIds, '', this.cookieRoot, '', '' );
		this.toggleExpandCollapse();
	},

	/**
	 * Remove opened menu group id from cookie
	 * @param {string} group id
	 */ 
	removeFromCookie: function(menuGroupId){
		var openMenuGroupIds = get_cookie('openMenuGroupIds');
	
		if (openMenuGroupIds){
			openMenuGroupIds = openMenuGroupIds.replace((";"+menuGroupId), "");
			openMenuGroupIds = openMenuGroupIds.replace(menuGroupId, "");
		
			if(openMenuGroupIds.indexOf(';')==0)
				openMenuGroupIds = openMenuGroupIds.substr(1,openMenuGroupIds.length);
			
			set_cookie('openMenuGroupIds', openMenuGroupIds, '', this.cookieRoot, '', '' );
		}
		
		setTimeout(function(){
			Runner.menu.TreeLikeVmenu.prototype.toggleExpandCollapse();
		},500);
	},

	/**
	 * Expand/Collapse button control
	 * If all menu groups was opened then change img for minus
	 * Else if all menu groups was closed then change img for minus
	 */ 
	toggleExpandCollapse: function(){
		var visibleLength = $(".runner-vmenu.tree.subitem:visible").length,
			hiddenLength = $(".runner-vmenu.tree.subitem:hidden").length;
		
		if (visibleLength == 0 && hiddenLength > 0){
			$('.manage a').empty();
			$('.groupImg').attr('src','images/plus.gif');
			$('.manage a').append('<img src=\"images/plus.gif\" border=0> &nbsp;&nbsp;'+Runner.lang.constants.TEXT_EXPAND_ALL);	
		}
		else if (visibleLength != 0 && hiddenLength == 0){
			$('.manage a').empty();
			$('.groupImg').attr('src','images/minus.gif');
			$('.manage a').append('<img src=\"images/minus.gif\" border=0> &nbsp;&nbsp;'+Runner.lang.constants.TEXT_COLLAPSE_ALL);
		}
	},

	/**
	 * Open menu's group on page after load
	 */
	openMenuOnLoad: function(){
		this.findCookieRoot();
		var openMenuGroupIds = get_cookie('openMenuGroupIds');
		if (openMenuGroupIds){
			var groupForOpenArr = openMenuGroupIds.split(";");
			for (var i = 0; i < groupForOpenArr.length; i++){
				if(groupForOpenArr[i].indexOf('item') == -1){
					continue;
				}	
				var group = $('#'+groupForOpenArr[i]);
				group.removeClass('runner-hiddenelem');
				$('.runner-vmenu.tree[parent='+groupForOpenArr[i]+']').removeClass('runner-hiddenelem');
				$(".groupImg", group).attr("src", "images/minus.gif");
			}
			this.afterOpenMenuOnLoad();
		}
		this.toggleExpandCollapse();
	},
	
	/**
	 * Set current style to opened group after open menu on load
	 * @param {object}
	 */
	afterOpenMenuOnLoad: function(item){
		if(typeof item == 'undefined'){
			if(this.curItem)
				item = this.curItem;
			else
				return;
		}
		//if item topItem 
		if(item.attr('view') == 'topitem')
			return;
		//remove current class from top item
		$('#'+item.attr('topparent')).removeClass('current');	
		//get item parent	
		var itemPar = $('#'+item.attr('parent'));
		//if parent open, then check recursively next parent 
		if($(".groupImg", itemPar).attr('src') == 'images/plus.gif'){
			itemPar.addClass('current');
			if(itemPar.attr('view')!='topitem'){
				this.afterOpenMenuOnLoad(itemPar);
			}	
		}	
	}
	
});

// create namespace
Runner.namespace('Runner.s508');

/**
 * @class Runner.s508
 * Abstract base class that provides section508 functionality. 
 */
Runner.s508 = Runner.extend(Runner.emptyFn, {
	/**
	 * Inner flag
	 * @type {bool}
	 */
	calc508column: true,
	/**
	 * TD which contain current control
	 * @type {object}
	 */
	s508td: null,
	/**
	 * Number of grid column which contain current control
	 * @type {integer}
	 */
	s508column:-1,
	/**
	 * Grid object
	 * @type {object}
	 */
	gridObj: null,
	/**
	 * ID of current page
	 * @type {integer}
	 */
	pageId: -1,
	/**
	 * URL of current page
	 * @type {string}
	 */
	pageURL: null,
	/**
	 * Count of pages in pagination
	 */
	maxPages: 1,
	/**
	 * Use InlineEdit or not
	 * @type {boolean}
	 */
	isUseInlineEdit: false,

	constructor: function(cfg){
		Runner.apply(this, cfg);
	},
	
	init: function(){
		this.section508setEvents();
		this.s508pagination();
		this.s508jumpto();
		if(this.isUseInlineEdit){
			this.s508inlineEdit();
		}
	},
	
	/**
	 * section508setEvents
	 * @param {integer} pageid - id of page,
	 * @param {} focusedControl
	 * @param {} setFocusInTD
	 */
	section508setEvents: function (focusedControl, setFocusInTD){
		if(!this.gridObj){
			return false;
		}
		if(this.gridObj.attr("s508table") == undefined){
			this.gridObj.attr("s508table", "true");
			this.setFocusFirstElement();
		}
		var self = this,
			// define handler functions
			s508eventArrow = function(e){
				self.calc508column = true;
				switch (e.which){
					case 40:
						self.calc508column = false;
						self.click_arrow(this, "next", self.s508column);
						break;
					case 38:
						self.calc508column = false;
						self.click_arrow(this, "prev", self.s508column);
						break;
					case 9:
						self.calc508column = true;
						break;
					default:
						self.calc508column  =true;
						break;
				}
			},
			focusHandler = function(){
				if(self.calc508column){
					self.s508column = self.getControlColumn(this);
				}
			};
			
		// assign handler functions
		$("input,a,textarea,select", this.gridObj).each(function(){
			if(!$(this).attr("event508")){
				$(this).attr("event508", "true");
				$(this).bind({keydown: s508eventArrow, focus: focusHandler});
			}
		});

		// calc focused column    
		if(focusedControl){
			this.s508column = this.getControlColumn(focusedControl.spanContElem);
		}

		// set focus to visible element in current td 
		// set event alt+e to Inline Edit
		if(setFocusInTD && this.s508td){
			var func = function(){
				$("input:first,a:first,textarea:first,select:first", self.s508td).focus();
			};
			setTimeout(func, 100);
				
			$("a[id^=iEditLink"+this.pageId+"_]", this.s508td).bind("keydown", function(e){
				if(e.altKey && e.which == 69){
					$(this).click();
				}
			});
		}
	},

	/**
	 * getControlColumn
	 * Find index of column which contain the control
	 * @param {object} control - control inside the grid
	 * @return {integer} index of column
	 */
	getControlColumn: function (control){
		var parentTr = this.find508tr(control),
			parentTrFound = false,
			tdObj = false;

		// find parent TD
		$(control).parents().each(function(k, elem){
			if(parentTrFound){
				return;
			}
			if(elem === parentTr){
				parentTrFound = true;
			}
			if($(elem).tagName() == "td"){
				tdObj = elem;
			}
		});
		
		// find parent TD's index
		var numberTd = 0,
			colspanAttr = "",
			tdFound = false;

		$(parentTr).children().each(function(k,elem){
			if(elem === tdObj){
				tdFound = true;
			}
			if(tdFound){
				return;
			}
			numberTd++;
			colspanAttr = $(elem).attr("colspan");
			if(colspanAttr){
				numberTd += parseInt(colspanAttr) - 1;
			}
		});
		this.s508td = tdObj;
		return numberTd;
	},

	/**
	 * find506tr
	 * Find the main table TR which contain the control
	 * @param {object} control
	 * @return {object} TR which contain the control
	 */
	find508tr: function (control){
		var trObj = false,
			mainTableFound = false;

		$(control).parents().each(function(k, elem){
			if(mainTableFound){
				return;
			}
			if($(elem).tagName() == "tr"){
				trObj = elem;
			}
			if($(elem).attr("s508table") == "true"){
				mainTableFound = true;
			}
		});
		return trObj;
	},

	/**
	 * click_arrow
	 * Move focus to the next/previous row in table when down/up arrow pressed
	 * @param {object} control wich handling event
	 * @param {string} moving direction
	 * @param {integer} number of column wich contain control
	 */
	click_arrow: function (control, direction, colnum){
		// get target TR    
		var tr = $(this.find508tr(control)),
			isNext = direction == "next",
			trobj = isNext ? $(tr).next() : $(tr).prev();

		// skip empty and hidden TRs       
		while($("input,a,textarea,select", trobj).length == 0 && $(trobj).get(0) 
			|| $(trobj).css("display") == "none"){
			trobj = isNext ? $(trobj).next() : $(trobj).prev();
		}

		// skip from THEAD to TBODY and vice versa    
		if(!isNext && !$(trobj).get(0) && $(tr).parents().tagName() == "tbody" 
			&& $("thead tr:visible", this.gridObj).length > 0){
			trobj = $("thead tr:visible", this.gridObj).get(0);
		}
		if(isNext && !$(trobj).get(0) && $(tr).parents().tagName() == "thead" 
			&& $("tbody tr:visible", this.gridObj).length > 0){
			trobj = $("tbody tr:visible", this.gridObj).get(0);
		}
		
		// find element in the target row and set focus to it
		var altElem = focusSet = false;
		if(colnum == -1){
			$("input:first,a:first,textarea:first,select:first", trobj).focus();
		}else{
			$(trobj).children().each(function(k, elem){
				if(focusSet){
					return;
				}
				if($("input:first,a:first,textarea:first,select:first", elem).length > 0){
					//  find left neighbour                
					if(k < colnum && altElem || !altElem){
						altElem = elem;
					}
					// exact fit
					if(k == colnum){
						$("input:first,a:first,textarea:first,select:first", elem).focus();
						focusSet = true;
					}
				}
			});
			if(!focusSet && altElem){
				$("input:first,a:first,textarea:first,select:first", altElem).focus();
			}
		}
	},

	/**
	 * setFocusFirst
	 * Set focus to the first element in the first TD in the grid
	 
	setFocusFirst: function (){
		//sObj.find("td:first").find("input:first,a:first,textarea:first,select:first").focus();
		$("input:first,a:first,textarea:first,select:first", this.gridObj).focus();
	},*/
	
	/**
	 * setFocusFirstElement
	 * Set focus to the first element in the grid body
	 */
	setFocusFirstElement: function (sObj){
		//sObj.find("tbody").find("tr:first").next().find("input:first,a:first,textarea:first,select:first").focus();
		$("input:first,a:first,textarea:first,select:first", $("tbody tr:visible:first", this.gridObj)).focus();
	},

	/**
	 * getURLParam
	 * Return value of given get-parameter from request URL
	 * @param {string} parameter name
	 * @return {string} parameter value
	 */
	getURLParam: function (name){
		name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
		var regexS = "[\\?&]" + name + "=([^&#]*)",
			regex = new RegExp(regexS),
		  	results = regex.exec( window.location.href );
		return results == null ? "" : results[1];
	},

	/**
	 * s508pagination
	 * Bind pagination to keydown event of the page
	 */
	s508pagination: function (){
		var self = this,
			s508eventPagination = function(e){
				NumberPage = self.getURLParam("goto");
				if(NumberPage == ""){
					NumberPage = 1;
				}	
				if(e.ctrlKey && e.which == 37){
					if(NumberPage > 1){
						NumberPage--;
						window.location.href = self.pageURL + "?goto=" + NumberPage;
					}else{
						NumberPage = 1;
					}	
				}
				if(e.ctrlKey && e.which == 39){
					if(NumberPage < self.maxPages){
						NumberPage++;
						window.location.href = self.pageURL + "?goto=" + NumberPage;
					}else{
						NumberPage = self.maxPages;
					}	
				}
			};
		$(document).keydown(s508eventPagination);
	},

	/**
	 * s508jumpto
	 * Bind "jumpto" to keydown event of the page
	 */
	s508jumpto: function (){
		var self = this,
			s508eventJumpTo = function(e){
				if(e.altKey && e.which == 70){ // alt+f jump to search
					$("#ctlSearchFor" + self.pageId).focus();
				}
				if(e.altKey && e.which == 77){ // alt+m jump to menu
					$("input:first,a:first,textarea:first,select:first", $("#toplinks_block" + self.pageId)).focus();
				}
			};
		$(document).keydown(s508eventJumpTo);
	},

	/**
	 * s508inlineEdit
	 */
	s508inlineEdit: function ()	{
		var self = this;
		$("a[id^=iEditLink]").each(function(){
			var parent_tr = $(this).parent("td").parent("tr"), 
				record_id = this.id;
			$("input,a,textarea,select", parent_tr).each(function(){
				$(this).keydown(function(e){
					if(e.altKey && e.which == 69){
						$("#" + record_id).click();
						Runner.Event.prototype.stopEvent(e);
					}
				});
			});
		});
	}
});


