/*
Product Name: dhtmlxSuite 
Version: 4.0.3 
Edition: Professional 
License: content of this file is covered by DHTMLX Commercial or Enterprise license. Usage without proper license is prohibited. To obtain it contact sales@dhtmlx.com
Copyright UAB Dinamenta http://www.dhtmlx.com
*/

/*
This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
to use it in non-GPL project. Please contact sales@dhtmlx.com for details
*/
/*DHX:Depend core/dhx.js*/
/*DHX:Depend core/assert.js*/
if (!window.dhx) 
	dhx={};

//check some rule, show message as error if rule is not correct
dhx.assert = function(test, message){
	if (!test){
		dhx.assert_error(message);
	}
};

dhx.assert_error = function(message){
	dhx.log("error",message);
	if (dhx.message && typeof message == "string")
		dhx.message({ type:"debug", text:message, expire:-1 });
	if (dhx.debug !== false)
		eval("debugger;");
};

//entry point for analitic scripts
dhx.assert_core_ready = function(){
	if (window.dhx_on_core_ready)	
		dhx_on_core_ready();
};

/*
	Common helpers
*/
dhx.version="3.0";
dhx.codebase="./";
dhx.name = "Core";

//coding helpers
dhx.clone = function(source){
	var f = dhx.clone._function;
	f.prototype = source;
	return new f();
};
dhx.clone._function = function(){};

//copies methods and properties from source to the target
dhx.extend = function(base, source, force){
	dhx.assert(base,"Invalid mixing target");
	dhx.assert(source,"Invalid mixing source");

	if (base._dhx_proto_wait){
		dhx.PowerArray.insertAt.call(base._dhx_proto_wait, source,1);
		return base;
	}
	
	//copy methods, overwrite existing ones in case of conflict
	for (var method in source)
		if (!base[method] || force)
			base[method] = source[method];
		
	//in case of defaults - preffer top one
	if (source.defaults)
		dhx.extend(base.defaults, source.defaults);
	
	//if source object has init code - call init against target
	if (source.$init)	
		source.$init.call(base);
				
	return base;	
};

//copies methods and properties from source to the target from all levels
dhx.copy = function(source){
	dhx.assert(source,"Invalid mixing target");
	if(arguments.length>1){
		var target = arguments[0];
		source = arguments[1];
	} else 
		var target =  (dhx.isArray(source)?[]:{});

	for (var method in source){
		if(source[method] && typeof source[method] == "object" && !dhx.isDate(source[method])){
			target[method] = (dhx.isArray(source[method])?[]:{});
			dhx.copy(target[method],source[method]);
		}else{
			target[method] = source[method];
		}
	}

	return target;	
};


dhx.single = function(source){ 
	var instance = null;
	var t = function(config){
		if (!instance)
			instance = new source({});
			
		if (instance._reinit)
			instance._reinit.apply(instance, arguments);
		return instance;
	};
	return t;
};

dhx.protoUI = function(){
	if (dhx.debug_proto)
		dhx.log("UI registered: "+arguments[0].name);
		
	var origins = arguments;
	var selfname = origins[0].name;
	
	var t = function(data){
		if (!t)
			return dhx.ui[selfname].prototype;

		var origins = t._dhx_proto_wait;
		if (origins){
			var params = [origins[0]];
			
			for (var i=1; i < origins.length; i++){
				params[i] = origins[i];
				
				if (params[i]._dhx_proto_wait)
					params[i] = params[i].call(dhx, params[i].name);

				if (params[i].prototype && params[i].prototype.name)
					dhx.ui[params[i].prototype.name] = params[i];
			}
			dhx.ui[selfname] = dhx.proto.apply(dhx, params);
			if (t._dhx_type_wait)	
				for (var i=0; i < t._dhx_type_wait.length; i++)
					dhx.Type(dhx.ui[selfname], t._dhx_type_wait[i]);
				
			t = origins = null;	
		}
			
		if (this != dhx)
			return new dhx.ui[selfname](data);
		else 
			return dhx.ui[selfname];
	};
	t._dhx_proto_wait = Array.prototype.slice.call(arguments, 0);
	return dhx.ui[selfname]=t;
};

dhx.proto = function(){
	 
	if (dhx.debug_proto)
		dhx.log("Proto chain:"+arguments[0].name+"["+arguments.length+"]");

	var origins = arguments;
	var compilation = origins[0];
	var has_constructor = !!compilation.$init;
	var construct = [];
	
	dhx.assert(compilation,"Invalid mixing target");
		
	for (var i=origins.length-1; i>0; i--) {
		dhx.assert(origins[i],"Invalid mixing source");
		if (typeof origins[i]== "function")
			origins[i]=origins[i].prototype;
		if (origins[i].$init) 
			construct.push(origins[i].$init);
		if (origins[i].defaults){ 
			var defaults = origins[i].defaults;
			if (!compilation.defaults)
				compilation.defaults = {};
			for (var def in defaults)
				if (dhx.isUndefined(compilation.defaults[def]))
					compilation.defaults[def] = defaults[def];
		}
		if (origins[i].type && compilation.type){
			for (var def in origins[i].type)
				if (!compilation.type[def])
					compilation.type[def] = origins[i].type[def];
		}
			
		for (var key in origins[i]){
			if (!compilation[key])
				compilation[key] = origins[i][key];
		}
	}
	
	if (has_constructor)
		construct.push(compilation.$init);
	
	
	compilation.$init = function(){
		for (var i=0; i<construct.length; i++)
			construct[i].apply(this, arguments);
	};
	var result = function(config){
		this.$ready=[];
		dhx.assert(this.$init,"object without init method");
		this.$init(config);
		if (this._parseSettings)
			this._parseSettings(config, this.defaults);
		for (var i=0; i < this.$ready.length; i++)
			this.$ready[i].call(this);
	};
	result.prototype = compilation;
	
	compilation = origins = null;
	return result;
};
//creates function with specified "this" pointer
dhx.bind=function(functor, object){ 
	return function(){ return functor.apply(object,arguments); };  
};

//loads module from external js file
dhx.require=function(module, callback, master){
	if (typeof module != "string"){
		var count = module.length||0;
		var callback_origin = callback;

		if (!count){
			for (var file in module) count++;
			callback = function(){ count--; if (count === 0) callback_origin.apply(this, arguments); };
			for (var file in module)
				dhx.require(file, callback, master);
		} else {
			callback = function(){
				if (count){
					count--;
					dhx.require(module[module.length - count - 1], callback, master);
				} else 
					return callback_origin.apply(this, arguments);
				
			};
			callback();
		}
		return;
	}

	if (dhx._modules[module] !== true){
		if (module.substr(-4) == ".css") {
			var link = dhx.html.create("LINK",{  type:"text/css", rel:"stylesheet", href:dhx.codebase+module});
			document.head.appendChild(link);
			if (callback)
				callback.call(master||window);
			return;
		}

		var step = arguments[4];

		//load and exec the required module
		if (!callback){
			//sync mode
			dhx.exec( dhx.ajax().sync().get(dhx.codebase+module).responseText );
			dhx._modules[module]=true;
		} else {

			if (!dhx._modules[module]){	//first call
				dhx._modules[module] = [[callback, master]];

				dhx.ajax(dhx.codebase+module, function(text){
					dhx.exec(text);	//evaluate code
					var calls = dhx._modules[module];	//callbacks
					dhx._modules[module] = true;
					for (var i=0; i<calls.length; i++)
						calls[i][0].call(calls[i][1]||window, !i);	//first callback get true as parameter
				});
			} else	//module already loading
				dhx._modules[module].push([callback, master]);
		}
		
	}
};
dhx._modules = {};	//hash of already loaded modules

//evaluate javascript code in the global scoope
dhx.exec=function(code){
	if (window.execScript)	//special handling for IE
		window.execScript(code);
	else window.eval(code);
};

dhx.wrap = function(code, wrap){
	if (!code) return wrap;
	return function(){
		var result = code.apply(this, arguments);
		wrap.apply(this,arguments);
		return result;
	};
};

//check === undefined
dhx.isUndefined=function(a){
	return typeof a == "undefined";
};
//delay call to after-render time
dhx.delay=function(method, obj, params, delay){
	return window.setTimeout(function(){
		var ret = method.apply(obj,(params||[]));
		method = obj = params = null;
		return ret;
	},delay||1);
};

//common helpers

//generates unique ID (unique per window, nog GUID)
dhx.uid = function(){
	if (!this._seed) this._seed=(new Date).valueOf();	//init seed with timestemp
	this._seed++;
	return this._seed;
};
//resolve ID as html object
dhx.toNode = function(node){
	if (typeof node == "string") return document.getElementById(node);
	return node;
};
//adds extra methods for the array
dhx.toArray = function(array){ 
	return dhx.extend((array||[]),dhx.PowerArray, true);
};
//resolve function name
dhx.toFunctor=function(str){ 
	return (typeof(str)=="string") ? eval(str) : str; 
};
/*checks where an object is instance of Array*/
dhx.isArray = function(obj) {
	return Array.isArray?Array.isArray(obj):(Object.prototype.toString.call(obj) === '[object Array]');
};
dhx.isDate = function(obj){
	return obj instanceof Date;
};

//dom helpers

//hash of attached events
dhx._events = {};
//attach event to the DOM element
dhx.event=function(node,event,handler,master){
	node = dhx.toNode(node);
	
	var id = dhx.uid();
	if (master) 
		handler=dhx.bind(handler,master);	
		
	dhx._events[id]=[node,event,handler];	//store event info, for detaching
		
	//use IE's of FF's way of event's attaching
	if (node.addEventListener)
		node.addEventListener(event, handler, false);
	else if (node.attachEvent)
		node.attachEvent("on"+event, handler);

	return id;	//return id of newly created event, can be used in eventRemove
};

//remove previously attached event
dhx.eventRemove=function(id){
	
	if (!id) return;
	dhx.assert(this._events[id],"Removing non-existing event");
		
	var ev = dhx._events[id];
	//browser specific event removing
	if (ev[0].removeEventListener)
		ev[0].removeEventListener(ev[1],ev[2],false);
	else if (ev[0].detachEvent)
		ev[0].detachEvent("on"+ev[1],ev[2]);
		
	delete this._events[id];	//delete all traces
};


//debugger helpers
//anything starting from error or log will be removed during code compression

//add message in the log
dhx.log = function(type,message,details){
	if (arguments.length == 1){
		message = type;
		type = "log";
	}
	/*jsl:ignore*/
	if (window.console && console.log){
		type=type.toLowerCase();
		if (window.console[type])
			window.console[type](message||"unknown error");
		else
			window.console.log(type +": "+message);

		if (details) 
			window.console.log(details);
	}	
	/*jsl:end*/
};
//register rendering time from call point 
dhx.log_full_time = function(name){
	dhx._start_time_log = new Date();
	dhx.log("Timing start ["+name+"]");
	window.setTimeout(function(){
		var time = new Date();
		dhx.log("Timing end ["+name+"]:"+(time.valueOf()-dhx._start_time_log.valueOf())/1000+"s");
	},1);
};
//register execution time from call point
dhx.log_time = function(name){
	var fname = "_start_time_log"+name;
	if (!dhx[fname]){
		dhx[fname] = new Date();
		dhx.log("Info","Timing start ["+name+"]");
	} else {
		var time = new Date();
		dhx.log("Info","Timing end ["+name+"]:"+(time.valueOf()-dhx[fname].valueOf())/1000+"s");
		dhx[fname] = null;
	}
};
dhx.debug_code = function(code){
	code.call(dhx);
};
//event system
dhx.EventSystem={
	$init:function(){
		if (!this._evs_events){
			this._evs_events = {};		//hash of event handlers, name => handler
			this._evs_handlers = {};	//hash of event handlers, ID => handler
			this._evs_map = {};
		}
	},
	//temporary block event triggering
	blockEvent : function(){
		this._evs_events._block = true;
	},
	//re-enable event triggering
	unblockEvent : function(){
		this._evs_events._block = false;
	},
	mapEvent:function(map){
		dhx.extend(this._evs_map, map, true);
	},
	on_setter:function(config){
		if(config){
			for(var i in config){
				if(typeof config[i] == 'function')
					this.attachEvent(i, config[i]);
			}
		}
	},
	//trigger event
	callEvent:function(type,params){
		if (this._evs_events._block) return true;
		
		type = type.toLowerCase();
		var event_stack =this._evs_events[type.toLowerCase()];	//all events for provided name
		var return_value = true;

		if (dhx.debug)	//can slowdown a lot
			dhx.log("info","["+this.name+"] event:"+type,params);
		
		if (event_stack)
			for(var i=0; i<event_stack.length; i++){
				/*
					Call events one by one
					If any event return false - result of whole event will be false
					Handlers which are not returning anything - counted as positive
				*/
				if (event_stack[i].apply(this,(params||[]))===false) return_value=false;
			}
		if (this._evs_map[type] && !this._evs_map[type].callEvent(type,params))
			return_value =	false;
			
		return return_value;
	},
	//assign handler for some named event
	attachEvent:function(type,functor,id){
		dhx.assert(functor, "Invalid event handler for "+type);

		type=type.toLowerCase();
		
		id=id||dhx.uid(); //ID can be used for detachEvent
		functor = dhx.toFunctor(functor);	//functor can be a name of method

		var event_stack=this._evs_events[type]||dhx.toArray();
		//save new event handler
		event_stack.push(functor);
		this._evs_events[type]=event_stack;
		this._evs_handlers[id]={ f:functor,t:type };
		
		return id;
	},
	//remove event handler
	detachEvent:function(id){
		if(!this._evs_handlers[id]){
			return;
		}
		var type=this._evs_handlers[id].t;
		var functor=this._evs_handlers[id].f;
		
		//remove from all collections
		var event_stack=this._evs_events[type];
		event_stack.remove(functor);
		delete this._evs_handlers[id];
	},
	hasEvent:function(type){
		type=type.toLowerCase();
		return this._evs_events[type]?true:false;
	}
};

