2541 lines
66 KiB
JavaScript
2541 lines
66 KiB
JavaScript
/*
|
|
Product Name: dhtmlxSuite
|
|
Version: 5.2.0
|
|
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
|
|
*/
|
|
|
|
/*
|
|
Copyright DHTMLX LTD. http://www.dhtmlx.com
|
|
You allowed to use this component or parts of it under GPL terms
|
|
To use it on other terms or get Professional edition of the component please contact us at sales@dhtmlx.com
|
|
*/
|
|
/*
|
|
2014 March 19
|
|
*/
|
|
|
|
|
|
|
|
/* DHX DEPEND FROM FILE 'assert.js'*/
|
|
|
|
|
|
if (!window.dhtmlx)
|
|
dhtmlx={};
|
|
|
|
//check some rule, show message as error if rule is not correct
|
|
dhtmlx.assert = function(test, message){
|
|
if (!test) dhtmlx.error(message);
|
|
};
|
|
dhtmlx.assert_enabled=function(){ return false; };
|
|
|
|
//register names of event, which can be triggered by the object
|
|
dhtmlx.assert_event = function(obj, evs){
|
|
if (!obj._event_check){
|
|
obj._event_check = {};
|
|
obj._event_check_size = {};
|
|
}
|
|
|
|
for (var a in evs){
|
|
obj._event_check[a.toLowerCase()]=evs[a];
|
|
var count=-1; for (var t in evs[a]) count++;
|
|
obj._event_check_size[a.toLowerCase()]=count;
|
|
}
|
|
};
|
|
dhtmlx.assert_method_info=function(obj, name, descr, rules){
|
|
var args = [];
|
|
for (var i=0; i < rules.length; i++) {
|
|
args.push(rules[i][0]+" : "+rules[i][1]+"\n "+rules[i][2].describe()+(rules[i][3]?"; optional":""));
|
|
}
|
|
return obj.name+"."+name+"\n"+descr+"\n Arguments:\n - "+args.join("\n - ");
|
|
};
|
|
dhtmlx.assert_method = function(obj, config){
|
|
for (var key in config)
|
|
dhtmlx.assert_method_process(obj, key, config[key].descr, config[key].args, (config[key].min||99), config[key].skip);
|
|
};
|
|
dhtmlx.assert_method_process = function (obj, name, descr, rules, min, skip){
|
|
var old = obj[name];
|
|
if (!skip)
|
|
obj[name] = function(){
|
|
if (arguments.length != rules.length && arguments.length < min)
|
|
dhtmlx.log("warn","Incorrect count of parameters\n"+obj[name].describe()+"\n\nExpecting "+rules.length+" but have only "+arguments.length);
|
|
else
|
|
for (var i=0; i<rules.length; i++)
|
|
if (!rules[i][3] && !rules[i][2](arguments[i]))
|
|
dhtmlx.log("warn","Incorrect method call\n"+obj[name].describe()+"\n\nActual value of "+(i+1)+" parameter: {"+(typeof arguments[i])+"} "+arguments[i]);
|
|
|
|
return old.apply(this, arguments);
|
|
};
|
|
obj[name].describe = function(){ return dhtmlx.assert_method_info(obj, name, descr, rules); };
|
|
};
|
|
dhtmlx.assert_event_call = function(obj, name, args){
|
|
if (obj._event_check){
|
|
if (!obj._event_check[name])
|
|
dhtmlx.log("warn","Not expected event call :"+name);
|
|
else if (dhtmlx.isNotDefined(args))
|
|
dhtmlx.log("warn","Event without parameters :"+name);
|
|
else if (obj._event_check_size[name] != args.length)
|
|
dhtmlx.log("warn","Incorrect event call, expected "+obj._event_check_size[name]+" parameter(s), but have "+args.length +" parameter(s), for "+name+" event");
|
|
}
|
|
};
|
|
dhtmlx.assert_event_attach = function(obj, name){
|
|
if (obj._event_check && !obj._event_check[name])
|
|
dhtmlx.log("warn","Unknown event name: "+name);
|
|
};
|
|
//register names of properties, which can be used in object's configuration
|
|
dhtmlx.assert_property = function(obj, evs){
|
|
if (!obj._settings_check)
|
|
obj._settings_check={};
|
|
dhtmlx.extend(obj._settings_check, evs);
|
|
};
|
|
//check all options in collection, against list of allowed properties
|
|
dhtmlx.assert_check = function(data,coll){
|
|
if (typeof data == "object"){
|
|
for (var key in data){
|
|
dhtmlx.assert_settings(key,data[key],coll);
|
|
}
|
|
}
|
|
};
|
|
//check if type and value of property is the same as in scheme
|
|
dhtmlx.assert_settings = function(mode,value,coll){
|
|
coll = coll || this._settings_check;
|
|
|
|
//if value is not in collection of defined ones
|
|
if (coll){
|
|
if (!coll[mode]) //not registered property
|
|
return dhtmlx.log("warn","Unknown propery: "+mode);
|
|
|
|
var descr = "";
|
|
var error = "";
|
|
var check = false;
|
|
for (var i=0; i<coll[mode].length; i++){
|
|
var rule = coll[mode][i];
|
|
if (typeof rule == "string")
|
|
continue;
|
|
if (typeof rule == "function")
|
|
check = check || rule(value);
|
|
else if (typeof rule == "object" && typeof rule[1] == "function"){
|
|
check = check || rule[1](value);
|
|
if (check && rule[2])
|
|
dhtmlx["assert_check"](value, rule[2]); //temporary fix , for sources generator
|
|
}
|
|
if (check) break;
|
|
}
|
|
if (!check )
|
|
dhtmlx.log("warn","Invalid configuration\n"+dhtmlx.assert_info(mode,coll)+"\nActual value: {"+(typeof value)+"} "+value);
|
|
}
|
|
};
|
|
|
|
dhtmlx.assert_info=function(name, set){
|
|
var ruleset = set[name];
|
|
var descr = "";
|
|
var expected = [];
|
|
for (var i=0; i<ruleset.length; i++){
|
|
if (typeof rule == "string")
|
|
descr = ruleset[i];
|
|
else if (ruleset[i].describe)
|
|
expected.push(ruleset[i].describe());
|
|
else if (ruleset[i][1] && ruleset[i][1].describe)
|
|
expected.push(ruleset[i][1].describe());
|
|
}
|
|
return "Property: "+name+", "+descr+" \nExpected value: \n - "+expected.join("\n - ");
|
|
};
|
|
|
|
|
|
if (dhtmlx.assert_enabled()){
|
|
|
|
dhtmlx.assert_rule_color=function(check){
|
|
if (typeof check != "string") return false;
|
|
if (check.indexOf("#")!==0) return false;
|
|
if (check.substr(1).replace(/[0-9A-F]/gi,"")!=="") return false;
|
|
return true;
|
|
};
|
|
dhtmlx.assert_rule_color.describe = function(){
|
|
return "{String} Value must start from # and contain hexadecimal code of color";
|
|
};
|
|
|
|
dhtmlx.assert_rule_template=function(check){
|
|
if (typeof check == "function") return true;
|
|
if (typeof check == "string") return true;
|
|
return false;
|
|
};
|
|
dhtmlx.assert_rule_template.describe = function(){
|
|
return "{Function},{String} Value must be a function which accepts data object and return text string, or a sting with optional template markers";
|
|
};
|
|
|
|
dhtmlx.assert_rule_boolean=function(check){
|
|
if (typeof check == "boolean") return true;
|
|
return false;
|
|
};
|
|
dhtmlx.assert_rule_boolean.describe = function(){
|
|
return "{Boolean} true or false";
|
|
};
|
|
|
|
dhtmlx.assert_rule_object=function(check, sub){
|
|
if (typeof check == "object") return true;
|
|
return false;
|
|
};
|
|
dhtmlx.assert_rule_object.describe = function(){
|
|
return "{Object} Configuration object";
|
|
};
|
|
|
|
|
|
dhtmlx.assert_rule_string=function(check){
|
|
if (typeof check == "string") return true;
|
|
return false;
|
|
};
|
|
dhtmlx.assert_rule_string.describe = function(){
|
|
return "{String} Plain string";
|
|
};
|
|
|
|
|
|
dhtmlx.assert_rule_htmlpt=function(check){
|
|
return !!dhtmlx.toNode(check);
|
|
};
|
|
dhtmlx.assert_rule_htmlpt.describe = function(){
|
|
return "{Object},{String} HTML node or ID of HTML Node";
|
|
};
|
|
|
|
dhtmlx.assert_rule_notdocumented=function(check){
|
|
return false;
|
|
};
|
|
dhtmlx.assert_rule_notdocumented.describe = function(){
|
|
return "This options wasn't documented";
|
|
};
|
|
|
|
dhtmlx.assert_rule_key=function(obj){
|
|
var t = function (check){
|
|
return obj[check];
|
|
};
|
|
t.describe=function(){
|
|
var opts = [];
|
|
for(var key in obj)
|
|
opts.push(key);
|
|
return "{String} can take one of next values: "+opts.join(", ");
|
|
};
|
|
return t;
|
|
};
|
|
|
|
dhtmlx.assert_rule_dimension=function(check){
|
|
if (check*1 == check && !isNaN(check) && check >= 0) return true;
|
|
return false;
|
|
};
|
|
dhtmlx.assert_rule_dimension.describe=function(){
|
|
return "{Integer} value must be a positive number";
|
|
};
|
|
|
|
dhtmlx.assert_rule_number=function(check){
|
|
if (typeof check == "number") return true;
|
|
return false;
|
|
};
|
|
dhtmlx.assert_rule_number.describe=function(){
|
|
return "{Integer} value must be a number";
|
|
};
|
|
|
|
dhtmlx.assert_rule_function=function(check){
|
|
if (typeof check == "function") return true;
|
|
return false;
|
|
};
|
|
dhtmlx.assert_rule_function.describe=function(){
|
|
return "{Function} value must be a custom function";
|
|
};
|
|
|
|
dhtmlx.assert_rule_any=function(check){
|
|
return true;
|
|
};
|
|
dhtmlx.assert_rule_any.describe=function(){
|
|
return "Any value";
|
|
};
|
|
|
|
dhtmlx.assert_rule_mix=function(a,b){
|
|
var t = function(check){
|
|
if (a(check)||b(check)) return true;
|
|
return false;
|
|
};
|
|
t.describe = function(){
|
|
return a.describe();
|
|
};
|
|
return t;
|
|
};
|
|
|
|
}
|
|
|
|
|
|
/* DHX DEPEND FROM FILE 'dhtmlx.js'*/
|
|
|
|
|
|
/*DHX:Depend assert.js*/
|
|
|
|
/*
|
|
Common helpers
|
|
*/
|
|
dhtmlx.codebase="./";
|
|
|
|
//coding helpers
|
|
|
|
dhtmlx.copy = function(source){
|
|
var f = dhtmlx.copy._function;
|
|
f.prototype = source;
|
|
return new f();
|
|
};
|
|
dhtmlx.copy._function = function(){};
|
|
|
|
//copies methods and properties from source to the target
|
|
dhtmlx.extend = function(target, source){
|
|
for (var method in source)
|
|
target[method] = source[method];
|
|
|
|
//applying asserts
|
|
if (dhtmlx.assert_enabled() && source._assert){
|
|
target._assert();
|
|
target._assert=null;
|
|
}
|
|
|
|
dhtmlx.assert(target,"Invalid nesting target");
|
|
dhtmlx.assert(source,"Invalid nesting source");
|
|
//if source object has init code - call init against target
|
|
if (source._init)
|
|
target._init();
|
|
|
|
return target;
|
|
};
|
|
dhtmlx.proto_extend = function(){
|
|
var origins = arguments;
|
|
var compilation = origins[0];
|
|
var construct = [];
|
|
|
|
for (var i=origins.length-1; i>0; i--) {
|
|
if (typeof origins[i]== "function")
|
|
origins[i]=origins[i].prototype;
|
|
for (var key in origins[i]){
|
|
if (key == "_init")
|
|
construct.push(origins[i][key]);
|
|
else if (!compilation[key])
|
|
compilation[key] = origins[i][key];
|
|
}
|
|
};
|
|
|
|
if (origins[0]._init)
|
|
construct.push(origins[0]._init);
|
|
|
|
compilation._init = function(){
|
|
for (var i=0; i<construct.length; i++)
|
|
construct[i].apply(this, arguments);
|
|
};
|
|
compilation.base = origins[1];
|
|
var result = function(config){
|
|
this._init(config);
|
|
if (this._parseSettings)
|
|
this._parseSettings(config, this.defaults);
|
|
};
|
|
result.prototype = compilation;
|
|
|
|
compilation = origins = null;
|
|
return result;
|
|
};
|
|
//creates function with specified "this" pointer
|
|
dhtmlx.bind=function(functor, object){
|
|
return function(){ return functor.apply(object,arguments); };
|
|
};
|
|
|
|
//loads module from external js file
|
|
dhtmlx.require=function(module){
|
|
if (!dhtmlx._modules[module]){
|
|
dhtmlx.assert(dhtmlx.ajax,"load module is required");
|
|
|
|
//load and exec the required module
|
|
dhtmlx.exec( dhtmlx.ajax().sync().get(dhtmlx.codebase+module).responseText );
|
|
dhtmlx._modules[module]=true;
|
|
}
|
|
};
|
|
dhtmlx._modules = {}; //hash of already loaded modules
|
|
|
|
//evaluate javascript code in the global scoope
|
|
dhtmlx.exec=function(code){
|
|
if (window.execScript) //special handling for IE
|
|
window.execScript(code);
|
|
else window.eval(code);
|
|
};
|
|
|
|
/*
|
|
creates method in the target object which will transfer call to the source object
|
|
if event parameter was provided , each call of method will generate onBefore and onAfter events
|
|
*/
|
|
dhtmlx.methodPush=function(object,method,event){
|
|
return function(){
|
|
var res = false;
|
|
//if (!event || this.callEvent("onBefore"+event,arguments)){ //not used anymore, probably can be removed
|
|
res=object[method].apply(object,arguments);
|
|
// if (event) this.callEvent("onAfter"+event,arguments);
|
|
//}
|
|
return res; //result of wrapped method
|
|
};
|
|
};
|
|
//check === undefined
|
|
dhtmlx.isNotDefined=function(a){
|
|
return typeof a == "undefined";
|
|
};
|
|
//delay call to after-render time
|
|
dhtmlx.delay=function(method, obj, params, delay){
|
|
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)
|
|
dhtmlx.uid = function(){
|
|
if (!this._seed) this._seed=(new Date).valueOf(); //init seed with timestemp
|
|
this._seed++;
|
|
return this._seed;
|
|
};
|
|
//resolve ID as html object
|
|
dhtmlx.toNode = function(node){
|
|
if (typeof node == "string") return document.getElementById(node);
|
|
return node;
|
|
};
|
|
//adds extra methods for the array
|
|
dhtmlx.toArray = function(array){
|
|
return dhtmlx.extend((array||[]),dhtmlx.PowerArray);
|
|
};
|
|
//resolve function name
|
|
dhtmlx.toFunctor=function(str){
|
|
return (typeof(str)=="string") ? eval(str) : str;
|
|
};
|
|
|
|
//dom helpers
|
|
|
|
//hash of attached events
|
|
dhtmlx._events = {};
|
|
//attach event to the DOM element
|
|
dhtmlx.event=function(node,event,handler,master){
|
|
node = dhtmlx.toNode(node);
|
|
|
|
var id = dhtmlx.uid();
|
|
dhtmlx._events[id]=[node,event,handler]; //store event info, for detaching
|
|
|
|
if (master)
|
|
handler=dhtmlx.bind(handler,master);
|
|
|
|
//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
|
|
dhtmlx.eventRemove=function(id){
|
|
|
|
if (!id) return;
|
|
dhtmlx.assert(this._events[id],"Removing non-existing event");
|
|
|
|
var ev = dhtmlx._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
|
|
dhtmlx.log = function(type,message,details){
|
|
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);
|
|
}
|
|
};
|
|
//register rendering time from call point
|
|
dhtmlx.log_full_time = function(name){
|
|
dhtmlx._start_time_log = new Date();
|
|
dhtmlx.log("Info","Timing start ["+name+"]");
|
|
window.setTimeout(function(){
|
|
var time = new Date();
|
|
dhtmlx.log("Info","Timing end ["+name+"]:"+(time.valueOf()-dhtmlx._start_time_log.valueOf())/1000+"s");
|
|
},1);
|
|
};
|
|
//register execution time from call point
|
|
dhtmlx.log_time = function(name){
|
|
var fname = "_start_time_log"+name;
|
|
if (!dhtmlx[fname]){
|
|
dhtmlx[fname] = new Date();
|
|
dhtmlx.log("Info","Timing start ["+name+"]");
|
|
} else {
|
|
var time = new Date();
|
|
dhtmlx.log("Info","Timing end ["+name+"]:"+(time.valueOf()-dhtmlx[fname].valueOf())/1000+"s");
|
|
dhtmlx[fname] = null;
|
|
}
|
|
};
|
|
//log message with type=error
|
|
dhtmlx.error = function(message,details){
|
|
dhtmlx.log("error",message,details);
|
|
};
|
|
//event system
|
|
dhtmlx.EventSystem={
|
|
_init:function(){
|
|
this._events = {}; //hash of event handlers, name => handler
|
|
this._handlers = {}; //hash of event handlers, ID => handler
|
|
this._map = {};
|
|
},
|
|
//temporary block event triggering
|
|
block : function(){
|
|
this._events._block = true;
|
|
},
|
|
//re-enable event triggering
|
|
unblock : function(){
|
|
this._events._block = false;
|
|
},
|
|
mapEvent:function(map){
|
|
dhtmlx.extend(this._map, map);
|
|
},
|
|
//trigger event
|
|
callEvent:function(type,params){
|
|
if (this._events._block) return true;
|
|
|
|
type = type.toLowerCase();
|
|
dhtmlx.assert_event_call(this, type, params);
|
|
|
|
var event_stack =this._events[type.toLowerCase()]; //all events for provided name
|
|
var return_value = true;
|
|
|
|
if (dhtmlx.debug) //can slowdown a lot
|
|
dhtmlx.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._map[type] && !this._map[type].callEvent(type,params))
|
|
return_value = false;
|
|
|
|
return return_value;
|
|
},
|
|
//assign handler for some named event
|
|
attachEvent:function(type,functor,id){
|
|
type=type.toLowerCase();
|
|
dhtmlx.assert_event_attach(this, type);
|
|
|
|
id=id||dhtmlx.uid(); //ID can be used for detachEvent
|
|
functor = dhtmlx.toFunctor(functor); //functor can be a name of method
|
|
|
|
var event_stack=this._events[type]||dhtmlx.toArray();
|
|
//save new event handler
|
|
event_stack.push(functor);
|
|
this._events[type]=event_stack;
|
|
this._handlers[id]={ f:functor,t:type };
|
|
|
|
return id;
|
|
},
|
|
//remove event handler
|
|
detachEvent:function(id){
|
|
if(this._handlers[id]){
|
|
var type=this._handlers[id].t;
|
|
var functor=this._handlers[id].f;
|
|
|
|
//remove from all collections
|
|
var event_stack=this._events[type];
|
|
event_stack.remove(functor);
|
|
delete this._handlers[id];
|
|
}
|
|
}
|
|
};
|
|
|
|
//array helper
|
|
//can be used by dhtmlx.toArray()
|
|
dhtmlx.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;
|
|
}
|
|
};
|
|
|
|
dhtmlx.env = {};
|
|
|
|
//environment detection
|
|
if (navigator.userAgent.indexOf('Opera') != -1)
|
|
dhtmlx._isOpera=true;
|
|
else{
|
|
//very rough detection, but it is enough for current goals
|
|
dhtmlx._isIE=!!document.all;
|
|
dhtmlx._isFF=!document.all;
|
|
dhtmlx._isWebKit=(navigator.userAgent.indexOf("KHTML")!=-1);
|
|
if (navigator.appVersion.indexOf("MSIE 8.0")!= -1 && document.compatMode != "BackCompat")
|
|
dhtmlx._isIE=8;
|
|
if (navigator.appVersion.indexOf("MSIE 9.0")!= -1 && document.compatMode != "BackCompat")
|
|
dhtmlx._isIE=9;
|
|
}
|
|
|
|
dhtmlx.env = {};
|
|
|
|
// dhtmlx.env.transform
|
|
// dhtmlx.env.transition
|
|
(function(){
|
|
dhtmlx.env.transform = false;
|
|
dhtmlx.env.transition = false;
|
|
var options = {};
|
|
options.names = ['transform', 'transition'];
|
|
options.transform = ['transform', 'WebkitTransform', 'MozTransform', 'oTransform','msTransform'];
|
|
options.transition = ['transition', 'WebkitTransition', 'MozTransition', 'oTransition'];
|
|
|
|
var d = document.createElement("DIV");
|
|
var property;
|
|
for(var i=0; i<options.names.length; i++) {
|
|
while (p = options[options.names[i]].pop()) {
|
|
if(typeof d.style[p] != 'undefined')
|
|
dhtmlx.env[options.names[i]] = true;
|
|
}
|
|
}
|
|
})();
|
|
dhtmlx.env.transform_prefix = (function(){
|
|
var prefix;
|
|
if(dhtmlx._isOpera)
|
|
prefix = '-o-';
|
|
else {
|
|
prefix = ''; // default option
|
|
if(dhtmlx._isFF)
|
|
prefix = '-moz-';
|
|
if(dhtmlx._isWebKit)
|
|
prefix = '-webkit-';
|
|
}
|
|
return prefix;
|
|
})();
|
|
dhtmlx.env.svg = (function(){
|
|
return document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1");
|
|
})();
|
|
|
|
//store maximum used z-index
|
|
dhtmlx.zIndex={ drag : 10000 };
|
|
|
|
//html helpers
|
|
dhtmlx.html={
|
|
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 = dhtmlx.toNode(node);
|
|
if (!node) return "";
|
|
return dhtmlx.isNotDefined(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.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){
|
|
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 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 = ((dhtmlx._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();
|
|
dhtmlx.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"),"");
|
|
}
|
|
};
|
|
|
|
//autodetect codebase folder
|
|
(function(){
|
|
var temp = document.getElementsByTagName("SCRIPT"); //current script, most probably
|
|
dhtmlx.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);
|
|
dhtmlx.codebase = temp.slice(0, temp.length).join("/")+"/";
|
|
}
|
|
})();
|
|
|
|
if (!dhtmlx.ui)
|
|
dhtmlx.ui={};
|
|
|
|
|
|
/* DHX DEPEND FROM FILE 'destructor.js'*/
|
|
|
|
|
|
/*
|
|
Behavior:Destruction
|
|
|
|
@export
|
|
destructor
|
|
*/
|
|
|
|
/*DHX:Depend dhtmlx.js*/
|
|
|
|
dhtmlx.Destruction = {
|
|
_init:function(){
|
|
//register self in global list of destructors
|
|
dhtmlx.destructors.push(this);
|
|
},
|
|
//will be called automatically on unload, can be called manually
|
|
//simplifies job of GC
|
|
destructor:function(mode){
|
|
this.destructor=function(){}; //destructor can be called only once
|
|
|
|
//html collection
|
|
this._htmlmap = null;
|
|
this._htmlrows = null;
|
|
|
|
//temp html element, used by toHTML
|
|
if (this._html)
|
|
document.body.appendChild(this._html); //need to attach, for IE's GC
|
|
|
|
this._html = null;
|
|
if (this._obj) {
|
|
this._obj.innerHTML="";
|
|
this._obj._htmlmap = null;
|
|
}
|
|
this._obj = this._dataobj = null;
|
|
this.data = null;
|
|
this._events = this._handlers = {};
|
|
this.canvases = [];
|
|
if(this.render)
|
|
this.render = function(){};//need in case of delayed method calls (virtual render case)
|
|
|
|
// not effective, need to remove all event listeners as well
|
|
//
|
|
// if (mode != -1)
|
|
// for (var i=0; i<dhtmlx.destructors.length; i++)
|
|
// if (dhtmlx.destructors[i] == this){
|
|
// dhtmlx.destructors.splice(i,1);
|
|
// break;
|
|
// }
|
|
}
|
|
};
|
|
//global list of destructors
|
|
dhtmlx.destructors = [];
|
|
dhtmlx.event(window,"unload",function(){
|
|
//call all registered destructors
|
|
if (dhtmlx.destructors){
|
|
for (var i=0; i<dhtmlx.destructors.length; i++)
|
|
dhtmlx.destructors[i].destructor(-1);
|
|
dhtmlx.destructors = [];
|
|
}
|
|
|
|
//detach all known DOM events
|
|
for (var a in dhtmlx._events){
|
|
var ev = dhtmlx._events[a];
|
|
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 dhtmlx._events[a];
|
|
}
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
/* DHX DEPEND FROM FILE 'load.js'*/
|
|
|
|
|
|
/*
|
|
ajax operations
|
|
|
|
can be used for direct loading as
|
|
dhtmlx.ajax(ulr, callback)
|
|
or
|
|
dhtmlx.ajax().item(url)
|
|
dhtmlx.ajax().post(url)
|
|
|
|
*/
|
|
|
|
/*DHX:Depend dhtmlx.js*/
|
|
|
|
dhtmlx.ajax = function(url,call,master){
|
|
//if parameters was provided - made fast call
|
|
if (arguments.length!==0){
|
|
var http_request = new dhtmlx.ajax();
|
|
if (master) http_request.master=master;
|
|
http_request.get(url,null,call);
|
|
}
|
|
if (!this.getXHR) return new dhtmlx.ajax(); //allow to create new instance without direct new declaration
|
|
|
|
return this;
|
|
};
|
|
dhtmlx.ajax.prototype={
|
|
//creates xmlHTTP object
|
|
getXHR:function(){
|
|
if (dhtmlx._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 (typeof call == "function")
|
|
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 === dhtmlx.undefined)
|
|
value = "";
|
|
t.push(a+"="+encodeURIComponent(value));// utf-8 escaping
|
|
}
|
|
params=t.join("&");
|
|
}
|
|
if (params && !this.post){
|
|
url=url+(url.indexOf("?")!=-1 ? "&" : "?")+params;
|
|
params=null;
|
|
}
|
|
|
|
x.open(this.post?"POST":"GET",url,!this._sync);
|
|
if (this.post)
|
|
x.setRequestHeader('Content-type','application/x-www-form-urlencoded');
|
|
|
|
//async mode, define loading callback
|
|
//if (!this._sync){
|
|
var self=this;
|
|
x.onreadystatechange= function(){
|
|
if (!x.readyState || x.readyState == 4){
|
|
//dhtmlx.log_full_time("data_loading"); //log rendering time
|
|
if (call && self)
|
|
for (var i=0; i < call.length; i++) //there can be multiple callbacks
|
|
if (call[i])
|
|
call[i].call((self.master||self),x.responseText,x.responseXML,x);
|
|
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){
|
|
this.post=false;
|
|
return this.send(url,params,call);
|
|
},
|
|
//POST request
|
|
post:function(url,params,call){
|
|
this.post=true;
|
|
return this.send(url,params,call);
|
|
},
|
|
sync:function(){
|
|
this._sync = true;
|
|
return this;
|
|
}
|
|
};
|
|
|
|
|
|
dhtmlx.AtomDataLoader={
|
|
_init:function(config){
|
|
//prepare data store
|
|
this.data = {};
|
|
if (config){
|
|
this._settings.datatype = config.datatype||"json";
|
|
this._after_init.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;
|
|
},
|
|
//loads data from external URL
|
|
load:function(url,call){
|
|
this.callEvent("onXLS",[]);
|
|
if (typeof call == "string"){ //second parameter can be a loading type or callback
|
|
this.data.driver = dhtmlx.DataDriver[call];
|
|
call = arguments[2];
|
|
}
|
|
else
|
|
this.data.driver = dhtmlx.DataDriver[this._settings.datatype||"xml"];
|
|
//load data by async ajax call
|
|
if (window.dhx4){
|
|
return dhx4.ajax.get(url,dhtmlx.bind(function(x){
|
|
var loader = x.xmlDoc;
|
|
var text = loader.responseText;
|
|
var xml = loader.responseXML;
|
|
|
|
if (this._onLoad)
|
|
this._onLoad.call(this, text, xml, loader);
|
|
if (call)
|
|
call.call(this, text, xml, loader);
|
|
},this));
|
|
} else {
|
|
dhtmlx.ajax(url,[this._onLoad,call],this);
|
|
}
|
|
},
|
|
//loads data from object
|
|
parse:function(data,type){
|
|
this.callEvent("onXLS",[]);
|
|
this.data.driver = dhtmlx.DataDriver[type||"xml"];
|
|
this._onLoad(data,null);
|
|
},
|
|
//default after loading callback
|
|
_onLoad:function(text,xml,loader){
|
|
var driver = this.data.driver;
|
|
var top = driver.getRecords(driver.toObject(text,xml))[0];
|
|
this.data=(driver?driver.getDetails(top):text);
|
|
this.callEvent("onXLE",[]);
|
|
},
|
|
_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",[]);
|
|
dhtmlx.ajax(url, function(text,xml){
|
|
this._ignore_feed=true;
|
|
this.setValues(dhtmlx.DataDriver.json.toObject(text)[0]);
|
|
this._ignore_feed=false;
|
|
this.callEvent("onXLE",[]);
|
|
}, this);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
/*
|
|
Abstraction layer for different data types
|
|
*/
|
|
|
|
dhtmlx.DataDriver={};
|
|
dhtmlx.DataDriver.json={
|
|
//convert json string to json object if necessary
|
|
toObject:function(data){
|
|
if (!data) data="[]";
|
|
if (typeof data == "string"){
|
|
eval ("dhtmlx.temp="+data);
|
|
return dhtmlx.temp;
|
|
}
|
|
return data;
|
|
},
|
|
//get array of records
|
|
getRecords:function(data){
|
|
if (data && data.data)
|
|
data = data.data;
|
|
if (data && !(data instanceof Array))
|
|
return [data];
|
|
return data;
|
|
},
|
|
//get hash of properties for single record
|
|
getDetails:function(data){
|
|
return data;
|
|
},
|
|
//get count of data and position at which new data need to be inserted
|
|
getInfo:function(data){
|
|
return {
|
|
_size:(data.total_count||0),
|
|
_from:(data.pos||0),
|
|
_key:(data.dhx_security)
|
|
};
|
|
}
|
|
};
|
|
|
|
dhtmlx.DataDriver.json_ext={
|
|
//convert json string to json object if necessary
|
|
toObject:function(data){
|
|
if (!data) data="[]";
|
|
if (typeof data == "string"){
|
|
var temp;
|
|
eval ("temp="+data);
|
|
dhtmlx.temp = [];
|
|
var header = temp.header;
|
|
for (var i = 0; i < temp.data.length; i++) {
|
|
var item = {};
|
|
for (var j = 0; j < header.length; j++) {
|
|
if (typeof(temp.data[i][j]) != "undefined")
|
|
item[header[j]] = temp.data[i][j];
|
|
}
|
|
dhtmlx.temp.push(item);
|
|
}
|
|
return dhtmlx.temp;
|
|
}
|
|
return data;
|
|
},
|
|
//get array of records
|
|
getRecords:function(data){
|
|
if (data && !(data instanceof Array))
|
|
return [data];
|
|
return data;
|
|
},
|
|
//get hash of properties for single record
|
|
getDetails:function(data){
|
|
return data;
|
|
},
|
|
//get count of data and position at which new data need to be inserted
|
|
getInfo:function(data){
|
|
return {
|
|
_size:(data.total_count||0),
|
|
_from:(data.pos||0)
|
|
};
|
|
}
|
|
};
|
|
|
|
dhtmlx.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 = dhtmlx.toNode(data);
|
|
if (!t){
|
|
t=document.createElement("DIV");
|
|
t.innerHTML = data;
|
|
}
|
|
|
|
return t.getElementsByTagName(this.tag);
|
|
}
|
|
return data;
|
|
},
|
|
//get array of records
|
|
getRecords:function(data){
|
|
if (data.tagName)
|
|
return data.childNodes;
|
|
return data;
|
|
},
|
|
//get hash of properties for single record
|
|
getDetails:function(data){
|
|
return dhtmlx.DataDriver.xml.tagToObject(data);
|
|
},
|
|
//dyn loading is not supported by HTML data source
|
|
getInfo:function(data){
|
|
return {
|
|
_size:0,
|
|
_from:0
|
|
};
|
|
},
|
|
tag: "LI"
|
|
};
|
|
|
|
dhtmlx.DataDriver.jsarray={
|
|
//eval jsarray string to jsarray object if necessary
|
|
toObject:function(data){
|
|
if (typeof data == "string"){
|
|
eval ("dhtmlx.temp="+data);
|
|
return dhtmlx.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
|
|
};
|
|
}
|
|
};
|
|
|
|
dhtmlx.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
|
|
};
|
|
|
|
dhtmlx.DataDriver.xml={
|
|
//convert xml string to xml object if necessary
|
|
toObject:function(text,xml){
|
|
if (xml && (xml=this.checkResponse(text,xml))) //checkResponse - fix incorrect content type and extra whitespaces errors
|
|
return xml;
|
|
if (typeof text == "string"){
|
|
return this.fromString(text);
|
|
}
|
|
return text;
|
|
},
|
|
//get array of records
|
|
getRecords:function(data){
|
|
return this.xpath(data,this.records);
|
|
},
|
|
records:"/*/item",
|
|
//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){
|
|
return {
|
|
_size:(data.documentElement.getAttribute("total_count")||0),
|
|
_from:(data.documentElement.getAttribute("pos")||0),
|
|
_key:(data.documentElement.getAttribute("dhx_security"))
|
|
};
|
|
},
|
|
//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);
|
|
}
|
|
}
|
|
},
|
|
//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 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 (!(z[name] instanceof Array))
|
|
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;
|
|
}
|
|
}
|
|
|
|
//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;
|
|
}
|
|
|
|
if (!flag)
|
|
return this.nodeValue(tag);
|
|
//each object will have its text content as "value" property
|
|
z.value = this.nodeValue(tag);
|
|
return z;
|
|
},
|
|
//get value of xml node
|
|
nodeValue:function(node){
|
|
if (node.firstChild)
|
|
return node.firstChild.wholeText||node.firstChild.data;
|
|
return "";
|
|
},
|
|
//convert XML string to XML object
|
|
fromString:function(xmlString){
|
|
if (window.DOMParser && !dhtmlx._isIE) // 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;
|
|
}
|
|
dhtmlx.error("Load from xml string is not supported");
|
|
},
|
|
//check is XML correct and try to reparse it if its invalid
|
|
checkResponse:function(text,xml){
|
|
if (xml && ( xml.firstChild && xml.firstChild.tagName != "parsererror") )
|
|
return xml;
|
|
//parsing as string resolves incorrect content type
|
|
//regexp removes whitespaces before xml declaration, which is vital for FF
|
|
var a=this.fromString(text.replace(/^[\s]+/,""));
|
|
if (a) return a;
|
|
|
|
dhtmlx.error("xml can't be parsed",text);
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
/* DHX DEPEND FROM FILE 'datastore.js'*/
|
|
|
|
|
|
/*DHX:Depend load.js*/
|
|
/*DHX:Depend dhtmlx.js*/
|
|
|
|
/*
|
|
Behavior:DataLoader - load data in the component
|
|
|
|
@export
|
|
load
|
|
parse
|
|
*/
|
|
dhtmlx.DataLoader={
|
|
_init:function(config){
|
|
//prepare data store
|
|
config = config || "";
|
|
this.name = "DataStore";
|
|
this.data = (config.datastore)||(new dhtmlx.DataStore());
|
|
this._readyHandler = this.data.attachEvent("onStoreLoad",dhtmlx.bind(this._call_onready,this));
|
|
},
|
|
//loads data from external URL
|
|
load:function(url,call){
|
|
dhtmlx.AtomDataLoader.load.apply(this, arguments);
|
|
//prepare data feed for dyn. loading
|
|
if (!this.data.feed)
|
|
this.data.feed = function(from,count){
|
|
//allow only single request at same time
|
|
if (this._load_count)
|
|
return this._load_count=[from,count]; //save last ignored request
|
|
else
|
|
this._load_count=true;
|
|
|
|
this.load(url+((url.indexOf("?")==-1)?"?":"&")+"posStart="+from+"&count="+count,function(){
|
|
//after loading check if we have some ignored requests
|
|
var temp = this._load_count;
|
|
this._load_count = false;
|
|
if (typeof temp =="object")
|
|
this.data.feed.apply(this, temp); //load last ignored request
|
|
});
|
|
};
|
|
},
|
|
//default after loading callback
|
|
_onLoad:function(text,xml,loader){
|
|
this.data._parse(this.data.driver.toObject(text,xml));
|
|
this.callEvent("onXLE",[]);
|
|
if(this._readyHandler){
|
|
this.data.detachEvent(this._readyHandler);
|
|
this._readyHandler = null;
|
|
}
|
|
},
|
|
dataFeed_setter:function(value){
|
|
this.data.attachEvent("onBeforeFilter", dhtmlx.bind(function(text, value){
|
|
if (this._settings.dataFeed){
|
|
var filter = {};
|
|
if (!text && !filter) return;
|
|
if (typeof text == "function"){
|
|
if (!value) return;
|
|
text(value, filter);
|
|
} else
|
|
filter = { text:value };
|
|
|
|
this.clearAll();
|
|
var url = this._settings.dataFeed;
|
|
if (typeof url == "function")
|
|
return url.call(this, value, filter);
|
|
var urldata = [];
|
|
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;
|
|
},
|
|
_call_onready:function(){
|
|
if (this._settings.ready){
|
|
var code = dhtmlx.toFunctor(this._settings.ready);
|
|
if (code && code.call) code.apply(this, arguments);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/*
|
|
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
|
|
*/
|
|
dhtmlx.DataStore = function(){
|
|
this.name = "DataStore";
|
|
|
|
dhtmlx.extend(this, dhtmlx.EventSystem);
|
|
|
|
this.setDriver("xml"); //default data source is an XML
|
|
this.pull = {}; //hash of IDs
|
|
this.order = dhtmlx.toArray(); //order of IDs
|
|
};
|
|
|
|
dhtmlx.DataStore.prototype={
|
|
//defines type of used data driver
|
|
//data driver is an abstraction other different data formats - xml, json, csv, etc.
|
|
setDriver:function(type){
|
|
dhtmlx.assert(dhtmlx.DataDriver[type],"incorrect DataDriver");
|
|
this.driver = dhtmlx.DataDriver[type];
|
|
},
|
|
//process incoming raw data
|
|
_parse:function(data){
|
|
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)
|
|
dhtmlx.security_key = info._key;
|
|
//get array of records
|
|
|
|
var recs = this.driver.getRecords(data);
|
|
var from = (info._from||0)*1;
|
|
|
|
if (from === 0 && this.order[0]) //update mode
|
|
from = this.order.length;
|
|
|
|
var j=0;
|
|
for (var i=0; i<recs.length; i++){
|
|
//get has 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++;
|
|
}
|
|
this.pull[id]=temp;
|
|
//if (this._format) this._format(temp);
|
|
|
|
if (this.extraParser)
|
|
this.extraParser(temp);
|
|
if (this._scheme){
|
|
if (this._scheme.$init)
|
|
this._scheme.$update(temp);
|
|
else if (this._scheme.$update)
|
|
this._scheme.$update(temp);
|
|
}
|
|
}
|
|
|
|
//for all not loaded data
|
|
for (var i=0; i < info._size; i++)
|
|
if (!this.order[i]){
|
|
var id = dhtmlx.uid();
|
|
var temp = {id:id, $template:"loading"}; //create fake records
|
|
this.pull[id]=temp;
|
|
this.order[i]=id;
|
|
}
|
|
|
|
this.callEvent("onStoreLoad",[this.driver, data]);
|
|
//repaint self after data loading
|
|
this.refresh();
|
|
},
|
|
//generate id for data object
|
|
id:function(data){
|
|
return data.id||(data.id=dhtmlx.uid());
|
|
},
|
|
changeId:function(old, newid){
|
|
dhtmlx.assert(this.pull[old],"Can't change id, for non existing item: "+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;
|
|
this.callEvent("onIdChange", [old, newid]);
|
|
if (this._render_change_id)
|
|
this._render_change_id(old, newid);
|
|
},
|
|
get:function(id){
|
|
return this.item(id);
|
|
},
|
|
set:function(id, data){
|
|
return this.update(id, data);
|
|
},
|
|
//get data from hash by id
|
|
item:function(id){
|
|
return this.pull[id];
|
|
},
|
|
//assigns data by id
|
|
update:function(id,data){
|
|
if (this._scheme && this._scheme.$update)
|
|
this._scheme.$update(data);
|
|
if (this.callEvent("onBeforeUpdate", [id, data]) === false) return false;
|
|
this.pull[id]=data;
|
|
this.refresh(id);
|
|
},
|
|
//sends repainting signal
|
|
refresh:function(id){
|
|
if (this._skip_refresh) return;
|
|
|
|
if (id)
|
|
this.callEvent("onStoreUpdated",[id, this.pull[id], "update"]);
|
|
else
|
|
this.callEvent("onStoreUpdated",[null,null,null]);
|
|
},
|
|
silent:function(code){
|
|
this._skip_refresh = true;
|
|
code.call(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.startOffset||0;
|
|
if (to)
|
|
to = this.indexById(to);
|
|
else {
|
|
to = Math.min((this.endOffset||Infinity),(this.dataCount()-1));
|
|
if (to<0) to = 0; //we have not data in the store
|
|
}
|
|
|
|
if (this.min)
|
|
from = this.min;
|
|
if (this.max)
|
|
to = this.max;
|
|
|
|
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=dhtmlx.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){
|
|
if (sindex<0 || tindex<0){
|
|
dhtmlx.error("DataStore::move","Incorrect indexes");
|
|
return;
|
|
}
|
|
|
|
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){
|
|
/*
|
|
some.scheme({
|
|
order:1,
|
|
name:"dummy",
|
|
title:""
|
|
})
|
|
*/
|
|
this._scheme = config;
|
|
|
|
},
|
|
sync:function(source, filter, silent){
|
|
if (typeof filter != "function"){
|
|
silent = filter;
|
|
filter = null;
|
|
}
|
|
|
|
if (dhtmlx.debug_bind){
|
|
this.debug_sync_master = source;
|
|
dhtmlx.log("[sync] "+this.debug_bind_master.name+"@"+this.debug_bind_master._settings.id+" <= "+this.debug_sync_master.name+"@"+this.debug_sync_master._settings.id);
|
|
}
|
|
|
|
var topsource = source;
|
|
if (source.name != "DataStore")
|
|
source = source.data;
|
|
|
|
var sync_logic = dhtmlx.bind(function(id, data, mode){
|
|
if (mode != "update" || filter)
|
|
id = null;
|
|
|
|
if (!id){
|
|
this.order = dhtmlx.toArray([].concat(source.order));
|
|
this._filter_order = null;
|
|
this.pull = source.pull;
|
|
|
|
if (filter)
|
|
this.silent(filter);
|
|
|
|
if (this._on_sync)
|
|
this._on_sync();
|
|
}
|
|
|
|
if (dhtmlx.debug_bind)
|
|
dhtmlx.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);
|
|
if (!silent)
|
|
this.refresh(id);
|
|
else
|
|
silent = false;
|
|
}, this);
|
|
|
|
source.attachEvent("onStoreUpdated", sync_logic);
|
|
this.feed = function(from, count){
|
|
topsource.loadNext(count, from);
|
|
};
|
|
sync_logic();
|
|
},
|
|
//adds item to the store
|
|
add:function(obj,index){
|
|
|
|
if (this._scheme){
|
|
obj = obj||{};
|
|
for (var key in this._scheme)
|
|
obj[key] = obj[key]||this._scheme[key];
|
|
if (this._scheme){
|
|
if (this._scheme.$init)
|
|
this._scheme.$update(obj);
|
|
else if (this._scheme.$update)
|
|
this._scheme.$update(obj);
|
|
}
|
|
}
|
|
|
|
//generate id for the item
|
|
var id = this.id(obj);
|
|
|
|
//by default item is added to the end of the list
|
|
var data_size = this.dataCount();
|
|
|
|
if (dhtmlx.isNotDefined(index) || index < 0)
|
|
index = data_size;
|
|
//check to prevent too big indexes
|
|
if (index > data_size){
|
|
dhtmlx.log("Warning","DataStore:add","Index of out of bounds");
|
|
index = Math.min(this.order.length,index);
|
|
}
|
|
if (this.callEvent("onBeforeAdd", [id, obj, index]) === false) return false;
|
|
|
|
if (this.exists(id)) return dhtmlx.error("Not unique ID");
|
|
|
|
this.pull[id]=obj;
|
|
this.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 (id instanceof Array){
|
|
for (var i=0; i < id.length; i++)
|
|
this.remove(id[i]);
|
|
return;
|
|
}
|
|
if (this.callEvent("onBeforeDelete",[id]) === false) return false;
|
|
if (!this.exists(id)) return dhtmlx.error("Not existing ID",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];
|
|
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 = dhtmlx.toArray();
|
|
this.feed = null;
|
|
this._filter_order = null;
|
|
this.callEvent("onClearAll",[]);
|
|
this.refresh();
|
|
},
|
|
//converts id to index
|
|
idByIndex:function(index){
|
|
if (index>=this.order.length || index<0)
|
|
dhtmlx.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])
|
|
// dhtmlx.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, dir:dir, as:as};
|
|
|
|
|
|
var parameters = [sort.by, sort.dir, sort.as];
|
|
if (!this.callEvent("onbeforesort",parameters)) return;
|
|
|
|
if (this.order.length){
|
|
var sorter = dhtmlx.sort.create(sort);
|
|
//get array of IDs
|
|
var neworder = this.getRange(this.first(), this.last());
|
|
neworder.sort(sorter);
|
|
this.order = neworder.map(function(obj){ return this.id(obj); },this);
|
|
}
|
|
|
|
//repaint self
|
|
this.refresh();
|
|
|
|
this.callEvent("onaftersort",parameters);
|
|
},
|
|
/*
|
|
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:function(text,value){
|
|
if (!this.callEvent("onBeforeFilter", [text, value])) return;
|
|
|
|
//remove previous filtering , if any
|
|
if (this._filter_order){
|
|
this.order = this._filter_order;
|
|
delete this._filter_order;
|
|
}
|
|
|
|
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 = dhtmlx.Template.fromHTML(text);
|
|
value = value.toString().toLowerCase();
|
|
filter = function(obj,value){ //default filter - string start from, case in-sensitive
|
|
return text(obj).toLowerCase().indexOf(value)!=-1;
|
|
};
|
|
}
|
|
|
|
|
|
var neworder = dhtmlx.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
|
|
this._filter_order = this.order;
|
|
this.order = neworder;
|
|
}
|
|
//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]));
|
|
},
|
|
/*
|
|
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 = ["get","set","sort","add","remove","exists","idByIndex","indexById","item","update","refresh","dataCount","filter","next","previous","clearAll","first","last","serialize"];
|
|
for (var i=0; i < list.length; i++)
|
|
target[list[i]]=dhtmlx.methodPush(this,list[i]);
|
|
|
|
if (dhtmlx.assert_enabled())
|
|
this.assert_event(target);
|
|
},
|
|
/*
|
|
serializes data to a json object
|
|
*/
|
|
serialize: function(){
|
|
var ids = this.order;
|
|
var result = [];
|
|
for(var i=0; i< ids.length;i++)
|
|
result.push(this.pull[ids[i]]);
|
|
return result;
|
|
}
|
|
};
|
|
|
|
dhtmlx.sort = {
|
|
create:function(config){
|
|
return dhtmlx.sort.dir(config.dir, dhtmlx.sort.by(config.by, config.as));
|
|
},
|
|
as:{
|
|
"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){
|
|
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 = dhtmlx.sort.as[method||"string"];
|
|
prop = dhtmlx.Template.fromHTML(prop);
|
|
return function(a,b){
|
|
return method(prop(a),prop(b));
|
|
};
|
|
},
|
|
dir:function(prop, method){
|
|
if (prop == "asc")
|
|
return method;
|
|
return function(a,b){
|
|
return method(a,b)*-1;
|
|
};
|
|
}
|
|
};
|
|
|
|
|
|
|
|
/* DHX DEPEND FROM FILE 'key.js'*/
|
|
|
|
|
|
/*
|
|
Behavior:KeyEvents - hears keyboard
|
|
*/
|
|
dhtmlx.KeyEvents = {
|
|
_init:function(){
|
|
//attach handler to the main container
|
|
dhtmlx.event(this._obj,"keypress",this._onKeyPress,this);
|
|
},
|
|
//called on each key press , when focus is inside of related component
|
|
_onKeyPress:function(e){
|
|
e=e||event;
|
|
var code = e.which||e.keyCode; //FIXME better solution is required
|
|
this.callEvent((this._edit_id?"onEditKeyPress":"onKeyPress"),[code,e.ctrlKey,e.shiftKey,e]);
|
|
}
|
|
};
|
|
|
|
|
|
/* DHX DEPEND FROM FILE 'mouse.js'*/
|
|
|
|
|
|
/*
|
|
Behavior:MouseEvents - provides inner evnets for mouse actions
|
|
*/
|
|
dhtmlx.MouseEvents={
|
|
_init: function(){
|
|
//attach dom events if related collection is defined
|
|
if (this.on_click){
|
|
dhtmlx.event(this._obj,"click",this._onClick,this);
|
|
dhtmlx.event(this._obj,"contextmenu",this._onContext,this);
|
|
}
|
|
if (this.on_dblclick)
|
|
dhtmlx.event(this._obj,"dblclick",this._onDblClick,this);
|
|
if (this.on_mouse_move){
|
|
dhtmlx.event(this._obj,"mousemove",this._onMouse,this);
|
|
dhtmlx.event(this._obj,(dhtmlx._isIE?"mouseleave":"mouseout"),this._onMouse,this);
|
|
}
|
|
|
|
},
|
|
//inner onclick object handler
|
|
_onClick: function(e) {
|
|
return this._mouseEvent(e,this.on_click,"ItemClick");
|
|
},
|
|
//inner ondblclick object handler
|
|
_onDblClick: function(e) {
|
|
return this._mouseEvent(e,this.on_dblclick,"ItemDblClick");
|
|
},
|
|
//process oncontextmenu events
|
|
_onContext: function(e) {
|
|
var id = dhtmlx.html.locate(e, this._id);
|
|
if (id && !this.callEvent("onBeforeContextMenu", [id,e]))
|
|
return dhtmlx.html.preventEvent(e);
|
|
},
|
|
/*
|
|
event throttler - ignore events which occurs too fast
|
|
during mouse moving there are a lot of event firing - we need no so much
|
|
also, mouseout can fire when moving inside the same html container - we need to ignore such fake calls
|
|
*/
|
|
_onMouse:function(e){
|
|
if (dhtmlx._isIE) //make a copy of event, will be used in timed call
|
|
e = document.createEventObject(event);
|
|
|
|
if (this._mouse_move_timer) //clear old event timer
|
|
window.clearTimeout(this._mouse_move_timer);
|
|
|
|
//this event just inform about moving operation, we don't care about details
|
|
this.callEvent("onMouseMoving",[e]);
|
|
//set new event timer
|
|
this._mouse_move_timer = window.setTimeout(dhtmlx.bind(function(){
|
|
//called only when we have at least 100ms after previous event
|
|
if (e.type == "mousemove")
|
|
this._onMouseMove(e);
|
|
else
|
|
this._onMouseOut(e);
|
|
},this),500);
|
|
},
|
|
//inner mousemove object handler
|
|
_onMouseMove: function(e) {
|
|
if (!this._mouseEvent(e,this.on_mouse_move,"MouseMove"))
|
|
this.callEvent("onMouseOut",[e||event]);
|
|
},
|
|
//inner mouseout object handler
|
|
_onMouseOut: function(e) {
|
|
this.callEvent("onMouseOut",[e||event]);
|
|
},
|
|
//common logic for click and dbl-click processing
|
|
_mouseEvent:function(e,hash,name){
|
|
e=e||event;
|
|
var trg=e.target||e.srcElement;
|
|
var css = "";
|
|
var id = null;
|
|
var found = false;
|
|
//loop through all parents
|
|
while (trg && trg.parentNode){
|
|
if (!found && trg.getAttribute){ //if element with ID mark is not detected yet
|
|
id = trg.getAttribute(this._id); //check id of current one
|
|
if (id){
|
|
if (trg.getAttribute("userdata"))
|
|
this.callEvent("onLocateData",[id,trg,e]);
|
|
if (!this.callEvent("on"+name,[id,e,trg])) return; //it will be triggered only for first detected ID, in case of nested elements
|
|
found = true; //set found flag
|
|
}
|
|
}
|
|
css=trg.className;
|
|
if (css){ //check if pre-defined reaction for element's css name exists
|
|
css = css.split(" ");
|
|
css = css[0]||css[1]; //FIXME:bad solution, workaround css classes which are starting from whitespace
|
|
if (hash[css])
|
|
return hash[css].call(this,e,id||dhtmlx.html.locate(e, this._id),trg);
|
|
}
|
|
trg=trg.parentNode;
|
|
}
|
|
return found; //returns true if item was located and event was triggered
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
/* DHX DEPEND FROM FILE 'config.js'*/
|
|
|
|
|
|
/*
|
|
Behavior:Settings
|
|
|
|
@export
|
|
customize
|
|
config
|
|
*/
|
|
|
|
/*DHX:Depend template.js*/
|
|
/*DHX:Depend dhtmlx.js*/
|
|
|
|
dhtmlx.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){
|
|
dhtmlx.assert_settings.call(this,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):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 = dhtmlx.extend({},initial);
|
|
//code below will copy all properties over default one
|
|
if (typeof obj == "object" && !obj.tagName)
|
|
dhtmlx.extend(settings,obj);
|
|
//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;
|
|
},
|
|
//helper for html container init
|
|
_parseContainer:function(obj,name,fallback){
|
|
/*
|
|
parameter can be a config object, in such case real container will be obj.container
|
|
or it can be html object or ID of html object
|
|
*/
|
|
if (typeof obj == "object" && !obj.tagName)
|
|
obj=obj.container;
|
|
this._obj = this.$view = dhtmlx.toNode(obj);
|
|
if (!this._obj && fallback)
|
|
this._obj = fallback(obj);
|
|
|
|
dhtmlx.assert(this._obj, "Incorrect html container");
|
|
|
|
this._obj.className+=" "+name;
|
|
this._obj.onselectstart=function(){return false;}; //block selection by default
|
|
this._dataobj = this._obj;//separate reference for rendering modules
|
|
},
|
|
//apply template-type
|
|
_set_type:function(name){
|
|
//parameter can be a hash of settings
|
|
if (typeof name == "object")
|
|
return this.type_setter(name);
|
|
|
|
dhtmlx.assert(this.types, "RenderStack :: Types are not defined");
|
|
dhtmlx.assert(this.types[name],"RenderStack :: Inccorect type name",name);
|
|
//or parameter can be a name of existing template-type
|
|
this.type=dhtmlx.extend({},this.types[name]);
|
|
this.customize(); //init configs
|
|
},
|
|
customize:function(obj){
|
|
//apply new properties
|
|
if (obj) dhtmlx.extend(this.type,obj);
|
|
|
|
//init tempaltes for item start and item end
|
|
this.type._item_start = dhtmlx.Template.fromHTML(this.template_item_start(this.type));
|
|
this.type._item_end = this.template_item_end(this.type);
|
|
|
|
//repaint self
|
|
this.render();
|
|
},
|
|
//config.type - creates new template-type, based on configuration object
|
|
type_setter:function(value){
|
|
this._set_type(typeof value == "object"?dhtmlx.Type.add(this,value):value);
|
|
return value;
|
|
},
|
|
//config.template - creates new template-type with defined template string
|
|
template_setter:function(value){
|
|
return this.type_setter({template:value});
|
|
},
|
|
//config.css - css name for top level container
|
|
css_setter:function(value){
|
|
this._obj.className += " "+value;
|
|
return value;
|
|
}
|
|
};
|
|
|
|
|
|
|
|
/* DHX DEPEND FROM FILE 'template.js'*/
|
|
|
|
|
|
/*
|
|
Template - handles html templates
|
|
*/
|
|
|
|
/*DHX:Depend dhtmlx.js*/
|
|
|
|
dhtmlx.Template={
|
|
_cache:{
|
|
},
|
|
empty:function(){
|
|
return "";
|
|
},
|
|
setter:function(value){
|
|
return dhtmlx.Template.fromHTML(value);
|
|
},
|
|
obj_setter:function(value){
|
|
var f = dhtmlx.Template.setter(value);
|
|
var obj = this;
|
|
return function(){
|
|
return f.apply(obj, arguments);
|
|
};
|
|
},
|
|
fromHTML:function(str){
|
|
if (typeof str == "function") return str;
|
|
if (this._cache[str])
|
|
return this._cache[str];
|
|
|
|
//supported idioms
|
|
// {obj} => value
|
|
// {obj.attr} => named attribute or value of sub-tag in case of xml
|
|
// {obj.attr?some:other} conditional output
|
|
// {-obj => sub-template
|
|
str=(str||"").toString();
|
|
str=str.replace(/[\r\n]+/g,"\\n");
|
|
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(obj):\"\")+\"");
|
|
str=str.replace(/\{obj\.([^}]*)\}/g,"\"+obj.$1+\"");
|
|
str=str.replace(/#([a-z0-9_]+)#/gi,"\"+obj.$1+\"");
|
|
str=str.replace(/\{obj\}/g,"\"+obj+\"");
|
|
str=str.replace(/\{-obj/g,"{obj");
|
|
str=str.replace(/\{-common/g,"{common");
|
|
str="return \""+str+"\";";
|
|
return this._cache[str]= Function("obj","common",str);
|
|
}
|
|
};
|
|
|
|
dhtmlx.Type={
|
|
/*
|
|
adds new template-type
|
|
obj - object to which template will be added
|
|
data - properties of template
|
|
*/
|
|
add:function(obj, data){
|
|
//auto switch to prototype, if name of class was provided
|
|
if (!obj.types && obj.prototype.types)
|
|
obj = obj.prototype;
|
|
//if (typeof data == "string")
|
|
// data = { template:data };
|
|
|
|
if (dhtmlx.assert_enabled())
|
|
this.assert_event(data);
|
|
|
|
var name = data.name||"default";
|
|
|
|
//predefined templates - autoprocessing
|
|
this._template(data);
|
|
this._template(data,"edit");
|
|
this._template(data,"loading");
|
|
|
|
obj.types[name]=dhtmlx.extend(dhtmlx.extend({},(obj.types[name]||this._default)),data);
|
|
return name;
|
|
},
|
|
//default template value - basically empty box with 5px margin
|
|
_default:{
|
|
css:"default",
|
|
template:function(){ return ""; },
|
|
template_edit:function(){ return ""; },
|
|
template_loading:function(){ return "..."; },
|
|
width:150,
|
|
height:80,
|
|
margin:5,
|
|
padding:0
|
|
},
|
|
//template creation helper
|
|
_template:function(obj,name){
|
|
name = "template"+(name?("_"+name):"");
|
|
var data = obj[name];
|
|
//if template is a string - check is it plain string or reference to external content
|
|
if (data && (typeof data == "string")){
|
|
if (data.indexOf("->")!=-1){
|
|
data = data.split("->");
|
|
switch(data[0]){
|
|
case "html": //load from some container on the page
|
|
data = dhtmlx.html.getValue(data[1]).replace(/\"/g,"\\\"");
|
|
break;
|
|
case "http": //load from external file
|
|
data = new dhtmlx.ajax().sync().get(data[1],{uid:(new Date()).valueOf()}).responseText;
|
|
break;
|
|
default:
|
|
//do nothing, will use template as is
|
|
break;
|
|
}
|
|
}
|
|
obj[name] = dhtmlx.Template.fromHTML(data);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/* DHX DEPEND FROM FILE 'single_render.js'*/
|
|
|
|
|
|
/*
|
|
REnders single item.
|
|
Can be used for elements without datastore, or with complex custom rendering logic
|
|
|
|
@export
|
|
render
|
|
*/
|
|
|
|
/*DHX:Depend template.js*/
|
|
|
|
dhtmlx.SingleRender={
|
|
_init:function(){
|
|
},
|
|
//convert item to the HTML text
|
|
_toHTML:function(obj){
|
|
/*
|
|
this one doesn't support per-item-$template
|
|
it has not sense, because we have only single item per object
|
|
*/
|
|
return this.type._item_start(obj,this.type)+this.type.template(obj,this.type)+this.type._item_end;
|
|
},
|
|
//render self, by templating data object
|
|
render:function(){
|
|
if (!this.callEvent || this.callEvent("onBeforeRender",[this.data])){
|
|
if (this.data)
|
|
this._dataobj.innerHTML = this._toHTML(this.data);
|
|
if (this.callEvent) this.callEvent("onAfterRender",[]);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/* DHX DEPEND FROM FILE 'tooltip.js'*/
|
|
|
|
|
|
/*
|
|
UI: Tooltip
|
|
|
|
@export
|
|
show
|
|
hide
|
|
*/
|
|
|
|
/*DHX:Depend tooltip.css*/
|
|
/*DHX:Depend template.js*/
|
|
/*DHX:Depend single_render.js*/
|
|
|
|
dhtmlx.ui.Tooltip=function(container){
|
|
this.name = "Tooltip";
|
|
|
|
if (dhtmlx.assert_enabled()) this._assert();
|
|
|
|
if (typeof container == "string"){
|
|
container = { template:container };
|
|
}
|
|
|
|
dhtmlx.extend(this, dhtmlx.Settings);
|
|
dhtmlx.extend(this, dhtmlx.SingleRender);
|
|
this._parseSettings(container,{
|
|
type:"default",
|
|
dy:0,
|
|
dx:20
|
|
});
|
|
|
|
//create container for future tooltip
|
|
this._dataobj = this._obj = document.createElement("DIV");
|
|
this._obj.className="dhx_tooltip";
|
|
dhtmlx.html.insertBefore(this._obj,document.body.firstChild);
|
|
};
|
|
dhtmlx.ui.Tooltip.prototype = {
|
|
//show tooptip
|
|
//pos - object, pos.x - left, pox.y - top
|
|
show:function(data,pos){
|
|
if (this._disabled) return;
|
|
//render sefl only if new data was provided
|
|
if (this.data!=data){
|
|
this.data=data;
|
|
this.render(data);
|
|
}
|
|
//show at specified position
|
|
this._obj.style.top = pos.y+this._settings.dy+"px";
|
|
this._obj.style.left = pos.x+this._settings.dx+"px";
|
|
this._obj.style.display="block";
|
|
},
|
|
//hide tooltip
|
|
hide:function(){
|
|
this.data=null; //nulify, to be sure that on next show it will be fresh-rendered
|
|
this._obj.style.display="none";
|
|
},
|
|
disable:function(){
|
|
this._disabled = true;
|
|
},
|
|
enable:function(){
|
|
this._disabled = false;
|
|
},
|
|
types:{
|
|
"default":dhtmlx.Template.fromHTML("{obj.id}")
|
|
},
|
|
template_item_start:dhtmlx.Template.empty,
|
|
template_item_end:dhtmlx.Template.empty
|
|
};
|
|
|
|
|
|
|
|
/* DHX DEPEND FROM FILE 'autotooltip.js'*/
|
|
|
|
|
|
/*
|
|
Behavior: AutoTooltip - links tooltip to data driven item
|
|
*/
|
|
|
|
/*DHX:Depend tooltip.js*/
|
|
|
|
dhtmlx.AutoTooltip = {
|
|
tooltip_setter:function(value){
|
|
var t = new dhtmlx.ui.Tooltip(value);
|
|
this.attachEvent("onMouseMove",function(id,e){ //show tooltip on mousemove
|
|
t.show(this.get(id),dhtmlx.html.pos(e));
|
|
});
|
|
this.attachEvent("onMouseOut",function(id,e){ //hide tooltip on mouseout
|
|
t.hide();
|
|
});
|
|
this.attachEvent("onMouseMoving",function(id,e){ //hide tooltip just after moving start
|
|
t.hide();
|
|
});
|
|
return t;
|
|
}
|
|
};
|
|
|
|
|
|
/* DHX DEPEND FROM FILE 'compatibility.js'*/
|
|
|
|
|
|
/*
|
|
Collection of compatibility hacks
|
|
*/
|
|
|
|
/*DHX:Depend dhtmlx.js*/
|
|
|
|
dhtmlx.compat=function(name, obj){
|
|
//check if name hash present, and applies it when necessary
|
|
if (dhtmlx.compat[name])
|
|
dhtmlx.compat[name](obj);
|
|
};
|
|
|
|
|
|
/* DHX DEPEND FROM FILE 'compatibility_layout.js'*/
|
|
|
|
|
|
/*DHX:Depend dhtmlx.js*/
|
|
/*DHX:Depend compatibility.js*/
|
|
|
|
if (!dhtmlx.attaches)
|
|
dhtmlx.attaches = {};
|
|
|
|
dhtmlx.attaches.attachAbstract=function(name, conf){
|
|
var obj = document.createElement("DIV");
|
|
obj.id = "CustomObject_"+dhtmlx.uid();
|
|
obj.style.width = "100%";
|
|
obj.style.height = "100%";
|
|
obj.cmp = "grid";
|
|
document.body.appendChild(obj);
|
|
this.attachObject(obj.id);
|
|
|
|
conf.container = obj.id;
|
|
|
|
var that = this.vs[this.av];
|
|
that.grid = new window[name](conf);
|
|
|
|
that.gridId = obj.id;
|
|
that.gridObj = obj;
|
|
|
|
|
|
that.grid.setSizes = function(){
|
|
if (this.resize) this.resize();
|
|
else this.render();
|
|
};
|
|
|
|
var method_name="_viewRestore";
|
|
return this.vs[this[method_name]()].grid;
|
|
};
|
|
dhtmlx.attaches.attachDataView = function(conf){
|
|
return this.attachAbstract("dhtmlXDataView",conf);
|
|
};
|
|
dhtmlx.attaches.attachChart = function(conf){
|
|
return this.attachAbstract("dhtmlXChart",conf);
|
|
};
|
|
|
|
dhtmlx.compat.layout = function(){};
|
|
|
|
|
|
|
|
|