/*
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 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=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")!=-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= 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; ito){ //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 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; ib?1:(ab?1:(ab?1:(ab?1:(a "+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