dhx.extend(dhx, dhx.EventSystem);

//array helper
//can be used by dhx.toArray()
dhx.PowerArray={
	//remove element at specified position
	removeAt:function(pos,len){
		if (pos>=0) this.splice(pos,(len||1));
	},
	//find element in collection and remove it 
	remove:function(value){
		this.removeAt(this.find(value));
	},	
	//add element to collection at specific position
	insertAt:function(data,pos){
		if (!pos && pos!==0) 	//add to the end by default
			this.push(data);
		else {	
			var b = this.splice(pos,(this.length-pos));
  			this[pos] = data;
  			this.push.apply(this,b); //reconstruct array without loosing this pointer
  		}
  	},  	
  	//return index of element, -1 if it doesn't exists
  	find:function(data){ 
  		for (var i=0; i<this.length; i++) 
  			if (data==this[i]) return i; 	
  		return -1; 
  	},
  	//execute some method for each element of array
  	each:function(functor,master){
		for (var i=0; i < this.length; i++)
			functor.call((master||this),this[i]);
	},
	//create new array from source, by using results of functor 
	map:function(functor,master){
		for (var i=0; i < this.length; i++)
			this[i]=functor.call((master||this),this[i]);
		return this;
	}, 
	filter:function(functor, master){
		for (var i=0; i < this.length; i++)
			if (!functor.call((master||this),this[i])){
				this.splice(i,1);
				i--;
			}
		return this;
	}
};

dhx.env = {};

// dhx.env.transform 
// dhx.env.transition
(function(){
	if (navigator.userAgent.indexOf("Mobile")!=-1) 
		dhx.env.mobile = true;
	if (dhx.env.mobile || navigator.userAgent.indexOf("iPad")!=-1 || navigator.userAgent.indexOf("Android")!=-1)
		dhx.env.touch = true;
	if (navigator.userAgent.indexOf('Opera')!=-1)
		dhx.env.isOpera=true;
	else{
		//very rough detection, but it is enough for current goals
		dhx.env.isIE=!!document.all;
		dhx.env.isFF=!document.all;
		dhx.env.isWebKit=(navigator.userAgent.indexOf("KHTML")!=-1);
		dhx.env.isSafari=dhx.env.isWebKit && (navigator.userAgent.indexOf('Mac')!=-1);
	}
	if(navigator.userAgent.toLowerCase().indexOf("android")!=-1)
		dhx.env.isAndroid = true;
	dhx.env.transform = false;
	dhx.env.transition = false;
	var options = {};
	options.names = ['transform', 'transition'];
	options.transform = ['transform', 'WebkitTransform', 'MozTransform', 'OTransform', 'msTransform'];
	options.transition = ['transition', 'WebkitTransition', 'MozTransition', 'OTransition', 'msTransition'];
	
	var d = document.createElement("DIV");
	for(var i=0; i<options.names.length; i++) {
		var coll = options[options.names[i]];
		
		for (var j=0; j < coll.length; j++) {
			if(typeof d.style[coll[j]] != 'undefined'){
				dhx.env[options.names[i]] = coll[j];
				break;
			}
		}
	}
    d.style[dhx.env.transform] = "translate3d(0,0,0)";
    dhx.env.translate = (d.style[dhx.env.transform])?"translate3d":"translate";

	var prefix = ''; // default option
	var cssprefix = false;
	if(dhx.env.isOpera){
		prefix = '-o-';
		cssprefix = "O";
	}
	if(dhx.env.isFF)
		prefix = '-Moz-';
	if(dhx.env.isWebKit)
		prefix = '-webkit-';
	if(dhx.env.isIE)
		prefix = '-ms-';

    dhx.env.transformCSSPrefix = prefix;

	dhx.env.transformPrefix = cssprefix||(dhx.env.transformCSSPrefix.replace(/-/gi, ""));
	dhx.env.transitionEnd = ((dhx.env.transformCSSPrefix == '-Moz-')?"transitionend":(dhx.env.transformPrefix+"TransitionEnd"));
})();


dhx.env.svg = (function(){
		return document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1");
})();


//html helpers
dhx.html={
	_native_on_selectstart:0,
	denySelect:function(){
		if (!dhx._native_on_selectstart)
			dhx._native_on_selectstart = document.onselectstart;
		document.onselectstart = dhx.html.stopEvent;
	},
	allowSelect:function(){
		if (dhx._native_on_selectstart !== 0){
			document.onselectstart = dhx._native_on_selectstart||null;
		}
		dhx._native_on_selectstart = 0;

	},
	index:function(node){
		var k=0;
		//must be =, it is not a comparation!
		/*jsl:ignore*/
		while (node = node.previousSibling) k++;
		/*jsl:end*/
		return k;
	},
	_style_cache:{},
	createCss:function(rule){
		var text = "";
		for (var key in rule)
			text+= key+":"+rule[key]+";";
		
		var name = this._style_cache[text];
		if (!name){
			name = "s"+dhx.uid();
			this.addStyle("."+name+"{"+text+"}");
			this._style_cache[text] = name;
		}
		return name;
	},
	addStyle:function(rule){
		var style = document.createElement("style");
		style.setAttribute("type", "text/css");
		style.setAttribute("media", "screen"); 
		/*IE8*/
		if (style.styleSheet)
			style.styleSheet.cssText = rule;
		else
			style.appendChild(document.createTextNode(rule));
		document.getElementsByTagName("head")[0].appendChild(style);
	},
	create:function(name,attrs,html){
		attrs = attrs || {};
		var node = document.createElement(name);
		for (var attr_name in attrs)
			node.setAttribute(attr_name, attrs[attr_name]);
		if (attrs.style)
			node.style.cssText = attrs.style;
		if (attrs["class"])
			node.className = attrs["class"];
		if (html)
			node.innerHTML=html;
		return node;
	},
	//return node value, different logic for different html elements
	getValue:function(node){
		node = dhx.toNode(node);
		if (!node) return "";
		return dhx.isUndefined(node.value)?node.innerHTML:node.value;
	},
	//remove html node, can process an array of nodes at once
	remove:function(node){
		if (node instanceof Array)
			for (var i=0; i < node.length; i++)
				this.remove(node[i]);
		else
			if (node && node.parentNode)
				node.parentNode.removeChild(node);
	},
	//insert new node before sibling, or at the end if sibling doesn't exist
	insertBefore: function(node,before,rescue){
		if (!node) return;
		if (before && before.parentNode)
			before.parentNode.insertBefore(node, before);
		else
			rescue.appendChild(node);
	},
	//return custom ID from html element 
	//will check all parents starting from event's target
	locate:function(e,id){
		if (e.tagName)
			var trg = e;
		else {
			e=e||event;
			var trg=e.target||e.srcElement;
		}
		
		while (trg){
			if (trg.getAttribute){	//text nodes has not getAttribute
				var test = trg.getAttribute(id);
				if (test) return test;
			}
			trg=trg.parentNode;
		}	
		return null;
	},
	//returns position of html element on the page
	offset:function(elem) {
		if (elem.getBoundingClientRect) { //HTML5 method
			var box = elem.getBoundingClientRect();
			var body = document.body;
			var docElem = document.documentElement;
			var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
			var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;
			var clientTop = docElem.clientTop || body.clientTop || 0;
			var clientLeft = docElem.clientLeft || body.clientLeft || 0;
			var top  = box.top +  scrollTop - clientTop;
			var left = box.left + scrollLeft - clientLeft;
			return { y: Math.round(top), x: Math.round(left) };
		} else { //fallback to naive approach
			var top=0, left=0;
			while(elem) {
				top = top + parseInt(elem.offsetTop,10);
				left = left + parseInt(elem.offsetLeft,10);
				elem = elem.offsetParent;
			}
			return {y: top, x: left};
		}
	},
	//returns relative position of event
	posRelative:function(ev){
		ev = ev || event;
		if (!dhx.isUndefined(ev.offsetX))
			return { x:ev.offsetX, y:ev.offsetY };	//ie, webkit
		else
			return { x:ev.layerX, y:ev.layerY };	//firefox
	},
	//returns position of event
	pos:function(ev){
		ev = ev || event;
        if(ev.pageX || ev.pageY)	//FF, KHTML
            return {x:ev.pageX, y:ev.pageY};
        //IE
        var d  =  ((dhx.env.isIE)&&(document.compatMode != "BackCompat"))?document.documentElement:document.body;
        return {
                x:ev.clientX + d.scrollLeft - d.clientLeft,
                y:ev.clientY + d.scrollTop  - d.clientTop
        };
	},
	//prevent event action
	preventEvent:function(e){
		if (e && e.preventDefault) e.preventDefault();
		return dhx.html.stopEvent(e);
	},
	//stop event bubbling
	stopEvent:function(e){
		(e||event).cancelBubble=true;
		return false;
	},
	//add css class to the node
	addCss:function(node,name){
        node.className+=" "+name;
    },
    //remove css class from the node
    removeCss:function(node,name){
        node.className=node.className.replace(RegExp(" "+name,"g"),"");
    }
};

dhx.ready = function(code){
	if (this._ready) code.call();
	else this._ready_code.push(code);
};
dhx._ready_code = [];

//autodetect codebase folder
(function(){
	var temp = document.getElementsByTagName("SCRIPT");	//current script, most probably
	dhx.assert(temp.length,"Can't locate codebase");
	if (temp.length){
		//full path to script
		temp = (temp[temp.length-1].getAttribute("src")||"").split("/");
		//get folder name
		temp.splice(temp.length-1, 1);
		dhx.codebase = temp.slice(0, temp.length).join("/")+"/";
	}
	dhx.event(window, "load", function(){
		dhx.callEvent("onReady",[]);
		dhx.delay(function(){
			dhx._ready = true;
			for (var i=0; i < dhx._ready_code.length; i++)
				dhx._ready_code[i].call();
			dhx._ready_code=[];
		});
	});
	
})();

dhx.locale=dhx.locale||{};


dhx.assert_core_ready();


dhx.ready(function(){
	dhx.event(document.body,"click", function(e){
		dhx.callEvent("onClick",[e||event]);
	});
});


/*DHX:Depend core/bind.js*/
/*DHX:Depend core/dhx.js*/
/*DHX:Depend core/config.js*/
/*
	Behavior:Settings
	
	@export
		customize
		config
*/

/*DHX:Depend core/template.js*/
/*
	Template - handles html templates
*/

/*DHX:Depend core/dhx.js*/

(function(){

var _cache = {};
var newlines = new RegExp("(\\r\\n|\\n)","g");
var quotes = new RegExp("(\\\")","g");

dhx.Template = function(str){
	if (typeof str == "function") return str;
	if (_cache[str])
		return _cache[str];
		
	str=(str||"").toString();			
	if (str.indexOf("->")!=-1){
		str = str.split("->");
		switch(str[0]){
			case "html": 	//load from some container on the page
				str = dhx.html.getValue(str[1]);
				break;
			case "http": 	//load from external file
				str = new dhx.ajax().sync().get(str[1],{uid:dhx.uid()}).responseText;
				break;
			default:
				//do nothing, will use template as is
				break;
		}
	}
		
	//supported idioms
	// {obj.attr} => named attribute or value of sub-tag in case of xml
	str=(str||"").toString();		
	str=str.replace(newlines,"\\n");
	str=str.replace(quotes,"\\\"");

	str=str.replace(/\{obj\.([^}?]+)\?([^:]*):([^}]*)\}/g,"\"+(obj.$1?\"$2\":\"$3\")+\"");
	str=str.replace(/\{common\.([^}\(]*)\}/g,"\"+(common.$1||'')+\"");
	str=str.replace(/\{common\.([^\}\(]*)\(\)\}/g,"\"+(common.$1?common.$1.apply(this, arguments):\"\")+\"");
	str=str.replace(/\{obj\.([^}]*)\}/g,"\"+(obj.$1)+\"");
	str=str.replace("{obj}","\"+obj+\"");
	str=str.replace(/#([^#'";, ]+)#/gi,"\"+(obj.$1)+\"");

	try {
		_cache[str] = Function("obj","common","return \""+str+"\";");
	} catch(e){
		dhx.assert_error("Invalid template:"+str);
	}

	return _cache[str];
};


dhx.Template.empty=function(){	return "";	};
dhx.Template.bind =function(value){	return dhx.bind(dhx.Template(value),this); };


	/*
		adds new template-type
		obj - object to which template will be added
		data - properties of template
	*/
dhx.Type=function(obj, data){ 
	if (obj._dhx_proto_wait){
		if (!obj._dhx_type_wait)
			obj._dhx_type_wait = [];
				obj._dhx_type_wait.push(data);
		return;
	}
		
	//auto switch to prototype, if name of class was provided
	if (typeof obj == "function")
		obj = obj.prototype;
	if (!obj.types){
		obj.types = { "default" : obj.type };
		obj.type.name = "default";
	}
	
	var name = data.name;
	var type = obj.type;
	if (name)
		type = obj.types[name] = dhx.clone(data.baseType?obj.types[data.baseType]:obj.type);
	
	for(var key in data){
		if (key.indexOf("template")===0)
			type[key] = dhx.Template(data[key]);
		else
			type[key]=data[key];
	}

	return name;
};

})();
/*DHX:Depend core/dhx.js*/

dhx.Settings={
	$init:function(){
		/* 
			property can be accessed as this.config.some
			in same time for inner call it have sense to use _settings
			because it will be minified in final version
		*/
		this._settings = this.config= {}; 
	},
	define:function(property, value){
		if (typeof property == "object")
			return this._parseSeetingColl(property);
		return this._define(property, value);
	},
	_define:function(property,value){
		//method with name {prop}_setter will be used as property setter
		//setter is optional
		var setter = this[property+"_setter"];
		return this._settings[property]=setter?setter.call(this,value,property):value;
	},
	//process configuration object
	_parseSeetingColl:function(coll){
		if (coll){
			for (var a in coll)				//for each setting
				this._define(a,coll[a]);		//set value through config
		}
	},
	//helper for object initialization
	_parseSettings:function(obj,initial){
		//initial - set of default values
		var settings = {}; 
		if (initial)
			settings = dhx.extend(settings,initial);
					
		//code below will copy all properties over default one
		if (typeof obj == "object" && !obj.tagName)
			dhx.extend(settings,obj, true);	
		//call config for each setting
		this._parseSeetingColl(settings);
	},
	_mergeSettings:function(config, defaults){
		for (var key in defaults)
			switch(typeof config[key]){
				case "object": 
					config[key] = this._mergeSettings((config[key]||{}), defaults[key]);
					break;
				case "undefined":
					config[key] = defaults[key];
					break;
				default:	//do nothing
					break;
			}
		return config;
	},

	debug_freid_c_id:true,
	debug_freid_a_name:true
};
/*DHX:Depend core/datastore.js*/
/*DHX:Depend core/load.js*/
/* 
	ajax operations 
	
	can be used for direct loading as
		dhx.ajax(ulr, callback)
	or
		dhx.ajax().item(url)
		dhx.ajax().post(url)

*/

/*DHX:Depend core/dhx.js*/

dhx.ajax = function(url,call,master){
	//if parameters was provided - made fast call
	if (arguments.length!==0){
		var http_request = new dhx.ajax();
		if (master) http_request.master=master;
		return http_request.get(url,null,call);
	}
	if (!this.getXHR) return new dhx.ajax(); //allow to create new instance without direct new declaration
	
	return this;
};
dhx.ajax.count = 0;
dhx.ajax.prototype={
	master:null,
	//creates xmlHTTP object
	getXHR:function(){
		if (dhx.env.isIE)
		 return new ActiveXObject("Microsoft.xmlHTTP");
		else 
		 return new XMLHttpRequest();
	},
	/*
		send data to the server
		params - hash of properties which will be added to the url
		call - callback, can be an array of functions
	*/
	send:function(url,params,call){
		var x=this.getXHR();
		if (!dhx.isArray(call))
			call = [call];
		//add extra params to the url
		if (typeof params == "object"){
			var t=[];
			for (var a in params){
				var value = params[a];
				if (value === null || value === dhx.undefined)
					value = "";
				t.push(a+"="+encodeURIComponent(value));// utf-8 escaping
		 	}
			params=t.join("&");
		}
		if (params && this.request==='GET'){
			url=url+(url.indexOf("?")!=-1 ? "&" : "?")+params;
			params=null;
		}
		
		x.open(this.request,url,!this._sync);
		if (this.request === 'POST')
			x.setRequestHeader('Content-type','application/x-www-form-urlencoded');
		 
		//async mode, define loading callback
		 var self=this;
		 x.onreadystatechange= function(){
			if (!x.readyState || x.readyState == 4){
				if (dhx.debug_time) dhx.log_full_time("data_loading");	//log rendering time
				dhx.ajax.count++;
				if (call && self){
					for (var i=0; i < call.length; i++)	//there can be multiple callbacks
						if (call[i]){
							var method = (call[i].success||call[i]);
							if (x.status >= 400 || (!x.status && !x.responseText))
								method = call[i].error;
							if (method)
								method.call((self.master||self),x.responseText,x.responseXML,x);
						}
				}
				if (self) self.master=null;
				call=self=null;	//anti-leak
			}
		 };
		
		x.send(params||null);
		return x; //return XHR, which can be used in case of sync. mode
	},
	//GET request
	get:function(url,params,call){
		if (arguments.length == 2){
			call = params;
			params = null;
		}
		this.request='GET';
		return this.send(url,params,call);
	},
	//POST request
	post:function(url,params,call){
		this.request='POST';
		return this.send(url,params,call);
	},
	//PUT request
	put:function(url,params,call){
		this.request='PUT';
		return this.send(url,params,call);
	},
	//POST request
	del:function(url,params,call){
		this.request='DELETE';
		return this.send(url,params,call);
	}, 
	sync:function(){
		this._sync = true;
		return this;
	},
	bind:function(master){
		this.master = master;
		return this;
	}
};
/*submits values*/
dhx.send = function(url, values, method, target){
	var form = dhx.html.create("FORM",{
		"target":(target||"_self"),
		"action":url,
		"method":(method||"POST")
	},"");
	for (var k in values) {
		var field = dhx.html.create("INPUT",{"type":"hidden","name": k,"value": values[k]},"");
		form.appendChild(field);
	}
	form.style.display = "none";
	document.body.appendChild(form);
	form.submit();
	document.body.removeChild(form);
};


dhx.AtomDataLoader={
	$init:function(config){
		//prepare data store
		this.data = {}; 
		if (config){
			this._settings.datatype = config.datatype||"json";
			this.$ready.push(this._load_when_ready);
		}
	},
	_load_when_ready:function(){
		this._ready_for_data = true;
		
		if (this._settings.url)
			this.url_setter(this._settings.url);
		if (this._settings.data)
			this.data_setter(this._settings.data);
	},
	url_setter:function(value){
		if (!this._ready_for_data) return value;
		this.load(value, this._settings.datatype);	
		return value;
	},
	data_setter:function(value){
		if (!this._ready_for_data) return value;
		this.parse(value, this._settings.datatype);
		return true;
	},
	debug_freid_c_datatype:true,
	debug_freid_c_dataFeed:true,

	//loads data from external URL
	load:function(url,call){
		if (url.$proxy) {
			url.load(this, typeof call == "string" ? call : "json");
			return;
		}

		this.callEvent("onXLS",[]);
		if (typeof call == "string"){	//second parameter can be a loading type or callback
			//we are not using setDriver as data may be a non-datastore here
			this.data.driver = dhx.DataDriver[call];
			call = arguments[2];
		} else if (!this.data.driver)
			this.data.driver = dhx.DataDriver.json;

		//load data by async ajax call
		//loading_key - can be set by component, to ignore data from old async requests
		var callback = [{
			success: this._onLoad,
			error: this._onLoadError
		}];
		
		if (call){
			if (dhx.isArray(call))
				callback.push.apply(callback,call);
			else
				callback.push(call);
		}
			

		return dhx.ajax(url,callback,this);
	},
	//loads data from object
	parse:function(data,type){
		this.callEvent("onXLS",[]);
		this.data.driver = dhx.DataDriver[type||"json"];
		this._onLoad(data,null);
	},
	//default after loading callback
	_onLoad:function(text,xml,loader,key){
		var driver = this.data.driver;
		var data = driver.toObject(text,xml);
		if (data){
			var top = driver.getRecords(data)[0];
			this.data=(driver?driver.getDetails(top):text);
		} else 
			this._onLoadError(text,xml,loader);

		this.callEvent("onXLE",[]);
	},
	_onLoadError:function(text, xml, xhttp){
		this.callEvent("onXLE",[]);
		this.callEvent("onLoadError",arguments);
		dhx.callEvent("onLoadError", [text, xml, xhttp, this]);
	},
	_check_data_feed:function(data){
		if (!this._settings.dataFeed || this._ignore_feed || !data) return true;
		var url = this._settings.dataFeed;
		if (typeof url == "function")
			return url.call(this, (data.id||data), data);
		url = url+(url.indexOf("?")==-1?"?":"&")+"action=get&id="+encodeURIComponent(data.id||data);
		this.callEvent("onXLS",[]);
		dhx.ajax(url, function(text,xml,loader){
			this._ignore_feed=true;
			var data = dhx.DataDriver.toObject(text, xml);
			if (data)
				this.setValues(data.getDetails(data.getRecords()[0]));
			else
				this._onLoadError(text,xml,loader);
			this._ignore_feed=false;
			this.callEvent("onXLE",[]);
		}, this);
		return false;
	}
};

/*
	Abstraction layer for different data types
*/

dhx.DataDriver={};
dhx.DataDriver.json={
	//convert json string to json object if necessary
	toObject:function(data){
		if (!data) data="[]";
		if (typeof data == "string"){
			try{
				eval ("dhx.temp="+data);
			} catch(e){
				dhx.assert_error(e);
				return null;
			}
			data = dhx.temp;
		}

		if (data.data){ 
			var t = data.data.config = {};
			for (var key in data)
				if (key!="data")
					t[key] = data[key];
			data = data.data;
		}
			
		return data;
	},
	//get array of records
	getRecords:function(data){
		if (data && !dhx.isArray(data))
		 return [data];
		return data;
	},
	//get hash of properties for single record
	getDetails:function(data){
		if (typeof data == "string")
			return { id:dhx.uid(), value:data };
		return data;
	},
	//get count of data and position at which new data need to be inserted
	getInfo:function(data){
		var cfg = data.config;
		if (!cfg) return {};

		return { 
		 _size:(cfg.total_count||0),
		 _from:(cfg.pos||0),
		 _parent:(cfg.parent||0),
		 _config:(cfg.config),
		 _key:(cfg.dhx_security)
		};
	},
	child:"data"
};

dhx.DataDriver.html={
	/*
		incoming data can be
		 - collection of nodes
		 - ID of parent container
		 - HTML text
	*/
	toObject:function(data){
		if (typeof data == "string"){
		 var t=null;
		 if (data.indexOf("<")==-1)	//if no tags inside - probably its an ID
			t = dhx.toNode(data);
		 if (!t){
			t=document.createElement("DIV");
			t.innerHTML = data;
		 }
		 
		 return t.getElementsByTagName(this.tag);
		}
		return data;
	},
	//get array of records
	getRecords:function(node){
		var data = [];
		for (var i=0; i<node.childNodes.length; i++){
			var child = node.childNodes[i];
			if (child.nodeType == 1)
				data.push(child);
		}
		return data;
	},
	//get hash of properties for single record
	getDetails:function(data){
		return dhx.DataDriver.xml.tagToObject(data);
	},
	//dyn loading is not supported by HTML data source
	getInfo:function(data){
		return { 
		 _size:0,
		 _from:0
		};
	},
	tag: "LI"
};

dhx.DataDriver.jsarray={
	//eval jsarray string to jsarray object if necessary
	toObject:function(data){
		if (typeof data == "string"){
		 eval ("dhx.temp="+data);
		 return dhx.temp;
		}
		return data;
	},
	//get array of records
	getRecords:function(data){
		return data;
	},
	//get hash of properties for single record, in case of array they will have names as "data{index}"
	getDetails:function(data){
		var result = {};
		for (var i=0; i < data.length; i++) 
		 result["data"+i]=data[i];
		 
		return result;
	},
	//dyn loading is not supported by js-array data source
	getInfo:function(data){
		return { 
		 _size:0,
		 _from:0
		};
	}
};

dhx.DataDriver.csv={
	//incoming data always a string
	toObject:function(data){
		return data;
	},
	//get array of records
	getRecords:function(data){
		return data.split(this.row);
	},
	//get hash of properties for single record, data named as "data{index}"
	getDetails:function(data){
		data = this.stringToArray(data);
		var result = {};
		for (var i=0; i < data.length; i++) 
		 result["data"+i]=data[i];
		 
		return result;
	},
	//dyn loading is not supported by csv data source
	getInfo:function(data){
		return { 
		 _size:0,
		 _from:0
		};
	},
	//split string in array, takes string surrounding quotes in account
	stringToArray:function(data){
		data = data.split(this.cell);
		for (var i=0; i < data.length; i++)
		 data[i] = data[i].replace(/^[ \t\n\r]*(\"|)/g,"").replace(/(\"|)[ \t\n\r]*$/g,"");
		return data;
	},
	row:"\n",	//default row separator
	cell:","	//default cell separator
};

dhx.DataDriver.xml={
	_isValidXML:function(data){
		if (!data || !data.documentElement)
			return null;
		if (data.getElementsByTagName("parsererror").length)
			return null;
		return data;
	},
	//convert xml string to xml object if necessary
	toObject:function(text,xml){
		if (this._isValidXML(data))
			return data;
		if (typeof text == "string")
			var data = this.fromString(text.replace(/^[\s]+/,""));
		else
			data = text;

		if (this._isValidXML(data))
			return data;
		return null;
	},
	//get array of records
	getRecords:function(data){
		return this.xpath(data,this.records);
	},
	records:"/*/item",
	child:"item",
	config:"/*/config",
	//get hash of properties for single record
	getDetails:function(data){
		return this.tagToObject(data,{});
	},
	//get count of data and position at which new data_loading need to be inserted
	getInfo:function(data){
		
		var config = this.xpath(data, this.config);
		if (config.length)
			config = this.assignTypes(this.tagToObject(config[0],{}));
		else 
			config = null;

		return { 
		 _size:(data.documentElement.getAttribute("total_count")||0),
		 _from:(data.documentElement.getAttribute("pos")||0),
		 _parent:(data.documentElement.getAttribute("parent")||0),
		 _config:config,
		 _key:(data.documentElement.getAttribute("dhx_security")||null)
		};
	},
	//xpath helper
	xpath:function(xml,path){
		if (window.XPathResult){	//FF, KHTML, Opera
		 var node=xml;
		 if(xml.nodeName.indexOf("document")==-1)
		 xml=xml.ownerDocument;
		 var res = [];
		 var col = xml.evaluate(path, node, null, XPathResult.ANY_TYPE, null);
		 var temp = col.iterateNext();
		 while (temp){ 
			res.push(temp);
			temp = col.iterateNext();
		}
		return res;
		}	
		else {
			var test = true;
			try {
				if (typeof(xml.selectNodes)=="undefined")
					test = false;
			} catch(e){ /*IE7 and below can't operate with xml object*/ }
			//IE
			if (test)
				return xml.selectNodes(path);
			else {
				//Google hate us, there is no interface to do XPath
				//use naive approach
				var name = path.split("/").pop();
				return xml.getElementsByTagName(name);
			}
		}
	},
	assignTypes:function(obj){
		for (var k in obj){
			var test = obj[k];
			if (typeof test == "object")
				this.assignTypes(test);
			else if (typeof test == "string"){
				if (test === "") 
					continue;
				if (test == "true")
					obj[k] = true;
				else if (test == "false")
					obj[k] = false;
				else if (test == test*1)
					obj[k] = obj[k]*1;
			}
		}
		return obj;
	},
	//convert xml tag to js object, all subtags and attributes are mapped to the properties of result object
	tagToObject:function(tag,z){
		z=z||{};
		var flag=false;
				
		//map attributes
		var a=tag.attributes;
		if(a && a.length){
			for (var i=0; i<a.length; i++)
		 		z[a[i].name]=a[i].value;
		 	flag = true;
	 	}
		//map subtags
		
		var b=tag.childNodes;
		var state = {};
		for (var i=0; i<b.length; i++){
			if (b[i].nodeType==1){
				var name = b[i].tagName;
				if (typeof z[name] != "undefined"){
					if (!dhx.isArray(z[name]))
						z[name]=[z[name]];
					z[name].push(this.tagToObject(b[i],{}));
				}
				else
					z[b[i].tagName]=this.tagToObject(b[i],{});	//sub-object for complex subtags
				flag=true;
			}
		}
		
		if (!flag)
			return this.nodeValue(tag);
		//each object will have its text content as "value" property
		z.value = z.value||this.nodeValue(tag);
		return z;
	},
	//get value of xml node 
	nodeValue:function(node){
		if (node.firstChild)
		 return node.firstChild.data;	//FIXME - long text nodes in FF not supported for now
		return "";
	},
	//convert XML string to XML object
	fromString:function(xmlString){
		try{
			if (window.DOMParser)		// FF, KHTML, Opera
				return (new DOMParser()).parseFromString(xmlString,"text/xml");
			if (window.ActiveXObject){	// IE, utf-8 only 
				var temp=new ActiveXObject("Microsoft.xmlDOM");
				temp.loadXML(xmlString);
				return temp;
			}
		} catch(e){
			dhx.assert_error(e);
			return null;
		}
		dhx.assert_error("Load from xml string is not supported");
	}
};

/*DHX:Depend core/dhx.js*/

/*
	Behavior:DataLoader - load data in the component
	
	@export
		load
		parse
*/
dhx.DataLoader=dhx.proto({
	$init:function(config){
		//prepare data store
		config = config || "";
		
		//list of all active ajax requests
		this._ajax_queue = dhx.toArray();

		this.data = new dhx.DataStore();
		this.data.attachEvent("onClearAll",dhx.bind(this._call_onclearall,this));
		this.data.attachEvent("onServerConfig", dhx.bind(this._call_on_config, this));
		this.data.feed = this._feed;

	},

	_feed:function(from,count,callback){
				//allow only single request at same time
				if (this._load_count)
					return this._load_count=[from,count,callback];	//save last ignored request
				else
					this._load_count=true;
				this._feed_last = [from, count];
				this._feed_common.call(this, from, count, callback);
	},
	_feed_common:function(from, count, callback){
		var url = this.data.url;
		if (from<0) from = 0;
		this.load(url+((url.indexOf("?")==-1)?"?":"&")+(this.dataCount()?("continue=true&"):"")+"start="+from+"&count="+count,[
			this._feed_callback,
			callback
		]);
	},
	_feed_callback:function(){
		//after loading check if we have some ignored requests
		var temp = this._load_count;
		var last = this._feed_last;
		this._load_count = false;
		if (typeof temp =="object" && (temp[0]!=last[0] || temp[1]!=last[1]))
			this.data.feed.apply(this, temp);	//load last ignored request
	},
	//loads data from external URL
	load:function(url,call){
		var ajax = dhx.AtomDataLoader.load.apply(this, arguments);
		this._ajax_queue.push(ajax);

		//prepare data feed for dyn. loading
		if (!this.data.url)
			this.data.url = url;
	},
	//load next set of data rows
	loadNext:function(count, start, callback, url, now){
		if (this._settings.datathrottle && !now){
			if (this._throttle_request)
				window.clearTimeout(this._throttle_request);
			this._throttle_request = dhx.delay(function(){
				this.loadNext(count, start, callback, url, true);
			},this, 0, this._settings.datathrottle);
			return;
		}

		if (!start && start !== 0) start = this.dataCount();
		this.data.url = this.data.url || url;

		if (this.callEvent("onDataRequest", [start,count,callback,url]) && this.data.url)
			this.data.feed.call(this, start, count, callback);
	},
	_maybe_loading_already:function(count, from){
		var last = this._feed_last;
		if(this._load_count && last){
			if (last[0]<=from && (last[1]+last[0] >= count + from )) return true;
		}
		return false;
	},
	//default after loading callback
	_onLoad:function(text,xml,loader){
		//ignore data loading command if data was reloaded 
		this._ajax_queue.remove(loader);

		var data = this.data.driver.toObject(text,xml);
		if (data) 
			this.data._parse(data);
		else
			return this._onLoadError(text, xml, loader);
		
		//data loaded, view rendered, call onready handler
		this._call_onready();

		this.callEvent("onXLE",[]);
	},
	removeMissed_setter:function(value){
		return this.data._removeMissed = value;
	},
	scheme_setter:function(value){
		this.data.scheme(value);
	},	
	dataFeed_setter:function(value){
		this.data.attachEvent("onBeforeFilter", dhx.bind(function(text, value){
			if (this._settings.dataFeed){

				var filter = {};				
				if (!text && !value) return;
				if (typeof text == "function"){
					if (!value) return;
					text(value, filter);
				} else 
					filter = { text:value };

				this.clearAll();
				var url = this._settings.dataFeed;
				var urldata = [];
				if (typeof url == "function")
					return url.call(this, value, filter);
				for (var key in filter)
					urldata.push("dhx_filter["+key+"]="+encodeURIComponent(filter[key]));
				this.load(url+(url.indexOf("?")<0?"?":"&")+urldata.join("&"), this._settings.datatype);
				return false;
			}
		},this));
		return value;
	},

	debug_freid_c_ready:true,
	debug_freid_c_datathrottle:true,
	
	_call_onready:function(){
		if (this._settings.ready && !this._ready_was_used){
			var code = dhx.toFunctor(this._settings.ready);
			if (code)
				dhx.delay(code, this, arguments);
			this._ready_was_used = true;
		}
	},
	_call_onclearall:function(){
		for (var i = 0; i < this._ajax_queue.length; i++)
			this._ajax_queue[i].abort();

		this._ajax_queue = dhx.toArray();
	},
	_call_on_config:function(config){
		this._parseSeetingColl(config);
	}
},dhx.AtomDataLoader);


/*
	DataStore is not a behavior, it standalone object, which represents collection of data.
	Call provideAPI to map data API

	@export
		exists
		idByIndex
		indexById
		get
		set
		refresh
		dataCount
		sort
		filter
		next
		previous
		clearAll
		first
		last
*/
dhx.DataStore = function(){
	this.name = "DataStore";
	
	dhx.extend(this, dhx.EventSystem);

	this.setDriver("json");	//default data source is an
	this.pull = {};						//hash of IDs
	this.order = dhx.toArray();		//order of IDs
	this._marks = {};
};

dhx.DataStore.prototype={
	//defines type of used data driver
	//data driver is an abstraction other different data formats - xml, json, csv, etc.
	setDriver:function(type){
		dhx.assert(dhx.DataDriver[type],"incorrect DataDriver");
		this.driver = dhx.DataDriver[type];
	},
	//process incoming raw data
	_parse:function(data,master){
		this.callEvent("onParse", [this.driver, data]);
		if (this._filter_order)
			this.filter();
			
		//get size and position of data
		var info = this.driver.getInfo(data);
		if (info._key)
			dhx.securityKey = info._key;
		if (info._config)
			this.callEvent("onServerConfig",[info._config]);

		//get array of records
		var recs = this.driver.getRecords(data);

		this._inner_parse(info, recs);

		//in case of tree store we may want to group data
		if (this._scheme_group && this._group_processing)
			this._group_processing(this._scheme_group);

		//optional data sorting
		if (this._scheme_sort){
			this.blockEvent();
			this.sort(this._scheme_sort);
			this.unblockEvent();
		}
		this.callEvent("onStoreLoad",[this.driver, data]);
		//repaint self after data loading
		this.refresh();
	},
	_inner_parse:function(info, recs){
		var from = (info._from||0)*1;
		var subload = true;
		var marks = false;

		if (from === 0 && this.order[0]){ //update mode
			if (this._removeMissed){
				//update mode, create kill list
				marks = {};
				for (var i=0; i<this.order.length; i++)
					marks[this.order[i]]=true;
			}
			
			subload = false;
			from = this.order.length;
		}

		var j=0;
		for (var i=0; i<recs.length; i++){
			//get hash of details for each record
			var temp = this.driver.getDetails(recs[i]);
			var id = this.id(temp); 	//generate ID for the record
			if (!this.pull[id]){		//if such ID already exists - update instead of insert
				this.order[j+from]=id;	
				j++;
			} else if (subload && this.order[j+from])
				j++;

			if(this.pull[id]){
				dhx.extend(this.pull[id],temp,true);//add only new properties
				if (this._scheme_update)
					this._scheme_update(this.pull[id]);
				//update mode, remove item from kill list
				if (marks)
					delete marks[id];
			} else{
				this.pull[id] = temp;
				if (this._scheme_init)
					this._scheme_init(temp);
			}
			
		}

		//update mode, delete items which are not existing in the new xml
		if (marks){
			this.blockEvent();
			for (var delid in marks)
				this.remove(delid);
			this.unblockEvent();
		}

		if (!this.order[info._size-1])
			this.order[info._size-1] = dhx.undefined;
	},
	//generate id for data object
	id:function(data){
		return data.id||(data.id=dhx.uid());
	},
	changeId:function(old, newid){
		//dhx.assert(this.pull[old],"Can't change id, for non existing item: "+old);
		if(this.pull[old])
			this.pull[newid] = this.pull[old];
		this.pull[newid].id = newid;
		this.order[this.order.find(old)]=newid;
		if (this._filter_order)
			this._filter_order[this._filter_order.find(old)]=newid;
		if (this._marks[old]){
			this._marks[newid] = this._marks[old];
			delete this._marks[old];
		}


		this.callEvent("onIdChange", [old, newid]);
		if (this._render_change_id)
			this._render_change_id(old, newid);
		delete this.pull[old];
	},
	//get data from hash by id
	item:function(id){
		return this.pull[id];
	},
	//assigns data by id
	update:function(id,data){
		if (dhx.isUndefined(data)) data = this.item(id);
		if (this._scheme_update)
			this._scheme_update(data);
		if (this.callEvent("onBeforeUpdate", [id, data]) === false) return false;
		this.pull[id]=data;
		this.callEvent("onStoreUpdated",[id, data, "update"]);
	},
	//sends repainting signal
	refresh:function(id){
		if (this._skip_refresh) return; 
		
		if (id)
			this.callEvent("onStoreUpdated",[id, this.pull[id], "paint"]);
		else
			this.callEvent("onStoreUpdated",[null,null,null]);
	},
	silent:function(code, master){
		this._skip_refresh = true;
		code.call(master||this);
		this._skip_refresh = false;
	},
	//converts range IDs to array of all IDs between them
	getRange:function(from,to){		
		//if some point is not defined - use first or last id
		//BEWARE - do not use empty or null ID
		if (from)
			from = this.indexById(from);
		else 
			from = (this.$min||this.startOffset)||0;
		if (to)
			to = this.indexById(to);
		else {
			to = Math.min(((this.$max||this.endOffset)||Infinity),(this.dataCount()-1));
			if (to<0) to = 0; //we have not data in the store
		}

		if (from>to){ //can be in case of backward shift-selection
			var a=to; to=from; from=a;
		}

		return this.getIndexRange(from,to);
	},
	//converts range of indexes to array of all IDs between them
	getIndexRange:function(from,to){
		to=Math.min((to||Infinity),this.dataCount()-1);
		
		var ret=dhx.toArray(); //result of method is rich-array
		for (var i=(from||0); i <= to; i++)
			ret.push(this.item(this.order[i]));
		return ret;
	},
	//returns total count of elements
	dataCount:function(){
		return this.order.length;
	},
	//returns truy if item with such ID exists
	exists:function(id){
		return !!(this.pull[id]);
	},
	//nextmethod is not visible on component level, check DataMove.move
	//moves item from source index to the target index
	move:function(sindex,tindex){
		dhx.assert(sindex>=0 && tindex>=0, "DataStore::move","Incorrect indexes");

		var id = this.idByIndex(sindex);
		var obj = this.item(id);
		
		this.order.removeAt(sindex);	//remove at old position
		//if (sindex<tindex) tindex--;	//correct shift, caused by element removing
		this.order.insertAt(id,Math.min(this.order.length, tindex));	//insert at new position
		
		//repaint signal
		this.callEvent("onStoreUpdated",[id,obj,"move"]);
	},
	scheme:function(config){
		this._scheme = {};
		this._scheme_init = config.$init;
		this._scheme_update = config.$update;
		this._scheme_serialize = config.$serialize;
		this._scheme_group = config.$group;
		this._scheme_sort = config.$sort;

		//ignore $-starting properties, as they have special meaning
		for (var key in config)
			if (key.substr(0,1) != "$")
				this._scheme[key] = config[key];
	},
	sync:function(source, filter, silent){
		if (typeof source == "string")
			source = $$("source");

		if (typeof filter != "function"){
			silent = filter;
			filter = null;
		}
		
		if (dhx.debug_bind){
			this.debug_sync_master = source; 
			dhx.log("[sync] "+this.debug_bind_master.name+"@"+this.debug_bind_master._settings.id+" <= "+this.debug_sync_master.name+"@"+this.debug_sync_master._settings.id);
		}

		this._backbone_source = false;
		if (source.name != "DataStore"){
			if (source.data && source.data.name == "DataStore")
				source = source.data;
			else
				this._backbone_source = true;
		}

		
		var	sync_logic = dhx.bind(function(mode, record, data){
			if (this._backbone_source){
				//ignore first call for backbone sync
				if (!mode) return; 
				//data changing
				if (mode.indexOf("change") === 0){
					if (mode == "change"){
						this.pull[record.id] = record.attributes;
						this.refresh(record.id);
						return;
					} else return;	//ignoring property change event
				}

				//we need to access global model, it has different position for different events
				if (mode == "reset")
					data = record;
				//fill data collections from backbone model
				this.order = []; this.pull = {};
				this._filter_order = null;
				for (var i=0; i<data.models.length; i++){
					var id = data.models[i].id;
					this.order.push(id);
					this.pull[id] = data.models[i].attributes;
				}
			} else {
				this._filter_order = null;
				this.order = dhx.toArray([].concat(source.order));
				this.pull = source.pull;
			}
			
			
			if (filter)
				this.silent(filter);
			
			if (this._on_sync)
				this._on_sync();
			if (dhx.debug_bind)
				dhx.log("[sync:request] "+this.debug_sync_master.name+"@"+this.debug_sync_master._settings.id + " <= "+this.debug_bind_master.name+"@"+this.debug_bind_master._settings.id);
			this.callEvent("onSyncApply",[]);
			if (!silent) 
				this.refresh();
			else
				silent = false;
		}, this);

		if (this._backbone_source)
			source.bind('all', sync_logic);
		else
			this._sync_events = [
				source.attachEvent("onStoreUpdated", sync_logic),
				source.attachEvent("onIdChange", dhx.bind(function(old, nid){ this.changeId(old, nid); }, this))
			];

		sync_logic();
	},
	//adds item to the store
	add:function(obj,index){
		//default values		
		if (this._scheme)
			for (var key in this._scheme)
				if (dhx.isUndefined(obj[key]))
					obj[key] = this._scheme[key];
		
		if (this._scheme_init)
			this._scheme_init(obj);
		
		//generate id for the item
		var id = this.id(obj);

		//in case of treetable order is sent as 3rd parameter
		var order = arguments[2]||this.order;
		
		//by default item is added to the end of the list
		var data_size = order.length;
		
		if (dhx.isUndefined(index) || index < 0)
			index = data_size; 
		//check to prevent too big indexes			
		if (index > data_size){
			dhx.log("Warning","DataStore:add","Index of out of bounds");
			index = Math.min(order.length,index);
		}
		if (this.callEvent("onBeforeAdd", [id, obj, index]) === false) return false;

		dhx.assert(!this.exists(id), "Not unique ID");
		
		this.pull[id]=obj;
		order.insertAt(id,index);
		if (this._filter_order){	//adding during filtering
			//we can't know the location of new item in full dataset, making suggestion
			//put at end by default
			var original_index = this._filter_order.length;
			//put at start only if adding to the start and some data exists
			if (!index && this.order.length)
				original_index = 0;

			this._filter_order.insertAt(id,original_index);
		}
		this.callEvent("onAfterAdd",[id,index]);
		//repaint signal
		this.callEvent("onStoreUpdated",[id,obj,"add"]);
		return id;
	},
	
	//removes element from datastore
	remove:function(id){
		//id can be an array of IDs - result of getSelect, for example
		if (dhx.isArray(id)){
			for (var i=0; i < id.length; i++)
				this.remove(id[i]);
			return;
		}
		if (this.callEvent("onBeforeDelete",[id]) === false) return false;
		
		dhx.assert(this.exists(id), "Not existing ID in remove command"+id);

		var obj = this.item(id);	//save for later event
		//clear from collections
		this.order.remove(id);
		if (this._filter_order) 
			this._filter_order.remove(id);
			
		delete this.pull[id];
		if (this._marks[id])
			delete this._marks[id];

		this.callEvent("onAfterDelete",[id]);
		//repaint signal
		this.callEvent("onStoreUpdated",[id,obj,"delete"]);
	},
	//deletes all records in datastore
	clearAll:function(){
		//instead of deleting one by one - just reset inner collections
		this.pull = {};
		this.order = dhx.toArray();
		//this.feed = null;
		this._filter_order = this.url = null;
		this.callEvent("onClearAll",[]);
		this.refresh();
	},
	//converts id to index
	idByIndex:function(index){
		if (index>=this.order.length || index<0)
			dhx.log("Warning","DataStore::idByIndex Incorrect index");
			
		return this.order[index];
	},
	//converts index to id
	indexById:function(id){
		var res = this.order.find(id);	//slower than idByIndex
		
		if (!this.pull[id])
			dhx.log("Warning","DataStore::indexById Non-existing ID: "+ id);
			
		return res;
	},
	//returns ID of next element
	next:function(id,step){
		return this.order[this.indexById(id)+(step||1)];
	},
	//returns ID of first element
	first:function(){
		return this.order[0];
	},
	//returns ID of last element
	last:function(){
		return this.order[this.order.length-1];
	},
	//returns ID of previous element
	previous:function(id,step){
		return this.order[this.indexById(id)-(step||1)];
	},
	/*
		sort data in collection
			by - settings of sorting
		
		or
		
			by - sorting function
			dir - "asc" or "desc"
			
		or
		
			by - property
			dir - "asc" or "desc"
			as - type of sortings
		
		Sorting function will accept 2 parameters and must return 1,0,-1, based on desired order
	*/
	sort:function(by, dir, as){
		var sort = by;	
		if (typeof by == "function")
			sort = {as:by, dir:dir};
		else if (typeof by == "string")
			sort = {by:by.replace(/#/g,""), dir:dir, as:as};

		
		var parameters = [sort.by, sort.dir, sort.as];
		if (!this.callEvent("onBeforeSort",parameters)) return;	
		
		this._sort_core(sort);
		
		//repaint self
		this.refresh();
		
		this.callEvent("onAfterSort",parameters);
	},
	_sort_core:function(sort){
		if (this.order.length){
			var sorter = this._sort._create(sort);
			//get array of IDs
			var neworder = this.getRange(this.first(), this.last());
			neworder.sort(sorter);
			this.order = neworder.map(function(obj){ 
				dhx.assert(obj, "Client sorting can't be used with dynamic loading");
				return this.id(obj);
			},this);
		}
	},
	/*
		Filter datasource
		
		text - property, by which filter
		value - filter mask
		
		or
		
		text  - filter method
		
		Filter method will receive data object and must return true or false
	*/
	_filter_reset:function(preserve){
		//remove previous filtering , if any
		if (this._filter_order && !preserve){
			this.order = this._filter_order;
			delete this._filter_order;
		}
	},
	_filter_core:function(filter, value, preserve){
		var neworder = dhx.toArray();
		for (var i=0; i < this.order.length; i++){
			var id = this.order[i];
			if (filter(this.item(id),value))
				neworder.push(id);
		}
		//set new order of items, store original
		if (!preserve ||  !this._filter_order)
			this._filter_order = this.order;
		this.order = neworder;
	},
	filter:function(text,value,preserve){
		if (!this.callEvent("onBeforeFilter", [text, value])) return;
		
		this._filter_reset(preserve);
		if (!this.order.length) return;
		
		//if text not define -just unfilter previous state and exit
		if (text){
			var filter = text;
			value = value||"";
			if (typeof text == "string"){
				text = text.replace(/#/g,"");
				if (typeof value == "function")
					filter = function(obj){
						return value(obj[text]);
					};
				else{
					value = value.toString().toLowerCase();
					filter = function(obj,value){	//default filter - string start from, case in-sensitive
						dhx.assert(obj, "Client side filtering can't be used with dynamic loading");
						return (obj[text]||"").toString().toLowerCase().indexOf(value)!=-1;
					};
				}
			}
			
			this._filter_core(filter, value, preserve, this._filterMode);
		}
		//repaint self
		this.refresh();
		
		this.callEvent("onAfterFilter", []);
	},
	/*
		Iterate through collection
	*/
	each:function(method,master){
		for (var i=0; i<this.order.length; i++)
			method.call((master||this), this.item(this.order[i]));
	},
	_methodPush:function(object,method){
		return function(){ return object[method].apply(object,arguments); };
	},

	addMark:function(id, mark, css, value){
		var obj = this._marks[id]||{};
		this._marks[id] = obj;
		if (!obj[mark]){
			obj[mark] = value||true;	
			if (css){
				this.item(id).$css = (this.item(id).$css||"")+" "+mark;
				this.refresh(id);
			}
		}
		return obj[mark];
	},
	removeMark:function(id, mark, css){
		var obj = this._marks[id];
		if (obj && obj[mark])
			delete obj[mark];
		if (css){
			var current_css = this.item(id).$css;
			if (current_css){
				this.item(id).$css = current_css.replace(mark, "");
				this.refresh(id);
			}
		}
	},
	hasMark:function(id, mark){
		var obj = this._marks[id];
		return (obj && obj[mark]);
	},
	/*
		map inner methods to some distant object
	*/
	provideApi:function(target,eventable){
		this.debug_bind_master = target;
			
		if (eventable){
			this.mapEvent({
				onbeforesort:	target,
				onaftersort:	target,
				onbeforeadd:	target,
				onafteradd:		target,
				onbeforedelete:	target,
				onafterdelete:	target,
				onbeforeupdate: target/*,
				onafterfilter:	target,
				onbeforefilter:	target*/
			});
		}
			
		var list = ["sort","add","remove","exists","idByIndex","indexById","item","update","refresh","dataCount","filter","next","previous","clearAll","first","last","serialize","sync","addMark","removeMark","hasMark"];
		for (var i=0; i < list.length; i++)
			target[list[i]] = this._methodPush(this,list[i]);
			
	},
	/*
		serializes data to a json object
	*/
	serialize: function(){
		var ids = this.order;
		var result = [];
		for(var i=0; i< ids.length;i++) {
			var el = this.pull[ids[i]];
			if (this._scheme_serialize){
				el = this._scheme_serialize(el);
				if (el===false) continue;
			}
			result.push(el);
		}
		return result;
	},

	_sort:{
		_create:function(config){
			return this._dir(config.dir, this._by(config.by, config.as));
		},
		_as:{
			"date":function(a,b){
				a=a-0; b=b-0;
				return a>b?1:(a<b?-1:0);
			},
			"int":function(a,b){
				a = a*1; b=b*1;
				return a>b?1:(a<b?-1:0);
			},
			"string_strict":function(a,b){
				a = a.toString(); b=b.toString();
				return a>b?1:(a<b?-1:0);
			},
			"string":function(a,b){
				if (!b) return 1;
				if (!a) return -1;
				
				a = a.toString().toLowerCase(); b=b.toString().toLowerCase();
				return a>b?1:(a<b?-1:0);
			}
		},
		_by:function(prop, method){
			if (!prop)
				return method;
			if (typeof method != "function")
				method = this._as[method||"string"];

			dhx.assert(method, "Invalid sorting method");
			return function(a,b){
				return method(a[prop],b[prop]);
			};
		},
		_dir:function(prop, method){
			if (prop == "asc" || !prop)
				return method;
			return function(a,b){
				return method(a,b)*-1;
			};
		}
	}
};




//UI interface
dhx.BaseBind = {
	debug_freid_ignore:{
		"id":true
	},
	
	bind:function(target, rule, format){
		if (typeof target == 'string')
			target = dhx.ui.get(target);
			
		if (target._initBindSource) target._initBindSource();
		if (this._initBindSource) this._initBindSource();

		
			
		if (!target.getBindData)
			dhx.extend(target, dhx.BindSource);
		if (!this._bind_ready){
			var old_render = this.render;
			if (this.filter){
				var key = this._settings.id;
				this.data._on_sync = function(){
					target._bind_updated[key] = false;
				};
			}
			this.render = function(){
				if (this._in_bind_processing) return;
				
				this._in_bind_processing = true;
				var result = this.callEvent("onBindRequest");
				this._in_bind_processing = false;
				
				return old_render.apply(this, ((result === false)?arguments:[]));
			};
			if (this.getValue||this.getValues)
				this.save = function(){
					if (this.validate && !this.validate()) return;
					target.setBindData((this.getValue?this.getValue:this.getValues()),this._settings.id);
				};
			this._bind_ready = true;
		}
		target.addBind(this._settings.id, rule, format);
		
		if (dhx.debug_bind)
			dhx.log("[bind] "+this.name+"@"+this._settings.id+" <= "+target.name+"@"+target._settings.id);

		var target_id = this._settings.id;
		//FIXME - check for touchable is not the best solution, to detect necessary event
		this.attachEvent(this.touchable?"onAfterRender":"onBindRequest", function(){
			return target.getBindData(target_id);
		});
		//we want to refresh list after data loading if it has master link
		//in same time we do not want such operation for dataFeed components
		//as they are reloading data as response to the master link
		if (!this._settings.dataFeed && this.loadNext)
			this.data.attachEvent("onStoreLoad", function(){
				target._bind_updated[target_id] = false;
			});

		if (this.isVisible(this._settings.id))
			this.refresh();
	},
	_unbind:function(target){
		target.removeBind(this._settings.id);
		var events = (this._sync_events||(this.data?this.data._sync_events:0));
		if (events && target.data)
			for (var i=0; i<events.length; i++)
				target.data.detachEvent(events[i]);
	}
};

//bind interface
dhx.BindSource = {
	$init:function(){
		this._bind_hash = {};		//rules per target
		this._bind_updated = {};	//update flags
		this._ignore_binds = {};
		
		//apply specific bind extension
		this._bind_specific_rules(this);
	},
	saveBatch:function(code){
		this._do_not_update_binds = true;
		code.call(this);
		this._do_not_update_binds = false;
		this._update_binds();
	},
	setBindData:function(data, key){
		if (key)
			this._ignore_binds[key] = true;

		if (dhx.debug_bind)
				dhx.log("[bind:save] "+this.name+"@"+this._settings.id+" <= "+"@"+key);
		if (this.setValue)
			this.setValue(data);
		else if (this.setValues)
			this.setValues(data);
		else {
			var id = this.getCursor();
			if (id){
				data = dhx.extend(this.item(id), data, true);
				this.update(id, data);
			}
		}
		this.callEvent("onBindUpdate", [data, key]);		
		if (this.save)
			this.save();
		
		if (key)
			this._ignore_binds[key] = false;
	},
	//fill target with data
	getBindData:function(key, update){
		//fire only if we have data updates from the last time
		if (this._bind_updated[key]) return false;
		var target = dhx.ui.get(key);
		//fill target only when it visible
		if (target.isVisible(target._settings.id)){
			this._bind_updated[key] = true;
			if (dhx.debug_bind)
				dhx.log("[bind:request] "+this.name+"@"+this._settings.id+" => "+target.name+"@"+target._settings.id);
			this._bind_update(target, this._bind_hash[key][0], this._bind_hash[key][1]); //trigger component specific updating logic
			if (update && target.filter)
				target.refresh();
		}
	},
	//add one more bind target
	addBind:function(source, rule, format){
		this._bind_hash[source] = [rule, format];
	},
	removeBind:function(source){
		delete this._bind_hash[source];
		delete this._bind_updated[source];
		delete this._ignore_binds[source];
	},
	//returns true if object belong to "collection" type
	_bind_specific_rules:function(obj){
		if (obj.filter)
			dhx.extend(this, dhx.CollectionBind);
		else if (obj.setValue)
			dhx.extend(this, dhx.ValueBind);
		else
			dhx.extend(this, dhx.RecordBind);
	},
	//inform all binded objects, that source data was updated
	_update_binds:function(){
		if (!this._do_not_update_binds)
			for (var key in this._bind_hash){
				if (this._ignore_binds[key]) continue;
				this._bind_updated[key] = false;
				this.getBindData(key, true);
			}
	},
	//copy data from source to the target
	_bind_update_common:function(target, rule, data){
		if (target.setValue)
			target.setValue(data?data[rule]:data);
		else if (!target.filter){
			if (!data && target.clear)
				target.clear();
			else {
				if (target._check_data_feed(data))
					target.setValues(dhx.clone(data));
			}
		} else {
			target.data.silent(function(){
				this.filter(rule,data);
			});
		}
		target.callEvent("onBindApply", [data,rule,this]);
	}
};


//pure data objects
dhx.DataValue = dhx.proto({
	name:"DataValue",
	isVisible:function(){ return true; },
	$init:function(config){ 
		this.data = ""||config; 
		var id = (config&&config.id)?config.id:dhx.uid();
		this._settings = { id:id };
		dhx.ui.views[id] = this;
	},
	setValue:function(value){
		this.data = value;
		this.callEvent("onChange", [value]);
	},
	getValue:function(){
		return this.data;
	},
	refresh:function(){ this.callEvent("onBindRequest"); }
}, dhx.EventSystem, dhx.BaseBind);

dhx.DataRecord = dhx.proto({
	name:"DataRecord",
	isVisible:function(){ return true; },
	$init:function(config){
		this.data = config||{}; 
		var id = (config&&config.id)?config.id:dhx.uid();
		this._settings = { id:id };
		dhx.ui.views[id] = this;
	},
	getValues:function(){
		return this.data;
	},
	setValues:function(data){
		this.data = data;
		this.callEvent("onChange", [data]);
	},
	refresh:function(){ this.callEvent("onBindRequest"); }
}, dhx.EventSystem, dhx.BaseBind, dhx.AtomDataLoader, dhx.Settings);


dhx.DataCollection = dhx.proto({
	name:"DataCollection",
	isVisible:function(){ 
		if (!this.data.order.length && !this.data._filter_order && !this._settings.dataFeed) return false;
		return true; 
	},
	$init:function(config){
		this.data.provideApi(this, true);
		var id = (config&&config.id)?config.id:dhx.uid();
		this._settings.id =id;
		dhx.ui.views[id] = this;
		this.data.attachEvent("onStoreLoad", dhx.bind(function(){
			this.callEvent("onBindRequest",[]);
		}, this));
	},
	refresh:function(){ this.callEvent("onBindRequest",[]); }
}, dhx.DataLoader, dhx.EventSystem, dhx.BaseBind, dhx.Settings);




dhx.ValueBind={
	$init:function(){
		this.attachEvent("onChange", this._update_binds);
	},
	_bind_update:function(target, rule, format){
		var data = this.getValue()||"";
		if (format) data = format(data);
		
		if (target.setValue)
			target.setValue(data);
		else if (!target.filter){
			var pod = {}; pod[rule] = data;
			if (target._check_data_feed(data))
				target.setValues(pod);
		} else{
			target.data.silent(function(){
				this.filter(rule,data);
			});
		}
		target.callEvent("onBindApply", [data,rule,this]);
	}
};

dhx.RecordBind={
	$init:function(){
		this.attachEvent("onChange", this._update_binds);		
	},
	_bind_update:function(target, rule){
		var data = this.getValues()||null;
		this._bind_update_common(target, rule, data);
	}
};

dhx.CollectionBind={
	$init:function(){
		this._cursor = null;
		this.attachEvent("onSelectChange", function(data){
			var sel = this.getSelected();
			this.setCursor(sel?(sel.id||sel):null);
		});
		this.attachEvent("onAfterCursorChange", this._update_binds);		
		this.data.attachEvent("onStoreUpdated", dhx.bind(function(id, data, mode){
			if (id && id == this.getCursor() && mode != "paint")
				this._update_binds();
		},this));
		this.data.attachEvent("onClearAll", dhx.bind(function(){
			this._cursor = null;
		},this));
		this.data.attachEvent("onIdChange", dhx.bind(function(oldid, newid){
			if (this._cursor == oldid)
				this._cursor = newid;
		},this));
	},
	setCursor:function(id){
		if (id == this._cursor || (id !== null && !this.item(id))) return;
		
		this.callEvent("onBeforeCursorChange", [this._cursor]);
		this._cursor = id;
		this.callEvent("onAfterCursorChange",[id]);
	},
	getCursor:function(){
		return this._cursor;
	},
	_bind_update:function(target, rule){ 
		var data = this.item(this.getCursor())|| this._settings.defaultData || null;
		this._bind_update_common(target, rule, data);
	}
};	
/*DHX:Depend core/legacy_bind.js*/
/*DHX:Depend core/dhx.js*/
/*DHX:Depend core/bind.js*/

/*jsl:ignore*/

if (!dhx.ui)
	dhx.ui = {};

if (!dhx.ui.views){
	dhx.ui.views = {};
	dhx.ui.get = function(id){
		if (id._settings) return id;
		return dhx.ui.views[id];
	};
}

dhtmlXDataStore = function(config){
	var obj = new dhx.DataCollection(config);
	var name = "_dp_init";
	obj[name]=function(dp){
		//map methods
		var varname = "_methods";
		dp[varname]=["dummy","dummy","changeId","dummy"];
		
		this.data._old_names = {
			"add":"inserted",
			"update":"updated",
			"delete":"deleted"
		};
		this.data.attachEvent("onStoreUpdated",function(id,data,mode){
			if (id && !dp._silent)
				dp.setUpdated(id,true,this._old_names[mode]);
		});
		
		
		varname = "_getRowData";
		//serialize item's data in URL
		dp[varname]=function(id,pref){
			var ev=this.obj.data.item(id);
			var data = { id:id };
			data[this.action_param] = this.obj.getUserData(id);
			if (ev)
				for (var a in ev){
						data[a]=ev[a];
				}
			
			return data;
		};

		this.changeId = function(oldid, newid){ 
			this.data.changeId(oldid, newid);	
			dp._silent = true;
			this.data.callEvent("onStoreUpdated", [newid, this.item(newid), "update"]);
			dp._silent = false;
		};	

		varname = "_clearUpdateFlag";
		dp[varname]=function(){};
		this._userdata = {};

	};
	obj.dummy = function(){};
	obj.setUserData=function(id,name,value){
		this._userdata[id]=value;
	};
	obj.getUserData=function(id,name){
		return this._userdata[id];
	};
	obj.dataFeed=function(obj){
		this.define("dataFeed", obj);
	};
	dhx.extend(obj, dhx.BindSource);
	return obj;
};

if (window.dhtmlXDataView)
	dhtmlXDataView.prototype._initBindSource=function(){
		this.isVisible = function(){
			if (!this.data.order.length && !this.data._filter_order && !this._settings.dataFeed) return false;
			return true;
		};
		var settings = "_settings";
		this._settings = this._settings || this[settings];
		if (!this._settings.id)
			this._settings.id = dhx.uid();
		this.unbind = dhx.BaseBind.unbind;
		this.unsync = dhx.BaseBind.unsync;
		dhx.ui.views[this._settings.id] = this;
	};

if (window.dhtmlXChart)
	dhtmlXChart.prototype._initBindSource=function(){
		this.isVisible = function(){
			if (!this.data.order.length && !this.data._filtered_state && !this._settings.dataFeed) return false;
			return true;
		};
		var settings = "_settings";
		this._settings = this._settings || this[settings];
		if (!this._settings.id)
			this._settings.id = dhx.uid();
		this.unbind = dhx.BaseBind.unbind;
		this.unsync = dhx.BaseBind.unsync;
		dhx.ui.views[this._settings.id] = this;
	};
	

dhx.BaseBind.unsync = function(target){
	return dhx.BaseBind._unbind.call(this, target);
}
dhx.BaseBind.unbind = function(target){
	return dhx.BaseBind._unbind.call(this, target);
}
dhx.BaseBind.legacyBind = function(){
	return dhx.BaseBind.bind.apply(this, arguments);
};
dhx.BaseBind.legacySync = function(source, rule){
	if (this._initBindSource) this._initBindSource();
	if (source._initBindSource) source._initBindSource();

	this.attachEvent("onAfterEditStop", function(id){
		this.save(id);
		return true;
	});

		
		

	this.attachEvent("onDataRequest", function(start, count){
		for (var i=start; i<start+count; i++)
			if (!source.data.order[i]){
				source.loadNext(count, start);
				return false;
			}
	});



	this.save = function(id){
		if (!id) id = this.getCursor();
		var sobj = this.item(id);
		var tobj = source.item(id);
		for (var key in sobj)
			if (key.indexOf("$")!==0)
				tobj[key] = sobj[key];
		source.refresh(id);
	};

	if (source && source.name == "DataCollection")
		return source.data.sync.apply(this.data, arguments);
	else
		return this.data.sync.apply(this.data, arguments);
};

if (window.dhtmlXForm){
	
	dhtmlXForm.prototype.bind = function(target){
		dhx.BaseBind.bind.apply(this, arguments);
		target.getBindData(this._settings.id);
	};
	dhtmlXForm.prototype.unbind = function(target){
		dhx.BaseBind._unbind.call(this,target);
	};

	dhtmlXForm.prototype._initBindSource = function(){
		if (dhx.isUndefined(this._settings)){
			this._settings = {
				id: dhx.uid(),
				dataFeed:this._server_feed
			};
			dhx.ui.views[this._settings.id] = this;
		}
	};
	dhtmlXForm.prototype._check_data_feed = function(data){
		if (!this._settings.dataFeed || this._ignore_feed || !data) return true;
		var url = this._settings.dataFeed;
		if (typeof url == "function")
			return url.call(this, (data.id||data), data);
		url = url+(url.indexOf("?")==-1?"?":"&")+"action=get&id="+encodeURIComponent(data.id||data);
		this.load(url);
		return false;
	};
	dhtmlXForm.prototype.setValues = dhtmlXForm.prototype.setFormData;
	dhtmlXForm.prototype.getValues = function(){
		return this.getFormData(false, true);
	};

	dhtmlXForm.prototype.dataFeed = function(value){
		if (this._settings)
			this._settings.dataFeed = value;
		else
			this._server_feed = value;
	};

	dhtmlXForm.prototype.refresh = dhtmlXForm.prototype.isVisible = function(value){
		return true;
	};
}
 
if (window.scheduler){
	if (!window.Scheduler)
		window.Scheduler = {};
	Scheduler.$syncFactory=function(scheduler){
		scheduler.sync = function(source, rule){
			if (this._initBindSource) this._initBindSource();
			if (source._initBindSource) source._initBindSource();

			var process = "_process_loading";
			var insync = function(ignore){
					scheduler.clearAll();
					var order = source.data.order;
					var pull = source.data.pull;
					var evs = [];
					for (var i=0; i<order.length; i++){
						if (rule && rule.copy)
							evs[i]=dhx.clone(pull[order[i]]);
						else
							evs[i]=pull[order[i]];
					}
					scheduler[process](evs);
			};
			this.save = function(id){
				if (!id) id = this.getCursor();
				var data = this.item(id);
				var olddat = source.item(id);

				if (this.callEvent("onStoreSave", [id, data, olddat])){
					dhx.extend(source.item(id),data, true);
					source.update(id);
				}
			};
			this.item = function(id){
				return this.getEvent(id);
			};
			this._sync_events=[
				source.data.attachEvent("onStoreUpdated", function(id, data, mode){ 
					insync.call(this);
				}),
				source.data.attachEvent("onIdChange", function(oldid, newid){
					combo.changeOptionId(oldid, newid);
				})
			];
			this.attachEvent("onEventChanged", function(id){
				this.save(id);
			});
			this.attachEvent("onEventAdded", function(id, data){
				if (!source.data.pull[id])
				source.add(data);
			});
			this.attachEvent("onEventDeleted", function(id){
				if (source.data.pull[id])
					source.remove(id);
			});
			insync();
		};
		scheduler.unsync = function(target){
			dhx.BaseBind._unbind.call(this,target);
		}
		scheduler._initBindSource = function(){
			if (!this._settings)
				this._settings = { id:dhx.uid() };
		}
	}
	Scheduler.$syncFactory(window.scheduler);
}
if (window.dhtmlXCombo){
	dhtmlXCombo.prototype.bind = function(){
		dhx.BaseBind.bind.apply(this, arguments);
	};
	dhtmlXCombo.unbind = function(target){
		dhx.BaseBind._unbind.call(this,target);
	}
	dhtmlXCombo.unsync = function(target){
		dhx.BaseBind._unbind.call(this,target);
	}

	dhtmlXCombo.prototype.dataFeed = function(value){
		if (this._settings)
			this._settings.dataFeed = value;
		else
			this._server_feed = value;
	};

	dhtmlXCombo.prototype.sync = function(source, rule){
		if (this._initBindSource) this._initBindSource();
		if (source._initBindSource) source._initBindSource();


		var combo = this;

		var insync = function(ignore){
			combo.clearAll();
			combo.addOption(this.serialize());
		};

		//source.data.attachEvent("onStoreLoad", insync);
		this._sync_events=[
			source.data.attachEvent("onStoreUpdated", function(id, data, mode){ 
				insync.call(this);
			}),
			source.data.attachEvent("onIdChange", function(oldid, newid){
				combo.changeOptionId(oldid, newid);
			})
		];


		insync.call(source);
	};

	dhtmlXCombo.prototype._initBindSource = function() { 
		if (dhx.isUndefined(this._settings)){
			this._settings = {
				id: dhx.uid(),
				dataFeed:this._server_feed
			};
			dhx.ui.views[this._settings.id] = this;

			this.data = { silent:dhx.bind(function(code){
				code.call(this);
			},this)};

			dhtmlxEventable(this.data);

			this.attachEvent("onChange", function() {
				this.callEvent("onSelectChange", [this.getSelectedValue()]);
			});
			this.attachEvent("onXLE", function(){
				this.callEvent("onBindRequest",[]);
			});
		}
	};

	dhtmlXCombo.prototype.item = function(id) {
		return this.getOption(id);
	};

	dhtmlXCombo.prototype.getSelected = function() {
		return this.getSelectedValue();
	};
	dhtmlXCombo.prototype.isVisible = function() {
		if (!this.optionsArr.length && !this._settings.dataFeed) return false;
		return true;
	};
	dhtmlXCombo.prototype.refresh = function() {
		this.render(true);
	};

	dhtmlXCombo.prototype.filter = function(callback, master){
		alert("not implemented");
	};
}

if (window.dhtmlXGridObject){
	dhtmlXGridObject.prototype.bind = function(source, rule, format) {
		dhx.BaseBind.bind.apply(this, arguments);
	};
	dhtmlXGridObject.prototype.unbind = function(target){
		dhx.BaseBind._unbind.call(this,target);
	}
	dhtmlXGridObject.prototype.unsync = function(target){
		dhx.BaseBind._unbind.call(this,target);
	}
	
	dhtmlXGridObject.prototype.dataFeed = function(value){
		if (this._settings)
			this._settings.dataFeed = value;
		else
			this._server_feed = value;
	};

	dhtmlXGridObject.prototype.sync = function(source, rule){
		if (this._initBindSource) this._initBindSource();
		if (source._initBindSource) source._initBindSource();

		var grid = this;
		var parsing = "_parsing";
		var parser = "_parser";
		var locator = "_locator";
		var parser_func = "_process_store_row";
		var locator_func = "_get_store_data";

		this.save = function(id){
			if (!id) id = this.getCursor();
			dhx.extend(source.item(id),this.item(id), true);
			source.update(id);
		};
		var insync = function(ignore){
			var cursor = grid.getCursor?grid.getCursor():null;

			var from = 0; 
			if (grid._legacy_ignore_next){
				from  = grid._legacy_ignore_next;
				grid._legacy_ignore_next = false;
			} else {
				grid.clearAll();
			}

			var count = this.dataCount();
			if (count){
				grid[parsing]=true;
				for (var i = from; i < count; i++){
					var id = this.order[i];
					if (!id) continue;
					if (from && grid.rowsBuffer[i]) continue;
					grid.rowsBuffer[i]={
						idd: id,
						data: this.pull[id]
					};
					grid.rowsBuffer[i][parser] = grid[parser_func];
					grid.rowsBuffer[i][locator] = grid[locator_func];
					grid.rowsAr[id]=this.pull[id];
				}
				if (!grid.rowsBuffer[count-1]){
					grid.rowsBuffer[count-1] = dhtmlx.undefined;
					grid.xmlFileUrl = grid.xmlFileUrl||this.url;
				}

				if (grid.pagingOn)
					grid.changePage();
				else {
					if (grid._srnd && grid._fillers)
						grid._update_srnd_view();
					else{
						grid.render_dataset();
						grid.callEvent("onXLE",[]);
					}
				}
				grid[parsing]=false;
			}

			if (cursor && grid.setCursor)
				grid.setCursor(grid.rowsAr[cursor]?cursor:null);
		};

		//source.data.attachEvent("onStoreLoad", insync);
		this._sync_events=[
			source.data.attachEvent("onStoreUpdated", function(id, data, mode){ 
				if (mode == "delete"){
					grid.deleteRow(id);
					grid.data.callEvent("onStoreUpdated",[id, data, mode]);
				} else if (mode == "update"){
					grid.callEvent("onSyncUpdate", [data, mode]);
					grid.update(id, data);
					grid.data.callEvent("onStoreUpdated",[id, data, mode]);
				} else if (mode == "add"){
					grid.callEvent("onSyncUpdate", [data, mode]);
					grid.add(id, data, this.indexById(id));
					grid.data.callEvent("onStoreUpdated",[id,data,mode]);
				} else insync.call(this);

			}),
			source.data.attachEvent("onStoreLoad", function(driver, data){
				grid.xmlFileUrl = source.data.url;
				grid._legacy_ignore_next = driver.getInfo(data)._from;
			}),
			source.data.attachEvent("onIdChange", function(oldid, newid){
				grid.changeRowId(oldid, newid);
			})
		];
		
		grid.attachEvent("onDynXLS", function(start, count){
			for (var i=start; i<start+count; i++)
				if (!source.data.order[i]){
					source.loadNext(count, start);
					return false;
				}
			grid._legacy_ignore_next = start;
			insync.call(source.data);
		});

		insync.call(source.data);
		grid.attachEvent("onEditCell", function(stage, id, ind, value, oldvalue){
			if (stage==2 && value != oldvalue)
				this.save(id);
			return true;
		});
		grid.attachEvent("onClearAll",function(){
			var name = "_f_rowsBuffer";
	    	this[name]=null; 
	    });
	
		if (rule && rule.sort)	
			grid.attachEvent("onBeforeSorting", function(ind, type, dir){
				if (type == "connector") return false;
				var id = this.getColumnId(ind);
				source.sort("#"+id+"#", (dir=="asc"?"asc":"desc"), (type=="int"?type:"string"));
				grid.setSortImgState(true, ind, dir);
				return false;
			});

		if (rule && rule.filter){
			grid.attachEvent("onFilterStart", function(cols, values){
				var name = "_con_f_used";
				if (grid[name] && grid[name].length)
					return false;

				source.data.silent(function(){
					source.filter();
					for (var i=0; i<cols.length; i++){
						if (values[i] == "") continue;
						var id = grid.getColumnId(cols[i]);
						source.filter("#"+id+"#", values[i], i!=0);
					}
				});

				source.refresh();
				return false;
			});
			grid.collectValues = function(index){
				var id = this.getColumnId(index);
				return (function(id){
					var values = [];
					var checks = {};
					this.data.each(function(obj){
						var value = obj[id];
						if (!checks[value]){
							checks[value] = true;
							values.push(value);
						}
					});
					values.sort();
					return values;
				}).call(source, id);
			};
		}

		if (rule && rule.select)
			grid.attachEvent("onRowSelect", function(id){
				source.setCursor(id);
			});

		grid.clearAndLoad = function(url){
			source.clearAll();
			source.load(url);
		};
		

				    
	};

	dhtmlXGridObject.prototype._initBindSource = function() { 
		if (dhx.isUndefined(this._settings)){
			this._settings = {
				id: dhx.uid(),
				dataFeed:this._server_feed
			};
			dhx.ui.views[this._settings.id] = this;

			this.data = { silent:dhx.bind(function(code){
				code.call(this);
			},this)};

			dhtmlxEventable(this.data);
			var name = "_cCount";
			for (var i=0; i<this[name]; i++)
				if (!this.columnIds[i])
					this.columnIds[i] = "cell"+i;

			this.attachEvent("onSelectStateChanged", function(id) {
				this.callEvent("onSelectChange", [id]);
			});
			this.attachEvent("onSelectionCleared", function() {
				this.callEvent("onSelectChange", [null]);
			});
			this.attachEvent("onEditCell", function(stage,rId) {
				if (stage === 2 && this.getCursor) {
					if (rId && rId == this.getCursor())
						this._update_binds();
				}
				return true;
			});
			this.attachEvent("onXLE", function(){
				this.callEvent("onBindRequest",[]);
			});
		}
	};

	dhtmlXGridObject.prototype.item = function(id) {
		if (id === null) return null;
		var source = this.getRowById(id);
		if (!source) return null;
		
		var name = "_attrs";
		var data = dhx.copy(source[name]);
			data.id = id;
		var length = this.getColumnsNum();
		for (var i = 0; i < length; i++) {
			data[this.columnIds[i]] = this.cells(id, i).getValue();
		}
		return data;
	};

	dhtmlXGridObject.prototype.update = function(id,data){
		for (var i=0; i<this.columnIds.length; i++){
			var key = this.columnIds[i];
			if (!dhx.isUndefined(data[key]))
				this.cells(id, i).setValue(data[key]);
		}
		var name = "_attrs";
		var attrs = this.getRowById(id)[name];
		for (var key in data)
			attrs[key] = data[key];
		this.callEvent("onBindUpdate",[id]);
	};

	dhtmlXGridObject.prototype.add = function(id,data,index){
		var ar_data = [];
		for (var i=0; i<this.columnIds.length; i++){
			var key = this.columnIds[i];
			ar_data[i] = dhx.isUndefined(data[key])?"":data[key];
		}
		this.addRow(id, ar_data, index);
		var name = "_attrs";
		this.getRowById(id)[name] = dhx.copy(data);
	};

	dhtmlXGridObject.prototype.getSelected = function() {
		return this.getSelectedRowId();
	};
	dhtmlXGridObject.prototype.isVisible = function() {
		var name = "_f_rowsBuffer";
		if (!this.rowsBuffer.length && !this[name] && !this._settings.dataFeed) return false;
		return true;
	};
	dhtmlXGridObject.prototype.refresh = function() {
		this.render_dataset();
	};

	dhtmlXGridObject.prototype.filter = function(callback, master){
		//if (!this.rowsBuffer.length && !this._f_rowsBuffer) return;
		if (this._settings.dataFeed){
			var filter = {};
			if (!callback && !master) return;
			if (typeof callback == "function"){
				if (!master) return;
				callback(master, filter);
			} else  if (dhx.isUndefined(callback))
				filter = master;
			else
				filter[callback] = master;

			this.clearAll(); 
			var url = this._settings.dataFeed;
			if (typeof url == "function")
				return url.call(this, master, filter);

			var urldata = [];
			for (var key in filter)
				urldata.push("dhx_filter["+key+"]="+encodeURIComponent(filter[key]));

			this.load(url+(url.indexOf("?")<0?"?":"&")+urldata.join("&"));
			return false;
		}

		if (master === null) {
			return this.filterBy(0, function(){ return false; });
		}

		this.filterBy(0, function(value, id){
			return callback.call(this, id, master);
		});
	};
}


if (window.dhtmlXTreeObject){
	dhtmlXTreeObject.prototype.bind = function() {
		dhx.BaseBind.bind.apply(this, arguments);
	};
	dhtmlXTreeObject.prototype.unbind = function(target){
		dhx.BaseBind._unbind.call(this,target);
	}

	

	dhtmlXTreeObject.prototype.dataFeed = function(value){
		if (this._settings)
			this._settings.dataFeed = value;
		else
			this._server_feed = value;
	};

	dhtmlXTreeObject.prototype._initBindSource = function() {
		if (dhx.isUndefined(this._settings)){
			this._settings = {
				id: dhx.uid(),
				dataFeed:this._server_feed
			};
			dhx.ui.views[this._settings.id] = this;

			this.data = { silent:dhx.bind(function(code){
				code.call(this);
			},this)};

			dhtmlxEventable(this.data);

			this.attachEvent("onSelect", function(id) {
				this.callEvent("onSelectChange", [id]);
			});
			this.attachEvent("onEdit", function(stage,rId) {
				if (stage === 2) {
					if (rId && rId == this.getCursor())
						this._update_binds();
				}
				return true;
			});
		}
	};

	dhtmlXTreeObject.prototype.item = function(id) {
		if (id === null) return null;
		return { id: id, text:this.getItemText(id)};
	};

	dhtmlXTreeObject.prototype.getSelected = function() {
		return this.getSelectedItemId();
	};
	dhtmlXTreeObject.prototype.isVisible = function() {
		return true;
	};
	dhtmlXTreeObject.prototype.refresh = function() {
		//dummy placeholder
	};

	dhtmlXTreeObject.prototype.filter = function(callback, master){
		//dummy placeholder, because tree doesn't support filtering
		if (this._settings.dataFeed){
			var filter = {};
			if (!callback && !master) return;
			if (typeof callback == "function"){
				if (!master) return;
				callback(master, filter);
			} else  if (dhx.isUndefined(callback))
				filter = master;
			else
				filter[callback] = master;

			this.deleteChildItems(0); 
			var url = this._settings.dataFeed;
			if (typeof url == "function")
				return url.call(this, [(data.id||data), data]);
			var urldata = [];
			for (var key in filter)
				urldata.push("dhx_filter["+key+"]="+encodeURIComponent(filter[key]));

			this.loadXML(url+(url.indexOf("?")<0?"?":"&")+urldata.join("&"));
			return false;
		}
	};

	dhtmlXTreeObject.prototype.update = function(id,data){
		if (!dhx.isUndefined(data.text))
			this.setItemText(id, data.text);
	};
}

/*jsl:end*/

