forked from elmau/empresa-libre
44587 lines
1.2 MiB
44587 lines
1.2 MiB
/*
|
|
@license
|
|
webix UI v.5.0.1
|
|
This software is covered by Webix Commercial License.
|
|
Usage without proper license is prohibited.
|
|
(c) XB Software Ltd.
|
|
*/
|
|
if (!window.webix)
|
|
webix={};
|
|
|
|
//check some rule, show message as error if rule is not correct
|
|
webix.assert = function(test, message){
|
|
if (!test){
|
|
webix.assert_error(message);
|
|
}
|
|
};
|
|
|
|
webix.assert_config = function(obj){
|
|
var coll = obj.cells || obj.rows || obj.elements || obj.cols;
|
|
if (coll)
|
|
for (var i=0; i<coll.length; i++)
|
|
if (coll[i] === null || typeof coll[i] === "undefined")
|
|
webix.assert_error("You have trailing comma or Null element in collection's configuration");
|
|
};
|
|
|
|
webix.assert_error = function(message){
|
|
//jshint debug:true
|
|
webix.log("error",message);
|
|
if (webix.message && typeof message == "string")
|
|
webix.message({ type:"debug", text:message, expire:-1 });
|
|
if (webix.debug !== false)
|
|
debugger;
|
|
};
|
|
|
|
//entry point for analitic scripts
|
|
webix.assert_core_ready = function(){
|
|
if (window.webix_on_core_ready)
|
|
window.webix_on_core_ready();
|
|
};
|
|
|
|
webix.assert_level = 0;
|
|
|
|
webix.assert_level_in = function(){
|
|
webix.assert_level++;
|
|
if (webix.assert_level == 100)
|
|
webix.assert_error("Attempt to copy object with self reference");
|
|
};
|
|
webix.assert_level_out = function(){
|
|
webix.assert_level--;
|
|
};
|
|
|
|
/*
|
|
Common helpers
|
|
*/
|
|
webix.version="5.0.1";
|
|
webix.codebase="./";
|
|
webix.name = "core";
|
|
webix.cdn = "//cdn.webix.com";
|
|
|
|
//coding helpers
|
|
webix.clone = function(source){
|
|
var f = webix.clone._function;
|
|
f.prototype = source;
|
|
return new f();
|
|
};
|
|
webix.clone._function = function(){};
|
|
|
|
//copies methods and properties from source to the target
|
|
webix.extend = function(base, source, force){
|
|
webix.assert(base,"Invalid mixing target");
|
|
webix.assert(source,"Invalid mixing source");
|
|
|
|
if (base.$protoWait){
|
|
webix.PowerArray.insertAt.call(base.$protoWait, source,1);
|
|
return base;
|
|
}
|
|
|
|
//copy methods, overwrite existing ones in case of conflict
|
|
for (var method in source)
|
|
if ((!(method in base)) || force)
|
|
base[method] = source[method];
|
|
|
|
//in case of defaults - preffer top one
|
|
if (source.defaults)
|
|
webix.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
|
|
webix.copy = function(source){
|
|
webix.assert(source,"Invalid mixing target");
|
|
webix.assert_level_in();
|
|
|
|
var target;
|
|
if(arguments.length>1){
|
|
target = arguments[0];
|
|
source = arguments[1];
|
|
} else
|
|
target = (webix.isArray(source)?[]:{});
|
|
|
|
for (var method in source){
|
|
var from = source[method];
|
|
if(from && typeof from == "object" && !(from instanceof RegExp)){
|
|
if (!webix.isDate(from)){
|
|
target[method] = (webix.isArray(from)?[]:{});
|
|
webix.copy(target[method],from);
|
|
} else
|
|
target[method] = new Date(from);
|
|
} else {
|
|
target[method] = from;
|
|
}
|
|
}
|
|
|
|
webix.assert_level_out();
|
|
return target;
|
|
};
|
|
|
|
webix.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;
|
|
};
|
|
|
|
webix.protoUI = function(){
|
|
if (webix.debug_proto)
|
|
webix.log("UI registered: "+arguments[0].name);
|
|
|
|
var origins = arguments;
|
|
var selfname = origins[0].name;
|
|
|
|
var t = function(data){
|
|
if (!t)
|
|
return webix.ui[selfname].prototype;
|
|
|
|
var origins = t.$protoWait;
|
|
if (origins){
|
|
var params = [origins[0]];
|
|
|
|
for (var i=1; i < origins.length; i++){
|
|
params[i] = origins[i];
|
|
|
|
if (params[i].$protoWait)
|
|
params[i] = params[i].call(webix, params[i].name);
|
|
|
|
if (params[i].prototype && params[i].prototype.name)
|
|
webix.ui[params[i].prototype.name] = params[i];
|
|
}
|
|
webix.ui[selfname] = webix.proto.apply(webix, params);
|
|
|
|
if (t._webix_type_wait)
|
|
for (var i=0; i < t._webix_type_wait.length; i++)
|
|
webix.type(webix.ui[selfname], t._webix_type_wait[i]);
|
|
|
|
t = origins = null;
|
|
}
|
|
|
|
if (this != webix)
|
|
return new webix.ui[selfname](data);
|
|
else
|
|
return webix.ui[selfname];
|
|
};
|
|
t.$protoWait = Array.prototype.slice.call(arguments, 0);
|
|
return (webix.ui[selfname]=t);
|
|
};
|
|
|
|
webix.proto = function(){
|
|
|
|
if (webix.debug_proto)
|
|
webix.log("Proto chain:"+arguments[0].name+"["+arguments.length+"]");
|
|
|
|
var origins = arguments;
|
|
var compilation = origins[0];
|
|
var has_constructor = !!compilation.$init;
|
|
var construct = [];
|
|
|
|
webix.assert(compilation,"Invalid mixing target");
|
|
|
|
for (var i=origins.length-1; i>0; i--) {
|
|
webix.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 (webix.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] !== false)
|
|
compilation[key] = origins[i][key];
|
|
}
|
|
}
|
|
|
|
if (has_constructor)
|
|
construct.push(compilation.$init);
|
|
|
|
|
|
compilation.$init = function(){
|
|
for (var i=0; i<construct.length; i++)
|
|
construct[i].apply(this, arguments);
|
|
};
|
|
if (compilation.$skin)
|
|
compilation.$skin();
|
|
|
|
var result = function(config){
|
|
this.$ready=[];
|
|
webix.assert(this.$init,"object without init method");
|
|
this.$init(config);
|
|
if (this._parseSettings)
|
|
this._parseSettings(config, this.defaults);
|
|
for (var i=0; i < this.$ready.length; i++)
|
|
this.$ready[i].call(this);
|
|
};
|
|
result.prototype = compilation;
|
|
|
|
compilation = origins = null;
|
|
return result;
|
|
};
|
|
//creates function with specified "this" pointer
|
|
webix.bind=function(functor, object){
|
|
return function(){ return functor.apply(object,arguments); };
|
|
};
|
|
|
|
//loads module from external js file
|
|
webix.require=function(module, callback, master){
|
|
var promise = webix.promise.defer();
|
|
|
|
if (callback && callback !== true)
|
|
promise = promise.then(function(){ callback.call(master || this); });
|
|
|
|
if (webix.require.disabled){
|
|
promise.resolve();
|
|
return promise;
|
|
}
|
|
|
|
//multiple files required at once
|
|
if (typeof module != "string"){
|
|
var count = module.length||0;
|
|
|
|
if (!count){
|
|
// { file: true, other: true }
|
|
for (var file in module) count++;
|
|
var callback2 = function(){
|
|
count--;
|
|
if (count === 0)
|
|
promise.resolve();
|
|
};
|
|
for (var file in module)
|
|
webix.require(file, callback2, master);
|
|
} else {
|
|
// [ file, other ]
|
|
var callback2 = function(){
|
|
if (count){
|
|
count--;
|
|
webix.require(module[module.length - count - 1], callback2, master);
|
|
} else {
|
|
promise.resolve();
|
|
}
|
|
};
|
|
callback2();
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (webix._modules[module] !== true){
|
|
var fullpath = module;
|
|
if (!module.toString().match(/^([a-z]+\:)*\/\//i))
|
|
fullpath = webix.codebase + module;
|
|
|
|
//css, async, no waiting
|
|
if (module.substr(module.length-4) == ".css") {
|
|
var link = webix.html.create("LINK",{ type:"text/css", rel:"stylesheet", href:fullpath});
|
|
document.getElementsByTagName('head')[0].appendChild(link);
|
|
promise.resolve();
|
|
return promise;
|
|
}
|
|
|
|
//js, async, waiting
|
|
if (callback === true){
|
|
//sync mode
|
|
webix.exec( webix.ajax().sync().get(fullpath).responseText );
|
|
webix._modules[module]=true;
|
|
|
|
} else {
|
|
|
|
if (!webix._modules[module]){ //first call
|
|
webix._modules[module] = [promise];
|
|
|
|
webix.ajax(fullpath, function(text){
|
|
webix.exec(text); //evaluate code
|
|
var calls = webix._modules[module]; //callbacks
|
|
webix._modules[module] = true;
|
|
for (var i=0; i<calls.length; i++)
|
|
calls[i].resolve();
|
|
});
|
|
} else //module already loading
|
|
webix._modules[module].push(promise);
|
|
}
|
|
} else
|
|
promise.resolve();
|
|
|
|
return promise;
|
|
};
|
|
webix._modules = {}; //hash of already loaded modules
|
|
|
|
//evaluate javascript code in the global scoope
|
|
webix.exec=function(code){
|
|
if (window.execScript) //special handling for IE
|
|
window.execScript(code);
|
|
else window.eval(code);
|
|
};
|
|
|
|
webix.wrap = function(code, wrap){
|
|
if (!code) return wrap;
|
|
return function(){
|
|
var result = code.apply(this, arguments);
|
|
wrap.apply(this,arguments);
|
|
return result;
|
|
};
|
|
};
|
|
|
|
//check === undefined
|
|
webix.isUndefined=function(a){
|
|
return typeof a == "undefined";
|
|
};
|
|
//delay call to after-render time
|
|
webix.delay=function(method, obj, params, delay){
|
|
return window.setTimeout(function(){
|
|
if(!(obj&&obj.$destructed)){
|
|
var ret = method.apply(obj,(params||[]));
|
|
method = obj = params = null;
|
|
return ret;
|
|
}
|
|
},delay||1);
|
|
};
|
|
|
|
webix.once=function(method){
|
|
var flag = true;
|
|
return function(){
|
|
if (flag){
|
|
flag = false;
|
|
method.apply(this, arguments);
|
|
}
|
|
};
|
|
};
|
|
|
|
//common helpers
|
|
|
|
//generates unique ID (unique per window, nog GUID)
|
|
webix.uid = function(){
|
|
if (!this._seed) this._seed=(new Date()).valueOf(); //init seed with timestemp
|
|
this._seed++;
|
|
return this._seed;
|
|
};
|
|
//resolve ID as html object
|
|
webix.toNode = function(node){
|
|
if (typeof node == "string") return document.getElementById(node);
|
|
return node;
|
|
};
|
|
//adds extra methods for the array
|
|
webix.toArray = function(array){
|
|
return webix.extend((array||[]),webix.PowerArray, true);
|
|
};
|
|
//resolve function name
|
|
webix.toFunctor=function(str, scope){
|
|
if (typeof(str)=="string"){
|
|
var method = str.replace("()","");
|
|
if (scope && scope[method]) return scope[method];
|
|
return window[method] || eval(str);
|
|
}
|
|
return str;
|
|
};
|
|
/*checks where an object is instance of Array*/
|
|
webix.isArray = function(obj) {
|
|
return Array.isArray?Array.isArray(obj):(Object.prototype.toString.call(obj) === '[object Array]');
|
|
};
|
|
webix.isDate = function(obj){
|
|
return obj instanceof Date;
|
|
};
|
|
// converts an object into a string with respect to dates
|
|
webix.stringify = function(obj){
|
|
var origin = Date.prototype.toJSON;
|
|
Date.prototype.toJSON = function(){
|
|
return webix.i18n.parseFormatStr(this);
|
|
};
|
|
|
|
var result;
|
|
if (obj instanceof Date)
|
|
result = obj.toJSON();
|
|
else
|
|
result = JSON.stringify(obj);
|
|
|
|
Date.prototype.toJSON = origin;
|
|
return result;
|
|
};
|
|
|
|
//dom helpers
|
|
|
|
//hash of attached events
|
|
webix._events = {};
|
|
//private version of API, do not register ID for event detaching
|
|
webix._event = function(a,b,c,d){
|
|
d = d || {};
|
|
d.inner = true;
|
|
webix.event(a,b,c,d);
|
|
};
|
|
//attach event to the DOM element
|
|
webix.event=function(node,event,handler,context){
|
|
context = context || {};
|
|
node = webix.toNode(node);
|
|
webix.assert(node, "Invalid node as target for webix.event");
|
|
|
|
var id = context.id || webix.uid();
|
|
|
|
if(context.bind)
|
|
handler=webix.bind(handler,context.bind);
|
|
|
|
var info = [node,event,handler,context.capture];
|
|
if (!context.inner)
|
|
webix._events[id]=info; //store event info, for detaching
|
|
|
|
//use IE's of FF's way of event's attaching
|
|
if (node.addEventListener)
|
|
node.addEventListener(event, handler, !!context.capture);
|
|
else if (node.attachEvent)
|
|
node.attachEvent("on"+event, info[2] = function(){
|
|
return handler.apply(node, arguments); //IE8 fix
|
|
});
|
|
|
|
return id; //return id of newly created event, can be used in eventRemove
|
|
};
|
|
|
|
//remove previously attached event
|
|
webix.eventRemove=function(id){
|
|
|
|
if (!id) return;
|
|
webix.assert(this._events[id],"Removing non-existing event");
|
|
|
|
var ev = webix._events[id];
|
|
//browser specific event removing
|
|
if (ev[0].removeEventListener)
|
|
ev[0].removeEventListener(ev[1],ev[2],!!ev[3]);
|
|
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
|
|
webix.log = function(type,message,details){
|
|
if (arguments.length == 1){
|
|
message = type;
|
|
type = "log";
|
|
}
|
|
/*jsl:ignore*/
|
|
if (window.console && window.console.log){
|
|
type=type.toLowerCase();
|
|
if (window.console[type])
|
|
window.console[type](message||"unknown error");
|
|
else
|
|
window.console.log(type +": "+message);
|
|
|
|
if (details)
|
|
window.console.log(details);
|
|
}
|
|
/*jsl:end*/
|
|
};
|
|
//register rendering time from call point
|
|
webix.log_full_time = function(name){
|
|
webix._start_time_log = new Date();
|
|
webix.log("Timing start ["+name+"]");
|
|
window.setTimeout(function(){
|
|
var time = new Date();
|
|
webix.log("Timing end ["+name+"]:"+(time.valueOf()-webix._start_time_log.valueOf())/1000+"s");
|
|
},1);
|
|
};
|
|
//register execution time from call point
|
|
webix.log_time = function(name){
|
|
var fname = "_start_time_log"+name;
|
|
if (!webix[fname]){
|
|
webix[fname] = new Date();
|
|
webix.log("Info","Timing start ["+name+"]");
|
|
} else {
|
|
var time = new Date();
|
|
webix.log("Info","Timing end ["+name+"]:"+(time.valueOf()-webix[fname].valueOf())/1000+"s");
|
|
webix[fname] = null;
|
|
}
|
|
};
|
|
webix.debug_code = function(code){
|
|
code.call(webix);
|
|
};
|
|
//event system
|
|
webix.EventSystem={
|
|
$init:function(){
|
|
if (!this._evs_events){
|
|
this._evs_events = {}; //hash of event handlers, name => handler
|
|
this._evs_handlers = {}; //hash of event handlers, ID => handler
|
|
this._evs_map = {};
|
|
}
|
|
},
|
|
//temporary block event triggering
|
|
blockEvent : function(){
|
|
this._evs_events._block = true;
|
|
},
|
|
//re-enable event triggering
|
|
unblockEvent : function(){
|
|
this._evs_events._block = false;
|
|
},
|
|
mapEvent:function(map){
|
|
webix.extend(this._evs_map, map, true);
|
|
},
|
|
on_setter:function(config){
|
|
if(config){
|
|
for(var i in config){
|
|
var method = webix.toFunctor(config[i], this.$scope);
|
|
var sub = i.indexOf("->");
|
|
if (sub !== -1){
|
|
this[i.substr(0,sub)].attachEvent(i.substr(sub+2), webix.bind(method, this));
|
|
} else
|
|
this.attachEvent(i, method);
|
|
}
|
|
}
|
|
},
|
|
//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 (webix.log)
|
|
if ((webix.debug || this.debug) && !webix.debug_blacklist[type]) //can slowdown a lot
|
|
webix.log("info","["+this.name+"@"+((this._settings||{}).id)+"] event:"+type,params);
|
|
|
|
if (event_stack)
|
|
for(var i=0; i<event_stack.length; i++){
|
|
/*
|
|
Call events one by one
|
|
If any event return false - result of whole event will be false
|
|
Handlers which are not returning anything - counted as positive
|
|
*/
|
|
if (event_stack[i].apply(this,(params||[]))===false) return_value=false;
|
|
}
|
|
if (this._evs_map[type]){
|
|
var target = this._evs_map[type];
|
|
target.$eventSource = this;
|
|
if (!target.callEvent(type,params))
|
|
return_value = false;
|
|
target.$eventSource = null;
|
|
}
|
|
|
|
return return_value;
|
|
},
|
|
//assign handler for some named event
|
|
attachEvent:function(type,functor,id){
|
|
webix.assert(functor, "Invalid event handler for "+type);
|
|
|
|
type=type.toLowerCase();
|
|
|
|
id=id||webix.uid(); //ID can be used for detachEvent
|
|
functor = webix.toFunctor(functor, this.$scope); //functor can be a name of method
|
|
|
|
var event_stack=this._evs_events[type]||webix.toArray();
|
|
//save new event handler
|
|
if (arguments[3])
|
|
event_stack.unshift(functor);
|
|
else
|
|
event_stack.push(functor);
|
|
this._evs_events[type]=event_stack;
|
|
this._evs_handlers[id]={ f:functor,t:type };
|
|
|
|
return id;
|
|
},
|
|
//remove event handler
|
|
detachEvent:function(id){
|
|
if(!this._evs_handlers[id]){
|
|
var name = (id+"").toLowerCase();
|
|
if (this._evs_events[name]){
|
|
this._evs_events[name] = webix.toArray();
|
|
}
|
|
return;
|
|
}
|
|
var type=this._evs_handlers[id].t;
|
|
var functor=this._evs_handlers[id].f;
|
|
|
|
//remove from all collections
|
|
var event_stack=this._evs_events[type];
|
|
event_stack.remove(functor);
|
|
delete this._evs_handlers[id];
|
|
},
|
|
hasEvent:function(type){
|
|
type=type.toLowerCase();
|
|
var stack = this._evs_events[type];
|
|
if (stack && stack.length) return true;
|
|
|
|
var parent = this._evs_map[type];
|
|
if (parent)
|
|
return parent.hasEvent(type);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
webix.extend(webix, webix.EventSystem, true);
|
|
|
|
//array helper
|
|
//can be used by webix.toArray()
|
|
webix.PowerArray={
|
|
//remove element at specified position
|
|
removeAt:function(pos,len){
|
|
if (pos>=0) this.splice(pos,(len||1));
|
|
},
|
|
//find element in collection and remove it
|
|
remove:function(value){
|
|
this.removeAt(this.find(value));
|
|
},
|
|
//add element to collection at specific position
|
|
insertAt:function(data,pos){
|
|
if (!pos && pos!==0) //add to the end by default
|
|
this.push(data);
|
|
else {
|
|
var b = this.splice(pos,(this.length-pos));
|
|
this[pos] = data;
|
|
this.push.apply(this,b); //reconstruct array without loosing this pointer
|
|
}
|
|
},
|
|
//return index of element, -1 if it doesn't exists
|
|
find:function(data){
|
|
for (var i=0; i<this.length; i++)
|
|
if (data==this[i]) return i;
|
|
return -1;
|
|
},
|
|
//execute some method for each element of array
|
|
each:function(functor,master){
|
|
for (var i=0; i < this.length; i++)
|
|
functor.call((master||this),this[i]);
|
|
},
|
|
//create new array from source, by using results of functor
|
|
map:function(functor,master){
|
|
for (var i=0; i < this.length; i++)
|
|
this[i]=functor.call((master||this),this[i]);
|
|
return this;
|
|
},
|
|
filter:function(functor, master){
|
|
for (var i=0; i < this.length; i++)
|
|
if (!functor.call((master||this),this[i])){
|
|
this.splice(i,1);
|
|
i--;
|
|
}
|
|
return this;
|
|
}
|
|
};
|
|
|
|
webix.env = {};
|
|
|
|
// webix.env.transform
|
|
// webix.env.transition
|
|
(function(){
|
|
webix.env.strict = !!window.webix_strict;
|
|
webix.env.https = document.location.protocol === "https:";
|
|
|
|
var agent = navigator.userAgent;
|
|
|
|
if (agent.indexOf("Mobile")!=-1 || agent.indexOf("Windows Phone")!=-1)
|
|
webix.env.mobile = true;
|
|
if (webix.env.mobile || agent.indexOf("iPad")!=-1 || agent.indexOf("Android")!=-1)
|
|
webix.env.touch = true;
|
|
if (agent.indexOf('Opera')!=-1)
|
|
webix.env.isOpera=true;
|
|
else{
|
|
//very rough detection, but it is enough for current goals
|
|
webix.env.isIE=!!document.all || (agent.indexOf("Trident") !== -1);
|
|
if (webix.env.isIE){
|
|
var version = parseFloat(navigator.appVersion.split("MSIE")[1]);
|
|
if (version == 8)
|
|
webix.env.isIE8 = true;
|
|
}
|
|
webix.env.isEdge=(agent.indexOf("Edge")!=-1);
|
|
webix.env.isFF=(agent.indexOf("Firefox")!=-1);
|
|
webix.env.isWebKit=(agent.indexOf("KHTML")!=-1);
|
|
webix.env.isSafari=webix.env.isWebKit && (agent.indexOf('Mac')!=-1) && (agent.indexOf('Chrome')==-1);
|
|
|
|
//maximum height/width for HTML elements in pixels (rough), bigger values will be ignored by browser
|
|
if(webix.env.isIE || webix.env.isEdge || webix.env.isFF)
|
|
webix.env.maxHTMLElementSize = 10000000;
|
|
if(webix.env.isSafari)
|
|
webix.env.maxHTMLElementSize = 100000000;
|
|
}
|
|
|
|
if(agent.toLowerCase().indexOf("android")!=-1){
|
|
webix.env.isAndroid = true;
|
|
if(agent.toLowerCase().indexOf("trident")!=-1){
|
|
webix.env.isAndroid = false;
|
|
webix.env.isIEMobile = true;
|
|
}
|
|
}
|
|
|
|
webix.env.transform = false;
|
|
webix.env.transition = false;
|
|
|
|
var found_index = -1;
|
|
var js_list = ['', 'webkit', 'Moz', 'O', 'ms'];
|
|
var css_list = ['', '-webkit-', '-Moz-', '-o-', '-ms-'];
|
|
|
|
|
|
var d = document.createElement("DIV");
|
|
for (var j=0; j < js_list.length; j++) {
|
|
var name = js_list[j] ? (js_list[j]+"Transform") : "transform";
|
|
if(typeof d.style[name] != 'undefined'){
|
|
found_index = j;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
if (found_index > -1){
|
|
webix.env.cssPrefix = css_list[found_index];
|
|
var jp = webix.env.jsPrefix = js_list[found_index];
|
|
|
|
webix.env.transform = jp ? jp+"Transform" : "transform";
|
|
webix.env.transition = jp ? jp+"Transition" : "transition";
|
|
webix.env.transitionDuration = jp ? jp+"TransitionDuration" : "transitionDuration";
|
|
|
|
d.style[webix.env.transform] = "translate3d(0,0,0)";
|
|
webix.env.translate = (d.style[webix.env.transform])?"translate3d":"translate";
|
|
webix.env.transitionEnd = ((webix.env.cssPrefix == '-Moz-')?"transitionend":(jp ? jp+"TransitionEnd" : "transitionend"));
|
|
}
|
|
|
|
webix.env.pointerevents = (!webix.env.isIE ||(new RegExp("Trident/.*rv:11")).exec(agent) !== null);
|
|
})();
|
|
|
|
|
|
webix.env.svg = (function(){
|
|
return document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1");
|
|
})();
|
|
|
|
webix.env.svganimation = (function(){
|
|
return document.implementation.hasFeature("https://www.w3.org/TR/SVG11/feature#SVG-animation", "1.1");
|
|
})();
|
|
|
|
|
|
//html helpers
|
|
webix.html={
|
|
_native_on_selectstart:0,
|
|
_style_element:{},
|
|
denySelect:function(){
|
|
if (!webix._native_on_selectstart)
|
|
webix._native_on_selectstart = document.onselectstart;
|
|
document.onselectstart = webix.html.stopEvent;
|
|
},
|
|
allowSelect:function(){
|
|
if (webix._native_on_selectstart !== 0){
|
|
document.onselectstart = webix._native_on_selectstart||null;
|
|
}
|
|
webix._native_on_selectstart = 0;
|
|
|
|
},
|
|
index:function(node){
|
|
var k=0;
|
|
//must be =, it is not a comparation!
|
|
while ((node = node.previousSibling)) k++;
|
|
return k;
|
|
},
|
|
_style_cache:{},
|
|
createCss:function(rule, sufix){
|
|
var text = "";
|
|
sufix = sufix || "";
|
|
|
|
for (var key in rule)
|
|
text+= key+":"+rule[key]+";";
|
|
|
|
var name = this._style_cache[text+sufix];
|
|
if (!name){
|
|
name = "s"+webix.uid();
|
|
this.addStyle("."+name+(sufix||"")+"{"+text+"}");
|
|
this._style_cache[text+sufix] = name;
|
|
}
|
|
return name;
|
|
},
|
|
addStyle:function(rule, group){
|
|
var style = group ? this._style_element[group] :this._style_element["default"];
|
|
if(!style){
|
|
style = document.createElement("style");
|
|
style.setAttribute("type", "text/css");
|
|
style.setAttribute("media", "screen,print");
|
|
document.getElementsByTagName("head")[0].appendChild(style);
|
|
|
|
if (group)
|
|
this._style_element[group] = style;
|
|
else
|
|
this._style_element["default"] = style;
|
|
}
|
|
/*IE8*/
|
|
if (style.styleSheet)
|
|
style.styleSheet.cssText += rule;
|
|
else
|
|
style.appendChild(document.createTextNode(rule));
|
|
},
|
|
removeStyle:function(group){
|
|
var box = this._style_element[group||"default"];
|
|
if (box)
|
|
box.innerHTML = "";
|
|
},
|
|
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 = webix.toNode(node);
|
|
if (!node) return "";
|
|
return webix.isUndefined(node.value)?node.innerHTML:node.value;
|
|
},
|
|
//remove html node, can process an array of nodes at once
|
|
remove:function(node){
|
|
if (node instanceof Array)
|
|
for (var i=0; i < node.length; i++)
|
|
this.remove(node[i]);
|
|
else if (node && node.parentNode)
|
|
node.parentNode.removeChild(node);
|
|
},
|
|
//insert new node before sibling, or at the end if sibling doesn't exist
|
|
insertBefore: function(node,before,rescue){
|
|
if (!node) return;
|
|
if (before && before.parentNode)
|
|
before.parentNode.insertBefore(node, before);
|
|
else
|
|
rescue.appendChild(node);
|
|
},
|
|
//return custom ID from html element
|
|
//will check all parents starting from event's target
|
|
locate:function(e,id){
|
|
var trg;
|
|
if (e.tagName)
|
|
trg = e;
|
|
else {
|
|
e=e||event;
|
|
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), width:elem.offsetWidth, height:elem.offsetHeight };
|
|
} 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, width:elem.offsetHeight, height:elem.offsetWidth };
|
|
}
|
|
},
|
|
//returns relative position of event
|
|
posRelative:function(ev){
|
|
ev = ev || event;
|
|
if (!webix.isUndefined(ev.offsetX))
|
|
return { x:ev.offsetX, y:ev.offsetY }; //ie, webkit
|
|
else
|
|
return { x:ev.layerX, y:ev.layerY }; //firefox
|
|
},
|
|
//returns position of event
|
|
pos:function(ev){
|
|
ev = ev || event;
|
|
if (ev.touches && ev.touches[0])
|
|
ev = ev.touches[0];
|
|
|
|
if(ev.pageX || ev.pageY) //FF, KHTML
|
|
return {x:ev.pageX, y:ev.pageY};
|
|
//IE
|
|
var d = ((webix.env.isIE)&&(document.compatMode != "BackCompat"))?document.documentElement:document.body;
|
|
return {
|
|
x:ev.clientX + d.scrollLeft - d.clientLeft,
|
|
y:ev.clientY + d.scrollTop - d.clientTop
|
|
};
|
|
},
|
|
//prevent event action
|
|
preventEvent:function(e){
|
|
if(e && e.preventDefault) e.preventDefault();
|
|
if(e) e.returnValue = false;
|
|
return webix.html.stopEvent(e);
|
|
},
|
|
//stop event bubbling
|
|
stopEvent:function(e){
|
|
e = (e||event);
|
|
if(e.stopPropagation) e.stopPropagation();
|
|
e.cancelBubble=true;
|
|
return false;
|
|
},
|
|
triggerEvent:function(node, type, name){
|
|
if(document.createEventObject){
|
|
var ev = document.createEventObject();
|
|
if (node.fireEvent)
|
|
node.fireEvent("on"+name, ev);
|
|
} else{
|
|
var ev = document.createEvent(type);
|
|
ev.initEvent(name, true, true);
|
|
if (node.dispatchEvent)
|
|
node.dispatchEvent(ev);
|
|
}
|
|
},
|
|
//add css class to the node
|
|
addCss:function(node,name,check){
|
|
if (!check || node.className.indexOf(name) === -1)
|
|
node.className+=" "+name;
|
|
},
|
|
//remove css class from the node
|
|
removeCss:function(node,name){
|
|
node.className=node.className.replace(RegExp(" "+name,"g"),"");
|
|
},
|
|
getTextSize:function(text, css, width){
|
|
var d = webix.html.create("DIV",{"class":"webix_view webix_measure_size "+(css||"")},"");
|
|
d.style.cssText = "height:auto;visibility:hidden; position:absolute; top:0px; left:0px; overflow:hidden;"+(width?("width:"+width+"px;"):"width:auto;white-space:nowrap;");
|
|
document.body.appendChild(d);
|
|
|
|
var all = (typeof text !== "object") ? [text] : text;
|
|
var width = 0;
|
|
var height = 0;
|
|
|
|
for (var i = 0; i < all.length; i++) {
|
|
d.innerHTML = all[i];
|
|
width = Math.max(width, d.offsetWidth);
|
|
height = Math.max(height, d.offsetHeight);
|
|
}
|
|
|
|
webix.html.remove(d);
|
|
return { width:width, height:height };
|
|
},
|
|
download:function(data, filename){
|
|
var objUrl = false;
|
|
|
|
if(typeof data =="object"){//blob
|
|
if(window.navigator.msSaveBlob)
|
|
return window.navigator.msSaveBlob(data, filename);
|
|
else {
|
|
data = window.URL.createObjectURL(data);
|
|
objUrl = true;
|
|
}
|
|
}
|
|
//data url or blob url
|
|
var link = document.createElement("a");
|
|
link.href = data;
|
|
link.download = filename;
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
|
|
webix.delay(function(){
|
|
if(objUrl) window.URL.revokeObjectURL(data);
|
|
document.body.removeChild(link);
|
|
link.remove();
|
|
});
|
|
},
|
|
_getClassName: function(node){
|
|
if(!node) return "";
|
|
|
|
var className = node.className || "";
|
|
if(className.baseVal)//'className' exist but not a string - IE svg element in DOM
|
|
className = className.baseVal;
|
|
|
|
if(!className.indexOf)
|
|
className = "";
|
|
|
|
return className;
|
|
},
|
|
setSelectionRange:function(node, start, end){
|
|
start = start || 0;
|
|
end = end || start;
|
|
|
|
node.focus();
|
|
if(node.setSelectionRange)
|
|
node.setSelectionRange(start, end);
|
|
else{
|
|
//ie8
|
|
var textRange = node.createTextRange();
|
|
textRange.collapse(true);
|
|
textRange.moveEnd('character', end);
|
|
textRange.moveStart('character', start);
|
|
textRange.select();
|
|
}
|
|
},
|
|
getSelectionRange:function(node){
|
|
if("selectionStart" in node)
|
|
return {start:node.selectionStart || 0, end:node.selectionEnd || 0};
|
|
else{
|
|
//ie8
|
|
node.focus();
|
|
var selection = document.selection.createRange();
|
|
var bookmark = selection.getBookmark();
|
|
var textRange = node.createTextRange();
|
|
|
|
textRange.moveToBookmark(bookmark);
|
|
var length = textRange.text.length;
|
|
|
|
textRange.collapse(true);
|
|
textRange.moveStart('character', -node.value.length);
|
|
|
|
var start = textRange.text.length;
|
|
return {start:start, end: start + length};
|
|
}
|
|
}
|
|
};
|
|
|
|
webix.ready = function(code){
|
|
if (this._ready) code.call();
|
|
else this._ready_code.push(code);
|
|
};
|
|
webix.debug_ready = webix.ready; //same command but will work only in dev. build
|
|
webix._ready_code = [];
|
|
|
|
//autodetect codebase folder
|
|
(function(){
|
|
var temp = document.getElementsByTagName("SCRIPT"); //current script, most probably
|
|
webix.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);
|
|
webix.codebase = temp.slice(0, temp.length).join("/")+"/";
|
|
}
|
|
|
|
var ready = function(){
|
|
if(webix.env.isIE)
|
|
document.body.className += " webix_ie";
|
|
webix.callEvent("onReady",[]);
|
|
};
|
|
|
|
var doit = function(){
|
|
webix._ready = true;
|
|
|
|
//global plugins
|
|
if (window.webix_ready && webix.isArray(webix_ready))
|
|
webix._ready_code = webix_ready.concat(webix._ready_code);
|
|
|
|
for (var i=0; i < webix._ready_code.length; i++)
|
|
webix._ready_code[i].call();
|
|
webix._ready_code=[];
|
|
};
|
|
|
|
webix.attachEvent("onReady", function(force){
|
|
if (force)
|
|
doit();
|
|
else
|
|
webix.delay(doit);
|
|
});
|
|
|
|
if (document.readyState == "complete") ready();
|
|
else webix.event(window, "load", ready);
|
|
|
|
})();
|
|
|
|
webix.locale=webix.locale||{};
|
|
|
|
|
|
webix.assert_core_ready();
|
|
|
|
|
|
webix.ready(function(){
|
|
webix.event(document.body,"click", function(e){
|
|
webix.callEvent("onClick",[e||event]);
|
|
});
|
|
});
|
|
webix.editStop = function(){
|
|
webix.callEvent("onEditEnd", []);
|
|
};
|
|
|
|
|
|
webix.debug_blacklist={
|
|
onmousemoving:1
|
|
};
|
|
|
|
/**
|
|
Bazed on Promiz - A fast Promises/A+ library
|
|
https://github.com/Zolmeister/promiz
|
|
The MIT License (MIT)
|
|
Copyright (c) 2014 Zolmeister
|
|
*/
|
|
|
|
/* jshint ignore:start */
|
|
(function (self) {
|
|
var global = this
|
|
|
|
var queueId = 1
|
|
var queue = {}
|
|
var isRunningTask = false
|
|
|
|
if (!global.setImmediate && global.addEventListener)
|
|
global.addEventListener('message', function (e) {
|
|
if (e.source == global){
|
|
if (isRunningTask)
|
|
nextTick(queue[e.data])
|
|
else {
|
|
isRunningTask = true
|
|
try {
|
|
queue[e.data]()
|
|
} catch (e) {}
|
|
|
|
delete queue[e.data]
|
|
isRunningTask = false
|
|
}
|
|
}
|
|
})
|
|
|
|
function nextTick(fn) {
|
|
if (global.setImmediate) setImmediate(fn)
|
|
// if inside of web worker
|
|
else if (global.importScripts || !global.addEventListener) setTimeout(fn)
|
|
else {
|
|
queueId++
|
|
queue[queueId] = fn
|
|
global.postMessage(queueId, '*')
|
|
}
|
|
}
|
|
|
|
Deferred.resolve = function (value) {
|
|
if (!(this._d == 1))
|
|
throw TypeError()
|
|
|
|
if (value instanceof Deferred)
|
|
return value
|
|
|
|
return new Deferred(function (resolve) {
|
|
resolve(value)
|
|
})
|
|
}
|
|
|
|
Deferred.reject = function (value) {
|
|
if (!(this._d == 1))
|
|
throw TypeError()
|
|
|
|
return new Deferred(function (resolve, reject) {
|
|
reject(value)
|
|
})
|
|
}
|
|
|
|
Deferred.all = function (arr) {
|
|
if (!(this._d == 1))
|
|
throw TypeError()
|
|
|
|
if (!(arr instanceof Array))
|
|
return Deferred.reject(TypeError())
|
|
|
|
var d = new Deferred()
|
|
|
|
function done(e, v) {
|
|
if (v)
|
|
return d.resolve(v)
|
|
|
|
if (e)
|
|
return d.reject(e)
|
|
|
|
var unresolved = arr.reduce(function (cnt, v) {
|
|
if (v && v.then)
|
|
return cnt + 1
|
|
return cnt
|
|
}, 0)
|
|
|
|
if(unresolved == 0)
|
|
d.resolve(arr)
|
|
|
|
|
|
arr.map(function (v, i) {
|
|
if (v && v.then)
|
|
v.then(function (r) {
|
|
arr[i] = r
|
|
done()
|
|
return r
|
|
}, done)
|
|
})
|
|
}
|
|
|
|
done()
|
|
|
|
return d
|
|
}
|
|
|
|
Deferred.race = function (arr) {
|
|
if (!(this._d == 1))
|
|
throw TypeError()
|
|
|
|
if (!(arr instanceof Array))
|
|
return Deferred.reject(TypeError())
|
|
|
|
if (arr.length == 0)
|
|
return new Deferred()
|
|
|
|
var d = new Deferred()
|
|
|
|
function done(e, v) {
|
|
if (v)
|
|
return d.resolve(v)
|
|
|
|
if (e)
|
|
return d.reject(e)
|
|
|
|
var unresolved = arr.reduce(function (cnt, v) {
|
|
if (v && v.then)
|
|
return cnt + 1
|
|
return cnt
|
|
}, 0)
|
|
|
|
if(unresolved == 0)
|
|
d.resolve(arr)
|
|
|
|
arr.map(function (v, i) {
|
|
if (v && v.then)
|
|
v.then(function (r) {
|
|
done(null, r)
|
|
}, done)
|
|
})
|
|
}
|
|
|
|
done()
|
|
|
|
return d
|
|
}
|
|
|
|
Deferred._d = 1
|
|
|
|
|
|
/**
|
|
* @constructor
|
|
*/
|
|
function Deferred(resolver) {
|
|
'use strict'
|
|
if (typeof resolver != 'function' && resolver != undefined)
|
|
throw TypeError()
|
|
|
|
if (typeof this != 'object' || (this && this.then))
|
|
throw TypeError()
|
|
|
|
// states
|
|
// 0: pending
|
|
// 1: resolving
|
|
// 2: rejecting
|
|
// 3: resolved
|
|
// 4: rejected
|
|
var self = this,
|
|
state = 0,
|
|
val = 0,
|
|
next = [],
|
|
fn, er;
|
|
|
|
self['promise'] = self
|
|
|
|
self['resolve'] = function (v) {
|
|
fn = self.fn
|
|
er = self.er
|
|
if (!state) {
|
|
val = v
|
|
state = 1
|
|
|
|
nextTick(fire)
|
|
}
|
|
return self
|
|
}
|
|
|
|
self['reject'] = function (v) {
|
|
fn = self.fn
|
|
er = self.er
|
|
if (!state) {
|
|
val = v
|
|
state = 2
|
|
|
|
nextTick(fire)
|
|
|
|
}
|
|
return self
|
|
}
|
|
|
|
self['_d'] = 1
|
|
|
|
self['then'] = function (_fn, _er) {
|
|
if (!(this._d == 1))
|
|
throw TypeError()
|
|
|
|
var d = new Deferred()
|
|
|
|
d.fn = _fn
|
|
d.er = _er
|
|
if (state == 3) {
|
|
d.resolve(val)
|
|
}
|
|
else if (state == 4) {
|
|
d.reject(val)
|
|
}
|
|
else {
|
|
next.push(d)
|
|
}
|
|
|
|
return d
|
|
}
|
|
|
|
self['catch'] = function (_er) {
|
|
return self['then'](null, _er)
|
|
}
|
|
|
|
//compatibility with old version of promiz lib
|
|
self['fail'] = function (_er) {
|
|
return self['then'](null, _er)
|
|
}
|
|
|
|
var finish = function (type) {
|
|
state = type || 4
|
|
for (var i=0; i<next.length; i++){
|
|
var p = next[i];
|
|
state == 3 && p.resolve(val) || p.reject(val);
|
|
}
|
|
}
|
|
|
|
try {
|
|
if (typeof resolver == 'function')
|
|
resolver(self['resolve'], self['reject'])
|
|
} catch (e) {
|
|
self['reject'](e)
|
|
}
|
|
|
|
return self
|
|
|
|
// ref : reference to 'then' function
|
|
// cb, ec, cn : successCallback, failureCallback, notThennableCallback
|
|
function thennable (ref, cb, ec, cn) {
|
|
// Promises can be rejected with other promises, which should pass through
|
|
if (state == 2) {
|
|
return cn()
|
|
}
|
|
if ((typeof val == 'object' || typeof val == 'function') && typeof ref == 'function') {
|
|
try {
|
|
|
|
// cnt protects against abuse calls from spec checker
|
|
var cnt = 0
|
|
ref.call(val, function (v) {
|
|
if (cnt++) return
|
|
val = v
|
|
cb()
|
|
}, function (v) {
|
|
if (cnt++) return
|
|
val = v
|
|
ec()
|
|
})
|
|
} catch (e) {
|
|
val = e
|
|
ec()
|
|
}
|
|
} else {
|
|
cn()
|
|
}
|
|
};
|
|
|
|
function fire() {
|
|
// check if it's a thenable
|
|
var ref;
|
|
try {
|
|
ref = val && val.then
|
|
} catch (e) {
|
|
val = e
|
|
state = 2
|
|
return fire()
|
|
}
|
|
|
|
thennable(ref, function () {
|
|
state = 1
|
|
fire()
|
|
}, function () {
|
|
state = 2
|
|
fire()
|
|
}, function () {
|
|
try {
|
|
if (state == 1 && typeof fn == 'function') {
|
|
val = fn(val)
|
|
}
|
|
|
|
else if (state == 2 && typeof er == 'function') {
|
|
val = er(val)
|
|
state = 1
|
|
}
|
|
} catch (e) {
|
|
val = e
|
|
return finish()
|
|
}
|
|
|
|
if (val == self) {
|
|
val = TypeError()
|
|
finish()
|
|
} else thennable(ref, function () {
|
|
finish(3)
|
|
}, finish, function () {
|
|
finish(state == 1 && 3)
|
|
})
|
|
|
|
})
|
|
}
|
|
|
|
|
|
}
|
|
|
|
// promise factory
|
|
Deferred.defer = function () {
|
|
return new Deferred(null)
|
|
}
|
|
|
|
self.promise = Deferred;
|
|
})(webix);
|
|
/* jshint ignore:end */
|
|
|
|
(function(){
|
|
|
|
var error_key = "__webix_remote_error";
|
|
|
|
function RemoteContext(url, config){
|
|
this._proxy = {};
|
|
this._queue = [];
|
|
this._url = url;
|
|
this._key = "";
|
|
|
|
if (config)
|
|
this._process(config);
|
|
else
|
|
this._ready = webix.ajax(url)
|
|
.then(function(data){
|
|
return data.text();
|
|
})
|
|
.then(webix.bind(function(text){
|
|
text = text.split("/*api*/")[1];
|
|
this._process(JSON.parse(text));
|
|
return this._proxy;
|
|
}, this));
|
|
}
|
|
RemoteContext.prototype = {
|
|
_process:function(config){
|
|
if (config.$key)
|
|
this._key = config.$key;
|
|
if (config.$vars)
|
|
for (var key in config.$vars)
|
|
this._proxy[key] = config.$vars[key];
|
|
|
|
this._parse(config, this._proxy, "");
|
|
},
|
|
_parse:function(api, obj, prefix){
|
|
for (var key in api){
|
|
if (key === "$key" || key === "$vars") continue;
|
|
var val = api[key];
|
|
if (typeof val == "object"){
|
|
var sub = obj[key] = {};
|
|
this._parse(val, sub, prefix+key+".");
|
|
} else
|
|
obj[key] = this._proxy_call(this, prefix+key);
|
|
}
|
|
},
|
|
_call:function(name, args){
|
|
var def = this._deffer(this, name, args);
|
|
this._queue.push(def);
|
|
this._start_queue();
|
|
return def;
|
|
},
|
|
_start_queue:function(){
|
|
if (!this._timer)
|
|
this._timer = setTimeout(webix.bind(this._run_queue, this), 1);
|
|
},
|
|
_run_queue:function(){
|
|
var data = [], defs = this._queue;
|
|
for (var i=0; i<this._queue.length; i++){
|
|
var def = this._queue[i];
|
|
if (def.$sync){
|
|
defs.splice(i,1); i--;
|
|
} else
|
|
data.push({ name: def.$name, args: def.$args });
|
|
}
|
|
|
|
if (defs.length){
|
|
var ajax = webix.ajax();
|
|
var pack = this._pack(data);
|
|
webix.callEvent("onBeforeRemoteCall", [ajax, pack, {}]);
|
|
var promise = ajax.post(this._url, pack)
|
|
.then(function(res){
|
|
var data = res.json();
|
|
var results = data.data;
|
|
for (var i=0; i<results.length; i++){
|
|
var res = results[i];
|
|
var error = results[i] && results[i][error_key];
|
|
if (error){
|
|
webix.callEvent("onRemoteError", [error]);
|
|
defs[i].reject(error);
|
|
} else {
|
|
defs[i].resolve(res);
|
|
}
|
|
}
|
|
}, function(res){
|
|
for (var i=0; i<defs.length; i++)
|
|
defs[i].reject(res);
|
|
throw res;
|
|
});
|
|
webix.callEvent("onAfterRemoteCall", [promise]);
|
|
}
|
|
|
|
this._queue = [];
|
|
this._timer = null;
|
|
},
|
|
_sync:function(){
|
|
var value = null;
|
|
this.$sync = true;
|
|
var data = [{ name: this.$name, args: this.$args }];
|
|
|
|
try {
|
|
var ajax = webix.ajax();
|
|
var pack = this.$context._pack(data);
|
|
webix.callEvent("onBeforeRemoteCall", [ajax, pack, { sync: true }]);
|
|
var xhr = ajax.sync().post(this.$context._url, pack);
|
|
webix.callEvent("onAfterRemoteCall", [null]);
|
|
var value = JSON.parse(xhr.responseText).data[0];
|
|
if (value[error_key])
|
|
value = null;
|
|
} catch(e){}
|
|
|
|
return value;
|
|
},
|
|
_deffer:function(master, name, args){
|
|
var pr = webix.promise.defer();
|
|
pr.sync = master._sync;
|
|
pr.$name = name;
|
|
pr.$args = args;
|
|
pr.$context = this;
|
|
|
|
return pr;
|
|
},
|
|
_proxy_call:function(master, name){
|
|
return function(){
|
|
return master._call(name, [].slice.call(arguments));
|
|
};
|
|
},
|
|
_getProxy:function(){
|
|
return this._ready || this._proxy;
|
|
},
|
|
_pack:function(obj){
|
|
return {
|
|
key: this._key,
|
|
payload:obj
|
|
};
|
|
}
|
|
};
|
|
|
|
function getApi(url, config){
|
|
var ctx = new RemoteContext(url, config);
|
|
return ctx._getProxy();
|
|
}
|
|
|
|
webix.remote = function(url, config){
|
|
if (typeof url === "object"){
|
|
var scripts = document.getElementsByTagName("script");
|
|
config = url;
|
|
url = scripts[scripts.length - 1].src;
|
|
webix.remote = getApi(url, config);
|
|
} else
|
|
return getApi(url, config);
|
|
};
|
|
|
|
|
|
})();
|
|
|
|
/*
|
|
UI:DataView
|
|
*/
|
|
|
|
|
|
webix.skin={};
|
|
|
|
webix.skin.air = {
|
|
topLayout:"wide",
|
|
//bar in accordion
|
|
barHeight:34, //!!!Set the same in skin.less!!!
|
|
tabbarHeight: 36,
|
|
rowHeight:34,
|
|
toolbarHeight:22,
|
|
listItemHeight:28, //list, grouplist, dataview, etc.
|
|
inputHeight:34,
|
|
inputPadding: 2,
|
|
menuHeight: 34,
|
|
menuMargin:0,
|
|
labelTopHeight: 16,
|
|
|
|
inputSpacing: 4,
|
|
borderWidth: 1,
|
|
|
|
sliderHandleWidth: 14,
|
|
sliderPadding: 10,
|
|
sliderBorder: 1,
|
|
vSliderPadding:13,
|
|
vSliderHeight:100,
|
|
|
|
//margin - distance between cells
|
|
layoutMargin:{ space:10, wide:4, clean:0, head:4, line:-1, toolbar:4, form:8 },
|
|
//padding - distance insede cell between cell border and cell content
|
|
layoutPadding:{ space:10, wide:0, clean:0, head:0, line:0, toolbar:4, form:8 },
|
|
//space between tabs in tabbar
|
|
tabMargin:0,
|
|
|
|
popupPadding: 8,
|
|
|
|
calendarHeight: 70,
|
|
padding:0,
|
|
|
|
optionHeight: 27
|
|
};
|
|
webix.skin["aircompact"] = {
|
|
topLayout:"wide",
|
|
//bar in accordion
|
|
barHeight:24, //!!!Set the same in skin.less!!!
|
|
tabbarHeight: 26,
|
|
rowHeight:26,
|
|
toolbarHeight:22,
|
|
listItemHeight:28, //list, grouplist, dataview, etc.
|
|
inputHeight:29,
|
|
inputPadding: 2,
|
|
menuHeight: 25,
|
|
menuMargin:0,
|
|
labelTopHeight: 16,
|
|
|
|
inputSpacing: 4,
|
|
borderWidth: 1,
|
|
|
|
sliderHandleWidth: 12,
|
|
sliderPadding: 10,
|
|
sliderBorder: 1,
|
|
vSliderPadding:12.5,
|
|
vSliderHeight:100,
|
|
|
|
//margin - distance between cells
|
|
layoutMargin:{ space:10, wide:4, clean:0, head:4, line:-1, toolbar:4, form:8 },
|
|
//padding - distance insede cell between cell border and cell content
|
|
layoutPadding:{ space:10, wide:0, clean:0, head:0, line:0, toolbar:4, form:8 },
|
|
//space between tabs in tabbar
|
|
tabMargin:0,
|
|
|
|
popupPadding: 8,
|
|
|
|
calendarHeight: 70,
|
|
padding:0,
|
|
|
|
optionHeight: 23
|
|
};
|
|
|
|
webix.skin.web = {
|
|
name:"web",
|
|
topLayout:"space",
|
|
//bar in accordion
|
|
barHeight:28, //!!!Set the same in skin.less!!!
|
|
tabbarHeight: 30,
|
|
rowHeight:30,
|
|
toolbarHeight:22,
|
|
listItemHeight:28, //list, grouplist, dataview, etc.
|
|
inputHeight:28,
|
|
inputPadding: 2,
|
|
menuMargin: 0,
|
|
menuHeight: 27,
|
|
labelTopHeight: 16,
|
|
//accordionMargin: 9,
|
|
|
|
inputSpacing: 4,
|
|
borderWidth: 1,
|
|
|
|
sliderHandleWidth: 11,
|
|
sliderPadding: 10,
|
|
sliderBorder: 1,
|
|
vSliderPadding:10,
|
|
vSliderHeight:100,
|
|
|
|
//margin - distance between cells
|
|
layoutMargin:{ space:10, wide:4, clean:0, head:4, line:-1, toolbar:4, form:8, accordion: 9 },
|
|
//padding - distance insede cell between cell border and cell content
|
|
layoutPadding:{ space:10, wide:0, clean:0, head:0, line:0, toolbar:4, form:8, accordion:0 },
|
|
//space between tabs in tabbar
|
|
tabMargin:3,
|
|
tabTopOffset:3,
|
|
|
|
popupPadding: 8,
|
|
|
|
calendarHeight: 70,
|
|
padding:0,
|
|
|
|
optionHeight: 22
|
|
};
|
|
webix.skin.clouds = {
|
|
topLayout:"wide",
|
|
//bar in accordion
|
|
barHeight:36, //!!!Set the same in skin.less!!!
|
|
tabbarHeight: 46,
|
|
rowHeight:34,
|
|
toolbarHeight:22,
|
|
listItemHeight:32, //list, grouplist, dataview, etc.
|
|
inputHeight:30,
|
|
inputPadding: 2,
|
|
menuHeight: 34,
|
|
labelTopHeight: 16,
|
|
|
|
inputSpacing: 4,
|
|
borderWidth: 1,
|
|
|
|
sliderHandleWidth: 12,
|
|
sliderPadding: 10,
|
|
sliderBorder: 1,
|
|
vSliderPadding:11,
|
|
vSliderHeight:100,
|
|
|
|
//margin - distance between cells
|
|
layoutMargin:{ space:10, wide:4, clean:0, head:4, line:-1, toolbar:4, form:8 },
|
|
//padding - distance insede cell between cell border and cell content
|
|
layoutPadding:{ space:10, wide:0, clean:0, head:0, line:0, toolbar:4, form:8 },
|
|
//space between tabs in tabbar
|
|
tabMargin:2,
|
|
tabOffset:0,
|
|
tabBottomOffset: 10,
|
|
|
|
popupPadding: 8,
|
|
|
|
calendarHeight: 70,
|
|
padding:0
|
|
};
|
|
webix.skin.terrace = {
|
|
topLayout:"space",
|
|
//bar in accordion
|
|
barHeight:37, //!!!Set the same in skin.less!!!
|
|
tabbarHeight: 39,
|
|
rowHeight:38,
|
|
toolbarHeight:22,
|
|
listItemHeight:28, //list, grouplist, dataview, etc.
|
|
inputHeight:30,
|
|
inputPadding: 2,
|
|
menuMargin: 0,
|
|
menuHeight: 32,
|
|
labelTopHeight: 16,
|
|
|
|
inputSpacing: 4,
|
|
borderWidth: 1,
|
|
|
|
sliderHandleWidth: 14,
|
|
sliderPadding: 10,
|
|
sliderBorder: 1,
|
|
vSliderPadding:14,
|
|
vSliderHeight:100,
|
|
|
|
//margin - distance between cells
|
|
layoutMargin:{ space:20, wide:20, clean:0, head:4, line:-1, toolbar:4, form:8},
|
|
//padding - distance insede cell between cell border and cell content
|
|
layoutPadding:{ space:20, wide:0, clean:0, head:0, line:0, toolbar:4, form:8 },
|
|
tabMargin:2,
|
|
tabOffset:0,
|
|
|
|
popupPadding: 8,
|
|
|
|
calendarHeight: 70,
|
|
//space between tabs in tabbar
|
|
padding:17,
|
|
|
|
optionHeight: 24
|
|
};
|
|
webix.skin.metro = {
|
|
topLayout:"space",
|
|
//bar in accordion
|
|
barHeight:36, //!!!Set the same in skin.less!!!
|
|
tabbarHeight: 46,
|
|
rowHeight:34,
|
|
toolbarHeight:36,
|
|
listItemHeight:32, //list, grouplist, dataview, etc.
|
|
inputHeight:30,
|
|
buttonHeight: 45,
|
|
inputPadding: 2,
|
|
menuHeight: 36,
|
|
labelTopHeight: 16,
|
|
|
|
inputSpacing: 4,
|
|
borderWidth: 1,
|
|
|
|
sliderHandleWidth: 14,
|
|
sliderPadding: 10,
|
|
sliderBorder: 1,
|
|
vSliderPadding:13,
|
|
vSliderHeight:100,
|
|
|
|
//margin - distance between cells
|
|
layoutMargin:{ space:10, wide:4, clean:0, head:4, line:-1, toolbar:4, form:8, accordion: 9 },
|
|
//padding - distance insede cell between cell border and cell content
|
|
layoutPadding:{ space:10, wide:0, clean:0, head:0, line:0, toolbar:0, form:8, accordion: 0 },
|
|
//space between tabs in tabbar
|
|
tabMargin:2,
|
|
tabOffset:0,
|
|
tabBottomOffset: 10,
|
|
|
|
popupPadding: 8,
|
|
|
|
calendarHeight: 70,
|
|
padding:0,
|
|
|
|
optionHeight: 23
|
|
};
|
|
webix.skin.light = {
|
|
topLayout:"space",
|
|
//bar in accordion
|
|
barHeight:36, //!!!Set the same in skin.less!!!
|
|
tabbarHeight: 46,
|
|
rowHeight:32,
|
|
toolbarHeight:36,
|
|
listItemHeight:32, //list, grouplist, dataview, etc.
|
|
inputHeight:34,
|
|
buttonHeight: 45,
|
|
inputPadding: 3,
|
|
menuHeight: 36,
|
|
labelTopHeight: 16,
|
|
|
|
inputSpacing: 4,
|
|
borderWidth: 1,
|
|
|
|
sliderHandleWidth: 14,
|
|
sliderPadding: 10,
|
|
sliderBorder: 1,
|
|
vSliderPadding:13,
|
|
vSliderHeight:100,
|
|
|
|
//margin - distance between cells
|
|
layoutMargin:{ space:15, wide:15, clean:0, head:4, line:-1, toolbar:4, form:8, accordion: 10 },
|
|
//padding - distance insede cell between cell border and cell content
|
|
layoutPadding:{ space:15, wide:0, clean:0, head:0, line:0, toolbar:0, form:8, accordion: 0 },
|
|
//space between tabs in tabbar
|
|
tabMargin:2,
|
|
tabOffset:0,
|
|
tabBottomOffset: 10,
|
|
|
|
popupPadding: 8,
|
|
|
|
|
|
calendarHeight: 70,
|
|
padding:0,
|
|
|
|
optionHeight: 27
|
|
};
|
|
webix.skin.glamour = {
|
|
topLayout:"space",
|
|
//bar in accordion
|
|
barHeight:39, //!!!Set the same in skin.less!!!
|
|
tabbarHeight: 39,
|
|
rowHeight:32,
|
|
toolbarHeight:39,
|
|
listItemHeight:32, //list, grouplist, dataview, etc.
|
|
inputHeight:34,
|
|
buttonHeight: 34,
|
|
inputPadding: 3,
|
|
menuHeight: 36,
|
|
labelTopHeight: 16,
|
|
|
|
inputSpacing: 4,
|
|
borderWidth: 1,
|
|
|
|
sliderHandleWidth: 13,
|
|
sliderPadding: 10,
|
|
sliderBorder: 1,
|
|
vSliderPadding:13,
|
|
vSliderHeight:100,
|
|
|
|
//margin - distance between cells
|
|
layoutMargin:{ space:15, wide:15, clean:0, head:4, line:-1, toolbar:4, form:8, accordion: 10 },
|
|
//padding - distance insede cell between cell border and cell content
|
|
layoutPadding:{ space:15, wide:0, clean:0, head:0, line:0, toolbar:3, form:8, accordion: 0 },
|
|
//space between tabs in tabbar
|
|
tabMargin:1,
|
|
tabOffset:0,
|
|
tabBottomOffset: 1,
|
|
|
|
popupPadding: 8,
|
|
|
|
calendarHeight: 70,
|
|
padding:0,
|
|
|
|
optionHeight: 27
|
|
};
|
|
webix.skin.touch = {
|
|
topLayout:"space",
|
|
//bar in accordion
|
|
barHeight:42, //!!!Set the same in skin.less!!!
|
|
tabbarHeight: 50,
|
|
rowHeight:42,
|
|
toolbarHeight: 42,
|
|
listItemHeight:42, //list, grouplist, dataview, etc.
|
|
inputHeight:42,
|
|
inputPadding: 4,
|
|
menuHeight: 42,
|
|
labelTopHeight: 24,
|
|
unitHeaderHeight: 34,
|
|
|
|
inputSpacing: 4,
|
|
borderWidth: 1,
|
|
|
|
sliderHandleWidth: 18,
|
|
sliderPadding: 10,
|
|
sliderBorder: 1,
|
|
vSliderPadding:17,
|
|
vSliderHeight:100,
|
|
|
|
//margin - distance between cells
|
|
layoutMargin:{ space:10, wide:4, clean:0, head:4, line:-1, toolbar:0, form:0, accordion: 9 },
|
|
//padding - distance insede cell between cell border and cell content
|
|
layoutPadding:{ space:10, wide:0, clean:0, head:0, line:0, toolbar:4, form:8, accordion: 0 },
|
|
//space between tabs in tabbar
|
|
tabMargin:2,
|
|
tabOffset:0,
|
|
tabBottomOffset: 10,
|
|
calendar:{headerHeight: 70, timepickerHeight:35, height: 310, width: 300},
|
|
padding:0,
|
|
customCheckbox: true,
|
|
customRadio: true,
|
|
|
|
popupPadding: 8,
|
|
|
|
optionHeight: 32
|
|
};
|
|
webix.skin.flat = {
|
|
topLayout:"space",
|
|
//bar in accordion
|
|
barHeight:46, //!!!Set the same in skin.less!!!
|
|
tabbarHeight: 46,
|
|
rowHeight:34,
|
|
toolbarHeight:46,
|
|
listItemHeight:34, //list, grouplist, dataview, etc.
|
|
inputHeight: 38,
|
|
buttonHeight: 38,
|
|
inputPadding: 3,
|
|
menuHeight: 34,
|
|
labelTopHeight: 22,
|
|
propertyItemHeight: 28,
|
|
|
|
inputSpacing: 4,
|
|
borderWidth: 1,
|
|
|
|
sliderHandleWidth: 14,
|
|
sliderPadding: 10,
|
|
sliderBorder: 1,
|
|
vSliderPadding:15,
|
|
vSliderHeight:100,
|
|
|
|
|
|
//margin - distance between cells
|
|
layoutMargin:{ space:10, wide:10, clean:0, head:4, line:-1, toolbar:4, form:8, accordion: 10 },
|
|
//padding - distance insede cell between cell border and cell content
|
|
layoutPadding:{ space:10, wide:0, clean:0, head:0, line:0, toolbar:4, form:17, accordion: 0 },
|
|
//space between tabs in tabbar
|
|
tabMargin:4,
|
|
tabOffset: 0,
|
|
tabBottomOffset: 6,
|
|
tabTopOffset:1,
|
|
|
|
customCheckbox: true,
|
|
customRadio: true,
|
|
|
|
popupPadding: 8,
|
|
|
|
calendarHeight: 70,
|
|
padding:0,
|
|
accordionType: "accordion",
|
|
|
|
optionHeight: 32
|
|
};
|
|
webix.skin.compact = {
|
|
topLayout:"space",
|
|
//bar in accordion
|
|
barHeight:34, //!!!Set the same in skin.less!!!
|
|
tabbarHeight: 34,
|
|
rowHeight:24,
|
|
toolbarHeight:34,
|
|
listItemHeight:28, //list, grouplist, dataview, etc.
|
|
inputHeight: 30,
|
|
buttonHeight: 30,
|
|
inputPadding: 3,
|
|
menuHeight: 28,
|
|
labelTopHeight: 16,
|
|
|
|
inputSpacing: 4,
|
|
borderWidth: 1,
|
|
|
|
sliderHandleWidth: 12,
|
|
sliderPadding: 10,
|
|
sliderBorder: 1,
|
|
vSliderPadding:13,
|
|
vSliderHeight:100,
|
|
|
|
//margin - distance between cells
|
|
layoutMargin:{ space:5, wide:5, clean:0, head:4, line:-1, toolbar:4, form:4, accordion: 5 },
|
|
//padding - distance insede cell between cell border and cell content
|
|
layoutPadding:{ space:5, wide:0, clean:0, head:0, line:0, toolbar:2, form:12, accordion: 0 },
|
|
//space between tabs in tabbar
|
|
tabMargin:3,
|
|
tabOffset: 0,
|
|
tabBottomOffset: 3,
|
|
tabTopOffset:1,
|
|
|
|
customCheckbox: true,
|
|
customRadio: true,
|
|
|
|
popupPadding: 8,
|
|
|
|
calendarHeight: 70,
|
|
padding:0,
|
|
accordionType: "accordion",
|
|
|
|
optionHeight: 23
|
|
};
|
|
webix.skin.material = {
|
|
topLayout:"space",
|
|
//bar in accordion
|
|
barHeight:45, //!!!Set the same in skin.less!!!
|
|
tabbarHeight:47,
|
|
rowHeight:38,
|
|
toolbarHeight:22,
|
|
listItemHeight:34, //list, grouplist, dataview, etc.
|
|
inputHeight:38,
|
|
buttonHeight:38,
|
|
inputPadding: 2,
|
|
menuMargin: 0,
|
|
menuHeight: 34,
|
|
labelTopHeight: 16,
|
|
propertyItemHeight: 34,
|
|
|
|
inputSpacing: 4,
|
|
borderWidth: 1,
|
|
|
|
sliderHandleWidth: 16,
|
|
sliderPadding: 10,
|
|
sliderBorder: 1,
|
|
|
|
//margin - distance between cells
|
|
layoutMargin:{ material:10, space:10, wide:10, clean:0, head:4, line:-1, toolbar:4, form:16, accordion: 0 },
|
|
//padding - distance insede cell between cell border and cell content
|
|
layoutPadding:{ material:10, space:10, wide:0, clean:0, head:0, line:0, toolbar:4, form:16, accordion: 0 },
|
|
//space between tabs in tabbar
|
|
tabMargin:0,
|
|
tabOffset: 0,
|
|
tabBottomOffset: 0,
|
|
tabTopOffset:0,
|
|
|
|
customCheckbox: true,
|
|
customRadio: true,
|
|
|
|
popupPadding: 8,
|
|
|
|
calendarHeight: 70,
|
|
padding:0,
|
|
accordionType: "accordion"
|
|
};
|
|
webix.skin.contrast = {
|
|
topLayout:"space",
|
|
//bar in accordion
|
|
barHeight:46, // !!!Set the same in skin.less!!!
|
|
tabbarHeight: 46,
|
|
rowHeight:34,
|
|
toolbarHeight:46,
|
|
listItemHeight:34, // list, grouplist, dataview, etc.
|
|
inputHeight: 38,
|
|
buttonHeight: 38,
|
|
inputPadding: 3,
|
|
menuHeight: 34,
|
|
labelTopHeight: 22,
|
|
propertyItemHeight: 28,
|
|
|
|
inputSpacing: 4,
|
|
borderWidth: 1,
|
|
|
|
sliderHandleWidth: 14,
|
|
sliderPadding: 10,
|
|
sliderBorder: 1,
|
|
vSliderPadding:15,
|
|
vSliderHeight:100,
|
|
|
|
//margin - distance between cells
|
|
layoutMargin:{ space:10, wide:10, clean:0, head:4, line:-1, toolbar:8, form:8, accordion: 10 },
|
|
//padding - distance insede cell between cell border and cell content
|
|
layoutPadding:{ space:10, wide:0, clean:0, head:0, line:0, toolbar:4, form:17, accordion: 0 },
|
|
//space between tabs in tabbar
|
|
tabMargin:4,
|
|
tabOffset: 0,
|
|
tabBottomOffset: 6,
|
|
tabTopOffset:1,
|
|
|
|
customCheckbox: true,
|
|
customRadio: true,
|
|
|
|
popupPadding: 8,
|
|
|
|
calendarHeight: 70,
|
|
padding:0,
|
|
accordionType: "accordion",
|
|
|
|
optionHeight: 32
|
|
};
|
|
|
|
webix.skin.set = function(name){
|
|
webix.assert(webix.skin[name], "Incorrect skin name: "+name);
|
|
|
|
webix.skin.$active = webix.skin[name];
|
|
webix.skin.$name = name;
|
|
if (webix.ui){
|
|
for (var key in webix.ui){
|
|
var view = webix.ui[key];
|
|
if (view && view.prototype && view.prototype.$skin)
|
|
view.prototype.$skin(view.prototype);
|
|
}
|
|
}
|
|
};
|
|
webix.skin.set(window.webix_skin || "flat");
|
|
|
|
/*
|
|
Behavior:Destruction
|
|
|
|
@export
|
|
destructor
|
|
*/
|
|
|
|
|
|
|
|
webix.Destruction = {
|
|
$init:function(){
|
|
//wrap in object to simplify removing self-reference
|
|
var t = this._destructor_handler = { obj: this};
|
|
|
|
//register self in global list of destructors
|
|
webix.destructors.push(t);
|
|
},
|
|
//will be called automatically on unload, can be called manually
|
|
//simplifies job of GC
|
|
destructor:function(){
|
|
var config = this._settings;
|
|
|
|
if (this._last_editor)
|
|
this.editCancel();
|
|
|
|
if(this.callEvent)
|
|
this.callEvent("onDestruct",[]);
|
|
|
|
//destructor can be called only once
|
|
this.destructor=function(){};
|
|
//remove self reference from global destructions collection
|
|
this._destructor_handler.obj = null;
|
|
|
|
//destroy child and related cells
|
|
if (this.getChildViews){
|
|
var cells = this.getChildViews();
|
|
if (cells)
|
|
for (var i=0; i < cells.length; i++)
|
|
cells[i].destructor();
|
|
|
|
if (this._destroy_with_me)
|
|
for (var i=0; i < this._destroy_with_me.length; i++)
|
|
this._destroy_with_me[i].destructor();
|
|
}
|
|
|
|
delete webix.ui.views[config.id];
|
|
|
|
if (config.$id){
|
|
var top = this.getTopParentView();
|
|
if (top && top._destroy_child)
|
|
top._destroy_child(config.$id);
|
|
}
|
|
|
|
//html collection
|
|
this._htmlmap = null;
|
|
this._htmlrows = null;
|
|
this._html = null;
|
|
|
|
|
|
if (this._contentobj) {
|
|
this._contentobj.innerHTML="";
|
|
this._contentobj._htmlmap = null;
|
|
}
|
|
|
|
//removes view container
|
|
if (this._viewobj&&this._viewobj.parentNode){
|
|
this._viewobj.parentNode.removeChild(this._viewobj);
|
|
}
|
|
|
|
if (this.data && this.data.destructor)
|
|
this.data.destructor();
|
|
|
|
if (this.unbind)
|
|
this.unbind();
|
|
|
|
this.data = null;
|
|
this._viewobj = this.$view = this._contentobj = this._dataobj = null;
|
|
this._evs_events = this._evs_handlers = {};
|
|
|
|
//remove focus from destructed view
|
|
if (webix.UIManager._view == this)
|
|
webix.UIManager._view = null;
|
|
|
|
var url = config.url;
|
|
if (url && url.$proxy && url.release)
|
|
url.release();
|
|
|
|
this.$scope = null;
|
|
// this flag is checked in delay method
|
|
this.$destructed = true;
|
|
}
|
|
};
|
|
//global list of destructors
|
|
webix.destructors = [];
|
|
webix.event(window,"unload",function(){
|
|
webix.callEvent("unload", []);
|
|
webix._final_destruction = true;
|
|
|
|
//call all registered destructors
|
|
for (var i=0; i<webix.destructors.length; i++){
|
|
var obj = webix.destructors[i].obj;
|
|
if (obj)
|
|
obj.destructor();
|
|
}
|
|
webix.destructors = [];
|
|
webix.ui._popups = webix.toArray();
|
|
|
|
//detach all known DOM events
|
|
for (var a in webix._events)
|
|
webix.eventRemove(a);
|
|
});
|
|
|
|
/*
|
|
Behavior:Settings
|
|
|
|
@export
|
|
customize
|
|
config
|
|
*/
|
|
|
|
/*
|
|
Template - handles html templates
|
|
*/
|
|
|
|
|
|
|
|
(function(){
|
|
|
|
var _cache = {};
|
|
var _csp_cache = {};
|
|
var newlines = new RegExp("(\\r\\n|\\n)","g");
|
|
var quotes = new RegExp("(\\\")","g");
|
|
var slashes = new RegExp("(\\\\)","g");
|
|
var escape = {
|
|
"&": "&",
|
|
"<": "<",
|
|
">": ">",
|
|
'"': """,
|
|
"'": "'",
|
|
"`": "`"
|
|
};
|
|
var badChars = /[&<>"'`]/g;
|
|
var escapeChar = function(chr) {
|
|
return escape[chr] || "&";
|
|
};
|
|
|
|
|
|
webix.template = function(str){
|
|
if (typeof str == "function") return str;
|
|
if (_cache[str])
|
|
return _cache[str];
|
|
|
|
str=(str||"").toString();
|
|
if (str.indexOf("->")!=-1){
|
|
var teststr = str.split("->");
|
|
switch(teststr[0]){
|
|
case "html": //load from some container on the page
|
|
str = webix.html.getValue(teststr[1]);
|
|
break;
|
|
case "http": //load from external file
|
|
str = new webix.ajax().sync().get(teststr[1],{uid:webix.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();
|
|
|
|
// Content Security Policy enabled
|
|
if(webix.env.strict){
|
|
if (!_csp_cache[str]){
|
|
_csp_cache[str] = [];
|
|
|
|
// get an array of objects (not sorted by position)
|
|
var temp_res = [];
|
|
str.replace(/\{obj\.([^}?]+)\?([^:]*):([^}]*)\}/g,function(search,s1,s2,s3,pos){
|
|
temp_res.push({pos: pos, str: search, fn: function(obj,common){
|
|
return obj[s1]?s2:s3;
|
|
}});
|
|
});
|
|
str.replace(/\{common\.([^}\(]*)\}/g,function(search,s,pos){
|
|
temp_res.push({pos: pos, str: search, fn: function(obj,common){
|
|
return common[s]||'';
|
|
}});
|
|
});
|
|
str.replace(/\{common\.([^\}\(]*)\(\)\}/g,function(search,s,pos){
|
|
temp_res.push({pos: pos, str: search, fn: function(obj,common){
|
|
return (common[s]?common[s].apply(this, arguments):"");
|
|
}});
|
|
});
|
|
str.replace(/\{obj\.([^:}]*)\}/g,function(search,s,pos){
|
|
temp_res.push({pos: pos, str: search, fn: function(obj,common){
|
|
return obj[s];
|
|
}});
|
|
});
|
|
str.replace("{obj}",function(search,s,pos){
|
|
temp_res.push({pos: pos, str: search, fn: function(obj,common){
|
|
return obj;
|
|
}});
|
|
});
|
|
str.replace(/#([^#'";, ]+)#/gi,function(search,s,pos){
|
|
if(s.charAt(0)=="!"){
|
|
s = s.substr(1);
|
|
temp_res.push({pos: pos, str: search, fn: function(obj,common){
|
|
if(s.indexOf(".")!= -1)
|
|
obj = webix.CodeParser.collapseNames(obj); // apply complex properties
|
|
return webix.template.escape(obj[s]);
|
|
}});
|
|
}
|
|
else{
|
|
temp_res.push({pos: pos, str: search, fn: function(obj,common){
|
|
if(s.indexOf(".")!= -1)
|
|
obj = webix.CodeParser.collapseNames(obj); // apply complex properties
|
|
return obj[s];
|
|
}});
|
|
}
|
|
|
|
});
|
|
|
|
// sort template parts by position
|
|
temp_res.sort(function(a,b){
|
|
return (a.pos > b.pos)?1:-1;
|
|
});
|
|
|
|
// create an array of functions that return parts of html string
|
|
if(temp_res.length){
|
|
var lastPos = 0;
|
|
var addStr = function(str,n0,n1){
|
|
_csp_cache[str].push(function(){
|
|
return str.slice(n0,n1);
|
|
});
|
|
};
|
|
for(var i = 0; i< temp_res.length; i++){
|
|
var pos = temp_res[i].pos;
|
|
addStr(str,lastPos,pos);
|
|
_csp_cache[str].push(temp_res[i].fn);
|
|
lastPos = pos + temp_res[i].str.length;
|
|
}
|
|
addStr(str,lastPos,str.length);
|
|
}
|
|
else
|
|
_csp_cache[str].push(function(){return str;});
|
|
}
|
|
return function(){
|
|
var s = "";
|
|
for(var i=0; i < _csp_cache[str].length;i++){
|
|
s += _csp_cache[str][i].apply(this,arguments);
|
|
}
|
|
return s;
|
|
};
|
|
}
|
|
|
|
str=str.replace(slashes,"\\\\");
|
|
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,function(str, key){
|
|
if (key.charAt(0)=="!")
|
|
return "\"+webix.template.escape(obj."+key.substr(1)+")+\"";
|
|
else
|
|
return "\"+(obj."+key+")+\"";
|
|
});
|
|
|
|
try {
|
|
_cache[str] = Function("obj","common","return \""+str+"\";");
|
|
} catch(e){
|
|
webix.assert_error("Invalid template:"+str);
|
|
}
|
|
|
|
return _cache[str];
|
|
};
|
|
|
|
|
|
|
|
webix.template.escape = function(str){
|
|
if (str === webix.undefined || str === null) return "";
|
|
return (str.toString() || "" ).replace(badChars, escapeChar);
|
|
};
|
|
webix.template.empty=function(){ return ""; };
|
|
webix.template.bind =function(value){ return webix.bind(webix.template(value),this); };
|
|
|
|
|
|
/*
|
|
adds new template-type
|
|
obj - object to which template will be added
|
|
data - properties of template
|
|
*/
|
|
webix.type=function(obj, data){
|
|
if (obj.$protoWait){
|
|
if (!obj._webix_type_wait)
|
|
obj._webix_type_wait = [];
|
|
obj._webix_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] = webix.clone(data.baseType?obj.types[data.baseType]:obj.type);
|
|
|
|
for(var key in data){
|
|
if (key.indexOf("template")===0)
|
|
type[key] = webix.template(data[key]);
|
|
else
|
|
type[key]=data[key];
|
|
}
|
|
|
|
return name;
|
|
};
|
|
|
|
})();
|
|
|
|
|
|
webix.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 = webix.extend(settings,initial);
|
|
|
|
//code below will copy all properties over default one
|
|
if (typeof obj == "object" && !obj.tagName)
|
|
webix.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;
|
|
}
|
|
};
|
|
/*
|
|
ajax operations
|
|
|
|
can be used for direct loading as
|
|
webix.ajax(ulr, callback)
|
|
or
|
|
webix.ajax().getItem(url)
|
|
webix.ajax().post(url)
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
webix.proxy = function(name, source, extra){
|
|
webix.assert(webix.proxy[name], "Invalid proxy name: "+name);
|
|
|
|
var copy = webix.copy(webix.proxy[name]);
|
|
copy.source = source;
|
|
|
|
if (extra)
|
|
webix.extend(copy, extra, true);
|
|
|
|
if (copy.init) copy.init();
|
|
return copy;
|
|
};
|
|
|
|
webix.proxy.$parse = function(value){
|
|
if (typeof value == "string" && value.indexOf("->") != -1){
|
|
var parts = value.split("->");
|
|
return webix.proxy(parts[0], parts[1]);
|
|
}
|
|
return value;
|
|
};
|
|
|
|
webix.proxy.post = {
|
|
$proxy:true,
|
|
load:function(view, callback, params){
|
|
params = webix.extend(params||{}, this.params || {}, true);
|
|
webix.ajax().bind(view).post(this.source, params, callback);
|
|
}
|
|
};
|
|
|
|
webix.proxy.sync = {
|
|
$proxy:true,
|
|
load:function(view, callback){
|
|
webix.ajax().sync().bind(view).get(this.source, null, callback);
|
|
}
|
|
};
|
|
|
|
webix.proxy.connector = {
|
|
$proxy:true,
|
|
|
|
connectorName:"!nativeeditor_status",
|
|
load:function(view, callback){
|
|
webix.ajax(this.source, callback, view);
|
|
},
|
|
saveAll:function(view, updates, dp, callback){
|
|
var url = this.source;
|
|
|
|
var data = {};
|
|
var ids = [];
|
|
for (var i = 0; i < updates.length; i++) {
|
|
var action = updates[i];
|
|
ids.push(action.id);
|
|
|
|
for (var j in action.data)
|
|
if (j.indexOf("$")!==0)
|
|
data[action.id+"_"+j] = action.data[j];
|
|
data[action.id+"_"+this.connectorName] = action.operation;
|
|
}
|
|
|
|
data.ids = ids.join(",");
|
|
data.webix_security = webix.securityKey;
|
|
|
|
url += (url.indexOf("?") == -1) ? "?" : "&";
|
|
url += "editing=true";
|
|
|
|
webix.ajax().post(url, data, callback);
|
|
},
|
|
result:function(state, view, dp, text, data, loader){
|
|
data = data.xml();
|
|
if (!data)
|
|
return dp._processError(null, text, data, loader);
|
|
|
|
|
|
var actions = data.data.action;
|
|
if (!actions.length)
|
|
actions = [actions];
|
|
|
|
|
|
var hash = [];
|
|
|
|
for (var i = 0; i < actions.length; i++) {
|
|
var obj = actions[i];
|
|
hash.push(obj);
|
|
|
|
obj.status = obj.type;
|
|
obj.id = obj.sid;
|
|
obj.newid = obj.tid;
|
|
|
|
dp.processResult(obj, obj, {text:text, data:data, loader:loader});
|
|
}
|
|
|
|
return hash;
|
|
}
|
|
};
|
|
|
|
webix.proxy.debug = {
|
|
$proxy:true,
|
|
load:function(){},
|
|
save:function(v,u,d,c){
|
|
webix.delay(function(){
|
|
window.console.log("[DP] "+u.id+" -> "+u.operation, u.data);
|
|
var data = {
|
|
id:u.data.id,
|
|
newid:u.data.id,
|
|
status:u.data.operation
|
|
};
|
|
d.processResult(data, data);
|
|
});
|
|
}
|
|
};
|
|
|
|
webix.proxy.rest = {
|
|
$proxy:true,
|
|
load:function(view, callback){
|
|
webix.ajax(this.source, callback, view);
|
|
},
|
|
save:function(view, update, dp, callback){
|
|
return webix.proxy.rest._save_logic.call(this, view, update, dp, callback, webix.ajax());
|
|
},
|
|
_save_logic:function(view, update, dp, callback, ajax){
|
|
var url = this.source;
|
|
var query = "";
|
|
var mark = url.indexOf("?");
|
|
|
|
if (mark !== -1){
|
|
query = url.substr(mark);
|
|
url = url.substr(0, mark);
|
|
}
|
|
|
|
url += url.charAt(url.length-1) == "/" ? "" : "/";
|
|
var mode = update.operation;
|
|
|
|
|
|
var data = update.data;
|
|
if (mode == "insert") delete data.id;
|
|
|
|
//call rest URI
|
|
if (mode == "update"){
|
|
ajax.put(url + data.id + query, data, callback);
|
|
} else if (mode == "delete") {
|
|
ajax.del(url + data.id + query, data, callback);
|
|
} else {
|
|
ajax.post(url + query, data, callback);
|
|
}
|
|
}
|
|
};
|
|
|
|
webix.proxy.json = {
|
|
$proxy:true,
|
|
load:function(view, callback){
|
|
webix.ajax(this.source, callback, view);
|
|
},
|
|
save:function(view, update, dp, callback){
|
|
var ajax = webix.ajax().headers({ "Content-Type":"application/json" });
|
|
return webix.proxy.rest._save_logic.call(this, view, update, dp, callback, ajax);
|
|
}
|
|
};
|
|
|
|
webix.proxy.faye = {
|
|
$proxy:true,
|
|
init:function(){
|
|
this.clientId = this.clientId || webix.uid();
|
|
},
|
|
load:function(view){
|
|
var selfid = this.clientId;
|
|
|
|
this.client.subscribe(this.source, function(update){
|
|
if (update.clientId == selfid) return;
|
|
|
|
webix.dp(view).ignore(function(){
|
|
if (update.operation == "delete")
|
|
view.remove(update.data.id);
|
|
else if (update.operation == "insert")
|
|
view.add(update.data);
|
|
else if (update.operation == "update"){
|
|
var item = view.getItem(update.data.id);
|
|
if (item){
|
|
webix.extend(item, update.data, true);
|
|
view.refresh(item.id);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
},
|
|
save:function(view, update, dp, callback){
|
|
update.clientId = this.clientId;
|
|
this.client.publish(this.source, update);
|
|
}
|
|
};
|
|
|
|
//indexdb->database/collection
|
|
webix.proxy.indexdb = {
|
|
$proxy:true,
|
|
create:function(db, config, version, callback){
|
|
this.source = db + "/";
|
|
this._get_db(callback, version, function(e){
|
|
var db = e.target.result;
|
|
for (var key in config){
|
|
var data = config[key];
|
|
var store = db.createObjectStore(key, { keyPath: "id", autoIncrement:true });
|
|
for (var i = 0; i < data.length; i++)
|
|
store.put(data[i]);
|
|
}
|
|
});
|
|
},
|
|
_get_db:function(callback, version, upgrade){
|
|
if (this.source.indexOf("/") != -1){
|
|
var parts = this.source.split("/");
|
|
this.source = parts[1];
|
|
version = version || parts[2];
|
|
|
|
var _index = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;
|
|
|
|
var db;
|
|
if (version)
|
|
db = _index.open(parts[0], version);
|
|
else
|
|
db = _index.open(parts[0]);
|
|
|
|
if (upgrade)
|
|
db.onupgradeneeded = upgrade;
|
|
db.onerror = function(){ };
|
|
db.onblocked = function(){ };
|
|
db.onsuccess = webix.bind(function(e){
|
|
this.db = e.target.result;
|
|
if (callback)
|
|
callback.call(this);
|
|
},this);
|
|
} else if (this.db)
|
|
callback.call(this);
|
|
else
|
|
webix.delay(this._get_db, this, [callback], 50);
|
|
},
|
|
|
|
load:function(view, callback){
|
|
this._get_db(function(){
|
|
var store = this.db.transaction(this.source).objectStore(this.source);
|
|
var data = [];
|
|
|
|
store.openCursor().onsuccess = function(e) {
|
|
var result = e.target.result;
|
|
if(result){
|
|
data.push(result.value);
|
|
result["continue"]();
|
|
} else {
|
|
view.parse(data);
|
|
webix.ajax.$callback(view, callback, "[]", data);
|
|
}
|
|
};
|
|
});
|
|
},
|
|
save:function(view, update, dp, callback){
|
|
this._get_db(function(){
|
|
var mode = update.operation;
|
|
var data = update.data;
|
|
var id = update.id;
|
|
|
|
var store = this.db.transaction([this.source], "readwrite").objectStore(this.source);
|
|
|
|
var req;
|
|
if (mode == "delete")
|
|
req = store["delete"](id);
|
|
else if (mode == "update")
|
|
req = store.put(data);
|
|
else if (mode == "insert"){
|
|
delete data.id;
|
|
req = store.add(data);
|
|
}
|
|
|
|
req.onsuccess = function(e) {
|
|
var result = { status: mode, id:update.id };
|
|
if (mode == "insert")
|
|
result.newid = e.target.result;
|
|
dp.processResult(result, result);
|
|
};
|
|
});
|
|
}
|
|
};
|
|
|
|
webix.proxy.binary = {
|
|
$proxy:true,
|
|
load:function(view, callback){
|
|
var parts = this.source.split("@");
|
|
var ext = parts[0].split(".").pop();
|
|
return webix.ajax().response("arraybuffer").get(parts[0]).then(function(res){
|
|
var options = { ext:ext, dataurl : parts[1] };
|
|
webix.ajax.$callback(view, callback, "", { data:res, options:options }, -1);
|
|
});
|
|
}
|
|
};
|
|
|
|
webix.ajax = function(url,params,call){
|
|
//if parameters was provided - made fast call
|
|
if (arguments.length!==0)
|
|
return (new webix.ajax()).get(url,params,call);
|
|
|
|
if (!this.getXHR) return new webix.ajax(); //allow to create new instance without direct new declaration
|
|
|
|
return this;
|
|
};
|
|
webix.ajax.count = 0;
|
|
webix.ajax.prototype={
|
|
master:null,
|
|
//creates xmlHTTP object
|
|
getXHR:function(){
|
|
return new XMLHttpRequest();
|
|
},
|
|
stringify:function(obj){
|
|
return webix.stringify(obj);
|
|
},
|
|
/*
|
|
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, mode){
|
|
var master;
|
|
if (params && (webix.isArray(params) || (typeof (params.success || params.error || params) == "function"))){
|
|
master = call;
|
|
call = params;
|
|
params = null;
|
|
}
|
|
|
|
var defer = webix.promise.defer();
|
|
|
|
var x=this.getXHR();
|
|
if (!webix.isArray(call))
|
|
call = [call];
|
|
|
|
call.push({ success: function(t, d){ defer.resolve(d); },
|
|
error: function(t, d){ defer.reject(x); }});
|
|
|
|
var headers = this._header || {};
|
|
|
|
if (!webix.callEvent("onBeforeAjax", [mode, url, params, x, headers, null, defer])) return;
|
|
|
|
//add content-type to POST|PUT|DELETE
|
|
var json_mode = false;
|
|
if (mode !== 'GET'){
|
|
var found = false;
|
|
for (var key in headers)
|
|
if (key.toString().toLowerCase() == "content-type"){
|
|
found = true;
|
|
if (headers[key] == "application/json")
|
|
json_mode = true;
|
|
}
|
|
if (!found)
|
|
headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
|
}
|
|
|
|
//add extra params to the url
|
|
if (typeof params == "object" && !(window.FormData && (params instanceof window.FormData))){
|
|
if (json_mode)
|
|
params = this.stringify(params);
|
|
else {
|
|
var t=[];
|
|
for (var a in params){
|
|
var value = params[a];
|
|
if (value === null || value === webix.undefined)
|
|
value = "";
|
|
if(typeof value==="object")
|
|
value = this.stringify(value);
|
|
t.push(a+"="+encodeURIComponent(value));// utf-8 escaping
|
|
}
|
|
params=t.join("&");
|
|
}
|
|
}
|
|
|
|
if (params && mode==='GET'){
|
|
url=url+(url.indexOf("?")!=-1 ? "&" : "?")+params;
|
|
params = null;
|
|
}
|
|
|
|
x.open(mode, url, !this._sync);
|
|
|
|
var type = this._response;
|
|
if (type) x.responseType = type;
|
|
|
|
//if header was provided - use it
|
|
for (var key in headers)
|
|
x.setRequestHeader(key, headers[key]);
|
|
|
|
//async mode, define loading callback
|
|
var self=this;
|
|
this.master = this.master || master;
|
|
x.onreadystatechange = function(){
|
|
if (!x.readyState || x.readyState == 4){
|
|
if (webix.debug_time) webix.log_full_time("data_loading"); //log rendering time
|
|
|
|
webix.ajax.count++;
|
|
if (call && self && !x.aborted){
|
|
//IE8 and IE9, handling .abort call
|
|
if (webix._xhr_aborted.find(x) != -1)
|
|
return webix._xhr_aborted.remove(x);
|
|
|
|
var owner = self.master||self;
|
|
|
|
var is_error = x.status >= 400 || x.status === 0;
|
|
var text, data;
|
|
if (x.responseType == "blob" || x.responseType == "arraybuffer"){
|
|
text = "";
|
|
data = x.response;
|
|
} else {
|
|
text = x.responseText||"";
|
|
data = self._data(x);
|
|
}
|
|
|
|
webix.ajax.$callback(owner, call, text, data, x, is_error);
|
|
}
|
|
if (self) self.master=null;
|
|
call=self=master=null; //anti-leak
|
|
}
|
|
};
|
|
|
|
if (this._timeout)
|
|
x.timeout = this._timeout;
|
|
|
|
//IE can use sync mode sometimes, fix it
|
|
if (!this._sync)
|
|
setTimeout(function(){
|
|
if (!x.aborted){
|
|
//abort handling in IE9
|
|
if (webix._xhr_aborted.find(x) != -1)
|
|
webix._xhr_aborted.remove(x);
|
|
else
|
|
x.send(params||null);
|
|
}
|
|
}, 1);
|
|
else
|
|
x.send(params||null);
|
|
|
|
if (this.master && this.master._ajax_queue)
|
|
this.master._ajax_queue.push(x);
|
|
|
|
return this._sync?x:defer; //return XHR, which can be used in case of sync. mode
|
|
},
|
|
_data:function(x){
|
|
return {
|
|
xml:function(){
|
|
try{
|
|
return webix.DataDriver.xml.tagToObject(webix.DataDriver.xml.toObject(x.responseText, this));
|
|
}
|
|
catch(e){
|
|
webix.log(x.responseText);
|
|
webix.log(e.toString()); webix.assert_error("Invalid xml data for parsing");
|
|
}
|
|
},
|
|
rawxml:function(){
|
|
if (!window.XPathResult)
|
|
return webix.DataDriver.xml.fromString(x.responseText);
|
|
return x.responseXML;
|
|
},
|
|
text:function(){ return x.responseText; },
|
|
json:function(){
|
|
return webix.DataDriver.json.toObject(x.responseText, false);
|
|
}
|
|
};
|
|
},
|
|
//GET request
|
|
get:function(url,params,call){
|
|
return this._send(url,params,call,"GET");
|
|
},
|
|
//POST request
|
|
post:function(url,params,call){
|
|
return this._send(url,params,call,"POST");
|
|
},
|
|
//PUT request
|
|
put:function(url,params,call){
|
|
return this._send(url,params,call,"PUT");
|
|
},
|
|
//DELETE request
|
|
del:function(url,params,call){
|
|
return this._send(url,params,call,"DELETE");
|
|
},
|
|
//PATCH request
|
|
patch:function(url,params,call){
|
|
return this._send(url,params,call,"PATCH");
|
|
},
|
|
|
|
sync:function(){
|
|
this._sync = true;
|
|
return this;
|
|
},
|
|
timeout:function(num){
|
|
this._timeout = num;
|
|
return this;
|
|
},
|
|
response:function(value){
|
|
this._response = value;
|
|
return this;
|
|
},
|
|
//deprecated, remove in 3.0
|
|
//[DEPRECATED]
|
|
header:function(header){
|
|
webix.assert(false, "ajax.header is deprecated in favor of ajax.headers");
|
|
this._header = header;
|
|
return this;
|
|
},
|
|
headers:function(header){
|
|
this._header = webix.extend(this._header||{},header);
|
|
return this;
|
|
},
|
|
bind:function(master){
|
|
this.master = master;
|
|
return this;
|
|
}
|
|
};
|
|
webix.ajax.$callback = function(owner, call, text, data, x, is_error){
|
|
if (owner.$destructed) return;
|
|
if (x === -1 && data && typeof data.json == "function")
|
|
data = data.json();
|
|
|
|
if (is_error)
|
|
webix.callEvent("onAjaxError", [x]);
|
|
|
|
if (!webix.isArray(call))
|
|
call = [call];
|
|
|
|
if (!is_error)
|
|
for (var i=0; i < call.length; i++){
|
|
if (call[i]){
|
|
var before = call[i].before;
|
|
if (before)
|
|
before.call(owner, text, data, x);
|
|
}
|
|
}
|
|
|
|
for (var i=0; i < call.length; i++) //there can be multiple callbacks
|
|
if (call[i]){
|
|
var method = (call[i].success||call[i]);
|
|
if (is_error)
|
|
method = call[i].error;
|
|
if (method && method.call)
|
|
method.call(owner,text,data,x);
|
|
}
|
|
};
|
|
|
|
/*submits values*/
|
|
webix.send = function(url, values, method, target){
|
|
var form = webix.html.create("FORM",{
|
|
"target":(target||"_self"),
|
|
"action":url,
|
|
"method":(method||"POST")
|
|
},"");
|
|
for (var k in values) {
|
|
var field = webix.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);
|
|
};
|
|
|
|
|
|
webix.AtomDataLoader={
|
|
$init:function(config){
|
|
//prepare data store
|
|
this.data = {};
|
|
this.waitData = webix.promise.defer();
|
|
|
|
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){
|
|
value = webix.proxy.$parse(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){
|
|
var details = arguments[2] || null;
|
|
|
|
if(!this.callEvent("onBeforeLoad",[]))
|
|
return webix.promise.reject();
|
|
|
|
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 = webix.DataDriver[call];
|
|
call = arguments[2];
|
|
} else if (!this.data.driver)
|
|
this.data.driver = webix.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 (webix.isArray(call))
|
|
callback.push.apply(callback,call);
|
|
else
|
|
callback.push(call);
|
|
}
|
|
|
|
//proxy
|
|
url = webix.proxy.$parse(url);
|
|
if (url.$proxy && url.load)
|
|
return url.load(this, callback, details);
|
|
|
|
//promize
|
|
if (typeof url === "function"){
|
|
return url(details).then(
|
|
webix.bind(function(data){
|
|
webix.ajax.$callback(this, callback, "", data, -1);
|
|
}, this),
|
|
webix.bind(function(x){
|
|
webix.ajax.$callback(this, callback, "", null, x, true);
|
|
}, this)
|
|
);
|
|
}
|
|
|
|
//normal url
|
|
return webix.ajax(url,callback,this);
|
|
},
|
|
//loads data from object
|
|
parse:function(data,type){
|
|
//[webix.remote]
|
|
if (data && data.then && typeof data.then == "function"){
|
|
return data.then(webix.bind(function(data){
|
|
if (data && typeof data.json == "function")
|
|
data = data.json();
|
|
this.parse(data, type);
|
|
}, this));
|
|
}
|
|
|
|
//loading data from other component
|
|
if (data && data.sync && this.sync)
|
|
return this._syncData(data);
|
|
|
|
if(!this.callEvent("onBeforeLoad",[]))
|
|
return webix.promise.reject();
|
|
|
|
this.data.driver = webix.DataDriver[type||"json"];
|
|
this._onLoad(data,null);
|
|
},
|
|
_syncData: function(data){
|
|
if(this.data)
|
|
this.data.attachEvent("onSyncApply",webix.bind(function(){
|
|
if(this._call_onready)
|
|
this._call_onready();
|
|
},this));
|
|
|
|
this.sync(data);
|
|
},
|
|
_parse:function(data){
|
|
var parsed, record,
|
|
driver = this.data.driver;
|
|
|
|
record = driver.getRecords(data)[0];
|
|
parsed = record?driver.getDetails(record):{};
|
|
|
|
if (this.setValues)
|
|
this.setValues(parsed);
|
|
else
|
|
this.data = parsed;
|
|
},
|
|
_onLoadContinue:function(data, text, response, loader){
|
|
if (data){
|
|
if(!this.$onLoad || !this.$onLoad(data, this.data.driver)){
|
|
if(this.data && this.data._parse)
|
|
this.data._parse(data); //datastore
|
|
else
|
|
this._parse(data);
|
|
}
|
|
}
|
|
else
|
|
this._onLoadError(text, response, loader);
|
|
|
|
//data loaded, view rendered, call onready handler
|
|
if(this._call_onready)
|
|
this._call_onready();
|
|
|
|
this.callEvent("onAfterLoad",[]);
|
|
this.waitData.resolve();
|
|
},
|
|
//default after loading callback
|
|
_onLoad:function(text, response, loader){
|
|
var driver = this.data.driver;
|
|
var data;
|
|
|
|
if (loader === -1)
|
|
data = driver.toObject(response);
|
|
else{
|
|
//ignore data loading command if data was reloaded
|
|
if(this._ajax_queue)
|
|
this._ajax_queue.remove(loader);
|
|
data = driver.toObject(text, response);
|
|
}
|
|
|
|
if(!data || !data.then)
|
|
this._onLoadContinue(data);
|
|
else if(data.then && typeof data.then == "function")
|
|
data.then(webix.bind(this._onLoadContinue, this));
|
|
},
|
|
_onLoadError:function(text, xml, xhttp){
|
|
this.callEvent("onAfterLoad",[]);
|
|
this.callEvent("onLoadError",arguments);
|
|
webix.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);
|
|
if(!this.callEvent("onBeforeLoad",[]))
|
|
return false;
|
|
webix.ajax(url, function(text,xml,loader){
|
|
this._ignore_feed=true;
|
|
var driver = webix.DataDriver.json;
|
|
var data = driver.toObject(text, xml);
|
|
if (data)
|
|
this.setValues(driver.getDetails(driver.getRecords(data)[0]));
|
|
else
|
|
this._onLoadError(text,xml,loader);
|
|
this._ignore_feed=false;
|
|
this.callEvent("onAfterLoad",[]);
|
|
}, this);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
/*
|
|
Abstraction layer for different data types
|
|
*/
|
|
|
|
webix.DataDriver={};
|
|
webix.DataDriver.json={
|
|
//convert json string to json object if necessary
|
|
toObject:function(data){
|
|
if (!data) return null;
|
|
if (typeof data == "string"){
|
|
try{
|
|
if (this.parseDates){
|
|
var isodate = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(.\d{1-3})?Z/;
|
|
data = JSON.parse(data, function(key, value){
|
|
if (typeof value == "string"){
|
|
if (isodate.test(value))
|
|
return new Date(value);
|
|
}
|
|
return value;
|
|
});
|
|
} else {
|
|
data =JSON.parse(data);
|
|
}
|
|
} catch(e){
|
|
webix.log(e);
|
|
webix.log(data);
|
|
webix.assert_error("Invalid JSON data for parsing");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
return data;
|
|
},
|
|
//get array of records
|
|
getRecords:function(data){
|
|
if (data && data.data)
|
|
data = data.data;
|
|
|
|
if (data && !webix.isArray(data))
|
|
return [data];
|
|
return data;
|
|
},
|
|
//get hash of properties for single record
|
|
getDetails:function(data){
|
|
if (typeof data == "string")
|
|
return { id:(data||webix.uid()), value:data };
|
|
return data;
|
|
},
|
|
getOptions:function(data){
|
|
return data.collections;
|
|
},
|
|
//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),
|
|
parent:(data.parent||0),
|
|
config:(data.config),
|
|
key:(data.webix_security)
|
|
};
|
|
},
|
|
child:"data",
|
|
parseDates:false
|
|
};
|
|
|
|
webix.DataDriver.html={
|
|
/*
|
|
incoming data can be
|
|
- 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 = webix.toNode(data);
|
|
if (!t){
|
|
t=document.createElement("DIV");
|
|
t.innerHTML = data;
|
|
}
|
|
|
|
return t.firstChild;
|
|
}
|
|
return data;
|
|
},
|
|
//get array of records
|
|
getRecords:function(node){
|
|
return node.getElementsByTagName(this.tag);
|
|
},
|
|
//get hash of properties for single record
|
|
getDetails:function(data){
|
|
return webix.DataDriver.xml.tagToObject(data);
|
|
},
|
|
getOptions:function(){
|
|
return false;
|
|
},
|
|
//dyn loading is not supported by HTML data source
|
|
getInfo:function(data){
|
|
return {
|
|
size:0,
|
|
from:0
|
|
};
|
|
},
|
|
tag: "LI"
|
|
};
|
|
|
|
webix.DataDriver.jsarray={
|
|
//parse jsarray string to jsarray object if necessary
|
|
toObject:function(data){
|
|
if (typeof data == "string")
|
|
return JSON.parse(data);
|
|
return data;
|
|
},
|
|
//get array of records
|
|
getRecords:function(data){
|
|
if (data && data.data)
|
|
data = data.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];
|
|
if (this.idColumn !== null)
|
|
result.id = data[this.idColumn];
|
|
|
|
return result;
|
|
},
|
|
getOptions:function(){ return false; },
|
|
//dyn loading is not supported by js-array data source
|
|
getInfo:function(data){
|
|
return {
|
|
size:0,
|
|
from:0
|
|
};
|
|
},
|
|
idColumn:null
|
|
};
|
|
|
|
webix.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];
|
|
|
|
if (this.idColumn !== null)
|
|
result.id = data[this.idColumn];
|
|
|
|
return result;
|
|
},
|
|
getOptions:function(){ return false; },
|
|
//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;
|
|
},
|
|
idColumn:null,
|
|
row:"\n", //default row separator
|
|
cell:"," //default cell separator
|
|
};
|
|
|
|
webix.DataDriver.xml={
|
|
_isValidXML:function(data){
|
|
if (!data || !data.documentElement)
|
|
return null;
|
|
if (data.getElementsByTagName("parsererror").length)
|
|
return null;
|
|
return data;
|
|
},
|
|
//convert xml string to xml object if necessary
|
|
toObject:function(text, response){
|
|
var data = response ? (response.rawxml ? response.rawxml() : response) :null;
|
|
if (this._isValidXML(data))
|
|
return data;
|
|
if (typeof text == "string")
|
|
data = this.fromString(text.replace(/^[\s]+/,""));
|
|
else
|
|
data = text;
|
|
|
|
if (this._isValidXML(data))
|
|
return data;
|
|
return null;
|
|
},
|
|
//get array of records
|
|
getRecords:function(data){
|
|
return this.xpath(data,this.records);
|
|
},
|
|
records:"/*/item",
|
|
child:"item",
|
|
config:"/*/config",
|
|
//get hash of properties for single record
|
|
getDetails:function(data){
|
|
return this.tagToObject(data,{});
|
|
},
|
|
getOptions:function(){
|
|
return false;
|
|
},
|
|
//get count of data and position at which new data_loading need to be inserted
|
|
getInfo:function(data){
|
|
|
|
var config = this.xpath(data, this.config);
|
|
if (config.length)
|
|
config = this.assignTypes(this.tagToObject(config[0],{}));
|
|
else
|
|
config = null;
|
|
|
|
return {
|
|
size:(data.documentElement.getAttribute("total_count")||0),
|
|
from:(data.documentElement.getAttribute("pos")||0),
|
|
parent:(data.documentElement.getAttribute("parent")||0),
|
|
config:config,
|
|
key:(data.documentElement.getAttribute("webix_security")||null)
|
|
};
|
|
},
|
|
//xpath helper
|
|
xpath:function(xml,path){
|
|
if (window.XPathResult){ //FF, KHTML, Opera
|
|
var node=xml;
|
|
if(xml.nodeName.indexOf("document")==-1)
|
|
xml=xml.ownerDocument;
|
|
var res = [];
|
|
var col = xml.evaluate(path, node, null, XPathResult.ANY_TYPE, null);
|
|
var temp = col.iterateNext();
|
|
while (temp){
|
|
res.push(temp);
|
|
temp = col.iterateNext();
|
|
}
|
|
return res;
|
|
}
|
|
else {
|
|
var test = true;
|
|
try {
|
|
if (typeof(xml.selectNodes)=="undefined")
|
|
test = false;
|
|
} catch(e){ /*IE7 and below can't operate with xml object*/ }
|
|
//IE
|
|
if (test)
|
|
return xml.selectNodes(path);
|
|
else {
|
|
//there is no interface to do XPath
|
|
//use naive approach
|
|
var name = path.split("/").pop();
|
|
|
|
return xml.getElementsByTagName(name);
|
|
}
|
|
}
|
|
},
|
|
assignTypes:function(obj){
|
|
for (var k in obj){
|
|
var test = obj[k];
|
|
if (typeof test == "object")
|
|
this.assignTypes(test);
|
|
else if (typeof test == "string"){
|
|
if (test === "")
|
|
continue;
|
|
if (test == "true")
|
|
obj[k] = true;
|
|
else if (test == "false")
|
|
obj[k] = false;
|
|
else if (test == test*1)
|
|
obj[k] = obj[k]*1;
|
|
}
|
|
}
|
|
return obj;
|
|
},
|
|
//convert xml tag to js object, all subtags and attributes are mapped to the properties of result object
|
|
tagToObject:function(tag,z){
|
|
var isArray = tag.nodeType == 1 && tag.getAttribute("stack");
|
|
var hasSubTags = 0;
|
|
|
|
if (!isArray){
|
|
z=z||{};
|
|
|
|
|
|
//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;
|
|
hasSubTags = 1;
|
|
}
|
|
|
|
//map subtags
|
|
var b=tag.childNodes;
|
|
for (var i=0; i<b.length; i++)
|
|
if (b[i].nodeType==1){
|
|
var name = b[i].tagName;
|
|
if (z[name]){
|
|
if (typeof z[name].push != "function")
|
|
z[name] = [z[name]];
|
|
z[name].push(this.tagToObject(b[i],{}));
|
|
} else
|
|
z[name]=this.tagToObject(b[i],{}); //sub-object for complex subtags
|
|
hasSubTags = 2;
|
|
}
|
|
|
|
if (!hasSubTags)
|
|
return this.nodeValue(tag);
|
|
//each object will have its text content as "value" property
|
|
//only if has not sub tags
|
|
if (hasSubTags < 2)
|
|
z.value = z.value||this.nodeValue(tag);
|
|
|
|
} else {
|
|
z = [];
|
|
var b=tag.childNodes;
|
|
for (var i=0; i<b.length; i++)
|
|
if (b[i].nodeType==1)
|
|
z.push(this.tagToObject(b[i],{}));
|
|
}
|
|
|
|
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){
|
|
try{
|
|
if (window.DOMParser) // FF, KHTML, Opera
|
|
return (new DOMParser()).parseFromString(xmlString,"text/xml");
|
|
if (window.ActiveXObject){ // IE, utf-8 only
|
|
var temp=new ActiveXObject("Microsoft.xmlDOM");
|
|
temp.loadXML(xmlString);
|
|
return temp;
|
|
}
|
|
} catch(e){
|
|
webix.assert_error(e);
|
|
return null;
|
|
}
|
|
webix.assert_error("Load from xml string is not supported");
|
|
}
|
|
};
|
|
|
|
|
|
webix.debug_code(function(){
|
|
webix.debug_load_event = webix.attachEvent("onLoadError", function(text, xml, xhttp, owner){
|
|
text = text || "[EMPTY DATA]";
|
|
var error_text = "Data loading error, check console for details";
|
|
if (text.indexOf("<?php") === 0)
|
|
error_text = "PHP support missed";
|
|
else if (text.indexOf("WEBIX_ERROR:") === 0)
|
|
error_text = text.replace("WEBIX_ERROR:","");
|
|
|
|
if (webix.message)
|
|
webix.message({
|
|
type:"debug",
|
|
text:error_text,
|
|
expire:-1
|
|
});
|
|
if (window.console){
|
|
var logger = window.console;
|
|
logger.log("Data loading error");
|
|
logger.log("Object:", owner);
|
|
logger.log("Response:", text);
|
|
logger.log("XHTTP:", xhttp);
|
|
}
|
|
});
|
|
|
|
webix.ready(function(){
|
|
var path = document.location.href;
|
|
if (path.indexOf("file:")===0){
|
|
if (webix.message)
|
|
webix.message({
|
|
type:"error",
|
|
text:"Please open sample by http,<br>not as file://",
|
|
expire:-1
|
|
});
|
|
else
|
|
window.alert("Please open sample by http, not as file://");
|
|
}
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
//UI interface
|
|
webix.BaseBind = {
|
|
bind:function(target, rule, format){
|
|
if (!this.attachEvent)
|
|
webix.extend(this, webix.EventSystem);
|
|
|
|
if (typeof target == 'string')
|
|
target = webix.$$(target);
|
|
|
|
if (target._initBindSource) target._initBindSource();
|
|
if (this._initBindSource) this._initBindSource();
|
|
|
|
|
|
|
|
if (!target.getBindData)
|
|
webix.extend(target, webix.BindSource);
|
|
|
|
this._bind_ready();
|
|
|
|
target.addBind(this._settings.id, rule, format);
|
|
this._bind_source = target._settings.id;
|
|
|
|
if (webix.debug_bind)
|
|
webix.log("[bind] "+this.name+"@"+this._settings.id+" <= "+target.name+"@"+target._settings.id);
|
|
|
|
var target_id = this._settings.id;
|
|
//FIXME - check for touchable is not the best solution, to detect necessary event
|
|
this._bind_refresh_handler = this.attachEvent(this.touchable?"onAfterRender":"onBindRequest", function(){
|
|
return target.getBindData(target_id);
|
|
});
|
|
|
|
if (this.refresh && this.isVisible(this._settings.id))
|
|
this.refresh();
|
|
},
|
|
unbind:function(){
|
|
if (this._bind_source){
|
|
var target = webix.$$(this._bind_source);
|
|
if (target)
|
|
target.removeBind(this._settings.id);
|
|
this.detachEvent(this._bind_refresh_handler);
|
|
this._bind_source = null;
|
|
}
|
|
},
|
|
_bind_ready:function(){
|
|
var config = this._settings;
|
|
if (this.filter){
|
|
var key = config.id;
|
|
this.data._on_sync = webix.bind(function(){
|
|
webix.$$(this._bind_source)._bind_updated[key] = false;
|
|
}, this);
|
|
}
|
|
|
|
var old_render = this.render;
|
|
this.render = function(){
|
|
if (this._in_bind_processing) return;
|
|
|
|
this._in_bind_processing = true;
|
|
var result = this.callEvent("onBindRequest");
|
|
this._in_bind_processing = false;
|
|
|
|
return old_render.apply(this, ((result === false)?arguments:[]));
|
|
};
|
|
|
|
if (this.getValue||this.getValues)
|
|
this.save = function(data){
|
|
var source = webix.$$(this._bind_source);
|
|
if (data)
|
|
source.setBindData(data);
|
|
else {
|
|
if (this.validate && !this.validate()) return false;
|
|
var values = this.getValue?this.getValue:this.getValues();
|
|
source.setBindData(values,this._settings.id);
|
|
//reset form, so it will be counted as saved
|
|
if (this.setDirty)
|
|
this.setDirty(false);
|
|
}
|
|
};
|
|
|
|
this._bind_ready = function(){};
|
|
}
|
|
};
|
|
|
|
//bind interface
|
|
webix.BindSource = {
|
|
$init:function(){
|
|
this._bind_hash = {}; //rules per target
|
|
this._bind_updated = {}; //update flags
|
|
this._ignore_binds = {};
|
|
|
|
//apply specific bind extension
|
|
this._bind_specific_rules(this);
|
|
},
|
|
saveBatch:function(code){
|
|
this._do_not_update_binds = true;
|
|
code.call(this);
|
|
this._do_not_update_binds = false;
|
|
this._update_binds();
|
|
},
|
|
setBindData:function(data, key){
|
|
//save called, updating master data
|
|
if (key)
|
|
this._ignore_binds[key] = true;
|
|
|
|
if (webix.debug_bind)
|
|
webix.log("[bind:save] "+this.name+"@"+this._settings.id+" <= "+"@"+key);
|
|
if (this.setValue)
|
|
this.setValue(data);
|
|
else if (this.setValues)
|
|
this.setValues(data);
|
|
else {
|
|
var id = this.getCursor();
|
|
if (id)
|
|
this.updateItem(id, data);
|
|
else
|
|
this.add(data);
|
|
}
|
|
this.callEvent("onBindUpdate", [data, key]);
|
|
if (this.save)
|
|
this.save();
|
|
|
|
if (key)
|
|
this._ignore_binds[key] = false;
|
|
},
|
|
//fill target with data
|
|
getBindData:function(key, update){
|
|
//fire only if we have data updates from the last time
|
|
if (this._bind_updated[key]) return false;
|
|
var target = webix.$$(key);
|
|
//fill target only when it visible
|
|
if (target.isVisible(target._settings.id)){
|
|
this._bind_updated[key] = true;
|
|
if (webix.debug_bind)
|
|
webix.log("[bind:request] "+this.name+"@"+this._settings.id+" => "+target.name+"@"+target._settings.id);
|
|
this._bind_update(target, this._bind_hash[key][0], this._bind_hash[key][1]); //trigger component specific updating logic
|
|
if (update && target.filter)
|
|
target.refresh();
|
|
}
|
|
},
|
|
//add one more bind target
|
|
addBind:function(source, rule, format){
|
|
this._bind_hash[source] = [rule, format];
|
|
},
|
|
removeBind:function(source){
|
|
delete this._bind_hash[source];
|
|
delete this._bind_updated[source];
|
|
delete this._ignore_binds[source];
|
|
},
|
|
//returns true if object belong to "collection" type
|
|
_bind_specific_rules:function(obj){
|
|
if (obj.filter)
|
|
webix.extend(this, webix.CollectionBind);
|
|
else if (obj.setValue)
|
|
webix.extend(this, webix.ValueBind);
|
|
else
|
|
webix.extend(this, webix.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&&rule)?data[rule]:data);
|
|
else if (!target.filter){
|
|
if (!data && target.clear)
|
|
target.clear();
|
|
else {
|
|
if (target._check_data_feed(data))
|
|
target.setValues(webix.clone(data));
|
|
}
|
|
} else {
|
|
target.data.silent(function(){
|
|
this.filter(rule,data);
|
|
});
|
|
}
|
|
target.callEvent("onBindApply", [data,rule,this]);
|
|
}
|
|
};
|
|
|
|
|
|
//pure data objects
|
|
webix.DataValue = webix.proto({
|
|
name:"DataValue",
|
|
isVisible:function(){ return true; },
|
|
$init:function(config){
|
|
if (!config || webix.isUndefined(config.value))
|
|
this.data = config||"";
|
|
|
|
var id = (config&&config.id)?config.id:webix.uid();
|
|
this._settings = { id:id };
|
|
webix.ui.views[id] = this;
|
|
},
|
|
setValue:function(value){
|
|
this.data = value;
|
|
this.callEvent("onChange", [value]);
|
|
},
|
|
getValue:function(){
|
|
return this.data;
|
|
},
|
|
refresh:function(){ this.callEvent("onBindRequest"); }
|
|
}, webix.EventSystem, webix.BaseBind);
|
|
|
|
webix.DataRecord = webix.proto({
|
|
name:"DataRecord",
|
|
isVisible:function(){ return true; },
|
|
$init:function(config){
|
|
this.data = config||{};
|
|
var id = (config&&config.id)?config.id:webix.uid();
|
|
this._settings = { id:id };
|
|
webix.ui.views[id] = this;
|
|
},
|
|
getValues:function(){
|
|
return this.data;
|
|
},
|
|
setValues:function(data, update){
|
|
this.data = update?webix.extend(this.data, data, true):data;
|
|
this.callEvent("onChange", [data]);
|
|
},
|
|
refresh:function(){ this.callEvent("onBindRequest"); }
|
|
}, webix.EventSystem, webix.BaseBind, webix.AtomDataLoader, webix.Settings);
|
|
|
|
|
|
webix.ValueBind={
|
|
$init:function(){
|
|
this.attachEvent("onChange", this._update_binds);
|
|
},
|
|
_bind_update:function(target, rule, format){
|
|
rule = rule || "value";
|
|
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]);
|
|
}
|
|
};
|
|
|
|
webix.RecordBind={
|
|
$init:function(){
|
|
this.attachEvent("onChange", this._update_binds);
|
|
},
|
|
_bind_update:function(target, rule, format){
|
|
var data = this.getValues()||null;
|
|
if (format)
|
|
data = format(data);
|
|
this._bind_update_common(target, rule, data);
|
|
}
|
|
};
|
|
|
|
webix.CollectionBind={
|
|
$init:function(){
|
|
this._cursor = null;
|
|
this.attachEvent("onSelectChange", function(data){
|
|
var sel = this.getSelectedId();
|
|
this.setCursor(sel?(sel.id||sel):null);
|
|
});
|
|
this.attachEvent("onAfterCursorChange", this._update_binds);
|
|
this.attachEvent("onAfterDelete", function(id){
|
|
if (id == this.getCursor())
|
|
this.setCursor(null);
|
|
});
|
|
this.data.attachEvent("onStoreUpdated", webix.bind(function(id, data, mode){
|
|
//paint - ignored
|
|
//delete - handled by onAfterDelete above
|
|
if (id && id == this.getCursor() && mode != "paint" && mode != "delete")
|
|
this._update_binds();
|
|
|
|
},this));
|
|
this.data.attachEvent("onClearAll", webix.bind(function(){
|
|
this._cursor = null;
|
|
},this));
|
|
this.data.attachEvent("onIdChange", webix.bind(function(oldid, newid){
|
|
if (this._cursor == oldid){
|
|
this._cursor = newid;
|
|
this._update_binds();
|
|
}
|
|
},this));
|
|
},
|
|
refreshCursor:function(){
|
|
if (this._cursor)
|
|
this.callEvent("onAfterCursorChange",[this._cursor]);
|
|
},
|
|
setCursor:function(id){
|
|
if (id == this._cursor || (id !== null && !this.getItem(id))) return;
|
|
|
|
this.callEvent("onBeforeCursorChange", [this._cursor]);
|
|
this._cursor = id;
|
|
this.callEvent("onAfterCursorChange",[id]);
|
|
},
|
|
getCursor:function(){
|
|
return this._cursor;
|
|
},
|
|
_bind_update:function(target, rule, format){
|
|
if (rule == "$level" && this.data.getBranch)
|
|
return (target.data || target).importData(this.data.getBranch(this.getCursor()));
|
|
|
|
var data = this.getItem(this.getCursor())|| this._settings.defaultData || null;
|
|
if (rule == "$data"){
|
|
if (typeof format === "function")
|
|
format.call(target, data, this);
|
|
else
|
|
target.data.importData(data?data[format]:[]);
|
|
target.callEvent("onBindApply", [data,rule,this]);
|
|
} else {
|
|
if (format)
|
|
data = format(data);
|
|
this._bind_update_common(target, rule, data);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
REnders single item.
|
|
Can be used for elements without datastore, or with complex custom rendering logic
|
|
|
|
@export
|
|
render
|
|
*/
|
|
|
|
|
|
|
|
webix.AtomRender={
|
|
//convert item to the HTML text
|
|
_toHTML:function(obj){
|
|
if (obj.$empty )
|
|
return "";
|
|
return this._settings.template(obj, this);
|
|
},
|
|
//render self, by templating data object
|
|
render:function(){
|
|
var cfg = this._settings;
|
|
if (this.isVisible(cfg.id)){
|
|
if (webix.debug_render)
|
|
webix.log("Render: "+this.name+"@"+cfg.id);
|
|
if (!this.callEvent || this.callEvent("onBeforeRender",[this.data])){
|
|
if (this.data && !cfg.content){
|
|
//it is critical to have this as two commands
|
|
//its prevent destruction race in Chrome
|
|
this._dataobj.innerHTML = "";
|
|
this._dataobj.innerHTML = this._toHTML(this.data);
|
|
}
|
|
if (this.callEvent) this.callEvent("onAfterRender",[]);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
sync:function(source){
|
|
this._backbone_sync = false;
|
|
if (source.name != "DataStore"){
|
|
if (source.data && source.name == "DataStore"){
|
|
source = source.data;
|
|
} else {
|
|
this._backbone_sync = true;
|
|
}
|
|
}
|
|
|
|
|
|
if (this._backbone_sync)
|
|
source.bind("change", webix.bind(function(data){
|
|
if (data.id == this.data.id){
|
|
this.data = data.attributes;
|
|
this.refresh();
|
|
}
|
|
}, this));
|
|
else
|
|
source.attachEvent("onStoreUpdated", webix.bind(function(id){
|
|
if (!id || id == this.data.id){
|
|
this.data = source.pull[id];
|
|
this.refresh();
|
|
}
|
|
}, this));
|
|
},
|
|
template_setter:webix.template
|
|
};
|
|
|
|
webix.SingleRender=webix.proto({
|
|
template_setter:function(value){
|
|
this.type.template=webix.template(value);
|
|
},
|
|
//convert item to the HTML text
|
|
_toHTML:function(obj){
|
|
var type = this.type;
|
|
return (type.templateStart?type.templateStart(obj,type):"") + type.template(obj,type) + (type.templateEnd?type.templateEnd(obj,type):"");
|
|
},
|
|
customize:function(obj){
|
|
webix.type(this,obj);
|
|
}
|
|
}, webix.AtomRender);
|
|
|
|
webix.UIManager = {
|
|
_view: null,
|
|
_hotkeys: {},
|
|
_focus_time:0,
|
|
_controls: {
|
|
'enter': 13,
|
|
'tab': 9,
|
|
'esc': 27,
|
|
'escape': 27,
|
|
'up': 38,
|
|
'down': 40,
|
|
'left': 37,
|
|
'right': 39,
|
|
'pgdown': 34,
|
|
'pagedown': 34,
|
|
'pgup': 33,
|
|
'pageup': 33,
|
|
'end': 35,
|
|
'home': 36,
|
|
'insert': 45,
|
|
'delete': 46,
|
|
'backspace': 8,
|
|
'space': 32,
|
|
'meta': 91,
|
|
'win': 91,
|
|
'mac': 91,
|
|
'multiply': 106,
|
|
'add': 107,
|
|
'subtract': 109,
|
|
'decimal': 110,
|
|
'divide': 111,
|
|
'scrollock':145,
|
|
'pausebreak':19,
|
|
'numlock':144,
|
|
'5numlocked':12,
|
|
'shift':16,
|
|
'capslock':20
|
|
},
|
|
_inputs:{
|
|
"input": 1,
|
|
"button":1,
|
|
"textarea":1,
|
|
"select":1
|
|
},
|
|
_enable: function() {
|
|
// attaching events here
|
|
webix.event(document.body, "click", webix.bind(this._focus_click, this));
|
|
webix.event(document, "keydown", webix.bind(this._keypress, this));
|
|
|
|
if (document.body.addEventListener)
|
|
webix.event(document.body, "focus", this._focus_tab, { capture:true, bind: this });
|
|
|
|
webix.destructors.push({obj:this});
|
|
},
|
|
destructor:function(){
|
|
webix.UIManager._view = null;
|
|
},
|
|
getFocus: function() {
|
|
return this._view;
|
|
},
|
|
_focus_action:function(view){
|
|
this._focus_was_there = this._focus_was_there || view._settings.id;
|
|
},
|
|
setFocus: function(view, only_api){
|
|
//view can be empty
|
|
view = webix.$$(view);
|
|
//unfocus if view is hidden
|
|
if (view && !view.$view) view = null;
|
|
|
|
//store last click time, it is necessary to prevent refocusing
|
|
//for example when user moves focus from onclick handler somewher
|
|
//and we want to prevent autofocusing, when event will reach document.body
|
|
this._focus_time = webix._focus_time = new Date();
|
|
|
|
if (this._view === view) return true;
|
|
if (this._view && this._view.callEvent)
|
|
this._view.callEvent("onBlur", [this._view]);
|
|
|
|
if (view && view.callEvent)
|
|
view.callEvent("onFocus", [view, this._view]);
|
|
webix.callEvent("onFocusChange", [view, this._view]);
|
|
|
|
if (this._view && this._view.blur && !only_api) this._view.blur();
|
|
this._view = view;
|
|
if (view && view.focus && !only_api) view.focus();
|
|
return true;
|
|
},
|
|
applyChanges: function(element){
|
|
var view = this.getFocus();
|
|
if (view && view != element && view._applyChanges)
|
|
view._applyChanges(element);
|
|
},
|
|
hasFocus: function(view) {
|
|
return (view === this._view) ? true : false;
|
|
},
|
|
_focus: function(e, dont_clear) {
|
|
var view = webix.html.locate(e, "view_id") || this._focus_was_there;
|
|
|
|
//if html was repainted we can miss the view, so checking last processed one
|
|
view = webix.$$(view);
|
|
this._focus_was_there = null;
|
|
|
|
//set timer, to fix issue with Android input focusin
|
|
webix._focus_time = new Date();
|
|
|
|
if (view == this._view) return;
|
|
|
|
if (!dont_clear)
|
|
this._focus_was_there = null;
|
|
|
|
if (view){
|
|
view = webix.$$(view);
|
|
if (this.canFocus(view)){
|
|
//[ACTIVECONTENT] focus operations for active content
|
|
if (view.getNode) view.getNode(e);
|
|
this.setFocus(view);
|
|
}
|
|
} else if (!dont_clear)
|
|
this.setFocus(null);
|
|
|
|
return true;
|
|
},
|
|
_focus_click:function(e){
|
|
// if it was onfocus/onclick less then 100ms behore then we ignore it
|
|
if ((new Date())-this._focus_time < 100) {
|
|
this._focus_was_there = null;
|
|
return false;
|
|
}
|
|
return this._focus(e);
|
|
},
|
|
_focus_tab: function(e) {
|
|
if(!this._inputs[e.target.nodeName.toLowerCase()])
|
|
return false;
|
|
return this._focus(e, true);
|
|
},
|
|
canFocus:function(view){
|
|
return view.isVisible() && view.isEnabled();
|
|
},
|
|
|
|
_moveChildFocus: function(check_view){
|
|
var focus = this.getFocus();
|
|
//we have not focus inside of closing item
|
|
if (check_view && !this._is_child_of(check_view, focus))
|
|
return false;
|
|
|
|
if (!this._focus_logic("getPrev", check_view))
|
|
this._view = null;
|
|
},
|
|
_translation_table:{
|
|
},
|
|
_is_child_of: function(parent, child) {
|
|
if (!parent) return false;
|
|
if (!child) return false;
|
|
while (child) {
|
|
if (child === parent) return true;
|
|
child = child.getParentView();
|
|
}
|
|
return false;
|
|
},
|
|
_keypress_timed:function(){
|
|
if (this && this.callEvent)
|
|
this.callEvent("onTimedKeyPress",[]);
|
|
},
|
|
_isNumPad: function(code){
|
|
return code < 112 && code>105;
|
|
},
|
|
_keypress: function(e) {
|
|
var code = e.which || e.keyCode;
|
|
if(code>95 && code< 106)
|
|
code -= 48; //numpad support (numbers)
|
|
code = this._translation_table[code] || code;
|
|
|
|
var ctrl = e.ctrlKey;
|
|
var shift = e.shiftKey;
|
|
var alt = e.altKey;
|
|
var meta = e.metaKey;
|
|
var codeid = this._keycode(code, ctrl, shift, alt, meta);
|
|
var view = this.getFocus();
|
|
if (view && view.callEvent) {
|
|
if (view.callEvent("onKeyPress", [code,e]) === false)
|
|
webix.html.preventEvent(e);
|
|
if (view.hasEvent("onTimedKeyPress")){
|
|
clearTimeout(view._key_press_timeout);
|
|
view._key_press_timeout = webix.delay(this._keypress_timed, view, [], (view._settings.keyPressTimeout||250));
|
|
}
|
|
}
|
|
|
|
if(!this._isNumPad(code))
|
|
codeid = this._keycode(String.fromCharCode(code), ctrl, shift, alt, meta);
|
|
//flag, that some non-special key was pressed
|
|
var is_any = !ctrl && !alt && !meta && (code!=9)&&(code!=27)&&(code!=13);
|
|
|
|
if (this._check_keycode(codeid, is_any, e) === false) {
|
|
webix.html.preventEvent(e);
|
|
return false;
|
|
}
|
|
},
|
|
|
|
// dir - getNext or getPrev
|
|
_focus_logic: function(dir) {
|
|
if (!this.getFocus()) return null;
|
|
|
|
dir = dir || "getNext";
|
|
var next = this.getFocus();
|
|
var start = next;
|
|
var marker = webix.uid();
|
|
|
|
while (true) {
|
|
next = this[dir](next);
|
|
// view with focus ability
|
|
if (next && this.canFocus(next))
|
|
return this.setFocus(next);
|
|
|
|
// elements with focus ability not found
|
|
if (next === start || next.$fmarker == marker)
|
|
return null;
|
|
|
|
//prevents infinity loop
|
|
next.$fmarker = marker;
|
|
}
|
|
},
|
|
_tab_logic:function(view, e){
|
|
var mode = !e.shiftKey;
|
|
webix.UIManager._tab_time = new Date();
|
|
if (view && view._custom_tab_handler && !view._custom_tab_handler(mode, e))
|
|
return false;
|
|
|
|
if (view && view._in_edit_mode){
|
|
if (view.editNext)
|
|
return view.editNext(mode);
|
|
else if (view.editStop){
|
|
view.editStop();
|
|
return true;
|
|
}
|
|
} else
|
|
webix.delay(function(){
|
|
webix.UIManager.setFocus(webix.$$(document.activeElement), true);
|
|
},1);
|
|
},
|
|
getTop: function(id) {
|
|
var next, view = webix.$$(id);
|
|
|
|
while (view && (next = view.getParentView()))
|
|
view = next;
|
|
return view;
|
|
},
|
|
|
|
getNext: function(view, _inner_call) {
|
|
var cells = view.getChildViews();
|
|
//tab to first children
|
|
if (cells.length && !_inner_call) return cells[0];
|
|
|
|
//unique case - single view without child and parent
|
|
var parent = view.getParentView();
|
|
if (!parent)
|
|
return view;
|
|
|
|
var p_cells = parent.getChildViews();
|
|
if (p_cells.length){
|
|
var index = webix.PowerArray.find.call(p_cells, view)+1;
|
|
while (index < p_cells.length) {
|
|
//next visible child
|
|
if (this.canFocus(p_cells[index]))
|
|
return p_cells[index];
|
|
|
|
index++;
|
|
}
|
|
}
|
|
|
|
//sibling of parent
|
|
return this.getNext(parent, true);
|
|
},
|
|
|
|
getPrev: function(view, _inner_call) {
|
|
var cells = view.getChildViews();
|
|
//last child of last child
|
|
if (cells.length && _inner_call)
|
|
return this.getPrev(cells[cells.length - 1], true);
|
|
if (_inner_call) return view;
|
|
|
|
//fallback from top to bottom
|
|
var parent = view.getParentView();
|
|
if (!parent) return this.getPrev(view, true);
|
|
|
|
|
|
var p_cells = parent.getChildViews();
|
|
if (p_cells) {
|
|
var index = webix.PowerArray.find.call(p_cells, view)-1;
|
|
while (index >= 0) {
|
|
if (this.canFocus(p_cells[index]))
|
|
return this.getPrev(p_cells[index], true);
|
|
index--;
|
|
}
|
|
}
|
|
|
|
return parent;
|
|
},
|
|
addHotKey: function(keys, handler, view) {
|
|
webix.assert(handler, "Hot key handler is not defined");
|
|
var pack = this._parse_keys(keys);
|
|
webix.assert(pack.letter, "Unknown key code");
|
|
if (!view) view = null;
|
|
pack.handler = handler;
|
|
pack.view = view;
|
|
|
|
|
|
var code = this._keycode(pack.letter, pack.ctrl, pack.shift, pack.alt, pack.meta);
|
|
if (!this._hotkeys[code]) this._hotkeys[code] = [];
|
|
this._hotkeys[code].push(pack);
|
|
|
|
return keys;
|
|
},
|
|
removeHotKey: function(keys, func, view){
|
|
var pack = this._parse_keys(keys);
|
|
var code = this._keycode(pack.letter, pack.ctrl, pack.shift, pack.alt, pack.meta);
|
|
if (!func && !view)
|
|
delete this._hotkeys[code];
|
|
else {
|
|
var t = this._hotkeys[code];
|
|
if (t){
|
|
for (var i = t.length - 1; i >= 0; i--) {
|
|
if (view && t[i].view !== view) continue;
|
|
if (func && t[i].handler !== func) continue;
|
|
t.splice(i,1);
|
|
}
|
|
if (!t.length)
|
|
delete this._hotkeys[code];
|
|
}
|
|
|
|
}
|
|
},
|
|
_keycode: function(code, ctrl, shift, alt, meta) {
|
|
return code+"_"+["", (ctrl ? '1' : '0'), (shift ? '1' : '0'), (alt ? '1' : '0'), (meta ? '1' : '0')].join('');
|
|
},
|
|
|
|
_check_keycode: function(code, is_any, e){
|
|
var focus = this.getFocus();
|
|
if (this._hotkeys[code])
|
|
return this._process_calls(this._hotkeys[code], focus, e);
|
|
else if (is_any && this._hotkeys["ANY_0000"])
|
|
return this._process_calls(this._hotkeys["ANY_0000"], focus, e);
|
|
|
|
return true;
|
|
},
|
|
_process_calls:function(calls, focus, e){
|
|
for (var i = 0; i < calls.length; i++) {
|
|
var key = calls[i];
|
|
var call = false;
|
|
if ((key.view !== null) && //common hot-key
|
|
(focus !== key.view) && //hot-key for current view
|
|
//hotkey for current type of view
|
|
(typeof(key.view) !== 'string' || !focus || focus.name !== key.view)) continue;
|
|
|
|
var temp_result = key.handler(focus, e);
|
|
if (!!temp_result === temp_result) return temp_result;
|
|
}
|
|
return true;
|
|
},
|
|
_parse_keys: function(keys) {
|
|
var controls = this._controls;
|
|
var parts = keys.toLowerCase().split(/[\+\-_]/);
|
|
var ctrl, shift, alt, meta;
|
|
ctrl = shift = alt = meta = 0;
|
|
var letter = "";
|
|
for (var i = 0; i < parts.length; i++) {
|
|
if (parts[i] === 'ctrl') ctrl = 1;
|
|
else if (parts[i] === 'shift') shift = 1;
|
|
else if (parts[i] === 'alt') alt = 1;
|
|
else if (parts[i] === 'command') meta = 1;
|
|
else {
|
|
if (controls[parts[i]]) {
|
|
var code = controls[parts[i]];
|
|
if(this._isNumPad(code))
|
|
letter = code.toString();
|
|
else
|
|
letter = String.fromCharCode(code);
|
|
} else {
|
|
letter = parts[i];
|
|
}
|
|
}
|
|
}
|
|
return {
|
|
letter: letter.toUpperCase(),
|
|
ctrl: ctrl,
|
|
shift: shift,
|
|
alt: alt,
|
|
meta: meta,
|
|
debug:keys
|
|
};
|
|
}
|
|
};
|
|
|
|
webix.ready(function() {
|
|
webix.UIManager._enable();
|
|
|
|
webix.UIManager.addHotKey("enter", function(view, ev){
|
|
if (view && view.editStop && view._in_edit_mode){
|
|
view.editStop();
|
|
return true;
|
|
} else if (view && view.touchable){
|
|
var form = view.getFormView();
|
|
if (form && !view._skipSubmit)
|
|
form.callEvent("onSubmit",[view,ev]);
|
|
}
|
|
});
|
|
webix.UIManager.addHotKey("esc", function(view){
|
|
if (view){
|
|
if (view.editCancel && view._in_edit_mode){
|
|
view.editCancel();
|
|
return true;
|
|
}
|
|
var top = view.getTopParentView();
|
|
if (top && top.setPosition)
|
|
top._hide();
|
|
}
|
|
});
|
|
webix.UIManager.addHotKey("shift+tab", webix.UIManager._tab_logic);
|
|
webix.UIManager.addHotKey("tab", webix.UIManager._tab_logic);
|
|
});
|
|
|
|
webix.IdSpace = {
|
|
$init:function(){
|
|
this._elements = {};
|
|
this._translate_ids = {};
|
|
this.getTopParentView = this._get_self = webix.bind(function(){ return this;}, this);
|
|
|
|
this._run_inner_init_logic();
|
|
this.$ready.push(this._run_after_inner_init_logic);
|
|
},
|
|
$$:function(id){
|
|
return this._elements[id];
|
|
},
|
|
innerId:function(id){
|
|
return this._translate_ids[id];
|
|
},
|
|
_run_inner_init_logic:function(callback){
|
|
this._prev_global_col = webix._global_collection;
|
|
webix._global_collection = this;
|
|
},
|
|
_run_after_inner_init_logic:function(temp){
|
|
for (var name in this._elements){
|
|
var input = this._elements[name];
|
|
if (this.callEvent && input.mapEvent && !input._evs_map.onitemclick)
|
|
input.mapEvent({
|
|
onitemclick:this
|
|
});
|
|
input.getTopParentView = this._get_self;
|
|
}
|
|
|
|
webix._global_collection = this._prev_global_col;
|
|
this._prev_global_col = 0;
|
|
},
|
|
_destroy_child:function(id){
|
|
delete this._elements[id];
|
|
},
|
|
ui:function(){
|
|
this._run_inner_init_logic();
|
|
var temp = webix.ui.apply(webix, arguments);
|
|
this._run_after_inner_init_logic();
|
|
return temp;
|
|
}
|
|
};
|
|
|
|
|
|
(function(){
|
|
|
|
var resize = [];
|
|
var ui = webix.ui;
|
|
|
|
if (!webix.ui){
|
|
ui = webix.ui = function(config, parent, id){
|
|
webix._ui_creation = true;
|
|
var multiset = webix.isArray(config);
|
|
var node = webix.toNode((config.container||parent)||document.body);
|
|
|
|
// solve problem with non-unique ids
|
|
if(node._settings)
|
|
id = _correctId(node, multiset, id);
|
|
|
|
var top_node;
|
|
var body_child = (node == document.body);
|
|
if (config._settings || (node && multiset)){
|
|
top_node = config;
|
|
} else {
|
|
if (node && body_child)
|
|
config.$topView = true;
|
|
if (!config._inner)
|
|
config._inner = {};
|
|
|
|
top_node = ui._view(config);
|
|
}
|
|
|
|
if (body_child && !top_node.setPosition && !top_node.$apiOnly)
|
|
webix.ui._fixHeight();
|
|
|
|
if (top_node._settings && top_node._settings._hidden && !node.$view){
|
|
top_node._settings._container = node;
|
|
} else if (!top_node.$apiOnly){
|
|
if (node.appendChild)
|
|
_appendDom(node, top_node, config);
|
|
else if (node.destructor){
|
|
var target = node;
|
|
|
|
//addView or view moving with target id
|
|
if (!id && id!==0 && !webix.isArray(top_node)){
|
|
id = node;
|
|
node = node.getParentView();
|
|
}
|
|
|
|
//if target supports view adding
|
|
if (node && node._replace){
|
|
//if source supports view removing
|
|
if (top_node.getParentView && top_node.getParentView())
|
|
top_node.getParentView()._remove(top_node);
|
|
|
|
node._replace(top_node, id);
|
|
} else {
|
|
var parent = target.$view.parentNode;
|
|
target.destructor();
|
|
_appendDom(parent, top_node, config);
|
|
}
|
|
} else
|
|
webix.assert_error("Not existing parent:"+config.container);
|
|
}
|
|
|
|
webix._ui_creation = false;
|
|
return top_node;
|
|
};
|
|
|
|
var _appendDom = function(node, top_node, config){
|
|
node.appendChild(top_node._viewobj);
|
|
//resize window with position center or top
|
|
//do not resize other windows and elements
|
|
// which are attached to custom html containers
|
|
if (((!top_node.setPosition || top_node._settings.fullscreen) && node == document.body) || top_node._settings.position )
|
|
resize.push(top_node._destructor_handler);
|
|
if (!config.skipResize)
|
|
top_node.adjust();
|
|
};
|
|
|
|
var _correctId = function(target, multiset, id){
|
|
//replace view
|
|
var views = [target];
|
|
//replace content of layout
|
|
if (multiset)
|
|
views = target.getChildViews();
|
|
//replace content of window
|
|
else if (target._body_cell)
|
|
views = [target._body_cell];
|
|
//add cell in layout by number
|
|
else if (typeof id == "number"){
|
|
return id;
|
|
//replace cell in layout by id
|
|
} else if (id){
|
|
views = [webix.$$(id)];
|
|
_deleteIds(views);
|
|
return views[0].config.id;
|
|
}
|
|
|
|
_deleteIds(views);
|
|
return id;
|
|
};
|
|
|
|
var _deleteIds = function(views){
|
|
for (var i = views.length - 1; i >= 0; i--){
|
|
//remove original id
|
|
delete webix.ui.views[views[i].config.id];
|
|
//create temp id
|
|
views[i].config.id = "x"+webix.uid();
|
|
webix.ui.views[views[i].config.id] = views[i];
|
|
//process childs
|
|
_deleteIds(views[i].getChildViews());
|
|
}
|
|
};
|
|
}
|
|
|
|
webix.ui.animate = function(ui, parent, config){
|
|
var pobj = webix.$$(parent);
|
|
if (pobj){
|
|
var aniset = config || { type:"slide", direction:"left" };
|
|
var d = pobj._viewobj.cloneNode(true);
|
|
var view = webix.ui(ui, parent);
|
|
|
|
view._viewobj.parentNode.appendChild(d);
|
|
var line = webix.animate.formLine(
|
|
view._viewobj,
|
|
d,
|
|
aniset
|
|
);
|
|
|
|
aniset.callback = function(){
|
|
webix.animate.breakLine(line);
|
|
};
|
|
webix.animate(line, aniset);
|
|
|
|
return view;
|
|
}
|
|
};
|
|
|
|
webix.ui.animateView = function(view, stateHandler, config){
|
|
view = webix.$$(view);
|
|
if (view){
|
|
config = config || { type:"slide", direction:"left" };
|
|
|
|
var getHTML = function(view){
|
|
var el = view._viewobj;
|
|
var css = el.className;
|
|
var content =el.innerHTML;
|
|
return "<div class='"+css+"' style='width:"+el.offsetWidth+"px;height:"+el.offsetHeight+"px;'>"+content+"</div>";
|
|
};
|
|
|
|
// get 'display' state of child nodes
|
|
var display = [];
|
|
for(var i =0; i< view._viewobj.childNodes.length;i++){
|
|
var node = view._viewobj.childNodes[i];
|
|
var value = node.currentStyle ?node.currentStyle.display : getComputedStyle(node, null).display;
|
|
display.push(value||"");
|
|
}
|
|
// get current html content
|
|
var currentState = getHTML(view);
|
|
|
|
// apply new state
|
|
if(typeof stateHandler == "function"){
|
|
stateHandler.call(this);
|
|
}
|
|
|
|
// get new html content
|
|
var newState = getHTML(view);
|
|
|
|
// insert elements into the view
|
|
var tempParent = view._viewobj.insertBefore(webix.html.create("DIV",{
|
|
"class" : "webix_view_animate",
|
|
"style" : "width:"+view._viewobj.offsetWidth+"px;height:"+view._viewobj.offsetHeight+"px;"
|
|
}, newState+currentState),view._viewobj.firstChild);
|
|
|
|
// hide child nodes
|
|
for(var i =1; i< view._viewobj.childNodes.length;i++){
|
|
view._viewobj.childNodes[i].style.display = "none";
|
|
}
|
|
|
|
// animate inserted elements
|
|
var line = webix.animate.formLine(
|
|
tempParent.childNodes[0],
|
|
tempParent.childNodes[1],
|
|
config
|
|
);
|
|
config.callback = function(){
|
|
if(tempParent){
|
|
view._viewobj.removeChild(tempParent);
|
|
tempParent = null;
|
|
// restore 'display' state of child nodes
|
|
for(var i =0; i< view._viewobj.childNodes.length;i++){
|
|
view._viewobj.childNodes[i].style.display = display[i];
|
|
}
|
|
}
|
|
};
|
|
webix.animate(line, config);
|
|
|
|
return view;
|
|
}
|
|
};
|
|
|
|
/*called in baseview $init for calculate scrollSize*/
|
|
webix.ui._detectScrollSize = function(){
|
|
var div = webix.html.create("div");
|
|
div.className = "webix_skin_mark";
|
|
div.style.cssText="position:absolute;left:-1000px;width:100px;padding:0px;margin:0px;min-height:100px;overflow-y:scroll;";
|
|
|
|
document.body.appendChild(div);
|
|
var width = div.offsetWidth-div.clientWidth;
|
|
var skin = { 110:"air", 120:"aircompact", 130:"clouds", 140:"web", 150:"terrace", 160:"metro", 170:"light", 180:"glamour", 190:"touch", 200:"flat" , 210:"compact", 220:"material", 230: "contrast" }[Math.floor(div.offsetHeight/10)*10];
|
|
document.body.removeChild(div);
|
|
|
|
if (skin){
|
|
var skinobj = webix.skin[skin];
|
|
if (skinobj && skinobj != webix.skin.$active)
|
|
webix.skin.set(skin);
|
|
}
|
|
|
|
if (webix.env.$customScroll) return 0;
|
|
return width;
|
|
};
|
|
webix.ui.scrollSize = ((webix.env.touch||webix.env.$customScroll)?0:17);
|
|
webix.ready(function(){
|
|
var size = webix.ui._detectScrollSize();
|
|
webix.ui.scrollSize = webix.env.touch ? 0 : size;
|
|
});
|
|
|
|
webix.ui._uid = function(name){
|
|
return "$"+name+(this._namecount[name] = (this._namecount[name]||0)+1);
|
|
};
|
|
webix.ui._namecount = {};
|
|
|
|
webix.ui._fixHeight = function (){
|
|
webix.html.addStyle("html, body{ height:100%; }");
|
|
document.body.className+=" webix_full_screen";
|
|
webix.ui._fixHeight = function(){};
|
|
webix.Touch.limit(false);
|
|
};
|
|
webix.ui.resize = function(){
|
|
webix.UIManager.applyChanges();
|
|
webix.callEvent("onClick",[]);
|
|
if (!webix.ui.$freeze)
|
|
for (var i=resize.length - 1; i>=0; i--){
|
|
if (resize[i].obj)
|
|
resize[i].obj.adjust();
|
|
}
|
|
};
|
|
webix.ui.each = function(parent, logic, master, include){
|
|
if (parent){
|
|
var children = include ? [parent] : parent.getChildViews();
|
|
for (var i = 0; i < children.length; i++){
|
|
if (logic.call((master || webix), children[i]) !== false)
|
|
webix.ui.each(children[i], logic, master);
|
|
}
|
|
}
|
|
};
|
|
webix.event(window, "resize", function() {
|
|
// check for virtual keyboard
|
|
if(webix.env.touch && ( webix.edit_open_time && (new Date())-webix.edit_open_time < 750 || webix._focus_time && (new Date())-webix._focus_time < 750)){
|
|
//workaround for android chrome bug with scrolling to the focused input if overflow:hidden on container
|
|
if(webix.env.isWebKit && document.activeElement){
|
|
var wactiv = webix.$$(document.activeElement);
|
|
if (wactiv && wactiv.getInputNode && document.activeElement.scrollIntoView)
|
|
document.activeElement.scrollIntoView();
|
|
}
|
|
return;
|
|
} else {
|
|
webix.ui.resize();
|
|
}
|
|
});
|
|
|
|
ui._delays = {};
|
|
ui.delay = function(config){
|
|
webix.ui._delays[config.id] = config;
|
|
};
|
|
ui.hasMethod = function(view, method){
|
|
var obj = webix.ui[view];
|
|
if (!obj) return false;
|
|
|
|
if (obj.$protoWait)
|
|
obj = obj.call(webix);
|
|
|
|
return !!webix.ui[view].prototype[method];
|
|
};
|
|
webix.ui.zIndex = function(){
|
|
return webix.ui.zIndexBase++;
|
|
};
|
|
webix.ui.zIndexBase = 100;
|
|
|
|
ui._view = function(config){
|
|
webix.assert_config(config);
|
|
if (config.view){
|
|
var view = config.view;
|
|
webix.assert(ui[view], "unknown view:"+view);
|
|
return new ui[view](config);
|
|
} else if (config.rows || config.cols){
|
|
var cells = config.rows||config.cols;
|
|
var accordion = false;
|
|
for (var i=0; i<cells.length; i++){
|
|
if (cells[i].body && !cells[i].view && !cells[i].align)
|
|
accordion = true;
|
|
}
|
|
if (accordion){
|
|
return new ui.headerlayout(config);
|
|
} else
|
|
return new ui.layout(config);
|
|
}
|
|
else if (config.cells)
|
|
return new ui.multiview(config);
|
|
else if (config.template || config.content)
|
|
return new ui.template(config);
|
|
else if (config.align && config.body){
|
|
return new ui.align(config);
|
|
} else return new ui.spacer(config);
|
|
};
|
|
|
|
ui.views = {};
|
|
webix.$$ = function(id){
|
|
if (!id) return null;
|
|
|
|
if (ui.views[id]) return ui.views[id];
|
|
if (ui._delays[id]) return webix.ui(ui._delays[id]);
|
|
|
|
var name = id;
|
|
if (typeof id == "object"){
|
|
if (id._settings)
|
|
return id;
|
|
name = (id.target||id.srcElement)||id;
|
|
}
|
|
return ui.views[webix.html.locate({ target:webix.toNode(name)},"view_id")];
|
|
};
|
|
if (webix.isUndefined(window.$$)) window.$$=webix.$$;
|
|
|
|
webix.UIExtension = window.webix_view||{};
|
|
|
|
webix.protoUI({
|
|
name:"baseview",
|
|
//attribute , which will be used for ID storing
|
|
$init:function(config){
|
|
if (!config.id)
|
|
config.id = webix.ui._uid(this.name);
|
|
|
|
this._parent_cell = webix._parent_cell;
|
|
webix._parent_cell = null;
|
|
|
|
this.$scope = config.$scope || (this._parent_cell ? this._parent_cell.$scope : null);
|
|
|
|
if (!this._viewobj){
|
|
this._contentobj = this._viewobj = webix.html.create("DIV",{
|
|
"class":"webix_view"
|
|
});
|
|
this.$view = this._viewobj;
|
|
}
|
|
},
|
|
$skin:false,
|
|
defaults:{
|
|
width:0,
|
|
height:0,
|
|
gravity:1
|
|
},
|
|
getNode:function(){
|
|
return this._viewobj;
|
|
},
|
|
getParentView:function(){
|
|
return this._parent_cell||null;
|
|
},
|
|
getTopParentView:function(){
|
|
var parent = this.getParentView();
|
|
return parent ? parent.getTopParentView() : this;
|
|
},
|
|
getFormView:function(){
|
|
var parent = this.getParentView();
|
|
return (!parent || parent.setValues) ? parent : parent.getFormView();
|
|
},
|
|
getChildViews:function(){ return []; },
|
|
queryView:function(search, all){
|
|
var confirm;
|
|
if (typeof search === "object"){
|
|
var keys = Object.keys(search);
|
|
var values = [];
|
|
for (var i=0; i<keys.length; i++)
|
|
values[i] = search[keys[i]];
|
|
|
|
var confirm = function(test){
|
|
var config = test.config;
|
|
for (var j=0; j<keys.length; j++)
|
|
if (config[keys[j]] != values[j])
|
|
return false;
|
|
return true;
|
|
};
|
|
} else
|
|
confirm = search;
|
|
|
|
var results = all === "all" ? [] : false;
|
|
var direction = all === "parent" ? this._queryGoUp : this._queryGoDown;
|
|
var found = this._queryView(confirm, direction, results);
|
|
return all === "all" ? results : found;
|
|
},
|
|
_queryGoDown:function(node){
|
|
return node.getChildViews();
|
|
},
|
|
_queryGoUp:function(node){
|
|
var parent = node.getParentView();
|
|
return parent ? [parent] : [];
|
|
},
|
|
_queryView:function(confirm, next, all){
|
|
var kids = next(this);
|
|
for (var i =0; i<kids.length; i++){
|
|
if (confirm(kids[i])){
|
|
if (all)
|
|
all.push(kids[i]);
|
|
else
|
|
return kids[i];
|
|
}
|
|
else {
|
|
var sub = kids[i]._queryView(confirm, next, all);
|
|
if (sub)
|
|
return sub;
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
isVisible:function(base_id, prev_id){
|
|
if (this._settings.hidden){
|
|
if(base_id){
|
|
if (!this._hidden_render) {
|
|
this._hidden_render = [];
|
|
this._hidden_hash = {};
|
|
}
|
|
if (!this._hidden_hash[base_id]){
|
|
this._hidden_hash[base_id] = true;
|
|
this._hidden_render.push(base_id);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
var parent = this.getParentView();
|
|
if (parent) return parent.isVisible(base_id, this._settings.id);
|
|
|
|
return true;
|
|
},
|
|
isEnabled:function(){
|
|
if(this._disable_cover)
|
|
return false;
|
|
|
|
var parent= this.getParentView();
|
|
if(parent)
|
|
return parent.isEnabled();
|
|
|
|
return true;
|
|
},
|
|
disable:function(){
|
|
webix.html.remove(this._disable_cover);
|
|
this._settings.disabled = true;
|
|
|
|
this._disable_cover = webix.html.create('div',{
|
|
"class":"webix_disabled"
|
|
});
|
|
|
|
if(window.getComputedStyle)
|
|
this._disabled_view_pos = window.getComputedStyle(this._viewobj, null).getPropertyValue("position");
|
|
|
|
if (this._disabled_view_pos != "absolute")
|
|
this._viewobj.style.position = "relative";
|
|
this._viewobj.appendChild(this._disable_cover);
|
|
this._viewobj.setAttribute("aria-disabled", "true");
|
|
webix.html.addCss(this._viewobj,"webix_disabled_view",true);
|
|
webix.UIManager._moveChildFocus(this);
|
|
},
|
|
enable:function(){
|
|
this._settings.disabled = false;
|
|
|
|
if (this._disable_cover){
|
|
webix.html.remove(this._disable_cover);
|
|
webix.html.removeCss(this._viewobj,"webix_disabled_view");
|
|
this._viewobj.removeAttribute("aria-disabled");
|
|
this._disable_cover = null;
|
|
if(this._disabled_view_pos)
|
|
this._viewobj.style.position = this._disabled_view_pos;
|
|
}
|
|
},
|
|
disabled_setter:function(value){
|
|
if (value)
|
|
this.disable();
|
|
else
|
|
this.enable();
|
|
return value;
|
|
},
|
|
container_setter:function(value){
|
|
webix.assert(webix.toNode(value),"Invalid container");
|
|
return true;
|
|
},
|
|
css_setter:function(value){
|
|
if (typeof value == "object")
|
|
value = webix.html.createCss(value);
|
|
|
|
this._viewobj.className += " "+value;
|
|
return value;
|
|
},
|
|
id_setter:function(value){
|
|
if (webix._global_collection && (webix._global_collection != this || this._prev_global_col)){
|
|
var oldvalue = this.config.$id = value;
|
|
(this._prev_global_col || webix._global_collection)._elements[value] = this;
|
|
value = webix.ui._uid(this.name);
|
|
(this._prev_global_col || webix._global_collection)._translate_ids[value]=oldvalue;
|
|
}
|
|
webix.assert(!webix.ui.views[value], "Non unique view id: "+value);
|
|
webix.ui.views[value] = this;
|
|
this._viewobj.setAttribute("view_id", value);
|
|
return value;
|
|
},
|
|
$setSize:function(x,y){
|
|
var last = this._last_size;
|
|
if (last && last[0]==x && last[1]==y) {
|
|
webix.debug_size_box(this, [x,y,"not changed"]);
|
|
return false;
|
|
}
|
|
|
|
webix.debug_size_box(this, [x,y]);
|
|
|
|
this._last_size = [x,y];
|
|
this.$width = this._content_width = x-(this._scroll_y?webix.ui.scrollSize:0);
|
|
this.$height = this._content_height = y-(this._scroll_x?webix.ui.scrollSize:0);
|
|
|
|
var config = this._settings;
|
|
if (!config.flex){
|
|
this._viewobj.style.width = x+"px";
|
|
this._viewobj.style.height = y+"px";
|
|
}
|
|
|
|
return true;
|
|
},
|
|
$getSize:function(dx, dy){
|
|
var s = this._settings;
|
|
|
|
var size = [
|
|
(s.width || s.minWidth || 0)*1,
|
|
(s.width || s.maxWidth || 100000)*1,
|
|
(s.height || s.minHeight || 0)*1,
|
|
(s.height || s.maxHeight || 100000)*1,
|
|
s.gravity
|
|
];
|
|
|
|
if (webix.assert){
|
|
var check = (isNaN(size[0]) || isNaN(size[1]) || isNaN(size[2]) || isNaN(size[3]));
|
|
if (check){
|
|
webix.assert(false, "Size is not a number "+this._settings.id);
|
|
s.width = s.height = s.maxWidth = s.maxHeight = s.minWidth = s.minHeight = 0;
|
|
size = [0,0,100000,100000,1];
|
|
}
|
|
}
|
|
|
|
size[0]+=dx; size[1]+=dx;
|
|
size[2]+=dy; size[3]+=dy;
|
|
return size;
|
|
},
|
|
show:function(force, animate_settings){
|
|
var parent = this.getParentView();
|
|
var show = !arguments[2];
|
|
if (parent) {
|
|
if(!animate_settings && animate_settings !== false && this._settings.animate)
|
|
if (parent._settings.animate)
|
|
animate_settings = webix.extend((parent._settings.animate?webix.extend({},parent._settings.animate):{}), this._settings.animate, true);
|
|
|
|
if (show?parent._show:parent._hide)
|
|
(show?parent._show:parent._hide).call(parent, this, animate_settings);
|
|
if (show)
|
|
this._render_hidden_views();
|
|
|
|
//force show of parent view
|
|
//stop further processing is view is a part of isolated scope
|
|
if (force && show)
|
|
parent.show(parent.$$?false:force);
|
|
}
|
|
else{
|
|
if (this._settings.hidden){
|
|
if (show){
|
|
var node = webix.toNode(this._settings._container||document.body);
|
|
node.appendChild(this._viewobj);
|
|
this._settings.hidden = false;
|
|
|
|
this.adjust();
|
|
this._render_hidden_views();
|
|
}
|
|
} else {
|
|
if (!show){
|
|
this._settings.hidden = this._settings._hidden = true;
|
|
if (this._viewobj){
|
|
this._settings._container = this._viewobj.parentNode;
|
|
webix.html.remove(this._viewobj);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
_render_hidden_views:function(){
|
|
if (this._hidden_render){
|
|
for (var i=0; i < this._hidden_render.length; i++){
|
|
var ui_to_render = webix.$$(this._hidden_render[i]);
|
|
if (ui_to_render)
|
|
ui_to_render.render();
|
|
}
|
|
this._hidden_render = [];
|
|
this._hidden_hash = {};
|
|
}
|
|
},
|
|
_onKeyPress:function(code, e){
|
|
var target = e.srcElement || e.target, role = target.getAttribute("role");
|
|
|
|
if((code === 13 || code === 32) && role == "button" && !this._settings.disabled){
|
|
webix.html.triggerEvent(target, "MouseEvents", "click");
|
|
webix.html.preventEvent(e);
|
|
}
|
|
},
|
|
hidden_setter:function(value){
|
|
if (value) this.hide();
|
|
return this._settings.hidden;
|
|
},
|
|
hide:function(){
|
|
this.show(null, null, true);
|
|
webix.UIManager._moveChildFocus(this);
|
|
},
|
|
adjust:function(){
|
|
if(!this._viewobj.parentNode)
|
|
return false;
|
|
|
|
var x = this._viewobj.parentNode.clientWidth||0;
|
|
var y = this._viewobj.parentNode.clientHeight||0;
|
|
|
|
var sizes=this.$getSize(0,0);
|
|
var fullscreen = (this._viewobj.parentNode == document.body) && !this.setPosition;
|
|
|
|
//minWidth
|
|
if (sizes[0]>x) x = sizes[0];
|
|
//minHeight
|
|
if (sizes[2]>y) y = sizes[2];
|
|
|
|
//maxWidth rule
|
|
if ((!fullscreen || this._settings.width) && x>sizes[1]) x = sizes[1];
|
|
//maxHeight rule
|
|
if ((!fullscreen || this._settings.height) && y>sizes[3]) y = sizes[3];
|
|
|
|
this.$setSize(x,y);
|
|
if (webix._responsive_exception){
|
|
webix._responsive_exception = false;
|
|
this.adjust();
|
|
}
|
|
},
|
|
resize:function(force){
|
|
if (webix._child_sizing_active || webix.ui.$freeze || webix._responsive_tinkery ) return;
|
|
|
|
var parent = this.getParentView();
|
|
if (parent){
|
|
if (parent.resizeChildren)
|
|
parent.resizeChildren();
|
|
else
|
|
parent.resize();
|
|
} else {
|
|
this.adjust();
|
|
webix.callEvent("onResize",[]);
|
|
}
|
|
}
|
|
}, webix.Settings, webix.Destruction, webix.BaseBind, webix.UIExtension);
|
|
|
|
|
|
|
|
/*
|
|
don't render borders itself , but aware of layout , which can set some borders
|
|
*/
|
|
webix.protoUI({
|
|
name:"view",
|
|
$init:function(config){
|
|
this._set_inner(config);
|
|
},
|
|
|
|
//deside, will component use borders or not
|
|
_set_inner:function(config){
|
|
var border_not_set = webix.isUndefined(config.borderless);
|
|
if (border_not_set && !this.setPosition && config.$topView){
|
|
config.borderless = true;
|
|
border_not_set = false;
|
|
}
|
|
|
|
if ((border_not_set && this.defaults.borderless) || config.borderless){
|
|
//button and custom borderless
|
|
config._inner = { top:true, left:true, bottom:true, right:true };
|
|
} else {
|
|
//default borders
|
|
if (!config._inner)
|
|
config._inner = {};
|
|
this._contentobj.style.borderWidth="1px";
|
|
}
|
|
},
|
|
|
|
$getSize:function(dx, dy){
|
|
|
|
var _borders = this._settings._inner;
|
|
if (_borders){
|
|
dx += (_borders.left?0:1)+(_borders.right?0:1);
|
|
dy += (_borders.top?0:1)+(_borders.bottom?0:1);
|
|
}
|
|
|
|
var size = webix.ui.baseview.prototype.$getSize.call(this, dx, dy);
|
|
|
|
webix.debug_size_box(this, size, true);
|
|
return size;
|
|
},
|
|
$setSize:function(x,y){
|
|
webix.debug_size_box(this, [x,y]);
|
|
|
|
var _borders = this._settings._inner;
|
|
if (_borders){
|
|
x -= (_borders.left?0:1)+(_borders.right?0:1);
|
|
y -= (_borders.top?0:1)+(_borders.bottom?0:1);
|
|
}
|
|
|
|
return webix.ui.baseview.prototype.$setSize.call(this,x,y);
|
|
}
|
|
}, webix.ui.baseview);
|
|
|
|
})();
|
|
|
|
webix.ui.view.call(webix);
|
|
|
|
webix.debug_size_indent = 0;
|
|
webix.debug_size_step = function(){
|
|
var str = "";
|
|
for (var i=0; i<webix.debug_size_indent; i++)
|
|
str+="| ";
|
|
return str;
|
|
};
|
|
webix.debug_size_box_start = function(comp, get){
|
|
if (!webix.debug_size) return;
|
|
if (!webix.debug_size_indent)
|
|
webix.log(get?"--- get sizes ---":"--- set sizes ---");
|
|
webix.log(webix.debug_size_step()+comp.name+"@"+comp.config.id);
|
|
webix.debug_size_indent++;
|
|
};
|
|
webix.debug_size_box_end = function(comp, sizes){
|
|
if (!webix.debug_size) return;
|
|
webix.debug_size_indent--;
|
|
webix.log(webix.debug_size_step()+sizes.join(","));
|
|
};
|
|
|
|
webix.debug_size_box = function(comp, sizes, get){
|
|
if (!webix.debug_size) return;
|
|
if (!webix.debug_size_indent)
|
|
webix.log(get?"--- get sizes ---":"--- set sizes ---");
|
|
webix.log(webix.debug_size_step()+comp.name+"@"+comp.config.id+" "+sizes.join(","));
|
|
};
|
|
|
|
webix.protoUI({
|
|
name:"spacer",
|
|
defaults:{
|
|
borderless:true
|
|
},
|
|
$init:function(){
|
|
this._viewobj.className += " webix_spacer";
|
|
}
|
|
}, webix.ui.view);
|
|
|
|
webix.protoUI({
|
|
name:"baselayout",
|
|
$init:function(config){
|
|
this.$ready.push(this._parse_cells);
|
|
this._dataobj = this._contentobj;
|
|
this._layout_sizes = [];
|
|
this._responsive = [];
|
|
|
|
if (config.$topView){
|
|
config.borderless = true;
|
|
config._inner = { top:true, left:true, bottom:true, right:true };
|
|
}
|
|
|
|
if (config.isolate)
|
|
webix.extend(this, webix.IdSpace);
|
|
},
|
|
rows_setter:function(value){
|
|
this._vertical_orientation = 1;
|
|
this._collection = value;
|
|
},
|
|
cols_setter:function(value){
|
|
this._vertical_orientation = 0;
|
|
this.$view.style.whiteSpace = "nowrap";
|
|
this._collection = value;
|
|
},
|
|
_remove:function(view){
|
|
webix.PowerArray.removeAt.call(this._cells, webix.PowerArray.find.call(this._cells, view));
|
|
this.resizeChildren(true);
|
|
},
|
|
_replace:function(new_view,target_id){
|
|
if (webix.isUndefined(target_id)){
|
|
for (var i=0; i < this._cells.length; i++)
|
|
this._cells[i].destructor();
|
|
this._collection = new_view;
|
|
this._parse_cells();
|
|
} else {
|
|
var source;
|
|
if (typeof target_id == "number"){
|
|
if (target_id<0 || target_id > this._cells.length)
|
|
target_id = this._cells.length;
|
|
var prev_node = (this._cells[target_id]||{})._viewobj;
|
|
webix.PowerArray.insertAt.call(this._cells, new_view, target_id);
|
|
if (!new_view._settings.hidden)
|
|
webix.html.insertBefore(new_view._viewobj, prev_node, this._dataobj);
|
|
} else {
|
|
source = webix.$$(target_id);
|
|
target_id = webix.PowerArray.find.call(this._cells, source);
|
|
webix.assert(target_id!=-1, "Attempt to replace the non-existing view");
|
|
var parent = source._viewobj.parentNode;
|
|
if (parent && !new_view._settings.hidden)
|
|
parent.insertBefore(new_view._viewobj, source._viewobj);
|
|
|
|
source.destructor();
|
|
this._cells[target_id] = new_view;
|
|
}
|
|
|
|
if (!this._vertical_orientation)
|
|
this._fix_vertical_layout(new_view);
|
|
|
|
this._cells[target_id]._parent_cell = this;
|
|
}
|
|
this.resizeChildren(true);
|
|
|
|
var form = this.elements ? this : this.getFormView();
|
|
if (form) form._recollect_elements();
|
|
|
|
webix.callEvent("onReconstruct",[this]);
|
|
},
|
|
_fix_vertical_layout:function(cell){
|
|
cell._viewobj.style.display = "inline-block";
|
|
cell._viewobj.style.verticalAlign = "top";
|
|
},
|
|
addView:function(view, index){
|
|
if (webix.isUndefined(index))
|
|
index = this._cells.length;
|
|
var top = this.$$ ? this : this.getTopParentView();
|
|
top = (top && top.ui) ? top : webix;
|
|
return top.ui(view, this, index)._settings.id;
|
|
},
|
|
removeView:function(id){
|
|
var view;
|
|
if (typeof id != "object")
|
|
view = webix.$$(id) || (this.$$ ? this.$$(id) : null);
|
|
else
|
|
view = id;
|
|
|
|
var target = webix.PowerArray.find.call(this._cells, view);
|
|
if (target >= 0){
|
|
if (this._beforeRemoveView)
|
|
this._beforeRemoveView(target, view);
|
|
|
|
var form = this.elements ? this : this.getFormView();
|
|
|
|
this._cells.splice(target, 1);
|
|
if (form)
|
|
webix.ui.each(view, function(sub){
|
|
if (sub.name)
|
|
delete form.getCleanValues()[sub.config.name];
|
|
}, form, true);
|
|
|
|
view.destructor();
|
|
this.resizeChildren(true);
|
|
|
|
if (form)
|
|
form._recollect_elements();
|
|
} else
|
|
webix.assert(false, "Attemp to remove not existing view: "+id);
|
|
|
|
webix.callEvent("onReconstruct",[this]);
|
|
},
|
|
reconstruct:function(){
|
|
this._hiddencells = 0;
|
|
this._replace(this._collection);
|
|
},
|
|
_hide:function(obj, settings, silent){
|
|
if (obj._settings.hidden) return;
|
|
obj._settings.hidden = true;
|
|
webix.html.remove(obj._viewobj);
|
|
this._hiddencells++;
|
|
if (!silent && !webix._ui_creation)
|
|
this.resizeChildren(true);
|
|
},
|
|
_signal_hidden_cells:function(view){
|
|
if (view.callEvent)
|
|
view.callEvent("onViewShow",[]);
|
|
},
|
|
resizeChildren:function(){
|
|
if (webix.ui.$freeze) return;
|
|
|
|
if (this._layout_sizes){
|
|
var parent = this.getParentView();
|
|
if (parent){
|
|
if (parent.resizeChildren)
|
|
return parent.resizeChildren();
|
|
else
|
|
return parent.resize();
|
|
}
|
|
|
|
var sizes = this.$getSize(0,0);
|
|
|
|
var x,y,nx,ny;
|
|
nx = x = this._layout_sizes[0] || 0;
|
|
ny = y = this._layout_sizes[1] || 0;
|
|
|
|
//for auto-fill content, use adjust strategy
|
|
if ((sizes[1]>=100000 || sizes[3] >= 100000) && this._viewobj.parentNode){
|
|
//in hidden container adjust doesn't work, so fallback to last known size
|
|
//also, ensure that min-size is not violated
|
|
nx = x = Math.max(sizes[0], (this._settings.width || this._viewobj.parentNode.offsetWidth || x || 0));
|
|
ny = y = Math.max(sizes[2], (this._settings.height || this._viewobj.parentNode.offsetHeight || y || 0));
|
|
}
|
|
|
|
if (!parent){
|
|
//minWidth
|
|
if (sizes[0]>x) nx = sizes[0];
|
|
//minHeight
|
|
if (sizes[2]>y) ny = sizes[2];
|
|
|
|
//maxWidth rule
|
|
if (x>sizes[1]) nx = sizes[1];
|
|
//maxHeight rule
|
|
if (y>sizes[3]) ny = sizes[3];
|
|
|
|
this.$setSize(nx,ny);
|
|
} else
|
|
this._set_child_size(x,y);
|
|
|
|
if (webix._responsive_exception){
|
|
webix._responsive_exception = false;
|
|
this.resizeChildren();
|
|
}
|
|
|
|
webix.callEvent("onResize",[]);
|
|
}
|
|
},
|
|
getChildViews:function(){
|
|
return this._cells;
|
|
},
|
|
index:function(obj){
|
|
if (obj._settings)
|
|
obj = obj._settings.id;
|
|
for (var i=0; i < this._cells.length; i++)
|
|
if (this._cells[i]._settings.id == obj)
|
|
return i;
|
|
return -1;
|
|
},
|
|
_show:function(obj, settings, silent){
|
|
|
|
if (!obj._settings.hidden) return;
|
|
obj._settings.hidden = false;
|
|
|
|
//index of sibling cell, next to which new item will appear
|
|
var index = this.index(obj)+1;
|
|
//locate nearest visible cell
|
|
while (this._cells[index] && this._cells[index]._settings.hidden) index++;
|
|
var view = this._cells[index] ? this._cells[index]._viewobj : null;
|
|
|
|
webix.html.insertBefore(obj._viewobj, view, (this._dataobj||this._viewobj));
|
|
this._hiddencells--;
|
|
|
|
if (!silent){
|
|
this.resizeChildren(true);
|
|
if (obj.refresh)
|
|
obj.refresh();
|
|
}
|
|
|
|
if (obj.callEvent){
|
|
obj.callEvent("onViewShow", []);
|
|
webix.ui.each(obj, this._signal_hidden_cells);
|
|
}
|
|
},
|
|
showBatch:function(name, mode){
|
|
var preserve = typeof mode != "undefined";
|
|
mode = mode !== false;
|
|
|
|
if (!preserve){
|
|
if (this._settings.visibleBatch == name ) return;
|
|
this._settings.visibleBatch = name;
|
|
} else
|
|
this._settings.visibleBatch = "";
|
|
|
|
var show = [];
|
|
for (var i=0; i < this._cells.length; i++){
|
|
if (!this._cells[i]._settings.batch)
|
|
show.push(this._cells[i]);
|
|
else if (this._cells[i]._settings.batch == name){
|
|
if (mode)
|
|
show.push(this._cells[i]);
|
|
else
|
|
this._hide(this._cells[i], null, true);
|
|
} else if (!preserve)
|
|
this._hide(this._cells[i], null, true);
|
|
}
|
|
|
|
for (var i=0; i < show.length; i++){
|
|
this._show(show[i], null, true);
|
|
show[i]._render_hidden_views();
|
|
}
|
|
|
|
this.resizeChildren(true);
|
|
},
|
|
_parse_cells:function(collection){
|
|
this._cells=[];
|
|
|
|
webix.assert(collection,this.name+" was incorrectly defined. <br><br> You have missed rows|cols|cells|elements collection");
|
|
for (var i=0; i<collection.length; i++){
|
|
webix._parent_cell = this;
|
|
if (!collection[i]._inner)
|
|
collection[i].borderless = true;
|
|
|
|
this._cells[i]=webix.ui._view(collection[i], this);
|
|
if (!this._vertical_orientation)
|
|
this._fix_vertical_layout(this._cells[i]);
|
|
|
|
if (this._settings.visibleBatch && this._settings.visibleBatch != this._cells[i]._settings.batch && this._cells[i]._settings.batch){
|
|
this._cells[i]._settings.hidden = true;
|
|
this._hiddencells++;
|
|
}
|
|
|
|
if (!this._cells[i]._settings.hidden){
|
|
(this._dataobj||this._contentobj).appendChild(this._cells[i]._viewobj);
|
|
if (this._cells[i].$nospace)
|
|
this._hiddencells++;
|
|
}
|
|
}
|
|
|
|
if (this._parse_cells_ext_end)
|
|
this._parse_cells_ext_end(collection);
|
|
},
|
|
_bubble_size:function(prop, size, vertical){
|
|
if (this._vertical_orientation != vertical)
|
|
for (var i=0; i<this._cells.length; i++){
|
|
this._cells[i]._settings[prop] = size;
|
|
if (this._cells[i]._bubble_size)
|
|
this._cells[i]._bubble_size(prop, size, vertical);
|
|
}
|
|
},
|
|
$getSize:function(dx, dy){
|
|
webix.debug_size_box_start(this, true);
|
|
var minWidth = 0;
|
|
var maxWidth = 100000;
|
|
var maxHeight = 100000;
|
|
var minHeight = 0;
|
|
if (this._vertical_orientation) maxHeight=0; else maxWidth = 0;
|
|
|
|
var fixed = 0;
|
|
var fixed_count = 0;
|
|
var gravity = 0;
|
|
this._sizes=[];
|
|
|
|
for (var i=0; i < this._cells.length; i++) {
|
|
//ignore hidden cells
|
|
if (this._cells[i]._settings.hidden)
|
|
continue;
|
|
|
|
var sizes = this._sizes[i] = this._cells[i].$getSize(0,0);
|
|
|
|
if (this._cells[i].$nospace){
|
|
fixed_count++;
|
|
continue;
|
|
}
|
|
|
|
if (this._vertical_orientation){
|
|
//take max minSize value
|
|
if (sizes[0]>minWidth) minWidth = sizes[0];
|
|
//take min maxSize value
|
|
if (sizes[1]<maxWidth) maxWidth = sizes[1];
|
|
|
|
minHeight += sizes[2];
|
|
maxHeight += sizes[3];
|
|
|
|
if (sizes[2] == sizes[3] && sizes[2] != -1){ fixed+=sizes[2]; fixed_count++; }
|
|
else gravity += sizes[4];
|
|
} else {
|
|
//take max minSize value
|
|
if (sizes[2]>minHeight) minHeight = sizes[2];
|
|
//take min maxSize value
|
|
if (sizes[3]<maxHeight) maxHeight = sizes[3];
|
|
|
|
minWidth += sizes[0];
|
|
maxWidth += sizes[1];
|
|
|
|
if (sizes[0] == sizes[1] && sizes[0] != -1){ fixed+=sizes[0]; fixed_count++; }
|
|
else gravity += sizes[4];
|
|
}
|
|
}
|
|
|
|
if (minHeight>maxHeight)
|
|
maxHeight = minHeight;
|
|
if (minWidth>maxWidth)
|
|
maxWidth = minWidth;
|
|
|
|
this._master_size = [fixed, this._cells.length - fixed_count, gravity];
|
|
this._desired_size = [minWidth+dx, minHeight+dy];
|
|
|
|
//get layout sizes
|
|
var self_size = webix.ui.baseview.prototype.$getSize.call(this, 0, 0);
|
|
//use child settings if layout's one was not defined
|
|
if (self_size[1] >= 100000) self_size[1]=0;
|
|
if (self_size[3] >= 100000) self_size[3]=0;
|
|
|
|
self_size[0] = (self_size[0] || minWidth ) +dx;
|
|
self_size[1] = Math.max(self_size[0], (self_size[1] || maxWidth ) +dx);
|
|
self_size[2] = (self_size[2] || minHeight) +dy;
|
|
self_size[3] = Math.max(self_size[2], (self_size[3] || maxHeight) +dy);
|
|
|
|
webix.debug_size_box_end(this, self_size);
|
|
|
|
if (!this._vertical_orientation && this._settings.responsive)
|
|
self_size[0] = 0;
|
|
|
|
return self_size;
|
|
},
|
|
$setSize:function(x,y){
|
|
this._layout_sizes = [x,y];
|
|
webix.debug_size_box_start(this);
|
|
|
|
webix.ui.baseview.prototype.$setSize.call(this,x,y);
|
|
this._set_child_size(x,y);
|
|
|
|
webix.debug_size_box_end(this, [x,y]);
|
|
},
|
|
_set_child_size_a:function(sizes, min, max){
|
|
min = sizes[min]; max = sizes[max];
|
|
var height = min;
|
|
|
|
if (min != max){
|
|
var ps = this._set_size_delta * sizes[4]/this._set_size_gravity;
|
|
if (ps < min){
|
|
height = min;
|
|
this._set_size_gravity -= sizes[4];
|
|
this._set_size_delta -= height;
|
|
} else if (ps > max){
|
|
height = max;
|
|
this._set_size_gravity -= sizes[4];
|
|
this._set_size_delta -= height;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return height;
|
|
},
|
|
_responsive_hide:function(cell, mode){
|
|
var target = webix.$$(mode);
|
|
|
|
if (target === "hide" || !target){
|
|
cell.hide();
|
|
cell._responsive_marker = "hide";
|
|
} else{
|
|
//for SideBar in Webix 1.9
|
|
if (!target)
|
|
target = webix.ui({ view:"popup", body:[{}]});
|
|
|
|
cell._responsive_width = cell._settings.width;
|
|
cell._responsive_height = cell._settings.height;
|
|
cell._responsive_marker = target._settings.id;
|
|
cell._settings.width = 0;
|
|
if (!cell._settings.height)
|
|
cell._settings.autoheight = true;
|
|
|
|
webix.ui(cell, target, this._responsive.length);
|
|
}
|
|
|
|
this._responsive.push(cell);
|
|
},
|
|
_responsive_show:function(cell){
|
|
var target = cell._responsive_marker;
|
|
cell._responsive_marker = 0;
|
|
|
|
if (target === "hide" || !target){
|
|
cell.show();
|
|
} else {
|
|
cell._settings.width = cell._responsive_width;
|
|
cell._settings.height = cell._responsive_height;
|
|
delete cell._settings.autoheight;
|
|
|
|
var index = 0;
|
|
while (this._cells[index] && this._cells[index]._settings.responsiveCell === false) index++;
|
|
webix.ui(cell, this, index);
|
|
}
|
|
this._responsive.pop();
|
|
},
|
|
_responsive_cells:function(x,y){
|
|
webix._responsive_tinkery = true;
|
|
if (x + this._paddingX*2 + this._margin * (this._cells.length-1)< this._desired_size[0]){
|
|
var max = this._cells.length - 1;
|
|
for (var i = 0; i < max; i++){
|
|
var cell = this._cells[i];
|
|
if (!cell._responsive_marker){
|
|
if (cell._settings.responsiveCell !== false){
|
|
this._responsive_hide(cell, this._settings.responsive);
|
|
webix.callEvent("onResponsiveHide", [cell._settings.id]);
|
|
webix._responsive_exception = true;
|
|
break;
|
|
} else {
|
|
max = this._cells.length;
|
|
}
|
|
}
|
|
}
|
|
} else if (this._responsive.length){
|
|
var cell = this._responsive[this._responsive.length-1];
|
|
var dx = cell._responsive_marker == "hide" ? 0 : cell._responsive_width;
|
|
var px = cell.$getSize(dx,0);
|
|
if (px[0] + this._desired_size[0] + this._margin + 20 <= x ){
|
|
this._responsive_show(cell);
|
|
webix.callEvent("onResponsiveShow", [cell._settings.id]);
|
|
webix._responsive_exception = true;
|
|
}
|
|
}
|
|
|
|
webix._responsive_tinkery = false;
|
|
},
|
|
_set_child_size:function(x,y){
|
|
webix._child_sizing_active = (webix._child_sizing_active||0)+1;
|
|
|
|
if (!this._vertical_orientation && this._settings.responsive)
|
|
this._responsive_cells(x,y);
|
|
|
|
|
|
this._set_size_delta = (this._vertical_orientation?y:x) - this._master_size[0];
|
|
this._set_size_gravity = this._master_size[2];
|
|
var width = x; var height = y;
|
|
|
|
var auto = [];
|
|
for (var i=0; i < this._cells.length; i++){
|
|
//ignore hidden cells
|
|
if (this._cells[i]._settings.hidden || !this._sizes[i])
|
|
continue;
|
|
|
|
var sizes = this._sizes[i];
|
|
|
|
if (this._vertical_orientation){
|
|
var height = this._set_child_size_a(sizes,2,3);
|
|
if (height < 0) { auto.push(i); continue; }
|
|
} else {
|
|
var width = this._set_child_size_a(sizes,0,1);
|
|
if (width < 0) { auto.push(i); continue; }
|
|
}
|
|
this._cells[i].$setSize(width,height);
|
|
}
|
|
|
|
for (var i = 0; i < auto.length; i++){
|
|
var index = auto[i];
|
|
var sizes = this._sizes[index];
|
|
var dx = Math.round(this._set_size_delta * sizes[4]/this._set_size_gravity);
|
|
this._set_size_delta -= dx; this._set_size_gravity -= sizes[4];
|
|
if (this._vertical_orientation)
|
|
height = dx;
|
|
else {
|
|
width = dx;
|
|
}
|
|
|
|
this._cells[index].$setSize(width,height);
|
|
}
|
|
|
|
webix._child_sizing_active -= 1;
|
|
},
|
|
_next:function(obj, mode){
|
|
var index = this.index(obj);
|
|
if (index == -1) return null;
|
|
return this._cells[index+mode];
|
|
},
|
|
_first:function(){
|
|
return this._cells[0];
|
|
}
|
|
}, webix.EventSystem, webix.ui.baseview);
|
|
|
|
|
|
|
|
|
|
webix.protoUI({
|
|
name:"layout",
|
|
$init:function(config){
|
|
this._hiddencells = 0;
|
|
},
|
|
defaults:{
|
|
type:"line"
|
|
},
|
|
_parse_cells:function(){
|
|
if (this._parse_cells_ext)
|
|
collection = this._parse_cells_ext(collection);
|
|
|
|
if (!this._parse_once){
|
|
this._viewobj.className += " webix_layout_"+(this._settings.type||"");
|
|
this._parse_once = 1;
|
|
}
|
|
|
|
if (this._settings.margin !== webix.undefined)
|
|
this._margin = this._settings.margin;
|
|
|
|
if (this._settings.padding != webix.undefined)
|
|
this._paddingX = this._paddingY = this._settings.padding;
|
|
if (this._settings.paddingX !== webix.undefined)
|
|
this._paddingX = this._settings.paddingX;
|
|
if (this._settings.paddingY !== webix.undefined)
|
|
this._paddingY = this._settings.paddingY;
|
|
|
|
if (this._paddingY || this._paddingX)
|
|
this._padding = true;
|
|
|
|
//if layout has paddings we need to set the visible border
|
|
if (this._hasBorders() && !this._settings.borderless){
|
|
this._contentobj.style.borderWidth="1px";
|
|
//if layout has border - normal bordering rules are applied
|
|
this._render_borders = true;
|
|
}
|
|
|
|
|
|
var collection = this._collection;
|
|
|
|
if (this._settings.borderless)
|
|
this._settings._inner = { top:true, left:true, right:true, bottom:true};
|
|
|
|
this._beforeResetBorders(collection);
|
|
webix.ui.baselayout.prototype._parse_cells.call(this, collection);
|
|
this._afterResetBorders(collection);
|
|
},
|
|
$getSize:function(dx, dy){
|
|
dx=dx||0; dy=dy||0;
|
|
|
|
var correction = this._margin*(this._cells.length-this._hiddencells-1);
|
|
if (this._render_borders || this._hasBorders()){
|
|
var _borders = this._settings._inner;
|
|
if (_borders){
|
|
dx += (_borders.left?0:1)+(_borders.right?0:1);
|
|
dy += (_borders.top?0:1)+(_borders.bottom?0:1);
|
|
}
|
|
}
|
|
|
|
if (!this._settings.height)
|
|
dy += (this._paddingY||0)*2 + (this._vertical_orientation ? correction : 0);
|
|
|
|
if (!this._settings.width)
|
|
dx += (this._paddingX||0)*2 + (this._vertical_orientation ? 0 : correction);
|
|
|
|
return webix.ui.baselayout.prototype.$getSize.call(this, dx, dy);
|
|
},
|
|
$setSize:function(x,y){
|
|
this._layout_sizes = [x,y];
|
|
webix.debug_size_box_start(this);
|
|
|
|
var result;
|
|
if (this._hasBorders()||this._render_borders)
|
|
result = webix.ui.view.prototype.$setSize.call(this,x,y);
|
|
else
|
|
result = webix.ui.baseview.prototype.$setSize.call(this,x,y);
|
|
|
|
//form with scroll
|
|
y = this._content_height;
|
|
x = this._content_width;
|
|
|
|
var config = this._settings;
|
|
if (config.scroll){
|
|
y = Math.max(y, this._desired_size[1]);
|
|
x = Math.max(x, this._desired_size[0]);
|
|
}
|
|
|
|
this._set_child_size(x, y);
|
|
|
|
webix.debug_size_box_end(this, [x,y]);
|
|
},
|
|
_set_child_size:function(x,y){
|
|
var correction = this._margin*(this._cells.length-this._hiddencells-1);
|
|
|
|
if (this._vertical_orientation){
|
|
y-=correction+this._paddingY*2;
|
|
x-=this._paddingX*2;
|
|
}
|
|
else {
|
|
x-=correction+this._paddingX*2;
|
|
y-=this._paddingY*2;
|
|
}
|
|
return webix.ui.baselayout.prototype._set_child_size.call(this, x, y);
|
|
},
|
|
resizeChildren:function(structure_changed){
|
|
if (structure_changed){
|
|
this._last_size = null; //forces children resize
|
|
var config = [];
|
|
for (var i = 0; i < this._cells.length; i++){
|
|
var cell = this._cells[i];
|
|
config[i] = cell._settings;
|
|
var n = ((cell._layout_sizes && !cell._render_borders) || cell._settings.borderless)?"0px":"1px";
|
|
|
|
cell._viewobj.style.borderTopWidth=cell._viewobj.style.borderBottomWidth=cell._viewobj.style.borderLeftWidth=cell._viewobj.style.borderRightWidth=n;
|
|
}
|
|
|
|
this._beforeResetBorders(config);
|
|
for (var i=0; i<config.length; i++)
|
|
if (config[i].borderless && this._cells[i]._set_inner)
|
|
this._cells[i]._set_inner(config[i]);
|
|
this._afterResetBorders(this._cells);
|
|
}
|
|
|
|
if (webix._responsive_tinkery) return;
|
|
webix.ui.baselayout.prototype.resizeChildren.call(this);
|
|
},
|
|
_hasBorders:function(){
|
|
return this._padding && this._margin>0 && !this._cleanlayout;
|
|
},
|
|
_beforeResetBorders:function(collection){
|
|
if (this._hasBorders() && (!this._settings.borderless || this._settings.type == "space")){
|
|
for (var i=0; i < collection.length; i++){
|
|
if (!collection[i]._inner || !collection[i].borderless)
|
|
collection[i]._inner={ top:false, left:false, right:false, bottom:false};
|
|
}
|
|
} else {
|
|
for (var i=0; i < collection.length; i++)
|
|
collection[i]._inner=webix.clone(this._settings._inner);
|
|
var mode = false;
|
|
if (this._cleanlayout)
|
|
mode = true;
|
|
|
|
var maxlength = collection.length;
|
|
if (this._vertical_orientation){
|
|
for (var i=1; i < maxlength-1; i++)
|
|
collection[i]._inner.top = collection[i]._inner.bottom = mode;
|
|
if (maxlength>1){
|
|
if (this._settings.type!="head")
|
|
collection[0]._inner.bottom = mode;
|
|
|
|
while (collection[maxlength-1].hidden && maxlength>1)
|
|
maxlength--;
|
|
if (maxlength>0)
|
|
collection[maxlength-1]._inner.top = mode;
|
|
}
|
|
}
|
|
else {
|
|
for (var i=1; i < maxlength-1; i++)
|
|
collection[i]._inner.left = collection[i]._inner.right= mode;
|
|
if (maxlength>1){
|
|
if (this._settings.type!="head")
|
|
collection[0]._inner.right= mode;
|
|
collection[maxlength-1]._inner.left = mode;
|
|
|
|
while (maxlength>1 && collection[maxlength-1].hidden)
|
|
maxlength--;
|
|
if (maxlength>0)
|
|
collection[maxlength-1]._inner.left = mode;
|
|
}
|
|
}
|
|
|
|
}
|
|
},
|
|
_fix_container_borders:function(style, inner){
|
|
if (inner.top)
|
|
style.borderTopWidth="0px";
|
|
if (inner.left)
|
|
style.borderLeftWidth="0px";
|
|
if (inner.right)
|
|
style.borderRightWidth="0px";
|
|
if (inner.bottom)
|
|
style.borderBottomWidth="0px";
|
|
},
|
|
_afterResetBorders:function(collection){
|
|
var start = 0;
|
|
for (var i=0; i<collection.length; i++){
|
|
var cell = this._cells[i];
|
|
|
|
var s_inner = cell._settings._inner;
|
|
if (cell._settings.hidden && this._cells[i+1]){
|
|
var s_next = this._cells[i+1]._settings._inner;
|
|
if (!s_inner.top)
|
|
s_next.top = false;
|
|
if (!s_inner.left)
|
|
s_next.left = false;
|
|
|
|
if (i==start) start++;
|
|
}
|
|
this._fix_container_borders(cell._viewobj.style, cell._settings._inner);
|
|
}
|
|
|
|
var style = this._vertical_orientation?"marginLeft":"marginTop";
|
|
var contrstyle = this._vertical_orientation?"marginTop":"marginLeft";
|
|
var padding = this._vertical_orientation?this._paddingX:this._paddingY;
|
|
var contrpadding = this._vertical_orientation?this._paddingY:this._paddingX;
|
|
|
|
//add top offset to all
|
|
for (var i=0; i<collection.length; i++)
|
|
this._cells[i]._viewobj.style[style] = (padding||0) + "px";
|
|
|
|
//add left offset to first cell
|
|
if (this._cells.length)
|
|
this._cells[start]._viewobj.style[contrstyle] = (contrpadding||0)+"px";
|
|
|
|
//add offset between cells
|
|
for (var index=start+1; index<collection.length; index++)
|
|
this._cells[index]._viewobj.style[contrstyle]=this._margin+"px";
|
|
|
|
},
|
|
type_setter:function(value){
|
|
this._margin = (typeof this._margin_set[value] != "undefined"? this._margin_set[value]: this._margin_set["line"]);
|
|
this._paddingX = this._paddingY = (typeof this._margin_set[value] != "undefined"? this._padding_set[value]: this._padding_set["line"]);
|
|
this._cleanlayout = (value=="material" || value=="clean");
|
|
if (value == "material")
|
|
this._settings.borderless = true;
|
|
|
|
return value;
|
|
},
|
|
$skin:function(){
|
|
var skin = webix.skin.$active;
|
|
this._margin_set = skin.layoutMargin;
|
|
this._padding_set = skin.layoutPadding;
|
|
}
|
|
}, webix.ui.baselayout);
|
|
|
|
webix.ui.layout.call(webix);
|
|
|
|
webix.FlexLayout = {
|
|
$init:function(){
|
|
this.$view.className += " webix_flexlayout";
|
|
},
|
|
_fix_vertical_layout:function(){
|
|
|
|
},
|
|
_beforeResetBorders:function(){
|
|
|
|
},
|
|
_afterResetBorders:function(){
|
|
|
|
},
|
|
$getSize:function(dx, dy){
|
|
webix.debug_size_box_start(this, true);
|
|
|
|
var w=0, h=0, g = this._settings.gravity;
|
|
this._sizes = [];
|
|
|
|
for (var i=0; i<this._cells.length; i++){
|
|
var size = this._cells[i].$getSize(0,0);
|
|
this._sizes.push(size);
|
|
|
|
w = Math.max(w, size[0]);
|
|
h = Math.max(h, size[2]);
|
|
}
|
|
|
|
w += (this._paddingX||0)*2;
|
|
h += (this._paddingY||0)*2;
|
|
|
|
if (this._settings.width)
|
|
w = Math.max(w, this._settings.width);
|
|
if (this._settings.height)
|
|
h = Math.max(h, this._settings.height);
|
|
|
|
var self_size = [w, 100000, h, 100000, g];
|
|
webix.debug_size_box_end(this, self_size);
|
|
return self_size;
|
|
},
|
|
_set_child_size:function(x,y){
|
|
var st = this.$view.style;
|
|
var margin = Math.round(this._margin/2);
|
|
st.paddingTop = st.paddingBottom = this._paddingY-margin + "px";
|
|
st.paddingLeft = st.paddingRight = this._paddingX-margin + "px";
|
|
|
|
for (var i=0; i<this._cells.length; i++){
|
|
if (this._cells[i]._settings.hidden) continue;
|
|
var view = this._cells[i].$view;
|
|
var size = this._sizes[i];
|
|
var config = this._cells[i]._settings;
|
|
|
|
if (view){
|
|
view.style.minWidth = size[0]+"px";
|
|
if (size[1] < 100000 && size[1] != size[0])
|
|
view.style.maxWidth = size[1]+"px";
|
|
|
|
view.style.flexBasis = config.flexBasis || (size[0])+"px";
|
|
view.style.flexGrow = config.flexGrow || ((size[1] != size[0]) ? size[4] : 0);
|
|
view.style.height = (size[3] != size[2]) ? "auto" : (size[2] + "px");
|
|
|
|
view.style.minHeight = size[2]+"px";
|
|
if (size[3] < 100000 && size[3] != size[2])
|
|
view.style.maxHeight = size[3]+"px";
|
|
|
|
view.style.margin = margin + "px";
|
|
}
|
|
}
|
|
|
|
var whs = [];
|
|
for (var i=0; i<this._cells.length; i++){
|
|
if (this._cells[i]._settings.hidden) continue;
|
|
var view = this._cells[i].$view;
|
|
whs[i] = [view.offsetWidth, view.offsetHeight];
|
|
}
|
|
|
|
for (var i=0; i<this._cells.length; i++){
|
|
if (this._cells[i]._settings.hidden) continue;
|
|
var cell = this._cells[i];
|
|
var view = cell.$view;
|
|
if (view){
|
|
cell._settings.flex = true;
|
|
var size = this._sizes[i];
|
|
var h = size[2] == size[3] ? size[2] : whs[i][1];
|
|
cell.$setSize(whs[i][0], h);
|
|
cell._settings.flex = false;
|
|
}
|
|
}
|
|
|
|
this.$height = this._content_height = this.$view.scrollHeight;
|
|
this.$view.style.height = this._content_height+"px";
|
|
}
|
|
};
|
|
webix.protoUI({
|
|
$init:function(){
|
|
webix.extend(this, webix.FlexLayout, true);
|
|
},
|
|
name:"flexlayout"
|
|
}, webix.ui.layout);
|
|
|
|
|
|
webix.protoUI({
|
|
name:"align",
|
|
defaults:{
|
|
borderless:true,
|
|
left:0, top:0, right:0, bottom:0
|
|
},
|
|
$init:function(){
|
|
this._viewobj.className += " webix_view_align";
|
|
},
|
|
getChildViews:function(){
|
|
return [this._body_cell];
|
|
},
|
|
body_setter:function(value){
|
|
value._inner = { top:false, left:false, right:false, bottom:false};
|
|
this._body_cell = webix.ui._view(value);
|
|
this._body_cell._parent_cell = this;
|
|
|
|
this._viewobj.appendChild(this._body_cell._viewobj);
|
|
return value;
|
|
},
|
|
align_setter:function(value){
|
|
if (typeof value === "string")
|
|
value = value.split(",");
|
|
|
|
this._x_align = this._y_align = this._p_align = "";
|
|
for (var i=0; i<value.length; i++){
|
|
var c = value[i];
|
|
if (c === "center" || c === "left" || c === "right")
|
|
this._x_align = c;
|
|
if (c === "top" || c === "bottom" || c === "middle")
|
|
this._y_align = c;
|
|
if (c === "absolute")
|
|
this._x_align = this._y_align = this._p_align = "precise";
|
|
}
|
|
|
|
return value;
|
|
},
|
|
getBody:function(){
|
|
return this._body_cell;
|
|
},
|
|
$setSize:function(x,y){
|
|
webix.ui.view.prototype.$setSize.call(this, x,y);
|
|
|
|
var dx, dy;
|
|
if (this._p_align){
|
|
dx = x - this._settings.left - this._settings.right;
|
|
dy = y - this._settings.top - this._settings.bottom;
|
|
} else {
|
|
dx = this._desired_size[0] || x;
|
|
dy = this._desired_size[2] || y;
|
|
}
|
|
|
|
|
|
|
|
this._body_cell.$setSize(dx, dy);
|
|
|
|
var box = this._body_cell._viewobj;
|
|
|
|
if (this._x_align == "center")
|
|
box.style.marginLeft = Math.ceil((x-dx)/2)+"px";
|
|
else if (this._x_align == "right")
|
|
box.style.marginLeft = (x-dx)+"px";
|
|
else
|
|
box.style.marginLeft = (this._p_align ? this._settings.left : 0) +"px";
|
|
|
|
if (this._y_align == "middle")
|
|
box.style.marginTop = Math.ceil((y-dy)/2)+"px";
|
|
else if (this._y_align == "bottom")
|
|
box.style.marginTop = (y-dy)+"px";
|
|
else
|
|
box.style.marginTop = (this._p_align ? this._settings.top : 0) + "px";
|
|
},
|
|
$getSize:function(dx,dy){
|
|
var size = this._desired_size = this._body_cell.$getSize(0,0);
|
|
var self_size = webix.ui.baseview.prototype.$getSize.call(this, 0, 0);
|
|
|
|
if (this._p_align){
|
|
dx += this._settings.left + this._settings.right;
|
|
dy += this._settings.top + this._settings.bottom;
|
|
}
|
|
|
|
if (!this._x_align || this._p_align){
|
|
self_size[0] = size[0]+dx;
|
|
self_size[1] = size[1]+dx;
|
|
} else {
|
|
self_size[0] = (self_size[0] || size[0] ) +dy;
|
|
self_size[1] += dx;
|
|
}
|
|
|
|
if (!this._y_align || this._p_align){
|
|
self_size[2] = size[2]+dy;
|
|
self_size[3] = size[3]+dy;
|
|
} else {
|
|
self_size[2] = (self_size[2] || size[2] ) +dy;
|
|
self_size[3] += dy;
|
|
}
|
|
|
|
return self_size;
|
|
}
|
|
}, webix.ui.view);
|
|
|
|
|
|
|
|
webix.animate = function(html_element, config){
|
|
var animation = config;
|
|
if (webix.isArray(html_element)){
|
|
for (var i=0; i < html_element.length; i++) {
|
|
if(webix.isArray(config))
|
|
animation = config[i];
|
|
|
|
if(animation.type == 'slide'){
|
|
if(animation.subtype == 'out' && i===0) { // next
|
|
continue;
|
|
}
|
|
if(animation.subtype == 'in' && i==1) { // current
|
|
continue;
|
|
}
|
|
}
|
|
if(animation.type == 'flip'){
|
|
var animation_copy = webix.clone(animation);
|
|
if(i===0) { // next
|
|
animation_copy.type = 'flipback';
|
|
}
|
|
if(i==1) { // current
|
|
animation_copy.callback = null;
|
|
}
|
|
webix.animate(html_element[i], animation_copy);
|
|
continue;
|
|
}
|
|
webix.animate(html_element[i], animation);
|
|
}
|
|
return;
|
|
}
|
|
var node = webix.toNode(html_element);
|
|
if (node._has_animation)
|
|
webix.animate.end(node, animation);
|
|
else
|
|
webix.animate.start(node, animation);
|
|
};
|
|
webix.animate.end = function(node, animation){
|
|
//stop animation
|
|
node.style[webix.env.transitionDuration] = "1ms";
|
|
node._has_animation = null;
|
|
//clear animation wait order, if any
|
|
if (webix._wait_animate)
|
|
window.clearTimeout(webix._wait_animate);
|
|
|
|
//plan next animation, if any
|
|
webix._wait_animate = webix.delay(webix.animate, webix, [node,animation],10);
|
|
};
|
|
webix.animate.isSupported=function(){
|
|
return !webix.$testmode && !webix.noanimate && webix.env.transform && webix.env.transition && !webix.env.isOpera;
|
|
};
|
|
webix.animate.formLine=function(next, current, animation){
|
|
var direction = animation.direction;
|
|
|
|
//sometimes user can initiate animation multiple times ( fast clicking )
|
|
//as result animation may be called against already removed from the dom node
|
|
if(current.parentNode)
|
|
current.parentNode.style.position = "relative";
|
|
|
|
current.style.position = "absolute";
|
|
next.style.position = "absolute";
|
|
|
|
//this is initial shift of second view in animation
|
|
//normally we need to have this value as 0
|
|
//but FF has bug with animation initially invisible elements
|
|
//so we are adjusting this value, to make 1px of second view visible
|
|
var defAniPos = webix.env.isFF ? ( direction == "top" || direction == "left" ? -1 : 1) : 0;
|
|
|
|
if(direction=="top"||direction=="bottom"){
|
|
next.style.left="0px";
|
|
next.style.top = (animation.top || defAniPos) + (direction=="top"?1:-1)*current.offsetHeight+"px";
|
|
}
|
|
else{
|
|
next.style.top = (animation.top || 0) + "px";
|
|
next.style.left = defAniPos + (direction=="left"?1:-1)*current.offsetWidth+"px";
|
|
}
|
|
|
|
// apply 'keepViews' mode, iframe, datatable with x scroll solution
|
|
//( keepViews won't work in case of "in" and "out" subtypes )
|
|
if(current.parentNode == next.parentNode && animation.keepViews)
|
|
next.style.display = "";
|
|
else
|
|
webix.html.insertBefore(next, current.nextSibling, current.parentNode);
|
|
|
|
if(animation.type == 'slide' && animation.subtype == 'out') {
|
|
next.style.left = "0px";
|
|
next.style.top = (animation.top || 0)+"px";
|
|
current.parentNode.removeChild(current);
|
|
webix.html.insertBefore(current, next.nextSibling, next.parentNode);
|
|
}
|
|
return [next, current];
|
|
};
|
|
webix.animate.breakLine=function(line){
|
|
if(arguments[1])
|
|
line[1].style.display = "none"; // 'keepViews' multiview mode
|
|
else
|
|
webix.html.remove(line[1]); // 1 = current
|
|
webix.animate.clear(line[0]);
|
|
webix.animate.clear(line[1]);
|
|
line[0].style.position="";
|
|
};
|
|
webix.animate.clear=function(node){
|
|
node.style[webix.env.transform] = "none";
|
|
node.style[webix.env.transition] = "none";
|
|
node.style.top = node.style.left = "";
|
|
};
|
|
webix.animate.defaults = {
|
|
type: 'slide',
|
|
delay: '0',
|
|
duration: '500',
|
|
timing: 'ease-in-out',
|
|
x: 0,
|
|
y: 0
|
|
};
|
|
webix.animate.start = function(node, animation){
|
|
//getting config object by merging specified and default options
|
|
if (typeof animation == 'string')
|
|
animation = {type: animation};
|
|
|
|
animation = webix.Settings._mergeSettings(animation,webix.animate.defaults);
|
|
|
|
var prefix = webix.env.cssPrefix;
|
|
var settings = node._has_animation = animation;
|
|
var skew_options, scale_type;
|
|
|
|
//jshint -W086:true
|
|
switch(settings.type == 'slide' && settings.direction) { // getting new x, y in case it is slide with direction
|
|
case 'right':
|
|
settings.x = node.offsetWidth;
|
|
break;
|
|
case 'left':
|
|
settings.x = -node.offsetWidth;
|
|
break;
|
|
case 'top':
|
|
settings.y = -node.offsetHeight;
|
|
break;
|
|
case 'bottom':
|
|
default:
|
|
settings.y = settings.y||node.offsetHeight;
|
|
break;
|
|
}
|
|
|
|
if(settings.type == 'flip' || settings.type == 'flipback') {
|
|
skew_options = [0, 0];
|
|
scale_type = 'scaleX';
|
|
if(settings.subtype == 'vertical') {
|
|
skew_options[0] = 20;
|
|
scale_type = 'scaleY';
|
|
}
|
|
else
|
|
skew_options[1] = 20;
|
|
if(settings.direction == 'right' || settings.direction == 'bottom') {
|
|
skew_options[0] *= -1; skew_options[1] *= -1;
|
|
}
|
|
}
|
|
|
|
var duration = settings.duration + "ms " + settings.timing + " " + settings.delay+"ms";
|
|
var css_general = prefix+"TransformStyle: preserve-3d;"; // general css rules
|
|
var css_transition = '';
|
|
var css_transform = '';
|
|
|
|
switch(settings.type) {
|
|
case 'fade': // changes opacity to 0
|
|
css_transition = "opacity " + duration;
|
|
css_general = "opacity: 0;";
|
|
break;
|
|
case 'show': // changes opacity to 1
|
|
css_transition = "opacity " + duration;
|
|
css_general = "opacity: 1;";
|
|
break;
|
|
case 'flip':
|
|
duration = (settings.duration/2) + "ms " + settings.timing + " " + settings.delay+"ms";
|
|
css_transform = "skew("+skew_options[0]+"deg, "+skew_options[1]+"deg) "+scale_type+"(0.00001)";
|
|
css_transition = "all "+(duration);
|
|
break;
|
|
case 'flipback':
|
|
settings.delay += settings.duration/2;
|
|
duration = (settings.duration/2) + "ms " + settings.timing + " " + settings.delay+"ms";
|
|
node.style[webix.env.transform] = "skew("+(-1*skew_options[0])+"deg, "+(-1*skew_options[1])+"deg) "+scale_type+"(0.00001)";
|
|
node.style.left = "0";
|
|
|
|
css_transform = "skew(0deg, 0deg) "+scale_type+"(1)";
|
|
css_transition = "all "+(duration);
|
|
break;
|
|
case 'slide': // moves object to specified location
|
|
var x = settings.x +"px";
|
|
var y = settings.y +"px";
|
|
// translate(x, y) OR translate3d(x, y, 0)
|
|
css_transform = webix.env.translate+"("+x+", "+y+((webix.env.translate=="translate3d")?", 0":"")+")";
|
|
css_transition = prefix+"transform " + duration;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
//set styles only after applying transition settings
|
|
webix.delay(function(){
|
|
node.style[webix.env.transition] = css_transition;
|
|
webix.delay(function(){
|
|
if (css_general)
|
|
node.style.cssText += css_general;
|
|
if (css_transform)
|
|
node.style[webix.env.transform] = css_transform;
|
|
var transitionEnded = false;
|
|
var tid = webix.event(node, webix.env.transitionEnd, function(ev){
|
|
node._has_animation = null;
|
|
if (settings.callback) settings.callback.call((settings.master||window), node,settings,ev);
|
|
transitionEnded = true;
|
|
webix.eventRemove(tid);
|
|
});
|
|
window.setTimeout(function(){
|
|
if(!transitionEnded){
|
|
node._has_animation = null;
|
|
if (settings.callback) settings.callback.call((settings.master||window), node,settings);
|
|
transitionEnded = true;
|
|
webix.eventRemove(tid);
|
|
}
|
|
}, (settings.duration*1+settings.delay*1)*1.3);
|
|
});
|
|
});
|
|
};
|
|
|
|
/*
|
|
Behavior:MouseEvents - provides inner evnets for mouse actions
|
|
*/
|
|
|
|
webix.MouseEvents={
|
|
$init: function(config){
|
|
config = config || {};
|
|
|
|
this._clickstamp = 0;
|
|
this._dbl_sensetive = 300;
|
|
this._item_clicked = null;
|
|
|
|
this._mouse_action_extend(config.onClick, "on_click");
|
|
this._mouse_action_extend(config.onContext, "on_context");
|
|
this._mouse_action_extend(config.onDblClick, "on_dblclick");
|
|
this._mouse_action_extend(config.onMouseMove, "on_mouse_move");
|
|
|
|
//attach dom events if related collection is defined
|
|
if (this.on_click){
|
|
webix._event(this._contentobj,"click",this._onClick,{bind:this});
|
|
if (webix.env.isIE8 && this.on_dblclick)
|
|
webix._event(this._contentobj, "dblclick", this._onDblClick, {bind:this});
|
|
}
|
|
if (this.on_context)
|
|
webix._event(this._contentobj,"contextmenu",this._onContext,{bind:this});
|
|
|
|
if (this.on_mouse_move)
|
|
this._enable_mouse_move();
|
|
},
|
|
|
|
_enable_mouse_move:function(){
|
|
if (!this._mouse_move_enabled){
|
|
this.on_mouse_move = this.on_mouse_move || {};
|
|
webix._event(this._contentobj,"mousemove",this._onMouse,{bind:this});
|
|
webix._event(this._contentobj,(webix.env.isIE?"mouseleave":"mouseout"),this._onMouse,{bind:this});
|
|
this._mouse_move_enabled = 1;
|
|
this.attachEvent("onDestruct", function(){
|
|
if (this._mouse_move_timer)
|
|
window.clearTimeout(this._mouse_move_timer);
|
|
});
|
|
}
|
|
|
|
},
|
|
|
|
_mouse_action_extend:function(config, key){
|
|
if (config){
|
|
var now = this[key];
|
|
var step = now ? webix.extend({}, now) : {};
|
|
this[key] = webix.extend(step, config);
|
|
}
|
|
},
|
|
|
|
//inner onclick object handler
|
|
_onClick: function(e){
|
|
if(!this.isEnabled())
|
|
return false;
|
|
|
|
webix.UIManager._focus_action(this);
|
|
if(this.on_dblclick){
|
|
// emulates double click
|
|
var stamp = (new Date()).valueOf();
|
|
|
|
if (stamp - this._clickstamp <= this._dbl_sensetive && this.locate){
|
|
var item = this.locate(e);
|
|
if (""+item == ""+this._item_clicked) {
|
|
this._clickstamp = 0;
|
|
return this._onDblClick(e);
|
|
}
|
|
}
|
|
this._clickstamp = stamp;
|
|
}
|
|
|
|
var result = this._mouseEvent(e,this.on_click,"ItemClick");
|
|
return result;
|
|
},
|
|
//inner ondblclick object handler
|
|
_onDblClick: function(e) {
|
|
return this._mouseEvent(e,this.on_dblclick,"ItemDblClick");
|
|
},
|
|
//process oncontextmenu events
|
|
_onContext: function(e) {
|
|
this._mouseEvent(e, this.on_context, "BeforeContextMenu", "AfterContextMenu");
|
|
},
|
|
/*
|
|
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 (this.$destructed) return;
|
|
if (document.createEventObject) //make a copy of event, will be used in timed call
|
|
e = document.createEventObject(event);
|
|
else if (!webix.$testmode && !webix.isUndefined(e.movementY) && !e.movementY && !e.movementX)
|
|
return; //logitech mouse driver can send false signals in Chrome
|
|
|
|
|
|
|
|
|
|
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(webix.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),(this._settings.mouseEventDelay||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, pair){
|
|
e=e||event;
|
|
|
|
if (e.processed || !this._viewobj) return;
|
|
e.processed = true;
|
|
|
|
var trg=e.target||e.srcElement;
|
|
|
|
//IE8 can't modify event object
|
|
//so we need to stop event bubbling to prevent double processing
|
|
if (webix.env.isIE8){
|
|
var vid = this._settings.id;
|
|
var wid = trg.w_view;
|
|
|
|
if (!wid) trg.w_view = vid; else if (wid !== vid) return;
|
|
}
|
|
|
|
var css = "";
|
|
var id = null;
|
|
var found = false;
|
|
//loop through all parents
|
|
//we need to check for this._viewobj as some handler can destroy the view
|
|
while (trg && trg.parentNode && this._viewobj && trg != this._viewobj.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){
|
|
this._item_clicked = id;
|
|
if (this.callEvent){
|
|
//it will be triggered only for first detected ID, in case of nested elements
|
|
if (!this.callEvent("on"+name,[id,e,trg])) return;
|
|
if (pair) this.callEvent("on"+pair,[id,e,trg]);
|
|
}
|
|
//set found flag
|
|
found = true;
|
|
}
|
|
}
|
|
css=webix.html._getClassName(trg);
|
|
if (css){ //check if pre-defined reaction for element's css name exists
|
|
css = css.toString().split(" ");
|
|
for (var i=0; i<css.length; i++){
|
|
if (hash[css[i]]){
|
|
var functor = webix.toFunctor(hash[css[i]], this.$scope);
|
|
var res = functor.call(this,e,id||webix.html.locate(e, this._id),trg);
|
|
if(res === false)
|
|
return found;
|
|
}
|
|
}
|
|
}
|
|
trg=trg.parentNode;
|
|
}
|
|
|
|
return found; //returns true if item was located and event was triggered
|
|
}
|
|
};
|
|
|
|
|
|
webix.protoUI({
|
|
name:"accordionitem",
|
|
$init:function(config){
|
|
this._viewobj.innerHTML = "<div webix_ai_id='"+config.id+"' class='webix_accordionitem_header'><div tabindex='0' role='button' class='webix_accordionitem_button' ></div><div class='webix_accordionitem_label' ></div></div><div class='webix_accordionitem_body'></div>";
|
|
|
|
this._contentobj = this._viewobj;
|
|
this._headobj = this._contentobj.childNodes[0];
|
|
if(!config.header)
|
|
this._headobj.style.display = "none";
|
|
this._headlabel = this._contentobj.childNodes[0].childNodes[1];
|
|
this._headbutton = this._contentobj.childNodes[0].childNodes[0];
|
|
this._bodyobj = this._contentobj.childNodes[1];
|
|
this._viewobj.className +=" webix_accordionitem";
|
|
this._head_cell = this._body_cell = null;
|
|
this._cells = true;
|
|
|
|
this._bodyobj.setAttribute("role", "tabpanel");
|
|
this._headobj.setAttribute("role", "tab");
|
|
|
|
this.attachEvent("onKeyPress", this._onKeyPress);
|
|
},
|
|
_remove:function(){
|
|
this._body_cell = { destructor:function(){} };
|
|
},
|
|
_replace:function(new_view){
|
|
this._body_cell.destructor();
|
|
this._body_cell = new_view;
|
|
this._body_cell._parent_cell = this;
|
|
|
|
this._bodyobj.appendChild(this._body_cell._viewobj);
|
|
this.resize();
|
|
},
|
|
_id:"webix_ai_id",
|
|
getChildViews:function(){
|
|
return [this._body_cell];
|
|
},
|
|
body_setter:function(value){
|
|
if (typeof value != "object")
|
|
value = {template:value };
|
|
|
|
value._inner = { top:true, left:true, right:true, bottom:true};
|
|
this._body_cell = webix.ui._view(value);
|
|
this._body_cell.$view.style.border = "0px solid red";
|
|
this._body_cell._parent_cell = this;
|
|
|
|
this._bodyobj.appendChild(this._body_cell._viewobj);
|
|
return value;
|
|
},
|
|
header_setter:function(value){
|
|
if(value)
|
|
value = webix.template(value);
|
|
return value;
|
|
},
|
|
headerAlt_setter:function(value){
|
|
if(value)
|
|
value = webix.template(value);
|
|
return value;
|
|
},
|
|
$getSize:function(dx, dy){
|
|
var size = this._body_cell.$getSize(0, 0);
|
|
|
|
//apply external border to inner content sizes
|
|
var _borders = this._settings._inner;
|
|
if (_borders){
|
|
dx += (_borders.left?0:1)+(_borders.right?0:1);
|
|
dy += (_borders.top?0:1)+(_borders.bottom?0:1);
|
|
}
|
|
|
|
var header = 0;
|
|
var self_size = webix.ui.baseview.prototype.$getSize.call(this, 0, 0);
|
|
|
|
//use child settings if layout's one was not defined
|
|
self_size[0] = (self_size[0] || size[0] ) +dx;
|
|
if (self_size[1] >= 100000)
|
|
self_size[1] = size[1];
|
|
self_size[1] += dx;
|
|
|
|
self_size[2] = (self_size[2] || size[2] ) +dy;
|
|
var fixedHeight = (self_size[3]< 100000);
|
|
if (!fixedHeight)
|
|
self_size[3] = size[3];
|
|
|
|
self_size[3] += dy;
|
|
|
|
if(this.getParentView()._vertical_orientation){
|
|
if (this._settings.collapsed){
|
|
self_size[2] = self_size[3] = this._getHeaderSize();
|
|
} else if(this._settings.header)
|
|
header = this._settings.headerHeight;
|
|
} else {
|
|
if (this._settings.collapsed)
|
|
self_size[0] = self_size[1] = this._getHeaderSize();
|
|
if(this._settings.header)
|
|
header = this._settings.headerHeight;
|
|
}
|
|
|
|
//include header in total height calculation
|
|
if(!fixedHeight){
|
|
self_size[2] += header;
|
|
self_size[3] += header;
|
|
}
|
|
|
|
webix.debug_size_box(this, self_size, true);
|
|
return self_size;
|
|
},
|
|
on_click:{
|
|
webix_accordionitem_header:function(e, id){
|
|
this._toggle(e);
|
|
return false;
|
|
},
|
|
webix_accordionitem_header_v:function(e, id){
|
|
this._toggle(e);
|
|
return false;
|
|
}
|
|
},
|
|
_toggle:function(e){
|
|
this.define("collapsed", !this._settings.collapsed);
|
|
},
|
|
collapsed_setter:function(value){
|
|
if (this._settings.header === false) return;
|
|
//use last layout element if parent is not known yet
|
|
var parent = this.getParentView();
|
|
if(parent){
|
|
if(!value)
|
|
this._expand();
|
|
else{
|
|
if ( parent._canCollapse(this))
|
|
this._collapse();
|
|
else{
|
|
var success = 0;
|
|
if(parent._cells.length > 1)
|
|
for (var i=0; i < parent._cells.length; i++){
|
|
var sibl = parent._cells[i];
|
|
if (this != sibl && sibl.isVisible() && sibl.expand){
|
|
sibl.expand();
|
|
this._collapse();
|
|
success = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (!success) return;
|
|
}
|
|
}
|
|
|
|
this._settings.collapsed = value;
|
|
if (!value) parent._afterOpen(this);
|
|
|
|
this.refresh();
|
|
if (!webix._ui_creation)
|
|
this.resize();
|
|
|
|
parent.callEvent("onAfter"+(value?"Collapse":"Expand"), [this._settings.id]);
|
|
|
|
this._settings.$noresize = value;
|
|
}
|
|
return value;
|
|
},
|
|
collapse:function(){
|
|
this.define("collapsed", true);
|
|
webix.UIManager._moveChildFocus(this);
|
|
},
|
|
expand:function(){
|
|
this.define("collapsed", false);
|
|
},
|
|
_show: function() {
|
|
this.show();
|
|
},
|
|
_hide: function() {
|
|
this.hide();
|
|
},
|
|
_expand:function(){
|
|
this._bodyobj.style.display = "";
|
|
webix.html.removeCss(this.$view, "collapsed");
|
|
webix.html.removeCss(this._headobj, "collapsed");
|
|
|
|
this._headobj.setAttribute("aria-expanded", "true");
|
|
},
|
|
_collapse:function(){
|
|
var vertical = this.getParentView()._vertical_orientation;
|
|
//this._original_size = (vertical?this._settings.height:this._settings.width)||-1;
|
|
|
|
if(this._settings.headerAlt)
|
|
this._headlabel.innerHTML = this._settings.headerAlt();
|
|
this._bodyobj.style.display = "none";
|
|
webix.html.addCss(this.$view, "collapsed");
|
|
webix.html.addCss(this._headobj, "collapsed");
|
|
|
|
this._headobj.setAttribute("aria-expanded", "false");
|
|
},
|
|
refresh:function(){
|
|
var template = this._settings[this._settings.collapsed?"headerAlt":"header"] ||this._settings.header;
|
|
if (template){
|
|
this._headlabel.innerHTML = template();
|
|
this._headbutton.setAttribute("aria-label", template());
|
|
}
|
|
|
|
var css = (this.getParentView()._vertical_orientation?"vertical":"horizontal");
|
|
if(this._viewobj.className.indexOf(" "+css) < 0 ){
|
|
webix.html.addCss(this._viewobj, css);
|
|
}
|
|
//fix collapsed columns in IE8
|
|
if(!webix.env.transform){
|
|
webix.html.addCss(this._viewobj,"webix_ie",true);
|
|
}
|
|
},
|
|
_getHeaderSize:function(){
|
|
return (this._settings.collapsed?this._settings.headerAltHeight:this._settings.headerHeight);
|
|
},
|
|
$setSize:function(x,y){
|
|
if (webix.ui.view.prototype.$setSize.call(this,x,y) || this._getHeaderSize() != this._last_set_header_size){
|
|
x = this._content_width;
|
|
y = this._content_height;
|
|
|
|
var headerSize = this._last_set_header_size = this._getHeaderSize();//-(this._settings._inner.top?0:1);
|
|
if (this._settings.header){
|
|
|
|
this._headobj.style.height=headerSize+"px";
|
|
this._headobj.style.width="auto";
|
|
this._headobj.style[webix.env.transform]="";
|
|
|
|
|
|
this._headobj.style.borderBottomWidth = (this._settings.collapsed?0:1)+"px";
|
|
|
|
if(this.getParentView()._vertical_orientation||!this._settings.collapsed){
|
|
y-=this._getHeaderSize();
|
|
} else if (this._settings.collapsed){
|
|
//-2 - borders
|
|
if (webix.animate.isSupported()){
|
|
this._headobj.style.width = y + "px";
|
|
this._headobj.style.height = x + 3 + "px";
|
|
var d = Math.floor(y/2-x/2)+(x-this._settings.headerAltHeight)/2;
|
|
this._headobj.style[webix.env.transform]="rotate(90deg) translate("+d+"px, "+(d+1)+"px)";
|
|
}
|
|
else { //IE8 fix
|
|
this._headobj.style.width = x + "px";
|
|
this._headobj.style.height = y + 3 + "px";
|
|
}
|
|
|
|
}
|
|
}
|
|
if(!this._settings.collapsed){
|
|
this._body_cell.$setSize(x,y);
|
|
this._last_size_y = y;
|
|
}
|
|
} else if (!this._settings.collapsed){
|
|
var body = this._body_cell;
|
|
if (this._last_size_y)
|
|
body.$setSize(this._content_width, this._last_size_y);
|
|
}
|
|
},
|
|
$skin:function(){
|
|
var defaults = this.defaults;
|
|
defaults.headerAltHeight = defaults.headerHeight = webix.skin.$active.barHeight;
|
|
if(webix.skin.$active.borderlessAccordion)
|
|
defaults.borderless = true;
|
|
},
|
|
defaults:{
|
|
header:false,
|
|
headerAlt:false,
|
|
body:""
|
|
}
|
|
}, webix.MouseEvents, webix.EventSystem, webix.ui.view);
|
|
|
|
webix.protoUI({
|
|
name:"accordion",
|
|
defaults:{
|
|
panelClass:"accordionitem",
|
|
multi:false,
|
|
collapsed:false
|
|
},
|
|
$init:function(){
|
|
this._viewobj.setAttribute("role", "tablist");
|
|
this._viewobj.setAttribute("aria-multiselectable", "true");
|
|
},
|
|
addView:function(view){
|
|
//adding view to the accordion
|
|
var id = webix.ui.layout.prototype.addView.apply(this, arguments);
|
|
var child = webix.$$(id);
|
|
//repainting sub-panels in the accordion
|
|
if (child.collapsed_setter && child.refresh) child.refresh();
|
|
return id;
|
|
},
|
|
_parse_cells:function(){
|
|
var panel = this._settings.panelClass;
|
|
var cells = this._collection;
|
|
|
|
for (var i=0; i<cells.length; i++){
|
|
if ((cells[i].body || cells[i].header)&& !cells[i].view && !cells[i].align)
|
|
cells[i].view = panel;
|
|
if (webix.isUndefined(cells[i].collapsed))
|
|
cells[i].collapsed = this._settings.collapsed;
|
|
|
|
}
|
|
|
|
|
|
this._skin_render_collapse = true;
|
|
webix.ui.layout.prototype._parse_cells.call(this);
|
|
this._skin_render_collapse = false;
|
|
|
|
for (var i=0; i < this._cells.length; i++){
|
|
if (this._cells[i].name == panel)
|
|
this._cells[i].refresh();
|
|
this._cells[i]._accLastChild = false;
|
|
}
|
|
var found = false;
|
|
for (var i= this._cells.length-1; i>=0 &&!found; i--){
|
|
if(!this._cells[i]._settings.hidden){
|
|
this._cells[i]._accLastChild = true;
|
|
found = true;
|
|
}
|
|
}
|
|
|
|
},
|
|
_afterOpen:function(view){
|
|
if (this._settings.multi === false && this._skin_render_collapse !== true){
|
|
for (var i=0; i < this._cells.length; i++) {
|
|
if (view != this._cells[i] && !this._cells[i]._settings.collapsed && this._cells[i].collapse)
|
|
this._cells[i].collapse();
|
|
}
|
|
}
|
|
if (view.callEvent){
|
|
view.callEvent("onViewShow",[]);
|
|
webix.ui.each(view, this._signal_hidden_cells);
|
|
}
|
|
},
|
|
_canCollapse:function(view){
|
|
if (this._settings.multi === true || this._skin_render_collapse) return true;
|
|
//can collapse only if you have other item to open
|
|
for (var i=0; i < this._cells.length; i++)
|
|
if (view != this._cells[i] && !this._cells[i]._settings.collapsed && this._cells[i].isVisible() && !this._cells[i].$nospace)
|
|
return true;
|
|
return false;
|
|
},
|
|
$skin:function(){
|
|
var defaults = this.defaults;
|
|
if(webix.skin.$active.accordionType)
|
|
defaults.type = webix.skin.$active.accordionType;
|
|
}
|
|
}, webix.ui.layout);
|
|
|
|
webix.protoUI({
|
|
name:"headerlayout",
|
|
defaults:{
|
|
type: "accordion",
|
|
multi:"mixed",
|
|
collapsed:false
|
|
}
|
|
}, webix.ui.accordion);
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
Behavior:DND - low-level dnd handling
|
|
@export
|
|
getContext
|
|
addDrop
|
|
addDrag
|
|
|
|
DND master can define next handlers
|
|
onCreateDrag
|
|
onDragIng
|
|
onDragOut
|
|
onDrag
|
|
onDrop
|
|
all are optional
|
|
*/
|
|
|
|
|
|
|
|
webix.DragControl={
|
|
//has of known dnd masters
|
|
_drag_masters : webix.toArray(["dummy"]),
|
|
/*
|
|
register drop area
|
|
@param node html node or ID
|
|
@param ctrl options dnd master
|
|
@param master_mode true if you have complex drag-area rules
|
|
*/
|
|
addDrop:function(node,ctrl,master_mode){
|
|
node = webix.toNode(node);
|
|
node.webix_drop=this._getCtrl(ctrl);
|
|
if (master_mode) node.webix_master=true;
|
|
},
|
|
//return index of master in collection
|
|
//it done in such way to prevent dnd master duplication
|
|
//probably useless, used only by addDrop and addDrag methods
|
|
_getCtrl:function(ctrl){
|
|
ctrl = ctrl||webix.DragControl;
|
|
var index = this._drag_masters.find(ctrl);
|
|
if (index<0){
|
|
index = this._drag_masters.length;
|
|
this._drag_masters.push(ctrl);
|
|
}
|
|
return index;
|
|
},
|
|
_createTouchDrag: function(e){
|
|
var dragCtrl = webix.DragControl;
|
|
var master = this._getActiveDragMaster();
|
|
// for data items only
|
|
if(master && master._getDragItemPos){
|
|
|
|
if(!dragCtrl._html)
|
|
dragCtrl.createDrag(e);
|
|
var ctx = dragCtrl._drag_context;
|
|
dragCtrl._html.style.left= e.x+dragCtrl.left+ (ctx.x_offset||0)+"px";
|
|
dragCtrl._html.style.top= e.y+dragCtrl.top+ (ctx.y_offset||0) +"px";
|
|
}
|
|
},
|
|
/*
|
|
register drag area
|
|
@param node html node or ID
|
|
@param ctrl options dnd master
|
|
*/
|
|
addDrag:function(node,ctrl){
|
|
node = webix.toNode(node);
|
|
node.webix_drag=this._getCtrl(ctrl);
|
|
webix._event(node,webix.env.mouse.down,this._preStart,{ bind:node });
|
|
webix._event(node,"dragstart",webix.html.preventEvent);
|
|
},
|
|
//logic of drag - start, we are not creating drag immediately, instead of that we hears mouse moving
|
|
_preStart:function(e){
|
|
if (webix.DragControl._active){
|
|
//if we have nested drag areas, use the top one and ignore the inner one
|
|
if (webix.DragControl._saved_event == e) return;
|
|
webix.DragControl._preStartFalse();
|
|
webix.DragControl.destroyDrag(e);
|
|
}
|
|
webix.DragControl._active=this;
|
|
|
|
var evobj = webix.env.mouse.context(e);
|
|
webix.DragControl._start_pos=evobj;
|
|
webix.DragControl._saved_event = e;
|
|
|
|
webix.DragControl._webix_drag_mm = webix.event(document.body,webix.env.mouse.move,webix.DragControl._startDrag);
|
|
webix.DragControl._webix_drag_mu = webix.event(document,webix.env.mouse.up,webix.DragControl._preStartFalse);
|
|
|
|
//need to run here, or will not work in IE
|
|
webix.html.addCss(document.body,"webix_noselect", 1);
|
|
},
|
|
//if mouse was released before moving - this is not a dnd, remove event handlers
|
|
_preStartFalse:function(){
|
|
webix.DragControl._clean_dom_after_drag();
|
|
},
|
|
//mouse was moved without button released - dnd started, update event handlers
|
|
_startDrag:function(e){
|
|
//prevent unwanted dnd
|
|
var pos = webix.env.mouse.context(e);
|
|
var master = webix.DragControl._getActiveDragMaster();
|
|
// only long-touched elements can be dragged
|
|
|
|
var longTouchLimit = (master && webix.env.touch && master._getDragItemPos && !webix.Touch._long_touched);
|
|
if (longTouchLimit || Math.abs(pos.x-webix.DragControl._start_pos.x)<5 && Math.abs(pos.y-webix.DragControl._start_pos.y)<5)
|
|
return;
|
|
|
|
webix.DragControl._clean_dom_after_drag(true);
|
|
if(!webix.DragControl._html)
|
|
if (!webix.DragControl.createDrag(webix.DragControl._saved_event)) return;
|
|
|
|
webix.DragControl.sendSignal("start"); //useless for now
|
|
webix.DragControl._webix_drag_mm = webix.event(document.body,webix.env.mouse.move,webix.DragControl._moveDrag);
|
|
webix.DragControl._webix_drag_mu = webix.event(document,webix.env.mouse.up,webix.DragControl._stopDrag);
|
|
webix.DragControl._moveDrag(e);
|
|
|
|
if (webix.env.touch)
|
|
return webix.html.preventEvent(e);
|
|
},
|
|
//mouse was released while dnd is active - process target
|
|
_stopDrag:function(e){
|
|
webix.DragControl._clean_dom_after_drag();
|
|
webix.DragControl._saved_event = null;
|
|
|
|
if (webix.DragControl._last){ //if some drop target was confirmed
|
|
webix.DragControl.$drop(webix.DragControl._active, webix.DragControl._last, e);
|
|
webix.DragControl.$dragOut(webix.DragControl._active,webix.DragControl._last,null,e);
|
|
}
|
|
webix.DragControl.destroyDrag(e);
|
|
webix.DragControl.sendSignal("stop"); //useless for now
|
|
},
|
|
_clean_dom_after_drag:function(still_drag){
|
|
this._webix_drag_mm = webix.eventRemove(this._webix_drag_mm);
|
|
this._webix_drag_mu = webix.eventRemove(this._webix_drag_mu);
|
|
if (!still_drag)
|
|
webix.html.removeCss(document.body,"webix_noselect");
|
|
},
|
|
//dnd is active and mouse position was changed
|
|
_moveDrag:function(e){
|
|
var dragCtrl = webix.DragControl;
|
|
var pos = webix.html.pos(e);
|
|
var evobj = webix.env.mouse.context(e);
|
|
|
|
//give possibility to customize drag position
|
|
var customPos = dragCtrl.$dragPos(pos, e);
|
|
//adjust drag marker position
|
|
var ctx = dragCtrl._drag_context;
|
|
dragCtrl._html.style.top=pos.y+dragCtrl.top+(customPos||!ctx.y_offset?0:ctx.y_offset) +"px";
|
|
dragCtrl._html.style.left=pos.x+dragCtrl.left+(customPos||!ctx.x_offset?0:ctx.x_offset)+"px";
|
|
|
|
if (dragCtrl._skip)
|
|
dragCtrl._skip=false;
|
|
else {
|
|
var target = evobj.target = webix.env.touch ? document.elementFromPoint(evobj.x, evobj.y) : evobj.target;
|
|
var touch_event = webix.env.touch ? evobj : e;
|
|
dragCtrl._checkLand(target, touch_event);
|
|
}
|
|
|
|
return webix.html.preventEvent(e);
|
|
},
|
|
//check if item under mouse can be used as drop landing
|
|
_checkLand:function(node,e){
|
|
while (node && node.tagName!="BODY"){
|
|
if (node.webix_drop){ //if drop area registered
|
|
if (this._last && (this._last!=node || node.webix_master)) //if this area with complex dnd master
|
|
this.$dragOut(this._active,this._last,node,e); //inform master about possible mouse-out
|
|
if (!this._last || this._last!=node || node.webix_master){ //if this is new are or area with complex dnd master
|
|
this._last=null; //inform master about possible mouse-in
|
|
this._landing=this.$dragIn(webix.DragControl._active,node,e);
|
|
if (this._landing) //landing was rejected
|
|
this._last=node;
|
|
return;
|
|
}
|
|
return;
|
|
}
|
|
node=node.parentNode;
|
|
}
|
|
if (this._last) //mouse was moved out of previous landing, and without finding new one
|
|
this._last = this._landing = this.$dragOut(this._active,this._last,null,e);
|
|
},
|
|
//mostly useless for now, can be used to add cross-frame dnd
|
|
sendSignal:function(signal){
|
|
webix.DragControl.active=(signal=="start");
|
|
},
|
|
|
|
//return master for html area
|
|
getMaster:function(t){
|
|
return this._drag_masters[t.webix_drag||t.webix_drop];
|
|
},
|
|
//return dhd-context object
|
|
getContext:function(){
|
|
return this._drag_context;
|
|
},
|
|
getNode:function(){
|
|
return this._html;
|
|
},
|
|
//called when dnd is initiated, must create drag representation
|
|
createDrag:function(e){
|
|
var dragCtl = webix.DragControl;
|
|
var a=dragCtl._active;
|
|
|
|
dragCtl._drag_context = {};
|
|
var master = this._drag_masters[a.webix_drag];
|
|
var drag_container;
|
|
|
|
//if custom method is defined - use it
|
|
if (master.$dragCreate){
|
|
drag_container=master.$dragCreate(a,e);
|
|
if (!drag_container) return false;
|
|
this._setDragOffset(e);
|
|
drag_container.style.position = 'absolute';
|
|
} else {
|
|
//overvise use default one
|
|
var text = dragCtl.$drag(a,e);
|
|
dragCtl._setDragOffset(e);
|
|
|
|
if (!text) return false;
|
|
drag_container = document.createElement("DIV");
|
|
drag_container.innerHTML=text;
|
|
drag_container.className="webix_drag_zone";
|
|
document.body.appendChild(drag_container);
|
|
|
|
var context = dragCtl._drag_context;
|
|
if (context.html && webix.env.pointerevents){
|
|
context.x_offset = -Math.round(drag_container.offsetWidth * 0.5);
|
|
context.y_offset = -Math.round(drag_container.offsetHeight * 0.75);
|
|
}
|
|
}
|
|
/*
|
|
dragged item must have topmost z-index
|
|
in some cases item already have z-index
|
|
so we will preserve it if possible
|
|
*/
|
|
drag_container.style.zIndex = Math.max(drag_container.style.zIndex,webix.ui.zIndex());
|
|
|
|
webix.DragControl._skipDropH = webix.event(drag_container,webix.env.mouse.move,webix.DragControl._skip_mark);
|
|
|
|
if (!webix.DragControl._drag_context.from)
|
|
webix.DragControl._drag_context = {source:a, from:a};
|
|
|
|
webix.DragControl._html=drag_container;
|
|
return true;
|
|
},
|
|
//helper, prevents unwanted mouse-out events
|
|
_skip_mark:function(){
|
|
webix.DragControl._skip=true;
|
|
},
|
|
//after dnd end, remove all traces and used html elements
|
|
destroyDrag:function(e){
|
|
var a=webix.DragControl._active;
|
|
var master = this._drag_masters[a.webix_drag];
|
|
|
|
if (master && master.$dragDestroy){
|
|
webix.DragControl._skipDropH = webix.eventRemove(webix.DragControl._skipDropH);
|
|
if(webix.DragControl._html)
|
|
master.$dragDestroy(a,webix.DragControl._html,e);
|
|
}
|
|
else{
|
|
webix.html.remove(webix.DragControl._html);
|
|
}
|
|
webix.DragControl._landing=webix.DragControl._active=webix.DragControl._last=webix.DragControl._html=null;
|
|
//webix.DragControl._x_offset = webix.DragControl._y_offset = null;
|
|
},
|
|
_getActiveDragMaster: function(){
|
|
return webix.DragControl._drag_masters[webix.DragControl._active.webix_drag];
|
|
},
|
|
top:5, //relative position of drag marker to mouse cursor
|
|
left:5,
|
|
_setDragOffset:function(e){
|
|
var dragCtl = webix.DragControl;
|
|
var pos = dragCtl._start_pos;
|
|
var ctx = dragCtl._drag_context;
|
|
|
|
if(typeof ctx.x_offset != "undefined" && typeof ctx.y_offset != "undefined")
|
|
return null;
|
|
|
|
ctx.x_offset = ctx.y_offset = 0;
|
|
if(webix.env.pointerevents){
|
|
var m=webix.DragControl._getActiveDragMaster();
|
|
|
|
if (m._getDragItemPos && m!==this){
|
|
var itemPos = m._getDragItemPos(pos,e);
|
|
|
|
if(itemPos){
|
|
ctx.x_offset = itemPos.x - pos.x;
|
|
ctx.y_offset = itemPos.y - pos.y;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
},
|
|
$dragPos:function(pos, e){
|
|
var m=this._drag_masters[webix.DragControl._active.webix_drag];
|
|
if (m.$dragPos && m!=this){
|
|
m.$dragPos(pos, e, webix.DragControl._html);
|
|
return true;
|
|
}
|
|
},
|
|
//called when mouse was moved in drop area
|
|
$dragIn:function(s,t,e){
|
|
var m=this._drag_masters[t.webix_drop];
|
|
if (m.$dragIn && m!=this) return m.$dragIn(s,t,e);
|
|
t.className=t.className+" webix_drop_zone";
|
|
return t;
|
|
},
|
|
//called when mouse was moved out drop area
|
|
$dragOut:function(s,t,n,e){
|
|
var m=this._drag_masters[t.webix_drop];
|
|
if (m.$dragOut && m!=this) return m.$dragOut(s,t,n,e);
|
|
t.className=t.className.replace("webix_drop_zone","");
|
|
return null;
|
|
},
|
|
//called when mouse was released over drop area
|
|
$drop:function(s,t,e){
|
|
var m=this._drag_masters[t.webix_drop];
|
|
webix.DragControl._drag_context.from = webix.DragControl.getMaster(s);
|
|
if (m.$drop && m!=this) return m.$drop(s,t,e);
|
|
t.appendChild(s);
|
|
},
|
|
//called when dnd just started
|
|
$drag:function(s,e){
|
|
var m=this._drag_masters[s.webix_drag];
|
|
if (m.$drag && m!=this) return m.$drag(s,e);
|
|
return "<div style='"+s.style.cssText+"'>"+s.innerHTML+"</div>";
|
|
}
|
|
};
|
|
|
|
//global touch-drag handler
|
|
webix.attachEvent("onLongTouch", function(ev){
|
|
if(webix.DragControl._active)
|
|
webix.DragControl._createTouchDrag(ev);
|
|
});
|
|
|
|
/*
|
|
Behavior:DataMove - allows to move and copy elements, heavily relays on DataStore.move
|
|
@export
|
|
copy
|
|
move
|
|
*/
|
|
webix.DataMove={
|
|
//creates a copy of the item
|
|
copy:function(sid,tindex,tobj, details){
|
|
details = details || {};
|
|
var new_id = details.newId || sid;
|
|
tobj = tobj||this;
|
|
|
|
var data = this.getItem(sid);
|
|
webix.assert(data,"Incorrect ID in DataMove::copy");
|
|
|
|
//make data conversion between objects
|
|
if (tobj)
|
|
data = tobj._externalData(data);
|
|
|
|
//adds new element same as original
|
|
return tobj.data.add(tobj._externalData(data,new_id),tindex,(details.parent || 0));
|
|
},
|
|
_next_move_index:function(nid, next, source){
|
|
if (next && nid){
|
|
var new_index = this.getIndexById(nid);
|
|
return new_index+(source == this && source.getIndexById(next)<new_index?0:1);
|
|
}
|
|
},
|
|
//move item to the new position
|
|
move:function(sid,tindex,tobj, details){
|
|
details = details || {};
|
|
var new_id = details.newId || sid;
|
|
|
|
tobj = tobj||this;
|
|
webix.assert(tobj.data, "moving attempt to component without datastore");
|
|
if (!tobj.data) return;
|
|
|
|
//can process an arrya - it allows to use it from onDrag
|
|
if (webix.isArray(sid)){
|
|
//block separate repaint operations
|
|
if (sid.length > 3) //heuristic value, duplicated below
|
|
this.$blockRender = tobj.$blockRender = true;
|
|
|
|
for (var i=0; i < sid.length; i++) {
|
|
//increase index for each next item in the set, so order of insertion will be equal to order in the array
|
|
var nid = this.move(sid[i], tindex, tobj, details);
|
|
tindex = tobj._next_move_index(nid, sid[i+1], this);
|
|
}
|
|
|
|
this.$blockRender = tobj.$blockRender = false;
|
|
if (sid.length > 3){
|
|
//repaint whole component
|
|
this.refresh();
|
|
if (tobj != this)
|
|
tobj.refresh();
|
|
}
|
|
return;
|
|
}
|
|
|
|
var nid = sid; //id after moving
|
|
|
|
var data = this.getItem(sid);
|
|
webix.assert(data,"Incorrect ID in DataMove::move");
|
|
|
|
if (!tobj || tobj == this){
|
|
if (tindex < 0) tindex = this.data.order.length - 1;
|
|
this.data.move(this.getIndexById(sid),tindex); //move inside the same object
|
|
this.data.callEvent("onDataMove", [sid, tindex, null, this.data.order[tindex+1]]);
|
|
} else {
|
|
//copy to the new object
|
|
nid = tobj.data.add(tobj._externalData(data,new_id),tindex, (details.parent || 0));
|
|
this.data.remove(sid);//delete in old object
|
|
}
|
|
return nid; //return ID of item after moving
|
|
},
|
|
//move item on one position up
|
|
moveUp:function(id,step){
|
|
return this.move(id,this.getIndexById(id)-(step||1));
|
|
},
|
|
//move item on one position down
|
|
moveDown:function(id,step){
|
|
return this.moveUp(id, (step||1)*-1);
|
|
},
|
|
//move item to the first position
|
|
moveTop:function(id){
|
|
return this.move(id,0);
|
|
},
|
|
//move item to the last position
|
|
moveBottom:function(id){
|
|
return this.move(id,this.data.count()-1);
|
|
},
|
|
/*
|
|
this is a stub for future functionality
|
|
currently it just makes a copy of data object, which is enough for current situation
|
|
*/
|
|
_externalData:function(data,id){
|
|
var newdata = webix.extend({},data);
|
|
newdata.id = (!id || this.data.pull[id])?webix.uid():id;
|
|
|
|
|
|
newdata.$template=null;
|
|
|
|
if (this._settings.externalData)
|
|
newdata = this._settings.externalData.call(this, newdata, id, data);
|
|
return newdata;
|
|
}
|
|
};
|
|
|
|
|
|
webix.Movable = {
|
|
move_setter: function (value) {
|
|
if (value){
|
|
this._move_admin = webix.clone(this._move_admin);
|
|
this._move_admin.master = this;
|
|
|
|
webix.DragControl.addDrag(this._headobj, this._move_admin);
|
|
}
|
|
return value;
|
|
},
|
|
_move_admin: {
|
|
$dragCreate:function(object, e){
|
|
if(this.master.config.move){
|
|
var offset = webix.html.offset(object);
|
|
var pos = webix.html.pos(e);
|
|
webix.DragControl.top = offset.y - pos.y;
|
|
webix.DragControl.left = offset.x - pos.x;
|
|
|
|
return webix.toNode(this.master._viewobj);
|
|
}
|
|
},
|
|
$dragDestroy:function(node, drag){
|
|
var view = this.master;
|
|
if (view._settings){
|
|
view._settings.top = parseInt(drag.style.top,10);
|
|
view._settings.left = parseInt(drag.style.left,10);
|
|
}
|
|
|
|
webix.DragControl.top = webix.DragControl.left = 5;
|
|
this.master.callEvent("onViewMoveEnd", []);
|
|
return;
|
|
},
|
|
$dragPos:function(pos, e){
|
|
this.master.callEvent("onViewMove", [pos, e]);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
|
|
webix.Modality = {
|
|
_modal_set:function(value){
|
|
if (value){
|
|
if (!this._modal_cover){
|
|
this._modal_cover = webix.html.create('div',{
|
|
"class":"webix_modal"
|
|
});
|
|
/* with below code we will have the same zIndex for modal layer as for the previous
|
|
abs positioned element, but because of attaching order modal layer will be on top anyway
|
|
*/
|
|
var zIndex = this._settings.zIndex||webix.ui.zIndex();
|
|
|
|
//set topmost modal layer
|
|
this._previous_modality = webix._modality;
|
|
webix._modality = zIndex;
|
|
|
|
|
|
this._modal_cover.style.zIndex = zIndex-1;
|
|
this._viewobj.style.zIndex = zIndex;
|
|
document.body.appendChild(this._modal_cover);
|
|
document.body.style.overflow = "hidden";
|
|
webix._event( this._modal_cover, "click", webix.bind(this._ignore_clicks, this));
|
|
}
|
|
}
|
|
else {
|
|
if (this._modal_cover){
|
|
webix.html.remove(this._modal_cover);
|
|
document.body.style.overflow = "";
|
|
|
|
//restore topmost modal layer
|
|
//set delay, as current window closing may have not finished click event
|
|
//need to wait while it is not fully processed
|
|
var topmost = this._previous_modality;
|
|
setTimeout(function(){ webix._modality = topmost; }, 1);
|
|
|
|
this._modal_cover = null;
|
|
}
|
|
}
|
|
return value;
|
|
}
|
|
};
|
|
|
|
|
|
webix.protoUI({
|
|
name:"window",
|
|
|
|
$init:function(config){
|
|
this._viewobj.innerHTML = "<div class='webix_win_content'><div class='webix_win_head'></div><div class='webix_win_body'></div></div>";
|
|
|
|
this._contentobj = this._viewobj.firstChild;
|
|
this._headobj = this._contentobj.childNodes[0];
|
|
this._dataobj = this._bodyobj = this._contentobj.childNodes[1];
|
|
this._viewobj.className +=" webix_window";
|
|
|
|
this._viewobj.setAttribute("role", "dialog");
|
|
this._viewobj.setAttribute("tabindex", "0");
|
|
|
|
this._head_cell = this._body_cell = null;
|
|
this._settings._inner = {top:false, left:false, right:false, bottom:false }; //set border flags
|
|
if (!config.id) config.id = webix.uid();
|
|
|
|
webix._event(this._contentobj, "click", webix.bind(this._ignore_clicks, this));
|
|
|
|
// IE8 does not allow to define event capturing
|
|
if(this._contentobj.addEventListener)
|
|
webix._event(this._contentobj, "click", function(){
|
|
// brings a window to the front of other windows
|
|
if(!this._settings.zIndex && this._settings.toFront){
|
|
this._viewobj.style.zIndex = webix.ui.zIndex();
|
|
}
|
|
}, {bind:this, capture: true});
|
|
|
|
// hidden_setter handling
|
|
if(config.modal)
|
|
this._modal = true;
|
|
|
|
this.attachEvent("onViewMoveEnd", function(){
|
|
if(this._settings.position)
|
|
delete this._settings.position;
|
|
});
|
|
},
|
|
_ignore_clicks:function(e){
|
|
var popups = webix.ui._popups;
|
|
var index = popups.find(this);
|
|
if (index == -1)
|
|
index = popups.length - 1;
|
|
|
|
e.click_view = index;
|
|
if (webix.env.isIE8)
|
|
e.srcElement.click_view = index;
|
|
},
|
|
getChildViews:function(){
|
|
if (this._head_cell)
|
|
return [this._head_cell, this._body_cell];
|
|
else
|
|
return [this._body_cell];
|
|
},
|
|
zIndex_setter:function(value){
|
|
this._viewobj.style.zIndex = value;
|
|
return value;
|
|
},
|
|
_remove:function(){
|
|
this._body_cell = { destructor:function(){} };
|
|
},
|
|
_replace:function(new_view){
|
|
this._body_cell.destructor();
|
|
this._body_cell = new_view;
|
|
this._body_cell._parent_cell = this;
|
|
|
|
this._bodyobj.appendChild(this._body_cell._viewobj);
|
|
|
|
var cell = this._body_cell._viewobj.style;
|
|
cell.borderTopWidth = cell.borderBottomWidth = cell.borderLeftWidth = cell.borderRightWidth = "1px";
|
|
this._body_cell._settings._inner = webix.clone(this._settings._inner);
|
|
|
|
this.resize(true);
|
|
},
|
|
show:function(node, mode, point){
|
|
if (node === true){
|
|
//recursive call from some child item
|
|
if (!this._settings.hidden)
|
|
return;
|
|
node = null;
|
|
}
|
|
|
|
if(!this.callEvent("onBeforeShow",arguments))
|
|
return false;
|
|
|
|
this._settings.hidden = false;
|
|
this._viewobj.style.zIndex = (this._settings.zIndex||webix.ui.zIndex());
|
|
if (this._settings.modal || this._modal){
|
|
this._modal_set(true);
|
|
this._modal = null; // hidden_setter handling
|
|
}
|
|
|
|
var pos, dx, dy;
|
|
mode = mode || {};
|
|
if (!mode.pos)
|
|
mode.pos = this._settings.relative;
|
|
|
|
//get position of source html node
|
|
//we need to show popup which pointing to that node
|
|
if (node){
|
|
//if event was provided - get node info from it
|
|
if (typeof node == "object" && !node.tagName){
|
|
/*below logic is far from ideal*/
|
|
if (node.target || node.srcElement){
|
|
pos = webix.html.pos(node);
|
|
dx = 20;
|
|
dy = 5;
|
|
} else
|
|
pos = node;
|
|
|
|
|
|
} else {
|
|
node = webix.toNode(node);
|
|
webix.assert(node,"Not existing target for window:show");
|
|
pos = webix.html.offset(node);
|
|
}
|
|
|
|
//size of body, we need to fit popup inside
|
|
var x = Math.max(window.innerWidth || 0, document.body.offsetWidth);
|
|
var y = Math.max(window.innerHeight || 0, document.body.offsetHeight);
|
|
|
|
//size of node, near which popup will be rendered
|
|
dx = dx || node.offsetWidth || 0;
|
|
dy = dy || node.offsetHeight || 0;
|
|
//size of popup element
|
|
var size = this._last_size;
|
|
|
|
var fin_x = pos.x;
|
|
var fin_y = pos.y;
|
|
var point_y=0;
|
|
var point_x = 0;
|
|
var scrollLeft = 0, scrollTop = 0;
|
|
var fit = this._settings.autofit;
|
|
if (fit){
|
|
var nochange = (fit === "node");
|
|
var delta_x = 6; var delta_y=6; var delta_point = 6;
|
|
|
|
//default pointer position - top
|
|
point = "top";
|
|
fin_y=0; fin_x = 0;
|
|
|
|
scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft;
|
|
//if we want to place menu at righ, but there is no place move it to left instead
|
|
if (x - pos.x - dx < size[0] && mode.pos == "right" && !nochange)
|
|
mode.pos = "left";
|
|
|
|
if (mode.pos == "right"){
|
|
fin_x = pos.x+delta_x+dx;
|
|
delta_y = -dy;
|
|
point = "left";
|
|
point_y = Math.round(pos.y+dy/2);
|
|
point_x = fin_x - delta_point;
|
|
} else if (mode.pos == "left"){
|
|
fin_x = pos.x-delta_x-size[0]-1;
|
|
delta_y = -dy;
|
|
point = "right";
|
|
point_y = Math.round(pos.y+dy/2);
|
|
point_x = fin_x + size[0]+1;
|
|
} else {
|
|
//left border of screen
|
|
if (pos.x < scrollLeft){
|
|
fin_x = scrollLeft;
|
|
//popup exceed the right border of screen
|
|
} else if (x+scrollLeft-pos.x > size[0]){
|
|
fin_x = pos.x; //aligned
|
|
} else{
|
|
fin_x = x+scrollLeft-delta_x-size[0]; //not aligned
|
|
}
|
|
|
|
point_x = Math.round(pos.x+dx/2);
|
|
//when we have a small popup, point need to be rendered at center of popup
|
|
point_x = Math.min(point_x, fin_x + size[0] - delta_point*3);
|
|
}
|
|
|
|
//if height is not fixed - use default position
|
|
scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
|
|
if (((!size[1] || (y+scrollTop-dy-pos.y-delta_y > size[1])) || nochange) && mode.pos != "top"){
|
|
//bottom
|
|
fin_y = dy+pos.y+delta_y - 4;
|
|
if (!point_y){
|
|
point = "top";
|
|
point_y = fin_y-delta_point;
|
|
}
|
|
} else {
|
|
//top
|
|
fin_y = pos.y-delta_y - size[1];
|
|
if (fin_y < 0){
|
|
fin_y = 0;
|
|
//left|right point can be used, but there is no place for top point
|
|
if (point == "top") point = false;
|
|
} else if (!point_y){
|
|
point = "bottom";
|
|
fin_y --;
|
|
point_y = fin_y+size[1]+1;
|
|
}
|
|
}
|
|
}
|
|
|
|
var deltax = (mode.x || 0);
|
|
var deltay = (mode.y || 0);
|
|
|
|
var fixed = this._checkFixedPosition();
|
|
if (fixed){
|
|
fin_y = fin_y - scrollTop;
|
|
point_y = point_y - scrollTop;
|
|
}
|
|
|
|
this.setPosition(fin_x+deltax, fin_y+deltay);
|
|
if (this._set_point){
|
|
if (point)
|
|
this._set_point(point,point_x+deltax, point_y+deltay, fixed);
|
|
else
|
|
this._hide_point();
|
|
}
|
|
} else if (this._settings.position)
|
|
this._setPosition();
|
|
|
|
this._viewobj.style.display = "block";
|
|
this._hide_timer = 1;
|
|
webix.delay(function(){ this._hide_timer = 0; }, this, [], (webix.env.touch ? 400 : 100 ));
|
|
|
|
this._render_hidden_views();
|
|
|
|
|
|
if (this.config.autofocus){
|
|
this._prev_focus = webix.UIManager.getFocus();
|
|
webix.UIManager.setFocus(this);
|
|
}
|
|
|
|
if (-1 == webix.ui._popups.find(this))
|
|
webix.ui._popups.push(this);
|
|
|
|
this.callEvent("onShow",[]);
|
|
},
|
|
_hide:function(e){
|
|
//do not hide modal windows
|
|
if (this._settings.hidden || this._settings.modal || this._hide_timer || (e && e.showpopup)) return;
|
|
//do not hide popup, when we have modal layer above the popup
|
|
if (webix._modality && this._settings.zIndex <= webix._modality) return;
|
|
|
|
//ignore inside clicks and clicks in child-popups
|
|
|
|
if (e){
|
|
var index = webix.env.isIE8 ? e.srcElement.click_view : e.click_view;
|
|
if (!index && index !== 0) index = -1;
|
|
|
|
var myindex = webix.ui._popups.find(this);
|
|
|
|
if (myindex <= index) return;
|
|
}
|
|
|
|
this.hide();
|
|
},
|
|
hidden_setter:function(value){
|
|
if(value)
|
|
this.hide();
|
|
else
|
|
this.show();
|
|
return !!value;
|
|
},
|
|
hide:function(force){
|
|
if (this.$destructed) return;
|
|
|
|
if (!force)
|
|
if(this._settings.hidden) return;
|
|
|
|
if (this._settings.modal)
|
|
this._modal_set(false);
|
|
|
|
if (this._settings.position == "top"){
|
|
webix.animate(this._viewobj, {type: 'slide', x:0, y:-(this._content_height+20), duration: 300,
|
|
callback:this._hide_callback, master:this});
|
|
}
|
|
else
|
|
this._hide_callback();
|
|
|
|
if (this._settings.autofocus){
|
|
var el = document.activeElement;
|
|
//as result of hotkey, we can have a activeElement set to document.body
|
|
if (el && this._viewobj && (this._viewobj.contains(el) || el === document.body)){
|
|
webix.UIManager.setFocus(this._prev_focus);
|
|
this._prev_focus = null;
|
|
}
|
|
}
|
|
|
|
this._hide_sub_popups();
|
|
},
|
|
//hide all child-popups
|
|
_hide_sub_popups:function(){
|
|
var order = webix.ui._popups;
|
|
var index = order.find(this);
|
|
var size = order.length - 1;
|
|
|
|
if (index > -1)
|
|
for (var i = size; i > index; i--)
|
|
if (order[i]._hide_point) //hide only popups, skip windows
|
|
order[i].hide();
|
|
|
|
order.removeAt(index);
|
|
},
|
|
destructor: function() {
|
|
this._modal_set(false);
|
|
webix.html.remove(this._viewobj);
|
|
|
|
if (this._settings.autofocus){
|
|
if (!webix._final_destruction)
|
|
webix.UIManager.setFocus(this._prev_focus);
|
|
this._prev_focus = null;
|
|
}
|
|
|
|
this._hide_sub_popups();
|
|
if (this._hide_point)
|
|
this._hide_point();
|
|
webix.Destruction.destructor.apply(this, []);
|
|
},
|
|
_hide_callback:function(){
|
|
if (!this.$destructed){
|
|
this._viewobj.style.display = "none";
|
|
this._settings.hidden = true;
|
|
this.callEvent("onHide",[]);
|
|
}
|
|
},
|
|
close:function(){
|
|
this.destructor();
|
|
},
|
|
_inner_body_set:function(value){
|
|
value.borderless = true;
|
|
},
|
|
body_setter:function(value){
|
|
if (typeof value != "object")
|
|
value = {template:value };
|
|
this._inner_body_set(value);
|
|
|
|
webix._parent_cell = this;
|
|
this._body_cell = webix.ui._view(value);
|
|
this._body_cell._parent_cell = this;
|
|
|
|
this._bodyobj.appendChild(this._body_cell._viewobj);
|
|
return value;
|
|
},
|
|
head_setter:function(value){
|
|
if (value === false) return value;
|
|
if (typeof value != "object"){
|
|
this._viewobj.setAttribute("aria-label", value);
|
|
value = { template:value, padding:0 };
|
|
}
|
|
|
|
value.borderless = true;
|
|
|
|
webix._parent_cell = this;
|
|
this._head_cell = webix.ui._view(value);
|
|
this._head_cell._parent_cell = this;
|
|
|
|
this._headobj.appendChild(this._head_cell._viewobj);
|
|
return value;
|
|
},
|
|
getBody:function(){
|
|
return this._body_cell;
|
|
},
|
|
getHead:function(){
|
|
return this._head_cell;
|
|
},
|
|
adjust:function(){ return this.resize(); },
|
|
resizeChildren:function(){
|
|
if (this._body_cell)
|
|
this.resize();
|
|
},
|
|
resize:function(){
|
|
webix.ui.baseview.prototype.adjust.call(this);
|
|
this._setPosition(this._settings.left, this._settings.top);
|
|
},
|
|
_checkFixedPosition: function() {
|
|
if(this._settings.master) {
|
|
var top = webix.$$(this._settings.master).getTopParentView().$view;
|
|
return top && top.style.position === "fixed";
|
|
}
|
|
return false;
|
|
},
|
|
_setPosition:function(x,y){
|
|
if(this._checkFixedPosition()) this._settings.position = "center";
|
|
if (this._settings.position){
|
|
this.$view.style.position = "fixed";
|
|
|
|
var width = this._content_width;
|
|
var height = this._content_height;
|
|
webix.assert(width && height, "Attempt to show not rendered window");
|
|
|
|
var maxWidth = (window.innerWidth||document.documentElement.offsetWidth);
|
|
var maxHeight = (window.innerHeight||document.documentElement.offsetHeight);
|
|
var left = Math.round((maxWidth-width)/2);
|
|
var top = Math.round((maxHeight-height)/2);
|
|
|
|
if (typeof this._settings.position == "function"){
|
|
var state = { left:left, top:top,
|
|
width:width, height:height,
|
|
maxWidth:maxWidth, maxHeight:maxHeight };
|
|
this._settings.position.call(this, state);
|
|
if (state.width != width || state.height != height)
|
|
this.$setSize(state.width, state.height);
|
|
|
|
this.setPosition(state.left, state.top);
|
|
} else {
|
|
if (this._settings.position == "top"){
|
|
if (webix.animate.isSupported())
|
|
top = -1*height;
|
|
else
|
|
top = 10;
|
|
}
|
|
this.setPosition(left, top);
|
|
}
|
|
|
|
if (this._settings.position == "top")
|
|
webix.animate(this._viewobj, {type: 'slide', x:0, y:height-((this._settings.padding||0)*2), duration: 300 ,callback:this._topPositionCallback, master:this});
|
|
} else
|
|
this.setPosition(x,y);
|
|
},
|
|
_topPositionCallback:function(node){
|
|
webix.animate.clear(node);
|
|
this._settings.top=-((this._settings.padding||0)*2);
|
|
this.setPosition(this._settings.left, this._settings.top);
|
|
},
|
|
setPosition:function(x,y){
|
|
this._viewobj.style.top = y+"px";
|
|
this._viewobj.style.left = x+"px";
|
|
this._settings.left = x; this._settings.top=y;
|
|
},
|
|
$getSize:function(dx, dy){
|
|
var _borders = this._settings._inner;
|
|
if (_borders){
|
|
dx += (_borders.left?0:1)+(_borders.right?0:1);
|
|
dy += (_borders.top?0:1)+(_borders.bottom?0:1);
|
|
}
|
|
//line between head and body
|
|
if (this._settings.head)
|
|
dy += 1;
|
|
|
|
var size = this._body_cell.$getSize(0,0);
|
|
var headMinWidth = 0;
|
|
if (this._head_cell){
|
|
var head_size = this._head_cell.$getSize(0,0);
|
|
if (head_size[3]==head_size[2])
|
|
this._settings.headHeight = head_size[3];
|
|
dy += this._settings.headHeight;
|
|
headMinWidth = head_size[0];
|
|
}
|
|
|
|
if (this._settings.fullscreen){
|
|
var width = window.innerWidth || document.body.clientWidth;
|
|
var height = window.innerHeight || document.body.clientHeight;
|
|
return [width, width, height, height];
|
|
}
|
|
|
|
//get layout sizes
|
|
var self_size = webix.ui.view.prototype.$getSize.call(this, 0, 0);
|
|
|
|
//use child settings if layout's one was not defined
|
|
if (headMinWidth && size[1] > 100000)
|
|
size[0] = Math.max(headMinWidth, size[0]);
|
|
|
|
self_size[1] = Math.min(self_size[1],(size[1]>=100000&&self_size[1]>=100000?Math.max(size[0], 300):size[1])+dx);
|
|
self_size[3] = Math.min(self_size[3],(size[3]>=100000&&self_size[3]>=100000?Math.max(size[2], 200):size[3])+dy);
|
|
|
|
self_size[0] = Math.min(Math.max(self_size[0],size[0] + dx), self_size[1]);
|
|
self_size[2] = Math.min(Math.max(self_size[2],size[2] + dy), self_size[3]);
|
|
|
|
return self_size;
|
|
},
|
|
$setSize:function(x,y){
|
|
webix.ui.view.prototype.$setSize.call(this,x,y);
|
|
x = this._content_width;
|
|
y = this._content_height;
|
|
if (this._settings.head === false) {
|
|
this._headobj.style.display="none";
|
|
this._body_cell.$setSize(x,y);
|
|
} else {
|
|
this._head_cell.$setSize(x,this._settings.headHeight);
|
|
this._body_cell.$setSize(x,y-this._settings.headHeight);
|
|
}
|
|
},
|
|
$skin:function(){
|
|
this.defaults.headHeight = webix.skin.$active.barHeight;
|
|
},
|
|
defaults:{
|
|
top:0,
|
|
left:0,
|
|
autofit:true,
|
|
relative:"bottom",
|
|
body:"",
|
|
head:"",
|
|
hidden: true,
|
|
autofocus:true
|
|
}
|
|
}, webix.ui.view, webix.Movable, webix.Modality, webix.EventSystem);
|
|
|
|
webix.protoUI({
|
|
name:"popup",
|
|
$init:function(){
|
|
this._settings.head = false;
|
|
this.$view.className += " webix_popup";
|
|
webix.attachEvent("onClick", webix.bind(this._hide, this));
|
|
this.attachEvent("onHide", this._hide_point);
|
|
},
|
|
$skin:function(){
|
|
this.defaults.headHeight = webix.skin.$active.barHeight;
|
|
this.defaults.padding = webix.skin.$active.popupPadding;
|
|
},
|
|
close:function(){
|
|
webix.html.remove(this._point_element);
|
|
webix.ui.window.prototype.close.call(this);
|
|
},
|
|
$getSize:function(x,y){
|
|
return webix.ui.window.prototype.$getSize.call(this, x+this._settings.padding*2,y+this._settings.padding*2);
|
|
},
|
|
$setSize:function(x,y){
|
|
webix.ui.view.prototype.$setSize.call(this,x,y);
|
|
x = this._content_width-this._settings.padding*2;
|
|
y = this._content_height-this._settings.padding*2;
|
|
this._contentobj.style.padding = this._settings.padding+"px";
|
|
this._headobj.style.display="none";
|
|
this._body_cell.$setSize(x,y);
|
|
},
|
|
//redefine to preserve inner borders
|
|
_inner_body_set:function(){},
|
|
head_setter:function(){
|
|
},
|
|
_set_point:function(mode, left, top, fixed){
|
|
this._hide_point();
|
|
document.body.appendChild(this._point_element = webix.html.create("DIV",{ "class":"webix_point_"+mode },""));
|
|
this._point_element.style.zIndex = webix.ui.zIndex();
|
|
this._point_element.style.position = fixed ? "fixed":"absolute";
|
|
|
|
this._point_element.style.top = top+"px";
|
|
this._point_element.style.left = left+"px";
|
|
},
|
|
_hide_point:function(){
|
|
this._point_element = webix.html.remove(this._point_element);
|
|
}
|
|
}, webix.ui.window);
|
|
|
|
webix.ui._popups = webix.toArray();
|
|
|
|
webix.extend(webix.ui.window, {
|
|
resize_setter:function(value){
|
|
if (value && !this._resizeHandlers)
|
|
this._renderResizeHandler();
|
|
|
|
return value;
|
|
},
|
|
_renderResizeHandler: function(){
|
|
if(!this._rwHandle){
|
|
this._viewobj.firstChild.style.position = "relative";
|
|
this._rwHandle = webix.html.create("DIV",{
|
|
"class" : "webix_resize_handle"
|
|
});
|
|
this._viewobj.firstChild.appendChild(this._rwHandle);
|
|
webix._event(this._rwHandle, webix.env.mouse.down, this._wrDown, {bind:this});
|
|
}
|
|
},
|
|
_showResizeFrame: function(width,height){
|
|
if(!this._resizeFrame){
|
|
this._resizeFrame = webix.html.create("div", {"class":"webix_resize_frame"},"");
|
|
document.body.appendChild(this._resizeFrame);
|
|
var pos = webix.html.offset(this._viewobj);
|
|
this._resizeFrame.style.left = pos.x+"px";
|
|
this._resizeFrame.style.top = pos.y+"px";
|
|
this._resizeFrame.style.zIndex = webix.ui.zIndex();
|
|
}
|
|
|
|
this._resizeFrame.style.width = width + "px";
|
|
this._resizeFrame.style.height = height + "px";
|
|
},
|
|
_wrDown:function(e){
|
|
if (this.config.resize){
|
|
webix.html.addCss(document.body,"webix_noselect webix_resize_cursor");
|
|
this._wsReady = webix.html.offset(this._viewobj);
|
|
|
|
this._resizeHandlersMove = webix.event(document.body, webix.env.mouse.move, this._wrMove, {bind:this});
|
|
this._resizeHandlersUp = webix.event(document.body, webix.env.mouse.up, this._wrUp, {bind:this});
|
|
}
|
|
},
|
|
_wrMove:function(e){
|
|
if (this._wsReady !== false){
|
|
var pos = webix.html.pos(e);
|
|
var progress = {x:pos.x - this._wsReady.x+10, y: pos.y - this._wsReady.y+10};
|
|
|
|
if (Math.abs(this._wsReady.x - pos.x) < (this.config.minWidth||100) || Math.abs(this._wsReady.y - pos.y) < (this.config.maxHeight||100))
|
|
return;
|
|
|
|
this._wsProgress = progress;
|
|
this._showResizeFrame(progress.x,progress.y);
|
|
}
|
|
},
|
|
_wrUp:function(){
|
|
// remove resize frame and css styles
|
|
if (this._resizeFrame)
|
|
this._resizeFrame = webix.html.remove(this._resizeFrame);
|
|
|
|
webix.html.removeCss(document.body,"webix_resize_cursor");
|
|
webix.html.removeCss(document.body,"webix_noselect");
|
|
webix.eventRemove(this._resizeHandlersMove);
|
|
webix.eventRemove(this._resizeHandlersUp);
|
|
|
|
// set Window sizes
|
|
if (this._wsProgress){
|
|
this.config.width = this._wsProgress.x;
|
|
this.config.height = this._wsProgress.y;
|
|
this.resize();
|
|
}
|
|
|
|
this._wsReady = this._wsProgress = false;
|
|
this.callEvent("onViewResize",[]);
|
|
}
|
|
});
|
|
|
|
|
|
webix.protoUI({
|
|
name:"suggest",
|
|
defaults:{
|
|
autofocus:false,
|
|
type:"list",
|
|
keyPressTimeout:1,
|
|
body:{
|
|
yCount:10,
|
|
autoheight:true,
|
|
body:true,
|
|
select:true,
|
|
borderless:true,
|
|
navigation:true
|
|
},
|
|
filter:function(item,value){
|
|
if (item.value.toString().toLowerCase().indexOf(value.toLowerCase())===0) return true;
|
|
return false;
|
|
}
|
|
},
|
|
template_setter:webix.template,
|
|
filter_setter:function(value){
|
|
return webix.toFunctor(value, this.$scope);
|
|
},
|
|
$init:function(obj){
|
|
var temp = {};
|
|
webix.extend(temp, webix.copy(this.defaults.body));
|
|
temp.view = obj.type || this.defaults.type;
|
|
|
|
var etemp = this._get_extendable_cell(temp);
|
|
if (obj.body)
|
|
webix.extend(etemp, obj.body, true);
|
|
|
|
if (obj.data)
|
|
etemp.data = obj.data;
|
|
if (obj.url)
|
|
etemp.url = obj.url;
|
|
if (obj.datatype)
|
|
etemp.datatype = obj.datatype;
|
|
|
|
if (obj.id)
|
|
temp.id = temp.id || (obj.id+"_"+temp.view);
|
|
|
|
obj.body = temp;
|
|
this.$ready.push(this._set_on_popup_click);
|
|
|
|
this.attachEvent("onShow", function(){
|
|
if (this._settings.master){
|
|
var master = webix.$$(this._settings.master);
|
|
if(master){
|
|
var node = master._getInputDiv ? master._getInputDiv() : master.getInputNode();
|
|
node.setAttribute("aria-expanded", "true");
|
|
}
|
|
|
|
}
|
|
this._show_selection();
|
|
});
|
|
this.attachEvent("onHide", function(){
|
|
if (this._settings.master){
|
|
var master = webix.$$(this._settings.master);
|
|
if(master){
|
|
var node = master._getInputDiv ? master._getInputDiv() : master.getInputNode();
|
|
node.setAttribute("aria-expanded", "false");
|
|
}
|
|
|
|
}
|
|
});
|
|
this._old_text = {};
|
|
},
|
|
_get_extendable_cell:function(obj){
|
|
return obj;
|
|
},
|
|
_preselectMasterOption: function(data){
|
|
var master, node, text = "";
|
|
|
|
if (data){
|
|
if (this._settings.master){
|
|
master = webix.$$(this._settings.master);
|
|
node = master.getInputNode();
|
|
if(node && master.$setValueHere){
|
|
master.$setValueHere(data.value);
|
|
}
|
|
else if (node){
|
|
if(master.options_setter)
|
|
text = this.getItemText(data.id);
|
|
else if(data.value)
|
|
text = master._get_visible_text ? master._get_visible_text(data.value) : data.value.toString();
|
|
|
|
if (webix.isUndefined(node.value))
|
|
node.innerHTML = text;
|
|
else
|
|
node.value = text.replace(/<[^>]*>/g,"");
|
|
}
|
|
}
|
|
}
|
|
node = node || this._last_input_target;
|
|
if(node)
|
|
node.focus();
|
|
},
|
|
setMasterValue:function(data, refresh){
|
|
var text = data.id ? this.getItemText(data.id) : (data.text||data.value);
|
|
|
|
if (this._settings.master){
|
|
var master = webix.$$(this._settings.master);
|
|
if (refresh && data.id)
|
|
master.refresh();
|
|
else if (master.options_setter)
|
|
master.setValue(data.$empty?"":data.id);
|
|
else if(master.setValueHere)
|
|
master.setValueHere(text);
|
|
else
|
|
master.setValue(text);
|
|
} else if (this._last_input_target){
|
|
this._last_input_target.value = text;
|
|
}
|
|
|
|
if (!refresh){
|
|
this.hide(true);
|
|
if (this._last_input_target)
|
|
this._last_input_target.focus();
|
|
}
|
|
this.callEvent("onValueSuggest", [data, text]);
|
|
webix.delay(function(){
|
|
webix.callEvent("onEditEnd",[]);
|
|
});
|
|
},
|
|
getMasterValue:function(){
|
|
if (this._settings.master)
|
|
return webix.$$(this._settings.master).getValue();
|
|
return null;
|
|
},
|
|
getItemId:function(text){
|
|
var list = this.getList();
|
|
var type = list.type;
|
|
for (var key in list.data.pull){
|
|
var obj = list.getItem(key);
|
|
if (this._settings.filter.call(this, obj, text))
|
|
return obj.id;
|
|
}
|
|
},
|
|
getItemText:function(id){
|
|
var item = this.getList().getItem(id);
|
|
|
|
if (!item)
|
|
return this._old_text[id] || "";
|
|
|
|
if (this._settings.template)
|
|
return this._settings.template.call(this, item, this.type);
|
|
|
|
if (this._settings.textValue)
|
|
return item[this._settings.textValue];
|
|
|
|
var type = this.getList().type;
|
|
var text = type.template.call(type, item, type);
|
|
|
|
return (this._old_text[id] = text);
|
|
},
|
|
getSuggestion:function(){
|
|
var id,
|
|
list = this.getList(),
|
|
order = list.data.order;
|
|
|
|
if (list.getSelectedId)
|
|
id = list.getSelectedId();
|
|
|
|
if (order.length && (!id || order.find(id) <0) )
|
|
id = order[0];
|
|
|
|
//complex id in datatable
|
|
if (id && typeof id == "object") id = id+"";
|
|
return id;
|
|
},
|
|
getList:function(){
|
|
return this._body_cell;
|
|
},
|
|
_set_on_popup_click:function(){
|
|
var list = this.getList();
|
|
var type = this._settings.type;
|
|
|
|
if (list.count){
|
|
list.attachEvent("onItemClick", webix.bind(function(item){
|
|
this.setMasterValue(list.getItem(item));
|
|
}, this));
|
|
list.data.attachEvent("onstoreupdated",webix.bind(function(id, obj, mode){
|
|
if (mode == "delete" && id == this.getMasterValue())
|
|
this.setMasterValue({ id:"", text:"" }, 1);
|
|
else if (mode == "update" && id == this.getMasterValue()){
|
|
this.setMasterValue(obj, 1);
|
|
}
|
|
}, this));
|
|
list.data.attachEvent("onAfterFilter", webix.bind(this._suggest_after_filter, this));
|
|
list.data.attachEvent("onStoreLoad", webix.bind(this._suggest_after_filter, this));
|
|
if (webix.isUndefined(this._settings.fitMaster))
|
|
this._settings.fitMaster = true;
|
|
} else if (type == "calendar"){
|
|
list.attachEvent("onDateSelect", function(date){
|
|
this.getParentView().setMasterValue({ value:date});
|
|
});
|
|
list.attachEvent("onTodaySet", function(date){
|
|
this.getParentView().setMasterValue({ value:date});
|
|
});
|
|
list.attachEvent("onDateClear", function(date){
|
|
this.getParentView().setMasterValue({ value:date});
|
|
});
|
|
} else if (type == "colorboard"){
|
|
list.attachEvent("onItemClick", function(value){
|
|
this.getParentView().setMasterValue({ value:value });
|
|
});
|
|
}
|
|
},
|
|
input_setter: function(value) {
|
|
this.linkInput(value);
|
|
return 0;
|
|
},
|
|
linkInput: function(input){
|
|
var node;
|
|
if (input.getInputNode){
|
|
node = input.getInputNode();
|
|
node.webix_master_id = input._settings.id;
|
|
} else
|
|
node = webix.toNode(input);
|
|
|
|
webix._event(node,"keydown",function(e){
|
|
if ((node != document.body || this.isVisible()) && (input.config ? (!input.config.readonly) : (!node.getAttribute("readonly"))))
|
|
this._suggestions(e);
|
|
},{bind:this});
|
|
|
|
if(input._getInputDiv)
|
|
node = input._getInputDiv();
|
|
|
|
node.setAttribute("aria-autocomplete", "list");
|
|
node.setAttribute("aria-expanded", "false");
|
|
|
|
if(node.tagName === "DIV"){
|
|
node.setAttribute("aria-live", "assertive");
|
|
node.setAttribute("aria-atomic", "true");
|
|
}
|
|
|
|
this._non_ui_mode = true;
|
|
},
|
|
_suggestions: function(e){
|
|
e = (e||event);
|
|
var list = this.getList();
|
|
|
|
var trg = e.target||e.srcElement;
|
|
|
|
this._last_input_target = trg;
|
|
this._settings.master = trg.webix_master_id;
|
|
|
|
window.clearTimeout(this._key_timer);
|
|
|
|
var code = e.keyCode;
|
|
//shift and ctrl
|
|
if (code == 16 || code == 17) return;
|
|
|
|
// tab - hide popup and do nothing
|
|
if (code == 9)
|
|
return this._tab_key(this,list);
|
|
|
|
// escape - hide popup
|
|
if (code == 27)
|
|
return this._escape_key(this,list);
|
|
|
|
// enter
|
|
if (code == 13)
|
|
return this.$enterKey(this,list);
|
|
|
|
// up/down/right/left are used for navigation
|
|
if (this._navigate(e)) {
|
|
webix.html.preventEvent(e);
|
|
return false;
|
|
}
|
|
|
|
if (webix.isUndefined(trg.value)) return;
|
|
|
|
clearTimeout(this._last_delay);
|
|
this._last_delay = webix.delay(function(){
|
|
//focus moved to the different control, suggest is not necessary
|
|
if (!this._non_ui_mode &&
|
|
webix.UIManager.getFocus() != webix.$$(this._settings.master)) return;
|
|
|
|
this._resolve_popup = true;
|
|
//for multicombo
|
|
var val = trg.value;
|
|
|
|
// used to prevent showing popup when it was initialized
|
|
if (list.config.dataFeed)
|
|
list.filter("value", val);
|
|
else if (list.filter){
|
|
list.filter(webix.bind(function(item){
|
|
return this._settings.filter.call(this,item,val);
|
|
}, this));
|
|
}
|
|
},this, [], this._settings.keyPressTimeout);
|
|
},
|
|
_suggest_after_filter: function() {
|
|
if (!this._resolve_popup) return true;
|
|
this._resolve_popup = false;
|
|
|
|
var list = this.getList();
|
|
|
|
// filtering is complete
|
|
// if there are as min 1 variant it must be shown, hidden otherwise
|
|
if (list.count() >0){
|
|
this.adjust();
|
|
if(!this.isVisible())
|
|
this._dont_unfilter = true;
|
|
this.show(this._last_input_target,null,true);
|
|
this._dont_unfilter = false;
|
|
} else {
|
|
this.hide(true);
|
|
this._last_input_target = null;
|
|
}
|
|
},
|
|
|
|
show:function(node){
|
|
if (!this.isVisible()){
|
|
var list = this.getList();
|
|
if (list.filter && !this._dont_unfilter){
|
|
list.filter("");
|
|
}
|
|
|
|
if(this.$customWidth){
|
|
this.$customWidth(node);
|
|
}
|
|
if (node.tagName && this._settings.fitMaster){
|
|
this._settings.width = node.offsetWidth -2 ; //2 - borders
|
|
}
|
|
if (list._zoom_level)
|
|
list.render();
|
|
|
|
this.adjust();
|
|
|
|
// needed to return focus
|
|
if(node.tagName == "INPUT")
|
|
this._last_input_target = node;
|
|
}
|
|
webix.ui.popup.prototype.show.apply(this, arguments);
|
|
},
|
|
_show_selection:function(list){
|
|
list = list||this.getList();
|
|
var value = this.getMasterValue();
|
|
|
|
if( list.select && list.showItem ){
|
|
|
|
if (value && list.exists && list.exists(value)){
|
|
list.select(value);
|
|
list.showItem(value);
|
|
}
|
|
else{
|
|
list.unselect();
|
|
list.showItem(list.getFirstId());
|
|
}
|
|
}
|
|
else if(list.setValue){
|
|
if (this._settings.master)
|
|
value = webix.$$(this._settings.master).$prepareValue(value);
|
|
list.setValue(value);
|
|
}
|
|
},
|
|
$enterKey: function(popup,list) {
|
|
var value;
|
|
|
|
if (popup.isVisible()) {
|
|
if (list.count && list.count()){
|
|
value = list.getSelectedId(false, true);
|
|
if(list.count()==1 && list.getFirstId()!=value)
|
|
value = list.getFirstId();
|
|
if(value)
|
|
value = list.getItem(value);
|
|
}
|
|
else if(list.getSelectedDate && list.getSelectedDate())
|
|
value = { value:list.getSelectedDate() };
|
|
else if(list.getValue && list.getValue())
|
|
value = {value: list.getValue() };
|
|
|
|
if (value)
|
|
this.setMasterValue(value);
|
|
|
|
popup.hide(true);
|
|
}
|
|
else
|
|
popup.show(this._last_input_target);
|
|
},
|
|
_escape_key: function(popup, list) {
|
|
return popup.hide(true);
|
|
},
|
|
_tab_key: function(popup, list) {
|
|
return popup.hide(true);
|
|
},
|
|
/*! suggestions navigation: up/down buttons move selection
|
|
* @param e
|
|
* event object
|
|
**/
|
|
_navigate: function(e) {
|
|
var list = this.getList();
|
|
var code = e.keyCode;
|
|
var data;
|
|
|
|
if( list.moveSelection && code < 41 && code > 32 && !e.ctrlKey && !e.metaKey && !e.shiftKey && !e.altKey) {
|
|
// down arrow
|
|
if (code === 40 ) {
|
|
var visible = this.isVisible();
|
|
if (!visible)
|
|
this.show(this._last_input_target);
|
|
|
|
list.moveSelection("down", false, false);
|
|
}// other arrows
|
|
else {
|
|
if((list.count && code !==38) || (!list.count && !list.isVisible()))
|
|
return false;
|
|
|
|
var dir;
|
|
if(code == 33) dir = "pgup";
|
|
if(code == 34) dir = "pgdown";
|
|
if(code == 35) dir = "bottom";
|
|
if(code == 36) dir = "top";
|
|
if(code == 37) dir = "left";
|
|
if(code == 38) dir = "up";
|
|
if(code == 39) dir = "right";
|
|
|
|
list.moveSelection(dir, false, false);
|
|
}
|
|
|
|
if(list.count)
|
|
data = list.getSelectedItem();
|
|
else if(list.getSelectedDate)
|
|
data = { value:list.getVisibleDate()};
|
|
else if(list.getValue)
|
|
data = { value:list.getValue() };
|
|
|
|
this._preselectMasterOption(data);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
},
|
|
getValue:function(){
|
|
var list = this.getList();
|
|
var value = (list.getValue ? list.getValue() : list.getSelectedId()) || "";
|
|
value = value.id || value;
|
|
|
|
// check empty
|
|
if(list.getItem){
|
|
var item = list.getItem(value);
|
|
if(item && item.$empty)
|
|
return "";
|
|
}
|
|
return value;
|
|
},
|
|
setValue:function(value){
|
|
var list = this.getList();
|
|
if(value){
|
|
if(list.exists(value)){
|
|
list.select(value);
|
|
list.showItem(value);
|
|
}
|
|
}else{
|
|
list.unselect();
|
|
list.showItem(list.getFirstId());
|
|
}
|
|
}
|
|
}, webix.ui.popup);
|
|
/*aria-style handling for options of multiple-value controls (radio, segmented, tabbar)*/
|
|
|
|
webix.HTMLOptions = {
|
|
$init:function(config){
|
|
if(webix.skin.$active.customRadio || this.addOption)
|
|
webix._event( this.$view, "keydown", this._moveSelection, {bind:this});
|
|
},
|
|
_focus: function(){
|
|
var input = this._getInputNode();
|
|
if(input)
|
|
for(var i=0; i<input.length; i++){
|
|
if(input[i].getAttribute("tabindex") == "0")
|
|
input[i].focus();
|
|
}
|
|
},
|
|
_blur: function(){
|
|
var input = this._getInputNode();
|
|
if(input)
|
|
for(var i=0; i<input.length; i++){
|
|
if(input[i].getAttribute("tabindex") == "0") input[i].blur();
|
|
}
|
|
},
|
|
_moveSelection:function(e){
|
|
var code = e.which || e.keyCode;
|
|
|
|
var startCode = this.addOption?34:36;
|
|
|
|
if(code>startCode && code <41){
|
|
webix.html.preventEvent(e);
|
|
var index;
|
|
var inp = this._getInputNode();
|
|
|
|
if(code == 35) index = inp.length-1;
|
|
else if(code === 36 ) index = 0;
|
|
else{
|
|
var dir = (code === 37 || code ===38)?-1:1;
|
|
for(var i =0; i<inp.length; i++){
|
|
if(inp[i].getAttribute("tabindex") == "0"){
|
|
index = i + dir;
|
|
if(index<0) index = inp.length-1;
|
|
else if(index>=inp.length) index = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if(!webix.isUndefined(index)){
|
|
var id = this.addOption ? inp[index].getAttribute("button_id") : inp[index].value;
|
|
if(webix.skin.$active.customRadio && !this.addOption)
|
|
inp = this.$view.getElementsByTagName("BUTTON");
|
|
|
|
this.setValue(id);
|
|
inp[index].focus();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
webix.attachEvent("onClick", function(e){
|
|
var element = webix.$$(e);
|
|
if (element && element.touchable){
|
|
webix.UIManager.applyChanges(element);
|
|
|
|
//for inline elements - restore pointer to the master element
|
|
element.getNode(e);
|
|
//reaction on custom css elements in buttons
|
|
var trg=e.target||e.srcElement;
|
|
if (trg.className == "webix_disabled")
|
|
return;
|
|
|
|
var css = "";
|
|
var id = null;
|
|
var found = false;
|
|
if (trg.className && trg.className.toString().indexOf("webix_view")===0) return;
|
|
|
|
if (element)
|
|
webix.UIManager._focus_action(element);
|
|
|
|
//loop through all parents
|
|
while (trg && trg.parentNode){
|
|
if (trg.getAttribute){
|
|
if (trg.getAttribute("view_id"))
|
|
break;
|
|
|
|
css=trg.className;
|
|
if (css){
|
|
css = css.toString().split(" ");
|
|
for (var i =0; i<css.length; i++){
|
|
if (element.on_click[css[i]]){
|
|
var res = element.on_click[css[i]].call(element,e,element._settings.id,trg);
|
|
if (res===false)
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
trg=trg.parentNode;
|
|
}
|
|
|
|
|
|
if (element._settings.click){
|
|
var code = webix.toFunctor(element._settings.click, element.$scope);
|
|
if (code && code.call) code.call(element, element._settings.id, e);
|
|
}
|
|
|
|
|
|
|
|
var popup = element._settings.popup;
|
|
if (element._settings.popup && !element._settings.readonly){
|
|
if (typeof popup == "object" && !popup.name)
|
|
popup = element._settings.popup = webix.ui(popup)._settings.id;
|
|
|
|
var popup = webix.$$(popup);
|
|
webix.assert(popup, "Unknown popup");
|
|
|
|
if (!popup.isVisible()){
|
|
popup._settings.master = element._settings.id;
|
|
popup.show((element.getInputNode()||element.getNode()),null,true);
|
|
}
|
|
}
|
|
|
|
element.callEvent("onItemClick", [element._settings.id, e]);
|
|
}
|
|
});
|
|
|
|
|
|
webix.protoUI({
|
|
name:"button",
|
|
touchable:true,
|
|
$skin:function(){
|
|
this.defaults.height = webix.skin.$active.buttonHeight||webix.skin.$active.inputHeight;
|
|
//used in "text"
|
|
this._labelTopHeight = webix.skin.$active.labelTopHeight||15;
|
|
this._borderWidth = webix.skin.$active.borderWidth;
|
|
},
|
|
defaults:{
|
|
template:function(obj, common){
|
|
var text = common.$renderInput(obj, common);
|
|
if (obj.badge) text = text.replace("</button>", "<span class='webix_badge'>"+obj.badge+"</span></button>");
|
|
return "<div class='webix_el_box' style='width:"+obj.awidth+"px; height:"+obj.aheight+"px'>"+ text + "</div>";
|
|
},
|
|
label:"",
|
|
borderless:true
|
|
},
|
|
$renderInput:function(obj){
|
|
var css = "class='webixtype_"+(obj.type||"base")+"' ";
|
|
return "<button type='button' "+(obj.popup?"aria-haspopup='true'":"")+css+">"+webix.template.escape(obj.label||obj.value)+"</button>";
|
|
},
|
|
$init:function(config){
|
|
this._viewobj.className += " webix_control webix_el_"+(this.$cssName||this.name);
|
|
|
|
this.data = this._settings;
|
|
this._dataobj = this._viewobj;
|
|
|
|
this._calc_size(config);
|
|
},
|
|
hotkey_setter: function(key){
|
|
var control = this;
|
|
this._addElementHotKey(key, function(view,ev){
|
|
var elem = control.$view.firstChild;
|
|
webix.html.triggerEvent(elem, "MouseEvents", "click");
|
|
webix.html.preventEvent(ev);
|
|
});
|
|
},
|
|
|
|
_addElementHotKey: function(key, func, view){
|
|
var keyCode = webix.UIManager.addHotKey(key, func, view);
|
|
this.attachEvent("onDestruct", function(){
|
|
webix.UIManager.removeHotKey(keyCode, func, view);
|
|
});
|
|
},
|
|
tooltip_setter: function(value){
|
|
var box = this._getBox() || this.$view.firstChild;
|
|
if(box)
|
|
box.title = value;
|
|
return value;
|
|
},
|
|
type_setter:function(value){
|
|
if (this._types[value])
|
|
this.$renderInput = webix.template(this._types[value]);
|
|
if (value == 'prev' || value == 'next')
|
|
this._set_inner_size = this._set_inner_size_next;
|
|
else
|
|
this._set_inner_size = false;
|
|
return value;
|
|
},
|
|
_types:{
|
|
htmlbutton: "<button type='button' class='webix_el_htmlbutton webixtype_base'>#label#</button>",
|
|
|
|
prev:"<input type='button' class='webixtype_prev' value='#label#' /><div class='webix_el_arrow webixtype_prev_arrow'></div>",
|
|
next:"<input type='button' class='webixtype_next' value='#label#' /><div class='webix_el_arrow webixtype_next_arrow'></div>",
|
|
|
|
imageButton:"<button type='button' class='webix_img_btn_abs webixtype_base' style='width:100%; line-height:#cheight#px'><div class='webix_image' style='width:#dheight#px;height:#dheight#px;background-image:url(#image#);'> </div> #label#</button>",
|
|
imageButtonTop:"<button type='button' class='webix_img_btn_abs webix_img_btn_abs_top webixtype_base'><div class='webix_image' style='width:100%;height:100%;background-image:url(#image#);'> </div> <div class='webix_img_btn_text'>#label#</div></button>",
|
|
|
|
image:"<button type='button' class='webix_img_btn' style='line-height:#cheight#px;'><div class='webix_image' style='width:#cheight#px;height:#cheight#px;background-image:url(#image#);'> </div> #label#</button>",
|
|
imageTop:"<button type='button' class='webix_img_btn_top'><div class='webix_image' style='width:100%;height:100%;background-image:url(#image#);'></div> <div class='webix_img_btn_text'>#label#</div></button>",
|
|
|
|
icon:"<button type='button' class='webix_img_btn' style='line-height:#cheight#px;'><span class='webix_icon_btn fa-#icon#' style='max-width:#cheight#px;'></span>#label#</button>",
|
|
iconButton:"<button type='button' class='webix_img_btn_abs webixtype_base' style='width:100%;'><span class='webix_icon fa-#icon#'></span> #label#</button>",
|
|
iconTop:"<button type='button' class='webix_img_btn_top' style='width:100%;top:4px;text-align:center;'><span class='webix_icon fa-#icon#'></span><div class='webix_img_btn_text'>#label#</div></button>",
|
|
iconButtonTop:"<button type='button' class='webix_img_btn_abs webix_img_btn_abs_top webixtype_base' style='width:100%;top:0px;text-align:center;'><span class='webix_icon fa-#icon#'></span><div class='webix_img_btn_text'>#label#</div></button>"
|
|
|
|
},
|
|
_findAllInputs: function(){
|
|
var result = [];
|
|
var tagNames = ["input","select","textarea","button"];
|
|
for(var i=0; i< tagNames.length; i++){
|
|
var inputs = this.$view.getElementsByTagName(tagNames[i]);
|
|
for(var j = 0; j< inputs.length; j++){
|
|
result.push(inputs[j]);
|
|
}
|
|
}
|
|
return result;
|
|
},
|
|
disable: function(){
|
|
var i, node,
|
|
elem = this._getBox();
|
|
webix.ui.baseview.prototype.disable.apply(this, arguments);
|
|
if(elem && elem.className.indexOf(" webix_disabled_box")== -1){
|
|
elem.className += " webix_disabled_box";
|
|
var inputs = this._findAllInputs();
|
|
for(i=0; i< inputs.length; i++)
|
|
inputs[i].setAttribute("disabled",true);
|
|
|
|
// richselect and based on it
|
|
node = this.getInputNode();
|
|
if(node && node.tagName.toLowerCase() == "div"){
|
|
this._disabledTabIndex = node.getAttribute("tabIndex");
|
|
node.removeAttribute("tabIndex");
|
|
}
|
|
|
|
if(this._settings.labelPosition == "top"){
|
|
var label = this._dataobj.firstChild;
|
|
if(label)
|
|
label.className += " webix_disabled_top_label";
|
|
}
|
|
}
|
|
},
|
|
enable: function(){
|
|
webix.ui.baseview.prototype.enable.apply(this, arguments);
|
|
var node,
|
|
elem = this._getBox();
|
|
if(elem){
|
|
elem.className = elem.className.replace(" webix_disabled_box","");
|
|
var inputs = this._findAllInputs();
|
|
for(var i=0; i< inputs.length; i++)
|
|
inputs[i].removeAttribute("disabled");
|
|
|
|
node = this.getInputNode();
|
|
if(node && !webix.isUndefined(this._disabledTabIndex))
|
|
node.setAttribute("tabIndex",this._disabledTabIndex);
|
|
|
|
if(this._settings.labelPosition == "top"){
|
|
var label = this._dataobj.firstChild;
|
|
if(label)
|
|
label.className = label.className.replace(" webix_disabled_top_label","");
|
|
}
|
|
}
|
|
},
|
|
$setSize:function(x,y){
|
|
if(webix.ui.view.prototype.$setSize.call(this,x,y)){
|
|
this.render();
|
|
}
|
|
},
|
|
setValue:function(value){
|
|
value = this.$prepareValue(value);
|
|
var oldvalue = this._settings.value;
|
|
|
|
if (this.$compareValue(oldvalue, value)) return false;
|
|
|
|
this._settings.value = value;
|
|
if (this._rendered_input)
|
|
this.$setValue(value);
|
|
|
|
this.callEvent("onChange", [value, oldvalue]);
|
|
},
|
|
$compareValue:function(oldvalue, value){ return oldvalue == value; },
|
|
$prepareValue:function(value){ return this._pattern(value, false); },
|
|
_pattern :function(value){ return value; },
|
|
//visual part of setValue
|
|
$setValue:function(value){
|
|
// this._settings.label = value;
|
|
(this.getInputNode()||{}).value = value;
|
|
},
|
|
getValue:function(){
|
|
//if button was rendered - returning actual value
|
|
//otherwise - returning last set value
|
|
var value = this._rendered_input? this.$getValue() : this._settings.value;
|
|
return (typeof value == "undefined") ? "" : value;
|
|
},
|
|
$getValue:function(){
|
|
return this._settings.value||"";
|
|
},
|
|
focus:function(){
|
|
if(!this._settings.disabled){
|
|
var input = this.getInputNode();
|
|
if (input && input.focus) input.focus();
|
|
}
|
|
|
|
},
|
|
blur:function() {
|
|
var input = this.getInputNode();
|
|
if (input && input.blur) input.blur();
|
|
},
|
|
//get input element
|
|
getInputNode: function() {
|
|
return this._dataobj.getElementsByTagName('input')[0]||this._dataobj.getElementsByTagName('button')[0];
|
|
},
|
|
//get top-level sub-container
|
|
_getBox:function(){
|
|
for(var i=0;i< this._dataobj.childNodes.length;i++){
|
|
if(this._dataobj.childNodes[i].className.indexOf("webix_el_box")>=0)
|
|
return this._dataobj.childNodes[i];
|
|
}
|
|
return null;
|
|
},
|
|
_sqrt_2:Math.sqrt(2),
|
|
_set_inner_size_next:function(){
|
|
var cfg = this._settings;
|
|
var arrow = this._getBox().childNodes[1];
|
|
var button = arrow.previousSibling;
|
|
var style = cfg.type == "next"?"right":"left";
|
|
var height = cfg.aheight-webix.skin.$active.inputPadding*2-2*this._borderWidth; //-2 - borders
|
|
|
|
var arrowEdge = height*this._sqrt_2/2;
|
|
arrow.style.width = arrowEdge+"px";
|
|
arrow.style.height = arrowEdge+"px";
|
|
arrow.style.top = (height - arrowEdge)/2 + webix.skin.$active.inputPadding+ "px";
|
|
arrow.style[style] = (height - arrowEdge)/2 +this._sqrt_2/2+ "px";
|
|
button.style.width = cfg.awidth - height/2 -2 + "px";
|
|
button.style.height = height + 2 + "px";
|
|
button.style[style] = height/2 + 2 + "px";
|
|
button.style.top = webix.skin.$active.inputPadding+ "px";
|
|
|
|
},
|
|
_calc_size:function(config){
|
|
config = config || this._settings;
|
|
if (config.autowidth)
|
|
config.width = webix.html.getTextSize((config.value||config.label), "webixbutton").width +
|
|
(config.badge ? 15 : 0) +
|
|
(config.type === "iconButton" ? 30 : 0) +
|
|
(config.type === "icon"? 20 : 0);
|
|
},
|
|
_calck_input_size:function(){
|
|
//use width for both width and inputWidth settings in clever way
|
|
//in form, we can define width for some element smaller than for siblings
|
|
//it will use inputWidth to render the desired view
|
|
this._input_width = this._settings.inputWidth ||
|
|
((this._content_width - this._settings.width > 2)?this._settings.width:0) || this._content_width;
|
|
this._input_height = this._settings.inputHeight||this._inputHeight||0;
|
|
},
|
|
resize: function(){
|
|
this._calc_size();
|
|
return webix.ui.view.prototype.resize.apply(this,arguments);
|
|
},
|
|
render:function(){
|
|
this._calck_input_size();
|
|
this._settings.awidth = this._input_width||this._content_width;
|
|
this._settings.aheight = this._input_height||this._content_height;
|
|
|
|
//image button - image width
|
|
this._settings.bheight = this._settings.aheight+2;
|
|
this._settings.cheight = this._settings.aheight- 2*webix.skin.$active.inputPadding;
|
|
this._settings.dheight = this._settings.cheight - 2; // - borders
|
|
|
|
if(webix.AtomRender.render.call(this)){
|
|
this._rendered_input = true;
|
|
if (this._set_inner_size) this._set_inner_size();
|
|
if (this._settings.align){
|
|
var handle = this._dataobj.firstChild;
|
|
if (this._settings.labelPosition == "top" && handle.nextSibling)
|
|
handle = handle.nextSibling;
|
|
|
|
switch(this._settings.align){
|
|
case "right":
|
|
handle.style.cssFloat = "right";
|
|
break;
|
|
case "center":
|
|
handle.style.display = "inline-block";
|
|
handle.parentNode.style.textAlign = "center";
|
|
break;
|
|
case "middle":
|
|
handle.style.marginTop = Math.round((this._content_height-this._input_height)/2)+"px";
|
|
break;
|
|
case "bottom":
|
|
handle.style.marginTop = (this._content_height-this._input_height)+"px";
|
|
break;
|
|
case "left":
|
|
handle.style.cssFloat = "left";
|
|
break;
|
|
default:
|
|
webix.assert(false, "Unknown align mode: "+this._settings.align);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (this.$render)
|
|
this.$render(this.data);
|
|
|
|
if (this._settings.disabled)
|
|
this.disable();
|
|
|
|
// set tooltip after render
|
|
if (this._settings.tooltip)
|
|
this.define("tooltip",this._settings.tooltip );
|
|
|
|
if (this._init_once){
|
|
this._init_once(this.data);
|
|
this._init_once = 0;
|
|
}
|
|
}
|
|
},
|
|
|
|
refresh:function(){ this.render(); },
|
|
|
|
on_click:{
|
|
_handle_tab_click: function(ev, button){
|
|
var id = webix.html.locate(ev, "button_id");
|
|
if (id && this.callEvent("onBeforeTabClick", [id, ev])){
|
|
this.setValue(id);
|
|
this.callEvent("onAfterTabClick", [id, ev]);
|
|
}
|
|
},
|
|
webix_all_segments:function(ev, button){
|
|
this.on_click._handle_tab_click.call(this, ev, button);
|
|
},
|
|
webix_all_tabs:function(ev, button) {
|
|
this.on_click._handle_tab_click.call(this, ev, button);
|
|
},
|
|
webix_inp_counter_next:function(e, obj, node){
|
|
if (!this._settings.readonly)
|
|
this.next();
|
|
},
|
|
webix_inp_counter_prev:function(e, obj, node){
|
|
if (!this._settings.readonly)
|
|
this.prev();
|
|
},
|
|
webix_input_icon:function(e, obj, node){
|
|
this.getInputNode().focus();
|
|
},
|
|
webix_inp_checkbox_border: function(e, obj, node) {
|
|
if (!this._settings.disabled && (e.target||e.srcElement).tagName != "DIV" && !this._settings.readonly)
|
|
this.toggle();
|
|
},
|
|
webix_inp_checkbox_label: function(e, obj, node) {
|
|
if (!this._settings.readonly)
|
|
this.toggle();
|
|
},
|
|
webix_inp_radio_border: function(e, obj, node) {
|
|
var value = webix.html.locate(e, "radio_id");
|
|
this.setValue(value);
|
|
},
|
|
webix_inp_radio_label: function(e, obj, node) {
|
|
node = node.parentNode.getElementsByTagName('input')[0];
|
|
return this.on_click.webix_inp_radio_border.call(this, node, obj, node);
|
|
},
|
|
webix_tab_more_icon: function(ev,obj, node){
|
|
this.getPopup().resize();
|
|
this.getPopup().show(node,null,true);
|
|
},
|
|
webix_tab_close:function(ev, obj, node){
|
|
var id = webix.html.locate(ev, "button_id");
|
|
if (id && this.callEvent("onBeforeTabClose", [id, ev]))
|
|
this.removeOption(id);
|
|
}
|
|
},
|
|
|
|
//method do not used by button, but used by other child-views
|
|
_check_options:function(opts){
|
|
webix.assert(opts, this.name+": options not defined");
|
|
for(var i=0;i<opts.length;i++){
|
|
//FIXME: asserts need to be removed in final version
|
|
webix.assert(!opts[i].text, "Please replace .text with .value in control config");
|
|
webix.assert(!opts[i].label, "Please replace .label with .value in control config");
|
|
|
|
if(typeof opts[i]=="string"){
|
|
opts[i] = {id:opts[i], value:opts[i]};
|
|
}
|
|
else {
|
|
if(webix.isUndefined(opts[i].id))
|
|
opts[i].id = opts[i].value;
|
|
|
|
if(webix.isUndefined(opts[i].value))
|
|
opts[i].value = opts[i].id;
|
|
}
|
|
}
|
|
return opts;
|
|
},
|
|
_get_div_placeholder: function(obj){
|
|
var placeholder = (obj?obj.placeholder:this._settings.placeholder);
|
|
return (placeholder?"<span class='webix_placeholder'>"+placeholder+"</span>":"");
|
|
}
|
|
}, webix.ui.view, webix.AtomRender, webix.Settings, webix.EventSystem);
|
|
|
|
webix.protoUI({
|
|
name:"label",
|
|
defaults:{
|
|
template:"<div style='height:100%;line-height:#cheight#px'>#label#</div>"
|
|
},
|
|
$skin:function(){
|
|
this.defaults.height = webix.skin.$active.inputHeight;
|
|
},
|
|
focus:function(){ return false; },
|
|
_getBox:function(){
|
|
return this._dataobj.firstChild;
|
|
},
|
|
setHTML:function(html){
|
|
this._settings.template = function(){ return html; };
|
|
this.refresh();
|
|
},
|
|
setValue: function(value){
|
|
this._settings.label = value;
|
|
webix.ui.button.prototype.setValue.apply(this,arguments);
|
|
},
|
|
$setValue:function(value){
|
|
this._dataobj.firstChild.innerHTML = value;
|
|
},
|
|
_set_inner_size:function(){},
|
|
_calc_size:function(config){
|
|
config = config || this._settings;
|
|
if (config.autowidth)
|
|
config.width = webix.html.getTextSize((config.value||config.label), "webix_el_label").width;
|
|
}
|
|
}, webix.ui.button);
|
|
|
|
webix.protoUI({
|
|
name:"icon",
|
|
$skin:function(){
|
|
this.defaults.height = webix.skin.$active.inputHeight;
|
|
},
|
|
defaults:{
|
|
template:function(obj){
|
|
return "<button type='button' "+" style='height:100%;width:100%;' class='webix_icon_button'><span class='webix_icon fa-"+obj.icon+" '></span>"+
|
|
(obj.badge ? "<span class='webix_badge'>"+obj.badge+"</span>":"")+
|
|
"</button>";
|
|
},
|
|
width:33
|
|
},
|
|
_set_inner_size:function(){
|
|
|
|
}
|
|
}, webix.ui.button);
|
|
|
|
|
|
|
|
webix.protoUI({
|
|
name:"text",
|
|
_allowsClear:true,
|
|
_init_onchange:function(){
|
|
if (this._allowsClear){
|
|
//attach onChange handler only for controls which do not manage blur on their own
|
|
//for example - combo
|
|
if (!this._onBlur)
|
|
webix._event(this.getInputNode(), "change", this._applyChanges, {bind:this});
|
|
if (this._settings.suggest)
|
|
webix.$$(this._settings.suggest).linkInput(this);
|
|
}
|
|
},
|
|
_applyChanges: function(){
|
|
var newvalue = this.getValue();
|
|
|
|
if (newvalue != this._settings.value)
|
|
this.setValue(newvalue, true);
|
|
},
|
|
$skin:function(){
|
|
this.defaults.height = webix.skin.$active.inputHeight;
|
|
this.defaults.inputPadding = webix.skin.$active.inputPadding;
|
|
this._inputSpacing = webix.skin.$active.inputSpacing;
|
|
},
|
|
$init:function(config){
|
|
if (config.labelPosition == "top")
|
|
if (webix.isUndefined(config.height) && this.defaults.height) // textarea
|
|
config.height = this.defaults.height + this._labelTopHeight;
|
|
|
|
//suggest reference for destructor
|
|
this._destroy_with_me = [];
|
|
|
|
this.attachEvent("onAfterRender", this._init_onchange);
|
|
this.attachEvent("onBlur", function(){
|
|
if(this._onBlur) this._onBlur();
|
|
});
|
|
},
|
|
$renderIcon:function(){
|
|
var config = this._settings;
|
|
if (config.icon){
|
|
var height = config.aheight - 2*config.inputPadding,
|
|
padding = (height - 18)/2 -1,
|
|
aria = this.addSection ? "role='button' tabindex='0' aria-label='"+(webix.i18n.aria["multitext"+(config.mode || "")+"Section"])+"'": "";
|
|
return "<span style='height:"+(height-padding)+"px;padding-top:"+padding+"px;' class='webix_input_icon fa-"+config.icon+"' "+aria+"></span>";
|
|
}
|
|
return "";
|
|
},
|
|
relatedView_setter:function(value){
|
|
this.attachEvent("onChange", function(){
|
|
var value = this.getValue();
|
|
var mode = this._settings.relatedAction;
|
|
var viewid = this._settings.relatedView;
|
|
var view = webix.$$(viewid);
|
|
if (!view){
|
|
var top = this.getTopParentView();
|
|
if (top && top.$$)
|
|
view = top.$$(viewid);
|
|
}
|
|
|
|
webix.assert(view, "Invalid relatedView: "+viewid);
|
|
|
|
if (mode == "enable"){
|
|
if (value) view.enable(); else view.disable();
|
|
} else {
|
|
if (value) view.show(); else view.hide();
|
|
}
|
|
});
|
|
return value;
|
|
},
|
|
validateEvent_setter:function(value){
|
|
if (value == "blur")
|
|
this.attachEvent("onBlur", this.validate);
|
|
|
|
if (value == "key")
|
|
this.attachEvent("onTimedKeyPress", this.validate);
|
|
|
|
return value;
|
|
},
|
|
validate:function(){
|
|
var rule = this._settings.validate;
|
|
if (!rule && this._settings.required)
|
|
rule = webix.rules.isNotEmpty;
|
|
|
|
var form =this.getFormView();
|
|
var name = this._settings.name;
|
|
var value = this.getValue();
|
|
var data = {}; data[name] = value;
|
|
|
|
webix.assert(form, "Validation works only for fields in the form");
|
|
webix.assert(name, "Validation works only for fields with name");
|
|
|
|
if (rule && !form._validate(rule, value, data, name))
|
|
return false;
|
|
return true;
|
|
},
|
|
bottomLabel_setter: function(value){
|
|
if(!this._settings.bottomPadding)
|
|
this._settings.bottomPadding = 18;
|
|
return value;
|
|
},
|
|
_getInvalidText: function(){
|
|
var text = this._settings.invalidMessage;
|
|
if(typeof text == "function"){
|
|
text.call(this);
|
|
}
|
|
return text;
|
|
},
|
|
setBottomText: function(text, height){
|
|
var config = this._settings;
|
|
if (typeof text != "undefined"){
|
|
if (config.bottomLabel == text) return;
|
|
config.bottomLabel = text;
|
|
}
|
|
|
|
var message = (config.invalid ? config.invalidMessage : "" ) || config.bottomLabel;
|
|
if (!message && !config.bottomPadding)
|
|
config.inputHeight = 0;
|
|
if (message && !config.bottomPadding){
|
|
this._restorePadding = 1;
|
|
config.bottomPadding = config.bottomPadding || height || 18;
|
|
this.render();
|
|
this.resize();
|
|
} else if (!message && this._restorePadding){
|
|
config.bottomPadding = this._restorePadding = 0;
|
|
//textarea
|
|
if (!config.height)
|
|
this.render();
|
|
this.resize();
|
|
} else
|
|
this.render();
|
|
},
|
|
$getSize: function(){
|
|
var sizes = webix.ui.view.prototype.$getSize.apply(this,arguments);
|
|
var heightInc = this.config.bottomPadding;
|
|
if(heightInc){
|
|
sizes[2] += heightInc;
|
|
sizes[3] += heightInc;
|
|
}
|
|
return sizes;
|
|
},
|
|
$setSize:function(x,y){
|
|
var config = this._settings;
|
|
|
|
if(webix.ui.view.prototype.$setSize.call(this,x,y)){
|
|
if (!x || !y) return;
|
|
|
|
if (config.labelPosition == "top"){
|
|
// textarea
|
|
if (!config.inputHeight)
|
|
this._inputHeight = this._content_height - this._labelTopHeight - (this.config.bottomPadding||0);
|
|
config.labelWidth = 0;
|
|
} else if (config.bottomPadding){
|
|
config.inputHeight = this._content_height - this.config.bottomPadding;
|
|
}
|
|
this.render();
|
|
}
|
|
},
|
|
_get_input_width: function(config){
|
|
var width = (this._input_width||0)-(config.label?this._settings.labelWidth:0) - this._inputSpacing - (config.iconWidth || 0);
|
|
|
|
//prevent js error in IE
|
|
return (width < 0)?0:width;
|
|
},
|
|
_render_div_block:function(obj, common){
|
|
var id = "x"+webix.uid();
|
|
var width = common._get_input_width(obj);
|
|
var inputAlign = obj.inputAlign || "left";
|
|
var icon = this.$renderIcon?this.$renderIcon(obj):"";
|
|
var height = this._settings.aheight - 2*webix.skin.$active.inputPadding -2*this._borderWidth;
|
|
var text = (obj.text||obj.value||this._get_div_placeholder(obj));
|
|
var html = "<div class='webix_inp_static' role='combobox' aria-label='"+webix.template.escape(obj.label)+"' tabindex='0'"+(obj.readonly?" aria-readonly='true'":"")+(obj.invalid?"aria-invalid='true'":"")+" onclick='' style='line-height:"+height+"px;width: " + width + "px; text-align: " + inputAlign + ";' >"+ text +"</div>";
|
|
return common.$renderInput(obj, html, id);
|
|
},
|
|
_baseInputHTML:function(tag){
|
|
var html = "<"+tag+(this._settings.placeholder?" placeholder='"+this._settings.placeholder+"' ":" ");
|
|
if (this._settings.readonly)
|
|
html += "readonly='true' aria-readonly=''";
|
|
if(this._settings.required)
|
|
html += "aria-required='true'";
|
|
if(this._settings.invalid)
|
|
html += "aria-invalid='true'";
|
|
|
|
var attrs = this._settings.attributes;
|
|
if (attrs)
|
|
for(var prop in attrs)
|
|
html += prop+"='"+attrs[prop]+"' ";
|
|
return html;
|
|
},
|
|
$renderLabel: function(config, id){
|
|
var labelAlign = (config.labelAlign||"left");
|
|
var top = this._settings.labelPosition == "top";
|
|
var labelTop = top?"display:block;":("width: " + this._settings.labelWidth + "px;");
|
|
var label = "";
|
|
var labelHeight = top?this._labelTopHeight-2*this._borderWidth:( this._settings.aheight - 2*this._settings.inputPadding);
|
|
if (config.label)
|
|
label = "<label style='"+labelTop+"text-align: " + labelAlign + ";line-height:"+labelHeight+"px;' onclick='' for='"+id+"' class='webix_inp_"+(top?"top_":"")+"label "+(config.required?"webix_required":"")+"'>" + (config.label||"") + "</label>";
|
|
return label;
|
|
},
|
|
$renderInput: function(config, div_start, id) {
|
|
var inputAlign = (config.inputAlign||"left");
|
|
var top = (config.labelPosition == "top");
|
|
var inputWidth = this._get_input_width(config);
|
|
|
|
id = id||webix.uid();
|
|
|
|
var label = this.$renderLabel(config,id);
|
|
|
|
var html = "";
|
|
if(div_start){
|
|
html += div_start;
|
|
} else {
|
|
var value = webix.template.escape(config.text || this._pattern(config.value)|| ( config.value ===0 ?"0":"") );
|
|
html += this._baseInputHTML("input")+"id='" + id + "' type='"+(config.type||this.name)+"'"+(config.editable?" role='combobox'":"")+" value='" + value + "' style='width: " + inputWidth + "px; text-align: " + inputAlign + ";'";
|
|
var attrs = config.attributes;
|
|
if (attrs)
|
|
for(var prop in attrs)
|
|
html += " "+prop+"='"+attrs[prop]+"'";
|
|
html += " />";
|
|
}
|
|
var icon = this.$renderIcon?this.$renderIcon(config):"";
|
|
html += icon;
|
|
|
|
var result = "";
|
|
//label position, top or left
|
|
if (top)
|
|
result = label+"<div class='webix_el_box' style='width:"+config.awidth+"px; height:"+config.aheight+"px'>"+html+"</div>";
|
|
else
|
|
result = "<div class='webix_el_box' style='width:"+config.awidth+"px; height:"+config.aheight+"px'>"+label+html+"</div>";
|
|
|
|
|
|
//bottom message width
|
|
var padding = config.awidth-inputWidth-webix.skin.$active.inputPadding*2;
|
|
//bottom message text
|
|
var message = (config.invalid ? config.invalidMessage : "") || config.bottomLabel;
|
|
if (message)
|
|
result += "<div class='webix_inp_bottom_label'"+(config.invalid?"role='alert' aria-relevant='all'":"")+" style='width:"+(inputWidth||config.awidth)+"px;margin-left:"+Math.max(padding,webix.skin.$active.inputPadding)+"px;'>"+message+"</div>";
|
|
|
|
return result;
|
|
},
|
|
defaults:{
|
|
template:function(obj, common){
|
|
return common.$renderInput(obj);
|
|
},
|
|
label:"",
|
|
labelWidth:80
|
|
},
|
|
type_setter:function(value){ return value; },
|
|
_set_inner_size:false,
|
|
$setValue:function(value){
|
|
this.getInputNode().value = this._pattern(value);
|
|
},
|
|
$getValue:function(){
|
|
return this._pattern(this.getInputNode().value, false);
|
|
},
|
|
suggest_setter:function(value){
|
|
if (value){
|
|
webix.assert(value !== true, "suggest options can't be set as true, data need to be provided instead");
|
|
|
|
if (typeof value == "string"){
|
|
var attempt = webix.$$(value);
|
|
if (attempt)
|
|
return webix.$$(value)._settings.id;
|
|
|
|
value = { body: { url:value , dataFeed :value } };
|
|
} else if (webix.isArray(value))
|
|
value = { body: { data: this._check_options(value) } };
|
|
else if (!value.body)
|
|
value.body = {};
|
|
|
|
webix.extend(value, { view:"suggest" });
|
|
|
|
var view = webix.ui(value);
|
|
this._destroy_with_me.push(view);
|
|
return view._settings.id;
|
|
}
|
|
return false;
|
|
}
|
|
}, webix.ui.button);
|
|
|
|
webix.protoUI({
|
|
name:"segmented",
|
|
_allowsClear:false,
|
|
$init:function(){
|
|
this.attachEvent("onChange", function(value){
|
|
if (this._settings.multiview)
|
|
this._show_view(value);
|
|
});
|
|
this.attachEvent("onAfterRender", webix.once(function(){
|
|
if (this._settings.multiview && this._settings.value)
|
|
this._show_view(this._settings.value);
|
|
}));
|
|
},
|
|
_show_view:function(value){
|
|
var top = this.getTopParentView();
|
|
var view = null;
|
|
|
|
//get from local isolate
|
|
if (top && top.$$)
|
|
view = top.$$(value);
|
|
//or check globally
|
|
if (!view)
|
|
view = webix.$$(value);
|
|
|
|
if(view && view.show)
|
|
view.show();
|
|
},
|
|
defaults:{
|
|
template:function(obj, common){
|
|
if(!obj.options)
|
|
webix.assert(false, "segmented: options undefined");
|
|
var options = obj.options;
|
|
common._check_options(options);
|
|
options = common._filterOptions(options);
|
|
|
|
var width = common._get_input_width(obj);
|
|
|
|
var id = webix.uid();
|
|
var html = "<div style='width:"+width+"px' class='webix_all_segments' role='tablist' aria-label='"+webix.template.escape(obj.label)+"'>";
|
|
var optionWidth = obj.optionWidth || Math.floor(width/options.length);
|
|
if(!obj.value)
|
|
obj.value = options[0].id;
|
|
|
|
for(var i=0; i<options.length; i++){
|
|
html+="<button type='button' style='width:"+(options[i].width || optionWidth)+"px' role='tab' aria-selected='"+(obj.value==options[i].id?"true":"false")+"' tabindex='"+(obj.value==options[i].id?"0":"-1")+"'";
|
|
html+="class='"+"webix_segment_"+((i==options.length-1)?"N":(i>0?1:0))+((obj.value==options[i].id)?" webix_selected ":"")+"' button_id='"+options[i].id+"' "+(options[i].tooltip?("title='"+options[i].tooltip+"'"):"")+">";
|
|
html+= options[i].value+"</button>";
|
|
}
|
|
|
|
return common.$renderInput(obj, html+"</div>", id);
|
|
}
|
|
},
|
|
_getInputNode:function(){
|
|
return this.$view.getElementsByTagName("BUTTON");
|
|
},
|
|
focus: function(){ this._focus(); },
|
|
blur: function(){ this._blur(); },
|
|
$setValue:function(value){
|
|
|
|
var options = this._getInputNode();
|
|
|
|
for(var i=0; i<options.length; i++){
|
|
var id = options[i].getAttribute("button_id");
|
|
options[i].setAttribute("aria-selected", (value==id?"true":"false"));
|
|
options[i].setAttribute("tabindex", (value==id?"0":"-1"));
|
|
if(value==id)
|
|
webix.html.addCss(options[i], "webix_selected");
|
|
else
|
|
webix.html.removeCss(options[i], "webix_selected");
|
|
}
|
|
//refresh tabbar if the option is in the popup list
|
|
var popup = this.config.tabbarPopup;
|
|
if(popup && webix.$$(popup) && webix.$$(popup).getBody().exists(value))
|
|
this.refresh();
|
|
},
|
|
getValue:function(){
|
|
return this._settings.value;
|
|
},
|
|
getInputNode:function(){
|
|
return null;
|
|
},
|
|
optionIndex:function(id){
|
|
var pages = this._settings.options;
|
|
for (var i=0; i<pages.length; i++)
|
|
if (pages[i].id == id)
|
|
return i;
|
|
return -1;
|
|
},
|
|
addOption:function(id, value, show, index){
|
|
var obj = id;
|
|
if (typeof id != "object"){
|
|
value = value || id;
|
|
obj = { id:id, value:value };
|
|
} else {
|
|
id = obj.id;
|
|
index = show;
|
|
show = value;
|
|
}
|
|
|
|
if (this.optionIndex(id) < 0)
|
|
webix.PowerArray.insertAt.call(this._settings.options, obj, index);
|
|
this.refresh();
|
|
|
|
if (show)
|
|
this.setValue(id);
|
|
},
|
|
removeOption:function(id, value){
|
|
var index = this.optionIndex(id);
|
|
var options = this._settings.options;
|
|
|
|
if (index >= 0)
|
|
webix.PowerArray.removeAt.call(options, index);
|
|
|
|
// if we remove a selected option
|
|
if(this._settings.value == id)
|
|
this._setNextVisible(options, index);
|
|
|
|
this.refresh();
|
|
this.callEvent("onOptionRemove", [id, this._settings.value]);
|
|
},
|
|
_setNextVisible: function(options, index){
|
|
var size = options.length;
|
|
|
|
if(size){
|
|
index = Math.min(index, size-1);
|
|
//forward search
|
|
for (var i=index; i<size; i++)
|
|
if (!options[i].hidden)
|
|
return this.setValue(options[i].id);
|
|
//backward search
|
|
for (var i=index; i>=0; i--)
|
|
if (!options[i].hidden)
|
|
return this.setValue(options[i].id);
|
|
}
|
|
|
|
//nothing found
|
|
this.setValue("");
|
|
},
|
|
_filterOptions: function(options){
|
|
var copy = [];
|
|
for(var i=0; i<options.length;i++)
|
|
if(!options[i].hidden)
|
|
copy.push(options[i]);
|
|
return copy;
|
|
},
|
|
_setOptionVisibility: function(id, state){
|
|
var options = this._settings.options;
|
|
var index = this.optionIndex(id);
|
|
var option = options[index];
|
|
if (option && state == !!option.hidden){ //new state differs from previous one
|
|
option.hidden = !state;
|
|
if (state || this._settings.value != id){ //show item, no need for extra steps
|
|
this.refresh();
|
|
} else { //hide item, switch to next visible one
|
|
this._setNextVisible(options, index);
|
|
}
|
|
}
|
|
},
|
|
hideOption: function(id){
|
|
this._setOptionVisibility(id,false);
|
|
},
|
|
showOption: function(id){
|
|
this._setOptionVisibility(id,true);
|
|
},
|
|
_set_inner_size:false
|
|
}, webix.HTMLOptions, webix.ui.text);
|
|
|
|
webix.protoUI({
|
|
name:"search",
|
|
$init:function(){
|
|
this.on_click["fa-search"] = function(e){
|
|
this.callEvent("onSearchIconClick", [e]);
|
|
};
|
|
},
|
|
$skin:function(){
|
|
this.defaults.inputPadding = webix.skin.$active.inputPadding;
|
|
},
|
|
defaults:{
|
|
type:"text",
|
|
icon:"search"
|
|
}
|
|
}, webix.ui.text);
|
|
|
|
webix.protoUI({
|
|
name:"toggle",
|
|
_allowsClear:true,
|
|
$init:function(){
|
|
this.attachEvent("onItemClick", function(){
|
|
this.toggle();
|
|
});
|
|
},
|
|
$setValue:function(value){
|
|
var input = this.getInputNode();
|
|
var obj = this._settings;
|
|
var isPressed = (value && value != "0");
|
|
var text = (isPressed ? obj.onLabel : obj.offLabel) || obj.label;
|
|
|
|
input.setAttribute("aria-pressed", isPressed?"true":false);
|
|
input.value = text;
|
|
if (input.lastChild)
|
|
input.lastChild.nodeValue = " "+text;
|
|
|
|
//icon or image button
|
|
if(input.firstChild && input.firstChild.nodeName ==="SPAN" && obj.onIcon && obj.offIcon && obj.onIcon !==obj.offIcon)
|
|
input.firstChild.className = input.firstChild.className.replace((isPressed?obj.offIcon:obj.onIcon), (isPressed?obj.onIcon:obj.offIcon));
|
|
|
|
var parent = input.parentNode;
|
|
if(isPressed)
|
|
webix.html.addCss(parent, "webix_pressed");
|
|
else
|
|
webix.html.removeCss(parent, "webix_pressed");
|
|
},
|
|
toggle:function(){
|
|
this.setValue(!this.getValue());
|
|
},
|
|
getValue:function(){
|
|
var value = this._settings.value;
|
|
return (!value||value=="0")?0:1;
|
|
},
|
|
defaults:{
|
|
template:function(obj, common){
|
|
var isPressed = (obj.value && obj.value != "0");
|
|
var css = isPressed ? " webix_pressed" : "";
|
|
|
|
obj.label = (isPressed ? obj.onLabel : obj.offLabel) || obj.label;
|
|
obj.icon = (isPressed ? obj.onIcon : obj.offIcon) || obj.icon;
|
|
var html = "<div class='webix_el_box"+css+"' style='width:"+obj.awidth+"px; height:"+obj.aheight+"px'>"+common.$renderInput(obj, common)+"</div>";
|
|
html = html.replace(/(button)\s*(?=\w)/, "$1"+(" aria-pressed='"+(isPressed?"true":"false")+"' "));
|
|
return html;
|
|
}
|
|
},
|
|
_set_inner_size:false
|
|
}, webix.ui.button);
|
|
|
|
webix.protoUI({
|
|
name:"select",
|
|
defaults:{
|
|
template:function(obj,common) {
|
|
var options = common._check_options(obj.options);
|
|
var id = "x"+webix.uid();
|
|
var html = common._baseInputHTML("select")+"id='"+id+"' style='width:"+common._get_input_width(obj)+"px;'>";
|
|
|
|
var optview = webix.$$(options);
|
|
if(optview && optview.data && optview.data.each){
|
|
optview.data.each(function(option){
|
|
html+="<option"+((option.id == obj.value)?" selected='true'":"")+" value='"+option.id+"'>"+option.value+"</option>";
|
|
});
|
|
}else
|
|
for(var i=0; i<options.length; i++) {
|
|
html+="<option"+((options[i].id == obj.value)?" selected='true'":"")+" value='"+options[i].id+"'>"+options[i].value+"</option>";
|
|
}
|
|
html += "</select>";
|
|
return common.$renderInput(obj, html, id);
|
|
}
|
|
},
|
|
options_setter:function(value){
|
|
if(value){
|
|
if(typeof value =="string"){
|
|
var collection = new webix.DataCollection({url:value});
|
|
collection.data.attachEvent("onStoreLoad", webix.bind(this.refresh, this));
|
|
return collection;
|
|
}
|
|
else
|
|
return value;
|
|
}
|
|
},
|
|
//get input element
|
|
getInputNode: function() {
|
|
return this._dataobj.getElementsByTagName('select')[0];
|
|
}
|
|
}, webix.ui.text);
|
|
|
|
webix.protoUI({
|
|
name:"textarea",
|
|
defaults:{
|
|
template:function(obj, common){
|
|
var name = obj.name || obj.id;
|
|
var id = "x"+webix.uid();
|
|
|
|
var html = common._baseInputHTML("textarea")+"style='width:"+common._get_input_width(obj)+"px;'";
|
|
html +=" id='"+id+"' name='"+name+"' class='webix_inp_textarea'>"+common._pattern(obj.value|| (obj.value ===0?"0":""))+"</textarea>";
|
|
|
|
return common.$renderInput(obj, html, id);
|
|
},
|
|
height:0,
|
|
minHeight:60
|
|
},
|
|
$skin:function(){
|
|
this.defaults.inputPadding = webix.skin.$active.inputPadding;
|
|
this._inputSpacing = webix.skin.$active.inputSpacing;
|
|
},
|
|
_skipSubmit: true,
|
|
$renderLabel: function(config, id){
|
|
var labelAlign = (config.labelAlign||"left");
|
|
var top = this._settings.labelPosition == "top";
|
|
var labelTop = top?"display:block;":("width: " + this._settings.labelWidth + "px;");
|
|
var label = "";
|
|
var labelHeight = top?this._labelTopHeight-2*this._borderWidth:( (webix.skin.$active.inputHeight||this._settings.aheight) - 2*this._settings.inputPadding);
|
|
if (config.label)
|
|
label = "<label style='"+labelTop+"text-align: " + labelAlign + ";' onclick='' for='"+id+"' class='webix_inp_"+(top?"top_":"")+"label "+(config.required?"webix_required":"")+"'>" + (config.label||"") + "</label>";
|
|
return label;
|
|
},
|
|
//get input element
|
|
getInputNode: function() {
|
|
return this._dataobj.getElementsByTagName('textarea')[0];
|
|
}
|
|
}, webix.ui.text);
|
|
|
|
webix.protoUI({
|
|
name:"counter",
|
|
defaults:{
|
|
template:function(config, common){
|
|
var value = (config.value||0);
|
|
|
|
var id = "x"+webix.uid();
|
|
var html = "<div role='spinbutton' aria-label='"+webix.template.escape(config.label)+"' aria-valuemin='"+config.min+"' aria-valuemax='"+config.max+"' aria-valuenow='"+config.value+"' class='webix_el_group' style='width:"+common._get_input_width(config)+"px'>";
|
|
html += "<button type='button' class='webix_inp_counter_prev' tabindex='-1' aria-label='"+webix.i18n.aria.decreaseValue+"'>-</button>";
|
|
html += common._baseInputHTML("input")+" id='"+id+"' type='text' class='webix_inp_counter_value' aria-live='assertive'"+" value='"+value+"'></input>";
|
|
html += "<button type='button' class='webix_inp_counter_next' tabindex='-1' aria-label='"+webix.i18n.aria.increaseValue+"'>+</button></div>";
|
|
return common.$renderInput(config, html, id);
|
|
},
|
|
min:0,
|
|
max:Infinity,
|
|
step:1
|
|
},
|
|
$init:function(){
|
|
webix._event(this.$view, "keydown", this._keyshift, {bind:this});
|
|
},
|
|
_keyshift:function(e){
|
|
var code = e.which || e.keyCode, c = this._settings, value = c.value || c.min;
|
|
|
|
if(code>32 && code <41){
|
|
if(code === 35) value = c.min;
|
|
else if(code === 36) value = c.max === Infinity? 1000000 :c.max;
|
|
else if(code === 33) this.next();
|
|
else if(code === 34) this.prev();
|
|
else value = value+(code === 37 || code ===40?-1:1);
|
|
|
|
if(code>34 && value>=c.min && value <=c.max)
|
|
this.setValue(value);
|
|
}
|
|
},
|
|
$setValue:function(value){
|
|
this.getInputNode().value = value;
|
|
},
|
|
getInputNode:function(){
|
|
return this._dataobj.getElementsByTagName("input")[0];
|
|
},
|
|
getValue:function(obj){
|
|
return webix.ui.button.prototype.getValue.apply(this,arguments)*1;
|
|
},
|
|
next:function(step){
|
|
step = this._settings.step;
|
|
this.shift(step);
|
|
},
|
|
prev:function(step){
|
|
step = (-1)*this._settings.step;
|
|
this.shift(step);
|
|
},
|
|
shift:function(step){
|
|
var min = this._settings.min;
|
|
var max = this._settings.max;
|
|
|
|
var new_value = this.getValue() + step;
|
|
if (new_value >= min && new_value <= max)
|
|
this.setValue(new_value);
|
|
}
|
|
}, webix.ui.text);
|
|
|
|
|
|
webix.protoUI({
|
|
name:"checkbox",
|
|
defaults:{
|
|
checkValue:1,
|
|
uncheckValue:0,
|
|
template:function(config, common) {
|
|
var id = "x"+webix.uid();
|
|
var rightlabel = "";
|
|
if (config.labelRight){
|
|
rightlabel = "<label class='webix_label_right'>"+config.labelRight+"</label>";
|
|
//user clearly attempts to hide the label, help him
|
|
if (config.labelWidth)
|
|
config.label = config.label || " ";
|
|
}
|
|
var checked = (config.checkValue == config.value);
|
|
var margin = Math.floor((common._settings.aheight-16)/2);
|
|
var ch = common._baseInputHTML("input")+"style='margin-top:"+margin+"px;"+(config.customCheckbox?"display:none":"")+"' id='"+id+"' type='checkbox' "+(checked?"checked='1'":"")+(config.labelRight?" aria-label='"+webix.template.escape(config.labelRight)+"'":"")+"/>";
|
|
var className = "webix_inp_checkbox_border webix_el_group webix_checkbox_"+(checked?"1":"0");
|
|
var customCheckbox = config.customCheckbox || "";
|
|
if(customCheckbox){
|
|
customCheckbox = customCheckbox.replace(/(aria-checked=')\w*(?=')/, "$1"+(config.value == config.checkValue?"true":"false"));
|
|
customCheckbox = customCheckbox.replace(/(aria-label=')\w*(?=')/, "$1"+webix.template.escape(config.labelRight || config.label));
|
|
customCheckbox = customCheckbox.replace(/(aria-invalid=')\w*(?=')/, "$1"+(config.invalid?"true":"false"));
|
|
}
|
|
var html = "<div style='line-height:"+common._settings.cheight+"px' class='"+className+"'>"+ch+customCheckbox+rightlabel+"</div>";
|
|
return common.$renderInput(config, html, id);
|
|
}
|
|
},
|
|
customCheckbox_setter: function(value){
|
|
if( value === true && webix.skin.$active.customCheckbox){
|
|
value = "<a role='presentation' onclick='javascript:void(0)'><button role='checkbox' aria-checked='false' aria-label='' type='button' aria-invalid='' class='webix_custom_checkbox'></button></a>";
|
|
}
|
|
return value;
|
|
},
|
|
focus: function(){
|
|
var input = this.getInputNode();
|
|
if(input) input.focus();
|
|
},
|
|
blur: function(){
|
|
var input = this.getInputNode();
|
|
if(input) input.blur();
|
|
},
|
|
_init_onchange: function(){},
|
|
$setValue:function(value){
|
|
var isChecked = (value == this._settings.checkValue);
|
|
var input = this.$view.getElementsByTagName("input")[0];
|
|
var parentNode = input?input.parentNode:null;
|
|
|
|
if(parentNode && this._settings.customCheckbox){
|
|
var button = parentNode.getElementsByTagName("BUTTON");
|
|
if(button[0]) button[0].setAttribute("aria-checked", isChecked?"true":"false");
|
|
}
|
|
if(parentNode){
|
|
parentNode.className = parentNode.className.replace(/(webix_checkbox_)\d/,"$1"+(isChecked?1:0));
|
|
}
|
|
input.checked = isChecked;
|
|
},
|
|
toggle:function(){
|
|
var value = (this.getValue() != this._settings.checkValue)?this._settings.checkValue:this._settings.uncheckValue;
|
|
this.setValue(value);
|
|
},
|
|
getValue:function(){
|
|
var value = this._settings.value;
|
|
return (value == this._settings.checkValue)?this._settings.checkValue:this._settings.uncheckValue;
|
|
},
|
|
getInputNode: function() {
|
|
return this.$view.getElementsByTagName(this._settings.customCheckbox?"button":"input")[0];
|
|
},
|
|
$skin:function(){
|
|
if(webix.skin.$active.customCheckbox)
|
|
this.defaults.customCheckbox = true;
|
|
}
|
|
}, webix.ui.text);
|
|
|
|
webix.protoUI({
|
|
name:"radio",
|
|
defaults:{
|
|
template: function(config,common) {
|
|
var options = common._check_options(config.options);
|
|
var html = [];
|
|
var id;
|
|
|
|
for (var i=0; i < options.length; i++) {
|
|
var eachid = "x"+webix.uid();
|
|
id = id || eachid;
|
|
|
|
if (i && (options[i].newline || config.vertical))
|
|
html.push("<div class='webix_line_break'></div>");
|
|
var isChecked = (options[i].id == config.value);
|
|
var label = options[i].value || "";
|
|
|
|
var customRadio = config.customRadio|| "";
|
|
if(customRadio){
|
|
var optlabel = (i === 0 ? config.label+" " : "")+label;
|
|
customRadio = customRadio.replace(/(aria-label=')\w*(?=')/, "$1"+webix.template.escape(optlabel));
|
|
customRadio = customRadio.replace(/(aria-checked=')\w*(?=')/, "$1"+(isChecked?"true":"false"));
|
|
customRadio = customRadio.replace(/(tabindex=')\w*(?=')/, "$1"+(isChecked || (i === 0 && !config.value)?"0":"-1"));
|
|
customRadio = customRadio.replace(/(aria-invalid=')\w*(?=')/, "$1"+(config.invalid?"true":"false"));
|
|
}
|
|
var rd = common._baseInputHTML("input")+" name='"+(config.name || config.id)+"' type='radio' "+(isChecked?"checked='1'":"")+"tabindex="+(isChecked || (i === 0 && !config.value)?"0":"-1")+" value='"+options[i].id+"' id='"+eachid+"' style='"+(customRadio?"display:none":"")+"' />";
|
|
var input = "<div radio_id='"+options[i].id+"' class='webix_inp_radio_border webix_radio_"+(isChecked?"1":"0")+"' role='presentation'>"+rd+customRadio+"</div>";
|
|
if (label)
|
|
label = "<label for='"+eachid+"' class='webix_label_right'>" + label + "</label>";
|
|
|
|
html.push("<div class='webix_radio_option' role='presentation'>"+input + label+"</div>");
|
|
|
|
}
|
|
html = "<div class='webix_el_group' role='radiogroup' style='margin-left:"+(config.label?config.labelWidth:0)+"px;'>"+html.join("")+"</div>";
|
|
|
|
return common.$renderInput(config, html, id);
|
|
}
|
|
},
|
|
refresh:function(){
|
|
this.render();
|
|
if (this._last_size && this.$getSize(0,0)[2] != this._last_size[1])
|
|
this.resize();
|
|
},
|
|
$getSize:function(dx, dy){
|
|
var size = webix.ui.button.prototype.$getSize.call(this, dx, dy);
|
|
if (this._settings.options){
|
|
var count = this._settings.vertical?0:1;
|
|
for (var i=0; i < this._settings.options.length; i++)
|
|
if (this._settings.vertical || this._settings.options[i].newline)
|
|
count++;
|
|
size[3] = size[2] = Math.max(size[2], (this._settings.optionHeight||25) * count+this._settings.inputPadding*2+ (this._settings.labelPosition == "top"?this._labelTopHeight:0));
|
|
}
|
|
var heightInc = this.config.bottomPadding;
|
|
if(heightInc){
|
|
size[2] += heightInc;
|
|
size[3] += heightInc;
|
|
}
|
|
return size;
|
|
},
|
|
_getInputNode: function(){
|
|
return this._dataobj.getElementsByTagName('input');
|
|
},
|
|
$setValue:function(value){
|
|
var inp = this._getInputNode();
|
|
|
|
for (var i=0; i < inp.length; i++){
|
|
if (inp[i].parentNode.getAttribute("radio_id")==value){
|
|
inp[i].className = "webix_inp_radio_on";
|
|
inp[i].checked = true;
|
|
inp[i].setAttribute("tabindex","0");
|
|
} else{
|
|
inp[i].className = "webix_inp_radio_on webix_hidden";
|
|
inp[i].checked = false;
|
|
inp[i].setAttribute("tabindex","-1");
|
|
}
|
|
var parentNode = inp[i]?inp[i].parentNode:null;
|
|
|
|
if(parentNode){
|
|
parentNode.className = parentNode.className.replace(/(webix_radio_)\d/,"$1"+(inp[i].checked?1:0));
|
|
if(this._settings.customRadio){
|
|
var button = parentNode.getElementsByTagName("BUTTON");
|
|
if(button[0]){
|
|
button[0].setAttribute("aria-checked", inp[i].checked?"true":"false");
|
|
button[0].setAttribute("tabindex", inp[i].checked?"0":"-1");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
getValue:function(obj){
|
|
return this._settings.value;
|
|
},
|
|
focus: function(){ this._focus(); },
|
|
blur: function(){ this._blur(); },
|
|
customRadio_setter: function(value){
|
|
if(value === true && webix.skin.$active.customRadio)
|
|
value = "<a role='presentation' onclick='javascript:void(0)'><button type='button' class='webix_custom_radio' role='radio' aria-checked='false' aria-label='' aria-invalid='' tabindex=''></button></a>";
|
|
return value;
|
|
},
|
|
$skin:function(){
|
|
if(webix.skin.$active.customRadio)
|
|
this.defaults.customRadio = true;
|
|
if(webix.skin.$active.optionHeight)
|
|
this.defaults.optionHeight = webix.skin.$active.optionHeight;
|
|
}
|
|
}, webix.HTMLOptions, webix.ui.text);
|
|
|
|
webix.protoUI({
|
|
name:"richselect",
|
|
defaults:{
|
|
template:function(obj,common){
|
|
return common._render_div_block(obj, common);
|
|
},
|
|
popupWidth:200,
|
|
icon: "angle-down"
|
|
},
|
|
_onBlur:function(){
|
|
if (this._settings.text == this.getText() || (webix.isUndefined(this._settings.text) && !this.getText()))
|
|
return;
|
|
|
|
var suggest = this.getPopup(),
|
|
value = suggest.getSuggestion();
|
|
|
|
if (value && !(this.getInputNode().value==="" && suggest.getItemText(value)!==""))
|
|
this.setValue(value);
|
|
else if(this._revertValue)
|
|
this._revertValue();
|
|
},
|
|
suggest_setter:function(value){
|
|
return this.options_setter(value);
|
|
},
|
|
options_setter:function(value){
|
|
value = this._suggest_config ? this._suggest_config(value) : value;
|
|
var suggest = (this._settings.popup = this._settings.suggest = webix.ui.text.prototype.suggest_setter.call(this, value));
|
|
var list = webix.$$(suggest).getList();
|
|
if (list)
|
|
list.attachEvent("onAfterLoad", webix.bind(this._reset_value, this));
|
|
|
|
return suggest;
|
|
},
|
|
getList: function(){
|
|
var suggest = webix.$$(this._settings.suggest);
|
|
webix.assert(suggest, "Input doesn't have a list");
|
|
return suggest.getList();
|
|
},
|
|
_reset_value:function(){
|
|
var value = this._settings.value;
|
|
//this._dataobj.firstChild - check that input is already rendered, as in IE11 it can be destroy during parent repainting
|
|
if(!webix.isUndefined(value) && !this.getPopup().isVisible() && !this._settings.text && this._dataobj.firstChild)
|
|
this.$setValue(value);
|
|
},
|
|
$skin:function(){
|
|
this.defaults.inputPadding = webix.skin.$active.inputPadding;
|
|
},
|
|
$render:function(obj){
|
|
if (webix.isUndefined(obj.value)) return;
|
|
this.$setValue(obj.value);
|
|
},
|
|
getInputNode: function(){
|
|
return this._dataobj.getElementsByTagName("DIV")[1];
|
|
},
|
|
getPopup: function(){
|
|
return webix.$$(this._settings.popup);
|
|
},
|
|
getText:function(){
|
|
var value = this._settings.value,
|
|
node = this.getInputNode();
|
|
if(!node)
|
|
return value?this.getPopup().getItemText(value):"";
|
|
if (typeof node.value == "undefined"){
|
|
if (node.firstChild && node.firstChild.className === "webix_placeholder")
|
|
return "";
|
|
return node.innerHTML;
|
|
}
|
|
return node.value;
|
|
},
|
|
$setValue:function(value){
|
|
if (!this._rendered_input) return;
|
|
|
|
var text = value;
|
|
var popup = this.getPopup();
|
|
|
|
if (popup)
|
|
var text = this.getPopup().getItemText(value);
|
|
|
|
if (!text && value && value.id){ //add new value
|
|
this.getPopup().getList().add(value);
|
|
text = this.getPopup().getItemText(value.id);
|
|
this._settings.value = value.id;
|
|
}
|
|
|
|
var node = this.getInputNode();
|
|
|
|
if (webix.isUndefined(node.value))
|
|
node.innerHTML = text || this._get_div_placeholder();
|
|
else
|
|
node.value = text = text.replace(/<[^>]*>/g,"");
|
|
|
|
this._settings.text = text;
|
|
},
|
|
getValue:function(){
|
|
return this._settings.value||"";
|
|
}
|
|
}, webix.ui.text);
|
|
|
|
|
|
webix.protoUI({
|
|
name:"combo",
|
|
getInputNode:function(){
|
|
return this._dataobj.getElementsByTagName('input')[0];
|
|
},
|
|
$render:function(obj){
|
|
if (webix.isUndefined(obj.value)) return;
|
|
this.$setValue(obj.value);
|
|
},
|
|
_revertValue:function(){
|
|
if(!this._settings.editable){
|
|
var value = this.getValue();
|
|
this.$setValue(webix.isUndefined(value)?"":value);
|
|
}
|
|
},
|
|
_applyChanges:function(){
|
|
var input = this.getInputNode(),
|
|
value = "",
|
|
suggest = this.getPopup();
|
|
|
|
if (input.value){
|
|
value = this._settings.value;
|
|
if(suggest.getItemText(value) != this.getText())
|
|
value = suggest.getSuggestion()||value;
|
|
}
|
|
if (value != this._settings.value)
|
|
this.setValue(value, true);
|
|
else
|
|
this.$setValue(value);
|
|
},
|
|
defaults:{
|
|
template:function(config, common){
|
|
return common.$renderInput(config).replace(/(<input)\s*(?=\w)/, "$1"+" role='combobox'");
|
|
},
|
|
icon: "angle-down"
|
|
}
|
|
}, webix.ui.richselect);
|
|
|
|
|
|
webix.protoUI({
|
|
name:"datepicker",
|
|
$init:function(){
|
|
this.$ready.push(this._init_popup);
|
|
},
|
|
defaults:{
|
|
template:function(obj, common){
|
|
if(common._settings.type == "time"){
|
|
common._settings.icon = common._settings.timeIcon;
|
|
}
|
|
//temporary remove obj.type [[DIRTY]]
|
|
var t = obj.type; obj.type = "";
|
|
var res = obj.editable?common.$renderInput(obj):common._render_div_block(obj, common);
|
|
obj.type = t;
|
|
return res;
|
|
},
|
|
stringResult:false,
|
|
timepicker:false,
|
|
icon:"calendar",
|
|
icons: true,
|
|
timeIcon: "clock-o"
|
|
},
|
|
_onBlur:function(){
|
|
if (this._settings.text == this.getText() || (webix.isUndefined(this._settings.text) && !this.getText()))
|
|
return;
|
|
|
|
var value = this.getPopup().getValue();
|
|
if (value)
|
|
this.setValue(value);
|
|
},
|
|
$skin:function(){
|
|
this.defaults.inputPadding = webix.skin.$active.inputPadding;
|
|
},
|
|
getPopup: function(){
|
|
return webix.$$(this._settings.popup);
|
|
},
|
|
_init_popup:function(){
|
|
var obj = this._settings;
|
|
if (obj.suggest)
|
|
obj.popup = obj.suggest;
|
|
else if (!obj.popup){
|
|
var timepicker = this._settings.timepicker;
|
|
obj.popup = obj.suggest = this.suggest_setter({
|
|
type:"calendar", height:240+(timepicker?30:0), width:250, padding:0,
|
|
body: { timepicker:timepicker, type: this._settings.type, icons: this._settings.icons }
|
|
});
|
|
}
|
|
|
|
this._init_once = function(){};
|
|
},
|
|
$render:function(obj){
|
|
if (webix.isUndefined(obj.value)) return;
|
|
obj.value = this.$prepareValue(obj.value);
|
|
this.$setValue(obj.value);
|
|
},
|
|
$prepareValue:function(value){
|
|
var type = this._settings.type;
|
|
var timeMode = type == "time";
|
|
|
|
//setValue("1980-12-25")
|
|
if(!isNaN(parseFloat(value)))
|
|
value = ""+value;
|
|
|
|
if (typeof value=="string" && value){
|
|
var formatDate = null;
|
|
if((type == "month" || type == "year") && this._formatDate){
|
|
formatDate = this._formatDate;
|
|
}
|
|
else
|
|
formatDate = (timeMode?webix.i18n.parseTimeFormatDate:webix.i18n.parseFormatDate);
|
|
value = formatDate(value);
|
|
}
|
|
|
|
if (value){
|
|
//time mode
|
|
if(timeMode){
|
|
//setValue([16,24])
|
|
if(webix.isArray(value)){
|
|
var time = new Date();
|
|
time.setHours(value[0]);
|
|
time.setMinutes(value[1]);
|
|
value = time;
|
|
}
|
|
}
|
|
//setValue(invalid date)
|
|
if(isNaN(value.getTime()))
|
|
value = "";
|
|
}
|
|
|
|
return value;
|
|
},
|
|
_get_visible_text:function(value){
|
|
var timeMode = this._settings.type == "time";
|
|
var timepicker = this.config.timepicker;
|
|
var formatStr = this._formatStr||(timeMode?webix.i18n.timeFormatStr:(timepicker?webix.i18n.fullDateFormatStr:webix.i18n.dateFormatStr));
|
|
return formatStr(value);
|
|
},
|
|
_set_visible_text:function(){
|
|
var node = this.getInputNode();
|
|
if(node.value == webix.undefined){
|
|
node.innerHTML = this._settings.text || this._get_div_placeholder();
|
|
}
|
|
else{
|
|
node.value = this._settings.text || "";
|
|
}
|
|
},
|
|
$compareValue:function(oldvalue, value){
|
|
if(!oldvalue && !value) return true;
|
|
return webix.Date.equal(oldvalue, value);
|
|
},
|
|
$setValue:function(value){
|
|
this._settings.text = (value?this._get_visible_text(value):"");
|
|
this._set_visible_text();
|
|
},
|
|
format_setter:function(value){
|
|
if(value){
|
|
if (typeof value === "function")
|
|
this._formatStr = value;
|
|
else {
|
|
this._formatStr = webix.Date.dateToStr(value);
|
|
this._formatDate = webix.Date.strToDate(value);
|
|
}
|
|
}
|
|
else
|
|
this._formatStr = this._formatDate = null;
|
|
return value;
|
|
},
|
|
getInputNode: function(){
|
|
return this._settings.editable?this._dataobj.getElementsByTagName('input')[0]:this._dataobj.getElementsByTagName("DIV")[1];
|
|
},
|
|
getValue:function(){
|
|
var type = this._settings.type;
|
|
//time mode
|
|
var timeMode = (type == "time");
|
|
//date and time mode
|
|
var timepicker = this.config.timepicker;
|
|
|
|
var value = this._settings.value;
|
|
|
|
//input was not rendered, we need to parse value from setValue method
|
|
if (!this._rendered_input)
|
|
value = this.$prepareValue(value) || null;
|
|
//rendere and in edit mode
|
|
else if (this._settings.editable){
|
|
var formatDate = this._formatDate||(timeMode?webix.i18n.timeFormatDate:(timepicker?webix.i18n.fullDateFormatDate:webix.i18n.dateFormatDate));
|
|
value = formatDate(this.getInputNode().value);
|
|
}
|
|
|
|
//return string from getValue
|
|
if(this._settings.stringResult){
|
|
var formatStr =webix.i18n.parseFormatStr;
|
|
if(timeMode)
|
|
formatStr = webix.i18n.parseTimeFormatStr;
|
|
if(this._formatStr && (type == "month" || type == "year")){
|
|
formatStr = this._formatStr;
|
|
}
|
|
|
|
return (value?formatStr(value):"");
|
|
}
|
|
|
|
return value||null;
|
|
},
|
|
getText:function(){
|
|
var node = this.getInputNode();
|
|
return (node?(typeof node.value == "undefined" ? (this.getValue()?node.innerHTML:"") : node.value):"");
|
|
}
|
|
}, webix.ui.text);
|
|
|
|
webix.protoUI({
|
|
name:"colorpicker",
|
|
$init:function(){
|
|
this.$ready.push(this._init_popup);
|
|
},
|
|
defaults:{
|
|
icon:true
|
|
},
|
|
_init_popup:function(){
|
|
var obj = this._settings;
|
|
if (obj.suggest)
|
|
obj.popup = obj.suggest;
|
|
else if (!obj.popup)
|
|
obj.popup = obj.suggest = this.suggest_setter({
|
|
type:"colorboard", height:200
|
|
});
|
|
this._init_once = function(){};
|
|
},
|
|
$render:function(obj){
|
|
if (webix.isUndefined(obj.value)) return;
|
|
obj.value = this.$prepareValue(obj.value);
|
|
this.$setValue(obj.value);
|
|
},
|
|
getValue:function(){
|
|
if (this._rendered_input && this._settings.editable)
|
|
return this.getInputNode().value;
|
|
else
|
|
return this._settings.value;
|
|
},
|
|
$prepareValue:function(value){
|
|
if(value && value.charAt && value.charAt(0) != "#")
|
|
value = '#' + value;
|
|
return value || "";
|
|
},
|
|
_getColorNode: function(){
|
|
return this.$view.getElementsByTagName("DIV")[this._settings.editable?1:2];
|
|
},
|
|
_get_visible_text:function(value){
|
|
return value;
|
|
},
|
|
$setValue:function(value){
|
|
this._getColorNode().style.backgroundColor = value;
|
|
this._settings.text = value;
|
|
|
|
var node = this.getInputNode();
|
|
if(node.value == webix.undefined)
|
|
node.innerHTML = value;
|
|
else
|
|
node.value = value;
|
|
},
|
|
$renderIcon:function(){
|
|
var config = this.config;
|
|
return '<div class="webix_input_icon" style="background-color:'+config.value+';"> </div>';
|
|
}
|
|
}, webix.ui.datepicker);
|
|
|
|
|
|
/*
|
|
Renders collection of items
|
|
Behavior uses plain strategy which suits only for relative small datasets
|
|
|
|
*/
|
|
|
|
|
|
webix.RenderStack={
|
|
$init:function(){
|
|
webix.assert(this.data,"RenderStack :: Component doesn't have DataStore");
|
|
webix.assert(webix.template,"webix.template :: webix.template is not accessible");
|
|
|
|
//used for temporary HTML elements
|
|
//automatically nulified during destruction
|
|
this._html = document.createElement("DIV");
|
|
|
|
this.data.attachEvent("onIdChange", webix.bind(this._render_change_id, this));
|
|
this.attachEvent("onItemClick", this._call_onclick);
|
|
|
|
//create copy of default type, and set it as active one
|
|
if (!this.types){
|
|
this.types = { "default" : this.type };
|
|
this.type.name = "default";
|
|
}
|
|
|
|
this.type = webix.clone(this.type);
|
|
},
|
|
|
|
customize:function(obj){
|
|
webix.type(this,obj);
|
|
},
|
|
item_setter:function(value){
|
|
return this.type_setter(value);
|
|
},
|
|
type_setter:function(value){
|
|
if(!this.types[value])
|
|
this.customize(value);
|
|
else {
|
|
this.type = webix.clone(this.types[value]);
|
|
if (this.type.css)
|
|
this._contentobj.className+=" "+this.type.css;
|
|
}
|
|
if (this.type.on_click)
|
|
webix.extend(this.on_click, this.type.on_click);
|
|
|
|
return value;
|
|
},
|
|
|
|
template_setter:function(value){
|
|
this.type.template=webix.template(value);
|
|
},
|
|
//convert single item to HTML text (templating)
|
|
_toHTML:function(obj){
|
|
var mark = this.data._marks[obj.id];
|
|
//check if related template exist
|
|
webix.assert((!obj.$template || this.type["template"+obj.$template]),"RenderStack :: Unknown template: "+obj.$template);
|
|
this.callEvent("onItemRender",[obj]);
|
|
return this.type.templateStart(obj,this.type, mark)+(obj.$template?this.type["template"+obj.$template]:this.type.template)(obj,this.type,mark)+this.type.templateEnd(obj, this.type,mark);
|
|
},
|
|
//convert item to HTML object (templating)
|
|
_toHTMLObject:function(obj){
|
|
this._html.innerHTML = this._toHTML(obj);
|
|
return this._html.firstChild;
|
|
},
|
|
_render_change_id:function(old, newid){
|
|
var obj = this.getItemNode(old);
|
|
if (obj) {
|
|
obj.setAttribute(this._id, newid);
|
|
this._htmlmap[newid] = this._htmlmap[old];
|
|
delete this._htmlmap[old];
|
|
}
|
|
},
|
|
//calls function that is set in onclick property
|
|
_call_onclick:function(){
|
|
if (this._settings.click){
|
|
var code = webix.toFunctor(this._settings.click, this.$scope);
|
|
if (code && code.call) code.apply(this,arguments);
|
|
}
|
|
},
|
|
//return html container by its ID
|
|
//can return undefined if container doesn't exists
|
|
getItemNode:function(search_id){
|
|
if (this._htmlmap)
|
|
return this._htmlmap[search_id];
|
|
|
|
//fill map if it doesn't created yet
|
|
this._htmlmap={};
|
|
|
|
var t = this._dataobj.childNodes;
|
|
for (var i=0; i < t.length; i++){
|
|
var id = t[i].getAttribute(this._id); //get item's
|
|
if (id)
|
|
this._htmlmap[id]=t[i];
|
|
}
|
|
//call locator again, when map is filled
|
|
return this.getItemNode(search_id);
|
|
},
|
|
//return id of item from html event
|
|
locate:function(e){ return webix.html.locate(e,this._id); },
|
|
/*change scrolling state of top level container, so related item will be in visible part*/
|
|
showItem:function(id){
|
|
|
|
var html = this.getItemNode(id);
|
|
if (html&&this.scrollTo){
|
|
var txmin = Math.abs(this._contentobj.offsetLeft-html.offsetLeft);
|
|
var txmax = txmin + html.offsetWidth;
|
|
var tymin = Math.abs(this._contentobj.offsetTop-html.offsetTop);
|
|
var tymax = tymin + html.offsetHeight;
|
|
var state = this.getScrollState();
|
|
|
|
var x = state.x;
|
|
if (x > txmin || x + this._content_width < txmax )
|
|
x = txmin;
|
|
var y = state.y;
|
|
if (y > tymin || y + this._content_height < tymax )
|
|
y = tymin - 5;
|
|
|
|
this.scrollTo(x,y);
|
|
if(this._setItemActive)
|
|
this._setItemActive(id);
|
|
}
|
|
},
|
|
//update view after data update
|
|
//method calls low-level rendering for related items
|
|
//when called without parameters - all view refreshed
|
|
render:function(id,data,type){
|
|
if (!this.isVisible(this._settings.id) || this.$blockRender)
|
|
return;
|
|
|
|
if (webix.debug_render)
|
|
webix.log("Render: "+this.name+"@"+this._settings.id+", mode:"+(type||"#")+", item:"+(id||"#"));
|
|
|
|
if (id){
|
|
var cont = this.getItemNode(id); //get html element of updated item
|
|
switch(type){
|
|
case "paint":
|
|
case "update":
|
|
//in case of update - replace existing html with updated one
|
|
if (!cont) return;
|
|
var t = this._htmlmap[id] = this._toHTMLObject(data);
|
|
webix.html.insertBefore(t, cont);
|
|
webix.html.remove(cont);
|
|
break;
|
|
case "delete":
|
|
//in case of delete - remove related html
|
|
if (!cont) return;
|
|
webix.html.remove(cont);
|
|
delete this._htmlmap[id];
|
|
break;
|
|
case "add":
|
|
//in case of add - put new html at necessary position
|
|
var t = this._htmlmap[id] = this._toHTMLObject(data);
|
|
webix.html.insertBefore(t, this.getItemNode(this.data.getNextId(id)), this._dataobj);
|
|
break;
|
|
case "move":
|
|
//moving without repainting the item
|
|
webix.html.insertBefore(this.getItemNode(id), this.getItemNode(this.data.getNextId(id)), this._dataobj);
|
|
break;
|
|
default:
|
|
webix.assert_error("Unknown render command: "+type);
|
|
break;
|
|
}
|
|
} else {
|
|
//full reset
|
|
if (this.callEvent("onBeforeRender",[this.data])){
|
|
/*if (this.getScrollState)
|
|
var scroll = this.getScrollState();*/
|
|
|
|
//getRange - returns all elements
|
|
(this._renderobj||this._dataobj).innerHTML = this.data.getRange().map(this._toHTML,this).join("");
|
|
this._htmlmap = null; //clear map, it will be filled at first getItemNode
|
|
this.callEvent("onAfterRender",[]);
|
|
var t = this._dataobj.offsetHeight;
|
|
|
|
/*if (this.getScrollState)
|
|
this.scrollTo(scroll.x, scroll.y);*/
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
webix.ValidateData = {
|
|
$init:function(){
|
|
if(this._events)
|
|
this.attachEvent("onChange",this.clearValidation);
|
|
},
|
|
clearValidation:function(){
|
|
if(this.elements){
|
|
for(var id in this.elements){
|
|
this._clear_invalid(id);
|
|
}
|
|
}
|
|
},
|
|
validate:function(mode, obj) {
|
|
webix.assert(this.callEvent, "using validate for eventless object");
|
|
|
|
this.callEvent("onBeforeValidate", []);
|
|
var failed = this._validate_details = {};
|
|
|
|
//optimistic by default :)
|
|
var result =true;
|
|
var rules = this._settings.rules;
|
|
|
|
var isHidden = this.isVisible && !this.isVisible();
|
|
var validateHidden = mode && mode.hidden;
|
|
var validateDisabled = mode && mode.disabled;
|
|
|
|
//prevent validation of hidden elements
|
|
var elements = {}, hidden = {};
|
|
for(var i in this.elements){
|
|
var name = this.elements[i].config.name;
|
|
//we are ignoring hidden and disabled fields during validation
|
|
//if mode doesn not instruct us otherwise
|
|
//if form itself is hidden, we can't separate hidden fiels,
|
|
//so we will vaidate all fields
|
|
if((isHidden || this.elements[i].isVisible() || validateHidden) && (this.elements[i].isEnabled() || validateDisabled))
|
|
elements[name] = this.elements[i];
|
|
else{
|
|
hidden[name]=true;
|
|
}
|
|
}
|
|
if (rules || elements)
|
|
if(!obj && this.getValues)
|
|
obj = this.getValues();
|
|
|
|
if (rules){
|
|
//complex rule, which may chcek all properties of object
|
|
if (rules.$obj)
|
|
result = this._validate(rules.$obj, obj, obj, "") && result;
|
|
|
|
//all - applied to all fields
|
|
var all = rules.$all;
|
|
var data = obj;
|
|
|
|
if (this._settings.complexData)
|
|
data = webix.CodeParser.collapseNames(obj);
|
|
|
|
if (all)
|
|
for (var key in obj){
|
|
if(hidden[key]) continue;
|
|
var subresult = this._validate(all, data[key], obj, key);
|
|
if (!subresult)
|
|
failed[key] = true;
|
|
result = subresult && result;
|
|
}
|
|
|
|
|
|
//per-field rules
|
|
for (var key in rules){
|
|
if(hidden[key]) continue;
|
|
if (key.indexOf("$")!==0 && !failed[key]){
|
|
webix.assert(rules[key], "Invalid rule for:"+key);
|
|
var subresult = this._validate(rules[key], data[key], obj, key);
|
|
if (!subresult)
|
|
failed[key] = true;
|
|
result = subresult && result;
|
|
}
|
|
}
|
|
}
|
|
|
|
//check personal validation rules
|
|
if (elements){
|
|
for (var key in elements){
|
|
if (failed[key]) continue;
|
|
|
|
var subview = elements[key];
|
|
if (subview.validate){
|
|
var subresult = subview.validate();
|
|
result = subresult && result;
|
|
if (!subresult)
|
|
failed[key] = true;
|
|
} else {
|
|
var input = subview._settings;
|
|
if (input){ //ignore non webix inputs
|
|
var validator = input.validate;
|
|
if (!validator && input.required)
|
|
validator = webix.rules.isNotEmpty;
|
|
|
|
if (validator){
|
|
var subresult = this._validate(validator, obj[key], obj, key);
|
|
if (!subresult)
|
|
failed[key] = true;
|
|
result = subresult && result;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
this.callEvent("onAfterValidation", [result, this._validate_details]);
|
|
return result;
|
|
},
|
|
_validate:function(rule, data, obj, key){
|
|
if (typeof rule == "string")
|
|
rule = webix.rules[rule];
|
|
if (rule.call(this, data, obj, key)){
|
|
if(this.callEvent("onValidationSuccess",[key, obj]) && this._clear_invalid)
|
|
this._clear_invalid(key);
|
|
return true;
|
|
}
|
|
else {
|
|
if(this.callEvent("onValidationError",[key, obj]) && this._mark_invalid)
|
|
this._mark_invalid(key);
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
|
|
webix.ValidateCollection = {
|
|
_validate_init_once:function(){
|
|
this.data.attachEvent("onStoreUpdated",webix.bind(function(id, data, mode){
|
|
if (id && (mode == "add" || mode == "update"))
|
|
this.validate(id);
|
|
}, this));
|
|
this.data.attachEvent("onClearAll",webix.bind(this.clearValidation, this));
|
|
|
|
this._validate_init_once = function(){};
|
|
},
|
|
rules_setter:function(value){
|
|
if (value){
|
|
this._validate_init_once();
|
|
}
|
|
return value;
|
|
},
|
|
clearValidation:function(){
|
|
this.data.clearMark("webix_invalid", true);
|
|
},
|
|
validate:function(id){
|
|
var result = true;
|
|
if (!id)
|
|
for (var key in this.data.pull)
|
|
var result = this.validate(key) && result;
|
|
else {
|
|
this._validate_details = {};
|
|
var obj = this.getItem(id);
|
|
result = webix.ValidateData.validate.call(this, null, obj);
|
|
if (result){
|
|
if (this.callEvent("onValidationSuccess",[id, obj]))
|
|
this._clear_invalid(id);
|
|
} else {
|
|
if (this.callEvent("onValidationError",[id, obj, this._validate_details]))
|
|
this._mark_invalid(id, this._validate_details);
|
|
}
|
|
}
|
|
return result;
|
|
},
|
|
_validate:function(rule, data, obj, key){
|
|
if (typeof rule == "string")
|
|
rule = webix.rules[rule];
|
|
|
|
var res = rule.call(this, data, obj, key);
|
|
if (!res){
|
|
this._validate_details[key] = true;
|
|
}
|
|
return res;
|
|
},
|
|
_clear_invalid:function(id){
|
|
this.data.removeMark(id, "webix_invalid", true);
|
|
},
|
|
_mark_invalid:function(id, details){
|
|
this.data.addMark(id, "webix_invalid", true);
|
|
}
|
|
};
|
|
|
|
|
|
webix.rules = {
|
|
isEmail: function(value){
|
|
return (/\S+@[^@\s]+\.[^@\s]+$/).test((value || "").toString());
|
|
},
|
|
isNumber: function(value){
|
|
return (parseFloat(value) == value);
|
|
},
|
|
isChecked: function(value){
|
|
return (!!value) || value === "0";
|
|
},
|
|
isNotEmpty: function(value){
|
|
return (value === 0 || value);
|
|
}
|
|
};
|
|
/*Data collection mapping logic */
|
|
|
|
webix.MapCollection = {
|
|
$init:function(){
|
|
this.$ready.push(this._create_scheme_init);
|
|
this.attachEvent("onStructureUpdate", this._create_scheme_init);
|
|
this.attachEvent("onStructureLoad", function(){
|
|
if(!this._scheme_init_order.length)
|
|
this._create_scheme_init();
|
|
});
|
|
},
|
|
_create_scheme_init:function(order){
|
|
var order = this._scheme_init_order = [];
|
|
var config = this._settings;
|
|
|
|
if (config.columns)
|
|
this._build_data_map(config.columns);
|
|
if (this._settings.map)
|
|
this._process_field_map(config.map);
|
|
|
|
if (this._scheme_init_order.length){
|
|
try {
|
|
this.data._scheme_init = Function("obj",order.join("\n"));
|
|
} catch(e){
|
|
webix.assert_error("Invalid data map:"+order.join("\n"));
|
|
}
|
|
}
|
|
},
|
|
_process_field_map:function(map){
|
|
for (var key in map)
|
|
this._scheme_init_order.push(this._process_single_map(key, map[key]));
|
|
},
|
|
_process_single_map:function(id, map, extra){
|
|
var start = "";
|
|
var end = "";
|
|
|
|
if (map.indexOf("(date)")===0){
|
|
start = "webix.i18n.parseFormatDate("; end=")";
|
|
if (extra && !extra.format) extra.format = webix.i18n.dateFormatStr;
|
|
map = map.replace("(date)","");
|
|
} else if (map.indexOf("(number)")===0){
|
|
start = "("; end=")*1";
|
|
map = map.replace("(number)","");
|
|
}
|
|
|
|
if (map !== ""){
|
|
map=map.replace(/\{obj\.([^}]*)\}/g,"\"+(obj.$1||'')+\"");
|
|
map=map.replace(/#([^#'";, ]+)#/gi,"\"+(obj.$1||'')+\"");
|
|
} else
|
|
map = "\"+(obj."+id+"||'')+\"";
|
|
|
|
|
|
return "obj."+id+" = "+start+'"'+map+'"'+end+";";
|
|
},
|
|
_build_data_map:function(columns){ //for datatable
|
|
for (var i=0; i<columns.length; i++){
|
|
var map = columns[i].map;
|
|
var id = columns[i].id;
|
|
if (!id) {
|
|
id = columns[i].id = "i"+webix.uid();
|
|
if (!columns[i].header)
|
|
columns[i].header = "";
|
|
}
|
|
if (map)
|
|
this._scheme_init_order.push(this._process_single_map(id, map, columns[i]));
|
|
|
|
this._map_options(columns[i]);
|
|
}
|
|
},
|
|
_map_options:function(element){
|
|
var options = element.options||element.collection;
|
|
if(options){
|
|
if (typeof options === "string"){
|
|
//id of some other view
|
|
var options_view = webix.$$(options);
|
|
//or url
|
|
if (!options_view){
|
|
options_view = new webix.DataCollection({ url: options });
|
|
this._destroy_with_me.push(options_view);
|
|
}
|
|
//if it was a view, special check for suggests
|
|
if (options_view.getBody) options_view = options_view.getBody();
|
|
this._bind_collection(options_view, element);
|
|
} else if (!options.loadNext){
|
|
if (options[0] && typeof options[0] == "object"){
|
|
//[{ id:1, value:"one"}, ...]
|
|
options = new webix.DataCollection({ data:options });
|
|
this._bind_collection(options, element);
|
|
this._destroy_with_me.push(options);
|
|
} else {
|
|
//["one", "two"]
|
|
//or
|
|
//{ 1: "one", 2: "two"}
|
|
if (webix.isArray(options)){
|
|
var data = {};
|
|
for (var ij=0; ij<options.length; ij++) data[options[ij]] = options[ij];
|
|
element.options = options = data;
|
|
}
|
|
element.template = element.template || this._collection_accesser(options, element.id, element.optionslist);
|
|
}
|
|
} else {
|
|
//data collection or view
|
|
this._bind_collection(options, element);
|
|
}
|
|
}
|
|
},
|
|
_bind_collection:function(options, element){
|
|
if (element){
|
|
delete element.options;
|
|
element.collection = options;
|
|
element.template = element.template || this._bind_accesser(options, element.id, element.optionslist);
|
|
var id = options.data.attachEvent("onStoreUpdated", webix.bind(function(){
|
|
this.refresh();
|
|
if(this.refreshFilter)
|
|
this.refreshFilter(element.id);
|
|
}, this));
|
|
this.attachEvent("onDestruct", function(){
|
|
if (!options.$destructed) options.data.detachEvent(id);
|
|
});
|
|
}
|
|
},
|
|
_collection_accesser:function(options, id, multi){
|
|
if (multi){
|
|
var separator = typeof multi=="string"?multi:",";
|
|
return function(obj, common){
|
|
var value = obj[id] || obj.value;
|
|
if (!value) return "";
|
|
var ids = value.split(separator);
|
|
for (var i = 0; i < ids.length; i++)
|
|
ids[i] = options[ids[i]] || "";
|
|
|
|
return ids.join(", ");
|
|
};
|
|
} else {
|
|
return function(obj, common){
|
|
return options[obj[id]]||obj.value||"";
|
|
};
|
|
}
|
|
},
|
|
_bind_accesser:function(col, id, multi){
|
|
if (multi) {
|
|
var separator = typeof multi=="string"?multi:",";
|
|
return function(obj, common){
|
|
var value = obj[id] || obj.value;
|
|
if (!value) return "";
|
|
|
|
var ids = value.split(separator);
|
|
for (var i = 0; i < ids.length; i++){
|
|
var data = col.data.pull[ids[i]];
|
|
ids[i] = data ? (data.value || "") : "";
|
|
}
|
|
|
|
return ids.join(", ");
|
|
};
|
|
} else {
|
|
return function(obj, common){
|
|
var prop = obj[id]||obj.value,
|
|
data = col.data.pull[prop];
|
|
if (data && (data.value || data.value ===0))
|
|
return data.value;
|
|
return "";
|
|
};
|
|
}
|
|
}
|
|
};
|
|
webix.Undo= {
|
|
$init:function(){
|
|
this._undoHistory = webix.extend([],webix.PowerArray,true);
|
|
this._undoCursor = -1;
|
|
},
|
|
undo_setter: function(value){
|
|
if(value){
|
|
this._init_undo();
|
|
this._init_undo = function(){};
|
|
}
|
|
return value;
|
|
},
|
|
_init_undo: function(){
|
|
var view = this;
|
|
|
|
// drag-n-drop
|
|
this.attachEvent("onBeforeDrop", function(context){
|
|
if(context.from == context.to){
|
|
var item = view._draggedItem = webix.copy(this.getItem(context.start));
|
|
if(this.data.branch){
|
|
item.$index = this.getBranchIndex(item.id);
|
|
}
|
|
else
|
|
item.$index = this.getIndexById(item.id);
|
|
}
|
|
});
|
|
this.data.attachEvent("onDataMove", function( sid ){
|
|
if(view._draggedItem && view._draggedItem.id == sid){
|
|
var data = view._draggedItem;
|
|
view._draggedItem = null;
|
|
view._addToHistory(sid, data, "move");
|
|
}
|
|
});
|
|
|
|
// add, remove
|
|
this.data.attachEvent("onBeforeDelete", function(id){
|
|
if(this.getItem(id)){
|
|
var item = view._deletedItem = webix.copy(this.getItem(id));
|
|
if(this.branch){
|
|
item.$index = this.getBranchIndex(id);
|
|
if(this.branch[id])
|
|
item.$branch = webix.copy(this.serialize(id));
|
|
}
|
|
else
|
|
item.$index = this.getIndexById(id);
|
|
}
|
|
});
|
|
this.data.attachEvent("onDataUpdate", function(id, data, old){
|
|
view._addToHistory(id+"", old, "update");
|
|
});
|
|
this.data.attachEvent("onStoreUpdated", function(id, item, mode){
|
|
var data = null;
|
|
if(id){
|
|
if(mode == "add"){
|
|
data = webix.copy(item);
|
|
}
|
|
else if( mode == "delete") {
|
|
data = view._deletedItem;
|
|
}
|
|
|
|
if(data)
|
|
view._addToHistory(id, data, mode);
|
|
}
|
|
});
|
|
|
|
// id change
|
|
this.data.attachEvent("onIdChange", function(oldId,newId){
|
|
if(typeof oldId == "object")
|
|
oldId = oldId.row;
|
|
for(var i =0; i < view._undoHistory.length; i++){
|
|
if(view._undoHistory[i].id == oldId){
|
|
view._undoHistory[i].id = newId;
|
|
}
|
|
}
|
|
});
|
|
},
|
|
_addToHistory: function(id, data, action){
|
|
if(!this._skipHistory && this._settings.undo){
|
|
this._undoHistory.push({id: id, action: action, data: data});
|
|
if(this._undoHistory.length==20)
|
|
this._undoHistory.splice(0,1);
|
|
if(!this._skipCursorInc)
|
|
this._undoCursor = this._undoHistory.length - 1;
|
|
}
|
|
},
|
|
ignoreUndo: function(func, master){
|
|
this._skipHistory = true;
|
|
func.call(master||this);
|
|
this._skipHistory = false;
|
|
},
|
|
removeUndo: function(id){
|
|
for( var i = this._undoHistory.length-1; i >=0; i--){
|
|
if(this._undoHistory[i].id == id){
|
|
if(this._undoHistory[i].action == "id"){
|
|
id = this._undoHistory[i].data;
|
|
}
|
|
this._undoHistory.removeAt(i);
|
|
}
|
|
}
|
|
this._undoCursor = this._undoHistory.length - 1;
|
|
},
|
|
undo: function(id){
|
|
if(id){
|
|
this.ignoreUndo(function(){
|
|
var data, i;
|
|
for( i = this._undoHistory.length-1; !data && i >=0; i--){
|
|
if(this._undoHistory[i].id == id)
|
|
data = this._undoHistory[i];
|
|
}
|
|
|
|
if(data){
|
|
/*if(data.action == "id")
|
|
id = data.data;*/
|
|
this._undoAction(data);
|
|
this._undoHistory.removeAt(i+1);
|
|
this._undoCursor = this._undoHistory.length - 1;
|
|
}
|
|
});
|
|
}
|
|
else{
|
|
var data = this._undoHistory[this._undoCursor];
|
|
if(data){
|
|
this.ignoreUndo(function(){
|
|
this._undoAction(data);
|
|
this._undoHistory.removeAt(this._undoCursor);
|
|
});
|
|
this._undoCursor--;
|
|
/*if(data.action == "id")
|
|
this.undo();*/
|
|
}
|
|
}
|
|
},
|
|
_undoAction: function(obj){
|
|
if(obj.action == "delete"){
|
|
var branch = null,
|
|
parentId = obj.data.$parent;
|
|
|
|
if(obj.data.$branch){
|
|
branch = {
|
|
parent: obj.id,
|
|
data: webix.copy(obj.data.$branch)
|
|
};
|
|
delete obj.data.$branch;
|
|
if(parentId && !this.data.branch[parentId])
|
|
parentId = 0;
|
|
}
|
|
|
|
this.add(obj.data, obj.data.$index, parentId);
|
|
if(branch){
|
|
this.parse(branch);
|
|
}
|
|
}
|
|
else if(obj.action == "add"){
|
|
this.remove(obj.id);
|
|
}
|
|
else if(obj.action == "update"){
|
|
this.updateItem(obj.id, obj.data);
|
|
}
|
|
else if(obj.action == "move"){
|
|
if(obj.data.$parent){
|
|
if(this.getItem(obj.data.$parent))
|
|
this.move(obj.id, obj.data.$index, null, {parent: obj.data.$parent});
|
|
}
|
|
else
|
|
this.move(obj.id, obj.data.$index);
|
|
}
|
|
/*else if(obj.action == "id"){
|
|
this.data.changeId(obj.id, obj.data);
|
|
}*/
|
|
}
|
|
};
|
|
|
|
/*
|
|
Behavior:DataLoader - load data in the component
|
|
|
|
@export
|
|
load
|
|
parse
|
|
*/
|
|
webix.DataLoader=webix.proto({
|
|
$init:function(config){
|
|
//prepare data store
|
|
config = config || "";
|
|
|
|
//list of all active ajax requests
|
|
this._ajax_queue = webix.toArray();
|
|
this._feed_last = {};
|
|
|
|
this.data = new webix.DataStore();
|
|
|
|
this.data.attachEvent("onClearAll",webix.bind(this._call_onclearall,this));
|
|
this.data.attachEvent("onServerConfig", webix.bind(this._call_on_config, this));
|
|
this.attachEvent("onDestruct", this._call_onclearall);
|
|
|
|
this.data.feed = this._feed;
|
|
this.data.owner = config.id;
|
|
},
|
|
_feed:function(from,count,callback){
|
|
//allow only single request at same time
|
|
if (this._load_count)
|
|
return (this._load_count=[from,count,callback]); //save last ignored request
|
|
else
|
|
this._load_count=true;
|
|
this._feed_last.from = from;
|
|
this._feed_last.count = count;
|
|
this._feed_common.call(this, from, count, callback);
|
|
},
|
|
_feed_common:function(from, count, callback, url, details){
|
|
var state = null,
|
|
url = url || this.data.url;
|
|
|
|
var final_callback = [
|
|
{ success: this._feed_callback, error: this._feed_callback },
|
|
callback
|
|
];
|
|
|
|
if (from<0) from = 0;
|
|
|
|
if(!details)
|
|
details = { start: from, count:count };
|
|
|
|
if(this.count())
|
|
details["continue"] = "true";
|
|
|
|
if (this.getState)
|
|
state = this.getState();
|
|
|
|
// proxy
|
|
if (url && typeof url != "string"){
|
|
if (state){
|
|
if (state.sort)
|
|
details.sort = state.sort;
|
|
if (state.filter)
|
|
details.filter = state.filter;
|
|
}
|
|
this.load(url, final_callback, details);
|
|
} else { // GET
|
|
url = url+((url.indexOf("?")==-1)?"?":"&");
|
|
|
|
var params = [];
|
|
for(var d in details){
|
|
params.push(d+"="+details[d]);
|
|
}
|
|
if (state){
|
|
if (state.sort)
|
|
params.push("sort["+state.sort.id+"]="+encodeURIComponent(state.sort.dir));
|
|
if (state.filter)
|
|
for (var key in state.filter){
|
|
var filterValue = state.filter[key];
|
|
if(typeof filterValue == "object")
|
|
filterValue = webix.ajax().stringify(filterValue); //server daterangefilter
|
|
params.push("filter["+key+"]="+encodeURIComponent(filterValue));
|
|
}
|
|
}
|
|
|
|
url += params.join("&");
|
|
if (this._feed_last.url !== url){
|
|
this._feed_last.url = url;
|
|
this.load(url, final_callback);
|
|
} else {
|
|
this._load_count = false;
|
|
}
|
|
}
|
|
},
|
|
_feed_callback: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
|
|
},
|
|
//loads data from external URL
|
|
load:function(url,call){
|
|
var url = webix.proxy.$parse(url);
|
|
var ajax = webix.AtomDataLoader.load.apply(this, arguments);
|
|
|
|
//prepare data feed for dyn. loading
|
|
if (!this.data.url)
|
|
this.data.url = url;
|
|
|
|
return ajax;
|
|
},
|
|
//load next set of data rows
|
|
loadNext:function(count, start, callback, url, now){
|
|
var config = this._settings;
|
|
if (config.datathrottle && !now){
|
|
if (this._throttle_request)
|
|
window.clearTimeout(this._throttle_request);
|
|
this._throttle_request = webix.delay(function(){
|
|
this.loadNext(count, start, callback, url, true);
|
|
},this, 0, config.datathrottle);
|
|
return;
|
|
}
|
|
|
|
if (!start && start !== 0) start = this.count();
|
|
if (!count)
|
|
count = config.datafetch || this.count();
|
|
|
|
this.data.url = this.data.url || url;
|
|
if (this.callEvent("onDataRequest", [start,count,callback,url]) && this.data.url)
|
|
this.data.feed.call(this, start, count, callback);
|
|
},
|
|
_maybe_loading_already:function(count, from){
|
|
var last = this._feed_last;
|
|
if(this._load_count && last.url){
|
|
if (last.from<=from && (last.count+last.from >= count + from )) return true;
|
|
}
|
|
return false;
|
|
},
|
|
removeMissed_setter:function(value){
|
|
return (this.data._removeMissed = value);
|
|
},
|
|
//init of dataprocessor delayed after all settings processing
|
|
//because it need to be the last in the event processing chain
|
|
//to get valid validation state
|
|
_init_dataprocessor:function(){
|
|
var url = this._settings.save;
|
|
|
|
if (url === true)
|
|
url = this._settings.save = this._settings.url;
|
|
|
|
var obj = { master: this };
|
|
|
|
if (url && url.url)
|
|
webix.extend(obj, url);
|
|
else
|
|
obj.url = url;
|
|
|
|
webix.dp(obj);
|
|
},
|
|
save_setter:function(value){
|
|
if (value)
|
|
this.$ready.push(this._init_dataprocessor);
|
|
|
|
return value;
|
|
},
|
|
scheme_setter:function(value){
|
|
this.data.scheme(value);
|
|
},
|
|
dataFeed_setter:function(value){
|
|
value = webix.proxy.$parse(value);
|
|
|
|
this.data.attachEvent("onBeforeFilter", webix.bind(function(text, filtervalue){
|
|
//complex filtering, can't be routed to dataFeed
|
|
if (typeof text == "function") return true;
|
|
|
|
//we have dataFeed and some text
|
|
if (this._settings.dataFeed && (text || filtervalue)){
|
|
text = text || "id";
|
|
if (filtervalue && typeof filtervalue == "object")
|
|
filtervalue = filtervalue.id;
|
|
|
|
this.clearAll();
|
|
var url = this._settings.dataFeed;
|
|
|
|
//js data feed
|
|
if (typeof url == "function"){
|
|
var filter = {};
|
|
filter[text] = filtervalue;
|
|
url.call(this, filtervalue, filter);
|
|
} else if (url.$proxy) {
|
|
if (url.load){
|
|
var filterobj = {}; filterobj[text] = filtervalue;
|
|
url.load(this, {
|
|
success: this._onLoad,
|
|
error: this._onLoadError
|
|
}, { filter: filterobj });
|
|
}
|
|
} else {
|
|
//url data feed
|
|
var urldata = "filter["+text+"]="+encodeURIComponent(filtervalue);
|
|
this.load(url+(url.indexOf("?")<0?"?":"&")+urldata, this._settings.datatype);
|
|
}
|
|
return false;
|
|
}
|
|
},this));
|
|
return value;
|
|
},
|
|
_call_onready:function(){
|
|
if (this._settings.ready && !this._ready_was_used){
|
|
var code = webix.toFunctor(this._settings.ready, this.$scope);
|
|
if (code)
|
|
webix.delay(code, this, arguments);
|
|
if (this.callEvent)
|
|
webix.delay(this.callEvent, this, ["onReady", []]);
|
|
this._ready_was_used = true;
|
|
}
|
|
},
|
|
_call_onclearall:function(soft){
|
|
for (var i = 0; i < this._ajax_queue.length; i++){
|
|
var xhr = this._ajax_queue[i];
|
|
|
|
//IE9 and IE8 deny extending of ActiveX wrappers
|
|
try { xhr.aborted = true; } catch(e){
|
|
webix._xhr_aborted.push(xhr);
|
|
}
|
|
xhr.abort();
|
|
}
|
|
if (!soft){
|
|
this._load_count = false;
|
|
this._feed_last = {};
|
|
this._ajax_queue = webix.toArray();
|
|
this.waitData = webix.promise.defer();
|
|
}
|
|
},
|
|
_call_on_config:function(config){
|
|
this._parseSeetingColl(config);
|
|
}
|
|
},webix.AtomDataLoader);
|
|
|
|
//ie8 compatibility
|
|
webix._xhr_aborted = webix.toArray();
|
|
|
|
webix.DataMarks = {
|
|
addCss:function(id, css, silent){
|
|
if (!this.addRowCss && !silent){
|
|
if (!this.hasCss(id, css)){
|
|
var node = this.getItemNode(id);
|
|
if (node){
|
|
node.className += " "+css;
|
|
silent = true;
|
|
}
|
|
}
|
|
}
|
|
return this.data.addMark(id, css, 1, 1, silent);
|
|
},
|
|
removeCss:function(id, css, silent){
|
|
if (!this.addRowCss && !silent){
|
|
if (this.hasCss(id, css)){
|
|
var node = this.getItemNode(id);
|
|
if (node){
|
|
node.className = node.className.replace(css,"").replace(" "," ");
|
|
silent = true;
|
|
}
|
|
}
|
|
}
|
|
return this.data.removeMark(id, css, 1, silent);
|
|
},
|
|
hasCss:function(id, mark){
|
|
return this.data.getMark(id, mark);
|
|
},
|
|
clearCss:function(css, silent){
|
|
return this.data.clearMark(css, 1, silent);
|
|
}
|
|
};
|
|
|
|
/*
|
|
DataStore is not a behavior, it standalone object, which represents collection of data.
|
|
Call provideAPI to map data API
|
|
|
|
@export
|
|
exists
|
|
getIdByIndex
|
|
getIndexById
|
|
get
|
|
set
|
|
refresh
|
|
count
|
|
sort
|
|
filter
|
|
next
|
|
previous
|
|
clearAll
|
|
first
|
|
last
|
|
*/
|
|
webix.DataStore = function(){
|
|
this.name = "DataStore";
|
|
|
|
webix.extend(this, webix.EventSystem);
|
|
|
|
this.setDriver("json"); //default data source is an
|
|
this.pull = {}; //hash of IDs
|
|
this.order = webix.toArray(); //order of IDs
|
|
this._marks = {};
|
|
};
|
|
|
|
webix.DataStore.prototype={
|
|
//defines type of used data driver
|
|
//data driver is an abstraction other different data formats - xml, json, csv, etc.
|
|
setDriver:function(type){
|
|
webix.assert(webix.DataDriver[type],"incorrect DataDriver");
|
|
this.driver = webix.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);
|
|
|
|
//generated by connectors only
|
|
if (info.key)
|
|
webix.securityKey = info.key;
|
|
|
|
if (info.config)
|
|
this.callEvent("onServerConfig",[info.config]);
|
|
|
|
var options = this.driver.getOptions(data);
|
|
if (options)
|
|
this.callEvent("onServerOptions", [options]);
|
|
|
|
//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._not_grouped_order)
|
|
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] && this.order[this.order.length-1]){ //update mode
|
|
if (this._removeMissed){
|
|
//update mode, create kill list
|
|
marks = {};
|
|
for (var i=0; i<this.order.length; i++)
|
|
marks[this.order[i]]=true;
|
|
}
|
|
|
|
subload = false;
|
|
from = this.order.length;
|
|
}
|
|
|
|
var j=0;
|
|
for (var i=0; i<recs.length; i++){
|
|
//get hash of details for each record
|
|
var temp = this.driver.getDetails(recs[i]);
|
|
var id = this.id(temp); //generate ID for the record
|
|
if (!this.pull[id]){ //if such ID already exists - update instead of insert
|
|
this.order[j+from]=id;
|
|
j++;
|
|
} else if (subload && this.order[j+from])
|
|
j++;
|
|
|
|
if(this.pull[id]){
|
|
webix.extend(this.pull[id],temp,true);//add only new properties
|
|
if (this._scheme_update)
|
|
this._scheme_update(this.pull[id]);
|
|
//update mode, remove item from kill list
|
|
if (marks)
|
|
delete marks[id];
|
|
} else{
|
|
this.pull[id] = temp;
|
|
if (this._scheme_init)
|
|
this._scheme_init(temp);
|
|
}
|
|
|
|
}
|
|
|
|
//update mode, delete items which are not existing in the new xml
|
|
if (marks){
|
|
this.blockEvent();
|
|
for (var delid in marks)
|
|
this.remove(delid);
|
|
this.unblockEvent();
|
|
}
|
|
|
|
if (!this.order[info.size-1])
|
|
this.order[info.size-1] = webix.undefined;
|
|
},
|
|
//generate id for data object
|
|
id:function(data){
|
|
return data.id||(data.id=webix.uid());
|
|
},
|
|
changeId:function(old, newid){
|
|
//webix.assert(this.pull[old],"Can't change id, for non existing item: "+old);
|
|
if(this.pull[old])
|
|
this.pull[newid] = this.pull[old];
|
|
|
|
this.pull[newid].id = newid;
|
|
this.order[this.order.find(old)]=newid;
|
|
if (this._filter_order)
|
|
this._filter_order[this._filter_order.find(old)]=newid;
|
|
if (this._marks[old]){
|
|
this._marks[newid] = this._marks[old];
|
|
delete this._marks[old];
|
|
}
|
|
|
|
|
|
this.callEvent("onIdChange", [old, newid]);
|
|
if (this._render_change_id)
|
|
this._render_change_id(old, newid);
|
|
delete this.pull[old];
|
|
},
|
|
//get data from hash by id
|
|
getItem:function(id){
|
|
return this.pull[id];
|
|
},
|
|
//assigns data by id
|
|
updateItem:function(id, update, mode){
|
|
var data = this.getItem(id);
|
|
var old = null;
|
|
|
|
//check is change tracking active
|
|
var changeTrack = this.hasEvent("onDataUpdate");
|
|
|
|
webix.assert(data, "Ivalid ID for updateItem");
|
|
webix.assert(!update || !update.id || update.id == id, "Attempt to change ID in updateItem");
|
|
if (!webix.isUndefined(update) && data !== update){
|
|
//preserve original object
|
|
if (changeTrack)
|
|
old = webix.copy(data);
|
|
|
|
id = data.id; //preserve id
|
|
webix.extend(data, update, true);
|
|
data.id = id;
|
|
}
|
|
|
|
if (this._scheme_update)
|
|
this._scheme_update(data);
|
|
|
|
this.callEvent("onStoreUpdated",[id.toString(), data, (mode||"update")]);
|
|
|
|
if (changeTrack)
|
|
this.callEvent("onDataUpdate", [id, data, old]);
|
|
},
|
|
//sends repainting signal
|
|
refresh:function(id){
|
|
if (this._skip_refresh) return;
|
|
|
|
if (id){
|
|
if (this.exists(id))
|
|
this.callEvent("onStoreUpdated",[id, this.pull[id], "paint"]);
|
|
}else
|
|
this.callEvent("onStoreUpdated",[null,null,null]);
|
|
},
|
|
silent:function(code, master){
|
|
this._skip_refresh = true;
|
|
code.call(master||this);
|
|
this._skip_refresh = false;
|
|
},
|
|
//converts range IDs to array of all IDs between them
|
|
getRange:function(from,to){
|
|
//if some point is not defined - use first or last id
|
|
//BEWARE - do not use empty or null ID
|
|
if (from)
|
|
from = this.getIndexById(from);
|
|
else
|
|
from = (this.$min||this.startOffset)||0;
|
|
if (to)
|
|
to = this.getIndexById(to);
|
|
else {
|
|
to = this.$max === 0 ? 0 : Math.min((this.$max?this.$max-1:(this.endOffset||Infinity)),(this.count()-1));
|
|
if (to<0) to = 0; //we have not data in the store
|
|
}
|
|
|
|
if (from>to){ //can be in case of backward shift-selection
|
|
var a=to; to=from; from=a;
|
|
}
|
|
|
|
return this.getIndexRange(from,to);
|
|
},
|
|
//converts range of indexes to array of all IDs between them
|
|
getIndexRange:function(from,to){
|
|
to=Math.min((to === 0 ? 0 :(to||Infinity)),this.count()-1);
|
|
|
|
var ret=webix.toArray(); //result of method is rich-array
|
|
for (var i=(from||0); i <= to; i++)
|
|
ret.push(this.getItem(this.order[i]));
|
|
return ret;
|
|
},
|
|
//returns total count of elements
|
|
count: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){
|
|
webix.assert(sindex>=0 && tindex>=0, "DataStore::move","Incorrect indexes");
|
|
if (sindex == tindex) return;
|
|
|
|
var id = this.getIdByIndex(sindex);
|
|
var obj = this.getItem(id);
|
|
|
|
if (this._filter_order)
|
|
this._move_inner(this._filter_order, 0, 0, this.getIdByIndex(sindex), this.getIdByIndex(tindex));
|
|
|
|
this._move_inner(this.order, sindex, tindex);
|
|
|
|
|
|
//repaint signal
|
|
this.callEvent("onStoreUpdated",[id,obj,"move"]);
|
|
},
|
|
_move_inner:function(col, sindex, tindex, sid, tid){
|
|
if (sid||tid){
|
|
sindex = tindex = -1;
|
|
for (var i=0; i<col.length; i++){
|
|
if (col[i] == sid && sindex<0)
|
|
sindex = i;
|
|
if (col[i] == tid && tindex<0)
|
|
tindex = i;
|
|
}
|
|
}
|
|
var id = col[sindex];
|
|
col.removeAt(sindex); //remove at old position
|
|
col.insertAt(id,Math.min(col.length, tindex)); //insert at new position
|
|
},
|
|
scheme:function(config){
|
|
this._scheme = {};
|
|
this._scheme_save = config.$save;
|
|
this._scheme_init = config.$init||config.$change;
|
|
this._scheme_update = config.$update||config.$change;
|
|
this._scheme_serialize = config.$serialize;
|
|
this._scheme_group = config.$group;
|
|
this._scheme_sort = config.$sort;
|
|
|
|
//ignore $-starting properties, as they have special meaning
|
|
for (var key in config)
|
|
if (key.substr(0,1) != "$")
|
|
this._scheme[key] = config[key];
|
|
},
|
|
importData:function(target, silent){
|
|
var data = target ? (target.data || target) : [];
|
|
this._filter_order = null;
|
|
|
|
if (typeof data.serialize == "function"){
|
|
this.order = webix.toArray([].concat(data.order));
|
|
|
|
//make full copy, to preserve object properties
|
|
//[WE-CAN-DO-BETTER]
|
|
if (this._make_full_copy){
|
|
this._make_full_copy = false;
|
|
var oldpull = this.pull;
|
|
this.pull = {};
|
|
for (var key in data.pull){
|
|
var old = oldpull[key];
|
|
this.pull[key] = webix.copy(data.pull[key]);
|
|
if (old && old.open) this.pull[key].open = true;
|
|
}
|
|
}
|
|
else
|
|
this.pull = data.pull;
|
|
|
|
if (data.branch && this.branch){
|
|
this.branch = webix.copy(data.branch);
|
|
this._filter_branch = null;
|
|
}
|
|
|
|
} else {
|
|
this.order = webix.toArray();
|
|
this.pull = {};
|
|
var id, obj;
|
|
|
|
if (webix.isArray(target))
|
|
for (var key=0; key<target.length; key++){
|
|
obj = id = target[key];
|
|
if (typeof obj == "object")
|
|
obj.id = obj.id || webix.uid();
|
|
else
|
|
obj = { id:id, value:id };
|
|
|
|
this.order.push(obj.id);
|
|
if (this._scheme_init)
|
|
this._scheme_init(obj);
|
|
this.pull[obj.id] = obj;
|
|
}
|
|
else
|
|
for (var key in data){
|
|
this.order.push(key);
|
|
this.pull[key] = { id:key, value: data[key] };
|
|
}
|
|
}
|
|
if (this._extraParser && !data.branch){
|
|
this.branch = { 0:[]};
|
|
if (!this._datadriver_child)
|
|
this._set_child_scheme("data");
|
|
|
|
for (var i = 0; i<this.order.length; i++){
|
|
var key = this.order[i];
|
|
this._extraParser(this.pull[key], 0, 0, false);
|
|
}
|
|
}
|
|
|
|
this.callEvent("onStoreLoad",[]);
|
|
if (!silent)
|
|
this.callEvent("onStoreUpdated",[]);
|
|
},
|
|
sync:function(source, filter, silent){
|
|
this.unsync();
|
|
|
|
var type = typeof source;
|
|
if (type == "string")
|
|
source = webix.$$("source");
|
|
|
|
if (type != "function" && type != "object"){
|
|
silent = filter;
|
|
filter = null;
|
|
}
|
|
|
|
if (webix.debug_bind){
|
|
this.debug_sync_master = source;
|
|
webix.log("[sync] "+this.debug_bind_master.name+"@"+this.debug_bind_master._settings.id+" <= "+this.debug_sync_master.name+"@"+this.debug_sync_master._settings.id);
|
|
}
|
|
|
|
if (source.name != "DataStore"){
|
|
if (source.data && (source.data.name === "DataStore" || source.data.name === "TreeStore"))
|
|
source = source.data;
|
|
else {
|
|
this._sync_source = source;
|
|
return webix.callEvent("onSyncUnknown", [this, source, filter]);
|
|
}
|
|
}
|
|
|
|
var sync_logic = webix.bind(function(mode, record, data){
|
|
if (this._skip_next_sync) return;
|
|
|
|
//sync of tree-structure with after-filtering
|
|
//we need to make a full copy, to preserve $count
|
|
//[WE-CAN-DO-BETTER]
|
|
if (filter && this.branch) this._make_full_copy = true;
|
|
this.importData(source, true);
|
|
|
|
if (filter)
|
|
this.silent(filter);
|
|
if (this._on_sync)
|
|
this._on_sync();
|
|
if (this._make_full_copy){
|
|
|
|
}
|
|
|
|
if (webix.debug_bind)
|
|
webix.log("[sync:request] "+this.debug_sync_master.name+"@"+this.debug_sync_master._settings.id + " <= "+this.debug_bind_master.name+"@"+this.debug_bind_master._settings.id);
|
|
|
|
this.callEvent("onSyncApply",[]);
|
|
|
|
if (!silent)
|
|
this.refresh();
|
|
else
|
|
silent = false;
|
|
}, this);
|
|
|
|
|
|
|
|
this._sync_events = [
|
|
source.attachEvent("onStoreUpdated", sync_logic),
|
|
source.attachEvent("onIdChange", webix.bind(function(old, nid){ this.changeId(old, nid); this.refresh(nid); }, this))
|
|
];
|
|
this._sync_source = source;
|
|
|
|
//backward data saving
|
|
this._back_sync_handler = this.attachEvent("onStoreUpdated", function(id, data, mode){
|
|
if (mode == "update" || mode == "save"){
|
|
this._skip_next_sync = 1;
|
|
source.updateItem(id, data);
|
|
this._skip_next_sync = 0;
|
|
}
|
|
});
|
|
|
|
sync_logic();
|
|
},
|
|
unsync:function(){
|
|
if (this._sync_source){
|
|
var source = this._sync_source;
|
|
|
|
if (source.name != "DataStore" &&
|
|
(!source.data || source.data.name != "DataStore")){
|
|
//data sync with external component
|
|
webix.callEvent("onUnSyncUnknown", [this, source]);
|
|
} else {
|
|
//data sync with webix component
|
|
for (var i = 0; i < this._sync_events.length; i++)
|
|
source.detachEvent(this._sync_events[i]);
|
|
this.detachEvent(this._back_sync_handler);
|
|
}
|
|
|
|
this._sync_source = null;
|
|
}
|
|
},
|
|
destructor:function(){
|
|
this.unsync();
|
|
|
|
this.pull = this.order = this._marks = null;
|
|
this._evs_events = this._evs_handlers = {};
|
|
},
|
|
//adds item to the store
|
|
add:function(obj,index){
|
|
//default values
|
|
if (this._scheme)
|
|
for (var key in this._scheme)
|
|
if (webix.isUndefined(obj[key]))
|
|
obj[key] = this._scheme[key];
|
|
|
|
if (this._scheme_init)
|
|
this._scheme_init(obj);
|
|
|
|
//generate id for the item
|
|
var id = this.id(obj);
|
|
|
|
//in case of treetable order is sent as 3rd parameter
|
|
var order = arguments[2]||this.order;
|
|
|
|
//by default item is added to the end of the list
|
|
var data_size = order.length;
|
|
|
|
if (webix.isUndefined(index) || index < 0)
|
|
index = data_size;
|
|
//check to prevent too big indexes
|
|
if (index > data_size){
|
|
webix.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;
|
|
|
|
webix.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 of original dataset by default
|
|
var original_index = this._filter_order.length;
|
|
//if some data exists, put at the same position in original and filtered lists
|
|
if (this.order.length)
|
|
original_index = Math.min((index || 0), original_index);
|
|
|
|
this._filter_order.insertAt(id,original_index);
|
|
}
|
|
|
|
//repaint signal
|
|
this.callEvent("onStoreUpdated",[id,obj,"add"]);
|
|
this.callEvent("onAfterAdd",[id,index]);
|
|
|
|
return obj.id;
|
|
},
|
|
|
|
//removes element from datastore
|
|
remove:function(id){
|
|
//id can be an array of IDs - result of getSelect, for example
|
|
if (webix.isArray(id)){
|
|
for (var i=0; i < id.length; i++)
|
|
this.remove(id[i]);
|
|
return;
|
|
}
|
|
if (this.callEvent("onBeforeDelete",[id]) === false) return false;
|
|
|
|
webix.assert(this.exists(id), "Not existing ID in remove command"+id);
|
|
|
|
var obj = this.getItem(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];
|
|
|
|
//repaint signal
|
|
this.callEvent("onStoreUpdated",[id,obj,"delete"]);
|
|
this.callEvent("onAfterDelete",[id]);
|
|
},
|
|
//deletes all records in datastore
|
|
clearAll:function(soft){
|
|
//instead of deleting one by one - just reset inner collections
|
|
this.pull = {};
|
|
this._marks = {};
|
|
this.order = webix.toArray();
|
|
//this.feed = null;
|
|
this._filter_order = null;
|
|
if (!soft)
|
|
this.url = null;
|
|
this.callEvent("onClearAll",[soft]);
|
|
this.refresh();
|
|
},
|
|
//converts id to index
|
|
getIdByIndex:function(index){
|
|
webix.assert(index >= 0,"DataStore::getIdByIndex Incorrect index");
|
|
return this.order[index];
|
|
},
|
|
//converts index to id
|
|
getIndexById:function(id){
|
|
var res = this.order.find(id); //slower than getIdByIndex
|
|
if (!this.pull[id])
|
|
return -1;
|
|
|
|
return res;
|
|
},
|
|
//returns ID of next element
|
|
getNextId:function(id,step){
|
|
return this.order[this.getIndexById(id)+(step||1)];
|
|
},
|
|
//returns ID of first element
|
|
getFirstId:function(){
|
|
return this.order[0];
|
|
},
|
|
//returns ID of last element
|
|
getLastId:function(){
|
|
return this.order[this.order.length-1];
|
|
},
|
|
//returns ID of previous element
|
|
getPrevId:function(id,step){
|
|
return this.order[this.getIndexById(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, sort];
|
|
if (!this.callEvent("onBeforeSort",parameters)) return;
|
|
|
|
this.order = this._sort_core(sort, this.order);
|
|
if (this._filter_order && this._filter_order.length != this.order.length)
|
|
this._filter_order = this._sort_core(sort, this._filter_order);
|
|
|
|
//repaint self
|
|
this.refresh();
|
|
|
|
this.callEvent("onAfterSort",parameters);
|
|
},
|
|
_sort_core:function(sort, order){
|
|
var sorter = this.sorting.create(sort);
|
|
if (this.order.length){
|
|
var pre = order.splice(0, this.$freeze);
|
|
//get array of IDs
|
|
var neworder = webix.toArray();
|
|
for (var i=order.length-1; i>=0; i--)
|
|
neworder[i] = this.pull[order[i]];
|
|
|
|
neworder.sort(sorter);
|
|
return webix.toArray(pre.concat(neworder.map(function(obj){
|
|
webix.assert(obj, "Client sorting can't be used with dynamic loading");
|
|
return this.id(obj);
|
|
},this)));
|
|
}
|
|
return order;
|
|
},
|
|
/*
|
|
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 = webix.toArray();
|
|
var freeze = this.$freeze || 0;
|
|
|
|
for (var i=0; i < this.order.length; i++){
|
|
var id = this.order[i];
|
|
if (i < freeze || filter(this.getItem(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;
|
|
},
|
|
find:function(config, first){
|
|
var result = [];
|
|
|
|
for(var i in this.pull){
|
|
var data = this.pull[i];
|
|
|
|
var match = true;
|
|
if (typeof config == "object"){
|
|
for (var key in config)
|
|
if (data[key] != config[key]){
|
|
match = false;
|
|
break;
|
|
}
|
|
} else if (!config(data))
|
|
match = false;
|
|
|
|
if (match)
|
|
result.push(data);
|
|
|
|
if (first && result.length)
|
|
return result[0];
|
|
}
|
|
|
|
return result;
|
|
},
|
|
filter:function(text,value,preserve){
|
|
//unfilter call but we already in not-filtered state
|
|
if (!text && !this._filter_order && !this._filter_branch) return;
|
|
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
|
|
webix.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
|
|
*/
|
|
_obj_array:function(){
|
|
var data = [];
|
|
for (var i = this.order.length - 1; i >= 0; i--)
|
|
data[i]=this.pull[this.order[i]];
|
|
|
|
return data;
|
|
},
|
|
each:function(method, master, all){
|
|
var order = this.order;
|
|
if (all)
|
|
order = this._filter_order || order;
|
|
|
|
for (var i=0; i<order.length; i++)
|
|
method.call((master||this), this.getItem(order[i]), i);
|
|
},
|
|
_methodPush:function(object,method){
|
|
return function(){ return object[method].apply(object,arguments); };
|
|
},
|
|
/*
|
|
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,
|
|
ondataupdate: target/*,
|
|
onafterfilter: target,
|
|
onbeforefilter: target*/
|
|
});
|
|
}
|
|
|
|
var list = ["sort","add","remove","exists","getIdByIndex","getIndexById","getItem","updateItem","refresh","count","filter","find","getNextId","getPrevId","clearAll","getFirstId","getLastId","serialize","sync"];
|
|
for (var i=0; i < list.length; i++)
|
|
target[list[i]] = this._methodPush(this,list[i]);
|
|
},
|
|
addMark:function(id, mark, css, value, silent){
|
|
var obj = this._marks[id]||{};
|
|
this._marks[id] = obj;
|
|
if (!obj[mark]){
|
|
obj[mark] = value||true;
|
|
if (css){
|
|
var old_css = obj.$css||"";
|
|
obj.$css = old_css+" "+mark;
|
|
}
|
|
if (!silent)
|
|
this.refresh(id);
|
|
}
|
|
return obj[mark];
|
|
},
|
|
removeMark:function(id, mark, css, silent){
|
|
var obj = this._marks[id];
|
|
if (obj){
|
|
if (obj[mark])
|
|
delete obj[mark];
|
|
if (css){
|
|
var current_css = obj.$css;
|
|
if (current_css){
|
|
obj.$css = current_css.replace(mark, "").replace(" "," ");
|
|
}
|
|
}
|
|
if (!silent)
|
|
this.refresh(id);
|
|
}
|
|
},
|
|
getMark:function(id, mark){
|
|
var obj = this._marks[id];
|
|
return (obj?obj[mark]:false);
|
|
},
|
|
clearMark:function(name, css, silent){
|
|
for (var id in this._marks){
|
|
var obj = this._marks[id];
|
|
if (obj[name]){
|
|
delete obj[name];
|
|
if (css && obj.$css)
|
|
obj.$css = obj.$css.replace(name, "").replace(" "," ");
|
|
if (!silent)
|
|
this.refresh(id);
|
|
}
|
|
}
|
|
},
|
|
/*
|
|
serializes data to a json object
|
|
*/
|
|
serialize: function(all){
|
|
var ids = this.order;
|
|
if (all && this._filter_order)
|
|
ids = this._filter_order;
|
|
|
|
var result = [];
|
|
for(var i=0; i< ids.length;i++) {
|
|
var el = this.pull[ids[i]];
|
|
if (this._scheme_serialize){
|
|
el = this._scheme_serialize(el);
|
|
if (el===false) continue;
|
|
}
|
|
result.push(el);
|
|
}
|
|
return result;
|
|
},
|
|
sorting:{
|
|
create:function(config){
|
|
return this._dir(config.dir, this._by(config.by, config.as));
|
|
},
|
|
as:{
|
|
//handled by dataFeed
|
|
"server":function(){
|
|
return false;
|
|
},
|
|
"date":function(a,b){
|
|
a=a-0; b=b-0;
|
|
return a>b?1:(a<b?-1:0);
|
|
},
|
|
"int":function(a,b){
|
|
a = a*1; b=b*1;
|
|
return a>b?1:(a<b?-1:0);
|
|
},
|
|
"string_strict":function(a,b){
|
|
a = a.toString(); b=b.toString();
|
|
return a>b?1:(a<b?-1:0);
|
|
},
|
|
"string":function(a,b){
|
|
if (!b) return 1;
|
|
if (!a) return -1;
|
|
|
|
a = a.toString().toLowerCase(); b=b.toString().toLowerCase();
|
|
return a>b?1:(a<b?-1:0);
|
|
},
|
|
"raw":function(a,b){
|
|
return a>b?1:(a<b?-1:0);
|
|
}
|
|
},
|
|
_by:function(prop, method){
|
|
if (!prop)
|
|
return method;
|
|
if (typeof method != "function")
|
|
method = this.as[method||"string"];
|
|
|
|
webix.assert(method, "Invalid sorting method");
|
|
return function(a,b){
|
|
return method(a[prop],b[prop]);
|
|
};
|
|
},
|
|
_dir:function(prop, method){
|
|
if (prop == "asc" || !prop)
|
|
return method;
|
|
return function(a,b){
|
|
return method(a,b)*-1;
|
|
};
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
webix.DataCollection = webix.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:webix.uid();
|
|
this._settings.id =id;
|
|
webix.ui.views[id] = this;
|
|
this.data.attachEvent("onStoreLoad", webix.bind(function(){
|
|
this.callEvent("onBindRequest",[]);
|
|
}, this));
|
|
},
|
|
refresh:function(){ this.callEvent("onBindRequest",[]); }
|
|
}, webix.DataMove, webix.CollectionBind, webix.BindSource, webix.ValidateCollection, webix.DataLoader, webix.MapCollection, webix.EventSystem, webix.BaseBind, webix.Destruction, webix.Settings);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
webix.Scrollable = {
|
|
$init:function(config){
|
|
//do not spam unwanted scroll containers for templates
|
|
if (config && !config.scroll && this._one_time_scroll)
|
|
return (this._dataobj = (this._dataobj||this._contentobj));
|
|
|
|
(this._dataobj||this._contentobj).appendChild(webix.html.create("DIV",{ "class" : "webix_scroll_cont" },""));
|
|
this._dataobj=(this._dataobj||this._contentobj).firstChild;
|
|
|
|
if(!webix.env.touch)
|
|
webix._event(this._viewobj,"scroll", webix.bind(function(e){
|
|
if(this.callEvent)
|
|
webix.delay(function(){
|
|
this.callEvent("onAfterScroll", []);
|
|
}, this);
|
|
},this));
|
|
},
|
|
/*defaults:{
|
|
scroll:true
|
|
},*/
|
|
scroll_setter:function(value){
|
|
if (!value) return false;
|
|
var marker = (value=="x"?"x":(value=="xy"?"xy":(value=="a"?"xy":"y")));
|
|
if (webix.Touch && webix.Touch.$active){
|
|
this._dataobj.setAttribute("touch_scroll",marker);
|
|
if (this.attachEvent)
|
|
this.attachEvent("onAfterRender", webix.bind(this._refresh_scroll,this));
|
|
this._touch_scroll = true;
|
|
} else {
|
|
if (webix.env.$customScroll){
|
|
webix.CustomScroll.enable(this, marker);
|
|
} else {
|
|
var node = this._dataobj.parentNode.style;
|
|
if (value.toString().indexOf("a")!=-1){
|
|
node.overflowX = node.overflowY = "auto";
|
|
} else {
|
|
if (marker.indexOf("x")!=-1){
|
|
this._scroll_x = true;
|
|
node.overflowX = "scroll";
|
|
}
|
|
if (marker.indexOf("y")!=-1){
|
|
this._scroll_y = true;
|
|
node.overflowY = "scroll";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return marker;
|
|
},
|
|
_onoff_scroll:function(mode){
|
|
if (!!this._settings.scroll == !!mode) return;
|
|
|
|
if (!webix.env.$customScroll){
|
|
var style = this._dataobj.parentNode.style;
|
|
style.overflowX = style.overflowY = mode?"auto":"hidden";
|
|
}
|
|
|
|
this._scroll_x = this._scroll_y = !!mode;
|
|
this._settings.scroll = !!mode;
|
|
},
|
|
getScrollState:function(){
|
|
if (webix.Touch && webix.Touch.$active){
|
|
var temp = webix.Touch._get_matrix(this._dataobj);
|
|
return { x : -temp.e, y : -temp.f };
|
|
} else
|
|
return { x : this._dataobj.parentNode.scrollLeft, y : this._dataobj.parentNode.scrollTop };
|
|
},
|
|
scrollTo:function(x,y){
|
|
if (webix.Touch && webix.Touch.$active){
|
|
y = Math.max(0, Math.min(y, this._dataobj.offsetHeight - this._content_height));
|
|
x = Math.max(0, Math.min(x, this._dataobj.offsetWidth - this._content_width));
|
|
webix.Touch._set_matrix(this._dataobj, -x, -y, this._settings.scrollSpeed||"100ms");
|
|
} else {
|
|
this._dataobj.parentNode.scrollLeft=x;
|
|
this._dataobj.parentNode.scrollTop=y;
|
|
}
|
|
},
|
|
_refresh_scroll:function(){
|
|
if (this._settings.scroll.toString().indexOf("x")!=-1){
|
|
var x = this._dataobj.scrollWidth;
|
|
if (x){ //in hidden state we will have a Zero scrollWidth
|
|
this._dataobj.style.width = "100%";
|
|
this._dataobj.style.width = this._dataobj.scrollWidth + "px";
|
|
}
|
|
}
|
|
|
|
if(webix.Touch && webix.Touch.$active && this._touch_scroll){
|
|
webix.Touch._clear_artefacts();
|
|
webix.Touch._scroll_end();
|
|
var s = this.getScrollState();
|
|
var dx = this._dataobj.offsetWidth - this.$width - s.x;
|
|
var dy = this._dataobj.offsetHeight - this.$height - s.y;
|
|
|
|
//if current scroll is outside of data area
|
|
if(dx<0 || dy < 0){
|
|
//scroll to the end of data area
|
|
var x = (dx<0?Math.min(-dx - s.x,0):- s.x);
|
|
var y = (dy<0?Math.min(-dy - s.y,0):- s.y);
|
|
webix.Touch._set_matrix(this._dataobj, x, y, 0);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/*
|
|
UI:paging control
|
|
*/
|
|
|
|
|
|
|
|
webix.protoUI({
|
|
defaults:{
|
|
size:10, //items on page
|
|
page: 0, //current page
|
|
group:5,
|
|
template:"{common.pages()}",
|
|
maxWidth:100000,
|
|
height:30,
|
|
borderless:true
|
|
},
|
|
name:"pager",
|
|
on_click:{
|
|
//on paging button click
|
|
"webix_pager_item":function(e,id){
|
|
this.select(id);
|
|
}
|
|
},
|
|
$init:function(config){
|
|
this.data = this._settings;
|
|
this._dataobj = this._viewobj;
|
|
this._viewobj.className += " webix_pager";
|
|
|
|
if(config.master===false||config.master === 0)
|
|
this.$ready.push(this._remove_master);
|
|
},
|
|
_remove_master:function(){
|
|
this.refresh();
|
|
this.$master = { refresh:function(){}, select:function(){} };
|
|
},
|
|
select:function(id){
|
|
if (this.$master && this.$master.name == "pager")
|
|
return this.$master.select(id);
|
|
|
|
//id - id of button, number for page buttons
|
|
switch(id){
|
|
case "next":
|
|
id = this._settings.page+1;
|
|
break;
|
|
case "prev":
|
|
id = this._settings.page-1;
|
|
break;
|
|
case "first":
|
|
id = 0;
|
|
break;
|
|
case "last":
|
|
id = this._settings.limit-1;
|
|
break;
|
|
default:
|
|
//use incoming id
|
|
break;
|
|
}
|
|
if (id<0) id=0;
|
|
if (id>=this.data.limit) id=this.data.limit-1;
|
|
|
|
var old = this.data.page;
|
|
if (this.callEvent("onBeforePageChange",[id, old])){
|
|
this.data.page = id*1; //must be int
|
|
if (this.refresh()){
|
|
if (!this._settings.animate || !this._animate(old, id*1, this._settings.animate))
|
|
this.$master.refresh();
|
|
}
|
|
this.callEvent("onAfterPageChange",[id]);
|
|
}
|
|
},
|
|
_id:"webix_p_id",
|
|
template_setter:webix.template,
|
|
type:{
|
|
template:function(a,b){ return a.template.call(this, a,b); },
|
|
//list of page numbers
|
|
pages:function(obj){
|
|
var html="";
|
|
//skip rendering if paging is not fully initialized
|
|
if (obj.page == -1) return "";
|
|
//current page taken as center of view, calculate bounds of group
|
|
obj.$min = obj.page-Math.round((obj.group-1)/2);
|
|
obj.$max = obj.$min + obj.group*1 - 1;
|
|
if (obj.$min<0){
|
|
obj.$max+=obj.$min*(-1);
|
|
obj.$min=0;
|
|
}
|
|
if (obj.$max>=obj.limit){
|
|
obj.$min -= Math.min(obj.$min,obj.$max-obj.limit+1);
|
|
obj.$max = obj.limit-1;
|
|
}
|
|
//generate HTML code of buttons
|
|
for (var i=(obj.$min||0); i<=obj.$max; i++)
|
|
html+=this.button({id:i, index:(i+1), selected:(i == obj.page ?"_selected":""), label:webix.i18n.aria.page+" "+(i+1)});
|
|
return html;
|
|
},
|
|
page:function(obj){
|
|
return obj.page+1;
|
|
},
|
|
//go-to-first page button
|
|
first:function(){
|
|
return this.button({ id:"first", index:webix.locale.pager.first, selected:"", label:webix.i18n.aria.pages[0]});
|
|
},
|
|
//go-to-last page button
|
|
last:function(){
|
|
return this.button({ id:"last", index:webix.locale.pager.last, selected:"", label:webix.i18n.aria.pages[3]});
|
|
},
|
|
//go-to-prev page button
|
|
prev:function(){
|
|
return this.button({ id:"prev", index:webix.locale.pager.prev, selected:"", label:webix.i18n.aria.pages[1]});
|
|
},
|
|
//go-to-next page button
|
|
next:function(){
|
|
return this.button({ id:"next", index:webix.locale.pager.next, selected:"", label:webix.i18n.aria.pages[2]});
|
|
},
|
|
button:webix.template("<button type='button' webix_p_id='{obj.id}' class='webix_pager_item{obj.selected}' aria-label='{obj.label}'>{obj.index}</button>")
|
|
},
|
|
clone:function(pager){
|
|
if (!pager.$view){
|
|
pager.view = "pager";
|
|
pager = webix.ui(pager);
|
|
}
|
|
|
|
this._clone = pager;
|
|
pager.$master = this;
|
|
this._refresh_clone();
|
|
},
|
|
refresh:function(){
|
|
var s = this._settings;
|
|
if (!s.count) return;
|
|
|
|
//max page number
|
|
s.limit = Math.ceil(s.count/s.size);
|
|
|
|
var newPage = Math.min(s.limit-1, s.page);
|
|
|
|
if (newPage != s.page)
|
|
return this.$master.setPage(newPage);
|
|
|
|
s.page = newPage;
|
|
if (newPage>=0 && (newPage!=s.old_page) || (s.limit != s.old_limit) || (s.old_count != s.count)){
|
|
//refresh self only if current page or total limit was changed
|
|
this.render();
|
|
this._refresh_clone();
|
|
s.old_limit = s.limit; //save for onchange check in next iteration
|
|
s.old_page = s.page;
|
|
s.old_count = s.count;
|
|
return true;
|
|
}
|
|
},
|
|
apiOnly_setter:function(value){
|
|
return (this.$apiOnly=value);
|
|
},
|
|
_refresh_clone:function(){
|
|
if (this._clone){
|
|
this._clone._settings.count = this._settings.count;
|
|
this._clone._settings.page = this._settings.page;
|
|
this._clone.refresh();
|
|
}
|
|
},
|
|
_animate:function(old, id, config){
|
|
if (old == id) return false;
|
|
if (this._pgInAnimation){
|
|
if(this._pgAnimateTimeout){
|
|
window.clearTimeout(this._pgAnimateTimeout);
|
|
}
|
|
return (this._pgAnimateTimeout = webix.delay(this._animate, this,[old, id, config],100));
|
|
}
|
|
var direction = id > old ? "left" : "right";
|
|
if (config.direction == "top" || config.direction == "bottom")
|
|
direction = id > old ? "top" : "bottom";
|
|
if (config.flip)
|
|
direction = "";
|
|
|
|
//make copy of existing view
|
|
var top = 0;
|
|
var snode = this.$master._dataobj;
|
|
var isDataTable = !!this.$master._body;
|
|
|
|
if (isDataTable){
|
|
snode = this.$master._body;
|
|
top = snode.offsetTop;
|
|
webix.html.addCss(this.$master.$view, "webix_animation");
|
|
}
|
|
|
|
var onode = snode.cloneNode(true);
|
|
onode.style.width = snode.style.width = "100%";
|
|
|
|
//redraw page
|
|
this.$master.refresh();
|
|
//append copy next to original
|
|
webix.html.insertBefore(onode, snode.nextSibling, snode.parentNode);
|
|
if(isDataTable)
|
|
onode.childNodes[1].scrollLeft = snode.childNodes[1].scrollLeft;
|
|
|
|
//animation config
|
|
var line;
|
|
var base = config !== true ? config : {};
|
|
var aniset = webix.extend({
|
|
direction:direction,
|
|
callback:webix.bind(function(){
|
|
aniset.callback = null;
|
|
webix.animate.breakLine(line);
|
|
this._pgInAnimation = false;
|
|
if (this.$master._body)
|
|
webix.html.removeCss(this.$master.$view, "webix_animation");
|
|
},this),
|
|
top:top, keepViews: isDataTable
|
|
}, base);
|
|
|
|
//run animation
|
|
line = webix.animate.formLine(snode, onode, aniset);
|
|
webix.animate([ snode, onode ], aniset );
|
|
this._pgInAnimation = true;
|
|
}
|
|
}, webix.MouseEvents, webix.SingleRender, webix.ui.view, webix.EventSystem);
|
|
|
|
webix.locale.pager = {
|
|
first: " << ",
|
|
last: " >> ",
|
|
next: " > ",
|
|
prev: " < "
|
|
};
|
|
|
|
|
|
webix.PagingAbility = {
|
|
pager_setter:function(pager){
|
|
if (typeof pager == "string"){
|
|
var ui_pager = webix.$$(pager);
|
|
if (!ui_pager){
|
|
this.$blockRender = true;
|
|
webix.delay(function(){
|
|
var obj = webix.$$(pager);
|
|
|
|
this._settings.pager = this.pager_setter(obj);
|
|
var s = obj._settings;
|
|
s.count = this.data._count_pager_total(s.level);
|
|
obj.refresh();
|
|
|
|
this.$blockRender = false;
|
|
this.render();
|
|
}, this);
|
|
return null;
|
|
}
|
|
pager = ui_pager;
|
|
}
|
|
|
|
function check_pager_sizes(repeat){
|
|
if (pager.config.autosize && this.getVisibleCount){
|
|
var count = this.getVisibleCount();
|
|
if (isNaN(count)){
|
|
pager.config.size = 1;
|
|
webix.delay(check_pager_sizes, this, [true]);
|
|
} else if (count != pager.config.size){
|
|
pager.config.size = count;
|
|
pager.refresh();
|
|
if (repeat === true)
|
|
this.refresh();
|
|
}
|
|
}
|
|
|
|
var s = this._settings.pager;
|
|
//initial value of pager = -1, waiting for real value
|
|
if (s.page == -1) return false;
|
|
|
|
this.data.$min = this._count_pager_index(0, s.page*s.size); //affect data.getRange
|
|
this.data.$max = this._count_pager_index(this.data.$min, s.size);
|
|
this.data.$pagesize = this.data.$max - this.data.$min;
|
|
|
|
return true;
|
|
}
|
|
|
|
this.attachEvent("onBeforeRender",check_pager_sizes);
|
|
|
|
if (!pager.$view){
|
|
pager.view = "pager";
|
|
pager = webix.ui(pager);
|
|
}
|
|
this._pager = pager;
|
|
pager.$master = this;
|
|
|
|
this.data.attachEvent("onStoreUpdated", function(){
|
|
var s = pager._settings;
|
|
s.count = this._count_pager_total(s.level);
|
|
pager.refresh();
|
|
});
|
|
this.data._count_pager_total = this._count_pager_total;
|
|
|
|
return pager._settings;
|
|
},
|
|
_count_pager_total:function(level){
|
|
if (level && level !== 0){
|
|
var count = 0;
|
|
this.each(function(obj){
|
|
if (obj.$level == level) count++;
|
|
});
|
|
return count;
|
|
} else
|
|
return this.count();
|
|
},
|
|
_count_pager_index:function(start, count){
|
|
var s = this._settings.pager;
|
|
|
|
if (s.level && s.level !== 0){
|
|
var end = start;
|
|
var max = this.data.order.length;
|
|
|
|
if (count)
|
|
while (end < max){
|
|
if (this.data.getItem(this.data.order[end]).$level == s.level){
|
|
if (count === 0)
|
|
break;
|
|
else
|
|
count--;
|
|
}
|
|
end++;
|
|
}
|
|
|
|
return end;
|
|
} else
|
|
return start+count;
|
|
},
|
|
setPage:function(value){
|
|
if (this._pager)
|
|
this._pager.select(value);
|
|
},
|
|
getPage:function(){
|
|
return this._pager._settings.page;
|
|
},
|
|
getPager:function(){
|
|
return this._pager;
|
|
}
|
|
};
|
|
/*
|
|
Behavior: AutoTooltip - links tooltip to data driven item
|
|
*/
|
|
|
|
/*
|
|
UI: Tooltip
|
|
|
|
@export
|
|
show
|
|
hide
|
|
*/
|
|
|
|
|
|
|
|
|
|
webix.protoUI({
|
|
name:"tooltip",
|
|
defaults:{
|
|
dy:0,
|
|
dx:20
|
|
},
|
|
$init:function(container){
|
|
if (typeof container == "string"){
|
|
container = { template:container };
|
|
}
|
|
|
|
this.type = webix.extend({}, this.type);
|
|
|
|
//create container for future tooltip
|
|
this.$view = this._viewobj = this._contentobj = this._dataobj = webix.html.create("DIV", {role:"alert", "aria-atomic":"true"});
|
|
this._contentobj.className = "webix_tooltip";
|
|
webix.html.insertBefore(this._contentobj,document.body.firstChild,document.body);
|
|
this._hideHandler = webix.attachEvent("onClick", webix.bind(function(e){
|
|
if (this._visible && webix.$$(e) != this)
|
|
this.hide();
|
|
}, this));
|
|
|
|
//detach global event handler on destruction
|
|
this.attachEvent("onDestruct", function(){
|
|
webix.detachEvent(this._hideHandler);
|
|
});
|
|
},
|
|
adjust:function(){ },
|
|
//show tooptip
|
|
//pos - object, pos.x - left, pox.y - top
|
|
isVisible:function(){
|
|
return true;
|
|
},
|
|
show:function(data,pos){
|
|
if (this._disabled) return;
|
|
//render sefl only if new data was provided
|
|
if (this.data!=data){
|
|
this.data=webix.extend({},data);
|
|
this.render(data);
|
|
}
|
|
|
|
if (this._dataobj.firstChild){
|
|
//show at specified position
|
|
var w = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
|
|
var h = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
|
|
var positionX = w - pos.x;
|
|
var positionY = h - pos.y;
|
|
|
|
this._contentobj.style.display="block";
|
|
|
|
if(positionX - this._settings.dx > this._contentobj.offsetWidth)
|
|
positionX = pos.x;
|
|
else {
|
|
positionX = (pos.x - (this._settings.dx * 2)) - this._contentobj.offsetWidth;
|
|
if(positionX <= 0) positionX = 0;
|
|
}
|
|
|
|
if(positionY - this._settings.dy > this._contentobj.offsetHeight)
|
|
positionY = pos.y;
|
|
else
|
|
positionY = (pos.y - this._settings.dy) - this._contentobj.offsetHeight;
|
|
this._contentobj.style.left = positionX+this._settings.dx+"px";
|
|
this._contentobj.style.top = positionY+this._settings.dy+"px";
|
|
}
|
|
this._visible = true;
|
|
},
|
|
//hide tooltip
|
|
hide:function(){
|
|
this.data=null; //nulify, to be sure that on next show it will be fresh-rendered
|
|
this._contentobj.style.display="none";
|
|
this._visible = false;
|
|
},
|
|
disable:function(){
|
|
this._disabled = true;
|
|
},
|
|
enable:function(){
|
|
this._disabled = false;
|
|
},
|
|
type:{
|
|
template:webix.template("{obj.id}"),
|
|
templateStart:webix.template.empty,
|
|
templateEnd:webix.template.empty
|
|
}
|
|
|
|
}, webix.SingleRender, webix.Settings, webix.EventSystem, webix.ui.view);
|
|
|
|
|
|
|
|
webix.AutoTooltip = {
|
|
tooltip_setter:function(value){
|
|
if (value){
|
|
if (typeof value == "function")
|
|
value = { template:value };
|
|
|
|
var col_mode = !value.template;
|
|
var t = new webix.ui.tooltip(value);
|
|
this._enable_mouse_move();
|
|
var showEvent = this.attachEvent("onMouseMove",function(id,e){ //show tooltip on mousemove
|
|
this._mouseEventX = e.clientX;
|
|
this._mouseEventY = e.clientY;
|
|
if (this.getColumnConfig){
|
|
var config = t.type.column = this.getColumnConfig(id.column);
|
|
if (col_mode){
|
|
//empty tooltip - ignoring
|
|
if (!config.tooltip && config.tooltip != webix.undefined)
|
|
return;
|
|
var trg = e.target || e.srcElements;
|
|
|
|
if(trg.getAttribute("webix_area") && config.tooltip){
|
|
var area = trg.getAttribute("webix_area");
|
|
t.type.template = function(obj,common){
|
|
var values = obj[common.column.id];
|
|
return webix.template(config.tooltip).call(this,obj,common,values[area],area);
|
|
};
|
|
}
|
|
else{
|
|
if (config.tooltip)
|
|
t.type.template = config.tooltip = webix.template(config.tooltip);
|
|
else {
|
|
var text = this.getText(id.row, id.column);
|
|
t.type.template = function(){ return text; };
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!webix.DragControl.active)
|
|
t.show(this.getItem(id.row || id),webix.html.pos(e));
|
|
});
|
|
// [[IMPROVE]] As we can can have only one instance of tooltip per page
|
|
// this handler can be attached once per page, not once per component
|
|
var hideEvent = webix.event(document.body, "mousemove", webix.bind(function(e){
|
|
e = e||event;
|
|
if(this._mouseEventX != e.clientX || this._mouseEventY != e.clientY)
|
|
t.hide();
|
|
},this));
|
|
this.attachEvent("onDestruct",function(){
|
|
if(this.config.tooltip)
|
|
this.config.tooltip.destructor();
|
|
});
|
|
this.attachEvent("onAfterScroll", function(){
|
|
t.hide();
|
|
});
|
|
t.attachEvent("onDestruct",webix.bind(function(){
|
|
this.detachEvent(showEvent);
|
|
webix.eventRemove(hideEvent);
|
|
},this));
|
|
return t;
|
|
}
|
|
}
|
|
};
|
|
|
|
webix.protoUI({
|
|
name:"proto",
|
|
$init:function(){
|
|
this.data.provideApi(this, true);
|
|
this._dataobj = this._dataobj || this._contentobj;
|
|
|
|
//render self , each time when data is updated
|
|
this.data.attachEvent("onStoreUpdated",webix.bind(function(){
|
|
this.render.apply(this,arguments);
|
|
},this));
|
|
},
|
|
$setSize:function(){
|
|
if (webix.ui.view.prototype.$setSize.apply(this, arguments))
|
|
this.render();
|
|
},
|
|
_id:"webix_item",
|
|
on_mouse_move:{
|
|
},
|
|
type:{}
|
|
}, webix.PagingAbility, webix.DataMarks, webix.AutoTooltip,webix.ValidateCollection,webix.RenderStack, webix.DataLoader, webix.ui.view, webix.EventSystem, webix.Settings);
|
|
|
|
webix.CodeParser = {
|
|
//converts a complex object into an object with primitives properties
|
|
collapseNames:function(base, prefix, data){
|
|
data = data || {};
|
|
prefix = prefix || "";
|
|
|
|
if(!base || typeof base != "object")
|
|
return null;
|
|
|
|
for(var prop in base){
|
|
if(base[prop] && typeof base[prop] == "object" && !webix.isDate(base[prop]) && !webix.isArray(base[prop])){
|
|
webix.CodeParser.collapseNames(base[prop], prefix+prop+".", data);
|
|
} else {
|
|
data[prefix+prop] = base[prop];
|
|
}
|
|
}
|
|
return data;
|
|
},
|
|
//converts an object with primitive properties into an object with complex properties
|
|
expandNames:function(base){
|
|
var data = {},
|
|
i, lastIndex, name, obj, prop;
|
|
|
|
for(prop in base){
|
|
name = prop.split(".");
|
|
lastIndex = name.length-1;
|
|
obj = data;
|
|
for( i =0; i < lastIndex; i++ ){
|
|
if(!obj[name[i]])
|
|
obj[name[i]] = {};
|
|
obj = obj[name[i]];
|
|
}
|
|
obj[name[lastIndex]] = base[prop];
|
|
}
|
|
|
|
return data;
|
|
}
|
|
};
|
|
|
|
webix.Values = {
|
|
$init:function(){
|
|
this.elements = {};
|
|
},
|
|
focus:function(name){
|
|
if (name){
|
|
webix.assert(this.elements[name],"unknown input name: "+name);
|
|
this._focus(this.elements[name]);
|
|
} else{
|
|
for(var n in this.elements){
|
|
if(this._focus(this.elements[n]))
|
|
return true;
|
|
}
|
|
}
|
|
},
|
|
_focus: function(target){
|
|
if (target && target.focus){
|
|
target.focus();
|
|
return true;
|
|
}
|
|
},
|
|
setValues:function(data, update){
|
|
if (this._settings.complexData)
|
|
data = webix.CodeParser.collapseNames(data);
|
|
|
|
this._inner_setValues(data, update);
|
|
},
|
|
_inner_setValues:function(data, update){
|
|
this._is_form_dirty = update;
|
|
//prevent onChange calls from separate controls
|
|
this.blockEvent();
|
|
|
|
if (!update || !this._values)
|
|
this._values = {};
|
|
|
|
if (webix.debug_render)
|
|
webix.log("Render: "+this.name+"@"+this._settings.id);
|
|
|
|
for (var name in data)
|
|
if (!this.elements[name])
|
|
this._values[name] = data[name];
|
|
|
|
for (var name in this.elements){
|
|
var input = this.elements[name];
|
|
if (input){
|
|
if (!webix.isUndefined(data[name]))
|
|
input.setValue(data[name]);
|
|
else if (!update && input._allowsClear)
|
|
input.setValue("");
|
|
this._values[name] = input.getValue();
|
|
}
|
|
}
|
|
|
|
this.unblockEvent();
|
|
this.callEvent("onValues",[]);
|
|
},
|
|
isDirty:function(){
|
|
if (this._is_form_dirty) return true;
|
|
if (this.getDirtyValues(1) === 1)
|
|
return true;
|
|
|
|
return false;
|
|
},
|
|
setDirty:function(flag){
|
|
this._is_form_dirty = flag;
|
|
if (!flag)
|
|
this._values = this._inner_getValues();
|
|
},
|
|
getDirtyValues:function(){
|
|
var result = {};
|
|
if (this._values){
|
|
for (var name in this.elements){
|
|
var value = this.elements[name].getValue();
|
|
if (this._values[name] != value){
|
|
result[name] = value;
|
|
//FIXME - used by isDirty
|
|
if (arguments[0])
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
},
|
|
getCleanValues:function(){
|
|
return this._values;
|
|
},
|
|
getValues:function(filter){
|
|
var data = this._inner_getValues(filter);
|
|
if (this._settings.complexData)
|
|
data = webix.CodeParser.expandNames(data);
|
|
|
|
return data;
|
|
},
|
|
_inner_getValues:function(filter){
|
|
//get original data
|
|
var success,
|
|
elem = null,
|
|
data = (this._values?webix.copy(this._values):{});
|
|
|
|
//update properties from linked controls
|
|
for (var name in this.elements){
|
|
elem = this.elements[name];
|
|
success = true;
|
|
if(filter){
|
|
if(typeof filter == "object"){
|
|
if(filter.hidden === false)
|
|
success = elem.isVisible();
|
|
if(success && filter.disabled === false)
|
|
success = elem.isEnabled();
|
|
}
|
|
else
|
|
success = filter.call(this,elem);
|
|
}
|
|
if(success)
|
|
data[name] = elem.getValue();
|
|
else
|
|
delete data[name]; //in case of this._values[name]
|
|
}
|
|
return data;
|
|
},
|
|
clear:function(){
|
|
this._is_form_dirty = false;
|
|
var data = {};
|
|
for (var name in this.elements)
|
|
if (this.elements[name]._allowsClear)
|
|
data[name] = this.elements[name]._settings.defaultValue||"";
|
|
|
|
this._inner_setValues(data);
|
|
},
|
|
markInvalid: function(name, state){
|
|
// remove 'invalid' mark
|
|
if(state === false){
|
|
this._clear_invalid(name);
|
|
}
|
|
// add 'invalid' mark
|
|
else{
|
|
// set invalidMessage
|
|
if(typeof state == "string"){
|
|
var input = this.elements[name];
|
|
if(input)
|
|
input._settings.invalidMessage = state;
|
|
}
|
|
this._mark_invalid(name);
|
|
}
|
|
},
|
|
_mark_invalid:function(id){
|
|
var input = this.elements[id];
|
|
if (id && input){
|
|
this._clear_invalid(id,true);
|
|
webix.html.addCss(input._viewobj, "webix_invalid");
|
|
input._settings.invalid = true;
|
|
var message = input._settings.invalidMessage;
|
|
if(typeof message === "string" && input.setBottomText)
|
|
input.setBottomText();
|
|
}
|
|
},
|
|
_clear_invalid:function(id,silent){
|
|
var input = this.elements[id];
|
|
if(id && input && input.$view && input._settings.invalid){
|
|
webix.html.removeCss(input._viewobj, "webix_invalid");
|
|
input._settings.invalid = false;
|
|
var message = input._settings.invalidMessage;
|
|
if(typeof message === "string" && !silent && input.setBottomText)
|
|
input.setBottomText();
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
webix.protoUI({
|
|
name:"toolbar",
|
|
defaults:{
|
|
type:'toolbar'
|
|
},
|
|
_render_borders:true,
|
|
_form_classname:"webix_toolbar",
|
|
_form_vertical:false,
|
|
$init:function(config){
|
|
if (!config.borderless)
|
|
this._contentobj.style.borderWidth="1px";
|
|
|
|
this._contentobj.className+=" "+this._form_classname;
|
|
this._viewobj.setAttribute("role", "toolbar");
|
|
},
|
|
_recollect_elements:function(){
|
|
var form = this;
|
|
form.elements = {};
|
|
webix.ui.each(this, function(view){
|
|
if (view._settings.name && view.getValue && view.setValue){
|
|
form.elements[view._settings.name] = view;
|
|
if (view.mapEvent)
|
|
view.mapEvent({
|
|
onbeforetabclick:form,
|
|
onaftertabclick:form,
|
|
onitemclick:form,
|
|
onchange:form
|
|
});
|
|
}
|
|
|
|
if (view.setValues) return false;
|
|
});
|
|
var old = this._values;
|
|
this.setDirty(false);
|
|
if (old) {
|
|
//restore dirty state after form reconstructing
|
|
var now = this._values;
|
|
for (var key in form.elements)
|
|
if (old[key] && now[key] != old[key]){
|
|
now[key] = old[key];
|
|
this.setDirty(true);
|
|
}
|
|
}
|
|
},
|
|
_parse_cells_ext_end:function(){
|
|
this._recollect_elements();
|
|
},
|
|
_parse_cells_ext:function(collection){
|
|
var config = this._settings;
|
|
if (config.elements && !collection){
|
|
this._collection = collection = config.elements;
|
|
this._vertical_orientation = this._form_vertical;
|
|
delete config.elements;
|
|
}
|
|
|
|
if (this._settings.elementsConfig)
|
|
this._rec_apply_settings(this._collection, config.elementsConfig);
|
|
|
|
return collection;
|
|
},
|
|
_rec_apply_settings:function(col, settings){
|
|
for (var i=0; i<col.length; i++){
|
|
var element = col[i];
|
|
webix.extend( element, settings );
|
|
var nextsettings = settings;
|
|
|
|
if (element.elementsConfig)
|
|
nextsettings = webix.extend(webix.extend({}, element.elementsConfig), settings);
|
|
|
|
var sub;
|
|
if (element.body)
|
|
sub = [element.body];
|
|
else
|
|
sub = element.rows || element.cols || element.cells || element.body;
|
|
|
|
if (sub)
|
|
this._rec_apply_settings(sub, nextsettings);
|
|
}
|
|
},
|
|
$getSize:function(dx, dy){
|
|
var sizes = webix.ui.layout.prototype.$getSize.call(this, dx, dy);
|
|
var parent = this.getParentView();
|
|
var index = this._vertical_orientation?3:1;
|
|
if (parent && this._vertical_orientation != parent._vertical_orientation)
|
|
sizes[index]+=100000;
|
|
|
|
webix.debug_size_box(this, sizes, true);
|
|
return sizes;
|
|
},
|
|
render:function(){
|
|
},
|
|
refresh:function(){
|
|
this.render();
|
|
}
|
|
}, webix.Scrollable, webix.AtomDataLoader, webix.Values, webix.ui.layout, webix.ValidateData);
|
|
|
|
|
|
webix.protoUI({
|
|
name:"template",
|
|
$init:function(config){
|
|
var subtype = this._template_types[config.type];
|
|
if (subtype){
|
|
webix.extend(config, subtype);
|
|
|
|
//will reset borders for "section"
|
|
if (config.borderless){
|
|
delete config._inner;
|
|
this._set_inner(config);
|
|
}
|
|
}
|
|
|
|
if (this._dataobj == this._viewobj){
|
|
this._dataobj = webix.html.create("DIV");
|
|
this._dataobj.className = " webix_template";
|
|
this._viewobj.appendChild(this._dataobj);
|
|
} else
|
|
this._dataobj.className += " webix_template";
|
|
|
|
this.attachEvent("onAfterRender", this._correct_width_scroll);
|
|
},
|
|
setValues:function(obj, update){
|
|
this.data = update?webix.extend(this.data, obj, true):obj;
|
|
this.render();
|
|
},
|
|
getValues:function(){
|
|
return this.data;
|
|
},
|
|
$skin:function(){
|
|
this._template_types.header.height = this._template_types.section.height = webix.skin.$active.barHeight;
|
|
},
|
|
_template_types:{
|
|
"header":{
|
|
css:"webix_header"
|
|
},
|
|
"section":{
|
|
css:"webix_section",
|
|
borderless:true
|
|
},
|
|
"clean":{
|
|
css:"webix_clean",
|
|
borderless:true
|
|
}
|
|
},
|
|
onClick_setter:function(value){
|
|
this.on_click = webix.extend((this.on_click || {}), value, true);
|
|
|
|
if (!this._onClick)
|
|
webix.extend(this, webix.MouseEvents);
|
|
|
|
return value;
|
|
},
|
|
defaults:{
|
|
template:webix.template.empty
|
|
},
|
|
_render_me:function(){
|
|
this._not_render_me = false;
|
|
this._probably_render_me();
|
|
this.resize();
|
|
},
|
|
_probably_render_me:function(){
|
|
if (!this._not_render_me){
|
|
this._not_render_me = true;
|
|
this.render();
|
|
}
|
|
},
|
|
src_setter:function(value){
|
|
this._not_render_me = true;
|
|
|
|
if(!this.callEvent("onBeforeLoad",[]))
|
|
return "";
|
|
webix.ajax(value, webix.bind(function(text){
|
|
this._settings.template = webix.template(text);
|
|
this._render_me();
|
|
this.callEvent("onAfterLoad",[]);
|
|
}, this));
|
|
return value;
|
|
},
|
|
_correct_width_scroll:function(){
|
|
//we need to force auto height calculation after content change
|
|
//dropping the last_size flag will ensure that inner logic of $setSize will be processed
|
|
if (this._settings.autoheight){
|
|
this._last_size = null;
|
|
this.resize();
|
|
}
|
|
|
|
if (this._settings.scroll && this._settings.scroll.indexOf("x") != -1)
|
|
this._dataobj.style.width = this._dataobj.scrollWidth + "px";
|
|
},
|
|
content_setter:function(config){
|
|
if (config){
|
|
this._not_render_me = true;
|
|
this.render = function(){};
|
|
this._dataobj.appendChild(webix.toNode(config));
|
|
}
|
|
},
|
|
refresh:function(){
|
|
this.render();
|
|
},
|
|
setHTML:function(html){
|
|
this._settings.template = function(){ return html; };
|
|
this.refresh();
|
|
},
|
|
setContent:function(content){
|
|
this._dataobj.innerHTML = "";
|
|
this.content_setter(content);
|
|
},
|
|
$setSize:function(x,y){
|
|
if (webix.ui.view.prototype.$setSize.call(this,x,y)){
|
|
this._probably_render_me();
|
|
if (this._settings.autoheight){
|
|
var top =this.getTopParentView();
|
|
clearTimeout(top._template_resize_timer);
|
|
top._template_resize_timer = webix.delay(this.resize, this);
|
|
}
|
|
return true;
|
|
}
|
|
},
|
|
$getSize:function(x,y){
|
|
if (this._settings.autoheight && !this._settings.type)
|
|
this._settings.height = this._get_auto_height();
|
|
|
|
return webix.ui.view.prototype.$getSize.call(this,x,y);
|
|
},
|
|
_get_auto_height:function(){
|
|
var size;
|
|
var padding = webix.skin.$active.layoutPadding.space;
|
|
|
|
this._probably_render_me();
|
|
|
|
if(!this.isVisible()){ //try getting height of a hidden template
|
|
size = webix.html.getTextSize(
|
|
this._toHTML(this.data) || this._dataobj.innerHTML, //check for config.content
|
|
"webix_template",
|
|
(this.$width || (this.getParentView() ? this.getParentView().$width :0))-padding
|
|
).height;
|
|
}
|
|
else{
|
|
this._dataobj.style.height = "auto";
|
|
size = this._dataobj.scrollHeight;
|
|
this._dataobj.style.height = "";
|
|
}
|
|
return size;
|
|
},
|
|
_one_time_scroll:true //scroll will appear only if set directly in config
|
|
}, webix.Scrollable, webix.AtomDataLoader, webix.AtomRender, webix.EventSystem, webix.ui.view);
|
|
|
|
webix.protoUI({
|
|
name:"iframe",
|
|
$init:function(config){
|
|
this._dataobj = this._contentobj;
|
|
this._contentobj.innerHTML = "<iframe style='width:100%; height:100%' frameborder='0' onload='var t = $$(this.parentNode.getAttribute(\"view_id\")); if (t) t.callEvent(\"onAfterLoad\",[]);' src='about:blank'></iframe>";
|
|
},
|
|
load:function(value){
|
|
this.src_setter(value);
|
|
},
|
|
src_setter:function(value){
|
|
if(!this.callEvent("onBeforeLoad",[]))
|
|
return "";
|
|
this.getIframe().src = value;
|
|
return value;
|
|
},
|
|
getIframe:function(){
|
|
return this._contentobj.getElementsByTagName("iframe")[0];
|
|
},
|
|
getWindow:function(){
|
|
return this.getIframe().contentWindow;
|
|
}
|
|
}, webix.ui.view, webix.EventSystem);
|
|
|
|
webix.OverlayBox = {
|
|
showOverlay:function(message){
|
|
if (!this._overlay){
|
|
this._overlay = webix.html.create("DIV",{ "class":"webix_overlay" },(message||""));
|
|
webix.html.insertBefore(this._overlay, this._viewobj.firstChild, this._viewobj);
|
|
this._viewobj.style.position = "relative";
|
|
} else
|
|
this._overlay.innerHTML = message;
|
|
},
|
|
hideOverlay:function(){
|
|
if (this._overlay){
|
|
webix.html.remove(this._overlay);
|
|
this._overlay = null;
|
|
}
|
|
}
|
|
};
|
|
|
|
/*scrollable view with another view insize*/
|
|
webix.protoUI({
|
|
name:"scrollview",
|
|
defaults:{
|
|
scroll:"y",
|
|
scrollSpeed:"0ms"
|
|
},
|
|
$init:function(){
|
|
this._viewobj.className += " webix_scrollview";
|
|
},
|
|
body_setter:function(config){
|
|
config.borderless = true;
|
|
this._body_cell = webix.ui._view(config);
|
|
this._body_cell._parent_cell = this;
|
|
this._dataobj.appendChild(this._body_cell._viewobj);
|
|
},
|
|
getChildViews:function(){
|
|
return [this._body_cell];
|
|
},
|
|
getBody:function(){
|
|
return this._body_cell;
|
|
},
|
|
resizeChildren:function(){
|
|
this._desired_size = this._body_cell.$getSize(0, 0);
|
|
this._resizeChildren();
|
|
webix.callEvent("onResize",[]);
|
|
},
|
|
_resizeChildren:function(){
|
|
var scroll_size = this._native_scroll || webix.ui.scrollSize;
|
|
var cx = Math.max(this._content_width, this._desired_size[0]);
|
|
var cy = Math.max(this._content_height, this._desired_size[2]);
|
|
this._body_cell.$setSize(cx, cy);
|
|
this._dataobj.style.width = this._body_cell._content_width+"px";
|
|
this._dataobj.style.height = this._body_cell._content_height+"px";
|
|
if (webix.env.touch){
|
|
var state = this.getScrollState();
|
|
var top = this._body_cell._content_height - this._content_height;
|
|
if (top < state.y)
|
|
this.scrollTo(null, top);
|
|
}
|
|
if (webix._responsive_exception){
|
|
webix._responsive_exception = false;
|
|
this._desired_size = this._body_cell.$getSize(0, 0);
|
|
this._resizeChildren();
|
|
}
|
|
},
|
|
$getSize:function(dx, dy){
|
|
var desired_size = this._desired_size = this._body_cell.$getSize(0, 0);
|
|
var self_sizes = webix.ui.view.prototype.$getSize.call(this, dx, dy);
|
|
var scroll_size = this._native_scroll || webix.ui.scrollSize;
|
|
|
|
if(this._settings.scroll=="x"){
|
|
self_sizes[2] = Math.max(self_sizes[2], desired_size[2]) + scroll_size;
|
|
self_sizes[3] = Math.min(self_sizes[3], desired_size[3]) + scroll_size;
|
|
} else if(this._settings.scroll=="y"){
|
|
self_sizes[0] = Math.max(self_sizes[0], desired_size[0]) + scroll_size;
|
|
self_sizes[1] = Math.min(self_sizes[1], desired_size[1]) + scroll_size;
|
|
}
|
|
return self_sizes;
|
|
},
|
|
$setSize:function(x,y){
|
|
var temp = webix.ui.scrollSize;
|
|
webix.ui.scrollSize = this._native_scroll || temp;
|
|
|
|
if (webix.ui.view.prototype.$setSize.call(this,x,y))
|
|
this._resizeChildren();
|
|
|
|
webix.ui.scrollSize = temp;
|
|
},
|
|
scroll_setter:function(value){
|
|
var custom = webix.env.$customScroll;
|
|
if (typeof value == "string" && value.indexOf("native-") === 0){
|
|
this._native_scroll = 17;
|
|
value = value.replace("native-");
|
|
webix.env.$customScroll = false;
|
|
}
|
|
|
|
value = webix.Scrollable.scroll_setter.call(this, value);
|
|
|
|
webix.env.$customScroll = custom;
|
|
return value;
|
|
},
|
|
_replace:function(new_view){
|
|
this._body_cell.destructor();
|
|
this._body_cell = new_view;
|
|
this._body_cell._parent_cell = this;
|
|
|
|
this._bodyobj.appendChild(this._body_cell._viewobj);
|
|
this.resize();
|
|
},
|
|
showView: function(id){
|
|
var topPos = webix.$$(id).$view.offsetTop-webix.$$(id).$view.parentNode.offsetTop;
|
|
this.scrollTo(0, topPos);
|
|
}
|
|
}, webix.Scrollable, webix.EventSystem, webix.ui.view);
|
|
|
|
/*
|
|
UI:TreeMenu
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
webix.TreeRenderStack={
|
|
$init:function(){
|
|
webix.assert(this.render,"TreeRenderStack :: Object must use RenderStack first");
|
|
},
|
|
_toHTMLItem:function(obj){
|
|
var mark = this.data._marks[obj.id];
|
|
this.callEvent("onItemRender",[obj]);
|
|
return this.type.templateStart(obj,this.type,mark)+(obj.$template?this.type["template"+obj.$template](obj,this.type,mark):this.type.template(obj,this.type,mark))+this.type.templateEnd();
|
|
},
|
|
_toHTMLItemObject:function(obj){
|
|
this._html.innerHTML = this._toHTMLItem(obj);
|
|
return this._html.firstChild;
|
|
},
|
|
//convert single item to HTML text (templating)
|
|
_toHTML:function(obj){
|
|
//check if related template exist
|
|
webix.assert((!obj.$template || this.type["template"+obj.$template]),"RenderStack :: Unknown template: "+obj.$template);
|
|
var html="<div role='presentation' class='webix_tree_branch_"+obj.$level+"'>"+this._toHTMLItem(obj);
|
|
|
|
if (obj.open)
|
|
html+=this._toHTMLLevel(obj.id);
|
|
|
|
html+="</div>";
|
|
|
|
return html;
|
|
},
|
|
_toHTMLLevel:function(id){
|
|
var html = "";
|
|
var leaves = this.data.branch[id];
|
|
if (leaves){
|
|
html+="<div role='presentation' class='webix_tree_leaves'>";
|
|
var last = leaves.length-1;
|
|
for (var i=0; i <= last; i++){
|
|
var obj = this.getItem(leaves[i]);
|
|
this.type._tree_branch_render_state[obj.$level] = (i == last);
|
|
html+=this._toHTML(obj);
|
|
}
|
|
html+="</div>";
|
|
}
|
|
return html;
|
|
},
|
|
//return true when some actual rendering done
|
|
render:function(id,data,type){
|
|
webix.TreeRenderStack._obj = this; //can be used from complex render
|
|
|
|
if (!this.isVisible(this._settings.id) || this.$blockRender)
|
|
return;
|
|
|
|
if (webix.debug_render)
|
|
webix.log("Render: "+this.name+"@"+this._settings.id);
|
|
|
|
if (id){
|
|
var cont;
|
|
var item = this.getItem(id);
|
|
if (type!="add"){
|
|
cont = this.getItemNode(id);
|
|
if (!cont) return;
|
|
}
|
|
|
|
switch(type){
|
|
case "branch":
|
|
var branch = cont.parentNode;
|
|
var node = this._toHTMLObject(item);
|
|
|
|
webix.html.insertBefore(node, branch);
|
|
webix.html.remove(branch);
|
|
this._htmlmap = null;
|
|
break;
|
|
case "paint":
|
|
case "update":
|
|
var node = this._htmlmap[id] = this._toHTMLItemObject(item);
|
|
webix.html.insertBefore(node, cont);
|
|
webix.html.remove(cont);
|
|
break;
|
|
case "delete":
|
|
//deleting not item , but full branch
|
|
webix.html.remove(cont.parentNode);
|
|
break;
|
|
case "add":
|
|
var parent;
|
|
//we want process both empty value and 0 as string
|
|
//jshint -W041:true
|
|
if (item.$parent == 0){
|
|
parent = this._dataobj.firstChild;
|
|
} else if(this.getItem(item.$parent).open){
|
|
parent = this.getItemNode(item.$parent);
|
|
if (parent){
|
|
//when item created by the script, it will miss the container for child notes
|
|
//create it on demand
|
|
if (!parent.nextSibling){
|
|
var leafs = webix.html.create("DIV", { "class" : "webix_tree_leaves" },"");
|
|
parent.parentNode.appendChild(leafs);
|
|
}
|
|
parent = parent.nextSibling;
|
|
}
|
|
}
|
|
|
|
if (parent){
|
|
var next = this.data.getNextSiblingId(id);
|
|
next = this.getItemNode(next);
|
|
if (next)
|
|
next = next.parentNode;
|
|
|
|
var node = this._toHTMLObject(item);
|
|
this._htmlmap[id] = node.firstChild;
|
|
webix.html.insertBefore(node, next, parent);
|
|
}
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
this.callEvent("onPartialRender", [id,data,type]);
|
|
} else
|
|
//full reset
|
|
if (this.callEvent("onBeforeRender",[this.data])){
|
|
//will be used for lines management
|
|
this.type._tree_branch_render_state = [];
|
|
//getTopRange - returns all elements on top level
|
|
this._dataobj.innerHTML = this._toHTMLLevel(0);
|
|
|
|
this._htmlmap = null; //clear map, it will be filled at first getItemNode
|
|
this.callEvent("onAfterRender",[]);
|
|
}
|
|
|
|
//clear after usage
|
|
this.type._tree_branch_render_state = 0;
|
|
webix.TreeRenderStack._obj = null;
|
|
return true;
|
|
},
|
|
getItemNode:function(search_id){
|
|
if (this._htmlmap)
|
|
return this._htmlmap[search_id];
|
|
|
|
//fill map if it doesn't created yet
|
|
this._htmlmap={};
|
|
|
|
var t = this._dataobj.getElementsByTagName("DIV");
|
|
for (var i=0; i < t.length; i++){
|
|
var id = t[i].getAttribute(this._id); //get item's
|
|
if (id)
|
|
this._htmlmap[id]=t[i];
|
|
}
|
|
//call locator again, when map is filled
|
|
return this.getItemNode(search_id);
|
|
},
|
|
_branch_render_supported:1
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
Behavior:SelectionModel - manage selection states
|
|
@export
|
|
select
|
|
unselect
|
|
selectAll
|
|
unselectAll
|
|
isSelected
|
|
getSelectedId
|
|
*/
|
|
webix.SelectionModel={
|
|
$init:function(){
|
|
//collection of selected IDs
|
|
this._selected = webix.toArray();
|
|
webix.assert(this.data, "SelectionModel :: Component doesn't have DataStore");
|
|
|
|
//remove selection from deleted items
|
|
this.data.attachEvent("onStoreUpdated",webix.bind(this._data_updated,this));
|
|
this.data.attachEvent("onStoreLoad", webix.bind(this._data_loaded,this));
|
|
this.data.attachEvent("onAfterFilter", webix.bind(this._data_filtered,this));
|
|
this.data.attachEvent("onSyncApply", webix.bind(this._select_check,this));
|
|
this.data.attachEvent("onIdChange", webix.bind(this._id_changed,this));
|
|
this.$ready.push(this._set_noselect);
|
|
},
|
|
_set_noselect: function(){
|
|
if (this._settings.select=="multiselect" || this._settings.multiselect)
|
|
webix._event(this.$view,"mousedown", function(e){
|
|
var shiftKey = (e||event).shiftKey;
|
|
if(shiftKey){
|
|
webix._noselect_element = this;
|
|
webix.html.addCss(this,"webix_noselect",1);
|
|
}
|
|
});
|
|
},
|
|
_id_changed:function(oldid, newid){
|
|
for (var i = this._selected.length - 1; i >= 0; i--)
|
|
if (this._selected[i]==oldid)
|
|
this._selected[i]=newid;
|
|
},
|
|
_data_filtered:function(){
|
|
for (var i = this._selected.length - 1; i >= 0; i--){
|
|
if (this.data.getIndexById(this._selected[i]) < 0) {
|
|
var id = this._selected[i];
|
|
this.removeCss(id, "webix_selected", true);
|
|
this._selected.splice(i,1);
|
|
this.callEvent("onSelectChange",[id]);
|
|
}
|
|
}
|
|
},
|
|
//helper - linked to onStoreUpdated
|
|
_data_updated:function(id,obj,type){
|
|
if (type == "delete"){ //remove selection from deleted items
|
|
if (this.loadBranch){
|
|
//hierarchy, need to check all
|
|
this._select_check();
|
|
} else
|
|
this._selected.remove(id);
|
|
}
|
|
else if (!id && !this.data.count() && !this.data._filter_order){ //remove selection for clearAll
|
|
this._selected = webix.toArray();
|
|
}
|
|
},
|
|
_data_loaded:function(){
|
|
if (this._settings.select)
|
|
this.data.each(function(obj){
|
|
if (obj && obj.$selected) this.select(obj.id);
|
|
}, this);
|
|
},
|
|
_select_check:function(){
|
|
for (var i = this._selected.length - 1; i >= 0; i--)
|
|
if (!this.exists(this._selected[i]))
|
|
this._selected.splice(i,1);
|
|
},
|
|
//helper - changes state of selection for some item
|
|
_select_mark:function(id,state,refresh,need_unselect){
|
|
var name = state ? "onBeforeSelect" : "onBeforeUnSelect";
|
|
if (!this.callEvent(name,[id,state])) return false;
|
|
|
|
if (need_unselect){
|
|
this._silent_selection = true;
|
|
this.unselectAll();
|
|
this._silent_selection = false;
|
|
}
|
|
|
|
if (state)
|
|
this.addCss(id, "webix_selected", true);
|
|
else
|
|
this.removeCss(id, "webix_selected", true);
|
|
|
|
if (refresh)
|
|
refresh.push(id); //if we in the mass-select mode - collect all changed IDs
|
|
else{
|
|
if (state)
|
|
this._selected.push(id); //then add to list of selected items
|
|
else
|
|
this._selected.remove(id);
|
|
this._refresh_selection(id); //othervise trigger repainting
|
|
}
|
|
|
|
var name = state ? "onAfterSelect" : "onAfterUnSelect";
|
|
this.callEvent(name,[id]);
|
|
|
|
return true;
|
|
},
|
|
//select some item
|
|
select:function(id,preserve){
|
|
var ctrlKey = arguments[2];
|
|
var shiftKey = arguments[3];
|
|
//if id not provide - works as selectAll
|
|
if (!id) return this.selectAll();
|
|
|
|
//allow an array of ids as parameter
|
|
if (webix.isArray(id)){
|
|
for (var i=0; i < id.length; i++)
|
|
this.select(id[i], (i?1:preserve), ctrlKey, shiftKey);
|
|
return;
|
|
}
|
|
|
|
webix.assert(this.data.exists(id), "Incorrect id in select command: "+id);
|
|
|
|
//block selection mode
|
|
if (shiftKey && this._selected.length)
|
|
return this.selectAll(this._selected[this._selected.length-1],id);
|
|
|
|
//single selection mode
|
|
var need_unselect = false;
|
|
if (!ctrlKey && !preserve && (this._selected.length!=1 || this._selected[0]!=id))
|
|
need_unselect = true;
|
|
|
|
if (!need_unselect && this.isSelected(id)){
|
|
if (ctrlKey) this.unselect(id); //ctrl-selection of already selected item
|
|
return;
|
|
}
|
|
|
|
this._select_mark(id, true, null, need_unselect);
|
|
},
|
|
//unselect some item
|
|
unselect:function(id){
|
|
//if id is not provided - unselect all items
|
|
if (!id) return this.unselectAll();
|
|
if (!this.isSelected(id)) return;
|
|
|
|
this._select_mark(id,false);
|
|
},
|
|
//select all items, or all in defined range
|
|
selectAll:function(from,to){
|
|
var range;
|
|
var refresh=[];
|
|
|
|
if (from||to)
|
|
range = this.data.getRange(from||null,to||null); //get limited set if bounds defined
|
|
else
|
|
range = this.data.getRange(); //get all items in other case
|
|
//in case of paging - it will be current page only
|
|
range.each(function(obj){
|
|
if (!this.data.getMark(obj.id, "webix_selected")){
|
|
this._selected.push(obj.id);
|
|
this._select_mark(obj.id,true,refresh);
|
|
}
|
|
},this);
|
|
//repaint self
|
|
this._refresh_selection(refresh);
|
|
},
|
|
//remove selection from all items
|
|
unselectAll:function(){
|
|
var refresh=[];
|
|
|
|
this._selected.each(function(id){
|
|
this._select_mark(id,false,refresh); //unmark selected only
|
|
},this);
|
|
|
|
this._selected=webix.toArray();
|
|
this._refresh_selection(refresh); //repaint self
|
|
},
|
|
//returns true if item is selected
|
|
isSelected:function(id){
|
|
return this._selected.find(id)!=-1;
|
|
},
|
|
/*
|
|
returns ID of selected items or array of IDs
|
|
to make result predictable - as_array can be used,
|
|
with such flag command will always return an array
|
|
empty array in case when no item was selected
|
|
*/
|
|
getSelectedId:function(as_array){
|
|
switch(this._selected.length){
|
|
case 0: return as_array?[]:"";
|
|
case 1: return as_array?[this._selected[0]]:this._selected[0];
|
|
default: return ([].concat(this._selected)); //isolation
|
|
}
|
|
},
|
|
getSelectedItem:function(as_array){
|
|
var sel = this.getSelectedId(true);
|
|
if (sel.length > 1 || as_array){
|
|
for (var i = sel.length - 1; i >= 0; i--)
|
|
sel[i] = this.getItem(sel[i]);
|
|
return sel;
|
|
} else if (sel.length)
|
|
return this.getItem(sel[0]);
|
|
},
|
|
//detects which repainting mode need to be used
|
|
_is_mass_selection:function(obj){
|
|
// crappy heuristic, but will do the job
|
|
return obj.length>100 || obj.length > this.data.count/2;
|
|
},
|
|
_refresh_selection:function(refresh){
|
|
if (typeof refresh != "object") refresh = [refresh];
|
|
if (!refresh.length) return; //nothing to repaint
|
|
|
|
if (this._is_mass_selection(refresh))
|
|
this.data.refresh(); //many items was selected - repaint whole view
|
|
else
|
|
for (var i=0; i < refresh.length; i++) //repaint only selected
|
|
this.render(refresh[i],this.data.getItem(refresh[i]),"update");
|
|
|
|
if (!this._silent_selection)
|
|
this.callEvent("onSelectChange",[refresh]);
|
|
}
|
|
};
|
|
|
|
webix.ready(function(){
|
|
webix.event(document.body,"mouseup", function(e){
|
|
if(webix._noselect_element){
|
|
webix.html.removeCss(webix._noselect_element,"webix_noselect");
|
|
webix._noselect_element = null;
|
|
}
|
|
});
|
|
});
|
|
/*
|
|
Behavior:DataMove - allows to move and copy elements, heavily relays on DataStore.move
|
|
@export
|
|
copy
|
|
move
|
|
*/
|
|
webix.TreeDataMove={
|
|
$init:function(){
|
|
webix.assert(this.data, "DataMove :: Component doesn't have DataStore");
|
|
},
|
|
//creates a copy of the item
|
|
copy:function(sid,tindex,tobj,details){
|
|
details = details || {};
|
|
details.copy = true;
|
|
return this.move(sid, tindex, tobj, details);
|
|
},
|
|
_next_move_index:function(nid, next, source){
|
|
if (next && nid){
|
|
var new_index = this.getBranchIndex(nid);
|
|
return new_index+(source == this && source.getBranchIndex(next)<new_index?0:1);
|
|
}
|
|
},
|
|
_check_branch_child:function(parent, child){
|
|
var t = this.data.branch[parent];
|
|
if (t && t.length){
|
|
for (var i=0; i < t.length; i++) {
|
|
if (t[i] == child) return true;
|
|
if (this._check_branch_child(t[i], child)) return true;
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
//move item to the new position
|
|
move:function(sid,tindex,tobj, details){
|
|
details = details || {};
|
|
tindex = tindex || 0;
|
|
var new_id = details.newId || sid;
|
|
var target_parent = details.parent || 0;
|
|
|
|
tobj = tobj||this;
|
|
webix.assert(tobj.data, "moving attempt to component without datastore");
|
|
if (!tobj.data) return;
|
|
|
|
if (webix.isArray(sid)){
|
|
for (var i=0; i < sid.length; i++) {
|
|
//increase index for each next item in the set, so order of insertion will be equal to order in the array
|
|
var nid = this.move(sid[i], tindex, tobj, details);
|
|
tindex = tobj._next_move_index(nid, sid[i+1], this);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (this != tobj || details.copy){
|
|
new_id = tobj.data.add(tobj._externalData(this.getItem(sid),new_id), tindex, (target_parent || 0));
|
|
if (this.data.branch[sid] && tobj.getBranchIndex){
|
|
var temp = this.data._scheme_serialize;
|
|
this.data._scheme_serialize = function(obj){
|
|
var copy = webix.copy(obj);
|
|
delete copy.$parent; delete copy.$level; delete copy.$child;
|
|
if (tobj.data.pull[copy.id])
|
|
copy.id = webix.uid();
|
|
return copy;
|
|
};
|
|
var copy_data = { data:this.serialize(sid, true), parent:new_id };
|
|
this.data._scheme_serialize = temp;
|
|
tobj.parse(copy_data);
|
|
}
|
|
if (!details.copy)
|
|
this.data.remove(sid);
|
|
} else {
|
|
//move in self
|
|
if (sid == target_parent || this._check_branch_child(sid,target_parent)) return;
|
|
|
|
var source = this.getItem(sid);
|
|
var tbranch = this.data.branch[target_parent];
|
|
if (!tbranch)
|
|
tbranch = this.data.branch[target_parent] = [];
|
|
var sbranch = this.data.branch[source.$parent];
|
|
|
|
var sindex = webix.PowerArray.find.call(sbranch, sid);
|
|
if (tindex < 0) tindex = Math.max(tbranch.length - 1, 0);
|
|
//in the same branch
|
|
if (sbranch === tbranch && tindex === sindex) return; //same position
|
|
|
|
webix.PowerArray.removeAt.call(sbranch, sindex);
|
|
webix.PowerArray.insertAt.call(tbranch, sid, Math.min(tbranch.length, tindex));
|
|
|
|
if (!sbranch.length)
|
|
delete this.data.branch[source.$parent];
|
|
|
|
|
|
if(source.$parent && source.$parent != "0")
|
|
this.getItem(source.$parent).$count--;
|
|
|
|
if (target_parent && target_parent != "0"){
|
|
var target = tobj.getItem(target_parent);
|
|
target.$count++;
|
|
this._set_level_rec(source, target.$level+1);
|
|
} else
|
|
this._set_level_rec(source, 1);
|
|
|
|
source.$parent = target_parent;
|
|
tobj.data.callEvent("onDataMove", [sid, tindex, target_parent, tbranch[tindex+1]]);
|
|
}
|
|
|
|
this.refresh();
|
|
return new_id; //return ID of item after moving
|
|
},
|
|
_set_level_rec:function(item, value){
|
|
item.$level = value;
|
|
var branch = this.data.branch[item.id];
|
|
if (branch)
|
|
for (var i=0; i<branch.length; i++)
|
|
this._set_level_rec(this.getItem(branch[i]), value+1);
|
|
},
|
|
//reaction on pause during dnd
|
|
_drag_pause:function(id){
|
|
if (id && !id.header) //ignore drag other header
|
|
this.open(id);
|
|
},
|
|
$dropAllow:function(context){
|
|
if (context.from != context.to) return true;
|
|
for (var i=0; i<context.source.length; i++)
|
|
if (context.source == context.target || this._check_branch_child(context.source, context.target)) return false;
|
|
|
|
return true;
|
|
},
|
|
/*
|
|
this is a stub for future functionality
|
|
currently it just makes a copy of data object, which is enough for current situation
|
|
*/
|
|
_externalData:function(data,id){
|
|
var new_data = webix.DataMove._externalData.call(this, data, id);
|
|
delete new_data.open;
|
|
return new_data;
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
webix.TreeDataLoader = {
|
|
$init:function(){
|
|
this.data.attachEvent("onStoreUpdated", webix.bind(this._sync_hierarchy, this), null, true);
|
|
|
|
// #FIXME: constructor call chain
|
|
//redefine methods
|
|
this._feed_common = this._feed_commonA;
|
|
},
|
|
_feed_commonA:function(id, count, callback, url){
|
|
// branch loading
|
|
var details = (count === 0?{parent: encodeURIComponent(id)}:null);
|
|
|
|
webix.DataLoader.prototype._feed_common.call(this,id, count, callback, url, details);
|
|
},
|
|
//load next set of data rows
|
|
loadBranch:function(id, callback, url){
|
|
id = id ||0;
|
|
this.data.url = url || this.data.url;
|
|
if (this.callEvent("onDataRequest", [id,callback,this.data.url]) && this.data.url)
|
|
this.data.feed.call(this, id, 0, callback, url);
|
|
},
|
|
_sync_hierarchy:function(id, data, mode){
|
|
if (!mode || mode == "add" || mode == "delete" || mode == "branch"){
|
|
this.data._sync_to_order(this);
|
|
}
|
|
}
|
|
};
|
|
|
|
webix.TreeStore = {
|
|
name:"TreeStore",
|
|
$init:function() {
|
|
this._filterMode={
|
|
//level:1,
|
|
showSubItems:true
|
|
};
|
|
this.branch = { 0:[] };
|
|
this.attachEvent("onParse", function(driver, data){
|
|
this._set_child_scheme(driver.child);
|
|
var parent = driver.getInfo(data).parent;
|
|
});
|
|
this.attachEvent("onClearAll", webix.bind(function(){
|
|
this._filter_branch = null;
|
|
},this));
|
|
},
|
|
filterMode_setter:function(mode){
|
|
return webix.extend(this._filterMode, mode, true);
|
|
},
|
|
_filter_reset:function(preserve){
|
|
//remove previous filtering , if any
|
|
if (this._filter_branch && !preserve){
|
|
this.branch = this._filter_branch;
|
|
this.order = webix.toArray(webix.copy(this.branch[0]));
|
|
for (var key in this.branch)
|
|
if (key != "0") //exclude 0 - virtual root
|
|
this.getItem(key).$count = this.branch[key].length;
|
|
delete this._filter_branch;
|
|
}
|
|
},
|
|
_filter_core:function(filter, value, preserve, filterMode){
|
|
//for tree we have few filtering options
|
|
//- filter leafs only
|
|
//- filter data on specific level
|
|
//- filter data on all levels
|
|
//- in all cases we can show or hide empty folder
|
|
//- in all cases we can show or hide childs for matched item
|
|
|
|
//set new order of items, store original
|
|
if (!preserve || !this._filter_branch){
|
|
this._filter_branch = this.branch;
|
|
this.branch = webix.clone(this.branch);
|
|
}
|
|
|
|
this.branch[0] = this._filter_branch_rec(filter, value, this.branch[0], 1, (filterMode||{}));
|
|
},
|
|
_filter_branch_rec:function(filter, value, branch, level, config){
|
|
//jshint -W041
|
|
var neworder = [];
|
|
|
|
var allow = (config.level && config.level != level);
|
|
|
|
for (var i=0; i < branch.length; i++){
|
|
var id = branch[i];
|
|
var item = this.getItem(id);
|
|
var child_run = false;
|
|
var sub = this.branch[id];
|
|
|
|
if (allow){
|
|
child_run = true;
|
|
} else if (filter(this.getItem(id),value)){
|
|
neworder.push(id);
|
|
// open all parents of the found item
|
|
if (config.openParents !== false){
|
|
var parentId = this.getParentId(id);
|
|
while(parentId && parentId != "0"){
|
|
this.getItem(parentId).open = 1;
|
|
parentId = this.getParentId(parentId);
|
|
}
|
|
}
|
|
//in case of of fixed level filtering - do not change child-items
|
|
if (config.level || config.showSubItems)
|
|
continue;
|
|
} else {
|
|
//filtering level, not match
|
|
child_run = true;
|
|
}
|
|
|
|
//if "filter by all levels" - filter childs
|
|
if (allow || !config.level){
|
|
if (sub){
|
|
var newsub = this.branch[id] = this._filter_branch_rec(filter, value, sub, level+1, config);
|
|
item.$count = newsub.length;
|
|
if (child_run && newsub.length)
|
|
neworder.push(id);
|
|
}
|
|
}
|
|
}
|
|
return neworder;
|
|
},
|
|
count:function(){
|
|
if (this.order.length)
|
|
return this.order.length;
|
|
|
|
//we must return some non-zero value, or logic of selection will think that we have not data at all
|
|
var count=0;
|
|
this.eachOpen(function(){ count++; });
|
|
return count;
|
|
},
|
|
_change_branch_id:function(branches, parent, old, newid){
|
|
if (branches[old]){
|
|
var branch = branches[newid] = branches[old];
|
|
for (var i = 0; i < branch.length; i++)
|
|
this.getItem(branch[i]).$parent = newid;
|
|
delete branches[old];
|
|
}
|
|
if (branches[parent]){
|
|
var index = webix.PowerArray.find.call(branches[parent], old);
|
|
if (index >= 0)
|
|
branches[parent][index] = newid;
|
|
}
|
|
},
|
|
changeId:function(old, newid){
|
|
var parent = this.getItem(old).$parent;
|
|
this._change_branch_id(this.branch, parent, old, newid);
|
|
|
|
//in case of filter applied, update id in filtered state as well
|
|
if (this._filter_branch)
|
|
this._change_branch_id(this._filter_branch, parent, old, newid);
|
|
|
|
return webix.DataStore.prototype.changeId.call(this, old, newid);
|
|
},
|
|
clearAll:function(soft){
|
|
this.branch = { 0:[] };
|
|
webix.DataStore.prototype.clearAll.call(this, soft);
|
|
},
|
|
getPrevSiblingId:function(id){
|
|
var order = this.branch[this.getItem(id).$parent];
|
|
var pos = webix.PowerArray.find.call(order, id)-1;
|
|
if (pos>=0)
|
|
return order[pos];
|
|
return null;
|
|
},
|
|
getNextSiblingId:function(id){
|
|
var order = this.branch[this.getItem(id).$parent];
|
|
var pos = webix.PowerArray.find.call(order, id)+1;
|
|
if (pos<order.length)
|
|
return order[pos];
|
|
return null;
|
|
},
|
|
getParentId:function(id){
|
|
return this.getItem(id).$parent;
|
|
},
|
|
getFirstChildId:function(id){
|
|
var order = this.branch[id];
|
|
if (order && order.length)
|
|
return order[0];
|
|
return null;
|
|
},
|
|
isBranch:function(parent){
|
|
return !!this.branch[parent];
|
|
},
|
|
getBranchIndex:function(child){
|
|
var t = this.branch[this.pull[child].$parent];
|
|
return webix.PowerArray.find.call(t, child);
|
|
},
|
|
_set_child_scheme:function(parse_name){
|
|
|
|
if (typeof parse_name == "string")
|
|
this._datadriver_child = function(obj){
|
|
var t = obj[parse_name];
|
|
if (t)
|
|
delete obj[parse_name];
|
|
return t;
|
|
};
|
|
else
|
|
this._datadriver_child = parse_name;
|
|
},
|
|
_inner_parse:function(info, recs){
|
|
var parent = info.parent || 0;
|
|
|
|
for (var i=0; i<recs.length; i++){
|
|
//get hash of details for each record
|
|
var temp = this.driver.getDetails(recs[i]);
|
|
var id = this.id(temp); //generate ID for the record
|
|
var update = !!this.pull[id]; //update mode
|
|
|
|
if (update){
|
|
temp = webix.extend(this.pull[id], temp, true);
|
|
if (this._scheme_update)
|
|
this._scheme_update(temp);
|
|
} else {
|
|
if (this._scheme_init)
|
|
this._scheme_init(temp);
|
|
this.pull[id]=temp;
|
|
}
|
|
|
|
this._extraParser(temp, parent, 0, update, info.from ? info.from*1+i : 0);
|
|
}
|
|
|
|
//fix state of top item after data loading
|
|
var pItem = this.pull[parent] || {};
|
|
var pBranch = this.branch[parent] || [];
|
|
pItem.$count = pBranch.length;
|
|
delete pItem.webix_kids;
|
|
|
|
if (info.size && info.size != pBranch.length)
|
|
pBranch[info.size] = null;
|
|
},
|
|
_extraParser:function(obj, parent, level, update, from){
|
|
//processing top item
|
|
obj.$count = 0;
|
|
//using soft check, as parent can be a both 0 and "0" ( second one in case of loading from server side )
|
|
obj.$parent = parent!="0"?parent:0;
|
|
obj.$level = level||(parent!="0"?this.pull[parent].$level+1:1);
|
|
|
|
var parent_branch = this.branch[obj.$parent];
|
|
if (!parent_branch)
|
|
parent_branch = this.branch[obj.$parent] = [];
|
|
if (this._filter_branch)
|
|
this._filter_branch[obj.$parent] = parent_branch;
|
|
|
|
if (!update){
|
|
var pos = from || parent_branch.length;
|
|
parent_branch[pos] = obj.id;
|
|
}
|
|
|
|
var child = this._datadriver_child(obj);
|
|
|
|
if (obj.webix_kids){
|
|
return (obj.$count = -1);
|
|
}
|
|
|
|
if (!child) //ignore childless
|
|
return (obj.$count = 0);
|
|
|
|
//when loading from xml we can have a single item instead of an array
|
|
if (!webix.isArray(child))
|
|
child = [child];
|
|
|
|
|
|
for (var i=0; i < child.length; i++) {
|
|
//extra processing to convert strings to objects
|
|
var item = webix.DataDriver.json.getDetails(child[i]);
|
|
var itemid = this.id(item);
|
|
update = !!this.pull[itemid];
|
|
|
|
if (update){
|
|
item = webix.extend(this.pull[itemid], item, true);
|
|
if (this._scheme_update)
|
|
this._scheme_update(item);
|
|
} else {
|
|
if (this._scheme_init)
|
|
this._scheme_init(item);
|
|
this.pull[itemid]=item;
|
|
}
|
|
this._extraParser(item, obj.id, obj.$level+1, update);
|
|
}
|
|
|
|
//processing childrens
|
|
var branch = this.branch[obj.id];
|
|
if (branch)
|
|
obj.$count = branch.length;
|
|
},
|
|
_sync_to_order:function(master){
|
|
this.order = webix.toArray();
|
|
this._sync_each_child(0, master);
|
|
},
|
|
_sync_each_child:function(start, master){
|
|
var branch = this.branch[start];
|
|
for (var i=0; i<branch.length; i++){
|
|
var id = branch[i];
|
|
this.order.push(id);
|
|
var item = this.pull[id];
|
|
if (item){
|
|
if (item.open){
|
|
if (item.$count == -1)
|
|
master.loadBranch(id);
|
|
else if (item.$count)
|
|
this._sync_each_child(id, master);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
provideApi:function(target,eventable){
|
|
var list = ["getPrevSiblingId","getNextSiblingId","getParentId","getFirstChildId","isBranch","getBranchIndex","filterMode_setter"];
|
|
for (var i=0; i < list.length; i++)
|
|
target[list[i]]=this._methodPush(this,list[i]);
|
|
|
|
if (!target.getIndexById)
|
|
webix.DataStore.prototype.provideApi.call(this, target, eventable);
|
|
},
|
|
getTopRange:function(){
|
|
return webix.toArray([].concat(this.branch[0])).map(function(id){
|
|
return this.getItem(id);
|
|
}, this);
|
|
},
|
|
eachChild:function(id, functor, master, all){
|
|
var branch = this.branch;
|
|
if (all && this._filter_branch)
|
|
branch = this._filter_branch;
|
|
|
|
var stack = branch[id];
|
|
if (stack)
|
|
for (var i=0; i<stack.length; i++)
|
|
functor.call((master||this), this.getItem(stack[i]));
|
|
},
|
|
each:function(method,master, all, id){
|
|
this.eachChild((id||0), function(item){
|
|
var branch = this.branch;
|
|
|
|
method.call((master||this), item);
|
|
|
|
if (all && this._filter_branch)
|
|
branch = this._filter_branch;
|
|
|
|
if (item && branch[item.id])
|
|
this.each(method, master, all, item.id);
|
|
}, this, all);
|
|
},
|
|
eachOpen:function(method,master, id){
|
|
this.eachChild((id||0), function(item){
|
|
method.call((master||this), item);
|
|
if (this.branch[item.id] && item.open)
|
|
this.eachOpen(method, master, item.id);
|
|
});
|
|
},
|
|
eachSubItem:function(id, functor){
|
|
var top = this.branch[id||0];
|
|
if (top)
|
|
for (var i=0; i<top.length; i++){
|
|
var key = top[i];
|
|
if (this.branch[key]){
|
|
functor.call(this, this.getItem(key),true);
|
|
this.eachSubItem(key, functor);
|
|
} else
|
|
functor.call(this, this.getItem(key), false);
|
|
}
|
|
},
|
|
eachLeaf:function(id, functor){
|
|
var top = this.branch[id||0];
|
|
if (top)
|
|
for (var i=0; i<top.length; i++){
|
|
var key = top[i];
|
|
if (this.branch[key]){
|
|
this.eachLeaf(key, functor);
|
|
} else
|
|
functor.call(this, this.getItem(key), false);
|
|
}
|
|
},
|
|
_sort_core:function(sort, order){
|
|
var sorter = this.sorting.create(sort);
|
|
for (var key in this.branch){
|
|
var bset = this.branch[key];
|
|
var data = [];
|
|
|
|
for (var i=0; i<bset.length; i++)
|
|
data.push(this.pull[bset[i]]);
|
|
|
|
data.sort(sorter);
|
|
|
|
for (var i=0; i<bset.length; i++)
|
|
data[i] = data[i].id;
|
|
|
|
this.branch[key] = data;
|
|
}
|
|
return order;
|
|
},
|
|
add:function(obj, index, pid){
|
|
var refresh_parent = false;
|
|
|
|
var parent = this.getItem(pid||0);
|
|
if(parent){
|
|
//when adding items to leaf item - it need to be repainted
|
|
if (!this.branch[parent.id])
|
|
refresh_parent = true;
|
|
|
|
parent.$count++;
|
|
//fix for the adding into dynamic loading branch
|
|
//dynamic branch has $count as -1
|
|
if (!parent.$count) parent.$count = 1;
|
|
}
|
|
|
|
this.branch[pid||0] = this.order = webix.toArray(this.branch[pid||0]);
|
|
|
|
obj.$count = 0;
|
|
obj.$level= (parent?parent.$level+1:1);
|
|
obj.$parent = (parent?parent.id:0);
|
|
|
|
if (this._filter_branch){ //adding during filtering
|
|
var origin = this._filter_branch[pid||0];
|
|
//newly created branch
|
|
if (!origin) origin = this._filter_branch[pid] = this.order;
|
|
|
|
//branch can be shared bettwen collections, ignore such cases
|
|
if (this.order !== origin){
|
|
//we can't know the location of new item in full dataset, making suggestion
|
|
//put at end by default
|
|
var original_index = origin.length;
|
|
//put at start only if adding to the start and some data exists
|
|
if (!index && this.branch[pid||0].length)
|
|
original_index = 0;
|
|
|
|
origin = webix.toArray(origin);
|
|
obj.id = obj.id || webix.uid();
|
|
origin.insertAt(obj.id,original_index);
|
|
}
|
|
}
|
|
|
|
//call original adding logic
|
|
var result = webix.DataStore.prototype.add.call(this, obj, index);
|
|
|
|
|
|
if (refresh_parent)
|
|
this.refresh(pid);
|
|
|
|
return result;
|
|
},
|
|
_rec_remove:function(id, inner){
|
|
var obj = this.pull[id];
|
|
if(this.branch[obj.id] && this.branch[obj.id].length > 0){
|
|
var branch = this.branch[id];
|
|
for(var i=0;i<branch.length;i++)
|
|
this._rec_remove(branch[i], true);
|
|
}
|
|
delete this.branch[id];
|
|
if(this._filter_branch)
|
|
delete this._filter_branch[id];
|
|
delete this.pull[id];
|
|
if (this._marks[id])
|
|
delete this._marks[id];
|
|
},
|
|
_filter_removed:function(pull, parentId, id){
|
|
var branch = pull[parentId];
|
|
if (branch.length == 1 && branch[0] == id && parentId){
|
|
delete pull[parentId];
|
|
} else
|
|
webix.toArray(branch).remove(id);
|
|
},
|
|
remove:function(id){
|
|
//id can be an array of IDs - result of getSelect, for example
|
|
if (webix.isArray(id)){
|
|
for (var i=0; i < id.length; i++)
|
|
this.remove(id[i]);
|
|
return;
|
|
}
|
|
|
|
webix.assert(this.exists(id), "Not existing ID in remove command"+id);
|
|
var obj = this.pull[id];
|
|
var parentId = (obj.$parent||0);
|
|
|
|
if (this.callEvent("onBeforeDelete",[id]) === false) return false;
|
|
this._rec_remove(id);
|
|
this.callEvent("onAfterDelete",[id]);
|
|
|
|
var parent = this.pull[parentId];
|
|
this._filter_removed(this.branch, parentId, id);
|
|
if (this._filter_branch)
|
|
this._filter_removed(this._filter_branch, parentId, id);
|
|
|
|
var refresh_parent = 0;
|
|
if (parent){
|
|
parent.$count--;
|
|
if (parent.$count<=0){
|
|
parent.$count=0;
|
|
parent.open = 0;
|
|
refresh_parent = 1;
|
|
}
|
|
}
|
|
|
|
//repaint signal
|
|
this.callEvent("onStoreUpdated",[id,obj,"delete"]);
|
|
if (refresh_parent)
|
|
this.refresh(parent.id);
|
|
},
|
|
/*
|
|
serializes data to a json object
|
|
*/
|
|
getBranch:function(id){
|
|
var out = [];
|
|
var items = (this._filter_branch || this.branch)[id];
|
|
if (items)
|
|
for (var i = 0; i < items.length; i++) out[i] = this.pull[items[i]];
|
|
|
|
return out;
|
|
},
|
|
serialize: function(id, all){
|
|
var coll = this.branch;
|
|
//use original collection of branches
|
|
if (all && this._filter_branch) coll = this._filter_branch;
|
|
|
|
var ids = this.branch[id||0];
|
|
var result = [];
|
|
for(var i=0; i< ids.length;i++) {
|
|
var obj = this.pull[ids[i]];
|
|
var rel;
|
|
|
|
if (this._scheme_serialize){
|
|
rel = this._scheme_serialize(obj);
|
|
if (rel===false) continue;
|
|
} else
|
|
rel = webix.copy(obj);
|
|
|
|
if (this.branch[obj.id])
|
|
rel.data = this.serialize(obj.id, all);
|
|
|
|
result.push(rel);
|
|
}
|
|
return result;
|
|
}
|
|
};
|
|
|
|
|
|
webix.TreeType={
|
|
space:function(obj,common){
|
|
var html = "";
|
|
for (var i=1; i<obj.$level; i++)
|
|
html += "<div class='webix_tree_none'></div>";
|
|
return html;
|
|
},
|
|
icon:function(obj,common){
|
|
if (obj.$count){
|
|
if (obj.open)
|
|
return "<div class='webix_tree_open'></div>";
|
|
else
|
|
return "<div class='webix_tree_close'></div>";
|
|
} else
|
|
return "<div class='webix_tree_none'></div>";
|
|
},
|
|
checkbox:function(obj, common){
|
|
if(obj.nocheckbox)
|
|
return "";
|
|
return "<input type='checkbox' class='webix_tree_checkbox' "+(obj.checked?"checked":"")+(obj.disabled?" disabled":"")+">";
|
|
},
|
|
folder:function(obj, common){
|
|
if (obj.icon)
|
|
return "<div class='webix_tree_file webix_tree_"+obj.icon+"'></div>";
|
|
|
|
if (obj.$count){
|
|
if (obj.open)
|
|
return "<div class='webix_tree_folder_open'></div>";
|
|
else
|
|
return "<div class='webix_tree_folder'></div>";
|
|
}
|
|
return "<div class='webix_tree_file'></div>";
|
|
}
|
|
};
|
|
|
|
webix.TreeAPI = {
|
|
open: function(id, show) {
|
|
if (!id) return;
|
|
//ignore open for leaf items
|
|
var item = this.getItem(id);
|
|
if (!item.$count || item.open) return;
|
|
|
|
if (this.callEvent("onBeforeOpen",[id])){
|
|
item.open=true;
|
|
this.data.callEvent("onStoreUpdated",[id, 0, "branch"]);
|
|
this.callEvent("onAfterOpen",[id]);
|
|
}
|
|
|
|
if (show && id != "0")
|
|
this.open(this.getParentId(id), show);
|
|
},
|
|
close: function(id) {
|
|
if (!id) return;
|
|
var item = this.getItem(id);
|
|
if (!item.open) return;
|
|
|
|
if (this.callEvent("onBeforeClose",[id])){
|
|
item.open=false;
|
|
this.data.callEvent("onStoreUpdated",[id, 0, "branch"]);
|
|
this.callEvent("onAfterClose",[id]);
|
|
}
|
|
},
|
|
openAll: function(id){
|
|
this.data.eachSubItem((id||0), function(obj, branch){
|
|
if (branch)
|
|
obj.open = true;
|
|
});
|
|
this.data.refresh();
|
|
},
|
|
closeAll: function(id){
|
|
this.data.eachSubItem((id||0), function(obj, branch){
|
|
if (branch)
|
|
obj.open = false;
|
|
});
|
|
this.data.refresh();
|
|
},
|
|
_tree_check_uncheck:function(id,mode,e){
|
|
if(this._settings.threeState)
|
|
return this._tree_check_uncheck_3(id,(mode !== null?mode:""));
|
|
|
|
var value,
|
|
item = this.getItem(id),
|
|
trg = (e? (e.target|| e.srcElement):null);
|
|
|
|
//read actual value from HTML tag when possible
|
|
//as it can be affected by dbl-clicks
|
|
if(trg && trg.type == "checkbox")
|
|
value = trg.checked?true:false;
|
|
else
|
|
value = (mode !== null?mode:!item.checked);
|
|
|
|
item.checked = value;
|
|
this.callEvent("onItemCheck", [id, item.checked, e]);
|
|
},
|
|
isBranchOpen:function(search_id){
|
|
if (search_id == "0") return true;
|
|
|
|
var item = this.getItem(search_id);
|
|
if (item.open)
|
|
return this.isBranchOpen(item.$parent);
|
|
return false;
|
|
},
|
|
getOpenItems: function() {
|
|
var open = [];
|
|
for (var id in this.data.branch) {
|
|
if (this.exists(id) && this.getItem(id).open)
|
|
open.push(id);
|
|
}
|
|
return open;
|
|
},
|
|
getState: function(){
|
|
return {
|
|
open: this.getOpenItems(),
|
|
select: this.getSelectedId(true)
|
|
};
|
|
},
|
|
_repeat_set_state:function(tree, open){
|
|
var event = this.data.attachEvent("onStoreLoad", function(){
|
|
tree.setState.call(tree,open);
|
|
tree.data.detachEvent(event);
|
|
tree = null;
|
|
});
|
|
},
|
|
setState: function(state){
|
|
var repeat = false;
|
|
var dyn = false;
|
|
|
|
if (state.open){
|
|
this.closeAll();
|
|
var open = state.open;
|
|
for (var i = 0; i < open.length; i++){
|
|
var item = this.getItem(open[i]);
|
|
if (item && item.$count){
|
|
item.open=true;
|
|
//dynamic loading
|
|
if (item.$count == -1){
|
|
//call the same method after data loading
|
|
this._repeat_set_state(this, state);
|
|
this.refresh();
|
|
return 0;
|
|
//end processing
|
|
}
|
|
}
|
|
}
|
|
this.refresh();
|
|
}
|
|
|
|
|
|
if (state.select && this.select){
|
|
var select = state.select;
|
|
this.unselect();
|
|
for (var i = 0; i < select.length; i++)
|
|
if (this.exists(select[i]))
|
|
this.select(select[i], true);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
};
|
|
|
|
webix.TreeClick = {
|
|
webix_tree_open:function(e, id){
|
|
this.close(id);
|
|
return false;
|
|
},
|
|
webix_tree_close:function(e, id){
|
|
this.open(id);
|
|
return false;
|
|
},
|
|
webix_tree_checkbox:function(e,id){
|
|
this._tree_check_uncheck(id, null, e);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
webix.TreeCollection = webix.proto({
|
|
name:"TreeCollection",
|
|
$init:function(){
|
|
webix.extend(this.data, webix.TreeStore, true);
|
|
this.data.provideApi(this,true);
|
|
webix.extend(this, webix.TreeDataMove, true);
|
|
}
|
|
}, webix.TreeDataLoader, webix.DataCollection);
|
|
|
|
/*
|
|
Behavior:DragItem - adds ability to move items by dnd
|
|
|
|
dnd context can have next properties
|
|
from - source object
|
|
to - target object
|
|
source - id of dragged item(s)
|
|
target - id of drop target, null for drop on empty space
|
|
start - id from which DND was started
|
|
*/
|
|
|
|
|
|
|
|
|
|
webix.AutoScroll = {
|
|
_auto_scroll:function(pos, id){
|
|
var yscroll = 1;
|
|
var xscroll = 0;
|
|
|
|
var scroll = this._settings.dragscroll;
|
|
if (typeof scroll == "string"){
|
|
xscroll = scroll.indexOf("x") != -1;
|
|
yscroll = scroll.indexOf("y") != -1;
|
|
}
|
|
|
|
var data = this._body || this.$view;
|
|
var box = webix.html.offset(data);
|
|
|
|
var top = box.y;
|
|
var bottom = top + data.offsetHeight;
|
|
var left = box.x;
|
|
var right = left + data.offsetWidth;
|
|
|
|
var scroll = this.getScrollState();
|
|
var reset = false;
|
|
var sense = Math.max(this.type&&!isNaN(parseFloat(this.type.height))?this.type.height+5:0,40); //dnd auto-scroll sensivity
|
|
|
|
if (yscroll){
|
|
var config = this._settings;
|
|
if(config.topSplit){
|
|
var topSplitPos = this._cellPosition(this.getIdByIndex(config.topSplit-1), this.columnId(0));
|
|
top += topSplitPos.top + topSplitPos.height;
|
|
}
|
|
|
|
if (pos.y < (top + sense)){
|
|
this._auto_scrollTo(scroll.x, scroll.y-sense*2, pos);
|
|
reset = true;
|
|
} else if (pos.y > bottom - sense){
|
|
this._auto_scrollTo(scroll.x, scroll.y+sense*2, pos);
|
|
reset = true;
|
|
}
|
|
}
|
|
|
|
if (xscroll){
|
|
if (pos.x < (left + sense)){
|
|
this._auto_scrollTo(scroll.x-sense*2, scroll.y, pos);
|
|
reset = true;
|
|
} else if (pos.x > right - sense){
|
|
this._auto_scrollTo(scroll.x+sense*2, scroll.y, pos);
|
|
reset = true;
|
|
}
|
|
}
|
|
|
|
if (reset)
|
|
this._auto_scroll_delay = webix.delay(this._auto_scroll, this, [pos], 100);
|
|
|
|
},
|
|
_auto_scrollTo: function(x,y,pos){
|
|
if(this.callEvent("onBeforeAutoScroll",[pos]))
|
|
this.scrollTo(x,y);
|
|
}
|
|
};
|
|
|
|
webix.DragOrder={
|
|
_do_not_drag_selection:true,
|
|
$drag:function(s,e){
|
|
var html = webix.DragItem.$drag.call(this,s,e);
|
|
if (html){
|
|
var context = webix.DragControl.getContext();
|
|
if (this.getBranchIndex)
|
|
this._drag_order_stored_left = this._drag_order_complex?((this.getItem(context.start).$level) * 16):0;
|
|
if (!context.fragile)
|
|
this.addCss(context.start, "webix_transparent");
|
|
}
|
|
return html;
|
|
},
|
|
_getDragItemPos: function(pos,e){
|
|
return webix.DragItem._getDragItemPos(pos,e);
|
|
},
|
|
$dragPos:function(pos,e, node){
|
|
var box = webix.html.offset(this.$view);
|
|
var left = box.x + (this._drag_order_complex?( 1+this._drag_order_stored_left):1);
|
|
var top = pos.y;
|
|
var config = this._settings;
|
|
var xdrag = (config.layout == "x");
|
|
|
|
if (xdrag){
|
|
top = box.y + (this._drag_order_complex?( + box.height - webix.ui.scrollSize - 1):1);
|
|
left = pos.x;
|
|
}
|
|
|
|
node.style.display = 'none';
|
|
|
|
var html = document.elementFromPoint(left, top);
|
|
|
|
if (html != this._last_sort_dnd_node){
|
|
var view = webix.$$(html);
|
|
//this type of dnd is limited to the self
|
|
if (view && view == this){
|
|
var id = this.locate(html, true);
|
|
// sometimes 'mousedown' on item is followed by 'mousemove' on empty area and item caanot be located
|
|
if(!id && webix.DragControl._saved_event)
|
|
id = this.locate(webix.DragControl._saved_event, true);
|
|
|
|
var start_id = webix.DragControl.getContext().start;
|
|
this._auto_scroll_force = true;
|
|
if (id){
|
|
|
|
if (id != this._last_sort_dnd_node){
|
|
if (id != start_id){
|
|
var details, index;
|
|
|
|
if (this.getBranchIndex){
|
|
details = { parent:this.getParentId(id) };
|
|
index = this.getBranchIndex(id);
|
|
} else {
|
|
details = {};
|
|
index = this.getIndexById(id);
|
|
}
|
|
|
|
if (this.callEvent("onBeforeDropOrder",[start_id, index, e, details])){
|
|
this.move(start_id, index, this, details);
|
|
this._last_sort_dnd_node = id;
|
|
}
|
|
}
|
|
webix.DragControl._last = this._contentobj;
|
|
}
|
|
}
|
|
else {
|
|
id = "$webix-last";
|
|
if (this._last_sort_dnd_node != id){
|
|
if (!this.callEvent("onBeforeDropOrder",[start_id, -1, e, { parent: 0} ])) return;
|
|
this._last_sort_dnd_node = id;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
node.style.display = 'block';
|
|
|
|
|
|
if (xdrag){
|
|
pos.y = box.y;
|
|
pos.x = pos.x-18;
|
|
|
|
if (pos.x < box.x)
|
|
pos.x = box.x;
|
|
else {
|
|
var max = box.x + this.$view.offsetWidth - 60;
|
|
if (pos.x > max)
|
|
pos.x = max;
|
|
}
|
|
} else {
|
|
box.y += this._header_height;
|
|
pos.x = this._drag_order_stored_left||box.x;
|
|
pos.y = pos.y-18;
|
|
|
|
if (pos.y < box.y)
|
|
pos.y = box.y;
|
|
else {
|
|
var max = box.y + this.$view.offsetHeight - 60;
|
|
if (pos.y > max)
|
|
pos.y = max;
|
|
}
|
|
}
|
|
|
|
if (this._auto_scroll_delay)
|
|
this._auto_scroll_delay = window.clearTimeout(this._auto_scroll_delay);
|
|
|
|
this._auto_scroll_delay = webix.delay(this._auto_scroll, this, [webix.html.pos(e), this.locate(e) || null],250);
|
|
|
|
//prevent normal dnd landing checking
|
|
webix.DragControl._skip = true;
|
|
},
|
|
$dragIn:function(){
|
|
return false;
|
|
},
|
|
$drop:function(s,t,e){
|
|
if (this._auto_scroll_delay){
|
|
this._auto_scroll_force = null;
|
|
this._auto_scroll_delay = window.clearTimeout(this._auto_scroll_delay);
|
|
}
|
|
|
|
var context = webix.DragControl.getContext();
|
|
var id = context.start;
|
|
this.removeCss(id, "webix_transparent");
|
|
|
|
var index = this.getIndexById(id);
|
|
this.callEvent("onAfterDropOrder",[id, index , e]);
|
|
if (context.fragile)
|
|
this.refresh();
|
|
}
|
|
};
|
|
webix.DragItem={
|
|
//helper - defines component's container as active zone for dragging and for dropping
|
|
_initHandlers:function(obj, source, target){
|
|
if (!source) webix.DragControl.addDrop(obj._contentobj,obj,true);
|
|
if (!target) webix.DragControl.addDrag(obj._contentobj,obj);
|
|
this.attachEvent("onDragOut",function(a,b){ this.$dragMark(a,b); });
|
|
this.attachEvent("onBeforeAutoScroll",function(){
|
|
var context = webix.DragControl.getContext();
|
|
return !!(webix.DragControl._active && context && (context.to === this || this._auto_scroll_force));
|
|
});
|
|
},
|
|
drag_setter:function(value){
|
|
if (value){
|
|
webix.extend(this, webix.AutoScroll, true);
|
|
if (value == "order")
|
|
webix.extend(this, webix.DragOrder, true);
|
|
if (value == "inner")
|
|
this._inner_drag_only = true;
|
|
|
|
this._initHandlers(this, value == "source", value == "target");
|
|
delete this.drag_setter; //prevent double initialization
|
|
}
|
|
return value;
|
|
},
|
|
/*
|
|
s - source html element
|
|
t - target html element
|
|
d - drop-on html element ( can be not equal to the target )
|
|
e - native html event
|
|
*/
|
|
//called when drag moved over possible target
|
|
$dragIn:function(s,t,e){
|
|
var id = this.locate(e) || null;
|
|
var context = webix.DragControl._drag_context;
|
|
|
|
//in inner drag mode - ignore dnd from other components
|
|
if ((this._inner_drag_only || context.from._inner_drag_only) && context.from !== this) return false;
|
|
|
|
var to = webix.DragControl.getMaster(t);
|
|
//previous target
|
|
var html = (this.getItemNode(id, e)||this._dataobj);
|
|
//prevent double processing of same target
|
|
if (html == webix.DragControl._landing) return html;
|
|
context.target = id;
|
|
context.to = to;
|
|
|
|
if (this._auto_scroll_delay)
|
|
this._auto_scroll_delay = window.clearTimeout(this._auto_scroll_delay);
|
|
|
|
this._auto_scroll_delay = webix.delay(function(pos,id){
|
|
this._drag_pause(id);
|
|
this._auto_scroll(pos,id);
|
|
}, this, [webix.html.pos(e), id], 250);
|
|
|
|
if (!this.$dropAllow(context, e) || !this.callEvent("onBeforeDragIn",[context, e])){
|
|
context.to = context.target = null;
|
|
if (this._auto_scroll_delay)
|
|
this._auto_scroll_delay = window.clearTimeout(this._auto_scroll_delay);
|
|
return null;
|
|
}
|
|
//mark target only when landing confirmed
|
|
this.$dragMark(context,e);
|
|
return html;
|
|
},
|
|
$dropAllow:function(){
|
|
return true;
|
|
},
|
|
_drag_pause:function(id){
|
|
//may be reimplemented in some components
|
|
// tree for example
|
|
},
|
|
_target_to_id:function(target){
|
|
return target && typeof target === "object" ? target.toString() : target;
|
|
},
|
|
//called when drag moved out from possible target
|
|
$dragOut:function(s,t,n,e){
|
|
var id = (this._viewobj.contains(n) ? this.locate(e): null) || null;
|
|
var context = webix.DragControl._drag_context;
|
|
|
|
//still over previous target
|
|
if ((context.target||"").toString() == (id||"").toString()) return null;
|
|
if (this._auto_scroll_delay){
|
|
this._auto_scroll_force = null;
|
|
this._auto_scroll_delay = window.clearTimeout(this._auto_scroll_delay);
|
|
}
|
|
|
|
//unmark previous target
|
|
context.target = context.to = null;
|
|
this.callEvent("onDragOut",[context,e]);
|
|
return null;
|
|
},
|
|
//called when drag moved on target and button is released
|
|
$drop:function(s,t,e){
|
|
if (this._auto_scroll_delay)
|
|
this._auto_scroll_delay = window.clearTimeout(this._auto_scroll_delay);
|
|
|
|
var context = webix.DragControl._drag_context;
|
|
//finalize context details
|
|
context.to = this;
|
|
var target = this._target_to_id(context.target);
|
|
|
|
if (this.getBranchIndex){
|
|
if (target){
|
|
context.parent = this.getParentId(target);
|
|
context.index = this.getBranchIndex(target);
|
|
}
|
|
} else
|
|
context.index = target?this.getIndexById(target):this.count();
|
|
|
|
//unmark last target
|
|
this.$dragMark({}, e);
|
|
|
|
if( context.from && context.from != context.to && context.from.callEvent ){
|
|
context.from.callEvent("onBeforeDropOut", [context,e]);
|
|
}
|
|
|
|
if (!this.callEvent("onBeforeDrop",[context,e])) return;
|
|
//moving
|
|
this._context_to_move(context,e);
|
|
|
|
this.callEvent("onAfterDrop",[context,e]);
|
|
},
|
|
_context_to_move:function(context,e){
|
|
webix.assert(context.from, "Unsopported d-n-d combination");
|
|
if (context.from){ //from different component
|
|
var details = { parent: context.parent, mode: context.pos };
|
|
context.from.move(context.source,context.index,context.to, details);
|
|
}
|
|
},
|
|
_getDragItemPos: function(pos,e){
|
|
if (this.getItemNode){
|
|
var id = this.locate(e, true);
|
|
//in some case, node may be outiside of dom ( spans in datatable for example )
|
|
//so getItemNode can return null
|
|
var node = id ? this.getItemNode(id) : null;
|
|
return node ? webix.html.offset(node) : node;
|
|
}
|
|
},
|
|
//called when drag action started
|
|
$drag:function(s,e){
|
|
var id = this.locate(e, true);
|
|
if (id){
|
|
var list = [id];
|
|
|
|
if (this.getSelectedId && !this._do_not_drag_selection){ //has selection model
|
|
//if dragged item is one of selected - drag all selected
|
|
var selection = this.getSelectedId(true, true);
|
|
|
|
if (selection && selection.length > 1 && webix.PowerArray.find.call(selection,id)!=-1){
|
|
var hash = {};
|
|
var list = [];
|
|
for (var i=0;i<selection.length; i++)
|
|
hash[selection[i]]=true;
|
|
for (var i = 0; i<this.data.order.length; i++){
|
|
var hash_id = this.data.order[i];
|
|
if (hash[hash_id])
|
|
list.push(hash_id);
|
|
}
|
|
}
|
|
}
|
|
//save initial dnd params
|
|
var context = webix.DragControl._drag_context= { source:list, start:id };
|
|
context.fragile = (this.addRowCss && webix.env.touch && ( webix.env.isWebKit || webix.env.isFF ));
|
|
context.from = this;
|
|
|
|
if (this.callEvent("onBeforeDrag",[context,e])){
|
|
if (webix.Touch)
|
|
webix.Touch._start_context = null;
|
|
|
|
//set drag representation
|
|
return context.html||this.$dragHTML(this.getItem(id), e);
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
$dragHTML:function(obj, e){
|
|
return this._toHTML(obj);
|
|
},
|
|
$dragMark:function(context, ev){
|
|
var target = null;
|
|
if (context.target)
|
|
target = this._target_to_id(context.target);
|
|
|
|
//touch webkit will stop touchmove event if source node removed
|
|
//datatable can't repaint rows without repainting
|
|
if (this._marked && this._marked != target){
|
|
if (!context.fragile) this.removeCss(this._marked, "webix_drag_over");
|
|
this._marked = null;
|
|
}
|
|
|
|
if (!this._marked && target){
|
|
this._marked = target;
|
|
if (!context.fragile) this.addCss(target, "webix_drag_over");
|
|
return target;
|
|
}
|
|
|
|
if (context.to){
|
|
return true;
|
|
}else
|
|
return false;
|
|
}
|
|
};
|
|
|
|
|
|
|
|
webix.Group = {
|
|
$init:function(){
|
|
webix.extend(this.data, webix.GroupStore);
|
|
//in case of plain store we need to remove store original dataset
|
|
this.data.attachEvent("onClearAll",webix.bind(function(){
|
|
this.data._not_grouped_order = this.data._not_grouped_pull = null;
|
|
this._group_level_count = 0;
|
|
},this));
|
|
},
|
|
group:function(config){
|
|
this.data.ungroup(true);
|
|
this.data.group(config);
|
|
},
|
|
ungroup:function(skipRender){
|
|
this.data.ungroup(skipRender);
|
|
}
|
|
};
|
|
|
|
webix.GroupMethods = {
|
|
sum:function(property, data){
|
|
data = data || this;
|
|
var summ = 0;
|
|
for (var i = 0; i < data.length; i++)
|
|
summ+=property(data[i])*1;
|
|
|
|
return summ;
|
|
},
|
|
min:function(property, data){
|
|
data = data || this;
|
|
var min = Infinity;
|
|
|
|
for (var i = 0; i < data.length; i++)
|
|
if (property(data[i])*1 < min) min = property(data[i])*1;
|
|
|
|
return min*1;
|
|
},
|
|
max:function(property, data){
|
|
data = data || this;
|
|
var max = -Infinity;
|
|
|
|
for (var i = 0; i < data.length; i++)
|
|
if (property(data[i])*1 > max) max = property(data[i])*1;
|
|
|
|
return max*1;
|
|
},
|
|
count:function(property, data){
|
|
var count = 0;
|
|
for (var i = 0; i < data.length; i++) {
|
|
var some = property(data[i]);
|
|
if (some !== null && typeof some !== "undefined")
|
|
count++;
|
|
}
|
|
return count;
|
|
},
|
|
any:function(property, data){
|
|
return property(data[0]);
|
|
},
|
|
string:function(property, data){
|
|
return property.$name;
|
|
}
|
|
};
|
|
|
|
webix.GroupStore = {
|
|
$init:function(){
|
|
this.attachEvent("onClearAll", this._reset_groups);
|
|
},
|
|
_reset_groups:function(){
|
|
this._not_grouped_order = this._not_grouped_pull = null;
|
|
this._group_level_count = 0;
|
|
},
|
|
ungroup:function(skipRender){
|
|
if (this.getBranchIndex)
|
|
return this._ungroup_tree.apply(this, arguments);
|
|
|
|
if (this._not_grouped_order){
|
|
this.order = this._not_grouped_order;
|
|
this.pull = this._not_grouped_pull;
|
|
this._not_grouped_pull = this._not_grouped_order = null;
|
|
if(!skipRender)
|
|
this.callEvent("onStoreUpdated",[]);
|
|
}
|
|
|
|
},
|
|
_group_processing:function(scheme){
|
|
this.blockEvent();
|
|
this.group(scheme);
|
|
this.unblockEvent();
|
|
},
|
|
_group_prop_accessor:function(val){
|
|
if (typeof val == "function")
|
|
return val;
|
|
var acc = function(obj){ return obj[val]; };
|
|
acc.$name = val;
|
|
return acc;
|
|
},
|
|
group:function(stats){
|
|
if (this.getBranchIndex)
|
|
return this._group_tree.apply(this, arguments);
|
|
|
|
var key = this._group_prop_accessor(stats.by);
|
|
if (!stats.map[key])
|
|
stats.map[key] = [key, this._any];
|
|
|
|
var groups = {};
|
|
var labels = [];
|
|
this.each(function(data){
|
|
var current = key(data);
|
|
if (!groups[current]){
|
|
labels.push({ id:current, $group:true, $row:stats.row });
|
|
groups[current] = webix.toArray();
|
|
}
|
|
groups[current].push(data);
|
|
});
|
|
for (var prop in stats.map){
|
|
var functor = (stats.map[prop][1]||"any");
|
|
var property = this._group_prop_accessor(stats.map[prop][0]);
|
|
if (typeof functor != "function"){
|
|
webix.assert(webix.GroupMethods[functor], "unknown grouping rule: "+functor);
|
|
functor = webix.GroupMethods[functor];
|
|
}
|
|
|
|
for (var i=0; i < labels.length; i++) {
|
|
labels[i][prop]=functor.call(this, property, groups[labels[i].id]);
|
|
}
|
|
}
|
|
|
|
this._not_grouped_order = this.order;
|
|
this._not_grouped_pull = this.pull;
|
|
|
|
this.order = webix.toArray();
|
|
this.pull = {};
|
|
for (var i=0; i < labels.length; i++){
|
|
var id = this.id(labels[i]);
|
|
this.pull[id] = labels[i];
|
|
this.order.push(id);
|
|
if (this._scheme_init)
|
|
this._scheme_init(labels[i]);
|
|
}
|
|
|
|
this.callEvent("onStoreUpdated",[]);
|
|
},
|
|
_group_tree:function(input, parent){
|
|
this._group_level_count = (this._group_level_count||0) + 1;
|
|
|
|
//supports simplified group by syntax
|
|
var stats;
|
|
if (typeof input == "string"){
|
|
stats = { by:this._group_prop_accessor(input), map:{} };
|
|
stats.map[input] = [input];
|
|
} else if (typeof input == "function"){
|
|
stats = { by:input, map:{} };
|
|
} else
|
|
stats = input;
|
|
|
|
//prepare
|
|
var level;
|
|
if (parent)
|
|
level = this.getItem(parent).$level;
|
|
else {
|
|
parent = 0;
|
|
level = 0;
|
|
}
|
|
|
|
var order = this.branch[parent];
|
|
var key = this._group_prop_accessor(stats.by);
|
|
|
|
//run
|
|
var topbranch = [];
|
|
var labels = [];
|
|
for (var i=0; i<order.length; i++){
|
|
var data = this.getItem(order[i]);
|
|
var current = key(data);
|
|
var current_id = level+"$"+current;
|
|
var ancestor = this.branch[current_id];
|
|
|
|
if (!ancestor){
|
|
var newitem = this.pull[current_id] = { id:current_id, value:current, $group:true, $row:stats.row};
|
|
if (this._scheme_init)
|
|
this._scheme_init(newitem);
|
|
labels.push(newitem);
|
|
ancestor = this.branch[current_id] = [];
|
|
ancestor._formath = [];
|
|
topbranch.push(current_id);
|
|
}
|
|
ancestor.push(data.id);
|
|
ancestor._formath.push(data);
|
|
}
|
|
|
|
this.branch[parent] = topbranch;
|
|
for (var prop in stats.map){
|
|
var functor = (stats.map[prop][1]||"any");
|
|
var property = this._group_prop_accessor(stats.map[prop][0]);
|
|
if (typeof functor != "function"){
|
|
webix.assert(webix.GroupMethods[functor], "unknown grouping rule: "+functor);
|
|
functor = webix.GroupMethods[functor];
|
|
}
|
|
|
|
for (var i=0; i < labels.length; i++)
|
|
labels[i][prop]=functor.call(this, property, this.branch[labels[i].id]._formath);
|
|
}
|
|
|
|
for (var i=0; i < labels.length; i++){
|
|
var group = labels[i];
|
|
|
|
if (this.hasEvent("onGroupCreated"))
|
|
this.callEvent("onGroupCreated", [group.id, group.value, this.branch[group.id]._formath]);
|
|
|
|
if (stats.footer){
|
|
var id = "footer$"+group.id;
|
|
var footer = this.pull[id] = { id:id, $footer:true, value: group.value, $level:level, $count:0, $parent:group.id, $row:stats.footer.row};
|
|
for (var prop in stats.footer){
|
|
var functor = (stats.footer[prop][1]||"any");
|
|
var property = this._group_prop_accessor(stats.footer[prop][0]);
|
|
if (typeof functor != "function"){
|
|
webix.assert(webix.GroupMethods[functor], "unknown grouping rule: "+functor);
|
|
functor = webix.GroupMethods[functor];
|
|
}
|
|
|
|
footer[prop]=functor.call(this, property, this.branch[labels[i].id]._formath);
|
|
}
|
|
|
|
this.branch[group.id].push(footer.id);
|
|
this.callEvent("onGroupFooter", [footer.id, footer.value, this.branch[group.id]._formath]);
|
|
}
|
|
|
|
delete this.branch[group.id]._formath;
|
|
}
|
|
|
|
|
|
this._fix_group_levels(topbranch, parent, level+1);
|
|
|
|
this.callEvent("onStoreUpdated",[]);
|
|
},
|
|
_ungroup_tree:function(skipRender, parent, force){
|
|
//not grouped
|
|
if (!force && !this._group_level_count) return;
|
|
this._group_level_count = Math.max(0, this._group_level_count -1 );
|
|
|
|
parent = parent || 0;
|
|
var order = [];
|
|
var toporder = this.branch[parent];
|
|
for (var i=0; i<toporder.length; i++){
|
|
var id = toporder[i];
|
|
var branch = this.branch[id];
|
|
if (branch)
|
|
order = order.concat(branch);
|
|
|
|
delete this.pull[id];
|
|
delete this.branch[id];
|
|
}
|
|
|
|
this.branch[parent] = order;
|
|
for (var i = order.length - 1; i >= 0; i--) {
|
|
if (this.pull[order[i]].$footer)
|
|
order.splice(i,1);
|
|
}
|
|
this._fix_group_levels(order, 0, 1);
|
|
|
|
if (!skipRender)
|
|
this.callEvent("onStoreUpdated",[]);
|
|
},
|
|
_fix_group_levels:function(branch, parent, level){
|
|
if (parent)
|
|
this.getItem(parent).$count = branch.length;
|
|
|
|
for (var i = 0; i < branch.length; i++) {
|
|
var item = this.pull[branch[i]];
|
|
item.$level = level;
|
|
item.$parent = parent;
|
|
var next = this.branch[item.id];
|
|
if (next)
|
|
this._fix_group_levels(next, item.id, level+1);
|
|
}
|
|
}
|
|
};
|
|
webix.clipbuffer = {
|
|
|
|
_area: null,
|
|
_blur_id: null,
|
|
_ctrl: 0,
|
|
|
|
/*! create textarea or returns existing
|
|
**/
|
|
init: function() {
|
|
// returns existing textarea
|
|
if (this._area !== null)
|
|
return this._area;
|
|
|
|
webix.destructors.push({ obj: this });
|
|
// creates new textarea
|
|
this._area = document.createElement('textarea');
|
|
this._area.className = "webix_clipbuffer";
|
|
this._area.setAttribute("webixignore", 1);
|
|
this._area.setAttribute("spellcheck", "false");
|
|
this._area.setAttribute("autocapitalize", "off");
|
|
this._area.setAttribute("autocorrect", "off");
|
|
this._area.setAttribute("autocomplete", "off");
|
|
document.body.appendChild(this._area);
|
|
|
|
webix.event(document.body, 'keydown', webix.bind(function(e){
|
|
var key = e.keyCode;
|
|
var ctrl = !!(e.ctrlKey || e.metaKey);
|
|
if (key === 86 && ctrl)
|
|
webix.delay(this._paste, this, [e], 100);
|
|
}, this));
|
|
|
|
return this._area;
|
|
},
|
|
destructor: function(){
|
|
this._area = null;
|
|
},
|
|
/*! set text into buffer
|
|
**/
|
|
set: function(text) {
|
|
this.init();
|
|
this._area.value = text;
|
|
this.focus();
|
|
},
|
|
/*! select text in textarea
|
|
**/
|
|
focus: function() {
|
|
// if there is native browser selection, skip focus
|
|
if(!this._isSelectRange()){
|
|
this.init();
|
|
this._area.focus();
|
|
this._area.select();
|
|
}
|
|
|
|
},
|
|
/*! checks document selection
|
|
**/
|
|
_isSelectRange: function() {
|
|
var text = "";
|
|
if (typeof window.getSelection != "undefined") {
|
|
text = window.getSelection().toString();
|
|
} else if (typeof document.selection != "undefined" && document.selection.type == "Text") {
|
|
text = document.selection.createRange().text;
|
|
}
|
|
return !!text;
|
|
},
|
|
/*! process ctrl+V pressing
|
|
**/
|
|
_paste: function(e) {
|
|
var trg = e.target || e.srcElement;
|
|
if (trg === this._area) {
|
|
var text = this._area.value;
|
|
var last_active = webix.UIManager.getFocus();
|
|
if (last_active && (!last_active.getEditor || !last_active.getEditor())){
|
|
last_active.callEvent("onPaste", [text]);
|
|
this._area.select();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
webix.CopyPaste = {
|
|
clipboard_setter: function(value) {
|
|
if (value === true || value === 1) value = "modify";
|
|
this.attachEvent("onAfterSelect", function(id) {
|
|
if (!this.getEditor || !this.getEditor()){
|
|
var item = this.getItem(id);
|
|
var text = this.type.templateCopy(item);
|
|
webix.clipbuffer.set(text, this);
|
|
webix.clipbuffer.focus();
|
|
webix.UIManager.setFocus(this);
|
|
}
|
|
});
|
|
this.attachEvent("onPaste", function(text) {
|
|
if (!webix.isUndefined(this._paste[this._settings.clipboard]))
|
|
this._paste[this._settings.clipboard].call(this, text);
|
|
});
|
|
this.attachEvent("onFocus", function() {
|
|
webix.clipbuffer.focus();
|
|
});
|
|
// solution for clicks on selected items
|
|
this.attachEvent("onItemClick",function(id){
|
|
if(!this._selected || this._selected.find(id)!==-1){
|
|
webix.clipbuffer.focus();
|
|
webix.UIManager.setFocus(this);
|
|
}
|
|
});
|
|
return value;
|
|
},
|
|
_paste: {
|
|
// insert new item with pasted value
|
|
insert: function(text) {
|
|
this.add({ value: text });
|
|
},
|
|
// change value of each selected item
|
|
modify: function(text) {
|
|
var sel = this.getSelectedId(true);
|
|
for (var i = 0; i < sel.length; i++) {
|
|
this.getItem(sel[i]).value = text;
|
|
this.refresh(sel[i]);
|
|
}
|
|
},
|
|
// do nothing
|
|
custom: function(text) {}
|
|
},
|
|
templateCopy_setter: function(value) {
|
|
this.type.templateCopy = webix.template(value);
|
|
},
|
|
type:{
|
|
templateCopy: function(item) {
|
|
return this.template(item);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
webix.KeysNavigation = {
|
|
$init:function(){
|
|
if(this.getSelectedId){
|
|
this.attachEvent("onAfterRender", this._set_focusable_item);
|
|
this.attachEvent("onAfterSelect", webix.once(function(){
|
|
if(this.count()>1){
|
|
var node = this._dataobj.querySelector("["+this._id+"]");
|
|
if(node) node.setAttribute("tabindex", "-1");
|
|
}
|
|
}));
|
|
}
|
|
},
|
|
_set_focusable_item:function(){
|
|
var sel = this.getSelectedId(true);
|
|
if(!sel.length || !this.getItemNode(sel[0])){
|
|
var node = this._dataobj.querySelector("["+this._id+"]");
|
|
if(node) node.setAttribute("tabindex", "0");
|
|
}
|
|
},
|
|
_navigation_helper:function(mode){
|
|
return function(view, e){
|
|
var tag = (e.srcElement || e.target);
|
|
|
|
//ignore clipboard listener
|
|
if (!tag.getAttribute("webixignore")){
|
|
//ignore hotkeys if focus in the common input
|
|
//to allow normal text edit operations
|
|
var name = tag.tagName;
|
|
if (name == "INPUT" || name == "TEXTAREA" || name == "SELECT") return true;
|
|
}
|
|
|
|
if (view && view.moveSelection && view.config.navigation && !view._in_edit_mode){
|
|
webix.html.preventEvent(e);
|
|
return view.moveSelection(mode, {shift:e.shiftKey, ctrl:e.ctrlKey});
|
|
}
|
|
return true;
|
|
};
|
|
},
|
|
moveSelection:function(mode, details, focus){
|
|
var config = this._settings;
|
|
if(config.disabled) return;
|
|
//get existing selection
|
|
var selected = this.getSelectedId(true);
|
|
var x_layout = (this.count && (config.layout =="x" || config.xCount > 1));
|
|
|
|
|
|
if((mode == "right" || mode == "left") && this._parent_menu){
|
|
var parent = webix.$$(this._parent_menu);
|
|
|
|
parent._hide_sub_menu(true);
|
|
if(parent.config.layout === "x")
|
|
parent.moveSelection(mode);
|
|
else
|
|
webix.UIManager.setFocus(parent);
|
|
return;
|
|
}
|
|
|
|
if (!selected.length && this.count()){
|
|
if (mode == "down" || (mode == "right" && x_layout)) mode = "top";
|
|
else if (mode == "up" || (mode == "left" && x_layout)) mode = "bottom";
|
|
else return;
|
|
selected = [this.getFirstId()];
|
|
}
|
|
|
|
if (selected.length == 1){ //if we have a selection
|
|
selected = selected[0];
|
|
var prev = selected;
|
|
|
|
if (mode == "left" && this.close)
|
|
return this.close(selected);
|
|
if (mode == "right" && this.open)
|
|
return this.open(selected);
|
|
|
|
else if (mode == "top") {
|
|
selected = this.getFirstId();
|
|
} else if (mode == "bottom") {
|
|
selected = this.getLastId();
|
|
} else if (mode == "up" || mode == "left" || mode == "pgup") {
|
|
var index = this.getIndexById(selected);
|
|
var step = mode == "pgup" ? 10 : 1;
|
|
selected = this.getIdByIndex(Math.max(0, index-step));
|
|
} else if (mode == "down" || mode == "right" || mode == "pgdown") {
|
|
var index = this.getIndexById(selected);
|
|
var step = mode == "pgdown" ? 10 : 1;
|
|
selected = this.getIdByIndex(Math.min(this.count()-1, index+step));
|
|
} else {
|
|
webix.assert(false, "Not supported selection moving mode");
|
|
return;
|
|
}
|
|
|
|
if(this._skip_item)
|
|
selected = this._skip_item(selected, prev, mode);
|
|
|
|
this.showItem(selected);
|
|
this.select(selected);
|
|
|
|
if(this.getSubMenu && this.getSubMenu(selected))
|
|
this._mouse_move_activation(selected, this.getItemNode(selected));
|
|
|
|
if(!this.config.clipboard && focus !== false){
|
|
var node = this.getItemNode(selected);
|
|
if(node) node.focus();
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
navigation_setter:function(value){
|
|
//using global flag to apply hotkey only once
|
|
if (value && !webix.UIManager._global_nav_grid_hotkeys){
|
|
webix.UIManager._global_nav_grid_hotkeys = true;
|
|
//hotkeys will react on any component but will not work in edit mode
|
|
//you can define moveSelection method to handle navigation keys
|
|
webix.UIManager.addHotKey("up", this._navigation_helper("up"));
|
|
webix.UIManager.addHotKey("down", this._navigation_helper("down"));
|
|
webix.UIManager.addHotKey("right", this._navigation_helper("right"));
|
|
webix.UIManager.addHotKey("left", this._navigation_helper("left"));
|
|
|
|
webix.UIManager.addHotKey("shift+up", this._navigation_helper("up"));
|
|
webix.UIManager.addHotKey("shift+down", this._navigation_helper("down"));
|
|
webix.UIManager.addHotKey("shift+right", this._navigation_helper("right"));
|
|
webix.UIManager.addHotKey("shift+left", this._navigation_helper("left"));
|
|
|
|
webix.UIManager.addHotKey("ctrl+shift+up", this._navigation_helper("up"));
|
|
webix.UIManager.addHotKey("ctrl+shift+down", this._navigation_helper("down"));
|
|
webix.UIManager.addHotKey("ctrl+shift+right", this._navigation_helper("right"));
|
|
webix.UIManager.addHotKey("ctrl+shift+left", this._navigation_helper("left"));
|
|
|
|
webix.UIManager.addHotKey("pageup", this._navigation_helper("pgup"));
|
|
webix.UIManager.addHotKey("pagedown", this._navigation_helper("pgdown"));
|
|
webix.UIManager.addHotKey("home", this._navigation_helper("top"));
|
|
webix.UIManager.addHotKey("end", this._navigation_helper("bottom"));
|
|
|
|
|
|
|
|
}
|
|
|
|
return value;
|
|
}
|
|
};
|
|
|
|
|
|
|
|
webix.protoUI({
|
|
name:"tree",
|
|
defaults:{
|
|
scroll:"a",
|
|
navigation:true
|
|
},
|
|
$init:function(){
|
|
this._viewobj.className += " webix_tree";
|
|
|
|
//map API of DataStore on self
|
|
webix.extend(this.data, webix.TreeStore, true);
|
|
webix.extend(this.on_click, webix.TreeClick);
|
|
this.attachEvent("onAfterRender", this._refresh_scroll);
|
|
this.attachEvent("onPartialRender", this._refresh_scroll);
|
|
this.data.provideApi(this,true);
|
|
this._viewobj.setAttribute("role", "tree");
|
|
|
|
},
|
|
//attribute , which will be used for ID storing
|
|
_id:"webix_tm_id",
|
|
//supports custom context menu
|
|
on_context:{},
|
|
on_dblclick:{
|
|
webix_tree_checkbox:function(){
|
|
if(this.on_click.webix_tree_checkbox)
|
|
return this.on_click.webix_tree_checkbox.apply(this,arguments);
|
|
}
|
|
},
|
|
$fixEditor: function(editor) {
|
|
var item = this.getItemNode(editor.id).querySelector("span");
|
|
if (item){
|
|
if (item.innerHTML === "") item.innerHTML =" ";
|
|
var padding = 10;
|
|
var pos = item.offsetLeft;
|
|
editor.node.style.width = this.$view.scrollWidth - pos - padding + "px";
|
|
editor.node.style.marginLeft = pos + "px";
|
|
editor.node.style.left = "0px";
|
|
}
|
|
},
|
|
//css class to action map, for onclick event
|
|
on_click:{
|
|
webix_tree_item:function(e, id, node){
|
|
if(this._settings.activeTitle){
|
|
var item = this.getItem(id);
|
|
if(item.open)
|
|
this.close(id);
|
|
else
|
|
this.open(id);
|
|
}
|
|
if (this._settings.select){
|
|
if (this._settings.select=="multiselect" || this._settings.multiselect){
|
|
if (this._settings.multiselect == "level"){
|
|
//allow only selection on the same level
|
|
var select = this.getSelectedId(true)[0];
|
|
if (select && this.getParentId(id) != this.getParentId(select))
|
|
return;
|
|
}
|
|
this.select(id, false, (e.ctrlKey || e.metaKey || (this._settings.multiselect == "touch")), e.shiftKey); //multiselection
|
|
} else
|
|
this.select(id);
|
|
}
|
|
}
|
|
},
|
|
_paste: {
|
|
// insert new item with pasted value
|
|
insert: function(text) {
|
|
var parent = this.getSelectedId() ||'0' ;
|
|
this.add({ value: text }, null, parent);
|
|
},
|
|
// change value of each selected item
|
|
modify: function(text) {
|
|
var sel = this.getSelectedId(true);
|
|
for (var i = 0; i < sel.length; i++) {
|
|
this.getItem(sel[i]).value = text;
|
|
this.refresh(sel[i]);
|
|
}
|
|
},
|
|
// do nothing
|
|
custom: function(text) {}
|
|
},
|
|
_drag_order_complex:true,
|
|
$dragHTML:function(obj){
|
|
return "<div class='borderless'>"+this.type.template(obj, this.type)+"</div>";
|
|
},
|
|
|
|
//css class to action map, for dblclick event
|
|
type:webix.extend({
|
|
//normal state of item
|
|
template:function(obj,common){
|
|
var template = common["template"+obj.level]||common.templateCommon;
|
|
return template.apply(this, arguments);
|
|
},
|
|
classname:function(obj, common, marks){
|
|
var css = "webix_tree_item";
|
|
|
|
if (obj.$css){
|
|
if (typeof obj.$css == "object")
|
|
obj.$css = webix.html.createCss(obj.$css);
|
|
css += " "+obj.$css;
|
|
}
|
|
if (marks && marks.$css)
|
|
css += " "+marks.$css;
|
|
|
|
return css;
|
|
},
|
|
aria:function(obj, common, marks){
|
|
return 'role="treeitem"'+(marks && marks.webix_selected?' aria-selected="true" tabindex="0"':' tabindex="-1"')+
|
|
(obj.$count?('aria-expanded="'+(obj.open?"true":"false")+'"'):'')+'aria-level="'+obj.$level+'"';
|
|
},
|
|
templateCommon:webix.template("{common.icon()} {common.folder()} <span>#value#</span>"),
|
|
templateStart:webix.template('<div webix_tm_id="#id#" class="{common.classname()}" {common.aria()}>'),
|
|
templateEnd:webix.template("</div>"),
|
|
templateCopy: webix.template("#value#")
|
|
}, webix.TreeType)
|
|
}, webix.AutoTooltip, webix.Group, webix.TreeAPI, webix.DragItem, webix.TreeDataMove, webix.SelectionModel, webix.KeysNavigation, webix.MouseEvents, webix.Scrollable, webix.TreeDataLoader, webix.ui.proto, webix.TreeRenderStack, webix.CopyPaste, webix.EventSystem);
|
|
|
|
webix.TreeStateCheckbox = {
|
|
_init_render_tree_state: function(){
|
|
if (this._branch_render_supported){
|
|
var old_render = this.render;
|
|
this.render = function(id,data,mode){
|
|
var updated = old_render.apply(this,arguments);
|
|
|
|
if(this._settings.threeState && updated && data != "checkbox")
|
|
this._setThirdState.apply(this,arguments);
|
|
};
|
|
this._init_render_tree_state=function(){};
|
|
}
|
|
},
|
|
threeState_setter:function(value){
|
|
if (value)
|
|
this._init_render_tree_state();
|
|
return value;
|
|
},
|
|
_setThirdState:function(id){
|
|
var i,leaves,parents,checkedParents,tree;
|
|
parents = [];
|
|
tree = this;
|
|
|
|
/*if item was removed*/
|
|
if(id&&!tree.data.pull[id]){
|
|
id = 0;
|
|
}
|
|
/*sets checkbox states*/
|
|
/*if branch or full reloading*/
|
|
if(!id||tree.data.pull[id].$count){
|
|
leaves = this._getAllLeaves(id);
|
|
leaves.sort(function(a,b){
|
|
return tree.data.pull[b].$level - tree.data.pull[a].$level;
|
|
});
|
|
for(i=0;i < leaves.length;i++){
|
|
if(!i||tree.data.pull[leaves[i]].$parent!=tree.data.pull[leaves[i-1]].$parent)
|
|
parents = parents.concat(tree._setParentThirdState(leaves[i]));
|
|
}
|
|
}
|
|
else{
|
|
/*an item is a leaf */
|
|
parents = parents.concat(tree._setParentThirdState(id));
|
|
}
|
|
|
|
checkedParents = {};
|
|
for(i=0;i<parents.length;i++){
|
|
if(!checkedParents[parents[i]]){
|
|
checkedParents[parents[i]] = 1;
|
|
this._setCheckboxIndeterminate(parents[i]);
|
|
}
|
|
}
|
|
|
|
tree = null;
|
|
},
|
|
_setCheckboxIndeterminate:function(id){
|
|
var chElem, elem;
|
|
elem = this.getItemNode(id);
|
|
if(elem){
|
|
this.render(id,"checkbox","update");
|
|
/*needed to get the new input obj and to set indeterminate state*/
|
|
if(this.getItem(id).indeterminate){
|
|
elem = this.getItemNode(id);
|
|
chElem = elem.getElementsByTagName("input")[0];
|
|
if(chElem)
|
|
chElem.indeterminate = this.getItem(id).indeterminate;
|
|
}
|
|
}
|
|
},
|
|
_setParentThirdState:function(itemId){
|
|
//we need to use dynamic function creating
|
|
//jshint -W083:true
|
|
|
|
var checked, checkedCount,indeterminate, parentId,result,tree,unsureCount,needrender;
|
|
parentId = this.getParentId(itemId);
|
|
tree = this;
|
|
result = [];
|
|
while(parentId && parentId != "0"){
|
|
unsureCount = 0;
|
|
checkedCount = 0;
|
|
this.data.eachChild(parentId,function(obj){
|
|
if(obj.indeterminate){
|
|
unsureCount++;
|
|
}
|
|
else if(obj.checked){
|
|
checkedCount++;
|
|
}
|
|
});
|
|
|
|
checked = indeterminate = needrender = false;
|
|
|
|
var item = this.getItem(parentId);
|
|
if(checkedCount==item.$count){
|
|
checked = true;
|
|
}
|
|
else if(checkedCount>0||unsureCount>0){
|
|
indeterminate = true;
|
|
}
|
|
|
|
//we need to reset indeterminate in any case :(
|
|
if (indeterminate || indeterminate != item.indeterminate)
|
|
needrender = true;
|
|
item.indeterminate = indeterminate;
|
|
if (checked || item.checked != checked)
|
|
needrender = true;
|
|
item.checked = checked;
|
|
|
|
if (needrender){
|
|
result.push(parentId);
|
|
parentId = this.getParentId(parentId);
|
|
} else
|
|
parentId = 0;
|
|
}
|
|
|
|
return result;
|
|
},
|
|
/*get all checked items in tree*/
|
|
getChecked:function(){
|
|
var result=[];
|
|
var tree = this;
|
|
this.data.eachSubItem(0,function(obj){
|
|
if (tree.isChecked(obj.id))
|
|
result.push(obj.id);
|
|
});
|
|
return result;
|
|
},
|
|
_tree_check_uncheck_3:function(id, mode){
|
|
var item = this.getItem(id);
|
|
if(item){
|
|
if (mode === "")
|
|
mode = !item.checked;
|
|
if(item.checked != mode || item.indeterminate){
|
|
item.checked = mode;
|
|
this._correctThreeState(id);
|
|
var parents = this._setParentThirdState(id);
|
|
if (this._branch_render_supported && parents.length < 5){
|
|
for (var i=0; i<parents.length; i++)
|
|
this._setCheckboxIndeterminate(parents[i]);
|
|
} else
|
|
this.refresh();
|
|
this.callEvent("onItemCheck", [id, mode]);
|
|
}
|
|
}
|
|
},
|
|
/*set checked state for item checkbox*/
|
|
checkItem:function(id){
|
|
this._tree_check_uncheck(id, true);
|
|
this.updateItem(id);
|
|
},
|
|
/*uncheckes an item checkbox*/
|
|
uncheckItem:function(id){
|
|
this._tree_check_uncheck(id, false);
|
|
this.updateItem(id);
|
|
},
|
|
_checkUncheckAll: function(id,mode,all){
|
|
var method = mode?"checkItem":"uncheckItem";
|
|
if(!id)
|
|
id = 0;
|
|
else
|
|
this[method](id);
|
|
if(this._settings.threeState){
|
|
if(!id)
|
|
this.data.eachChild(0,function(item){
|
|
this[method](item.id);
|
|
},this,all);
|
|
}
|
|
else
|
|
this.data.each(function(item){
|
|
this[method](item.id);
|
|
},this,all,id);
|
|
|
|
},
|
|
/*checkes checkboxes of all items in a branch/tree*/
|
|
checkAll: function(id, all){
|
|
this._checkUncheckAll(id,true,all);
|
|
|
|
},
|
|
/*uncheckes checkboxes of all items in a branch/tree*/
|
|
uncheckAll: function(id, all){
|
|
this._checkUncheckAll(id,false,all);
|
|
},
|
|
_correctThreeState:function(id){
|
|
var i,leaves,state;
|
|
var item = this.getItem(id);
|
|
|
|
item.indeterminate = false;
|
|
state = item.checked;
|
|
|
|
this.data.eachSubItem(id, function(child){
|
|
child.indeterminate = false;
|
|
child.checked = state;
|
|
});
|
|
|
|
if(this._branch_render_supported && this.isBranchOpen(item.$parent)){ //for tree-render only
|
|
this.render(id,0,"branch");
|
|
}
|
|
},
|
|
/*returns checked state of item checkbox*/
|
|
isChecked:function(id){
|
|
return this.getItem(id).checked;
|
|
},
|
|
/*gets all leaves in a certain branch (in the whole tree if id is not set)*/
|
|
_getAllLeaves:function(parentId){
|
|
var result = [];
|
|
this.data.eachSubItem(parentId, function(obj, branch){
|
|
if (!branch)
|
|
result.push(obj.id);
|
|
});
|
|
return result;
|
|
}
|
|
};
|
|
|
|
if (webix.ui.tree)
|
|
webix.extend(webix.ui.tree, webix.TreeStateCheckbox, true);
|
|
webix.type(webix.ui.tree, {
|
|
name:"lineTree",
|
|
css:"webixLineTree",
|
|
icon:function(obj, common){
|
|
var html = "";
|
|
var open = "";
|
|
for (var i=1; i<=obj.$level; i++){
|
|
if (i==obj.$level)
|
|
var open = (obj.$count?(obj.open?'webix_tree_open ':'webix_tree_close '):'webix_tree_none ');
|
|
|
|
var icon = this._icon_src(obj, common, i);
|
|
if (icon)
|
|
html+="<div class='"+open+"webix_tree_img webix_tree_"+icon+"'></div>";
|
|
}
|
|
return html;
|
|
},
|
|
_icon_src:function(obj, common, level){
|
|
var lines = common._tree_branch_render_state;
|
|
var tree = webix.TreeRenderStack._obj;
|
|
|
|
if (lines === 0 && tree){
|
|
//we are in standalone rendering
|
|
//need to reconstruct rendering state
|
|
var lines_level = obj.$level;
|
|
var branch_id = obj.id;
|
|
|
|
lines = [];
|
|
while (lines_level){
|
|
var parent_id = tree.getParentId(branch_id);
|
|
var pbranch = tree.data.branch[parent_id];
|
|
if (pbranch[pbranch.length-1] == branch_id)
|
|
lines[lines_level] = true;
|
|
|
|
branch_id = parent_id;
|
|
lines_level--;
|
|
}
|
|
|
|
//store for next round
|
|
common._tree_branch_render_state = lines;
|
|
}
|
|
if (!lines)
|
|
return 0;
|
|
//need to be replaced with image urls
|
|
if (level == obj.$level){
|
|
var mode = 3; //3-way line
|
|
if (!obj.$parent){ //top level
|
|
if (obj.$index === 0)
|
|
mode = 4; //firts top item
|
|
}
|
|
|
|
if (lines[obj.$level])
|
|
mode = 2;
|
|
|
|
if (obj.$count){
|
|
if (obj.open)
|
|
return "minus"+mode;
|
|
else
|
|
return "plus"+mode;
|
|
} else
|
|
return "line"+mode;
|
|
} else {
|
|
if (!lines[level])
|
|
return "line1";
|
|
return "blank";
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
UI: navigation control
|
|
*/
|
|
webix.NavigationButtons = {
|
|
$init:function(){
|
|
this.$ready.push(function(){
|
|
this.attachEvent("onKeyPress", this._onKeyPress);
|
|
});
|
|
},
|
|
_moveActive:function(code, e){
|
|
if(code === 37 || code === 39){
|
|
webix.html.preventEvent(e);
|
|
this._showNavItem(code===37?-1:1);
|
|
|
|
var node = this._navPanel.querySelector("[tabindex='0']");
|
|
if(node) node.focus();
|
|
}
|
|
},
|
|
_renderPanel:function(){
|
|
webix.html.remove(this._navPanel);
|
|
|
|
|
|
this._navPanel = webix.html.create("DIV",{
|
|
"class":"webix_nav_panel "+"webix_nav_panel_"+this._settings.navigation.type,
|
|
"role":"tablist"
|
|
},"");
|
|
|
|
this._viewobj.appendChild(this._navPanel);
|
|
|
|
|
|
this._renderNavItems();
|
|
this._renderNavButtons();
|
|
this._setLinkEventHandler();
|
|
},
|
|
_setLinkEventHandler: function(){
|
|
var h = [];
|
|
if(this._navPanel)
|
|
h[0] = webix.event(this._navPanel,"click", webix.bind(function(e){
|
|
var elem = (e.srcElement || e.target);
|
|
var found = false;
|
|
while(elem != this._navPanel && !found){
|
|
var bindId = elem.getAttribute(this._linkAttr);
|
|
if(bindId){
|
|
found = true;
|
|
this._showPanelBind(bindId);
|
|
}
|
|
elem = elem.parentNode;
|
|
}
|
|
},this));
|
|
if(this._prevNavButton)
|
|
h[1] = webix.event(this._prevNavButton,"click", webix.bind(function(e){
|
|
this._showNavItem(-1);
|
|
},this));
|
|
if(this._nextNavButton)
|
|
h[1] = webix.event(this._nextNavButton,"click", webix.bind(function(e){
|
|
this._showNavItem(1);
|
|
},this));
|
|
this.attachEvent("onDestruct", function(){
|
|
for(var i=0;i< h.length; i++){
|
|
this.detachEvent(h[i]);
|
|
}
|
|
h = null;
|
|
});
|
|
},
|
|
_showNavItem: function(inc){
|
|
if(this._cells){
|
|
var index = this._active_cell + inc;
|
|
if(index >= this._cells.length || index < 0){
|
|
index = (index < 0?this._cells.length-1:0);
|
|
}
|
|
this.setActiveIndex(index);
|
|
}
|
|
},
|
|
_showPanelBind: function(id){
|
|
if(this._cells)
|
|
webix.$$(id).show();
|
|
},
|
|
_renderNavItems:function(){
|
|
var item, config;
|
|
config = this._settings.navigation;
|
|
if(config.items){
|
|
this._linkAttr = config.linkAttr || "bind_id";
|
|
|
|
if(!this._navPanel)
|
|
this._renderPanel();
|
|
else
|
|
this._clearPanel();
|
|
|
|
var data = (this._cells?this._cells:this.data.order);
|
|
if(data.length>1){
|
|
for (var i=0; i < data.length; i++){
|
|
|
|
item = webix.html.create("DIV",{
|
|
"class":"webix_nav_item webix_nav_"+(i==this._active_cell?"active":"inactive"),
|
|
"role":"tab",
|
|
"tabindex":(i==this._active_cell?"0":"-1")
|
|
},"<div></div>");
|
|
var id = this._cells?this._cells[i]._settings.id:data[i];
|
|
if(id)
|
|
item.setAttribute(this._linkAttr, id);
|
|
this._navPanel.appendChild(item);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
_clearPanel:function(){
|
|
if (this._navPanel){
|
|
var coll = this._navPanel.childNodes;
|
|
for (var i = coll.length - 1; i >= 0; i--)
|
|
webix.html.remove(coll[i]);
|
|
}
|
|
},
|
|
_renderNavButtons: function(){
|
|
var item, config;
|
|
config = this._settings.navigation;
|
|
if(config.buttons){
|
|
|
|
if(this._prevNavButton)
|
|
webix.html.remove(this._prevNavButton);
|
|
if(this._prevNavButton)
|
|
webix.html.remove(this._nextNavButton);
|
|
|
|
|
|
this._prevNavButton = webix.html.create(
|
|
"DIV",
|
|
{
|
|
"class":"webix_nav_button_"+config.type+" webix_nav_button_prev ",
|
|
"role":"button",
|
|
"tabindex":"0",
|
|
"aria-label":webix.i18n.aria.prevTab
|
|
},
|
|
"<div class=\"webix_nav_button_inner\"></div>"
|
|
);
|
|
this._viewobj.appendChild(this._prevNavButton);
|
|
|
|
this._nextNavButton = webix.html.create(
|
|
"DIV",
|
|
{
|
|
"class":"webix_nav_button_"+config.type+" webix_nav_button_next ",
|
|
"role":"button",
|
|
"tabindex":"0",
|
|
"aria-label":webix.i18n.aria.nextTab
|
|
},
|
|
"<div class=\"webix_nav_button_inner\"></div>"
|
|
);
|
|
this._viewobj.appendChild(this._nextNavButton);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
(function(){
|
|
|
|
webix.env.printPPI = 96;
|
|
webix.env.printMargin = 0.75*webix.env.printPPI;
|
|
|
|
var ppi = webix.env.printPPI;
|
|
var margin = webix.env.printMargin;
|
|
var papers = { "a4":"A4", "a3":"A3", "letter":"letter"};
|
|
var fits = { page:true, data:true};
|
|
var modes = { portrait:true, landscape:true};
|
|
|
|
var sizes = {//inches, real size is value*ppi
|
|
"A3": { width: 11.7, height: 16.5 },
|
|
"A4": { width: 8.27, height:11.7 },
|
|
"letter": { width: 8.5, height:11 }
|
|
};
|
|
|
|
webix.print = function(id, options){
|
|
|
|
var view = webix.$$(id);
|
|
if (view && view.$printView)
|
|
view = view.$printView();
|
|
|
|
webix.assert(view, "non-existing view for printing");
|
|
if(!view) return;
|
|
|
|
if(view.callEvent)
|
|
view.callEvent("onBeforePrint", [options]);
|
|
|
|
options = _checkOptions(options);
|
|
_beforePrint(options);
|
|
|
|
//try widget's custom logic first, sometimes it may deny
|
|
if(!view.$customPrint || view.$customPrint(options) === true)
|
|
_print(view, options);
|
|
|
|
_afterPrint(options);
|
|
};
|
|
|
|
/*processing print options*/
|
|
function _checkOptions(options){
|
|
|
|
options = options || {};
|
|
options.paper = papers[(options.paper || "").toLowerCase()] || "A4";
|
|
options.mode = modes[options.mode] ? options.mode : "portrait";
|
|
options.fit = fits[options.fit] ? options.fit: "page";
|
|
options.scroll = options.scroll || false;
|
|
options.size = sizes[options.paper];
|
|
|
|
options.margin = (options.margin || options.margin === 0) ? options.margin : {};
|
|
margin = isNaN(options.margin*1) ? margin : options.margin;
|
|
options.margin = {
|
|
top:(options.margin.top || options.margin.top === 0) ? options.margin.top: margin,
|
|
bottom:(options.margin.bottom || options.margin.bottom === 0) ? options.margin.bottom: margin,
|
|
right:(options.margin.right || options.margin.right === 0) ? options.margin.right: margin,
|
|
left:(options.margin.left || options.margin.left === 0) ? options.margin.left: margin
|
|
};
|
|
|
|
return options;
|
|
}
|
|
|
|
/*preparing printing environment*/
|
|
function _beforePrint(options){
|
|
webix.html.addCss(document.body,"webix_print");
|
|
|
|
if(options.docHeader) _getHeaderFooter("Header", options);
|
|
if(options.docFooter) _getHeaderFooter("Footer", options);
|
|
|
|
/* static print styles are located at 'css/print.less'*/
|
|
var cssString = "@media print { "+
|
|
"@page{ size:"+options.paper+" "+options.mode+";"+
|
|
"margin-top:"+options.margin.top+"px;margin-bottom:"+options.margin.bottom+
|
|
"px;margin-right:"+options.margin.right+"px;margin-left:"+options.margin.left+
|
|
"px;}"+
|
|
"}";
|
|
webix.html.addStyle(cssString, "print");
|
|
}
|
|
|
|
/*cleaning environment*/
|
|
function _afterPrint(options){
|
|
webix.html.removeCss(document.body, "webix_print");
|
|
webix.html.removeStyle("print");
|
|
|
|
if(options.docHeader) webix.html.remove(options.docHeader);
|
|
if(options.docFooter) webix.html.remove(options.docFooter);
|
|
}
|
|
|
|
/*common print actions */
|
|
function _print(view, options){
|
|
var doc = view.$view.cloneNode(true);
|
|
|
|
//copy data from all canvases
|
|
var canvases = view.$view.getElementsByTagName("canvas");
|
|
if(canvases.length)
|
|
for(var i = canvases.length-1; i >=0; i--){
|
|
var destCtx = doc.getElementsByTagName("canvas")[i].getContext('2d');
|
|
destCtx.drawImage(canvases[i], 0, 0);
|
|
}
|
|
|
|
webix.html.insertBefore(doc, options.docFooter, document.body);
|
|
|
|
webix.html.addCss(doc,"webix_ui_print");
|
|
if(!options.scroll && ((view._dataobj && view.data && view.data.pull) || view.getBody))
|
|
webix.html.addCss(doc, "webix_print_noscroll");
|
|
|
|
window.print();
|
|
|
|
webix.html.remove(doc);
|
|
}
|
|
/*custom header nad footer*/
|
|
function _getHeaderFooter(group, options){
|
|
var header = webix.html.create("div", {
|
|
"class":"webix_view webix_print_"+group.toLowerCase(),
|
|
"style":"height:0px;visibility:hidden;"
|
|
}, options["doc"+group]);
|
|
|
|
if(group ==="Header")
|
|
webix.html.insertBefore(header, document.body.firstChild);
|
|
else
|
|
document.body.appendChild(header);
|
|
|
|
options["doc"+group] = header;
|
|
}
|
|
|
|
})();
|
|
|
|
webix.CustomPrint = {
|
|
$customPrint:function(options, htmlOnly){
|
|
if(this._prePrint(options, htmlOnly))
|
|
return true;
|
|
|
|
var tableData = this._getTableArray(options);
|
|
var table = this._getTableHTML(tableData, options);
|
|
|
|
if(htmlOnly)
|
|
return table;
|
|
|
|
var doc = webix.html.create("div", { "class":"webix_ui_print"});
|
|
doc.appendChild(table);
|
|
|
|
webix.html.insertBefore(doc, options.docFooter, document.body);
|
|
window.print();
|
|
|
|
webix.html.remove(doc);
|
|
},
|
|
_prePrint:function(options, htmlOnly){
|
|
if(!htmlOnly && (this.config.layout =="y" || options.scroll || this.config.prerender || this.config.autoheight)) return true;
|
|
|
|
if(this.config.layout =="x")
|
|
webix.extend(options || {}, {xCount:this.count(), nobreaks:true}, true);
|
|
},
|
|
_getPageWidth:function(options){
|
|
if(options.fit =="page") return Infinity;
|
|
|
|
var size = options.size;
|
|
var width = size[options.mode == "portrait"?"width":"height"];
|
|
|
|
return Math.min(width*webix.env.printPPI-2*webix.env.printMargin);
|
|
},
|
|
_getTableArray:function(options, base, start){
|
|
var maxWidth = this._getPageWidth(options);
|
|
var xCount = options.xCount || this._getVisibleRange()._dx;
|
|
|
|
var tableArray = [];
|
|
var colrow = [];
|
|
var width = 0;
|
|
|
|
var newTableStart, rownum, colnum;
|
|
|
|
start = start || 0;
|
|
base = base || [];
|
|
|
|
for(var i = 0; i<this.data.order.length;){
|
|
var obj = this.data.pull[this.data.order[i]];
|
|
rownum = parseInt(i/xCount);
|
|
colnum = i-(rownum*xCount);
|
|
|
|
if(obj && colnum>=start){
|
|
width += this.type.width;
|
|
|
|
//start a new table, if cells do not fit page width
|
|
if(width > maxWidth && colnum>start){ // 'colnum>start' ensures that a single long cell will have to fit the page
|
|
newTableStart = colrow.length+start;
|
|
tableArray.push(colrow);
|
|
i = i+(xCount-colrow.length);
|
|
colrow = [];
|
|
width = 0;
|
|
continue;
|
|
}
|
|
|
|
var cellValue = this.type.template(obj, this.type);
|
|
var className = this._itemClassName;
|
|
|
|
var style = {
|
|
display:"table-cell",
|
|
height:this.type.height + "px",
|
|
width:this.type.width + "px"
|
|
};
|
|
//push a cell to a row
|
|
colrow.push({
|
|
txt: cellValue,
|
|
className: className+" "+(obj.$css || ""),
|
|
style: style
|
|
});
|
|
//push a row to a table and start a new row
|
|
if((i+1)%xCount === 0){
|
|
tableArray.push(colrow);
|
|
colrow = [];
|
|
width = 0;
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
|
|
base.push(tableArray);
|
|
|
|
if(newTableStart)
|
|
this._getTableArray(options, base, newTableStart);
|
|
|
|
return base;
|
|
},
|
|
_getTableHTML:function(tableData, options){
|
|
|
|
var container = webix.html.create("div");
|
|
|
|
tableData.forEach(webix.bind(function(table, i){
|
|
|
|
var tableHTML = webix.html.create("table", {
|
|
"class":"webix_table_print "+this.$view.className,
|
|
"style":"border-collapse:collapse"
|
|
});
|
|
|
|
table.forEach(function(row){
|
|
var tr = webix.html.create("tr");
|
|
|
|
row.forEach(function(column){
|
|
var td = webix.html.create("td");
|
|
|
|
|
|
if (column.txt) td.innerHTML = column.txt;
|
|
if (column.className) td.className = column.className;
|
|
if (column.style) {
|
|
var keys = Object.keys(column.style);
|
|
keys.forEach(function(key){
|
|
if (column.style[key])
|
|
td.style[key] = column.style[key];
|
|
});
|
|
}
|
|
if(column.span){
|
|
if(column.span.colspan > 1)
|
|
td.colSpan = column.span.colspan;
|
|
if(column.span.rowspan > 1)
|
|
td.rowSpan = column.span.rowspan;
|
|
}
|
|
tr.appendChild(td);
|
|
});
|
|
tableHTML.appendChild(tr);
|
|
});
|
|
container.appendChild(tableHTML);
|
|
|
|
if(!options.nobreaks && i+1 < tableData.length){
|
|
var br = webix.html.create("DIV", {"class":"webix_print_pagebreak"});
|
|
container.appendChild(br);
|
|
}
|
|
|
|
}, this));
|
|
|
|
return container;
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
webix.protoUI({
|
|
name:"list",
|
|
_listClassName : "webix_list",
|
|
_itemClassName:"webix_list_item",
|
|
$init:function(config){
|
|
webix.html.addCss(this._viewobj, this._listClassName + (((config.layout||this.defaults.layout) == "x")?"-x":"") );
|
|
this.data.provideApi(this,true);
|
|
|
|
this._auto_resize = webix.bind(this._auto_resize, this);
|
|
this.data.attachEvent("onStoreUpdated", this._auto_resize);
|
|
this.data.attachEvent("onSyncApply", this._auto_resize);
|
|
this.attachEvent("onAfterRender", this._correct_width_scroll);
|
|
|
|
this._viewobj.setAttribute("role", "listbox");
|
|
},
|
|
$dragHTML:function(obj, e){
|
|
if (this._settings.layout == "y" && this.type.width == "auto"){
|
|
this.type.width = this._content_width;
|
|
var node = this._toHTML(obj);
|
|
this.type.width = "auto";
|
|
return node;
|
|
}
|
|
return this._toHTML(obj);
|
|
},
|
|
defaults:{
|
|
select:false,
|
|
scroll:true,
|
|
layout:"y",
|
|
navigation:true
|
|
},
|
|
_id:"webix_l_id",
|
|
on_click:{
|
|
webix_list_item:function(e,id){
|
|
if (this._settings.select){
|
|
this._no_animation = true;
|
|
if (this._settings.select=="multiselect" || this._settings.multiselect)
|
|
this.select(id, false, (e.ctrlKey || e.metaKey || (this._settings.multiselect == "touch")), e.shiftKey); //multiselection
|
|
else
|
|
this.select(id);
|
|
this._no_animation = false;
|
|
}
|
|
}
|
|
},
|
|
on_dblclick:{
|
|
},
|
|
getVisibleCount:function(){
|
|
return Math.floor(this._content_height / this._one_height());
|
|
},
|
|
_auto_resize:function(){
|
|
if (this._settings.autoheight || this._settings.autowidth)
|
|
this.resize();
|
|
},
|
|
_auto_height_calc:function(count){
|
|
var value = this.data.$pagesize||this.count();
|
|
|
|
this._onoff_scroll(count && count < value);
|
|
if (this._settings.autoheight && value < (count||Infinity) )
|
|
count = value;
|
|
var height = this._one_height() * count + (this.type.margin||0);
|
|
//unitlist
|
|
if(this.getUnits)
|
|
height += this.getUnits().length*this.type.headerHeight;
|
|
|
|
return Math.max(height,this._settings.minHeight||0);
|
|
},
|
|
_one_height:function(){
|
|
return this.type.height + (this.type.margin||0);
|
|
},
|
|
_auto_width_calc:function(count){
|
|
var value = this.data.$pagesize||this.count();
|
|
|
|
this._onoff_scroll(count && count < value);
|
|
if (this._settings.autowidth && value < (count||Infinity) )
|
|
count = value;
|
|
|
|
return (this.type.width * count);
|
|
},
|
|
_correct_width_scroll:function(){
|
|
if (this._settings.layout == "x")
|
|
this._dataobj.style.width = (this.type.width != "auto") ? (this.type.width * this.count() + "px") : "auto";
|
|
},
|
|
$getSize:function(dx,dy){
|
|
if (this._settings.layout == "y"){
|
|
if (this.type.width!="auto")
|
|
this._settings.width = this.type.width + (this._scroll_y?webix.ui.scrollSize:0);
|
|
if (this._settings.yCount || this._settings.autoheight)
|
|
this._settings.height = this._auto_height_calc(this._settings.yCount)||1;
|
|
}
|
|
else {
|
|
if (this.type.height!="auto")
|
|
this._settings.height = this._one_height() + (this._scroll_x?webix.ui.scrollSize:0);
|
|
if (this._settings.xCount || this._settings.autowidth)
|
|
this._settings.width = this._auto_width_calc(this._settings.xCount)||1;
|
|
}
|
|
return webix.ui.view.prototype.$getSize.call(this, dx, dy);
|
|
},
|
|
$setSize:function(){
|
|
webix.ui.view.prototype.$setSize.apply(this, arguments);
|
|
},
|
|
type:{
|
|
css:"",
|
|
widthSize:function(obj, common){
|
|
return common.width+(common.width>-1?"px":"");
|
|
},
|
|
heightSize:function(obj, common){
|
|
return common.height+(common.height>-1?"px":"");
|
|
},
|
|
classname:function(obj, common, marks){
|
|
var css = "webix_list_item";
|
|
if (obj.$css){
|
|
if (typeof obj.$css == "object")
|
|
obj.$css = webix.html.createCss(obj.$css);
|
|
css += " "+obj.$css;
|
|
}
|
|
if (marks && marks.$css)
|
|
css += " "+marks.$css;
|
|
|
|
return css;
|
|
},
|
|
aria:function(obj, common, marks){
|
|
return 'role="option"'+(marks && marks.webix_selected?' aria-selected="true" tabindex="0"':' tabindex="-1"')+(obj.$count && obj.$template?'aria-expanded="true"':'');
|
|
},
|
|
template:function(obj){
|
|
return (obj.icon?("<span class='webix_icon fa-"+obj.icon+"'></span> "):"") + obj.value + (obj.badge?("<div class='webix_badge'>"+obj.badge+"</div>"):"");
|
|
},
|
|
width:"auto",
|
|
templateStart:webix.template('<div webix_l_id="#id#" class="{common.classname()}" style="width:{common.widthSize()}; height:{common.heightSize()}; overflow:hidden;" {common.aria()}>'),
|
|
templateEnd:webix.template("</div>")
|
|
},
|
|
$skin:function(){
|
|
this.type.height = webix.skin.$active.listItemHeight;
|
|
}
|
|
}, webix.CustomPrint, webix.KeysNavigation, webix.DataMove, webix.DragItem, webix.MouseEvents, webix.SelectionModel, webix.Scrollable, webix.ui.proto, webix.CopyPaste);
|
|
|
|
webix.protoUI({
|
|
name:"grouplist",
|
|
defaults:{
|
|
animate:{
|
|
}
|
|
},
|
|
_listClassName : "webix_grouplist",
|
|
$init:function(){
|
|
webix.extend(this.data, webix.TreeStore, true);
|
|
//needed for getRange
|
|
this.data.count = function(){ return this.order.length; };
|
|
this.data.provideApi(this,true);
|
|
this.data.attachEvent("onClearAll", webix.bind(this._onClear, this));
|
|
this._onClear();
|
|
},
|
|
_onClear:function(){
|
|
this._nested_cursor = [];
|
|
this._nested_chain = [];
|
|
},
|
|
$setSize:function(){
|
|
if (webix.ui.view.prototype.$setSize.apply(this, arguments)){
|
|
//critical for animations in group list
|
|
this._dataobj.style.width = this._content_width;
|
|
}
|
|
},
|
|
on_click:{
|
|
webix_list_item:function(e,id){
|
|
if (this._in_animation) {
|
|
return false;
|
|
}
|
|
|
|
for (var i=0; i < this._nested_chain.length; i++){
|
|
if (this._nested_chain[i] == id){ //one level up
|
|
for (var j=i; j < this._nested_chain.length; j++) {
|
|
this.data.getItem(this._nested_chain[j]).$template="";
|
|
}
|
|
if (!i){ //top level
|
|
this._nested_cursor = this.data.branch[0];
|
|
this._nested_chain = [];
|
|
} else {
|
|
this._nested_cursor= this.data.branch[this._nested_chain[i-1]];
|
|
this._nested_chain.splice(i);
|
|
}
|
|
this._is_level_down = false;
|
|
return this.render();
|
|
}
|
|
}
|
|
|
|
var obj = this.getItem(id);
|
|
if (obj.$count){ //one level down
|
|
this._is_level_down = true;
|
|
this._nested_chain.push(id);
|
|
obj.$template = "Back";
|
|
this._nested_cursor = this.data.branch[obj.id];
|
|
return this.render();
|
|
} else {
|
|
if (this._settings.select){
|
|
this._no_animation = true;
|
|
if (this._settings.select=="multiselect" || this._settings.multiselect)
|
|
this.select(id, false, ((this._settings.multiselect == "touch") || e.ctrlKey || e.metaKey), e.shiftKey); //multiselection
|
|
else
|
|
this.select(id);
|
|
this._no_animation = false;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
getOpenState:function(){
|
|
return {parents:this._nested_chain,branch:this._nested_cursor};
|
|
},
|
|
render:function(id,data,type,after){
|
|
var i, lastChain;
|
|
|
|
//start filtering processing=>
|
|
this._nested_chain = webix.copy(this._nested_chain);
|
|
this._nested_cursor = webix.copy(this._nested_cursor);
|
|
|
|
if(this._nested_chain.length){
|
|
for(i = 0;i<this._nested_chain.length;i++){
|
|
if(!this.data.branch[this._nested_chain[i]]){
|
|
this._nested_chain.splice(i,1);
|
|
i--;
|
|
}
|
|
}
|
|
}
|
|
lastChain = (this._nested_chain.length?this._nested_chain[this._nested_chain.length-1]:0);
|
|
this._nested_cursor = webix.copy(this.data.branch[lastChain]) ;
|
|
|
|
if(!this._nested_cursor.length&&this._nested_chain.length){
|
|
this._nested_cursor = [lastChain];
|
|
this._nested_chain.pop();
|
|
}
|
|
//<= end filtering processing
|
|
|
|
if (this._in_animation) {
|
|
return webix.delay(this.render, this, arguments, 100);
|
|
}
|
|
for (i=0; i < this._nested_cursor.length; i++)
|
|
this.data.getItem(this._nested_cursor[i]).$template = "";
|
|
|
|
if (!this._nested_cursor.length)
|
|
this._nested_cursor = this.data.branch[0];
|
|
|
|
this.data.order = webix.toArray([].concat(this._nested_chain).concat(this._nested_cursor));
|
|
|
|
if (this.callEvent("onBeforeRender",[this.data])){
|
|
if(this._no_animation || !this._dataobj.innerHTML || !(webix.animate.isSupported() && this._settings.animate) || (this._prev_nested_chain_length == this._nested_chain.length)) { // if dataobj is empty or animation is not supported
|
|
webix.RenderStack.render.apply(this, arguments);
|
|
}
|
|
else {
|
|
//getRange - returns all elements
|
|
if (this.callEvent("onBeforeRender",[this.data])){
|
|
|
|
if(!this._back_scroll_states)
|
|
this._back_scroll_states = [];
|
|
|
|
var next_div = this._dataobj.cloneNode(false);
|
|
next_div.innerHTML = this.data.getRange().map(this._toHTML,this).join("");
|
|
|
|
var aniset = webix.extend({}, this._settings.animate);
|
|
aniset.direction = (this._is_level_down)?'left':'right';
|
|
|
|
/*scroll position restore*/
|
|
var animArr = [webix.clone(aniset),webix.clone(aniset)];
|
|
if(this._is_level_down){
|
|
this._back_scroll_states.push(this.getScrollState());
|
|
if(webix.Touch&&webix.Touch.$active){
|
|
animArr[0].y = 0;
|
|
animArr[1].y = - this.getScrollState().y;
|
|
}
|
|
}
|
|
else{
|
|
var getScrollState = this._back_scroll_states.pop();
|
|
if(webix.Touch&&webix.Touch.$active){
|
|
animArr[0].y = -getScrollState.y;
|
|
animArr[1].y = - this.getScrollState().y;
|
|
}
|
|
}
|
|
|
|
var line = webix.animate.formLine(
|
|
next_div,
|
|
this._dataobj,
|
|
aniset
|
|
);
|
|
|
|
/*keeping scroll position*/
|
|
if(webix.Touch&&webix.Touch.$active)
|
|
webix.Touch._set_matrix(next_div, 0,this._is_level_down?0:animArr[0].y, "0ms");
|
|
|
|
aniset.master = this;
|
|
aniset.callback = function(){
|
|
this._dataobj = next_div;
|
|
|
|
/*scroll position restore*/
|
|
if(!this._is_level_down){
|
|
if(webix.Touch&&webix.Touch.$active){
|
|
webix.delay(function(){
|
|
webix.Touch._set_matrix(next_div, 0,animArr[0].y, "0ms");
|
|
},this);
|
|
} else if (getScrollState)
|
|
this.scrollTo(0,getScrollState.y);
|
|
}
|
|
else if(!(webix.Touch&&webix.Touch.$active)){
|
|
this.scrollTo(0,0);
|
|
}
|
|
|
|
webix.animate.breakLine(line);
|
|
aniset.master = aniset.callback = null;
|
|
this._htmlmap = null; //clear map, it will be filled at first getItemNode
|
|
this._in_animation = false;
|
|
this.callEvent("onAfterRender",[]);
|
|
};
|
|
|
|
this._in_animation = true;
|
|
webix.animate(line, animArr);
|
|
}
|
|
}
|
|
this._prev_nested_chain_length = this._nested_chain.length;
|
|
}
|
|
},
|
|
templateBack_setter:function(config){
|
|
this.type.templateBack = webix.template(config);
|
|
},
|
|
templateItem_setter:function(config){
|
|
this.type.templateItem = webix.template(config);
|
|
},
|
|
templateGroup_setter:function(config){
|
|
this.type.templateGroup = webix.template(config);
|
|
},
|
|
type:{
|
|
template:function(obj, common){
|
|
if (obj.$count)
|
|
return common.templateGroup(obj, common);
|
|
return common.templateItem(obj, common);
|
|
},
|
|
css:"group",
|
|
classname:function(obj, common, marks){
|
|
return "webix_list_item webix_"+(obj.$count?"group":"item")+(obj.$template?"_back":"")+((marks&&marks.webix_selected)?" webix_selected ":"")+ (obj.$css?obj.$css:"");
|
|
},
|
|
templateStart:webix.template('<div webix_l_id="#id#" class="{common.classname()}" style="width:{common.widthSize()}; height:{common.heightSize()}; overflow:hidden;" {common.aria()}>'),
|
|
templateBack:webix.template("#value#"),
|
|
templateItem:webix.template("#value#"),
|
|
templateGroup:webix.template("#value#"),
|
|
templateEnd:function(obj, common){
|
|
var html = '';
|
|
if(obj.$count) html += "<div class='webix_arrow_icon'></div>";
|
|
html += "</div>";
|
|
return html;
|
|
}
|
|
},
|
|
showItem:function(id){
|
|
var obj, parent;
|
|
if(id){
|
|
obj = this.getItem(id);
|
|
parent = obj.$parent;
|
|
|
|
if (obj.$count)
|
|
parent = obj.id;
|
|
}
|
|
this._nested_cursor = this.data.branch[parent||0];
|
|
this._nested_chain=[];
|
|
|
|
//build _nested_chain
|
|
while(parent){
|
|
this.getItem(parent).$template = "Back";
|
|
this._nested_chain.unshift(parent);
|
|
parent = this.getItem(parent).$parent;
|
|
}
|
|
|
|
//render
|
|
this._no_animation = true;
|
|
this.render();
|
|
this._no_animation = false;
|
|
|
|
//scroll if necessary
|
|
webix.RenderStack.showItem.call(this,id);
|
|
}
|
|
}, webix.Group, webix.ui.list );
|
|
webix.type(webix.ui.grouplist,{});
|
|
|
|
|
|
webix.protoUI({
|
|
name:"unitlist",
|
|
_id:"webix_item_id",
|
|
uniteBy_setter: webix.template,
|
|
render:function(id,data,type,after){
|
|
var config = this._settings;
|
|
if (!this.isVisible(config.id))
|
|
return;
|
|
if (webix.debug_render)
|
|
webix.log("Render: "+this.name+"@"+config.id);
|
|
if(!config.uniteBy){
|
|
if (webix.debug_render){
|
|
webix.log("uniteBy is undefined");
|
|
}
|
|
return false;
|
|
}
|
|
if (id){
|
|
var cont = this.getItemNode(id); //get html element of updated item
|
|
if(cont&&type=="update"&&(this._settings.uniteBy.call(this,data)==this.getItem(id).$unitValue)){
|
|
var t = this._htmlmap[id] = this._toHTMLObject(data);
|
|
webix.html.insertBefore(t, cont);
|
|
webix.html.remove(cont);
|
|
return;
|
|
}
|
|
}
|
|
//full reset
|
|
if (this.callEvent("onBeforeRender",[this.data])){
|
|
this.units = null;
|
|
this._setUnits();
|
|
if(this.units){
|
|
this._dataobj.innerHTML = this._getUnitRange().map(this._toHTML, this).join("");
|
|
this._htmlmap = null;
|
|
}
|
|
this.callEvent("onAfterRender",[]);
|
|
}
|
|
},
|
|
getUnits:function(){
|
|
var result = [];
|
|
if(this.units){
|
|
for(var b in this.units){
|
|
result.push(b);
|
|
}
|
|
}
|
|
return result;
|
|
},
|
|
getUnitList:function(id){
|
|
return (this.units?this.units[id]:null);
|
|
},
|
|
_toHTML:function(obj){
|
|
//check if related template exist
|
|
var mark = this.data._marks[obj.id];
|
|
webix.assert((!obj.$template || this.type["template"+obj.$template]),"RenderStack :: Unknown template: "+obj.$template);
|
|
this.callEvent("onItemRender",[obj]);
|
|
if(obj.$unit){
|
|
return this.type.templateStartHeader(obj,this.type)+this.type.templateHeader.call(this,obj.$unit)+this.type.templateEnd(obj, this.type);
|
|
}
|
|
return this.type.templateStart(obj,this.type,mark)+(obj.$template?this.type["template"+obj.$template]:this.type.template)(obj,this.type)+this.type.templateEnd(obj, this.type);
|
|
},
|
|
_getUnitRange:function(){
|
|
var data,i,u,unit;
|
|
data = [];
|
|
var min = this.data.$min || 0;
|
|
var max = this.data.$max || Infinity;
|
|
var count = 0;
|
|
|
|
for(u in this.units){
|
|
data.push({$unit:u});
|
|
unit = this.units[u];
|
|
for(i=0;i < unit.length;i++){
|
|
if (count == min) data = [{$unit:u}];
|
|
data.push(this.getItem(unit[i]));
|
|
if (count == max) return webix.toArray(data);
|
|
count++;
|
|
}
|
|
}
|
|
|
|
return webix.toArray(data);
|
|
},
|
|
_setUnits: function(){
|
|
var list = this;
|
|
this.units = {};
|
|
this.data.each(function(obj){
|
|
var result = list._settings.uniteBy.call(this,obj);
|
|
obj.$unitValue = result;
|
|
if(!list.units[result])
|
|
list.units[result] = [];
|
|
list.units[result].push(obj.id);
|
|
});
|
|
},
|
|
type:{
|
|
headerHeight: 20,
|
|
templateHeader: function(value){
|
|
return "<span class='webix_unit_header_inner'>"+value+"</span>";
|
|
},
|
|
templateStart:function(obj,type,marks){
|
|
if(obj.$unit)
|
|
return type.templateStartHeader.apply(this,arguments);
|
|
var className = "webix_list_item webix_list_"+(type.css)+"_item"+((marks&&marks.webix_selected)?" webix_selected":"")+(obj.$css?obj.$css:"");
|
|
var style = "width:"+type.widthSize(obj,type,marks)+"; height:"+type.heightSize(obj,type,marks)+"; overflow:hidden;"+(type.layout&&type.layout=="x"?"float:left;":"");
|
|
return '<div webix_item_id="'+obj.id+'" class="'+className+'" style="'+style+'" '+type.aria(obj, type, marks)+'>';
|
|
},
|
|
templateStartHeader:function(obj,type,marks){
|
|
var className = "webix_unit_header webix_unit_"+(type.css)+"_header"+(obj.$selected?"_selected":"");
|
|
var style = "width:"+type.widthSize(obj,type,marks)+"; height:"+type.headerHeight+"px; overflow:hidden;";
|
|
return '<div webix_unit_id="'+obj.$unit+'" class="'+className+'" style="'+style+'">';
|
|
}
|
|
},
|
|
$skin:function(){
|
|
this.type.headerHeight = webix.skin.$active.unitHeaderHeight||20;
|
|
}
|
|
}, webix.ui.list);
|
|
|
|
/*
|
|
UI:DataView
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
Behavior:EditAbility - enables item operation for the items
|
|
|
|
@export
|
|
edit
|
|
stopEdit
|
|
*/
|
|
|
|
|
|
|
|
webix.EditAbility={
|
|
defaults:{
|
|
editaction:"click"
|
|
},
|
|
$init:function(config){
|
|
this._editors = {};
|
|
this._in_edit_mode = 0;
|
|
this._edit_open_time = 0;
|
|
this._contentobj.style.position = "relative";
|
|
if (config)
|
|
config.onDblClick = config.onDblClick || {};
|
|
|
|
this.attachEvent("onAfterRender", this._refocus_inline_editor);
|
|
|
|
//when we call webix.extend the editable prop can be already set
|
|
if (this._settings.editable)
|
|
this._init_edit_events_once();
|
|
|
|
webix.extend(this,webix.Undo);
|
|
},
|
|
_refocus_try:function(newnode){
|
|
try{ //Chrome throws an error if selectionStart is not accessible
|
|
if (typeof newnode.selectionStart == "number") {
|
|
newnode.selectionStart = newnode.selectionEnd = newnode.value.length;
|
|
} else if (typeof newnode.createTextRange != "undefined") {
|
|
var range = newnode.createTextRange();
|
|
range.collapse(false);
|
|
range.select();
|
|
}
|
|
} catch(e){}
|
|
},
|
|
_refocus_inline_editor:function(){
|
|
var editor = this.getEditor();
|
|
if (editor && editor.$inline && !editor.getPopup){
|
|
var newnode = this._locateInput(editor);
|
|
if (newnode && newnode != editor.node){
|
|
var text = editor.node.value;
|
|
editor.node = newnode;
|
|
newnode.value = text;
|
|
newnode.focus();
|
|
|
|
this._refocus_try(newnode);
|
|
} else
|
|
this.editStop();
|
|
}
|
|
},
|
|
editable_setter:function(value){
|
|
if (value)
|
|
this._init_edit_events_once();
|
|
return value;
|
|
},
|
|
_init_edit_events_once:function(){
|
|
//will close editor on any click outside
|
|
webix.attachEvent("onEditEnd", webix.bind(function(){
|
|
if (this._in_edit_mode)
|
|
this.editStop();
|
|
}, this));
|
|
webix.attachEvent("onClick", webix.bind(function(e){
|
|
//but ignore click which opens editor
|
|
if (this._in_edit_mode && (new Date())-this._edit_open_time > 200){
|
|
if (!this._last_editor || this._last_editor.popupType || !e || ( !this._last_editor.node || !this._last_editor.node.contains(e.target || e.srcElement)))
|
|
this.editStop();
|
|
}
|
|
}, this));
|
|
|
|
//property sheet has simple data object, without events
|
|
if (this.data.attachEvent)
|
|
this.data.attachEvent("onIdChange", webix.bind(function(oldid, newid){
|
|
this._changeEditorId(oldid, newid);
|
|
}, this));
|
|
|
|
//when clicking on row - will start editor
|
|
this.attachEvent("onItemClick", function(id){
|
|
if (this._settings.editable && this._settings.editaction == "click")
|
|
this.edit(id);
|
|
});
|
|
this.attachEvent("onItemDblClick", function(id){
|
|
if (this._settings.editable && this._settings.editaction == "dblclick")
|
|
this.edit(id);
|
|
});
|
|
//each time when we clicking on input, reset timer to prevent self-closing
|
|
this._reset_active_editor = webix.bind(function(){
|
|
this._edit_open_time = new Date();
|
|
},this);
|
|
|
|
this._init_edit_events_once = function(){};
|
|
|
|
if (this._component_specific_edit_init)
|
|
this._component_specific_edit_init();
|
|
},
|
|
_handle_live_edits:function(){
|
|
webix.delay(function(){
|
|
var editor = this.getEditor();
|
|
if (editor && editor.config.liveEdit){
|
|
var state = { value:editor.getValue(), old: editor.value };
|
|
if (state.value == state.old) return;
|
|
|
|
editor.value = state.value;
|
|
this._set_new_value(editor, state.value, false);
|
|
this.callEvent("onLiveEdit", [state, editor]);
|
|
}
|
|
}, this);
|
|
},
|
|
_show_editor_form:function(id){
|
|
var form = this._settings.form;
|
|
if (typeof form != "string")
|
|
this._settings.form = form = webix.ui(form).config.id;
|
|
|
|
var form = webix.$$(form);
|
|
var realform = form.setValues?form:form.getChildViews()[0];
|
|
|
|
|
|
realform.setValues(this.getItem(id.row || id));
|
|
form.config.master = this.config.id;
|
|
form.show( this.getItemNode(id) );
|
|
|
|
var first = realform.getChildViews()[0];
|
|
if (first.focus)
|
|
first.focus();
|
|
},
|
|
edit:function(id, preserve, show){
|
|
if (!this.callEvent("onBeforeEditStart", [id])) return;
|
|
if (this._settings.form)
|
|
return this._show_editor_form(id);
|
|
|
|
var editor = this._get_editor_type(id);
|
|
if (editor){
|
|
if (this.getEditor(id)) return;
|
|
if (!preserve) this.editStop();
|
|
|
|
//render html input
|
|
webix.assert(webix.editors[editor], "Invalid editor type: "+editor);
|
|
var type = webix.extend({}, webix.editors[editor]);
|
|
|
|
var node = this._init_editor(id, type, show);
|
|
if (type.config.liveEdit)
|
|
this._live_edits_handler = this.attachEvent("onKeyPress", this._handle_live_edits);
|
|
|
|
var area = type.getPopup?type.getPopup(node)._viewobj:node;
|
|
|
|
if (area)
|
|
webix._event(area, "click", this._reset_active_editor);
|
|
if (node)
|
|
webix._event(node, "change", this._on_editor_change, { bind:{ view:this, id:id }});
|
|
if (show !== false)
|
|
type.focus();
|
|
|
|
if (this.$fixEditor)
|
|
this.$fixEditor(type);
|
|
|
|
//save time of creation to prevent instant closing from the same click
|
|
this._edit_open_time = webix.edit_open_time = new Date();
|
|
|
|
webix.UIManager.setFocus(this, true);
|
|
this.callEvent("onAfterEditStart", [id]);
|
|
return type;
|
|
}
|
|
return null;
|
|
},
|
|
getEditor:function(id){
|
|
if (!id)
|
|
return this._last_editor;
|
|
|
|
return this._editors[id];
|
|
},
|
|
_changeEditorId:function(oldid, newid) {
|
|
var editor = this._editors[oldid];
|
|
if (editor){
|
|
this._editors[newid] = editor;
|
|
editor.id = newid;
|
|
delete this._editors[oldid];
|
|
}
|
|
},
|
|
_on_editor_change:function(e){
|
|
if (this.view.hasEvent("onEditorChange"))
|
|
this.view.callEvent("onEditorChange", [this.id, this.view.getEditorValue(this.id) ]);
|
|
},
|
|
_get_edit_config:function(id){
|
|
return this._settings;
|
|
},
|
|
_init_editor:function(id, type, show){
|
|
var config = type.config = this._get_edit_config(id);
|
|
var node = type.render();
|
|
|
|
if (type.$inline)
|
|
node = this._locateInput(id);
|
|
type.node = node;
|
|
|
|
var item = this.getItem(id);
|
|
//value can be configured by editValue option
|
|
var value = item[this._settings.editValue||"value"];
|
|
//if property was not defined - use empty value
|
|
if (webix.isUndefined(value))
|
|
value = "";
|
|
|
|
type.setValue(value, item);
|
|
type.value = value;
|
|
|
|
this._addEditor(id, type);
|
|
|
|
//show it over cell
|
|
if (show !== false)
|
|
this.showItem(id);
|
|
if (!type.$inline)
|
|
this._sizeToCell(id, node, true);
|
|
|
|
if (type.afterRender)
|
|
type.afterRender();
|
|
|
|
return node;
|
|
},
|
|
_locate_cell:function(id){
|
|
return this.getItemNode(id);
|
|
},
|
|
_locateInput:function(id){
|
|
var cell = this._locate_cell(id);
|
|
if (cell)
|
|
cell = cell.getElementsByTagName("input")[0] || cell;
|
|
|
|
return cell;
|
|
},
|
|
_get_editor_type:function(id){
|
|
return this._settings.editor;
|
|
},
|
|
_addEditor:function(id, type){
|
|
type.id = id;
|
|
this._editors[id]= this._last_editor = type;
|
|
this._in_edit_mode++;
|
|
},
|
|
_removeEditor:function(editor){
|
|
if (this._last_editor == editor)
|
|
this._last_editor = 0;
|
|
|
|
if (editor.destroy)
|
|
editor.destroy();
|
|
|
|
delete editor.popup;
|
|
delete editor.node;
|
|
|
|
delete this._editors[editor.id];
|
|
this._in_edit_mode--;
|
|
},
|
|
focusEditor:function(id){
|
|
var editor = this.getEditor.apply(this, arguments);
|
|
if (editor && editor.focus)
|
|
editor.focus();
|
|
},
|
|
editCancel:function(){
|
|
this.editStop(null, null, true);
|
|
},
|
|
_applyChanges: function(el){
|
|
if (el){
|
|
var ed = this.getEditor();
|
|
if (ed && ed.getPopup && ed.getPopup() == el.getTopParentView()) return;
|
|
}
|
|
this.editStop();
|
|
},
|
|
editStop:function(id){
|
|
if (this._edit_stop) return;
|
|
this._edit_stop = 1;
|
|
|
|
|
|
var cancel = arguments[2];
|
|
var result = 1;
|
|
if (!id){
|
|
this._for_each_editor(function(editor){
|
|
result = result * this._editStop(editor, cancel);
|
|
});
|
|
} else
|
|
result = this._editStop(this._editors[id], cancel);
|
|
|
|
this._edit_stop = 0;
|
|
return result;
|
|
},
|
|
_cellPosition:function(id){
|
|
var html = this.getItemNode(id);
|
|
return {
|
|
left:html.offsetLeft,
|
|
top:html.offsetTop,
|
|
height:html.offsetHeight,
|
|
width:html.offsetWidth,
|
|
parent:this._contentobj
|
|
};
|
|
},
|
|
_sizeToCell:function(id, node, inline){
|
|
//fake inputs
|
|
if (!node.style) return;
|
|
|
|
var pos = this._cellPosition(id);
|
|
|
|
node.style.top = pos.top + "px";
|
|
node.style.left = pos.left + "px";
|
|
|
|
node.style.width = pos.width-1+"px";
|
|
node.style.height = pos.height-1+"px";
|
|
|
|
node.top = pos.top; //later will be used during y-scrolling
|
|
|
|
if (inline) pos.parent.appendChild(node);
|
|
},
|
|
_for_each_editor:function(handler){
|
|
for (var editor in this._editors)
|
|
handler.call(this, this._editors[editor]);
|
|
},
|
|
_editStop:function(editor, ignore){
|
|
if (!editor || webix._final_destruction) return;
|
|
var state = {
|
|
value : editor.getValue(),
|
|
old : editor.value
|
|
};
|
|
if (this.callEvent("onBeforeEditStop", [state, editor, ignore])){
|
|
if (!ignore){
|
|
//special case, state.old = 0, state.value = ""
|
|
//we need to state.old to string, to detect the change
|
|
var old = state.old;
|
|
if (typeof state.value == "string") old += "";
|
|
|
|
if (old != state.value || editor.config.liveEdit){
|
|
var item = this._set_new_value(editor, state.value, true);
|
|
this.updateItem(editor.row || editor.id, item);
|
|
}
|
|
}
|
|
if (editor.$inline)
|
|
editor.node = null;
|
|
else
|
|
webix.html.remove(editor.node);
|
|
|
|
var popup = editor.config.suggest;
|
|
if (popup && typeof popup == "string")
|
|
webix.$$(popup).hide();
|
|
|
|
this._removeEditor(editor);
|
|
if (this._live_edits_handler)
|
|
this.detachEvent(this._live_edits_handler);
|
|
|
|
this.callEvent("onAfterEditStop", [state, editor, ignore]);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
},
|
|
validateEditor:function(id){
|
|
var result = true;
|
|
if (this._settings.rules){
|
|
var editor = this.getEditor(id);
|
|
var key = editor.column||this._settings.editValue||"value";
|
|
var rule = this._settings.rules[key];
|
|
var all = this._settings.rules.$all;
|
|
|
|
if (rule || all){
|
|
var obj = this.data.getItem(editor.row||editor.id);
|
|
var value = editor.getValue();
|
|
var input = editor.getInputNode();
|
|
|
|
if (rule)
|
|
result = rule.call(this, value, obj, key);
|
|
if (all)
|
|
result = all.call(this, value, obj, key) && result;
|
|
|
|
if (result)
|
|
webix.html.removeCss(input, "webix_invalid");
|
|
else
|
|
webix.html.addCss(input, "webix_invalid");
|
|
|
|
webix.callEvent("onLiveValidation", [editor, result, obj, value]);
|
|
}
|
|
}
|
|
return result;
|
|
},
|
|
getEditorValue:function(id){
|
|
var editor;
|
|
if (arguments.length === 0)
|
|
editor = this._last_editor;
|
|
else
|
|
editor = this.getEditor(id);
|
|
|
|
if (editor)
|
|
return editor.getValue();
|
|
},
|
|
getEditState:function(){
|
|
return this._last_editor || false;
|
|
},
|
|
editNext:function(next, from){
|
|
next = next !== false; //true by default
|
|
if (this._in_edit_mode == 1 || from){
|
|
//only if one editor is active
|
|
var editor_next = this._find_cell_next((this._last_editor || from), function(id){
|
|
if (this._get_editor_type(id))
|
|
return true;
|
|
return false;
|
|
}, next);
|
|
|
|
if (this.editStop()){ //if we was able to close previous editor
|
|
if (editor_next){ //and there is a new target
|
|
this.edit(editor_next); //init new editor
|
|
this._after_edit_next(editor_next);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
},
|
|
//stab, used in datatable
|
|
_after_edit_next:function(){},
|
|
_find_cell_next:function(start, check, direction){
|
|
var row = this.getIndexById(start.id);
|
|
var order = this.data.order;
|
|
|
|
if (direction){
|
|
for (var i=row+1; i<order.length; i++){
|
|
if (check.call(this, order[i]))
|
|
return order[i];
|
|
}
|
|
} else {
|
|
for (var i=row-1; i>=0; i--){
|
|
if (check.call(this, order[i]))
|
|
return order[i];
|
|
}
|
|
}
|
|
|
|
return null;
|
|
},
|
|
_set_new_value:function(editor, new_value, copy){
|
|
var item = copy ? {} : this.getItem(editor.id);
|
|
item[this._settings.editValue||"value"] = new_value;
|
|
return item;
|
|
}
|
|
};
|
|
|
|
|
|
(function(){
|
|
|
|
function init_suggest(editor, input){
|
|
var suggest = editor.config.suggest;
|
|
if (suggest){
|
|
var box = editor.config.suggest = create_suggest(suggest);
|
|
var boxobj = webix.$$(box);
|
|
if (boxobj && input)
|
|
boxobj.linkInput(input);
|
|
}
|
|
}
|
|
|
|
function create_suggest(config){
|
|
if (typeof config == "string") return config;
|
|
if (config.linkInput) return config._settings.id;
|
|
|
|
|
|
if (typeof config == "object"){
|
|
if (webix.isArray(config))
|
|
config = { data: config };
|
|
config.view = config.view || "suggest";
|
|
} else if (config === true)
|
|
config = { view:"suggest" };
|
|
|
|
var obj = webix.ui(config);
|
|
return obj.config.id;
|
|
}
|
|
|
|
function getLabel(config){
|
|
var text = config.header && config.header[0]?config.header[0].text:config.editValue || config.label;
|
|
return (text || "").toString().replace(/<[^>]*>/g, "");
|
|
}
|
|
|
|
/*
|
|
this.node - html node, available after render call
|
|
this.config - editor config
|
|
this.value - original value
|
|
this.popup - id of popup
|
|
*/
|
|
webix.editors = {
|
|
"text":{
|
|
focus:function(){
|
|
this.getInputNode(this.node).focus();
|
|
this.getInputNode(this.node).select();
|
|
},
|
|
getValue:function(){
|
|
return this.getInputNode(this.node).value;
|
|
},
|
|
setValue:function(value){
|
|
var input = this.getInputNode(this.node);
|
|
input.value = value;
|
|
|
|
init_suggest(this, input);
|
|
},
|
|
getInputNode:function(){
|
|
return this.node.firstChild;
|
|
},
|
|
render:function(){
|
|
return webix.html.create("div", {
|
|
"class":"webix_dt_editor"
|
|
}, "<input type='text' aria-label='"+getLabel(this.config)+"'>");
|
|
}
|
|
},
|
|
"inline-checkbox":{
|
|
render:function(){ return {}; },
|
|
getValue:function(){
|
|
return this.node.checked;
|
|
},
|
|
setValue:function(){},
|
|
focus:function(){
|
|
this.node.focus();
|
|
},
|
|
getInputNode:function(){},
|
|
$inline:true
|
|
},
|
|
"inline-text":{
|
|
render:function(){ return {}; },
|
|
getValue:function(){
|
|
return this.node.value;
|
|
},
|
|
setValue:function(){},
|
|
focus:function(){
|
|
try{ //IE9
|
|
this.node.select();
|
|
this.node.focus();
|
|
} catch(e){}
|
|
},
|
|
getInputNode:function(){},
|
|
$inline:true
|
|
},
|
|
"checkbox":{
|
|
focus:function(){
|
|
this.getInputNode().focus();
|
|
},
|
|
getValue:function(){
|
|
return this.getInputNode().checked;
|
|
},
|
|
setValue:function(value){
|
|
this.getInputNode().checked = !!value;
|
|
},
|
|
getInputNode:function(){
|
|
return this.node.firstChild.firstChild;
|
|
},
|
|
render:function(){
|
|
return webix.html.create("div", {
|
|
"class":"webix_dt_editor"
|
|
}, "<div><input type='checkbox' aria-label='"+getLabel(this.config)+"'></div>");
|
|
}
|
|
},
|
|
"select":{
|
|
focus:function(){
|
|
this.getInputNode().focus();
|
|
},
|
|
getValue:function(){
|
|
return this.getInputNode().value;
|
|
},
|
|
setValue:function(value){
|
|
this.getInputNode().value = value;
|
|
},
|
|
getInputNode:function(){
|
|
return this.node.firstChild;
|
|
},
|
|
render:function(){
|
|
var html = "";
|
|
var options = this.config.options || this.config.collection;
|
|
webix.assert(options,"options not defined for select editor");
|
|
|
|
if (options.data && options.data.each)
|
|
options.data.each(function(obj){
|
|
html +="<option value='"+obj.id+"'>"+obj.value+"</option>";
|
|
});
|
|
else {
|
|
if (webix.isArray(options)){
|
|
for (var i=0; i<options.length; i++){
|
|
var rec = options[i];
|
|
var isplain = webix.isUndefined(rec.id);
|
|
var id = isplain ? rec : rec.id;
|
|
var label = isplain ? rec : rec.value;
|
|
|
|
html +="<option value='"+id+"'>"+label+"</option>";
|
|
}
|
|
} else for (var key in options){
|
|
html +="<option value='"+key+"'>"+options[key]+"</option>";
|
|
}
|
|
}
|
|
|
|
return webix.html.create("div", {
|
|
"class":"webix_dt_editor"
|
|
}, "<select aria-label='"+getLabel(this.config)+"'>"+html+"</select>");
|
|
}
|
|
},
|
|
popup:{
|
|
focus:function(){
|
|
this.getInputNode().focus();
|
|
},
|
|
destroy:function(){
|
|
this.getPopup().hide();
|
|
},
|
|
getValue:function(){
|
|
return this.getInputNode().getValue()||"";
|
|
},
|
|
setValue:function(value){
|
|
this.getPopup().show(this.node);
|
|
this.getInputNode().setValue(value);
|
|
},
|
|
getInputNode:function(){
|
|
return this.getPopup().getChildViews()[0];
|
|
},
|
|
getPopup:function(){
|
|
if (!this.config._popup)
|
|
this.config._popup = this.config.popup = this.createPopup();
|
|
|
|
return webix.$$(this.config.popup);
|
|
},
|
|
createPopup:function(){
|
|
var popup = this.config.popup || this.config.suggest;
|
|
if (popup){
|
|
var pobj;
|
|
if (typeof popup == "object" && !popup.name){
|
|
popup.view = popup.view || "suggest";
|
|
pobj = webix.ui(webix.copy(popup));
|
|
} else
|
|
pobj = webix.$$(popup);
|
|
|
|
//custom popup may be linked already
|
|
if(!pobj._linked){
|
|
if (pobj.linkInput)
|
|
pobj.linkInput(document.body);
|
|
else if(this.linkInput)
|
|
this.linkInput(document.body);
|
|
pobj._linked = true;
|
|
}
|
|
|
|
return pobj;
|
|
}
|
|
|
|
var type = webix.editors.$popup[this.popupType];
|
|
if (typeof type != "string" && !type.name){
|
|
type = webix.editors.$popup[this.popupType] = webix.ui(type);
|
|
this.popupInit(type);
|
|
|
|
if(!type.linkInput)
|
|
this.linkInput(document.body);
|
|
|
|
}
|
|
return type._settings.id;
|
|
},
|
|
linkInput:function(node){
|
|
webix._event(webix.toNode(node), "keydown", webix.bind(function(e){
|
|
var code = e.which || e.keyCode, list = this.getInputNode();
|
|
if(!list.isVisible()) return;
|
|
|
|
if(list.moveSelection && code < 41 && code > 32){
|
|
var dir;
|
|
if(code == 33) dir = "pgup";
|
|
if(code == 34) dir = "pgdown";
|
|
if(code == 35) dir = "bottom";
|
|
if(code == 36) dir = "top";
|
|
if(code == 37) dir = "left";
|
|
if(code == 38) dir = "up";
|
|
if(code == 39) dir = "right";
|
|
if(code == 40) dir = "down";
|
|
|
|
list.moveSelection(dir);
|
|
}
|
|
// shift+enter support for 'popup' editor
|
|
else if(code === 13 && ( e.target.nodeName !=="TEXTAREA" || !e.shiftKey))
|
|
webix.callEvent("onEditEnd", []);
|
|
|
|
}, this));
|
|
},
|
|
|
|
popupInit:function(popup){},
|
|
popupType:"text",
|
|
render :function(){ return {}; },
|
|
$inline:true
|
|
}
|
|
};
|
|
|
|
webix.editors.color = webix.extend({
|
|
focus :function(){},
|
|
popupType:"color",
|
|
popupInit:function(popup){
|
|
popup.getChildViews()[0].attachEvent("onItemClick", function(value){
|
|
webix.callEvent("onEditEnd",[value]);
|
|
});
|
|
}
|
|
}, webix.editors.popup);
|
|
|
|
webix.editors.date = webix.extend({
|
|
focus :function(){},
|
|
popupType:"date",
|
|
setValue:function(value){
|
|
this._is_string = this.config.stringResult || (value && typeof value == "string");
|
|
webix.editors.popup.setValue.call(this, value);
|
|
},
|
|
getValue:function(){
|
|
return this.getInputNode().getValue(this._is_string?webix.i18n.parseFormatStr:"")||"";
|
|
},
|
|
popupInit:function(popup){
|
|
popup.getChildViews()[0].attachEvent("onDateSelect", function(value){
|
|
webix.callEvent("onEditEnd",[value]);
|
|
});
|
|
}
|
|
}, webix.editors.popup);
|
|
|
|
webix.editors.combo = webix.extend({
|
|
_create_suggest:function(config){
|
|
if(this.config.popup){
|
|
return this.config.popup.config.id;
|
|
}
|
|
else if (config){
|
|
return create_suggest(config);
|
|
} else
|
|
return this._shared_suggest(config);
|
|
},
|
|
_shared_suggest:function(){
|
|
var e = webix.editors.combo;
|
|
return (e._suggest = e._suggest || this._create_suggest(true));
|
|
},
|
|
render:function(){
|
|
var node = webix.html.create("div", {
|
|
"class":"webix_dt_editor"
|
|
}, "<input type='text' role='combobox' aria-label='"+getLabel(this.config)+"'>");
|
|
|
|
//save suggest id for future reference
|
|
var suggest = this.config.suggest = this._create_suggest(this.config.suggest);
|
|
|
|
if (suggest){
|
|
webix.$$(suggest).linkInput(node.firstChild, true);
|
|
webix._event(node.firstChild, "click",webix.bind(this.showPopup, this));
|
|
}
|
|
return node;
|
|
},
|
|
getPopup:function(){
|
|
return webix.$$(this.config.suggest);
|
|
},
|
|
showPopup:function(){
|
|
var popup = this.getPopup();
|
|
var list = popup.getList();
|
|
var input = this.getInputNode();
|
|
var value = this._initial_value;
|
|
|
|
popup.show(input);
|
|
input.setAttribute("aria-expanded", "true");
|
|
if(value ){
|
|
webix.assert(list.exists(value), "Option with ID "+value+" doesn't exist");
|
|
if(list.exists(value)){
|
|
list.select(value);
|
|
list.showItem(value);
|
|
}
|
|
}else{
|
|
list.unselect();
|
|
list.showItem(list.getFirstId());
|
|
}
|
|
popup._last_input_target = input;
|
|
},
|
|
afterRender:function(){
|
|
this.showPopup();
|
|
},
|
|
setValue:function(value){
|
|
this._initial_value = value;
|
|
if (this.config.suggest){
|
|
var sobj = webix.$$(this.config.suggest);
|
|
var data = this.config.collection || this.config.options;
|
|
if (data)
|
|
sobj.getList().data.importData(data);
|
|
|
|
this.getInputNode(this.node).value = sobj.getItemText(value);
|
|
}
|
|
},
|
|
getValue:function(){
|
|
var value = this.getInputNode().value;
|
|
if (this.config.suggest){
|
|
var suggest = webix.$$(this.config.suggest),
|
|
list = suggest.getList();
|
|
if (value || (list.getSelectedId && list.getSelectedId()))
|
|
value = suggest.getSuggestion();
|
|
}
|
|
|
|
return value;
|
|
}
|
|
}, webix.editors.text);
|
|
|
|
|
|
webix.editors.richselect = webix.extend({
|
|
focus:function(){},
|
|
getValue:function(){
|
|
return this.getPopup().getValue();
|
|
},
|
|
setValue:function(value){
|
|
var suggest = this.config.collection || this.config.options;
|
|
var list = this.getInputNode();
|
|
if (suggest)
|
|
this.getPopup().getList().data.importData(suggest);
|
|
|
|
this.getPopup().show(this.node);
|
|
this.getPopup().setValue(value);
|
|
},
|
|
getInputNode:function(){
|
|
return this.getPopup().getList();
|
|
},
|
|
popupInit:function(popup){
|
|
popup.linkInput(document.body);
|
|
},
|
|
popupType:"richselect"
|
|
}, webix.editors.popup);
|
|
|
|
webix.editors.password = webix.extend({
|
|
render:function(){
|
|
return webix.html.create("div", {
|
|
"class":"webix_dt_editor"
|
|
}, "<input type='password' aria-label='"+getLabel(this.config)+"'>");
|
|
}
|
|
}, webix.editors.text);
|
|
|
|
webix.editors.$popup = {
|
|
text:{
|
|
view:"popup", width:250, height:150,
|
|
body:{ view:"textarea" }
|
|
},
|
|
color:{
|
|
view:"popup",
|
|
body:{ view:"colorboard" }
|
|
},
|
|
date:{
|
|
view:"popup", width:250, height:250, padding:0,
|
|
body:{ view:"calendar", icons:true, borderless:true }
|
|
},
|
|
richselect:{
|
|
view:"suggest",
|
|
body:{ view:"list", select:true }
|
|
}
|
|
};
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
Renders collection of items
|
|
Always shows y-scroll
|
|
Can be used with huge datasets
|
|
|
|
@export
|
|
show
|
|
render
|
|
*/
|
|
|
|
|
|
|
|
webix.VirtualRenderStack={
|
|
$init:function(){
|
|
webix.assert(this.render,"VirtualRenderStack :: Object must use RenderStack first");
|
|
|
|
this._htmlmap={}; //init map of rendered elements
|
|
|
|
//we need to repaint area each time when view resized or scrolling state is changed
|
|
webix._event(this._viewobj,"scroll",webix.bind(this._render_visible_rows,this));
|
|
if(webix.env.touch){
|
|
this.attachEvent("onAfterScroll", webix.bind(this._render_visible_rows,this));
|
|
}
|
|
//here we store IDs of elemenst which doesn't loadede yet, but need to be rendered
|
|
this._unrendered_area=[];
|
|
},
|
|
//return html object by item's ID. Can return null for not-rendering element
|
|
getItemNode:function(search_id){
|
|
//collection was filled in _render_visible_rows
|
|
return this._htmlmap[search_id];
|
|
},
|
|
//adjust scrolls to make item visible
|
|
showItem:function(id){
|
|
var range = this._getVisibleRange();
|
|
var ind = this.data.getIndexById(id);
|
|
//we can't use DOM method for not-rendered-yet items, so fallback to pure math
|
|
var dy = Math.floor(ind/range._dx)*range._y;
|
|
var state = this.getScrollState();
|
|
if (dy<state.y || dy + this._settings.height >= state.y + this._content_height)
|
|
this.scrollTo(0, dy);
|
|
},
|
|
//repain self after changes in DOM
|
|
//for add, delete, move operations - render is delayed, to minify performance impact
|
|
render:function(id,data,type){
|
|
if (!this.isVisible(this._settings.id) || this.$blockRender)
|
|
return;
|
|
|
|
if (webix.debug_render)
|
|
webix.log("Render: "+this.name+"@"+this._settings.id);
|
|
|
|
if (id){
|
|
var cont = this.getItemNode(id); //old html element
|
|
switch(type){
|
|
case "update":
|
|
if (!cont) return;
|
|
//replace old with new
|
|
var t = this._htmlmap[id] = this._toHTMLObject(data);
|
|
webix.html.insertBefore(t, cont);
|
|
webix.html.remove(cont);
|
|
break;
|
|
default: // "move", "add", "delete"
|
|
/*
|
|
for all above operations, full repainting is necessary
|
|
but from practical point of view, we need only one repainting per thread
|
|
code below initiates double-thread-rendering trick
|
|
*/
|
|
this._render_delayed();
|
|
break;
|
|
}
|
|
} else {
|
|
//full repainting
|
|
if (this.callEvent("onBeforeRender",[this.data])){
|
|
this._htmlmap = {}; //nulify links to already rendered elements
|
|
this._render_visible_rows(null, true);
|
|
// clear delayed-rendering, because we already have repaint view
|
|
this._wait_for_render = false;
|
|
this.callEvent("onAfterRender",[]);
|
|
}
|
|
}
|
|
},
|
|
//implement double-thread-rendering pattern
|
|
_render_delayed:function(){
|
|
//this flag can be reset from outside, to prevent actual rendering
|
|
if (this._wait_for_render) return;
|
|
this._wait_for_render = true;
|
|
|
|
window.setTimeout(webix.bind(function(){
|
|
this.render();
|
|
},this),1);
|
|
},
|
|
//create empty placeholders, which will take space before rendering
|
|
_create_placeholder:function(height){
|
|
if(webix.env.maxHTMLElementSize)
|
|
height = Math.min(webix.env.maxHTMLElementSize, height);
|
|
var node = document.createElement("DIV");
|
|
node.style.cssText = "height:"+height+"px; width:100%; overflow:hidden;";
|
|
return node;
|
|
},
|
|
/*
|
|
Methods get coordinatest of visible area and checks that all related items are rendered
|
|
If, during rendering, some not-loaded items was detected - extra data loading is initiated.
|
|
reset - flag, which forces clearing of previously rendered elements
|
|
*/
|
|
_render_visible_rows:function(e,reset){
|
|
this._unrendered_area=[]; //clear results of previous calls
|
|
|
|
var viewport = this._getVisibleRange(); //details of visible view
|
|
|
|
if (!this._dataobj.firstChild || reset){ //create initial placeholder - for all view space
|
|
this._dataobj.innerHTML="";
|
|
this._dataobj.appendChild(this._create_placeholder(viewport._max));
|
|
//register placeholder in collection
|
|
this._htmlrows = [this._dataobj.firstChild];
|
|
}
|
|
|
|
/*
|
|
virtual rendering breaks all view on rows, because we know widht of item
|
|
we can calculate how much items can be placed on single row, and knowledge
|
|
of that, allows to calculate count of such rows
|
|
|
|
each time after scrolling, code iterate through visible rows and render items
|
|
in them, if they are not rendered yet
|
|
|
|
both rendered rows and placeholders are registered in _htmlrows collection
|
|
*/
|
|
|
|
//position of first visible row
|
|
var t = viewport._from;
|
|
|
|
while(t<=viewport._height){ //loop for all visible rows
|
|
//skip already rendered rows
|
|
while(this._htmlrows[t] && this._htmlrows[t]._filled && t<=viewport._height){
|
|
t++;
|
|
}
|
|
//go out if all is rendered
|
|
if (t>viewport._height) break;
|
|
|
|
//locate nearest placeholder
|
|
var holder = t;
|
|
while (!this._htmlrows[holder]) holder--;
|
|
var holder_row = this._htmlrows[holder];
|
|
|
|
//render elements in the row
|
|
var base = t*viewport._dx+(this.data.$min||0); //index of rendered item
|
|
if (base > (this.data.$max||Infinity)) break; //check that row is in virtual bounds, defined by paging
|
|
var nextpoint = Math.min(base+viewport._dx-1,(this.data.$max?this.data.$max-1:Infinity));
|
|
var node = this._create_placeholder(viewport._y);
|
|
//all items in rendered row
|
|
var range = this.data.getIndexRange(base, nextpoint);
|
|
if (!range.length) break;
|
|
|
|
var loading = { $template:"Loading" };
|
|
for (var i=0; i<range.length; i++){
|
|
if (!range[i])
|
|
this._unrendered_area.push(base+i);
|
|
range[i] = this._toHTML(range[i]||loading);
|
|
}
|
|
|
|
node.innerHTML=range.join(""); //actual rendering
|
|
for (var i=0; i < range.length; i++) //register all new elements for later usage in getItemNode
|
|
this._htmlmap[this.data.getIdByIndex(base+i)]=node.childNodes[i];
|
|
|
|
//correct placeholders
|
|
var h = parseFloat(holder_row.style.height,10);
|
|
var delta = (t-holder)*viewport._y;
|
|
var delta2 = (h-delta-viewport._y);
|
|
|
|
//add new row to the DOOM
|
|
webix.html.insertBefore(node,delta?holder_row.nextSibling:holder_row,this._dataobj);
|
|
this._htmlrows[t]=node;
|
|
node._filled = true;
|
|
|
|
/*
|
|
if new row is at start of placeholder - decrease placeholder's height
|
|
else if new row takes whole placeholder - remove placeholder from DOM
|
|
else
|
|
we are inserting row in the middle of existing placeholder
|
|
decrease height of existing one, and add one more,
|
|
before the newly added row
|
|
*/
|
|
if (delta <= 0 && delta2>0){
|
|
holder_row.style.height = delta2+"px";
|
|
this._htmlrows[t+1] = holder_row;
|
|
} else {
|
|
if (delta<0)
|
|
webix.html.remove(holder_row);
|
|
else
|
|
holder_row.style.height = delta+"px";
|
|
if (delta2>0){
|
|
var new_space = this._htmlrows[t+1] = this._create_placeholder(delta2);
|
|
webix.html.insertBefore(new_space,node.nextSibling,this._dataobj);
|
|
}
|
|
}
|
|
|
|
|
|
t++;
|
|
}
|
|
|
|
//when all done, check for non-loaded items
|
|
if (this._unrendered_area.length){
|
|
//we have some data to load
|
|
//detect borders
|
|
var from = this._unrendered_area[0];
|
|
var to = this._unrendered_area.pop()+1;
|
|
if (to>from){
|
|
//initiate data loading
|
|
var count = to - from;
|
|
if (this._maybe_loading_already(count, from)) return;
|
|
|
|
count = Math.max(count, (this._settings.datafetch||this._settings.loadahead||0));
|
|
this.loadNext(count, from);
|
|
}
|
|
}
|
|
},
|
|
//calculates visible view
|
|
_getVisibleRange:function(){
|
|
var state = this.getScrollState();
|
|
var top = state.y;
|
|
var width = this._content_width;
|
|
var height = this._content_height;
|
|
|
|
//size of single item
|
|
var t = this.type;
|
|
|
|
var dx = Math.floor(width/t.width)||1; //at least single item per row
|
|
|
|
var min = Math.floor(top/t.height); //index of first visible row
|
|
var dy = Math.ceil((height+top)/t.height)-1; //index of last visible row
|
|
//total count of items, paging can affect this math
|
|
var count = this.data.$max?(this.data.$max-this.data.$min):this.data.count();
|
|
var max = Math.ceil(count/dx)*t.height; //size of view in rows
|
|
|
|
return { _from:min, _height:dy, _top:top, _max:max, _y:t.height, _dx:dx};
|
|
},
|
|
_cellPosition:function(id){
|
|
var html = this.getItemNode(id);
|
|
if (!html){
|
|
this.showItem(id);
|
|
this._render_visible_rows();
|
|
html = this.getItemNode(id);
|
|
}
|
|
return {
|
|
left:html.offsetLeft,
|
|
top:html.offsetTop,
|
|
height:html.offsetHeight,
|
|
width:html.offsetWidth,
|
|
parent:this._contentobj
|
|
};
|
|
}
|
|
};
|
|
|
|
|
|
|
|
webix.protoUI({
|
|
name:"dataview",
|
|
$init:function(config){
|
|
if (config.sizeToContent)
|
|
//method need to be called before data-loaders
|
|
//so we are using unshift to place it at start
|
|
this.$ready.unshift(this._after_init_call);
|
|
var prerender = config.prerender || this.defaults.prerender;
|
|
if (prerender === false || (prerender !== true && config.height !== "auto" && !config.autoheight))
|
|
webix.extend(this, webix.VirtualRenderStack, true);
|
|
if (config.autoheight)
|
|
config.scroll = false;
|
|
|
|
this._contentobj.className+=" webix_dataview";
|
|
|
|
this._viewobj.setAttribute("role", "listbox");
|
|
},
|
|
_after_init_call:function(){
|
|
var test = webix.html.create("DIV",0,this.type.template({}));
|
|
test.style.position="absolute";
|
|
document.body.appendChild(test);
|
|
this.type.width = test.offsetWidth;
|
|
this.type.height = test.offsetHeight;
|
|
|
|
webix.html.remove(test);
|
|
},
|
|
defaults:{
|
|
scroll:true,
|
|
datafetch:50,
|
|
navigation:true
|
|
},
|
|
_id:"webix_f_id",
|
|
_itemClassName:"webix_dataview_item",
|
|
on_click:{
|
|
webix_dataview_item:function(e,id){
|
|
if (this._settings.select){
|
|
if (this._settings.select=="multiselect" || this._settings.multiselect)
|
|
this.select(id, false, ((this._settings.multiselect == "touch") || e.ctrlKey || e.metaKey), e.shiftKey); //multiselection
|
|
else
|
|
this.select(id);
|
|
}
|
|
}
|
|
},
|
|
on_dblclick:{
|
|
},
|
|
on_mouse_move:{
|
|
},
|
|
type:{
|
|
//normal state of item
|
|
template:webix.template("#value#"),
|
|
//in case of dyn. loading - temporary spacer
|
|
templateLoading:webix.template("Loading..."),
|
|
width:160,
|
|
height:50,
|
|
classname:function(obj, common, marks){
|
|
var css = "webix_dataview_item ";
|
|
|
|
if (common.css) css +=common.css+" ";
|
|
if (obj.$css){
|
|
if (typeof obj.$css == "object")
|
|
obj.$css = webix.html.createCss(obj.$css);
|
|
css +=obj.$css+" ";
|
|
}
|
|
if (marks && marks.$css) css +=marks.$css+" ";
|
|
|
|
return css;
|
|
},
|
|
aria:function(obj, common, marks){
|
|
return 'role="option"'+(marks && marks.webix_selected?' aria-selected="true" tabindex="0"':' tabindex="-1"');
|
|
},
|
|
templateStart:webix.template('<div webix_f_id="#id#" class="{common.classname()}" {common.aria()} style="width:{common.width}px; height:{common.height}px; float:left; overflow:hidden;">'),
|
|
templateEnd:webix.template("</div>")
|
|
|
|
},
|
|
_calck_autoheight:function(width){
|
|
return (this._settings.height = this.type.height * Math.ceil( this.data.count() / Math.floor(width / this.type.width)));
|
|
},
|
|
autoheight_setter:function(mode){
|
|
if (mode){
|
|
this.data.attachEvent("onStoreLoad", webix.bind(this.resize, this));
|
|
this._contentobj.style.overflowY = "hidden";
|
|
}
|
|
return mode;
|
|
},
|
|
$getSize:function(dx, dy){
|
|
if ((this._settings.xCount >0) && this.type.width != "auto" && !this._autowidth)
|
|
this._settings.width = this.type.width*this._settings.xCount + (this._scroll_y?webix.ui.scrollSize:0);
|
|
if (this._settings.yCount && this.type.height != "auto")
|
|
this._settings.height = this.type.height*this._settings.yCount;
|
|
|
|
var width = this._settings.width || this._content_width;
|
|
if (this._settings.autoheight && width){
|
|
this._calck_autoheight(width);
|
|
this.scroll_setter(false);
|
|
}
|
|
return webix.ui.view.prototype.$getSize.call(this, dx, dy);
|
|
},
|
|
_recalk_counts:function(){
|
|
var render = false;
|
|
if (this._settings.yCount && this.type.height == "auto"){
|
|
this.type.height = Math.floor(this._content_height/this._settings.yCount);
|
|
render = true;
|
|
}
|
|
if (this._settings.xCount && (this.type.width == "auto"||this._autowidth)){
|
|
this._autowidth = true; //flag marks that width was set to "auto" initially
|
|
this.type.width = Math.floor(this._content_width/this._settings.xCount);
|
|
render = true;
|
|
} else
|
|
this._autowidth = false;
|
|
|
|
return render;
|
|
},
|
|
$setSize:function(x,y){
|
|
if (webix.ui.view.prototype.$setSize.call(this, x, y)){
|
|
if (this._settings.autoheight && this._calck_autoheight() != this._content_height)
|
|
return webix.delay(this.resize, this);
|
|
|
|
if (this._recalk_counts() || this._render_visible_rows)
|
|
this.render();
|
|
}
|
|
}
|
|
}, webix.DataMove, webix.DragItem, webix.MouseEvents, webix.KeysNavigation, webix.SelectionModel, webix.Scrollable, webix.CustomPrint, webix.ui.proto);
|
|
|
|
|
|
|
|
webix.DataDriver.htmltable={
|
|
|
|
//convert json string to json object if necessary
|
|
toObject:function(data){
|
|
data = webix.toNode(data);
|
|
webix.assert(data, "table is not found");
|
|
webix.assert(data.tagName.toLowerCase() === 'table', "Incorrect table object");
|
|
|
|
var tr = data.rows;
|
|
webix.html.remove(data);
|
|
return tr;
|
|
},
|
|
//get array of records
|
|
getRecords:function(data){
|
|
var new_data = [];
|
|
//skip header rows if necessary
|
|
var i = (data[0] && data[0]._webix_skip)?1:0;
|
|
|
|
for (; i < data.length; i++)
|
|
new_data.push(data[i]);
|
|
return new_data;
|
|
},
|
|
//get hash of properties for single record
|
|
getDetails:function(data){
|
|
var td = data.getElementsByTagName('td');
|
|
data = {};
|
|
//get hash of properties for single record, data named as "data{index}"
|
|
for (var i=0; i < td.length; i++) {
|
|
data['data' + i] = td[i].innerHTML;
|
|
}
|
|
return data;
|
|
},
|
|
//get count of data and position at which new data need to be inserted
|
|
getInfo:function(data){
|
|
// dyn loading is not supported for htmltable
|
|
return {
|
|
size:0,
|
|
from:0
|
|
};
|
|
},
|
|
getOptions:function(){},
|
|
|
|
/*! gets header from first table row
|
|
**/
|
|
getConfig: function(data) {
|
|
var columns = [];
|
|
var td = data[0].getElementsByTagName('th');
|
|
if (td.length) data[0]._webix_skip = true;
|
|
for (var i = 0; i < td.length; i++) {
|
|
var col = {
|
|
id: 'data' + i,
|
|
header: this._de_json(td[i].innerHTML)
|
|
};
|
|
var attrs = this._get_attrs(td[i]);
|
|
col = webix.extend(col, attrs);
|
|
columns.push(col);
|
|
}
|
|
return columns;
|
|
},
|
|
|
|
_de_json:function(str){
|
|
var pos = str.indexOf("json://");
|
|
|
|
if (pos != -1)
|
|
str = JSON.parse(str.substr(pos+7));
|
|
return str;
|
|
},
|
|
|
|
/*! gets hash of html-element attributes
|
|
**/
|
|
_get_attrs: function(el) {
|
|
var attr = el.attributes;
|
|
var hash = {};
|
|
for (var i = 0; i < attr.length; i++) {
|
|
hash[attr[i].nodeName] = this._de_json(attr[i].nodeValue);
|
|
}
|
|
hash.width = parseInt(hash.width, 10);
|
|
return hash;
|
|
}
|
|
};
|
|
webix.protoUI({
|
|
name:"vscroll",
|
|
defaults:{
|
|
scroll:"x",
|
|
scrollStep:40,
|
|
scrollPos:0,
|
|
scrollSize:18,
|
|
scrollVisible:1,
|
|
zoom:1
|
|
},
|
|
$init:function(config){
|
|
var dir = config.scroll||"x";
|
|
var node = this._viewobj = webix.toNode(config.container);
|
|
node.className += " webix_vscroll_"+dir;
|
|
node.innerHTML="<div class='webix_vscroll_body'></div>";
|
|
webix._event(node,"scroll", this._onscroll,{bind:this});
|
|
|
|
this._last_set_size = 0;
|
|
this._last_scroll_pos = 0;
|
|
},
|
|
reset:function(){
|
|
this._last_scroll_pos = this.config.scrollPos = 0;
|
|
this._viewobj[this.config.scroll == "x"?"scrollLeft":"scrollTop"] = 0;
|
|
},
|
|
_check_quantum:function(value){
|
|
if (value>1500000){
|
|
this._settings.zoom = Math.floor(value/1500000)+1;
|
|
this._zoom_limit = value-this._last_set_size;
|
|
value = Math.floor(value/this._settings.zoom)+this._last_set_size;
|
|
} else {
|
|
this._settings.zoom = 1;
|
|
this._zoom_limit = Infinity;
|
|
}
|
|
return value;
|
|
},
|
|
scrollWidth_setter:function(value){
|
|
value = this._check_quantum(value);
|
|
this._viewobj.firstChild.style.width = value+"px";
|
|
return value;
|
|
},
|
|
scrollHeight_setter:function(value){
|
|
value = this._check_quantum(value);
|
|
this._viewobj.firstChild.style.height = value+"px";
|
|
return value;
|
|
},
|
|
sizeTo:function(value, top, bottom){
|
|
value = value-(top||0)-(bottom||0);
|
|
|
|
var width = this._settings.scrollSize;
|
|
//IEFix
|
|
//IE doesn't react on scroll-click if it has not at least 1 px of visible content
|
|
if (webix.env.isIE && width)
|
|
width += 1;
|
|
if (!width && this._settings.scrollVisible && !webix.env.$customScroll){
|
|
this._viewobj.style.pointerEvents="none";
|
|
width = 14;
|
|
}
|
|
|
|
if (!width){
|
|
this._viewobj.style.display = 'none';
|
|
} else {
|
|
this._viewobj.style.display = 'block';
|
|
if (top)
|
|
this._viewobj.style.marginTop = top+ "px";
|
|
this._viewobj.style[this._settings.scroll == "x"?"width":"height"] = Math.max(0,value)+"px";
|
|
this._viewobj.style[this._settings.scroll == "x"?"height":"width"] = width+"px";
|
|
}
|
|
|
|
this._last_set_size = value;
|
|
},
|
|
getScroll:function(){
|
|
return this._settings.scrollPos*this._settings.zoom;
|
|
},
|
|
getSize:function(){
|
|
return (this._settings.scrollWidth||this._settings.scrollHeight)*this._settings.zoom;
|
|
},
|
|
scrollTo:function(value){
|
|
if (value<0)
|
|
value = 0;
|
|
var config = this._settings;
|
|
|
|
value = Math.min(((config.scrollWidth||config.scrollHeight)-this._last_set_size)*config.zoom, value);
|
|
|
|
if (value < 0) value = 0;
|
|
var svalue = value/config.zoom;
|
|
|
|
if (this._last_scroll_pos != svalue){
|
|
this._viewobj[config.scroll == "x"?"scrollLeft":"scrollTop"] = svalue;
|
|
this._onscroll_inner(svalue);
|
|
return true;
|
|
}
|
|
},
|
|
_onscroll:function(){
|
|
var x = this._viewobj[this._settings.scroll == "x"?"scrollLeft":"scrollTop"];
|
|
if (x != this._last_scroll_pos)
|
|
this._onscroll_inner(x);
|
|
},
|
|
_onscroll_inner:function(value){
|
|
this._last_scroll_pos = value;
|
|
this._settings.scrollPos = (Math.min(this._zoom_limit, value*this._settings.zoom) || 0);
|
|
|
|
this.callEvent("onScroll",[this._settings.scrollPos]);
|
|
},
|
|
activeArea:function(area, x_mode){
|
|
this._x_scroll_mode = x_mode;
|
|
webix._event(area,(webix.env.isIE8 ? "mousewheel" : "wheel"),this._on_wheel,{bind:this});
|
|
this._add_touch_events(area);
|
|
},
|
|
|
|
_add_touch_events: function(area){
|
|
if(!webix.env.touch && window.navigator.pointerEnabled){
|
|
webix.html.addCss(area,"webix_scroll_touch_ie",true);
|
|
webix._event(area, "pointerdown", function(e){
|
|
if(e.pointerType == "touch" || e.pointerType == "pen"){
|
|
this._start_context = webix.Touch._get_context_m(e);
|
|
this._start_scroll_pos = this._settings.scrollPos;
|
|
}
|
|
},{bind:this});
|
|
|
|
webix.event(document.body, "pointermove", function(e){
|
|
var scroll;
|
|
if(this._start_context){
|
|
this._current_context = webix.Touch._get_context_m(e);
|
|
if(this._settings.scroll == "x" ){
|
|
scroll = this._current_context.x - this._start_context.x;
|
|
}
|
|
else if(this._settings.scroll == "y"){
|
|
scroll = this._current_context.y - this._start_context.y;
|
|
}
|
|
if(scroll && Math.abs(scroll) > 5){
|
|
this.scrollTo(this._start_scroll_pos - scroll);
|
|
}
|
|
}
|
|
},{bind:this});
|
|
webix.event(window, "pointerup", function(e){
|
|
if(this._start_context){
|
|
this._start_context = this._current_context = null;
|
|
}
|
|
},{bind:this});
|
|
}
|
|
|
|
},
|
|
_on_wheel:function(e){
|
|
var dir = 0;
|
|
var step = e.deltaMode === 0 ? 30 : 1;
|
|
|
|
if (webix.env.isIE8)
|
|
dir = e.detail = -e.wheelDelta / 30;
|
|
|
|
if (e.deltaX && Math.abs(e.deltaX) > Math.abs(e.deltaY)){
|
|
//x-scroll
|
|
if (this._x_scroll_mode && this._settings.scrollVisible)
|
|
dir = e.deltaX / step;
|
|
} else {
|
|
//y-scroll
|
|
if (!this._x_scroll_mode && this._settings.scrollVisible){
|
|
if (webix.isUndefined(e.deltaY))
|
|
dir = e.detail;
|
|
else
|
|
dir = e.deltaY / step;
|
|
}
|
|
}
|
|
|
|
// Safari requires target preserving
|
|
// (used in _check_rendered_cols of DataTable)
|
|
if(webix.env.isSafari)
|
|
this._scroll_trg = e.target|| e.srcElement;
|
|
|
|
if (dir)
|
|
if (this.scrollTo(this._settings.scrollPos + dir*this._settings.scrollStep))
|
|
return webix.html.preventEvent(e);
|
|
|
|
}
|
|
}, webix.EventSystem, webix.Settings);
|
|
|
|
|
|
webix.Number={
|
|
format: function(value, config){
|
|
if (value === "" || typeof value === "undefined") return value;
|
|
|
|
config = config||webix.i18n;
|
|
value = parseFloat(value);
|
|
|
|
var sign = value < 0 ? "-":"";
|
|
value = Math.abs(value);
|
|
|
|
var str = value.toFixed(config.decimalSize).toString();
|
|
str = str.split(".");
|
|
|
|
var int_value = "";
|
|
if (config.groupSize){
|
|
var step = config.groupSize;
|
|
var i=str[0].length;
|
|
do {
|
|
i-=step;
|
|
var chunk = (i>0)?str[0].substr(i,step):str[0].substr(0,step+i);
|
|
int_value = chunk+(int_value?config.groupDelimiter+int_value:"");
|
|
} while(i>0);
|
|
} else
|
|
int_value = str[0];
|
|
|
|
if (config.decimalSize)
|
|
return sign + int_value + config.decimalDelimiter + str[1];
|
|
else
|
|
return sign + int_value;
|
|
},
|
|
numToStr:function(config){
|
|
return function(value){
|
|
return webix.Number.format(value, config);
|
|
};
|
|
}
|
|
};
|
|
|
|
webix.Date={
|
|
startOnMonday:false,
|
|
|
|
toFixed:function(num){
|
|
if (num<10) return "0"+num;
|
|
return num;
|
|
},
|
|
weekStart:function(date){
|
|
date = this.copy(date);
|
|
|
|
var shift=date.getDay();
|
|
if (this.startOnMonday){
|
|
if (shift===0) shift=6;
|
|
else shift--;
|
|
}
|
|
return this.datePart(this.add(date,-1*shift,"day"));
|
|
},
|
|
monthStart:function(date){
|
|
date = this.copy(date);
|
|
|
|
date.setDate(1);
|
|
return this.datePart(date);
|
|
},
|
|
yearStart:function(date){
|
|
date = this.copy(date);
|
|
|
|
date.setMonth(0);
|
|
return this.monthStart(date);
|
|
},
|
|
dayStart:function(date){
|
|
return this.datePart(date, true);
|
|
},
|
|
dateToStr:function(format,utc){
|
|
if (typeof format == "function") return format;
|
|
|
|
if(webix.env.strict){
|
|
return function(date){
|
|
var str = "";
|
|
var lastPos = 0;
|
|
format.replace(/%[a-zA-Z]/g,function(s,pos){
|
|
str += format.slice(lastPos,pos);
|
|
var fn = function(date){
|
|
if( s == "%d") return webix.Date.toFixed(date.getDate());
|
|
if( s == "%m") return webix.Date.toFixed((date.getMonth()+1));
|
|
if( s == "%j") return date.getDate();
|
|
if( s == "%n") return (date.getMonth()+1);
|
|
if( s == "%y") return webix.Date.toFixed(date.getFullYear()%100);
|
|
if( s == "%Y") return date.getFullYear();
|
|
if( s == "%D") return webix.i18n.calendar.dayShort[date.getDay()];
|
|
if( s == "%l") return webix.i18n.calendar.dayFull[date.getDay()];
|
|
if( s == "%M") return webix.i18n.calendar.monthShort[date.getMonth()];
|
|
if( s == "%F") return webix.i18n.calendar.monthFull[date.getMonth()];
|
|
if( s == "%h") return webix.Date.toFixed((date.getHours()+11)%12+1);
|
|
if( s == "%g") return ((date.getHours()+11)%12+1);
|
|
if( s == "%G") return date.getHours();
|
|
if( s == "%H") return webix.Date.toFixed(date.getHours());
|
|
if( s == "%i") return webix.Date.toFixed(date.getMinutes());
|
|
if( s == "%a") return (date.getHours()>11?webix.i18n.pm[0]:webix.i18n.am[0]);
|
|
if( s == "%A") return (date.getHours()>11?webix.i18n.pm[1]:webix.i18n.am[1]);
|
|
if( s == "%s") return webix.Date.toFixed(date.getSeconds());
|
|
if( s == "%S") return webix.Date.toFixed(date.getMilliseconds());
|
|
if( s == "%W") return webix.Date.toFixed(webix.Date.getISOWeek(date));
|
|
if( s == "%c"){
|
|
var str = date.getFullYear();
|
|
str += "-"+webix.Date.toFixed((date.getMonth()+1));
|
|
str += "-"+webix.Date.toFixed(date.getDate());
|
|
str += "T";
|
|
str += webix.Date.toFixed(date.getHours());
|
|
str += ":"+webix.Date.toFixed(date.getMinutes());
|
|
str += ":"+webix.Date.toFixed(date.getSeconds());
|
|
return str;
|
|
}
|
|
return s;
|
|
};
|
|
str += fn(date);
|
|
lastPos = pos + 2;
|
|
});
|
|
str += format.slice(lastPos,format.length);
|
|
return str;
|
|
};
|
|
|
|
}
|
|
|
|
format=format.replace(/%[a-zA-Z]/g,function(a){
|
|
switch(a){
|
|
case "%d": return "\"+webix.Date.toFixed(date.getDate())+\"";
|
|
case "%m": return "\"+webix.Date.toFixed((date.getMonth()+1))+\"";
|
|
case "%j": return "\"+date.getDate()+\"";
|
|
case "%n": return "\"+(date.getMonth()+1)+\"";
|
|
case "%y": return "\"+webix.Date.toFixed(date.getFullYear()%100)+\"";
|
|
case "%Y": return "\"+date.getFullYear()+\"";
|
|
case "%D": return "\"+webix.i18n.calendar.dayShort[date.getDay()]+\"";
|
|
case "%l": return "\"+webix.i18n.calendar.dayFull[date.getDay()]+\"";
|
|
case "%M": return "\"+webix.i18n.calendar.monthShort[date.getMonth()]+\"";
|
|
case "%F": return "\"+webix.i18n.calendar.monthFull[date.getMonth()]+\"";
|
|
case "%h": return "\"+webix.Date.toFixed((date.getHours()+11)%12+1)+\"";
|
|
case "%g": return "\"+((date.getHours()+11)%12+1)+\"";
|
|
case "%G": return "\"+date.getHours()+\"";
|
|
case "%H": return "\"+webix.Date.toFixed(date.getHours())+\"";
|
|
case "%i": return "\"+webix.Date.toFixed(date.getMinutes())+\"";
|
|
case "%a": return "\"+(date.getHours()>11?webix.i18n.pm[0]:webix.i18n.am[0])+\"";
|
|
case "%A": return "\"+(date.getHours()>11?webix.i18n.pm[1]:webix.i18n.am[1])+\"";
|
|
case "%s": return "\"+webix.Date.toFixed(date.getSeconds())+\"";
|
|
case "%S": return "\"+webix.Date.toFixed(date.getMilliseconds())+\"";
|
|
case "%W": return "\"+webix.Date.toFixed(webix.Date.getISOWeek(date))+\"";
|
|
case "%c":
|
|
var str = "\"+date.getFullYear()+\"";
|
|
str += "-\"+webix.Date.toFixed((date.getMonth()+1))+\"";
|
|
str += "-\"+webix.Date.toFixed(date.getDate())+\"";
|
|
str += "T";
|
|
str += "\"+webix.Date.toFixed(date.getHours())+\"";
|
|
str += ":\"+webix.Date.toFixed(date.getMinutes())+\"";
|
|
str += ":\"+webix.Date.toFixed(date.getSeconds())+\"";
|
|
if(utc === true)
|
|
str += "Z";
|
|
return str;
|
|
|
|
default: return a;
|
|
}
|
|
});
|
|
if (utc===true) format=format.replace(/date\.get/g,"date.getUTC");
|
|
return new Function("date","if (!date) return ''; if (!date.getMonth) date=webix.i18n.parseFormatDate(date); return \""+format+"\";");
|
|
},
|
|
strToDate:function(format,utc){
|
|
if (typeof format == "function") return format;
|
|
|
|
var mask=format.match(/%[a-zA-Z]/g);
|
|
var splt="var temp=date.split(/[^0-9a-zA-Z]+/g);";
|
|
var i,t,s;
|
|
|
|
if(!webix.i18n.calendar.monthShort_hash){
|
|
s = webix.i18n.calendar.monthShort;
|
|
t = webix.i18n.calendar.monthShort_hash = {};
|
|
for (i = 0; i < s.length; i++)
|
|
t[s[i]]=i;
|
|
|
|
s = webix.i18n.calendar.monthFull;
|
|
t = webix.i18n.calendar.monthFull_hash = {};
|
|
for (i = 0; i < s.length; i++)
|
|
t[s[i]]=i;
|
|
}
|
|
|
|
if(webix.env.strict){
|
|
return function(date){
|
|
if (!date) return '';
|
|
if (typeof date == 'object') return date;
|
|
var temp=date.split(/[^0-9a-zA-Z]+/g);
|
|
var set=[0,0,1,0,0,0,0];
|
|
for (i=0; i<mask.length; i++){
|
|
var a = mask[i];
|
|
if( a == "%y")
|
|
set[0]=temp[i]*1+(temp[i]>30?1900:2000);
|
|
else if( a == "%Y"){
|
|
set[0]=(temp[i]||0)*1; if (set[0]<30) set[0]+=2000;
|
|
}
|
|
else if( a == "%n" || a == "%m")
|
|
set[1]=(temp[i]||1)-1;
|
|
else if( a == "%M")
|
|
set[1]=webix.i18n.calendar.monthShort_hash[temp[i]]||0;
|
|
else if( a == "%F")
|
|
set[1]=webix.i18n.calendar.monthFull_hash[temp[i]]||0;
|
|
else if( a == "%j" || a == "%d")
|
|
set[2]=temp[i]||1;
|
|
else if( a == "%g" || a == "%G" || a == "%h" || a == "%H")
|
|
set[3]=temp[i]||0;
|
|
else if( a == "%a")
|
|
set[3]=set[3]%12+((temp[i]||'')==webix.i18n.am[0]?0:12);
|
|
else if( a == "%A")
|
|
set[3]=set[3]%12+((temp[i]||'')==webix.i18n.am[1]?0:12);
|
|
else if( a == "%i")
|
|
set[4]=temp[i]||0;
|
|
else if( a == "%s")
|
|
set[5]=temp[i]||0;
|
|
else if( a == "%S")
|
|
set[6]=temp[i]||0;
|
|
else if( a == "%c"){
|
|
var reg = /(\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+)(\+.*|)/g;
|
|
var res = reg.exec(date);
|
|
set[0]= (res[1]||0)*1; if (set[0]<30) set[0]+=2000;
|
|
set[1]= (res[2]||1)-1;
|
|
set[2]= res[3]||1;
|
|
set[3]= res[4]||0;
|
|
set[4]= res[5]||0;
|
|
set[5]= res[6]||0;
|
|
}
|
|
}
|
|
if(utc)
|
|
return new Date(Date.UTC(set[0],set[1],set[2],set[3],set[4],set[5], set[6]));
|
|
return new Date(set[0],set[1],set[2],set[3],set[4],set[5], set[6]);
|
|
};
|
|
}
|
|
|
|
for (i=0; i<mask.length; i++){
|
|
switch(mask[i]){
|
|
case "%j":
|
|
case "%d": splt+="set[2]=temp["+i+"]||1;";
|
|
break;
|
|
case "%n":
|
|
case "%m": splt+="set[1]=(temp["+i+"]||1)-1;";
|
|
break;
|
|
case "%y": splt+="set[0]=temp["+i+"]*1+(temp["+i+"]>30?1900:2000);";
|
|
break;
|
|
case "%g":
|
|
case "%G":
|
|
case "%h":
|
|
case "%H":
|
|
splt+="set[3]=temp["+i+"]||0;";
|
|
break;
|
|
case "%i":
|
|
splt+="set[4]=temp["+i+"]||0;";
|
|
break;
|
|
case "%Y": splt+="set[0]=(temp["+i+"]||0)*1; if (set[0]<30) set[0]+=2000;";
|
|
break;
|
|
case "%a":
|
|
splt+= "set[3]=set[3]%12+(temp["+i+"]==webix.i18n.am[0]?0:12);";
|
|
break;
|
|
case "%A":
|
|
splt+= "set[3]=set[3]%12+(temp["+i+"]==webix.i18n.am[1]?0:12);";
|
|
break;
|
|
case "%s": splt+="set[5]=temp["+i+"]||0;";
|
|
break;
|
|
case "%S": splt+="set[6]=temp["+i+"]||0;";
|
|
break;
|
|
case "%M": splt+="set[1]=webix.i18n.calendar.monthShort_hash[temp["+i+"]]||0;";
|
|
break;
|
|
case "%F": splt+="set[1]=webix.i18n.calendar.monthFull_hash[temp["+i+"]]||0;";
|
|
break;
|
|
case "%c":
|
|
splt+= "var res = date.split('T');";
|
|
splt+= "if(res[0]){ var d = res[0].split('-');";
|
|
splt+= "set[0]= (d[0]||0)*1; if (set[0]<30) set[0]+=2000;";
|
|
splt+= "set[1]= (d[1]||1)-1;";
|
|
splt+= "set[2]= d[2]||1;}";
|
|
splt+= "if(res[1]){ var t = res[1].split(':');";
|
|
splt+= "set[3]= t[0]||0;";
|
|
splt+= "set[4]= t[1]||0;";
|
|
splt+= "set[5]= parseInt(t[2])||0;}";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
var code ="set[0],set[1],set[2],set[3],set[4],set[5], set[6]";
|
|
if (utc) code =" Date.UTC("+code+")";
|
|
return new Function("date","if (!date) return ''; if (typeof date == 'object') return date; var set=[0,0,1,0,0,0,0]; "+splt+" return new Date("+code+");");
|
|
},
|
|
|
|
getISOWeek: function(ndate) {
|
|
if(!ndate) return false;
|
|
var nday = ndate.getDay();
|
|
if (nday === 0) {
|
|
nday = 7;
|
|
}
|
|
var first_thursday = new Date(ndate.valueOf());
|
|
first_thursday.setDate(ndate.getDate() + (4 - nday));
|
|
var year_number = first_thursday.getFullYear(); // year of the first Thursday
|
|
var ordinal_date = Math.floor( (first_thursday.getTime() - new Date(year_number, 0, 1).getTime()) / 86400000); //ordinal date of the first Thursday - 1 (so not really ordinal date)
|
|
var weekNumber = 1 + Math.floor( ordinal_date / 7);
|
|
return weekNumber;
|
|
},
|
|
|
|
getUTCISOWeek: function(ndate){
|
|
return this.getISOWeek(ndate);
|
|
},
|
|
_correctDate: function(d,d0,inc,checkFunc){
|
|
if(!inc)
|
|
return;
|
|
var incorrect = checkFunc(d,d0);
|
|
if(incorrect){
|
|
var i = (inc>0?1:-1);
|
|
|
|
while(incorrect){
|
|
d.setHours(d.getHours()+i);
|
|
incorrect = checkFunc(d,d0);
|
|
i += (inc>0?1:-1);
|
|
}
|
|
}
|
|
},
|
|
add:function(date,inc,mode,copy){
|
|
if (copy) date = this.copy(date);
|
|
var d = webix.Date.copy(date);
|
|
switch(mode){
|
|
case "day":
|
|
date.setDate(date.getDate()+inc);
|
|
this._correctDate(date,d,inc,function(d,d0){
|
|
return webix.Date.datePart(d0,true).valueOf()== webix.Date.datePart(d,true).valueOf();
|
|
});
|
|
break;
|
|
case "week":
|
|
date.setDate(date.getDate()+7*inc);
|
|
this._correctDate(date,d,7*inc,function(d,d0){
|
|
return webix.Date.datePart(d0,true).valueOf()== webix.Date.datePart(d,true).valueOf();
|
|
});
|
|
break;
|
|
case "month":
|
|
date.setMonth(date.getMonth()+inc);
|
|
this._correctDate(date,d,inc,function(d,d0){
|
|
return d0.getMonth() == d.getMonth() && d0.getYear() == d.getYear();
|
|
});
|
|
break;
|
|
case "year":
|
|
date.setYear(date.getFullYear()+inc);
|
|
this._correctDate(date,d,inc,function(d,d0){
|
|
return d0.getFullYear() == d.getFullYear();
|
|
});
|
|
break;
|
|
case "hour":
|
|
date.setHours(date.getHours()+inc);
|
|
this._correctDate(date,d,inc,function(d,d0){
|
|
return d0.getHours() == d.getHours() && webix.Date.datePart(d0,true)== webix.Date.datePart(d,true);
|
|
});
|
|
break;
|
|
case "minute": date.setMinutes(date.getMinutes()+inc); break;
|
|
default:
|
|
webix.Date.add[mode](date, inc, mode);
|
|
break;
|
|
}
|
|
return date;
|
|
},
|
|
datePart:function(date, copy){
|
|
if (copy) date = this.copy(date);
|
|
|
|
// workaround for non-existent hours
|
|
var d = this.copy(date);
|
|
d.setHours(0);
|
|
if(d.getDate()!=date.getDate()){
|
|
date.setHours(1);
|
|
}
|
|
else{
|
|
date.setHours(0);
|
|
}
|
|
|
|
date.setMinutes(0);
|
|
date.setSeconds(0);
|
|
date.setMilliseconds(0);
|
|
return date;
|
|
},
|
|
timePart:function(date, copy){
|
|
if (copy) date = this.copy(date);
|
|
return (date.valueOf()/1000 - date.getTimezoneOffset()*60)%86400;
|
|
},
|
|
copy:function(date){
|
|
return new Date(date.valueOf());
|
|
},
|
|
equal:function(a,b){
|
|
if (!a || !b) return false;
|
|
return a.valueOf() === b.valueOf();
|
|
},
|
|
isHoliday:function(day){
|
|
day = day.getDay();
|
|
if (day === 0 || day==6) return "webix_cal_event";
|
|
}
|
|
};
|
|
|
|
|
|
webix.i18n = {
|
|
_dateMethods:["fullDateFormat", "timeFormat", "dateFormat", "longDateFormat", "parseFormat", "parseTimeFormat"],
|
|
parseFormat:"%Y-%m-%d %H:%i",
|
|
parseTimeFormat:"%H:%i",
|
|
numberFormat:webix.Number.format,
|
|
priceFormat:function(value){ return webix.i18n._price_format(webix.i18n.numberFormat(value, webix.i18n._price_settings)); },
|
|
|
|
setLocale:function(locale){
|
|
var extend = function(base,source){
|
|
for (var method in source){
|
|
if(typeof(source[method]) == "object" && !webix.isArray(source[method])){
|
|
if(!base[method]){
|
|
base[method] = {};
|
|
}
|
|
extend(base[method],source[method]);
|
|
}
|
|
else
|
|
base[method] = source[method];
|
|
}
|
|
};
|
|
|
|
if (typeof locale == "string")
|
|
locale = this.locales[locale];
|
|
if (locale){
|
|
extend(this, locale);
|
|
}
|
|
var helpers = webix.i18n._dateMethods;
|
|
for( var i=0; i<helpers.length; i++){
|
|
var key = helpers[i];
|
|
var utc = webix.i18n[key+"UTC"];
|
|
webix.i18n[key+"Str"] = webix.Date.dateToStr(webix.i18n[key], utc);
|
|
webix.i18n[key+"Date"] = webix.Date.strToDate(webix.i18n[key], utc);
|
|
}
|
|
|
|
this._price_format = webix.template(this.price);
|
|
this._price_settings = this.priceSettings || this;
|
|
|
|
this.intFormat = webix.Number.numToStr({ groupSize:this.groupSize, groupDelimiter:this.groupDelimiter, decimalSize : 0});
|
|
}
|
|
};
|
|
|
|
|
|
webix.i18n.locales={};
|
|
webix.i18n.locales["en-US"]={
|
|
groupDelimiter:",",
|
|
groupSize:3,
|
|
decimalDelimiter:".",
|
|
decimalSize:2,
|
|
|
|
dateFormat:"%m/%d/%Y",
|
|
timeFormat:"%h:%i %A",
|
|
longDateFormat:"%d %F %Y",
|
|
fullDateFormat:"%m/%d/%Y %h:%i %A",
|
|
am:["am","AM"],
|
|
pm:["pm","PM"],
|
|
|
|
price:"${obj}",
|
|
priceSettings:{
|
|
groupDelimiter:",",
|
|
groupSize:3,
|
|
decimalDelimiter:".",
|
|
decimalSize:2
|
|
},
|
|
fileSize: ["b","Kb","Mb","Gb","Tb","Pb","Eb"],
|
|
|
|
calendar: {
|
|
monthFull:["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
|
|
monthShort:["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
|
|
dayFull:["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
|
|
dayShort:["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
|
|
hours: "Hours",
|
|
minutes: "Minutes",
|
|
done:"Done",
|
|
clear: "Clear",
|
|
today: "Today"
|
|
},
|
|
|
|
controls:{
|
|
select:"Select",
|
|
invalidMessage: "Invalid input value"
|
|
},
|
|
dataExport:{
|
|
page:"Page",
|
|
of:"of"
|
|
},
|
|
PDFviewer:{
|
|
of:"of",
|
|
automaticZoom:"Automatic Zoom",
|
|
actualSize:"Actual Size",
|
|
pageFit:"Page Fit",
|
|
pageWidth:"Page Width",
|
|
pageHeight:"Page Height"
|
|
},
|
|
aria:{
|
|
calendar:"Calendar",
|
|
increaseValue:"Increase value",
|
|
decreaseValue:"Decrease value",
|
|
navMonth:["Previous month", "Next month"],
|
|
navYear:["Previous year", "Next year"],
|
|
navDecade:["Previous decade", "Next decade"],
|
|
dateFormat:"%d %F %Y",
|
|
monthFormat:"%F %Y",
|
|
yearFormat:"%Y",
|
|
hourFormat:"Hours: %h %A",
|
|
minuteFormat:"Minutes: %i",
|
|
removeItem:"Remove item",
|
|
pages:["First page", "Previous page", "Next page", "Last page"],
|
|
page:"Page",
|
|
headermenu:"Header menu",
|
|
openGroup:"Open column group",
|
|
closeGroup:"Close column group",
|
|
closeTab:"Close tab",
|
|
showTabs:"Show more tabs",
|
|
resetTreeMap:"Reset tree map",
|
|
navTreeMap:"Level up",
|
|
nextTab:"Next tab",
|
|
prevTab:"Previous tab",
|
|
multitextSection:"Add section",
|
|
multitextextraSection:"Remove section",
|
|
showChart:"Show chart",
|
|
hideChart:"Hide chart",
|
|
resizeChart:"Resize chart"
|
|
},
|
|
richtext:{
|
|
underline: "Underline",
|
|
bold: "Bold",
|
|
italic: "Italic"
|
|
},
|
|
combo:{
|
|
selectAll:"Select all",
|
|
unselectAll:"Unselect all"
|
|
}
|
|
};
|
|
webix.i18n.setLocale("en-US");
|
|
|
|
|
|
|
|
|
|
|
|
webix.protoUI({
|
|
name:"datatable",
|
|
defaults:{
|
|
leftSplit:0,
|
|
rightSplit:0,
|
|
topSplit:0,
|
|
columnWidth:100,
|
|
minColumnWidth:20,
|
|
minColumnHeight:26,
|
|
prerender:false,
|
|
autoheight:false,
|
|
autowidth:false,
|
|
header:true,
|
|
fixedRowHeight:true,
|
|
scrollAlignY:true,
|
|
scrollX:true,
|
|
scrollY:true,
|
|
datafetch:50,
|
|
navigation:true
|
|
},
|
|
$skin:function(){
|
|
var height = webix.skin.$active.rowHeight;
|
|
var defaults = this.defaults;
|
|
defaults.rowHeight = height;
|
|
defaults.headerRowHeight = webix.skin.$active.barHeight;
|
|
},
|
|
on_click:{
|
|
webix_richfilter:function(){
|
|
return false;
|
|
},
|
|
webix_table_checkbox:function(e, id){
|
|
id = this.locate(e);
|
|
|
|
var item = this.getItem(id.row);
|
|
var col = this.getColumnConfig(id.column);
|
|
var trg = e.target|| e.srcElement;
|
|
|
|
//read actual value from HTML tag when possible
|
|
//as it can be affected by dbl-clicks
|
|
var check = (trg.type == "checkbox")?trg.checked:(item[id.column] != col.checkValue);
|
|
var value = check ? col.checkValue : col.uncheckValue;
|
|
|
|
var update = {};
|
|
update[id.column] = value;
|
|
this.updateItem(id.row, update, (this._settings.checkboxRefresh?"update":"save"));
|
|
|
|
this.callEvent("onCheck", [id.row, id.column, value]);
|
|
return false;
|
|
},
|
|
webix_table_radio:function(e){
|
|
var id = this.locate(e);
|
|
|
|
var item = this.getItem(id.row);
|
|
var col = this.getColumnConfig(id.column);
|
|
|
|
var checked = 0;
|
|
this.eachRow(function(rowid){
|
|
var item = this.data.pull[rowid];
|
|
if (item && item[id.column] == col.checkValue)
|
|
item[id.column] = col.uncheckValue;
|
|
});
|
|
|
|
item[id.column] = col.checkValue;
|
|
|
|
this.callEvent("onCheck", [id.row, id.column, true]);
|
|
this.refresh();
|
|
return false;
|
|
}
|
|
},
|
|
on_dblclick:{
|
|
webix_table_checkbox: function(){
|
|
return this.on_click.webix_table_checkbox.apply(this,arguments);
|
|
}
|
|
},
|
|
on_context:{
|
|
},
|
|
$init:function(config){
|
|
this.on_click = webix.extend({}, this.on_click);
|
|
var html = "<div class='webix_ss_header'><div class='webix_hs_left'></div><div class='webix_hs_center'></div><div class='webix_hs_right'></div></div><div class='webix_ss_body'><div class='webix_ss_left'><div class='webix_ss_center_scroll'></div></div>";
|
|
html += "<div class='webix_ss_center'><div class='webix_ss_center_scroll' role='rowgroup'></div></div>";
|
|
html += "<div class='webix_ss_right'><div class='webix_ss_center_scroll'></div></div></div>";
|
|
html += "<div class='webix_ss_hscroll' role='scrollbar' aria-orientation='horizontal'></div><div class='webix_ss_footer'><div class='webix_hs_left'></div><div class='webix_hs_center'></div><div class='webix_hs_right'></div></div><div class='webix_ss_vscroll_header'></div><div class='webix_ss_vscroll' role='scrollbar' aria-orientation='vertical'></div><div class='webix_ss_vscroll_footer'></div>";
|
|
|
|
this._contentobj.innerHTML = html;
|
|
this._top_id = this._contentobj.id = this.name+webix.uid();
|
|
this._contentobj.className +=" webix_dtable";
|
|
|
|
this._dataobj = this._contentobj;
|
|
|
|
this._header = this._contentobj.firstChild;
|
|
this._body = this._header.nextSibling;
|
|
this._footer = this._body.nextSibling.nextSibling;
|
|
|
|
this._viewobj.setAttribute("role", "grid");
|
|
if(!config.editable)
|
|
this._viewobj.setAttribute("aria-readonly", "true");
|
|
|
|
this.data.provideApi(this, true);
|
|
this.data.attachEvent("onParse", webix.bind(this._call_onparse, this));
|
|
|
|
this.$ready.push(this._first_render);
|
|
|
|
this._columns = [];
|
|
this._hidden_column_order = [];
|
|
this._headers = [];
|
|
this._footers = [];
|
|
this._rows_cache = [];
|
|
this._active_headers = {};
|
|
this._filter_elements = {};
|
|
this._header_height = this._footer_height = 0;
|
|
|
|
//component can create new view
|
|
this._destroy_with_me = [];
|
|
|
|
this.data.attachEvent("onServerConfig", webix.bind(this._config_table_from_file, this));
|
|
this.data.attachEvent("onServerOptions", webix.bind(this._config_options_from_file, this));
|
|
this.attachEvent("onViewShow", function(){
|
|
this._restore_scroll_state();
|
|
this._refresh_any_header_content();
|
|
});
|
|
this.data.attachEvent("onClearAll", webix.bind(function(soft){
|
|
if (!soft){
|
|
this._scrollLeft = this._scrollTop = 0;
|
|
if (this._x_scroll) this._x_scroll.reset();
|
|
if (this._y_scroll) this._y_scroll.reset();
|
|
}
|
|
}, this));
|
|
this.attachEvent("onDestruct", this._clean_config_struct);
|
|
this.attachEvent("onKeyPress", this._onKeyPress);
|
|
this.attachEvent("onScrollY", this._adjust_rows);
|
|
|
|
webix.callEvent("onDataTable", [this, config]);
|
|
},
|
|
_render_initial:function(){
|
|
this._scrollSizeX = this._scrollSizeY = webix.ui.scrollSize;
|
|
|
|
webix.html.addStyle("#"+this._top_id +" .webix_cell { height:"+this._settings.rowHeight+"px; line-height:"+(this._settings.rowLineHeight || this._settings.rowHeight)+"px;" +(this._settings.fixedRowHeight?"":"white-space:normal;")+" }");
|
|
webix.html.addStyle("#"+this._top_id +" .webix_hcell { height:"+this._settings.headerRowHeight+"px; line-height:"+this._settings.headerRowHeight+"px;}");
|
|
this._render_initial = function(){};
|
|
},
|
|
_first_render:function(){
|
|
this.data.attachEvent("onStoreLoad", webix.bind(this._refresh_any_header_content, this));
|
|
this.data.attachEvent("onSyncApply", webix.bind(this._refresh_any_header_content, this));
|
|
this.data.attachEvent("onStoreUpdated", webix.bind(function(){ return this.render.apply(this, arguments); }, this));
|
|
this.data.attachEvent("onStoreUpdated", webix.bind(this._refresh_tracking_header_content, this));
|
|
this.render();
|
|
},
|
|
refresh:function(){
|
|
this.render();
|
|
},
|
|
render:function(id, data, mode){
|
|
//pure data saving call
|
|
if (mode == "save") return;
|
|
//during dnd we must not repaint anything in mobile webkit
|
|
if (mode == "move"){
|
|
var context = webix.DragControl.getContext();
|
|
if (context && context.fragile) return;
|
|
}
|
|
|
|
if (!this._columns.length){
|
|
var cols = this._settings.columns;
|
|
if (!cols || !cols.length) {
|
|
if (this._settings.autoConfig && this.data.order.length){
|
|
this._dtable_fully_ready = 0;
|
|
this._autoDetectConfig();
|
|
} else
|
|
return;
|
|
}
|
|
this._define_structure();
|
|
}
|
|
|
|
if (!this.isVisible(this._settings.id) || this.$blockRender)
|
|
return this._render_initial(); //Chrome 34, Custom Font loading bug
|
|
|
|
//replace multiple atomic updates by single big repaint
|
|
if (id && data != -1 && (mode == "paint" || mode == "update")){
|
|
if (this._render_timer)
|
|
clearTimeout(this._render_timer);
|
|
|
|
if (!this._render_timer || this._render_timer_id == id){
|
|
this._render_timer_id = id;
|
|
this._render_timer = webix.delay(function(){
|
|
//if only one call - repaint single item
|
|
this.render(id, -1, mode);
|
|
}, this);
|
|
} else {
|
|
this._render_timer_id = null;
|
|
this._render_timer = webix.delay(function(){
|
|
//if ther was a serie of calls - replace them with single full repaint
|
|
this.render();
|
|
}, this);
|
|
}
|
|
return;
|
|
} else if (this._render_timer){
|
|
clearTimeout(this._render_timer);
|
|
this._render_timer = 0;
|
|
}
|
|
|
|
if (this.callEvent("onBeforeRender",[this.data])){
|
|
|
|
this._render_initial();
|
|
if (!this._dtable_fully_ready)
|
|
this._apply_headers();
|
|
|
|
if (this._content_width){
|
|
if (this["_settings"].experimental && (mode == "paint" || mode == "update") && id)
|
|
this._repaint_single_row(id);
|
|
else
|
|
this._check_rendered_cols(true, true);
|
|
}
|
|
|
|
if (!id || mode!="update"){
|
|
this._dtable_height = this._get_total_height();
|
|
this._set_split_sizes_y();
|
|
}
|
|
|
|
//don't depend on hidden rows/rolumns
|
|
this._viewobj.setAttribute("aria-colcount", Math.max(this._hidden_column_order.length, this._columns.length));
|
|
this._viewobj.setAttribute("aria-rowcount", this.data.count());
|
|
|
|
this.callEvent("onAfterRender",[this.data]);
|
|
return true;
|
|
}
|
|
},
|
|
getColumnConfig:function(id){
|
|
return this._columns_pull[id] || this._hidden_column_hash[id];
|
|
},
|
|
_config_options_from_file:function(colls){
|
|
for (var key in colls){
|
|
var column = this.getColumnConfig(key);
|
|
webix.assert(column, "Orphan collection: "+key);
|
|
var temp = new webix.DataCollection({
|
|
data:colls[key]
|
|
});
|
|
this._destroy_with_me.push(temp);
|
|
this._bind_collection(temp, column);
|
|
}
|
|
},
|
|
//xml has different configuration structure, fixing
|
|
_config_table_from_file:function(config){
|
|
if (config.columns && this._dtable_fully_ready)
|
|
this.refreshColumns(null, true);
|
|
},
|
|
_define_structure:function(){
|
|
if (this._settings.columns){
|
|
this._columns = this._settings.columns;
|
|
this._columns_pull = {};
|
|
|
|
for (var i = 0; i < this._columns.length; i++){
|
|
var col = this._columns[i];
|
|
this._columns_pull[col.id] = col;
|
|
|
|
var format = col.cssFormat;
|
|
if (format)
|
|
col.cssFormat = webix.toFunctor(format, this.$scope);
|
|
|
|
col.width = col.width||this._settings.columnWidth;
|
|
if (typeof col.format == "string")
|
|
col.format = webix.i18n[col.format]||window[col.format];
|
|
|
|
//default settings for checkboxes and radios
|
|
if (webix.isUndefined(col.checkValue)) col.checkValue = 1;
|
|
if (webix.isUndefined(col.uncheckValue)) col.uncheckValue = 0;
|
|
|
|
if (col.css && typeof col.css == "object")
|
|
col.css = webix.html.createCss(col.css);
|
|
|
|
var template = col.template;
|
|
if (template){
|
|
if (typeof template == "string")
|
|
template = template.replace(/#\$value#/g,"#"+col.id+"#");
|
|
col.template = webix.template(template);
|
|
}
|
|
}
|
|
|
|
this._normalize_headers("header", this._headers);
|
|
this._normalize_headers("footer", this._footers);
|
|
|
|
this.callEvent("onStructureLoad",[]);
|
|
}
|
|
},
|
|
_define_structure_and_render:function(){
|
|
this._apply_headers();
|
|
},
|
|
_clean_config_struct:function(){
|
|
//remove column technical info from the column
|
|
//it allows to reuse the same config object for new grid
|
|
for (var i = 0; i < this._columns.length; i++){
|
|
delete this._columns[i].attached;
|
|
delete this._columns[i].node;
|
|
}
|
|
},
|
|
_apply_headers:function(){
|
|
this._rightSplit = this._columns.length-this._settings.rightSplit;
|
|
this._dtable_width = 0;
|
|
|
|
for (var i = 0; i < this._columns.length; i++){
|
|
if (!this._columns[i].node){
|
|
|
|
var temp = webix.html.create("DIV");
|
|
temp.style.width = this._columns[i].width + "px";
|
|
this._columns[i].node = temp;
|
|
}
|
|
if (i>=this._settings.leftSplit && i<this._rightSplit)
|
|
this._dtable_width += this._columns[i].width;
|
|
}
|
|
|
|
var marks = [];
|
|
|
|
if (this._settings.rightSplit){
|
|
var nr = this._columns.length-this._settings.rightSplit;
|
|
marks[nr] =" webix_first";
|
|
marks[nr-1]=" webix_last";
|
|
}
|
|
if (this._settings.leftSplit){
|
|
var nl = this._settings.leftSplit;
|
|
marks[nl] =" webix_first";
|
|
marks[nl-1]=" webix_last";
|
|
}
|
|
marks[0] = (marks[0]||"")+" webix_first";
|
|
var last_index = this._columns.length-1;
|
|
marks[last_index] = (marks[last_index]||"")+" webix_last";
|
|
|
|
|
|
for (var i=0; i<this._columns.length; i++){
|
|
var node = this._columns[i].node;
|
|
node.setAttribute("column", i);
|
|
node.className = "webix_column "+(this._columns[i].css||"")+(marks[i]||'');
|
|
}
|
|
|
|
this._create_scrolls();
|
|
|
|
this._set_columns_positions();
|
|
this._set_split_sizes_x();
|
|
this._render_header_and_footer();
|
|
|
|
this._dtable_fully_ready = true;
|
|
},
|
|
_set_columns_positions:function(){
|
|
var left = 0;
|
|
for (var i = 0; i < this._columns.length; i++){
|
|
var column = this._columns[i];
|
|
if (i == this._settings.leftSplit || i == this._rightSplit)
|
|
left = 0;
|
|
|
|
if (column.node){
|
|
column.node.style.left = left+"px";
|
|
if (this._settings.leftSplit || this._settings.rightSplit){
|
|
webix.html.remove(column.node);
|
|
column.attached = false;
|
|
}
|
|
}
|
|
left += column.width;
|
|
}
|
|
},
|
|
_render_header_and_footer:function(){
|
|
if (!this._header_fix_width)
|
|
this._header_fix_width = 0;
|
|
|
|
this._header_height = this._footer_height = 0;
|
|
|
|
if (this._settings.header) {
|
|
this._refreshHeaderContent(this._header, 0, 1);
|
|
this._normalize_headers("header", this._headers);
|
|
this._header_height = this._headers._summ;
|
|
this._render_header_section(this._header, "header", this._headers);
|
|
}
|
|
if (this._settings.footer){
|
|
this._refreshHeaderContent(this._footer, 0, 1);
|
|
this._normalize_headers("footer", this._footers);
|
|
this._footer_height = this._footers._summ;
|
|
this._render_header_section(this._footer, "footer", this._footers);
|
|
}
|
|
|
|
this.refreshHeaderContent(false, false);
|
|
this._size_header_footer_fix();
|
|
|
|
if (this._last_sorted)
|
|
this.markSorting(this._last_sorted, this._last_order);
|
|
},
|
|
_getHeaderHeight:function(header, column, ind){
|
|
var width = 0;
|
|
var colspan = header.colspan || 1;
|
|
var css = "webix_hcell "+(header.css||"");
|
|
|
|
if(header.rotate)
|
|
css += " webix_measure_rotate";
|
|
else
|
|
for(var i = 0; i<colspan; i++)
|
|
width += this._columns[ind+i] ? this._columns[ind+i].width : this.config.columnWidth;
|
|
|
|
var size = webix.html.getTextSize(
|
|
[header.text],
|
|
css,
|
|
width
|
|
);
|
|
|
|
//+1 to compensate for scrollHeight rounding
|
|
return (header.rotate ? size.width : size.height ) + 1;
|
|
},
|
|
_normalize_headers:function(collection, heights){
|
|
var rows = 0;
|
|
|
|
for (var i=0; i<this._columns.length; i++){
|
|
var data = this._columns[i][collection];
|
|
if (!data || typeof data != "object" || !data.length){
|
|
if (webix.isUndefined(data)){
|
|
if (collection == "header")
|
|
data = this._columns[i].id;
|
|
else
|
|
data = "";
|
|
}
|
|
data = [data];
|
|
}
|
|
for (var j = 0; j < data.length; j++){
|
|
if (typeof data[j] != "object")
|
|
data[j] = { text:data[j] };
|
|
|
|
if (data[j] && data[j].height) heights[j] = data[j].height;
|
|
if (data[j] && data[j].autoheight) heights[j] = this._getHeaderHeight(data[j], this._columns[i], i);
|
|
}
|
|
rows = Math.max(rows, data.length);
|
|
this._columns[i][collection] = data;
|
|
}
|
|
|
|
heights._summ = rows;
|
|
for (var i = rows-1; i >= 0; i--){
|
|
heights[i] = heights[i] || this._settings.headerRowHeight;
|
|
heights._summ += heights[i]*1;
|
|
}
|
|
|
|
//set null to cells included in col|row spans
|
|
for (var i=0; i<this._columns.length; i++){
|
|
var col = this._columns[i][collection];
|
|
for (var j=0; j<col.length; j++){
|
|
if (col[j] && col[j].rowspan)
|
|
for (var z=1; z<col[j].rowspan; z++)
|
|
col[j+z] = null;
|
|
if (col[j] && col[j].colspan)
|
|
for (var z=1; z<col[j].colspan; z++)
|
|
this._columns[i+z][collection][j] = null;
|
|
}
|
|
}
|
|
|
|
//auto-rowspan cells, which has not enough header lines
|
|
for (var i=0; i<this._columns.length; i++){
|
|
var data = this._columns[i][collection];
|
|
if (data.length < rows){
|
|
var end = data.length-1;
|
|
data[end].rowspan = rows - data.length + 1;
|
|
for (var j=end+1; j<rows; j++)
|
|
data[j]=null;
|
|
}
|
|
}
|
|
return rows;
|
|
},
|
|
_find_header_content:function(sec, id){
|
|
var alltd = sec.getElementsByTagName("TD");
|
|
for (var i = 0; i < alltd.length; i++)
|
|
if (alltd[i].getAttribute("active_id") == id)
|
|
return alltd[i];
|
|
},
|
|
getHeaderContent:function(id){
|
|
var obj = this._find_header_content(this._header, id);
|
|
if (!obj)
|
|
obj = this._find_header_content(this._footer, id);
|
|
|
|
if (obj){
|
|
var config = this._active_headers[id];
|
|
var type = webix.ui.datafilter[config.content];
|
|
|
|
if (type.getHelper) return type.getHelper(obj, config);
|
|
return {
|
|
type: type,
|
|
getValue:function(){ return type.getValue(obj); },
|
|
setValue:function(value){ return type.setValue(obj, value); }
|
|
};
|
|
}
|
|
},
|
|
_summ_next:function(heights, start, i){
|
|
var summ = i ? -1 : 0;
|
|
|
|
i += start;
|
|
for (start; start<i; start++)
|
|
summ+=heights[start] + 1;
|
|
|
|
return summ;
|
|
},
|
|
_render_subheader:function(start, end, width, name, heights){
|
|
if (start == end) return "";
|
|
|
|
var html = "<table role='presentation' style='width:"+width+"px' cellspacing='0' cellpadding='0'>";
|
|
for (var i = start; i < end; i++){
|
|
html += "<tr class='webix_size_row'>";
|
|
for (var i = start; i < end; i++)
|
|
html += "<td style='width:"+this._columns[i].width+"px;'></td>";
|
|
html += "</tr>";
|
|
}
|
|
|
|
var count = this._columns[0][name].length;
|
|
var block_evs = [];
|
|
|
|
for (var j = 0; j < count; j++){
|
|
html += "<tr section='"+name+"' role='row'>";
|
|
for (var i = start; i < end; i++){
|
|
var header = this._columns[i][name][j];
|
|
if (header === null) continue;
|
|
|
|
if (header.content){
|
|
header.contentId = header.contentId||webix.uid();
|
|
header.columnId = this._columns[i].id;
|
|
header.format = this._columns[i].format;
|
|
|
|
webix.assert(webix.ui.datafilter, "Filtering extension was not included");
|
|
webix.assert(webix.ui.datafilter[header.content], "Unknown content type: "+header.content);
|
|
|
|
header.text = webix.ui.datafilter[header.content].render(this, header);
|
|
this._active_headers[header.contentId] = header;
|
|
this._has_active_headers = true;
|
|
}
|
|
|
|
html += "<td role='presentation' column='"+(header.colspan?(header.colspan-1+i):i)+"'";
|
|
|
|
var hcss = '';
|
|
if (i==start)
|
|
hcss+="webix_first";
|
|
var column_pos = i + (header.colspan?header.colspan-1:0);
|
|
if (column_pos>=end-1)
|
|
hcss+=" webix_last";
|
|
if (hcss)
|
|
html+=' class="'+hcss+'"';
|
|
|
|
var cell_height = heights[j];
|
|
var sheight="";
|
|
if (header.contentId)
|
|
html+=" active_id='"+header.contentId+"'";
|
|
if (header.colspan)
|
|
html+=" colspan='"+header.colspan+"'";
|
|
if (header.rowspan){
|
|
html+=" rowspan='"+header.rowspan+"'";
|
|
cell_height = this._summ_next(this._headers, j, header.rowspan);
|
|
}
|
|
|
|
if (cell_height != this._settings.headerRowHeight)
|
|
sheight =" style='line-height:"+cell_height+"px; height:"+cell_height+"px;'";
|
|
|
|
var css ="webix_hcell";
|
|
var header_css = header.css;
|
|
if (header_css){
|
|
if (typeof header_css == "object")
|
|
header.css = header_css = webix.html.createCss(header_css);
|
|
css+=" "+header_css;
|
|
}
|
|
if (this._columns[i].$selected)
|
|
css += " webix_sel_hcell";
|
|
|
|
html+="><div role='columnheader' class='"+css+"'"+sheight+">";
|
|
|
|
var text = (header.text===""?" ":header.text);
|
|
if (header.rotate)
|
|
text = "<div class='webix_rotate' style='width:"+(cell_height-10)+"px; transform-origin:center "+(cell_height-15)/2+"px;-webkit-transform-origin:center "+(cell_height-15)/2+"px;'>"+text+"</div>";
|
|
|
|
html += text + "</div></td>";
|
|
}
|
|
html += "</tr>";
|
|
}
|
|
html+="</tr></table>";
|
|
|
|
return html;
|
|
},
|
|
showItemByIndex:function(row_ind, column_ind){
|
|
var pager = this._settings.pager;
|
|
if (pager){
|
|
var target = Math.floor(row_ind/pager.size);
|
|
if (target != pager.page)
|
|
webix.$$(pager.id).select(target);
|
|
}
|
|
|
|
//parameter will be set to -1, to mark that scroll need not to be adjusted
|
|
if (row_ind != -1){
|
|
var state = this._get_y_range();
|
|
if (row_ind < state[0]+1 || row_ind >= state[1]-1 ){
|
|
//not visible currently
|
|
var summ = this._getHeightByIndexSumm((pager?this.data.$min:0),row_ind);
|
|
if (row_ind < state[0]+1){
|
|
//scroll top - show row at top of screen
|
|
summ = Math.max(0, summ-1) - this._top_split_height;
|
|
} else {
|
|
//scroll bottom - show row at bottom of screen
|
|
summ += this._getHeightByIndex(row_ind) - this._dtable_offset_height;
|
|
//because of row rounding we neet to scroll some extra
|
|
//TODO: create a better heuristic
|
|
if (row_ind>0)
|
|
summ += this._getHeightByIndex(row_ind-1)-1;
|
|
}
|
|
|
|
this._y_scroll.scrollTo(summ);
|
|
}
|
|
}
|
|
if (column_ind != -1){
|
|
//ignore split columns - they are always visible
|
|
if (column_ind < this._settings.leftSplit) return;
|
|
if (column_ind >= this._rightSplit) return;
|
|
|
|
//very similar to y-logic above
|
|
var state = this._get_x_range();
|
|
if (column_ind < state[0]+1 || column_ind >= state[1]-1 ){
|
|
//not visible currently
|
|
var summ = 0;
|
|
for (var i=this._settings.leftSplit; i<column_ind; i++)
|
|
summ += this._columns[i].width;
|
|
|
|
/*jsl:ignore*/
|
|
if (column_ind < state[0]+1){
|
|
//scroll to left border
|
|
} else {
|
|
//scroll to right border
|
|
summ += this._columns[column_ind].width - this._center_width;
|
|
}
|
|
/*jsl:end*/
|
|
this._x_scroll.scrollTo(summ);
|
|
}
|
|
}
|
|
},
|
|
showCell:function(row, column){
|
|
if (!column || !row){
|
|
//if column or row not provided - take from current selection
|
|
var t=this.getSelectedId(true);
|
|
if (t.length == 1){
|
|
column = column || t[0].column;
|
|
row = row || t[0].row;
|
|
}
|
|
}
|
|
//convert id to index
|
|
column = column?this.getColumnIndex(column):-1;
|
|
row = row?this.getIndexById(row):-1;
|
|
this.showItemByIndex(row, column);
|
|
|
|
},
|
|
scrollTo:function(x,y){
|
|
if (!this._x_scroll) return;
|
|
if (this._scrollTo_touch)
|
|
return this._scrollTo_touch(x,y);
|
|
|
|
if (x !== null)
|
|
this._x_scroll.scrollTo(x);
|
|
if (y !== null)
|
|
this._y_scroll.scrollTo(y);
|
|
},
|
|
getScrollState:function(){
|
|
if (this._getScrollState_touch)
|
|
return this._getScrollState_touch();
|
|
|
|
var diff = this._render_scroll_shift?0:(this._render_scroll_diff||0);
|
|
return {x:(this._scrollLeft||0), y:(this._scrollTop + diff)};
|
|
},
|
|
showItem:function(id){
|
|
this.showItemByIndex(this.getIndexById(id), -1);
|
|
},
|
|
_render_header_section:function(sec, name, heights){
|
|
sec.childNodes[0].innerHTML = this._render_subheader(0, this._settings.leftSplit, this._left_width, name, heights);
|
|
sec.childNodes[1].innerHTML = this._render_subheader(this._settings.leftSplit, this._rightSplit, this._dtable_width, name, heights);
|
|
sec.childNodes[1].onscroll = webix.bind(this._scroll_with_header, this);
|
|
sec.childNodes[2].innerHTML = this._render_subheader(this._rightSplit, this._columns.length, this._right_width, name, heights);
|
|
},
|
|
_scroll_with_header:function(){
|
|
var active = this.getScrollState().x;
|
|
var header = this._header.childNodes[1].scrollLeft;
|
|
if (header != active)
|
|
this.scrollTo(header, null);
|
|
},
|
|
_refresh_tracking_header_content:function(){
|
|
this.refreshHeaderContent(true, true);
|
|
},
|
|
_refresh_any_header_content:function(){
|
|
this.refreshHeaderContent(false, true);
|
|
},
|
|
//[DEPRECATE] - v3.0, move to private
|
|
refreshHeaderContent:function(trackedOnly, preserve, id){
|
|
if (this._settings.header){
|
|
if (preserve) this._refreshHeaderContent(this._header, trackedOnly, 1, id);
|
|
this._refreshHeaderContent(this._header, trackedOnly, 0, id);
|
|
}
|
|
if (this._settings.footer){
|
|
if (preserve) this._refreshHeaderContent(this._footer, trackedOnly, 1, id);
|
|
this._refreshHeaderContent(this._footer, trackedOnly, 0, id);
|
|
}
|
|
},
|
|
refreshFilter:function(id){
|
|
if (id && !this._active_headers[id]) return;
|
|
this.refreshHeaderContent(false, true, id);
|
|
},
|
|
_refreshHeaderContent:function(sec, cellTrackOnly, getOnly, byId){
|
|
if (this._has_active_headers && sec){
|
|
var alltd = sec.getElementsByTagName("TD");
|
|
|
|
for (var i = 0; i < alltd.length; i++){
|
|
if (alltd[i].getAttribute("active_id")){
|
|
var obj = this._active_headers[alltd[i].getAttribute("active_id")];
|
|
if (byId && byId != obj.columnId) continue;
|
|
|
|
|
|
var content = webix.ui.datafilter[obj.content];
|
|
|
|
if (getOnly){
|
|
if (content.getValue)
|
|
obj.value = content.getValue(alltd[i]);
|
|
} else if (!cellTrackOnly || content.trackCells){
|
|
content.refresh(this, alltd[i], obj);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
headerContent:[],
|
|
_set_size_scroll_area:function(obj, height, hdx){
|
|
if (this._scrollSizeY){
|
|
|
|
obj.style.height = Math.max(height,1)-1+"px";
|
|
obj.style.width = (this._rightSplit?0:hdx)+this._scrollSizeY-1+"px";
|
|
|
|
// temp. fix: Chrome [DIRTY]
|
|
if (webix.env.isWebKit)
|
|
var w = obj.offsetWidth;
|
|
} else
|
|
obj.style.display = "none";
|
|
},
|
|
_size_header_footer_fix:function(){
|
|
if (this._settings.header)
|
|
this._set_size_scroll_area(this._header_scroll, this._header_height, this._header_fix_width);
|
|
if (this._settings.footer)
|
|
this._set_size_scroll_area(this._footer_scroll, this._footer_height, this._header_fix_width);
|
|
},
|
|
_update_scroll:function(x,y){
|
|
var hasX = !(this._settings.autowidth || this._settings.scrollX === false);
|
|
this._scrollSizeX = hasX ? webix.ui.scrollSize : 0;
|
|
var hasY = !(this._settings.autoheight || this._settings.scrollY === false);
|
|
this._scrollSizeY = hasY ? webix.ui.scrollSize : 0;
|
|
if(webix.env.touch)
|
|
hasX = hasY = false;
|
|
if (this._x_scroll){
|
|
this._x_scroll._settings.scrollSize = this._scrollSizeX;
|
|
this._x_scroll._settings.scrollVisible = hasX;
|
|
}
|
|
if (this._y_scroll){
|
|
this._y_scroll._settings.scrollSize = this._scrollSizeY;
|
|
this._y_scroll._settings.scrollVisible = hasY;
|
|
}
|
|
},
|
|
_create_scrolls:function(){
|
|
|
|
this._scrollTop = 0;
|
|
this._scrollLeft = 0;
|
|
var scrx, scry; scrx = scry = 1;
|
|
|
|
if (this._settings.autoheight || this._settings.scrollY === false)
|
|
scry = this._scrollSizeY = 0;
|
|
if (this._settings.autowidth || this._settings.scrollX === false)
|
|
scrx = this._scrollSizeX = 0;
|
|
|
|
if (webix.env.touch) scrx = scry = 0;
|
|
|
|
if (!this._x_scroll){
|
|
this._x_scroll = new webix.ui.vscroll({
|
|
container:this._footer.previousSibling,
|
|
scrollWidth:this._dtable_width,
|
|
scrollSize:this._scrollSizeX,
|
|
scrollVisible:scrx
|
|
});
|
|
|
|
//fix for scroll space on Mac
|
|
if (scrx && !this._scrollSizeX && !webix.env.$customScroll)
|
|
this._x_scroll._viewobj.style.position="absolute";
|
|
|
|
this._x_scroll.attachEvent("onScroll", webix.bind(this._onscroll_x, this));
|
|
}
|
|
|
|
if (!this._y_scroll){
|
|
this._header_scroll = this._footer.nextSibling;
|
|
var vscroll_view = this._header_scroll.nextSibling;
|
|
this._footer_scroll = vscroll_view.nextSibling;
|
|
|
|
this._y_scroll = new webix.ui.vscroll({
|
|
container:vscroll_view,
|
|
scrollHeight:100,
|
|
scroll:"y",
|
|
scrollSize:this._scrollSizeY,
|
|
scrollVisible:scry
|
|
});
|
|
|
|
this._y_scroll.activeArea(this._body);
|
|
this._x_scroll.activeArea(this._body, true);
|
|
this._y_scroll.attachEvent("onScroll", webix.bind(this._onscroll_y, this));
|
|
}
|
|
|
|
if (this._content_width)
|
|
this.callEvent("onResize",[this._content_width, this._content_height]);
|
|
|
|
if (webix.env.$customScroll)
|
|
webix.CustomScroll.enable(this);
|
|
|
|
this._create_scrolls = function(){};
|
|
},
|
|
columnId:function(index){
|
|
return this._columns[index].id;
|
|
},
|
|
getColumnIndex:function(id){
|
|
for (var i = 0; i < this._columns.length; i++)
|
|
if (this._columns[i].id == id)
|
|
return i;
|
|
return -1;
|
|
},
|
|
_getNodeBox:function(rid, cid){
|
|
var xs=0, xe=0, ye=0, ys=0;
|
|
var i; var zone = 0;
|
|
for (i = 0; i < this._columns.length; i++){
|
|
if (this._rightSplit == i || this._settings.leftSplit == i){
|
|
xs=0; zone++;
|
|
}
|
|
if (this._columns[i].id == cid)
|
|
break;
|
|
xs+=this._columns[i].width;
|
|
}
|
|
xe+=this._columns[i].width;
|
|
|
|
for (i = 0; i < this.data.order.length; i++){
|
|
if (this.data.order[i] ==rid)
|
|
break;
|
|
ys+=this._getHeightByIndex(i);
|
|
}
|
|
ye+=this._getHeightByIndex(i);
|
|
return [xs,xe,ys-this._scrollTop,ye, this._body.childNodes[zone]];
|
|
},
|
|
_id_to_string:function(){ return this.row; },
|
|
locate:function(node, idOnly){
|
|
if (this._settings.subview && this != webix.$$(node)) return null;
|
|
|
|
node = node.target||node.srcElement||node;
|
|
while (node && node.getAttribute){
|
|
if (node === this.$view)
|
|
break;
|
|
var cs = webix.html._getClassName(node).toString();
|
|
|
|
var pos = null;
|
|
if (cs.indexOf("webix_cell")!=-1){
|
|
pos = this._locate(node);
|
|
if (pos)
|
|
pos.row = this.data.order[pos.rind];
|
|
}
|
|
if (cs.indexOf("webix_hcell")!=-1){
|
|
pos = this._locate(node);
|
|
if (pos)
|
|
pos.header = true;
|
|
}
|
|
|
|
if (pos){
|
|
if (idOnly) return pos.header ? null : pos.row;
|
|
pos.column = this._columns[pos.cind].id;
|
|
pos.toString = this._id_to_string;
|
|
return pos;
|
|
}
|
|
|
|
node = node.parentNode;
|
|
}
|
|
return null;
|
|
},
|
|
_locate:function(node){
|
|
var cdiv = node.parentNode;
|
|
if (!cdiv) return null;
|
|
var column = (node.getAttribute("column") || cdiv.getAttribute("column"))*1;
|
|
var row = node.getAttribute("row") || 0;
|
|
if (!row)
|
|
for (var i = 0; i < cdiv.childNodes.length; i++)
|
|
if (cdiv.childNodes[i] == node){
|
|
if (i >= this._settings.topSplit)
|
|
row = i+this._columns[column]._yr0 - this._settings.topSplit;
|
|
else
|
|
row = i;
|
|
}
|
|
|
|
return { rind:row, cind:column };
|
|
},
|
|
_correctScrollSize:function(){
|
|
var center = -this._center_width;
|
|
for (var i=0; i<this._columns.length; i++)
|
|
center += this._columns[i].width;
|
|
this._scrollLeft = Math.min(this._scrollLeft, Math.max(0, center));
|
|
},
|
|
_updateColsSizeSettings:function(silent){
|
|
if (!this._dtable_fully_ready) return;
|
|
|
|
this._correctScrollSize();
|
|
this._set_columns_positions();
|
|
this._set_split_sizes_x();
|
|
this._render_header_and_footer();
|
|
|
|
if (!silent)
|
|
this._check_rendered_cols(false, false);
|
|
},
|
|
setColumnWidth:function(col, width, skip_update){
|
|
return this._setColumnWidth( this.getColumnIndex(col), width, skip_update);
|
|
},
|
|
_setColumnWidth:function(col, width, skip_update, by_user){
|
|
if (isNaN(width) || col < 0) return;
|
|
var column = this._columns[col];
|
|
|
|
if (column.minWidth && width < column.minWidth)
|
|
width = column.minWidth;
|
|
else if (width<this._settings.minColumnWidth)
|
|
width = this._settings.minColumnWidth;
|
|
|
|
var old = column.width;
|
|
if (old !=width){
|
|
if (col>=this._settings.leftSplit && col<this._rightSplit)
|
|
this._dtable_width += width-old;
|
|
|
|
column.width = width;
|
|
if (column.node) //method can be called from onStructLoad
|
|
column.node.style.width = width+"px";
|
|
else
|
|
return false;
|
|
|
|
if(!skip_update)
|
|
this._updateColsSizeSettings();
|
|
|
|
this.callEvent("onColumnResize", [column.id, width, old, !!by_user]);
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
_getRowHeight:function(row){
|
|
return (row.$height || this._settings.rowHeight)+(row.$subopen?row.$subHeight:0);
|
|
},
|
|
_getHeightByIndex:function(index){
|
|
var id = this.data.order[index];
|
|
if (!id) return this._settings.rowHeight;
|
|
return this._getRowHeight(this.data.pull[id]);
|
|
},
|
|
_getHeightByIndexSumm:function(index1, index2){
|
|
if (this._settings.fixedRowHeight)
|
|
return (index2-index1)*this._settings.rowHeight;
|
|
else {
|
|
var summ = 0;
|
|
for (; index1<index2; index1++)
|
|
summ += this._getHeightByIndex(index1);
|
|
return summ;
|
|
}
|
|
},
|
|
_cellPosition:function(row, column){
|
|
var top;
|
|
if (arguments.length == 1){
|
|
column = row.column; row = row.row;
|
|
}
|
|
var item = this.getItem(row);
|
|
var config = this.getColumnConfig(column);
|
|
var left = 0;
|
|
var parent = 0;
|
|
|
|
for (var index=0; index < this._columns.length; index++){
|
|
if (index == this._settings.leftSplit || index == this._rightSplit)
|
|
left = 0;
|
|
var leftcolumn = this._columns[index];
|
|
if (leftcolumn.id == column){
|
|
var split_column = index<this._settings.leftSplit ? 0 :( index >= this._rightSplit ? 2 : 1);
|
|
parent = this._body.childNodes[split_column].firstChild;
|
|
break;
|
|
}
|
|
|
|
left += leftcolumn.width;
|
|
}
|
|
|
|
|
|
if(this.getIndexById(row) < this._settings.topSplit)
|
|
top = this._getHeightByIndexSumm(0, this.getIndexById(row));
|
|
else
|
|
top = this._getHeightByIndexSumm((this._render_scroll_top||0)-this._settings.topSplit, this.getIndexById(row)) + (this._render_scroll_shift||0);
|
|
|
|
return {
|
|
parent: parent,
|
|
top: top,
|
|
left: left,
|
|
width: config.width,
|
|
height: (item.$height || this._settings.rowHeight)
|
|
};
|
|
},
|
|
_get_total_height:function(){
|
|
var pager = this._settings.pager;
|
|
var start = 0;
|
|
var max = this.data.order.length;
|
|
|
|
if (pager){
|
|
start = pager.size * pager.page;
|
|
max = Math.min(max, start + pager.size);
|
|
if (pager.level){
|
|
start = this.data.$min;
|
|
max = this.data.$max;
|
|
}
|
|
}
|
|
|
|
return this._getHeightByIndexSumm(start, max);
|
|
},
|
|
setRowHeight:function(rowId, height){
|
|
if (isNaN(height)) return;
|
|
if (height<this._settings.minColumnHeight)
|
|
height = this._settings.minColumnHeight;
|
|
|
|
var item = this.getItem(rowId);
|
|
var old_height = item.$height||this._settings.rowHeight;
|
|
|
|
if (old_height != height){
|
|
item.$height = height;
|
|
this.config.fixedRowHeight = false;
|
|
this.render();
|
|
this.callEvent("onRowResize", [rowId, height, old_height]);
|
|
}
|
|
},
|
|
_onscroll_y:function(value){
|
|
var scrollChange = (this._scrollTop !== value);
|
|
|
|
this._scrollTop = value;
|
|
if (!this._settings.prerender){
|
|
this._check_rendered_cols();
|
|
}
|
|
else {
|
|
var conts = this._body.childNodes;
|
|
for (var i = 0; i < conts.length; i++){
|
|
conts[i].scrollTop = value;
|
|
}
|
|
}
|
|
|
|
if (webix.env.$customScroll) webix.CustomScroll._update_scroll(this._body);
|
|
if(scrollChange){
|
|
this.callEvent("onScrollY",[]);
|
|
this.callEvent("onAfterScroll",[]);
|
|
}
|
|
},
|
|
_onscroll_x:function(value){
|
|
var scrollChange = (this._scrollLeft !== value);
|
|
|
|
this._body.childNodes[1].scrollLeft = this._scrollLeft = value;
|
|
if (this._settings.header)
|
|
this._header.childNodes[1].scrollLeft = value;
|
|
if (this._settings.footer)
|
|
this._footer.childNodes[1].scrollLeft = value;
|
|
if (this._settings.prerender===false)
|
|
this._check_rendered_cols(this._minimize_dom_changes?false:true);
|
|
|
|
if (webix.env.$customScroll) webix.CustomScroll._update_scroll(this._body);
|
|
|
|
if(scrollChange){
|
|
this.callEvent("onScrollX",[]);
|
|
this.callEvent("onAfterScroll",[]);
|
|
}
|
|
},
|
|
_get_x_range:function(full){
|
|
if (full) return [0,this._columns.length];
|
|
|
|
var t = this._scrollLeft;
|
|
|
|
var xind = this._settings.leftSplit;
|
|
while (t>0 && this._columns.length - 1 > xind){
|
|
t-=this._columns[xind].width;
|
|
xind++;
|
|
}
|
|
var xend = xind;
|
|
if (t) xind--;
|
|
|
|
t+=this._center_width;
|
|
while (t>0 && xend<this._rightSplit){
|
|
t-=this._columns[xend].width;
|
|
xend++;
|
|
}
|
|
|
|
return [xind, xend];
|
|
},
|
|
getVisibleCount:function(){
|
|
return Math.floor((this._dtable_offset_height) / this.config.rowHeight);
|
|
},
|
|
//returns info about y-scroll position
|
|
_get_y_range:function(full){
|
|
var t = this._scrollTop;
|
|
var start = 0;
|
|
var end = this.count();
|
|
|
|
//apply pager, if defined
|
|
var pager = this._settings.pager;
|
|
if (pager){
|
|
var start = pager.page*pager.size;
|
|
var end = Math.min(end, start+pager.size);
|
|
if (pager.level){
|
|
start = this.data.$min;
|
|
end = this.data.$max;
|
|
}
|
|
}
|
|
|
|
//in case of autoheight - request full rendering
|
|
if (this._settings.autoheight)
|
|
return [start, end, 0];
|
|
|
|
|
|
|
|
|
|
if (full) return [start, end, 0];
|
|
var xind = start;
|
|
var topSplit = this._settings.topSplit || 0;
|
|
if (topSplit)
|
|
xind += topSplit;
|
|
|
|
var rowHeight = this._settings.fixedRowHeight?this._settings.rowHeight:0;
|
|
if (rowHeight){
|
|
var dep = Math.ceil(t/rowHeight);
|
|
t -= dep*rowHeight;
|
|
xind += dep;
|
|
} else
|
|
while (t>0){
|
|
t-=this._getHeightByIndex(xind);
|
|
xind++;
|
|
}
|
|
|
|
//how much of the first cell is scrolled out
|
|
var xdef = (xind>0 && t)?-(this._getHeightByIndex(xind-1)+t):0;
|
|
var xend = xind;
|
|
if (t) xind--;
|
|
|
|
t+=(this._dtable_offset_height||this._content_height) - (this._top_split_height||0);
|
|
|
|
if (rowHeight){
|
|
var dep = Math.ceil(t/rowHeight);
|
|
t-=dep*rowHeight;
|
|
xend+=dep;
|
|
} else {
|
|
while (t>0 && xend<end){
|
|
t-=this._getHeightByIndex(xend);
|
|
xend++;
|
|
}
|
|
}
|
|
|
|
if (xend>end)
|
|
xend = end;
|
|
|
|
return [xind, xend, xdef];
|
|
},
|
|
_repaint_single_row:function(id){
|
|
var item = this.getItem(id);
|
|
var rowindex = this.getIndexById(id);
|
|
|
|
var state = this._get_y_range();
|
|
var freeze = this.config.topSplit;
|
|
var freezeCss = "";
|
|
|
|
if (rowindex >= freeze){
|
|
//row not visible
|
|
if (rowindex < state[0] || rowindex >= state[1]) return;
|
|
rowindex -= state[0]-freeze;
|
|
} else {
|
|
freezeCss = (rowindex == freeze-1) ? " webix_topcell webix_last_topcell" : " webix_topcell";
|
|
}
|
|
|
|
//get visible column
|
|
var x_range = this._get_x_range();
|
|
for (var i=0; i<this._columns.length; i++){
|
|
var column = this._columns[i];
|
|
|
|
//column not visible
|
|
if (i < this._rightSplit && i >= this._settings.leftSplit && ( i<x_range[0] || i > x_range[1]))
|
|
column._yr0 = -999; //ensure that column will not be reused
|
|
|
|
if (column.attached && column.node){
|
|
var node = column.node.childNodes[rowindex];
|
|
var value = this._getValue(item, this._columns[i], 0);
|
|
|
|
node.innerHTML = value;
|
|
node.className = this._getCss(this._columns[i], value, item, id) + freezeCss;
|
|
}
|
|
}
|
|
},
|
|
_check_rendered_cols:function(x_scroll, force){
|
|
if (!this._columns.length) return;
|
|
|
|
if (force)
|
|
this._clearColumnCache();
|
|
|
|
if (webix.debug_render)
|
|
webix.log("Render: "+this.name+"@"+this._settings.id);
|
|
|
|
|
|
var xr = this._get_x_range(this._settings.prerender);
|
|
var yr = this._get_y_range(this._settings.prerender === true);
|
|
|
|
if (x_scroll){
|
|
for (var i=this._settings.leftSplit; i<xr[0]; i++)
|
|
this._hideColumn(i, force);
|
|
for (var i=xr[1]; i<this._rightSplit; i++)
|
|
this._hideColumn(i, force);
|
|
}
|
|
|
|
this._render_full_rows = [];
|
|
var rendered = 0;
|
|
|
|
for (var i=0; i<this._settings.leftSplit; i++)
|
|
rendered += this._renderColumn(i,yr,force);
|
|
for (var i=xr[0]; i<xr[1]; i++)
|
|
rendered += this._renderColumn(i,yr,force, i == xr[0]);
|
|
for (var i=this._rightSplit; i<this._columns.length; i++)
|
|
rendered += this._renderColumn(i,yr,force);
|
|
|
|
this._check_and_render_full_rows(yr[0], yr[1], force);
|
|
this._check_load_next(yr);
|
|
},
|
|
_delete_full_rows:function(start, end){
|
|
this._rows_cache_start = start;
|
|
this._rows_cache_end = end;
|
|
|
|
webix.html.remove(this._rows_cache);
|
|
this._rows_cache=[];
|
|
},
|
|
_adjust_rows:function(){
|
|
if(this._settings.prerender && this._rows_body){
|
|
var state = this.getScrollState();
|
|
this._rows_body.style.top = "-"+(state.y||0) +"px";
|
|
}
|
|
},
|
|
_check_and_render_full_rows:function(start, end, force){
|
|
if (this._rows_body)
|
|
this._rows_body.style.top = this._render_scroll_shift+"px";
|
|
|
|
if (!force && start == this._rows_cache_start && end == this._rows_cache_end){
|
|
if(this.config.topSplit){ //don't move split rows
|
|
for (var i=0; i<this._render_full_rows.length; i++){
|
|
var row = this._rows_cache[i];
|
|
if(this._render_full_rows[i].index<this.config.topSplit){
|
|
row.style.top = this._render_full_rows[i].top-this._render_scroll_shift+"px";
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
this._delete_full_rows(start, end);
|
|
|
|
if (this._render_full_row_some)
|
|
this._render_full_row_some = false;
|
|
else return;
|
|
|
|
for (var i=0; i<this._render_full_rows.length; i++){
|
|
var info = this._render_full_rows[i];
|
|
var item = this.getItem(info.id);
|
|
|
|
var value;
|
|
if (typeof item.$row == "function"){
|
|
value = item.$row.call(this, item, this.type);
|
|
} else {
|
|
value = this._getValue(item, this.getColumnConfig(item.$row), i);
|
|
}
|
|
|
|
var row = this._rows_cache[i] = webix.html.create("DIV", null , value);
|
|
row.className = "webix_cell "+(item.$sub ? ("webix_dtable_sub"+(this._settings.subview?"view":"row")) : "webix_dtable_colrow"+(item.$row?" webix_topcell":""));
|
|
row.setAttribute("column", 0);
|
|
row.setAttribute("row", info.index);
|
|
|
|
var height = (item.$height || this._settings.rowHeight);
|
|
if (item.$subopen)
|
|
row.style.height = item.$subHeight+"px";
|
|
else
|
|
row.style.height = height +"px";
|
|
row.style.paddingRight = webix.ui.scrollSize+"px";
|
|
|
|
var topDelta = (this._render_full_rows[i].index<this.config.topSplit) ? -this._render_scroll_shift : 0;
|
|
row.style.top = topDelta + info.top + (item.$subopen ? height-1 : 0) + "px";
|
|
|
|
if (!this._rows_body){
|
|
this._rows_body = webix.html.create("DIV");
|
|
this._rows_body.style.position = "relative";
|
|
this._rows_body.style.top = this._render_scroll_shift+"px";
|
|
this._body.appendChild(this._rows_body);
|
|
}
|
|
this._rows_body.appendChild(row);
|
|
this.attachEvent("onSyncScroll", function(x,y,t){
|
|
webix.Touch._set_matrix(this._rows_body,0,y,t);
|
|
});
|
|
if (this._settings.subview)
|
|
this.callEvent("onSubViewRender", [item, row]);
|
|
}
|
|
},
|
|
_check_load_next:function(yr){
|
|
var paging = this._settings.pager;
|
|
var fetch = this._settings.datafetch;
|
|
|
|
var direction = (!this._last_valid_render_pos || yr[0] >= this._last_valid_render_pos);
|
|
this._last_valid_render_pos = yr[0];
|
|
|
|
if (this._data_request_flag){
|
|
if (paging && (!fetch || fetch >= paging.size))
|
|
if (this._check_rows([0,paging.size*paging.page], Math.max(fetch, paging.size), true))
|
|
return (this._data_request_flag = null);
|
|
|
|
this._run_load_next(this._data_request_flag, direction);
|
|
this._data_request_flag = null;
|
|
} else {
|
|
if (this._settings.loadahead)
|
|
var check = this._check_rows(yr, this._settings.loadahead, direction);
|
|
}
|
|
},
|
|
_check_rows:function(view, count, dir){
|
|
var start = view[1];
|
|
var end = start+count;
|
|
if (!dir){
|
|
start = view[0]-count;
|
|
end = view[0];
|
|
}
|
|
|
|
if (start<0) start = 0;
|
|
end = Math.min(end, this.data.order.length-1);
|
|
|
|
var result = false;
|
|
for (var i=start; i<end; i++)
|
|
if (!this.data.order[i]){
|
|
if (!result)
|
|
result = { start:i, count:(end-start) };
|
|
else {
|
|
result.last = i;
|
|
result.count = (i-start);
|
|
}
|
|
}
|
|
if (result){
|
|
this._run_load_next(result, dir);
|
|
return true;
|
|
}
|
|
},
|
|
_run_load_next:function(conf, direction){
|
|
var count = Math.max(conf.count, (this._settings.datafetch||this._settings.loadahead||0));
|
|
var start = direction?conf.start:(conf.last - count+1);
|
|
|
|
if (this._maybe_loading_already(conf.count, conf.start)) return;
|
|
this.loadNext(count, start);
|
|
},
|
|
// necessary for safari only
|
|
_preserveScrollTarget: function(columnNode){
|
|
if (webix.env.isSafari){
|
|
var i, node, newNode, scroll,
|
|
dir = [this._x_scroll, this._y_scroll];
|
|
|
|
for(i = 0; i < 2; i++){
|
|
scroll = dir[i];
|
|
if(scroll && scroll._scroll_trg && scroll._scroll_trg.parentNode == columnNode){
|
|
node = scroll._scroll_trg;
|
|
}
|
|
}
|
|
|
|
if(node){
|
|
if(this._scrollWheelTrg)
|
|
webix.html.remove(this._scrollWheelTrg);
|
|
this._scrollWheelTrg = node;
|
|
newNode = node.cloneNode(true); // required for _hideColumn
|
|
node.parentNode.insertBefore(newNode, node);
|
|
this._scrollWheelTrg.style.display = "none";
|
|
this._body.appendChild(this._scrollWheelTrg);
|
|
}
|
|
}
|
|
},
|
|
_hideColumn:function(index){
|
|
var col = this._columns[index];
|
|
|
|
// preserve target node for Safari wheel event
|
|
this._preserveScrollTarget(col.node);
|
|
webix.html.remove(col.node);
|
|
col.attached = false;
|
|
},
|
|
_clearColumnCache:function(){
|
|
for (var i = 0; i < this._columns.length; i++)
|
|
this._columns[i]._yr0 = -1;
|
|
|
|
if (this._rows_cache.length){
|
|
webix.html.remove(this._rows_cache);
|
|
this._rows_cache = [];
|
|
}
|
|
},
|
|
getText:function(row_id, column_id){
|
|
return this._getValue(this.getItem(row_id), this.getColumnConfig(column_id), 0);
|
|
},
|
|
getCss:function(row_id, column_id){
|
|
var item = this.getItem(row_id);
|
|
return this._getCss(this.getColumnConfig(column_id), item[column_id], item, row_id);
|
|
},
|
|
_getCss:function(config, value, item, id){
|
|
var css = "webix_cell";
|
|
|
|
if (config.cssFormat){
|
|
var per_css = config.cssFormat(value, item, id, config.id);
|
|
if (per_css){
|
|
if (typeof per_css == "object")
|
|
css+= " "+webix.html.createCss(per_css);
|
|
else
|
|
css+=" "+per_css;
|
|
}
|
|
}
|
|
|
|
var row_css = item.$css;
|
|
if (row_css){
|
|
if (typeof row_css == "object")
|
|
item.$css = row_css = webix.html.createCss(row_css);
|
|
css+=" "+row_css;
|
|
}
|
|
|
|
var mark = this.data._marks[id];
|
|
if (mark){
|
|
if (mark.$css)
|
|
css+=" "+mark.$css;
|
|
if (mark.$cellCss){
|
|
var mark_marker = mark.$cellCss[config.id];
|
|
if (mark_marker)
|
|
css+=" "+mark_marker;
|
|
}
|
|
}
|
|
|
|
if (item.$cellCss){
|
|
var css_marker = item.$cellCss[config.id];
|
|
if (css_marker){
|
|
if (typeof css_marker == "object")
|
|
css_marker = webix.html.createCss(css_marker);
|
|
css += " "+css_marker;
|
|
}
|
|
}
|
|
|
|
//cell-selection
|
|
var selected = this.data.getMark(item.id,"webix_selected");
|
|
if ((selected && (selected.$row || selected[config.id]))||config.$selected) css+=this._select_css;
|
|
|
|
return css;
|
|
},
|
|
_getValue:function(item, config, i){
|
|
if (!item)
|
|
return "";
|
|
|
|
var value;
|
|
|
|
value = item[config.id];
|
|
if (value === webix.undefined || value === null)
|
|
value = "";
|
|
else if (config.format)
|
|
value = config.format(value);
|
|
if (config.template)
|
|
value = config.template(item, this.type, value, config, i);
|
|
|
|
return value;
|
|
},
|
|
//we don't use render-stack, but still need a place for common helpers
|
|
//so creating a simple "type" holder
|
|
type:{
|
|
checkbox:function(obj, common, value, config){
|
|
var checked = (value == config.checkValue) ? 'checked="true"' : '';
|
|
return "<input class='webix_table_checkbox' type='checkbox' "+checked+">";
|
|
},
|
|
radio:function(obj, common, value, config){
|
|
var checked = (value == config.checkValue) ? 'checked="true"' : '';
|
|
return "<input class='webix_table_radio' type='radio' "+checked+">";
|
|
},
|
|
editIcon:function(){
|
|
return "<span class='webix_icon fa-pencil'></span>";
|
|
},
|
|
trashIcon:function(){
|
|
return "<span class='webix_icon fa-trash'></span>";
|
|
}
|
|
},
|
|
type_setter:function(value){
|
|
if(!this.types || !this.types[value])
|
|
webix.type(this, value);
|
|
else {
|
|
this.type = webix.clone(this.types[value]);
|
|
if (this.type.css)
|
|
this._contentobj.className+=" "+this.type.css;
|
|
}
|
|
if (this.type.on_click)
|
|
webix.extend(this.on_click, this.type.on_click);
|
|
|
|
return value;
|
|
},
|
|
_renderColumn:function(index,yr,force, single){
|
|
var col = this._columns[index];
|
|
if (!col.attached){
|
|
var split_column = index<this._settings.leftSplit ? 0 :( index >= this._rightSplit ? 2 : 1);
|
|
this._body.childNodes[split_column].firstChild.appendChild(col.node);
|
|
col.attached = true;
|
|
col.split = split_column;
|
|
}
|
|
|
|
this._render_scroll_top = yr[0];
|
|
this._render_scroll_shift = 0;
|
|
this._render_scroll_diff = yr[2];
|
|
|
|
//if columns not aligned during scroll - set correct scroll top value for each column
|
|
if (this._settings.scrollAlignY){
|
|
if ((yr[1] == this.data.order.length) || (this.data.$pagesize && yr[1] % this.data.$pagesize === 0 )){
|
|
col.node.style.top = (this._render_scroll_shift = yr[2])+"px";
|
|
} else if (col._yr2)
|
|
col.node.style.top = "0px";
|
|
} else {
|
|
this._render_scroll_shift = yr[2];
|
|
col.node.style.top = yr[2]+"px";
|
|
}
|
|
|
|
if (!force && (col._yr0 == yr[0] && col._yr1 == yr[1]) && (!this._settings.topSplit || col._render_scroll_shift==this._render_scroll_shift)) return 0;
|
|
|
|
var html="";
|
|
var config = this._settings.columns[index];
|
|
var state = {
|
|
row: this._settings.rowHeight,
|
|
total: 0,
|
|
single: single
|
|
};
|
|
|
|
for (var i=0; i<this._settings.topSplit; i++)
|
|
html += this._render_single_cell(i, config, yr, state, -this._render_scroll_shift);
|
|
|
|
for (var i = Math.max(yr[0], this._settings.topSplit); i < yr[1]; i++)
|
|
html += this._render_single_cell(i, config, yr, state, -1);
|
|
|
|
// preserve target node for Safari wheel event
|
|
this._preserveScrollTarget(col.node);
|
|
|
|
col.node.innerHTML = html;
|
|
col._yr0=yr[0];
|
|
col._yr1=yr[1];
|
|
col._yr2=yr[2];
|
|
col._render_scroll_shift=this._render_scroll_shift;
|
|
return 1;
|
|
},
|
|
_render_single_cell:function(i, config, yr, state, top){
|
|
var id = this.data.order[i];
|
|
var item = this.data.getItem(id);
|
|
var html = "";
|
|
|
|
var value;
|
|
if (item){
|
|
var aria = " role='gridcell' aria-rowindex='"+(i+1)+"' aria-colindex='"+(this.getColumnIndex(config.id)+1)+"'"+
|
|
(item.$count || item.$sub?(" aria-expanded='"+(item.open || item.$subopen?"true":"false")+"'"):"")+
|
|
(item.$level?" aria-level='"+item.$level+"'":"");
|
|
|
|
if (state.single && item.$row){
|
|
this._render_full_row_some = true;
|
|
this._render_full_rows.push({ top:state.total, id:item.id, index:i});
|
|
if (!item.$sub){
|
|
var rowHeight = (item.$height || state.row);
|
|
state.total += rowHeight;
|
|
return "<div"+aria+" class='webix_cell' style='height:"+rowHeight+"px;'></div>";
|
|
}
|
|
}
|
|
var value = this._getValue(item, config, i);
|
|
var css = this._getCss(config, value, item, id);
|
|
|
|
if(css.indexOf("select") !==-1 ) aria += " aria-selected='true' tabindex='0'";
|
|
|
|
var isOpen = !!item.$subopen;
|
|
var margin = isOpen ? "margin-bottom:"+item.$subHeight+"px;" : "";
|
|
|
|
if (top>=0){
|
|
if (top>0) margin+="top:"+top+"px;'";
|
|
css = "webix_topcell "+css;
|
|
if(i == this._settings.topSplit-1)
|
|
css = "webix_last_topcell "+css;
|
|
}
|
|
if (item.$height){
|
|
html = "<div"+aria+" class='"+css+"' style='height:"+item.$height+"px;"+margin+"'>"+value+"</div>";
|
|
state.total += item.$height - state.row;
|
|
} else {
|
|
html = "<div"+aria+" class='"+css+"'"+(margin?" style='"+margin+"'":"")+">"+value+"</div>";
|
|
}
|
|
|
|
if (isOpen)
|
|
state.total += item.$subHeight;
|
|
|
|
} else {
|
|
html = "<div role='gridcell' class='webix_cell'></div>";
|
|
if (!this._data_request_flag)
|
|
this._data_request_flag = {start:i, count:yr[1]-i};
|
|
else
|
|
this._data_request_flag.last = i;
|
|
}
|
|
state.total += state.row;
|
|
return html;
|
|
},
|
|
_set_split_sizes_y:function(){
|
|
if (!this._columns.length || isNaN(this._content_height*1)) return;
|
|
webix.debug_size_box(this, ["y-sizing"], true);
|
|
|
|
var wanted_height = this._dtable_height+(this._scrollSizeX?this._scrollSizeX:0);
|
|
if ((this._settings.autoheight || this._settings.yCount) && this.resize())
|
|
return;
|
|
|
|
this._y_scroll.sizeTo(this._content_height, this._header_height, this._footer_height);
|
|
this._y_scroll.define("scrollHeight", wanted_height);
|
|
|
|
this._top_split_height = this._settings.topSplit ? this._getHeightByIndexSumm(0, this._settings.topSplit) : 0;
|
|
this._dtable_offset_height = Math.max(0,this._content_height-this._scrollSizeX-this._header_height-this._footer_height);
|
|
for (var i = 0; i < 3; i++){
|
|
|
|
this._body.childNodes[i].style.height = this._dtable_offset_height+"px";
|
|
if (this._settings.prerender)
|
|
this._body.childNodes[i].firstChild.style.height = this._dtable_height+"px";
|
|
else
|
|
this._body.childNodes[i].firstChild.style.height = this._dtable_offset_height+"px";
|
|
}
|
|
//prevent float overflow, when we have split and very small
|
|
this._header.style.height = this._header_height+"px";
|
|
},
|
|
_set_split_sizes_x:function(){
|
|
if (!this._columns.length) return;
|
|
if (webix.debug_size) webix.log(" - "+this.name+"@"+this._settings.id+" X sizing");
|
|
|
|
var index = 0;
|
|
this._left_width = 0;
|
|
this._right_width = 0;
|
|
this._center_width = 0;
|
|
|
|
while (index<this._settings.leftSplit){
|
|
this._left_width += this._columns[index].width;
|
|
index++;
|
|
}
|
|
|
|
index = this._columns.length-1;
|
|
|
|
while (index>=this._rightSplit){
|
|
this._right_width += this._columns[index].width;
|
|
index--;
|
|
}
|
|
|
|
if (!this._content_width) return;
|
|
|
|
if (this._settings.autowidth && this.resize())
|
|
return;
|
|
|
|
this._center_width = this._content_width - this._right_width - this._left_width - this._scrollSizeY;
|
|
|
|
this._body.childNodes[1].firstChild.style.width = this._dtable_width+"px";
|
|
|
|
this._body.childNodes[0].style.width = this._left_width+"px";
|
|
this._body.childNodes[1].style.width = this._center_width+"px";
|
|
this._body.childNodes[2].style.width = this._right_width+"px";
|
|
this._header.childNodes[0].style.width = this._left_width+"px";
|
|
this._header.childNodes[1].style.width = this._center_width+"px";
|
|
this._header.childNodes[2].style.width = this._right_width+"px";
|
|
this._footer.childNodes[0].style.width = this._left_width+"px";
|
|
this._footer.childNodes[1].style.width = this._center_width+"px";
|
|
this._footer.childNodes[2].style.width = this._right_width+"px";
|
|
|
|
var delta = this._center_width - this._dtable_width;
|
|
if (delta<0) delta=0; //negative header space has not sense
|
|
|
|
if (delta != this._header_fix_width){
|
|
this._header_fix_width = delta;
|
|
this._size_header_footer_fix();
|
|
}
|
|
|
|
// temp. fix: Chrome [DIRTY]
|
|
if (webix.env.isWebKit){
|
|
var w = this._body.childNodes[0].offsetWidth;
|
|
w = this._body.childNodes[1].offsetWidth;
|
|
w = this._body.childNodes[1].firstChild.offsetWidth;
|
|
w = this._body.childNodes[2].offsetWidth;
|
|
}
|
|
|
|
this._x_scroll.sizeTo(this._content_width-this._scrollSizeY);
|
|
this._x_scroll.define("scrollWidth", this._dtable_width+this._left_width+this._right_width);
|
|
},
|
|
$getSize:function(dx, dy){
|
|
if ((this._settings.autoheight || this._settings.yCount) && this._settings.columns){
|
|
//if limit set - use it
|
|
var desired = ((this._settings.yCount || 0) * this._settings.rowHeight);
|
|
//else try to use actual rendered size
|
|
//if component invisible - this is not valid, so fallback to all rows
|
|
if (!desired) desired = this.isVisible() ? this._dtable_height : (this.count() * this._settings.rowHeight);
|
|
//add scroll and check minHeight limit
|
|
this._settings.height = Math.max(desired+(this._scrollSizeX?this._scrollSizeX:0)-1, (this._settings.minHeight||0))+this._header_height+this._footer_height;
|
|
}
|
|
if (this._settings.autowidth && this._settings.columns)
|
|
this._settings.width = Math.max(this._dtable_width+this._left_width+this._right_width+this._scrollSizeY,(this._settings.minWidth||0));
|
|
|
|
|
|
var minwidth = this._left_width+this._right_width+this._scrollSizeY;
|
|
var sizes = webix.ui.view.prototype.$getSize.call(this, dx, dy);
|
|
|
|
|
|
sizes[0] = Math.max(sizes[0]||minwidth);
|
|
return sizes;
|
|
},
|
|
_restore_scroll_state:function(){
|
|
if (this._x_scroll && !webix.env.touch){
|
|
var state = this.getScrollState();
|
|
this._x_scroll._last_scroll_pos = this._y_scroll._last_scroll_pos = -1;
|
|
this.scrollTo(state.x, state.y);
|
|
}
|
|
},
|
|
$setSize:function(x,y){
|
|
var oldw = this._content_width;
|
|
var oldh = this._content_height;
|
|
|
|
if (webix.ui.view.prototype.$setSize.apply(this, arguments)){
|
|
if (this._dtable_fully_ready){
|
|
this.callEvent("onResize",[this._content_width, this._content_height, oldw, oldh]);
|
|
this._set_split_sizes_x();
|
|
this._set_split_sizes_y();
|
|
}
|
|
this.render();
|
|
}
|
|
},
|
|
_on_header_click:function(column){
|
|
var col = this.getColumnConfig(column);
|
|
if (!col.sort) return;
|
|
|
|
var order = 'asc';
|
|
if (col.id == this._last_sorted)
|
|
order = this._last_order == "asc" ? "desc" : "asc";
|
|
|
|
this._sort(col.id, order, col.sort);
|
|
},
|
|
markSorting:function(column, order){
|
|
if (!this._sort_sign)
|
|
this._sort_sign = webix.html.create("DIV");
|
|
|
|
var parent = this._sort_sign.parentNode;
|
|
if(parent){
|
|
parent.removeAttribute("aria-sort");
|
|
parent.removeAttribute("tabindex");
|
|
}
|
|
webix.html.remove(this._sort_sign);
|
|
|
|
if (order){
|
|
var cell = this._get_header_cell(this.getColumnIndex(column));
|
|
if (cell){
|
|
this._sort_sign.className = "webix_ss_sort_"+order;
|
|
cell.style.position = "relative";
|
|
cell.appendChild(this._sort_sign);
|
|
cell.setAttribute("aria-sort", order+"ending");
|
|
cell.setAttribute("tabindex", "0");
|
|
}
|
|
|
|
this._last_sorted = column;
|
|
this._last_order = order;
|
|
} else {
|
|
this._last_sorted = this._last_order = null;
|
|
}
|
|
},
|
|
scroll_setter:function(mode){
|
|
if (typeof mode == "string"){
|
|
this._settings.scrollX = (mode.indexOf("x") != -1);
|
|
this._settings.scrollY = (mode.indexOf("y") != -1);
|
|
return mode;
|
|
} else
|
|
return (this._settings.scrollX = this._settings.scrollY = mode);
|
|
},
|
|
_get_header_cell:function(column){
|
|
var cells = this._header.getElementsByTagName("TD");
|
|
var maybe = null;
|
|
for (var i = 0; i<cells.length; i++)
|
|
if (cells[i].getAttribute("column") == column && !cells[i].getAttribute("active_id")){
|
|
maybe = cells[i].firstChild;
|
|
if ((cells[i].colSpan||0) < 2) return maybe;
|
|
}
|
|
return maybe;
|
|
},
|
|
_sort:function(col_id, direction, type){
|
|
direction = direction || "asc";
|
|
this.markSorting(col_id, direction);
|
|
|
|
if (type == "server"){
|
|
this.callEvent("onBeforeSort",[col_id, direction, type]);
|
|
this.loadNext(0, 0, {
|
|
before:function(){
|
|
this.clearAll(true);
|
|
},
|
|
success:function(){
|
|
this.callEvent("onAfterSort",[col_id, direction, type]);
|
|
}
|
|
}, 0, 1);
|
|
} else {
|
|
if (type == "text"){
|
|
this.data.each(function(obj){ obj.$text = this.getText(obj.id, col_id); }, this);
|
|
type="string"; col_id = "$text";
|
|
}
|
|
|
|
if (typeof type == "function")
|
|
this.data.sort(type, direction);
|
|
else
|
|
this.data.sort(col_id, direction, type || "string");
|
|
}
|
|
},
|
|
_mouseEventCall: function( css_call, e, id, trg ) {
|
|
var functor, i, res;
|
|
if (css_call.length){
|
|
for ( i = 0; i < css_call.length; i++) {
|
|
functor = webix.toFunctor(css_call[i], this.$scope);
|
|
res = functor.call(this,e,id,trg);
|
|
if (res===false) return false;
|
|
}
|
|
}
|
|
},
|
|
//because we using non-standard rendering model, custom logic for mouse detection need to be used
|
|
_mouseEvent:function(e,hash,name,pair){
|
|
e=e||event;
|
|
var trg=e.target||e.srcElement;
|
|
if (this._settings.subview && this != webix.$$(trg)) return;
|
|
|
|
//define some vars, which will be used below
|
|
var css = '',
|
|
css_call = [],
|
|
found = false,
|
|
id = null,
|
|
res,
|
|
trg=e.target||e.srcElement;
|
|
|
|
//loop through all parents
|
|
while (trg && trg.parentNode && trg != this._viewobj.parentNode){
|
|
var trgCss = webix.html._getClassName(trg);
|
|
if ((css = trgCss)) {
|
|
css = css.toString().split(" ");
|
|
|
|
for (var i = css.length - 1; i >= 0; i--)
|
|
if (hash[css[i]])
|
|
css_call.push(hash[css[i]]);
|
|
}
|
|
|
|
if (trg.parentNode.getAttribute && !id){
|
|
var column = trg.parentNode.getAttribute("column") || trg.getAttribute("column");
|
|
if (column){ //we need to ignore TD - which is header|footer
|
|
var isBody = trg.parentNode.tagName == "DIV";
|
|
|
|
//column already hidden or removed
|
|
if(!this._columns[column]) return;
|
|
|
|
found = true;
|
|
if (isBody){
|
|
var index = trg.parentNode.getAttribute("row") || trg.getAttribute("row");
|
|
if (!index){
|
|
index = webix.html.index(trg);
|
|
if (index >= this._settings.topSplit)
|
|
index += this._columns[column]._yr0 - this._settings.topSplit;
|
|
}
|
|
|
|
this._item_clicked = id = { row:this.data.order[index], column:this._columns[column].id};
|
|
id.toString = this._id_to_string;
|
|
} else
|
|
this._item_clicked = id = { column:this._columns[column].id };
|
|
|
|
//some custom css handlers was found
|
|
res = this._mouseEventCall(css_call, e, id, trg);
|
|
if (res===false) return;
|
|
|
|
//call inner handler
|
|
if (isBody ){
|
|
if(this.callEvent("on"+name,[id,e,trg])&&pair){
|
|
this.callEvent("on"+pair,[id,e,trg]);
|
|
}
|
|
}
|
|
else if (name == "ItemClick"){
|
|
var isHeader = (trg.parentNode.parentNode.getAttribute("section") == "header");
|
|
if (isHeader && this.callEvent("onHeaderClick", [id, e, trg]))
|
|
this._on_header_click(id.column);
|
|
}
|
|
css_call = [];
|
|
}
|
|
}
|
|
|
|
trg=trg.parentNode;
|
|
}
|
|
this._mouseEventCall(css_call, e, id, this.$view);
|
|
return found; //returns true if item was located and event was triggered
|
|
},
|
|
|
|
|
|
|
|
|
|
showOverlay:function(message){
|
|
if (!this._datatable_overlay){
|
|
var t = webix.html.create("DIV", { "class":"webix_overlay" }, "");
|
|
this._body.appendChild(t);
|
|
this._datatable_overlay = t;
|
|
}
|
|
this._datatable_overlay.innerHTML = message;
|
|
},
|
|
hideOverlay:function(){
|
|
if (this._datatable_overlay){
|
|
webix.html.remove(this._datatable_overlay);
|
|
this._datatable_overlay = null;
|
|
}
|
|
},
|
|
mapCells: function(startrow, startcol, numrows, numcols, callback, getOnly) {
|
|
if (startrow === null && this.data.order.length > 0) startrow = this.data.order[0];
|
|
if (startcol === null) startcol = this.columnId(0);
|
|
if (numrows === null) numrows = this.data.order.length;
|
|
if (numcols === null) numcols = this._settings.columns.length;
|
|
|
|
if (!this.exists(startrow)) return;
|
|
startrow = this.getIndexById(startrow);
|
|
startcol = this.getColumnIndex(startcol);
|
|
if (startcol === null) return;
|
|
|
|
for (var i = 0; i < numrows && (startrow + i) < this.data.order.length; i++) {
|
|
var row_ind = startrow + i;
|
|
var row_id = this.data.order[row_ind];
|
|
var item = this.getItem(row_id);
|
|
for (var j = 0; j < numcols && (startcol + j) < this._settings.columns.length; j++) {
|
|
var col_ind = startcol + j;
|
|
var col_id = this.columnId(col_ind);
|
|
var result = callback(item[col_id], row_id, col_id, i, j);
|
|
if (!getOnly)
|
|
item[col_id] = result;
|
|
}
|
|
}
|
|
},
|
|
_call_onparse: function(driver, data){
|
|
if (!this._settings.columns && driver.getConfig)
|
|
this.define("columns", driver.getConfig(data));
|
|
},
|
|
_autoDetectConfig:function(){
|
|
var test = this.getItem(this.getFirstId());
|
|
var res = this._settings.columns = [];
|
|
for (var key in test)
|
|
if (key != "id" && key[0] != "$")
|
|
res.push({ id:key, header:key[0].toUpperCase()+key.substr(1), sort:"string", editor:"text" });
|
|
if (res.length)
|
|
res[0].fillspace = true;
|
|
if (typeof this._settings.select == "undefined")
|
|
this.define("select", "row");
|
|
}
|
|
},webix.AutoTooltip, webix.Group, webix.DataMarks, webix.DataLoader, webix.MouseEvents, webix.MapCollection, webix.ui.view, webix.EventSystem, webix.Settings);
|
|
|
|
webix.ui.datafilter = {
|
|
textWaitDelay:500,
|
|
"summColumn":{
|
|
getValue:function(node){ return node.firstChild.innerHTML; },
|
|
setValue: function(){},
|
|
refresh:function(master, node, value){
|
|
var result = 0;
|
|
master.mapCells(null, value.columnId, null, 1, function(value){
|
|
value = value*1;
|
|
if (!isNaN(value))
|
|
result+=value;
|
|
}, true);
|
|
|
|
if (value.format)
|
|
result = value.format(result);
|
|
if (value.template)
|
|
result = value.template({value:result});
|
|
|
|
node.firstChild.innerHTML = result;
|
|
},
|
|
trackCells:true,
|
|
render:function(master, config){
|
|
if (config.template)
|
|
config.template = webix.template(config.template);
|
|
return "";
|
|
}
|
|
},
|
|
"masterCheckbox":{
|
|
getValue:function(){},
|
|
setValue:function(){},
|
|
getHelper:function(node, config){
|
|
return {
|
|
check:function(){ config.checked = false; node.onclick(); },
|
|
uncheck:function(){ config.checked = true; node.onclick(); },
|
|
isChecked:function(){ return config.checked; }
|
|
};
|
|
},
|
|
refresh:function(master, node, config){
|
|
node.onclick = function(){
|
|
this.getElementsByTagName("input")[0].checked = config.checked = !config.checked;
|
|
var column = master.getColumnConfig(config.columnId);
|
|
var checked = config.checked ? column.checkValue : column.uncheckValue;
|
|
master.data.each(function(obj){
|
|
if(obj){ //dyn loading
|
|
obj[config.columnId] = checked;
|
|
master.callEvent("onCheck", [obj.id, config.columnId, checked]);
|
|
this.callEvent("onStoreUpdated", [obj.id, obj, "save"]);
|
|
}
|
|
});
|
|
master.refresh();
|
|
};
|
|
},
|
|
render:function(master, config){
|
|
return "<input type='checkbox' "+(config.checked?"checked='1'":"")+">";
|
|
}
|
|
},
|
|
"textFilter":{
|
|
getInputNode:function(node){ return node.firstChild?node.firstChild.firstChild:{ value: null }; },
|
|
getValue:function(node){ return this.getInputNode(node).value; },
|
|
setValue:function(node, value){ this.getInputNode(node).value = value; },
|
|
refresh:function(master, node, value){
|
|
node.component = master._settings.id;
|
|
master.registerFilter(node, value, this);
|
|
node._comp_id = master._settings.id;
|
|
if (value.value && this.getValue(node) != value.value) this.setValue(node, value.value);
|
|
node.onclick = webix.html.preventEvent;
|
|
webix._event(node, "keydown", this._on_key_down);
|
|
},
|
|
render:function(master, config){
|
|
if (this.init) this.init(config);
|
|
config.css = "webix_ss_filter";
|
|
return "<input "+(config.placeholder?('placeholder="'+config.placeholder+'" '):"")+"type='text'>";
|
|
},
|
|
_on_key_down:function(e, node, value){
|
|
var id = this._comp_id;
|
|
|
|
//tabbing through filters must not trigger filtering
|
|
//we can improve this functionality by preserving initial filter value
|
|
//and comparing new one with it
|
|
if ((e.which || e.keyCode) == 9) return;
|
|
|
|
if (this._filter_timer) window.clearTimeout(this._filter_timer);
|
|
this._filter_timer=window.setTimeout(function(){
|
|
var ui = webix.$$(id);
|
|
//ensure that ui is not destroyed yet
|
|
if (ui) ui.filterByAll();
|
|
},webix.ui.datafilter.textWaitDelay);
|
|
}
|
|
},
|
|
"selectFilter":{
|
|
getInputNode:function(node){ return node.firstChild?node.firstChild.firstChild:{ value: null}; },
|
|
getValue:function(node){ return this.getInputNode(node).value; },
|
|
setValue:function(node, value){ this.getInputNode(node).value = value; },
|
|
refresh:function(master, node, value){
|
|
//value - config from header { contet: }
|
|
value.compare = value.compare || function(a,b){ return a == b; };
|
|
|
|
node.component = master._settings.id;
|
|
master.registerFilter(node, value, this);
|
|
|
|
var data;
|
|
var options = value.options;
|
|
if (options){
|
|
if(typeof options =="string"){
|
|
data = value.options = [];
|
|
webix.ajax(options).then(webix.bind(function(data){
|
|
value.options = data.json();
|
|
this.refresh(master, node, value);
|
|
}, this));
|
|
} else
|
|
data = options;
|
|
}
|
|
else{
|
|
data = master.collectValues(value.columnId);
|
|
data.unshift({ id:"", value:"" });
|
|
}
|
|
|
|
var optview = webix.$$(options);
|
|
if(optview && optview.data && optview.data.getRange){
|
|
data = optview.data.getRange();
|
|
}
|
|
//slow in IE
|
|
//http://jsperf.com/select-options-vs-innerhtml
|
|
|
|
var select = document.createElement("select");
|
|
for (var i = 0; i < data.length; i++){
|
|
var option = document.createElement("option");
|
|
option.value = data[i].id;
|
|
option.text = data[i].value;
|
|
select.add(option);
|
|
}
|
|
|
|
node.firstChild.innerHTML = "";
|
|
node.firstChild.appendChild(select);
|
|
|
|
if (value.value) this.setValue(node, value.value);
|
|
node.onclick = webix.html.preventEvent;
|
|
|
|
select._comp_id = master._settings.id;
|
|
webix._event(select, "change", this._on_change);
|
|
},
|
|
render:function(master, config){
|
|
if (this.init) this.init(config);
|
|
config.css = "webix_ss_filter"; return ""; },
|
|
_on_change:function(e, node, value){
|
|
webix.$$(this._comp_id).filterByAll();
|
|
}
|
|
}
|
|
};
|
|
|
|
webix.ui.datafilter.serverFilter = webix.extend({
|
|
$server: true,
|
|
_on_key_down:function(e, node, value){
|
|
var config, name,
|
|
id = this._comp_id,
|
|
code = (e.which || e.keyCode);
|
|
|
|
node = e.target || e.srcElement;
|
|
//ignore tab and navigation keys
|
|
if (code == 9 || ( code >= 33 && code <= 40)) return;
|
|
if (this._filter_timer) window.clearTimeout(this._filter_timer);
|
|
this._filter_timer=window.setTimeout(function(){
|
|
webix.$$(id).filterByAll();
|
|
},webix.ui.datafilter.textWaitDelay);
|
|
}
|
|
}, webix.ui.datafilter.textFilter);
|
|
|
|
webix.ui.datafilter.serverSelectFilter = webix.extend({
|
|
$server: true,
|
|
_on_change:function(e, node, value){
|
|
var id = this._comp_id;
|
|
webix.$$(id).filterByAll();
|
|
}
|
|
}, webix.ui.datafilter.selectFilter);
|
|
|
|
webix.ui.datafilter.numberFilter = webix.extend({
|
|
init:function(config){
|
|
config.prepare = function(value, filter){
|
|
var equality = (value.indexOf("=") != -1)?1:0;
|
|
var intvalue = this.format(value);
|
|
if (intvalue === "") return "";
|
|
|
|
if (value.indexOf(">") != -1)
|
|
config.compare = this._greater;
|
|
else if (value.indexOf("<") != -1){
|
|
config.compare = this._lesser;
|
|
equality *= -1;
|
|
}
|
|
else {
|
|
config.compare = this._equal;
|
|
equality = 0;
|
|
}
|
|
|
|
return intvalue - equality;
|
|
};
|
|
},
|
|
format:function(value){
|
|
return value.replace(/[^\-\.0-9]/g,"");
|
|
},
|
|
_greater:function(a,b){ return a*1>b; },
|
|
_lesser:function(a,b){ return a!=="" && a*1<b; },
|
|
_equal:function(a,b){ return a*1==b; }
|
|
}, webix.ui.datafilter.textFilter);
|
|
|
|
webix.ui.datafilter.dateFilter = webix.extend({
|
|
format:function(value){
|
|
if (value === "") return "";
|
|
var date = new Date();
|
|
|
|
if (value.indexOf("today") != -1){
|
|
date = webix.Date.dayStart(date);
|
|
} else if (value.indexOf("now") == -1){
|
|
var parts = value.match(/[0-9]+/g);
|
|
if (!parts||!parts.length) return "";
|
|
if (parts.length < 3){
|
|
parts.reverse();
|
|
date = new Date(parts[0], (parts[1]||1)-1, 1);
|
|
} else
|
|
date = webix.i18n.dateFormatDate(value.replace(/^[>< =]+/,""));
|
|
}
|
|
return date.valueOf();
|
|
}
|
|
}, webix.ui.datafilter.numberFilter);
|
|
|
|
webix.extend(webix.ui.datatable,{
|
|
filterByAll:function(){
|
|
//we need to use dynamic function creating
|
|
var server = false;
|
|
this.data.silent(function(){
|
|
this.filter();
|
|
var first = false;
|
|
for (var key in this._filter_elements){
|
|
webix.assert(key, "empty column id for column with filtering");
|
|
if(!this.isColumnVisible(key))
|
|
continue;
|
|
var record = this._filter_elements[key];
|
|
var originvalue = record[2].getValue(record[0]);
|
|
|
|
//saving last filter value, for usage in getState
|
|
var inputvalue = originvalue;
|
|
if (record[1].prepare)
|
|
inputvalue = record[1].prepare.call(record[2], inputvalue, record[1], this);
|
|
|
|
//preserve original value
|
|
record[1].value = originvalue;
|
|
var compare = record[1].compare;
|
|
|
|
if (!this.callEvent("onBeforeFilter",[key, inputvalue, record[1]])) continue;
|
|
if(record[2].$server || server){ //if one of filters is server side, do not run any client side filters
|
|
server = true;
|
|
} else {
|
|
if (inputvalue === "") continue;
|
|
|
|
if (compare){
|
|
compare = this._multi_compare(key, compare);
|
|
this.filter(webix.bind(function(obj, value){
|
|
if (!obj) return false;
|
|
return compare(obj[key], value, obj);
|
|
},this), inputvalue, first);
|
|
}
|
|
else
|
|
this.filter(key, inputvalue, first);
|
|
|
|
first = true;
|
|
}
|
|
}
|
|
|
|
if (server)
|
|
this._runServerFilter();
|
|
|
|
}, this);
|
|
|
|
if (!server){
|
|
this.refresh();
|
|
this.callEvent("onAfterFilter",[]);
|
|
}
|
|
},
|
|
_multi_compare: function(key, compare){
|
|
var column = this.getColumnConfig(key);
|
|
var separator = column ? column.optionslist : null;
|
|
|
|
//default mode
|
|
if (!separator)
|
|
return compare;
|
|
|
|
if(typeof separator != "string")
|
|
separator = ",";
|
|
|
|
return function(itemValue, inputValue, obj){
|
|
if(!itemValue)
|
|
return true;
|
|
var ids = itemValue.split(separator);
|
|
for (var i = 0; i < ids.length; i++) {
|
|
if (compare(ids[i], inputValue, obj))
|
|
return true;
|
|
}
|
|
};
|
|
},
|
|
filterMode_setter:function(mode){
|
|
return webix.extend(this.data._filterMode, mode, true);
|
|
},
|
|
getFilter:function(columnId){
|
|
var filter = this._filter_elements[columnId];
|
|
webix.assert(filter, "Filter doesn't exists for column in question");
|
|
|
|
if (filter && filter[2].getInputNode)
|
|
return filter[2].getInputNode(filter[0]);
|
|
return null;
|
|
},
|
|
registerFilter:function(node, config, obj){
|
|
this._filter_elements[config.columnId] = [node, config, obj];
|
|
},
|
|
collectValues:function(id){
|
|
var values = [];
|
|
var checks = { "" : true };
|
|
|
|
var obj = this.getColumnConfig(id);
|
|
var options = obj.options||obj.collection;
|
|
|
|
if (options){
|
|
if (typeof options == "object" && !options.loadNext){
|
|
//raw object
|
|
if (webix.isArray(options))
|
|
for (var i=0; i<options.length; i++)
|
|
values.push({ id:options[i], value:options[i] });
|
|
else
|
|
for (var key in options)
|
|
values.push({ id:key, value:options[key] });
|
|
return values;
|
|
} else {
|
|
//view
|
|
if (typeof options === "string")
|
|
options = webix.$$(options);
|
|
if (options.getBody)
|
|
options = options.getBody();
|
|
|
|
this._collectValues.call(options, "id", "value", values, checks);
|
|
}
|
|
} else
|
|
this._collectValues(obj.id, obj.id, values, checks);
|
|
|
|
var obj = { values: values };
|
|
this.callEvent("onCollectValues", [id, obj]);
|
|
return obj.values;
|
|
},
|
|
_collectValues:function(id, value, values, checks){
|
|
this.data.each(function(obj){
|
|
var test = obj ? obj[id] : "";
|
|
if (test !== webix.undefined && !checks[test]){
|
|
checks[test] = true;
|
|
values.push({ id:obj[id], value:obj[value] });
|
|
}
|
|
}, this, true);
|
|
|
|
if (values.length){
|
|
var type = typeof values[0].value === "string" ? "string" : "raw";
|
|
values.sort( this.data.sorting.create({ as:type, by:"value", dir:"asc" }) );
|
|
}
|
|
},
|
|
_runServerFilter: function(name){
|
|
this.loadNext(0,0,{
|
|
before:function(){
|
|
if (this.editStop) this.editStop();
|
|
this.clearAll(true);
|
|
},
|
|
success:function(){
|
|
this.callEvent("onAfterFilter",[]);
|
|
}
|
|
},0,1);
|
|
}
|
|
});
|
|
|
|
|
|
webix.extend(webix.ui.datatable, {
|
|
hover_setter:function(value){
|
|
if (value && !this._hover_initialized){
|
|
this._enable_mouse_move();
|
|
this.config.experimental = true;
|
|
|
|
this.attachEvent("onMouseMoving", function(e){
|
|
|
|
var row = this.locate(arguments[0]);
|
|
row = row ? row.row : null;
|
|
|
|
if (this._last_hover != row){
|
|
if (this._last_hover)
|
|
this.removeRowCss(this._last_hover, this._settings.hover);
|
|
|
|
this._delayed_hover_set();
|
|
this._last_hover = row;
|
|
}
|
|
});
|
|
|
|
this.attachEvent("onMouseOut", function(){
|
|
if (this._last_hover){
|
|
this.removeRowCss(this._last_hover, this._settings.hover);
|
|
this._last_hover = null;
|
|
}
|
|
});
|
|
|
|
this._hover_initialized = 1;
|
|
}
|
|
return value;
|
|
},
|
|
_delayed_hover_set:function(){
|
|
webix.delay(function(){
|
|
if (this._last_hover)
|
|
this.addRowCss( this._last_hover, this._settings.hover );
|
|
}, this, [], 5);
|
|
},
|
|
select_setter:function(value){
|
|
if (!this.select && value){
|
|
webix.extend(this, this._selections._commonselect, true);
|
|
if (value === true)
|
|
value = "row";
|
|
else if (value == "multiselect"){
|
|
value = "row";
|
|
this._settings.multiselect = true;
|
|
}
|
|
webix.assert(this._selections[value], "Unknown selection mode: "+value);
|
|
webix.extend(this, this._selections[value], true);
|
|
}
|
|
return value;
|
|
},
|
|
getSelectedId:function(mode){
|
|
return mode?[]:""; //dummy placeholder
|
|
},
|
|
getSelectedItem:function(mode){
|
|
return webix.SelectionModel.getSelectedItem.call(this, mode);
|
|
},
|
|
_selections:{
|
|
//shared methods for all selection models
|
|
_commonselect:{
|
|
_select_css:' webix_cell_select',
|
|
$init:function(){
|
|
this._reinit_selection();
|
|
|
|
this.on_click.webix_cell = webix.bind(this._click_before_select, this);
|
|
|
|
//temporary stab, actual handlers need to be created
|
|
this._data_cleared = this._data_filtered = function(){
|
|
this.unselect();
|
|
};
|
|
|
|
this.data.attachEvent("onStoreUpdated",webix.bind(this._data_updated,this));
|
|
this.data.attachEvent("onSyncApply", webix.bind(this._data_synced, this));
|
|
this.data.attachEvent("onClearAll", webix.bind(this._data_cleared,this));
|
|
this.data.attachEvent("onAfterFilter", webix.bind(this._data_filtered,this));
|
|
this.data.attachEvent("onIdChange", webix.bind(this._id_changed,this));
|
|
|
|
this.$ready.push(webix.SelectionModel._set_noselect);
|
|
},
|
|
_id_changed:function(oldid, newid){
|
|
for (var i=0; i<this._selected_rows.length; i++)
|
|
if (this._selected_rows[i] == oldid)
|
|
this._selected_rows[i] = newid;
|
|
|
|
for (var i=0; i<this._selected_areas.length; i++){
|
|
var item = this._selected_areas[i];
|
|
if (item.row == oldid){
|
|
oldid = this._select_key(item);
|
|
item.row = newid;
|
|
newid = this._select_key(item);
|
|
item.id = newid;
|
|
|
|
delete this._selected_pull[oldid];
|
|
this._selected_pull[newid] = true;
|
|
}
|
|
}
|
|
},
|
|
_data_updated:function(id, obj, type){
|
|
if (type == "delete")
|
|
this.unselect(id);
|
|
},
|
|
_data_synced:function(){
|
|
for (var i = this._selected_areas.length-1; i >=0 ; i--){
|
|
if (!this.exists(this._selected_areas[i].row))
|
|
this._selected_areas.splice(i,1);
|
|
}
|
|
},
|
|
_reinit_selection:function(){
|
|
//list of selected areas
|
|
this._selected_areas=[];
|
|
//key-value hash of selected areas, for fast search
|
|
this._selected_pull={};
|
|
//used to track selected cell objects
|
|
this._selected_rows = [];
|
|
},
|
|
isSelected:function(id, column){
|
|
var key;
|
|
if (!webix.isUndefined(column))
|
|
key = this._select_key({ row:id, column: column});
|
|
else
|
|
key = typeof id === "object"? this._select_key(id) : id;
|
|
|
|
return this._selected_pull[key];
|
|
},
|
|
getSelectedId:function(asArray, plain){
|
|
var result;
|
|
|
|
//if multiple selections was created - return array
|
|
//in case of single selection, return value or array, when asArray parameter provided
|
|
if (this._selected_areas.length > 1 || asArray){
|
|
result = [].concat(this._selected_areas);
|
|
if (plain)
|
|
for (var i = 0; i < result.length; i++)
|
|
result[i]=result[i].id;
|
|
} else {
|
|
result = this._selected_areas[0];
|
|
if (plain && result)
|
|
return result.id;
|
|
}
|
|
|
|
return result;
|
|
},
|
|
_id_to_string:function(){
|
|
return this.row;
|
|
},
|
|
_select:function(data, preserve){
|
|
var key = this._select_key(data);
|
|
//don't allow selection on unnamed columns
|
|
if (key === null) return;
|
|
|
|
if (preserve === -1)
|
|
return this._unselect(data);
|
|
|
|
data.id = key;
|
|
data.toString = this._id_to_string;
|
|
|
|
if (!this.callEvent("onBeforeSelect",[data, preserve])) return false;
|
|
|
|
//ignore area, if it was already selected and
|
|
// - we are preserving existing selection
|
|
// - this is the only selected area
|
|
// otherwise we need to clear other selected areas
|
|
if (this._selected_pull[key] && (preserve || this._selected_areas.length == 1)) return;
|
|
|
|
if (!preserve)
|
|
this._clear_selection();
|
|
|
|
this._selected_areas.push(data);
|
|
this._selected_pull[key] = true;
|
|
|
|
this.callEvent("onAfterSelect",[data, preserve]);
|
|
|
|
|
|
this._finalize_select(this._post_select(data));
|
|
return true;
|
|
},
|
|
_clear_selection:function(){
|
|
if (!this._selected_areas.length) return false;
|
|
|
|
for (var i=0; i<this._selected_areas.length; i++){
|
|
if (!this.callEvent("onBeforeUnSelect", [this._selected_areas[i]])) return false;
|
|
}
|
|
|
|
for (var i=0; i<this._selected_rows.length; i++)
|
|
this.data.removeMark(this._selected_rows[i], "webix_selected");
|
|
|
|
var cols = this._settings.columns;
|
|
if (cols)
|
|
for (var i = 0; i < cols.length; i++) {
|
|
cols[i].$selected = null;
|
|
}
|
|
|
|
var data = this._selected_areas;
|
|
this._reinit_selection();
|
|
for (var i=0; i<data.length; i++){
|
|
this.callEvent("onAfterUnSelect", [data[i]]);
|
|
}
|
|
return true;
|
|
},
|
|
unselectAll:function(){
|
|
this.clearSelection();
|
|
},
|
|
selectAll:function(){
|
|
this.selectRange();
|
|
},
|
|
clearSelection:function(){
|
|
if (this._clear_selection()){
|
|
this.callEvent("onSelectChange",[]);
|
|
this.render();
|
|
}
|
|
},
|
|
_unselect:function(data){
|
|
var key = this._select_key(data);
|
|
if (!key && this._selected_areas.length){
|
|
this.clearSelection();
|
|
this.callEvent("onSelectChange", []);
|
|
}
|
|
|
|
//ignore area, if it was already selected
|
|
if (!this._selected_pull[key]) return;
|
|
|
|
if (!this.callEvent("onBeforeUnSelect",[data])) return false;
|
|
|
|
for (var i = 0; i < this._selected_areas.length; i++){
|
|
if (this._selected_areas[i].id == key){
|
|
this._selected_areas.splice(i,1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
delete this._selected_pull[key];
|
|
|
|
this.callEvent("onAfterUnSelect",[data]);
|
|
this._finalize_select(0, this._post_unselect(data));
|
|
},
|
|
_add_item_select:function(id){
|
|
var item = this.getItem(id);
|
|
return this.data.addMark(item.id, "webix_selected", 0, { $count : 0 }, true);
|
|
|
|
},
|
|
_finalize_select:function(id){
|
|
if (id)
|
|
this._selected_rows.push(id);
|
|
if (!this._silent_selection){
|
|
this.render();
|
|
this.callEvent("onSelectChange",[]);
|
|
}
|
|
},
|
|
_click_before_select:function(e, id){
|
|
var preserve = e.ctrlKey || e.metaKey || (this._settings.multiselect == "touch");
|
|
var range = e.shiftKey;
|
|
|
|
if (!this._settings.multiselect && this._settings.select != "multiselect")
|
|
preserve = range = false;
|
|
|
|
if (range && this._selected_areas.length){
|
|
var last = this._selected_areas[this._selected_areas.length-1];
|
|
this._selectRange(id, last);
|
|
} else {
|
|
if (preserve && this._selected_pull[this._select_key(id)])
|
|
this._unselect(id);
|
|
else
|
|
this._select({ row: id.row, column:id.column }, preserve);
|
|
}
|
|
},
|
|
_mapSelection:function(callback, column, row){
|
|
var cols = this._settings.columns;
|
|
//selected columns only
|
|
if (column){
|
|
var temp = [];
|
|
for (var i=0; i<cols.length; i++)
|
|
if (cols[i].$selected)
|
|
temp.push(cols[i]);
|
|
cols = temp;
|
|
}
|
|
|
|
var rows = this.data.order;
|
|
var row_ind = 0;
|
|
|
|
for (var i=0; i<rows.length; i++){
|
|
var item = this.getItem(rows[i]);
|
|
if (!item) continue; //dyn loading, row is not available
|
|
var selection = this.data.getMark(item.id, "webix_selected");
|
|
if (selection || column){
|
|
var col_ind = 0;
|
|
for (var j = 0; j < cols.length; j++){
|
|
var id = cols[j].id;
|
|
if (row || column || selection[id]){
|
|
if (callback)
|
|
item[id] = callback(item[id], rows[i], id, row_ind, col_ind);
|
|
else
|
|
return {row:rows[i], column:id};
|
|
col_ind++;
|
|
}
|
|
}
|
|
//use separate row counter, to count only selected rows
|
|
row_ind++;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
row : {
|
|
_select_css:' webix_row_select',
|
|
_select_key:function(data){ return data.row; },
|
|
select:function(row_id, preserve){
|
|
//when we are using id from mouse events
|
|
if (row_id) row_id = row_id.toString();
|
|
|
|
webix.assert(this.data.exists(row_id), "Incorrect id in select command: "+row_id);
|
|
this._select({ row:row_id }, preserve);
|
|
},
|
|
_post_select:function(data){
|
|
this._add_item_select(data.row).$row = true;
|
|
return data.row;
|
|
},
|
|
unselect:function(row_id){
|
|
this._unselect({row : row_id});
|
|
},
|
|
_post_unselect:function(data){
|
|
this.data.removeMark(data.row, "webix_selected", 0, 1);
|
|
return data.row;
|
|
},
|
|
mapSelection:function(callback){
|
|
return this._mapSelection(callback, false, true);
|
|
},
|
|
_selectRange:function(a,b){
|
|
return this.selectRange(a.row, b.row);
|
|
},
|
|
selectRange:function(row_id, end_row_id, preserve){
|
|
if (webix.isUndefined(preserve)) preserve = true;
|
|
|
|
var row_start_ind = row_id ? this.getIndexById(row_id) : 0;
|
|
var row_end_ind = end_row_id ? this.getIndexById(end_row_id) : this.data.order.length-1;
|
|
|
|
if (row_start_ind>row_end_ind){
|
|
var temp = row_start_ind;
|
|
row_start_ind = row_end_ind;
|
|
row_end_ind = temp;
|
|
}
|
|
|
|
this._silent_selection = true;
|
|
for (var i=row_start_ind; i<=row_end_ind; i++){
|
|
var id = this.getIdByIndex(i);
|
|
if (!id){
|
|
if (row_id)
|
|
this.select(row_id);
|
|
break;
|
|
}
|
|
this.select(id, preserve);
|
|
}
|
|
|
|
this._silent_selection = false;
|
|
this._finalize_select();
|
|
}
|
|
},
|
|
|
|
cell:{
|
|
_select_key:function(data){
|
|
if (!data.column) return null;
|
|
return data.row+"_"+data.column;
|
|
},
|
|
select:function(row_id, column_id, preserve){
|
|
webix.assert(this.data.exists(row_id), "Incorrect id in select command: "+row_id);
|
|
this._select({row:row_id, column:column_id}, preserve);
|
|
},
|
|
_post_select:function(data){
|
|
var sel = this._add_item_select(data.row);
|
|
sel.$count++;
|
|
sel[data.column]=true;
|
|
return data.row;
|
|
},
|
|
unselect:function(row_id, column_id){
|
|
this._unselect({row:row_id, column:column_id});
|
|
},
|
|
_post_unselect:function(data){
|
|
var sel = this._add_item_select(data.row);
|
|
sel.$count-- ;
|
|
sel[data.column] = false;
|
|
if (sel.$count<=0)
|
|
this.data.removeMark(data.row,"webix_selected");
|
|
return data.row;
|
|
},
|
|
mapSelection:function(callback){
|
|
return this._mapSelection(callback, false, false);
|
|
},
|
|
_selectRange:function(a,b){
|
|
return this.selectRange(a.row, a.column, b.row, b.column);
|
|
},
|
|
|
|
selectRange:function(row_id, column_id, end_row_id, end_column_id, preserve){
|
|
if (webix.isUndefined(preserve)) preserve = true;
|
|
|
|
var row_start_ind = row_id ? this.getIndexById(row_id) : 0;
|
|
var row_end_ind = end_row_id ? this.getIndexById(end_row_id) : this.data.order.length-1;
|
|
|
|
var col_start_ind = column_id ? this.getColumnIndex(column_id) : 0;
|
|
var col_end_ind = end_column_id ? this.getColumnIndex(end_column_id) : this._columns.length-1;
|
|
|
|
if (row_start_ind>row_end_ind){
|
|
var temp = row_start_ind;
|
|
row_start_ind = row_end_ind;
|
|
row_end_ind = temp;
|
|
}
|
|
|
|
if (col_start_ind>col_end_ind){
|
|
var temp = col_start_ind;
|
|
col_start_ind = col_end_ind;
|
|
col_end_ind = temp;
|
|
}
|
|
|
|
this._silent_selection = true;
|
|
for (var i=row_start_ind; i<=row_end_ind; i++)
|
|
for (var j=col_start_ind; j<=col_end_ind; j++)
|
|
this.select(this.getIdByIndex(i), this.columnId(j), preserve);
|
|
this._silent_selection = false;
|
|
this._finalize_select();
|
|
}
|
|
},
|
|
|
|
column:{
|
|
_select_css:' webix_column_select',
|
|
_select_key:function(data){ return data.column; },
|
|
_id_to_string:function(){
|
|
return this.column;
|
|
},
|
|
//returns box-like area, with ordered selection cells
|
|
select:function(column_id, preserve){
|
|
this._select({ column:column_id }, preserve);
|
|
},
|
|
_post_select:function(data){
|
|
this._settings.columns[this.getColumnIndex(data.column)].$selected = true;
|
|
if (!this._silent_selection)
|
|
this._render_header_and_footer();
|
|
},
|
|
unselect:function(column_id){
|
|
this._unselect({column : column_id});
|
|
},
|
|
_post_unselect:function(data){
|
|
this._settings.columns[this.getColumnIndex(data.column)].$selected = null;
|
|
this._render_header_and_footer();
|
|
},
|
|
mapSelection:function(callback){
|
|
return this._mapSelection(callback, true, false);
|
|
},
|
|
_selectRange:function(a,b){
|
|
return this.selectRange(a.column, b.column);
|
|
},
|
|
selectRange:function(column_id, end_column_id, preserve){
|
|
if (webix.isUndefined(preserve)) preserve = true;
|
|
|
|
var column_start_ind = column_id ? this.getColumnIndex(column_id) : 0;
|
|
var column_end_ind = end_column_id ? this.getColumnIndex(end_column_id) : this._columns.length-1;
|
|
|
|
if (column_start_ind>column_end_ind){
|
|
var temp = column_start_ind;
|
|
column_start_ind = column_end_ind;
|
|
column_end_ind = temp;
|
|
}
|
|
|
|
this._silent_selection = true;
|
|
for (var i=column_start_ind; i<=column_end_ind; i++)
|
|
this.select(this.columnId(i), preserve);
|
|
|
|
this._silent_selection = false;
|
|
|
|
this._render_header_and_footer();
|
|
this._finalize_select();
|
|
},
|
|
_data_synced:function(){
|
|
//do nothing, as columns are not changed
|
|
}
|
|
},
|
|
area: {
|
|
_select_key:function(data){
|
|
return data.row+"_"+data.column;
|
|
},
|
|
getSelectedId: function(asArray){
|
|
var area = this.getSelectArea();
|
|
var result = [];
|
|
if(area){
|
|
if(asArray && ( area.start.row != area.end.row || area.start.column != area.end.column )){
|
|
var row_start_ind = this.getIndexById(area.start.row);
|
|
var row_end_ind = this.getIndexById(area.end.row);
|
|
//filtering in process
|
|
if(row_start_ind == -1 || row_end_ind == -1)
|
|
return result;
|
|
|
|
var col_start_ind = this.getColumnIndex(area.start.column);
|
|
var col_end_ind = this.getColumnIndex(area.end.column);
|
|
|
|
for (var i=row_start_ind; i<=row_end_ind; i++)
|
|
for (var j=col_start_ind; j<=col_end_ind; j++)
|
|
result.push({row:this.getIdByIndex(i), column:this.columnId(j)});
|
|
}
|
|
else{
|
|
result.push(area.end);
|
|
}
|
|
}
|
|
|
|
return asArray?result:result[0];
|
|
},
|
|
unselect:function(row_id){
|
|
this._unselect();
|
|
},
|
|
_unselect: function() {
|
|
this.removeSelectArea();
|
|
this.callEvent("onSelectChange", []);
|
|
},
|
|
mapSelection:function(callback){
|
|
var select = this.getSelectArea();
|
|
if (select){
|
|
var sind = this.getColumnIndex(select.start.column);
|
|
var eind = this.getColumnIndex(select.end.column);
|
|
var srow = this.getIndexById(select.start.row);
|
|
var erow = this.getIndexById(select.end.row);
|
|
|
|
for (var i = srow; i <= erow; i++) {
|
|
var rid = this.data.order[i];
|
|
var item = this.getItem(rid);
|
|
for (var j = sind; j <= eind; j++) {
|
|
var cid = this._columns[j].id;
|
|
if (callback)
|
|
item[cid] = callback((item[cid] || ""), rid, cid, i-srow, j-sind);
|
|
else
|
|
return { row:rid, column:cid };
|
|
}
|
|
}
|
|
}
|
|
},
|
|
select:function(row_id, column_id, preserve){
|
|
webix.assert(this.data.exists(row_id), "Incorrect id in select command: "+row_id);
|
|
this._select({row:row_id, column:column_id}, preserve);
|
|
},
|
|
_selectRange:function(id,last){
|
|
this._extendAreaRange(id, last);
|
|
},
|
|
_select: function(cell, preserve){
|
|
//ctrl-selection is not supported yet, so ignoring the preserve flag
|
|
this.addSelectArea(cell,cell,false);
|
|
return true;
|
|
},
|
|
_data_synced:function(){
|
|
if(this._selected_areas.length)
|
|
this.refreshSelectArea();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
webix.extend(webix.ui.datatable, {
|
|
blockselect_setter:function(value){
|
|
if (value && this._block_sel_flag){
|
|
webix._event(this._viewobj, webix.env.mouse.move, this._bs_move, {bind:this});
|
|
webix._event(this._viewobj, webix.env.mouse.down, this._bs_down, {bind:this});
|
|
webix.event(document.body, webix.env.mouse.up, this._bs_up, {bind:this});
|
|
this._block_sel_flag = this._bs_ready = this._bs_progress = false;
|
|
this.attachEvent("onAfterScroll", function(){
|
|
this._update_block_selection();
|
|
});
|
|
// auto scroll
|
|
webix.extend(this, webix.AutoScroll, true);
|
|
this.attachEvent("onBeforeAutoScroll",function(){
|
|
return this._bs_progress;
|
|
});
|
|
}
|
|
return value;
|
|
},
|
|
_block_sel_flag:true,
|
|
_childOf:function(e, tag){
|
|
var src = e.target||e.srcElement;
|
|
while (src){
|
|
if (src.getAttribute && src.getAttribute("webixignore")) return false;
|
|
if (src == tag)
|
|
return true;
|
|
src = src.parentNode;
|
|
}
|
|
return false;
|
|
},
|
|
_bs_down:function(e){
|
|
// do not listen to mousedown of subview on master
|
|
if (this._settings.subview && this != webix.$$(e.target||e.srcElement)) return;
|
|
if (this._childOf(e, this._body)){
|
|
//disable block selection when we have an active editor
|
|
if (e.target && e.target.tagName == "INPUT" || this._rs_process) return;
|
|
|
|
webix.html.addCss(document.body,"webix_noselect");
|
|
this._bs_position = webix.html.offset(this._body);
|
|
var pos = webix.html.pos(e);
|
|
this._bs_ready = [pos.x - this._bs_position.x, pos.y - this._bs_position.y];
|
|
}
|
|
},
|
|
_bs_up:function(e){
|
|
if (this._block_panel){
|
|
this._bs_select("select", true, e);
|
|
this._block_panel = webix.html.remove(this._block_panel);
|
|
}
|
|
webix.html.removeCss(document.body,"webix_noselect");
|
|
this._bs_ready = this._bs_progress = false;
|
|
if (this._auto_scroll_delay)
|
|
this._auto_scroll_delay = window.clearTimeout(this._auto_scroll_delay);
|
|
},
|
|
_update_block_selection: function(){
|
|
if (this._bs_progress)
|
|
this._bs_select(false, false);
|
|
},
|
|
_bs_select:function(mode, theend, e){
|
|
var start = null;
|
|
if(!this._bs_ready[2])
|
|
this._bs_ready[2] = this._locate_cell_xy.apply(this, this._bs_ready);
|
|
start = this._bs_ready[2];
|
|
|
|
var end = this._locate_cell_xy.apply(this, this._bs_progress);
|
|
|
|
if (!this.callEvent("onBeforeBlockSelect", [start, end, theend, e]))
|
|
return;
|
|
|
|
if ((!this._bs_do_select || this._bs_do_select(start, end, theend, e) !== false) && (start.row && end.row)){
|
|
if (mode === "select"){
|
|
this._clear_selection();
|
|
this._selectRange(start, end);
|
|
} else {
|
|
var startx, starty, endx, endy;
|
|
|
|
if (mode === "box"){
|
|
startx = Math.min(this._bs_ready[0],this._bs_progress[0]);
|
|
endx = Math.max(this._bs_ready[0],this._bs_progress[0]);
|
|
|
|
starty = Math.min(this._bs_ready[1],this._bs_progress[1]);
|
|
endy = Math.max(this._bs_ready[1],this._bs_progress[1]);
|
|
} else {
|
|
var startn = this._cellPosition(start.row, start.column);
|
|
var endn = this._cellPosition(end.row, end.column);
|
|
var scroll = this.getScrollState();
|
|
|
|
var startWidth = startn.width;
|
|
var endWidth = endn.width;
|
|
|
|
if (this._right_width && this._bs_ready[0] > this._left_width+this._center_width){
|
|
startn.left += this._left_width+this._center_width;
|
|
} else if (this._left_width){
|
|
|
|
if (this._bs_ready[0] > this._left_width){
|
|
if(startn.left < scroll.x){
|
|
startWidth -= scroll.x-startn.left;
|
|
startn.left = this._left_width;
|
|
}
|
|
else
|
|
startn.left+=this._left_width-scroll.x;
|
|
|
|
}
|
|
|
|
} else startn.left -= scroll.x;
|
|
|
|
|
|
|
|
if (this._right_width && this._bs_progress[0] > this._left_width+this._center_width){
|
|
endn.left += this._left_width+this._center_width;
|
|
} else if (this._left_width){
|
|
if (this._bs_progress[0] > this._left_width){
|
|
if(endn.left < scroll.x){
|
|
endWidth -= scroll.x-endn.left;
|
|
endn.left = this._left_width;
|
|
}
|
|
|
|
else
|
|
endn.left+=this._left_width-scroll.x;
|
|
}
|
|
} else endn.left -= scroll.x;
|
|
|
|
if(this._settings.prerender){
|
|
startn.top -= this._scrollTop;
|
|
endn.top -= this._scrollTop;
|
|
}
|
|
|
|
|
|
startx = Math.min(startn.left, endn.left);
|
|
endx = Math.max(startn.left+startWidth, endn.left+endWidth);
|
|
|
|
starty = Math.min(startn.top, endn.top);
|
|
endy = Math.max(startn.top+startn.height, endn.top+endn.height);
|
|
|
|
if(this._settings.topSplit)
|
|
starty += this._getTopSplitOffset(start);
|
|
|
|
if (this._auto_scroll_delay)
|
|
this._auto_scroll_delay = window.clearTimeout(this._auto_scroll_delay);
|
|
if(e)
|
|
this._auto_scroll_delay = webix.delay(this._auto_scroll, this, [webix.html.pos(e)], 250);
|
|
}
|
|
|
|
|
|
var style = this._block_panel.style;
|
|
style.left = startx+"px";
|
|
style.top = starty+"px";
|
|
style.width = (endx-startx)+"px";
|
|
style.height = (endy-starty)+"px";
|
|
|
|
}
|
|
}
|
|
|
|
if (theend)
|
|
this.callEvent("onAfterBlockSelect", [start, end]);
|
|
},
|
|
_bs_start:function(e){
|
|
this._block_panel = webix.html.create("div", {"class":"webix_block_selection"},"");
|
|
|
|
this._body.appendChild(this._block_panel);
|
|
},
|
|
_bs_move:function(e){
|
|
if (this._bs_ready !== false){
|
|
var pos = webix.html.pos(e);
|
|
var progress = [pos.x - this._bs_position.x, pos.y - this._bs_position.y];
|
|
|
|
//prevent unnecessary block selection while dbl-clicking
|
|
if (Math.abs(this._bs_ready[0] - progress[0]) < 5 && Math.abs(this._bs_ready[1] - progress[1]) < 5)
|
|
return;
|
|
|
|
if (this._bs_progress === false)
|
|
this._bs_start(e);
|
|
|
|
this._bs_progress = progress;
|
|
this._bs_select(this.config.blockselect, false, e);
|
|
}
|
|
},
|
|
_locate_cell_xy:function(x,y){
|
|
var inTopSplit = false,
|
|
row = null,
|
|
column = null;
|
|
|
|
|
|
if (this._right_width && x>this._left_width + this._center_width)
|
|
x+= this._x_scroll.getSize()-this._center_width-this._left_width-this._right_width;
|
|
else if (!this._left_width || x>this._left_width)
|
|
x+= this._x_scroll.getScroll();
|
|
|
|
if(this._settings.topSplit && this._render_scroll_top > this._settings.topSplit) {
|
|
var splitPos = this._cellPosition(this.getIdByIndex(this._settings.topSplit-1), this.columnId(0));
|
|
if(splitPos.top + splitPos.height > y){
|
|
inTopSplit = true;
|
|
}
|
|
}
|
|
if(!inTopSplit)
|
|
y += this.getScrollState().y;
|
|
|
|
if (x<0) x=0;
|
|
if (y<0) y=0;
|
|
|
|
var cols = this._settings.columns;
|
|
var rows = this.data.order;
|
|
|
|
var summ = 0;
|
|
for (var i=0; i<cols.length; i++){
|
|
summ+=cols[i].width;
|
|
if (summ>=x){
|
|
column = cols[i].id;
|
|
break;
|
|
}
|
|
}
|
|
if (!column)
|
|
column = cols[cols.length-1].id;
|
|
|
|
summ = 0;
|
|
|
|
var start = this.data.$min || 0;
|
|
if (this._settings.fixedRowHeight){
|
|
row = rows[start + Math.floor(y/this._settings.rowHeight)];
|
|
} else for (var i=start; i<rows.length; i++){
|
|
summ+=this._getHeightByIndex(i);
|
|
if (summ>=y){
|
|
row = rows[i];
|
|
break;
|
|
}
|
|
}
|
|
if (!row)
|
|
row = rows[rows.length-1];
|
|
|
|
return {row:row, column:column};
|
|
},
|
|
_getTopSplitOffset: function(cell, area){
|
|
var y = 0,
|
|
startIndex = this.getIndexById(cell.row);
|
|
|
|
if(startIndex >= this._settings.topSplit){
|
|
var startPos = this._cellPosition(this.getIdByIndex(startIndex), cell.column);
|
|
var splitPos = this._cellPosition(this.getIdByIndex(this._settings.topSplit-1), cell.column);
|
|
if(splitPos.top + splitPos.height - startPos.top > 0){
|
|
y = splitPos.top + splitPos.height - (startPos.top>0 ||!area?startPos.top:0);
|
|
}
|
|
}
|
|
|
|
return y;
|
|
}
|
|
});
|
|
webix.protoUI({
|
|
name:"resizearea",
|
|
defaults:{
|
|
dir:"x"
|
|
},
|
|
$init:function(config){
|
|
var dir = config.dir||"x";
|
|
var node = webix.toNode(config.container);
|
|
var size = (dir=="x"?"width":"height");
|
|
var margin = (config.margin? config.margin+"px":0);
|
|
|
|
this._key_property = (dir == "x"?"left":"top");
|
|
|
|
this._viewobj = webix.html.create("DIV",{
|
|
"class" : "webix_resize_area webix_dir_"+dir
|
|
});
|
|
//[[COMPAT]] FF12 can produce 2 move events
|
|
webix._event(this._viewobj, webix.env.mouse.down, webix.html.stopEvent);
|
|
|
|
if(margin){
|
|
if(dir=="x")
|
|
margin = margin+" 0 "+margin;
|
|
else
|
|
margin = "0 "+margin+" 0 "+margin;
|
|
}
|
|
this._dragobj = webix.html.create("DIV",{
|
|
"class" : "webix_resize_handle_"+dir,
|
|
"style" : (margin?"padding:"+margin:"")
|
|
},"<div class='webix_handle_content'></div>");
|
|
|
|
this._originobj = webix.html.create("DIV",{
|
|
"class" : "webix_resize_origin_"+dir
|
|
});
|
|
|
|
if(config[size]){
|
|
this._originobj.style[size] = config[size]+(config.border?1:0)+"px";
|
|
this._dragobj.style[size] = config[size]+"px";
|
|
}
|
|
if (config.cursor)
|
|
this._dragobj.style.cursor = this._originobj.style.cursor = this._viewobj.style.cursor = config.cursor;
|
|
this._moveev = webix.event(node, webix.env.mouse.move, this._onmove, {bind:this});
|
|
this._upev = webix.event(document.body, webix.env.mouse.up, this._onup, {bind:this});
|
|
|
|
this._dragobj.style[this._key_property] = this._originobj.style[this._key_property] = config.start+"px";
|
|
|
|
node.appendChild(this._viewobj);
|
|
node.appendChild(this._dragobj);
|
|
node.appendChild(this._originobj);
|
|
},
|
|
_onup:function(){
|
|
|
|
this.callEvent("onResizeEnd", [this._last_result]);
|
|
|
|
webix.eventRemove(this._moveev);
|
|
webix.eventRemove(this._upev);
|
|
|
|
webix.html.remove(this._viewobj);
|
|
webix.html.remove(this._dragobj);
|
|
webix.html.remove(this._originobj);
|
|
this._viewobj = this._dragobj = this._originobj = null;
|
|
},
|
|
_onmove:function(e){
|
|
var pos = webix.html.pos(e);
|
|
this._last_result = (this._settings.dir == "x" ? pos.x : pos.y)+this._settings.start-this._settings.eventPos;
|
|
this._dragobj.style[this._key_property] = this._last_result+"px";
|
|
this.callEvent("onResize", [this._last_result]);
|
|
}
|
|
}, webix.EventSystem, webix.Settings);
|
|
webix.extend(webix.ui.datatable, {
|
|
|
|
resizeRow_setter:function(value){
|
|
this._settings.scrollAlignY = false;
|
|
this._settings.fixedRowHeight = false;
|
|
return this.resizeColumn_setter(value);
|
|
},
|
|
resizeColumn_setter:function(value){
|
|
if (value && this._rs_init_flag){
|
|
webix._event(this._viewobj, "mousemove", this._rs_move, {bind:this});
|
|
webix._event(this._viewobj, "mousedown", this._rs_down, {bind:this});
|
|
webix._event(this._viewobj, "mouseup", this._rs_up, {bind:this});
|
|
this._rs_init_flag = false;
|
|
}
|
|
return value;
|
|
},
|
|
_rs_init_flag:true,
|
|
_rs_down:function(e){
|
|
// do not listen to mousedown of subview on master
|
|
if (this._settings.subview && this != webix.$$(e.target||e.srcElement)) return;
|
|
//if mouse was near border
|
|
if (!this._rs_ready) return;
|
|
this._rs_process = [webix.html.pos(e),this._rs_ready[2]];
|
|
webix.html.addCss(document.body,"webix_noselect");
|
|
webix.html.denySelect();
|
|
},
|
|
_rs_up:function(){
|
|
this._rs_process = false;
|
|
webix.html.removeCss(document.body,"webix_noselect");
|
|
webix.html.allowSelect();
|
|
},
|
|
_rs_start:function(e){
|
|
e = e||event;
|
|
if(this._rs_progress)
|
|
return;
|
|
var dir = this._rs_ready[0];
|
|
var node = this._rs_process[1];
|
|
var obj = this._locate(node);
|
|
if (!obj) return;
|
|
|
|
var eventPos = this._rs_process[0];
|
|
var start;
|
|
|
|
if (dir == "x"){
|
|
start = webix.html.offset(node).x+this._rs_ready[1] - webix.html.offset(this._body).x;
|
|
eventPos = eventPos.x;
|
|
if (!this._rs_ready[1]) obj.cind-=(node.parentNode.colSpan||1);
|
|
} else {
|
|
start = webix.html.offset(node).y+this._rs_ready[1] - webix.html.offset(this._body).y+this._header_height;
|
|
eventPos = eventPos.y;
|
|
if (!this._rs_ready[1]) obj.rind--;
|
|
}
|
|
if (obj.cind>=0 && obj.rind>=0){
|
|
this._rs_progress = [dir, obj, start];
|
|
|
|
var resize = new webix.ui.resizearea({
|
|
container:this._viewobj,
|
|
dir:dir,
|
|
eventPos:eventPos,
|
|
start:start,
|
|
cursor:(dir == "x"?"col":"row")+"-resize"
|
|
});
|
|
resize.attachEvent("onResizeEnd", webix.bind(this._rs_end, this));
|
|
}
|
|
this._rs_down = this._rs_ready = false;
|
|
},
|
|
_rs_end:function(result){
|
|
if (this._rs_progress){
|
|
var dir = this._rs_progress[0];
|
|
var obj = this._rs_progress[1];
|
|
var newsize = result-this._rs_progress[2];
|
|
if (dir == "x"){
|
|
|
|
//in case of right split - different sizing logic applied
|
|
if (this._settings.rightSplit && obj.cind+1>=this._rightSplit &&
|
|
obj.cind !== this._columns.length - 1)
|
|
{
|
|
obj.cind++;
|
|
newsize *= -1;
|
|
}
|
|
|
|
var column = this._columns[obj.cind];
|
|
var oldwidth = column.width;
|
|
delete column.fillspace;
|
|
delete column.adjust;
|
|
this._setColumnWidth(obj.cind, oldwidth + newsize, true, true);
|
|
this._updateColsSizeSettings();
|
|
}
|
|
else {
|
|
var rid = this.getIdByIndex(obj.rind);
|
|
var oldheight = this._getRowHeight(this.getItem(rid));
|
|
this.setRowHeight(rid, oldheight + newsize);
|
|
}
|
|
this._rs_up();
|
|
}
|
|
this._rs_progress = null;
|
|
},
|
|
_rs_move:function(e){
|
|
var cell= null,
|
|
config = this._settings;
|
|
if (this._rs_ready && this._rs_process)
|
|
return this._rs_start(e);
|
|
|
|
e = e||event;
|
|
var node = e.target||e.srcElement;
|
|
var mode = false; //resize ready flag
|
|
|
|
if (node.tagName == "TD" || node.tagName == "TABLE") return ;
|
|
var element_class = node.className||"";
|
|
var in_body = typeof element_class === "string" && element_class.indexOf("webix_cell")!=-1;
|
|
//ignore resize in case of drag-n-drop enabled
|
|
if (in_body && config.drag) return;
|
|
var in_header = typeof element_class === "string" && element_class.indexOf("webix_hcell")!=-1;
|
|
this._rs_ready = false;
|
|
|
|
if (in_body || in_header){
|
|
var dx = node.offsetWidth;
|
|
var dy = node.offsetHeight;
|
|
var pos = webix.html.posRelative(e);
|
|
|
|
var resizeRow = config.resizeRow;
|
|
// if resize is only within the first column
|
|
if(typeof resizeRow == "object" && resizeRow.headerOnly){
|
|
cell = this._locate(node);
|
|
if(cell.cind >0)
|
|
resizeRow = false;
|
|
}
|
|
|
|
if (in_body && resizeRow){
|
|
resizeRow = (typeof resizeRow == "object" && resizeRow.size?resizeRow.size:3);
|
|
if (pos.y<resizeRow){
|
|
if(!cell)
|
|
cell = this._locate(node);
|
|
// avoid resize header border
|
|
if(cell.rind){
|
|
this._rs_ready = ["y", 0, node];
|
|
mode = "row-resize";
|
|
}
|
|
} else if (dy-pos.y<resizeRow+1){
|
|
this._rs_ready = ["y", dy, node];
|
|
mode = "row-resize";
|
|
}
|
|
}
|
|
|
|
var resizeColumn = config.resizeColumn;
|
|
// if resize is only within the header
|
|
if(typeof resizeColumn == "object" && resizeColumn.headerOnly && in_body)
|
|
resizeColumn = false;
|
|
|
|
if (resizeColumn){
|
|
resizeColumn = (typeof resizeColumn == "object" && resizeColumn.size?resizeColumn.size:3);
|
|
|
|
if (pos.x<resizeColumn){
|
|
this._rs_ready = ["x", 0, node];
|
|
mode = "col-resize";
|
|
} else if (dx-pos.x<resizeColumn+1){
|
|
this._rs_ready = ["x", dx, node];
|
|
mode = "col-resize";
|
|
}
|
|
}
|
|
}
|
|
|
|
//mark or unmark resizing ready state
|
|
if (this._cursor_timer) window.clearTimeout(this._cursor_timer);
|
|
this._cursor_timer = webix.delay(this._mark_resize_ready, this, [mode], mode?100:0);
|
|
},
|
|
|
|
_mark_resize_ready:function(mode){
|
|
if (this._last_cursor_mode != mode){
|
|
this._last_cursor_mode = mode;
|
|
this._viewobj.style.cursor=mode||"default";
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
webix.extend(webix.ui.datatable,webix.PagingAbility);
|
|
|
|
webix.csv = {
|
|
escape:true,
|
|
delimiter:{
|
|
rows: "\n",
|
|
cols: "\t"
|
|
},
|
|
parse:function(text, sep){
|
|
sep = sep||this.delimiter;
|
|
if (!this.escape)
|
|
return this._split_clip_data(text, sep);
|
|
|
|
var lines = text.replace(/\n$/,"").split(sep.rows);
|
|
|
|
var i = 0;
|
|
while (i < lines.length - 1) {
|
|
if (this._substr_count(lines[i], '"') % 2 === 1) {
|
|
lines[i] += sep.rows + lines[i + 1];
|
|
delete lines[i + 1];
|
|
i++;
|
|
}
|
|
i++;
|
|
}
|
|
var csv = [];
|
|
for (i = 0; i < lines.length; i++) {
|
|
if (typeof(lines[i]) !== 'undefined') {
|
|
var line = lines[i].split(sep.cols);
|
|
for (var j = 0; j < line.length; j++) {
|
|
if (line[j].indexOf('"') === 0)
|
|
line[j] = line[j].substr(1, line[j].length - 2);
|
|
line[j] = line[j].replace('""', '"');
|
|
}
|
|
csv.push(line);
|
|
}
|
|
}
|
|
return csv;
|
|
},
|
|
_split_clip_data: function(text, sep) {
|
|
var lines = text.split(sep.rows);
|
|
for (var i = 0; i < lines.length; i++) {
|
|
lines[i] = lines[i].split(sep.cols);
|
|
}
|
|
return lines;
|
|
},
|
|
/*! counts how many occurances substring in string **/
|
|
_substr_count: function(string, substring) {
|
|
var arr = string.split(substring);
|
|
return arr.length - 1;
|
|
},
|
|
stringify:function(data, sep){
|
|
sep = sep||this.delimiter;
|
|
|
|
if (!this.escape){
|
|
for (var i = 0; i < data.length; i++)
|
|
data[i] = data[i].join(sep.cols);
|
|
return data.join(sep.rows);
|
|
}
|
|
|
|
var reg = /\n|\"|;|,/;
|
|
for (var i = 0; i < data.length; i++) {
|
|
for (var j = 0; j < data[i].length; j++) {
|
|
if (reg.test(data[i][j])) {
|
|
data[i][j] = data[i][j].replace(/"/g, '""');
|
|
data[i][j] = '"' + data[i][j] + '"';
|
|
}
|
|
}
|
|
data[i] = data[i].join(sep.cols);
|
|
}
|
|
data = data.join(sep.rows);
|
|
return data;
|
|
}
|
|
};
|
|
|
|
webix.TablePaste = {
|
|
clipboard_setter:function(value){
|
|
if (value === true || value === 1) this._settings.clipboard = 'block';
|
|
webix.clipbuffer.init();
|
|
this.attachEvent("onSelectChange",this._sel_to_clip);
|
|
// solution for clicks on selected items
|
|
this.attachEvent("onItemClick",function(id,e,node){
|
|
if(document.activeElement && this.$view.contains(document.activeElement)){
|
|
webix.clipbuffer.focus();
|
|
webix.UIManager.setFocus(this);
|
|
}
|
|
});
|
|
this.attachEvent("onPaste", this._clip_to_sel);
|
|
|
|
return value;
|
|
},
|
|
templateCopy_setter: webix.template,
|
|
_sel_to_clip: function() {
|
|
if (!this.getEditor || !this.getEditor()){
|
|
var data = this._get_sel_text();
|
|
webix.clipbuffer.set(data);
|
|
webix.UIManager.setFocus(this);
|
|
}
|
|
},
|
|
|
|
_get_sel_text: function() {
|
|
var data = [];
|
|
var filter = this._settings.templateCopy;
|
|
this.mapSelection(function(value, row, col, row_ind, col_ind) {
|
|
if (!data[row_ind]) data[row_ind] = [];
|
|
var newvalue = filter ? filter(value, row, col) : value;
|
|
data[row_ind].push(newvalue);
|
|
return value;
|
|
});
|
|
|
|
return webix.csv.stringify(data, this._settings.delimiter);
|
|
},
|
|
|
|
_clip_to_sel: function(text) {
|
|
if (!webix.isUndefined(this._paste[this._settings.clipboard])) {
|
|
var data = webix.csv.parse(text, this._settings.delimiter);
|
|
this._paste[this._settings.clipboard].call(this, data);
|
|
}
|
|
},
|
|
|
|
_paste: {
|
|
block: function(data) {
|
|
var leftTop = this.mapSelection(null);
|
|
if (!leftTop) return;
|
|
|
|
// filling cells with data
|
|
this.mapCells(leftTop.row, leftTop.column, data.length, null, function(value, row, col, row_ind, col_ind) {
|
|
if (data[row_ind] && data[row_ind].length>col_ind) {
|
|
return data[row_ind][col_ind];
|
|
}
|
|
return value;
|
|
});
|
|
this.render();
|
|
},
|
|
|
|
selection: function(data) {
|
|
this.mapSelection(function(value, row, col, row_ind, col_ind) {
|
|
if (data[row_ind] && data[row_ind].length>col_ind)
|
|
return data[row_ind][col_ind];
|
|
return value;
|
|
});
|
|
this.render();
|
|
},
|
|
|
|
repeat: function(data) {
|
|
this.mapSelection(function(value, row, col, row_ind, col_ind) {
|
|
row = data[row_ind%data.length];
|
|
value = row[col_ind%row.length];
|
|
return value;
|
|
});
|
|
this.render();
|
|
},
|
|
|
|
custom: function(text) {}
|
|
}
|
|
};
|
|
|
|
webix.extend(webix.ui.datatable, webix.TablePaste);
|
|
if(!webix.storage)
|
|
webix.storage = {};
|
|
|
|
webix.storage.local = {
|
|
put:function(name, data){
|
|
if(name && window.JSON && window.localStorage){
|
|
window.localStorage.setItem(name, webix.stringify(data));
|
|
}
|
|
},
|
|
get:function(name){
|
|
if(name && window.JSON && window.localStorage){
|
|
var json = window.localStorage.getItem(name);
|
|
if(!json)
|
|
return null;
|
|
return webix.DataDriver.json.toObject(json);
|
|
}else
|
|
return null;
|
|
},
|
|
remove:function(name){
|
|
if(name && window.JSON && window.localStorage){
|
|
window.localStorage.removeItem(name);
|
|
}
|
|
},
|
|
clear:function(){
|
|
window.localStorage.clear();
|
|
}
|
|
};
|
|
|
|
webix.storage.session = {
|
|
put:function(name, data){
|
|
if(name && window.JSON && window.sessionStorage){
|
|
window.sessionStorage.setItem(name, webix.stringify(data));
|
|
}
|
|
},
|
|
get:function(name){
|
|
if(name && window.JSON && window.sessionStorage){
|
|
var json = window.sessionStorage.getItem(name);
|
|
if(!json)
|
|
return null;
|
|
return webix.DataDriver.json.toObject(json);
|
|
}else
|
|
return null;
|
|
},
|
|
remove:function(name){
|
|
if(name && window.JSON && window.sessionStorage){
|
|
window.sessionStorage.removeItem(name);
|
|
}
|
|
},
|
|
clear:function(){
|
|
window.sessionStorage.clear();
|
|
}
|
|
};
|
|
|
|
webix.storage.cookie = {
|
|
put:function(name, data, domain, expires ){
|
|
if(name && window.JSON){
|
|
document.cookie = name + "=" + escape(webix.stringify(data)) +
|
|
(( expires && (expires instanceof Date)) ? ";expires=" + expires.toUTCString() : "" ) +
|
|
(( domain ) ? ";domain=" + domain : "" ) +
|
|
(( webix.env.https ) ? ";secure" : "");
|
|
}
|
|
},
|
|
getRaw:function(check_name){
|
|
// first we'll split this cookie up into name/value pairs
|
|
// note: document.cookie only returns name=value, not the other components
|
|
var a_all_cookies = document.cookie.split( ';' );
|
|
var a_temp_cookie = '';
|
|
var cookie_name = '';
|
|
var cookie_value = '';
|
|
var b_cookie_found = false; // set boolean t/f default f
|
|
|
|
for (var i = 0; i < a_all_cookies.length; i++ ){
|
|
// now we'll split apart each name=value pair
|
|
a_temp_cookie = a_all_cookies[i].split( '=' );
|
|
|
|
// and trim left/right whitespace while we're at it
|
|
cookie_name = a_temp_cookie[0].replace(/^\s+|\s+$/g, '');
|
|
|
|
// if the extracted name matches passed check_name
|
|
if (cookie_name == check_name ){
|
|
b_cookie_found = true;
|
|
// we need to handle case where cookie has no value but exists (no = sign, that is):
|
|
if ( a_temp_cookie.length > 1 ){
|
|
cookie_value = unescape( a_temp_cookie[1].replace(/^\s+|\s+$/g, '') );
|
|
}
|
|
// note that in cases where cookie is initialized but no value, null is returned
|
|
return cookie_value;
|
|
}
|
|
a_temp_cookie = null;
|
|
cookie_name = '';
|
|
}
|
|
if ( !b_cookie_found ){
|
|
return null;
|
|
}
|
|
return null;
|
|
},
|
|
get:function(name){
|
|
if(name && window.JSON){
|
|
var json = this.getRaw(name);
|
|
if(!json)
|
|
return null;
|
|
return webix.DataDriver.json.toObject(unescape(json));
|
|
}else
|
|
return null;
|
|
},
|
|
remove:function(name, domain){
|
|
if(name && this.getRaw(name))
|
|
document.cookie = name + "=" + (( domain ) ? ";domain=" + domain : "") + ";expires=Thu, 01-Jan-1970 00:00:01 GMT";
|
|
},
|
|
clear:function(domain){
|
|
var cookies = document.cookie.split(";");
|
|
for (var i = 0; i < cookies.length; i++)
|
|
document.cookie = /^[^=]+/.exec(cookies[i])[0] + "=" + (( domain ) ? ";domain=" + domain : "") + ";expires=Thu, 01-Jan-1970 00:00:01 GMT";
|
|
}
|
|
};
|
|
|
|
webix.DataState = {
|
|
getState:function(){
|
|
var cols_n = this.config.columns.length;
|
|
var columns = this.config.columns;
|
|
var settings = {
|
|
ids:[],
|
|
size:[],
|
|
select:this.getSelectedId(true),
|
|
scroll:this.getScrollState()
|
|
};
|
|
for(var i = 0; i < cols_n; i++){
|
|
var col = columns[i];
|
|
settings.ids.push(col.id);
|
|
settings.size.push((col.fillspace || col.adjust) ? -1 : col.width);
|
|
}
|
|
|
|
settings.order = [].concat(this._hidden_column_order.length ? this._hidden_column_order : settings.ids);
|
|
|
|
if(this._last_sorted){
|
|
settings.sort={
|
|
id:this._last_sorted,
|
|
dir:this._last_order
|
|
};
|
|
}
|
|
|
|
//this method will try to access the rendered values
|
|
//just ignore it if grid is not rendered yet
|
|
if (this._filter_elements && this._dtable_fully_ready) {
|
|
var filter = {};
|
|
var any_filter = 0;
|
|
for (var key in this._filter_elements) {
|
|
if (this._hidden_column_hash[key]) continue;
|
|
|
|
var f = this._filter_elements[key];
|
|
f[1].value = filter[key] = f[2].getValue(f[0]);
|
|
any_filter = 1;
|
|
}
|
|
if (any_filter)
|
|
settings.filter=filter;
|
|
}
|
|
|
|
settings.hidden = [];
|
|
for (var key in this._hidden_column_hash)
|
|
settings.hidden.push(key);
|
|
|
|
return settings;
|
|
},
|
|
setState:function(obj){
|
|
var columns = this.config.columns;
|
|
if(!obj) return;
|
|
|
|
this._last_sorted = null;
|
|
this.blockEvent();
|
|
|
|
if (obj.hidden){
|
|
var hihash = {};
|
|
for (var i=0; i<obj.hidden.length; i++){
|
|
hihash[obj.hidden[i]] = true;
|
|
if(!this._hidden_column_order.length)
|
|
this.hideColumn(obj.hidden[i]);
|
|
}
|
|
|
|
if(this._hidden_column_order.length){
|
|
for (var i=0; i<this._hidden_column_order.length; i++){
|
|
var hikey = this._hidden_column_order[i];
|
|
if (!!hihash[hikey] == !this._hidden_column_hash[hikey])
|
|
this.hideColumn(hikey, {}, false, !!hihash[hikey]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (obj.ids){
|
|
var reorder = false;
|
|
var cols = this.config.columns;
|
|
for (var i=0; i<cols.length; i++)
|
|
if (cols[i].id != obj.ids[i])
|
|
reorder = true;
|
|
if (reorder){
|
|
for (var i=0; i<obj.ids.length; i++)
|
|
cols[i] = this.getColumnConfig(obj.ids[i]) || cols[i];
|
|
this.refreshColumns();
|
|
}
|
|
}
|
|
|
|
if (obj.size){
|
|
var cols_n = Math.min(obj.size.length, columns.length);
|
|
for(var i = 0; i < cols_n; i++){
|
|
var col = columns[i];
|
|
if(col && obj.size[i] > 0 && col.width != obj.size[i]){
|
|
delete col.fillspace;
|
|
delete col.adjust;
|
|
this._setColumnWidth( i, obj.size[i], true);
|
|
}
|
|
}
|
|
}
|
|
|
|
this.unblockEvent();
|
|
|
|
var silent = !(this._settings.leftSplit || this._settings.rightSplit);
|
|
this._updateColsSizeSettings(silent);
|
|
this.callEvent("onStructureUpdate", []);
|
|
|
|
if(obj.sort){
|
|
var column = columns[this.getColumnIndex(obj.sort.id)];
|
|
if (column)
|
|
this._sort(obj.sort.id, obj.sort.dir, column.sort);
|
|
}
|
|
|
|
if (obj.filter){
|
|
//temporary disable filtering
|
|
var temp = this.filterByAll;
|
|
this.filterByAll = function(){};
|
|
|
|
//apply defined filters
|
|
for (var key in obj.filter) {
|
|
var value = obj.filter[key];
|
|
if (!value) continue;
|
|
|
|
if (!this._filter_elements[key]) continue;
|
|
var f = this._filter_elements[key];
|
|
f[2].setValue(f[0], value);
|
|
var contentid = f[1].contentId;
|
|
if (contentid)
|
|
this._active_headers[contentid].value = value;
|
|
}
|
|
|
|
//remove old filters
|
|
for (var key in this._filter_elements){
|
|
if (!obj.filter[key]){
|
|
var f = this._filter_elements[key];
|
|
f[2].setValue(f[0], "");
|
|
}
|
|
}
|
|
|
|
//restore and apply filtering
|
|
this.filterByAll = temp;
|
|
this.filterByAll();
|
|
}
|
|
|
|
if (obj.select && this.select){
|
|
var select = obj.select;
|
|
this.unselect();
|
|
for (var i = 0; i < select.length; i++)
|
|
if (!select[i].row || this.exists(select[i].row))
|
|
this._select(select[i], true);
|
|
}
|
|
|
|
if(obj.scroll)
|
|
this.scrollTo(obj.scroll.x, obj.scroll.y);
|
|
}
|
|
};
|
|
|
|
webix.extend(webix.ui.datatable, webix.DataState);
|
|
|
|
|
|
(function(){
|
|
var t = webix.Touch = {
|
|
config:{
|
|
longTouchDelay:1000,
|
|
scrollDelay:150,
|
|
gravity:500,
|
|
deltaStep:30,
|
|
speed:"0ms",
|
|
finish:1500,
|
|
ellastic:true
|
|
},
|
|
limit:function(value){
|
|
t._limited = value !== false;
|
|
},
|
|
disable:function(){
|
|
t._disabled = true;
|
|
},
|
|
enable:function(){
|
|
t._disabled = false;
|
|
},
|
|
$init:function(){
|
|
t.$init = function(){};
|
|
|
|
webix.event(document.body, mouse.down, t._touchstart);
|
|
webix.event(document.body, mouse.move, t._touchmove);
|
|
webix.event(document.body, mouse.up, t._touchend);
|
|
|
|
webix.event(document.body,"dragstart",function(e){
|
|
return webix.html.preventEvent(e);
|
|
});
|
|
webix.event(document.body,"touchstart",function(e){
|
|
if (t._disabled || t._limited) return;
|
|
//fast click mode for iOS
|
|
//To have working form elements Android must not block event - so there are no fast clicks for Android
|
|
//Selects still don't work with fast clicks
|
|
if (webix.env.isSafari) {
|
|
var tag = e.srcElement.tagName.toLowerCase();
|
|
if (tag == "input" || tag == "textarea" || tag == "select" || tag=="label")
|
|
return true;
|
|
|
|
t._fire_fast_event = true;
|
|
return webix.html.preventEvent(e);
|
|
}
|
|
});
|
|
|
|
t._clear_artefacts();
|
|
t._scroll = [null, null];
|
|
t.$active = true;
|
|
},
|
|
_clear_artefacts:function(){
|
|
t._start_context = t._current_context = t._prev_context = t._scroll_context = null;
|
|
t._scroll_mode = t._scroll_node = t._scroll_stat = this._long_touched = null;
|
|
//webix.html.remove(t._scroll);
|
|
//t._scroll = [null, null];
|
|
t._delta = { _x_moment:0, _y_moment:0, _time:0 };
|
|
|
|
if (t._css_button_remove){
|
|
webix.html.removeCss(t._css_button_remove,"webix_touch");
|
|
t._css_button_remove = null;
|
|
}
|
|
|
|
window.clearTimeout(t._long_touch_timer);
|
|
t._was_not_moved = true;
|
|
t._axis_x = true;
|
|
t._axis_y = true;
|
|
if (!t._active_transion)
|
|
t._scroll_end();
|
|
},
|
|
_touchend:function(e){
|
|
if (t._start_context) {
|
|
if (!t._scroll_mode) {
|
|
if (!this._long_touched) {
|
|
if (t._axis_y && !t._axis_x) {
|
|
t._translate_event("onSwipeX");
|
|
} else if (t._axis_x && !t._axis_y) {
|
|
t._translate_event("onSwipeY");
|
|
} else {
|
|
if (webix.env.isSafari && t._fire_fast_event) { //need to test for mobile ff and blackbery
|
|
t._fire_fast_event = false;
|
|
var target = t._start_context.target;
|
|
|
|
//dark iOS magic, without delay it can skip repainting
|
|
webix.delay(function () {
|
|
var click_event = document.createEvent('MouseEvents');
|
|
click_event.initEvent('click', true, true);
|
|
target.dispatchEvent(click_event);
|
|
});
|
|
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
|
|
|
|
var temp = t._get_matrix(t._scroll_node);
|
|
var x = temp.e;
|
|
var y = temp.f;
|
|
var finish = t.config.finish;
|
|
|
|
var delta = t._get_delta(e, true);
|
|
var view = webix.$$(t._scroll_node);
|
|
|
|
var gravity = (view && view.$scroll ? view.$scroll.gravity : t.config.gravity);
|
|
if (delta._time) {
|
|
var nx = x + gravity * delta._x_moment / delta._time;
|
|
var ny = y + gravity * delta._y_moment / delta._time;
|
|
|
|
var cnx = t._scroll[0] ? t._correct_minmax(nx, false, false, t._scroll_stat.dx, t._scroll_stat.px) : x;
|
|
var cny = t._scroll[1] ? t._correct_minmax(ny, false, false, t._scroll_stat.dy, t._scroll_stat.py) : y;
|
|
|
|
|
|
var size = Math.max(Math.abs(cnx - x), Math.abs(cny - y));
|
|
if (size < 150)
|
|
finish = finish * size / 150;
|
|
|
|
if (cnx != x || cny != y)
|
|
finish = Math.round(finish * Math.max((cnx - x) / (nx - x), (cny - y) / (ny - y)));
|
|
|
|
var result = {e: cnx, f: cny};
|
|
|
|
|
|
var view = webix.$$(t._scroll_node);
|
|
if (view && view.adjustScroll)
|
|
view.adjustScroll(result);
|
|
|
|
|
|
//finish = Math.max(100,(t._fast_correction?100:finish));
|
|
finish = Math.max(100, finish);
|
|
|
|
|
|
if (x != result.e || y != result.f) {
|
|
t._set_matrix(t._scroll_node, result.e, result.f, finish + "ms");
|
|
if (t._scroll_master)
|
|
t._scroll_master._sync_scroll(result.e, result.f, finish + "ms");
|
|
t._set_scroll(result.e, result.f, finish + "ms");
|
|
} else {
|
|
t._scroll_end();
|
|
}
|
|
} else
|
|
t._scroll_end();
|
|
}
|
|
t._translate_event("onTouchEnd");
|
|
t._clear_artefacts();
|
|
}
|
|
},
|
|
_touchmove:function(e){
|
|
if (!t._scroll_context || !t._start_context) return;
|
|
|
|
var delta = t._get_delta(e);
|
|
t._translate_event("onTouchMove");
|
|
|
|
if (t._scroll_mode){
|
|
t._set_scroll_pos(delta);
|
|
} else {
|
|
t._axis_x = t._axis_check(delta._x, "x", t._axis_x);
|
|
t._axis_y = t._axis_check(delta._y, "y", t._axis_y);
|
|
if (t._scroll_mode){
|
|
var view = t._get_event_view("onBeforeScroll", true);
|
|
if (view){
|
|
var data = {};
|
|
view.callEvent("onBeforeScroll",[data]);
|
|
if (data.update){
|
|
t.config.speed = data.speed;
|
|
t.config.scale = data.scale;
|
|
}
|
|
}
|
|
t._init_scroller(delta); //apply scrolling
|
|
}
|
|
}
|
|
|
|
return webix.html.preventEvent(e);
|
|
},
|
|
_set_scroll_pos:function(){
|
|
if (!t._scroll_node) return;
|
|
var temp = t._get_matrix(t._scroll_node);
|
|
var be = temp.e, bf = temp.f;
|
|
var prev = t._prev_context || t._start_context;
|
|
|
|
var view = webix.$$(t._scroll_node);
|
|
var ellastic = (view&&view.$scroll)?view.$scroll.ellastic: t.config.ellastic;
|
|
if (t._scroll[0])
|
|
temp.e = t._correct_minmax( temp.e - prev.x + t._current_context.x , ellastic, temp.e, t._scroll_stat.dx, t._scroll_stat.px);
|
|
if (t._scroll[1])
|
|
temp.f = t._correct_minmax( temp.f - prev.y + t._current_context.y , ellastic, temp.f, t._scroll_stat.dy, t._scroll_stat.py);
|
|
|
|
t._set_matrix(t._scroll_node, temp.e, temp.f, "0ms");
|
|
if (t._scroll_master)
|
|
t._scroll_master._sync_scroll(temp.e, temp.f, "0ms");
|
|
t._set_scroll(temp.e, temp.f, "0ms");
|
|
},
|
|
_set_scroll:function(dx, dy, speed){
|
|
|
|
var edx = t._scroll_stat.px/t._scroll_stat.dx * -dx;
|
|
var edy = t._scroll_stat.py/t._scroll_stat.dy * -dy;
|
|
if (t._scroll[0])
|
|
t._set_matrix(t._scroll[0], edx, 0 ,speed);
|
|
if (t._scroll[1])
|
|
t._set_matrix(t._scroll[1], 0, edy ,speed);
|
|
},
|
|
scrollTo:function(node, x, y, speed){
|
|
t._set_matrix(node,x,y,speed);
|
|
},
|
|
_set_matrix:function(node, xv, yv, speed){
|
|
if(!t._in_anim_frame && window.setAnimationFrame){
|
|
window.setAnimationFrame(function(){
|
|
t._in_anim_frame = true;
|
|
return t._set_matrix(node, xv, yv, speed);
|
|
});
|
|
}
|
|
t._in_anim_frame = null;
|
|
t._active_transion = true;
|
|
if (node){
|
|
var trans = t.config.translate || webix.env.translate;
|
|
node.style[webix.env.transform] = trans+"("+Math.round(xv)+"px, "+Math.round(yv)+"px"+((trans=="translate3d")?", 0":"")+")";
|
|
node.style[webix.env.transitionDuration] = speed;
|
|
}
|
|
},
|
|
_get_matrix:function(node){
|
|
var matrix = window.getComputedStyle(node)[webix.env.transform];
|
|
var tmatrix;
|
|
|
|
if (matrix == "none")
|
|
tmatrix = {e:0, f:0};
|
|
else {
|
|
if(window.WebKitCSSMatrix)
|
|
tmatrix = new WebKitCSSMatrix(matrix);
|
|
else if (window.MSCSSMatrix)
|
|
tmatrix = new MSCSSMatrix(matrix);
|
|
else {
|
|
// matrix(1, 0, 0, 1, 0, 0) --> 1, 0, 0, 1, 0, 0
|
|
var _tmatrix = matrix.replace(/(matrix\()(.*)(\))/gi, "$2");
|
|
// 1, 0, 0, 1, 0, 0 --> 1,0,0,1,0,0
|
|
_tmatrix = _tmatrix.replace(/\s/gi, "");
|
|
_tmatrix = _tmatrix.split(',');
|
|
|
|
var tmatrix = {};
|
|
var tkey = ['a', 'b', 'c', 'd', 'e', 'f'];
|
|
for(var i=0; i<tkey.length; i++){
|
|
tmatrix[tkey[i]] = parseInt(_tmatrix[i], 10);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (t._scroll_master)
|
|
t._scroll_master._sync_pos(tmatrix);
|
|
|
|
return tmatrix;
|
|
},
|
|
_correct_minmax:function(value, allow, current, dx, px){
|
|
if (value === current) return value;
|
|
|
|
var delta = Math.abs(value-current);
|
|
var sign = delta/(value-current);
|
|
// t._fast_correction = true;
|
|
|
|
|
|
if (value>0) return allow?(current + sign*Math.sqrt(delta)):0;
|
|
|
|
var max = dx - px;
|
|
if (max + value < 0)
|
|
return allow?(current - Math.sqrt(-(value-current))):-max;
|
|
|
|
// t._fast_correction = false;
|
|
return value;
|
|
},
|
|
_init_scroll_node:function(node){
|
|
if (!node.scroll_enabled){
|
|
node.scroll_enabled = true;
|
|
node.parentNode.style.position="relative";
|
|
var prefix = webix.env.cssPrefix;
|
|
node.style.cssText += prefix+"transition: "+prefix+"transform; "+prefix+"user-select:none; "+prefix+"transform-style:flat;";
|
|
node.addEventListener(webix.env.transitionEnd,t._scroll_end,false);
|
|
}
|
|
},
|
|
_init_scroller:function(delta){
|
|
if (t._scroll_mode.indexOf("x") != -1)
|
|
t._scroll[0] = t._create_scroll("x", t._scroll_stat.dx, t._scroll_stat.px, "width");
|
|
if (t._scroll_mode.indexOf("y") != -1)
|
|
t._scroll[1] = t._create_scroll("y", t._scroll_stat.dy, t._scroll_stat.py, "height");
|
|
|
|
t._init_scroll_node(t._scroll_node);
|
|
window.setTimeout(t._set_scroll_pos,1);
|
|
},
|
|
_create_scroll:function(mode, dy, py, dim){
|
|
if (dy - py <2){
|
|
var matrix = t._get_matrix(t._scroll_node);
|
|
var e = (mode=="y"?matrix.e:0);
|
|
var f = (mode=="y"?0:matrix.f);
|
|
if (!t._scroll_master)
|
|
t._set_matrix(t._scroll_node, e, f, "0ms");
|
|
t._scroll_mode = t._scroll_mode.replace(mode,"");
|
|
return "";
|
|
}
|
|
|
|
var scroll = webix.html.create("DIV", {
|
|
"class":"webix_scroll_"+mode
|
|
},"");
|
|
|
|
scroll.style[dim] = Math.max((py*py/dy-7),10) +"px";
|
|
if (t._scroll_stat.left)
|
|
if (mode === "x")
|
|
scroll.style.left = t._scroll_stat.left+"px";
|
|
else
|
|
scroll.style.right = (-t._scroll_stat.left)+"px";
|
|
if (t._scroll_stat.hidden)
|
|
scroll.style.visibility = "hidden";
|
|
|
|
t._scroll_node.parentNode.appendChild(scroll);
|
|
|
|
return scroll;
|
|
},
|
|
_axis_check:function(value, mode, old){
|
|
if (value > t.config.deltaStep){
|
|
if (t._was_not_moved){
|
|
t._long_move(mode);
|
|
t._locate(mode);
|
|
if ((t._scroll_mode||"").indexOf(mode) == -1) t._scroll_mode = "";
|
|
}
|
|
return false;
|
|
}
|
|
return old;
|
|
},
|
|
_scroll_end:function(){
|
|
//sending event to the owner of the scroll only
|
|
var result,state,view;
|
|
view = webix.$$(t._scroll_node||this);
|
|
if (view){
|
|
if (t._scroll_node)
|
|
result = t._get_matrix(t._scroll_node);
|
|
else if(view.getScrollState){
|
|
state = view.getScrollState();
|
|
result = {e:state.x, f:state.y};
|
|
}
|
|
webix.callEvent("onAfterScroll", [result]);
|
|
if (view.callEvent)
|
|
view.callEvent("onAfterScroll",[result]);
|
|
}
|
|
if (!t._scroll_mode){
|
|
webix.html.remove(t._scroll);
|
|
t._scroll = [null, null];
|
|
}
|
|
t._active_transion = false;
|
|
},
|
|
_long_move:function(mode){
|
|
window.clearTimeout(t._long_touch_timer);
|
|
t._was_not_moved = false;
|
|
},
|
|
_stop_old_scroll:function(e){
|
|
if (t._scroll[0] || t._scroll[1]){
|
|
t._stop_scroll(e, t._scroll[0]?"x":"y");
|
|
}else
|
|
return true;
|
|
},
|
|
_touchstart :function(e){
|
|
var target = e.target || event.srcElement;
|
|
|
|
|
|
if (t._disabled || (target.tagName&&target.tagName.toLowerCase() == "textarea" && target.offsetHeight<target.scrollHeight)) return;
|
|
t._long_touched = null;
|
|
t._scroll_context = t._start_context = mouse.context(e);
|
|
|
|
// in "limited" mode we should have possibility to use slider
|
|
var element = webix.$$(e);
|
|
|
|
if (t._limited && !t._is_scroll() && !(element && element.$touchCapture)){
|
|
t._scroll_context = null;
|
|
}
|
|
|
|
|
|
|
|
t._translate_event("onTouchStart");
|
|
|
|
if (t._stop_old_scroll(e))
|
|
t._long_touch_timer = window.setTimeout(t._long_touch, t.config.longTouchDelay);
|
|
|
|
if (element && element.touchable && (!target.className || target.className.indexOf("webix_view")!==0)){
|
|
t._css_button_remove = element.getNode(e);
|
|
webix.html.addCss(t._css_button_remove,"webix_touch");
|
|
}
|
|
|
|
},
|
|
_long_touch:function(e){
|
|
if(t._start_context){
|
|
t._translate_event("onLongTouch");
|
|
webix.callEvent("onClick", [t._start_context]);
|
|
t._long_touched = true;
|
|
//t._clear_artefacts();
|
|
}
|
|
},
|
|
_stop_scroll:function(e, stop_mode){
|
|
t._locate(stop_mode);
|
|
var scroll = t._scroll[0]||t._scroll[1];
|
|
if (scroll){
|
|
var view = t._get_event_view("onBeforeScroll", true);
|
|
if (view)
|
|
view.callEvent("onBeforeScroll", [t._start_context,t._current_context]);
|
|
}
|
|
if (scroll && (!t._scroll_node || scroll.parentNode != t._scroll_node.parentNode)){
|
|
t._clear_artefacts();
|
|
t._scroll_end();
|
|
t._start_context = mouse.context(e);
|
|
}
|
|
t._touchmove(e);
|
|
},
|
|
_get_delta:function(e, ch){
|
|
t._prev_context = t._current_context;
|
|
t._current_context = mouse.context(e);
|
|
|
|
t._delta._x = Math.abs(t._start_context.x - t._current_context.x);
|
|
t._delta._y = Math.abs(t._start_context.y - t._current_context.y);
|
|
|
|
if (t._prev_context){
|
|
if (t._current_context.time - t._prev_context.time < t.config.scrollDelay){
|
|
t._delta._x_moment = t._delta._x_moment/1.3+t._current_context.x - t._prev_context.x;
|
|
t._delta._y_moment = t._delta._y_moment/1.3+t._current_context.y - t._prev_context.y;
|
|
}
|
|
else {
|
|
t._delta._y_moment = t._delta._x_moment = 0;
|
|
}
|
|
t._delta._time = t._delta._time/1.3+(t._current_context.time - t._prev_context.time);
|
|
}
|
|
|
|
return t._delta;
|
|
},
|
|
_get_sizes:function(node){
|
|
t._scroll_stat = {
|
|
dx:node.offsetWidth,
|
|
dy:node.offsetHeight,
|
|
px:node.parentNode.offsetWidth,
|
|
py:node.parentNode.offsetHeight
|
|
};
|
|
},
|
|
_is_scroll:function(locate_mode){
|
|
var node = t._start_context.target;
|
|
if (!webix.env.touch && !webix.env.transition && !webix.env.transform) return null;
|
|
while(node && node.tagName!="BODY"){
|
|
if(node.getAttribute){
|
|
var mode = node.getAttribute("touch_scroll");
|
|
if (mode && (!locate_mode || mode.indexOf(locate_mode)!=-1))
|
|
return [node, mode];
|
|
}
|
|
node = node.parentNode;
|
|
}
|
|
return null;
|
|
},
|
|
_locate:function(locate_mode){
|
|
var state = this._is_scroll(locate_mode);
|
|
if (state){
|
|
t._scroll_mode = state[1];
|
|
t._scroll_node = state[0];
|
|
t._get_sizes(state[0]);
|
|
}
|
|
return state;
|
|
},
|
|
_translate_event:function(name){
|
|
webix.callEvent(name, [t._start_context,t._current_context]);
|
|
var view = t._get_event_view(name);
|
|
if (view)
|
|
view.callEvent(name, [t._start_context,t._current_context]);
|
|
},
|
|
_get_event_view:function(name, active){
|
|
var view = webix.$$(active ? t._scroll_node : t._start_context);
|
|
if(!view) return null;
|
|
|
|
while (view){
|
|
if (view.hasEvent&&view.hasEvent(name))
|
|
return view;
|
|
view = view.getParentView();
|
|
}
|
|
|
|
return null;
|
|
},
|
|
_get_context:function(e){
|
|
if (!e.touches[0]) {
|
|
var temp = t._current_context;
|
|
temp.time = new Date();
|
|
return temp;
|
|
}
|
|
|
|
return {
|
|
target:e.target,
|
|
x:e.touches[0].pageX,
|
|
y:e.touches[0].pageY,
|
|
time:new Date()
|
|
};
|
|
},
|
|
_get_context_m:function(e){
|
|
return {
|
|
target:e.target || e.srcElement,
|
|
x:e.pageX,
|
|
y:e.pageY,
|
|
time:new Date()
|
|
};
|
|
}
|
|
};
|
|
|
|
|
|
function touchInit(){
|
|
if (webix.env.touch){
|
|
t.$init();
|
|
//not full screen mode
|
|
if (document.body.className.indexOf("webix_full_screen") == -1)
|
|
t.limit(true);
|
|
|
|
if (window.MSCSSMatrix)
|
|
webix.html.addStyle(".webix_view{ -ms-touch-action: none; }");
|
|
} else {
|
|
var id = webix.event(document.body, "touchstart", function(ev){
|
|
if (ev.touches.length && ev.touches[0].radiusX > 4){
|
|
webix.env.touch = true;
|
|
setMouse(mouse);
|
|
touchInit();
|
|
for (var key in webix.ui.views){
|
|
var view = webix.ui.views[key];
|
|
if (view && view.$touch)
|
|
view.$touch();
|
|
}
|
|
}
|
|
webix.eventRemove(id);
|
|
}, { capture: true });
|
|
}
|
|
}
|
|
|
|
function setMouse(mouse){
|
|
mouse.down = "touchstart";
|
|
mouse.move = "touchmove";
|
|
mouse.up = "touchend";
|
|
mouse.context = t._get_context;
|
|
}
|
|
|
|
webix.ready(touchInit);
|
|
|
|
|
|
var mouse = webix.env.mouse = { down:"mousedown", up:"mouseup",
|
|
move:"mousemove", context:t._get_context_m };
|
|
|
|
if (window.navigator.pointerEnabled){
|
|
mouse.down = "pointerdown";
|
|
mouse.move = "pointermove";
|
|
mouse.up = "pointerup";
|
|
} else if (window.navigator.msPointerEnabled){
|
|
mouse.down = "MSPointerDown";
|
|
mouse.move = "MSPointerMove";
|
|
mouse.up = "MSPointerUp";
|
|
} else if (webix.env.touch)
|
|
setMouse(mouse);
|
|
|
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
webix.attachEvent("onDataTable", function(table, config){
|
|
if (webix.env.touch){
|
|
webix.Touch.$init();
|
|
config.scrollSize = 0;
|
|
|
|
// needed to show datatable scroll
|
|
if(webix.Touch._disabled)
|
|
webix.Touch.limit();
|
|
|
|
table.$ready.push(table.$touch);
|
|
}
|
|
});
|
|
|
|
webix.extend(webix.ui.datatable, {
|
|
$touch:function(){
|
|
var config = this._settings;
|
|
config.scrollAlignY = false;
|
|
|
|
webix.extend(this, (config.prerender===true)?this._touchNative:this._touch);
|
|
|
|
var scrollMode = "";
|
|
if (!config.autowidth && config.scrollX !== false)
|
|
scrollMode += "x";
|
|
if (!config.autoheight && config.scrollY !== false)
|
|
scrollMode += "y";
|
|
this._body.setAttribute("touch_scroll", scrollMode);
|
|
|
|
webix.Touch._init_scroll_node(this._body.childNodes[1].firstChild);
|
|
webix.Touch._set_matrix(this._body.childNodes[1].firstChild, 0,0,"0ms");
|
|
this._sync_scroll(0,0,"0ms");
|
|
},
|
|
_touchNative:{
|
|
_scrollTo_touch:function(x,y){
|
|
webix.Touch._set_matrix(this._body.childNodes[1].firstChild, 0,0,"0ms");
|
|
this._sync_scroll(x,y,"0ms");
|
|
},
|
|
_getScrollState_touch:function(){
|
|
var temp = webix.Touch._get_matrix(this._body.childNodes[1].firstChild);
|
|
return { x : -temp.e, y : -temp.f };
|
|
},
|
|
$init:function(){
|
|
this.attachEvent("onBeforeScroll", function(){
|
|
webix.Touch._scroll_node = this._body.childNodes[1].firstChild;
|
|
webix.Touch._get_sizes(webix.Touch._scroll_node);
|
|
webix.Touch._scroll_master = this;
|
|
});
|
|
this.attachEvent("onTouchEnd", function(){
|
|
webix.Touch._scroll_master = null;
|
|
});
|
|
},
|
|
_sync_scroll:function(x,y,t){
|
|
if (this._settings.leftSplit)
|
|
webix.Touch._set_matrix(this._body.childNodes[0].firstChild,0,y,t);
|
|
if (this._settings.rightSplit)
|
|
webix.Touch._set_matrix(this._body.childNodes[2].firstChild,0,y,t);
|
|
if (this._settings.header)
|
|
webix.Touch._set_matrix(this._header.childNodes[1].firstChild,x,0,t);
|
|
if (this._settings.footer)
|
|
webix.Touch._set_matrix(this._footer.childNodes[1].firstChild,x,0,t);
|
|
|
|
this.callEvent("onSyncScroll", [x,y,t]);
|
|
},
|
|
_sync_pos:function(){}
|
|
},
|
|
_touch:{
|
|
_scrollTo_touch:function(x,y){
|
|
webix.delay(function(){
|
|
this.callEvent("onAfterScroll", [{ e: -x, f: -y}]);
|
|
}, this);
|
|
|
|
},
|
|
$scroll:{
|
|
gravity:0,
|
|
elastic:false
|
|
},
|
|
$init:function(){
|
|
//if the result column's width < container's width,
|
|
this.attachEvent("onAfterColumnHide", function(){
|
|
this._scrollTo_touch(0, 0);
|
|
});
|
|
this.attachEvent("onBeforeScroll", function(){
|
|
var t = webix.Touch;
|
|
t._scroll_node = this._body.childNodes[1].firstChild;
|
|
t._get_sizes(t._scroll_node);
|
|
t._scroll_stat.left = this._scrollLeft;
|
|
t._scroll_stat.hidden = this._x_scroll._settings.scrollVisible || this._y_scroll._settings.scrollVisible;
|
|
t._scroll_stat.dy = this._dtable_height;
|
|
t._scroll_master = this;
|
|
});
|
|
this.attachEvent("onAfterScroll", function(result){
|
|
//onAfterScroll may be triggered by some non-touch related logic
|
|
if (!result) return;
|
|
|
|
var isScrollX = (this._scrollLeft != -result.e);
|
|
var isScrollY = (this._scrollTop != -result.f);
|
|
|
|
webix.Touch._scroll_master = null;
|
|
webix.Touch._fix_f = null;
|
|
|
|
this._scrollTop = 0;
|
|
this._scrollLeft = 0;
|
|
|
|
//ipad can delay content rendering if 3d transformation applied
|
|
//switch back to 2d
|
|
var temp = webix.Touch.config.translate;
|
|
webix.Touch.config.translate = "translate";
|
|
this._sync_scroll((this._x_scroll ? 0 : result.e), 0, "0ms");
|
|
webix.Touch.config.translate = temp;
|
|
|
|
this._scrollLeft = -result.e;
|
|
this._scrollTop = -result.f;
|
|
this._correctScrollSize();
|
|
|
|
this.render();
|
|
|
|
if(isScrollX){
|
|
if (this._x_scroll)
|
|
this._x_scroll.scrollTo(this._scrollLeft);
|
|
this.callEvent("onScrollX",[]);
|
|
}
|
|
if(isScrollY){
|
|
if (this._y_scroll)
|
|
this._y_scroll.scrollTo(this._scrollTop);
|
|
this.callEvent("onScrollY",[]);
|
|
}
|
|
|
|
return false;
|
|
});
|
|
},
|
|
_sync_scroll:function(x,y,t){
|
|
y += this._scrollTop;
|
|
x += this._scrollLeft;
|
|
|
|
webix.Touch._set_matrix(this._body.childNodes[1].firstChild, x, y, t);
|
|
if (this._settings.leftSplit)
|
|
webix.Touch._set_matrix(this._body.childNodes[0].firstChild,0,y,t);
|
|
if (this._settings.rightSplit)
|
|
webix.Touch._set_matrix(this._body.childNodes[2].firstChild,0,y,t);
|
|
if (this._settings.header)
|
|
webix.Touch._set_matrix(this._header.childNodes[1].firstChild,x,0,t);
|
|
if (this._settings.footer)
|
|
webix.Touch._set_matrix(this._footer.childNodes[1].firstChild,x,0,t);
|
|
|
|
this.callEvent("onSyncScroll", [x,y,t]);
|
|
},
|
|
_sync_pos:function(matrix){
|
|
matrix.f -= this._scrollTop;
|
|
matrix.e -= this._scrollLeft;
|
|
}
|
|
}
|
|
});
|
|
webix.extend(webix.ui.datatable, {
|
|
$init:function(){
|
|
this.data.attachEvent("onStoreUpdated", webix.bind(function(id){
|
|
if (!id) this._adjustColumns();
|
|
}, this));
|
|
this.attachEvent("onStructureLoad", this._adjustColumns);
|
|
|
|
this.attachEvent("onStructureUpdate", this._resizeColumns);
|
|
this.attachEvent("onColumnResize", function(a,b,c,user){
|
|
if (user)
|
|
this._resizeColumns();
|
|
});
|
|
this.attachEvent("onResize", this._resizeColumns);
|
|
},
|
|
_adjustColumns:function(){
|
|
var resize = false;
|
|
var cols = this._columns;
|
|
for (var i = 0; i < cols.length; i++)
|
|
if (cols[i].adjust && ( cols[i].adjust =="header" || this.count() ))
|
|
resize = this._adjustColumn(i, cols[i].adjust, true) || resize;
|
|
|
|
if (resize){
|
|
this._updateColsSizeSettings(true);
|
|
this._resizeColumns();
|
|
}
|
|
},
|
|
_resizeColumns:function(){
|
|
var cols = this._settings.columns;
|
|
var fill = [];
|
|
var summ = 0;
|
|
|
|
if (cols && !this._settings.autowidth)
|
|
for (var i = 0; i < cols.length; i++){
|
|
var colfil = cols[i].fillspace;
|
|
if (colfil){
|
|
fill[i] = colfil;
|
|
summ += colfil*1 || 1;
|
|
}
|
|
}
|
|
|
|
if (summ)
|
|
this._fillColumnSize(fill, summ);
|
|
},
|
|
_fillColumnSize:function(fill, summ){
|
|
var cols = this._settings.columns;
|
|
if (!cols) return;
|
|
|
|
var width = this._content_width - this._scrollSizeY;
|
|
var resize = false;
|
|
|
|
if (width>0){
|
|
for (var i=0; i<cols.length; i++)
|
|
if (!fill[i]) width -= (cols[i].width || this.config.columnWidth);
|
|
|
|
for (var i = 0; i < fill.length; i++)
|
|
if (fill[i]){
|
|
var request = Math.min(width, Math.round(width * fill[i]/summ));
|
|
resize = this._setColumnWidth(i, request, true) || resize;
|
|
width = width - cols[i].width;
|
|
summ = summ - fill[i];
|
|
}
|
|
|
|
if (resize)
|
|
this._updateColsSizeSettings(true);
|
|
}
|
|
},
|
|
_getColumnConfigSize:function(ind, headers){
|
|
var config = this._settings.columns[ind];
|
|
var max = config.minColumnWidth || 10;
|
|
|
|
//get max data width
|
|
if (headers != "header"){
|
|
var order = [].concat(this.data.order);
|
|
for (var i = 0; i < order.length; i++)
|
|
order[i] = order[i] ? this._getValue(this.getItem(order[i]), config, 0) : "";
|
|
max = Math.max(max, webix.html.getTextSize(order, "webix_table_cell webix_cell").width);
|
|
}
|
|
|
|
//get max header width
|
|
if (headers != "data"){
|
|
for (var i=0; i<config.header.length; i++){
|
|
var header = config.header[i];
|
|
if (header){
|
|
var width = 0;
|
|
if(header.rotate)
|
|
for(var h = 0; h<(header.rowspan || 1); h++)
|
|
width += this._headers[h];
|
|
var css = "webix_table_cell webix_cell "+(header.css||"") + (header.rotate?"webix_measure_rotate":"");
|
|
var size = webix.html.getTextSize([header.text], css, width);
|
|
max = Math.max(max, header.rotate?size.height:size.width);
|
|
}
|
|
}
|
|
}
|
|
|
|
//1px to compensate offsetWidth rounding
|
|
return max+1+(webix.env.isIE?webix.skin.$active.layoutPadding.space:0);
|
|
},
|
|
_adjustColumn:function(ind, headers, ignore){
|
|
if (ind >= 0){
|
|
var width = this._getColumnConfigSize(ind, headers);
|
|
return this._setColumnWidth(ind, width, ignore);
|
|
}
|
|
},
|
|
adjustColumn:function(id, headers){
|
|
this._adjustColumn(this.getColumnIndex(id), headers);
|
|
},
|
|
adjustRowHeight:function(id, silent){
|
|
if(id) {
|
|
var config = this.getColumnConfig(id);
|
|
var container;
|
|
var d = webix.html.create("DIV",{"class":"webix_table_cell webix_measure_size webix_cell"},"");
|
|
d.style.cssText = "width:"+config.width+"px; height:1px; visibility:hidden; position:absolute; top:0px; left:0px; overflow:hidden;";
|
|
this.$view.appendChild(d);
|
|
|
|
if (d.offsetHeight < 1){
|
|
//hidden container, height detection is broken
|
|
//reattach to the body
|
|
container = this.$view.cloneNode(true);
|
|
document.body.appendChild(container);
|
|
container.appendChild(d);
|
|
}
|
|
|
|
this.data.each(function(obj){
|
|
//in case of dyn. mode - this can be undefined
|
|
if (obj){
|
|
d.innerHTML = this._getValue(obj, config, 0);
|
|
obj.$height = Math.max(d.scrollHeight, this._settings.rowHeight);
|
|
}
|
|
}, this);
|
|
|
|
d = webix.html.remove(d);
|
|
if (container)
|
|
webix.html.remove(container);
|
|
} else {
|
|
//set size of array based on data size
|
|
//can be not-reliable for tree-like components anyway
|
|
var heightsArr = new Array(this.data.order.length);
|
|
var cols = this.config.columns;
|
|
|
|
//set 0 as initial height
|
|
var j = 0;
|
|
//iterate through all possible items
|
|
//we need to be sure that heightsArr is not lesser than real data count
|
|
for (var key in this.data.pull){
|
|
heightsArr[j] = 0;
|
|
j++;
|
|
}
|
|
|
|
for (var i = 0; i < cols.length; i++) {
|
|
//adjust size for single columns
|
|
this.adjustRowHeight(cols[i].id, true);
|
|
//for each row, set height as maximum between all columns
|
|
var j = 0;
|
|
this.data.each(function(obj, index){
|
|
//index is not reliable for tree-components, using a custom counter
|
|
if (obj.$height > heightsArr[j]) {
|
|
heightsArr[j] = obj.$height;
|
|
}
|
|
obj.$height = heightsArr[j];
|
|
j++;
|
|
});
|
|
}
|
|
}
|
|
|
|
if (!silent)
|
|
this.refresh();
|
|
}
|
|
});
|
|
|
|
webix.extend(webix.ui.datatable,{
|
|
|
|
math_setter:function(value){
|
|
if (value)
|
|
this._math_init();
|
|
return value;
|
|
},
|
|
|
|
_math_pref: '$',
|
|
|
|
_math_init: function() {
|
|
if(webix.env.strict) return;
|
|
|
|
this.data.attachEvent("onStoreUpdated", webix.bind(this._parse_row_math, this));
|
|
this.data.attachEvent("onStoreLoad", webix.bind(this._parse_math, this));
|
|
this.attachEvent("onStructureLoad", this._parse_math);
|
|
},
|
|
_parse_row_math:function(id, obj, action){
|
|
if (!id || (action=="delete" || action=="paint")) return;
|
|
|
|
if (action == "add")
|
|
this._exprs_by_columns(obj);
|
|
|
|
for (var i=0; i<this._columns.length; i++)
|
|
this._parse_cell_math(id, this._columns[i].id, action !== "add");
|
|
this._math_recalc = {};
|
|
},
|
|
_parse_cell_math: function(row, col, _inner_call) {
|
|
var item = this.getItem(row);
|
|
var value;
|
|
|
|
// if it's outer call we should use inputted value otherwise to take formula, not calculated value
|
|
if (_inner_call === true)
|
|
value = item[this._math_pref + col] || item[col];
|
|
else {
|
|
value = item[col];
|
|
this._math_recalc = {};
|
|
}
|
|
|
|
if (typeof value === "undefined" || value === null) return;
|
|
|
|
if (value.length > 0 && value.substr(0, 1) === '=') {
|
|
// calculate math value
|
|
if (!item[this._math_pref + col] || (_inner_call !== true))
|
|
item[this._math_pref + col] = item[col];
|
|
item[col] = this._calculate(value, row, col);
|
|
//this.updateItem(item);
|
|
} else {
|
|
// just a string
|
|
if (typeof(item[this._math_pref + col]) !== 'undefined')
|
|
delete item[this._math_pref + col];
|
|
// remove triggers if they were setted earlier
|
|
this._remove_old_triggers(row, col);
|
|
}
|
|
// recalculate depending cells
|
|
if (typeof(item.depends) !== 'undefined' && typeof(item.depends[col]) !== 'undefined') {
|
|
for (var i in item.depends[col]) {
|
|
var name = item.depends[col][i][0] + '__' + item.depends[col][i][1];
|
|
if (typeof(this._math_recalc[name]) === 'undefined') {
|
|
this._math_recalc[name] = true;
|
|
this._parse_cell_math(item.depends[col][i][0], item.depends[col][i][1], true);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
_set_original_value: function(row, col) {
|
|
var item = this.getItem(row);
|
|
if (typeof(item[this._math_pref + col]) !== 'undefined')
|
|
item[col] = item[this._math_pref + col];
|
|
},
|
|
|
|
_parse_math: function(){
|
|
if (!this._columns || !this.count()) return;
|
|
|
|
this._exprs_by_columns();
|
|
|
|
|
|
for (var j = 0; j < this._columns.length; j++){
|
|
var col = this.columnId(j);
|
|
this.data.each(function(obj){
|
|
this._parse_cell_math(obj.id, col);
|
|
}, this);
|
|
}
|
|
|
|
this._math_recalc = {};
|
|
},
|
|
|
|
_exprs_by_columns: function(row) {
|
|
for (var i = 0; i < this._columns.length; i++){
|
|
if (this._columns[i].math) {
|
|
var col = this.columnId(i);
|
|
var math = '=' + this._columns[i].math;
|
|
math = math.replace(/\$r/g, '#$r#');
|
|
math = math.replace(/\$c/g, '#$c#');
|
|
if (row)
|
|
row[col] = this._parse_relative_expr(math, row.id, col);
|
|
else
|
|
this.data.each(function(obj){
|
|
obj[col] = this._parse_relative_expr(math, obj.id, col);
|
|
}, this);
|
|
}
|
|
}
|
|
},
|
|
|
|
_parse_relative_expr: function(expr, row, col) {
|
|
return (webix.template(expr))({ '$r': row, '$c': col });
|
|
},
|
|
|
|
_get_calc_value: function(row, col) {
|
|
var item;
|
|
|
|
if (this.exists(row))
|
|
item = this.getItem(row);
|
|
else
|
|
return '#out_of_range';
|
|
|
|
var value = item[this._math_pref + col] || item[col] || 0;
|
|
value = value.toString();
|
|
if (value.substring(0, 1) !== '=')
|
|
// it's a string
|
|
return value;
|
|
else {
|
|
// TODO: check if value shouldn't be recalculated
|
|
// and return value calculated earlier
|
|
|
|
// calculate math expr value right now
|
|
if (typeof(item[this._math_pref + col]) === 'undefined')
|
|
item[this._math_pref + col] = item[col];
|
|
item[col] = this._calculate(value, row, col, true);
|
|
return item[col];
|
|
}
|
|
},
|
|
|
|
_calculate: function(value, row, col, _inner_call) {
|
|
// add coord in math trace to detect self-references
|
|
if (_inner_call === true) {
|
|
if (this._in_math_trace(row, col))
|
|
return '#selfreference';
|
|
} else
|
|
this._start_math_trace();
|
|
this._to_math_trace(row, col);
|
|
|
|
var item = this.getItem(row);
|
|
value = value.substring(1);
|
|
|
|
// get operations list
|
|
var operations = this._get_operations(value);
|
|
var triggers = this._get_refs(value);
|
|
|
|
if (operations) {
|
|
value = this._replace_refs(value, triggers);
|
|
value = this._parse_args(value, operations);
|
|
} else {
|
|
value = this._replace_refs(value, triggers, true);
|
|
}
|
|
|
|
var exc = this._math_exception(value);
|
|
if (exc !== false)
|
|
return exc;
|
|
|
|
// remove from coord from trace when calculations were finished - it's important!
|
|
this._from_math_trace(row, col);
|
|
|
|
// process triggers to know which cells should be recalculated when one was changed
|
|
this._remove_old_triggers(row, col);
|
|
for (var i = 0; i < triggers.length; i++) {
|
|
this._add_trigger([row, col], triggers[i]);
|
|
}
|
|
var exc = this._math_exception(value);
|
|
if (exc !== false)
|
|
return exc;
|
|
|
|
// there aren't any operations here. returns number or value of another cell
|
|
if (!value) return value;
|
|
|
|
// process mathematical expression and getting final result
|
|
value = this._compute(value);
|
|
var exc = this._math_exception(value);
|
|
if (exc !== false)
|
|
return exc;
|
|
return value;
|
|
},
|
|
|
|
_get_operations: function(value) {
|
|
// gettings operations list (+-*/)
|
|
var splitter = /(\+|\-|\*|\/)/g;
|
|
var operations = value.replace(/\[[^)]*?\]/g,"").match(splitter);
|
|
return operations;
|
|
},
|
|
|
|
/*! gets list of referencies in formula
|
|
**/
|
|
_get_refs: function(value) {
|
|
var reg = /\[([^\]]+),([^\]]+)\]/g;
|
|
var cells = value.match(reg);
|
|
if (cells === null) cells = [];
|
|
|
|
for (var i = 0; i < cells.length; i++) {
|
|
var cell = cells[i];
|
|
var tmp = cell;
|
|
cell = cell.substr(1, cell.length - 2);
|
|
cell = cell.split(',');
|
|
cell[0] = this._trim(cell[0]);
|
|
cell[1] = this._trim(cell[1]);
|
|
if (cell[0].substr(0, 1) === ':')
|
|
cell[0] = this.getIdByIndex(cell[0].substr(1));
|
|
if (cell[1].substr(0, 1) === ':')
|
|
cell[1] = this.columnId(cell[1].substr(1));
|
|
cell[2] = tmp;
|
|
cells[i] = cell;
|
|
}
|
|
|
|
return cells;
|
|
},
|
|
|
|
// replace given list of references by their values
|
|
_replace_refs: function(value, cells, clean) {
|
|
var dell = "(", delr = ")";
|
|
if (clean) dell = delr = "";
|
|
for (var i = 0; i < cells.length; i++) {
|
|
var cell = cells[i];
|
|
var cell_value = this._get_calc_value(cell[0], cell[1]);
|
|
if (isNaN(cell_value))
|
|
cell_value = '"'+cell_value+'"';
|
|
value = value.replace(cell[2], dell + cell_value + delr);
|
|
}
|
|
return value;
|
|
},
|
|
|
|
_parse_args: function(value, operations) {
|
|
var args = [];
|
|
for (var i = 0; i < operations.length; i++) {
|
|
var op = operations[i];
|
|
var temp = this._split_by(value, op);
|
|
args.push(temp[0]);
|
|
value = temp[1];
|
|
}
|
|
args.push(value);
|
|
|
|
//var reg = /^(-?\d|\.|\(|\))+$/;
|
|
for (var i = 0; i < args.length; i++) {
|
|
var arg = this._trim(args[i]);
|
|
// if (reg.test(arg) === false)
|
|
// return ''; //error
|
|
args[i] = arg;
|
|
}
|
|
|
|
var expr = "";
|
|
for (var i = 0; i < args.length - 1; i++) {
|
|
expr += args[i] + operations[i];
|
|
}
|
|
expr += args[args.length - 1];
|
|
return expr;
|
|
},
|
|
|
|
_compute: function(expr) {
|
|
try {
|
|
webix.temp_value = '';
|
|
expr = 'webix.temp_value = ' + expr;
|
|
eval(expr);
|
|
} catch(ex) {
|
|
webix.assert(false,"Math error in datatable<br>"+expr);
|
|
webix.temp_value = '';
|
|
}
|
|
var result = webix.temp_value;
|
|
webix.temp_value = null;
|
|
return result.toString();
|
|
},
|
|
|
|
_split_by: function(value, splitter) {
|
|
var pos = value.indexOf(splitter);
|
|
var before = value.substr(0, pos);
|
|
var after = value.substr(pos + 1);
|
|
return [before, after];
|
|
},
|
|
|
|
_trim: function(value) {
|
|
value = value.replace(/^ */g, '');
|
|
value = value.replace(/ *$/g, '');
|
|
return value;
|
|
},
|
|
|
|
_start_math_trace: function() {
|
|
this._math_trace = [];
|
|
},
|
|
_to_math_trace: function(row, col) {
|
|
this._math_trace[row + '__' + col] = true;
|
|
},
|
|
_from_math_trace: function(row, col) {
|
|
if (typeof(this._math_trace[row + '__' + col]) !== 'undefined')
|
|
delete this._math_trace[row + '__' + col];
|
|
},
|
|
_in_math_trace: function(row, col) {
|
|
if (typeof(this._math_trace[row + '__' + col]) !== 'undefined')
|
|
return true;
|
|
else
|
|
return false;
|
|
},
|
|
|
|
_add_trigger: function(depends, from) {
|
|
var item = this.getItem(from[0]);
|
|
if (typeof(item.depends) === 'undefined')
|
|
item.depends = {};
|
|
if (typeof(item.depends[from[1]]) === 'undefined')
|
|
item.depends[from[1]] = {};
|
|
item.depends[from[1]][depends[0] + '__' + depends[1]] = depends;
|
|
|
|
item = this.getItem(depends[0]);
|
|
if (typeof(item.triggers) === 'undefined')
|
|
item.triggers = {};
|
|
if (typeof(item.triggers[depends[1]]) === 'undefined')
|
|
item.triggers[depends[1]] = {};
|
|
item.triggers[depends[1]][from[0] + '__' + from[1]] = from;
|
|
},
|
|
|
|
_remove_old_triggers: function(row, col) {
|
|
if (!this.exists(row, col)) return;
|
|
var item = this.getItem(row, col);
|
|
if (typeof(item.triggers) === 'undefined') return;
|
|
for (var i in item.triggers[col]) {
|
|
var depend = item.triggers[col][i];
|
|
delete this.getItem(depend[0]).depends[depend[1]][row + '__' + col];
|
|
}
|
|
},
|
|
|
|
// check if exception syntax exists and returns exception text or false
|
|
_math_exception: function(value) {
|
|
var reg = /#\w+/;
|
|
var match = value.match(reg);
|
|
if (match !== null && match.length > 0)
|
|
return match[0];
|
|
return false;
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
webix.extend(webix.ui.datatable, {
|
|
|
|
/////////////////////////
|
|
// edit start //
|
|
/////////////////////////
|
|
_get_editor_type:function(id){
|
|
return this.getColumnConfig(id.column).editor;
|
|
},
|
|
getEditor:function(row, column){
|
|
if (!row)
|
|
return this._last_editor;
|
|
|
|
if (arguments.length == 1){
|
|
column = row.column;
|
|
row = row.row;
|
|
}
|
|
|
|
return ((this._editors[row]||{})[column]);
|
|
},
|
|
_for_each_editor:function(handler){
|
|
for (var row in this._editors){
|
|
var row_editors = this._editors[row];
|
|
for (var column in row_editors)
|
|
if (column!="$count")
|
|
handler.call(this, row_editors[column]);
|
|
}
|
|
},
|
|
_init_editor:function(id, type, show){
|
|
var row = id.row;
|
|
var column = id.column;
|
|
var col_settings = type.config = this.getColumnConfig(column);
|
|
//show it over cell
|
|
if (show !== false)
|
|
this.showCell(row, column);
|
|
|
|
var node = type.render();
|
|
|
|
if (type.$inline)
|
|
node = this._locateInput(id);
|
|
type.node = node;
|
|
|
|
var item = this.getItem(row);
|
|
var format = col_settings.editFormat;
|
|
|
|
var value;
|
|
if (this._settings.editMath)
|
|
value = item["$"+column];
|
|
value = value || item[column];
|
|
|
|
if (webix.isUndefined(value))
|
|
value="";
|
|
|
|
type.setValue(format?format(value):value, item);
|
|
type.value = item[column];
|
|
this._addEditor(id, type);
|
|
|
|
if (!type.$inline)
|
|
this._sizeToCell(id, node, true);
|
|
|
|
if (type.afterRender)
|
|
type.afterRender();
|
|
|
|
if (this._settings.liveValidation){
|
|
webix._event(type.node, "keyup", this._bind_live_validation(id, this));
|
|
this.validateEditor(id);
|
|
}
|
|
|
|
return node;
|
|
},
|
|
_bind_live_validation:function(id, that){
|
|
return function(){
|
|
that.validateEditor(id);
|
|
};
|
|
},
|
|
_set_new_value:function(editor, new_value, copy){
|
|
var parser = this.getColumnConfig(editor.column).editParse;
|
|
var item = copy ? {} : this.getItem(editor.row);
|
|
item[editor.column] = parser?parser(new_value):new_value;
|
|
|
|
if (this._settings.editMath)
|
|
item["$"+editor.column] = null;
|
|
|
|
return item;
|
|
},
|
|
//register editor in collection
|
|
_addEditor:function(id, type, node){
|
|
var row_editors = this._editors[id.row]=this._editors[id.row]||{};
|
|
|
|
row_editors.$count = (row_editors.$count||0)+1;
|
|
|
|
type.row = id.row; type.column = id.column;
|
|
this._last_editor = row_editors[id.column] = type;
|
|
|
|
this._in_edit_mode++;
|
|
this._last_editor_scroll = this.getScrollState();
|
|
},
|
|
_removeEditor:function(editor){
|
|
if (this._last_editor == editor)
|
|
this._last_editor = 0;
|
|
|
|
if (editor.destroy)
|
|
editor.destroy();
|
|
|
|
var row = this._editors[editor.row];
|
|
delete row[editor.column];
|
|
row.$count -- ;
|
|
if (!row.$count)
|
|
delete this._editors[editor.row];
|
|
this._in_edit_mode--;
|
|
},
|
|
_changeEditorId:function(oldid, newid) {
|
|
var editor = this._editors[oldid];
|
|
if (editor){
|
|
this._editors[newid] = editor;
|
|
delete this._editors[oldid];
|
|
for (var key in editor)
|
|
editor[key].row = newid;
|
|
}
|
|
},
|
|
//get html cell by combined id
|
|
_locate_cell:function(id){
|
|
var area, i, index, j, node, span,
|
|
config = this.getColumnConfig(id.column),
|
|
cell = 0;
|
|
|
|
if (config && config.node && config.attached){
|
|
index = this.getIndexById(id.row);
|
|
if(this._spans_pull){
|
|
span = this.getSpan(id.row,id.column);
|
|
if(span){
|
|
for (i=0; i<3; i++){
|
|
area = this._spans_areas[i];
|
|
for(j=0; !cell && j < area.childNodes.length; j++){
|
|
node = area.childNodes[j];
|
|
if(node.getAttribute("row") == index && node.getAttribute("column") == this.getColumnIndex(id.column))
|
|
cell = node;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!cell && index >= (config._yr0-this._settings.topSplit) && index< config._yr1)
|
|
cell = config.node.childNodes[index-config._yr0+this._settings.topSplit];
|
|
}
|
|
return cell;
|
|
},
|
|
|
|
|
|
/////////////////////////
|
|
// public methods //
|
|
/////////////////////////
|
|
editCell:function(row, column, preserve, show){
|
|
column = column || this._settings.columns[0].id;
|
|
return webix.EditAbility.edit.call(this, {row:row, column:column}, preserve, show);
|
|
},
|
|
editRow:function(id, focus){
|
|
if (id && id.row)
|
|
id = id.row;
|
|
|
|
var next = false;
|
|
this.eachColumn(function(column){
|
|
this.edit({ row:id, column:column}, next, !next);
|
|
next = true;
|
|
});
|
|
},
|
|
editColumn:function(id, focus){
|
|
if (id && id.column)
|
|
id = id.column;
|
|
|
|
var next = false;
|
|
this.eachRow(function(row){
|
|
this.edit({row:row, column:id}, next, !next);
|
|
next = true;
|
|
});
|
|
},
|
|
eachRow:function(handler, all){
|
|
var order = this.data.order;
|
|
if (all)
|
|
order = this.data._filter_order || order;
|
|
|
|
for (var i=0; i<order.length; i++)
|
|
handler.call(this, order[i]);
|
|
},
|
|
eachColumn:function(handler, all){
|
|
for (var i in this._columns_pull){
|
|
var column = this._columns_pull[i];
|
|
handler.call(this, column.id, column);
|
|
}
|
|
if (all){
|
|
for (var i in this._hidden_column_hash){
|
|
var column = this._hidden_column_hash[i];
|
|
handler.call(this, column.id, column);
|
|
}
|
|
}
|
|
},
|
|
|
|
|
|
////////////////////
|
|
// edit next //
|
|
////////////////////
|
|
_after_edit_next:function(editor_next){
|
|
if (this.getSelectedId){ //select related cell when possible
|
|
var sel = this.getSelectedId(true);
|
|
if (sel.length == 1){
|
|
this._select(editor_next);
|
|
return false;
|
|
}
|
|
}
|
|
},
|
|
_custom_tab_handler:function(tab, e){
|
|
if (this._settings.editable && !this._in_edit_mode){
|
|
//if we have focus in some custom input inside of datatable
|
|
if (e.target && e.target.tagName == "INPUT") return true;
|
|
|
|
//init editor related to a single selected row/column/cell
|
|
var selection = this.getSelectedId(true);
|
|
if (selection.length == 1){
|
|
var sel = selection[0];
|
|
if(this._settings.select == "row")
|
|
sel.column = this._settings.columns[e.shiftKey?0:this._settings.columns.length-1].id;
|
|
this.editNext(tab, sel);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
},
|
|
|
|
_find_cell_next:function(start, check, direction){
|
|
var row = this.getIndexById(start.row);
|
|
var column = this.getColumnIndex(start.column);
|
|
var order = this.data.order;
|
|
var cols = this._columns;
|
|
|
|
if (direction){
|
|
|
|
for (var i=row; i<order.length; i++){
|
|
for (var j=column+1; j<cols.length; j++){
|
|
var id = { row:order[i], column:cols[j].id};
|
|
if (check.call(this, id) && (!this._checkCellMerge || !this._checkCellMerge(start,id))){
|
|
return id;
|
|
}
|
|
}
|
|
column = -1;
|
|
}
|
|
} else {
|
|
for (var i=row; i>=0; i--){
|
|
for (var j=column-1; j>=0; j--){
|
|
var id = { row:order[i], column:cols[j].id};
|
|
if (check.call(this, id))
|
|
return id;
|
|
}
|
|
column = cols.length;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
},
|
|
|
|
|
|
/////////////////////////////
|
|
// scroll correction //
|
|
/////////////////////////////
|
|
_correct_after_focus_y:function(){
|
|
if (this._in_edit_mode){
|
|
if (this._ignore_after_focus_scroll)
|
|
this._ignore_after_focus_scroll = false;
|
|
else {
|
|
this._y_scroll.scrollTo(this.getScrollState().y+this._body.childNodes[1].firstChild.scrollTop);
|
|
this._body.childNodes[1].firstChild.scrollTop = 0;
|
|
this._ignore_after_focus_scroll = true;
|
|
}
|
|
}
|
|
},
|
|
_correct_after_focus_x:function(){
|
|
if (this._in_edit_mode){
|
|
this._x_scroll.scrollTo(this._body.childNodes[1].scrollLeft);
|
|
}
|
|
},
|
|
_component_specific_edit_init:function(){
|
|
this.attachEvent("onScrollY", this._update_editor_y_pos);
|
|
this.attachEvent("onScrollX", this._update_editor_y_pos);
|
|
this.attachEvent("onScrollY", this._refocus_inline_editor);
|
|
this.attachEvent("onColumnResize", function(){ this.editStop(); });
|
|
this.attachEvent("onAfterFilter", function(){ this.editStop(); });
|
|
this.attachEvent("onRowResize", function(){ this.editStop(); });
|
|
this.attachEvent("onAfterScroll", function(){ if(this._settings.topSplit) this.editStop(); });
|
|
this._body.childNodes[1].firstChild.onscroll = webix.bind(this._correct_after_focus_y, this);
|
|
this._body.childNodes[1].onscroll = webix.bind(this._correct_after_focus_x, this);
|
|
},
|
|
_update_editor_y_pos:function(){
|
|
if (this._in_edit_mode){
|
|
var old = this._last_editor_scroll;
|
|
this._last_editor_scroll = this.getScrollState();
|
|
|
|
var diff = this._last_editor_scroll.y - old.y;
|
|
this._for_each_editor(function(editor){
|
|
if (editor.getPopup){
|
|
var node = this.getItemNode(editor);
|
|
if (node)
|
|
editor.getPopup().show(node);
|
|
else
|
|
editor.getPopup().show({ x:-10000, y:-10000 });
|
|
} else if (!editor.$inline){
|
|
editor.node.top -= diff;
|
|
editor.node.style.top = editor.node.top + "px";
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
});
|
|
|
|
webix.extend(webix.ui.datatable, webix.EditAbility);
|
|
webix.extend(webix.ui.datatable, {
|
|
$init:function(){
|
|
this._clear_hidden_state();
|
|
this.attachEvent("onStructureLoad", this._hideInitialColumns);
|
|
},
|
|
_clear_hidden_state:function(){
|
|
this._hidden_column_hash = {};
|
|
this._hidden_column_order = webix.toArray();
|
|
this._hidden_split=[0,0];
|
|
},
|
|
_hideInitialColumns:function(){
|
|
var cols = this._columns;
|
|
|
|
for(var i = 0; i<cols.length; i++){
|
|
if(cols[i].header) this._getInitialSpans(cols, cols[i].header);
|
|
if(cols[i].footer) this._getInitialSpans(cols, cols[i].footer);
|
|
}
|
|
|
|
for (var i = cols.length-1; i>=0; i--){
|
|
if (cols[i].hidden)
|
|
this.hideColumn(cols[i].id, {}, true, true);
|
|
else if (cols[i].batch && this.config.visibleBatch && cols[i].batch!=this.config.visibleBatch){
|
|
this.hideColumn(cols[i].id, {}, true, true);
|
|
}
|
|
}
|
|
},
|
|
_getInitialSpans:function(cols, elements){
|
|
for(var h = 0; h<elements.length;h++){
|
|
var line = elements[h];
|
|
if(line && line.colspan)
|
|
line.$colspan = line.colspan;
|
|
}
|
|
},
|
|
moveColumn:function(id, index){
|
|
var start_index = this.getColumnIndex(id);
|
|
if (start_index == index) return; //already in place
|
|
var columns = this._settings.columns;
|
|
|
|
var start = columns.splice(start_index,1);
|
|
var pos = index - (index>start_index?1:0);
|
|
webix.PowerArray.insertAt.call(columns, start[0], pos);
|
|
|
|
//TODO: split handling
|
|
//we can move split line when column dropped after it
|
|
|
|
this._refresh_columns();
|
|
},
|
|
_init_horder:function(){
|
|
var horder = this._hidden_column_order;
|
|
var cols = this._settings.columns;
|
|
if (!horder.length){
|
|
for (var i=0; i<cols.length; i++)
|
|
horder[i] = cols[i].id;
|
|
this._hidden_split = [this._settings.leftSplit, this._rightSplit];
|
|
}
|
|
},
|
|
isColumnVisible:function(id){
|
|
return !this._hidden_column_hash[id];
|
|
},
|
|
hideColumn:function(id, opts, silent, mode){
|
|
var cols = this._settings.columns;
|
|
var horder = this._hidden_column_order;
|
|
var hhash = this._hidden_column_hash;
|
|
var column;
|
|
var span = 1;
|
|
opts = opts || {};
|
|
|
|
if (mode!==false){
|
|
|
|
var index = this.getColumnIndex(id);
|
|
webix.assert(index != -1, "hideColumn: invalid ID or already hidden");
|
|
if(index === -1 || !this.callEvent("onBeforeColumnHide", [id])) return;
|
|
|
|
//in case of second call to hide the same column, command will be ignored
|
|
if (index == -1) return;
|
|
|
|
this._init_horder();
|
|
|
|
if(opts.spans){
|
|
var header = cols[index].header;
|
|
for(var i = 0; i<header.length; i++){
|
|
if(header[i]){
|
|
header[i].$groupSpan = header[i].colspan || 1;
|
|
span = Math.max(span, header[i].$groupSpan);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (index<this._settings.leftSplit)
|
|
this._settings.leftSplit-=span;
|
|
if (index>=this._rightSplit)
|
|
this._settings.rightSplit-=span;
|
|
else
|
|
this._rightSplit-=span;
|
|
|
|
for (var i=index+span-1; i>=index; i--){
|
|
this._hideColumn(index);
|
|
column = cols.splice(index, 1)[0];
|
|
hhash[column.id] = column;
|
|
column._yr0 = -1;
|
|
delete this._columns_pull[column.id];
|
|
}
|
|
|
|
this.callEvent("onAfterColumnHide", [id]);
|
|
} else {
|
|
column = hhash[id];
|
|
webix.assert(column, "showColumn: invalid ID or already visible");
|
|
|
|
//in case of second show command for already visible column - ignoring
|
|
if(!column || !this.callEvent("onBeforeColumnShow", [id])) return;
|
|
|
|
var prev = null;
|
|
var i = 0;
|
|
var hindex = 0;
|
|
for (; i<horder.length; i++){
|
|
if (horder[i] == id){
|
|
hindex = i;
|
|
break;
|
|
}
|
|
if (!hhash[horder[i]])
|
|
prev = horder[i];
|
|
}
|
|
|
|
var index = prev?this.getColumnIndex(prev)+1:0;
|
|
|
|
if(opts.spans){
|
|
var header = column.header;
|
|
for(var i = 0; i<header.length; i++){
|
|
if(header[i]){
|
|
header[i].colspan = header[i].$groupSpan || header[i].colspan;
|
|
delete header[i].$groupSpan;
|
|
span = Math.max(span, (header[i].colspan || 1));
|
|
}
|
|
}
|
|
}
|
|
|
|
for (var i=hindex+span-1; i>=hindex; i--){
|
|
var column = hhash[horder[i]];
|
|
if(column){ //can be already shown by another action
|
|
webix.PowerArray.insertAt.call(cols, column, index);
|
|
delete column.hidden;
|
|
delete hhash[column.id];
|
|
this._columns_pull[column.id] = column;
|
|
}
|
|
else
|
|
span--;
|
|
}
|
|
|
|
if (hindex<this._hidden_split[0])
|
|
this._settings.leftSplit+=span;
|
|
if (hindex>=this._hidden_split[1])
|
|
this._settings.rightSplit+=span;
|
|
else
|
|
this._rightSplit+=span;
|
|
|
|
|
|
this.callEvent("onAfterColumnShow", [id]);
|
|
}
|
|
|
|
if(column.header) this._fixColspansHidden(column, mode !== false ? 0 : 1, "header");
|
|
if(column.footer) this._fixColspansHidden(column, mode !== false ? 0 : 1, "footer");
|
|
|
|
if (!silent)
|
|
this._refresh_columns();
|
|
},
|
|
_fixColspansHidden:function(config, mod, elName){
|
|
for (var i = config[elName].length - 1; i >= 0; i--) {
|
|
var ind = this._hidden_column_order;
|
|
var spanSource, isHidden = false, spanSize = 0;
|
|
|
|
for (var j = 0; j < ind.length; j++) {
|
|
var config = this.getColumnConfig(ind[j]);
|
|
var el = config[elName][i];
|
|
if (!this.isColumnVisible(ind[j])){
|
|
//hidden column
|
|
if (el && el.$colspan && spanSize <= 0){
|
|
//start of colspan in hidden
|
|
spanSize = el.colspan = el.$colspan;
|
|
isHidden = spanSource = el;
|
|
}
|
|
if (spanSource && spanSize > 0){
|
|
//hidden column in colspan, decrease colspan size
|
|
spanSource.colspan--;
|
|
}
|
|
} else {
|
|
//visible column
|
|
if (isHidden && spanSize > 0 && spanSource && spanSource.colspan > 0){
|
|
//bit start of colspan is hidden
|
|
el = config[elName][i] = spanSource;
|
|
spanSource = el;
|
|
} else if (el && el.$colspan && spanSize <= 0){
|
|
//visible start of colspan
|
|
spanSize = el.colspan = el.$colspan;
|
|
spanSource = el;
|
|
}
|
|
isHidden = null;
|
|
}
|
|
spanSize--;
|
|
}
|
|
}
|
|
},
|
|
refreshColumns:function(columns, reset){
|
|
if ((columns && columns != this.config.columns) || reset){
|
|
this._clear_hidden_state();
|
|
this._filter_elements = {};
|
|
if (columns)
|
|
this._rightSplit = columns.length - (this.config.rightSplit || 0);
|
|
}
|
|
|
|
this._columns_pull = {};
|
|
//clear rendered data
|
|
for (var i=0; i<this._columns.length; i++){
|
|
var col = this._columns[i];
|
|
this._columns_pull[col.id] = col;
|
|
col.attached = col.node = null;
|
|
}
|
|
for (var i=0; i<3; i++){
|
|
this._header.childNodes[i].innerHTML = "";
|
|
this._body.childNodes[i].firstChild.innerHTML = "";
|
|
}
|
|
|
|
//render new structure
|
|
this._columns = this.config.columns = (columns || this.config.columns);
|
|
this._rightSplit = this._columns.length-this._settings.rightSplit;
|
|
|
|
this._dtable_fully_ready = 0;
|
|
this._define_structure();
|
|
|
|
this.callEvent("onStructureUpdate");
|
|
|
|
this._update_scroll();
|
|
this.render();
|
|
},
|
|
_refresh_columns:function(){
|
|
this._dtable_fully_ready = 0;
|
|
this.callEvent("onStructureUpdate");
|
|
|
|
this._apply_headers();
|
|
this.render();
|
|
},
|
|
showColumn:function(id, opts, silent){
|
|
return this.hideColumn(id, opts, silent, false);
|
|
},
|
|
showColumnBatch:function(batch, mode){
|
|
var preserve = typeof mode != "undefined";
|
|
mode = mode !== false;
|
|
|
|
this.eachColumn(function(id, col){
|
|
if(col.batch){
|
|
var hidden = this._hidden_column_hash[col.id];
|
|
if (!mode) hidden = !hidden;
|
|
|
|
if(col.batch == batch && hidden)
|
|
this.hideColumn(col.id, { spans:true }, true,!mode);
|
|
else if(!preserve && col.batch!=batch && !hidden)
|
|
this.hideColumn(col.id, { spans:true }, true, mode);
|
|
}
|
|
}, true);
|
|
|
|
this._refresh_columns();
|
|
}
|
|
});
|
|
|
|
|
|
|
|
webix.extend(webix.ui.datatable, {
|
|
$init:function(){
|
|
this.attachEvent("onAfterScroll", this._set_focusable_item);
|
|
},
|
|
_set_focusable_item:function(){
|
|
var sel = this._getVisibleSelection();
|
|
if(!sel){
|
|
var node = this._dataobj.querySelector(".webix_cell");
|
|
if(node) node.setAttribute("tabindex", "0");
|
|
}
|
|
},
|
|
_getVisibleSelection:function(){
|
|
var sel = this.getSelectedId(true);
|
|
for(var i = 0; i<sel.length; i++){
|
|
if(this.isColumnVisible(sel[i].column))
|
|
return this.getItemNode(sel[i]);
|
|
}
|
|
return null;
|
|
},
|
|
moveSelection:function(mode, details, focus){
|
|
if(this._settings.disabled) return;
|
|
details = details || {};
|
|
|
|
//get existing selection as array
|
|
var t = this.getSelectedId(true);
|
|
var index = t.length-1;
|
|
var preserve = this._settings.multiselect || this._settings.areaselect ? details.shift : false;
|
|
|
|
//change defaults in case of multiselection
|
|
if(t.length>1 && this._settings.select !=="cell"){
|
|
t = t.sort(webix.bind(function(a, b){
|
|
if(this.getIndexById(a.row)>this.getIndexById(b.row) || this.getColumnIndex(a.column)>this.getColumnIndex(b.column)) return 1;
|
|
else return -1;
|
|
}, this));
|
|
if (mode == "up" || mode == "left" || mode =="top" || mode =="pgup")
|
|
index = 0;
|
|
|
|
}
|
|
|
|
if (index < 0 && this.count()){ //no selection
|
|
if (mode == "down" || mode == "right") mode = "top";
|
|
else if (mode == "up" || mode == "left") mode = "bottom";
|
|
else return;
|
|
index = 0;
|
|
t = [{ row:1, column:1 }];
|
|
}
|
|
|
|
|
|
|
|
if (index>=0){
|
|
var row = t[index].row;
|
|
var column = t[index].column;
|
|
|
|
if (mode == "top" || mode == "bottom") {
|
|
if (row) {
|
|
// first/last row setting
|
|
if (mode == "top")
|
|
row = this.data.getFirstId();
|
|
else if (mode == "bottom")
|
|
row = this.data.getLastId();
|
|
}
|
|
if (column) {
|
|
// first/last column setting
|
|
index = 0;
|
|
if(mode == "bottom")
|
|
index = this.config.columns.length-1;
|
|
column = this.columnId(index);
|
|
}
|
|
} else if (mode == "up" || mode== "down" || mode == "pgup" || mode == "pgdown"){
|
|
if (row){
|
|
//it seems row's can be seleted
|
|
var index = this.getIndexById(row);
|
|
var step = (mode == "pgup" || mode == "pgdown") ? Math.round(this._dtable_offset_height/this._settings.rowHeight) : 1;
|
|
//get new selection row
|
|
if (mode == "up" || mode == "pgup") index-=step;
|
|
else if (mode == "down" || mode == "pgdown") index+=step;
|
|
//check that we in valid row range
|
|
if (index <0) index=0;
|
|
if (index >=this.data.order.length) index=this.data.order.length-1;
|
|
|
|
row = this.getIdByIndex(index);
|
|
if (!row && this._settings.pager)
|
|
this.showItemByIndex(index);
|
|
}
|
|
} else if (mode == "right" || mode == "left"){
|
|
if (column && this.config.select != "row"){
|
|
//it seems column's can be selected
|
|
var index = this.getColumnIndex(column);
|
|
//get new selected column
|
|
if (mode == "right") index++;
|
|
else if (mode == "left") index--;
|
|
//check that result column index is in valid range
|
|
if (index<0) index = 0;
|
|
if (index>=this.config.columns.length) index = this.config.columns.length-1;
|
|
|
|
column = this.columnId(index);
|
|
} else if ((this.open || this._subViewStorage) && mode == "right"){
|
|
return this.open ? this.open(row) : this.openSub(row);
|
|
} else if ((this.close || this._subViewStorage) && mode == "left"){
|
|
return this.close ? this.close(row) : this.closeSub(row);
|
|
}
|
|
} else {
|
|
webix.assert(false, "Not supported selection moving mode");
|
|
return;
|
|
}
|
|
|
|
if (row){
|
|
this.showCell(row, column);
|
|
|
|
if(!this.select){ //switch on cell or row selection by default
|
|
webix.extend(this, this._selections._commonselect, true);
|
|
this._settings.select = (this.open || this._subViewStorage?"row":"cell");
|
|
webix.extend(this, this._selections[this._settings.select], true);
|
|
}
|
|
|
|
var cell = { row:row, column:column };
|
|
|
|
if(preserve && this._settings.select == "area"){
|
|
var last = this._selected_areas[this._selected_areas.length-1];
|
|
this._extendAreaRange(cell, last, mode, details);
|
|
}
|
|
else
|
|
this._select(cell, preserve);
|
|
|
|
if(!this._settings.clipboard && focus !==false){
|
|
var node = this.getItemNode(cell);
|
|
if(node) node.focus();
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
});
|
|
webix.extend(webix.ui.datatable, webix.KeysNavigation);
|
|
|
|
|
|
|
|
|
|
webix.extend(webix.ui.datatable,webix.DataMove);
|
|
webix.extend(webix.ui.datatable, {
|
|
drag_setter:function(value){
|
|
// disable drag-n-drop for frozen rows
|
|
this.attachEvent("onBeforeDrag", function(context){
|
|
return this._checkDragTopSplit(context.source);
|
|
});
|
|
this.attachEvent("onBeforeDragIn", function(context){
|
|
return this._checkDragTopSplit(context.target);
|
|
});
|
|
this.attachEvent("onBeforeDropOrder", function(startId, index){
|
|
return index<0 || index >= this._settings.topSplit;
|
|
});
|
|
|
|
return webix.DragItem.drag_setter.call(this,value);
|
|
},
|
|
_checkDragTopSplit: function(ids){
|
|
var i, index,
|
|
frozen = false;
|
|
if(this._settings.topSplit && ids){
|
|
if(!webix.isArray(ids))
|
|
ids = [ids];
|
|
for(i=0; !frozen && i< ids.length;i++ ){
|
|
index = this.getIndexById(ids[i]);
|
|
frozen = index < this._settings.topSplit;
|
|
}
|
|
}
|
|
return !frozen;
|
|
},
|
|
$dragHTML:function(item, e){
|
|
var width = this._content_width - this._scrollSizeY;
|
|
var html="<div class='webix_dd_drag' style='width:"+(width-2)+"px;'>";
|
|
var cols = this._settings.columns;
|
|
for (var i=0; i<cols.length; i++){
|
|
var value = this._getValue(item, cols[i]);
|
|
html += "<div style='width:"+cols[i].width+"px;'>"+value+"</div>";
|
|
}
|
|
return html+"</div>";
|
|
},
|
|
getHeaderNode:function(column_id, row_index){
|
|
return this._getHeaderNode(column_id, row_index, this._header);
|
|
},
|
|
getFooterNode:function(column_id, row_index){
|
|
return this._getHeaderNode(column_id, row_index, this._footer);
|
|
},
|
|
_getHeaderNode:function(column_id, row_index, group){
|
|
if(this.isColumnVisible(column_id)){
|
|
|
|
var ind = this.getColumnIndex(column_id);
|
|
var hind = this._settings.leftSplit > ind ? 0 : (this._rightSplit <=ind ? 2 :1 );
|
|
row_index = row_index || 0;
|
|
|
|
var rows = group.childNodes[hind].getElementsByTagName("TR");
|
|
if(rows.length){
|
|
var nodes = rows[row_index+1].childNodes;
|
|
for (var i=0; i<nodes.length; i++)
|
|
if (nodes[i].getAttribute("column") == ind)
|
|
return nodes[i].firstChild;
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
getItemNode:function(id, e){
|
|
if (id && !id.header){
|
|
var row = id.row || id;
|
|
var rowindex = this.getIndexById(row);
|
|
var state = this._get_y_range();
|
|
var minRow = state[0]-this._settings.topSplit;
|
|
//row not visible
|
|
if (rowindex < minRow && rowindex > state[1]) return;
|
|
|
|
//get visible column
|
|
var x_range = this._get_x_range();
|
|
var colindex = this._settings.leftSplit ? 0 : x_range[0];
|
|
if (id.column){
|
|
colindex = this.getColumnIndex(id.column);
|
|
//column not visible
|
|
if (colindex < this._rightSplit && colindex >= this._settings.leftSplit && ( colindex<x_range[0] || colindex > x_range[1]))
|
|
return;
|
|
}
|
|
|
|
var column = this._settings.columns[colindex];
|
|
|
|
if (column.attached && column.node){
|
|
var nodeIndex = rowindex < this._settings.topSplit?rowindex:(rowindex-minRow);
|
|
return column.node.childNodes[nodeIndex];
|
|
}
|
|
|
|
}
|
|
},
|
|
dragColumn_setter:function(value){
|
|
var control; //will be defined below
|
|
if (value == "order"){
|
|
control = {
|
|
$drag:webix.bind(function(s,e){
|
|
var id = this.locate(e);
|
|
if (this._rs_process || !id || !this.callEvent("onBeforeColumnDrag", [id.column, e])) return false;
|
|
webix.DragControl._drag_context = { from:control, start:id, custom:"column_dnd" };
|
|
|
|
var column = this.getColumnConfig(id.column);
|
|
|
|
this._relative_column_drag = webix.html.posRelative(e);
|
|
this._limit_column_drag = column.width;
|
|
|
|
return "<div class='webix_dd_drag_column' style='width:"+column.width+"px'>"+(column.header[0].text||" ")+"</div>";
|
|
}, this),
|
|
$dragPos:webix.bind(function(pos, e, node){
|
|
var context = webix.DragControl.getContext();
|
|
var box = webix.html.offset(this.$view);
|
|
node.style.display = 'none';
|
|
var html = document.elementFromPoint(pos.x, box.y+1);
|
|
|
|
var id = (html?this.locate(html):null);
|
|
|
|
var start = webix.DragControl.getContext().start.column;
|
|
|
|
if (id && id.column != start && (!this._column_dnd_temp_block || id.column != this._last_sort_dnd_node )){
|
|
//ignore normal dnd , and dnd from other components
|
|
if (context.custom == "column_dnd" && webix.$$(html) == this){
|
|
|
|
if (!this.callEvent("onBeforeColumnDropOrder",[start, id.column,e])) return;
|
|
|
|
var start_index = this.getColumnIndex(start);
|
|
var end_index = this.getColumnIndex(id.column);
|
|
|
|
//on touch devices we need to preserve drag-start element till the end of dnd
|
|
if(e.touches){
|
|
this._dragTarget = e.target;
|
|
this._dragTarget.style.display = "none";
|
|
this.$view.parentNode.appendChild(this._dragTarget);
|
|
}
|
|
|
|
this.moveColumn(start, end_index+(start_index<end_index?1:0));
|
|
this._last_sort_dnd_node = id.column;
|
|
this._column_dnd_temp_block = true;
|
|
}
|
|
} if (id && id.column == start){
|
|
//flag prevent flickering just after column move
|
|
this._column_dnd_temp_block = false;
|
|
}
|
|
|
|
node.style.display = 'block';
|
|
|
|
pos.x = pos.x - this._relative_column_drag.x;
|
|
pos.y = box.y;
|
|
|
|
if (pos.x < box.x)
|
|
pos.x = box.x;
|
|
else {
|
|
var max = box.x + this.$view.offsetWidth - this._scrollSizeY-this._limit_column_drag;
|
|
if (pos.x > max)
|
|
pos.x = max;
|
|
}
|
|
webix.DragControl._skip = true;
|
|
|
|
}, this),
|
|
$dragDestroy:webix.bind(function(a, node){
|
|
webix.html.remove(node);
|
|
//clean dnd source element
|
|
if(this._dragTarget)
|
|
webix.html.remove(this._dragTarget);
|
|
var id = webix.DragControl.getContext().start;
|
|
this.callEvent("onAfterColumnDropOrder",[id.column, this._last_sort_dnd_node, a]);
|
|
}, this),
|
|
$drop: function(){}
|
|
};
|
|
} else if (value) {
|
|
control = {
|
|
_inner_drag_only:true,
|
|
$drag:webix.bind(function(s,e){
|
|
var id = this.locate(e);
|
|
if (this._rs_process || !id || !this.callEvent("onBeforeColumnDrag", [id.column, e])) return false;
|
|
webix.DragControl._drag_context = { from:control, start:id, custom:"column_dnd" };
|
|
|
|
var header = this.getColumnConfig(id.column).header;
|
|
var text = " ";
|
|
for (var i = 0; i < header.length; i++)
|
|
if (header[i]){
|
|
text = header[i].text;
|
|
break;
|
|
}
|
|
|
|
return "<div class='webix_dd_drag_column'>"+text+"</div>";
|
|
}, this),
|
|
$drop:webix.bind(function(s,t,e){
|
|
var target = e;
|
|
//on touch devices event doesn't point to the actual drop target
|
|
if(e.touches && this._drag_column_last)
|
|
target = this._drag_column_last;
|
|
|
|
var id = this.locate(target);
|
|
|
|
if (!id) return false;
|
|
var start = webix.DragControl.getContext().start.column;
|
|
if (start != id.column){
|
|
if (!this.callEvent("onBeforeColumnDrop",[start, id.column ,e])) return;
|
|
var start_index = this.getColumnIndex(start);
|
|
var end_index = this.getColumnIndex(id.column);
|
|
|
|
this.moveColumn(start, end_index+(start_index<end_index?1:0));
|
|
this.callEvent("onAfterColumnDrop",[start, id.column, e]);
|
|
}
|
|
}, this),
|
|
$dragIn:webix.bind(function(s,t,e){
|
|
var context = webix.DragControl.getContext();
|
|
//ignore normal dnd , and dnd from other components
|
|
|
|
if (context.custom != "column_dnd" || context.from != control) return false;
|
|
|
|
var target = (e.target||e.srcElement);
|
|
while ((target.className||"").indexOf("webix_hcell") == -1){
|
|
target = target.parentNode;
|
|
if (!target) return;
|
|
}
|
|
|
|
if (target != this._drag_column_last){ //new target
|
|
if (this._drag_column_last)
|
|
webix.html.removeCss(this._drag_column_last, "webix_dd_over_column");
|
|
webix.html.addCss(target, "webix_dd_over_column");
|
|
}
|
|
|
|
return (this._drag_column_last = target);
|
|
}, this),
|
|
$dragDestroy:webix.bind(function(a,h){
|
|
if (this._drag_column_last)
|
|
webix.html.removeCss(this._drag_column_last, "webix_dd_over_column");
|
|
webix.html.remove(h);
|
|
}, this)
|
|
};
|
|
}
|
|
|
|
if (value){
|
|
webix.DragControl.addDrag(this._header, control);
|
|
webix.DragControl.addDrop(this._header, control, true);
|
|
}
|
|
}
|
|
});
|
|
webix.extend(webix.ui.datatable,webix.DragItem);
|
|
webix.extend(webix.ui.datatable, {
|
|
clearValidation:function(){
|
|
for(var i in this.data._marks)
|
|
this._clear_invalid_css(i);
|
|
this.data.clearMark("webix_invalid", true);
|
|
},
|
|
_mark_invalid:function(id, details){
|
|
this._clear_invalid_css(id);
|
|
for (var key in details)
|
|
this.addCellCss(id, key, "webix_invalid_cell");
|
|
|
|
this.addCss(id, "webix_invalid");
|
|
},
|
|
_clear_invalid:function(id){
|
|
this._clear_invalid_css(id);
|
|
this.removeCss(id, "webix_invalid");
|
|
},
|
|
_clear_invalid_css:function(id){
|
|
var item = this.getItem(id);
|
|
var mark = this.data.getMark(id, "$cellCss");
|
|
if (mark){
|
|
for (var key in mark)
|
|
mark[key] = mark[key].replace("webix_invalid_cell", "").replace(" "," ");
|
|
}
|
|
},
|
|
|
|
addRowCss:function(id, css, silent){
|
|
this.addCss(id, css, silent);
|
|
},
|
|
removeRowCss:function(id, css, silent){
|
|
this.removeCss(id, css, silent);
|
|
},
|
|
addCellCss:function(id, name, css, silent){
|
|
var mark = this.data.getMark(id, "$cellCss");
|
|
var newmark = mark || {};
|
|
|
|
var style = newmark[name]||"";
|
|
newmark[name] = style.replace(css, "").replace(" "," ")+" "+css;
|
|
|
|
if (!mark) this.data.addMark(id, "$cellCss", false, newmark, true);
|
|
if (!silent)
|
|
this.refresh(id);
|
|
},
|
|
removeCellCss:function(id, name, css, silent){
|
|
var mark = this.data.getMark(id, "$cellCss");
|
|
if (mark){
|
|
var style = mark[name]||"";
|
|
if (style)
|
|
mark[name] = style.replace(css, "").replace(" "," ");
|
|
if (!silent)
|
|
this.refresh(id);
|
|
}
|
|
}
|
|
});
|
|
webix.extend(webix.ui.datatable, webix.ValidateCollection);
|
|
|
|
(function(){
|
|
function getData(data){
|
|
var values = [];
|
|
for (var i = data.length - 1; i >= 0; i--) {
|
|
var value = data[i];
|
|
values[i] = (typeof value === "object" ? value.value : value);
|
|
}
|
|
return values;
|
|
}
|
|
|
|
var SLines = webix.Sparklines = function(){};
|
|
SLines.types ={};
|
|
|
|
SLines.getTemplate = function(customConfig){
|
|
var config = customConfig||{};
|
|
if(typeof customConfig == "string")
|
|
config = { type: customConfig };
|
|
|
|
webix.extend(config,{ type:"line" });
|
|
|
|
var slConstructor = this.types[config.type];
|
|
webix.assert(slConstructor,"Unknown sparkline type");
|
|
return webix.bind(this._template, new slConstructor(config));
|
|
};
|
|
|
|
SLines._template = function(item, common, data, column){
|
|
if (column)
|
|
return this.draw(getData(data), column.width, 33);
|
|
else
|
|
return this.draw(item.data || item, common.width, common.height);
|
|
};
|
|
})();
|
|
|
|
// add "sparklines" type
|
|
webix.attachEvent("onDataTable", function(table){
|
|
table.type.sparklines = webix.Sparklines.getTemplate();
|
|
});
|
|
|
|
(function(){
|
|
function setOpacity(color,opacity){
|
|
color = webix.color.toRgb(color);
|
|
color.push(opacity);
|
|
return "rgba("+color.join(",")+")";
|
|
}
|
|
|
|
function joinAttributes(attrs){
|
|
var result = ' ';
|
|
if(attrs)
|
|
for(var a in attrs)
|
|
result += a+'=\"'+attrs[a]+'\" ';
|
|
return result;
|
|
}
|
|
// SVG
|
|
var SVG = {};
|
|
|
|
SVG.draw = function(content, width, height, css){
|
|
var attrs = {
|
|
xmlns: 'http://www.w3.org/2000/svg',
|
|
version: '1.1',
|
|
height: '100%',
|
|
width: '100%',
|
|
viewBox: '0 0 '+width+' '+height,
|
|
"class": css||""
|
|
};
|
|
return '<svg '+joinAttributes(attrs)+'>'+content+'</svg>';
|
|
};
|
|
SVG.styleMap = {
|
|
"lineColor": "stroke",
|
|
"color": "fill"
|
|
};
|
|
SVG.group = function(path){
|
|
return "<g>"+path+"</g>";
|
|
};
|
|
SVG._handlers = {
|
|
// MoveTo: {x:px,y:py}
|
|
"M": function(p){
|
|
return " M "+ p.x+" "+ p.y;
|
|
},
|
|
// LineTo: {x:px,y:py}
|
|
"L": function(p){
|
|
return " L "+ p.x+" "+ p.y;
|
|
},
|
|
// Curve: 3 points {x:px,y:py}: two control points and an end point
|
|
"C": function(cp0, cp1, p){
|
|
return " C "+cp0.x + " "+cp0.y+" "+cp1.x + " "+cp1.y+" "+p.x + " "+p.y;
|
|
},
|
|
// Arc: center point {x:px,y:py}, radius, angle0, angle1
|
|
"A": function(p, radius, angle0, angle1){
|
|
var x = p.x+Math.cos(angle1)*radius;
|
|
var y = p.y+Math.sin(angle1)*radius;
|
|
var bigCircle = angle1-angle0 >= Math.PI;
|
|
return " A "+radius+" "+radius+" 0 "+(bigCircle?1:0)+" 1 "+x+" "+y;
|
|
}
|
|
};
|
|
// points is an array of an array with two elements: {string} line type, {array}
|
|
SVG.definePath = function(points, close){
|
|
var path = "";
|
|
for(var i =0; i < points.length; i++){
|
|
webix.assert(points[i][0]&&typeof points[i][0] == "string", "Path type must be a string");
|
|
var type = (points[i][0]).toUpperCase();
|
|
webix.assert(this._handlers[type], "Incorrect path type");
|
|
path += this._handlers[type].apply(this,points[i].slice(1));
|
|
|
|
}
|
|
if(close)
|
|
path += " Z";
|
|
|
|
return path;
|
|
};
|
|
SVG._linePoints = function(points){
|
|
var result = [];
|
|
for(var i = 0; i< points.length; i++){
|
|
result.push([i?"L":"M",points[i]]);
|
|
}
|
|
return result;
|
|
};
|
|
SVG.setOpacity = function(color,opacity){
|
|
color = webix.color.toRgb(color);
|
|
color.push(opacity);
|
|
return "rgba("+color.join(",")+")";
|
|
};
|
|
SVG._curvePoints = function(points){
|
|
var result = [];
|
|
for(var i = 0; i< points.length; i++){
|
|
var p = points[i];
|
|
if(!i){
|
|
result.push(["M",p[0]]);
|
|
}
|
|
result.push(["C",p[1],p[2],p[3]]);
|
|
}
|
|
return result;
|
|
};
|
|
SVG.getPath = function(path, css, attrs){
|
|
attrs = joinAttributes(attrs);
|
|
return '<path class="'+css+'" vector-effect="non-scaling-stroke" d="'+path+'" '+attrs+'/>';
|
|
};
|
|
SVG.getSector = function(p, radius, angle0, angle1, css, attrs){
|
|
attrs = joinAttributes(attrs);
|
|
var x0 = p.x+Math.cos(angle0)*radius;
|
|
var y0 = p.y+Math.sin(angle0)*radius;
|
|
var lines = [
|
|
["M",p],
|
|
["L",{x:x0, y:y0}],
|
|
["A", p,radius,angle0,angle1],
|
|
["L",p]
|
|
];
|
|
|
|
|
|
return '<path class="'+css+'" vector-effect="non-scaling-stroke" d="'+SVG.definePath(lines,true)+'" '+attrs+'/>';
|
|
};
|
|
SVG.getCurve = function(points,css, attrs){
|
|
attrs = joinAttributes(attrs);
|
|
var path = this.definePath(this._curvePoints(points));
|
|
return '<path fill="none" class="'+css+'" vector-effect="non-scaling-stroke" d="'+path+'" '+attrs+'/>';
|
|
};
|
|
SVG.getLine = function(p0,p1,css, attrs){
|
|
return this.getPath(this.definePath(this._linePoints([p0,p1]),true),css,attrs);
|
|
};
|
|
SVG.getCircle = function(p, radius, css, attrs){
|
|
attrs = joinAttributes(attrs);
|
|
return '<circle class="'+css+'" cx="'+ p.x+'" cy="'+ p.y+'" r="'+radius+'" '+attrs+'/>';
|
|
};
|
|
SVG.getRect = function(x, y, width, height, css, attrs){
|
|
attrs = joinAttributes(attrs);
|
|
return '<rect class="'+css+'" rx="0" ry="0" x="'+x+'" y="'+y+'" width="'+width+'" height="'+height+'" '+attrs+'/>';
|
|
};
|
|
webix._SVG = SVG;
|
|
})();
|
|
(function(){
|
|
var defaults = {
|
|
paddingX: 3,
|
|
paddingY: 4,
|
|
radius: 1,
|
|
minHeight: 4,
|
|
eventRadius: 8
|
|
};
|
|
|
|
function Area(config){
|
|
this.config = webix.extend(webix.copy(defaults),config||{},true);
|
|
}
|
|
|
|
Area.prototype.draw = function(data, width, height){
|
|
var eventRadius, graph, path, points, styles,
|
|
config = this.config,
|
|
Line = webix.Sparklines.types.line.prototype,
|
|
renderer = webix._SVG;
|
|
|
|
// draw area
|
|
points = this.getPoints(data, width, height);
|
|
path = renderer.definePath(Line._getLinePoints(points),true);
|
|
|
|
if(config.color)
|
|
styles = this._applyColor(renderer,config.color);
|
|
|
|
graph = renderer.group(renderer.getPath(path,'webix_sparklines_area'+(styles?' '+styles.area:'')));
|
|
// draw line
|
|
points.splice(points.length - 3, 3);
|
|
path = renderer.definePath(Line._getLinePoints(points));
|
|
graph += renderer.group(renderer.getPath(path,'webix_sparklines_line'+(styles?' '+styles.line:'')));
|
|
// draw items
|
|
graph += Line._drawItems(renderer, points, config.radius, 'webix_sparklines_item'+(styles?' '+styles.item:''));
|
|
// draw event areas
|
|
eventRadius = Math.min(data.length?(width-2*(config.paddingX||0))/data.length:0,config.eventRadius);
|
|
graph += Line._drawEventItems(renderer, points, eventRadius);
|
|
return renderer.draw(graph, width, height, 'webix_sparklines_area_chart'+(config.css?' '+config.css:''));
|
|
};
|
|
Area.prototype._applyColor = function(renderer,color){
|
|
var config = {'area': {}, 'line':{},'item':{}},
|
|
map = renderer.styleMap;
|
|
if(color){
|
|
config.area[map.color] = renderer.setOpacity(color,0.2);
|
|
config.line[map.lineColor] = color;
|
|
config.item[map.color] = color;
|
|
for(var name in config)
|
|
config[name] = webix.html.createCss(config[name]);
|
|
}
|
|
|
|
return config;
|
|
};
|
|
Area.prototype.getPoints = function(data, width, height){
|
|
var Line = webix.Sparklines.types.line.prototype;
|
|
var points =Line.getPoints.call(this, data, width, height);
|
|
var x = this.config.paddingX || 0;
|
|
var y = this.config.paddingY || 0;
|
|
points.push({x: width - x, y: height - y},{x: x, y: height - y},{x: x, y: points[0].y});
|
|
return points;
|
|
};
|
|
webix.Sparklines.types["area"]=Area;
|
|
})();
|
|
(function(){
|
|
var defaults = {
|
|
paddingX: 3,
|
|
paddingY: 4,
|
|
width: 20,
|
|
margin: 4,
|
|
minHeight: 4,
|
|
eventRadius: 8,
|
|
origin:0,
|
|
itemCss: function(value){return value < (this.config.origin||0)?" webix_sparklines_bar_negative":"";}
|
|
};
|
|
function Bar(config){
|
|
this.config = webix.extend(webix.copy(defaults),config||{},true);
|
|
}
|
|
|
|
Bar.prototype.draw = function(data, width, height){
|
|
var i, css, p, y, padding,
|
|
config = this.config,
|
|
graph = "", items = [],
|
|
points = this.getPoints(data, width, height),
|
|
renderer = webix._SVG;
|
|
|
|
// draw bars
|
|
for( i = 0; i< points.length; i++){
|
|
css = (typeof config.itemCss == 'function'?config.itemCss.call(this,data[i]):(config.itemCss||''));
|
|
if (config.negativeColor && data[i] < config.origin)
|
|
css += ' '+this._applyColor(renderer,config.negativeColor);
|
|
else if(config.color)
|
|
css += ' '+this._applyColor(renderer,config.color);
|
|
p = points[i];
|
|
items.push(renderer.getRect(p.x, p.y, p.width, p.height,'webix_sparklines_bar '+css));
|
|
}
|
|
graph += renderer.group(items.join(""));
|
|
// origin)
|
|
y = parseInt(this._getOrigin(data, width, height),10)+0.5;
|
|
padding = config.paddingX||0;
|
|
graph += renderer.group(renderer.getLine({x:padding, y: y},{x: width-padding, y: y},'webix_sparklines_origin'));
|
|
|
|
// event areas
|
|
var evPoints = this._getEventPoints(data, width, height);
|
|
var evItems = [];
|
|
for( i = 0; i< evPoints.length; i++){
|
|
p = evPoints[i];
|
|
evItems.push(renderer.getRect(p.x, p.y, p.width, p.height,'webix_sparklines_event_area ',{"webix_area":i}));
|
|
}
|
|
graph += renderer.group(evItems.join(""));
|
|
return renderer.draw(graph, width, height, 'webix_sparklines_bar_chart'+(config.css?' '+config.css:''));
|
|
};
|
|
Bar.prototype._applyColor = function(renderer,color){
|
|
var config = {},
|
|
map = renderer.styleMap;
|
|
if(color)
|
|
config[map.color] = color;
|
|
return webix.html.createCss(config);
|
|
};
|
|
Bar.prototype._getOrigin = function(data, width, height){
|
|
var config = this.config;
|
|
var y = config.paddingY||0;
|
|
height = (height||100)-y*2;
|
|
var pos = y+height;
|
|
if(config.origin !== false){
|
|
var minValue = Math.min.apply(null,data);
|
|
var maxValue = Math.max.apply(null,data);
|
|
var origin = config.origin||-0.000001;
|
|
if(origin >= maxValue){
|
|
pos = y;
|
|
}
|
|
else if(origin > minValue){
|
|
var unitY = height/(maxValue - minValue);
|
|
pos -= unitY*(origin-minValue);
|
|
}
|
|
}
|
|
return pos;
|
|
};
|
|
Bar.prototype._getEventPoints = function(data, width, height){
|
|
var result = [];
|
|
var x = this.config.paddingX||0;
|
|
var y = this.config.paddingY||0;
|
|
width = (width||100)-x*2;
|
|
height = (height||100)-y*2;
|
|
if(data.length){
|
|
var unitX = width/data.length;
|
|
for(var i=0; i < data.length; i++)
|
|
result.push({x: Math.ceil(unitX*i)+x, y: y, height: height, width: unitX});
|
|
}
|
|
return result;
|
|
};
|
|
Bar.prototype.getPoints = function(data, width, height){
|
|
var config = this.config;
|
|
var minValue = Math.min.apply(null,data);
|
|
if (config.origin < minValue)
|
|
minValue = config.origin;
|
|
|
|
var maxValue = Math.max.apply(null,data);
|
|
var result = [];
|
|
var x = config.paddingX;
|
|
var y = config.paddingY;
|
|
var margin = config.margin;
|
|
var barWidth = config.width||20;
|
|
var originY = this._getOrigin(data,width,height);
|
|
width = (width||100)-x*2;
|
|
height = (height||100)-y*2;
|
|
if(data.length){
|
|
var unitX = width/data.length;
|
|
var yNum = config.scale || (maxValue - minValue);
|
|
barWidth = Math.min(unitX-margin,barWidth);
|
|
margin = unitX-barWidth;
|
|
var minHeight = 0;
|
|
var origin = minValue;
|
|
|
|
if(config.origin !== false && config.origin > minValue)
|
|
origin = config.origin||0;
|
|
else
|
|
minHeight = config.minHeight;
|
|
|
|
var unitY = (height-minHeight)/(yNum?yNum:1);
|
|
|
|
for(var i=0; i < data.length; i++){
|
|
var h = Math.ceil(unitY*(data[i]-origin));
|
|
result.push({x: Math.ceil(unitX*i)+x+margin/2, y: originY-(data[i]>=origin?h:0)-minHeight, height: Math.abs(h)+minHeight, width: barWidth});
|
|
}
|
|
|
|
}
|
|
return result;
|
|
};
|
|
webix.Sparklines.types["bar"]=Bar;
|
|
})();
|
|
(function(){
|
|
var defaults = {
|
|
paddingX: 6,
|
|
paddingY: 6,
|
|
radius: 2,
|
|
minHeight: 4,
|
|
eventRadius: 8
|
|
};
|
|
function Line(config){
|
|
this.config = webix.extend(webix.copy(defaults),config||{},true);
|
|
}
|
|
|
|
Line.prototype.draw = function(data, width, height){
|
|
var points = this.getPoints(data, width, height);
|
|
var config = this.config;
|
|
var renderer = webix._SVG;
|
|
var styles = config.color?this._applyColor(renderer,config.color):null;
|
|
// draw line
|
|
var path = renderer.definePath(this._getLinePoints(points));
|
|
var graph = renderer.group(renderer.getPath(path,'webix_sparklines_line'+(styles?' '+styles.line:'')));
|
|
// draw items
|
|
graph += this._drawItems(renderer, points, config.radius, 'webix_sparklines_item'+(styles?' '+styles.item:''));
|
|
// draw event items
|
|
var eventRadius = Math.min(data.length?(width-2*(config.paddingX||0))/data.length:0,config.eventRadius);
|
|
graph += this._drawEventItems(renderer, points, eventRadius);
|
|
return renderer.draw(graph, width, height, "webix_sparklines_line_chart"+(config.css?' '+config.css:''));
|
|
};
|
|
Line.prototype._applyColor = function(renderer,color){
|
|
var config = {'line':{},'item':{}},
|
|
map = renderer.styleMap;
|
|
if(color){
|
|
config.line[map.lineColor] = color;
|
|
config.item[map.color] = color;
|
|
for(var name in config)
|
|
config[name] = webix.html.createCss(config[name]);
|
|
}
|
|
return config;
|
|
};
|
|
Line.prototype._drawItems = function(renderer,points,radius,css,attrs){
|
|
var items = [];
|
|
for(var i = 0; i< points.length; i++){
|
|
items.push(renderer.getCircle(points[i], radius, css,attrs));
|
|
}
|
|
return renderer.group(items.join(""));
|
|
};
|
|
Line.prototype._drawEventItems = function(renderer,points,radius){
|
|
var items = [];
|
|
for(var i = 0; i< points.length; i++){
|
|
items.push(renderer.getCircle(points[i], radius, 'webix_sparklines_event_area', {webix_area:i}));
|
|
}
|
|
return renderer.group(items.join(""));
|
|
};
|
|
|
|
Line.prototype._getLinePoints = function(points){
|
|
var i, type, result =[];
|
|
for( i =0; i< points.length; i++){
|
|
type = i?"L":"M";
|
|
result.push([type,points[i]]);
|
|
}
|
|
return result;
|
|
};
|
|
Line.prototype.getPoints = function(data, width, height) {
|
|
var config = this.config;
|
|
var minValue = Math.min.apply(null,data);
|
|
if (typeof config.origin !== "undefined")
|
|
minValue = Math.min(config.origin, minValue);
|
|
|
|
var maxValue = Math.max.apply(null,data);
|
|
var result = [];
|
|
var x = config.paddingX||0;
|
|
var y = config.paddingY||0;
|
|
width = (width||100)-x*2;
|
|
var minHeight = config.minHeight||0;
|
|
height = (height||100)-y*2;
|
|
if(data.length){
|
|
if(data.length==1)
|
|
result.push({x: width/2+x, y: height/2+x});
|
|
else{
|
|
var unitX = width/(data.length-1);
|
|
var yNum = config.scale || (maxValue - minValue);
|
|
var unitY = (height- minHeight)/(yNum?yNum:1);
|
|
if(!yNum)
|
|
height /= 2;
|
|
for(var i=0; i < data.length; i++){
|
|
result.push({x: Math.ceil(unitX*i)+x, y: height-Math.ceil(unitY*(data[i]-minValue))+y-minHeight});
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
};
|
|
webix.Sparklines.types["line"] = Line;
|
|
})();
|
|
(function(){
|
|
var defaults = {
|
|
paddingY: 2
|
|
};
|
|
|
|
function Pie(config){
|
|
this.config = webix.extend(defaults,config||{},true);
|
|
}
|
|
Pie.prototype._defColorsCursor = 0;
|
|
Pie.prototype._defColors = [
|
|
"#f55b50","#ff6d3f","#ffa521","#ffc927","#ffee54","#d3e153","#9acb61","#63b967",
|
|
"#21a497","#21c5da","#3ea4f5","#5868bf","#7b53c0","#a943ba","#ec3b77","#9eb0b8"
|
|
];
|
|
Pie.prototype._getColor = function(i,data){
|
|
var count = data.length;
|
|
var colorsCount = this._defColors.length;
|
|
if(colorsCount > count){
|
|
if(i){
|
|
if(i < colorsCount - count)
|
|
i = this._defColorsCursor +2;
|
|
else
|
|
i = this._defColorsCursor+1;
|
|
}
|
|
this._defColorsCursor = i;
|
|
}
|
|
else
|
|
i = i%colorsCount;
|
|
return this._defColors[i];
|
|
};
|
|
Pie.prototype.draw = function(data, width, height){
|
|
var attrs, graph, i, sectors,
|
|
config = this.config,
|
|
color = config.color||this._getColor,
|
|
points = this.getAngles(data),
|
|
renderer = webix._SVG,
|
|
y = config.paddingY|| 0,
|
|
// radius
|
|
r = height/2 - y,
|
|
// center
|
|
x0 = width/2, y0 = height/2;
|
|
|
|
// draw sectors
|
|
if(typeof color != "function")
|
|
color = function(){return color;};
|
|
sectors = "";
|
|
for( i =0; i < points.length; i++){
|
|
attrs = {};
|
|
attrs[renderer.styleMap['color']] = color.call(this,i,data,this._context);
|
|
sectors += renderer.getSector({x:x0,y:y0},r,points[i][0],points[i][1],'webix_sparklines_sector', attrs);
|
|
}
|
|
graph = renderer.group(sectors);
|
|
|
|
// draw event areas
|
|
sectors = "";
|
|
for(i =0; i < points.length; i++){
|
|
sectors += renderer.getSector({x:x0,y:y0},r,points[i][0],points[i][1],'webix_sparklines_event_area',{"webix_area":i});
|
|
}
|
|
graph += renderer.group(sectors);
|
|
|
|
return renderer.draw(graph, width, height, 'webix_sparklines_pie_chart'+(config.css?' '+config.css:''));
|
|
};
|
|
Pie.prototype.getAngles = function(data){
|
|
var a0 = -Math.PI/ 2, a1,
|
|
i, result = [];
|
|
|
|
var ratios = this._getRatios(data);
|
|
|
|
for( i =0; i < data.length; i++){
|
|
a1= -Math.PI/2+ratios[i]-0.0001;
|
|
result.push([a0,a1]);
|
|
a0 = a1;
|
|
}
|
|
return result;
|
|
};
|
|
Pie.prototype._getTotalValue = function(data){
|
|
var t=0;
|
|
for(var i = 0; i < data.length;i++)
|
|
t += data[i]*1;
|
|
return t;
|
|
};
|
|
Pie.prototype._getRatios = function(data){
|
|
var i, value,
|
|
ratios = [],
|
|
prevSum = 0,
|
|
totalValue = this._getTotalValue(data);
|
|
for(i = 0; i < data.length;i++){
|
|
value = data[i]*1;
|
|
ratios[i] = Math.PI*2*(totalValue?((value+prevSum)/totalValue):(1/data.length));
|
|
prevSum += value;
|
|
}
|
|
return ratios;
|
|
};
|
|
|
|
webix.Sparklines.types["pie"]=Pie;
|
|
})();
|
|
(function(){
|
|
var defaults = {
|
|
paddingX: 3,
|
|
paddingY: 6,
|
|
radius: 2,
|
|
minHeight: 4,
|
|
eventRadius: 8
|
|
};
|
|
|
|
function Spline(config){
|
|
this.config = webix.extend(webix.copy(defaults),config||{},true);
|
|
}
|
|
|
|
Spline.prototype.draw = function(data, width, height){
|
|
var config = this.config,
|
|
graph = "",
|
|
Line = webix.Sparklines.types.line.prototype,
|
|
points = this.getPoints(data, width, height),
|
|
renderer = webix._SVG,
|
|
styles = config.color?this._applyColor(renderer,config.color):null;
|
|
|
|
// draw spline
|
|
graph += renderer.group(renderer.getCurve(points, 'webix_sparklines_line'+(styles?' '+styles.line:'')));
|
|
|
|
var linePoints = Line.getPoints.call(this,data, width, height);
|
|
// draw items
|
|
graph += Line._drawItems(renderer, linePoints, config.radius, 'webix_sparklines_item'+(styles?' '+styles.item:''));
|
|
// draw event items
|
|
var eventRadius = Math.min(data.length?(width-2*(config.paddingX||0))/data.length:0,config.eventRadius);
|
|
graph += Line._drawEventItems(renderer, linePoints, eventRadius);
|
|
return renderer.draw(graph, width, height,"webix_sparklines_line_chart"+(config.css?' '+config.css:''));
|
|
};
|
|
Spline.prototype._applyColor = function(renderer,color){
|
|
var config = {'line':{},'item':{}},
|
|
map = renderer.styleMap;
|
|
if(color){
|
|
config.line[map.lineColor] = color;
|
|
config.item[map.color] = color;
|
|
for(var name in config)
|
|
config[name] = webix.html.createCss(config[name]);
|
|
}
|
|
return config;
|
|
};
|
|
Spline.prototype.getPoints = function(data, width, height){
|
|
var i, points, px, py,
|
|
result = [], x = [], y =[],
|
|
Line = webix.Sparklines.types.line.prototype;
|
|
|
|
points = Line.getPoints.call(this, data, width, height);
|
|
|
|
for(i = 0; i< points.length; i++){
|
|
x.push(points[i].x);
|
|
y.push(points[i].y);
|
|
}
|
|
px = this._getControlPoints(x);
|
|
py = this._getControlPoints(y);
|
|
/*updates path settings, the browser will draw the new spline*/
|
|
for ( i=0;i<points.length-1;i++){
|
|
result.push([points[i],{x:px[0][i],y:py[0][i]},{x:px[1][i],y:py[1][i]},points[i+1]]);
|
|
}
|
|
return result;
|
|
|
|
};
|
|
/* code from https://www.particleincell.com/2012/bezier-splines/ */
|
|
Spline.prototype._getControlPoints = function(points){
|
|
var a=[], b=[], c=[], r=[], p1=[], p2=[],
|
|
i, m, n = points.length-1;
|
|
|
|
a[0]=0;
|
|
b[0]=2;
|
|
c[0]=1;
|
|
r[0] = points[0] + 2*points[1];
|
|
|
|
for (i = 1; i < n - 1; i++){
|
|
a[i]=1;
|
|
b[i]=4;
|
|
c[i]=1;
|
|
r[i] = 4 * points[i] + 2 * points[i+1];
|
|
}
|
|
|
|
a[n-1]=2;
|
|
b[n-1]=7;
|
|
c[n-1]=0;
|
|
r[n-1] = 8*points[n-1]+points[n];
|
|
|
|
for (i = 1; i < n; i++){
|
|
m = a[i]/b[i-1];
|
|
b[i] = b[i] - m * c[i - 1];
|
|
r[i] = r[i] - m*r[i-1];
|
|
}
|
|
|
|
p1[n-1] = r[n-1]/b[n-1];
|
|
for (i = n - 2; i >= 0; --i)
|
|
p1[i] = (r[i] - c[i] * p1[i+1]) / b[i];
|
|
|
|
for (i=0;i<n-1;i++)
|
|
p2[i]=2*points[i+1]-p1[i+1];
|
|
|
|
p2[n-1]=0.5*(points[n]+p1[n-1]);
|
|
|
|
return [p1, p2];
|
|
};
|
|
|
|
webix.Sparklines.types["spline"] = Spline;
|
|
|
|
var defaultsArea = {
|
|
paddingX: 3,
|
|
paddingY: 6,
|
|
radius: 1,
|
|
minHeight: 4,
|
|
eventRadius: 8
|
|
};
|
|
// spline area
|
|
function SplineArea(config){
|
|
this.config = webix.extend(webix.copy(defaultsArea),config||{},true);
|
|
}
|
|
SplineArea.prototype = webix.copy(Spline.prototype);
|
|
SplineArea.prototype.draw = function(data, width, height){
|
|
var config = this.config,
|
|
Line = webix.Sparklines.types.line.prototype,
|
|
renderer = webix._SVG,
|
|
styles = config.color?this._applyColor(renderer,config.color):null;
|
|
|
|
var points = this.getPoints(data, width, height);
|
|
// draw area
|
|
var linePoints = points.splice(points.length - 3, 3);
|
|
var linePath = renderer._linePoints(linePoints);
|
|
linePath[0][0] = "L";
|
|
var areaPoints = renderer._curvePoints(points).concat(linePath);
|
|
var graph = renderer.group(renderer.getPath(renderer.definePath(areaPoints),'webix_sparklines_area'+(styles?' '+styles.area:''), true));
|
|
// draw line
|
|
graph += renderer.group(renderer.getPath(renderer.definePath(renderer._curvePoints(points)),'webix_sparklines_line'+(styles?' '+styles.line:'')));
|
|
|
|
var itemPoints = Line.getPoints.call(this,data, width, height);
|
|
// draw items
|
|
graph += Line._drawItems(renderer, itemPoints, config.radius, 'webix_sparklines_item'+(styles?' '+styles.item:''));
|
|
// draw event items
|
|
var eventRadius = Math.min(data.length?(width-2*(config.paddingX||0))/data.length:0,config.eventRadius);
|
|
graph += Line._drawEventItems(renderer, itemPoints, eventRadius);
|
|
return renderer.draw(graph, width, height, "webix_sparklines_splinearea_chart"+(config.css?' '+config.css:''));
|
|
};
|
|
SplineArea.prototype._applyColor = function(renderer,color){
|
|
var config = {'area': {}, 'line':{},'item':{}},
|
|
map = renderer.styleMap;
|
|
if(color){
|
|
config.area[map.color] = renderer.setOpacity(color,0.2);
|
|
config.line[map.lineColor] = color;
|
|
config.item[map.color] = color;
|
|
for(var name in config)
|
|
config[name] = webix.html.createCss(config[name]);
|
|
}
|
|
return config;
|
|
};
|
|
SplineArea.prototype.getPoints = function(data, width, height){
|
|
var points = Spline.prototype.getPoints.call(this, data, width, height);
|
|
var x = this.config.paddingX || 0;
|
|
var y = this.config.paddingY || 0;
|
|
points.push({x: width - x, y: height - y},{x: x, y: height - y},{x: x, y: points[0][0].y});
|
|
return points;
|
|
};
|
|
webix.Sparklines.types["splineArea"] = SplineArea;
|
|
})();
|
|
|
|
|
|
|
|
webix.extend(webix.ui.datatable, {
|
|
_prePrint:function(options, htmlOnly){
|
|
if(options.scroll && !htmlOnly) return true;
|
|
|
|
options.header = webix.isUndefined(options.header)?(this.config.header?true:false):options.header;
|
|
options.footer = webix.isUndefined(options.footer)?(this.config.footer?true:false):options.footer;
|
|
options.xCorrection = options.xCorrection || 0; //spreadsheet
|
|
},
|
|
_findIndex:function(arr, func){
|
|
var result = -1;
|
|
for(var i =0; result<0 && i < arr.length; i++){
|
|
if(func(arr[i]))
|
|
result = i;
|
|
}
|
|
return result;
|
|
},
|
|
_getTableHeader:function(base, columns, group){
|
|
|
|
var spans = {}, start = 0;
|
|
|
|
base.forEach(webix.bind(function(tableArray, tid){
|
|
var row = tableArray[0], headerArray = [], length = row.length;
|
|
|
|
row.forEach(webix.bind(function(cell, cid){
|
|
var column = columns[cid+start];
|
|
|
|
for(var h = 0; h< column[group].length; h++){
|
|
var header = column[group][h];
|
|
|
|
if(!header && !(spans[tid] && spans[tid][h])) continue;
|
|
|
|
header = webix.copy(header || {text:""});
|
|
|
|
if(spans[tid] && spans[tid][h] && cid ===0){
|
|
header.colspan = spans[tid][h];
|
|
spans[tid][h] = 0;
|
|
}
|
|
|
|
if(header.colspan){
|
|
var colspan = Math.min(header.colspan, (length-cid));
|
|
spans[tid+1] = spans[tid+1] || {};
|
|
spans[tid+1][h] = header.colspan-colspan;
|
|
header.colspan = colspan;
|
|
}
|
|
if(header.rowspan && length === 1){
|
|
header.height = (header.height || this.config.headerRowHeight)*header.rowspan;
|
|
header.rowspan = null;
|
|
}
|
|
|
|
var hcell = {
|
|
txt: header.rotate ? this.getHeaderNode(column.id, h).innerHTML:
|
|
(header.text || (header.contentId?this.getHeaderContent(header.contentId).getValue():"")),
|
|
className:"webix_hcell "+"webix_"+group+"_cell "+(header.css || ""),
|
|
style:{
|
|
height:(header.height || this.config.headerRowHeight)+"px",
|
|
width:header.colspan?"auto":column.width + "px"
|
|
},
|
|
span:(header.colspan || header.rowspan) ? {colspan:header.colspan || 1, rowspan:header.rowspan || 1}:null
|
|
};
|
|
headerArray[h] = headerArray[h] || [];
|
|
headerArray[h][cid] = hcell;
|
|
}
|
|
}, this));
|
|
if(group =="header")
|
|
base[tid] = headerArray.concat(tableArray);
|
|
else
|
|
base[tid] = tableArray.concat(headerArray);
|
|
start+=length;
|
|
}, this));
|
|
|
|
return base;
|
|
},
|
|
_getTableArray:function (options, base, start){
|
|
|
|
var columns = this.config.columns;
|
|
var sel = this.getSelectedId(true);
|
|
var maxWidth = this._getPageWidth(options);
|
|
|
|
var rightRestriction = 0;
|
|
var bottomRestriction = 0;
|
|
var tableArray = [];
|
|
var newTableStart = 0;
|
|
|
|
start = start || (0 + options.xCorrection);
|
|
base = base || [];
|
|
|
|
this.eachRow(webix.bind(function(row){
|
|
var width = 0;
|
|
var rowItem = this.getItem(row);
|
|
var rowIndex = this.getIndexById(row);
|
|
|
|
var colrow = [];
|
|
var datarow = false;
|
|
|
|
for(var c=start; c<columns.length; c++){
|
|
var column = columns[c].id;
|
|
var colIndex = this.getColumnIndex(column)-start;
|
|
|
|
if(columns[c]){
|
|
width += columns[c].width;
|
|
|
|
if(width > maxWidth && c>start){ // 'c>start' ensures that a single long column will have to fit the page
|
|
newTableStart = c; break; }
|
|
|
|
if(options.data !=="selection" || (options.data=="selection" && this._findIndex(sel, function(obj){
|
|
return obj.column == column && obj.row == row;
|
|
})!==-1)){
|
|
|
|
var span = this.getSpan(row, column);
|
|
//check span from previous table
|
|
if(span && this.getColumnIndex(column) === start){
|
|
var spanStart = this.getColumnIndex(span[1]);
|
|
if(spanStart < start){
|
|
span[2] = span[2] - (start-spanStart);
|
|
span[4] = span[4] ? span[4] : (rowItem[span[1]] ? this.getText(row, span[1]) : null);
|
|
span[1] = column;
|
|
}
|
|
}
|
|
|
|
if(!span || (span && span[0] == row && span[1] == column)){
|
|
var cellValue = span && span[4] ? span[4] : (this._columns_pull[column] ? this.getText(row, column) : "");
|
|
var className = this.getCss(row, column)+" "+(columns[c].css || "")+(span? (" webix_dtable_span "+ (span[5] || "")):"" );
|
|
|
|
var style = {
|
|
height:span && span[3] > 1? "auto": ((rowItem.$height || this.config.rowHeight) + "px"),
|
|
width: span && span [2] > 1? "auto": columns[c].width + "px"
|
|
};
|
|
|
|
colrow.push({
|
|
txt: cellValue, className: className, style: style,
|
|
span: (span ? {colspan:span[2], spanStart:this.getColumnIndex(span[1]), rowspan:span[3]}:null)
|
|
});
|
|
|
|
if (cellValue) {
|
|
rightRestriction = Math.max(colIndex+1, rightRestriction);
|
|
bottomRestriction = Math.max(rowIndex+1, bottomRestriction);
|
|
}
|
|
datarow = datarow || !!cellValue;
|
|
}
|
|
else if(span){
|
|
colrow.push({$inspan:true});
|
|
rightRestriction = Math.max(colIndex+1, rightRestriction);
|
|
bottomRestriction = Math.max(rowIndex+1, bottomRestriction);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!options.skiprows || datarow)
|
|
tableArray.push(colrow);
|
|
}, this));
|
|
|
|
if(bottomRestriction && rightRestriction){
|
|
if(options.trim){
|
|
tableArray.length = bottomRestriction;
|
|
tableArray = tableArray.map(function(item){
|
|
for(var i = item.length-1; i>=0; i--){
|
|
if(item[i].span && item[i].span.colspan){
|
|
item[i].span.colspan = Math.min(item[i].span.colspan, item.length-i);
|
|
break;
|
|
}
|
|
}
|
|
item.length = rightRestriction;
|
|
return item;
|
|
});
|
|
}
|
|
base.push(tableArray);
|
|
}
|
|
|
|
if(newTableStart)
|
|
this._getTableArray(options, base, newTableStart);
|
|
else{
|
|
//keep this order as logic relies on the first data row
|
|
if(options.footer)
|
|
base = this._getTableHeader(base, columns, "footer");
|
|
if(options.header)
|
|
base = this._getTableHeader(base, columns, "header");
|
|
}
|
|
|
|
return base;
|
|
},
|
|
_getTableHTML:function(tableData, options){
|
|
|
|
var container = webix.html.create("div");
|
|
|
|
tableData.forEach(webix.bind(function(table, i){
|
|
|
|
var tableHTML = webix.html.create("table", {
|
|
"class":"webix_table_print "+this.$view.className+(options.borderless?" borderless":""),
|
|
"style":"border-collapse:collapse"
|
|
});
|
|
|
|
table.forEach(function(row){
|
|
var tr = webix.html.create("tr");
|
|
|
|
row.forEach(function(cell, i){
|
|
if(!cell.$inspan){
|
|
var td = webix.html.create("td");
|
|
|
|
td.innerHTML = cell.txt;
|
|
td.className = cell.className;
|
|
|
|
for(var key in cell.style)
|
|
td.style[key] = cell.style[key];
|
|
|
|
if(cell.span){
|
|
td.colSpan = cell.span.colspan;
|
|
td.rowSpan = cell.span.rowspan;
|
|
}
|
|
tr.appendChild(td);
|
|
}
|
|
|
|
});
|
|
tableHTML.appendChild(tr);
|
|
});
|
|
container.appendChild(tableHTML);
|
|
|
|
if(i+1 < tableData.length){
|
|
var br = webix.html.create("DIV", {"class":"webix_print_pagebreak"});
|
|
container.appendChild(br);
|
|
}
|
|
|
|
}, this));
|
|
|
|
return container;
|
|
}
|
|
});
|
|
|
|
webix.extend(webix.ui.datatable, webix.CustomPrint);
|
|
webix.extend(webix.ui.datatable, {
|
|
$exportView:function(options){
|
|
if(options.export_mode !=="excel" || options.dataOnly || !options.styles)
|
|
return this;
|
|
else{ //excel export with styles
|
|
options.dataOnly = true;
|
|
options.heights = webix.isUndefined(options.heights)?"all":options.heights;
|
|
|
|
var data = webix.toExcel(this, options);
|
|
data[0].styles = this._getExportStyles(options);
|
|
|
|
delete options.dataOnly;
|
|
return data;
|
|
}
|
|
},
|
|
_getExportStyles:function(options){
|
|
var columns = this.config.columns, styles = [];
|
|
this._style_hash = this._style_hash || {};
|
|
|
|
if(options.docHeader)
|
|
styles = [{ 0:this._getExportDocStyle(options.docHeader.css)},{ 0:{}}];
|
|
if(options.header!==false)
|
|
styles = this._getExportHStyles(options, "header", styles);
|
|
|
|
this.data.each(function(obj){
|
|
var row = {};
|
|
for(var i = 0; i<columns.length; i++){
|
|
var cellCss = this.getCss(obj.id, columns[i].id);
|
|
var columnCss = columns[i].node.className;
|
|
var spanCss = "";
|
|
var span = null;
|
|
var node = null;
|
|
|
|
if(this._spans_pull && (span = this.getSpan(obj.id, columns[i].id))){
|
|
node = this.getSpanNode({row:span[0], column:span[1]});
|
|
spanCss = "webix_dtable_span "+(span[5] || "");
|
|
}
|
|
else
|
|
node = this.getItemNode({row:obj.id, column:columns[i].id});
|
|
|
|
if(!node){
|
|
node = webix.html.create("div", {
|
|
"class":cellCss, style:"visibility:hidden"
|
|
});
|
|
var cnode = columns[i].node;
|
|
if(!columns[i].attached){
|
|
cnode = webix.html.create("div", {
|
|
"class":columnCss, style:"visibility:hidden"
|
|
});
|
|
this._body.appendChild(cnode);
|
|
}
|
|
cnode.appendChild(node);
|
|
}
|
|
row[i] = this._getExportCellStyle(node, [cellCss, columnCss, spanCss].join(":"));
|
|
}
|
|
styles[styles.length] = row;
|
|
}, this);
|
|
|
|
if(options.footer!==false && this.config.footer)
|
|
styles = this._getExportHStyles(options, "footer", styles);
|
|
if(options.docFooter)
|
|
styles = styles.concat([{ 0:{}},{ 0:this._getExportDocStyle(options.docFooter.css)}]);
|
|
|
|
return styles;
|
|
},
|
|
_getExportHStyles:function(options, group, styles){
|
|
var columns = this.config.columns, hs = [];//spans
|
|
|
|
for(var h = 0; h<columns[0][group].length; h++){
|
|
var hrow = {};
|
|
for(var i = 0; i<columns.length; i++){
|
|
var header = columns[i][group][h];
|
|
//ToDo:make sure it is rendered and attached
|
|
if(header){ //can be null
|
|
var cid = header.colspan ? columns[i+header.colspan-1].id : columns[i].id;
|
|
var node = (group == "header"?this.getHeaderNode(cid, h):this.getFooterNode(cid, h));
|
|
if(node){
|
|
var name = [node.parentNode.className, (header.css||""), "webix_hcell", group];
|
|
hrow[i] = this._getExportCellStyle(node, name.join(":"));
|
|
|
|
if(header.colspan || header.rowspan)
|
|
hs.push([h, i, {colspan:header.colspan-1 || 0, rowspan:header.rowspan-1||0}, hrow[i]]);
|
|
}
|
|
}
|
|
else{
|
|
for(var s = 0; s<hs.length; s++){
|
|
var st = hs[s][2], hsc = hs[s][1], hsr = hs[s][0];
|
|
if(hsc+st.colspan >= i && hsr+st.rowspan>=h)
|
|
hrow[i] = hs[s][3];
|
|
}
|
|
}
|
|
}
|
|
styles[styles.length] = hrow;
|
|
}
|
|
return styles;
|
|
},
|
|
_getExportCellStyle:function(node, name){
|
|
if(this._style_hash[name])
|
|
return this._style_hash[name];
|
|
else{
|
|
var base = this._getRules(node);
|
|
var rules = { font:{},alignment:{},border:{}};
|
|
|
|
//font
|
|
rules.font.name = base["font-family"].replace(/,.*$/, ""); // cut off fallback font;
|
|
rules.font.sz = base["font-size"].replace("px", "")*0.75; //px to pt conversion
|
|
rules.font.color = {rgb:webix.color.rgbToHex(base["color"])};
|
|
if(base["font-weight"] !== "normal") rules.font.bold = true;
|
|
if(base["text-decoration-line"] === "underline") rules.font.underline = true;
|
|
if(base["font-style"] === "italic") rules.font.italic = true;
|
|
if(base["text-decoration-line"] === "line-through") rules.font.strike = true;
|
|
|
|
//alignment
|
|
rules.alignment.horizontal = base["text-align"];
|
|
rules.alignment.vertical = base["height"] == base["line-height"]?"center":"top";
|
|
if(base["white-space"] == "normal") rules.alignment.wrapText = true;
|
|
//rotated header
|
|
if(node.firstChild && node.firstChild.className && node.firstChild.className.indexOf("webix_rotate")!==-1)
|
|
rules.alignment.textRotation = 90;
|
|
|
|
//background
|
|
var bg = webix.color.rgbToHex(base["background-color"]);
|
|
if(bg) rules.fill = {fgColor:{rgb:bg}};
|
|
if(base["background-image"].indexOf("gradient")!==-1) //air skins use gradient for header
|
|
rules.fill = {fgColor: {rgb:webix.color.rgbToHex(base["background-image"].substring(base["background-image"].lastIndexOf("(")))}};
|
|
|
|
//borders
|
|
if(node.parentNode && node.parentNode.nodeName =="TD") //borders for header are set for parent td, so we change base rules here
|
|
base = this._getRules(node.parentNode);
|
|
if(base["border-right-width"]!=="0px")
|
|
rules.border.right = { style:"thin", color:{rgb:webix.color.rgbToHex(base["border-right-color"])}};
|
|
if(base["border-bottom-width"]!=="0px")
|
|
rules.border.bottom = { style:"thin", color:{rgb:webix.color.rgbToHex(base["border-bottom-color"])}};
|
|
if(base["border-left-width"]!=="0px")
|
|
rules.border.left = { style:"thin", color:{rgb:webix.color.rgbToHex(base["border-left-color"])}};
|
|
if(base["border-top-width"]!=="0px")
|
|
rules.border.top = { style:"thin", color:{rgb:webix.color.rgbToHex(base["border-top-color"])}};
|
|
|
|
this._style_hash[name] = rules;
|
|
return rules;
|
|
}
|
|
},
|
|
_getExportDocStyle:function(css){
|
|
css = webix.extend(css||{}, {visibility:"hidden", "white-space":"nowrap", "text-align":"left"});
|
|
var cssStr = "";
|
|
for(var i in css) cssStr += (i+":"+css[i]+";");
|
|
|
|
var node = webix.html.create("div", {style:cssStr});
|
|
this._body.appendChild(node);
|
|
var style = this._getExportCellStyle(node, cssStr);
|
|
webix.html.remove(node);
|
|
|
|
return style;
|
|
},
|
|
_getRules:function(node){
|
|
var style = {};
|
|
if(window.getComputedStyle)
|
|
style = window.getComputedStyle(node);
|
|
else
|
|
style = node.currentStyle;
|
|
return style;
|
|
}
|
|
});
|
|
|
|
webix.extend(webix.ui.datatable, {
|
|
topSplit_setter:function(value){
|
|
if (this.data)
|
|
this.data.$freeze = value;
|
|
return value;
|
|
},
|
|
freezeRow:function(id, mode){
|
|
var index,
|
|
freezeLine = this._settings.topSplit,
|
|
order = this.data.order,
|
|
filterOrder = this.data._filter_order;
|
|
|
|
function moveFrozenRow(index, id, mode, order, skipSplitChange){
|
|
var i;
|
|
if (mode && index >= freezeLine){
|
|
if(!skipSplitChange)
|
|
freezeLine++;
|
|
for (i=index; i >= freezeLine; i--){
|
|
order[i] = order[i-1];
|
|
}
|
|
order[freezeLine-1] = id;
|
|
}
|
|
if (!mode && index <freezeLine){
|
|
if(!skipSplitChange)
|
|
freezeLine--;
|
|
for (i=index; i<freezeLine; i++){
|
|
order[i] = order[i+1];
|
|
}
|
|
order[freezeLine] = id;
|
|
}
|
|
}
|
|
|
|
if(id){
|
|
index = this.getIndexById(id);
|
|
id = id.toString();
|
|
moveFrozenRow(index, id, mode, order);
|
|
if(filterOrder)
|
|
moveFrozenRow(filterOrder.find(id), id, mode, filterOrder, true);
|
|
}
|
|
else if(!mode)
|
|
freezeLine = 0; // unfreeze all rows
|
|
|
|
this.define("topSplit", freezeLine);
|
|
this.refresh();
|
|
}
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
webix.TreeTableClick = {};
|
|
|
|
webix.TreeTablePaste = {
|
|
insert: function(data) {
|
|
var parent = this.getSelectedId(true, true);
|
|
for (var i = 0; i < data.length; i++) {
|
|
var item = {};
|
|
for (var j = 0; j < this._settings.columns.length; j++) {
|
|
item[this._settings.columns[j].id] = data[i][j] || "";
|
|
}
|
|
if (!webix.isUndefined(item.id) && this.exists(item.id))
|
|
item.id = webix.uid();
|
|
this.add(item, null, parent[0]);
|
|
}
|
|
}
|
|
};
|
|
|
|
webix.protoUI({
|
|
name:"treetable",
|
|
$init:function(){
|
|
webix.extend(this.data, webix.TreeStore, true);
|
|
webix.extend(this.type, webix.TreeType);
|
|
webix.extend(this, webix.TreeDataMove, true);
|
|
|
|
for (var key in webix.TreeClick)
|
|
if (!this.on_click[key])
|
|
this.on_click[key] = this._unwrap_id(webix.TreeClick[key]);
|
|
|
|
this.type.treetable = webix.template("{common.space()}{common.icon()} {common.folder()}");
|
|
this.type.treecheckbox = function(obj, common){
|
|
if (obj.indeterminate && !obj.nocheckbox)
|
|
return "<div class='webix_tree_checkbox webix_indeterminate'></div>";
|
|
else
|
|
return webix.TreeType.checkbox.apply(this, arguments);
|
|
};
|
|
|
|
this.data.provideApi(this,true);
|
|
|
|
this._viewobj.setAttribute("role", "treegrid");
|
|
|
|
},
|
|
$exportView:function(options){
|
|
webix.extend(options, { filterHTML: true });
|
|
return this;
|
|
},
|
|
_drag_order_complex:false,
|
|
_unwrap_id:function(original){
|
|
return function (e,id){
|
|
id = id.row;
|
|
return original.call(this,e,id);
|
|
};
|
|
},
|
|
getState:function(){
|
|
var state = webix.DataState.getState.call(this);
|
|
webix.extend(state, webix.TreeAPI.getState.call(this));
|
|
return state;
|
|
},
|
|
setState:function(state){
|
|
if (webix.TreeAPI.setState.call(this, state)){
|
|
//run grid-state only when tree component was fully loaded
|
|
webix.DataState.setState.call(this, state);
|
|
}
|
|
},
|
|
clipboard_setter: function(value) {
|
|
webix.extend(this._paste, webix.TreeTablePaste);
|
|
return webix.TablePaste.clipboard_setter.call(this, value);
|
|
},
|
|
_run_load_next:function(conf, direction){
|
|
for (var i=0; i<conf.start; i++){
|
|
var id = this.data.order[i];
|
|
if (id && this.getItem(id).$level != 1)
|
|
conf.start--;
|
|
}
|
|
return webix.ui.datatable.prototype._run_load_next.call(this, conf, direction);
|
|
},
|
|
}, webix.TreeAPI, webix.TreeStateCheckbox, webix.TreeDataLoader, webix.ui.datatable);
|
|
|
|
|
|
webix.extend(webix.ui.datatable, {
|
|
spans_setter:function(value){
|
|
if (value && !this._spans_pull)
|
|
this._init_spans_once();
|
|
|
|
return value;
|
|
},
|
|
_init_spans_once:function(){
|
|
this._spans_pull = {};
|
|
this._spans_areas = [];
|
|
|
|
this.data.attachEvent("onStoreLoad", webix.bind(function(driver, data){
|
|
if (data && data.spans)
|
|
this.addSpan(data.spans);
|
|
}, this));
|
|
this.data.attachEvent("onClearAll", webix.bind(function(){
|
|
this._spans_pull = {};
|
|
}, this));
|
|
|
|
this.attachEvent("onScrollY", this._adjust_spans_xy);
|
|
this.attachEvent("onScrollX", this._adjust_spans_xy);
|
|
this.data.attachEvent("onStoreUpdated", webix.bind(function(id, obj, mode){
|
|
if (mode != "paint" && this._columns.length)
|
|
this._paint_spans();
|
|
}, this));
|
|
this.attachEvent("onStructureLoad", this._paint_spans);
|
|
this.attachEvent("onStructureUpdate", this._paint_spans);
|
|
|
|
this.attachEvent("onColumnResize", this._paint_spans);
|
|
this.attachEvent("onRowResize", this._paint_spans);
|
|
this.attachEvent("onSelectChange", this._paint_spans_selection);
|
|
},
|
|
addSpan:function(id, index, width, height, value, css){
|
|
//accept an array of objects
|
|
if (typeof id == "object"){
|
|
for (var i = 0; i < id.length; i++)
|
|
this.addSpan.apply(this, id[i]);
|
|
return;
|
|
}
|
|
|
|
height = height || 1;
|
|
width = width || 1;
|
|
|
|
if (!this._spans_pull[id])
|
|
this._spans_pull[id] = {};
|
|
|
|
this._spans_pull[id][index] = [width, height, value, css];
|
|
},
|
|
|
|
removeSpan:function(id, index){
|
|
if(!arguments.length)
|
|
this._spans_pull = {};
|
|
|
|
var line = this._spans_pull[id];
|
|
if (line)
|
|
delete line[index];
|
|
},
|
|
getSpan: function(row, column){
|
|
if (!row) return this._spans_pull;
|
|
|
|
var i, iSpan, j, jSpan, span,
|
|
column, row,
|
|
spans = this._spans_pull;
|
|
|
|
i = this.getIndexById(row);
|
|
j = this.getColumnIndex(column);
|
|
|
|
for(row in spans){
|
|
for(column in spans[row]){
|
|
span = spans[row][column];
|
|
iSpan = this.getIndexById(row);
|
|
jSpan = this.getColumnIndex(column);
|
|
if( !(i > iSpan+span[1]-1 || i < iSpan || j > jSpan+span[0]-1|| j < jSpan)){
|
|
return [row,column].concat(span);
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
},
|
|
_paint_spans:function(){
|
|
var area, i, rightNum = this._columns.length - this._settings.rightSplit;
|
|
webix.html.remove(this._spans_areas);
|
|
for (i=0; i<3; i++){
|
|
area = this._spans_areas[i] = webix.html.create("DIV",{ "class" : "webix_span_layer" });
|
|
this._body.childNodes[i].appendChild(area);
|
|
}
|
|
// touch scroll
|
|
this.attachEvent("onSyncScroll", function(x,y,t){
|
|
for (var i=0; i<3; i++) {
|
|
webix.Touch._set_matrix(this._spans_areas[i], (i==1?x:0), y, t);
|
|
}
|
|
});
|
|
|
|
this._adjust_spans_xy();
|
|
|
|
if (this._settings.leftSplit)
|
|
this._paint_spans_area(this._spans_areas[0],0,this._settings.leftSplit);
|
|
if (this._settings.rightSplit)
|
|
this._paint_spans_area(this._spans_areas[2],rightNum,this._columns.length);
|
|
|
|
this._paint_spans_area(this._spans_areas[1],this._settings.leftSplit,rightNum);
|
|
|
|
if(this._settings.topSplit && !webix.env.touch)
|
|
this._paintSpansTop();
|
|
},
|
|
_getSplitSizesX: function(){
|
|
var i = 0, leftWidth=0, centerWidth=0, rightWidth= 0, rightNum;
|
|
|
|
while (i<this._settings.leftSplit){
|
|
leftWidth += this._columns[i].width;
|
|
i++;
|
|
}
|
|
|
|
i = this._columns.length-1;
|
|
rightNum = i-this._settings.rightSplit;
|
|
|
|
while (i>= rightNum){
|
|
rightWidth += this._columns[i].width;
|
|
i--;
|
|
}
|
|
|
|
for(i = this._settings.leftSplit; i < this._columns.length-this._settings.rightSplit; i++)
|
|
centerWidth += this._columns[i].width;
|
|
|
|
return [leftWidth, centerWidth, rightWidth];
|
|
},
|
|
_paintSpansTop: function(){
|
|
var area, i, layerHeight, widths,
|
|
rightNum = this._columns.length - this._settings.rightSplit;
|
|
|
|
for ( i=3; i<6; i++){
|
|
area = this._spans_areas[i] = webix.html.create("DIV",{ "class" : "webix_span_layer_top" });
|
|
this._body.childNodes[i-3].appendChild(area);
|
|
}
|
|
|
|
widths = this._getSplitSizesX();
|
|
|
|
if (this._settings.leftSplit){
|
|
this._spans_areas[3].style.width = widths[0]+"px";
|
|
this._paint_spans_area(this._spans_areas[3],0,this._settings.leftSplit);
|
|
}
|
|
|
|
if (this._settings.rightSplit){
|
|
this._spans_areas[5].style.width = widths[2]+"px";
|
|
this._paint_spans_area(this._spans_areas[5],rightNum,this._columns.length);
|
|
}
|
|
|
|
this._spans_areas[4].style.width = widths[1]+"px";
|
|
this._paint_spans_area(this._spans_areas[4],this._settings.leftSplit, rightNum);
|
|
|
|
layerHeight = 0;
|
|
for( i=0; i < this._settings.topSplit;i++){
|
|
var item = this.getItem(this.getIdByIndex(i));
|
|
if(item)
|
|
layerHeight += this._getRowHeight(item);
|
|
}
|
|
for ( i=3; i<6; i++){
|
|
this._spans_areas[i].style.height = layerHeight+"px";
|
|
}
|
|
},
|
|
_paint_spans_area:function(area, start, end){
|
|
var top = 0;
|
|
var count = this.data.order.length;
|
|
for (var i = 0; i < count; i++) {
|
|
var id = this.data.order[i];
|
|
var line = this._spans_pull[id];
|
|
if (line){
|
|
for (var j = start; j < end; j++){
|
|
var cid = this._columns[j].id;
|
|
if (line[cid])
|
|
this._add_span_to_area(area, i, j, line, top, start, id, cid);
|
|
}
|
|
}
|
|
top += this._getRowHeight(this.getItem(id));
|
|
}
|
|
},
|
|
|
|
_paint_spans_selection:function(){
|
|
var config = this.config.select;
|
|
var cell = (config == "cell" || config == "column");
|
|
|
|
var selected = this.getSelectedId(true);
|
|
var newselected = [];
|
|
var last = this._last_selected || [];
|
|
var id = webix.uid()+"";
|
|
var repaint = false;
|
|
|
|
for (var i = 0; i < selected.length; i++){
|
|
var line = this._spans_pull[selected[i]];
|
|
if (line && (!cell || line[selected[i].column])){
|
|
if (!line.$selected || line.$selected.id != selected[i].id)
|
|
repaint = true;
|
|
line.$selected = selected[i];
|
|
line.$time = id;
|
|
newselected.push(selected[i].id);
|
|
}
|
|
}
|
|
|
|
|
|
for (var i = 0; i < last.length; i++){
|
|
var line = this._spans_pull[last[i]];
|
|
if (line && line.$time !== id){
|
|
delete line.$selected;
|
|
repaint = true;
|
|
}
|
|
}
|
|
|
|
this._last_selected = [].concat(selected);
|
|
if (repaint)
|
|
this._paint_spans();
|
|
},
|
|
|
|
_span_sum_width:function(start, end){
|
|
var summ = 0;
|
|
for (var i = start; i < end; i++){
|
|
var next = this._columns[i];
|
|
summ += next?next.width:0;
|
|
}
|
|
|
|
return summ;
|
|
},
|
|
|
|
_span_sum_height:function(start, end){
|
|
var summ = 0;
|
|
for (var i = start; i < end; i++){
|
|
var next = this.getItem(this.data.order[i]);
|
|
summ += next?this._getRowHeight(next):this._settings.rowHeight;
|
|
}
|
|
|
|
return summ;
|
|
},
|
|
|
|
_add_span_to_area:function(area, ind, cind, config, top, start, id, cid){
|
|
|
|
var line = config[cid];
|
|
var value = line[2] || this.getText(id, cid);
|
|
var selected = "";
|
|
if (config.$selected && (this._settings.select === "row" || config.$selected.column === cid))
|
|
selected = "webix_selected ";
|
|
|
|
var attributes = {
|
|
"column": cind,
|
|
"row" : ind,
|
|
"class" : selected+"webix_cell webix_table_cell webix_dtable_span "+(line[3]||""),
|
|
"aria-colindex":cind+1,
|
|
"aria-rowindex":ind+1
|
|
};
|
|
|
|
if(line[0]>1) attributes["aria-colspan"] = line[0];
|
|
if(line[1]>1) attributes["aria-rowspan"] = line[1];
|
|
|
|
var span = webix.html.create("DIV", attributes, ""+value);
|
|
|
|
span.style.top = top+"px";
|
|
span.style.left = this._span_sum_width(start, cind)+"px";
|
|
span.style.width = this._span_sum_width(cind, cind+line[0])+"px";
|
|
span.style.height = this._span_sum_height(ind, ind+line[1])+"px";
|
|
|
|
area.appendChild(span);
|
|
},
|
|
|
|
_adjust_spans_xy:function(){
|
|
if(!this._settings.prerender){
|
|
var state = this.getScrollState();
|
|
for (var i=0; i<3; i++)
|
|
this._spans_areas[i].style.top = "-"+(state.y||0) +"px";
|
|
}
|
|
},
|
|
_checkCellMerge:function(id0,id1){
|
|
var span0, span1,
|
|
result = false;
|
|
|
|
if(this._spans_pull){
|
|
span0 = this.getSpan(id0.row,id0.column);
|
|
span1 = this.getSpan(id1.row,id1.column);
|
|
if(span0 && span1 && span0[0] == span1[0] && span0[1] == span1[1])
|
|
result = true;
|
|
}
|
|
return result;
|
|
},
|
|
getSpanNode:function(id){
|
|
var areas = this._spans_areas;
|
|
var rind = this.getIndexById(id.row);
|
|
var cind = this.getColumnIndex(id.column);
|
|
|
|
for(var a = 0; a<areas.length; a++){
|
|
var parts = areas[a].childNodes;
|
|
for(var i = 0; i<parts.length; i++){
|
|
if(parts[i].getAttribute("row")==rind && parts[i].getAttribute("column")==cind)
|
|
return parts[i];
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
});
|
|
webix.extend(webix.ui.datatable, {
|
|
subrow_setter:function(value){
|
|
if (value){
|
|
this._init_subrow_once();
|
|
this._settings.fixedRowHeight = false;
|
|
return webix.template(value);
|
|
}
|
|
return false;
|
|
},
|
|
subview_setter:function(value){
|
|
if (value)
|
|
this._settings.subrow = this.subrow_setter("<div></div>");
|
|
return value;
|
|
},
|
|
defaults:{
|
|
subRowHeight:35
|
|
},
|
|
_refresh_sub_all: function(){
|
|
this.data.each(function(obj){
|
|
if (obj)
|
|
obj.$sub = this._settings.subrow(obj, this.type);
|
|
}, this);
|
|
|
|
this._resize_sub_all();
|
|
},
|
|
_resize_sub_all: function(resize){
|
|
if (this._settings.subRowHeight === "auto" && this._content_width)
|
|
this._adjustSubRowHeight();
|
|
if (resize && this._settings.subview){
|
|
for (var key in this._subViewStorage){
|
|
var subview = webix.$$(this._subViewStorage[key]);
|
|
if (!subview._settings.hidden)
|
|
subview.adjust();
|
|
}
|
|
}
|
|
},
|
|
_refresh_sub_one:function(id){
|
|
var obj = this.getItem(id);
|
|
obj.$sub = this._settings.subrow(obj, this.type);
|
|
|
|
if (this._settings.subRowHeight === "auto")
|
|
this._adjustSubRowHeight(obj.id, obj.$sub);
|
|
},
|
|
$init:function(){
|
|
this._init_subrow_once = webix.once(function(){
|
|
var css = "#"+this._top_id +" .webix_cell.webix_dtable_subview { line-height:normal;}";
|
|
//if initial fixedRowHeight is true, preserve white-space for non sub cells
|
|
if(this._settings.fixedRowHeight)
|
|
css += "#"+this._top_id +" .webix_column .webix_cell { white-space: nowrap;}";
|
|
|
|
webix.html.addStyle(css);
|
|
|
|
this._subViewStorage = {};
|
|
this.attachEvent("onSubViewRender", this._render_sub_view);
|
|
this.data.attachEvent("onStoreUpdated", webix.bind(function(id, data, mode){
|
|
if (!id)
|
|
this._refresh_sub_all();
|
|
else if (mode == "update" || mode == "add")
|
|
this._refresh_sub_one(id);
|
|
}, this));
|
|
this.attachEvent("onResize", function(w,h,wo){
|
|
if (wo != w)
|
|
this._resize_sub_all(true);
|
|
});
|
|
});
|
|
|
|
this.type.subrow = function(obj){
|
|
if (obj.$sub){
|
|
if (obj.$subopen)
|
|
return "<div class='webix_tree_open webix_sub_open'></div>";
|
|
else
|
|
return "<div class='webix_tree_close webix_sub_close'></div>";
|
|
} else
|
|
return "<div class='webix_tree_none'></div>";
|
|
};
|
|
this.on_click.webix_sub_open = function(e, id){
|
|
this.closeSub(id);
|
|
return false;
|
|
};
|
|
this.on_click.webix_sub_close = function(e, id){
|
|
this.openSub(id);
|
|
return false;
|
|
};
|
|
},
|
|
openSub:function(id){
|
|
var obj = this.getItem(id);
|
|
if (obj.$subopen) return;
|
|
|
|
obj.$row = this._settings.subrow;
|
|
obj.$subHeight = (obj.$subHeight || this._settings.subRowHeight);
|
|
obj.$subopen = true;
|
|
|
|
var sub = this._subViewStorage[obj.$subContent];
|
|
if (sub)
|
|
sub.repaintMe = true;
|
|
|
|
this.refresh(id);
|
|
this.callEvent("onSubViewOpen", [id]);
|
|
},
|
|
getSubView:function(id){
|
|
var obj = this.getItem(id);
|
|
if (obj){
|
|
var sub = this._subViewStorage[obj.$subContent];
|
|
if (sub)
|
|
return webix.$$(sub);
|
|
}
|
|
|
|
return null;
|
|
},
|
|
resizeSubView:function(id){
|
|
var view = this.getSubView(id);
|
|
if (view)
|
|
this._resizeSubView( this.getItem(id), view);
|
|
},
|
|
_resizeSubView:function(obj, view){
|
|
var height = view.$getSize(0,0)[2];
|
|
var eheight = obj.$subHeight || this._settings.subRowHeight;
|
|
var delta = Math.abs(height - (eheight || 0));
|
|
if (delta > 2){
|
|
obj.$subHeight = height;
|
|
this.refresh(obj.id);
|
|
}
|
|
},
|
|
_checkSubWidth: function(view){
|
|
var width = view.$width;
|
|
// if layout
|
|
if(view._layout_sizes){
|
|
var number = view._cells.length-view._hiddencells;
|
|
if (view._vertical_orientation)
|
|
width -= view._paddingX*2+2;
|
|
else
|
|
width -= view._margin*(number-1)+view._paddingX*2+number*2;
|
|
}
|
|
return width > 0;
|
|
},
|
|
_render_sub_view:function(obj, row){
|
|
var sub = this._subViewStorage[obj.$subContent], view;
|
|
if (sub){
|
|
row.firstChild.appendChild(sub);
|
|
view = webix.$$(obj.$subContent);
|
|
if (!this._checkSubWidth(view))
|
|
view.adjust();
|
|
if (sub.repaintMe){
|
|
delete sub.repaintMe;
|
|
view.config.hidden = false;
|
|
view._render_hidden_views();
|
|
}
|
|
} else {
|
|
view = webix.ui(webix.copy(this._settings.subview), row.firstChild);
|
|
view.getMasterView = webix.bind(function(){ return this; }, this);
|
|
obj.$subContent = view.config.id;
|
|
this._subViewStorage[obj.$subContent] = view.$view;
|
|
//special case, datatable inside of datatable
|
|
view.attachEvent("onResize", webix.bind(function(w,h, wo, ho){
|
|
if(h && h != ho) this.refresh(obj.id);
|
|
}, this));
|
|
|
|
this.callEvent("onSubViewCreate", [view, obj]);
|
|
}
|
|
this._resizeSubView(obj, (view || webix.$$(sub)));
|
|
},
|
|
_destroy_sub_view:function(id){
|
|
var obj = this.getItem(id);
|
|
var div = this._subViewStorage[obj.$subContent];
|
|
if (div){
|
|
delete obj.$subContent;
|
|
var view = webix.$$(div);
|
|
if (view && view != this)
|
|
view.destructor();
|
|
}
|
|
},
|
|
_adjustSubRowHeight:function(id, text){
|
|
var d = webix.html.create("DIV",{"class":"webix_measure_size webix_cell webix_dtable_subrow"}, "");
|
|
d.style.cssText = "width:"+this._content_width+"px; height:auto; visibility:hidden; position:absolute; top:0px; left:0px; overflow:hidden;";
|
|
this.$view.appendChild(d);
|
|
|
|
this.data.each(function(obj){
|
|
if (obj && !id || obj.id == id && obj.$sub){
|
|
d.innerHTML = text || this._settings.subrow(obj, this.type);
|
|
obj.$subHeight = d.offsetHeight;
|
|
}
|
|
}, this);
|
|
|
|
d = webix.html.remove(d);
|
|
},
|
|
closeSub:function(id){
|
|
var obj = this.getItem(id);
|
|
if (!obj.$subopen) return;
|
|
|
|
obj.$row = false;
|
|
obj.$subopen = false;
|
|
|
|
var sub = this._subViewStorage[obj.$subContent];
|
|
if (sub)
|
|
webix.$$(sub).config.hidden = true;
|
|
|
|
this.refresh(id);
|
|
this.callEvent("onSubViewClose", [id]);
|
|
}
|
|
});
|
|
webix.extend(webix.ui.datatable, {
|
|
headermenu_setter:function(value){
|
|
if (value){
|
|
if (value.data)
|
|
this._preconfigured_hmenu = true;
|
|
value = this._init_hmenu_once(value);
|
|
}
|
|
return value;
|
|
},
|
|
_init_hmenu_once:function(value){
|
|
|
|
var menuobj = {
|
|
view:"contextmenu",
|
|
template:"<span class='webix_icon {common.hidden()}'></span> #value#",
|
|
type:{
|
|
hidden:function(obj){
|
|
if (obj.hidden)
|
|
return "fa-empty";
|
|
else
|
|
return "fa-eye";
|
|
}
|
|
},
|
|
on:{
|
|
onMenuItemClick:webix.bind(function(id, ev){
|
|
var menu = webix.$$(this._settings.headermenu);
|
|
var state = menu.getItem(id).hidden;
|
|
menu.getItem(id).hidden = !state;
|
|
menu.refresh(id);
|
|
menu.$blockRender = true;
|
|
|
|
var opts = {spans:typeof value == "object" && value.spans};
|
|
if (state)
|
|
this.showColumn(id, opts);
|
|
else
|
|
this.hideColumn(id, opts);
|
|
|
|
menu.$blockRender = false;
|
|
return false;
|
|
}, this)
|
|
},
|
|
data:[]
|
|
};
|
|
if (typeof value == "object")
|
|
webix.extend(menuobj, value, true);
|
|
|
|
var menu = webix.ui(menuobj);
|
|
|
|
menu.attachTo(this._header);
|
|
this._destroy_with_me.push(menu);
|
|
this.attachEvent("onStructureLoad", this._generate_menu_columns);
|
|
this.attachEvent("onStructureUpdate", this._generate_menu_columns);
|
|
|
|
this._init_hmenu_once = function(v){ return v; };
|
|
return menu._settings.id;
|
|
},
|
|
_generate_menu_columns:function(){
|
|
var column, data, hidden, i;
|
|
|
|
var menu = webix.$$(this._settings.headermenu);
|
|
if (menu.$blockRender || this._preconfigured_hmenu) return;
|
|
|
|
data = [];
|
|
for (i = 0; i < this._columns.length; i++){
|
|
column = this._columns[i];
|
|
var content = column.header[0];
|
|
if (column.headermenu !== false && content)
|
|
data.push({ id:column.id, value:(content.groupText || content.text) });
|
|
}
|
|
|
|
hidden = this.getState().hidden;
|
|
for (i = hidden.length - 1; i >= 0; i--){
|
|
column = this.getColumnConfig(hidden[i]);
|
|
var content = column.header[0];
|
|
if (column.headermenu !== false && content)
|
|
data.push({ id:hidden[i], value:content.text, hidden:1 });
|
|
}
|
|
|
|
if (data.length)
|
|
menu.data.importData(data);
|
|
}
|
|
});
|
|
|
|
webix.ui.datafilter.headerMenu = {
|
|
getValue:function(){},
|
|
setValue:function(){},
|
|
refresh:function(master, node, config){
|
|
if (!master._settings.headermenu){
|
|
master.define("headermenu", true);
|
|
master._generate_menu_columns();
|
|
}
|
|
|
|
node.onclick = function(){
|
|
webix.$$(master.config.headermenu).show(node);
|
|
};
|
|
},
|
|
render:function(master, config){
|
|
return "<span class='webix_icon fa-columns' role='button' tabindex='0' aria-label='"+webix.i18n.aria.headermenu+"'>";
|
|
}
|
|
};
|
|
|
|
webix.ui.datafilter.richSelectFilter = {
|
|
getInputNode:function(node){
|
|
return webix.$$(node.$webix) || null;
|
|
},
|
|
getValue:function(node){
|
|
var ui = this.getInputNode(node);
|
|
return ui?ui.getValue():"";
|
|
},
|
|
setValue:function(node, value){
|
|
var ui = this.getInputNode(node);
|
|
return ui?ui.setValue(value):"";
|
|
},
|
|
compare:function(a,b){
|
|
return a == b;
|
|
},
|
|
refresh:function(master, node, value){
|
|
if (master.$destructed) return;
|
|
|
|
var select = webix.$$(value.richselect);
|
|
|
|
//IE8 can destory the content of richselect, so recreating
|
|
if (!select.$view.parentNode) {
|
|
var d = webix.html.create("div", { "class" : "webix_richfilter" });
|
|
d.appendChild(select.$view);
|
|
}
|
|
|
|
node.$webix = value.richselect;
|
|
node.style.marginLeft = "-10px";
|
|
|
|
value.compare = value.compare || this.compare;
|
|
value.prepare = value.prepare || this.prepare;
|
|
master.registerFilter(node, value, this);
|
|
|
|
var data;
|
|
var options = value.options;
|
|
if (options){
|
|
if(typeof options =="string"){
|
|
data = value.options = [];
|
|
webix.ajax(options).then(webix.bind(function(data){
|
|
value.options = data.json();
|
|
var node = document.body.contains(node) ? node : document.body.querySelector('[active_id="'+value.contentId+'"]');
|
|
this.refresh(master, node, value);
|
|
}, this));
|
|
} else
|
|
data = options;
|
|
} else
|
|
data = master.collectValues(value.columnId);
|
|
|
|
|
|
var list = select.getPopup().getList();
|
|
|
|
var optview = webix.$$(options);
|
|
if(optview && optview.data && optview.data.getRange){
|
|
data = optview.data.getRange();
|
|
}
|
|
|
|
//reattaching node back to master container
|
|
node.firstChild.appendChild(select.$view.parentNode);
|
|
|
|
//load data in list, must be after reattaching, as callback of parse can try to operate with innerHTML
|
|
if (list.parse){
|
|
list.clearAll();
|
|
list.parse(data);
|
|
|
|
if ((!this.$noEmptyOption && value.emptyOption !== false) || value.emptyOption){
|
|
var emptyOption = { id:"", value: value.emptyOption||"", $empty: true };
|
|
list.add(emptyOption,0);
|
|
}
|
|
}
|
|
|
|
//set actual value for the filter
|
|
if (value.value) this.setValue(node, value.value);
|
|
|
|
//repaint the filter control
|
|
select.render();
|
|
|
|
//adjust sizes after full rendering
|
|
webix.delay(select.resize, select);
|
|
},
|
|
render:function(master, config){
|
|
if (!config.richselect){
|
|
var d = webix.html.create("div", { "class" : "webix_richfilter" });
|
|
|
|
var richconfig = {
|
|
container:d,
|
|
view:this.inputtype,
|
|
options:[]
|
|
};
|
|
|
|
var inputConfig = webix.extend( this.inputConfig||{}, config.inputConfig||{}, true );
|
|
webix.extend(richconfig, inputConfig);
|
|
|
|
if (config.separator)
|
|
richconfig.separator = config.separator;
|
|
if(config.suggest)
|
|
richconfig.suggest = config.suggest;
|
|
|
|
var richselect = webix.ui(richconfig);
|
|
richselect.attachEvent("onChange", function(){
|
|
master.filterByAll();
|
|
});
|
|
|
|
config.richselect = richselect._settings.id;
|
|
master._destroy_with_me.push(richselect);
|
|
}
|
|
|
|
config.css = "webix_div_filter";
|
|
return " ";
|
|
},
|
|
inputtype:"richselect"
|
|
};
|
|
|
|
webix.ui.datafilter.serverRichSelectFilter = webix.extend({
|
|
$server:true
|
|
}, webix.ui.datafilter.richSelectFilter);
|
|
|
|
webix.ui.datafilter.multiSelectFilter = webix.extend({
|
|
$noEmptyOption: true,
|
|
inputtype:"multiselect",
|
|
prepare:function(value, filter){
|
|
if (!value) return value;
|
|
var hash = {};
|
|
var parts = value.toString().split(filter.separator || ",");
|
|
for (var i = 0; i < parts.length; i++)
|
|
hash[parts[i]] = 1;
|
|
return hash;
|
|
},
|
|
compare:function(a,b){
|
|
return !b || b[a];
|
|
}
|
|
}, webix.ui.datafilter.richSelectFilter);
|
|
|
|
webix.ui.datafilter.serverMultiSelectFilter = webix.extend({
|
|
$server:true,
|
|
_on_change:function(e, node, value){
|
|
var id = this._comp_id;
|
|
webix.$$(id).filterByAll();
|
|
}
|
|
}, webix.ui.datafilter.multiSelectFilter);
|
|
|
|
webix.ui.datafilter.multiComboFilter = webix.extend({
|
|
inputtype:"multicombo",
|
|
inputConfig:{
|
|
tagMode: false
|
|
}
|
|
}, webix.ui.datafilter.multiSelectFilter);
|
|
|
|
webix.ui.datafilter.serverMultiComboFilter = webix.extend({
|
|
inputtype:"multicombo",
|
|
inputConfig:{
|
|
tagMode: false
|
|
}
|
|
}, webix.ui.datafilter.serverMultiSelectFilter);
|
|
|
|
webix.ui.datafilter.datepickerFilter = webix.extend({
|
|
prepare:function(value){ return value||""; },
|
|
compare:function(a,b){ return a*1 == b*1; },
|
|
inputtype:"datepicker"
|
|
}, webix.ui.datafilter.richSelectFilter);
|
|
|
|
|
|
webix.ui.datafilter.columnGroup = {
|
|
getValue:function(){},
|
|
setValue:function(){},
|
|
getHelper:function(node, config){
|
|
return {
|
|
open:function(){ config.closed = true; node.onclick(); },
|
|
close:function(){ config.closed = false; node.onclick(); },
|
|
isOpened:function(){ return config.closed; }
|
|
};
|
|
},
|
|
refresh:function(master, node, config){
|
|
node.onclick = function(e){
|
|
webix.html.stopEvent(e);
|
|
var mark = this.firstChild.firstChild;
|
|
if (config.closed){
|
|
config.closed = false;
|
|
mark.className = "webix_tree_open";
|
|
} else {
|
|
config.closed = true;
|
|
mark.className = "webix_tree_close";
|
|
}
|
|
|
|
webix.delay(function(){
|
|
master.callEvent("onColumnGroupCollapse", [config.columnId, config.batch, !config.closed]);
|
|
master.showColumnBatch(config.batch, !config.closed);
|
|
});
|
|
};
|
|
|
|
if (!config.firstRun){
|
|
config.firstRun = 1;
|
|
if (config.closed)
|
|
master.showColumnBatch(config.batch, false);
|
|
}
|
|
},
|
|
render:function(master, config){
|
|
return "<div role='button' tabindex='0' aria-label='"+webix.i18n.aria[config.closed?"openGroup":"closeGroup"]+"' class='"+(config.closed?"webix_tree_close":"webix_tree_open")+"'></div> "+(config.groupText||"");
|
|
}
|
|
};
|
|
|
|
webix.ui.datafilter.dateRangeFilter = webix.extend({
|
|
prepare:function(value){
|
|
return webix.ui.daterange.prototype._correct_value(value);
|
|
},
|
|
compare:function(a, b){
|
|
return ((!b.start || a>=b.start) && (!b.end || a<=b.end));
|
|
},
|
|
inputtype:"daterangepicker"
|
|
}, webix.ui.datafilter.richSelectFilter);
|
|
|
|
webix.ui.datafilter.serverDateRangeFilter = webix.extend({
|
|
$server:true
|
|
}, webix.ui.datafilter.dateRangeFilter);
|
|
|
|
webix.editors.$popup.multiselect = {
|
|
view:"multisuggest",
|
|
suggest:{
|
|
button:true
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
webix.Canvas = webix.proto({
|
|
$init:function(container){
|
|
this._canvas_labels = [];
|
|
this._canvas_series = (!webix.isUndefined(container.series)?container.series:container.name);
|
|
this._obj = webix.toNode(container.container||container);
|
|
var width = container.width*(window.devicePixelRatio||1);
|
|
var height = container.height*(window.devicePixelRatio||1);
|
|
var style = container.style||"";
|
|
style += ";width:"+container.width+"px;height:"+container.height+"px;";
|
|
this._prepareCanvas(container.name, style ,width, height);
|
|
},
|
|
_prepareCanvas:function(name,style,x,y){
|
|
//canvas has the same size as master object
|
|
this._canvas = webix.html.create("canvas",{ title:name, width:x, height:y, canvas_id:name, style:(style||"")});
|
|
this._obj.appendChild(this._canvas);
|
|
//use excanvas in IE
|
|
if (!this._canvas.getContext){
|
|
if (webix.env.isIE){
|
|
webix.require("legacy/excanvas/excanvas.js", true); //sync loading
|
|
G_vmlCanvasManager.init_(document);
|
|
G_vmlCanvasManager.initElement(this._canvas);
|
|
} else //some other not supported browser
|
|
webix.assert(this._canvas.getContext,"Canvas is not supported in the browser");
|
|
}
|
|
return this._canvas;
|
|
},
|
|
getCanvas:function(context){
|
|
var ctx = (this._canvas||this._prepareCanvas(this._contentobj)).getContext(context||"2d");
|
|
if(!this._webixDevicePixelRatio){
|
|
this._webixDevicePixelRatio = true;
|
|
ctx.scale(window.devicePixelRatio||1, window.devicePixelRatio||1);
|
|
}
|
|
return ctx;
|
|
},
|
|
_resizeCanvas:function(x, y){
|
|
if (this._canvas){
|
|
this._canvas.setAttribute("width", x*(window.devicePixelRatio||1));
|
|
this._canvas.setAttribute("height", y*(window.devicePixelRatio||1));
|
|
this._canvas.style.width = x+"px";
|
|
this._canvas.style.height = y+"px";
|
|
this._webixDevicePixelRatio = false;
|
|
}
|
|
},
|
|
renderText:function(x,y,text,css,w){
|
|
if (!text) return; //ignore empty text
|
|
if (w) w = Math.max(w,0);
|
|
if (y) y = Math.max(y,0);
|
|
var t = webix.html.create("DIV",{
|
|
"class":"webix_canvas_text"+(css?(" "+css):""),
|
|
"style":"left:"+x+"px; top:"+y+"px;",
|
|
"aria-hidden":"true"
|
|
},text);
|
|
this._obj.appendChild(t);
|
|
this._canvas_labels.push(t); //destructor?
|
|
if (w)
|
|
t.style.width = w+"px";
|
|
return t;
|
|
},
|
|
renderTextAt:function(valign,align, x,y,t,c,w){
|
|
var text=this.renderText.call(this,x,y,t,c,w);
|
|
if (text){
|
|
if (valign){
|
|
if(valign == "middle")
|
|
text.style.top = parseInt(y-text.offsetHeight/2,10) + "px";
|
|
else
|
|
text.style.top = y-text.offsetHeight + "px";
|
|
}
|
|
if (align){
|
|
if(align == "left")
|
|
text.style.left = x-text.offsetWidth + "px";
|
|
else
|
|
text.style.left = parseInt(x-text.offsetWidth/2,10) + "px";
|
|
}
|
|
}
|
|
return text;
|
|
},
|
|
clearCanvas:function(skipMap){
|
|
var areas=[], i;
|
|
|
|
webix.html.remove(this._canvas_labels);
|
|
this._canvas_labels = [];
|
|
|
|
if (!skipMap&&this._obj._htmlmap){
|
|
|
|
//areas that correspond this canvas layer
|
|
areas = this._getMapAreas();
|
|
//removes areas of this canvas
|
|
while(areas.length){
|
|
areas[0].parentNode.removeChild(areas[0]);
|
|
areas.splice(0,1);
|
|
}
|
|
areas = null;
|
|
|
|
//removes _htmlmap object if all its child nodes are removed
|
|
if(!this._obj._htmlmap.getElementsByTagName("AREA").length){
|
|
this._obj._htmlmap.parentNode.removeChild(this._obj._htmlmap);
|
|
this._obj._htmlmap = null;
|
|
}
|
|
|
|
}
|
|
//FF breaks, when we are using clear canvas and call clearRect without parameters
|
|
this.getCanvas().clearRect(0,0,this._canvas.offsetWidth, this._canvas.offsetHeight);
|
|
},
|
|
toggleCanvas:function(){
|
|
this._toggleCanvas(this._canvas.style.display=="none");
|
|
},
|
|
showCanvas:function(){
|
|
this._toggleCanvas(true);
|
|
},
|
|
hideCanvas:function(){
|
|
this._toggleCanvas(false);
|
|
},
|
|
_toggleCanvas:function(show){
|
|
var areas, i;
|
|
|
|
for(i=0; i < this._canvas_labels.length;i++)
|
|
this._canvas_labels[i].style.display = (show?"":"none");
|
|
|
|
if (this._obj._htmlmap){
|
|
areas = this._getMapAreas();
|
|
for( i = 0; i < areas.length; i++){
|
|
if(show)
|
|
areas[i].removeAttribute("disabled");
|
|
else
|
|
areas[i].setAttribute("disabled","true");
|
|
}
|
|
}
|
|
//FF breaks, when we are using clear canvas and call clearRect without parameters
|
|
this._canvas.style.display = (show?"":"none");
|
|
},
|
|
_getMapAreas:function(){
|
|
var res = [], areas, i;
|
|
areas = this._obj._htmlmap.getElementsByTagName("AREA");
|
|
for(i = 0; i < areas.length; i++){
|
|
if(areas[i].getAttribute("userdata") == this._canvas_series){
|
|
res.push(areas[i]);
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
webix.color = {
|
|
_toHex:["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"],
|
|
toHex:function(number, length){
|
|
number=parseInt(number,10);
|
|
var str = "";
|
|
while (number>0){
|
|
str=this._toHex[number%16]+str;
|
|
number=Math.floor(number/16);
|
|
}
|
|
while (str.length <length)
|
|
str = "0"+str;
|
|
return str;
|
|
},
|
|
rgbToHex:function(rgb){
|
|
var arr=[];
|
|
if(typeof(rgb) === "string")
|
|
rgb.replace(/[\d+\.]+/g, function(v){
|
|
arr.push(parseFloat(v));
|
|
});
|
|
else if(webix.isArray(rgb)) arr = rgb;
|
|
|
|
//transparent
|
|
if(arr[3] === 0) return "";
|
|
|
|
return arr.slice(0, 3).map(function(n){
|
|
return webix.color.toHex(Math.floor(n), 2);
|
|
}).join("");
|
|
},
|
|
hexToDec:function(hex){
|
|
return parseInt(hex, 16);
|
|
},
|
|
toRgb:function(rgb){
|
|
var r,g,b,rgbArr;
|
|
if (typeof(rgb) != 'string') {
|
|
r = rgb[0];
|
|
g = rgb[1];
|
|
b = rgb[2];
|
|
} else if (rgb.indexOf('rgb')!=-1) {
|
|
rgbArr = rgb.substr(rgb.indexOf("(")+1,rgb.lastIndexOf(")")-rgb.indexOf("(")-1).split(",");
|
|
r = rgbArr[0];
|
|
g = rgbArr[1];
|
|
b = rgbArr[2];
|
|
} else {
|
|
if (rgb.substr(0, 1) == '#') {
|
|
rgb = rgb.substr(1);
|
|
}
|
|
r = this.hexToDec(rgb.substr(0, 2));
|
|
g = this.hexToDec(rgb.substr(2, 2));
|
|
b = this.hexToDec(rgb.substr(4, 2));
|
|
}
|
|
r = (parseInt(r,10)||0);
|
|
g = (parseInt(g,10)||0);
|
|
b = (parseInt(b,10)||0);
|
|
if (r < 0 || r > 255)
|
|
r = 0;
|
|
if (g < 0 || g > 255)
|
|
g = 0;
|
|
if (b < 0 || b > 255)
|
|
b = 0;
|
|
return [r,g,b];
|
|
},
|
|
hsvToRgb:function(h, s, v){
|
|
var hi,f,p,q,t,r,g,b;
|
|
hi = Math.floor((h/60))%6;
|
|
f = h/60-hi;
|
|
p = v*(1-s);
|
|
q = v*(1-f*s);
|
|
t = v*(1-(1-f)*s);
|
|
r = 0;
|
|
g = 0;
|
|
b = 0;
|
|
switch(hi) {
|
|
case 0:
|
|
r = v; g = t; b = p;
|
|
break;
|
|
case 1:
|
|
r = q; g = v; b = p;
|
|
break;
|
|
case 2:
|
|
r = p; g = v; b = t;
|
|
break;
|
|
case 3:
|
|
r = p; g = q; b = v;
|
|
break;
|
|
case 4:
|
|
r = t; g = p; b = v;
|
|
break;
|
|
case 5:
|
|
r = v; g = p; b = q;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
r = Math.floor(r*255);
|
|
g = Math.floor(g*255);
|
|
b = Math.floor(b*255);
|
|
return [r, g, b];
|
|
},
|
|
rgbToHsv:function(r, g, b){
|
|
var r0,g0,b0,min0,max0,s,h,v;
|
|
r0 = r/255;
|
|
g0 = g/255;
|
|
b0 = b/255;
|
|
min0 = Math.min(r0, g0, b0);
|
|
max0 = Math.max(r0, g0, b0);
|
|
h = 0;
|
|
s = max0===0?0:(1-min0/max0);
|
|
v = max0;
|
|
if (max0 == min0) {
|
|
h = 0;
|
|
} else if (max0 == r0 && g0>=b0) {
|
|
h = 60*(g0 - b0)/(max0 - min0)+0;
|
|
} else if (max0 == r0 && g0 < b0) {
|
|
h = 60*(g0 - b0)/(max0 - min0)+360;
|
|
} else if (max0 == g0) {
|
|
h = 60*(b0 - r0)/(max0-min0)+120;
|
|
} else if (max0 == b0) {
|
|
h = 60*(r0 - g0)/(max0 - min0)+240;
|
|
}
|
|
return [h, s, v];
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
webix.HtmlMap = webix.proto({
|
|
$init:function(key){
|
|
this._id = "map_"+webix.uid();
|
|
this._key = key;
|
|
this._map = [];
|
|
this._areas = [];
|
|
},
|
|
addRect: function(id,points,userdata) {
|
|
this._createMapArea(id,"RECT",points,userdata);
|
|
},
|
|
addPoly: function(id,points,userdata) {
|
|
this._createMapArea(id,"POLY",points,userdata);
|
|
},
|
|
_createMapArea:function(id,shape,coords,userdata){
|
|
var extra_data = "";
|
|
if(arguments.length==4)
|
|
extra_data = "userdata='"+userdata+"'";
|
|
this._map.push("<area "+this._key+"='"+id+"' shape='"+shape+"' coords='"+coords.join()+"' "+extra_data+"></area>");
|
|
this._areas.push({index: userdata, points:coords});
|
|
|
|
},
|
|
addSector:function(id,alpha0,alpha1,x,y,R,ky,userdata){
|
|
var points = [];
|
|
points.push(x);
|
|
points.push(Math.floor(y*ky));
|
|
for(var i = alpha0; i < alpha1; i+=Math.PI/18){
|
|
points.push(Math.floor(x+R*Math.cos(i)));
|
|
points.push(Math.floor((y+R*Math.sin(i))*ky));
|
|
}
|
|
points.push(Math.floor(x+R*Math.cos(alpha1)));
|
|
points.push(Math.floor((y+R*Math.sin(alpha1))*ky));
|
|
points.push(x);
|
|
points.push(Math.floor(y*ky));
|
|
|
|
return this.addPoly(id,points,userdata);
|
|
},
|
|
hide:function(obj, data, mode){
|
|
if (obj.querySelectorAll){
|
|
var nodes = obj.querySelectorAll("area[userdata=\""+data+"\"]");
|
|
for (var i = 0; i < nodes.length; i++){
|
|
var nod = nodes[i];
|
|
if (mode){
|
|
if (nod.getAttribute("coords")){
|
|
nod.coordsdis = nod.getAttribute("coords");
|
|
nod.setAttribute("coords", "");
|
|
nod.coords = "";
|
|
}
|
|
} else if (!mode){
|
|
if (nod.coordsdis){
|
|
nod.setAttribute("coords", nod.coordsdis);
|
|
nod.coords = nod.coordsdis;
|
|
nod.coordsdis = "";
|
|
}
|
|
}
|
|
nodes[i].style.display = mode?"none":"";
|
|
}
|
|
}
|
|
},
|
|
render:function(obj){
|
|
var d = webix.html.create("DIV");
|
|
d.style.cssText="position:absolute; width:100%; height:100%; top:0px; left:0px;";
|
|
obj.appendChild(d);
|
|
var src = webix.env.isIE?"":"src='data:image/gif;base64,R0lGODlhEgASAIAAAP///////yH5BAUUAAEALAAAAAASABIAAAIPjI+py+0Po5y02ouz3pwXADs='";
|
|
d.innerHTML="<map id='"+this._id+"' name='"+this._id+"'>"+this._map.join("\n")+"</map><img "+src+" class='webix_map_img' usemap='#"+this._id+"'>";
|
|
|
|
obj._htmlmap = d; //for clearing routine
|
|
|
|
this._map = [];
|
|
}
|
|
});
|
|
|
|
|
|
|
|
webix.protoUI({
|
|
name:"chart",
|
|
$init:function(config){
|
|
this._series = [this._settings];
|
|
this._legend_labels = [];
|
|
this._contentobj.className += " webix_chart";
|
|
this.$ready.push(this._after_init_call);
|
|
/*preset*/
|
|
if(config.preset){
|
|
this._definePreset(config);
|
|
}
|
|
|
|
// move series to end of configuration properties hash
|
|
// so it will be parsed after other settings
|
|
if(config.series){
|
|
var series = config.series;
|
|
delete config.series;
|
|
config.series = series;
|
|
}
|
|
|
|
this.attachEvent("onMouseMove",this._switchSeries);
|
|
|
|
this.data.provideApi(this, true);
|
|
},
|
|
_after_init_call:function(){
|
|
this.data.attachEvent("onStoreUpdated",webix.bind(function(){
|
|
this.render.apply(this,arguments);
|
|
},this));
|
|
},
|
|
defaults:{
|
|
ariaLabel:"chart",
|
|
color:"default",
|
|
alpha:"1",
|
|
label:false,
|
|
value:"{obj.value}",
|
|
padding:{},
|
|
type:"pie",
|
|
lineColor:"#ffffff",
|
|
cant:0.5,
|
|
barWidth: 30,
|
|
line:{
|
|
width:2,
|
|
color:"#1293f8"
|
|
},
|
|
item:{
|
|
radius:3,
|
|
borderColor:"#636363",
|
|
borderWidth:1,
|
|
color: "#ffffff",
|
|
alpha:1,
|
|
type:"r",
|
|
shadow:false
|
|
},
|
|
shadow:true,
|
|
gradient:false,
|
|
border:true,
|
|
labelOffset: 20,
|
|
origin:"auto",
|
|
scale: "linear"
|
|
},
|
|
_id:"webix_area_id",
|
|
on_click:{
|
|
webix_chart_legend_item: function(e,id,obj){
|
|
var series = obj.getAttribute("series_id");
|
|
if(this.callEvent("onLegendClick",[e,series,obj])){
|
|
var config = this._settings;
|
|
var values = config.legend.values;
|
|
var toggle = (values&&values[series].toggle)||config.legend.toggle;
|
|
if((typeof series != "undefined")&&this._series.length>1){
|
|
// hide action
|
|
if(toggle){
|
|
if(obj.className.indexOf("hidden")!=-1){
|
|
this.showSeries(series);
|
|
}
|
|
else{
|
|
this.hideSeries(series);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
on_dblclick:{
|
|
},
|
|
on_mouse_move:{
|
|
},
|
|
locate: function(e){
|
|
return webix.html.locate(e,this._id);
|
|
},
|
|
$setSize:function(x,y){
|
|
var res = webix.ui.view.prototype.$setSize.call(this,x,y);
|
|
if(res){
|
|
for(var c in this.canvases){
|
|
this.canvases[c]._resizeCanvas(this._content_width, this._content_height);
|
|
}
|
|
this.render();
|
|
}
|
|
return res;
|
|
},
|
|
type_setter:function(val){
|
|
webix.assert(this["$render_"+val], "Chart type is not supported, or extension is not loaded: "+val);
|
|
|
|
if (typeof this._settings.offset == "undefined"){
|
|
this._settings.offset = !(val.toLowerCase().indexOf("area")!=-1);
|
|
}
|
|
|
|
if(val=="radar"&&!this._settings.yAxis)
|
|
this.define("yAxis",{});
|
|
if(val=="scatter"){
|
|
if(!this._settings.yAxis)
|
|
this.define("yAxis",{});
|
|
if(!this._settings.xAxis)
|
|
this.define("xAxis",{});
|
|
}
|
|
|
|
|
|
return val;
|
|
},
|
|
destructor: function(){
|
|
this.removeAllSeries();
|
|
webix.Destruction.destructor.apply(this,arguments);
|
|
},
|
|
removeAllSeries: function(){
|
|
this.clearCanvas();
|
|
if(this._legendObj){
|
|
this._legendObj.innerHTML = "";
|
|
this._legendObj.parentNode.removeChild(this._legendObj);
|
|
this._legendObj = null;
|
|
}
|
|
if(this.canvases){
|
|
this.canvases = {};
|
|
}
|
|
this._contentobj.innerHTML="";
|
|
for(var i = 0; i < this._series.length; i++){
|
|
if(this._series[i].tooltip)
|
|
this._series[i].tooltip.destructor();
|
|
}
|
|
// this.callEvent("onDestruct",[]);
|
|
this._series = [];
|
|
},
|
|
clearCanvas:function(){
|
|
if(this.canvases&&typeof this.canvases == "object")
|
|
for(var c in this.canvases){
|
|
this.canvases[c].clearCanvas();
|
|
}
|
|
},
|
|
render:function(id,data, type){
|
|
var bounds, i, data, map, temp;
|
|
if (!this.isVisible(this._settings.id))
|
|
return;
|
|
|
|
data = this._getChartData();
|
|
|
|
if (!this.callEvent("onBeforeRender",[data, type]))
|
|
return;
|
|
if(this.canvases&&typeof this.canvases == "object"){
|
|
for(i in this.canvases){
|
|
this.canvases[i].clearCanvas();
|
|
}
|
|
}
|
|
else
|
|
this.canvases = {};
|
|
|
|
if(this._settings.legend){
|
|
if(!this.canvases["legend"])
|
|
this.canvases["legend"] = this._createCanvas("legend");
|
|
this._drawLegend(
|
|
this.data.getRange(),
|
|
this._content_width,
|
|
this._content_height
|
|
);
|
|
}
|
|
|
|
this._map = map = new webix.HtmlMap(this._id);
|
|
temp = this._settings;
|
|
|
|
bounds =this._getChartBounds(this._content_width,this._content_height);
|
|
|
|
if(this._series){
|
|
for(i=0; i < this._series.length;i++){
|
|
this._settings = this._series[i];
|
|
if(!this.canvases[i])
|
|
this.canvases[i] = this._createCanvas(this._settings.ariaLabel+" "+i,"z-index:"+(2+i),null,i);
|
|
this["$render_"+this._settings.type](
|
|
this.canvases[i].getCanvas(),
|
|
data,
|
|
bounds.start,
|
|
bounds.end,
|
|
i,
|
|
map
|
|
);
|
|
}
|
|
}
|
|
|
|
map.render(this._contentobj);
|
|
this._contentobj.lastChild.style.zIndex = 100;
|
|
this._applyBounds(this._contentobj.lastChild,bounds);
|
|
this.callEvent("onAfterRender",[data]);
|
|
this._settings = temp;
|
|
},
|
|
_applyBounds: function(elem,bounds){
|
|
var style = {};
|
|
style.left = bounds.start.x;
|
|
style.top = bounds.start.y;
|
|
style.width = bounds.end.x-bounds.start.x;
|
|
style.height = bounds.end.y - bounds.start.y;
|
|
for(var prop in style){
|
|
elem.style[prop] = style[prop]+"px";
|
|
}
|
|
},
|
|
_getChartData: function(){
|
|
var axis, axisConfig ,config, data, i, newData,
|
|
start, units, value, valuesHash;
|
|
data = this.data.getRange();
|
|
axis = (this._settings.type.toLowerCase().indexOf("barh")!=-1?"yAxis":"xAxis");
|
|
axisConfig = this._settings[axis];
|
|
if(axisConfig&&axisConfig.units&&(typeof axisConfig.units == "object")){
|
|
config = axisConfig.units;
|
|
units = [];
|
|
if(typeof config.start != "undefined"&&typeof config.end != "undefined" && typeof config.next != "undefined"){
|
|
start = config.start;
|
|
while(start<=config.end){
|
|
units.push(start);
|
|
start = config.next.call(this,start);
|
|
}
|
|
}
|
|
else if(Object.prototype.toString.call(config) === '[object Array]'){
|
|
units = config;
|
|
}
|
|
newData = [];
|
|
if(units.length){
|
|
value = axisConfig.value;
|
|
valuesHash = {};
|
|
for(i=0;i < data.length;i++){
|
|
valuesHash[value(data[i])] = i;
|
|
}
|
|
for(i=0;i< units.length;i++){
|
|
if(typeof valuesHash[units[i]]!= "undefined"){
|
|
data[valuesHash[units[i]]].$unit = units[i];
|
|
newData.push(data[valuesHash[units[i]]]);
|
|
}
|
|
else{
|
|
newData.push({$unit:units[i]});
|
|
}
|
|
}
|
|
}
|
|
return newData;
|
|
}
|
|
return data;
|
|
},
|
|
series_setter:function(config){
|
|
if(typeof config!="object"){
|
|
webix.assert(config,"Chart :: Series must be an array or object");
|
|
}
|
|
else{
|
|
|
|
this._parseSettings(!config.length?config:config[0]);
|
|
this._series = [this._settings];
|
|
|
|
|
|
for(var i=1;i< config.length;i++)
|
|
this.addSeries(config[i]);
|
|
}
|
|
return config;
|
|
},
|
|
value_setter:webix.template,
|
|
xValue_setter:webix.template,
|
|
yValue_setter:function(config){
|
|
this.define("value",config);
|
|
},
|
|
alpha_setter:webix.template,
|
|
label_setter:webix.template,
|
|
lineColor_setter:webix.template,
|
|
borderColor_setter:webix.template,
|
|
pieInnerText_setter:webix.template,
|
|
gradient_setter:function(config){
|
|
if((typeof(config)!="function")&&config&&(config === true))
|
|
config = "light";
|
|
return config;
|
|
},
|
|
colormap:{
|
|
"RAINBOW":function(obj){
|
|
var pos = Math.floor(this.getIndexById(obj.id)/this.count()*1536);
|
|
if (pos==1536) pos-=1;
|
|
return this._rainbow[Math.floor(pos/256)](pos%256);
|
|
},
|
|
|
|
"default": function(obj){
|
|
var count = this.count();
|
|
var colorsCount = this._defColors.length;
|
|
var i = this.getIndexById(obj.id);
|
|
if(colorsCount > count){
|
|
if(i){
|
|
if(i < colorsCount - count)
|
|
i = this._defColorsCursor +2;
|
|
else
|
|
i = this._defColorsCursor+1;
|
|
}
|
|
this._defColorsCursor = i;
|
|
}
|
|
else
|
|
i = i%colorsCount;
|
|
return this._defColors[i];
|
|
}
|
|
},
|
|
color_setter:function(value){
|
|
return this.colormap[value]||webix.template( value);
|
|
},
|
|
fill_setter:function(value){
|
|
return ((!value||value=="0")?false:webix.template( value));
|
|
},
|
|
_definePreset:function(obj){
|
|
this.define("preset",obj.preset);
|
|
delete obj.preset;
|
|
},
|
|
preset_setter:function(value){
|
|
var a, b, preset;
|
|
this.defaults = webix.extend({},this.defaults);
|
|
preset = this.presets[value];
|
|
|
|
if(typeof preset == "object"){
|
|
|
|
for(a in preset){
|
|
|
|
if(typeof preset[a]=="object"){
|
|
if(!this.defaults[a]||typeof this.defaults[a]!="object"){
|
|
this.defaults[a] = webix.extend({},preset[a]);
|
|
}
|
|
else{
|
|
this.defaults[a] = webix.extend({},this.defaults[a]);
|
|
for(b in preset[a]){
|
|
this.defaults[a][b] = preset[a][b];
|
|
}
|
|
}
|
|
}else{
|
|
this.defaults[a] = preset[a];
|
|
}
|
|
}
|
|
return value;
|
|
}
|
|
return false;
|
|
},
|
|
legend_setter:function( config){
|
|
if(!config){
|
|
if(this._legendObj){
|
|
this._legendObj.innerHTML = "";
|
|
this._legendObj = null;
|
|
}
|
|
return false;
|
|
}
|
|
if(typeof(config)!="object") //allow to use template string instead of object
|
|
config={template:config};
|
|
|
|
this._mergeSettings(config,{
|
|
width:150,
|
|
height:18,
|
|
layout:"y",
|
|
align:"left",
|
|
valign:"bottom",
|
|
template:"",
|
|
toggle:(this._settings.type.toLowerCase().indexOf("stacked")!=-1?"":"hide"),
|
|
marker:{
|
|
type:"square",
|
|
width:15,
|
|
height:15,
|
|
radius:3
|
|
},
|
|
margin: 4,
|
|
padding: 3
|
|
});
|
|
|
|
config.template = webix.template(config.template);
|
|
return config;
|
|
},
|
|
item_setter:function( config){
|
|
if(typeof(config)!="object")
|
|
config={color:config, borderColor:config};
|
|
this._mergeSettings(config,webix.extend({},this.defaults.item));
|
|
var settings = ["alpha","borderColor","color","radius"];
|
|
this._converToTemplate(settings,config);
|
|
return config;
|
|
},
|
|
line_setter:function( config){
|
|
if(typeof(config)!="object")
|
|
config={color:config};
|
|
|
|
config = webix.extend(config,this.defaults.line);
|
|
config.color = webix.template(config.color);
|
|
return config;
|
|
},
|
|
padding_setter:function( config){
|
|
if(typeof(config)!="object")
|
|
config={left:config, right:config, top:config, bottom:config};
|
|
this._mergeSettings(config,{
|
|
left:50,
|
|
right:20,
|
|
top:35,
|
|
bottom:40
|
|
});
|
|
return config;
|
|
},
|
|
xAxis_setter:function( config){
|
|
if(!config) return false;
|
|
if(typeof(config)!="object")
|
|
config={ template:config };
|
|
|
|
this._mergeSettings(config,{
|
|
title:"",
|
|
color:"#000000",
|
|
lineColor:"#cfcfcf",
|
|
template:"{obj}",
|
|
lines:true
|
|
});
|
|
var templates = ["lineColor","template","lines"];
|
|
this._converToTemplate(templates,config);
|
|
this._configXAxis = webix.extend({},config);
|
|
return config;
|
|
},
|
|
yAxis_setter:function( config){
|
|
this._mergeSettings(config,{
|
|
title:"",
|
|
color:"#000000",
|
|
lineColor:"#cfcfcf",
|
|
template:"{obj}",
|
|
lines:true,
|
|
bg:"#ffffff"
|
|
});
|
|
var templates = ["lineColor","template","lines","bg"];
|
|
this._converToTemplate(templates,config);
|
|
this._configYAxis = webix.extend({},config);
|
|
return config;
|
|
},
|
|
_converToTemplate:function(arr,config){
|
|
for(var i=0;i< arr.length;i++){
|
|
config[arr[i]] = webix.template(config[arr[i]]);
|
|
}
|
|
},
|
|
_createCanvas: function(name,style,container, index){
|
|
var params = {container:(container||this._contentobj),name:name, series: index, style:(style||""), width: this._content_width, height:this._content_height };
|
|
return new webix.Canvas(params);
|
|
},
|
|
_drawScales:function(data,point0,point1,start,end,cellWidth){
|
|
var ctx, y = 0;
|
|
if(this._settings.yAxis){
|
|
if(!this.canvases["y"])
|
|
this.canvases["y"] = this._createCanvas("axis_y");
|
|
|
|
y = this._drawYAxis(this.canvases["y"].getCanvas(),data,point0,point1,start,end);
|
|
}
|
|
if (this._settings.xAxis){
|
|
if (!this.canvases["x"])
|
|
this.canvases["x"] = this._createCanvas("axis_x");
|
|
ctx = this.canvases["x"].getCanvas();
|
|
if(this.callEvent("onBeforeXAxis",[ctx,data,point0,point1,cellWidth,y]))
|
|
this._drawXAxis(ctx, data, point0, point1, cellWidth, y);
|
|
}
|
|
return y;
|
|
},
|
|
_drawXAxis:function(ctx,data,point0,point1,cellWidth,y){
|
|
var i, unitPos,
|
|
config = this._settings,
|
|
x0 = point0.x-0.5,
|
|
y0 = parseInt((y?y:point1.y),10)+0.5,
|
|
x1 = point1.x,
|
|
center = true,
|
|
labelY = config.type == "stackedBar"?(point1.y+0.5):y0;
|
|
|
|
for(i=0; i < data.length;i++){
|
|
if(config.offset === true)
|
|
unitPos = x0+cellWidth/2+i*cellWidth;
|
|
else{
|
|
unitPos = (i==data.length-1 && !config.cellWidth)?point1.x:x0+i*cellWidth;
|
|
center = !!i;
|
|
}
|
|
unitPos = Math.ceil(unitPos)-0.5;
|
|
/*scale labels*/
|
|
var top = ((config.origin!="auto")&&(config.type=="bar")&&(parseFloat(config.value(data[i]))<config.origin));
|
|
this._drawXAxisLabel(unitPos,labelY,data[i],center,top);
|
|
/*draws a vertical line for the horizontal scale*/
|
|
if((config.offset||i||config.cellWidth)&&config.xAxis.lines.call(this,data[i]))
|
|
this._drawXAxisLine(ctx,unitPos,point1.y,point0.y,data[i]);
|
|
}
|
|
|
|
this.canvases["x"].renderTextAt(true, false, x0, point1.y + config.padding.bottom-3,
|
|
config.xAxis.title,
|
|
"webix_axis_title_x",
|
|
point1.x - point0.x
|
|
);
|
|
this._drawLine(ctx,x0,y0,x1,y0,config.xAxis.color,1);
|
|
/*the right border in lines in scale are enabled*/
|
|
if (!config.xAxis.lines.call(this,{}) || !config.offset) return;
|
|
this._drawLine(ctx,x1+0.5,point1.y,x1+0.5,point0.y+0.5,config.xAxis.color,0.2);
|
|
},
|
|
_drawYAxis:function(ctx,data,point0,point1,start,end){
|
|
var step;
|
|
var scaleParam= {};
|
|
if (!this._settings.yAxis) return;
|
|
|
|
var x0 = point0.x - 0.5;
|
|
var y0 = point1.y;
|
|
var y1 = point0.y;
|
|
var lineX = point1.y+0.5;
|
|
|
|
//this._drawLine(ctx,x0,y0,x0,y1,this._settings.yAxis.color,1);
|
|
|
|
if(this._settings.yAxis.step)
|
|
step = parseFloat(this._settings.yAxis.step);
|
|
|
|
if(typeof this._configYAxis.step =="undefined"||typeof this._configYAxis.start=="undefined"||typeof this._configYAxis.end =="undefined"){
|
|
scaleParam = this._calculateScale(start,end);
|
|
start = scaleParam.start;
|
|
end = scaleParam.end;
|
|
step = scaleParam.step;
|
|
|
|
this._settings.yAxis.end = end;
|
|
this._settings.yAxis.start = start;
|
|
}
|
|
else if(this.config.scale == "logarithmic")
|
|
this._logScaleCalc = true;
|
|
|
|
this._setYAxisTitle(point0,point1);
|
|
if(step===0) return;
|
|
if(end==start){
|
|
return y0;
|
|
}
|
|
var stepHeight = (y0-y1)*step/(end-start);
|
|
var c = 0;
|
|
for(var i = start; i<=end; i += step){
|
|
var value = this._logScaleCalc?Math.pow(10,i):i;
|
|
if (scaleParam.fixNum) value = parseFloat(value).toFixed(scaleParam.fixNum);
|
|
var yi = Math.floor(y0-c*stepHeight)+ 0.5;/*canvas line fix*/
|
|
if(!(i==start&&this._settings.origin=="auto") &&this._settings.yAxis.lines.call(this,i))
|
|
this._drawLine(ctx,x0,yi,point1.x,yi,this._settings.yAxis.lineColor.call(this,i),1);
|
|
if(i == this._settings.origin) lineX = yi;
|
|
/*correction for JS float calculation*/
|
|
if(step<1 && !this._logScaleCalc){
|
|
var power = Math.min(Math.floor(this._log10(step)),(start<=0?0:Math.floor(this._log10(start))));
|
|
var corr = Math.pow(10,-power);
|
|
value = Math.round(value*corr)/corr;
|
|
i = value;
|
|
}
|
|
this.canvases["y"].renderText(0,yi-5,
|
|
this._settings.yAxis.template(value.toString()),
|
|
"webix_axis_item_y",
|
|
point0.x-5
|
|
);
|
|
c++;
|
|
}
|
|
this._drawLine(ctx,x0,y0+1,x0,y1,this._settings.yAxis.color,1);
|
|
return lineX;
|
|
},
|
|
|
|
_setYAxisTitle:function(point0,point1){
|
|
var className = "webix_axis_title_y"+(webix._isIE&&webix._isIE !=9?" webix_ie_filter":"");
|
|
var text=this.canvases["y"].renderTextAt("middle",false,0,parseInt((point1.y-point0.y)/2+point0.y,10),this._settings.yAxis.title,className);
|
|
if (text)
|
|
text.style.left = (webix.env.transform?(text.offsetHeight-text.offsetWidth)/2:0)+"px";
|
|
},
|
|
_calculateLogScale: function(nmin,nmax){
|
|
var startPower = Math.floor(this._log10(nmin));
|
|
var endPower = Math.ceil(this._log10(nmax));
|
|
return {start: startPower, step: 1, end: endPower};
|
|
},
|
|
_normStep:function(step){
|
|
var power = Math.floor(this._log10(step));
|
|
var calculStep = Math.pow(10,power);
|
|
var stepVal = step/calculStep;
|
|
stepVal = (stepVal>5?10:5);
|
|
return parseInt(stepVal,10)*calculStep;
|
|
},
|
|
_calculateScale:function(nmin,nmax){
|
|
this._logScaleCalc = false;
|
|
if(this._settings.scale == "logarithmic"){
|
|
var logMin = Math.floor(this._log10(nmin));
|
|
var logMax = Math.ceil(this._log10(nmax));
|
|
if(nmin>0 && nmax > 0 && (logMax-logMin>1) ){
|
|
this._logScaleCalc = true;
|
|
return this._calculateLogScale(nmin,nmax);
|
|
}
|
|
|
|
}
|
|
if(this._settings.origin!="auto"&&this._settings.origin<nmin)
|
|
nmin = this._settings.origin;
|
|
var step,start,end;
|
|
step = this._normStep(((nmax-nmin)/8)||1);
|
|
|
|
if(step>Math.abs(nmin))
|
|
start = (nmin<0?-step:0);
|
|
else{
|
|
var absNmin = Math.abs(nmin);
|
|
var powerStart = Math.floor(this._log10(absNmin));
|
|
var nminVal = absNmin/Math.pow(10,powerStart);
|
|
start = Math.ceil(nminVal*10)/10*Math.pow(10,powerStart)-step;
|
|
if(absNmin>1&&step>0.1){
|
|
start = Math.ceil(start);
|
|
}
|
|
while(nmin<0?start<=nmin:start>=nmin)
|
|
start -= step;
|
|
if(nmin<0) start =-start-2*step;
|
|
|
|
}
|
|
if ((nmax-start) > 10)
|
|
step = this._normStep(((nmax-start)/8)||1);
|
|
end = start;
|
|
while(end<nmax){
|
|
end += step;
|
|
end = parseFloat((end*1.0).toFixed(Math.abs(power)));
|
|
}
|
|
|
|
var power = Math.floor(this._log10(step));
|
|
return { start:start,end:end,step:step,fixNum:power<0?Math.abs(power):0 };
|
|
},
|
|
_getLimits:function(orientation,value){
|
|
var data = this.data._obj_array();
|
|
|
|
var maxValue, minValue;
|
|
var axis = ((arguments.length && orientation=="h")?this._configXAxis:this._configYAxis);
|
|
value = value||"value";
|
|
if(axis&&(typeof axis.end!="undefined")&&(typeof axis.start!="undefined")&&axis.step){
|
|
maxValue = parseFloat(axis.end);
|
|
minValue = parseFloat(axis.start);
|
|
}
|
|
else{
|
|
maxValue = webix.GroupMethods.max(this._series[0][value], data);
|
|
minValue = (axis&&(typeof axis.start!="undefined"))?parseFloat(axis.start):webix.GroupMethods.min(this._series[0][value], data);
|
|
if(this._series.length>1)
|
|
for(var i=1; i < this._series.length;i++){
|
|
var maxI = webix.GroupMethods.max(this._series[i][value], data);
|
|
var minI = webix.GroupMethods.min(this._series[i][value], data);
|
|
if (maxI > maxValue) maxValue = maxI;
|
|
if (minI < minValue) minValue = minI;
|
|
}
|
|
}
|
|
return {max:maxValue,min:minValue};
|
|
},
|
|
_log10:function(n){
|
|
var method_name="log";
|
|
return Math[method_name](n)/Math.LN10;
|
|
},
|
|
_drawXAxisLabel:function(x,y,obj,center,top){
|
|
if (!this._settings.xAxis) return;
|
|
var elem = this.canvases["x"].renderTextAt(top, center, x,y-(top?2:0),this._settings.xAxis.template(obj));
|
|
if (elem)
|
|
elem.className += " webix_axis_item_x";
|
|
},
|
|
_drawXAxisLine:function(ctx,x,y1,y2,obj){
|
|
if (!this._settings.xAxis||!this._settings.xAxis.lines) return;
|
|
this._drawLine(ctx,x,y1,x,y2,this._settings.xAxis.lineColor.call(this,obj),1);
|
|
},
|
|
_drawLine:function(ctx,x1,y1,x2,y2,color,width){
|
|
ctx.strokeStyle = color;
|
|
ctx.lineWidth = width;
|
|
ctx.beginPath();
|
|
ctx.moveTo(x1,y1);
|
|
ctx.lineTo(x2,y2);
|
|
ctx.stroke();
|
|
ctx.lineWidth = 1;
|
|
},
|
|
_getRelativeValue:function(minValue,maxValue){
|
|
var relValue, origRelValue;
|
|
var valueFactor = 1;
|
|
if(maxValue != minValue){
|
|
relValue = maxValue - minValue;
|
|
}
|
|
else relValue = minValue;
|
|
return [relValue,valueFactor];
|
|
},
|
|
_rainbow : [
|
|
function(pos){ return "#FF"+webix.color.toHex(pos/2,2)+"00";},
|
|
function(pos){ return "#FF"+webix.color.toHex(pos/2+128,2)+"00";},
|
|
function(pos){ return "#"+webix.color.toHex(255-pos,2)+"FF00";},
|
|
function(pos){ return "#00FF"+webix.color.toHex(pos,2);},
|
|
function(pos){ return "#00"+webix.color.toHex(255-pos,2)+"FF";},
|
|
function(pos){ return "#"+webix.color.toHex(pos,2)+"00FF";}
|
|
],
|
|
_defColors : [
|
|
"#f55b50","#ff6d3f","#ffa521","#ffc927","#ffee54","#d3e153","#9acb61","#63b967",
|
|
"#21a497","#21c5da","#3ea4f5","#5868bf","#7b53c0","#a943ba","#ec3b77","#9eb0b8"
|
|
],
|
|
_defColorsCursor: 0,
|
|
/**
|
|
* adds series to the chart (value and color properties)
|
|
* @param: obj - obj with configuration properties
|
|
*/
|
|
addSeries:function(obj){
|
|
var temp = webix.extend({},this._settings);
|
|
this._settings = webix.extend({},temp);
|
|
this._parseSettings(obj,{});
|
|
this._series.push(this._settings);
|
|
this._settings = temp;
|
|
},
|
|
/*switch global settings to serit in question*/
|
|
_switchSeries:function(id, e, tag) {
|
|
var tip;
|
|
|
|
if(!tag.getAttribute("userdata"))
|
|
return;
|
|
|
|
this._active_serie = this._series.length==1?this._getActiveSeries(e):tag.getAttribute("userdata");
|
|
if (!this._series[this._active_serie]) return;
|
|
for (var i=0; i < this._series.length; i++) {
|
|
tip = this._series[i].tooltip;
|
|
|
|
if (tip)
|
|
tip.disable();
|
|
}
|
|
if(!tag.getAttribute("disabled")){
|
|
tip = this._series[this._active_serie].tooltip;
|
|
if (tip)
|
|
tip.enable();
|
|
}
|
|
},
|
|
_getActiveSeries: function(e){
|
|
var a, areas, i, offset, pos, selection, x, y;
|
|
|
|
areas = this._map._areas;
|
|
offset = webix.html.offset(this._contentobj._htmlmap);
|
|
pos = webix.html.pos(e);
|
|
x = pos.x - offset.x;
|
|
y = pos.y - offset.y;
|
|
|
|
for( i = 0; i < areas.length; i++){
|
|
a = areas[i].points;
|
|
if(x <= a[2] && x >= a[0] && y <= a[3] && y >= a[1]){
|
|
if(selection){
|
|
if(areas[i].index > selection.index)
|
|
selection = areas[i];
|
|
}
|
|
else
|
|
selection = areas[i];
|
|
}
|
|
}
|
|
|
|
return selection?selection.index:0;
|
|
},
|
|
hideSeries:function(series){
|
|
this.canvases[series].hideCanvas();
|
|
var legend = this._settings.legend;
|
|
if(legend && legend.values && legend.values[series]){
|
|
legend.values[series].$hidden = true;
|
|
this._drawLegend();
|
|
}
|
|
this._map.hide(this._contentobj, series, true);
|
|
},
|
|
showSeries:function(series){
|
|
this.canvases[series].showCanvas();
|
|
var legend = this._settings.legend;
|
|
if(legend && legend.values && legend.values[series]){
|
|
delete legend.values[series].$hidden;
|
|
this._drawLegend();
|
|
}
|
|
this._map.hide(this._contentobj, series, false);
|
|
},
|
|
/**
|
|
* renders legend block
|
|
* @param: ctx - canvas object
|
|
* @param: data - object those need to be displayed
|
|
* @param: width - the width of the container
|
|
* @param: height - the height of the container
|
|
*/
|
|
_drawLegend:function(data,width){
|
|
/*position of the legend block*/
|
|
var i, legend, legendContainer, legendHeight, legendItems, legendWidth, style,
|
|
x=0, y= 0, ctx, itemColor, disabled, item;
|
|
|
|
data = data||[];
|
|
width = width||this._content_width;
|
|
ctx = this.canvases["legend"].getCanvas();
|
|
/*legend config*/
|
|
legend = this._settings.legend;
|
|
/*the legend sizes*/
|
|
|
|
style = (this._settings.legend.layout!="x"?"width:"+legend.width+"px":"");
|
|
/*creation of legend container*/
|
|
|
|
if(this._legendObj){
|
|
|
|
this._legendObj.innerHTML = "";
|
|
this._legendObj.parentNode.removeChild(this._legendObj);
|
|
}
|
|
this.canvases["legend"].clearCanvas(true);
|
|
|
|
legendContainer = webix.html.create("DIV",{
|
|
"class":"webix_chart_legend",
|
|
"style":"left:"+x+"px; top:"+y+"px;"+style
|
|
},"");
|
|
if(legend.padding){
|
|
legendContainer.style.padding = legend.padding+"px";
|
|
}
|
|
this._legendObj = legendContainer;
|
|
this._contentobj.appendChild(legendContainer);
|
|
|
|
/*rendering legend text items*/
|
|
legendItems = [];
|
|
if(!legend.values)
|
|
for(i = 0; i < data.length; i++){
|
|
legendItems.push(this._drawLegendText(legendContainer,legend.template(data[i])));
|
|
}
|
|
else
|
|
for(i = 0; i < legend.values.length; i++){
|
|
legendItems.push(this._drawLegendText(legendContainer,legend.values[i].text,(typeof legend.values[i].id!="undefined"?typeof legend.values[i].id:i),legend.values[i].$hidden));
|
|
}
|
|
if (legendContainer.offsetWidth === 0)
|
|
legendContainer.style.width = "auto";
|
|
legendWidth = legendContainer.offsetWidth;
|
|
legendHeight = legendContainer.offsetHeight;
|
|
|
|
/*this._settings.legend.width = legendWidth;
|
|
this._settings.legend.height = legendHeight;*/
|
|
/*setting legend position*/
|
|
if(legendWidth<width){
|
|
if(legend.layout == "x"&&legend.align == "center"){
|
|
x = (width-legendWidth)/2;
|
|
}
|
|
if(legend.align == "right"){
|
|
x = width-legendWidth;
|
|
}
|
|
if(legend.margin&&legend.align != "center"){
|
|
x += (legend.align == "left"?1:-1)*legend.margin;
|
|
}
|
|
}
|
|
|
|
if(legendHeight<this._content_height){
|
|
if(legend.valign == "middle"&&legend.align != "center"&&legend.layout != "x")
|
|
y = (this._content_height-legendHeight)/2;
|
|
else if(legend.valign == "bottom")
|
|
y = this._content_height-legendHeight;
|
|
if(legend.margin&&legend.valign != "middle"){
|
|
y += (legend.valign == "top"?1:-1)*legend.margin;
|
|
}
|
|
}
|
|
legendContainer.style.left = x+"px";
|
|
legendContainer.style.top = y+"px";
|
|
|
|
/*drawing colorful markers*/
|
|
ctx.save();
|
|
for(i = 0; i < legendItems.length; i++){
|
|
item = legendItems[i];
|
|
if(legend.values&&legend.values[i].$hidden){
|
|
disabled = true;
|
|
itemColor = (legend.values[i].disableColor?legend.values[i].disableColor:"#d9d9d9");
|
|
}
|
|
else{
|
|
disabled = false;
|
|
itemColor = (legend.values?legend.values[i].color:this._settings.color.call(this,data[i]));
|
|
}
|
|
this._drawLegendMarker(ctx,item.offsetLeft+x,item.offsetTop+y,itemColor,item.offsetHeight,disabled,i);
|
|
}
|
|
ctx.restore();
|
|
legendItems = null;
|
|
},
|
|
/**
|
|
* appends legend item to legend block
|
|
* @param: ctx - canvas object
|
|
* @param: obj - data object that needs being represented
|
|
*/
|
|
_drawLegendText:function(cont,value,series,disabled){
|
|
var style = "";
|
|
if(this._settings.legend.layout=="x")
|
|
style = "float:left;";
|
|
/*the text of the legend item*/
|
|
var text = webix.html.create("DIV",{
|
|
"style":style+"padding-left:"+(10+this._settings.legend.marker.width)+"px",
|
|
"class":"webix_chart_legend_item"+(disabled?" hidden":""),
|
|
"role":"button",
|
|
"tabindex":"0",
|
|
"aria-label":(webix.i18n.aria[(disabled?"show":"hide")+"Chart"])+" "+value
|
|
},value);
|
|
if(arguments.length>2)
|
|
text.setAttribute("series_id",series);
|
|
cont.appendChild(text);
|
|
return text;
|
|
},
|
|
/**
|
|
* draw legend colorful marder
|
|
* @param: ctx - canvas object
|
|
* @param: x - the horizontal position of the marker
|
|
* @param: y - the vertical position of the marker
|
|
* @param: obj - data object which color needs being used
|
|
*/
|
|
_drawLegendMarker:function(ctx,x,y,color,height,disabled,i){
|
|
var p = [];
|
|
var marker = this._settings.legend.marker;
|
|
var values = this._settings.legend.values;
|
|
var type = (values&&values[i].markerType?values[i].markerType:marker.type);
|
|
if(color){
|
|
ctx.strokeStyle = ctx.fillStyle = color;
|
|
}
|
|
|
|
if(type=="round"||!marker.radius){
|
|
ctx.beginPath();
|
|
ctx.lineWidth = marker.height;
|
|
ctx.lineCap = marker.type;
|
|
/*start of marker*/
|
|
x += ctx.lineWidth/2+5;
|
|
y += height/2;
|
|
ctx.moveTo(x,y);
|
|
var x1 = x + marker.width-marker.height +1;
|
|
ctx.lineTo(x1,y);
|
|
ctx.stroke();
|
|
ctx.fill();
|
|
|
|
}
|
|
else if(type=="item"){
|
|
/*copy of line*/
|
|
if(this._settings.line&&this._settings.type != "scatter" && !this._settings.disableLines){
|
|
ctx.beginPath();
|
|
ctx.lineWidth = this._series[i].line.width;
|
|
ctx.strokeStyle = disabled?color:this._series[i].line.color.call(this,{});
|
|
var x0 = x + 5;
|
|
var y0 = y + height/2;
|
|
ctx.moveTo(x0,y0);
|
|
var x1 = x0 + marker.width;
|
|
ctx.lineTo(x1,y0);
|
|
ctx.stroke();
|
|
}
|
|
/*item copy*/
|
|
var config = this._series[i].item;
|
|
var radius = parseInt(config.radius.call(this,{}),10)||0;
|
|
if(radius){
|
|
ctx.beginPath();
|
|
if(disabled){
|
|
ctx.lineWidth = config.borderWidth;
|
|
ctx.strokeStyle = color;
|
|
ctx.fillStyle = color;
|
|
}
|
|
else{
|
|
ctx.lineWidth = config.borderWidth;
|
|
ctx.fillStyle = config.color.call(this,{});
|
|
ctx.strokeStyle = config.borderColor.call(this,{});
|
|
ctx.globalAlpha = config.alpha.call(this,{});
|
|
}
|
|
ctx.beginPath();
|
|
x += marker.width/2+5;
|
|
y += height/2;
|
|
this._strokeChartItem(ctx,x,y,radius+1,config.type);
|
|
ctx.fill();
|
|
ctx.stroke();
|
|
}
|
|
ctx.globalAlpha = 1;
|
|
}else{
|
|
ctx.beginPath();
|
|
ctx.lineWidth = 1;
|
|
x += 5;
|
|
y += height/2-marker.height/2;
|
|
p = [
|
|
[x+marker.radius,y+marker.radius,marker.radius,Math.PI,3*Math.PI/2,false],
|
|
[x+marker.width-marker.radius,y],
|
|
[x+marker.width-marker.radius,y+marker.radius,marker.radius,-Math.PI/2,0,false],
|
|
[x+marker.width,y+marker.height-marker.radius],
|
|
[x+marker.width-marker.radius,y+marker.height-marker.radius,marker.radius,0,Math.PI/2,false],
|
|
[x+marker.radius,y+marker.height],
|
|
[x+marker.radius,y+marker.height-marker.radius,marker.radius,Math.PI/2,Math.PI,false],
|
|
[x,y+marker.radius]
|
|
];
|
|
this._path(ctx,p);
|
|
ctx.stroke();
|
|
ctx.fill();
|
|
}
|
|
|
|
},
|
|
/**
|
|
* gets the points those represent chart left top and right bottom bounds
|
|
* @param: width - the width of the chart container
|
|
* @param: height - the height of the chart container
|
|
*/
|
|
_getChartBounds:function(width,height){
|
|
var chartX0, chartY0, chartX1, chartY1;
|
|
|
|
chartX0 = this._settings.padding.left;
|
|
chartY0 = this._settings.padding.top;
|
|
chartX1 = width - this._settings.padding.right;
|
|
chartY1 = height - this._settings.padding.bottom;
|
|
|
|
if(this._settings.legend){
|
|
var legend = this._settings.legend;
|
|
/*legend size*/
|
|
var legendWidth = this._settings.legend.width;
|
|
var legendHeight = this._settings.legend.height;
|
|
|
|
/*if legend is horizontal*/
|
|
if(legend.layout == "x"){
|
|
if(legend.valign == "center"){
|
|
if(legend.align == "right")
|
|
chartX1 -= legendWidth;
|
|
else if(legend.align == "left")
|
|
chartX0 += legendWidth;
|
|
}
|
|
else if(legend.valign == "bottom"){
|
|
chartY1 -= legendHeight;
|
|
}
|
|
else{
|
|
chartY0 += legendHeight;
|
|
}
|
|
}
|
|
/*vertical scale*/
|
|
else{
|
|
if(legend.align == "right")
|
|
chartX1 -= legendWidth;
|
|
else if(legend.align == "left")
|
|
chartX0 += legendWidth;
|
|
}
|
|
}
|
|
return {start:{x:chartX0,y:chartY0},end:{x:chartX1,y:chartY1}};
|
|
},
|
|
/**
|
|
* gets the maximum and minimum values for the stacked chart
|
|
* @param: data - data set
|
|
*/
|
|
_getStackedLimits:function(data){
|
|
var i, j, maxValue, minValue, value;
|
|
if(this._settings.yAxis&&(typeof this._settings.yAxis.end!="undefined")&&(typeof this._settings.yAxis.start!="undefined")&&this._settings.yAxis.step){
|
|
maxValue = parseFloat(this._settings.yAxis.end);
|
|
minValue = parseFloat(this._settings.yAxis.start);
|
|
}
|
|
else{
|
|
for(i=0; i < data.length; i++){
|
|
data[i].$sum = 0 ;
|
|
data[i].$min = Infinity;
|
|
for(j =0; j < this._series.length;j++){
|
|
value = Math.abs(parseFloat(this._series[j].value(data[i])||0));
|
|
if(isNaN(value)) continue;
|
|
if(this._series[j].type.toLowerCase().indexOf("stacked")!=-1)
|
|
data[i].$sum += value;
|
|
if(value < data[i].$min) data[i].$min = value;
|
|
}
|
|
}
|
|
maxValue = -Infinity;
|
|
minValue = Infinity;
|
|
for(i=0; i < data.length; i++){
|
|
if (data[i].$sum > maxValue) maxValue = data[i].$sum ;
|
|
if (data[i].$min < minValue) minValue = data[i].$min ;
|
|
}
|
|
if(minValue>0) minValue =0;
|
|
}
|
|
return {max: maxValue, min: minValue};
|
|
},
|
|
/*adds colors to the gradient object*/
|
|
_setBarGradient:function(ctx,x1,y1,x2,y2,type,color,axis){
|
|
var gradient, offset, rgb, hsv, color0, stops;
|
|
if(type == "light"){
|
|
if(axis == "x")
|
|
gradient = ctx.createLinearGradient(x1,y1,x2,y1);
|
|
else
|
|
gradient = ctx.createLinearGradient(x1,y1,x1,y2);
|
|
stops = [[0,"#FFFFFF"],[0.9,color],[1,color]];
|
|
offset = 2;
|
|
}
|
|
else if(type == "falling"||type == "rising"){
|
|
if(axis == "x")
|
|
gradient = ctx.createLinearGradient(x1,y1,x2,y1);
|
|
else
|
|
gradient = ctx.createLinearGradient(x1,y1,x1,y2);
|
|
rgb = webix.color.toRgb(color);
|
|
hsv = webix.color.rgbToHsv(rgb[0],rgb[1],rgb[2]);
|
|
hsv[1] *= 1/2;
|
|
color0 = "rgb("+webix.color.hsvToRgb(hsv[0],hsv[1],hsv[2])+")";
|
|
if(type == "falling"){
|
|
stops = [[0,color0],[0.7,color],[1,color]];
|
|
}
|
|
else if(type == "rising"){
|
|
stops = [[0,color],[0.3,color],[1,color0]];
|
|
}
|
|
offset = 0;
|
|
}
|
|
else{
|
|
ctx.globalAlpha = 0.37;
|
|
offset = 0;
|
|
if(axis == "x")
|
|
gradient = ctx.createLinearGradient(x1,y2,x1,y1);
|
|
else
|
|
gradient = ctx.createLinearGradient(x1,y1,x2,y1);
|
|
stops = [[0,"#9d9d9d"],[0.3,"#e8e8e8"],[0.45,"#ffffff"],[0.55,"#ffffff"],[0.7,"#e8e8e8"],[1,"#9d9d9d"]];
|
|
}
|
|
this._gradient(gradient,stops);
|
|
return {gradient: gradient,offset: offset};
|
|
},
|
|
/**
|
|
* returns the x and y position
|
|
* @param: a - angle
|
|
* @param: x - start x position
|
|
* @param: y - start y position
|
|
* @param: r - destination to the point
|
|
*/
|
|
_getPositionByAngle:function(a,x,y,r){
|
|
a *= (-1);
|
|
x = x+Math.cos(a)*r;
|
|
y = y-Math.sin(a)*r;
|
|
return {x:x,y:y};
|
|
},
|
|
_gradient:function(gradient,stops){
|
|
for(var i=0; i< stops.length; i++){
|
|
gradient.addColorStop(stops[i][0],stops[i][1]);
|
|
}
|
|
},
|
|
_path: function(ctx,points){
|
|
var i, method;
|
|
for(i = 0; i< points.length; i++){
|
|
method = (i?"lineTo":"moveTo");
|
|
if(points[i].length>2)
|
|
method = "arc";
|
|
ctx[method].apply(ctx,points[i]);
|
|
}
|
|
},
|
|
_addMapRect:function(map,id,points,bounds,sIndex){
|
|
map.addRect(id,[points[0].x-bounds.x,points[0].y-bounds.y,points[1].x-bounds.x,points[1].y-bounds.y],sIndex);
|
|
}
|
|
}, webix.Group, webix.AutoTooltip, webix.DataLoader, webix.MouseEvents, webix.EventSystem , webix.ui.view);
|
|
|
|
|
|
webix.extend(webix.ui.chart, {
|
|
$render_pie:function(ctx,data,x,y,sIndex,map){
|
|
this._renderPie(ctx,data,x,y,1,map,sIndex);
|
|
|
|
},
|
|
/**
|
|
* renders a pie chart
|
|
* @param: ctx - canvas object
|
|
* @param: data - object those need to be displayed
|
|
* @param: x - the width of the container
|
|
* @param: y - the height of the container
|
|
* @param: ky - value from 0 to 1 that defines an angle of inclination (0<ky<1 - 3D chart)
|
|
*/
|
|
_renderPie:function(ctx,data,point0,point1,ky,map,sIndex){
|
|
if(!data.length)
|
|
return;
|
|
var coord = this._getPieParameters(point0,point1);
|
|
/*pie radius*/
|
|
var radius = (this._settings.radius?this._settings.radius:coord.radius);
|
|
if(radius<0)
|
|
return;
|
|
|
|
/*real values*/
|
|
var values = this._getValues(data);
|
|
|
|
var totalValue = this._getTotalValue(values);
|
|
|
|
/*weighed values (the ratio of object value to total value)*/
|
|
var ratios = this._getRatios(values,totalValue);
|
|
|
|
/*pie center*/
|
|
var x0 = (this._settings.x?this._settings.x:coord.x);
|
|
var y0 = (this._settings.y?this._settings.y:coord.y);
|
|
/*adds shadow to the 2D pie*/
|
|
if(ky==1&&this._settings.shadow)
|
|
this._addShadow(ctx,x0,y0,radius);
|
|
|
|
/*changes vertical position of the center according to 3Dpie cant*/
|
|
y0 = y0/ky;
|
|
/*the angle defines the 1st edge of the sector*/
|
|
var alpha0 = -Math.PI/2;
|
|
var angles = [];
|
|
/*changes Canvas vertical scale*/
|
|
ctx.scale(1,ky);
|
|
/*adds radial gradient to a pie*/
|
|
if (this._settings.gradient){
|
|
var x1 = (ky!=1?x0+radius/3:x0);
|
|
var y1 = (ky!=1?y0+radius/3:y0);
|
|
this._showRadialGradient(ctx,x0,y0,radius,x1,y1);
|
|
}
|
|
for(var i = 0; i < data.length;i++){
|
|
if (!values[i]) continue;
|
|
/*drawing sector*/
|
|
//ctx.lineWidth = 2;
|
|
ctx.strokeStyle = this._settings.lineColor.call(this,data[i]);
|
|
ctx.beginPath();
|
|
ctx.moveTo(x0,y0);
|
|
angles.push(alpha0);
|
|
/*the angle defines the 2nd edge of the sector*/
|
|
var alpha1 = -Math.PI/2+ratios[i]-0.0001;
|
|
ctx.arc(x0,y0,radius,alpha0,alpha1,false);
|
|
ctx.lineTo(x0,y0);
|
|
|
|
var color = this._settings.color.call(this,data[i]);
|
|
ctx.fillStyle = color;
|
|
ctx.fill();
|
|
|
|
/*text that needs being displayed inside the sector*/
|
|
if(this._settings.pieInnerText)
|
|
this._drawSectorLabel(x0,y0,5*radius/6,alpha0,alpha1,ky,this._settings.pieInnerText(data[i],totalValue),true);
|
|
/*label outside the sector*/
|
|
if(this._settings.label)
|
|
this._drawSectorLabel(x0,y0,radius+this._settings.labelOffset,alpha0,alpha1,ky,this._settings.label(data[i]));
|
|
/*drawing lower part for 3D pie*/
|
|
if(ky!=1){
|
|
this._createLowerSector(ctx,x0,y0,alpha0,alpha1,radius,true);
|
|
ctx.fillStyle = "#000000";
|
|
ctx.globalAlpha = 0.2;
|
|
this._createLowerSector(ctx,x0,y0,alpha0,alpha1,radius,false);
|
|
ctx.globalAlpha = 1;
|
|
ctx.fillStyle = color;
|
|
}
|
|
/*creats map area (needed for events)*/
|
|
map.addSector(data[i].id,alpha0,alpha1,x0-point0.x,y0-point0.y/ky,radius,ky,sIndex);
|
|
|
|
alpha0 = alpha1;
|
|
}
|
|
/*renders radius lines and labels*/
|
|
ctx.globalAlpha = 0.8;
|
|
var p;
|
|
for(i=0;i< angles.length;i++){
|
|
p = this._getPositionByAngle(angles[i],x0,y0,radius);
|
|
this._drawLine(ctx,x0,y0,p.x,p.y,this._settings.lineColor.call(this,data[i]),2);
|
|
}
|
|
if(ky==1){
|
|
ctx.lineWidth = 2;
|
|
ctx.strokeStyle = "#ffffff";
|
|
ctx.beginPath();
|
|
ctx.arc(x0,y0,radius+1,0,2*Math.PI,false);
|
|
ctx.stroke();
|
|
}
|
|
ctx.globalAlpha =1;
|
|
|
|
ctx.scale(1,1/ky);
|
|
},
|
|
/**
|
|
* returns list of values
|
|
* @param: data array
|
|
*/
|
|
_getValues:function(data){
|
|
var v = [];
|
|
for(var i = 0; i < data.length;i++)
|
|
v.push(Math.abs(parseFloat(this._settings.value(data[i])||0)));
|
|
return v;
|
|
},
|
|
/**
|
|
* returns total value
|
|
* @param: the array of values
|
|
*/
|
|
_getTotalValue:function(values){
|
|
var t=0;
|
|
for(var i = 0; i < values.length;i++)
|
|
t += values[i];
|
|
return t;
|
|
},
|
|
/**
|
|
* gets angles for all values
|
|
* @param: the array of values
|
|
* @param: total value (optional)
|
|
*/
|
|
_getRatios:function(values,totalValue){
|
|
var value;
|
|
var ratios = [];
|
|
var prevSum = 0;
|
|
totalValue = totalValue||this._getTotalValue(values);
|
|
for(var i = 0; i < values.length;i++){
|
|
value = values[i];
|
|
|
|
ratios[i] = Math.PI*2*(totalValue?((value+prevSum)/totalValue):(1/values.length));
|
|
prevSum += value;
|
|
}
|
|
return ratios;
|
|
},
|
|
/**
|
|
* returns calculated pie parameters: center position and radius
|
|
* @param: x - the width of a container
|
|
* @param: y - the height of a container
|
|
*/
|
|
_getPieParameters:function(point0,point1){
|
|
/*var offsetX = 0;
|
|
var offsetY = 0;
|
|
if(this._settings.legend &&this._settings.legend.layout!="x")
|
|
offsetX = this._settings.legend.width*(this._settings.legend.align=="right"?-1:1);
|
|
var x0 = (x + offsetX)/2;
|
|
if(this._settings.legend &&this._settings.legend.layout=="x")
|
|
offsetY = this._settings.legend.height*(this._settings.legend.valign=="bottom"?-1:1);
|
|
var y0 = (y+offsetY)/2;*/
|
|
var width = point1.x-point0.x;
|
|
var height = point1.y-point0.y;
|
|
var x0 = point0.x+width/2;
|
|
var y0 = point0.y+height/2;
|
|
var radius = Math.min(width/2,height/2);
|
|
return {"x":x0,"y":y0,"radius":radius};
|
|
},
|
|
/**
|
|
* creates lower part of sector in 3Dpie
|
|
* @param: ctx - canvas object
|
|
* @param: x0 - the horizontal position of the pie center
|
|
* @param: y0 - the vertical position of the pie center
|
|
* @param: a0 - the angle that defines the first edge of a sector
|
|
* @param: a1 - the angle that defines the second edge of a sector
|
|
* @param: R - pie radius
|
|
* @param: line (boolean) - if the sector needs a border
|
|
*/
|
|
_createLowerSector:function(ctx,x0,y0,a1,a2,R,line){
|
|
ctx.lineWidth = 1;
|
|
/*checks if the lower sector needs being displayed*/
|
|
if(!((a1<=0 && a2>=0)||(a1>=0 && a2<=Math.PI)||(Math.abs(a1-Math.PI)>0.003&&a1<=Math.PI && a2>=Math.PI))) return;
|
|
|
|
if(a1<=0 && a2>=0){
|
|
a1 = 0;
|
|
line = false;
|
|
this._drawSectorLine(ctx,x0,y0,R,a1,a2);
|
|
}
|
|
if(a1<=Math.PI && a2>=Math.PI){
|
|
a2 = Math.PI;
|
|
line = false;
|
|
this._drawSectorLine(ctx,x0,y0,R,a1,a2);
|
|
}
|
|
/*the height of 3D pie*/
|
|
var offset = (this._settings.pieHeight||Math.floor(R/4))/this._settings.cant;
|
|
ctx.beginPath();
|
|
ctx.arc(x0,y0,R,a1,a2,false);
|
|
ctx.lineTo(x0+R*Math.cos(a2),y0+R*Math.sin(a2)+offset);
|
|
ctx.arc(x0,y0+offset,R,a2,a1,true);
|
|
ctx.lineTo(x0+R*Math.cos(a1),y0+R*Math.sin(a1));
|
|
ctx.fill();
|
|
if(line)
|
|
ctx.stroke();
|
|
},
|
|
/**
|
|
* draws a serctor arc
|
|
*/
|
|
_drawSectorLine:function(ctx,x0,y0,R,a1,a2){
|
|
ctx.beginPath();
|
|
ctx.arc(x0,y0,R,a1,a2,false);
|
|
ctx.stroke();
|
|
},
|
|
/**
|
|
* adds a shadow to pie
|
|
* @param: ctx - canvas object
|
|
* @param: x - the horizontal position of the pie center
|
|
* @param: y - the vertical position of the pie center
|
|
* @param: R - pie radius
|
|
*/
|
|
_addShadow:function(ctx,x,y,R){
|
|
ctx.globalAlpha = 0.5;
|
|
var shadows = ["#c4c4c4","#c6c6c6","#cacaca","#dcdcdc","#dddddd","#e0e0e0","#eeeeee","#f5f5f5","#f8f8f8"];
|
|
for(var i = shadows.length-1;i>-1;i--){
|
|
ctx.beginPath();
|
|
ctx.fillStyle = shadows[i];
|
|
ctx.arc(x+1,y+1,R+i,0,Math.PI*2,true);
|
|
ctx.fill();
|
|
}
|
|
ctx.globalAlpha = 1;
|
|
},
|
|
/**
|
|
* returns a gray gradient
|
|
* @param: gradient - gradient object
|
|
*/
|
|
_getGrayGradient:function(gradient){
|
|
gradient.addColorStop(0.0,"#ffffff");
|
|
gradient.addColorStop(0.7,"#7a7a7a");
|
|
gradient.addColorStop(1.0,"#000000");
|
|
return gradient;
|
|
},
|
|
/**
|
|
* adds gray radial gradient
|
|
* @param: ctx - canvas object
|
|
* @param: x - the horizontal position of the pie center
|
|
* @param: y - the vertical position of the pie center
|
|
* @param: radius - pie radius
|
|
* @param: x0 - the horizontal position of a gradient center
|
|
* @param: y0 - the vertical position of a gradient center
|
|
*/
|
|
_showRadialGradient:function(ctx,x,y,radius,x0,y0){
|
|
//ctx.globalAlpha = 0.3;
|
|
ctx.beginPath();
|
|
var gradient;
|
|
if(typeof this._settings.gradient!= "function"){
|
|
gradient = ctx.createRadialGradient(x0,y0,radius/4,x,y,radius);
|
|
gradient = this._getGrayGradient(gradient);
|
|
}
|
|
else gradient = this._settings.gradient(gradient);
|
|
ctx.fillStyle = gradient;
|
|
ctx.arc(x,y,radius,0,Math.PI*2,true);
|
|
ctx.fill();
|
|
//ctx.globalAlpha = 1;
|
|
ctx.globalAlpha = 0.7;
|
|
},
|
|
/**
|
|
* returns the calculates pie parameters: center position and radius
|
|
* @param: ctx - canvas object
|
|
* @param: x0 - the horizontal position of the pie center
|
|
* @param: y0 - the vertical position of the pie center
|
|
* @param: R - pie radius
|
|
* @param: alpha1 - the angle that defines the 1st edge of a sector
|
|
* @param: alpha2 - the angle that defines the 2nd edge of a sector
|
|
* @param: ky - the value that defines an angle of inclination
|
|
* @param: text - label text
|
|
* @param: in_width (boolean) - if label needs being displayed inside a pie
|
|
*/
|
|
_drawSectorLabel:function(x0,y0,R,alpha1,alpha2,ky,text,in_width){
|
|
var t = this.canvases[0].renderText(0,0,text,0,1);
|
|
if (!t) return;
|
|
|
|
//get existing width of text
|
|
var labelWidth = t.scrollWidth;
|
|
t.style.width = labelWidth+"px"; //adjust text label to fit all text
|
|
if (labelWidth>x0) labelWidth = x0; //the text can't be greater than half of view
|
|
|
|
//calculate expected correction based on default font metrics
|
|
var width = (alpha2-alpha1<0.2?4:8);
|
|
if (in_width) width = labelWidth/1.8;
|
|
var alpha = alpha1+(alpha2-alpha1)/2;
|
|
|
|
//position and its correction
|
|
R = R-(width-8)/2;
|
|
var corr_x = - width;
|
|
var corr_y = -8;
|
|
var align = "right";
|
|
|
|
//for items in left upper and lower sector
|
|
if(alpha>=Math.PI/2 && alpha<Math.PI || alpha<=3*Math.PI/2 && alpha>=Math.PI){
|
|
corr_x = -labelWidth-corr_x+1;/*correction for label width*/
|
|
align = "left";
|
|
}
|
|
|
|
/*
|
|
calculate position of text
|
|
basically get point at center of pie sector
|
|
*/
|
|
var offset = 0;
|
|
|
|
if(!in_width&&ky<1&&(alpha>0&&alpha<Math.PI))
|
|
offset = (this._settings.height||Math.floor(R/4))/ky;
|
|
|
|
var y = (y0+Math.floor((R+offset)*Math.sin(alpha)))*ky+corr_y;
|
|
var x = x0+Math.floor((R+width/2)*Math.cos(alpha))+corr_x;
|
|
|
|
/*
|
|
if pie sector starts in left of right part pie,
|
|
related text must be placed to the left of to the right of pie as well
|
|
*/
|
|
var left_end = (alpha2 < Math.PI/2+0.01);
|
|
var left_start = (alpha1 < Math.PI/2);
|
|
if (left_start && left_end){
|
|
x = Math.max(x,x0+3); //right part of pie
|
|
/*if(alpha2-alpha1<0.2)
|
|
x = x0;*/
|
|
}
|
|
else if (!left_start && !left_end)
|
|
x = Math.min(x,x0-labelWidth); //left part of pie
|
|
else if (!in_width&&(alpha>=Math.PI/2 && alpha<Math.PI || alpha<=3*Math.PI/2 && alpha>=Math.PI)){
|
|
x += labelWidth/3;
|
|
}
|
|
|
|
|
|
//we need to set position of text manually, based on above calculations
|
|
t.style.top = y+"px";
|
|
t.style.left = x+"px";
|
|
t.style.width = labelWidth+"px";
|
|
t.style.textAlign = align;
|
|
t.style.whiteSpace = "nowrap";
|
|
},
|
|
$render_pie3D:function(ctx,data,x,y,sIndex,map){
|
|
this._renderPie(ctx,data,x,y,this._settings.cant,map);
|
|
},
|
|
$render_donut:function(ctx,data,point0,point1,sIndex,map){
|
|
if(!data.length)
|
|
return;
|
|
this._renderPie(ctx,data,point0,point1,1,map,sIndex);
|
|
var config = this._settings;
|
|
var coord = this._getPieParameters(point0,point1);
|
|
var pieRadius = (config.radius?config.radius:coord.radius);
|
|
var innerRadius = ((config.innerRadius&&(config.innerRadius<pieRadius))?config.innerRadius:pieRadius/3);
|
|
var x0 = (config.x?config.x:coord.x);
|
|
var y0 = (config.y?config.y:coord.y);
|
|
ctx.fillStyle = "#ffffff";
|
|
ctx.beginPath();
|
|
ctx.arc(x0,y0,innerRadius,0,Math.PI*2,true);
|
|
ctx.fill();
|
|
}
|
|
});
|
|
//+pie3d
|
|
webix.extend(webix.ui.chart, {
|
|
/**
|
|
* renders a bar chart
|
|
* @param: ctx - canvas object
|
|
* @param: data - object those need to be displayed
|
|
* @param: x - the width of the container
|
|
* @param: y - the height of the container
|
|
* @param: sIndex - index of drawing chart
|
|
*/
|
|
$render_bar:function(ctx, data, point0, point1, sIndex, map){
|
|
var barWidth, cellWidth,
|
|
i,
|
|
limits, maxValue, minValue,
|
|
relValue, valueFactor, relativeValues,
|
|
startValue, unit,
|
|
xax, yax,
|
|
totalHeight = point1.y-point0.y;
|
|
|
|
yax = !!this._settings.yAxis;
|
|
xax = !!this._settings.xAxis;
|
|
|
|
limits = this._getLimits();
|
|
maxValue = limits.max;
|
|
minValue = limits.min;
|
|
|
|
/*an available width for one bar*/
|
|
cellWidth = (point1.x-point0.x)/data.length;
|
|
|
|
|
|
/*draws x and y scales*/
|
|
if(!sIndex&&!(this._settings.origin!="auto"&&!yax)){
|
|
this._drawScales(data,point0, point1,minValue,maxValue,cellWidth);
|
|
}
|
|
|
|
/*necessary for automatic scale*/
|
|
if(yax){
|
|
maxValue = parseFloat(this._settings.yAxis.end);
|
|
minValue = parseFloat(this._settings.yAxis.start);
|
|
}
|
|
|
|
/*unit calculation (bar_height = value*unit)*/
|
|
relativeValues = this._getRelativeValue(minValue,maxValue);
|
|
relValue = relativeValues[0];
|
|
valueFactor = relativeValues[1];
|
|
|
|
unit = (relValue?totalHeight/relValue:relValue);
|
|
|
|
if(!yax&&!(this._settings.origin!="auto"&&xax)){
|
|
/*defines start value for better representation of small values*/
|
|
startValue = 10;
|
|
unit = (relValue?(totalHeight-startValue)/relValue:startValue);
|
|
}
|
|
/*if yAxis isn't set, but with custom origin */
|
|
if(!sIndex&&(this._settings.origin!="auto"&&!yax)&&this._settings.origin>minValue){
|
|
this._drawXAxis(ctx,data,point0,point1,cellWidth,point1.y-unit*(this._settings.origin-minValue));
|
|
}
|
|
|
|
/*a real bar width */
|
|
barWidth = parseInt(this._settings.barWidth,10);
|
|
var seriesNumber = 0;
|
|
var seriesIndex = 0;
|
|
for(i=0; i<this._series.length; i++ ){
|
|
if(i == sIndex){
|
|
seriesIndex = seriesNumber;
|
|
}
|
|
if(this._series[i].type == "bar")
|
|
seriesNumber++;
|
|
}
|
|
if(this._series&&(barWidth*seriesNumber+4)>cellWidth) barWidth = parseInt(cellWidth/seriesNumber-4,10);
|
|
|
|
/*the half of distance between bars*/
|
|
var barOffset = (cellWidth - barWidth*seriesNumber)/2;
|
|
|
|
/*the radius of rounding in the top part of each bar*/
|
|
var radius = (typeof this._settings.radius!="undefined"?parseInt(this._settings.radius,10):Math.round(barWidth/5));
|
|
|
|
var inner_gradient = false;
|
|
var gradient = this._settings.gradient;
|
|
|
|
if(gradient && typeof(gradient) != "function"){
|
|
inner_gradient = gradient;
|
|
gradient = false;
|
|
} else if (gradient){
|
|
gradient = ctx.createLinearGradient(0,point1.y,0,point0.y);
|
|
this._settings.gradient(gradient);
|
|
}
|
|
/*draws a black line if the horizontal scale isn't defined*/
|
|
if(!xax){
|
|
this._drawLine(ctx,point0.x,point1.y+0.5,point1.x,point1.y+0.5,"#000000",1); //hardcoded color!
|
|
}
|
|
|
|
for(i=0; i < data.length;i ++){
|
|
|
|
var value = parseFloat(this._settings.value(data[i])||0);
|
|
if(this._logScaleCalc)
|
|
value = this._log10(value);
|
|
|
|
if(!value || isNaN(value))
|
|
continue;
|
|
|
|
if(value>maxValue) value = maxValue;
|
|
value -= minValue;
|
|
value *= valueFactor;
|
|
|
|
/*start point (bottom left)*/
|
|
var x0 = point0.x + barOffset + i*cellWidth+(barWidth+1)*seriesIndex;
|
|
var y0 = point1.y;
|
|
|
|
if(value<0||(this._settings.yAxis&&value===0&&!(this._settings.origin!="auto"&&this._settings.origin>minValue))){
|
|
this.canvases[sIndex].renderTextAt(true, true, x0+Math.floor(barWidth/2),y0,this._settings.label(data[i]));
|
|
continue;
|
|
}
|
|
|
|
/*takes start value into consideration*/
|
|
if(!yax&&!(this._settings.origin!="auto"&&xax)) value += startValue/unit;
|
|
|
|
var color = gradient||this._settings.color.call(this,data[i]);
|
|
|
|
|
|
/*drawing bar body*/
|
|
ctx.globalAlpha = this._settings.alpha.call(this,data[i]);
|
|
var points = this._drawBar(ctx,point0,x0,y0,barWidth,minValue,radius,unit,value,color,gradient,inner_gradient);
|
|
if (inner_gradient){
|
|
this._drawBarGradient(ctx,x0,y0,barWidth,minValue,radius,unit,value,color,inner_gradient);
|
|
}
|
|
/*drawing the gradient border of a bar*/
|
|
if(this._settings.border)
|
|
this._drawBarBorder(ctx,x0,y0,barWidth,minValue,radius,unit,value,color);
|
|
|
|
ctx.globalAlpha = 1;
|
|
|
|
/*sets a bar label*/
|
|
if(points[0]!=x0)
|
|
this.canvases[sIndex].renderTextAt(false, true, x0+Math.floor(barWidth/2),points[1],this._settings.label(data[i]));
|
|
else
|
|
this.canvases[sIndex].renderTextAt(true, true, x0+Math.floor(barWidth/2),points[3],this._settings.label(data[i]));
|
|
/*defines a map area for a bar*/
|
|
map.addRect(data[i].id,[x0-point0.x,points[3]-point0.y,points[2]-point0.x,points[1]-point0.y],sIndex);
|
|
//this._addMapRect(map,data[i].id,[{x:x0,y:points[3]},{x:points[2],y:points[1]}],point0,sIndex);
|
|
}
|
|
},
|
|
_correctBarParams:function(ctx,x,y,value,unit,barWidth,minValue){
|
|
var xax = this._settings.xAxis;
|
|
var axisStart = y;
|
|
if(!!xax&&this._settings.origin!="auto" && (this._settings.origin>minValue)){
|
|
y -= (this._settings.origin-minValue)*unit;
|
|
axisStart = y;
|
|
value = value-(this._settings.origin-minValue);
|
|
if(value < 0){
|
|
value *= (-1);
|
|
ctx.translate(x+barWidth,y);
|
|
ctx.rotate(Math.PI);
|
|
x = 0;
|
|
y = 0;
|
|
}
|
|
y -= 0.5;
|
|
}
|
|
|
|
return {value:value,x0:x,y0:y,start:axisStart};
|
|
},
|
|
_drawBar:function(ctx,point0,x0,y0,barWidth,minValue,radius,unit,value,color,gradient,inner_gradient){
|
|
ctx.save();
|
|
ctx.fillStyle = color;
|
|
var p = this._correctBarParams(ctx,x0,y0,value,unit,barWidth,minValue);
|
|
var points = this._setBarPoints(ctx,p.x0,p.y0,barWidth,radius,unit,p.value,(this._settings.border?1:0));
|
|
if (gradient&&!inner_gradient) ctx.lineTo(p.x0+(this._settings.border?1:0),point0.y); //fix gradient sphreading
|
|
ctx.fill();
|
|
ctx.restore();
|
|
var x1 = p.x0;
|
|
var x2 = (p.x0!=x0?x0+points[0]:points[0]);
|
|
var y1 = (p.x0!=x0?(p.start-points[1]-p.y0):p.y0);
|
|
var y2 = (p.x0!=x0?p.start-p.y0:points[1]);
|
|
|
|
return [x1,y1,x2,y2];
|
|
},
|
|
_setBorderStyles:function(ctx,color){
|
|
var hsv,rgb;
|
|
rgb = webix.color.toRgb(color);
|
|
hsv = webix.color.rgbToHsv(rgb[0],rgb[1],rgb[2]);
|
|
hsv[2] /= 1.4;
|
|
color = "rgb("+webix.color.hsvToRgb(hsv[0],hsv[1],hsv[2])+")";
|
|
ctx.strokeStyle = color;
|
|
if(ctx.globalAlpha==1)
|
|
ctx.globalAlpha = 0.9;
|
|
},
|
|
_drawBarBorder:function(ctx,x0,y0,barWidth,minValue,radius,unit,value,color){
|
|
var p;
|
|
ctx.save();
|
|
p = this._correctBarParams(ctx,x0,y0,value,unit,barWidth,minValue);
|
|
this._setBorderStyles(ctx,color);
|
|
this._setBarPoints(ctx,p.x0,p.y0,barWidth,radius,unit,p.value,ctx.lineWidth/2,1);
|
|
ctx.stroke();
|
|
/*ctx.fillStyle = color;
|
|
this._setBarPoints(ctx,p.x0,p.y0,barWidth,radius,unit,p.value,0);
|
|
ctx.lineTo(p.x0,0);
|
|
ctx.fill()
|
|
|
|
|
|
ctx.fillStyle = "#000000";
|
|
ctx.globalAlpha = 0.37;
|
|
|
|
this._setBarPoints(ctx,p.x0,p.y0,barWidth,radius,unit,p.value,0);
|
|
ctx.fill()
|
|
*/
|
|
ctx.restore();
|
|
},
|
|
_drawBarGradient:function(ctx,x0,y0,barWidth,minValue,radius,unit,value,color,inner_gradient){
|
|
ctx.save();
|
|
var p = this._correctBarParams(ctx,x0,y0,value,unit,barWidth,minValue);
|
|
var gradParam = this._setBarGradient(ctx,p.x0,p.y0,p.x0+barWidth,p.y0-unit*p.value+2,inner_gradient,color,"y");
|
|
var borderOffset = this._settings.border?1:0;
|
|
ctx.fillStyle = gradParam.gradient;
|
|
this._setBarPoints(ctx,p.x0+gradParam.offset,p.y0,barWidth-gradParam.offset*2,radius,unit,p.value,gradParam.offset+borderOffset);
|
|
ctx.fill();
|
|
ctx.restore();
|
|
},
|
|
/**
|
|
* sets points for bar and returns the position of the bottom right point
|
|
* @param: ctx - canvas object
|
|
* @param: x0 - the x position of start point
|
|
* @param: y0 - the y position of start point
|
|
* @param: barWidth - bar width
|
|
* @param: radius - the rounding radius of the top
|
|
* @param: unit - the value defines the correspondence between item value and bar height
|
|
* @param: value - item value
|
|
* @param: offset - the offset from expected bar edge (necessary for drawing border)
|
|
*/
|
|
_setBarPoints:function(ctx,x0,y0,barWidth,radius,unit,value,offset,skipBottom){
|
|
/*correction for displaing small values (when rounding radius is bigger than bar height)*/
|
|
ctx.beginPath();
|
|
//y0 = 0.5;
|
|
var angle_corr = 0;
|
|
if(radius>unit*value){
|
|
var cosA = (radius-unit*value)/radius;
|
|
if(cosA<=1&&cosA>=-1)
|
|
angle_corr = -Math.acos(cosA)+Math.PI/2;
|
|
}
|
|
/*start*/
|
|
ctx.moveTo(x0+offset,y0);
|
|
/*start of left rounding*/
|
|
var y1 = y0 - Math.floor(unit*value) + radius + (radius?0:offset);
|
|
if(radius<unit*value)
|
|
ctx.lineTo(x0+offset,y1);
|
|
/*left rounding*/
|
|
var x2 = x0 + radius;
|
|
|
|
if (radius&&radius>0)
|
|
ctx.arc(x2,y1,Math.max(radius-offset,0),-Math.PI+angle_corr,-Math.PI/2,false);
|
|
/*start of right rounding*/
|
|
var x3 = x0 + barWidth - radius - offset;
|
|
var y3 = y1 - radius + (radius?offset:0);
|
|
ctx.lineTo(x3,y3);
|
|
/*right rounding*/
|
|
if (radius&&radius>0)
|
|
ctx.arc(x3+offset,y1,Math.max(radius-offset,0),-Math.PI/2,0-angle_corr,false);
|
|
/*bottom right point*/
|
|
var x5 = x0 + barWidth-offset;
|
|
ctx.lineTo(x5,y0);
|
|
/*line to the start point*/
|
|
if(!skipBottom){
|
|
ctx.lineTo(x0+offset,y0);
|
|
}
|
|
// ctx.lineTo(x0,0); //IE fix!
|
|
return [x5,y3];
|
|
}
|
|
});
|
|
webix.extend(webix.ui.chart, {
|
|
/**
|
|
* renders a graphic
|
|
* @param: ctx - canvas object
|
|
* @param: data - object those need to be displayed
|
|
* @param: width - the width of the container
|
|
* @param: height - the height of the container
|
|
* @param: sIndex - index of drawing chart
|
|
*/
|
|
$render_line:function(ctx, data, point0, point1, sIndex, map){
|
|
var config,i,items,params,x0,x1,x2,y1,y2,y0,res1,res2;
|
|
params = this._calculateLineParams(ctx,data,point0,point1,sIndex);
|
|
config = this._settings;
|
|
|
|
if (data.length) {
|
|
x0 = (config.offset?point0.x+params.cellWidth*0.5:point0.x);
|
|
//finds items with data (excludes scale units)
|
|
items= [];
|
|
for(i=0; i < data.length;i++){
|
|
res2 = this._getPointY(data[i],point0,point1,params);
|
|
if(res2 || res2=="0"){
|
|
x2 = ((!i)?x0:params.cellWidth*i - 0.5 + x0);
|
|
y2 = (typeof res2 == "object"?res2.y0:res2);
|
|
if(i && this._settings.fixOverflow){
|
|
res1 = this._getPointY(data[i-1],point0,point1,params);
|
|
if(res1.out && res1.out == res2.out){
|
|
continue;
|
|
}
|
|
x1 = params.cellWidth*(i-1) - 0.5 + x0;
|
|
y1 = (typeof res1 == "object"?res1.y0:res1);
|
|
|
|
if(res1.out){
|
|
y0 = (res1.out == "min"?point1.y:point0.y);
|
|
items.push({x:this._calcOverflowX(x1,x2,y1,y2,y0),y:y0});
|
|
}
|
|
if(res2.out){
|
|
y0 = (res2.out == "min"?point1.y:point0.y);
|
|
items.push({x:this._calcOverflowX(x1,x2,y1,y2,y0),y:y0});
|
|
}
|
|
|
|
}
|
|
|
|
if(!res2.out)
|
|
items.push({x:x2, y: res2, index: i});
|
|
}
|
|
}
|
|
this._mapStart = point0;
|
|
for(i = 1; i <= items.length; i++){
|
|
//line start position
|
|
x1 = items[i-1].x;
|
|
y1 = items[i-1].y;
|
|
if(i<items.length){
|
|
//line end position
|
|
x2 = items[i].x;
|
|
y2 = items[i].y;
|
|
//line
|
|
this._drawLine(ctx,x1,y1,x2,y2,config.line.color.call(this,data[i-1]),config.line.width);
|
|
//line shadow
|
|
if(config.line&&config.line.shadow){
|
|
ctx.globalAlpha = 0.3;
|
|
this._drawLine(ctx,x1+2,y1+config.line.width+8,x2+2,y2+config.line.width+8,"#eeeeee",config.line.width+3);
|
|
ctx.globalAlpha = 1;
|
|
}
|
|
}
|
|
//item
|
|
if(typeof items[i-1].index != "undefined"){
|
|
this._drawItem(ctx,x1,y1,data[items[i-1].index],config.label(data[items[i-1].index]), sIndex, map, point0);
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
},
|
|
_calcOverflowX: function(x1,x2,y1,y2,y){
|
|
return x1 + ( y - y1 )*( x2 - x1 )/( y2 - y1 );
|
|
},
|
|
/**
|
|
* draws an item and its label
|
|
* @param: ctx - canvas object
|
|
* @param: x0 - the x position of a circle
|
|
* @param: y0 - the y position of a circle
|
|
* @param: obj - data object
|
|
* @param: label - (boolean) defines wherether label needs being drawn
|
|
*/
|
|
_drawItem:function(ctx,x0,y0,obj,label,sIndex,map){
|
|
var config = this._settings.item;
|
|
|
|
var R = parseInt(config.radius.call(this,obj),10)||0;
|
|
var mapStart = this._mapStart;
|
|
if(R){
|
|
ctx.save();
|
|
if(config.shadow){
|
|
ctx.lineWidth = 1;
|
|
ctx.strokeStyle = "#bdbdbd";
|
|
ctx.fillStyle = "#bdbdbd";
|
|
var alphas = [0.1,0.2,0.3];
|
|
for(var i=(alphas.length-1);i>=0;i--){
|
|
ctx.globalAlpha = alphas[i];
|
|
ctx.strokeStyle = "#d0d0d0";
|
|
ctx.beginPath();
|
|
this._strokeChartItem(ctx,x0,y0+2*R/3,R+i+1,config.type);
|
|
ctx.stroke();
|
|
}
|
|
ctx.beginPath();
|
|
ctx.globalAlpha = 0.3;
|
|
ctx.fillStyle = "#bdbdbd";
|
|
this._strokeChartItem(ctx,x0,y0+2*R/3,R+1,config.type);
|
|
ctx.fill();
|
|
}
|
|
ctx.restore();
|
|
ctx.lineWidth = config.borderWidth;
|
|
ctx.fillStyle = config.color.call(this,obj);
|
|
ctx.strokeStyle = config.borderColor.call(this,obj);
|
|
ctx.globalAlpha = config.alpha.call(this,obj);
|
|
ctx.beginPath();
|
|
this._strokeChartItem(ctx,x0,y0,R+1,config.type);
|
|
ctx.fill();
|
|
ctx.stroke();
|
|
ctx.globalAlpha = 1;
|
|
}
|
|
/*item label*/
|
|
if(label){
|
|
this.canvases[sIndex].renderTextAt(false, true, x0,y0-R-this._settings.labelOffset,this._settings.label.call(this,obj));
|
|
}
|
|
if(map){
|
|
var areaPos = (this._settings.eventRadius||R+1);
|
|
//this._addMapRect(map,obj.id,[{x:x0-areaPos,y:y0-areaPos},{x0+areaPos,y:y0+areaPos}],point0,sIndex);
|
|
map.addRect(obj.id,[x0-areaPos-mapStart.x,y0-areaPos-mapStart.y,x0+areaPos-mapStart.x,y0+areaPos-mapStart.y],sIndex);
|
|
}
|
|
|
|
},
|
|
_strokeChartItem:function(ctx,x0,y0,R,type){
|
|
var p=[];
|
|
if(type && (type=="square" || type=="s")){
|
|
R *= Math.sqrt(2)/2;
|
|
p = [
|
|
[x0-R-ctx.lineWidth/2,y0-R],
|
|
[x0+R,y0-R],
|
|
[x0+R,y0+R],
|
|
[x0-R,y0+R],
|
|
[x0-R,y0-R]
|
|
];
|
|
}
|
|
else if(type && (type=="diamond" || type=="d")){
|
|
var corr = (ctx.lineWidth>1?ctx.lineWidth*Math.sqrt(2)/4:0);
|
|
p = [
|
|
[x0,y0-R],
|
|
[x0+R,y0],
|
|
[x0,y0+R],
|
|
[x0-R,y0],
|
|
[x0+corr,y0-R-corr]
|
|
];
|
|
}
|
|
else if(type && (type=="triangle" || type=="t")){
|
|
p = [
|
|
[x0,y0-R],
|
|
[x0+Math.sqrt(3)*R/2,y0+R/2],
|
|
[x0-Math.sqrt(3)*R/2,y0+R/2],
|
|
[x0,y0-R]
|
|
];
|
|
}
|
|
else
|
|
p = [
|
|
[x0,y0,R,0,Math.PI*2,true]
|
|
];
|
|
this._path(ctx,p);
|
|
},
|
|
/**
|
|
* gets the vertical position of the item
|
|
* @param: data - data object
|
|
* @param: y0 - the y position of chart start
|
|
* @param: y1 - the y position of chart end
|
|
* @param: params - the object with elements: minValue, maxValue, unit, valueFactor (the value multiple of 10)
|
|
*/
|
|
_getPointY: function(data,point0,point1,params){
|
|
var minValue = params.minValue;
|
|
var maxValue = params.maxValue;
|
|
var unit = params.unit;
|
|
var valueFactor = params.valueFactor;
|
|
/*the real value of an object*/
|
|
var value = this._settings.value(data);
|
|
if(this._logScaleCalc){
|
|
value = this._log10(value);
|
|
}
|
|
/*a relative value*/
|
|
var v = (parseFloat(value||0) - minValue)*valueFactor;
|
|
if(!this._settings.yAxis)
|
|
v += params.startValue/unit;
|
|
/*a vertical coordinate*/
|
|
var y = point1.y - unit*v;
|
|
/*the limit of the max and min values*/
|
|
if(this._settings.fixOverflow && ( this._settings.type == "line" || this._settings.type == "area")){
|
|
if(value > maxValue)
|
|
y = {y: point0.y, y0: y, out: "max"};
|
|
else if(v<0 || value < minValue)
|
|
y = {y: point1.y, y0: y, out: "min"};
|
|
}
|
|
else{
|
|
if(value > maxValue)
|
|
y = point0.y;
|
|
if(v<0 || value < minValue)
|
|
y = point1.y;
|
|
}
|
|
return y;
|
|
},
|
|
_calculateLineParams: function(ctx,data,point0,point1,sIndex){
|
|
var params = {};
|
|
|
|
/*maxValue - minValue*/
|
|
var relValue;
|
|
|
|
/*available height*/
|
|
params.totalHeight = point1.y-point0.y;
|
|
|
|
/*a space available for a single item*/
|
|
//params.cellWidth = Math.round((point1.x-point0.x)/((!this._settings.offset&&this._settings.yAxis)?(data.length-1):data.length));
|
|
if(this._settings.cellWidth)
|
|
params.cellWidth = Math.min(point1.x-point0.x, this._settings.cellWidth);
|
|
else
|
|
params.cellWidth = (point1.x-point0.x)/((!this._settings.offset)?(data.length-1):data.length);
|
|
/*scales*/
|
|
var yax = !!this._settings.yAxis;
|
|
|
|
var limits = (this._settings.type.indexOf("stacked")!=-1?this._getStackedLimits(data):this._getLimits());
|
|
params.maxValue = limits.max;
|
|
params.minValue = limits.min;
|
|
|
|
/*draws x and y scales*/
|
|
if(!sIndex)
|
|
this._drawScales(data, point0, point1,params.minValue,params.maxValue,params.cellWidth);
|
|
|
|
/*necessary for automatic scale*/
|
|
if(yax){
|
|
params.maxValue = parseFloat(this._settings.yAxis.end);
|
|
params.minValue = parseFloat(this._settings.yAxis.start);
|
|
}
|
|
|
|
/*unit calculation (y_position = value*unit)*/
|
|
var relativeValues = this._getRelativeValue(params.minValue,params.maxValue);
|
|
relValue = relativeValues[0];
|
|
params.valueFactor = relativeValues[1];
|
|
params.unit = (relValue?params.totalHeight/relValue:10);
|
|
|
|
params.startValue = 0;
|
|
if(!yax){
|
|
/*defines start value for better representation of small values*/
|
|
params.startValue = 10;
|
|
if(params.unit!=params.totalHeight)
|
|
params.unit = (relValue?(params.totalHeight - params.startValue)/relValue:10);
|
|
}
|
|
return params;
|
|
}
|
|
});
|
|
|
|
|
|
webix.extend(webix.ui.chart, {
|
|
/**
|
|
* renders a bar chart
|
|
* @param: ctx - canvas object
|
|
* @param: data - object those need to be displayed
|
|
* @param: x - the width of the container
|
|
* @param: y - the height of the container
|
|
* @param: sIndex - index of drawing chart
|
|
*/
|
|
$render_barH:function(ctx, data, point0, point1, sIndex, map){
|
|
var barOffset, barWidth, cellWidth, color, gradient, i, limits, maxValue, minValue,
|
|
innerGradient, valueFactor, relValue, radius, relativeValues,
|
|
startValue, totalWidth,value, unit, x0, y0, xax;
|
|
|
|
/*an available width for one bar*/
|
|
cellWidth = (point1.y-point0.y)/data.length;
|
|
|
|
limits = this._getLimits("h");
|
|
|
|
maxValue = limits.max;
|
|
minValue = limits.min;
|
|
|
|
totalWidth = point1.x-point0.x;
|
|
|
|
xax = !!this._settings.xAxis;
|
|
|
|
/*draws x and y scales*/
|
|
if(!sIndex )
|
|
this._drawHScales(ctx,data,point0, point1,minValue,maxValue,cellWidth);
|
|
|
|
/*necessary for automatic scale*/
|
|
if(xax ){
|
|
maxValue = parseFloat(this._settings.xAxis.end);
|
|
minValue = parseFloat(this._settings.xAxis.start);
|
|
}
|
|
|
|
/*unit calculation (bar_height = value*unit)*/
|
|
relativeValues = this._getRelativeValue(minValue,maxValue);
|
|
relValue = relativeValues[0];
|
|
valueFactor = relativeValues[1];
|
|
|
|
unit = (relValue?totalWidth/relValue:10);
|
|
if(!xax){
|
|
/*defines start value for better representation of small values*/
|
|
startValue = 10;
|
|
unit = (relValue?(totalWidth-startValue)/relValue:10);
|
|
}
|
|
|
|
|
|
/*a real bar width */
|
|
barWidth = parseInt(this._settings.barWidth,10);
|
|
if((barWidth*this._series.length+4)>cellWidth) barWidth = cellWidth/this._series.length-4;
|
|
/*the half of distance between bars*/
|
|
barOffset = Math.floor((cellWidth - barWidth*this._series.length)/2);
|
|
/*the radius of rounding in the top part of each bar*/
|
|
radius = (typeof this._settings.radius!="undefined"?parseInt(this._settings.radius,10):Math.round(barWidth/5));
|
|
|
|
innerGradient = false;
|
|
gradient = this._settings.gradient;
|
|
|
|
if (gradient&&typeof(gradient) != "function"){
|
|
innerGradient = gradient;
|
|
gradient = false;
|
|
} else if (gradient){
|
|
gradient = ctx.createLinearGradient(point0.x,point0.y,point1.x,point0.y);
|
|
this._settings.gradient(gradient);
|
|
}
|
|
/*draws a black line if the horizontal scale isn't defined*/
|
|
if(!xax){
|
|
this._drawLine(ctx,point0.x-0.5,point0.y,point0.x-0.5,point1.y,"#000000",1); //hardcoded color!
|
|
}
|
|
|
|
|
|
|
|
for(i=0; i < data.length;i ++){
|
|
|
|
|
|
value = parseFloat(this._settings.value(data[i]||0));
|
|
if(this._logScaleCalc)
|
|
value = this._log10(value);
|
|
|
|
if(!value || isNaN(value))
|
|
continue;
|
|
|
|
if(value>maxValue) value = maxValue;
|
|
value -= minValue;
|
|
value *= valueFactor;
|
|
|
|
/*start point (bottom left)*/
|
|
x0 = point0.x;
|
|
y0 = point0.y+ barOffset + i*cellWidth+(barWidth+1)*sIndex;
|
|
|
|
if((value<0&&this._settings.origin=="auto")||(this._settings.xAxis&&value===0&&!(this._settings.origin!="auto"&&this._settings.origin>minValue))){
|
|
this.canvases[sIndex].renderTextAt("middle", "right", x0+10,y0+barWidth/2+barOffset,this._settings.label(data[i]));
|
|
continue;
|
|
}
|
|
if(value<0&&this._settings.origin!="auto"&&this._settings.origin>minValue){
|
|
value = 0;
|
|
}
|
|
|
|
/*takes start value into consideration*/
|
|
if(!xax) value += startValue/unit;
|
|
color = gradient||this._settings.color.call(this,data[i]);
|
|
|
|
/*drawing the gradient border of a bar*/
|
|
if(this._settings.border){
|
|
this._drawBarHBorder(ctx,x0,y0,barWidth,minValue,radius,unit,value,color);
|
|
}
|
|
|
|
/*drawing bar body*/
|
|
ctx.globalAlpha = this._settings.alpha.call(this,data[i]);
|
|
var points = this._drawBarH(ctx,point1,x0,y0,barWidth,minValue,radius,unit,value,color,gradient,innerGradient);
|
|
if (innerGradient){
|
|
this._drawBarHGradient(ctx,x0,y0,barWidth,minValue,radius,unit,value,color,innerGradient);
|
|
|
|
}
|
|
ctx.globalAlpha = 1;
|
|
|
|
|
|
/*sets a bar label and map area*/
|
|
|
|
if(points[3]==y0){
|
|
this.canvases[sIndex].renderTextAt("middle", "left", points[0]-5,points[3]+Math.floor(barWidth/2),this._settings.label(data[i]));
|
|
map.addRect(data[i].id,[points[0]-point0.x,points[3]-point0.y,points[2]-point0.x,points[3]+barWidth-point0.y],sIndex);
|
|
|
|
}else{
|
|
this.canvases[sIndex].renderTextAt("middle", false, points[2]+5,points[1]+Math.floor(barWidth/2),this._settings.label(data[i]));
|
|
map.addRect(data[i].id,[points[0]-point0.x,y0-point0.y,points[2]-point0.x,points[3]-point0.y],sIndex);
|
|
}
|
|
|
|
}
|
|
},
|
|
/**
|
|
* sets points for bar and returns the position of the bottom right point
|
|
* @param: ctx - canvas object
|
|
* @param: x0 - the x position of start point
|
|
* @param: y0 - the y position of start point
|
|
* @param: barWidth - bar width
|
|
* @param: radius - the rounding radius of the top
|
|
* @param: unit - the value defines the correspondence between item value and bar height
|
|
* @param: value - item value
|
|
* @param: offset - the offset from expected bar edge (necessary for drawing border)
|
|
*/
|
|
_setBarHPoints:function(ctx,x0,y0,barWidth,radius,unit,value,offset,skipLeft){
|
|
/*correction for displaing small values (when rounding radius is bigger than bar height)*/
|
|
var angle_corr = 0;
|
|
|
|
if(radius>unit*value){
|
|
var sinA = (radius-unit*value)/radius;
|
|
angle_corr = -Math.asin(sinA)+Math.PI/2;
|
|
}
|
|
/*start*/
|
|
ctx.moveTo(x0,y0+offset);
|
|
/*start of left rounding*/
|
|
var x1 = x0 + unit*value - radius - (radius?0:offset);
|
|
x1 = Math.max(x0,x1);
|
|
if(radius<unit*value)
|
|
ctx.lineTo(x1,y0+offset);
|
|
/*left rounding*/
|
|
var y2 = y0 + radius;
|
|
if (radius&&radius>0)
|
|
ctx.arc(x1,y2,radius-offset,-Math.PI/2+angle_corr,0,false);
|
|
/*start of right rounding*/
|
|
var y3 = y0 + barWidth - radius - (radius?0:offset);
|
|
var x3 = x1 + radius - (radius?offset:0);
|
|
ctx.lineTo(x3,y3);
|
|
/*right rounding*/
|
|
if (radius&&radius>0)
|
|
ctx.arc(x1,y3,radius-offset,0,Math.PI/2-angle_corr,false);
|
|
/*bottom right point*/
|
|
var y5 = y0 + barWidth-offset;
|
|
ctx.lineTo(x0,y5);
|
|
/*line to the start point*/
|
|
if(!skipLeft){
|
|
ctx.lineTo(x0,y0+offset);
|
|
}
|
|
// ctx.lineTo(x0,0); //IE fix!
|
|
return [x3,y5];
|
|
},
|
|
_drawHScales:function(ctx,data,point0,point1,start,end,cellWidth){
|
|
var x = 0;
|
|
if(this._settings.xAxis){
|
|
if(!this.canvases["x"])
|
|
this.canvases["x"] = this._createCanvas("axis_x");
|
|
x = this._drawHXAxis(this.canvases["x"].getCanvas(),data,point0,point1,start,end);
|
|
}
|
|
if (this._settings.yAxis){
|
|
if(!this.canvases["y"])
|
|
this.canvases["y"] = this._createCanvas("axis_y");
|
|
this._drawHYAxis(this.canvases["y"].getCanvas(),data,point0,point1,cellWidth,x);
|
|
}
|
|
},
|
|
_drawHYAxis:function(ctx,data,point0,point1,cellWidth,yAxisX){
|
|
if (!this._settings.yAxis) return;
|
|
var unitPos;
|
|
var x0 = parseInt((yAxisX?yAxisX:point0.x),10)-0.5;
|
|
var y0 = point1.y+0.5;
|
|
var y1 = point0.y;
|
|
this._drawLine(ctx,x0,y0,x0,y1,this._settings.yAxis.color,1);
|
|
|
|
|
|
|
|
for(var i=0; i < data.length;i ++){
|
|
|
|
/*scale labels*/
|
|
var right = ((this._settings.origin!="auto")&&(this._settings.type=="barH")&&(parseFloat(this._settings.value(data[i]))<this._settings.origin));
|
|
unitPos = y1+cellWidth/2+i*cellWidth;
|
|
this.canvases["y"].renderTextAt("middle",(right?false:"left"),(right?x0+5:x0-5),unitPos,
|
|
this._settings.yAxis.template(data[i]),
|
|
"webix_axis_item_y",(right?0:x0-10)
|
|
);
|
|
if(this._settings.yAxis.lines.call(this,data[i]))
|
|
this._drawLine(ctx,point0.x,unitPos,point1.x,unitPos,this._settings.yAxis.lineColor.call(this,data[i]),1);
|
|
}
|
|
|
|
if(this._settings.yAxis.lines.call(this,{}))
|
|
this._drawLine(ctx,point0.x+0.5,y1+0.5,point1.x,y1+0.5,this._settings.yAxis.lineColor.call(this,{}),1);
|
|
this._setYAxisTitle(point0,point1);
|
|
},
|
|
_drawHXAxis:function(ctx,data,point0,point1,start,end){
|
|
var step;
|
|
var scaleParam= {};
|
|
var axis = this._settings.xAxis;
|
|
if (!axis) return;
|
|
|
|
var y0 = point1.y+0.5;
|
|
var x0 = point0.x-0.5;
|
|
var x1 = point1.x-0.5;
|
|
var yAxisStart = point0.x;
|
|
this._drawLine(ctx,x0,y0,x1,y0,axis.color,1);
|
|
|
|
if(axis.step)
|
|
step = parseFloat(axis.step);
|
|
|
|
if(typeof this._configXAxis.step =="undefined"||typeof this._configXAxis.start=="undefined"||typeof this._configXAxis.end =="undefined"){
|
|
scaleParam = this._calculateScale(start,end);
|
|
start = scaleParam.start;
|
|
end = scaleParam.end;
|
|
step = scaleParam.step;
|
|
this._settings.xAxis.end = end;
|
|
this._settings.xAxis.start = start;
|
|
this._settings.xAxis.step = step;
|
|
}
|
|
|
|
if(step===0) return;
|
|
var stepHeight = (x1-x0)*step/(end-start);
|
|
var c = 0;
|
|
for(var i = start; i<=end; i += step){
|
|
var value = this._logScaleCalc?Math.pow(10,i):i;
|
|
if(scaleParam.fixNum) value = parseFloat(value).toFixed(scaleParam.fixNum);
|
|
var xi = Math.floor(x0+c*stepHeight)+ 0.5;/*canvas line fix*/
|
|
|
|
if(!(i==start&&this._settings.origin=="auto") &&axis.lines.call(this,i))
|
|
this._drawLine(ctx,xi,y0,xi,point0.y,this._settings.xAxis.lineColor.call(this,i),1);
|
|
if(i == this._settings.origin) yAxisStart = xi+1;
|
|
/*correction for JS float calculation*/
|
|
if(step<1 && !this._logScaleCalc){
|
|
var power = Math.min(Math.floor(this._log10(step)),(start<=0?0:Math.floor(this._log10(start))));
|
|
var corr = Math.pow(10,-power);
|
|
value = Math.round(value*corr)/corr;
|
|
i = value;
|
|
}
|
|
this.canvases["x"].renderTextAt(false, true,xi,y0+2,axis.template(value.toString()),"webix_axis_item_x");
|
|
c++;
|
|
}
|
|
this.canvases["x"].renderTextAt(true, false, x0,point1.y+this._settings.padding.bottom-3,
|
|
this._settings.xAxis.title,
|
|
"webix_axis_title_x",
|
|
point1.x - point0.x
|
|
);
|
|
/*the right border in lines in scale are enabled*/
|
|
if (!axis.lines.call(this,{})){
|
|
this._drawLine(ctx,x0,point0.y-0.5,x1,point0.y-0.5,this._settings.xAxis.color,0.2);
|
|
}
|
|
return yAxisStart;
|
|
},
|
|
_correctBarHParams:function(ctx,x,y,value,unit,barWidth,minValue){
|
|
var yax = this._settings.yAxis;
|
|
var axisStart = x;
|
|
if(!!yax&&this._settings.origin!="auto" && (this._settings.origin>minValue)){
|
|
x += (this._settings.origin-minValue)*unit;
|
|
axisStart = x;
|
|
value = value-(this._settings.origin-minValue);
|
|
if(value < 0){
|
|
value *= (-1);
|
|
ctx.translate(x,y+barWidth);
|
|
ctx.rotate(Math.PI);
|
|
x = 0.5;
|
|
y = 0;
|
|
}
|
|
x += 0.5;
|
|
}
|
|
|
|
return {value:value,x0:x,y0:y,start:axisStart};
|
|
},
|
|
_drawBarH:function(ctx,point1,x0,y0,barWidth,minValue,radius,unit,value,color,gradient,inner_gradient){
|
|
ctx.save();
|
|
var p = this._correctBarHParams(ctx,x0,y0,value,unit,barWidth,minValue);
|
|
ctx.fillStyle = color;
|
|
ctx.beginPath();
|
|
var points = this._setBarHPoints(ctx,p.x0,p.y0,barWidth,radius,unit,p.value,(this._settings.border?1:0));
|
|
if (gradient&&!inner_gradient) ctx.lineTo(point1.x,p.y0+(this._settings.border?1:0)); //fix gradient sphreading
|
|
ctx.fill();
|
|
ctx.restore();
|
|
var y1 = p.y0;
|
|
var y2 = (p.y0!=y0?y0:points[1]);
|
|
var x1 = (p.y0!=y0?(p.start-points[0]):p.start);
|
|
var x2 = (p.y0!=y0?p.start:points[0]);
|
|
|
|
return [x1,y1,x2,y2];
|
|
},
|
|
_drawBarHBorder:function(ctx,x0,y0,barWidth,minValue,radius,unit,value,color){
|
|
ctx.save();
|
|
var p = this._correctBarHParams(ctx,x0,y0,value,unit,barWidth,minValue);
|
|
|
|
ctx.beginPath();
|
|
this._setBorderStyles(ctx,color);
|
|
ctx.globalAlpha =0.9;
|
|
this._setBarHPoints(ctx,p.x0,p.y0,barWidth,radius,unit,p.value,ctx.lineWidth/2,1);
|
|
|
|
ctx.stroke();
|
|
ctx.restore();
|
|
},
|
|
_drawBarHGradient:function(ctx,x0,y0,barWidth,minValue,radius,unit,value,color,inner_gradient){
|
|
ctx.save();
|
|
//y0 -= (webix.env.isIE?0:0.5);
|
|
var p = this._correctBarHParams(ctx,x0,y0,value,unit,barWidth,minValue);
|
|
var gradParam = this._setBarGradient(ctx,p.x0,p.y0+barWidth,p.x0+unit*p.value,p.y0,inner_gradient,color,"x");
|
|
ctx.fillStyle = gradParam.gradient;
|
|
ctx.beginPath();
|
|
this._setBarHPoints(ctx,p.x0,p.y0+gradParam.offset,barWidth-gradParam.offset*2,radius,unit,p.value,gradParam.offset);
|
|
ctx.fill();
|
|
ctx.globalAlpha = 1;
|
|
ctx.restore();
|
|
}
|
|
});
|
|
|
|
webix.extend(webix.ui.chart, {
|
|
/**
|
|
* renders a bar chart
|
|
* @param: ctx - canvas object
|
|
* @param: data - object those need to be displayed
|
|
* @param: x - the width of the container
|
|
* @param: y - the height of the container
|
|
* @param: sIndex - index of drawing chart
|
|
*/
|
|
$render_stackedBar:function(ctx, data, point0, point1, sIndex, map){
|
|
var maxValue,minValue, xAxisY, x0, y0;
|
|
/*necessary if maxValue - minValue < 0*/
|
|
var valueFactor;
|
|
/*maxValue - minValue*/
|
|
var relValue;
|
|
var config = this._settings;
|
|
var total_height = point1.y-point0.y;
|
|
|
|
var yax = !!config.yAxis;
|
|
var xax = !!config.xAxis;
|
|
|
|
var limits = this._getStackedLimits(data);
|
|
|
|
var origin = (config.origin === 0);
|
|
|
|
maxValue = limits.max;
|
|
minValue = limits.min;
|
|
|
|
/*an available width for one bar*/
|
|
var cellWidth = Math.floor((point1.x-point0.x)/data.length);
|
|
|
|
/*draws x and y scales*/
|
|
if(!sIndex){
|
|
xAxisY = this._drawScales(data,point0, point1,minValue,maxValue,cellWidth);
|
|
}
|
|
|
|
/*necessary for automatic scale*/
|
|
if(yax){
|
|
maxValue = parseFloat(config.yAxis.end);
|
|
minValue = parseFloat(config.yAxis.start);
|
|
}
|
|
|
|
/*unit calculation (bar_height = value*unit)*/
|
|
var relativeValues = this._getRelativeValue(minValue,maxValue);
|
|
relValue = relativeValues[0];
|
|
valueFactor = relativeValues[1];
|
|
|
|
var unit = (relValue?total_height/relValue:10);
|
|
|
|
/*a real bar width */
|
|
var barWidth = parseInt(config.barWidth,10);
|
|
if(barWidth+4 > cellWidth) barWidth = cellWidth-4;
|
|
/*the half of distance between bars*/
|
|
var barOffset = Math.floor((cellWidth - barWidth)/2);
|
|
|
|
|
|
var inner_gradient = (config.gradient?config.gradient:false);
|
|
|
|
/*draws a black line if the horizontal scale isn't defined*/
|
|
if(!xax){
|
|
//scaleY = y-bottomPadding;
|
|
this._drawLine(ctx,point0.x,point1.y+0.5,point1.x,point1.y+0.5,"#000000",1); //hardcoded color!
|
|
}
|
|
|
|
for(var i=0; i < data.length;i ++){
|
|
var value = Math.abs(parseFloat(config.value(data[i]||0)));
|
|
|
|
if(this._logScaleCalc)
|
|
value = this._log10(value);
|
|
|
|
/*start point (bottom left)*/
|
|
x0 = point0.x + barOffset + i*cellWidth;
|
|
|
|
|
|
var negValue = origin&&value<0;
|
|
if(!sIndex){
|
|
y0 = xAxisY-1;
|
|
data[i].$startY = y0;
|
|
if(origin){
|
|
if(negValue)
|
|
y0 = xAxisY+1;
|
|
data[i].$startYN = xAxisY+1;
|
|
}
|
|
}
|
|
else{
|
|
y0 = negValue?data[i].$startYN:data[i].$startY;
|
|
}
|
|
|
|
if(!value || isNaN(value))
|
|
continue;
|
|
|
|
/*adjusts the first tab to the scale*/
|
|
if(!sIndex && !origin)
|
|
value -= minValue;
|
|
|
|
value *= valueFactor;
|
|
|
|
/*the max height limit*/
|
|
if(y0 < (point0.y+1)) continue;
|
|
|
|
var color = this._settings.color.call(this,data[i]);
|
|
|
|
var firstSector = Math.abs(y0-(origin?(point1.y+minValue*unit):point1.y))<3;
|
|
|
|
/*drawing bar body*/
|
|
ctx.globalAlpha = config.alpha.call(this,data[i]);
|
|
ctx.fillStyle = ctx.strokeStyle = config.color.call(this,data[i]);
|
|
ctx.beginPath();
|
|
|
|
var y1 = y0 - unit*value + (firstSector?(negValue?-1:1):0);
|
|
|
|
var points = this._setStakedBarPoints(ctx,x0-(config.border?0.5:0),y0,barWidth+(config.border?0.5:0),y1, 0,point0.y);
|
|
ctx.fill();
|
|
ctx.stroke();
|
|
|
|
/*gradient*/
|
|
if (inner_gradient){
|
|
ctx.save();
|
|
var gradParam = this._setBarGradient(ctx,x0,y0,x0+barWidth,points[1],inner_gradient,color,"y");
|
|
ctx.fillStyle = gradParam.gradient;
|
|
ctx.beginPath();
|
|
points = this._setStakedBarPoints(ctx,x0+gradParam.offset,y0,barWidth-gradParam.offset*2,y1,(config.border?1:0),point0.y);
|
|
ctx.fill();
|
|
ctx.restore();
|
|
}
|
|
/*drawing the gradient border of a bar*/
|
|
if(config.border){
|
|
ctx.save();
|
|
if(typeof config.border == "string")
|
|
ctx.strokeStyle = config.border;
|
|
else
|
|
this._setBorderStyles(ctx,color);
|
|
ctx.beginPath();
|
|
|
|
this._setStakedBarPoints(ctx,x0-0.5,parseInt(y0,10)+0.5,barWidth+1,parseInt(y1,10)+0.5,0,point0.y, firstSector);
|
|
ctx.stroke();
|
|
ctx.restore();
|
|
}
|
|
ctx.globalAlpha = 1;
|
|
|
|
/*sets a bar label*/
|
|
this.canvases[sIndex].renderTextAt(false, true, x0+Math.floor(barWidth/2),(points[1]+(y0-points[1])/2)-7,this._settings.label(data[i]));
|
|
/*defines a map area for a bar*/
|
|
map.addRect(data[i].id,[x0-point0.x,points[1]-point0.y,points[0]-point0.x,data[i][negValue?"$startYN":"$startY"]-point0.y],sIndex);
|
|
|
|
/*the start position for the next series*/
|
|
|
|
data[i][negValue?"$startYN":"$startY"] = points[1];
|
|
|
|
}
|
|
},
|
|
/**
|
|
* sets points for bar and returns the position of the bottom right point
|
|
* @param: ctx - canvas object
|
|
* @param: x0 - the x position of start point
|
|
* @param: y0 - the y position of start point
|
|
* @param: barWidth - bar width
|
|
* @param: radius - the rounding radius of the top
|
|
* @param: unit - the value defines the correspondence between item value and bar height
|
|
* @param: value - item value
|
|
* @param: offset - the offset from expected bar edge (necessary for drawing border)
|
|
* @param: minY - the minimum y position for the bars ()
|
|
*/
|
|
_setStakedBarPoints:function(ctx,x0,y0,barWidth,y1,offset,minY,skipBottom){
|
|
/*start*/
|
|
ctx.moveTo(x0,y0);
|
|
/*maximum height limit*/
|
|
|
|
if(y1<minY)
|
|
y1 = minY;
|
|
ctx.lineTo(x0,y1);
|
|
var x3 = x0 + barWidth;
|
|
var y3 = y1;
|
|
ctx.lineTo(x3,y3);
|
|
/*right rounding*/
|
|
/*bottom right point*/
|
|
var x5 = x0 + barWidth;
|
|
ctx.lineTo(x5,y0);
|
|
/*line to the start point*/
|
|
if(!skipBottom){
|
|
ctx.lineTo(x0,y0);
|
|
}
|
|
// ctx.lineTo(x0,0); //IE fix!
|
|
return [x5,y3];
|
|
}
|
|
});
|
|
|
|
webix.extend(webix.ui.chart, {
|
|
/**
|
|
* renders a bar chart
|
|
* @param: ctx - canvas object
|
|
* @param: data - object those need to be displayed
|
|
* @param: x - the width of the container
|
|
* @param: y - the height of the container
|
|
* @param: sIndex - index of drawing chart
|
|
* @param: map - map object
|
|
*/
|
|
$render_stackedBarH:function(ctx, data, point0, point1, sIndex, map){
|
|
var maxValue,minValue;
|
|
/*necessary if maxValue - minValue < 0*/
|
|
var valueFactor;
|
|
/*maxValue - minValue*/
|
|
var relValue;
|
|
|
|
var total_width = point1.x-point0.x;
|
|
|
|
var yax = !!this._settings.yAxis;
|
|
|
|
var limits = this._getStackedLimits(data);
|
|
maxValue = limits.max;
|
|
minValue = limits.min;
|
|
|
|
/*an available width for one bar*/
|
|
var cellWidth = Math.floor((point1.y-point0.y)/data.length);
|
|
|
|
/*draws x and y scales*/
|
|
if(!sIndex)
|
|
this._drawHScales(ctx,data,point0, point1,minValue,maxValue,cellWidth);
|
|
|
|
/*necessary for automatic scale*/
|
|
if(yax){
|
|
maxValue = parseFloat(this._settings.xAxis.end);
|
|
minValue = parseFloat(this._settings.xAxis.start);
|
|
}
|
|
|
|
/*unit calculation (bar_height = value*unit)*/
|
|
var relativeValues = this._getRelativeValue(minValue,maxValue);
|
|
relValue = relativeValues[0];
|
|
valueFactor = relativeValues[1];
|
|
|
|
var unit = (relValue?total_width/relValue:10);
|
|
var startValue = 0;
|
|
if(!yax){
|
|
/*defines start value for better representation of small values*/
|
|
startValue = 10;
|
|
unit = (relValue?(total_width-startValue)/relValue:10);
|
|
}
|
|
|
|
/*a real bar width */
|
|
var barWidth = parseInt(this._settings.barWidth,10);
|
|
if((barWidth+4)>cellWidth) barWidth = cellWidth-4;
|
|
/*the half of distance between bars*/
|
|
var barOffset = (cellWidth - barWidth)/2;
|
|
/*the radius of rounding in the top part of each bar*/
|
|
var radius = 0;
|
|
|
|
var inner_gradient = false;
|
|
var gradient = this._settings.gradient;
|
|
if (gradient){
|
|
inner_gradient = true;
|
|
}
|
|
/*draws a black line if the horizontal scale isn't defined*/
|
|
if(!yax){
|
|
this._drawLine(ctx,point0.x-0.5,point0.y,point0.x-0.5,point1.y,"#000000",1); //hardcoded color!
|
|
}
|
|
|
|
var seriesNumber = 0;
|
|
var seriesIndex = 0;
|
|
for(i=0; i<this._series.length; i++ ){
|
|
if(i == sIndex){
|
|
seriesIndex = seriesNumber;
|
|
}
|
|
if(this._series[i].type == "stackedBarH")
|
|
seriesNumber++;
|
|
}
|
|
|
|
for(var i=0; i < data.length;i ++){
|
|
|
|
if(!seriesIndex)
|
|
data[i].$startX = point0.x;
|
|
|
|
var value = Math.abs(parseFloat(this._settings.value(data[i]||0)));
|
|
if(value>maxValue) value = maxValue;
|
|
value -= minValue;
|
|
value *= valueFactor;
|
|
|
|
/*start point (bottom left)*/
|
|
var x0 = point0.x;
|
|
var y0 = point0.y+ barOffset + i*cellWidth;
|
|
|
|
if(!seriesIndex)
|
|
data[i].$startX = x0;
|
|
else
|
|
x0 = data[i].$startX;
|
|
|
|
if(!value || isNaN(value))
|
|
continue;
|
|
|
|
/*takes start value into consideration*/
|
|
if(!yax) value += startValue/unit;
|
|
var color = this._settings.color.call(this,data[i]);
|
|
|
|
|
|
/*drawing bar body*/
|
|
ctx.globalAlpha = this._settings.alpha.call(this,data[i]);
|
|
ctx.fillStyle = this._settings.color.call(this,data[i]);
|
|
ctx.beginPath();
|
|
var points = this._setBarHPoints(ctx,x0,y0,barWidth,radius,unit,value,0);
|
|
if (gradient&&!inner_gradient) ctx.lineTo(point0.x+total_width,y0+(this._settings.border?1:0)); //fix gradient sphreading
|
|
ctx.fill();
|
|
|
|
if (inner_gradient){
|
|
var gradParam = this._setBarGradient(ctx,x0,y0+barWidth,x0,y0,inner_gradient,color,"x");
|
|
ctx.fillStyle = gradParam.gradient;
|
|
ctx.beginPath();
|
|
points = this._setBarHPoints(ctx,x0,y0, barWidth,radius,unit,value,0);
|
|
ctx.fill();
|
|
}
|
|
/*drawing the gradient border of a bar*/
|
|
if(this._settings.border){
|
|
this._drawBarHBorder(ctx,x0,y0,barWidth,minValue,radius,unit,value,color);
|
|
}
|
|
|
|
ctx.globalAlpha = 1;
|
|
|
|
/*sets a bar label*/
|
|
this.canvases[sIndex].renderTextAt("middle",true,data[i].$startX+(points[0]-data[i].$startX)/2-1, y0+(points[1]-y0)/2, this._settings.label(data[i]));
|
|
/*defines a map area for a bar*/
|
|
map.addRect(data[i].id,[data[i].$startX-point0.x,y0-point0.y,points[0]-point0.x,points[1]-point0.y],sIndex);
|
|
/*the start position for the next series*/
|
|
data[i].$startX = points[0];
|
|
}
|
|
}
|
|
});
|
|
webix.extend(webix.ui.chart, {
|
|
/**
|
|
* renders a spline chart
|
|
* @param: ctx - canvas object
|
|
* @param: data - object those need to be displayed
|
|
* @param: width - the width of the container
|
|
* @param: height - the height of the container
|
|
* @param: sIndex - index of drawing chart
|
|
*/
|
|
$render_spline:function(ctx, data, point0, point1, sIndex, map){
|
|
var config,i,items,j,params,sparam,x,x0,x1,x2,y,y1,y2;
|
|
params = this._calculateLineParams(ctx,data,point0,point1,sIndex);
|
|
config = this._settings;
|
|
this._mapStart = point0;
|
|
|
|
/*array of all points*/
|
|
items = [];
|
|
|
|
/*drawing all items*/
|
|
if (data.length) {
|
|
|
|
/*getting all points*/
|
|
x0 = (config.offset?point0.x+params.cellWidth*0.5:point0.x);
|
|
for(i=0; i < data.length;i ++){
|
|
y = this._getPointY(data[i],point0,point1,params);
|
|
if(y || y=="0"){
|
|
x = ((!i)?x0:params.cellWidth*i - 0.5 + x0);
|
|
items.push({x:x,y:y,v:this._settings.value(data[i]),index:i});
|
|
}
|
|
}
|
|
sparam = this._getSplineParameters(items);
|
|
|
|
for(i =0; i< items.length; i++){
|
|
x1 = items[i].x;
|
|
y1 = items[i].y;
|
|
if(i<items.length-1){
|
|
x2 = items[i+1].x;
|
|
y2 = items[i+1].y;
|
|
for(j = x1; j < x2; j++){
|
|
var sY1 = this._getSplineYPoint(j,x1,i,sparam.a,sparam.b,sparam.c,sparam.d);
|
|
if(sY1<point0.y)
|
|
sY1=point0.y;
|
|
if(sY1>point1.y)
|
|
sY1=point1.y;
|
|
var sY2 = this._getSplineYPoint(j+1,x1,i,sparam.a,sparam.b,sparam.c,sparam.d);
|
|
if(sY2<point0.y)
|
|
sY2=point0.y;
|
|
if(sY2>point1.y)
|
|
sY2=point1.y;
|
|
this._drawLine(ctx,j,sY1,j+1,sY2,config.line.color(data[i]),config.line.width);
|
|
|
|
}
|
|
this._drawLine(ctx,x2-1,this._getSplineYPoint(j,x1,i,sparam.a,sparam.b,sparam.c,sparam.d),x2,y2,config.line.color(data[i]),config.line.width);
|
|
}
|
|
this._drawItem(ctx,x1,y1,data[items[i].index],config.label(data[items[i].index]), sIndex, map);
|
|
}
|
|
}
|
|
},
|
|
/*gets spline parameter*/
|
|
_getSplineParameters:function(points){
|
|
var a ,b, c, d, i, s, u, v,
|
|
h = [],
|
|
m = [],
|
|
n = points.length;
|
|
|
|
for(i =0; i<n-1;i++){
|
|
h[i] = points[i+1].x - points[i].x;
|
|
m[i] = (points[i+1].y - points[i].y)/h[i];
|
|
}
|
|
u = []; v = [];
|
|
u[0] = 0;
|
|
u[1] = 2*(h[0] + h[1]);
|
|
v[0] = 0;
|
|
v[1] = 6*(m[1] - m[0]);
|
|
for(i =2; i < n-1; i++){
|
|
u[i] = 2*(h[i-1]+h[i]) - h[i-1]*h[i-1]/u[i-1];
|
|
v[i] = 6*(m[i]-m[i-1]) - h[i-1]*v[i-1]/u[i-1];
|
|
}
|
|
|
|
s = [];
|
|
s[n-1] = s[0] = 0;
|
|
for(i = n -2; i>=1; i--)
|
|
s[i] = (v[i] - h[i]*s[i+1])/u[i];
|
|
|
|
a = []; b = []; c = []; d = [];
|
|
|
|
for(i =0; i<n-1;i++){
|
|
a[i] = points[i].y;
|
|
b[i] = - h[i]*s[i+1]/6 - h[i]*s[i]/3 + (points[i+1].y-points[i].y)/h[i];
|
|
c[i] = s[i]/2;
|
|
d[i] = (s[i+1] - s[i])/(6*h[i]);
|
|
}
|
|
|
|
for (i=0; i<points.length-1; i++){
|
|
if (points[i].v === 0 && points[i+1].v === 0){
|
|
a[i] = points[i].y;
|
|
d[i] = c[i] = b[i] = 0;
|
|
}
|
|
}
|
|
|
|
return {a:a,b:b,c:c,d:d};
|
|
},
|
|
/*returns the y position of the spline point */
|
|
_getSplineYPoint:function(x,xi,i,a,b,c,d){
|
|
return a[i] + (x - xi)*(b[i] + (x-xi)*(c[i]+(x-xi)*d[i]));
|
|
}
|
|
});
|
|
webix.extend(webix.ui.chart,{
|
|
/**
|
|
* renders an area chart
|
|
* @param: ctx - canvas object
|
|
* @param: data - object those need to be displayed
|
|
* @param: width - the width of the container
|
|
* @param: height - the height of the container
|
|
* @param: sIndex - index of drawing chart
|
|
*/
|
|
$render_area:function(ctx, data, point0, point1, sIndex, map){
|
|
|
|
var align, config, i, mapRect, obj, params, path,
|
|
res1, res2, x0, x1, y1, x2, y2, y0;
|
|
|
|
params = this._calculateLineParams(ctx,data,point0,point1,sIndex);
|
|
config = this._settings;
|
|
|
|
//the size of map area
|
|
mapRect = (config.eventRadius||Math.floor(params.cellWidth/2));
|
|
|
|
if (data.length) {
|
|
|
|
// area points
|
|
path = [];
|
|
|
|
//the x position of the first item
|
|
x0 = (!config.offset?point0.x:point0.x+params.cellWidth*0.5);
|
|
|
|
/*
|
|
iterates over all data items:
|
|
calculates [x,y] for area path, adds rect to chart map and renders labels
|
|
*/
|
|
for(i=0; i < data.length;i ++){
|
|
obj = data[i];
|
|
|
|
res2 = this._getPointY(obj,point0,point1,params);
|
|
x2 = x0 + params.cellWidth*i ;
|
|
if(res2){
|
|
y2 = (typeof res2 == "object"?res2.y0:res2);
|
|
if(i && this._settings.fixOverflow){
|
|
res1 = this._getPointY(data[i-1],point0,point1,params);
|
|
if(res1.out && res1.out == res2.out){
|
|
continue;
|
|
}
|
|
x1 = params.cellWidth*(i-1) - 0.5 + x0;
|
|
y1 = (typeof res1 == "object"?res1.y0:res1);
|
|
if(res1.out){
|
|
y0 = (res1.out == "min"?point1.y:point0.y);
|
|
path.push([this._calcOverflowX(x1,x2,y1,y2,y0),y0]);
|
|
}
|
|
if(res2.out){
|
|
y0 = (res2.out == "min"?point1.y:point0.y);
|
|
path.push([this._calcOverflowX(x1,x2,y1,y2,y0),y0]);
|
|
if(i == (data.length-1) && y0 == point0.y)
|
|
path.push([x2,point0.y]);
|
|
}
|
|
}
|
|
if(!res2.out){
|
|
path.push([x2,y2]);
|
|
//map
|
|
map.addRect(obj.id,[x2-mapRect-point0.x,y2-mapRect-point0.y,x2+mapRect-point0.x,y2+mapRect-point0.y],sIndex);
|
|
}
|
|
|
|
//labels
|
|
if(!config.yAxis){
|
|
align = (!config.offset&&(i == data.length-1)?"left":"center");
|
|
this.canvases[sIndex].renderTextAt(false, align, x2, y2-config.labelOffset,config.label(obj));
|
|
}
|
|
}
|
|
|
|
}
|
|
if(path.length){
|
|
path.push([x2,point1.y]);
|
|
path.push([path[0][0],point1.y]);
|
|
}
|
|
|
|
|
|
|
|
//filling area
|
|
ctx.globalAlpha = this._settings.alpha.call(this,data[0]);
|
|
ctx.fillStyle = this._settings.color.call(this,data[0]);
|
|
ctx.beginPath();
|
|
this._path(ctx,path);
|
|
ctx.fill();
|
|
|
|
ctx.lineWidth = 1;
|
|
ctx.globalAlpha =1;
|
|
|
|
//border
|
|
if(config.border){
|
|
ctx.lineWidth = config.borderWidth||1;
|
|
if(config.borderColor)
|
|
ctx.strokeStyle = config.borderColor.call(this,data[0]);
|
|
else
|
|
this._setBorderStyles(ctx,ctx.fillStyle);
|
|
|
|
ctx.beginPath();
|
|
this._path(ctx,path);
|
|
ctx.stroke();
|
|
|
|
}
|
|
|
|
|
|
}
|
|
},
|
|
|
|
/**
|
|
* renders an area chart
|
|
* @param: ctx - canvas object
|
|
* @param: data - object those need to be displayed
|
|
* @param: width - the width of the container
|
|
* @param: height - the height of the container
|
|
* @param: sIndex - index of drawing chart
|
|
*/
|
|
$render_stackedArea:function(ctx, data, point0, point1, sIndex, map){
|
|
|
|
var a0, a1, align, config, i, j, lastItem, mapRect, obj, params, path, x, y, yPos;
|
|
|
|
params = this._calculateLineParams(ctx,data,point0,point1,sIndex);
|
|
|
|
config = this._settings;
|
|
|
|
/*the value that defines the map area position*/
|
|
mapRect = (config.eventRadius||Math.floor(params.cellWidth/2));
|
|
|
|
|
|
/*drawing all items*/
|
|
if (data.length) {
|
|
|
|
// area points
|
|
path = [];
|
|
|
|
// y item positions
|
|
yPos = [];
|
|
|
|
//the x position of the first item
|
|
x = (!config.offset?point0.x:point0.x+params.cellWidth*0.5);
|
|
|
|
|
|
var setOffset = function(i,y){
|
|
return sIndex?(data[i].$startY?y-point1.y+data[i].$startY:0):y;
|
|
};
|
|
|
|
var solveEquation = function(x,p0,p1){
|
|
var k = (p1.y - p0.y)/(p1.x - p0.x);
|
|
return k*x + p0.y - k*p0.x;
|
|
};
|
|
|
|
/*
|
|
iterates over all data items:
|
|
calculates [x,y] for area path, adds rect to chart map and renders labels
|
|
*/
|
|
|
|
for(i=0; i < data.length;i ++){
|
|
obj = data[i];
|
|
|
|
if(!i){
|
|
y = setOffset(i,point1.y);
|
|
path.push([x,y]);
|
|
}
|
|
else{
|
|
x += params.cellWidth ;
|
|
}
|
|
|
|
y = setOffset(i,this._getPointY(obj,point0,point1,params));
|
|
|
|
yPos.push((isNaN(y)&&!i)?(data[i].$startY||point1.y):y);
|
|
|
|
if(y){
|
|
path.push([x,y]);
|
|
|
|
//map
|
|
map.addRect(obj.id,[x-mapRect-point0.x,y-mapRect-point0.y,x+mapRect-point0.x,y+mapRect-point0.y],sIndex);
|
|
|
|
//labels
|
|
if(!config.yAxis){
|
|
align = (!config.offset&&lastItem?"left":"center");
|
|
this.canvases[sIndex].renderTextAt(false, align, x, y-config.labelOffset,config.label(obj));
|
|
}
|
|
}
|
|
}
|
|
|
|
// bottom right point
|
|
path.push([x,setOffset(i-1,point1.y)]);
|
|
|
|
// lower border from the end to start
|
|
if(sIndex){
|
|
for(i=data.length-2; i > 0; i --){
|
|
x -= params.cellWidth ;
|
|
y = data[i].$startY;
|
|
if(y)
|
|
path.push([x,y]);
|
|
}
|
|
}
|
|
|
|
// go to start point
|
|
path.push([path[0][0],path[0][1]]);
|
|
|
|
// filling path
|
|
ctx.globalAlpha = this._settings.alpha.call(this,data[0]);
|
|
ctx.fillStyle = this._settings.color.call(this,data[0]);
|
|
ctx.beginPath();
|
|
this._path(ctx,path);
|
|
ctx.fill();
|
|
|
|
// set y positions of the next series
|
|
for(i=0; i < data.length;i ++){
|
|
y = yPos[i];
|
|
|
|
if(!y){
|
|
if(i == data.length-1){
|
|
y = data[i].$startY;
|
|
}
|
|
for(j =i+1; j< data.length; j++){
|
|
if(yPos[j]){
|
|
a0 = {x:point0.x,y:yPos[0]};
|
|
a1 = {x:(point0.x+params.cellWidth*j),y:yPos[j]};
|
|
y = solveEquation(point0.x+params.cellWidth*i,a0,a1);
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
data[i].$startY = y;
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
});
|
|
//+stackedArea
|
|
webix.extend(webix.ui.chart, {
|
|
$render_radar:function(ctx,data,x,y,sIndex,map){
|
|
this._renderRadarChart(ctx,data,x,y,sIndex,map);
|
|
|
|
},
|
|
/**
|
|
* renders a pie chart
|
|
* @param: ctx - canvas object
|
|
* @param: data - object those need to be displayed
|
|
* @param: x - the width of the container
|
|
* @param: y - the height of the container
|
|
* @param: ky - value from 0 to 1 that defines an angle of inclination (0<ky<1 - 3D chart)
|
|
*/
|
|
_renderRadarChart:function(ctx,data,point0,point1,sIndex,map){
|
|
if(!data.length)
|
|
return;
|
|
var coord = this._getPieParameters(point0,point1);
|
|
/*scale radius*/
|
|
var radius = (this._settings.radius?this._settings.radius:coord.radius);
|
|
/*scale center*/
|
|
var x0 = (this._settings.x?this._settings.x:coord.x);
|
|
var y0 = (this._settings.y?this._settings.y:coord.y);
|
|
/*angles for each unit*/
|
|
var ratioUnits = [];
|
|
for(var i=0;i<data.length;i++)
|
|
ratioUnits.push(1);
|
|
var ratios = this._getRatios(ratioUnits,data.length);
|
|
this._mapStart = point0;
|
|
if(!sIndex)
|
|
this._drawRadarAxises(ratios,x0,y0,radius,data);
|
|
this._drawRadarData(ctx,ratios,x0,y0,radius,data,sIndex,map);
|
|
},
|
|
_drawRadarData:function(ctx,ratios,x,y,radius,data,sIndex,map){
|
|
var alpha0 ,alpha1, config, i, min, max, pos0, pos1, posArr,
|
|
r0, r1, relValue, startAlpha, value, value0, value1, valueFactor,
|
|
unit, unitArr;
|
|
config = this._settings;
|
|
/*unit calculation (item_radius_pos = value*unit)*/
|
|
min = config.yAxis.start;
|
|
max = config.yAxis.end;
|
|
unitArr = this._getRelativeValue(min,max);
|
|
relValue = unitArr[0];
|
|
unit = (relValue?radius/relValue:radius/2);
|
|
valueFactor = unitArr[1];
|
|
|
|
startAlpha = -Math.PI/2;
|
|
alpha0 = alpha1 = startAlpha;
|
|
posArr = [];
|
|
pos1 = 0;
|
|
for(i=0;i<data.length;i++){
|
|
if(!value1){
|
|
value = config.value(data[i]);
|
|
if(this._logScaleCalc)
|
|
value = this._log10(value);
|
|
/*a relative value*/
|
|
value0 = (parseFloat(value||0) - min)*valueFactor;
|
|
}
|
|
else
|
|
value0 = value1;
|
|
r0 = Math.floor(unit*value0);
|
|
|
|
value = config.value((i!=(data.length-1))?data[i+1]:data[0]);
|
|
if(this._logScaleCalc)
|
|
value = this._log10(value);
|
|
|
|
value1 = (parseFloat(value||0) - min)*valueFactor;
|
|
r1 = Math.floor(unit*value1);
|
|
alpha0 = alpha1;
|
|
alpha1 = ((i!=(data.length-1))?(startAlpha+ratios[i]-0.0001):startAlpha);
|
|
pos0 = (pos1||this._getPositionByAngle(alpha0,x,y,r0));
|
|
pos1 = this._getPositionByAngle(alpha1,x,y,r1);
|
|
/*creates map area*/
|
|
/*areaWidth = (config.eventRadius||(parseInt(config.item.radius.call(this,data[i]),10)+config.item.borderWidth));
|
|
map.addRect(data[i].id,[pos0.x-areaWidth,pos0.y-areaWidth,pos0.x+areaWidth,pos0.y+areaWidth],sIndex);*/
|
|
//this._drawLine(ctx,pos0.x,pos0.y,pos1.x,pos1.y,config.line.color.call(this,data[i]),config.line.width)
|
|
posArr.push(pos0);
|
|
}
|
|
if(config.fill)
|
|
this._fillRadarChart(ctx,posArr,data);
|
|
if(!config.disableLines && data.length>2)
|
|
this._strokeRadarChart(ctx,posArr,data);
|
|
if(!config.disableItems || data.length<3)
|
|
this._drawRadarItemMarkers(ctx,posArr,data,sIndex,map);
|
|
posArr = null;
|
|
},
|
|
_drawRadarItemMarkers:function(ctx,points,data,sIndex,map){
|
|
for(var i=0;i < points.length;i++){
|
|
this._drawItem(ctx,points[i].x,points[i].y,data[i],this._settings.label.call(this,data),sIndex,map);
|
|
}
|
|
},
|
|
_fillRadarChart:function(ctx,points,data){
|
|
var pos0,pos1;
|
|
ctx.globalAlpha= this._settings.alpha.call(this,{});
|
|
|
|
ctx.beginPath();
|
|
for(var i=0;i < points.length;i++){
|
|
ctx.fillStyle = this._settings.fill.call(this,data[i]);
|
|
pos0 = points[i];
|
|
pos1 = (points[i+1]|| points[0]);
|
|
if(!i){
|
|
|
|
ctx.moveTo(pos0.x,pos0.y);
|
|
}
|
|
ctx.lineTo(pos1.x,pos1.y);
|
|
}
|
|
ctx.fill();
|
|
ctx.globalAlpha=1;
|
|
},
|
|
_strokeRadarChart:function(ctx,points,data){
|
|
var pos0,pos1;
|
|
for(var i=0;i < points.length;i++){
|
|
pos0 = points[i];
|
|
pos1 = (points[i+1]|| points[0]);
|
|
this._drawLine(ctx,pos0.x,pos0.y,pos1.x,pos1.y,this._settings.line.color.call(this,data[i]),this._settings.line.width);
|
|
}
|
|
},
|
|
_drawRadarAxises:function(ratios,x,y,radius,data){
|
|
var configY = this._settings.yAxis;
|
|
var configX = this._settings.xAxis;
|
|
var start = configY.start;
|
|
var end = configY.end;
|
|
var step = configY.step;
|
|
var scaleParam= {};
|
|
var config = this._configYAxis;
|
|
if(typeof config.step =="undefined"||typeof config.start=="undefined"||typeof config.end =="undefined"){
|
|
var limits = this._getLimits();
|
|
scaleParam = this._calculateScale(limits.min,limits.max);
|
|
start = scaleParam.start;
|
|
end = scaleParam.end;
|
|
step = scaleParam.step;
|
|
configY.end = end;
|
|
configY.start = start;
|
|
}
|
|
var units = [];
|
|
var i,j,p;
|
|
var c=0;
|
|
var stepHeight = radius*step/(end-start);
|
|
/*correction for small step*/
|
|
var power,corr;
|
|
if(step<1){
|
|
power = Math.min(this._log10(step),(start<=0?0:this._log10(start)));
|
|
corr = Math.pow(10,-power);
|
|
}
|
|
var angles = [];
|
|
if(!this.canvases["scale"])
|
|
this.canvases["scale"] = this._createCanvas("radar_scale");
|
|
var ctx = this.canvases["scale"].getCanvas();
|
|
for(i = end; i>=start; i -=step){
|
|
var value = this._logScaleCalc?Math.pow(10,i):i;
|
|
if(scaleParam.fixNum) value = parseFloat(i).toFixed(scaleParam.fixNum);
|
|
|
|
units.push(Math.floor(c*stepHeight)+ 0.5);
|
|
if(corr && !this._logScaleCalc){
|
|
value = Math.round(value*corr)/corr;
|
|
i = value;
|
|
}
|
|
var unitY = y-radius+units[units.length-1];
|
|
|
|
this.canvases["scale"].renderTextAt("middle","left",x,unitY,
|
|
configY.template(value.toString()),
|
|
"webix_axis_item_y webix_radar"
|
|
);
|
|
if(ratios.length<2){
|
|
this._drawScaleSector(ctx,"arc",x,y,radius-units[units.length-1],-Math.PI/2,3*Math.PI/2,i);
|
|
return;
|
|
}
|
|
var startAlpha = -Math.PI/2;/*possibly need to moved in config*/
|
|
var alpha0 = startAlpha;
|
|
var alpha1;
|
|
|
|
for(j=0;j< ratios.length;j++){
|
|
if(!c)
|
|
angles.push(alpha0);
|
|
alpha1 = startAlpha+ratios[j]-0.0001;
|
|
this._drawScaleSector(ctx,(ratios.length>2?(config.lineShape||"line"):"arc"),x,y,radius-units[units.length-1],alpha0,alpha1,i,j,data[i]);
|
|
alpha0 = alpha1;
|
|
}
|
|
c++;
|
|
}
|
|
/*renders radius lines and labels*/
|
|
for(i=0;i< angles.length;i++){
|
|
p = this._getPositionByAngle(angles[i],x,y,radius);
|
|
if(configX.lines.call(this,data[i],i))
|
|
this._drawLine(ctx,x,y,p.x,p.y,(configX?configX.lineColor.call(this,data[i]):"#cfcfcf"),1);
|
|
this._drawRadarScaleLabel(ctx,x,y,radius,angles[i],(configX?configX.template.call(this,data[i]):" "));
|
|
}
|
|
|
|
},
|
|
_drawScaleSector:function(ctx,shape,x,y,radius,a1,a2,i,j){
|
|
var pos1, pos2;
|
|
if(radius<0)
|
|
return false;
|
|
pos1 = this._getPositionByAngle(a1,x,y,radius);
|
|
pos2 = this._getPositionByAngle(a2,x,y,radius);
|
|
var configY = this._settings.yAxis;
|
|
if(configY.bg){
|
|
ctx.beginPath();
|
|
ctx.moveTo(x,y);
|
|
if(shape=="arc")
|
|
ctx.arc(x,y,radius,a1,a2,false);
|
|
else{
|
|
ctx.lineTo(pos1.x,pos1.y);
|
|
ctx.lineTo(pos2.x,pos2.y);
|
|
}
|
|
ctx.fillStyle = configY.bg(i,j);
|
|
ctx.moveTo(x,y);
|
|
ctx.fill();
|
|
ctx.closePath();
|
|
}
|
|
if(configY.lines.call(this,i)){
|
|
ctx.lineWidth = 1;
|
|
ctx.beginPath();
|
|
if(shape=="arc")
|
|
ctx.arc(x,y,radius,a1,a2,false);
|
|
else{
|
|
ctx.moveTo(pos1.x,pos1.y);
|
|
ctx.lineTo(pos2.x,pos2.y);
|
|
}
|
|
ctx.strokeStyle = configY.lineColor.call(this,i);
|
|
ctx.stroke();
|
|
}
|
|
},
|
|
_drawRadarScaleLabel:function(ctx,x,y,r,a,text){
|
|
if(!text)
|
|
return false;
|
|
var t = this.canvases["scale"].renderText(0,0,text,"webix_axis_radar_title",1);
|
|
var width = t.scrollWidth;
|
|
var height = t.offsetHeight;
|
|
var delta = 0.001;
|
|
var pos = this._getPositionByAngle(a,x,y,r+5);
|
|
var corr_x=0,corr_y=0;
|
|
if(a<0||a>Math.PI){
|
|
corr_y = -height;
|
|
}
|
|
if(a>Math.PI/2){
|
|
corr_x = -width;
|
|
}
|
|
if(Math.abs(a+Math.PI/2)<delta||Math.abs(a-Math.PI/2)<delta){
|
|
corr_x = -width/2;
|
|
}
|
|
else if(Math.abs(a)<delta||Math.abs(a-Math.PI)<delta){
|
|
corr_y = -height/2;
|
|
}
|
|
t.style.top = pos.y+corr_y+"px";
|
|
t.style.left = pos.x+corr_x+"px";
|
|
t.style.width = width+"px";
|
|
t.style.whiteSpace = "nowrap";
|
|
}
|
|
});
|
|
webix.extend(webix.ui.chart, {
|
|
|
|
/**
|
|
* renders a graphic
|
|
* @param: ctx - canvas object
|
|
* @param: data - object those need to be displayed
|
|
* @param: point0 - top left point of a chart
|
|
* @param: point1 - right bottom point of a chart
|
|
* @param: sIndex - index of drawing chart
|
|
* @param: map - map object
|
|
*/
|
|
$render_scatter:function(ctx, data, point0, point1, sIndex, map){
|
|
if(!this._settings.xValue)
|
|
return webix.log("warning","Undefined propery: xValue");
|
|
/*max in min values*/
|
|
var limitsY = this._getLimits();
|
|
var limitsX = this._getLimits("h","xValue");
|
|
/*render scale*/
|
|
if(!sIndex){
|
|
if(!this.canvases["x"])
|
|
this.canvases["x"] = this._createCanvas("axis_x");
|
|
if(!this.canvases["y"])
|
|
this.canvases["y"] = this._createCanvas("axis_y");
|
|
this._drawYAxis(this.canvases["y"].getCanvas(),data,point0,point1,limitsY.min,limitsY.max);
|
|
this._drawHXAxis(this.canvases["x"].getCanvas(),data,point0,point1,limitsX.min,limitsX.max);
|
|
}
|
|
limitsY = {min:this._settings.yAxis.start,max:this._settings.yAxis.end};
|
|
limitsX = {min:this._settings.xAxis.start,max:this._settings.xAxis.end};
|
|
var params = this._getScatterParams(ctx,data,point0,point1,limitsX,limitsY);
|
|
this._mapStart = point0;
|
|
for(var i=0;i<data.length;i++){
|
|
this._drawScatterItem(ctx,map,point0, point1, params,limitsX,limitsY,data[i],sIndex);
|
|
}
|
|
},
|
|
_getScatterParams:function(ctx, data, point0, point1,limitsX,limitsY){
|
|
var params = {};
|
|
/*available space*/
|
|
params.totalHeight = point1.y-point0.y;
|
|
/*available width*/
|
|
params.totalWidth = point1.x-point0.x;
|
|
/*unit calculation (y_position = value*unit)*/
|
|
this._calcScatterUnit(params,limitsX.min,limitsX.max,params.totalWidth,"X");
|
|
this._calcScatterUnit(params,limitsY.min,limitsY.max,params.totalHeight,"Y");
|
|
return params;
|
|
},
|
|
_drawScatterItem:function(ctx,map,point0, point1,params,limitsX,limitsY,obj,sIndex){
|
|
var x0 = this._calculateScatterItemPosition(params, point1, point0, limitsX, obj, "X");
|
|
var y0 = this._calculateScatterItemPosition(params, point0, point1, limitsY, obj, "Y");
|
|
this. _drawItem(ctx,x0,y0,obj,this._settings.label.call(this,obj),sIndex,map);
|
|
},
|
|
_calculateScatterItemPosition:function(params, point0, point1, limits, obj, axis){
|
|
/*the real value of an object*/
|
|
var value = this._settings[axis=="X"?"xValue":"value"].call(this,obj);
|
|
/*a relative value*/
|
|
var valueFactor = params["valueFactor"+axis];
|
|
var v = (parseFloat(value||0) - limits.min)*valueFactor;
|
|
/*a vertical coordinate*/
|
|
var unit = params["unit"+axis];
|
|
var pos = point1[axis.toLowerCase()] - (axis=="X"?(-1):1)*Math.floor(unit*v);
|
|
/*the limit of the minimum value is the minimum visible value*/
|
|
if(v<0)
|
|
pos = point1[axis.toLowerCase()];
|
|
/*the limit of the maximum value*/
|
|
if(value > limits.max)
|
|
pos = point0[axis.toLowerCase()];
|
|
/*the limit of the minimum value*/
|
|
if(value < limits.min)
|
|
pos = point1[axis.toLowerCase()];
|
|
return pos;
|
|
},
|
|
_calcScatterUnit:function(p,min,max,size,axis){
|
|
var relativeValues = this._getRelativeValue(min,max);
|
|
axis = (axis||"");
|
|
p["relValue"+axis] = relativeValues[0];
|
|
p["valueFactor"+axis] = relativeValues[1];
|
|
p["unit"+axis] = (p["relValue"+axis]?size/p["relValue"+axis]:10);
|
|
}
|
|
});
|
|
/*chart presents*/
|
|
webix.extend(webix.ui.chart, {
|
|
presets:{
|
|
"simple":{
|
|
item:{
|
|
borderColor: "#ffffff",
|
|
color: "#2b7100",
|
|
shadow: false,
|
|
borderWidth:2
|
|
},
|
|
line:{
|
|
color:"#8ecf03",
|
|
width:2
|
|
}
|
|
},
|
|
"plot":{
|
|
color:"#1293f8",
|
|
item:{
|
|
borderColor:"#636363",
|
|
borderWidth:1,
|
|
color: "#ffffff",
|
|
type:"r",
|
|
shadow: false
|
|
},
|
|
line:{
|
|
color:"#1293f8",
|
|
width:2
|
|
}
|
|
},
|
|
"diamond":{
|
|
color:"#b64040",
|
|
item:{
|
|
borderColor:"#b64040",
|
|
color: "#b64040",
|
|
type:"d",
|
|
radius:3,
|
|
shadow:true
|
|
},
|
|
line:{
|
|
color:"#ff9000",
|
|
width:2
|
|
}
|
|
},
|
|
"point":{
|
|
color:"#fe5916",
|
|
disableLines:true,
|
|
fill:false,
|
|
disableItems:false,
|
|
item:{
|
|
color:"#feb916",
|
|
borderColor:"#fe5916",
|
|
radius:2,
|
|
borderWidth:1,
|
|
type:"r"
|
|
},
|
|
alpha:1
|
|
},
|
|
"line":{
|
|
line:{
|
|
color:"#3399ff",
|
|
width:2
|
|
},
|
|
item:{
|
|
color:"#ffffff",
|
|
borderColor:"#3399ff",
|
|
radius:2,
|
|
borderWidth:2,
|
|
type:"d"
|
|
},
|
|
fill:false,
|
|
disableItems:false,
|
|
disableLines:false,
|
|
alpha:1
|
|
},
|
|
"area":{
|
|
fill:"#3399ff",
|
|
line:{
|
|
color:"#3399ff",
|
|
width:1
|
|
},
|
|
disableItems:true,
|
|
alpha: 0.2,
|
|
disableLines:false
|
|
},
|
|
"round":{
|
|
item:{
|
|
radius:3,
|
|
borderColor:"#3f83ff",
|
|
borderWidth:1,
|
|
color:"#3f83ff",
|
|
type:"r",
|
|
shadow:false,
|
|
alpha:0.6
|
|
}
|
|
},
|
|
"square":{
|
|
item:{
|
|
radius:3,
|
|
borderColor:"#447900",
|
|
borderWidth:2,
|
|
color:"#69ba00",
|
|
type:"s",
|
|
shadow:false,
|
|
alpha:1
|
|
}
|
|
},
|
|
/*bar*/
|
|
"column":{
|
|
color:"RAINBOW",
|
|
gradient:false,
|
|
barWidth:45,
|
|
radius:0,
|
|
alpha:1,
|
|
border:true
|
|
},
|
|
"stick":{
|
|
barWidth:5,
|
|
gradient:false,
|
|
color:"#67b5c9",
|
|
radius:2,
|
|
alpha:1,
|
|
border:false
|
|
},
|
|
"alpha":{
|
|
color:"#b9a8f9",
|
|
barWidth:70,
|
|
gradient:"falling",
|
|
radius:0,
|
|
alpha:0.5,
|
|
border:true
|
|
}
|
|
}
|
|
});
|
|
|
|
webix.extend(webix.ui.chart,{
|
|
/**
|
|
* renders an splineArea chart
|
|
* @param: ctx - canvas object
|
|
* @param: data - object those need to be displayed
|
|
* @param: width - the width of the container
|
|
* @param: height - the height of the container
|
|
* @param: sIndex - index of drawing chart
|
|
*/
|
|
$render_splineArea:function(ctx, data, point0, point1, sIndex, map){
|
|
var color, i,items,j,mapRect,params,sParams,
|
|
x,x0,x1,x2,y,y1,y2,
|
|
config = this._settings,
|
|
path = [];
|
|
|
|
params = this._calculateLineParams(ctx,data,point0,point1,sIndex);
|
|
mapRect = (config.eventRadius||Math.floor(params.cellWidth/2));
|
|
/*array of all points*/
|
|
items = [];
|
|
|
|
if (data.length) {
|
|
/*getting all points*/
|
|
x0 = point0.x;
|
|
for(i=0; i < data.length;i ++){
|
|
y = this._getPointY(data[i],point0,point1,params);
|
|
if(y || y=="0"){
|
|
x = ((!i)?x0:params.cellWidth*i - 0.5 + x0);
|
|
items.push({x:x,y:y,index:i});
|
|
map.addRect(data[i].id,[x-mapRect-point0.x,y-mapRect-point0.y,x+mapRect-point0.x,y+mapRect-point0.y],sIndex);
|
|
}
|
|
}
|
|
|
|
sParams = this._getSplineParameters(items);
|
|
|
|
for(i =0; i< items.length; i++){
|
|
x1 = items[i].x;
|
|
y1 = items[i].y;
|
|
if(i<items.length-1){
|
|
x2 = items[i+1].x;
|
|
y2 = items[i+1].y;
|
|
for(j = x1; j < x2; j++){
|
|
var sY1 = this._getSplineYPoint(j,x1,i,sParams.a,sParams.b,sParams.c,sParams.d);
|
|
if(sY1<point0.y)
|
|
sY1=point0.y;
|
|
if(sY1>point1.y)
|
|
sY1=point1.y;
|
|
var sY2 = this._getSplineYPoint(j+1,x1,i,sParams.a,sParams.b,sParams.c,sParams.d);
|
|
if(sY2<point0.y)
|
|
sY2=point0.y;
|
|
if(sY2>point1.y)
|
|
sY2=point1.y;
|
|
path.push([j,sY1]);
|
|
path.push([j+1,sY2]);
|
|
}
|
|
path.push([x2,y2]);
|
|
}
|
|
}
|
|
|
|
color = this._settings.color.call(this,data[0]);
|
|
|
|
if(path.length){
|
|
path.push([x2,point1.y]);
|
|
path.push([path[0][0],point1.y]);
|
|
}
|
|
|
|
//filling area
|
|
ctx.globalAlpha = this._settings.alpha.call(this,data[0]);
|
|
ctx.fillStyle = color;
|
|
ctx.beginPath();
|
|
this._path(ctx,path);
|
|
ctx.fill();
|
|
ctx.lineWidth = 1;
|
|
ctx.globalAlpha =1;
|
|
|
|
// draw line
|
|
if(config.border){
|
|
ctx.lineWidth = config.borderWidth||1;
|
|
if(config.borderColor)
|
|
ctx.strokeStyle = config.borderColor.call(this,data[0]);
|
|
else
|
|
this._setBorderStyles(ctx,color);
|
|
ctx.beginPath();
|
|
path.splice(path.length-3);
|
|
this._path(ctx,path);
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
(function(){
|
|
var animateDuration = 400,
|
|
cellWidth = 30;
|
|
|
|
webix.extend(webix.ui.chart, {
|
|
dynamic_setter: function(value){
|
|
if(value)
|
|
init(this);
|
|
return value;
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Sets event handlers and properties for a stock chart
|
|
* @param {object} chart - chart view
|
|
*/
|
|
function init(chart){
|
|
if(chart._stockRenderHandler)
|
|
return;
|
|
var config = chart._settings;
|
|
|
|
if(!config.cellWidth)
|
|
config.cellWidth = cellWidth;
|
|
if(!config.animateDuration)
|
|
config.animateDuration = animateDuration;
|
|
config.offset = false;
|
|
|
|
chart._stockRenderHandler = chart.attachEvent("onBeforeRender", function(data, type){
|
|
var bounds = chart._getChartBounds(chart._content_width, chart._content_height);
|
|
resizeStockCanvases(chart);
|
|
filterStockData(data, bounds.start, bounds.end, config.cellWidth);
|
|
if(type == "add")
|
|
startAnimation(chart);
|
|
});
|
|
chart._stockXAxisHandler = chart.attachEvent("onBeforeXAxis", function(ctx,data,point0,point1,cellWidth,y){
|
|
drawXAxis(chart,ctx,data,point0,point1,cellWidth,y);
|
|
return false;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Starts stock animation
|
|
* @param {object} chart - chart view
|
|
*/
|
|
function startAnimation(chart){
|
|
var cellWidth = chart._settings.cellWidth;
|
|
if(chart._stockAnimationOffset != cellWidth){
|
|
chart._stockAnimationOffset = cellWidth;
|
|
chart.render();
|
|
}
|
|
|
|
chart._stockAnimationOffset = 0;
|
|
chart._stockAnimationStart = null;
|
|
|
|
if(window.requestAnimationFrame && !document.hidden)
|
|
window.requestAnimationFrame(function(t){
|
|
animate(chart,t);
|
|
});
|
|
|
|
if(!chart._stockAnimateHandler)
|
|
chart._stockAnimateHandler = chart.attachEvent("onAfterRender", function(data){
|
|
applyStockOffset(chart, data);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Animates a chart
|
|
* @param {object} chart - chart view
|
|
* @param {number} timestamp - timestamp
|
|
*/
|
|
function animate(chart, timestamp){
|
|
var progress,
|
|
duration = chart._settings.animateDuration,
|
|
cellWidth = chart._settings.cellWidth;
|
|
|
|
if(cellWidth && chart.count() > 1){
|
|
if (!chart._stockAnimationStart)
|
|
chart._stockAnimationStart = timestamp;
|
|
progress = timestamp - chart._stockAnimationStart;
|
|
chart._stockAnimationOffset = Math.min(Math.max(progress/duration*cellWidth,1), cellWidth);
|
|
chart.render();
|
|
if (progress < duration)
|
|
window.requestAnimationFrame(function(t){
|
|
animate(chart,t);
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Applies animation offset to "series" and "x-axis" canvases
|
|
* @param {object} chart - chart view
|
|
* @param {object} data - data array
|
|
*/
|
|
function applyStockOffset(chart, data){
|
|
var count = chart.count(),
|
|
bounds = chart._getChartBounds(chart._content_width,chart._content_height),
|
|
cellWidth = chart._settings.cellWidth,
|
|
offset = chart._stockAnimationOffset || 0,
|
|
isScroll = (data.length < count || (data.length-1)*cellWidth > bounds.end.x-bounds.start.x);
|
|
|
|
function setCanvasOffset(canvas, x0, x1, skipRight){
|
|
var ctx = canvas.getCanvas(),
|
|
elem = canvas._canvas,
|
|
labels = canvas._canvas_labels,
|
|
series = canvas._canvas_series;
|
|
|
|
|
|
// if we need to display less values than they are
|
|
if(offset && (data.length < count || (data.length-1)*cellWidth > x1-x0)){
|
|
// move canvas to the left
|
|
elem.style.left = - offset + "px";
|
|
if(data.length > 1){
|
|
setLabelsOffset(labels, offset, series);
|
|
// clear out of the scale parts
|
|
ctx.clearRect(0, 0, x0+offset, elem.offsetHeight);
|
|
ctx.clearRect(x1+offset, 0, elem.offsetWidth, elem.offsetHeight);
|
|
}
|
|
}
|
|
// animation for the right part (added item)
|
|
else{
|
|
elem.style.left = "0px";
|
|
if(!skipRight && offset!= cellWidth)
|
|
ctx.clearRect(x0+(data.length-1)*cellWidth-cellWidth+offset, 0, elem.offsetWidth, elem.offsetHeight);
|
|
}
|
|
|
|
// show label for the last label after finishing animation
|
|
if(labels.length>1 && offset && offset != cellWidth){
|
|
var last = labels.length-1;
|
|
if(isAxisTitle(series, labels[last]))
|
|
last -= 1;
|
|
labels[last].style.display = "none";
|
|
}
|
|
|
|
}
|
|
|
|
eachStockCanvas(chart,function(name, canvas){
|
|
setCanvasOffset(canvas, bounds.start.x, bounds.end.x, name == "x");
|
|
});
|
|
|
|
setHtmlMapSizes(chart,bounds, isScroll?offset:0);
|
|
}
|
|
|
|
function isAxisTitle(series, label){
|
|
return series ==="axis_x" && label.className.indexOf("webix_axis_title_x") !== -1;
|
|
}
|
|
|
|
function setLabelsOffset(labels, offset, series){
|
|
if(labels.length){
|
|
|
|
webix.html.remove(labels[0]);
|
|
for(var i = 1; i< labels.length; i++){
|
|
//don't move axis title
|
|
if(isAxisTitle(series, labels[i])) continue;
|
|
labels[i].style.left = labels[i].offsetLeft - offset + "px";
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets visible chart data
|
|
* @param {object} data - an array with all chart data
|
|
* @param {object} point0 - a top left point of a plot
|
|
* @param {object} point1 - a bottom right point of a plot
|
|
* @param {number} cellWidth - a unit width
|
|
*/
|
|
function filterStockData(data, point0, point1, cellWidth){
|
|
if(cellWidth && data.length){
|
|
var limit = Math.ceil((point1.x - point0.x)/cellWidth);
|
|
if(data.length > limit+1)
|
|
data.splice(0, data.length - limit-1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calls a function for "series" and "x-axis" canvases
|
|
* @param {object} chart - chart view
|
|
* @param {function} func - function to call
|
|
*/
|
|
function eachStockCanvas(chart, func){
|
|
if(chart.canvases){
|
|
for(var i=0; i < chart._series.length;i++)
|
|
if (chart.canvases[i])
|
|
func(i,chart.canvases[i]);
|
|
|
|
if (chart.canvases["x"])
|
|
func("x",chart.canvases["x"]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set sizes for animated canvases
|
|
* @param {object} chart - chart view
|
|
*/
|
|
function resizeStockCanvases(chart){
|
|
eachStockCanvas(chart, function(name, canvas){
|
|
canvas._resizeCanvas(chart._content_width+2*chart._settings.cellWidth, chart._content_height);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Set sizes for an html map of a chart
|
|
* @param {object} chart - a chart view
|
|
* @param {object} bounds - start and end points of a plot
|
|
* @param {number} offset - an offset to apply
|
|
*/
|
|
function setHtmlMapSizes(chart, bounds, offset){
|
|
chart._contentobj._htmlmap.style.left = (bounds.start.x - offset)+"px";
|
|
chart._contentobj._htmlmap.style.width = (bounds.end.x-bounds.start.x+offset)+"px";
|
|
}
|
|
|
|
/**
|
|
* Renders lines and labels of an x-axis
|
|
* @param {object} chart - a chart view
|
|
* @param {object} ctx - a canvas Context
|
|
* @param {object} data - a data array
|
|
* @param {object} point0 - a top left point of a plot
|
|
* @param {object} point1 - a bottom right point of a plot
|
|
* @param {number} cellWidth - a width of a unit
|
|
* @param {number} y - the vertical position of an "x-axis" line
|
|
*/
|
|
function drawXAxis(chart, ctx, data,point0,point1,cellWidth,y){
|
|
var center, i, isScroll,unitPos,
|
|
config = chart._settings,
|
|
x0 = point0.x-0.5,
|
|
y0 = parseInt((y?y:point1.y),10)+0.5,
|
|
x1 = point1.x;
|
|
|
|
if(!config.dynamic)
|
|
return false;
|
|
|
|
isScroll = ((data.length-1)*cellWidth > x1-x0 || data.length < chart.count());
|
|
|
|
for(i=0; i < data.length;i++){
|
|
unitPos = x0+i*cellWidth ;
|
|
center = isScroll?i>1:!!i;
|
|
unitPos = Math.ceil(unitPos)-0.5;
|
|
//scale labels
|
|
chart._drawXAxisLabel(unitPos,y0,data[i],center);
|
|
//draws a vertical line for the horizontal scale
|
|
if(i && config.xAxis.lines.call(chart, data[i]))
|
|
chart._drawXAxisLine(ctx,unitPos,point1.y,point0.y,data[i]);
|
|
|
|
}
|
|
|
|
chart.canvases["x"].renderTextAt(true, false, x0, point1.y + config.padding.bottom-3,
|
|
config.xAxis.title,
|
|
"webix_axis_title_x",
|
|
point1.x - point0.x
|
|
);
|
|
chart._drawLine(ctx,x0,y0,x1+ (isScroll?chart._stockAnimationOffset:0),y0,config.xAxis.color,1);
|
|
}
|
|
})();
|
|
|
|
/*
|
|
UI:Calendar
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
webix.protoUI({
|
|
name:"calendar",
|
|
|
|
defaults:{
|
|
date: new Date(), //selected date, not selected by default
|
|
select: false,
|
|
navigation: true,
|
|
monthSelect: true,
|
|
weekHeader: true,
|
|
weekNumber: false,
|
|
skipEmptyWeeks: false,
|
|
|
|
calendarHeader: "%F %Y",
|
|
calendarWeekHeader: "W#",
|
|
//calendarTime: "%H:%i",
|
|
events:webix.Date.isHoliday,
|
|
minuteStep: 5,
|
|
icons: false,
|
|
timepickerHeight: 30,
|
|
headerHeight: 70,
|
|
dayTemplate: function(d){
|
|
return d.getDate();
|
|
},
|
|
width: 260,
|
|
height: 250
|
|
},
|
|
|
|
dayTemplate_setter: webix.template,
|
|
calendarHeader_setter:webix.Date.dateToStr,
|
|
calendarWeekHeader_setter:webix.Date.dateToStr,
|
|
calendarTime_setter:function(format){
|
|
this._calendarTime = format;
|
|
return webix.Date.dateToStr(format);
|
|
},
|
|
date_setter:function(date){
|
|
return this._string_to_date(date);
|
|
},
|
|
maxDate_setter:function(date){
|
|
return this._string_to_date(date);
|
|
},
|
|
minDate_setter:function(date){
|
|
return this._string_to_date(date);
|
|
},
|
|
minTime_setter:function(time){
|
|
if(typeof(time) == "string"){
|
|
time = webix.i18n.parseTimeFormatDate(time);
|
|
time = [time.getHours(),time.getMinutes()];
|
|
|
|
}
|
|
|
|
return time;
|
|
},
|
|
maxTime_setter:function(time){
|
|
if(typeof(time) == "string"){
|
|
time = webix.i18n.parseTimeFormatDate(time);
|
|
time = [time.getHours(),time.getMinutes()];
|
|
}
|
|
return time;
|
|
},
|
|
_ariaFocus:function(){
|
|
var ev = "focus"+(webix.env.isIE?"in":"");
|
|
|
|
webix._event(this.$view, ev, webix.bind(function(e){
|
|
var t = e.target.className;
|
|
var css = t.indexOf("webix_cal_day")!==-1 ? "webix_cal_day" : (t.indexOf("webix_cal_block")!==-1?"webix_cal_block":"");
|
|
|
|
if(new Date() - webix.UIManager._tab_time > 300 && css){
|
|
var prev = e.relatedTarget;
|
|
if(prev && !webix.isUndefined(prev.className)){
|
|
var date = (css=="webix_cal_day")?
|
|
this._locate_day(e.target):
|
|
this._locate_date(e.target);
|
|
this._moveSelection(date, false);
|
|
}
|
|
}
|
|
}, this), {capture:!webix.env.isIE});
|
|
},
|
|
$init: function() {
|
|
this._viewobj.className += " webix_calendar";
|
|
this._viewobj.setAttribute("role", "region");
|
|
this._viewobj.setAttribute("aria-label", webix.i18n.aria.calendar);
|
|
|
|
//special dates
|
|
this._special_dates = {};
|
|
this._selected_date_part = this._selected_date = null;
|
|
this._zoom_level = 0;
|
|
|
|
//navigation and aria
|
|
this._ariaFocus();
|
|
this.attachEvent("onKeyPress", this._onKeyPress);
|
|
this.attachEvent("onAfterZoom", function(zoom){
|
|
if(zoom >= 0) this.$view.querySelector(".webix_cal_month_name").blur();
|
|
});
|
|
},
|
|
type_setter: function(value){
|
|
if(value == "time"){
|
|
this._zoom_in = true;
|
|
this._zoom_level = -1;
|
|
}
|
|
else if(value == "year"){
|
|
this._fixed = true;
|
|
}
|
|
return value;
|
|
},
|
|
$setSize:function(x,y){
|
|
|
|
if(webix.ui.view.prototype.$setSize.call(this,x,y)){
|
|
//repaint calendar when size changed
|
|
this.render();
|
|
}
|
|
},
|
|
$getSize:function(dx, dy){
|
|
if (this._settings.cellHeight){
|
|
var state = this._getDateBoundaries(this._settings.date);
|
|
this._settings.height = this._settings.cellHeight * state._rows + (webix.skin.$active.calendarHeight||70);
|
|
}
|
|
return webix.ui.view.prototype.$getSize.call(this, dx,dy);
|
|
},
|
|
moveSelection:function(mode, details, focus){
|
|
if(this.config.master) return; //in daterange
|
|
|
|
var date = webix.Date.copy(this.getSelectedDate() || this.getVisibleDate());
|
|
this._moveSelection(date, mode, focus);
|
|
|
|
},
|
|
_moveSelection:function(date, mode, focus){
|
|
var css = this._zoom_logic[this._zoom_level]._keyshift(date, mode, this);
|
|
|
|
if(focus !==false){
|
|
var sel = this._viewobj.querySelector("."+css+"[tabindex='0']");
|
|
if(sel) sel.focus();
|
|
}
|
|
},
|
|
_getDateBoundaries: function(date, reset) {
|
|
// addition information about rendering event:
|
|
// how many days from the previous month,
|
|
// next,
|
|
// number of weeks to display and so on
|
|
|
|
if (!this._set_date_bounds || reset){
|
|
var month = date.getMonth();
|
|
var year = date.getFullYear();
|
|
|
|
var next = new Date(year, month+1, 1);
|
|
var start = webix.Date.weekStart(new Date(year, month, 1));
|
|
|
|
var days = Math.round((next.valueOf() - start.valueOf())/(60*1000*60*24));
|
|
var rows = this._settings.skipEmptyWeeks?Math.ceil(days/7):6;
|
|
|
|
this._set_date_bounds = { _month: month, _start:start, _next:next, _rows: rows};
|
|
}
|
|
|
|
return this._set_date_bounds;
|
|
},
|
|
$skin:function(){
|
|
if(webix.skin.$active.calendar){
|
|
if( webix.skin.$active.calendar.width)
|
|
this.defaults.width = webix.skin.$active.calendar.width;
|
|
if( webix.skin.$active.calendar.height)
|
|
this.defaults.height = webix.skin.$active.calendar.height;
|
|
if( webix.skin.$active.calendar.headerHeight)
|
|
this.defaults.headerHeight = webix.skin.$active.calendar.headerHeight;
|
|
if( webix.skin.$active.calendar.timepickerHeight)
|
|
this.defaults.timepickerHeight = webix.skin.$active.calendar.timepickerHeight;
|
|
}
|
|
|
|
},
|
|
_getColumnConfigSizes: function(date){
|
|
var bounds = this._getDateBoundaries(date);
|
|
|
|
var s = this._settings;
|
|
var _columnsHeight = [];
|
|
var _columnsWidth = [];
|
|
|
|
var containerWidth = this._content_width - 36;
|
|
|
|
var containerHeight = this._content_height - this._settings.headerHeight - 10 - (this._settings.timepicker||this._icons?this._settings.timepickerHeight:0);
|
|
|
|
var columnsNumber = (s.weekNumber)?8:7;
|
|
for(var i=0; i<columnsNumber; i++) {
|
|
_columnsWidth[i] = Math.ceil(containerWidth/(columnsNumber-i));
|
|
containerWidth -= _columnsWidth[i];
|
|
}
|
|
|
|
var rowsNumber = bounds._rows;
|
|
for (var k = 0; k < rowsNumber; k++) {
|
|
_columnsHeight[k] = Math.ceil(containerHeight/(rowsNumber-k) );
|
|
containerHeight -= _columnsHeight[k];
|
|
}
|
|
return [_columnsWidth, _columnsHeight];
|
|
},
|
|
icons_setter: function(value){
|
|
if(!value)
|
|
this._icons = null;
|
|
else if(typeof value == "object")
|
|
this._icons = value;
|
|
else
|
|
this._icons = this._icons2;
|
|
},
|
|
_icons: [],
|
|
_icons2: [
|
|
|
|
{
|
|
template: function(){
|
|
return "<span role='button' tabindex='0' class='webix_cal_icon_today webix_cal_icon'>"+webix.i18n.calendar.today+"</span>";
|
|
},
|
|
on_click:{
|
|
"webix_cal_icon_today": function(){
|
|
this.setValue(new Date());
|
|
this.callEvent("onTodaySet",[this.getSelectedDate()]);
|
|
}
|
|
}
|
|
},
|
|
{
|
|
template: function(){
|
|
return "<span role='button' tabindex='0' class='webix_cal_icon_clear webix_cal_icon'>"+webix.i18n.calendar.clear+"</span>";
|
|
},
|
|
on_click:{
|
|
"webix_cal_icon_clear": function(){
|
|
this.setValue("");
|
|
this.callEvent("onDateClear",[this.getSelectedDate()]);
|
|
}
|
|
}
|
|
}
|
|
],
|
|
refresh:function(){ this.render(); },
|
|
render: function() {
|
|
//reset zoom level
|
|
this._zoom_level = 0;
|
|
this._zoom_size = false;
|
|
|
|
var s = this._settings;
|
|
|
|
if (!this.isVisible(s.id)) return;
|
|
this._current_time = webix.Date.datePart(new Date());
|
|
|
|
if (webix.debug_render)
|
|
webix.log("Render: "+this.name+"@"+s.id);
|
|
this.callEvent("onBeforeRender",[]);
|
|
|
|
var date = this._settings.date;
|
|
|
|
var bounds = this._getDateBoundaries(date, true);
|
|
var sizes = this._getColumnConfigSizes(date);
|
|
var width = sizes[0];
|
|
var height = sizes[1];
|
|
|
|
var html = "<div class='webix_cal_month'><span role='button' tabindex='0' aria-live='assertive' aria-atomic='true' class='webix_cal_month_name"+(!this._settings.monthSelect?" webix_readonly":"")+"'>"+s.calendarHeader(date)+'</span>';
|
|
if (s.navigation)
|
|
html += "<div role='button' tabindex='0' aria-label='"+webix.i18n.aria.navMonth[0]+"' class='webix_cal_prev_button'></div><div role='button' tabindex='0' aria-label='"+webix.i18n.aria.navMonth[1]+"' class='webix_cal_next_button'></div>";
|
|
html += "</div>";
|
|
|
|
if(s.weekHeader)
|
|
html += "<div class='webix_cal_header' aria-hidden='true'>"+this._week_template(width)+"</div>";
|
|
html += "<div class='webix_cal_body'>"+this._body_template(width, height, bounds)+"</div>";
|
|
|
|
if (this._settings.timepicker || this._icons){
|
|
html += "<div class='webix_cal_footer'>";
|
|
if(this._settings.timepicker)
|
|
html += this._timepicker_template(date);
|
|
|
|
if(this._icons)
|
|
html += this._icons_template();
|
|
html += "</div>";
|
|
}
|
|
|
|
this._contentobj.innerHTML = html;
|
|
|
|
if(this._settings.type == "time"){
|
|
var time = this._settings.date;
|
|
if(time){
|
|
if(typeof(time) == "string"){
|
|
date = webix.i18n.parseTimeFormatDate(time);
|
|
}
|
|
else if(webix.isArray(time)){
|
|
date.setHours(time[0]);
|
|
date.setMinutes(time[1]);
|
|
}
|
|
}
|
|
this._changeZoomLevel(-1,date);
|
|
}
|
|
else if(this._settings.type == "month"){
|
|
this._changeZoomLevel(1,date);
|
|
}
|
|
else if(this._settings.type == "year"){
|
|
this._changeZoomLevel(2,date);
|
|
}
|
|
|
|
this.callEvent("onAfterRender",[]);
|
|
},
|
|
_icons_template: function(date){
|
|
var html = "<div class='webix_cal_icons'>";
|
|
var icons = this._icons;
|
|
|
|
for(var i=0; i < icons.length; i++){
|
|
if(icons[i].template){
|
|
var template = (typeof(icons[i].template) == "function"?icons[i].template: webix.template(icons[i].template));
|
|
html += template.call(this,date);
|
|
}
|
|
if(icons[i].on_click){
|
|
webix.extend(this.on_click,icons[i].on_click);
|
|
}
|
|
}
|
|
html += "</div>";
|
|
return html;
|
|
},
|
|
_timepicker_template:function(date){
|
|
var timeFormat = this._settings.calendarTime||webix.i18n.timeFormatStr;
|
|
var tpl = "";
|
|
|
|
if(!this._settings.master)
|
|
tpl = "<div role='button' tabindex='0' class='webix_cal_time"+(this._icons?" webix_cal_time_icons":"")+"'><span class='webix_icon fa-clock-o'></span> "+timeFormat(date)+"</div>";
|
|
else{
|
|
//daterange needs two clocks
|
|
var range_date = webix.copy(webix.$$(this._settings.master)._settings.value);
|
|
if(webix.Date.equal(range_date.end, date))
|
|
range_date.start = range_date.end;
|
|
|
|
for(var i in range_date){
|
|
tpl += "<div role='button' tabindex='0' class='webix_range_time_"+i+" webix_cal_time'><span class='webix_icon fa-clock-o'></span> "+timeFormat(range_date[i])+"</div>";
|
|
}
|
|
}
|
|
return tpl;
|
|
},
|
|
_week_template: function(widths){
|
|
var s = this._settings;
|
|
var week_template = '';
|
|
var correction = 0;
|
|
|
|
if(s.weekNumber) {
|
|
correction = 1;
|
|
week_template += "<div class='webix_cal_week_header' style='width: "+widths[0]+"px;' >"+s.calendarWeekHeader()+"</div>";
|
|
}
|
|
|
|
var k = (webix.Date.startOnMonday)?1:0;
|
|
for (var i=0; i<7; i++){ // 7 days total
|
|
var day_index = (k + i) % 7; // 0 - Sun, 6 - Sat as in Locale.date.day_short
|
|
var day = webix.i18n.calendar.dayShort[day_index]; // 01, 02 .. 31
|
|
week_template += "<div day='"+day_index+"' style='width: "+widths[i+correction]+"px;' >"+day+"</div>";
|
|
}
|
|
|
|
return week_template;
|
|
},
|
|
blockDates_setter:function(value){
|
|
return webix.toFunctor(value, this.$scope);
|
|
},
|
|
_day_css:function(day, bounds){
|
|
var css = "";
|
|
if (webix.Date.equal(day, this._current_time))
|
|
css += " webix_cal_today";
|
|
if (!this._checkDate(day))
|
|
css+= " webix_cal_day_disabled";
|
|
if (webix.Date.equal(day, this._selected_date_part))
|
|
css += " webix_cal_select";
|
|
if (day.getMonth() != bounds._month)
|
|
css += " webix_cal_outside";
|
|
if (this._settings.events)
|
|
css+=" "+(this._settings.events(day) || "");
|
|
css += " webix_cal_day";
|
|
return css;
|
|
},
|
|
_body_template: function(widths, heights, bounds){
|
|
var s = this._settings;
|
|
var html = "";
|
|
var day = webix.Date.datePart(webix.Date.copy(bounds._start));
|
|
var start = s.weekNumber?1:0;
|
|
var weekNumber = webix.Date.getISOWeek(webix.Date.add(day,2,"day", true));
|
|
var min = this._settings.minDate || new Date(1,1,1);
|
|
var max = this._settings.maxDate || new Date(9999,1,1);
|
|
|
|
for (var y=0; y<heights.length; y++){
|
|
html += "<div class='webix_cal_row' style='height:"+heights[y]+"px;line-height:"+heights[y]+"px'>";
|
|
|
|
if (start){
|
|
// recalculate week number for the first week of a year
|
|
if(!day.getMonth() && day.getDate()<7)
|
|
weekNumber = webix.Date.getISOWeek(webix.Date.add(day,2,"day", true));
|
|
html += "<div class='webix_cal_week_num' aria-hidden='true' style='width:"+widths[0]+"px'>"+weekNumber+"</div>";
|
|
}
|
|
|
|
for (var x=start; x<widths.length; x++){
|
|
var css = this._day_css(day, bounds);
|
|
var d = this._settings.dayTemplate.call(this,day);
|
|
var sel = webix.Date.equal(day, this._selected_date_part);
|
|
var alabel = "";
|
|
|
|
if(typeof d == "object"){
|
|
alabel = d.aria || alabel;
|
|
d = d.text;
|
|
}
|
|
else
|
|
alabel = webix.Date.dateToStr(webix.i18n.aria.dateFormat)(day);
|
|
|
|
html += "<div day='"+x+"' role='gridcell' "+(day.getMonth() != bounds._month?"aria-hidden='true'":"")+" aria-label='"+alabel+
|
|
"' tabindex='"+(sel?"0":"-1")+"' aria-selected='"+(sel?"true":"false")+
|
|
"' class='"+css+"' style='width:"+widths[x]+"px'><span aria-hidden='true' class='webix_cal_day_inner'>"+d+"</span></div>";
|
|
day = webix.Date.add(day, 1, "day");
|
|
if(day.getHours()){
|
|
day = webix.Date.datePart(day);
|
|
}
|
|
}
|
|
|
|
html += "</div>";
|
|
weekNumber++;
|
|
}
|
|
return html;
|
|
},
|
|
_changeDate:function(dir, step, notset){
|
|
var now = this._settings.date;
|
|
if(!step) { step = this._zoom_logic[this._zoom_level]._changeStep; }
|
|
if(!this._zoom_level){
|
|
now = webix.Date.copy(now);
|
|
now.setDate(1);
|
|
}
|
|
var next = webix.Date.add(now, dir*step, "month", true);
|
|
this._changeDateInternal(now, next);
|
|
},
|
|
_changeDateInternal:function(now, next){
|
|
if(this.callEvent("onBeforeMonthChange", [now, next])){
|
|
if (this._zoom_level){
|
|
this._update_zoom_level(next);
|
|
}
|
|
else{
|
|
this.showCalendar(next);
|
|
}
|
|
this.callEvent("onAfterMonthChange", [next, now]);
|
|
}
|
|
},
|
|
_zoom_logic:{
|
|
"-2":{
|
|
_isBlocked: function(i){
|
|
var config = this._settings,
|
|
date = config.date,
|
|
isBlocked = false;
|
|
|
|
var minHour = (config.minTime ? config.minTime[0] : 0);
|
|
var maxHour = (config.maxTime ? (config.maxTime[0] + ( config.maxTime[1] ? 1 : 0 )) : 24);
|
|
|
|
var minMinute = (config.minTime && (date.getHours()==minHour) ? config.minTime[1] : 0);
|
|
var maxMinute = (config.maxTime && config.maxTime[1] && (date.getHours()==(maxHour-1)) ? config.maxTime[1] : 60);
|
|
|
|
if(this._settings.blockTime){
|
|
var d = webix.Date.copy(date);
|
|
d.setMinutes(i);
|
|
isBlocked = this._settings.blockTime(d);
|
|
}
|
|
return (i < minMinute || i >= maxMinute || isBlocked);
|
|
|
|
},
|
|
_setContent:function(next, i){ next.setMinutes(i); },
|
|
_findActive:function(date, mode, calendar){
|
|
if(!this._isBlocked.call(calendar, date.getMinutes()))
|
|
return date;
|
|
else{
|
|
var step = calendar._settings.minuteStep;
|
|
var newdate = webix.Date.add(date, mode =="right"?step:-step, "minute", true);
|
|
if(date.getHours() === newdate.getHours())
|
|
return this._findActive(newdate, mode, calendar);
|
|
}
|
|
}
|
|
},
|
|
"-1":{
|
|
_isBlocked: function(i){
|
|
var config = this._settings,
|
|
date = config.date;
|
|
|
|
var minHour = (config.minTime? config.minTime[0]:0);
|
|
var maxHour = (config.maxTime? config.maxTime[0]+(config.maxTime[1]?1:0):24);
|
|
|
|
if (i < minHour || i >= maxHour) return true;
|
|
|
|
if(config.blockTime){
|
|
var d = webix.Date.copy(date);
|
|
d.setHours(i);
|
|
|
|
var minMinute = (config.minTime && (i==minHour) ? config.minTime[1] : 0);
|
|
var maxMinute = (config.maxTime && config.maxTime[1] && (i==(maxHour-1)) ? config.maxTime[1] : 60);
|
|
|
|
for (var j=minMinute; j<maxMinute; j+= config.minuteStep){
|
|
d.setMinutes(j);
|
|
if (!config.blockTime(d))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
},
|
|
_setContent:function(next, i){ next.setHours(i); },
|
|
_keyshift:function(date, mode, calendar){
|
|
var newdate, inc, step = calendar._settings.minuteStep;
|
|
|
|
if(mode === "bottom" || mode === "top"){
|
|
date.setHours(mode==="bottom"?23:0);
|
|
date.setMinutes(mode==="bottom"?55:0);
|
|
date.setSeconds(0);
|
|
date.setMilliseconds(0);
|
|
newdate = date;
|
|
}
|
|
else if(mode === "left" || mode === "right"){//minutes
|
|
|
|
inc = (mode==="right"?step:-step);
|
|
if(mode === "left" && date.getMinutes() < step ) inc = 60-step;
|
|
if(mode === "right" && date.getMinutes() >= (60-step)) inc = step-60;
|
|
inc -= date.getMinutes()%step;
|
|
newdate = calendar._zoom_logic["-2"]._findActive(webix.Date.add(date, inc, "minute"), mode, calendar);
|
|
}
|
|
else if(mode === "up" || mode === "down"){ //hours
|
|
inc = mode==="down"?1:-1;
|
|
if(mode === "down" && date.getHours() === 23) inc = -23;
|
|
if(mode === "up" && date.getHours() === 0) inc = 23;
|
|
newdate = this._findActive(webix.Date.add(date, inc, "hour"), mode, calendar);
|
|
}
|
|
else if(mode === false)
|
|
newdate = this._findActive(date, mode, calendar);
|
|
|
|
calendar.selectDate(newdate, false);
|
|
|
|
if(newdate){
|
|
calendar._update_zoom_level(newdate);
|
|
calendar.selectDate(newdate, false);
|
|
}
|
|
|
|
return "webix_cal_block"+(mode === "left" || mode === "right"?"_min":"");
|
|
},
|
|
_findActive:function(date, mode, calendar){
|
|
if(!this._isBlocked.call(calendar, date.getHours()))
|
|
return date;
|
|
else{
|
|
var newdate = webix.Date.add(date, mode =="down"?1:-1, "hour", true);
|
|
if(date.getDate() === newdate.getDate())
|
|
return this._findActive(newdate, mode, calendar);
|
|
}
|
|
}
|
|
},
|
|
"0":{//days
|
|
_changeStep:1,
|
|
_keyshift:function(date, mode, calendar){
|
|
var newdate = date;
|
|
if(mode === "pgup" || mode === "pgdown")
|
|
newdate = webix.Date.add(date, (mode==="pgdown"?1:-1), "month");
|
|
else if(mode === "bottom")
|
|
newdate = new Date(date.getFullYear(), date.getMonth()+1, 0);
|
|
else if(mode === "top")
|
|
newdate = new Date(date.setDate(1));
|
|
else if(mode === "left" || mode === "right")
|
|
newdate = webix.Date.add(date, (mode==="right"?1:-1), "day");
|
|
else if(mode === "up" || mode === "down")
|
|
newdate = webix.Date.add(date, (mode==="down"?1:-1), "week");
|
|
|
|
if(!calendar._checkDate(newdate))
|
|
newdate = calendar._findActive(date, mode);
|
|
|
|
if(newdate)
|
|
calendar.selectDate(newdate, true);
|
|
return "webix_cal_day";
|
|
},
|
|
|
|
},
|
|
"1":{ //months
|
|
_isBlocked: function(i,calendar){
|
|
var blocked = false, minYear, maxYear,
|
|
min = calendar._settings.minDate||null,
|
|
max = calendar._settings.maxDate||null,
|
|
year = calendar._settings.date.getFullYear();
|
|
|
|
if(min && max){
|
|
minYear = min.getFullYear();
|
|
maxYear = max.getFullYear();
|
|
if(year<minYear||year==minYear&&min.getMonth()>i || year>maxYear||year==maxYear&&max.getMonth()<i)
|
|
blocked = true;
|
|
}
|
|
return blocked;
|
|
},
|
|
_correctDate: function(date,calendar){
|
|
if(date < calendar._settings.minDate){
|
|
date = webix.Date.copy(calendar._settings.minDate);
|
|
}
|
|
else if(date > calendar._settings.maxDate){
|
|
date = webix.Date.copy(calendar._settings.maxDate);
|
|
}
|
|
return date;
|
|
},
|
|
_getTitle:function(date){ return date.getFullYear(); },
|
|
_getContent:function(i){ return webix.i18n.calendar.monthShort[i]; },
|
|
_setContent:function(next, i){ if(i!=next.getMonth()) next.setDate(1);next.setMonth(i); },
|
|
_changeStep:12,
|
|
_keyshift:function(date, mode, calendar){
|
|
var newdate = date;
|
|
if(mode === "pgup" || mode === "pgdown")
|
|
newdate = webix.Date.add(date, (mode==="pgdown"?1:-1), "year");
|
|
else if(mode === "bottom")
|
|
newdate = new Date(date.setMonth(11));
|
|
else if(mode === "top")
|
|
newdate = new Date(date.setMonth(0));
|
|
else if(mode === "left" || mode === "right")
|
|
newdate = webix.Date.add(date, (mode==="right"?1:-1), "month");
|
|
else if(mode === "up" || mode === "down")
|
|
newdate = webix.Date.add(date, (mode==="down"?4:-4), "month");
|
|
|
|
if(!calendar._checkDate(newdate))
|
|
newdate = calendar._findActive(date, mode);
|
|
|
|
if(newdate){
|
|
calendar._update_zoom_level(newdate);
|
|
calendar.selectDate(newdate, false);
|
|
}
|
|
|
|
return "webix_cal_block";
|
|
}
|
|
},
|
|
"2":{ //years
|
|
_isBlocked: function(i,calendar){
|
|
i += calendar._zoom_start_date;
|
|
var blocked = false;
|
|
var min = calendar._settings.minDate;
|
|
var max = calendar._settings.maxDate;
|
|
|
|
if( min && max && (min.getFullYear()>i || max.getFullYear()<i)){
|
|
blocked = true;
|
|
}
|
|
return blocked;
|
|
},
|
|
_correctDate: function(date,calendar){
|
|
if(date < calendar._settings.minDate){
|
|
date = webix.Date.copy(calendar._settings.minDate);
|
|
}
|
|
else if(date > calendar._settings.maxDate){
|
|
date = webix.Date.copy(calendar._settings.maxDate);
|
|
}
|
|
return date;
|
|
},
|
|
_getTitle:function(date, calendar){
|
|
var start = date.getFullYear();
|
|
calendar._zoom_start_date = start = start - start%10 - 1;
|
|
return start+" - "+(start+10 + 1);
|
|
},
|
|
_getContent:function(i, calendar){ return calendar._zoom_start_date+i; },
|
|
_setContent:function(next, i, calendar){ next.setFullYear(calendar._zoom_start_date+i); },
|
|
_changeStep:12*10,
|
|
_keyshift:function(date, mode, calendar){
|
|
var newdate = date;
|
|
if(mode === "pgup" || mode === "pgdown")
|
|
newdate = webix.Date.add(date, (mode==="pgdown"?10:-10), "year");
|
|
else if(mode === "bottom")
|
|
newdate = new Date(date.setYear(calendar._zoom_start_date+10));
|
|
else if(mode === "top")
|
|
newdate = new Date(date.setYear(calendar._zoom_start_date));
|
|
else if(mode === "left" || mode === "right")
|
|
newdate = webix.Date.add(date, (mode==="right"?1:-1), "year");
|
|
else if(mode === "up" || mode === "down")
|
|
newdate = webix.Date.add(date, (mode==="down"?4:-4), "year");
|
|
|
|
if(!calendar._checkDate(newdate))
|
|
newdate = calendar._findActive(date, mode);
|
|
|
|
if(newdate){
|
|
calendar._update_zoom_level(newdate);
|
|
calendar.selectDate(newdate, false);
|
|
}
|
|
|
|
return "webix_cal_block";
|
|
}
|
|
}
|
|
},
|
|
_correctBlockedTime: function(){
|
|
var i, isDisabledHour, isDisabledMinutes;
|
|
isDisabledHour = this._zoom_logic[-1]._isBlocked.call(this,this._settings.date.getHours());
|
|
if(isDisabledHour){
|
|
for (i= 0; i< 24; i++){
|
|
if(!this._zoom_logic[-1]._isBlocked.call(this,i)){
|
|
this._settings.date.setHours(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
isDisabledMinutes = this._zoom_logic[-2]._isBlocked.call(this,this._settings.date.getMinutes());
|
|
if(isDisabledMinutes){
|
|
for (i=0; i<60; i+=this._settings.minuteStep){
|
|
if(!this._zoom_logic[-2]._isBlocked.call(this,i)){
|
|
this._settings.date.setMinutes(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
_update_zoom_level:function(date){
|
|
var config, css, height, i, index, sections, selected, type, width, zlogic, value, temp;
|
|
var html = "";
|
|
|
|
config = this._settings;
|
|
index = config.weekHeader?2: 1;
|
|
zlogic = this._zoom_logic[this._zoom_level];
|
|
sections = this._contentobj.childNodes;
|
|
|
|
if (date){
|
|
config.date = date;
|
|
}
|
|
|
|
type = config.type;
|
|
|
|
|
|
|
|
//store width and height of draw area
|
|
if (!this._zoom_size){
|
|
/*this._reserve_box_height = sections[index].offsetHeight +(index==2?sections[1].offsetHeight:0);*/
|
|
|
|
this._reserve_box_height = this._contentobj.offsetHeight - config.headerHeight ;
|
|
if(type != "year" && type != "month")
|
|
this._reserve_box_height -= config.timepickerHeight;
|
|
else if(this._icons){
|
|
this._reserve_box_height -= 10;
|
|
}
|
|
this._reserve_box_width = sections[index].offsetWidth;
|
|
this._zoom_size = 1;
|
|
}
|
|
|
|
//main section
|
|
if (this._zoom_in){
|
|
//hours and minutes
|
|
height = this._reserve_box_height/6;
|
|
var timeColNum = 6;
|
|
var timeFormat = this._calendarTime||webix.i18n.timeFormat;
|
|
var enLocale = timeFormat.match(/%([a,A])/);
|
|
if(enLocale)
|
|
timeColNum++;
|
|
width = parseInt((this._reserve_box_width-3)/timeColNum,10);
|
|
|
|
html += "<div class='webix_time_header'>"+this._timeHeaderTemplate(width,enLocale)+"</div>";
|
|
html += "<div class='webix_cal_body' style='height:"+this._reserve_box_height+"px'>";
|
|
|
|
// check and change blocked selected time
|
|
this._correctBlockedTime();
|
|
|
|
html += "<div class='webix_hours'>";
|
|
selected = config.date.getHours();
|
|
temp = webix.Date.copy(config.date);
|
|
|
|
for (i= 0; i< 24; i++){
|
|
css="";
|
|
if(enLocale){
|
|
if(i%4===0){
|
|
var label = (!i ? webix.i18n.am[0] : (i==12?webix.i18n.pm[0]:""));
|
|
html += "<div class='webix_cal_block_empty"+css+"' style='"+this._getCalSizesString(width,height)+"clear:both;"+"'>"+label+"</div>";
|
|
}
|
|
}
|
|
if(this._zoom_logic[-1]._isBlocked.call(this,i)){
|
|
css += " webix_cal_day_disabled";
|
|
}
|
|
else if(selected == i)
|
|
css += " webix_selected";
|
|
|
|
|
|
temp.setHours(i);
|
|
|
|
html += "<div aria-label='"+webix.Date.dateToStr(webix.i18n.aria.hourFormat)(temp)+"' role='gridcell'"+
|
|
" tabindex='"+(selected==i?"0":"-1")+"' aria-selected='"+(selected==i?"true":"false")+
|
|
"' class='webix_cal_block"+css+"' data-value='"+i+"' style='"+
|
|
this._getCalSizesString(width,height)+(i%4===0&&!enLocale?"clear:both;":"")+"'>"+webix.Date.toFixed(enLocale?(!i||i==12?12:i%12):i)+"</div>";
|
|
}
|
|
html += "</div>";
|
|
|
|
html += "<div class='webix_minutes'>";
|
|
selected = config.date.getMinutes();
|
|
temp = webix.Date.copy(config.date);
|
|
|
|
|
|
for (i=0; i<60; i+=config.minuteStep){
|
|
css = "";
|
|
if(this._zoom_logic[-2]._isBlocked.call(this,i)){
|
|
css = " webix_cal_day_disabled";
|
|
}
|
|
else if(selected == i)
|
|
css = " webix_selected";
|
|
|
|
temp.setMinutes(i);
|
|
|
|
html += "<div aria-label='"+webix.Date.dateToStr(webix.i18n.aria.minuteFormat)(temp)+"' role='gridcell' tabindex='"+(selected==i?"0":"-1")+
|
|
"' aria-selected='"+(selected==i?"true":"false")+"' class='webix_cal_block webix_cal_block_min"+css+"' data-value='"+i+"' style='"+
|
|
this._getCalSizesString(width,height)+(i%2===0?"clear:both;":"")+"'>"+webix.Date.toFixed(i)+"</div>";
|
|
}
|
|
html += "</div>";
|
|
|
|
html += "</div>";
|
|
html += "<div class='webix_time_footer'>"+this._timeButtonsTemplate()+"</div>";
|
|
this._contentobj.innerHTML = html;
|
|
} else {
|
|
//years and months
|
|
|
|
//reset header
|
|
var header = sections[0].childNodes;
|
|
var labels = webix.i18n.aria["nav"+(this._zoom_level==1?"Year":"Decade")];
|
|
header[0].innerHTML = zlogic._getTitle(config.date, this);
|
|
header[1].setAttribute("aria-label", labels[0]);
|
|
header[2].setAttribute("aria-label", labels[1]);
|
|
|
|
height = this._reserve_box_height/3;
|
|
width = this._reserve_box_width/4;
|
|
if(this._checkDate(config.date))
|
|
selected = (this._zoom_level==1?config.date.getMonth():config.date.getFullYear());
|
|
for (i=0; i<12; i++){
|
|
css = (selected == (this._zoom_level==1?i:zlogic._getContent(i, this)) ? " webix_selected" : "");
|
|
if(zlogic._isBlocked(i,this)){
|
|
css += " webix_cal_day_disabled";
|
|
}
|
|
|
|
var format = webix.i18n.aria[(this._zoom_level==1?"month":"year")+"Format"];
|
|
html+="<div role='gridcell' aria-label='"+webix.Date.dateToStr(format)(config.date)+
|
|
"' tabindex='"+(css.indexOf("selected")!==-1?"0":"-1")+
|
|
"' aria-selected='"+(css.indexOf("selected")!==-1?"true":"false")+
|
|
"' class='webix_cal_block"+css+"' data-value='"+i+"' style='"+this._getCalSizesString(width,height)+"'>"+
|
|
zlogic._getContent(i, this)+"</div>";
|
|
}
|
|
if(index-1){
|
|
sections[index-1].style.display = "none";
|
|
}
|
|
sections[index].innerHTML = html;
|
|
if(type != "year" && type != "month"){
|
|
if(!sections[index+1])
|
|
this._contentobj.innerHTML += "<div class='webix_time_footer'>"+this._timeButtonsTemplate()+"</div>";
|
|
else
|
|
sections[index+1].innerHTML=this._timeButtonsTemplate();
|
|
}
|
|
sections[index].style.height = this._reserve_box_height+"px";
|
|
}
|
|
},
|
|
_getCalSizesString: function(width,height){
|
|
return "width:"+width+"px; height:"+height+"px; line-height:"+height+"px;";
|
|
},
|
|
_timeButtonsTemplate: function(){
|
|
return "<input type='button' style='width:100%' class='webix_cal_done' value='"+webix.i18n.calendar.done+"'>";
|
|
},
|
|
_timeHeaderTemplate: function(width,enLocale){
|
|
var w1 = width*(enLocale?5:4);
|
|
var w2 = width*2;
|
|
return "<div class='webix_cal_hours' style='width:"+w1+"px'>"+webix.i18n.calendar.hours+"</div><div class='webix_cal_minutes' style='width:"+w2+"px'>"+webix.i18n.calendar.minutes+"</div>";
|
|
},
|
|
_changeZoomLevel: function(zoom,date){
|
|
var oldzoom = this._zoom_level;
|
|
if(this.callEvent("onBeforeZoom",[zoom, oldzoom])){
|
|
this._zoom_level = zoom;
|
|
|
|
if(zoom)
|
|
this._update_zoom_level(date);
|
|
else
|
|
this.showCalendar(date);
|
|
this.callEvent("onAfterZoom",[zoom, oldzoom]);
|
|
}
|
|
},
|
|
_correctDate:function(date){
|
|
if(!this._checkDate(date) && this._zoom_logic[this._zoom_level]._correctDate)
|
|
date = this._zoom_logic[this._zoom_level]._correctDate(date,this);
|
|
return date;
|
|
},
|
|
_mode_selected:function(target){
|
|
|
|
var next = this._locate_date(target);
|
|
var zoom = this._zoom_level-(this._fixed?0:1);
|
|
|
|
next = this._correctDate(next);
|
|
if(this._checkDate(next)){
|
|
this._changeZoomLevel(zoom, next);
|
|
var type = this._settings.type;
|
|
if(type == "month" || type == "year")
|
|
this._selectDate(next);
|
|
}
|
|
},
|
|
// selects date and redraw calendar
|
|
_selectDate: function(date){
|
|
if(this.callEvent("onBeforeDateSelect", [date])){
|
|
this.selectDate(date, true);
|
|
this.callEvent("onDateSelect", [date]); // should be deleted in a future version
|
|
this.callEvent("onAfterDateSelect", [date]);
|
|
}
|
|
},
|
|
_locate_day:function(target){
|
|
var cind = webix.html.index(target) - (this._settings.weekNumber?1:0);
|
|
var rind = webix.html.index(target.parentNode);
|
|
var date = webix.Date.add(this._getDateBoundaries()._start, cind + rind*7, "day", true);
|
|
if (this._settings.timepicker){
|
|
date.setHours(this._settings.date.getHours());
|
|
date.setMinutes(this._settings.date.getMinutes());
|
|
}
|
|
return date;
|
|
},
|
|
_locate_date:function(target){
|
|
var value = target.getAttribute("data-value")*1;
|
|
var level = (target.className.indexOf("webix_cal_block_min")!=-1?this._zoom_level-1:this._zoom_level);
|
|
var now = this._settings.date;
|
|
var next = webix.Date.copy(now);
|
|
|
|
this._zoom_logic[level]._setContent(next, value, this);
|
|
|
|
return next;
|
|
},
|
|
on_click:{
|
|
webix_cal_prev_button: function(e, id, target){
|
|
this._changeDate(-1);
|
|
},
|
|
webix_cal_next_button: function(e, id, target){
|
|
this._changeDate(1);
|
|
},
|
|
webix_cal_day_disabled: function(){
|
|
return false;
|
|
},
|
|
webix_cal_outside: function(){
|
|
if(!this._settings.navigation)
|
|
return false;
|
|
},
|
|
webix_cal_day: function(e, id, target){
|
|
var date = this._locate_day(target);
|
|
this._selectDate(date);
|
|
},
|
|
webix_cal_time:function(e){
|
|
if(this._zoom_logic[this._zoom_level-1]){
|
|
this._zoom_in = true;
|
|
var zoom = this._zoom_level - 1;
|
|
this._changeZoomLevel(zoom);
|
|
}
|
|
},
|
|
webix_range_time_start:function(){
|
|
webix.$$(this._settings.master)._time_mode = "start";
|
|
},
|
|
webix_range_time_end:function(){
|
|
webix.$$(this._settings.master)._time_mode = "end";
|
|
},
|
|
webix_cal_done:function(e){
|
|
var date = webix.Date.copy(this._settings.date);
|
|
date = this._correctDate(date);
|
|
this._selectDate(date);
|
|
},
|
|
webix_cal_month_name:function(e){
|
|
this._zoom_in = false;
|
|
//maximum zoom reached
|
|
if (this._zoom_level == 2 || !this._settings.monthSelect) return;
|
|
|
|
var zoom = Math.max(this._zoom_level, 0) + 1;
|
|
this._changeZoomLevel(zoom);
|
|
},
|
|
webix_cal_block:function(e, id, trg){
|
|
if(this._zoom_in){
|
|
if(trg.className.indexOf('webix_cal_day_disabled')!==-1)
|
|
return false;
|
|
var next = this._locate_date(trg);
|
|
this._update_zoom_level(next);
|
|
}
|
|
else{
|
|
if(trg.className.indexOf('webix_cal_day_disabled')==-1)
|
|
this._mode_selected(trg);
|
|
}
|
|
}
|
|
},
|
|
_string_to_date: function(date, format){
|
|
if (!date){
|
|
return webix.Date.datePart(new Date());
|
|
}
|
|
if(typeof date == "string"){
|
|
if (format)
|
|
date = webix.Date.strToDate(format)(date);
|
|
else
|
|
date=webix.i18n.parseFormatDate(date);
|
|
}
|
|
|
|
return date;
|
|
},
|
|
_checkDate: function(date){
|
|
var blockedDate = (this._settings.blockDates && this._settings.blockDates.call(this,date));
|
|
var minDate = this._settings.minDate;
|
|
var maxDate = this._settings.maxDate;
|
|
var outOfRange = (date < minDate || date > maxDate);
|
|
return !blockedDate &&!outOfRange;
|
|
},
|
|
_findActive:function(date, mode){
|
|
var dir = (mode === "top" || mode ==="left" || mode === "pgup" || mode === "up") ? -1 : 1;
|
|
var newdate = webix.Date.add(date, dir, "day", true);
|
|
if(this._checkDate(newdate))
|
|
return newdate;
|
|
else{
|
|
var compare;
|
|
if(this._zoom_level === 0) compare = (date.getMonth() === newdate.getMonth());
|
|
else if(this._zoom_level === 1 ) compare = (date.getFullYear() === newdate.getFullYear());
|
|
else if(this._zoom_level === 2) compare = (newdate.getFullYear() > this._zoom_start_date && newdate.getFullYear() < this._zoom_start_date+10);
|
|
|
|
if(compare)
|
|
return this._findActive(newdate, mode);
|
|
}
|
|
},
|
|
showCalendar: function(date) {
|
|
date = this._string_to_date(date);
|
|
this._settings.date = date;
|
|
this.render();
|
|
this.resize();
|
|
},
|
|
getSelectedDate: function() {
|
|
return (this._selected_date)?webix.Date.copy(this._selected_date):this._selected_date;
|
|
|
|
},
|
|
getVisibleDate: function() {
|
|
return webix.Date.copy(this._settings.date);
|
|
},
|
|
setValue: function(date, format){
|
|
this.selectDate(date, true);
|
|
},
|
|
getValue: function(format){
|
|
var date = this.getSelectedDate();
|
|
if (format)
|
|
date = webix.Date.dateToStr(format)(date);
|
|
return date;
|
|
},
|
|
selectDate: function(date, show){
|
|
if(date){
|
|
date = this._string_to_date(date);
|
|
this._selected_date = date;
|
|
this._selected_date_part = webix.Date.datePart(webix.Date.copy(date));
|
|
}
|
|
else{ //deselect
|
|
this._selected_date = null;
|
|
this._selected_date_part = null;
|
|
if(this._settings.date){
|
|
webix.Date.datePart(this._settings.date);
|
|
}
|
|
}
|
|
|
|
if (show)
|
|
this.showCalendar(date);
|
|
else if(show !==false)
|
|
this.render();
|
|
|
|
this.callEvent("onChange",[date]);
|
|
},
|
|
locate:function(){ return null; }
|
|
|
|
}, webix.KeysNavigation, webix.MouseEvents, webix.ui.view, webix.EventSystem);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
webix.protoUI({
|
|
name:"property",
|
|
$init:function(){
|
|
this._contentobj.className+=" webix_property";
|
|
this._contentobj.setAttribute("role", "listbox");
|
|
this._destroy_with_me = [];
|
|
},
|
|
defaults:{
|
|
nameWidth:100,
|
|
editable:true
|
|
},
|
|
on_render:{
|
|
checkbox:function(value, config){
|
|
return "<input type='checkbox' class='webix_property_check' "+(value?"checked":"")+">";
|
|
},
|
|
color:function(value, config){
|
|
return "<div class=\"webix_property_col_val\"><div class='webix_property_col_ind' style=\"background-color:"+(value||"#FFFFFF")+";\"></div><span>" +value+"</span></div>";
|
|
}
|
|
},
|
|
on_edit:{
|
|
label:false
|
|
},
|
|
_id:"webix_f_id",
|
|
on_click:{
|
|
webix_property_check:function(ev){
|
|
var id = this.locate(ev);
|
|
this.getItem(id).value = !this.getItem(id).value;
|
|
this.callEvent("onCheck",[id, this.getItem(id).value]);
|
|
return false;
|
|
}
|
|
},
|
|
on_dblclick:{
|
|
},
|
|
registerType:function(name, data){
|
|
if (data.template)
|
|
this.on_render[name] = data.template;
|
|
if (data.editor)
|
|
this.on_edit[name] = data.editor;
|
|
if (data.click)
|
|
for (var key in data.click)
|
|
this.on_click[key] = data.click[key];
|
|
},
|
|
elements_setter:function(data){
|
|
this._idToLine = {};
|
|
for(var i =0; i < data.length; i++){
|
|
var line = data[i];
|
|
if (line.type == "multiselect")
|
|
line.optionslist = true;
|
|
|
|
//line.type = line.type||"label";
|
|
line.id = line.id||webix.uid();
|
|
line.label = line.label||"";
|
|
line.value = line.value||"";
|
|
this._idToLine[line.id] = i;
|
|
this.template = this._map_options(data[i]);
|
|
}
|
|
return data;
|
|
},
|
|
showItem:function(id){
|
|
webix.RenderStack.showItem.call(this, id);
|
|
},
|
|
locate:function(e){
|
|
return webix.html.locate(arguments[0], this._id);
|
|
},
|
|
getItemNode:function(id){
|
|
return this._dataobj.childNodes[this._idToLine[id]];
|
|
},
|
|
getItem:function(id){
|
|
return this._settings.elements[this._idToLine[id]];
|
|
},
|
|
_get_editor_type:function(id){
|
|
var type = this.getItem(id).type;
|
|
if (type == "checkbox") return "inline-checkbox";
|
|
var alter_type = this.on_edit[type];
|
|
return (alter_type === false)?false:(alter_type||type);
|
|
},
|
|
_get_edit_config:function(id){
|
|
return this.getItem(id);
|
|
},
|
|
_find_cell_next:function(start, check , direction){
|
|
var row = this._idToLine[start.id];
|
|
var order = this._settings.elements;
|
|
|
|
if (direction){
|
|
for (var i=row+1; i<order.length; i++){
|
|
if (check.call(this, order[i].id))
|
|
return order[i].id;
|
|
}
|
|
} else {
|
|
for (var i=row-1; i>=0; i--){
|
|
if (check.call(this, order[i].id))
|
|
return order[i].id;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
},
|
|
updateItem:function(key, data){
|
|
data = data || {};
|
|
|
|
var line = this.getItem(key);
|
|
if (line)
|
|
webix.extend(line, data, true);
|
|
|
|
this.refresh();
|
|
},
|
|
_cellPosition:function(id){
|
|
var html = this.getItemNode(id);
|
|
return {
|
|
left:html.offsetLeft+this._settings.nameWidth,
|
|
top:html.offsetTop,
|
|
height:html.firstChild.offsetHeight,
|
|
width:this._data_width,
|
|
parent:this._contentobj
|
|
};
|
|
},
|
|
setValues:function(data, update){
|
|
if (this._settings.complexData)
|
|
data = webix.CodeParser.collapseNames(data);
|
|
|
|
if(!update) this._clear();
|
|
for(var key in data){
|
|
var line = this.getItem(key);
|
|
if (line)
|
|
line.value = data[key];
|
|
}
|
|
|
|
this._props_dataset = data;
|
|
this.refresh();
|
|
},
|
|
_clear:function(){
|
|
var lines = this._settings.elements;
|
|
for (var i=0; i<lines.length; i++)
|
|
lines[i].value = "";
|
|
},
|
|
getValues:function(){
|
|
var data = webix.clone(this._props_dataset||{});
|
|
for (var i = 0; i < this._settings.elements.length; i++) {
|
|
var line = this._settings.elements[i];
|
|
if (line.type != "label")
|
|
data[line.id] = line.value;
|
|
}
|
|
|
|
if (this._settings.complexData)
|
|
data = webix.CodeParser.expandNames(data);
|
|
|
|
return data;
|
|
},
|
|
refresh:function(){
|
|
this.render();
|
|
},
|
|
$setSize:function(x,y){
|
|
if (webix.ui.view.prototype.$setSize.call(this, x, y)){
|
|
this._data_width = this._content_width - this._settings.nameWidth;
|
|
this.render();
|
|
}
|
|
},
|
|
$getSize:function(dx,dy){
|
|
if (this._settings.autoheight){
|
|
var count = this._settings.elements.length;
|
|
this._settings.height = Math.max(this.type.height * count,this._settings.minHeight||0);
|
|
}
|
|
return webix.ui.view.prototype.$getSize.call(this, dx, dy);
|
|
},
|
|
_toHTML:function(){
|
|
var html = [];
|
|
var els = this._settings.elements;
|
|
if (els)
|
|
for (var i=0; i<els.length; i++){
|
|
var data = els[i];
|
|
if (data.css && typeof data.css == "object")
|
|
data.css = webix.html.createCss(data.css);
|
|
|
|
var pre = '<div webix_f_id="'+data.id+'"'+(data.type!=='label'?'role="option" tabindex="0"':'')+' class="webix_property_line '+(data.css||'')+'">';
|
|
if (data.type == "label")
|
|
html[i] = pre+"<div class='webix_property_label_line'>"+data.label+"</div></div>";
|
|
else {
|
|
var render = this.on_render[data.type],
|
|
content;
|
|
var post = "<div class='webix_property_label' style='width:"+this._settings.nameWidth+"px'>"+data.label+"</div><div class='webix_property_value' style='width:"+this._data_width+"px'>";
|
|
if(data.collection || data.options){
|
|
content = data.template(data, data.value);
|
|
}else if(data.format)
|
|
content = data.format(data.value);
|
|
else
|
|
content = data.value;
|
|
if (render)
|
|
content = render.call(this, data.value, data);
|
|
html[i] = pre+post+content+"</div></div>";
|
|
}
|
|
}
|
|
return html.join("");
|
|
},
|
|
type:{
|
|
height:24,
|
|
templateStart:webix.template(""),
|
|
templateEnd:webix.template("</div>")
|
|
},
|
|
$skin: function(){
|
|
this.type.height = webix.skin.$active.propertyItemHeight||24;
|
|
}
|
|
}, webix.AutoTooltip, webix.EditAbility, webix.MapCollection, webix.MouseEvents, webix.Scrollable, webix.SingleRender, webix.AtomDataLoader, webix.EventSystem, webix.ui.view);
|
|
|
|
|
|
|
|
|
|
|
|
webix.protoUI({
|
|
name:"colorboard",
|
|
defaults:{
|
|
template:"<div style=\"width:100%;height:100%;background-color:{obj.val}\"></div>",
|
|
palette:null,
|
|
height:220,
|
|
width:220,
|
|
cols:12,
|
|
rows:10,
|
|
minLightness:0.15,
|
|
maxLightness:1,
|
|
navigation:true
|
|
},
|
|
$init:function(config){
|
|
webix._event(this._viewobj, "click", webix.bind(function(e){
|
|
var value = webix.html.locate(e, "webix_val");
|
|
|
|
this.setValue(value);
|
|
this.callEvent("onItemClick", [this._settings.value, e]);
|
|
this.callEvent("onSelect", [this._settings.value]);
|
|
}, this));
|
|
|
|
this.$view.setAttribute("role", "grid");
|
|
this._viewobj.setAttribute("aria-readonly", "true");
|
|
},
|
|
_findIndex:function(value){
|
|
var pal = this._settings.palette;
|
|
value = (value || "").toUpperCase();
|
|
for(var r= 0, rows= pal.length; r < rows; r++)
|
|
for(var c= 0, cols = pal[r].length; c < cols; c++){
|
|
if(pal[r][c].toUpperCase() == value){
|
|
return {row:r, col:c};
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
$setSize:function(x,y){
|
|
if(webix.ui.view.prototype.$setSize.call(this,x,y)){
|
|
this.render();
|
|
}
|
|
},
|
|
getValue:function(){
|
|
return this._settings.value;
|
|
},
|
|
_getBox:function(){
|
|
return this._viewobj.firstChild;
|
|
},
|
|
setValue:function(value){
|
|
if(value && value.toString().charAt(0) != "#")
|
|
value = '#' + value;
|
|
|
|
var oldvalue = this._settings.value;
|
|
|
|
this._settings.value = value;
|
|
this.$setValue(value, oldvalue);
|
|
|
|
return value;
|
|
},
|
|
_selectBox:null,
|
|
_getSelectBox:function(){
|
|
if( this._selectBox && this._selectBox.parentNode ){
|
|
return this._selectBox;
|
|
}else{
|
|
var div = this._selectBox = document.createElement("div");
|
|
div.className = "webix_color_selector";
|
|
this._viewobj.lastChild.appendChild(div);
|
|
return div;
|
|
}
|
|
},
|
|
$setValue:function(value, oldvalue){
|
|
if(this.isVisible(this._settings.id)){
|
|
var cell, div, ind, parent, style,
|
|
left = 0, top = 0;
|
|
|
|
//remove tabindex for previous selection
|
|
if(oldvalue) ind = this._findIndex(oldvalue);
|
|
if(!ind) ind = {row:0, col:0};
|
|
this._viewobj.lastChild.childNodes[ind.row].childNodes[ind.col].setAttribute("tabindex", "-1");
|
|
|
|
ind = this._findIndex(value);
|
|
if(ind){
|
|
cell = this._viewobj.lastChild.childNodes[ind.row].childNodes[ind.col];
|
|
}
|
|
|
|
if(cell && cell.parentNode && cell.parentNode.parentNode){
|
|
parent = cell.parentNode;
|
|
left = cell.offsetLeft - parent.offsetLeft ;
|
|
top = - (this.$height - (cell.offsetTop -parent.parentNode.offsetTop ));
|
|
|
|
cell.setAttribute("tabindex", "0");
|
|
cell.setAttribute("aria-selected", "true");
|
|
cell.setAttribute("tabindex", "0");
|
|
cell.setAttribute("aria-selected", "true");
|
|
}else{
|
|
if (this._selectBox)
|
|
this._selectBox.style.left = "-100px";
|
|
this._viewobj.lastChild.childNodes[0].childNodes[0].setAttribute("tabindex", "0");
|
|
return;
|
|
}
|
|
|
|
div = this._getSelectBox();
|
|
style = [
|
|
"left:" + left + "px",
|
|
"top:" + top+"px",
|
|
"width:" + cell.style.width,
|
|
"height:" + cell.style.height
|
|
].join(";");
|
|
|
|
if( typeof( div.style.cssText ) !== 'undefined' ) {
|
|
div.style.cssText = style;
|
|
} else {
|
|
div.setAttribute('style',style);
|
|
}
|
|
}
|
|
},
|
|
|
|
|
|
_initPalette:function(config){
|
|
function numToHex(n){
|
|
return webix.color.toHex(n, 2);
|
|
}
|
|
function rgbToHex(r,g,b){
|
|
return "#"+numToHex( Math.floor(r)) +numToHex( Math.floor(g)) + numToHex(Math.floor(b));
|
|
}
|
|
function hslToRgb(h, s, l){
|
|
var r, g, b;
|
|
if(!s){
|
|
r = g = b = l; // achromatic
|
|
}else{
|
|
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
var p = 2 * l - q;
|
|
r = hue2rgb(p, q, h + 1/3);
|
|
g = hue2rgb(p, q, h);
|
|
b = hue2rgb(p, q, h - 1/3);
|
|
}
|
|
|
|
return {r:r * 255, g:g * 255, b:b * 255};
|
|
}
|
|
function hue2rgb(p, q, t){
|
|
if(t < 0) t += 1;
|
|
if(t > 1) t -= 1;
|
|
if (t < 1/6)
|
|
return p + (q - p) * 6 * t;
|
|
else if (t <= 1/2)
|
|
return q;
|
|
else if (t < 2/3)
|
|
return p + (q - p) * (2/3 - t) * 6;
|
|
else
|
|
return p;
|
|
}
|
|
|
|
function renderGrayBar(colCount){
|
|
var gray = [],
|
|
val = 255,
|
|
step = val / colCount;
|
|
|
|
for(var i=0; i < colCount; i++){
|
|
val = Math.round(val > 0 ? val : 0);
|
|
gray.push(rgbToHex(val, val, val));
|
|
val -= step;
|
|
}
|
|
gray[gray.length - 1] = "#000000";
|
|
return gray;
|
|
}
|
|
|
|
var colors = [];
|
|
var colorRows = config.rows - 1;
|
|
var colorStep = 1/config.cols;
|
|
var lightStep = (config.maxLightness - config.minLightness)/colorRows;
|
|
var colorRange = null;
|
|
|
|
colors.push(renderGrayBar(config.cols));
|
|
|
|
for(var step = 0, lt = config.minLightness; step < colorRows; step++){
|
|
colorRange = [];
|
|
for(var c = 0, col = 0; c < config.cols; c++ ){
|
|
var val = hslToRgb(col, 1, lt );
|
|
colorRange.push(rgbToHex(val.r, val.g, val.b));
|
|
col += colorStep;
|
|
}
|
|
colors.push(colorRange);
|
|
lt+=lightStep;
|
|
}
|
|
|
|
this._settings.palette = colors;
|
|
},
|
|
moveSelection:function(mode, details, focus){
|
|
var value = this.getValue(), ind, cell;
|
|
|
|
if(value) ind = this._findIndex(value);
|
|
if(!ind) ind = {row:0, col:0};
|
|
|
|
if(ind){
|
|
if(mode == "up" || mode == "down")
|
|
ind.row = ind.row + (mode == "up"?-1:1);
|
|
else if(mode == "right" || mode == "left")
|
|
ind.col = ind.col +(mode == "right"?1:-1);
|
|
else if(mode == "top" )
|
|
ind.row = ind.col = 0;
|
|
else if(mode == "bottom"){
|
|
ind.row = this._viewobj.lastChild.querySelectorAll(".webix_color_row").length-1;
|
|
ind.col = this._viewobj.lastChild.childNodes[ind.row].childNodes.length-1;
|
|
}
|
|
|
|
if(ind.row>=0)
|
|
cell = this._viewobj.lastChild.childNodes[ind.row].childNodes[ind.col];
|
|
if(cell){
|
|
value = cell.getAttribute("webix_val");
|
|
this.setValue(value);
|
|
this.callEvent("onSelect", [this._settings.value]);
|
|
|
|
if(focus !==false){
|
|
var sel = this._viewobj.querySelector("div[tabindex='0']");
|
|
if(sel) sel.focus();
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
render:function(){
|
|
if(!this.isVisible(this._settings.id))
|
|
return;
|
|
|
|
if(!this._settings.palette)
|
|
this._initPalette(this._settings);
|
|
var palette = this._settings.palette;
|
|
|
|
this.callEvent("onBeforeRender",[]);
|
|
var config = this._settings,
|
|
itemTpl = webix.template("<div role='gridcell' tabindex='-1' aria-label=\"{obj.val}\" style=\"width:{obj.width}px;height:{obj.height}px;\" webix_val=\"{obj.val}\">" + (config.template||"") + "</div>"),
|
|
data = {width: 0, height:0, val:0},
|
|
width = this.$width,
|
|
height = this.$height,
|
|
widths = [];
|
|
|
|
var html = "<div class=\"webix_color_palette\"role=\"rowgroup\">";
|
|
|
|
var firstRow = (typeof palette[0] == "object") ? palette[0] : palette;
|
|
for(var i=0; i < firstRow.length; i++){
|
|
widths[i] = Math.floor(width/(firstRow.length - i));
|
|
width -= widths[i];
|
|
}
|
|
|
|
if(typeof palette[0] == "object"){
|
|
for(var r=0; r < palette.length; r++){
|
|
var cellHeight = Math.floor(height/(palette.length - r));
|
|
height -= cellHeight;
|
|
var row = palette[r];
|
|
html += renderRow(row, widths, cellHeight);
|
|
}
|
|
}else{
|
|
html+= renderRow(palette, widths, height);
|
|
}
|
|
|
|
html += "</div>";
|
|
this._viewobj.innerHTML = html;
|
|
|
|
function renderRow(row, widths, height){
|
|
var rowHtml = "<div class=\"webix_color_row\" role=\"row\">";
|
|
for(var cell = 0; cell < row.length; cell++){
|
|
data.width = widths[cell];
|
|
data.height = height;
|
|
data.val = row[cell];
|
|
rowHtml += itemTpl(data);
|
|
}
|
|
rowHtml += "</div>";
|
|
return rowHtml;
|
|
}
|
|
this._selectBox = null;
|
|
if(this._settings.value)
|
|
this.$setValue(this._settings.value);
|
|
else
|
|
this._viewobj.lastChild.childNodes[0].childNodes[0].setAttribute("tabindex", "0");
|
|
this.callEvent("onAfterRender",[]);
|
|
},
|
|
refresh:function(){ this.render(); }
|
|
}, webix.KeysNavigation, webix.ui.view, webix.EventSystem);
|
|
|
|
|
|
webix.protoUI({
|
|
name:"daterange",
|
|
defaults:{
|
|
button:false,
|
|
icons:false,
|
|
calendarCount:2,
|
|
borderless:false
|
|
},
|
|
$init:function(config){
|
|
config.calendar = config.calendar || {};
|
|
config.value = this._correct_value(config.value);
|
|
delete config.calendar.type; // other types are not implemented
|
|
|
|
this._viewobj.className += " webix_daterange";
|
|
this._zoom_level = this._types[config.calendar.type] || 0;
|
|
|
|
var cols = [],
|
|
skinConf = webix.skin.$active.calendar,
|
|
cheight = skinConf && skinConf.height ? skinConf.height : 250,
|
|
cwidth = skinConf && skinConf.width ? skinConf.width : 250,
|
|
calendar = webix.extend({ view:"calendar", width:cwidth, height:cheight }, config.calendar || {}, true),
|
|
count = config.calendarCount = this._zoom_level === 0 ? (config.calendarCount || this.defaults.calendarCount) : this.defaults.calendarCount,
|
|
basecss = (calendar.css?calendar.css + " ":"")+"webix_range_",
|
|
start = config.value.start || new Date();
|
|
|
|
for(var i = 0; i<count; i++){
|
|
var date = webix.Date.add(start, this._steps[this._zoom_level]*i, "month", true);
|
|
|
|
webix.extend(calendar, {
|
|
events:webix.bind(this._isInRange, this),
|
|
css:basecss+(count ===1?"":(i === 0 ? "0" : (i+1 == count ? "N" :"1"))),
|
|
timepicker: this._zoom_level === 0?config.timepicker:false,
|
|
borderless:true,
|
|
date:date,
|
|
master:config.id
|
|
}, true);
|
|
|
|
cols.push(webix.copy(calendar));
|
|
}
|
|
|
|
|
|
config.rows = [
|
|
{ type:"clean", cols: cols},
|
|
this._footer_row(config, cwidth*count)
|
|
];
|
|
|
|
config.height = config.height || (calendar.height+(config.icons || config.button?35:0));
|
|
config.type = "line";
|
|
|
|
this.$ready.push(this._after_init);
|
|
|
|
webix.event(this.$view, "keydown", webix.bind(function(e){
|
|
this._onKeyPress( e.which || e.keyCode, e);
|
|
}, this));
|
|
},
|
|
value_setter:function(value){
|
|
return this._correct_value(value);
|
|
},
|
|
getValue:function(){
|
|
return this._settings.value;
|
|
},
|
|
setValue:function(value, silent){
|
|
value = this._correct_value(value);
|
|
this._settings.value = value;
|
|
|
|
var start = value.start || value.end || new Date();
|
|
|
|
if(!silent){
|
|
this._cals[0].showCalendar(value.start);
|
|
|
|
for(var i = 1; i<this._cals.length; i++){
|
|
this._cals[i]._settings.date = start;
|
|
this._changeDateSilent(this._cals[i], 1, i);
|
|
}
|
|
}
|
|
this.callEvent("onChange", [value]);
|
|
this.refresh();
|
|
},
|
|
refresh:function(){
|
|
var v = this._settings.value;
|
|
for(var i = 0; i<this._cals.length; i++){
|
|
|
|
if(this._cals[i]._zoom_level === this._zoom_level){
|
|
webix.html.removeCss(this._cals[i].$view, "webix_cal_timepicker");
|
|
webix.html.removeCss(this._cals[i].$view, "webix_range_timepicker");
|
|
|
|
|
|
var rel = this._related_date(this._cals[i].getVisibleDate());
|
|
if(rel.start || rel.end){
|
|
this._cals[i]._settings.date = rel.start || rel.end;
|
|
if(this._settings.timepicker){
|
|
var css = "webix_"+(rel.start && rel.end?"range":"cal")+"_timepicker";
|
|
webix.html.addCss(this._cals[i].$view, css);
|
|
}
|
|
}
|
|
else
|
|
webix.Date.datePart(this._cals[i]._settings.date);
|
|
|
|
this._cals[i].refresh();
|
|
}
|
|
}
|
|
},
|
|
addToRange:function(date){
|
|
var value = this._add_date(this._string_to_date(date));
|
|
this.setValue(value);
|
|
},
|
|
_icons:[
|
|
{
|
|
template:function(){
|
|
return "<span role='button' tabindex='0' class='webix_cal_icon_today webix_cal_icon'>"+webix.i18n.calendar.today+"</span>";
|
|
},
|
|
on_click:{
|
|
"webix_cal_icon_today":function(){
|
|
this.addToRange(new Date());
|
|
this.callEvent("onTodaySet",[this.getValue()]);
|
|
}
|
|
}
|
|
},
|
|
{
|
|
template:function(){
|
|
return "<span role='button' tabindex='0' class='webix_cal_icon_clear webix_cal_icon'>"+webix.i18n.calendar.clear+"</span>";
|
|
},
|
|
on_click:{
|
|
"webix_cal_icon_clear":function(){
|
|
this.setValue("");
|
|
this.callEvent("onDateClear", []);
|
|
}
|
|
}
|
|
}
|
|
],
|
|
_icons_template:function(icons){
|
|
if(!icons)
|
|
return { width:0};
|
|
else{
|
|
icons = (typeof icons =="object") ? icons:this._icons; //custom or default
|
|
var icons_template = { css:"webix_cal_footer ", borderless:true, height:30, template:"<div class='webix_cal_icons'>", onClick:{}};
|
|
|
|
for(var i = 0; i<icons.length; i++){
|
|
if(icons[i].template){
|
|
var template = (typeof(icons[i].template) == "function"?icons[i].template: webix.template(icons[i].template));
|
|
icons_template.template += template.call(this);
|
|
}
|
|
if(icons[i].on_click){
|
|
for(var k in icons[i].on_click){
|
|
icons_template.onClick[k] = webix.bind(icons[i].on_click[k], this);
|
|
}
|
|
}
|
|
}
|
|
icons_template.template += "</div>";
|
|
icons_template.width = webix.html.getTextSize(icons_template.template).width+30;
|
|
return icons_template;
|
|
}
|
|
},
|
|
_footer_row:function(config, width){
|
|
var button = { view:"button", value:webix.i18n.calendar.done,
|
|
minWidth:100, maxWidth:230,
|
|
align:"center", height:30, click:function(){
|
|
this.getParentView().getParentView().hide();
|
|
}};
|
|
|
|
var icons = this._icons_template(config.icons);
|
|
|
|
var row = { css:"webix_range_footer", cols:[
|
|
{ width:icons.width }
|
|
]};
|
|
if((config.button || config.icons) && (icons.width*2+button.minWidth) > width)
|
|
row.cols[0].width = 0;
|
|
|
|
row.cols.push(config.button ? button : {});
|
|
row.cols.push(icons);
|
|
|
|
return row;
|
|
},
|
|
_types:{
|
|
"time":-1,
|
|
"month":1,
|
|
"year":2
|
|
},
|
|
_steps:{
|
|
0:1,
|
|
1:12,
|
|
2:120
|
|
},
|
|
_correct_value:function(value){
|
|
if(!value) value = { start:null, end:null};
|
|
|
|
if(!value.start && !value.end)
|
|
value = {start: value};
|
|
|
|
value.end = this._string_to_date(value.end) || null;
|
|
value.start = this._string_to_date(value.start) || null;
|
|
|
|
if((value.end && value.end < value.start) || !value.start)
|
|
value.end = [value.start, value.start = value.end][0];
|
|
return value;
|
|
},
|
|
_string_to_date:function(date, format){
|
|
if(typeof date == "string"){
|
|
if (format)
|
|
date = webix.Date.strToDate(format)(date);
|
|
else
|
|
date=webix.i18n.parseFormatDate(date);
|
|
}
|
|
return isNaN(date*1) ? null : date;
|
|
},
|
|
_isInRange:function(date){
|
|
var v = this._settings.value,
|
|
s = v.start? webix.Date.datePart(webix.Date.copy(v.start)) : null,
|
|
e = v.end ? webix.Date.datePart(webix.Date.copy(v.end)) : null,
|
|
d = webix.Date.datePart(date),
|
|
css = "";
|
|
|
|
if(d>=s && e && d<=e)
|
|
css = "webix_cal_range";
|
|
if(webix.Date.equal(d, s))
|
|
css = "webix_cal_range_start";
|
|
if(webix.Date.equal(d, e))
|
|
css = "webix_cal_range_end";
|
|
|
|
var holiday =webix.Date.isHoliday(date)+" " || "";
|
|
return css+" "+holiday;
|
|
},
|
|
_after_init:function(){
|
|
var cals = this._cals = this.getChildViews()[0].getChildViews();
|
|
var range = this;
|
|
|
|
this._cals_hash = {};
|
|
|
|
for(var i = 0; i<cals.length; i++){
|
|
this._cals_hash[cals[i].config.id] = i;
|
|
|
|
//events
|
|
cals[i].attachEvent("onBeforeDateSelect", function(date){ return range._on_date_select(this, date); });
|
|
cals[i].attachEvent("onBeforeZoom", function(zoom){ return range._before_zoom(this, zoom); });
|
|
|
|
if(i===0 || i === cals.length-1){
|
|
cals[i].attachEvent("onAfterMonthChange", webix.bind(this._month_change, this));
|
|
cals[i].attachEvent("onAfterZoom", function(zoom, oldzoom){ range._after_zoom(this, zoom, oldzoom);});
|
|
}
|
|
}
|
|
if(this._settings.timepicker)
|
|
this.refresh();
|
|
},
|
|
_before_zoom:function(view, zoom){
|
|
var ind = this._getIndexById(view.config.id);
|
|
|
|
if(zoom >=0 && ind>0 && ind !== this._cals.length-1)
|
|
return false;
|
|
if(zoom ===-1){ //time mode
|
|
var rel = this._related_date(view.getVisibleDate());
|
|
if(rel.start && rel.end) //both dates are in one calendar
|
|
view._settings.date = rel[this._time_mode];
|
|
}
|
|
return true;
|
|
},
|
|
_month_change:function(now, prev){
|
|
var dir = now>prev ? 1: -1;
|
|
var start = now>prev ? this._cals[this._cals.length-1] : this._cals[0];
|
|
var step = start._zoom_logic[start._zoom_level]._changeStep;
|
|
|
|
this._shift(dir, step, start);
|
|
this.refresh();
|
|
},
|
|
_after_zoom:function(start, zoom, oldzoom){
|
|
var step = start._zoom_logic[start._zoom_level]._changeStep;
|
|
var ind = this._getIndexById(start.config.id);
|
|
var dir = ind === 0 ? 1 :-1;
|
|
if(!this._cals[ind+dir])
|
|
return;
|
|
|
|
var next = this._cals[ind+dir]._settings.date;
|
|
|
|
if(oldzoom>zoom && zoom >=0){
|
|
var diff = 0;
|
|
if(zoom === 1){ //year was changed
|
|
var year = next.getFullYear();
|
|
if(this._zoom_level || (dir === -1 && next.getMonth() === 11) || (dir ===1 && next.getMonth() === 0))
|
|
year = year - dir;
|
|
diff = start._settings.date.getFullYear()-year;
|
|
}
|
|
else if(zoom === 0 ){//month was changed
|
|
var month = next.getMonth()-dir;
|
|
if(month === 12 || month ==-1)
|
|
month = (month === -1) ? 11: 0;
|
|
|
|
diff = start._settings.date.getMonth()-month;
|
|
}
|
|
this._shift(diff, step, start);
|
|
this.refresh();
|
|
}
|
|
},
|
|
_changeDateSilent:function(view, dir, step){
|
|
view.blockEvent();
|
|
if(view._zoom_level>=0)
|
|
view._changeDate(dir, step);
|
|
view.unblockEvent();
|
|
},
|
|
_getIndexById:function(id){
|
|
return this._cals_hash[id];
|
|
},
|
|
_shift:function(dir, step, start){
|
|
for(var i =0; i<this._cals.length; i++){
|
|
var next = this._cals[i];
|
|
if(!start || next.config.id !==start.config.id)
|
|
this._changeDateSilent(next, dir, step);
|
|
}
|
|
},
|
|
_related_date:function(date){
|
|
var v = this._settings.value;
|
|
var rel = {};
|
|
if(v.start && v.start.getYear() === date.getYear() && v.start.getMonth() === date.getMonth())
|
|
rel.start = v.start;
|
|
if(v.end && v.end.getYear() === date.getYear() && v.end.getMonth() === date.getMonth())
|
|
rel.end = v.end;
|
|
return rel;
|
|
},
|
|
_set_time:function(date, source){
|
|
date.setHours(source.getHours());
|
|
date.setMinutes(source.getMinutes());
|
|
date.setSeconds(source.getSeconds());
|
|
date.setMilliseconds(source.getMilliseconds());
|
|
},
|
|
_add_date:function(date, ind){
|
|
var v = webix.copy(this._settings.value);
|
|
//year, month
|
|
if(this._zoom_level !==0 && !webix.isUndefined(ind)){
|
|
var key = ind?"end":"start";
|
|
v[key] = date;
|
|
}
|
|
else{
|
|
if(v.start && !v.end)
|
|
v.end = date;
|
|
else {
|
|
v.start = date;
|
|
v.end = null;
|
|
}
|
|
}
|
|
|
|
return v;
|
|
},
|
|
_on_date_select:function(view, date){
|
|
if(this.callEvent("onBeforeDateSelect", [date])){
|
|
var v = this._settings.value;
|
|
|
|
if(view._zoom_level<0){ //time set
|
|
var rel = webix.copy(this._related_date(date)),
|
|
reldate;
|
|
|
|
reldate = (rel.start && rel.end) ? rel[this._time_mode] : rel.start || rel.end;
|
|
if(reldate)
|
|
this._set_time(reldate, date);
|
|
|
|
view._zoom_level = 0;
|
|
|
|
v = webix.extend(webix.copy(v), rel, true);
|
|
}
|
|
else{
|
|
var vis = view.getVisibleDate();
|
|
var ind = this._getIndexById(view.config.id);
|
|
|
|
if(date.getMonth() !== vis.getMonth() && (ind ===0 || ind === this._cals.length-1)){
|
|
var dir = date>vis? 1 : -1;
|
|
this._shift(dir, 1);
|
|
}
|
|
v = this._add_date(date, ind);
|
|
}
|
|
|
|
if(view._zoom_level !== this._zoom_level)
|
|
view.showCalendar(date);
|
|
|
|
this.setValue(v, true);
|
|
this.callEvent("onAfterDateSelect", [this.getValue()]);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}, webix.ui.layout);
|
|
|
|
|
|
|
|
webix.protoUI({
|
|
name:"daterangesuggest",
|
|
defaults:{
|
|
type:"daterange",
|
|
body: {
|
|
view:"daterange", icons:true, button:true, borderless:true
|
|
}
|
|
},
|
|
getValue:function(){
|
|
return this.getRange().getValue();
|
|
},
|
|
setValue:function(value){
|
|
this.getRange().setValue(webix.copy(value));
|
|
},
|
|
getRange:function(){
|
|
return this.getBody();
|
|
},
|
|
getButton:function(){
|
|
return this.getBody().getChildViews()[1].getChildViews()[1];
|
|
},
|
|
_setValue:function(value, hide){
|
|
var master = webix.$$(this._settings.master);
|
|
|
|
if(master){
|
|
master.setValue(value);
|
|
if(hide) this.hide();
|
|
}
|
|
else
|
|
this.setValue(value);
|
|
},
|
|
_set_on_popup_click:function(){
|
|
var range = this.getRange();
|
|
range.attachEvent("onAfterDateSelect", webix.bind(function(value) {this._setValue(value);}, this));
|
|
range.attachEvent("onDateClear", webix.bind(function(value) {this._setValue(value);}, this));
|
|
range.attachEvent("onTodaySet", webix.bind(function(value) {this._setValue(value);}, this));
|
|
}
|
|
}, webix.ui.suggest);
|
|
|
|
|
|
webix.protoUI({
|
|
$cssName:"datepicker",
|
|
name:"daterangepicker",
|
|
$init:function(){
|
|
//set non-empty initial value
|
|
this._settings.value = {};
|
|
},
|
|
_init_popup:function(){
|
|
var obj = this._settings;
|
|
if (obj.suggest)
|
|
obj.popup = obj.suggest;
|
|
else if (!obj.popup){
|
|
obj.popup = obj.suggest = this.suggest_setter({
|
|
view:"daterangesuggest", body:{
|
|
timepicker:obj.timepicker, calendarCount:obj.calendarCount, height:250+(obj.button || obj.icons?30:0)
|
|
}
|
|
});
|
|
}
|
|
this._init_once = function(){};
|
|
},
|
|
$prepareValue:function(value){
|
|
value = value || {};
|
|
value.start = webix.ui.datepicker.prototype.$prepareValue.call(this, value.start?value.start:null);
|
|
value.end = webix.ui.datepicker.prototype.$prepareValue.call(this, value.end?value.end:null);
|
|
return value;
|
|
},
|
|
$compareValue:function(oldvalue, value){
|
|
var compare = webix.ui.datepicker.prototype.$compareValue;
|
|
var start = compare.call(this, oldvalue.start, value.start);
|
|
var end = compare.call(this, oldvalue.end, value.end);
|
|
|
|
return (start && end);
|
|
},
|
|
$setValue:function(value){
|
|
value = value || {};
|
|
|
|
var popup = webix.$$(this._settings.popup.toString());
|
|
var daterange = popup.getRange();
|
|
|
|
this._settings.text = (value.start?this._get_visible_text(value.start):"")+(value.end?(" - "+ this._get_visible_text(value.end)):"");
|
|
this._set_visible_text();
|
|
},
|
|
$render:function(obj){
|
|
obj.value = this.$prepareValue(obj.value);
|
|
this.$setValue(obj.value);
|
|
},
|
|
getValue:function(){
|
|
|
|
var type = this._settings.type;
|
|
//time mode
|
|
var timeMode = (type == "time");
|
|
//date and time mode
|
|
var timepicker = this.config.timepicker;
|
|
|
|
var value = this._settings.value;
|
|
|
|
if(this._settings.stringResult){
|
|
var formatStr =webix.i18n.parseFormatStr;
|
|
if(timeMode)
|
|
formatStr = webix.i18n.parseTimeFormatStr;
|
|
if(this._formatStr && (type == "month" || type == "year")){
|
|
formatStr = this._formatStr;
|
|
}
|
|
|
|
return this._formatValue(formatStr, value);
|
|
}
|
|
|
|
return value||null;
|
|
},
|
|
_formatValue:function(format, value){
|
|
var popup = webix.$$(this._settings.popup.toString());
|
|
var daterange = popup.getRange();
|
|
value = webix.copy(daterange._correct_value(value));
|
|
|
|
if(value.start) value.start = format(value.start);
|
|
if(value.end) value.end = format(value.end);
|
|
return value;
|
|
}
|
|
}, webix.ui.datepicker);
|
|
|
|
|
|
|
|
webix.protoUI({
|
|
name:"resizer",
|
|
defaults:{
|
|
width:7, height:7
|
|
},
|
|
$init:function(config){
|
|
webix.assert(this.getParentView(), "Resizer can't be initialized outside a layout");
|
|
this._viewobj.className += " webix_resizer";
|
|
var space = this.getParentView()._margin;
|
|
|
|
webix._event(this._viewobj, webix.env.mouse.down, this._rsDown, {bind:this});
|
|
webix.event(document.body, webix.env.mouse.up, this._rsUp, {bind:this});
|
|
|
|
var dir = this._getResizeDir();
|
|
|
|
this._rs_started = false;
|
|
this._resizer_dir = dir;
|
|
|
|
this._resizer_dim = (dir=="x"?"width":"height");
|
|
|
|
if (dir=="x")
|
|
config.height = 0;
|
|
else
|
|
config.width = 0;
|
|
|
|
if (space>0){
|
|
this._viewobj.className += " webix_resizer_v"+dir;
|
|
this._viewobj.style.marginRight = "-"+space+"px";
|
|
if (dir == "x")
|
|
config.width = space;
|
|
else
|
|
config.height = space;
|
|
this.$nospace = true;
|
|
} else
|
|
this._viewobj.className += " webix_resizer_"+dir;
|
|
|
|
this._viewobj.innerHTML = "<div class='webix_resizer_content'></div>";
|
|
if (dir == "y" && space>0) this._viewobj.style.marginBottom = "-"+(config.height||this.defaults.height)+"px";
|
|
|
|
this._viewobj.setAttribute("tabindex", "-1");
|
|
this._viewobj.setAttribute("aria-grabbed", "false");
|
|
|
|
},
|
|
_rsDown:function(e){
|
|
var cells = this._getResizerCells();
|
|
//some sibling can block resize
|
|
if(cells && !this._settings.disabled){
|
|
e = e||event;
|
|
this._rs_started = true;
|
|
this._rs_process = webix.html.pos(e);
|
|
this._rsLimit = [];
|
|
this._viewobj.setAttribute("aria-grabbed", "true");
|
|
|
|
for(var i=0; i<2; i++)
|
|
cells[i].$view.setAttribute("aria-dropeffect", "move");
|
|
this._viewobj.setAttribute("aria-dropeffect", "move");
|
|
|
|
this._rsStart(e, cells[0]);
|
|
}
|
|
},
|
|
_rsUp:function(){
|
|
this._rs_started = false;
|
|
this._rs_process = false;
|
|
},
|
|
_rsStart:function(e, cell){
|
|
|
|
var dir,offset, pos,posParent,start;
|
|
e = e||event;
|
|
dir = this._resizer_dir;
|
|
|
|
/*layout position:relative to place absolutely positioned elements in it*/
|
|
this.getParentView()._viewobj.style.position = "relative";
|
|
pos = webix.html.offset(this._viewobj);
|
|
posParent = webix.html.offset(this.getParentView()._viewobj);
|
|
start = pos[dir]-posParent[dir];
|
|
offset = webix.html.offset(cell.$view)[dir]- webix.html.offset(this.getParentView().$view)[dir];
|
|
|
|
this._rs_progress = [dir,cell, start, offset];
|
|
/*resizer stick (resizerea ext)*/
|
|
|
|
this._resizeStick = new webix.ui.resizearea({
|
|
container:this.getParentView()._viewobj,
|
|
dir:dir,
|
|
eventPos:this._rs_process[dir],
|
|
start:start-1,
|
|
height: this.$height,
|
|
width: this.$width,
|
|
border: 1,
|
|
margin: this.getParentView()["_padding"+dir.toUpperCase()]
|
|
});
|
|
|
|
/*stops resizing on stick mouseup*/
|
|
this._resizeStick.attachEvent("onResizeEnd", webix.bind(this._rsEnd, this));
|
|
/*needed to stop stick moving when the limit for dimension is reached*/
|
|
this._resizeStick.attachEvent("onResize", webix.bind(this._rsResizeHandler, this));
|
|
|
|
webix.html.addCss(document.body,"webix_noselect",1);
|
|
},
|
|
_getResizeDir: function(){
|
|
return this.getParentView()._vertical_orientation?"y":"x";
|
|
},
|
|
_rsResizeHandler:function(){
|
|
var cells,config,cDiff,diff,dir,i,limits,limitSizes,sizes,totalSize;
|
|
if(this._rs_progress){
|
|
cells = this._getResizerCells();
|
|
dir = this._rs_progress[0];
|
|
/*vector distance between resizer and stick*/
|
|
diff = this._resizeStick._last_result -this._rs_progress[2];
|
|
/*new sizes for the resized cells, taking into account the stick position*/
|
|
sizes = this._rsGetDiffCellSizes(cells,dir,diff);
|
|
/*sum of cells dimensions*/
|
|
totalSize = cells[0]["$"+this._resizer_dim]+cells[1]["$"+this._resizer_dim];
|
|
/*max and min limits if they're set*/
|
|
limits = (dir=="y"?["minHeight","maxHeight"]:["minWidth","maxWidth"]);
|
|
for(i=0;i<2;i++){
|
|
config = cells[i]._settings;
|
|
cDiff = (i?-diff:diff);/*if cDiff is positive, the size of i cell is increased*/
|
|
/*if size is bigger than max limit or size is smaller than min limit*/
|
|
var min = config[limits[0]];
|
|
var max = config[limits[1]];
|
|
|
|
if(cDiff>0&&max&&max<=sizes[i] || cDiff<0&&(min||3)>=sizes[i]){
|
|
this._rsLimit[i] = (cDiff>0?max:(min||3));
|
|
/*new sizes, taking into account max and min limits*/
|
|
limitSizes = this._rsGetLimitCellSizes(cells,dir);
|
|
/*stick position*/
|
|
this._resizeStick._dragobj.style[(dir=="y"?"top":"left")] = this._rs_progress[3] + limitSizes[0]+"px";
|
|
return;
|
|
}else if(sizes[i]<3){/*cells size can not be less than 1*/
|
|
this._resizeStick._dragobj.style[(dir=="y"?"top":"left")] = this._rs_progress[3] + i*totalSize+1+"px";
|
|
}else{
|
|
this._rsLimit[i] = null;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
_getResizerCells:function(){
|
|
var cells,i;
|
|
cells = this.getParentView()._cells;
|
|
for(i=0; i< cells.length;i++){
|
|
if(cells[i]==this){
|
|
if (!cells[i-1] || cells[i-1]._settings.$noresize) return null;
|
|
if (!cells[i+1] || cells[i+1]._settings.$noresize) return null;
|
|
return [cells[i-1],cells[i+1]];
|
|
}
|
|
}
|
|
},
|
|
_rsEnd:function(result){
|
|
if (typeof result == "undefined") return;
|
|
|
|
var cells,dir,diff,i,size;
|
|
var vertical = this.getParentView()._vertical_orientation;
|
|
this._resizerStick = null;
|
|
if (this._rs_progress){
|
|
dir = this._rs_progress[0];
|
|
diff = result-this._rs_progress[2];
|
|
cells = this._getResizerCells();
|
|
if(cells[0]&&cells[1]){
|
|
/*new cell sizes*/
|
|
size = this._rsGetCellSizes(cells,dir,diff);
|
|
|
|
for (var i=0; i<2; i++){
|
|
//cell has not fixed size, of fully fixed layout
|
|
var cell_size = cells[i].$getSize(0,0);
|
|
if (vertical?(cell_size[2] == cell_size[3]):(Math.abs(cell_size[1]-cell_size[0])<3)){
|
|
/*set fixed sizes for both cells*/
|
|
cells[i]._settings[this._resizer_dim]=size[i];
|
|
if (cells[i]._bubble_size)
|
|
cells[i]._bubble_size(this._resizer_dim, size[i], vertical);
|
|
} else {
|
|
var actualSize = cells[i].$view[vertical?"offsetHeight":"offsetWidth"];//cells[i]["$"+this._resizer_dim];
|
|
cells[i]._settings.gravity = size[i]/actualSize*cells[i]._settings.gravity;
|
|
}
|
|
}
|
|
|
|
cells[0].resize();
|
|
|
|
for (var i = 0; i < 2; i++){
|
|
if (cells[i].callEvent)
|
|
cells[i].callEvent("onViewResize",[]);
|
|
cells[i].$view.removeAttribute("aria-dropeffect");
|
|
}
|
|
webix.callEvent("onLayoutResize", [cells]);
|
|
}
|
|
this._rs_progress = false;
|
|
}
|
|
this._rs_progress = false;
|
|
this._rs_started = false;
|
|
this._rsLimit = null;
|
|
webix.html.removeCss(document.body,"webix_noselect");
|
|
|
|
this._viewobj.setAttribute("aria-grabbed", "false");
|
|
this._viewobj.removeAttribute("aria-dropeffect");
|
|
},
|
|
_rsGetLimitCellSizes: function(cells){
|
|
var size1,size2,totalSize;
|
|
totalSize = cells[0]["$"+this._resizer_dim]+cells[1]["$"+this._resizer_dim];
|
|
if(this._rsLimit[0]){
|
|
size1 = this._rsLimit[0];
|
|
size2 = totalSize-size1;
|
|
}
|
|
else if(this._rsLimit[1]){
|
|
size2 = this._rsLimit[1];
|
|
size1 = totalSize-size2;
|
|
}
|
|
return [size1,size2];
|
|
},
|
|
_rsGetDiffCellSizes:function(cells,dir,diff){
|
|
var sizes =[];
|
|
var styleDim = this._resizer_dim=="height"?"offsetHeight":"offsetWidth";
|
|
for(var i=0;i<2;i++)
|
|
sizes[i] = cells[i].$view[styleDim]+(i?-1:1)*diff;
|
|
return sizes;
|
|
},
|
|
_rsGetCellSizes:function(cells,dir,diff){
|
|
var i,sizes,totalSize;
|
|
/*if max or min dimentsions are set*/
|
|
if(this._rsLimit[0]||this._rsLimit[1]){
|
|
sizes = this._rsGetLimitCellSizes(cells,dir);
|
|
}
|
|
else{
|
|
sizes = this._rsGetDiffCellSizes(cells,dir,diff);
|
|
for(i =0; i<2;i++ ){
|
|
/*if stick moving is stopped outsize cells borders*/
|
|
if(sizes[i]<0){
|
|
totalSize = sizes[0]+sizes[1];
|
|
sizes[i] =1;
|
|
sizes[1-i] = totalSize-1;
|
|
}
|
|
}
|
|
|
|
}
|
|
return sizes;
|
|
}
|
|
}, webix.MouseEvents, webix.Destruction, webix.ui.view);
|
|
|
|
|
|
|
|
webix.protoUI({
|
|
name:"multiview",
|
|
defaults:{
|
|
animate:{
|
|
}
|
|
},
|
|
setValue:function(val){
|
|
webix.$$(val).show();
|
|
},
|
|
getValue:function(){
|
|
return this.getActiveId();
|
|
},
|
|
$init:function(){
|
|
this._active_cell = 0;
|
|
this._vertical_orientation = 1;
|
|
this._viewobj.style.position = "relative";
|
|
this._viewobj.className += " webix_multiview";
|
|
this._back_queue = [];
|
|
},
|
|
_ask_render:function(cell_id, view_id){
|
|
var cell = webix.$$(cell_id);
|
|
if (!cell._render_hash){
|
|
cell._render_queue = [];
|
|
cell._render_hash = {};
|
|
}
|
|
if (!cell._render_hash[view_id]){
|
|
cell._render_hash[view_id]=true;
|
|
cell._render_queue.push(view_id);
|
|
}
|
|
},
|
|
_render_activation:function(cell_id){
|
|
var cell = webix.$$(cell_id);
|
|
if(this._settings.keepViews)
|
|
cell._viewobj.style.display = "";
|
|
/*back array*/
|
|
if(this._back_queue[this._back_queue.length-2]!=cell_id){
|
|
if(this._back_queue.length==10)
|
|
this._back_queue.splice(0,1);
|
|
this._back_queue.push(cell_id);
|
|
}
|
|
else
|
|
this._back_queue.splice(this._back_queue.length-1,1);
|
|
|
|
if (cell._render_hash){
|
|
for (var i=0; i < cell._render_queue.length; i++){
|
|
var subcell = webix.$$(cell._render_queue[i]);
|
|
//cell can be already destroyed
|
|
if (subcell)
|
|
subcell.render();
|
|
}
|
|
|
|
cell._render_queue = [];
|
|
cell._render_hash = {};
|
|
}
|
|
},
|
|
addView:function(){
|
|
var id = webix.ui.baselayout.prototype.addView.apply(this, arguments);
|
|
if(this._settings.keepViews)
|
|
webix.$$(id)._viewobj.style.display = "none";
|
|
else
|
|
webix.html.remove(webix.$$(id)._viewobj);
|
|
return id;
|
|
},
|
|
_beforeRemoveView:function(index, view){
|
|
//removing current view
|
|
if (index == this._active_cell){
|
|
var next = Math.max(index-1, 0);
|
|
if (this._cells[next]){
|
|
this._in_animation = false;
|
|
this._show(this._cells[next], false);
|
|
}
|
|
}
|
|
|
|
if (index < this._active_cell)
|
|
this._active_cell--;
|
|
},
|
|
//necessary, as we want to ignore hide calls for elements in multiview
|
|
_hide:function(){},
|
|
_parse_cells:function(collection){
|
|
collection = collection || this._collection;
|
|
|
|
for (var i=0; i < collection.length; i++)
|
|
collection[i]._inner = this._settings.borderless?{top:1, left:1, right:1, bottom:1}:(this._settings._inner||{});
|
|
|
|
webix.ui.baselayout.prototype._parse_cells.call(this, collection);
|
|
|
|
for (var i=1; i < this._cells.length; i++){
|
|
if(this._settings.keepViews)
|
|
this._cells[i]._viewobj.style.display = "none";
|
|
else
|
|
webix.html.remove(this._cells[i]._viewobj);
|
|
}
|
|
|
|
|
|
for (var i=0; i<collection.length; i++){
|
|
var cell = this._cells[i];
|
|
if (cell._cells && !cell._render_borders) continue;
|
|
|
|
var _inner = cell._settings._inner;
|
|
if (_inner.top)
|
|
cell._viewobj.style.borderTopWidth="0px";
|
|
if (_inner.left)
|
|
cell._viewobj.style.borderLeftWidth="0px";
|
|
if (_inner.right)
|
|
cell._viewobj.style.borderRightWidth="0px";
|
|
if (_inner.bottom)
|
|
cell._viewobj.style.borderBottomWidth="0px";
|
|
|
|
cell._viewobj.setAttribute("role", "tabpanel");
|
|
}
|
|
this._render_activation(this.getActiveId());
|
|
},
|
|
cells_setter:function(value){
|
|
webix.assert(value && value.length,"Multiview must have at least one view in 'cells'");
|
|
this._collection = value;
|
|
},
|
|
_getDirection:function(next, active){
|
|
var dir = (this._settings.animate || {}).direction;
|
|
var vx = (dir == "top" || dir == "bottom");
|
|
return next < active ? (vx?"bottom":"right"):(vx?"top":"left");
|
|
},
|
|
_show:function(obj, animation_options){
|
|
|
|
var parent = this.getParentView();
|
|
if (parent && parent.getTabbar)
|
|
parent.getTabbar().setValue(obj._settings.$id || obj._settings.id);
|
|
|
|
if (this._in_animation)
|
|
return webix.delay(this._show, this,[obj, animation_options],100);
|
|
|
|
var _next_cell = -1;
|
|
for (var i=0; i < this._cells.length; i++)
|
|
if (this._cells[i]==obj){
|
|
_next_cell = i;
|
|
break;
|
|
}
|
|
if (_next_cell < 0 || _next_cell == this._active_cell)
|
|
return;
|
|
|
|
|
|
var prev = this._cells[this._active_cell];
|
|
var next = this._cells[ _next_cell ];
|
|
var size = prev.$getSize(0,0);
|
|
|
|
//need to be moved in animate
|
|
if((animation_options||typeof animation_options=="undefined")&&webix.animate.isSupported() && this._settings.animate) {
|
|
var aniset = webix.extend({}, this._settings.animate);
|
|
if(this._settings.keepViews)
|
|
aniset.keepViews = true;
|
|
aniset.direction = this._getDirection(_next_cell,this._active_cell);
|
|
aniset = webix.Settings._mergeSettings(animation_options||{}, aniset);
|
|
|
|
var line = webix.animate.formLine(
|
|
next._viewobj,
|
|
prev._viewobj,
|
|
aniset);
|
|
next.$getSize(0,0);
|
|
next.$setSize(this._content_width,this._content_height);
|
|
|
|
var callback_original = aniset.callback;
|
|
aniset.callback = function(){
|
|
webix.animate.breakLine(line,this._settings.keepViews);
|
|
this._in_animation = false;
|
|
if (callback_original) callback_original.call(this);
|
|
callback_original = aniset.master = aniset.callback = null;
|
|
this.resize();
|
|
};
|
|
aniset.master = this;
|
|
|
|
this._active_cell = _next_cell;
|
|
this._render_activation(this.getActiveId());
|
|
|
|
webix.animate(line, aniset);
|
|
this._in_animation = true;
|
|
}
|
|
else { // browsers which don't support transform and transition
|
|
if(this._settings.keepViews){
|
|
prev._viewobj.style.display = "none";
|
|
}
|
|
else{
|
|
webix.html.remove(prev._viewobj);
|
|
this._viewobj.appendChild(this._cells[i]._viewobj);
|
|
}
|
|
|
|
this._active_cell = _next_cell;
|
|
|
|
prev.resize();
|
|
this._render_activation(this.getActiveId());
|
|
}
|
|
|
|
if (next.callEvent){
|
|
next.callEvent("onViewShow",[]);
|
|
webix.ui.each(next, this._signal_hidden_cells);
|
|
}
|
|
|
|
this.callEvent("onViewChange",[prev._settings.id, next._settings.id]);
|
|
|
|
},
|
|
$getSize:function(dx, dy){
|
|
if (!this._cells.length) return webix.ui.baseview.prototype.$getSize.call(this, 0, 0);
|
|
webix.debug_size_box_start(this, true);
|
|
var size = this._cells[this._active_cell].$getSize(0, 0);
|
|
if (this._settings.fitBiggest){
|
|
for (var i=0; i<this._cells.length; i++)
|
|
if (i != this._active_cell){
|
|
var other = this._cells[i].$getSize(0, 0);
|
|
for (var j = 0; j < 4; j++)
|
|
size[j] = Math.max(size[j], other[j]);
|
|
}
|
|
}
|
|
|
|
|
|
//get layout sizes
|
|
var self_size = webix.ui.baseview.prototype.$getSize.call(this, 0, 0);
|
|
//use child settings if layout's one was not defined
|
|
if (self_size[1] >= 100000) self_size[1]=0;
|
|
if (self_size[3] >= 100000) self_size[3]=0;
|
|
|
|
self_size[0] = (self_size[0] || size[0] ) +dx;
|
|
self_size[1] = (self_size[1] || size[1] ) +dx;
|
|
self_size[2] = (self_size[2] || size[2] ) +dy;
|
|
self_size[3] = (self_size[3] || size[3] ) +dy;
|
|
|
|
webix.debug_size_box_end(this, self_size);
|
|
|
|
return self_size;
|
|
},
|
|
$setSize:function(x,y){
|
|
if (!this._cells.length) return;
|
|
this._layout_sizes = [x,y];
|
|
webix.ui.baseview.prototype.$setSize.call(this,x,y);
|
|
this._cells[this._active_cell].$setSize(x,y);
|
|
},
|
|
isVisible:function(base_id, cell_id){
|
|
if (cell_id && cell_id != this.getActiveId()){
|
|
if (base_id)
|
|
this._ask_render(cell_id, base_id);
|
|
return false;
|
|
}
|
|
return webix.ui.view.prototype.isVisible.call(this, base_id, this._settings.id);
|
|
},
|
|
getActiveId:function(){
|
|
return this._cells.length?this._cells[this._active_cell]._settings.id:null;
|
|
},
|
|
back:function(step){
|
|
step=step||1;
|
|
if(this.callEvent("onBeforeBack",[this.getActiveId(), step])){
|
|
if(this._back_queue.length>step){
|
|
var viewId = this._back_queue[this._back_queue.length-step-1];
|
|
webix.$$(viewId).show();
|
|
return viewId;
|
|
}
|
|
return null;
|
|
}
|
|
return null;
|
|
|
|
}
|
|
},webix.ui.baselayout);
|
|
|
|
|
|
|
|
|
|
|
|
webix.protoUI({
|
|
name:"form",
|
|
defaults:{
|
|
type:"form",
|
|
autoheight:true
|
|
},
|
|
_default_height:-1,
|
|
_form_classname:"webix_form",
|
|
_form_vertical:true,
|
|
$init:function(){
|
|
this._viewobj.setAttribute("role", "form");
|
|
},
|
|
$getSize:function(dx, dy){
|
|
if (this._scroll_y && !this._settings.width) dx += webix.ui.scrollSize;
|
|
|
|
var sizes = webix.ui.layout.prototype.$getSize.call(this, dx, dy);
|
|
|
|
if (this._settings.scroll || !this._settings.autoheight){
|
|
sizes[2] = this._settings.height || this._settings.minHeight || 0;
|
|
sizes[3] += 100000;
|
|
}
|
|
|
|
return sizes;
|
|
}
|
|
}, webix.ui.toolbar);
|
|
|
|
|
|
(function(){
|
|
|
|
var controls = {};
|
|
for(var i in webix.UIManager._controls){
|
|
controls[webix.UIManager._controls[i]] = i;
|
|
}
|
|
var nav_controls = {
|
|
9:'tab',
|
|
38:'up',
|
|
40:'down',
|
|
37:'left',
|
|
39:'right'
|
|
};
|
|
|
|
webix.patterns = {
|
|
phone:{ mask:"+# (###) ###-####", allow:/[0-9]/g },
|
|
card: { mask:"#### #### #### ####", allow:/[0-9]/g },
|
|
date: { mask:"####-##-## ##:##", allow:/[0-9]/g }
|
|
};
|
|
|
|
webix.extend(webix.ui.text, {
|
|
$init:function(config){
|
|
if(config.pattern){
|
|
this.attachEvent("onKeyPress", function(code, e){
|
|
if(e.ctrlKey || e.altKey)
|
|
return;
|
|
|
|
if(code>105 && code<112) //numpad operators
|
|
code -=64;
|
|
|
|
if(controls[code] && code !== 8 && code !==46){ //del && bsp
|
|
if(!nav_controls[code])
|
|
webix.html.preventEvent(e);
|
|
return;
|
|
}
|
|
|
|
webix.html.preventEvent(e);
|
|
this._on_key_pressed(e, code);
|
|
});
|
|
|
|
this.attachEvent("onAfterRender", this._after_render);
|
|
this.getText = function(){ return this.getInputNode().value; };
|
|
this._pattern = function(value, mode){
|
|
if (mode === false)
|
|
return this._getRawValue(value);
|
|
else
|
|
return this._matchPattern(value);
|
|
};
|
|
config.invalidMessage = config.invalidMessage || webix.i18n.controls.invalidMessage;
|
|
}
|
|
},
|
|
pattern_setter:function(value){
|
|
var pattern = webix.patterns[value] || value;
|
|
|
|
if(typeof pattern =="string") pattern = { mask: pattern };
|
|
pattern.allow = pattern.allow || /[A-Za-z0-9]/g;
|
|
|
|
this._patternScheme(pattern);
|
|
return pattern;
|
|
},
|
|
_init_validation:function(){
|
|
this.config.validate = this.config.validate || webix.bind(function(){
|
|
var value = this.getText();
|
|
var raw = value.replace(this._pattern_chars, "");
|
|
var matches = (value.toString().match(this._pattern_allows) || []).join("");
|
|
return (matches.length == raw.length && value.length == this._settings.pattern.mask.length);
|
|
}, this);
|
|
},
|
|
_after_render:function(){
|
|
var ev = webix.env.isIE8?"propertychange":"input";
|
|
|
|
webix._event(this.getInputNode(), ev, function(e){
|
|
var stamp = (new Date()).valueOf();
|
|
var width = this.$view.offsetWidth; //dark ie8 magic
|
|
if(!this._property_stamp || stamp-this._property_stamp>100){
|
|
this._property_stamp = stamp;
|
|
this.$setValue(this.getText());
|
|
}
|
|
}, {bind:this});
|
|
|
|
webix._event(this.getInputNode(), "blur", function(e){
|
|
this._applyChanges();
|
|
}, {bind:this});
|
|
},
|
|
_patternScheme:function(pattern){
|
|
var mask = pattern.mask, scheme = {}, chars = "", count = 0;
|
|
|
|
for(var i = 0; i<mask.length; i++){
|
|
if(mask[i] === "#"){
|
|
scheme[i] = count; count++;
|
|
}
|
|
else{
|
|
scheme[i] = false;
|
|
if(chars.indexOf(mask[i]) === -1) chars+="\\"+mask[i];
|
|
}
|
|
}
|
|
this._pattern_allows = pattern.allow;
|
|
this._pattern_chars = new RegExp("["+chars+"]", "g");
|
|
this._pattern_scheme = scheme;
|
|
|
|
this._init_validation();
|
|
},
|
|
_on_key_pressed:function(e, code){
|
|
var node = this.getInputNode();
|
|
var value = node.value;
|
|
var pos = webix.html.getSelectionRange(node);
|
|
var chr = "";
|
|
|
|
if(code == 8 || code == 46){
|
|
if(pos.start == pos.end){
|
|
if(code == 8) pos.start--;
|
|
else pos.end++;
|
|
}
|
|
}
|
|
else{
|
|
chr = String.fromCharCode(code);
|
|
if(!e.shiftKey) chr = chr.toLowerCase();
|
|
}
|
|
|
|
value = value.substr(0, pos.start) + chr +value.substr(pos.end);
|
|
pos = this._getCaretPos(chr, value.length, pos.start, code);
|
|
|
|
this._input_code = code;
|
|
this.$setValue(value);
|
|
|
|
webix.html.setSelectionRange(node, pos);
|
|
},
|
|
_getCaretPos:function(chr, len, pos, code){
|
|
if((chr && chr.match(this._pattern_allows)) || (code ==8 || code ==46)){
|
|
pos = chr ? pos+1 : pos;
|
|
pos = this._fixCaretPos(pos, code);
|
|
}
|
|
else if(len-1 == pos && code !==8 && code !==46){
|
|
var rest = this._settings.pattern.mask.indexOf("#", pos);
|
|
if(rest>0) pos += rest;
|
|
}
|
|
return pos;
|
|
},
|
|
_fixCaretPos:function(pos, code){
|
|
var prev = pos-(code !== 46)*1;
|
|
|
|
if(this._pattern_scheme[prev] === false){
|
|
pos = pos+(code ==8 ? -1: 1);
|
|
return this._fixCaretPos(pos, code);
|
|
}
|
|
if(this._pattern_scheme[pos] === false && code !==8)
|
|
return this._fixCaretPos(pos+1, code)-1;
|
|
return pos;
|
|
},
|
|
_getRawValue:function(value){
|
|
value = value || "";
|
|
var matches = value.toString().match(this._pattern_allows) || [];
|
|
return matches.join("").replace(this._pattern_chars, "");
|
|
},
|
|
_matchPattern:function(value){
|
|
var raw = this._getRawValue(value),
|
|
pattern = this._settings.pattern.mask,
|
|
mask = this._settings.pattern.mask,
|
|
scheme = this._pattern_scheme,
|
|
end = false,
|
|
index = 0,
|
|
rawIndex = 0,
|
|
rawLength = 0;
|
|
|
|
for(var i in scheme){
|
|
if(scheme[i]!==false){
|
|
if(!end){
|
|
index = i*1;
|
|
rawIndex = scheme[i];
|
|
var rchar = raw[rawIndex]||"";
|
|
var next = raw[rawIndex+1];
|
|
|
|
pattern = (rchar?pattern.substr(0, index):"") + rchar +(rchar && next?pattern.substr(index + 1):"");
|
|
if(!next) end = true;
|
|
}
|
|
rawLength++;
|
|
}
|
|
}
|
|
|
|
//finalize value with subsequent mask chars
|
|
var icode = this._input_code;
|
|
if((icode && icode !== 8) || (!icode && rawLength-1 === rawIndex && pattern.length < mask.length)){
|
|
if(raw){
|
|
var nind = index+1;
|
|
if(mask.charAt(nind)!=="#" && pattern.length < mask.length){
|
|
var lind = mask.indexOf("#", nind);
|
|
if(lind<0) lind = mask.length;
|
|
pattern += mask.substr(nind, lind-nind);
|
|
}
|
|
}
|
|
else if(icode !==46){
|
|
pattern += mask.substr(0, mask.indexOf("#"));
|
|
}
|
|
}
|
|
this._input_code = null;
|
|
return pattern;
|
|
}
|
|
});
|
|
|
|
})();
|
|
webix.protoUI({
|
|
name:"gridsuggest",
|
|
defaults:{
|
|
type:"datatable",
|
|
fitMaster:false,
|
|
width:0,
|
|
body:{
|
|
navigation:true,
|
|
autoheight:true,
|
|
autowidth:true,
|
|
select:true
|
|
},
|
|
filter:function(item, value){
|
|
var text = this.config.template(item);
|
|
if (text.toString().toLowerCase().indexOf(value.toLowerCase())===0) return true;
|
|
return false;
|
|
}
|
|
},
|
|
$init:function(obj){
|
|
if (!obj.body.columns)
|
|
obj.body.autoConfig = true;
|
|
if (!obj.template)
|
|
obj.template = webix.bind(this._getText, this);
|
|
},
|
|
_getText:function(item, common){
|
|
var grid = this.getBody();
|
|
var value = this.config.textValue || grid.config.columns[0].id;
|
|
return grid.getText(item.id, value);
|
|
}
|
|
}, webix.ui.suggest);
|
|
webix.protoUI({
|
|
name:"datasuggest",
|
|
defaults:{
|
|
type:"dataview",
|
|
fitMaster:false,
|
|
width:0,
|
|
body:{
|
|
xCount:3,
|
|
autoheight:true,
|
|
select:true
|
|
}
|
|
}
|
|
}, webix.ui.suggest);
|
|
|
|
|
|
webix.protoUI({
|
|
name:"multiselect",
|
|
$cssName:"richselect",
|
|
defaults:{
|
|
separator:","
|
|
},
|
|
_suggest_config:function(value){
|
|
var isobj = !webix.isArray(value) && typeof value == "object" && !value.name;
|
|
var suggest = { view:"checksuggest", separator:this.config.separator, buttonText: this.config.buttonText, button: this.config.button };
|
|
|
|
if (this._settings.optionWidth)
|
|
suggest.width = this._settings.optionWidth;
|
|
else
|
|
suggest.fitMaster = true;
|
|
|
|
if (isobj)
|
|
webix.extend(suggest, value, true);
|
|
|
|
var view = webix.ui(suggest);
|
|
var list = view.getList();
|
|
if (typeof value == "string")
|
|
list.load(value);
|
|
else if (!isobj)
|
|
list.parse(value);
|
|
|
|
view.attachEvent("onShow",function(node,mode, point){
|
|
view.setValue(webix.$$(view._settings.master).config.value);
|
|
});
|
|
|
|
return view;
|
|
},
|
|
|
|
$setValue:function(value){
|
|
if (!this._rendered_input) return;
|
|
var popup = this.getPopup();
|
|
var text = "";
|
|
if(popup){
|
|
text = popup.setValue(value);
|
|
if(typeof text == "object"){
|
|
text = text.join(this.config.separator+" ");
|
|
}
|
|
|
|
}
|
|
this._settings.text = text;
|
|
|
|
var node = this.getInputNode();
|
|
node.innerHTML = text || this._get_div_placeholder();
|
|
},
|
|
getValue:function(){
|
|
return this._settings.value||"";
|
|
},
|
|
}, webix.ui.richselect);
|
|
|
|
webix.editors.multiselect = webix.extend({
|
|
popupType:"multiselect"
|
|
}, webix.editors.richselect);
|
|
|
|
webix.type(webix.ui.list, {
|
|
name:"multilist",
|
|
templateStart:webix.template('<div webix_l_id="#!id#" class="{common.classname()}" style="width:{common.widthSize()}; height:{common.heightSize()}; overflow:hidden;" {common.aria()}>')
|
|
}, "default");
|
|
|
|
webix.type(webix.ui.list, {
|
|
name:"checklist",
|
|
templateStart:webix.template('<div webix_l_id="#!id#" {common.aria()} class="{common.classname()}" style="width:{common.widthSize()}; height:{common.heightSize()}; overflow:hidden; white-space:nowrap;">{common.checkbox()}'),
|
|
checkbox: function(obj, common){
|
|
var icon = obj.$checked?"fa-check-square":"fa-square-o";
|
|
return "<span role='checkbox' tabindex='-1' aria-checked='"+(obj.$checked?"true":"false")+"' class='webix_icon "+icon+"'></span>";
|
|
},
|
|
aria:function(obj){
|
|
return "role='option' tabindex='-1' "+(obj.$checked?"aria-selected='true'":"");
|
|
},
|
|
template: webix.template("#value#")
|
|
}, "default");
|
|
|
|
|
|
webix.protoUI({
|
|
name:"multisuggest",
|
|
defaults:{
|
|
separator:",",
|
|
type:"layout",
|
|
button:true,
|
|
width:0,
|
|
filter:function(item,value){
|
|
var itemText = this.getItemText(item.id);
|
|
return (itemText.toString().toLowerCase().indexOf(value.toLowerCase())>-1);
|
|
},
|
|
body:{
|
|
rows:[
|
|
{ view:"list", type:"multilist", borderless:true, autoheight:true, yCount:5, multiselect:"touch", select:true,
|
|
on:{
|
|
onItemClick: function(id){
|
|
var popup = this.getParentView().getParentView();
|
|
webix.delay(function(){
|
|
popup._toggleOption(id);
|
|
});
|
|
}
|
|
}},
|
|
{ view:"button", click:function(){
|
|
var suggest = this.getParentView().getParentView();
|
|
suggest.setMasterValue({ id:suggest.getValue() });
|
|
suggest.hide();
|
|
}}
|
|
]
|
|
}
|
|
},
|
|
|
|
_toggleOption: function(id, ev, all){
|
|
var value = this.getValue();
|
|
var values = all || webix.toArray(value?this.getValue().split(this._settings.separator):[]);
|
|
var master = webix.$$(this._settings.master);
|
|
|
|
if(!all) {
|
|
if(values.find(id)<0)
|
|
values.push(id);
|
|
else
|
|
values.remove(id);
|
|
}
|
|
|
|
if(master){
|
|
master.setValue(values.join(this._settings.separator));
|
|
} else {
|
|
var text = this.setValue(values);
|
|
|
|
if (this._last_input_target)
|
|
this._last_input_target.value = text.join(this._settings.separator);
|
|
}
|
|
|
|
if(ev){ //only for clicks in checksuggest
|
|
var checkbox = this.getList().getItemNode(id).getElementsByTagName("SPAN");
|
|
if(checkbox && checkbox.length) checkbox[0].focus();
|
|
}
|
|
},
|
|
_get_extendable_cell:function(obj){
|
|
return obj.rows[0];
|
|
},
|
|
_set_on_popup_click:function(){
|
|
var button = this.getButton();
|
|
var text = (this._settings.button?(this._settings.buttonText || webix.i18n.controls.select):0);
|
|
if(button){
|
|
if(text){
|
|
button._settings.value = text;
|
|
button.refresh();
|
|
}
|
|
else
|
|
button.hide();
|
|
}
|
|
if (this._settings.selectAll)
|
|
return this.getBody().getChildViews()[0].show();
|
|
|
|
var list = this.getList();
|
|
//for standalone suggests we need to have a normal show/hide logic
|
|
//use a wrapper function, so it can later be cleared in multicombo
|
|
list.data.attachEvent("onAfterFilter", webix.bind(function(){
|
|
return this._suggest_after_filter();
|
|
}, this));
|
|
},
|
|
getButton:function(){
|
|
return this.getBody().getChildViews()[1];
|
|
},
|
|
getList:function(){
|
|
return this.getBody().getChildViews()[0];
|
|
},
|
|
setValue:function(value){
|
|
var text = [];
|
|
var list = this.getList();
|
|
list.unselect();
|
|
|
|
if (value){
|
|
if (!webix.isArray(value))
|
|
value = value.toString().split(this.config.separator);
|
|
|
|
if (value[0]){
|
|
for (var i = 0; i < value.length; i++){
|
|
if (list.getItem(value[i])){
|
|
if(list.exists(value[i]))
|
|
list.select(value[i], true);
|
|
text.push(this.getItemText(value[i]));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
this._settings.value = value?value.join(this.config.separator):"";
|
|
return text;
|
|
},
|
|
getValue:function(){
|
|
return this._settings.value;
|
|
}
|
|
}, webix.ui.suggest);
|
|
|
|
webix.protoUI({
|
|
name:"checksuggest",
|
|
defaults:{
|
|
button:false,
|
|
selectAll: false,
|
|
body:{
|
|
rows:[
|
|
{ view:"checkbox", hidden:true, customCheckbox:false, borderless: false, css:"webix_checksuggest_select_all",
|
|
labelRight:webix.i18n.combo.selectAll, labelWidth:0, value: 0,
|
|
height:28, inputHeight:17,
|
|
on:{
|
|
onItemClick: function(e){
|
|
var popup = this.getParentView().getParentView();
|
|
var check = popup.getList();
|
|
var values = check.data.order;
|
|
for(var i = 0; i < values.length; i++) {
|
|
var value = check.getItem(values[i]);
|
|
value.$checked = this.getValue();
|
|
}
|
|
var result = this.getValue() ? [].concat(values) : [];
|
|
popup._toggleOption(values[0], e, result);
|
|
check.refresh();
|
|
},
|
|
onChange: function() {
|
|
var link = this.$view.querySelector("label");
|
|
if(this.getValue())
|
|
link.textContent = webix.i18n.combo.unselectAll;
|
|
else
|
|
link.textContent = webix.i18n.combo.selectAll;
|
|
}
|
|
}},
|
|
{ view:"list", css:"webix_multilist", borderless:true, autoheight:true, yCount:5, select: true,
|
|
type:"checklist",
|
|
on:{
|
|
onItemClick: function(id, e){
|
|
var item = this.getItem(id);
|
|
item.$checked = item.$checked?0:1;
|
|
this.refresh(id);
|
|
var popup = this.getParentView().getParentView();
|
|
popup._click_stamp = new Date();
|
|
|
|
popup._toggleOption(id, e);
|
|
|
|
if(popup.config.selectAll) {
|
|
if (!item.$checked)
|
|
popup.getBody()._cells[0].setValue(0);
|
|
else {
|
|
var all = (popup.getValue() || "").split(",");
|
|
if (all.length === this.count())
|
|
popup.getBody()._cells[0].setValue(1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{ view:"button", click:function(){
|
|
var suggest = this.getParentView().getParentView();
|
|
suggest.setMasterValue({ id:suggest.getValue() });
|
|
suggest.hide();
|
|
}},
|
|
|
|
]
|
|
}
|
|
},
|
|
_get_extendable_cell:function(obj){
|
|
return obj.rows[1];
|
|
},
|
|
getButton:function(){
|
|
return this.getBody().getChildViews()[2];
|
|
},
|
|
getList:function(){
|
|
return this.getBody().getChildViews()[1];
|
|
},
|
|
$init: function(){
|
|
this._valueHistory = {};
|
|
this.$ready.push(this._onReady);
|
|
},
|
|
_onReady: function(){
|
|
var list = this.getList();
|
|
if(list.config.dataFeed){
|
|
var suggest = this;
|
|
list.attachEvent("onAfterLoad", function(){
|
|
suggest.setValue(suggest._settings.value);
|
|
});
|
|
list.getItem = function(id){
|
|
return this.data.pull[id] || suggest._valueHistory[id];
|
|
};
|
|
}
|
|
|
|
if(this.config.master && !this.config.selectAll)
|
|
this.getBody().getChildViews()[0].hide();
|
|
},
|
|
$enterKey: function(popup,list) {
|
|
if (list.count && list.count()){
|
|
if (popup.isVisible()) {
|
|
var value = list.getSelectedId(false, true);
|
|
if(value){
|
|
this._toggleOption(value);
|
|
}
|
|
popup.hide(true);
|
|
} else {
|
|
popup.show(this._last_input_target);
|
|
}
|
|
} else {
|
|
if (popup.isVisible())
|
|
popup.hide(true);
|
|
}
|
|
},
|
|
_show_selection: function(){
|
|
var list = this.getList();
|
|
if( list.select)
|
|
list.unselect();
|
|
},
|
|
setValue:function(value){
|
|
var i,
|
|
list = this.getList(),
|
|
text = [],
|
|
values = {},
|
|
changed = [];
|
|
|
|
value = value || [];
|
|
|
|
if (!webix.isArray(value))
|
|
value = value.toString().split(this.config.separator);
|
|
else if(list.config.dataFeed)
|
|
value = this._toMultiValue(value);
|
|
|
|
for ( i = 0; i < value.length; i++){
|
|
values[value[i]] = 1;
|
|
if(list.getItem(value[i])){
|
|
if( this._valueHistory)
|
|
this._valueHistory[value[i]] = webix.copy(list.getItem(value[i]));
|
|
text.push(this.getItemText(value[i]));
|
|
}
|
|
}
|
|
|
|
list.data.each(function(item){
|
|
if(item.$checked){
|
|
if(!values[item.id]){
|
|
item.$checked = 0;
|
|
changed.push(item.id);
|
|
}
|
|
}
|
|
else{
|
|
if(values[item.id]){
|
|
item.$checked = 1;
|
|
changed.push(item.id);
|
|
}
|
|
}
|
|
|
|
},this,true);
|
|
|
|
|
|
for( i=0; i < changed.length; i++ ){
|
|
list.refresh(changed[i]);
|
|
}
|
|
this._settings.value = value.length?value.join(this.config.separator):"";
|
|
return text;
|
|
},
|
|
getValue:function(){
|
|
return this._settings.value;
|
|
},
|
|
_preselectMasterOption: function(){
|
|
var node, master;
|
|
if (this._settings.master){
|
|
master = webix.$$(this._settings.master);
|
|
node = master.getInputNode();
|
|
}
|
|
node = node || this._last_input_target;
|
|
if(node)
|
|
node.focus();
|
|
},
|
|
_toMultiValue: function(value){
|
|
if(value && webix.isArray(value)){
|
|
var values = [];
|
|
for(var i =0; i < value.length; i++){
|
|
if(value[i].id){
|
|
this._valueHistory[value[i].id] = webix.copy(value[i]);
|
|
values.push(value[i].id);
|
|
}
|
|
else{
|
|
values.push(value[i]);
|
|
}
|
|
}
|
|
value = values;
|
|
}
|
|
return value;
|
|
}
|
|
}, webix.ui.multisuggest);
|
|
|
|
webix.protoUI({
|
|
name:"multicombo",
|
|
$cssName:"text",
|
|
defaults:{
|
|
keepText: false,
|
|
separator:",",
|
|
icon: false,
|
|
iconWidth: 0,
|
|
tagMode: true,
|
|
tagTemplate: function(values){
|
|
return (values.length?values.length+" item(s)":"");
|
|
},
|
|
template:function(obj,common){
|
|
return common._render_value_block(obj, common);
|
|
}
|
|
},
|
|
$init:function(){
|
|
this.$view.className += " webix_multicombo";
|
|
|
|
this.attachEvent("onBeforeRender",function(){
|
|
if(!this._inputHeight)
|
|
this._inputHeight = webix.skin.$active.inputHeight;
|
|
return true;
|
|
});
|
|
this.attachEvent("onAfterRender", function(){
|
|
this._last_size = null;
|
|
});
|
|
|
|
this._renderCount = 0;
|
|
},
|
|
|
|
on_click: {
|
|
"webix_multicombo_delete": function(e,view,node){
|
|
var value;
|
|
if(!this._settings.readonly && node && (value = node.parentNode.getAttribute("optvalue")))
|
|
this._removeValue(value);
|
|
return false;
|
|
}
|
|
},
|
|
_onBlur:function(){
|
|
var value = this.getInputNode().value;
|
|
//blurring caused by clicks in the suggest list cannot affect new values
|
|
if(value && this._settings.newValues && new Date()-(this.getPopup()._click_stamp ||0)>100){
|
|
this._addNewValue(value);
|
|
}
|
|
|
|
if (!this._settings.keepText)
|
|
this._inputValue = "";
|
|
this.refresh();
|
|
},
|
|
_removeValue: function(value){
|
|
var values = this._settings.value;
|
|
var suggest = webix.$$(this.config.suggest);
|
|
if(typeof values == "string")
|
|
values = values.split(this._settings.separator);
|
|
values = webix.toArray(webix.copy(values));
|
|
values.remove(value);
|
|
|
|
this.setValue(values.join(this._settings.separator));
|
|
if(suggest && suggest._settings.selectAll) {
|
|
suggest.getBody()._cells[0].setValue(0);
|
|
}
|
|
},
|
|
_addValue: function(newValue){
|
|
var suggest = webix.$$(this.config.suggest);
|
|
var list = suggest.getList();
|
|
var item = list.getItem(newValue);
|
|
|
|
if(item){
|
|
var values = suggest.getValue();
|
|
if(values && typeof values == "string")
|
|
values = values.split(suggest.config.separator);
|
|
values = webix.toArray(values||[]);
|
|
if(values.find(newValue)<0){
|
|
values.push(newValue);
|
|
suggest.setValue(values);
|
|
this.setValue(suggest.getValue());
|
|
}
|
|
}
|
|
},
|
|
_addNewValue: function(value){
|
|
var suggest = webix.$$(this.config.suggest);
|
|
var list = suggest.getList();
|
|
var id;
|
|
value = value.replace(/^\s+|\s+$/g,'');
|
|
|
|
if(value){
|
|
for(var i in list.data.pull)
|
|
if(suggest.getItemText(i) == value) id = i;
|
|
}
|
|
|
|
if(!id && value) id = list.add({id: value, value: value});
|
|
|
|
this._addValue(id);
|
|
},
|
|
_suggest_config:function(value){
|
|
var isObj = !webix.isArray(value) && typeof value == "object" && !value.name,
|
|
suggest = { view:"checksuggest", separator:this.config.separator, buttonText: this.config.buttonText, button: this.config.button },
|
|
combo = this;
|
|
|
|
if (this._settings.optionWidth)
|
|
suggest.width = this._settings.optionWidth;
|
|
|
|
if (isObj)
|
|
webix.extend(suggest, value, true);
|
|
|
|
var view = webix.ui(suggest);
|
|
if(!this._settings.optionWidth)
|
|
view.$customWidth = function(node){
|
|
this.config.width = combo._get_input_width(combo._settings);
|
|
};
|
|
view.attachEvent("onBeforeShow",function(node,mode, point){
|
|
if(this._settings.master){
|
|
this.setValue(webix.$$(this._settings.master).config.value);
|
|
|
|
if(webix.$$(this._settings.master).getInputNode().value){
|
|
this.getList().refresh();
|
|
this._dont_unfilter = true;
|
|
}
|
|
else
|
|
this.getList().filter();
|
|
|
|
if(node.tagName && node.tagName.toLowerCase() == "input"){
|
|
webix.ui.popup.prototype.show.apply(this, [node.parentNode,mode, point]);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
});
|
|
|
|
var list = view.getList();
|
|
if (typeof value == "string")
|
|
list.load(value);
|
|
else if (!isObj)
|
|
list.parse(value);
|
|
|
|
//prevent default show-hide logicfunction(){
|
|
view._suggest_after_filter = function(){};
|
|
|
|
return view;
|
|
},
|
|
_render_value_block:function(obj, common){
|
|
var id, input, inputAlign,inputStyle, inputValue, inputWidth,
|
|
height, html, label, list, message, padding, readOnly, width,
|
|
bottomLabel = "",
|
|
top = this._settings.labelPosition == "top";
|
|
|
|
id = "x"+webix.uid();
|
|
width = common._get_input_width(obj);
|
|
inputAlign = obj.inputAlign || "left";
|
|
|
|
height = this._inputHeight - 2*webix.skin.$active.inputPadding -2;
|
|
|
|
inputValue = (this._inputValue||"");
|
|
list = "<ul class='webix_multicombo_listbox' style='line-height:"+height+"px'></ul>";
|
|
|
|
inputWidth = Math.min(width,(common._inputWidth||7));
|
|
|
|
inputStyle = "width: "+inputWidth+"px;height:"+height+"px;max-width:"+(width-20)+"px";
|
|
|
|
readOnly = obj.readonly?" readonly ":"";
|
|
input = "<input id='"+id+"' role='combobox' aria-multiline='true' aria-label='"+webix.template.escape(obj.label)+"' tabindex='0' type='text' class='webix_multicombo_input' "+readOnly+" style='"+inputStyle+"' value='"+inputValue+"'/>";
|
|
html = "<div class='webix_inp_static' onclick='' style='line-height:"+height+"px;width: " + width + "px; text-align: " + inputAlign + ";height:auto' >"+list+input +"</div>";
|
|
|
|
label = common.$renderLabel(obj,id);
|
|
|
|
padding = this._settings.awidth - width - webix.skin.$active.inputPadding*2;
|
|
message = (obj.invalid ? obj.invalidMessage : "") || obj.bottomLabel;
|
|
if (message)
|
|
bottomLabel = "<div class='webix_inp_bottom_label' style='width:"+width+"px;margin-left:"+Math.max(padding,webix.skin.$active.inputPadding)+"px;'>"+message+"</div>";
|
|
|
|
if (top)
|
|
return label+"<div class='webix_el_box' style='width:"+this._settings.awidth+"px; '>"+html+bottomLabel+"</div>";
|
|
else
|
|
return "<div class='webix_el_box' style='width:"+this._settings.awidth+"px; min-height:"+this._settings.aheight+"px;'>"+label+html+bottomLabel+"</div>";
|
|
},
|
|
_getValueListBox: function(){
|
|
return this._getBox().getElementsByTagName("UL")[0];
|
|
},
|
|
|
|
_set_inner_size: function(){
|
|
var popup = this.getPopup();
|
|
if(popup){
|
|
|
|
var textArr = (popup ? popup.setValue(this._settings.value) : null);
|
|
if(popup._toMultiValue)
|
|
this._settings.value = popup._toMultiValue(this._settings.value);
|
|
var html = "";
|
|
var listbox = this._getValueListBox();
|
|
var text = textArr && textArr.length;
|
|
if(text){
|
|
var height = this._inputHeight - 2*webix.skin.$active.inputPadding - 8;
|
|
var values = this._settings.value;
|
|
if(typeof values == "string")
|
|
values = values.split(this._settings.separator);
|
|
|
|
if(this._settings.tagMode){
|
|
for(var i=0; i < textArr.length;i++){
|
|
var content = "<span>"+textArr[i]+"</span><span class='webix_multicombo_delete' role='button' aria-label='"+webix.i18n.aria.removeItem+"'>x</span>";
|
|
html += "<li class='webix_multicombo_value' style='line-height:"+height+"px;' optvalue='"+ values[i]+"'>"+content+"</li>";
|
|
}
|
|
}
|
|
else{
|
|
html += "<li class='webix_multicombo_tag' style='line-height:"+height+"px;'>"+this._settings.tagTemplate(values)+"</li>";
|
|
}
|
|
|
|
}
|
|
listbox.innerHTML = html;
|
|
// reset placeholder
|
|
var inp = this.getInputNode();
|
|
if(this._settings.placeholder){
|
|
if(text){
|
|
inp.placeholder = "";
|
|
if(!inp.value && inp.offsetWidth > 20)
|
|
inp.style.width = "20px";
|
|
}
|
|
else if(!inp.value){
|
|
inp.placeholder = this._settings.placeholder;
|
|
inp.style.width = this._get_input_width(this._settings)+"px";
|
|
}
|
|
}
|
|
|
|
if(!this._settings.tagMode && listbox.firstChild)
|
|
inp.style.width = this._getMultiComboInputWidth() +"px";
|
|
}
|
|
this._resizeToContent();
|
|
},
|
|
_focusAtEnd: function(inputEl){
|
|
inputEl = inputEl||this.getInputNode();
|
|
if (inputEl){
|
|
if(inputEl.value.length){
|
|
if (inputEl.createTextRange){
|
|
var FieldRange = inputEl.createTextRange();
|
|
FieldRange.moveStart('character',inputEl.value.length);
|
|
FieldRange.collapse();
|
|
FieldRange.select();
|
|
}else if (inputEl.selectionStart || inputEl.selectionStart == '0') {
|
|
var elemLen = inputEl.value.length;
|
|
inputEl.selectionStart = elemLen;
|
|
inputEl.selectionEnd = elemLen;
|
|
inputEl.focus();
|
|
}
|
|
}else{
|
|
inputEl.focus();
|
|
}
|
|
}
|
|
},
|
|
_resizeToContent: function(){
|
|
var top = this._settings.labelPosition == "top";
|
|
var inputDiv = this._getInputDiv();
|
|
var inputHeight = Math.max(inputDiv.offsetHeight+ 2*webix.skin.$active.inputPadding, this._inputHeight);
|
|
|
|
if(top)
|
|
inputHeight += this._labelTopHeight;
|
|
|
|
inputHeight += this._settings.bottomPadding ||0;
|
|
|
|
var sizes = this.$getSize(0,0);
|
|
|
|
if(inputHeight != sizes[2]){
|
|
var cHeight = inputDiv.offsetHeight + (top?this._labelTopHeight:0);
|
|
|
|
// workaround for potential rendering loop
|
|
if(cHeight == this._calcHeight)
|
|
this._renderCount++;
|
|
else
|
|
this._renderCount = 0;
|
|
|
|
if(this._renderCount > 10)
|
|
return false;
|
|
|
|
this._calcHeight = cHeight;
|
|
|
|
var topView =this.getTopParentView();
|
|
clearTimeout(topView._template_resize_timer);
|
|
topView._template_resize_timer = webix.delay(function(){
|
|
this.config.height = this._calcHeight + 2*webix.skin.$active.inputPadding;
|
|
this.resize();
|
|
|
|
if(this._typing){
|
|
this._focusAtEnd(this.getInputNode());
|
|
this._typing = false;
|
|
}
|
|
if(this._enter){
|
|
if(!this._settings.keepText)
|
|
this.getInputNode().value = "";
|
|
else
|
|
this.getInputNode().select();
|
|
this._enter = false;
|
|
}
|
|
if(this.getPopup().isVisible()||this._typing){
|
|
this.getPopup().show(this._getInputDiv());
|
|
}
|
|
|
|
}, this);
|
|
}
|
|
if(this._enter){
|
|
this.getInputNode().select();
|
|
}
|
|
},
|
|
_getInputDiv: function(){
|
|
var parentNode = this._getBox();
|
|
var nodes = parentNode.childNodes;
|
|
for(var i=0; i < nodes.length; i++){
|
|
if(nodes[i].className && nodes[i].className.indexOf("webix_inp_static")!=-1)
|
|
return nodes[i];
|
|
}
|
|
return parentNode;
|
|
},
|
|
getInputNode: function(){
|
|
return this._getBox().getElementsByTagName("INPUT")[0];
|
|
},
|
|
$setValue:function(){
|
|
if (this._rendered_input)
|
|
this._set_inner_size();
|
|
},
|
|
getValue:function(config){
|
|
if(typeof config == "object" && config.options)
|
|
return this._getSelectedOptions();
|
|
|
|
var value = this._settings.value;
|
|
if (!value) return "";
|
|
return (typeof value != "string"?value.join(this._settings.separator):value);
|
|
},
|
|
getText:function(){
|
|
var value = this._settings.value;
|
|
if(!value) return "";
|
|
|
|
if(typeof value == "string")
|
|
value = value.split(this._settings.separator);
|
|
|
|
var text = [];
|
|
for(var i = 0; i<value.length; i++)
|
|
text.push(this.getPopup().getItemText(value[i]));
|
|
return text.join(this._settings.separator);
|
|
},
|
|
_getSelectedOptions: function(){
|
|
var i, item, popup,
|
|
options = [],
|
|
value = this._settings.value;
|
|
|
|
if (!value) return [];
|
|
|
|
if(typeof value == "string")
|
|
value = value.split(this._settings.separator);
|
|
|
|
popup = this.getPopup();
|
|
|
|
for(i = 0; i < value.length; i++){
|
|
item = popup.getList().getItem(value[i]) || (popup._valueHistory?popup._valueHistory[value[i]]:null);
|
|
if(item)
|
|
options.push(item);
|
|
}
|
|
|
|
return options;
|
|
},
|
|
$setSize:function(x,y){
|
|
var config = this._settings;
|
|
if(webix.ui.view.prototype.$setSize.call(this,x,y)){
|
|
if (!x || !y) return;
|
|
if (config.labelPosition == "top"){
|
|
config.labelWidth = 0;
|
|
}
|
|
this.render();
|
|
}
|
|
},
|
|
_calcInputWidth: function(value){
|
|
var tmp = document.createElement("span");
|
|
tmp.className = "webix_multicombo_input";
|
|
tmp.style.visibility = "visible";
|
|
tmp.style.height = "0px";
|
|
tmp.innerHTML = value.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
|
document.body.appendChild(tmp);
|
|
var width = tmp.offsetWidth+10;
|
|
document.body.removeChild(tmp);
|
|
return width;
|
|
},
|
|
_getMultiComboInputWidth: function(){
|
|
var listbox = this._getValueListBox();
|
|
return listbox.offsetWidth - listbox.firstChild.offsetWidth - 17;
|
|
},
|
|
_init_onchange:function(){
|
|
// input focus and focus styling
|
|
webix._event(this._getBox(),"click",function(){
|
|
this.getInputNode().focus();
|
|
},{bind:this});
|
|
webix._event(this.getInputNode(),"focus",function(){
|
|
if(this._getBox().className.indexOf("webix_focused") == -1)
|
|
this._getBox().className += " webix_focused";
|
|
|
|
},{bind:this});
|
|
webix._event(this.getInputNode(),"blur",function(){
|
|
this._getBox().className = this._getBox().className.replace(" webix_focused","");
|
|
},{bind:this});
|
|
|
|
// need for clear click ("x") in IE
|
|
webix._event(this.getInputNode(),"input",function(){
|
|
if(!this.getInputNode().value && this._inputValue){
|
|
this.getInputNode().style.width = "20px";
|
|
this._inputWidth = 20;
|
|
|
|
this._inputValue = "";
|
|
this._typing = true;
|
|
|
|
this.getPopup().show(this._getInputDiv());
|
|
this._resizeToContent();
|
|
}
|
|
},{bind:this});
|
|
// resize
|
|
webix._event(this.getInputNode(),"keyup",function(e){
|
|
var inp = this.getInputNode();
|
|
var calcWidth, width;
|
|
|
|
e = (e||event);
|
|
// to show placeholder
|
|
if(this._settings.placeholder && !this._settings.value && !inp.value)
|
|
width = this._get_input_width(this._settings);
|
|
else{
|
|
width = calcWidth = this._calcInputWidth(inp.value)+10;
|
|
if(!this._settings.tagMode && this._getValueListBox().firstChild)
|
|
width = this._getMultiComboInputWidth();
|
|
}
|
|
|
|
inp.style.width = width +"px";
|
|
|
|
if(calcWidth!=this._inputWidth){
|
|
if(this._settings.keepText || e.keyCode !=13){
|
|
this._inputValue = inp.value;
|
|
}
|
|
else{
|
|
this._inputValue = false;
|
|
}
|
|
this._typing = true;
|
|
|
|
if(this._inputWidth)
|
|
this.getPopup().show(this._getInputDiv());
|
|
|
|
this._inputWidth = calcWidth||width;
|
|
this._resizeToContent();
|
|
}
|
|
else if(this._windowHeight != this.getPopup().$height){
|
|
this.getPopup().show(this._getInputDiv());
|
|
}
|
|
|
|
if(inp.value.indexOf(this._settings.separator) > -1 && this._settings.tagMode){
|
|
var newValue = inp.value.replace(this._settings.separator, '');
|
|
if (newValue){
|
|
if (this._settings.newValues)
|
|
this._addNewValue(newValue);
|
|
else{
|
|
var newId = this.getPopup().getItemId(newValue);
|
|
if (newId)
|
|
this._addValue(newId);
|
|
}
|
|
}
|
|
|
|
if(this._settings.keepText){
|
|
this._inputValue = newValue;
|
|
inp.value = newValue;
|
|
this._enter = true;
|
|
this._typing = true;
|
|
this._resizeToContent();
|
|
} else{
|
|
inp.value = "";
|
|
}
|
|
}
|
|
},{bind:this});
|
|
|
|
// remove the last value on Backspace click
|
|
webix._event(this.getInputNode(),"keydown",function(e){
|
|
this._enter = false;
|
|
if (this.isVisible()){
|
|
e = (e||event);
|
|
var node = this._getValueListBox().lastChild;
|
|
this._windowHeight = this.getPopup().$height;
|
|
if(e.keyCode == 8 && node){
|
|
if(!this.getInputNode().value && ((new Date()).valueOf() - (this._backspaceTime||0) > 100)){
|
|
this._typing = true;
|
|
this._removeValue(node.getAttribute("optvalue"));
|
|
}
|
|
else{
|
|
this._backspaceTime = (new Date()).valueOf();
|
|
}
|
|
}
|
|
|
|
if(e.keyCode == 13 || e.keyCode == 9){
|
|
var input = this.getInputNode();
|
|
var id = "";
|
|
var suggest = webix.$$(this._settings.suggest);
|
|
var list = suggest.getList();
|
|
// if no selected options
|
|
|
|
if(!list.getSelectedId()){
|
|
if (input.value)
|
|
id = suggest.getSuggestion();
|
|
|
|
if(this._settings.newValues){
|
|
if(e.keyCode == 13)
|
|
this._enter = true;
|
|
this._addNewValue(input.value);
|
|
if(this._settings.keepText)
|
|
this._inputValue = input.value;
|
|
else
|
|
input.value = "";
|
|
}
|
|
else if(id){
|
|
if(e.keyCode == 9){
|
|
this._typing = false;
|
|
this._inputValue = "";
|
|
this._inputWidth = 10;
|
|
input.value = "";
|
|
this._addValue(id);
|
|
}
|
|
else{
|
|
this._enter = true;
|
|
this._addValue(id);
|
|
if(this._settings.keepText)
|
|
this._inputValue = input.value;
|
|
else
|
|
input.value = "";
|
|
}
|
|
}
|
|
|
|
}
|
|
if(e.keyCode == 13){
|
|
this._enter = true;
|
|
this._typing = true;
|
|
if(this._settings.keepText)
|
|
this._inputValue = input.value;
|
|
else
|
|
input.value = "";
|
|
}
|
|
|
|
}
|
|
}
|
|
},{bind:this});
|
|
webix.$$(this._settings.suggest).linkInput(this);
|
|
}
|
|
}, webix.ui.richselect);
|
|
|
|
|
|
|
|
|
|
webix.protoUI({
|
|
name:"menu",
|
|
_listClassName:"webix_menu",
|
|
$init:function(config){
|
|
this.data._scheme_init = webix.bind(function(obj){
|
|
if (obj.disabled)
|
|
this.data.addMark(obj.id, "webix_disabled", true, 1, true);
|
|
}, this);
|
|
|
|
if (config.autowidth){
|
|
this._autowidth_submenu = true;
|
|
delete config.autowidth;
|
|
}
|
|
|
|
this.data.attachEvent('onStoreUpdated', webix.bind(function(){
|
|
this._hide_sub_menu();
|
|
},this));
|
|
this.attachEvent('onMouseMove', this._mouse_move_menu);
|
|
this.attachEvent('onMouseOut',function(e){
|
|
if (this._menu_was_activated() && this._settings.openAction == "click") return;
|
|
if (!this._child_menu_active && e.relatedTarget)
|
|
this._hide_sub_menu();
|
|
});
|
|
this.attachEvent('onItemClick', function(id, e, trg){
|
|
var item = this.getItem(id);
|
|
if (item){
|
|
if (item.$template) return;
|
|
|
|
var parent = this.getTopMenu();
|
|
|
|
//ignore disabled items
|
|
if (!this.data.getMark(id, "webix_disabled")){
|
|
if (!parent.callEvent("onMenuItemClick", [id, e, trg])){
|
|
e.showpopup = true;
|
|
return;
|
|
}
|
|
|
|
if (this != parent)
|
|
parent._call_onclick(id,e,trg);
|
|
|
|
//click on group - do not close submenus
|
|
if (!item.submenu){
|
|
parent._hide_sub_menu(true);
|
|
if (parent._hide_on_item_click)
|
|
parent.hide();
|
|
} else {
|
|
if ((this === parent || webix.env.touch ) && parent._settings.openAction == "click"){
|
|
this._mouse_move_activation(id, trg);
|
|
}
|
|
|
|
//do not close popups when clicking on menu folder
|
|
e.showpopup = true;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
this.attachEvent("onKeyPress", function(code, e){
|
|
if(code === 9) this.getTopMenu()._hide_sub_menu();
|
|
else if(code === 13 || code === 32){
|
|
var sel = this.getSelectedId(), node;
|
|
if(sel)
|
|
node = this.getItemNode(sel);
|
|
if(node)
|
|
webix.html.triggerEvent(node, "MouseEvents", "click");
|
|
}
|
|
|
|
});
|
|
|
|
this.data.attachEvent("onClearAll", function(){
|
|
this._hidden_items = [];
|
|
});
|
|
this.data._hidden_items = [];
|
|
|
|
this._viewobj.setAttribute("role", "menubar");
|
|
},
|
|
sizeToContent:function(){
|
|
if (this._settings.layout == "y"){
|
|
var texts = [];
|
|
var isSubmenu = false;
|
|
this.data.each(function(obj){
|
|
texts.push(this._toHTML(obj));
|
|
if(obj.submenu)
|
|
isSubmenu = true;
|
|
}, this);
|
|
// text width + padding + borders+ arrow
|
|
this.config.width = webix.html.getTextSize(texts, this.$view.className).width+8*2+2+(isSubmenu?15:0);
|
|
this.resize();
|
|
} else webix.assert(false, "sizeToContent will work for vertical menu only");
|
|
},
|
|
getTopMenu:function(){
|
|
var parent = this;
|
|
while (parent._parent_menu)
|
|
parent = webix.$$(parent._parent_menu);
|
|
return parent;
|
|
},
|
|
_auto_height_calc:function(count){
|
|
if (this._settings.autoheight) count = this.count();
|
|
|
|
var height = 0;
|
|
for (var i=0; i<count; i++){
|
|
var item = this.data.pull[this.data.order[i]];
|
|
if (item && item.$template == "Separator")
|
|
height+=4;
|
|
else
|
|
height+=this.type.height;
|
|
}
|
|
return height;
|
|
},
|
|
on_mouse_move:{},
|
|
type:{
|
|
css:"menu",
|
|
width:"auto",
|
|
aria:function(obj, common, marks){
|
|
return 'role="menuitem"'+(marks && marks.webix_selected?' aria-selected="true" tabindex="0"':'tabindex="-1"')+(obj.submenu?'aria-haspopup="true"':'')+(marks && marks.webix_disabled?' aria-disabled="true"':'');
|
|
},
|
|
templateStart:function(obj, common, mark){
|
|
if (obj.$template === "Separator" || obj.$template === "Spacer"){
|
|
return '<div webix_l_id="#id#" role="separator" tabindex="-1" class="webix_context_'+obj.$template.toLowerCase()+'">';
|
|
}
|
|
var link = (obj.href?" href='"+obj.href+"' ":"")+(obj.target?" target='"+obj.target+"' ":"");
|
|
return webix.ui.list.prototype.type.templateStart(obj,common,mark).replace(/^<div/,"<a "+link)+((obj.submenu && common.subsign)?"<div class='webix_submenu_icon'></div>":"");
|
|
},
|
|
templateEnd: function(obj, common, mark){
|
|
return (obj.$template === "Separator" || obj.$template === "Spacer")?"</div>":"</a>";
|
|
},
|
|
templateSeparator:webix.template("<div class='sep_line'></div>"),
|
|
templateSpacer:webix.template("<div></div>")
|
|
},
|
|
getMenu: function(id){
|
|
if (!this.data.pull[id]){
|
|
for (var subid in this.data.pull){
|
|
var obj = this.getItem(subid);
|
|
if (obj.submenu){
|
|
var search = this._get_submenu(obj).getMenu(id);
|
|
if (search) return search;
|
|
}
|
|
}
|
|
} else return this;
|
|
},
|
|
getSubMenu:function(id){
|
|
var menu = this.getMenu(id);
|
|
var obj = menu.getItem(id);
|
|
return (obj.submenu?menu._get_submenu(obj):null);
|
|
},
|
|
getMenuItem:function(id){
|
|
return this.getMenu(id).getItem(id);
|
|
},
|
|
_get_submenu:function(data){
|
|
var sub = webix.$$(data.submenu);
|
|
if (!sub){
|
|
data.submenu = this._create_sub_menu(data);
|
|
sub = webix.$$(data.submenu);
|
|
}
|
|
return sub;
|
|
},
|
|
_mouse_move_menu:function(id, e, target){
|
|
if (!this._menu_was_activated())
|
|
return;
|
|
|
|
this._mouse_move_activation(id, target);
|
|
},
|
|
_menu_was_activated:function(){
|
|
var top = this.getTopMenu();
|
|
if (top._settings.openAction == "click"){
|
|
if (webix.env.touch) return false;
|
|
var sub = top._open_sub_menu;
|
|
if (sub && webix.$$(sub).isVisible())
|
|
return true;
|
|
return false;
|
|
}
|
|
return true;
|
|
},
|
|
_mouse_move_activation:function(id, target){
|
|
var data = this.getItem(id);
|
|
if (!data) return;
|
|
|
|
//clear flag of submenu usage
|
|
this._child_menu_active = null;
|
|
|
|
//hide previously opened sub-menu
|
|
if (this._open_sub_menu && data.submenu != this._open_sub_menu)
|
|
this._hide_sub_menu(true);
|
|
|
|
//show submenu
|
|
if (data.submenu&&!this.config.hidden){
|
|
|
|
var sub = this._get_submenu(data);
|
|
if(this.data.getMark(id,"webix_disabled"))
|
|
return;
|
|
sub.show(target,{ pos:this._settings.subMenuPos });
|
|
|
|
sub._parent_menu = this._settings.id;
|
|
|
|
this._open_sub_menu = data.submenu;
|
|
}
|
|
},
|
|
disableItem:function(id){
|
|
this.getMenu(id).addCss(id, "webix_disabled");
|
|
},
|
|
enableItem:function(id){
|
|
this.getMenu(id).removeCss(id, "webix_disabled");
|
|
},
|
|
_set_item_hidden:function(id, state){
|
|
var menu = this.data;
|
|
if (menu._hidden_items[id] != state){
|
|
menu._hidden_items[id] = state;
|
|
menu.filter(function(obj){
|
|
return !menu._hidden_items[obj.id];
|
|
});
|
|
this.resize();
|
|
}
|
|
},
|
|
hideItem:function(id){
|
|
var menu = this.getMenu(id);
|
|
if (menu) menu._set_item_hidden(id, true);
|
|
},
|
|
showItem:function(id){
|
|
var menu = this.getMenu(id);
|
|
if (menu){
|
|
menu._set_item_hidden(id, false);
|
|
return webix.ui.list.prototype.showItem.call(menu, id);
|
|
}
|
|
},
|
|
_hide_sub_menu : function(mode){
|
|
if (this._open_sub_menu){
|
|
//recursive sub-closing
|
|
var sub = webix.$$(this._open_sub_menu);
|
|
if (sub._hide_sub_menu) //custom context may not have submenu
|
|
sub._hide_sub_menu(mode);
|
|
if (mode || !sub._show_on_mouse_out){
|
|
sub.hide();
|
|
this._open_sub_menu = null;
|
|
}
|
|
}
|
|
},
|
|
_create_sub_menu : function(data){
|
|
var listConfig = {
|
|
view:"submenu",
|
|
data:data.submenu
|
|
};
|
|
|
|
var settings = this.getTopMenu()._settings.submenuConfig;
|
|
if (settings)
|
|
webix.extend(listConfig, settings, true);
|
|
|
|
var parentData = this.getMenuItem(data.id);
|
|
if(parentData && parentData.config)
|
|
webix.extend(listConfig, parentData.config, true);
|
|
|
|
var menu = webix.ui(listConfig);
|
|
menu._parent_menu = this;
|
|
return menu._settings.id;
|
|
},
|
|
_skip_item:function(id, prev, mode){
|
|
var item = this.getItem(id);
|
|
if(item.$template == "Separator" || item.$template == "Spacer" || this.data.getMark(id, "webix_disabled")){
|
|
var index = this.getIndexById(id)+(mode == "up"?-1:1);
|
|
id = (index>=0)?this.getIdByIndex(index):null;
|
|
return id? this._skip_item(id, prev, mode) : prev;
|
|
}
|
|
else
|
|
return id;
|
|
},
|
|
$skin:function(){
|
|
webix.ui.list.prototype.$skin.call(this);
|
|
this.type.height = webix.skin.$active.menuHeight;
|
|
},
|
|
defaults:{
|
|
scroll:"",
|
|
layout:"x",
|
|
mouseEventDelay:100,
|
|
subMenuPos:"bottom"
|
|
}
|
|
}, webix.ui.list);
|
|
|
|
|
|
webix.protoUI({
|
|
name:"submenu",
|
|
$init:function(){
|
|
this._body_cell = webix.clone(this._dummy_cell_interface);
|
|
this._body_cell._view = this;
|
|
|
|
this.attachEvent('onMouseOut',function(e){
|
|
if (this.getTopMenu()._settings.openAction == "click")
|
|
return;
|
|
if (!this._child_menu_active && !this._show_on_mouse_out && e.relatedTarget)
|
|
this.hide();
|
|
});
|
|
|
|
//inform parent that focus is still in menu
|
|
this.attachEvent('onMouseMoving',function(){
|
|
if (this._parent_menu)
|
|
webix.$$(this._parent_menu)._child_menu_active = true;
|
|
});
|
|
this.attachEvent("onBeforeShow", function(){
|
|
if (this.getTopMenu()._autowidth_submenu && this.sizeToContent && !this.isVisible())
|
|
this.sizeToContent();
|
|
});
|
|
|
|
this._dataobj.setAttribute("role", "menu");
|
|
},
|
|
$skin:function(){
|
|
webix.ui.menu.prototype.$skin.call(this);
|
|
webix.ui.popup.prototype.$skin.call(this);
|
|
|
|
this.type.height = webix.skin.$active.menuHeight;
|
|
},
|
|
_dummy_cell_interface : {
|
|
$getSize:function(dx, dy){
|
|
//we saving height and width, as list can hardcode new values
|
|
var h = this._view._settings.height*1;
|
|
var w = this._view._settings.width*1;
|
|
var size = webix.ui.menu.prototype.$getSize.call(this._view, dx, dy);
|
|
//restoring
|
|
this._view._settings.height = h;
|
|
this._view._settings.width = w;
|
|
return size;
|
|
},
|
|
$setSize:function(x,y){
|
|
if (this._view._settings.scroll)
|
|
this._view._bodyobj.style.height = y+"px";
|
|
},
|
|
destructor:function(){ this._view = null; }
|
|
},
|
|
//ignore body element
|
|
body_setter:function(){
|
|
},
|
|
getChildViews:function(){ return []; },
|
|
defaults:{
|
|
width:150,
|
|
subMenuPos:"right",
|
|
layout:"y",
|
|
autoheight:true
|
|
},
|
|
type:{
|
|
height: webix.skin.menuHeight,
|
|
subsign:true
|
|
}
|
|
}, webix.ui.menu, webix.ui.popup);
|
|
|
|
|
|
|
|
|
|
webix.ContextHelper = {
|
|
defaults:{
|
|
padding:"4",
|
|
hidden:true
|
|
},
|
|
body_setter:function(value){
|
|
value = webix.ui.window.prototype.body_setter.call(this, value);
|
|
this._body_cell._viewobj.style.borderWidth = "0px";
|
|
return value;
|
|
},
|
|
attachTo:function(obj){
|
|
webix.assert(obj, "Invalid target for Context::attach");
|
|
var id;
|
|
if (obj.on_context)
|
|
id = obj.attachEvent("onAfterContextMenu", webix.bind(this._show_at_ui, this));
|
|
else
|
|
id = webix.event(obj, "contextmenu", this._show_at_node, {bind:this});
|
|
|
|
this.attachEvent("onDestruct", function(){
|
|
if (obj.callEvent)
|
|
obj.detachEvent(id);
|
|
else
|
|
webix.eventRemove(id);
|
|
obj = null;
|
|
});
|
|
},
|
|
getContext:function(){
|
|
return this._area;
|
|
},
|
|
setContext:function(area){
|
|
this._area = area;
|
|
},
|
|
_show_at_node:function(e){
|
|
this._area = webix.toNode(e||event);
|
|
return this._show_at(e);
|
|
},
|
|
_show_at_ui:function(id, e, trg){
|
|
this._area = { obj:webix.$$(e), id:id };
|
|
return this._show_at(e);
|
|
},
|
|
_show_at:function(e){
|
|
var result = this.show(e, null, true);
|
|
if (result === false) return result;
|
|
|
|
//event forced to close other popups|context menus
|
|
webix.callEvent("onClick", []);
|
|
return webix.html.preventEvent(e);
|
|
},
|
|
_show_on_mouse_out:true,
|
|
master_setter:function(value){
|
|
this.attachTo(value);
|
|
return null;
|
|
}
|
|
};
|
|
webix.protoUI({
|
|
name:"context"
|
|
}, webix.ContextHelper, webix.ui.popup);
|
|
|
|
webix.protoUI({
|
|
name:"contextmenu",
|
|
_hide_on_item_click:true,
|
|
$init: function(config){
|
|
if(config.submenuConfig)
|
|
webix.extend(config,config.submenuConfig);
|
|
}
|
|
}, webix.ContextHelper, webix.ui.submenu);
|
|
|
|
/*
|
|
|
|
*/
|
|
|
|
webix.protoUI({
|
|
name:"tabbar",
|
|
$init:function(){
|
|
this.attachEvent("onKeyPress", this._onKeyPress);
|
|
},
|
|
$skin:function(){
|
|
var skin = webix.skin.$active;
|
|
var defaults = this.defaults;
|
|
|
|
defaults.topOffset = skin.tabTopOffset||0;
|
|
defaults.tabOffset = (typeof skin.tabOffset != "undefined"?skin.tabOffset:10);
|
|
defaults.bottomOffset = skin.tabBottomOffset||0;
|
|
defaults.height = skin.tabbarHeight;
|
|
|
|
defaults.tabMargin = skin.tabMargin;
|
|
defaults.inputPadding = skin.inputPadding;
|
|
defaults.tabMinWidth = skin.tabMinWidth||100;
|
|
defaults.tabMoreWidth = skin.tabMoreWidth||40;
|
|
},
|
|
_getTabbarSizes: function(){
|
|
|
|
var config = this._settings,
|
|
i, len,
|
|
tabs = this._tabs||config.options,
|
|
totalWidth = this._input_width - config.tabOffset*2,
|
|
limitWidth = config.optionWidth||config.tabMinWidth;
|
|
|
|
len = tabs.length;
|
|
|
|
if(config.tabMinWidth && totalWidth/len < limitWidth){
|
|
return { max: (parseInt(totalWidth/limitWidth,10)||1)};
|
|
}
|
|
|
|
|
|
if(!config.optionWidth){
|
|
for(i=0;i< len; i++){
|
|
if(tabs[i].width){
|
|
totalWidth -= tabs[i].width+(!i&&!config .type?config.tabMargin:0);
|
|
len--;
|
|
}
|
|
}
|
|
}
|
|
|
|
return {width: (len?totalWidth/len:config.tabMinWidth)};
|
|
},
|
|
_init_popup: function(){
|
|
var obj = this._settings;
|
|
if (!obj.tabbarPopup){
|
|
var popupConfig = {
|
|
view: "popup",
|
|
width: (obj.popupWidth||200),
|
|
body:{
|
|
view: "list",
|
|
borderless: true,
|
|
select: true,
|
|
css: "webix_tab_list",
|
|
autoheight: true, yCount:obj.yCount,
|
|
type:{
|
|
template: obj.popupTemplate
|
|
}
|
|
}
|
|
};
|
|
var view = webix.ui(popupConfig);
|
|
view.getBody().attachEvent("onBeforeSelect",webix.bind(function(id){
|
|
if (id && this.callEvent("onBeforeTabClick", [id])){
|
|
this.setValue(id);
|
|
webix.$$(this._settings.tabbarPopup).hide();
|
|
this.callEvent("onAfterTabClick", [id]);
|
|
return true;
|
|
}
|
|
},this));
|
|
|
|
view.getBody().attachEvent("onAfterSelect", webix.bind(function(id){
|
|
this.refresh();
|
|
},this));
|
|
|
|
obj.tabbarPopup = view._settings.id;
|
|
this._destroy_with_me.push(view);
|
|
}
|
|
this._init_popup = function(){};
|
|
},
|
|
getPopup: function(){
|
|
this._init_popup();
|
|
return webix.$$(this._settings.tabbarPopup);
|
|
},
|
|
moreTemplate_setter: webix.template,
|
|
popupTemplate_setter: webix.template,
|
|
defaults:{
|
|
popupWidth: 200,
|
|
popupTemplate: "#value#",
|
|
yCount: 7,
|
|
moreTemplate: '<span class="webix_icon fa-ellipsis-h"></span>',
|
|
template:function(obj,common) {
|
|
var contentWidth, html, i, leafWidth, resultHTML, style, sum, tabs, verticalOffset, width;
|
|
|
|
common._tabs = tabs = common._filterOptions(obj.options);
|
|
|
|
if (!tabs.length){
|
|
html = "<div class='webix_tab_filler' style='width:"+common._input_width+"px; border-right:0px;'></div>";
|
|
} else {
|
|
common._check_options(tabs);
|
|
if (!obj.value && tabs.length)
|
|
obj.value = tabs[0].id;
|
|
|
|
html = "";
|
|
if (obj.tabOffset)
|
|
html += "<div class='webix_tab_filler' style='width:"+obj.tabOffset+"px;'> </div>";
|
|
contentWidth = common._input_width - obj.tabOffset*2-(!obj.type?(obj.tabMargin)*(tabs.length-1):0);
|
|
verticalOffset = obj.topOffset+obj.bottomOffset;
|
|
|
|
var sizes = common._getTabbarSizes();
|
|
|
|
if(sizes.max && sizes.max < tabs.length){
|
|
//we need popup
|
|
var popup = common.getPopup();
|
|
popup.hide();
|
|
|
|
var list = (popup.getBody()||null);
|
|
if(list){
|
|
if(sizes.max){
|
|
var found = false;
|
|
for( i = 0; i < tabs.length && !found; i++)
|
|
if(tabs[i].id== obj.value){
|
|
found = true;
|
|
if((i+1) > sizes.max){
|
|
var selectedTab = tabs.splice(i, 1);
|
|
var displayTabs = tabs.splice(0, sizes.max-1).concat(selectedTab);
|
|
tabs = displayTabs.concat(tabs);
|
|
}
|
|
}
|
|
list.clearAll();
|
|
list.parse(tabs.slice(sizes.max));
|
|
}
|
|
else{
|
|
list.clearAll();
|
|
}
|
|
}
|
|
} else if (common._settings.tabbarPopup)
|
|
webix.$$(common._settings.tabbarPopup).hide();
|
|
|
|
sum = obj.tabOffset;
|
|
var lastTab = false;
|
|
for(i = 0; (i<tabs.length) && !lastTab; i++) {
|
|
|
|
// tab width
|
|
if(sizes && sizes.max){
|
|
if(sizes.max == (i + 1)){
|
|
lastTab = true;
|
|
}
|
|
contentWidth = common._input_width - obj.tabOffset*2-(!obj.type&&(sizes.max>1)?(obj.tabMargin)*(sizes.max-1):0);
|
|
width = (contentWidth - obj.tabMoreWidth)/sizes.max ;
|
|
}
|
|
else
|
|
width = sizes.width;
|
|
|
|
width = (tabs[i].width||obj.optionWidth||width);
|
|
|
|
sum += width + (i&&!obj.type?obj.tabMargin:0);
|
|
|
|
if(obj.tabMargin>0&&i&&!obj.type)
|
|
html += "<div class='webix_tab_filler' style='width:"+obj.tabMargin+"px;'></div>";
|
|
|
|
// tab innerHTML
|
|
html += common._getTabHTML(tabs[i],width);
|
|
|
|
|
|
if(lastTab){
|
|
html += '<div role="button" tabindex="0" aria-label="'+webix.i18n.aria.showTabs+'" class="webix_tab_more_icon" style="width:'+obj.tabMoreWidth+'px;">'+obj.moreTemplate(obj,common)+'</div>';
|
|
sum += obj.tabMoreWidth;
|
|
}
|
|
}
|
|
|
|
|
|
leafWidth = common._content_width - sum;
|
|
|
|
if (leafWidth>0 && !obj.type)
|
|
html += "<div class='webix_tab_filler' style='width:"+leafWidth+"px;'> </div>";
|
|
}
|
|
|
|
resultHTML = "";
|
|
|
|
// consider top and bottom offset in tabs height (top tabbar)
|
|
style = (verticalOffset&& !obj.type)?"height:"+(common._content_height-verticalOffset)+"px":"";
|
|
|
|
//space above tabs (top tabbar)
|
|
if(obj.topOffset && !obj.type)
|
|
resultHTML += "<div class='webix_before_all_tabs' style='width:100%;height:"+obj.topOffset+"px'></div>";
|
|
|
|
// tabs html
|
|
resultHTML += "<div style='"+style+"' role='tablist' class='webix_all_tabs "+(obj.type?("webixtype_"+obj.type):"")+"'>"+html+"</div>";
|
|
|
|
//space below to tabs (top tabbar)
|
|
if(obj.bottomOffset && !obj.type)
|
|
resultHTML += "<div class='webix_after_all_tabs' style='width:100%;height:"+obj.bottomOffset+"px'></div>";
|
|
|
|
return resultHTML;
|
|
}
|
|
},
|
|
_getInputNode:function(){
|
|
return this.$view.querySelectorAll(".webix_item_tab");
|
|
},
|
|
_getTabHTML: function(tab,width){
|
|
var html,
|
|
className = '',
|
|
config = this.config;
|
|
|
|
if(tab.id== config.value)
|
|
className=" webix_selected";
|
|
|
|
if (tab.css)
|
|
className+=" "+tab.css;
|
|
|
|
width = (tab.width||width);
|
|
|
|
html ='<div class="webix_item_tab'+className+'" button_id="'+tab.id+'" role="tab" aria-selected="'+(tab.id== config.value?"true":"false")+'" tabindex="'+(tab.id== config.value?"0":"-1")+'" style="width:'+width+'px;">';
|
|
|
|
// a tab title
|
|
if(this._tabTemplate){
|
|
var calcHeight = this._content_height- config.inputPadding*2 - 2;
|
|
var height = this._content_height - 2;
|
|
var temp = webix.extend({ cheight: calcHeight, aheight:height }, tab);
|
|
html+= this._tabTemplate(temp);
|
|
}
|
|
else {
|
|
var icon = tab.icon?("<span class='webix_icon fa-"+tab.icon+"'></span> "):"";
|
|
html+=icon + tab.value;
|
|
}
|
|
|
|
if (tab.close || config.close)
|
|
html+="<span role='button' tabindex='0' aria-label='"+webix.i18n.aria.closeTab+"' class='webix_tab_close webix_icon fa-times'></span>";
|
|
|
|
html+="</div>";
|
|
return html;
|
|
},
|
|
_types:{
|
|
image:"<div class='webix_img_btn_top' style='height:#cheight#px;background-image:url(#image#);'><div class='webix_img_btn_text'>#value#</div></div>",
|
|
icon:"<div class='webix_img_btn' style='line-height:#cheight#px;height:#cheight#px;'><span class='webix_icon_btn fa-#icon#' style='max-width:#cheight#px;max-height:#cheight#px;'></span>#value#</div>",
|
|
iconTop:"<div class='webix_img_btn_top' style='height:#cheight#px;width:100%;top:0px;text-align:center;'><span class='webix_icon fa-#icon#'></span><div class='webix_img_btn_text'>#value#</div></div>"
|
|
},
|
|
type_setter:function(value){
|
|
this._settings.tabOffset = 0;
|
|
if (this._types[value])
|
|
this._tabTemplate = webix.template(this._types[value]);
|
|
return value;
|
|
}
|
|
}, webix.ui.segmented);
|
|
|
|
webix.protoUI({
|
|
name:"tabview",
|
|
defaults:{
|
|
type:"clean"
|
|
},
|
|
setValue:function(val){
|
|
this._cells[0].setValue(val);
|
|
},
|
|
getValue:function(){
|
|
return this._cells[0].getValue();
|
|
},
|
|
getTabbar:function(){
|
|
return this._cells[0];
|
|
},
|
|
getMultiview:function(){
|
|
return this._cells[1];
|
|
},
|
|
addView:function(obj){
|
|
var id = obj.body.id = obj.body.id || webix.uid();
|
|
|
|
this.getMultiview().addView(obj.body);
|
|
|
|
obj.id = obj.body.id;
|
|
obj.value = obj.header;
|
|
delete obj.body;
|
|
delete obj.header;
|
|
|
|
var t = this.getTabbar();
|
|
t.addOption(obj);
|
|
|
|
return id;
|
|
},
|
|
removeView:function(id){
|
|
var t = this.getTabbar();
|
|
t.removeOption(id);
|
|
t.refresh();
|
|
},
|
|
$init:function(config){
|
|
this.$ready.push(this._init_tabview_handlers);
|
|
|
|
var cells = config.cells;
|
|
var tabs = [];
|
|
|
|
webix.assert(cells && cells.length, "tabview must have cells collection");
|
|
|
|
for (var i = cells.length - 1; i >= 0; i--){
|
|
var view = cells[i].body||cells[i];
|
|
if (!view.id) view.id = "view"+webix.uid();
|
|
tabs[i] = { value:cells[i].header, id:view.id, close:cells[i].close, width:cells[i].width, hidden: !!cells[i].hidden};
|
|
cells[i] = view;
|
|
}
|
|
|
|
var tabbar = { view:"tabbar", multiview:true };
|
|
var mview = { view:"multiview", cells:cells, animate:(!!config.animate) };
|
|
|
|
if (config.value)
|
|
tabbar.value = config.value;
|
|
|
|
if (config.tabbar)
|
|
webix.extend(tabbar, config.tabbar, true);
|
|
if (config.multiview)
|
|
webix.extend(mview, config.multiview, true);
|
|
|
|
tabbar.options = tabbar.options || tabs;
|
|
|
|
config.rows = [
|
|
tabbar, mview
|
|
];
|
|
|
|
delete config.cells;
|
|
delete config.tabs;
|
|
},
|
|
_init_tabview_handlers:function(){
|
|
this.getTabbar().attachEvent("onOptionRemove", function(id){
|
|
var view = webix.$$(id);
|
|
if (view){
|
|
var parent = view.getParentView();
|
|
if(parent)
|
|
parent.removeView(view);
|
|
}
|
|
});
|
|
}
|
|
}, webix.ui.layout);
|
|
|
|
webix.protoUI({
|
|
name:"fieldset",
|
|
defaults:{
|
|
borderless:true,
|
|
$cssName:"webix_fieldset",
|
|
paddingX: 18,
|
|
paddingY: 30
|
|
},
|
|
$init:function(obj){
|
|
webix.assert(obj.body, "fieldset must have not-empty body");
|
|
|
|
this._viewobj.className += " "+this.defaults.$cssName;
|
|
this._viewobj.innerHTML = "<fieldset><legend></legend><div></div></fieldset>";
|
|
},
|
|
label_setter:function(value){
|
|
this._viewobj.firstChild.childNodes[0].innerHTML = value;
|
|
return value;
|
|
},
|
|
getChildViews:function(){
|
|
return [this._body_view];
|
|
},
|
|
body_setter:function(config){
|
|
this._body_view = webix.ui(config, this._viewobj.firstChild.childNodes[1]);
|
|
this._body_view._parent_cell = this;
|
|
return config;
|
|
},
|
|
getBody:function(){
|
|
return this._body_view;
|
|
},
|
|
resizeChildren:function(){
|
|
var x = this.$width - this._settings.paddingX;
|
|
var y = this.$height - this._settings.paddingY;
|
|
var sizes=this._body_view.$getSize(0,0);
|
|
|
|
//minWidth
|
|
if (sizes[0]>x) x = sizes[0];
|
|
//minHeight
|
|
if (sizes[2]>y) y = sizes[2];
|
|
|
|
this._body_view.$setSize(x,y);
|
|
this.resize();
|
|
},
|
|
$getSize:function(x, y){
|
|
webix.debug_size_box_start(this, true);
|
|
|
|
x += this._settings.paddingX;
|
|
y += this._settings.paddingY;
|
|
|
|
var t = this._body_view.$getSize(x, y);
|
|
var s = this._last_body_size = webix.ui.view.prototype.$getSize.call(this, x, y);
|
|
|
|
//inner content minWidth > outer
|
|
if (s[0] < t[0]) s[0] = t[0];
|
|
if (s[2] < t[2]) s[2] = t[2];
|
|
//inner content maxWidth < outer
|
|
if (s[1] > t[1]) s[1] = t[1];
|
|
if (s[3] > t[3]) s[3] = t[3];
|
|
|
|
webix.debug_size_box_end(this, s);
|
|
return s;
|
|
},
|
|
$setSize:function(x,y){
|
|
if (webix.ui.view.prototype.$setSize.call(this, x,y)){
|
|
x = Math.min(this._last_body_size[1], x);
|
|
y = Math.min(this._last_body_size[3], y);
|
|
this._body_view.$setSize(x - this._settings.paddingX, y - this._settings.paddingY);
|
|
}
|
|
}
|
|
}, webix.ui.view);
|
|
webix.protoUI({
|
|
name:"forminput",
|
|
defaults:{
|
|
$cssName:"webix_forminput",
|
|
labelWidth: 80,
|
|
labelAlign : "left"
|
|
},
|
|
setValue:function(value){
|
|
this._body_view.setValue(value);
|
|
},
|
|
focus:function(){
|
|
this._body_view.focus();
|
|
},
|
|
getValue:function(){
|
|
return this._body_view.getValue();
|
|
},
|
|
value_setter:function(value){
|
|
this.setValue(value);
|
|
},
|
|
getBody:function(){
|
|
return this._body_view;
|
|
},
|
|
$skin:function(){
|
|
this._inputPadding = webix.skin.$active.inputPadding;
|
|
this._inputSpacing = webix.skin.$active.inputSpacing;
|
|
},
|
|
$init:function(obj){
|
|
this.$ready.push(function(){
|
|
var label = this._viewobj.firstChild.childNodes[0];
|
|
label.style.width = this._settings.paddingX+"px";
|
|
label.style.textAlign = this._settings.labelAlign;
|
|
if (!this._settings.labelWidth)
|
|
label.style.display = "none";
|
|
});
|
|
|
|
var lw = webix.isUndefined(obj.labelWidth) ? this.defaults.labelWidth : obj.labelWidth;
|
|
obj.paddingX = lw - this._inputPadding*2 + this._inputSpacing* 2;
|
|
},
|
|
setBottomText: function(text) {
|
|
var config = this._settings;
|
|
if (typeof text != "undefined"){
|
|
if (config.bottomLabel == text) return;
|
|
config.bottomLabel = text;
|
|
}
|
|
var message = (config.invalid ? config.invalidMessage : "") || config.bottomLabel;
|
|
if(this._invalidMessage) {
|
|
webix.html.remove(this._invalidMessage);
|
|
}
|
|
if(message) {
|
|
this.$view.style.position = "relative";
|
|
this._invalidMessage = webix.html.create("div", { "class":"webix_inp_bottom_label", role:config.invalid?'alert':"", "aria-relevant":'all', style:"position:absolute; bottom:0px; padding:2px; background: white; left:"+this._settings.labelWidth+"px; " }, message);
|
|
this._viewobj.appendChild(this._invalidMessage);
|
|
}
|
|
}
|
|
}, webix.ui.fieldset);
|
|
|
|
|
|
webix.protoUI({
|
|
name: "dbllist",
|
|
defaults:{
|
|
borderless:true
|
|
},
|
|
$init: function(config) {
|
|
this._moved = {};
|
|
this._inRight = webix.bind(function(obj){ return this._moved[obj.id]; }, this);
|
|
this._inLeft = webix.bind(function(obj){ return !this._moved[obj.id]; }, this);
|
|
|
|
this.$view.className += " webix_dbllist";
|
|
this.$ready.unshift(this._setLayout);
|
|
},
|
|
$onLoad:function(data, driver){
|
|
this._updateAndResize(function(){
|
|
this.$$("left").data.driver = driver;
|
|
this.$$("left").parse(data);
|
|
this.$$("right").data.driver = driver;
|
|
this.$$("right").parse(data);
|
|
});
|
|
|
|
this._refresh();
|
|
},
|
|
_getButtons:function(){
|
|
if (this._settings.buttons === false)
|
|
return { width: 10 };
|
|
|
|
var i18n = webix.i18n.dbllist;
|
|
var buttons = [
|
|
this._getButton("deselect_all", i18n.deselectAll),
|
|
this._getButton("select_all", i18n.selectAll),
|
|
this._getButton("deselect_one", i18n.deselectOne),
|
|
this._getButton("select_one", i18n.selectOne)
|
|
];
|
|
|
|
|
|
var buttons = { width:120, template:buttons.join(""), onClick:{
|
|
dbllist_button:function(e, id, trg){
|
|
this.getTopParentView()._update_list(trg.getAttribute("action"));
|
|
}
|
|
}};
|
|
if (this._settings.buttons)
|
|
buttons.template = this._settings.buttons;
|
|
|
|
return buttons;
|
|
},
|
|
_getButton: function(action, label){
|
|
return "<button class='dbllist_button' action='"+action+"'>"+label+"</button>";
|
|
},
|
|
_getList: function(id, action, label, bottom){
|
|
var list = {
|
|
view: "list",
|
|
select: "multiselect",
|
|
multiselect: "touch",
|
|
id: id,
|
|
action: action,
|
|
drag: true,
|
|
type:{
|
|
margin:3,
|
|
id:id
|
|
},
|
|
on: {
|
|
onBeforeDrop: function(context) {
|
|
var source = context.from;
|
|
var target = context.to;
|
|
var top = source.getTopParentView();
|
|
|
|
if (top === this.getTopParentView()) {
|
|
var mode = (target._settings.action != "select_one");
|
|
top.select(context.source, mode);
|
|
top._refresh();
|
|
}
|
|
return false;
|
|
},
|
|
onItemDblClick: function(){
|
|
return this.getTopParentView()._update_list(this.config.action);
|
|
}
|
|
}
|
|
};
|
|
|
|
if (this._settings.list)
|
|
webix.extend(list, this._settings.list, true);
|
|
|
|
if (label)
|
|
list = { rows:[{ view:"label", label:label }, list] };
|
|
if (bottom)
|
|
return { rows:[list, { view:"label", height:20, label:bottom, css:"bottom_label" }] };
|
|
return list;
|
|
},
|
|
_setLayout: function() {
|
|
var cols = [{
|
|
margin: 10, type:"clean",
|
|
cols: [
|
|
this._getList("left", "select_one", this._settings.labelLeft, this._settings.labelBottomLeft),
|
|
this._getButtons(),
|
|
this._getList("right", "deselect_one", this._settings.labelRight, this._settings.labelBottomRight)
|
|
]
|
|
}];
|
|
|
|
this.cols_setter(cols);
|
|
},
|
|
_update_list: function(action) {
|
|
var top = this;
|
|
var id = null;
|
|
var mode = false;
|
|
|
|
if (action === "select_all"){
|
|
id = top.$$("left").data.order;
|
|
mode = true;
|
|
} else if (action === "select_one"){
|
|
id = top.$$("left").getSelectedId(true);
|
|
mode = true;
|
|
} else if (action === "deselect_all"){
|
|
id = top.$$("right").data.order;
|
|
mode = false;
|
|
} else if (action === "deselect_one"){
|
|
id = top.$$("right").getSelectedId(true);
|
|
mode = false;
|
|
}
|
|
|
|
top.select(id, mode);
|
|
},
|
|
select:function(id, mode){
|
|
var i;
|
|
if (typeof id !== "object") id = [id];
|
|
|
|
if (mode){
|
|
for (i = 0; i < id.length; i++)
|
|
this._moved[id[i]] = true;
|
|
} else {
|
|
for (i = 0; i < id.length; i++)
|
|
delete this._moved[id[i]];
|
|
}
|
|
this.callEvent("onChange", []);
|
|
this._refresh();
|
|
},
|
|
_updateAndResize:function(handler, size){
|
|
webix.ui.$freeze = true;
|
|
handler.call(this);
|
|
webix.ui.$freeze = false;
|
|
|
|
if (size && (this.$$("left")._settings.autoheight || this.$$("right")._settings.autoheight))
|
|
this.resize();
|
|
},
|
|
_refresh: function() {
|
|
var left = this.$$("left");
|
|
var right = this.$$("right");
|
|
|
|
if (left)
|
|
this._updateAndResize(function(){
|
|
left.filter(this._inLeft);
|
|
right.filter(this._inRight);
|
|
}, true);
|
|
},
|
|
focus:function(){
|
|
webix.UIManager.setFocus(this);
|
|
},
|
|
value_setter:function(val){
|
|
this.setValue(val);
|
|
},
|
|
setValue: function(value) {
|
|
this._moved = {};
|
|
if (typeof value !== "object")
|
|
value = value.toString().split(",");
|
|
for (var i = 0; i < value.length; i++)
|
|
this._moved[value[i]] = true;
|
|
|
|
|
|
this._refresh();
|
|
},
|
|
getValue: function() {
|
|
var value = [];
|
|
for (var key in this._moved)
|
|
value.push(key);
|
|
|
|
return value.join(",");
|
|
}
|
|
}, webix.AtomDataLoader, webix.IdSpace, webix.ui.layout);
|
|
|
|
webix.i18n.dbllist = {
|
|
selectAll : "<span class='webix_icon fa-angle-double-right'></span>",
|
|
selectOne : "<span class='webix_icon fa-angle-right'></span>",
|
|
deselectAll : "<span class='webix_icon fa-angle-double-left'></span>",
|
|
deselectOne : "<span class='webix_icon fa-angle-left'></span>",
|
|
};
|
|
|
|
|
|
|
|
|
|
(function(){
|
|
|
|
function _tagname(el) {
|
|
if (!el.tagName) return null;
|
|
return el.tagName.toLowerCase();
|
|
}
|
|
function _attribute(el, name) {
|
|
if (!el.getAttribute) return null;
|
|
var attr = el.getAttribute(name);
|
|
return attr ? attr.toLowerCase() : null;
|
|
}
|
|
function _get_html_value() {
|
|
var tagname = _tagname(this);
|
|
if (_get_value[tagname])
|
|
return _get_value[tagname](this);
|
|
return _get_value.other(this);
|
|
}
|
|
|
|
var _get_value = {
|
|
radio: function(el){
|
|
for (var i = 0; i < el.length; i++)
|
|
if (el[i].checked) return el[i].value;
|
|
return "";
|
|
},
|
|
input: function(el) {
|
|
var type = _attribute(el, 'type');
|
|
if (type === 'checkbox')
|
|
return el.checked;
|
|
return el.value;
|
|
},
|
|
textarea: function(el) {
|
|
return el.value;
|
|
},
|
|
select: function(el) {
|
|
var index = el.selectedIndex;
|
|
return el.options[index].value;
|
|
},
|
|
other: function(el) {
|
|
return el.innerHTML;
|
|
}
|
|
};
|
|
|
|
function _set_html_value(value) {
|
|
var tagname = _tagname(this);
|
|
if (_set_value[tagname])
|
|
return _set_value[tagname]( this, value);
|
|
return _set_value.other( this, value);
|
|
}
|
|
|
|
var _set_value = {
|
|
radio:function(el, value){
|
|
for (var i = 0; i < el.length; i++)
|
|
el[i].checked = (el[i].value == value);
|
|
},
|
|
input: function(el, value) {
|
|
var type = _attribute(el, 'type');
|
|
if (type === 'checkbox')
|
|
el.checked = (value) ? true : false;
|
|
else
|
|
el.value = value;
|
|
},
|
|
textarea: function(el, value) {
|
|
el.value = value;
|
|
},
|
|
select: function(el, value) {
|
|
//select first option if no provided and if possible
|
|
el.value = value?value:el.firstElementChild.value||value;
|
|
},
|
|
other: function(el, value) {
|
|
el.innerHTML = value;
|
|
}
|
|
};
|
|
|
|
|
|
webix.protoUI({
|
|
name:"htmlform",
|
|
$init: function(config) {
|
|
this.elements = {};
|
|
this._default_values = false;
|
|
|
|
if (config.content && (config.container == config.content || !config.container && config.content == document.body))
|
|
this._copy_inner_content = true;
|
|
},
|
|
content_setter:function(content){
|
|
content = webix.toNode(content);
|
|
if (this._copy_inner_content){
|
|
while (content.childNodes.length > 1)
|
|
this._viewobj.childNodes[0].appendChild(content.childNodes[0]);
|
|
} else {
|
|
this._viewobj.childNodes[0].appendChild(content);
|
|
}
|
|
this._parse_inputs();
|
|
return true;
|
|
},
|
|
render:function(){
|
|
webix.ui.template.prototype.render.apply(this, arguments);
|
|
this._parse_inputs();
|
|
},
|
|
_parse_inputs: function() {
|
|
var inputs = this._viewobj.querySelectorAll("[name]");
|
|
this.elements = {};
|
|
|
|
|
|
for (var i=0; i<inputs.length; i++){
|
|
var el = inputs[i];
|
|
var name = _attribute(el, "name");
|
|
if (name){
|
|
var tag = _tagname(el) === "button";
|
|
var type = _attribute(el, "type");
|
|
|
|
var cant_clear = tag || type === "button" || type === "submit";
|
|
|
|
if (type === "radio"){
|
|
var stack = this.elements[name] || [];
|
|
stack.tagName = "radio";
|
|
stack.push(el);
|
|
el = stack;
|
|
}
|
|
|
|
this.elements[name] = el;
|
|
|
|
el.getValue = _get_html_value;
|
|
el.setValue = _set_html_value;
|
|
el._allowsClear = !cant_clear;
|
|
el._settings = { defaultValue : el.getValue() };
|
|
}
|
|
}
|
|
|
|
return this.elements;
|
|
},
|
|
_mark_invalid:function(id,obj){
|
|
this._clear_invalid(id,obj);
|
|
var el = this._viewobj.querySelector('[name="' + id + '"]');
|
|
if (el) webix.html.addCss(el, "invalid");
|
|
},
|
|
_clear_invalid:function(id,obj){
|
|
var el = this._viewobj.querySelector('[name="' + id + '"]');
|
|
if (el) webix.html.removeCss(el, "invalid");
|
|
}
|
|
|
|
}, webix.ui.template, webix.Values);
|
|
|
|
})();
|
|
(function(){
|
|
var google, script;
|
|
webix.protoUI({
|
|
name:"google-map",
|
|
$init:function(config){
|
|
this.$view.innerHTML = "<div class='webix_map_content' style='width:100%;height:100%'></div>";
|
|
this._contentobj = this.$view.firstChild;
|
|
this._waitMap = webix.promise.defer();
|
|
|
|
this.data.provideApi(this, true);
|
|
this.$ready.push(this.render);
|
|
},
|
|
getMap:function(waitMap){
|
|
return waitMap?this._waitMap:this._map;
|
|
},
|
|
_getCallBack:function(prev){
|
|
return webix.bind(function(){
|
|
if (typeof prev === "function") prev();
|
|
|
|
google = google || window.google;
|
|
this._initMap.call(this);
|
|
}, this);
|
|
},
|
|
render:function(){
|
|
if(typeof window.google=="undefined"||typeof window.google.maps=="undefined"){
|
|
if(!script){
|
|
script = document.createElement("script");
|
|
script.type = "text/javascript";
|
|
|
|
var config = this._settings;
|
|
var src = config.src || "//maps.google.com/maps/api/js";
|
|
src += (src.indexOf("?")===-1 ? "?" :"&");
|
|
|
|
if (config.key)
|
|
src += "&key="+config.key;
|
|
if (config.libraries)
|
|
src += "&libraries="+config.libraries;
|
|
|
|
script.src = src;
|
|
document.getElementsByTagName("head")[0].appendChild(script);
|
|
}
|
|
script.onload = this._getCallBack(script.onload);
|
|
}
|
|
else //there's a custom link to google api in document head
|
|
(this._getCallBack())();
|
|
},
|
|
_initMap:function(){
|
|
var c = this.config;
|
|
if(this.isVisible(c.id)){
|
|
this._map = new google.maps.Map(this._contentobj, {
|
|
zoom: c.zoom,
|
|
center: new google.maps.LatLng(c.center[0], c.center[1]),
|
|
mapTypeId: google.maps.MapTypeId[c.mapType]
|
|
});
|
|
this._waitMap.resolve(this._map);
|
|
}
|
|
},
|
|
center_setter:function(config){
|
|
if(this._map)
|
|
this._map.setCenter(new google.maps.LatLng(config[0], config[1]));
|
|
|
|
return config;
|
|
},
|
|
mapType_setter:function(config){
|
|
/*ROADMAP,SATELLITE,HYBRID,TERRAIN*/
|
|
if(this._map)
|
|
this._map.setMapTypeId(google.maps.MapTypeId[config]);
|
|
|
|
return config;
|
|
},
|
|
zoom_setter:function(config){
|
|
if(this._map)
|
|
this._map.setZoom(config);
|
|
return config;
|
|
},
|
|
layerType_setter:function(config){
|
|
if(config == "heatmap")
|
|
this.config.libraries = "visualization";
|
|
if(this._layerApi[config]){
|
|
webix.extend(this, this._layerApi[config], true);
|
|
this.data.attachEvent("onStoreUpdated", webix.bind(this.drawData, this));
|
|
}
|
|
|
|
return config;
|
|
},
|
|
defaults:{
|
|
zoom: 5,
|
|
center:[ 39.5, -98.5 ],
|
|
mapType: "ROADMAP",
|
|
layerType:"marker"
|
|
},
|
|
$setSize:function(){
|
|
webix.ui.view.prototype.$setSize.apply(this, arguments);
|
|
if(this._map)
|
|
google.maps.event.trigger(this._map, "resize");
|
|
},
|
|
$onLoad:function(data){
|
|
if(!this._map){
|
|
this._waitMap.then(webix.bind(function(){
|
|
this.parse(data);
|
|
}, this));
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
_layerApi:{
|
|
marker:{
|
|
drawData:function(id, item, operation){
|
|
switch (operation){
|
|
case "add":
|
|
item.$marker = this._getItemConfig(item);
|
|
break;
|
|
case "update":
|
|
item.$marker.setMap(null);
|
|
item.$marker = this._getItemConfig(item);
|
|
break;
|
|
case "delete":
|
|
item.$marker.setMap(null);
|
|
break;
|
|
default:
|
|
this.data.each(function(item){
|
|
item.$marker = this._getItemConfig(item);
|
|
}, this);
|
|
break;
|
|
}
|
|
},
|
|
clearAll:function(soft){
|
|
this.data.each(function(obj){
|
|
obj.$marker.setMap(null);
|
|
});
|
|
this.data.clearAll(soft);
|
|
},
|
|
showItem:function(id){
|
|
var item = this.getItem(id);
|
|
this._map.setCenter(new google.maps.LatLng(item.lat, item.lng));
|
|
},
|
|
_getItemConfig:function(item){
|
|
var obj = {};
|
|
for(var i in item) obj[i] = item[i];
|
|
obj.position = new google.maps.LatLng(item.lat, item.lng);
|
|
obj.map = item.hidden? null: this._map;
|
|
|
|
var marker = new google.maps.Marker(obj);
|
|
this._events(marker);
|
|
this.callEvent("onItemRender", [item]);
|
|
|
|
return marker;
|
|
},
|
|
_events:function(marker){
|
|
var map = this;
|
|
|
|
marker.addListener('click', function(){
|
|
map.callEvent("onItemClick", [this.id, this]);
|
|
});
|
|
|
|
if(marker.getDraggable()){
|
|
marker.addListener('dragend', function(){ map._onDrag(this, true); });
|
|
marker.addListener('drag', function(){ map._onDrag(this); });
|
|
}
|
|
},
|
|
_onDrag:function(marker, end){
|
|
var item = this.getItem(marker.id);
|
|
var pos = marker.getPosition();
|
|
var ev = end?"onAfterDrop":"onDrag";
|
|
|
|
item.lat = pos.lat();
|
|
item.lng = pos.lng();
|
|
this.callEvent(ev, [item.id, item]);
|
|
}
|
|
},
|
|
heatmap:{
|
|
heatmapConfig_setter:function(value){
|
|
value = value || {};
|
|
return value;
|
|
},
|
|
drawData:function(){
|
|
if(this._heatmap){
|
|
this._heatmap.setMap(null);
|
|
this._heatmap = null;
|
|
}
|
|
|
|
var hdata = [];
|
|
this.data.each(function(item){ hdata.push(this._getConfig(item)); }, this);
|
|
|
|
if(hdata.length){
|
|
var data = webix.extend(this.config.heatmapConfig, {data:hdata, map:this._map}, true);
|
|
this._heatmap = new google.maps.visualization.HeatmapLayer(data);
|
|
this.callEvent("onHeatMapRender", [this._heatmap]);
|
|
}
|
|
},
|
|
getHeatmap:function(){
|
|
return this._heatmap;
|
|
},
|
|
_getConfig:function(item){
|
|
var obj = {};
|
|
for(var i in item) obj[i] = item[i];
|
|
obj.location = new google.maps.LatLng(item.lat, item.lng);
|
|
|
|
return obj;
|
|
}
|
|
}
|
|
}
|
|
}, webix.DataLoader, webix.EventSystem, webix.ui.view);
|
|
})();
|
|
|
|
(function(){
|
|
var google, script;
|
|
webix.protoUI({
|
|
name:"geochart",
|
|
defaults:{
|
|
chart:{
|
|
displayMode:"auto",
|
|
region:"world",
|
|
resolution:"countries"
|
|
}
|
|
},
|
|
$init:function(config){
|
|
this.$view.innerHTML = "<div class='webix_map_content' style='width:100%;height:100%'></div>";
|
|
this._contentobj = this.$view.firstChild;
|
|
this._waitMap = webix.promise.defer();
|
|
|
|
config.chart = webix.extend(config.chart||{}, this.defaults.chart);
|
|
|
|
this.data.provideApi(this, true);
|
|
this.$ready.push(this.render);
|
|
|
|
this.data.attachEvent("onClearAll", webix.bind(this._refreshColumns, this));
|
|
this.data.attachEvent("onStoreUpdated", webix.bind(this._drawData, this));
|
|
},
|
|
getMap:function(waitMap){
|
|
return waitMap?this._waitMap:this._map;
|
|
},
|
|
_getCallBack:function(prev){
|
|
return webix.bind(function(){
|
|
if (typeof prev === "function") prev();
|
|
|
|
google = google || window.google;
|
|
this._initMap();
|
|
}, this);
|
|
},
|
|
render:function(){
|
|
if(typeof window.google=="undefined"||typeof window.google.charts=="undefined"){
|
|
if(!script){
|
|
script = document.createElement("script");
|
|
script.type = "text/javascript";
|
|
|
|
script.src = "//www.gstatic.com/charts/loader.js";
|
|
document.getElementsByTagName("head")[0].appendChild(script);
|
|
}
|
|
script.onload = this._getCallBack(script.onload);
|
|
}
|
|
else //there's a custom link to google api in document head
|
|
(this._getCallBack())();
|
|
},
|
|
_initMap:function(){
|
|
if(!google.visualization || !google.visualization.GeoChart){
|
|
google.charts.load("current", {
|
|
"packages":["geochart"],
|
|
"mapsApiKey": this._settings.key
|
|
});
|
|
google.charts.setOnLoadCallback(webix.bind(function(){
|
|
this._initMap();
|
|
}, this));
|
|
}
|
|
else{
|
|
this._map = new google.visualization.GeoChart(this._contentobj);
|
|
this._mapEvents();
|
|
|
|
this._waitMap.resolve(this._map);
|
|
}
|
|
},
|
|
$onLoad:function(obj, driver){
|
|
if(!this._map){
|
|
this._waitMap.then(webix.bind(function(){
|
|
this.parse(obj, this._settings.datatype);
|
|
}, this));
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
_drawData:function(){
|
|
if(!this._map){
|
|
if(!this._map)
|
|
this._waitMap.then(webix.bind(this._drawData, this));
|
|
return;
|
|
}
|
|
|
|
var columns = this._columns&&this._columns.length?this._columns:this._defineColumns();
|
|
var data = [];
|
|
this.data.each(function(obj, i){
|
|
var line = [];
|
|
for(var c = 0; c<columns.length; c++){
|
|
var value = obj[columns[c].label];
|
|
if(columns[c].type == "number")
|
|
value = value*1;
|
|
else if(columns[c].role =="tooltip")
|
|
value = this._settings.tooltip(obj);
|
|
line.push(value);
|
|
}
|
|
data.push(line);
|
|
}, this);
|
|
|
|
if(columns.length){
|
|
var table = new google.visualization.DataTable();
|
|
for(var i = 0; i<columns.length; i++)
|
|
table.addColumn(columns[i]);
|
|
table.addRows(data);
|
|
|
|
var view = new google.visualization.DataView(table);
|
|
this._map.draw(view, this._settings.chart);
|
|
}
|
|
else //draw clean chart
|
|
this._map.draw(google.visualization.arrayToDataTable([["", ""]]), {});
|
|
|
|
},
|
|
setDisplayMode:function(value){
|
|
this._settings.chart.displayMode = value;
|
|
this.refresh();
|
|
},
|
|
setRegion:function(value){
|
|
this._settings.chart.region = value;
|
|
this.refresh();
|
|
},
|
|
refresh:function(){
|
|
this._map.clearChart();
|
|
this._drawData();
|
|
},
|
|
tooltip_setter:function(value){
|
|
var tooltip = this._settings.chart.tooltip;
|
|
this._settings.chart.tooltip = webix.extend(tooltip || {}, {isHtml:true});
|
|
return webix.template(value);
|
|
},
|
|
$setSize:function(w, h){
|
|
if (webix.ui.view.prototype.$setSize.apply(this, arguments) && this._map){
|
|
webix.extend(this._settings, {width:w, height:h});
|
|
this.refresh();
|
|
}
|
|
},
|
|
_refreshColumns:function(){
|
|
this._columns = null;
|
|
this._drawData();
|
|
},
|
|
_getColumnType:function(item, key){
|
|
if (!item || webix.isUndefined(item[key]))
|
|
return "string";
|
|
|
|
var type = typeof item[key];
|
|
if(type =="string" && !isNaN(item[key]*1))
|
|
type = "number";
|
|
return type;
|
|
},
|
|
_defineColumns:function(item){
|
|
var columns = this._settings.columns || [];
|
|
var item = this.data.pull[this.data.order[0]];
|
|
|
|
//auto columns
|
|
if (!columns.length && item){
|
|
for (var key in item)
|
|
if (key !== "id") columns.push(key);
|
|
}
|
|
//["title", "area"]
|
|
for(var i=0; i<columns.length; i++){
|
|
if (typeof columns[i] !== "object"){
|
|
columns[i] = {type:this._getColumnType(item, columns[i]), label:columns[i]};
|
|
}
|
|
}
|
|
|
|
if(this._settings.tooltip)
|
|
columns.push({type:"string", role:"tooltip", p:{"html": true}});
|
|
|
|
this._columns = columns;
|
|
return columns;
|
|
},
|
|
_mapEvents:function(){
|
|
google.visualization.events.addListener(this._map, "error", webix.bind(function(){this.callEvent("onMapError", arguments);}, this));
|
|
google.visualization.events.addListener(this._map, "ready", webix.bind(function(){this.callEvent("onMapReady", arguments);}, this));
|
|
google.visualization.events.addListener(this._map, "regionClick", webix.bind(function(){this.callEvent("onRegionClick", arguments);}, this));
|
|
google.visualization.events.addListener(this._map, "select", webix.bind(function(){
|
|
var selnow = this._map.getSelection()[0];
|
|
var sel = selnow || this._selprev;
|
|
if(sel){
|
|
var id = this.data.order[sel.row];
|
|
this._selprev = sel;
|
|
this.callEvent("onItemClick", [id, !!selnow]);
|
|
}
|
|
}, this));
|
|
}
|
|
}, webix.DataLoader, webix.EventSystem, webix.ui.view);
|
|
})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
webix.dp = function(name,getOnly){
|
|
if (typeof name == "object" && name._settings)
|
|
name = name._settings.id;
|
|
if (webix.dp._pull[name] || getOnly)
|
|
return webix.dp._pull[name];
|
|
|
|
if (typeof name == "string"||typeof name == "number")
|
|
name = { master:webix.$$(name) };
|
|
|
|
var dp = new webix.DataProcessor(name);
|
|
var masterId = dp._settings.master._settings.id;
|
|
webix.dp._pull[masterId]=dp;
|
|
|
|
webix.$$(masterId).attachEvent("onDestruct",function(){
|
|
webix.dp._pull[this._settings.id] = null;
|
|
delete webix.dp._pull[this._settings.id];
|
|
});
|
|
|
|
return dp;
|
|
};
|
|
webix.dp._pull = {};
|
|
webix.dp.$$ = function(id){
|
|
return webix.dp._pull[id];
|
|
};
|
|
|
|
|
|
webix.DataProcessor = webix.proto({
|
|
defaults: {
|
|
autoupdate:true,
|
|
updateFromResponse:false,
|
|
mode:"post",
|
|
operationName:"webix_operation",
|
|
trackMove:false
|
|
},
|
|
|
|
|
|
/*! constructor
|
|
**/
|
|
$init: function() {
|
|
this.reset();
|
|
this._ignore = false;
|
|
this.name = "DataProcessor";
|
|
this.$ready.push(this._after_init_call);
|
|
},
|
|
reset:function(){
|
|
this._updates = [];
|
|
},
|
|
url_setter:function(value){
|
|
/*
|
|
we can use simple url or mode->url
|
|
*/
|
|
var mode = "";
|
|
if (typeof value == "string"){
|
|
var parts = value.split("->");
|
|
if (parts.length > 1){
|
|
value = parts[1];
|
|
mode = parts[0];
|
|
}
|
|
} else if (value && value.mode){
|
|
mode = value.mode;
|
|
value = value.url;
|
|
}
|
|
|
|
if (mode)
|
|
return webix.proxy(mode, value);
|
|
|
|
return value;
|
|
},
|
|
master_setter:function(value){
|
|
var store = value;
|
|
if (value.name != "DataStore")
|
|
store = value.data;
|
|
|
|
this._settings.store = store;
|
|
return value;
|
|
},
|
|
/*! attaching onStoreUpdated event
|
|
**/
|
|
_after_init_call: function(){
|
|
webix.assert(this._settings.store, "store or master need to be defined for the dataprocessor");
|
|
this._settings.store.attachEvent("onStoreUpdated", webix.bind(this._onStoreUpdated, this));
|
|
this._settings.store.attachEvent("onDataMove", webix.bind(this._onDataMove, this));
|
|
},
|
|
ignore:function(code,master){
|
|
var temp = this._ignore;
|
|
this._ignore = true;
|
|
code.call((master||this));
|
|
this._ignore = temp;
|
|
},
|
|
off:function(){
|
|
this._ignore = true;
|
|
},
|
|
on:function(){
|
|
this._ignore = false;
|
|
},
|
|
|
|
_copy_data:function(source){
|
|
var obj = {};
|
|
for (var key in source)
|
|
if (key.indexOf("$")!==0)
|
|
obj[key]=source[key];
|
|
return obj;
|
|
},
|
|
save:function(id, operation, obj){
|
|
operation = operation || "update";
|
|
this._save_inner(id, (obj || this._settings.store.getItem(id)), operation);
|
|
},
|
|
_save_inner:function(id, obj, operation){
|
|
if (typeof id == "object") id = id.toString();
|
|
if (!id || this._ignore === true || !operation || operation == "paint") return true;
|
|
|
|
var store = this._settings.store;
|
|
if (store && store._scheme_serialize)
|
|
obj = store._scheme_serialize(obj);
|
|
|
|
var update = { id: id, data:this._copy_data(obj), operation:operation };
|
|
//save parent id
|
|
if (!webix.isUndefined(obj.$parent)) update.data.parent = obj.$parent;
|
|
|
|
if (update.operation != "delete"){
|
|
//prevent saving of not-validated records
|
|
var master = this._settings.master;
|
|
if (master && master.data && master.data.getMark && master.data.getMark(id, "webix_invalid"))
|
|
update._invalid = true;
|
|
|
|
if (!this.validate(null, update.data))
|
|
update._invalid = true;
|
|
}
|
|
|
|
if (this._check_unique(update))
|
|
this._updates.push(update);
|
|
|
|
if (this._settings.autoupdate)
|
|
this.send();
|
|
|
|
return true;
|
|
},
|
|
_onDataMove:function(sid, tindex, parent, targetid){
|
|
if (this._settings.trackMove){
|
|
var obj = webix.copy(this._settings.store.getItem(sid));
|
|
var order = this._settings.store.order;
|
|
|
|
obj.webix_move_index = tindex;
|
|
obj.webix_move_id = targetid;
|
|
obj.webix_move_parent = parent;
|
|
this._save_inner(sid, obj, "order");
|
|
}
|
|
},
|
|
_onStoreUpdated: function(id, obj, operation){
|
|
switch (operation) {
|
|
case 'save':
|
|
operation = "update";
|
|
break;
|
|
case 'update':
|
|
operation = "update";
|
|
break;
|
|
case 'add':
|
|
operation = "insert";
|
|
break;
|
|
case 'delete':
|
|
operation = "delete";
|
|
break;
|
|
default:
|
|
return true;
|
|
}
|
|
return this._save_inner(id, obj, operation);
|
|
},
|
|
_check_unique:function(check){
|
|
for (var i = 0; i < this._updates.length; i++){
|
|
var one = this._updates[i];
|
|
if (one.id == check.id){
|
|
if (check.operation == "delete"){
|
|
if (one.operation == "insert")
|
|
this._updates.splice(i,1);
|
|
else
|
|
one.operation = "delete";
|
|
}
|
|
one.data = check.data;
|
|
one._invalid = check._invalid;
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
},
|
|
send:function(){
|
|
this._sendData();
|
|
},
|
|
_sendData: function(){
|
|
if (!this._settings.url)
|
|
return;
|
|
|
|
var marked = this._updates;
|
|
var to_send = [];
|
|
var url = this._settings.url;
|
|
|
|
for (var i = 0; i < marked.length; i++) {
|
|
var tosave = marked[i];
|
|
|
|
if (tosave._in_progress) continue;
|
|
if (tosave._invalid) continue;
|
|
|
|
var id = tosave.id;
|
|
var operation = tosave.operation;
|
|
var precise_url = (typeof url == "object" && !url.$proxy) ? url[operation] : url;
|
|
var proxy = precise_url && (precise_url.$proxy || typeof precise_url === "function");
|
|
|
|
if (!precise_url) continue;
|
|
|
|
if (this._settings.store._scheme_save)
|
|
this._settings.store._scheme_save(tosave.data);
|
|
|
|
if (!this.callEvent("onBefore"+operation, [id, tosave]))
|
|
continue;
|
|
tosave._in_progress = true;
|
|
|
|
if (!this.callEvent("onBeforeDataSend", [tosave])) return;
|
|
|
|
tosave.data = this._updatesData(tosave.data);
|
|
|
|
var callback = this._send_callback({ id:tosave.id, status:tosave.operation });
|
|
if (precise_url.$proxy){
|
|
if (precise_url.save)
|
|
precise_url.save(this.config.master, tosave, this, callback);
|
|
else
|
|
to_send.push(tosave);
|
|
} else {
|
|
if (operation == "insert") delete tosave.data.id;
|
|
|
|
|
|
if (proxy){
|
|
//promise
|
|
precise_url(tosave.id, tosave.operation, tosave.data).then(
|
|
function(data){
|
|
if (data && typeof data.json == "function")
|
|
data = data.json();
|
|
callback.success("", data, -1);
|
|
},
|
|
function(error){
|
|
callback.error("", null, error);
|
|
}
|
|
);
|
|
} else {
|
|
//normal url
|
|
tosave.data[this._settings.operationName] = operation;
|
|
|
|
this._send(precise_url, tosave.data, this._settings.mode, operation, callback);
|
|
}
|
|
}
|
|
|
|
this.callEvent("onAfterDataSend", [tosave]);
|
|
}
|
|
|
|
if (url.$proxy && url.saveAll && to_send.length)
|
|
url.saveAll(this.config.master, to_send, this, this._send_callback({}));
|
|
},
|
|
|
|
|
|
/*! process updates list to POST and GET params according dataprocessor protocol
|
|
* @param updates
|
|
* list of objects { id: "item id", data: "data hash", operation: "type of operation"}
|
|
* @return
|
|
* object { post: { hash of post params as name: value }, get: { hash of get params as name: value } }
|
|
**/
|
|
|
|
|
|
|
|
_updatesData:function(source){
|
|
var target = {};
|
|
for (var j in source){
|
|
if (j.indexOf("$")!==0)
|
|
target[j] = source[j];
|
|
}
|
|
return target;
|
|
},
|
|
|
|
|
|
|
|
/*! send dataprocessor query to server
|
|
* and attach event to process result
|
|
* @param url
|
|
* server url
|
|
* @param get
|
|
* hash of get params
|
|
* @param post
|
|
* hash of post params
|
|
* @mode
|
|
* 'post' or 'get'
|
|
**/
|
|
_send: function(url, post, mode, operation, callback) {
|
|
webix.assert(url, "url was not set for DataProcessor");
|
|
|
|
if (typeof url == "function")
|
|
return url(post, operation, callback);
|
|
|
|
webix.ajax()[mode](url, post, callback);
|
|
},
|
|
_send_callback:function(id){
|
|
var self = this;
|
|
return {
|
|
success:function(t,d,l){ return self._processResult(id, t,d,l); },
|
|
error :function(t,d,l){ return self._processError(id, t,d,l); }
|
|
};
|
|
},
|
|
attachProgress:function(start, end, error){
|
|
this.attachEvent("onBeforeDataSend", start);
|
|
this.attachEvent("onAfterSync", end);
|
|
this.attachEvent("onAfterSaveError", error);
|
|
this.attachEvent("onLoadError", error);
|
|
},
|
|
_processError:function(id, text, data, loader){
|
|
if (id)
|
|
this._innerProcessResult(true, id.id, false, id.status, false, {text:text, data:data, loader:loader});
|
|
else {
|
|
this.callEvent("onLoadError", arguments);
|
|
webix.callEvent("onLoadError", [text, data, loader, this]);
|
|
}
|
|
},
|
|
_innerProcessResult:function(error, id, newid, status, obj, details){
|
|
var master = this._settings.master;
|
|
var update = this.getItemState(id);
|
|
update._in_progress = false;
|
|
|
|
if (error){
|
|
if (this.callEvent("onBeforeSaveError", [id, status, obj, details])){
|
|
update._invalid = true;
|
|
if(this._settings.undoOnError && master._settings.undo)
|
|
master.undo(id);
|
|
this.callEvent("onAfterSaveError", [id, status, obj, details]);
|
|
return;
|
|
}
|
|
} else
|
|
this.setItemState(id, false);
|
|
|
|
//update from response
|
|
if (newid && id != newid)
|
|
this._settings.store.changeId(id, newid);
|
|
|
|
if (obj && status != "delete" && this._settings.updateFromResponse)
|
|
this.ignore(function(){
|
|
this._settings.store.updateItem(newid || id, obj);
|
|
});
|
|
|
|
|
|
//clean undo history, for the saved record
|
|
if(this._settings.undoOnError && master._settings.undo)
|
|
master.removeUndo(newid||id);
|
|
|
|
this.callEvent("onAfterSave",[obj, id, details]);
|
|
this.callEvent("onAfter"+status, [obj, id, details]);
|
|
},
|
|
processResult: function(state, hash, details){
|
|
//compatibility with custom json response
|
|
var error = (hash && (hash.status == "error" || hash.status == "invalid"));
|
|
var newid = (hash ? ( hash.newid || hash.id ) : false);
|
|
|
|
this._innerProcessResult(error, state.id, newid, state.status, hash, details);
|
|
},
|
|
// process saving from result
|
|
_processResult: function(state, text, data, loader){
|
|
this.callEvent("onBeforeSync", [state, text, data, loader]);
|
|
|
|
if (loader === -1){
|
|
//callback from promise
|
|
this.processResult(state, data, {});
|
|
} else {
|
|
var proxy = this._settings.url;
|
|
if (proxy.$proxy && proxy.result)
|
|
proxy.result(state, this._settings.master, this, text, data, loader);
|
|
else {
|
|
var hash;
|
|
if (text){
|
|
hash = data.json();
|
|
//invalid response
|
|
if (text && typeof hash == "undefined")
|
|
hash = { status:"error" };
|
|
}
|
|
this.processResult(state, hash, {text:text, data:data, loader:loader});
|
|
}
|
|
}
|
|
|
|
this.callEvent("onAfterSync", [state, text, data, loader]);
|
|
},
|
|
|
|
|
|
/*! if it's defined escape function - call it
|
|
* @param value
|
|
* value to escape
|
|
* @return
|
|
* escaped value
|
|
**/
|
|
escape: function(value) {
|
|
if (this._settings.escape)
|
|
return this._settings.escape(value);
|
|
else
|
|
return encodeURIComponent(value);
|
|
},
|
|
getState:function(){
|
|
if (!this._updates.length) return false;
|
|
for (var i = this._updates.length - 1; i >= 0; i--)
|
|
if (this._updates[i]._in_progress)
|
|
return "saving";
|
|
|
|
return true;
|
|
},
|
|
getItemState:function(id){
|
|
var index = this._get_stack_index(id);
|
|
return this._updates[index] || null;
|
|
},
|
|
setItemState:function(id, state){
|
|
if (state)
|
|
this.save(id, state);
|
|
else{
|
|
var index = this._get_stack_index(id);
|
|
if (index > -1)
|
|
this._updates.splice(index, 1);
|
|
}
|
|
},
|
|
_get_stack_index: function(id) {
|
|
var index = -1;
|
|
var update = null;
|
|
for (var i=0; i < this._updates.length; i++)
|
|
if (this._updates[i].id == id) {
|
|
index = i;
|
|
break;
|
|
}
|
|
|
|
return index;
|
|
}
|
|
|
|
}, webix.Settings, webix.EventSystem, webix.ValidateData);
|
|
|
|
|
|
(function(){
|
|
|
|
var timers = {};
|
|
webix.jsonp = function(url, params, callback, master){
|
|
var defer = webix.promise.defer();
|
|
|
|
var id = "webix_jsonp_"+webix.uid();
|
|
var script = document.createElement('script');
|
|
script.id = id;
|
|
script.type = 'text/javascript';
|
|
|
|
var head = document.getElementsByTagName("head")[0];
|
|
|
|
if (typeof params == "function"){
|
|
master = callback;
|
|
callback = params;
|
|
params = {};
|
|
}
|
|
|
|
if (!params)
|
|
params = {};
|
|
|
|
params.jsonp = "webix.jsonp."+id;
|
|
webix.jsonp[id]=function(){
|
|
if (callback)
|
|
callback.apply(master||window, arguments);
|
|
defer.resolve(arguments[0]);
|
|
|
|
window.clearTimeout(timers[id]);
|
|
delete timers[id];
|
|
|
|
script.parentNode.removeChild(script);
|
|
callback = head = master = script = null;
|
|
delete webix.jsonp[id];
|
|
};
|
|
|
|
//timeout timer
|
|
timers[id] = window.setTimeout(function(){
|
|
defer.reject();
|
|
delete webix.jsonp[id];
|
|
}, webix.jsonp.timer);
|
|
|
|
var vals = [];
|
|
for (var key in params) vals.push(key+"="+encodeURIComponent(params[key]));
|
|
|
|
url += (url.indexOf("?") == -1 ? "?" : "&")+vals.join("&");
|
|
|
|
script.src = url;
|
|
head.appendChild(script);
|
|
|
|
return defer;
|
|
};
|
|
|
|
webix.jsonp.timer = 3000;
|
|
|
|
})();
|
|
|
|
webix.markup = {
|
|
namespace:"x",
|
|
attribute:"data-",
|
|
dataTag:"li",
|
|
_dash:/-([a-z])/g,
|
|
_after_dash:function (match) { return match[1].toUpperCase(); },
|
|
_parse_int:{
|
|
width:true,
|
|
height:true,
|
|
gravity:true,
|
|
margin:true,
|
|
padding:true,
|
|
paddingX:true,
|
|
paddingY:true,
|
|
minWidth:true,
|
|
maxWidth:true,
|
|
minHeight:true,
|
|
maxHeight:true,
|
|
headerRowHeight:true
|
|
},
|
|
_parse_bool:{
|
|
disabled:true,
|
|
hidden:true
|
|
},
|
|
_view_has_method:function(view, name){
|
|
return webix.ui.hasMethod(view, name);
|
|
},
|
|
|
|
init: function(node, target, scope){
|
|
node = node || document.body;
|
|
|
|
var els = [];
|
|
var temp = this._get_core_els(node);
|
|
var html = temp.html;
|
|
var ui = null;
|
|
|
|
//make copy to prevent node removing effects
|
|
for (var i = temp.length - 1; i >= 0; i--) els[i] = temp[i];
|
|
|
|
for (var i = 0; i < els.length; i++) {
|
|
var config, temp_config;
|
|
//collect configuration
|
|
config = this._sub_markup(els[i], html);
|
|
config.$scope = scope;
|
|
ui = this._initComponent(config, els[i], html, target);
|
|
}
|
|
return ui;
|
|
},
|
|
|
|
parse:function(source, mode){
|
|
//convert from string to object
|
|
if (typeof source == "string")
|
|
source = webix.DataDriver[mode || "xml"].toObject(source, source);
|
|
|
|
var els = this._get_core_els(source, mode);
|
|
return this._sub_markup(els[0], els.html);
|
|
},
|
|
|
|
_initComponent:function(config, node, html, target){
|
|
if (!target){
|
|
config.container = node.parentNode;
|
|
webix.html.remove(node);
|
|
} else
|
|
config.container = target;
|
|
|
|
if (this._view_has_method(config.view, "setPosition"))
|
|
delete config.container;
|
|
|
|
//init ui
|
|
return webix.ui(config);
|
|
},
|
|
|
|
_get_core_els:function(node){
|
|
this._full_prefix = this.namespace?(this.namespace+":"):"";
|
|
this._full_prefix_top = this._full_prefix+"ui";
|
|
|
|
//xhtml mode
|
|
var els = node.getElementsByTagName(this._full_prefix_top);
|
|
if (!els.length && node.documentElement && node.documentElement.tagName == this._full_prefix_top)
|
|
els = [ node.documentElement ];
|
|
|
|
//loading from xml file with valid namespace
|
|
if (!els.length && this.namespace){
|
|
els = node.getElementsByTagName("ui");
|
|
if (!els.length && node.documentElement && node.documentElement.tagName == "ui")
|
|
els = [ node.documentElement ];
|
|
}
|
|
|
|
if (!els.length){
|
|
//html mode
|
|
els = this._get_html_tops(node);
|
|
els.html = true;
|
|
}
|
|
return els;
|
|
},
|
|
|
|
//html conversion
|
|
_get_html_tops: function(node){
|
|
if (node.getAttribute && node.getAttribute(this.attribute+"view"))
|
|
return [node];
|
|
|
|
var els = node.querySelectorAll("["+this.attribute+"view]");
|
|
|
|
var tags = []; var marks = [];
|
|
for (var i = 0; i < els.length; i++)
|
|
if (!els[i].parentNode.getAttribute(this.attribute+"view"))
|
|
tags.push(els[i]);
|
|
|
|
return tags;
|
|
},
|
|
|
|
|
|
|
|
_sub_markup: function(el, html, json){
|
|
var htmltable = false;
|
|
//ignore top x:ui for xhtml and xml
|
|
if (!json){
|
|
var name = this._get_name(el, html);
|
|
if (name == "ui"){
|
|
var childs = el.childNodes;
|
|
for (var i = 0; i < childs.length; i++)
|
|
if (childs[i].nodeType == 1){
|
|
return this._sub_markup(childs[i], html);
|
|
}
|
|
}
|
|
json = { view: name };
|
|
if (html && el.tagName.toLowerCase() == "table"){
|
|
json.data = el;
|
|
json.datatype = "htmltable";
|
|
htmltable = true;
|
|
}
|
|
}
|
|
|
|
var is_layout = json.view == "cols" || json.view == "rows" || this._view_has_method(json.view, "addView");
|
|
|
|
var subs = [];
|
|
var has_tags = 0;
|
|
var allow_sub_tags = !(html || el.style); //only for xml documents
|
|
var first = el.firstChild;
|
|
while (first){
|
|
//tag node
|
|
if (first.nodeType == 1){
|
|
var name = this._get_name(first, html);
|
|
if (name == "data"){
|
|
has_tags = 1;
|
|
var data = first; first = first.nextSibling;
|
|
json.data = this._handle_data(data, html);
|
|
continue;
|
|
} else if (name == "config"){
|
|
this._get_config_html(first, json, html);
|
|
var confignode = first;
|
|
first = first.nextSibling;
|
|
|
|
webix.html.remove(confignode);
|
|
continue;
|
|
} else if (name == "column"){
|
|
has_tags = 1;
|
|
|
|
var column = this._tag_to_json(first, html);
|
|
column.header = column.header || column.value;
|
|
column.width = column.width * 1 || "";
|
|
|
|
json.columns = json.columns || [];
|
|
json.columns.push(column);
|
|
} else if (name || (is_layout && html)){
|
|
var obj = this._sub_markup(first , html , { view:name });
|
|
if (obj.view == "head")
|
|
json.head = obj.rows ? obj.rows[0] : obj.template;
|
|
else if (obj.view == "body"){
|
|
if (this._view_has_method(json.view, "addView")){
|
|
//multiview, accordion
|
|
|
|
//subtag or plain value
|
|
//in case of multiple sub tags, only first will be used
|
|
// #dirty
|
|
subs.push({
|
|
body: (obj.rows ? obj.rows[0] : obj.value),
|
|
header:obj.header || ""
|
|
});
|
|
} else {
|
|
//window, fieldset
|
|
|
|
//one sub tag - use it
|
|
//multiple sub tags - create sub layout
|
|
//or use plain text value
|
|
json.body = obj.rows ? ( obj.rows.length == 1 ? obj.rows[0] : { rows:obj.rows } ) : obj.value;
|
|
}
|
|
} else
|
|
subs.push(obj);
|
|
} else if (allow_sub_tags) {
|
|
has_tags = 1;
|
|
var tagName = first.tagName;
|
|
if (html) tagName = tagName.toLowerCase().replace(this._dash, this._after_dash);
|
|
json[tagName] = webix.DataDriver.xml.tagToObject(first);
|
|
|
|
}
|
|
}
|
|
|
|
first = first.nextSibling;
|
|
}
|
|
|
|
this._attrs_to_json(el, json, html);
|
|
|
|
if (subs.length){
|
|
if (json.stack)
|
|
json[json.stack] = subs;
|
|
else if (this._view_has_method(json.view, "setValues"))
|
|
json["elements"] = subs;
|
|
else if (json.view == "rows"){
|
|
json.view = "layout";
|
|
json.rows = subs;
|
|
} else if (json.view == "cols"){
|
|
json.view = "layout";
|
|
json.cols = subs;
|
|
} else if (this._view_has_method(json.view, "setValue")){
|
|
json["cells"] = subs;
|
|
} else if (this._view_has_method(json.view, "getBody")){
|
|
json.body = subs.length == 1 ? subs[0] : { rows:subs };
|
|
} else
|
|
json["rows"] = subs;
|
|
} else if (!htmltable && !has_tags){
|
|
if (html && !json.template && (!json.view || json.view == "template")){
|
|
json.view = "template";
|
|
json.content = el;
|
|
} else {
|
|
var content = this._content(el, html);
|
|
if (content){
|
|
var target = "template";
|
|
if (this._view_has_method(json.view, "setValue"))
|
|
target = "value";
|
|
json[target] = json[target] || content;
|
|
}
|
|
}
|
|
}
|
|
|
|
return json;
|
|
},
|
|
|
|
_empty: function(str) {
|
|
var clean = str.replace(/\s+/gm, '');
|
|
return (clean.length > 0) ? false : true;
|
|
},
|
|
|
|
_markup_names:{
|
|
body:1,
|
|
head:1,
|
|
data:1,
|
|
rows:1,
|
|
cols:1,
|
|
cells:1,
|
|
elements:1,
|
|
ui:1,
|
|
column:1,
|
|
config:1
|
|
},
|
|
|
|
_get_config_html:function(tag, json, html){
|
|
var master = this._attrs_to_json(tag, { });
|
|
if (master.name){
|
|
json[master.name] = master;
|
|
delete master.name;
|
|
} else
|
|
if (master.stack) json[master.stack] = [];
|
|
else
|
|
json = master;
|
|
|
|
var childs = tag.childNodes;
|
|
for (var i = 0; i < childs.length; i++) {
|
|
var sub = null;
|
|
if (childs[i].nodeType == 1 && childs[i].tagName.toLowerCase() == "config" && childs[i].attributes.length)
|
|
sub = this._get_config_html(childs[i], master, html);
|
|
else
|
|
sub = childs[i].innerHTML;
|
|
if (master.stack && sub)
|
|
json[master.stack].push(sub);
|
|
|
|
}
|
|
return json;
|
|
},
|
|
|
|
_get_name:function(tag, html){
|
|
//value of view attribute or config tag
|
|
if (html)
|
|
return tag.getAttribute(this.attribute+"view") || ( tag.tagName.toLowerCase() == "config" ? "config" : null);
|
|
var name = tag.tagName.toLowerCase();
|
|
if (this.namespace){
|
|
if (name.indexOf(this._full_prefix) === 0 || tag.scopeName == this.namespace)
|
|
return name.replace(this._full_prefix,"");
|
|
} else {
|
|
if (webix.ui[name] || this._markup_names[name])
|
|
return name;
|
|
}
|
|
return 0;
|
|
},
|
|
|
|
_handle_data:function(el, html){
|
|
var data = [];
|
|
|
|
var records = el.getElementsByTagName(webix.markup.dataTag);
|
|
for (var i=0; i<records.length; i++){
|
|
var rec = records[i];
|
|
if (rec.parentNode.parentNode.tagName != webix.markup.dataTag){
|
|
var json = this._tag_to_json(rec, html);
|
|
//reuse css class
|
|
if (rec.className) json.$css = rec.className;
|
|
data.push(json);
|
|
}
|
|
}
|
|
|
|
webix.html.remove(el);
|
|
|
|
return data;
|
|
},
|
|
_content:function(el, html){
|
|
if (el.style) return el.innerHTML;
|
|
if (el.firstChild)
|
|
return el.firstChild.wholeText||el.firstChild.data||"";
|
|
return "";
|
|
},
|
|
|
|
|
|
_tag_to_json:function(el, html){
|
|
if (!html)
|
|
return webix.DataDriver.xml.tagToObject(el);
|
|
|
|
var json = this._attrs_to_json(el, {}, html);
|
|
if (!json.value && el.childNodes.length)
|
|
json.value = this._content(el, html);
|
|
|
|
return json;
|
|
},
|
|
_attrs_to_json:function(el, json, html){
|
|
var attrs = el.attributes;
|
|
for (var i=0; i<attrs.length; i++){
|
|
var name = attrs[i].name;
|
|
if (html){
|
|
if (name.indexOf(this.attribute) !== 0)
|
|
continue;
|
|
name = name.replace(this.attribute,"").replace(this._dash, this._after_dash);
|
|
}
|
|
|
|
var value = attrs[i].value;
|
|
if (value.indexOf("json://") != -1)
|
|
value = JSON.parse(value.replace("json://",""));
|
|
|
|
if (this._parse_int[name])
|
|
value = parseInt(value,10);
|
|
else if (this._parse_bool[name])
|
|
value = (value && value !== "false" && value != "0");
|
|
|
|
json[name] = value;
|
|
}
|
|
return json;
|
|
}
|
|
};
|
|
(function(){
|
|
var _webix_msg_cfg = null;
|
|
function callback(config, result){
|
|
var usercall = config.callback;
|
|
modality(false);
|
|
config.box.parentNode.removeChild(config.box);
|
|
_webix_msg_cfg = config.box = null;
|
|
if (usercall)
|
|
usercall(result,config.details);
|
|
}
|
|
function modal_key(e){
|
|
if (_webix_msg_cfg){
|
|
e = e||event;
|
|
var code = e.which||event.keyCode;
|
|
if (webix.message.keyboard){
|
|
if (code == 13 || code == 32)
|
|
callback(_webix_msg_cfg, true);
|
|
if (code == 27)
|
|
callback(_webix_msg_cfg, false);
|
|
|
|
if (e.preventDefault)
|
|
e.preventDefault();
|
|
return !(e.cancelBubble = true);
|
|
}
|
|
}
|
|
}
|
|
|
|
webix.event(document, "keydown", modal_key, { capture: true });
|
|
|
|
function modality(mode){
|
|
if(!modality.cover || !modality.cover.parentNode){
|
|
modality.cover = document.createElement("DIV");
|
|
//necessary for IE only
|
|
modality.cover.onkeydown = modal_key;
|
|
modality.cover.className = "webix_modal_cover";
|
|
document.body.appendChild(modality.cover);
|
|
}
|
|
modality.cover.style.display = mode?"inline-block":"none";
|
|
}
|
|
|
|
function button(text, result, className){
|
|
return "<div role='button' tabindex='0' aria-label='"+text+"' class='webix_popup_button"+(className?(" "+className):"")+"' result='"+result+"' ><div>"+text+"</div></div>";
|
|
}
|
|
|
|
function info(text){
|
|
if (!t.area){
|
|
t.area = document.createElement("DIV");
|
|
t.area.className = "webix_message_area";
|
|
t.area.style[t.position]="5px";
|
|
|
|
document.body.appendChild(t.area);
|
|
}
|
|
t.area.setAttribute("role", "alert");
|
|
t.area.setAttribute("aria-atomic", true);
|
|
t.hide(text.id);
|
|
var message = document.createElement("DIV");
|
|
message.innerHTML = "<div>"+text.text+"</div>";
|
|
message.className = "webix_info webix_" + text.type;
|
|
message.onclick = function(){
|
|
t.hide(text.id);
|
|
text = null;
|
|
};
|
|
|
|
if (webix.$testmode)
|
|
message.className += " webix_no_transition";
|
|
|
|
if (t.position == "bottom" && t.area.firstChild)
|
|
t.area.insertBefore(message,t.area.firstChild);
|
|
else
|
|
t.area.appendChild(message);
|
|
|
|
if (text.expire > 0)
|
|
t.timers[text.id]=window.setTimeout(function(){
|
|
t.hide(text.id);
|
|
}, text.expire);
|
|
|
|
//styling for animation
|
|
message.style.height = message.offsetHeight-2+"px";
|
|
|
|
t.pull[text.id] = message;
|
|
message = null;
|
|
|
|
return text.id;
|
|
}
|
|
function _boxStructure(config, ok, cancel){
|
|
var box = document.createElement("DIV");
|
|
box.className = " webix_modal_box webix_"+config.type;
|
|
box.setAttribute("webixbox", 1);
|
|
box.setAttribute("role", "alertdialog");
|
|
box.setAttribute("aria-label", config.title || "");
|
|
box.setAttribute("tabindex", "0");
|
|
|
|
var inner = '';
|
|
if (config.width)
|
|
box.style.width = config.width+(webix.rules.isNumber(config.width)?"px":"");
|
|
if (config.height)
|
|
box.style.height = config.height+(webix.rules.isNumber(config.height)?"px":"");
|
|
if (config.title)
|
|
inner+='<div class="webix_popup_title">'+config.title+'</div>';
|
|
inner+='<div class="webix_popup_text"><span>'+(config.content?'':config.text)+'</span></div><div class="webix_popup_controls">';
|
|
if (ok || config.ok)
|
|
inner += button(config.ok || "OK", true,"confirm");
|
|
if (cancel || config.cancel)
|
|
inner += button(config.cancel || "Cancel", false);
|
|
if (config.buttons){
|
|
for (var i=0; i<config.buttons.length; i++)
|
|
inner += button(config.buttons[i],i);
|
|
}
|
|
inner += '</div>';
|
|
box.innerHTML = inner;
|
|
|
|
if (config.content){
|
|
var node = config.content;
|
|
if (typeof node == "string")
|
|
node = document.getElementById(node);
|
|
if (node.style.display == 'none')
|
|
node.style.display = "";
|
|
box.childNodes[config.title?1:0].appendChild(node);
|
|
}
|
|
|
|
box.onclick = function(e){
|
|
e = e ||event;
|
|
var source = e.target || e.srcElement;
|
|
if (!source.className) source = source.parentNode;
|
|
if (source.className.indexOf("webix_popup_button")!=-1){
|
|
var result = source.getAttribute("result");
|
|
result = (result == "true")||(result == "false"?false:result);
|
|
callback(config, result);
|
|
}
|
|
e.cancelBubble = true;
|
|
};
|
|
config.box = box;
|
|
if (ok||cancel||config.buttons)
|
|
_webix_msg_cfg = config;
|
|
|
|
return box;
|
|
}
|
|
function _createBox(config, ok, cancel){
|
|
var box = config.tagName ? config : _boxStructure(config, ok, cancel);
|
|
|
|
if (!config.hidden)
|
|
modality(true);
|
|
|
|
webix.toNode(config.container || document.body).appendChild(box);
|
|
|
|
var x = config.left||Math.abs(Math.floor(((window.innerWidth||document.documentElement.offsetWidth) - box.offsetWidth)/2));
|
|
var y = config.top||Math.abs(Math.floor(((window.innerHeight||document.documentElement.offsetHeight) - box.offsetHeight)/2));
|
|
if (config.position == "top")
|
|
box.style.top = "-3px";
|
|
else
|
|
box.style.top = y+'px';
|
|
box.style.left = x+'px';
|
|
//necessary for IE only
|
|
box.onkeydown = modal_key;
|
|
|
|
box.focus();
|
|
if (config.hidden)
|
|
webix.modalbox.hide(box);
|
|
|
|
return box;
|
|
}
|
|
|
|
function alertPopup(config){
|
|
return _createBox(config, true, false);
|
|
}
|
|
function confirmPopup(config){
|
|
return _createBox(config, true, true);
|
|
}
|
|
function boxPopup(config){
|
|
return _createBox(config);
|
|
}
|
|
function box_params(text, type, callback){
|
|
if (typeof text != "object"){
|
|
if (typeof type == "function"){
|
|
callback = type;
|
|
type = "";
|
|
}
|
|
text = {text:text, type:type, callback:callback };
|
|
}
|
|
return text;
|
|
}
|
|
function params(text, type, expire, id){
|
|
if (typeof text != "object")
|
|
text = {text:text, type:type, expire:expire, id:id};
|
|
text.id = text.id||t.uid();
|
|
text.expire = text.expire||t.expire;
|
|
return text;
|
|
}
|
|
webix.alert = function(){
|
|
var text = box_params.apply(this, arguments);
|
|
text.type = text.type || "confirm";
|
|
return alertPopup(text);
|
|
};
|
|
webix.confirm = function(){
|
|
var text = box_params.apply(this, arguments);
|
|
text.type = text.type || "alert";
|
|
return confirmPopup(text);
|
|
};
|
|
webix.modalbox = function(){
|
|
var text = box_params.apply(this, arguments);
|
|
text.type = text.type || "alert";
|
|
return boxPopup(text);
|
|
};
|
|
webix.modalbox.hide = function(node){
|
|
if(node){
|
|
while (node && node.getAttribute && !node.getAttribute("webixbox"))
|
|
node = node.parentNode;
|
|
if (node){
|
|
node.parentNode.removeChild(node);
|
|
}
|
|
}
|
|
|
|
modality(false);
|
|
_webix_msg_cfg = null;
|
|
};
|
|
var t = webix.message = function(text, type, expire, id){
|
|
text = params.apply(this, arguments);
|
|
text.type = text.type||"info";
|
|
|
|
var subtype = text.type.split("-")[0];
|
|
switch (subtype){
|
|
case "alert":
|
|
return alertPopup(text);
|
|
case "confirm":
|
|
return confirmPopup(text);
|
|
case "modalbox":
|
|
return boxPopup(text);
|
|
default:
|
|
return info(text);
|
|
}
|
|
};
|
|
|
|
t.seed = (new Date()).valueOf();
|
|
t.uid = function(){return t.seed++;};
|
|
t.expire = 4000;
|
|
t.keyboard = true;
|
|
t.position = "top";
|
|
t.pull = {};
|
|
t.timers = {};
|
|
|
|
t.hideAll = function(){
|
|
for (var key in t.pull)
|
|
t.hide(key);
|
|
};
|
|
t.hide = function(id){
|
|
var obj = t.pull[id];
|
|
if (obj && obj.parentNode){
|
|
window.setTimeout(function(){
|
|
obj.parentNode.removeChild(obj);
|
|
obj = null;
|
|
},2000);
|
|
//styling for animation
|
|
obj.style.height = 0;
|
|
obj.className+=" hidden";
|
|
t.area.removeAttribute("role");
|
|
|
|
if(t.timers[id])
|
|
window.clearTimeout(t.timers[id]);
|
|
delete t.pull[id];
|
|
}
|
|
};
|
|
})();
|
|
webix.debug_ready(function(){
|
|
|
|
var ignore = {
|
|
"_inner":true,
|
|
"awidth":true,
|
|
"cheight":true,
|
|
"bheight":true,
|
|
"aheight":true
|
|
};
|
|
|
|
function get_inspector_config(view){
|
|
var values={};
|
|
var options=[];
|
|
view = webix.$$(view);
|
|
|
|
for (var key in view.config){
|
|
if (ignore[key]) continue;
|
|
|
|
if (typeof view.config[key] == "object") continue;
|
|
if (typeof view.config[key] == "undefined") continue;
|
|
if (typeof view.config[key] == "function") continue;
|
|
|
|
if (key == "view" || key == "id")
|
|
options.push({ label:key, id:key});
|
|
else
|
|
options.push({ label:key, type:"text", id:key});
|
|
|
|
if (view.defaults[key] == view.config[key])
|
|
options[options.length - 1].css = { "color" : "#888" };
|
|
|
|
values[key] = view.config[key];
|
|
}
|
|
options.sort(function(a,b){
|
|
if (!a.css && b.css) return -1;
|
|
if (a.css && !b.css) return 1;
|
|
return (a.id > b.id) ? 1 : ((a.id == b.id) ? 0 : -1);
|
|
});
|
|
|
|
return { elements:options, data:values, head:" ["+view.name+"] <strong>"+view._settings.id+"</strong>" };
|
|
}
|
|
|
|
function create_inspector(){
|
|
if (!webix.$$("webix_debug_inspector_win"))
|
|
webix.ui({
|
|
id:"webix_debug_inspector_win",
|
|
view:"window",
|
|
top:2, left: 0, width:350, height:350,
|
|
head:false, autofit:false,
|
|
body:{cols:[
|
|
{ width:10},
|
|
{type:"clean", rows:[
|
|
{ view:"toolbar", elements:[
|
|
{ view:"label", value:"", id:"webix_debug_inspector_head" },
|
|
{ view:"button", width:100, value:"Hide", type:"custom", click:function(){
|
|
webix.debug_inspect();
|
|
}}
|
|
]},
|
|
{
|
|
id:"webix_debug_inspector", nameWidth:150,
|
|
view:"property", scroll:"y",
|
|
elements:[],
|
|
on:{
|
|
onaftereditstop:function(state, editor){
|
|
if (state.old == state.value) return;
|
|
|
|
var value = state.value;
|
|
if (value === "true" || value === "false"){
|
|
value = (value === "true");
|
|
} else {
|
|
var intvalue = parseInt(value,10);
|
|
if (intvalue == value)
|
|
value = intvalue;
|
|
}
|
|
|
|
var view = webix.$$(this.config.view);
|
|
view.define(editor.id, value);
|
|
if (view.refreshColumns)
|
|
view.refreshColumns();
|
|
else if (view.refresh)
|
|
view.refresh();
|
|
|
|
view.resize();
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}]
|
|
}
|
|
});
|
|
}
|
|
function show_inspector(view, ev){
|
|
create_inspector();
|
|
var win = webix.$$("webix_debug_inspector_win");
|
|
|
|
if (view){
|
|
var config = get_inspector_config(view);
|
|
var winx = document.body.offsetWidth;
|
|
var winy = document.body.offsetHeight;
|
|
var pos = ev?webix.html.pos(ev):{x:0,y:0};
|
|
|
|
win.define("height", Math.max(350, winy-4));
|
|
win.resize();
|
|
|
|
var props = webix.$$("webix_debug_inspector");
|
|
props.define("elements", config.elements);
|
|
props.define("view", view);
|
|
|
|
win.show({ x:(pos.x > winx/2 )?0:(winx-370), y:0 });
|
|
webix.$$("webix_debug_inspector").setValues(config.data);
|
|
webix.$$("webix_debug_inspector_head").setValue(config.head);
|
|
} else
|
|
win.hide();
|
|
}
|
|
webix.debug_inspect = show_inspector;
|
|
|
|
function infi(value){
|
|
if (value >= 100000)
|
|
return "Any";
|
|
return value;
|
|
}
|
|
function log_level(data, prefix, now){
|
|
window.console.log((data == now?">>":" ")+prefix + data.name+" / " +data.config.id);
|
|
prefix+=" ";
|
|
if (data._cells)
|
|
for (var i=0; i<data._cells.length; i++){
|
|
log_level(data._cells[i], prefix, now);
|
|
}
|
|
if (data._head_cell)
|
|
log_level(data._head_cell, prefix, now);
|
|
|
|
if (data._body_cell)
|
|
log_level(data._body_cell, prefix, now);
|
|
}
|
|
|
|
webix.ui({
|
|
view:"contextmenu",
|
|
id:"webix:debugmenu",
|
|
on:{
|
|
onBeforeShow:function(e){
|
|
if (!e.ctrlKey) return false;
|
|
|
|
var view = webix.html.locate(e, "view_id");
|
|
if (!view) return false;
|
|
this.config.lastTarget = view;
|
|
|
|
webix.blockEvent();
|
|
webix.delay(function(){ webix.unblockEvent(); });
|
|
},
|
|
onShow:function(){
|
|
var view = webix.$$(this.config.lastTarget);
|
|
var info = "<span style='color:#888'>"+view._settings.id + "<sup style='float:right'>["+view.name+"]</sup></span>";
|
|
document.getElementById("webix_debug_cmx").innerHTML = info;
|
|
}
|
|
},
|
|
data:[
|
|
"<div id='webix_debug_cmx'></div>",
|
|
{ id:"inspect", value:"Inspect"},
|
|
{ id:"docs", value:"Documentation"},
|
|
{
|
|
value:"Log to Console", submenu:[
|
|
{ id:"size", value:"Sizes" },
|
|
{ id:"tree", value:"Tree" },
|
|
{ id:"dump", value:"Dump"}
|
|
]
|
|
}
|
|
],
|
|
click:function(id, ev){
|
|
//mixing two object result in confusion
|
|
var obj = webix.$$(this.config.lastTarget);
|
|
|
|
if (id == "dump"){
|
|
window.console.info("\n"+obj.name+" / "+obj.config.id);
|
|
window.console.log("\nView: ",obj,", Config: ", obj.config, ", Data: ", obj.data);
|
|
window.console.log(obj.$view);
|
|
}
|
|
|
|
if (id == "tree"){
|
|
|
|
var now = obj;
|
|
while (obj.getParentView())
|
|
obj = obj.getParentView();
|
|
|
|
window.console.log("");
|
|
log_level(obj, "", now);
|
|
}
|
|
|
|
if (id == "size"){
|
|
window.console.info("");
|
|
window.console.info("\n"+obj.name+" / "+obj.config.id);
|
|
window.console.info("\n[min] ", obj.config.width, " x ", obj.config.height);
|
|
var sizes = obj.$getSize(0,0);
|
|
window.console.info("[max] ", infi(sizes[1]), " x ", infi(sizes[3])+(obj.config.autoheight?", auto height":""));
|
|
window.console.info("[gravity] ", obj.config.gravity);
|
|
|
|
window.console.info("\n[content] ", obj._content_width, " x ", obj._content_height);
|
|
window.console.info("[last set] ", obj._last_size[0], " x ", obj._last_size[1]);
|
|
if (obj._settings._inner)
|
|
window.console.info("\n[borders] ", "left:", !obj._settings._inner.left,"\ttop:", !obj._settings._inner.top, "\tright:", !obj._settings._inner.right, "\tbottom:", !obj._settings._inner.bottom);
|
|
else
|
|
window.console.info("\n[borders] none");
|
|
}
|
|
|
|
if (id == "docs")
|
|
window.open("http://docs.webix.com/api__refs__ui."+obj.name+".html","__blank");
|
|
|
|
if (id == "inspect"){
|
|
show_inspector(this.config.lastTarget, ev);
|
|
}
|
|
},
|
|
master:document.body
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
webix.protoUI({
|
|
name:"carousel",
|
|
defaults:{
|
|
scrollSpeed:"300ms",
|
|
type: "clean",
|
|
navigation: {},
|
|
animate:true
|
|
},
|
|
$init:function(config){
|
|
this._viewobj.className += " webix_carousel";
|
|
this._layout = null;
|
|
this._dataobj = null;
|
|
this._active_cell = 0;
|
|
this.$ready.unshift(this._initLayout);
|
|
this.$ready.push(this._after_init_call);
|
|
},
|
|
addView: function(view, index){
|
|
var t = this._layout.addView(view, index);
|
|
this._fix_after_view_add();
|
|
return t;
|
|
},
|
|
removeView: function(id){
|
|
this._layout.removeView(id);
|
|
this._fix_after_view_add();
|
|
},
|
|
_replace: function(new_view,target_id){
|
|
this._layout._replace(new_view, target_id);
|
|
this._fix_after_view_add();
|
|
},
|
|
_fix_after_view_add: function(){
|
|
this._cells = this._layout._cells;
|
|
this._renderPanel();
|
|
this.setActiveIndex(Math.min(this._active_cell, this._cells.length-1));
|
|
},
|
|
_initLayout: function(){
|
|
if(this._layout && this._layout.destructor)
|
|
this._layout.destructor();
|
|
|
|
var layout = "";
|
|
|
|
if(this.config.cols){
|
|
layout = "cols";
|
|
this._vertical_orientation = 0;
|
|
}
|
|
else{
|
|
layout = "rows";
|
|
this._vertical_orientation = 1;
|
|
}
|
|
|
|
var config = {borderless: true, type: "clean"};
|
|
config[layout] = webix.copy(this._settings[layout]);
|
|
var layoutProp = ["type", "margin", "marginX", "marginY", "padding", "paddingX", "paddingY"];
|
|
var layoutConfig = {};
|
|
for(var i=0; i< layoutProp.length; i++){
|
|
if(this._settings[layoutProp[i]]){
|
|
layoutConfig[layoutProp[i]] = this._settings[layoutProp[i]];
|
|
}
|
|
}
|
|
webix.extend(config,layoutConfig,true);
|
|
|
|
this._layout = webix.ui._view(config);
|
|
this._layout._parent_cell = this;
|
|
|
|
this._viewobj.appendChild(this._layout._viewobj);
|
|
this._cells = this._layout._cells;
|
|
|
|
this._layout._show = webix.bind(webix.ui.carousel.prototype._show,this);
|
|
this._layout.adjustScroll = webix.bind(webix.ui.carousel.prototype.adjustScroll,this);
|
|
|
|
webix.attachEvent("onReconstruct", webix.bind(function(view){
|
|
if(view == this._layout)
|
|
this._setScroll();
|
|
},this));
|
|
|
|
this._contentobj = this._viewobj.firstChild;
|
|
},
|
|
_onKeyPress:function(code, e){
|
|
if(this._settings.navigation.items && e.target.getAttribute("role") === "tab")
|
|
this._moveActive(code, e);
|
|
|
|
webix.ui.baseview.prototype._onKeyPress.call(this, code, e);
|
|
},
|
|
getChildViews:function(){
|
|
return [this._layout];
|
|
},
|
|
getLayout:function(){
|
|
return this._layout;
|
|
},
|
|
_after_init_call:function(){
|
|
this._contentobj.setAttribute("touch_scroll", (this._vertical_orientation?"y":"x"));
|
|
|
|
this._layout.attachEvent("onAfterScroll",webix.bind(function(view){
|
|
this.callEvent("onShow",[this.getActiveId()]);
|
|
},this));
|
|
|
|
webix.ui.each(this._layout, function(view){
|
|
view._viewobj.setAttribute("role", "tabpanel");
|
|
});
|
|
},
|
|
adjustScroll:function(matrix){
|
|
var size = (this._vertical_orientation?this._content_height:this._content_width);
|
|
|
|
var correction;
|
|
if (this._vertical_orientation) {
|
|
correction = Math.round(matrix.f/size);
|
|
matrix.f = correction*size;
|
|
} else {
|
|
correction = Math.round(matrix.e/size);
|
|
matrix.e = correction*size;
|
|
}
|
|
|
|
this._active_cell = - correction;
|
|
|
|
if(this._settings.navigation)
|
|
this._renderNavItems();
|
|
|
|
return true;
|
|
},
|
|
_show:function(obj){
|
|
var i, layout, _nextCell, _size, x, y;
|
|
_nextCell = -1;
|
|
layout = this._layout;
|
|
for (i=0; i < layout._cells.length; i++){
|
|
if (layout._cells[i]==obj){
|
|
_nextCell = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (_nextCell < 0 || _nextCell == this._active_cell)
|
|
return;
|
|
|
|
this._active_cell = _nextCell;
|
|
_size = (layout._vertical_orientation?this._content_height:this._content_width);
|
|
|
|
x = -(layout._vertical_orientation?0:_nextCell*_size);
|
|
y = -(layout._vertical_orientation?_nextCell*_size:0);
|
|
|
|
this.scrollTo(x,y);
|
|
this.callEvent("onShow",[layout._cells[this._active_cell]._settings.id]);
|
|
if(this._settings.navigation)
|
|
this._renderPanel();
|
|
},
|
|
scrollTo:function(x,y){
|
|
if (webix.Touch && webix.animate.isSupported() && this._settings.animate)
|
|
webix.Touch._set_matrix(this._contentobj, x,y, this._settings.scrollSpeed||"100ms");
|
|
else{
|
|
this._contentobj.style.marginLeft = x+"px";
|
|
this._contentobj.style.marginTop = y+"px";
|
|
}
|
|
},
|
|
navigation_setter:function(config){
|
|
this._mergeSettings(config,{
|
|
type: "corner",
|
|
buttons: true,
|
|
items: true
|
|
});
|
|
return config;
|
|
},
|
|
showNext:function(){
|
|
if (this._active_cell < this._layout._cells.length - 1)
|
|
this.setActiveIndex(this._active_cell+1);
|
|
},
|
|
showPrev:function(){
|
|
if (this._active_cell > 0)
|
|
this.setActiveIndex(this._active_cell-1);
|
|
},
|
|
setActiveIndex:function(value){
|
|
webix.assert(value < this._layout._cells.length, "Not existing index in collection");
|
|
|
|
var id = this._layout._cells[value]._settings.id;
|
|
webix.$$(id).show();
|
|
},
|
|
getActiveIndex:function(){
|
|
return this._active_cell;
|
|
},
|
|
$getSize:function(dx, dy){
|
|
var layoutSizes = this._layout.$getSize(0, 0);
|
|
var selfSizes = webix.ui.view.prototype.$getSize.call(this, dx, dy);
|
|
if(this._layout._vertical_orientation){
|
|
selfSizes[0] = Math.max(selfSizes[0], layoutSizes[0]);
|
|
selfSizes[1] = Math.min(selfSizes[1], layoutSizes[1]);
|
|
|
|
} else{
|
|
selfSizes[2] = Math.max(selfSizes[2], layoutSizes[2]);
|
|
selfSizes[3] = Math.min(selfSizes[3], layoutSizes[3]);
|
|
}
|
|
return selfSizes;
|
|
},
|
|
$setSize:function(x,y){
|
|
var layout = this._layout;
|
|
var c = layout._cells.length;
|
|
|
|
var changed = webix.ui.view.prototype.$setSize.call(this,x,y);
|
|
var yc = this._content_height*(layout._vertical_orientation?c:1);
|
|
var xc = this._content_width*(layout._vertical_orientation?1:c);
|
|
|
|
if (changed){
|
|
this._contentobj.style.height = yc+"px";
|
|
this._contentobj.style.width = xc+"px";
|
|
layout.$setSize(xc,yc);
|
|
this._setScroll();
|
|
} else
|
|
layout.$setSize(xc,yc);
|
|
},
|
|
_setScroll: function(){
|
|
var layout = this._layout;
|
|
var activeCell = this._active_cell||0;
|
|
var size = (layout._vertical_orientation?this._content_height:this._content_width);
|
|
|
|
var x = -(layout._vertical_orientation?0:activeCell*size);
|
|
var y = -(layout._vertical_orientation?activeCell*size:0);
|
|
|
|
|
|
this.scrollTo(x,y);
|
|
|
|
if(this._settings.navigation)
|
|
this._renderPanel();
|
|
},
|
|
getActiveId:function(){
|
|
var cell = this._layout._cells[this._active_cell];
|
|
return cell?cell._settings.id:null;
|
|
},
|
|
setActive:function(value){
|
|
webix.$$(value).show();
|
|
}
|
|
}, webix.EventSystem,webix.NavigationButtons, webix.ui.view);
|
|
|
|
/*
|
|
UI:Uploader
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
webix.type(webix.ui.list, {
|
|
name:"uploader",
|
|
template:"#name# {common.removeIcon()}{common.percent()}<div style='float:right'>#sizetext#</div>",
|
|
percent:function(obj){
|
|
if (obj.status == 'transfer')
|
|
return "<div style='width:60px; text-align:center; float:right'>"+obj.percent+"%</div>";
|
|
return "<div class='webix_upload_"+obj.status+"'><span class='"+(obj.status =="error"?"error_icon":"fa-check webix_icon")+"'></span></div>";
|
|
},
|
|
removeIcon:function(obj){
|
|
return "<div class='webix_remove_upload'><span class='cancel_icon'></span></div>";
|
|
},
|
|
on_click:{
|
|
"webix_remove_upload":function(ev, id){
|
|
webix.$$(this.config.uploader).files.remove(id);
|
|
}
|
|
}
|
|
});
|
|
|
|
webix.UploadDriver = {
|
|
flash: {
|
|
$render: function(render_config) {
|
|
|
|
if (!window.swfobject)
|
|
webix.require("legacy/swfobject.js", true); // sync loading
|
|
|
|
var config = this._settings;
|
|
config.swfId = (config.swfId||"webix_swf_"+webix.uid());
|
|
|
|
this._getBox().innerHTML += "<div class='webix_upload_flash'><div id='"+config.swfId+"'></div></div>";
|
|
this._upload_area = this._getBox().lastChild;
|
|
|
|
// add swf object
|
|
swfobject.embedSWF(webix.codebase+"/legacy/uploader.swf", config.swfId, "100%", "100%", "9", null, {
|
|
uploaderId: config.id,
|
|
ID: config.swfId,
|
|
enableLogs:(config.enableLogs?"1":""),
|
|
paramName:(config.inputName),
|
|
multiple:(config.multiple?"Y":"")
|
|
}, {wmode:"transparent"});
|
|
|
|
var v = swfobject.getFlashPlayerVersion();
|
|
|
|
webix._event(this._viewobj, "click", webix.bind(function() {
|
|
var now_date = new Date();
|
|
if (now_date - (this._upload_timer_click||0) > 250){
|
|
this.fileDialog();
|
|
}
|
|
}, this));
|
|
|
|
this.files.attachEvent("onBeforeDelete", webix.bind(this._stop_file,this));
|
|
},
|
|
$applyFlash: function(name,params){
|
|
return this[name].apply(this,params);
|
|
},
|
|
getSwfObject: function(){
|
|
return swfobject.getObjectById(this._settings.swfId);
|
|
},
|
|
fileDialog:function(){
|
|
if(this.getSwfObject())
|
|
this.getSwfObject().showDialog();
|
|
},
|
|
send: function(id){
|
|
if (typeof id == "function"){
|
|
this._last_assigned_upload_callback = id;
|
|
id = 0;
|
|
}
|
|
|
|
if (!id){
|
|
var order = this.files.data.order;
|
|
var complete = true;
|
|
if (order.length)
|
|
for (var i=0; i<order.length; i++){
|
|
complete = this.send(order[i])&&complete;
|
|
}
|
|
|
|
if (complete)
|
|
this._upload_complete();
|
|
|
|
return;
|
|
}
|
|
var item = this.files.getItem(id);
|
|
if (item.status !== 'client')
|
|
return false;
|
|
item.status = 'transfer';
|
|
|
|
if(this.getSwfObject()){
|
|
var url = this._get_active_url(item);
|
|
var details = webix.extend(item.formData||{},this._settings.formData||{});
|
|
this.getSwfObject().upload(id, url, details);
|
|
}
|
|
return true;
|
|
|
|
},
|
|
$beforeAddFileToQueue: function( id, name, size ){
|
|
|
|
var type = name.split(".").pop();
|
|
var format = this._format_size(size);
|
|
return this.callEvent("onBeforeFileAdd", [{
|
|
id: id,
|
|
name:name,
|
|
size:size,
|
|
sizetext:format,
|
|
type:type
|
|
}]);
|
|
},
|
|
$addFileToQueue: function(id, name, size){
|
|
if(this.files.exists(id))
|
|
return false;
|
|
if (!this._settings.multiple)
|
|
this.files.clearAll();
|
|
var type = name.split(".").pop();
|
|
var format = this._format_size(size);
|
|
var file_struct = {
|
|
name:name,
|
|
id: id,
|
|
size:size,
|
|
sizetext:format,
|
|
type:type,
|
|
status:"client"
|
|
};
|
|
this.files.add(file_struct);
|
|
this.callEvent("onAfterFileAdd", [file_struct]);
|
|
|
|
if (id && this._settings.autosend)
|
|
this.send(id);
|
|
},
|
|
stopUpload: function(id){
|
|
this._stop_file(id);
|
|
},
|
|
_stop_file: function(id) {
|
|
var item = this.files.getItem(id);
|
|
if(item.status == "transfer"){
|
|
this.getSwfObject().uploadStop(id);
|
|
item.status = "client";
|
|
}
|
|
},
|
|
$onUploadComplete: function(){
|
|
if(this._settings.autosend){
|
|
this._upload_complete();
|
|
}
|
|
},
|
|
$onUploadSuccess: function(id,name,response){
|
|
var item = this.files.getItem(id);
|
|
if(item){
|
|
item.status = "server";
|
|
item.progress = 100;
|
|
if(response.text && (typeof response.text == "string")){
|
|
|
|
|
|
webix.DataDriver.json.toObject(response.text);
|
|
|
|
webix.extend(item,response,true);
|
|
}
|
|
this.callEvent("onFileUpload", [item,response]);
|
|
this.callEvent("onChange", []);
|
|
this.files.updateItem(id);
|
|
}
|
|
},
|
|
$onUploadFail: function(id){
|
|
var item = this.files.getItem(id);
|
|
item.status = "error";
|
|
delete item.percent;
|
|
this.files.updateItem(id);
|
|
this.callEvent("onFileUploadError", [item, ""]);
|
|
}
|
|
},
|
|
html5: {
|
|
$render: function(config) {
|
|
if (this._upload_area){
|
|
//firstChild is webix_el_box container, which have relative position
|
|
//as result, file control is placed under the button and not in the top corner
|
|
this._contentobj.firstChild.appendChild(this._upload_area);
|
|
return;
|
|
}
|
|
this.files.attachEvent("onBeforeDelete", this._stop_file);
|
|
|
|
var input_config = {
|
|
"type": "file",
|
|
"class": "webix_hidden_upload",
|
|
tabindex:-1
|
|
};
|
|
|
|
if (this._settings.accept)
|
|
input_config.accept = this._settings.accept;
|
|
|
|
if (this._settings.multiple)
|
|
input_config.multiple = "true";
|
|
|
|
if (this._settings.directory) {
|
|
input_config.webkitdirectory = "true";
|
|
input_config.mozdirectory = "true";
|
|
input_config.directory = "true";
|
|
}
|
|
|
|
var f = webix.html.create("input", input_config);
|
|
this._upload_area = this._contentobj.firstChild.appendChild(f);
|
|
|
|
webix._event(this._viewobj, 'drop', webix.bind(function(e) {
|
|
this._drop(e);
|
|
webix.html.preventEvent(e);
|
|
}, this));
|
|
webix._event(f, 'change', webix.bind(function() {
|
|
this._add_files(f.files);
|
|
|
|
if (webix.env.isIE) {
|
|
var t = document.createElement("form");
|
|
t.appendChild(this._upload_area);
|
|
t.reset();
|
|
this._contentobj.firstChild.appendChild(f);
|
|
} else
|
|
f.value = "";
|
|
}, this));
|
|
webix._event(this._viewobj, "click", webix.bind(function() {
|
|
var now_date = new Date();
|
|
if (now_date - (this._upload_timer_click || 0) > 250) {
|
|
this.fileDialog();
|
|
}
|
|
}, this));
|
|
|
|
webix._event(this._viewobj, 'dragenter', webix.html.preventEvent);
|
|
webix._event(this._viewobj, 'dragexit', webix.html.preventEvent);
|
|
webix._event(this._viewobj, 'dragover', webix.html.preventEvent);
|
|
},
|
|
_directoryEntry: function(value) {
|
|
return value.isDirectory;
|
|
},
|
|
_directoryDrop: function(item, state, path) {
|
|
if (item.isFile){
|
|
item.file(function(file){
|
|
state.addFile(file, null, null, { name : path+"/"+file.name });
|
|
});
|
|
} else if (item.isDirectory) {
|
|
// Get folder contents
|
|
var dirReader = item.createReader();
|
|
dirReader.readEntries(function(entries){
|
|
for (var i = 0; i < entries.length; i++){
|
|
state._directoryDrop(entries[i], state, (path ? (path + "/") : "") + item.name);
|
|
}
|
|
});
|
|
}
|
|
},
|
|
// adding files by drag-n-drop
|
|
_drop: function(e) {
|
|
var files = e.dataTransfer.files;
|
|
var items = e.dataTransfer.items;
|
|
|
|
if (this.callEvent('onBeforeFileDrop', [files, e])) {
|
|
for (var i = 0; i < items.length; i++) {
|
|
//https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem/webkitGetAsEntry
|
|
var item = items[i];
|
|
if (this._settings.directory && item.webkitGetAsEntry){
|
|
item = item.webkitGetAsEntry();
|
|
if (item.isDirectory){
|
|
this._directoryDrop(item, this, "");
|
|
continue;
|
|
}
|
|
}
|
|
this.addFile(files[i]);
|
|
}
|
|
}
|
|
this.callEvent("onAfterFileDrop", [files, e]);
|
|
},
|
|
fileDialog:function(context){
|
|
this._upload_timer_click = new Date();
|
|
this._last_file_context = context;
|
|
var inputs = this._viewobj.getElementsByTagName("INPUT");
|
|
inputs[inputs.length-1].click();
|
|
},
|
|
send: function(id){
|
|
//alternative syntx send(callback)
|
|
if (typeof id == "function"){
|
|
this._last_assigned_upload_callback = id;
|
|
id = 0;
|
|
}
|
|
|
|
if (!id){
|
|
var order = this.files.data.order;
|
|
var complete = true;
|
|
|
|
if (order.length)
|
|
for (var i=0; i<order.length; i++)
|
|
complete = (!this.send(order[i])) && complete;
|
|
|
|
if (complete)
|
|
this._upload_complete();
|
|
|
|
return;
|
|
}
|
|
|
|
var item = this.files.getItem(id);
|
|
if (item.status !== 'client') return false;
|
|
|
|
webix.assert(this._settings.upload, "You need to define upload url for uploader component");
|
|
item.status = 'transfer';
|
|
|
|
var formData = new FormData();
|
|
|
|
if (item.folder) {
|
|
for (var i = 0; i < item.folder.length; i++){
|
|
formData.append(this.config.inputName + i, item.folder[i], item.folder[i].webkitRelativePath);
|
|
}
|
|
} else {
|
|
formData.append(this.config.inputName, item.file, item.name);
|
|
if (this._settings.directory)
|
|
formData.append(this.config.inputName+"_fullpath", item.name);
|
|
}
|
|
|
|
var headers = {};
|
|
var details = webix.extend(item.formData||{},this._settings.formData||{});
|
|
|
|
var xhr = new XMLHttpRequest();
|
|
var url = this._get_active_url(item);
|
|
if(webix.callEvent("onBeforeAjax",["POST", url, details, xhr, headers, formData])){
|
|
for (var key in details)
|
|
formData.append(key, details[key]);
|
|
|
|
item.xhr = xhr;
|
|
|
|
xhr.upload.addEventListener('progress', webix.bind(function(e){ this.$updateProgress(id, e.loaded/e.total*100); }, this), false);
|
|
xhr.onload = webix.bind(function(e){ if (!xhr.aborted) this._file_complete(id); }, this);
|
|
xhr.open('POST', url, true);
|
|
|
|
for (var key in headers)
|
|
xhr.setRequestHeader(key, headers[key]);
|
|
|
|
xhr.send(formData);
|
|
}
|
|
|
|
this.$updateProgress(id, 0);
|
|
return true;
|
|
},
|
|
|
|
|
|
_file_complete: function(id) {
|
|
var item = this.files.getItem(id);
|
|
if (item){
|
|
var response = null;
|
|
if(item.xhr.status < 400){
|
|
response = webix.DataDriver[this._settings.datatype||"json"].toObject(item.xhr.responseText);
|
|
}
|
|
if (!response || response.status == "error"){
|
|
item.status = "error";
|
|
delete item.percent;
|
|
this.files.updateItem(id);
|
|
this.callEvent("onFileUploadError", [item, response]);
|
|
} else {
|
|
this._complete(id, response);
|
|
}
|
|
delete item.xhr;
|
|
}
|
|
},
|
|
stopUpload: function(id){
|
|
webix.bind(this._stop_file,this.files)(id);
|
|
},
|
|
_stop_file: function(id) {
|
|
var item = this.getItem(id);
|
|
if (typeof(item.xhr) !== 'undefined'){
|
|
item.xhr.aborted = true;
|
|
item.xhr.abort();
|
|
delete item.xhr;
|
|
item.status = "client";
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
webix.protoUI({
|
|
name:"uploader",
|
|
defaults:{
|
|
autosend:true,
|
|
multiple:true,
|
|
inputName:"upload"
|
|
},
|
|
$cssName:"button",
|
|
_allowsClear:true,
|
|
on_click:{
|
|
//don't fire extra onItemClick events, visible button will do it
|
|
"webix_hidden_upload":function(){ return false; }
|
|
},
|
|
//will be redefined by upload driver
|
|
send:function(){},
|
|
fileDialog:function(){},
|
|
stopUpload:function(){},
|
|
|
|
$init:function(config){
|
|
var driver = webix.UploadDriver.html5;
|
|
this.files = new webix.DataCollection();
|
|
|
|
// browser doesn't support XMLHttpRequest2
|
|
if (webix.isUndefined(XMLHttpRequest) || webix.isUndefined((new XMLHttpRequest()).upload))
|
|
driver = webix.UploadDriver.flash;
|
|
|
|
webix.assert(driver,"incorrect driver");
|
|
webix.extend(this, driver, true);
|
|
},
|
|
$setSize:function(x,y){
|
|
if (webix.ui.view.prototype.$setSize.call(this,x,y)){
|
|
this.render();
|
|
}
|
|
},
|
|
apiOnly_setter:function(value){
|
|
webix.delay(this.render, this);
|
|
return (this.$apiOnly=value);
|
|
},
|
|
_add_files: function(files){
|
|
for (var i = 0; i < files.length; i++)
|
|
this.addFile(files[i]);
|
|
|
|
},
|
|
link_setter:function(value){
|
|
if (value)
|
|
webix.delay(function(){
|
|
var view = webix.$$(this._settings.link);
|
|
if (!view){
|
|
var top = this.getTopParentView();
|
|
if (top.$$)
|
|
view = top.$$(this._settings.link);
|
|
}
|
|
|
|
if (view.sync && view.filter)
|
|
view.sync(this.files);
|
|
else if (view.setValues)
|
|
this.files.data.attachEvent("onStoreUpdated", function(){
|
|
view.setValues(this);
|
|
});
|
|
view._settings.uploader = this._settings.id;
|
|
}, this);
|
|
return value;
|
|
},
|
|
addFile:function(name, size, type, extra){
|
|
var file = null;
|
|
if (typeof name == "object"){
|
|
file = name;
|
|
name = file.name;
|
|
size = file.size;
|
|
}
|
|
|
|
var format = this._format_size(size);
|
|
type = type || name.split(".").pop();
|
|
|
|
var file_struct = {
|
|
file: file,
|
|
name: name,
|
|
id: webix.uid(),
|
|
size: size,
|
|
sizetext: format,
|
|
type: type,
|
|
context: this._last_file_context,
|
|
status: "client"
|
|
};
|
|
|
|
if (this._settings.directory && file.webkitRelativePath)
|
|
file_struct.name = file.webkitRelativePath;
|
|
|
|
if (extra)
|
|
webix.extend(file_struct, extra, true);
|
|
|
|
if (this.callEvent("onBeforeFileAdd", [file_struct])){
|
|
if (!this._settings.multiple)
|
|
this.files.clearAll();
|
|
|
|
var id = this.files.add(file_struct);
|
|
this.callEvent("onAfterFileAdd", [file_struct]);
|
|
if (id && this._settings.autosend)
|
|
this.send(id);
|
|
}
|
|
|
|
return file_struct;
|
|
},
|
|
|
|
_get_active_url:function(item){
|
|
var url = this._settings.upload;
|
|
var urldata = webix.extend(item.urlData||{},this._settings.urlData||{});
|
|
if (url && urldata){
|
|
var subline = [];
|
|
for (var key in urldata)
|
|
subline.push(encodeURIComponent(key)+"="+encodeURIComponent(urldata[key]));
|
|
|
|
if (subline.length)
|
|
url += ((url.indexOf("?") ==-1) ? "?" : "&") + subline.join("&");
|
|
}
|
|
return url;
|
|
},
|
|
|
|
addDropZone:function(id, hover_text){
|
|
var node = webix.toNode(id);
|
|
var extra_css = "";
|
|
if (hover_text)
|
|
extra_css = " "+webix.html.createCss({ content:'"'+hover_text+'"' }, ":before");
|
|
|
|
var fullcss = "webix_drop_file"+extra_css;
|
|
var timer = null;
|
|
|
|
//web
|
|
webix._event(node,"dragover", webix.html.preventEvent);
|
|
webix._event(node,"dragover", function(e){
|
|
webix.html.addCss(node, fullcss, true);
|
|
if (timer){
|
|
clearTimeout(timer);
|
|
timer = null;
|
|
}
|
|
});
|
|
webix._event(node,"dragleave", function(e){
|
|
//when moving over html child elements
|
|
//browser will issue dragleave and dragover events
|
|
//ignore first one
|
|
timer = setTimeout(function(){
|
|
webix.html.removeCss(node, fullcss);
|
|
}, 150);
|
|
});
|
|
|
|
webix._event(node,"drop", webix.bind(function(e){
|
|
webix.html.removeCss(node, fullcss);
|
|
this._drop(e);
|
|
return webix.html.preventEvent(e);
|
|
}, this));
|
|
},
|
|
|
|
_format_size: function(size) {
|
|
var index = 0;
|
|
while (size > 1024){
|
|
index++;
|
|
size = size/1024;
|
|
}
|
|
return Math.round(size*100)/100+" "+webix.i18n.fileSize[index];
|
|
},
|
|
|
|
_complete: function(id, response) {
|
|
if (response.status != 'error') {
|
|
var item = this.files.getItem(id);
|
|
|
|
item.status = "server";
|
|
item.progress = 100;
|
|
webix.extend(item, response, true);
|
|
|
|
this.callEvent("onFileUpload", [item, response]);
|
|
this.callEvent("onChange", []);
|
|
this.files.updateItem(id);
|
|
}
|
|
|
|
if (this.isUploaded())
|
|
this._upload_complete(response);
|
|
},
|
|
_upload_complete:function(response){
|
|
this.callEvent("onUploadComplete", [response]);
|
|
if (this._last_assigned_upload_callback){
|
|
this._last_assigned_upload_callback.call(this, response);
|
|
this._last_assigned_upload_callback = 0;
|
|
}
|
|
},
|
|
isUploaded:function(){
|
|
var order = this.files.data.order;
|
|
for (var i=0; i<order.length; i++)
|
|
if (this.files.getItem(order[i]).status != "server")
|
|
return false;
|
|
|
|
return true;
|
|
},
|
|
$onUploadComplete: function(){
|
|
|
|
},
|
|
$updateProgress: function(id, percent) {
|
|
var item = this.files.getItem(id);
|
|
item.percent = Math.round(percent);
|
|
this.files.updateItem(id);
|
|
},
|
|
setValue:function(value){
|
|
if (typeof value == "string" && value)
|
|
value = { value:value, status:"server" };
|
|
|
|
this.files.clearAll();
|
|
if (value)
|
|
this.files.parse(value);
|
|
|
|
this.callEvent("onChange", []);
|
|
},
|
|
getValue:function(){
|
|
var data = [];
|
|
this.files.data.each(function(obj){
|
|
if (obj.status == "server")
|
|
data.push(obj.value||obj.name);
|
|
});
|
|
|
|
return data.join(",");
|
|
}
|
|
|
|
}, webix.ui.button);
|
|
|
|
webix.html.addMeta = function(name, value){
|
|
document.getElementsByTagName('head').item(0).appendChild(webix.html.create("meta",{
|
|
name:name,
|
|
content:value
|
|
}));
|
|
|
|
};
|
|
|
|
(function(){
|
|
|
|
var orientation = function(){
|
|
var new_orientation = !!(window.orientation%180);
|
|
if (webix.ui.orientation === new_orientation) return;
|
|
webix.ui.orientation = new_orientation;
|
|
webix.callEvent("onRotate", [new_orientation]);
|
|
};
|
|
if(webix.env.touch){
|
|
webix.ui.orientation = !!((webix.isUndefined(window.orientation)?90:window.orientation)%180);
|
|
webix.event(window, ("onorientationchange" in window ?"orientationchange":"resize"), orientation);
|
|
}
|
|
|
|
|
|
if(webix.env.isFF && window.matchMedia){
|
|
window.matchMedia("(orientation: portrait)").addListener(function() {webix.ui.orientation = false; });
|
|
window.matchMedia("(orientation: landscape)").addListener(function() { webix.ui.orientation = true; });
|
|
}
|
|
webix.ui.fullScreen = function(){
|
|
if (!webix.env.touch) return;
|
|
|
|
webix.html.addMeta("apple-mobile-web-app-capable","yes");
|
|
webix.html.addMeta("viewport","initial-scale=1, maximum-scale=1, user-scalable=no");
|
|
|
|
//in ios5 we can have empty offsetHeight just after page loading
|
|
var size = document.body.offsetHeight||document.body.scrollHeight;
|
|
|
|
var iphone = navigator.userAgent.indexOf("iPhone")!=-1;
|
|
var ipad = navigator.userAgent.indexOf("iPad")!=-1;
|
|
|
|
var version = navigator.userAgent.match(/iPhone OS (\d+)/);
|
|
var iOS7 = version&&(version[1]>=7);
|
|
|
|
|
|
var iphone_safari = iphone && (size == 356 || size == 208 || size == 306 || size == 158 || size == 444);
|
|
var iphone5 = (window.screen.height==568);
|
|
|
|
var fix = function(){
|
|
var x = 0; var y=0;
|
|
if (iphone && !iOS7){
|
|
if (!webix.ui.orientation){
|
|
x = 320;
|
|
y = iphone5?(iphone_safari?504:548):(iphone_safari?416:460);
|
|
} else {
|
|
x = iphone5?568:480;
|
|
y = iphone_safari?268:300;
|
|
}
|
|
} else if (webix.env.isAndroid){
|
|
|
|
if(!webix.env.isFF){
|
|
//ipad doesn't change orientation and zoom level, so just ignore those lines
|
|
document.body.style.width = document.body.style.height = "1px";
|
|
document.body.style.overflow="hidden";
|
|
|
|
var dmod = window.outerWidth/window.innerWidth; //<1
|
|
x = window.outerWidth/dmod;
|
|
y = window.outerHeight/dmod;
|
|
}
|
|
} else if(!webix.env.isIEMobile){
|
|
x = window.innerWidth;
|
|
y = window.innerHeight;
|
|
}
|
|
|
|
if (y){
|
|
document.body.style.height = y+"px";
|
|
document.body.style.width = x+"px";
|
|
}
|
|
|
|
webix.ui.$freeze = false;
|
|
webix.ui.resize();
|
|
};
|
|
|
|
var onrotate = function(){
|
|
webix.ui.$freeze = true;
|
|
if(webix.env.isSafari)
|
|
fix();
|
|
else
|
|
webix.delay(fix,null, [], 500);
|
|
};
|
|
|
|
|
|
webix.attachEvent("onRotate", onrotate);
|
|
orientation();
|
|
webix.delay(onrotate);
|
|
|
|
};
|
|
|
|
|
|
})();
|
|
|
|
/*
|
|
Behavior:History - change multiview state on 'back' button
|
|
|
|
*/
|
|
|
|
webix.history = {
|
|
track:function(id, url){
|
|
this._init_state(id, url);
|
|
|
|
if (this._aHandler)
|
|
webix.$$(this._aViewId).detachEvent(this._aHandler);
|
|
|
|
if (id){
|
|
this._aViewId = id;
|
|
var view = webix.$$(id);
|
|
|
|
var handler = function(){
|
|
if (webix.history._ignored) return;
|
|
|
|
if (view.getValue)
|
|
webix.history.push(id, view.getValue());
|
|
};
|
|
|
|
if (view.getActiveId)
|
|
this._aHandler = view.attachEvent("onViewChange", handler);
|
|
else
|
|
this._aHandler = view.attachEvent("onChange", handler);
|
|
}
|
|
},
|
|
_set_state:function(view, state){
|
|
webix.history._ignored = 1;
|
|
|
|
view = webix.$$(view);
|
|
if (view.callEvent("onBeforeHistoryNav", [state]))
|
|
if (view.setValue)
|
|
view.setValue(state);
|
|
|
|
webix.history._ignored = 0;
|
|
},
|
|
push:function(view, url, value){
|
|
view = webix.$$(view);
|
|
var new_url = "";
|
|
if (url)
|
|
new_url = "#!/"+url;
|
|
if (webix.isUndefined(value)){
|
|
if (view.getValue)
|
|
value = view.getValue();
|
|
else
|
|
value = url;
|
|
}
|
|
|
|
window.history.pushState({ webix:true, id:view._settings.id, value:value }, "", new_url);
|
|
},
|
|
_init_state:function(view, url){
|
|
webix.event(window, "popstate", function(ev){
|
|
if (ev.state && ev.state.webix){
|
|
webix.history._set_state(ev.state.id, ev.state.value);
|
|
}
|
|
});
|
|
|
|
var state = window.location.hash;
|
|
webix.noanimate = true;
|
|
if (state && state.indexOf("#!/") === 0)
|
|
webix.history._set_state(view, state.replace("#!/",""));
|
|
else if (url){
|
|
webix.history.push(view, url);
|
|
webix.history._set_state(view, url);
|
|
}
|
|
webix.noanimate = false;
|
|
|
|
this._init_state = function(){};
|
|
}
|
|
};
|
|
|
|
|
|
|
|
webix.protoUI({
|
|
name:"slider",
|
|
$touchCapture:true,
|
|
defaults:{
|
|
min:0,
|
|
max:100,
|
|
value:50,
|
|
step:1,
|
|
title:false,
|
|
moveTitle:true,
|
|
template:function(obj, common){
|
|
var id = common._handle_id = "x" +webix.uid();
|
|
var html = "";
|
|
var title = "<div class='webix_slider_title"+(obj.moveTitle?" webix_slider_move":"")+"'"+(!obj.moveTitle && obj.vertical?(" style='line-height:"+(obj.aheight-obj.inputPadding*2)+"px;'"):"")+"></div>";
|
|
var left = "<div class='webix_slider_left'> </div>";
|
|
var right = "<div class='webix_slider_right'></div>";
|
|
var handle = "<div class='webix_slider_handle' role='slider' aria-label='"+obj.label+(obj.title?(" "+obj.title(obj)):"")+"' aria-valuemax='"+obj.max+"' aria-valuemin='"+obj.min+"' aria-valuenow='"+obj.value+"' tabindex='0' id='"+id+"'> </div>";
|
|
|
|
if(obj.vertical) html = "<div class='webix_slider_box'>"+right+left+handle+"</div>"+title;
|
|
else html = title+"<div class='webix_slider_box'>"+left+right+handle+"</div>";
|
|
return common.$renderInput(obj, html, id);
|
|
}
|
|
},
|
|
type_setter:function(type){
|
|
this._viewobj.className += " webix_slider_"+type;
|
|
},
|
|
title_setter:function(value){
|
|
if (typeof value == 'string')
|
|
return webix.template(value);
|
|
return value;
|
|
},
|
|
_get_slider_handle:function(){
|
|
return this.$view.querySelector(".webix_slider_handle");
|
|
},
|
|
_set_inner_size:function(){
|
|
var handle = this._get_slider_handle();
|
|
var config = this._settings;
|
|
|
|
if(handle){ //view is rendered for sure
|
|
var size = config.vertical?this._content_height:this._get_input_width(config); //width or height
|
|
var value = config.value%config.step?(Math.round(config.value/config.step)*config.step):config.value;
|
|
var max = config.max - config.min;
|
|
|
|
value = Math.max(Math.min(value,config.max),config.min);
|
|
value = config.vertical?(max-(value-config.min)):(value-config.min);
|
|
|
|
//top or left
|
|
var corner1 = Math.ceil((size - 2 * this._sliderPadding) * value / max);
|
|
//bottom or right
|
|
var corner2 = size - 2 * this._sliderPadding - corner1;
|
|
|
|
var cornerStr = config.vertical?"top":"left";
|
|
var sizeStr = config.vertical?"height":"width";
|
|
|
|
handle.style[cornerStr] = this._sliderPadding + corner1 - this._sliderHandleWidth / 2 + "px";
|
|
handle.parentNode.style[sizeStr] = size+"px";
|
|
|
|
//1px border
|
|
corner2= Math.min(Math.max(corner2, 2 * this._sliderBorder), size - this._sliderPadding * 2 - 2 * this._sliderBorder);
|
|
corner1 = Math.min(Math.max(corner1, 2 * this._sliderBorder), size - this._sliderPadding * 2 - 2 * this._sliderBorder);
|
|
|
|
//width for left/top and right/bottom bars
|
|
var part = handle.previousSibling;
|
|
part.style[sizeStr] = corner2 + "px";
|
|
var last = part.previousSibling;
|
|
last.style[sizeStr] = corner1 + "px";
|
|
|
|
this._set_title(handle, corner1, corner2, cornerStr);
|
|
}
|
|
},
|
|
_set_title:function(handle, corner1, corner2, cornerStr){
|
|
var config = this._settings;
|
|
if (this._settings.title){
|
|
var title = handle.parentNode[config.vertical?"nextSibling":"previousSibling"];
|
|
title.innerHTML = this._settings.title(this._settings, this);
|
|
|
|
if(this._settings.moveTitle){
|
|
var pos = 0;
|
|
if(config.vertical) pos = corner1+2 * this._sliderBorder-this._sliderHandleWidth/2;
|
|
else{
|
|
var half = title.clientWidth/2;
|
|
var pos1 = half>corner1 ? (half-corner1-2*this._sliderBorder): 0;//left/top text is to large
|
|
var pos2 = half>corner2 ? (half-corner2-2*this._sliderBorder-this._sliderHandleWidth/2): 0;//right/bottom text is too large
|
|
pos = this._sliderPadding + corner1 - half + pos1 - pos2;
|
|
}
|
|
title.style[cornerStr] = pos+ "px";
|
|
}
|
|
}
|
|
},
|
|
_set_value_now:function(){
|
|
this._get_slider_handle().setAttribute("aria-valuenow", this._settings.value);
|
|
},
|
|
refresh:function(){
|
|
var handle = this._get_slider_handle();
|
|
if(handle){
|
|
this._set_value_now();
|
|
if(this._settings.title)
|
|
handle.setAttribute("aria-label", this._settings.label+" "+this._settings.title(this._settings, this));
|
|
|
|
this._set_inner_size();
|
|
}
|
|
},
|
|
$setValue:function(){
|
|
this.refresh();
|
|
},
|
|
$getValue:function(){
|
|
return this._settings.value;
|
|
},
|
|
$init:function(config){
|
|
if(webix.env.touch)
|
|
this.attachEvent("onTouchStart" , webix.bind(this._on_mouse_down_start, this));
|
|
else
|
|
webix._event(this._viewobj, "mousedown", webix.bind(this._on_mouse_down_start, this));
|
|
|
|
webix._event( this.$view, "keydown", webix.bind(this._handle_move_keyboard, this));
|
|
|
|
if(config.vertical){
|
|
config.height = config.height || webix.skin.$active.vSliderHeight;
|
|
this._viewobj.className += " webix_slider_vertical";
|
|
this._sliderPadding = webix.skin.$active.vSliderPadding;
|
|
}
|
|
},
|
|
$skin: function(){
|
|
this._sliderHandleWidth = webix.skin.$active.sliderHandleWidth; //8 - width of handle / 2
|
|
this._sliderPadding = webix.skin.$active.sliderPadding;//10 - padding of webix_slider_box ( 20 = 10*2 )
|
|
this._sliderBorder = webix.skin.$active.sliderBorder;//1px border
|
|
},
|
|
_handle_move_keyboard:function(e){
|
|
var code = e.keyCode, c = this._settings, value = c.value;
|
|
|
|
if(code>32 && code <41){
|
|
|
|
webix.html.preventEvent(e);
|
|
|
|
var trg = e.target || e.srcElement;
|
|
var match = /webix_slider_handle_(\d)/.exec(trg.className);
|
|
this._activeIndex = match?parseInt(match[1],10):-1;
|
|
if(match)
|
|
value = c.value[this._activeIndex];
|
|
value = value<c.min ? c.min:(value>c.max ? c.max : value);
|
|
|
|
if(code === 35) value = c.min;
|
|
else if(code === 36) value = c.max;
|
|
else{
|
|
var inc = (code === 37 || code ===40 || code === 34)?-1:1;
|
|
if(code === 33 || code === 34 || c.step>1)
|
|
inc = inc*c.step;
|
|
value = value*1+inc;
|
|
}
|
|
|
|
|
|
if(value>=c.min && value <=c.max){
|
|
if(match){
|
|
var temp =[];
|
|
for(var i=0; i<c.value.length; i++)
|
|
temp[i] = i === this._activeIndex ? value : c.value[i];
|
|
value = temp;
|
|
}
|
|
this.setValue(value);
|
|
this._activeIndex = -1;
|
|
}
|
|
}
|
|
},
|
|
_on_mouse_down_start:function(e){
|
|
var trg = e.target || e.srcElement;
|
|
if(this._mouse_down_process){
|
|
this._mouse_down_process(e);
|
|
}
|
|
|
|
var value = this._settings.value;
|
|
if(webix.isArray(value))
|
|
value = webix.copy(value);
|
|
|
|
if (trg.className.indexOf("webix_slider_handle")!=-1){
|
|
this._start_value = value;
|
|
return this._start_handle_dnd.apply(this,arguments);
|
|
} else if (trg.className.indexOf("webix_slider") != -1){
|
|
this._start_value = value;
|
|
|
|
this._settings.value = this._get_value_from_event.apply(this,arguments);
|
|
|
|
this._start_handle_dnd(e);
|
|
}
|
|
},
|
|
_start_handle_dnd:function(e){
|
|
if(webix.env.touch){
|
|
this._handle_drag_events = [
|
|
this.attachEvent("onTouchMove" , webix.bind(this._handle_move_process, this)),
|
|
this.attachEvent("onTouchEnd" , webix.bind(this._handle_move_stop, this))
|
|
];
|
|
}
|
|
else
|
|
this._handle_drag_events = [
|
|
webix.event(document.body, "mousemove", webix.bind(this._handle_move_process, this)),
|
|
webix.event(window, "mouseup", webix.bind(this._handle_move_stop, this))
|
|
];
|
|
webix.html.addCss(document.body,"webix_noselect");
|
|
},
|
|
_handle_move_stop:function(e){
|
|
//detach event handlers
|
|
if(this._handle_drag_events){
|
|
if(webix.env.touch){
|
|
webix.detachEvent(this._handle_drag_events[0]);
|
|
webix.detachEvent(this._handle_drag_events[1]);
|
|
}
|
|
else{
|
|
webix.eventRemove(this._handle_drag_events[0]);
|
|
webix.eventRemove(this._handle_drag_events[1]);
|
|
}
|
|
this._handle_drag_events = [];
|
|
}
|
|
|
|
webix.html.removeCss(document.body,"webix_noselect");
|
|
|
|
var value = this._settings.value;
|
|
|
|
if(webix.isArray(value))
|
|
value = webix.copy(value);
|
|
|
|
this._settings.value = this._start_value;
|
|
this.setValue(value);
|
|
|
|
this._get_slider_handle(this._activeIndex).focus();
|
|
this._activeIndex = -1;
|
|
},
|
|
_handle_move_process:function(e){
|
|
this._settings.value = this._get_value_from_event.apply(this,arguments);
|
|
this.refresh();
|
|
this.callEvent("onSliderDrag", []);
|
|
},
|
|
_get_value_from_event:function(event,touchContext){
|
|
// this method takes 2 arguments in case of touch env
|
|
var pos = 0;
|
|
var ax = this._settings.vertical?"y":"x";
|
|
if(webix.env.touch)
|
|
pos = touchContext?touchContext[ax]: event[ax];
|
|
else
|
|
pos = webix.html.pos(event)[ax];
|
|
return this._get_value_from_pos(pos);
|
|
},
|
|
_get_value_from_pos:function(pos){
|
|
var config = this._settings;
|
|
var max = config.max - config.min;
|
|
var ax = config.vertical?"y":"x";
|
|
|
|
//top or left depending on slider type
|
|
var corner = webix.html.offset(this._get_slider_handle().parentNode)[ax] + this._sliderPadding;
|
|
//height or width depending on slider type
|
|
var size = (config.vertical?this._content_height:this._get_input_width(config))-2*this._sliderPadding;
|
|
|
|
var newvalue = (size?(pos-corner) * max / size:0);
|
|
if(config.vertical)
|
|
newvalue = max-newvalue;
|
|
newvalue = Math.round((newvalue+config.min)/config.step) * config.step;
|
|
return Math.max(Math.min(newvalue, config.max), config.min);
|
|
},
|
|
_init_onchange:function(){} //need not ui.text logic
|
|
}, webix.ui.text);
|
|
|
|
|
|
webix.protoUI({
|
|
name:"rangeslider",
|
|
$cssName:"slider webix_rangeslider",
|
|
defaults:{
|
|
separator: ",",
|
|
value: "20,80",
|
|
template:function(obj, common){
|
|
var id = "x" + webix.uid();
|
|
common._handle_id = [id+"_0",id+"_1"];
|
|
|
|
var aria = "role='slider' aria-label='"+obj.label+(obj.title?(" "+obj.title(obj)):"")+"' aria-valuemax='"+obj.max+"' aria-valuemin='"+obj.min+"' tabindex='0'";
|
|
var handles = "<div class='webix_slider_handle webix_slider_handle_0' id='"+common._handle_id[0]+"' "+aria+" aria-valuenow='"+obj.value[0]+"'> </div>";
|
|
handles += "<div class='webix_slider_handle webix_slider_handle_1' id='"+common._handle_id[1]+"' "+aria+" aria-valuenow='"+obj.value[1]+"'> </div>";
|
|
var html = "<div class='webix_slider_title'></div><div class='webix_slider_box'><div class='webix_slider_right'> </div><div class='webix_slider_left'></div>"+handles+"</div>";
|
|
return common.$renderInput(obj, html, id);
|
|
}
|
|
},
|
|
value_setter: function(value){
|
|
|
|
if(!webix.isArray(value)){
|
|
value = value.toString().split(this._settings.separator);
|
|
}
|
|
if(value.length <2)
|
|
value[1] = value[0];
|
|
value[0] = parseFloat(value[0]);
|
|
value[1] = parseFloat(value[1]);
|
|
return value;
|
|
},
|
|
_get_slider_handle:function(index){
|
|
index = index && index>=0?index:0;
|
|
return this.$view.querySelector(".webix_slider_handle_"+(index||0));
|
|
},
|
|
_get_left_pos: function(width,index){
|
|
var config, max, value;
|
|
|
|
config = this._settings;
|
|
max = config.max - config.min;
|
|
value = config.value[index]%config.step?(Math.round(config.value[index]/config.step)*config.step):config.value[index];
|
|
value = Math.max(Math.min(value,config.max),config.min);
|
|
return Math.ceil((width - 20) * (value-config.min) / max);
|
|
},
|
|
_set_inner_size:function(){
|
|
var config, handle0, handle1,
|
|
left0, left1, parentBox, width;
|
|
|
|
handle0 =this._get_slider_handle(0);
|
|
handle1 = this._get_slider_handle(1);
|
|
config = this._settings;
|
|
|
|
if(!webix.isArray(config.value)){
|
|
this.define("value",config.value);
|
|
}
|
|
//10 - padding of webix_slider_box ( 20 = 10*2 )
|
|
//8 - width of handle / 2
|
|
|
|
if (handle0){
|
|
|
|
width = this._get_input_width(config);
|
|
|
|
parentBox = handle0.parentNode;
|
|
parentBox.style.width = width+"px";
|
|
|
|
left0 = this._get_left_pos(width, 0);
|
|
left1 = this._get_left_pos(width, 1);
|
|
|
|
handle0.style.left = 10 + left0 - 8 + "px";
|
|
handle1.style.left = 10 + left1 - 8 + "px";
|
|
|
|
parentBox.firstChild.style.width = width - 22+ "px";
|
|
|
|
parentBox.childNodes[1].style.width = left1 - left0 + "px";
|
|
parentBox.childNodes[1].style.left = left0+12 + "px";
|
|
|
|
|
|
if (this._settings.title){
|
|
handle0.parentNode.previousSibling.innerHTML = this._settings.title(this._settings, this);
|
|
}
|
|
}
|
|
},
|
|
_set_value_now:function(){
|
|
for(var i=0; i<2; i++){
|
|
this._get_slider_handle(i).setAttribute("aria-valuenow", this._settings.value[i]);
|
|
}
|
|
},
|
|
_mouse_down_process: function(e){
|
|
var trg = e.target || e.srcElement;
|
|
var match = /webix_slider_handle_(\d)/.exec(trg.className);
|
|
this._activeIndex = match?parseInt(match[1],10):-1;
|
|
|
|
if(match)
|
|
this._set_handle_active(this._activeIndex);
|
|
},
|
|
setValue:function(value){
|
|
var oldvalue = this._settings.value;
|
|
|
|
var temp = (typeof value == "object"?value.join(this._settings.separator):value);
|
|
|
|
if (oldvalue.join(this._settings.separator) == temp) return false;
|
|
|
|
this._settings.value = value;
|
|
if (this._rendered_input)
|
|
this.$setValue(value);
|
|
|
|
this.callEvent("onChange", [value, oldvalue]);
|
|
},
|
|
$getValue:function(){
|
|
var value = this._settings.value;
|
|
return this._settings.stringResult?value.join(this._settings.separator):value;
|
|
},
|
|
_set_handle_active: function(index){
|
|
var hActive = this._get_slider_handle(index);
|
|
var h = this._get_slider_handle(1-index);
|
|
if(hActive.className.indexOf("webix_slider_active") == -1)
|
|
hActive.className += " webix_slider_active";
|
|
h.className = h.className.replace(" webix_slider_active","");
|
|
},
|
|
_get_value_from_pos:function(pos){
|
|
var config = this._settings;
|
|
var value = config.value;
|
|
//10 - padding of slider box
|
|
var max = config.max - config.min;
|
|
|
|
var left = webix.html.offset(this._get_slider_handle().parentNode).x;
|
|
var newvalue = Math.ceil((pos-left) * max / this._get_input_width(config));
|
|
newvalue = Math.round((newvalue+config.min)/config.step) * config.step;
|
|
|
|
var index = null;
|
|
|
|
var pos0 = webix.html.offset(this._get_slider_handle(0)).x;
|
|
var pos1 = webix.html.offset(this._get_slider_handle(1)).x;
|
|
|
|
if(pos0==pos1 && (config.value[0] == config.min || config.value[0] == config.max) ){
|
|
index = (config.value[0] == config.min?1:0);
|
|
this._set_handle_active(index);
|
|
}
|
|
else{
|
|
if(this._activeIndex >=0){
|
|
index = this._activeIndex;
|
|
}else{
|
|
if(pos0==pos1){
|
|
index = (pos < pos0?0:1);
|
|
}
|
|
else{
|
|
var dist0 = Math.abs(pos0-pos);
|
|
var dist1 = Math.abs(pos1-pos);
|
|
index = dist0<dist1?0:1;
|
|
this._activeIndex = index;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if(index){
|
|
value[index] = Math.max(Math.min(newvalue, config.max), value[0]);
|
|
}
|
|
else{
|
|
value[index] = Math.max(Math.min(newvalue, value[1]), config.min);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
}, webix.ui.slider);
|
|
|
|
|
|
|
|
|
|
/*
|
|
view.load("offline->some.php")
|
|
|
|
or
|
|
|
|
view.load( webix.proxy("offline", "some.php") );
|
|
|
|
or
|
|
|
|
view.load( webix.proxy("offline", "post->url.php") );
|
|
*/
|
|
|
|
webix.proxy.offline = {
|
|
$proxy:true,
|
|
|
|
storage: webix.storage.local,
|
|
cache:false,
|
|
data:"",
|
|
|
|
_is_offline : function(){
|
|
if (!this.cache && !webix.env.offline){
|
|
webix.callEvent("onOfflineMode",[]);
|
|
webix.env.offline = true;
|
|
}
|
|
},
|
|
_is_online : function(){
|
|
if (!this.cache && webix.env.offline){
|
|
webix.env.offline = false;
|
|
webix.callEvent("onOnlineMode", []);
|
|
}
|
|
},
|
|
|
|
load:function(view, callback){
|
|
var mycallback = {
|
|
error:function(){
|
|
//assuming offline mode
|
|
var text = this.getCache() || this.data;
|
|
|
|
var loader = { responseText: text };
|
|
var data = webix.ajax.prototype._data(loader);
|
|
|
|
this._is_offline();
|
|
webix.ajax.$callback(view, callback, text, data, loader);
|
|
},
|
|
success:function(text, data, loader){
|
|
this._is_online();
|
|
webix.ajax.$callback(view, callback, text, data, loader);
|
|
|
|
this.setCache(text);
|
|
}
|
|
};
|
|
|
|
//in cache mode - always load data from cache
|
|
if (this.cache && this.getCache())
|
|
mycallback.error.call(this);
|
|
else {
|
|
//else try to load actual data first
|
|
if (this.source.$proxy)
|
|
this.source.load(this, mycallback);
|
|
else
|
|
webix.ajax(this.source, mycallback, this);
|
|
}
|
|
},
|
|
getCache:function(){
|
|
return this.storage.get(this._data_name());
|
|
},
|
|
clearCache:function(){
|
|
this.storage.remove(this._data_name());
|
|
},
|
|
setCache:function(text){
|
|
this.storage.put(this._data_name(), text);
|
|
},
|
|
_data_name:function(){
|
|
if (this.source.$proxy)
|
|
return this.source.source + "_$proxy$_data";
|
|
else
|
|
return this.source + "_$proxy$_data";
|
|
},
|
|
saveAll:function(view, update, dp, callback){
|
|
this.setCache(view.serialize());
|
|
webix.ajax.$callback(view, callback, "", update);
|
|
},
|
|
result:function(id, master, dp, text, data){
|
|
for (var i = 0; i < data.length; i++)
|
|
dp.processResult({ id: data[i].id, status: data[i].operation }, {}, {});
|
|
}
|
|
};
|
|
|
|
webix.proxy.cache = {
|
|
init:function(){
|
|
webix.extend(this, webix.proxy.offline);
|
|
},
|
|
cache:true
|
|
};
|
|
|
|
webix.proxy.local = {
|
|
init:function(){
|
|
webix.extend(this, webix.proxy.offline);
|
|
},
|
|
cache:true,
|
|
data:[]
|
|
};
|
|
|
|
|
|
|
|
|
|
webix.ActiveContent = {
|
|
$init:function(config){
|
|
if (config.activeContent){
|
|
this.$ready.push(this._init_active_content_list);
|
|
|
|
this._active_holders = {};
|
|
this._active_holders_item = {};
|
|
this._active_holders_values = {};
|
|
this._active_references = {};
|
|
|
|
for (var key in config.activeContent){
|
|
this[key] = this._bind_active_content(key);
|
|
if (config.activeContent[key].earlyInit){
|
|
var temp = webix._parent_cell; webix._parent_cell = null;
|
|
this[key].call(this,{},this, config.activeContent);
|
|
webix._parent_cell=temp;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
_destructActiveContent: function(){
|
|
for(var key in this._active_references){
|
|
var elem = this._active_references[key];
|
|
if(elem.destructor)
|
|
elem.destructor();
|
|
}
|
|
},
|
|
_init_active_content_list:function(){
|
|
this.attachEvent("onDestruct",webix.bind(this._destructActiveContent,this));
|
|
|
|
webix._event(this.$view, "blur", function(ev){
|
|
var target = ev.target || ev.srcElement;
|
|
|
|
// for inputs only
|
|
if(target.tagName != "BUTTON"){
|
|
var el = webix.$$(ev);
|
|
if (el && el !== this && el.getValue && el.setValue){
|
|
el.getNode(ev);
|
|
|
|
var newvalue = el.getValue();
|
|
if (newvalue != el._settings.value)
|
|
el.setValue(newvalue);
|
|
}
|
|
}
|
|
}, {bind:this, capture: true});
|
|
|
|
if (this.filter){
|
|
for (var key in this._settings.activeContent){
|
|
this.type[key] = this[key];
|
|
this[key] = this._locate_active_content_by_id(key);
|
|
}
|
|
//really bad!
|
|
this.attachEvent("onBeforeRender", function(){
|
|
this.type.masterUI = this;
|
|
});
|
|
this.type.masterUI = this;
|
|
}
|
|
},
|
|
_locate_active_content_by_id:function(key){
|
|
return function(id){
|
|
var button = this._active_references[key];
|
|
var button_id = button._settings.id;
|
|
var html = this.getItemNode(id).getElementsByTagName("DIV");
|
|
for (var i=0; i < html.length; i++) {
|
|
if (html[i].getAttribute("view_id") == button_id){
|
|
button._viewobj = button._dataobj = html[i];
|
|
break;
|
|
}
|
|
}
|
|
return button;
|
|
};
|
|
},
|
|
_get_active_node:function(el, key, master){
|
|
return function(e){
|
|
if (e){
|
|
var trg=e.target||e.srcElement;
|
|
while (trg){
|
|
if (trg.getAttribute && trg.getAttribute("view_id")){
|
|
master._setActiveContentView(el,trg);
|
|
if (master.locate){
|
|
var id = master.locate(trg.parentNode);
|
|
var value = master._active_holders_values[key][id];
|
|
el._settings.value = value;
|
|
el._settings.$masterId = id;
|
|
}
|
|
return trg;
|
|
}
|
|
trg = trg.parentNode;
|
|
}
|
|
}
|
|
return el._viewobj;
|
|
};
|
|
},
|
|
_set_new_active_value:function(key, master){
|
|
return function(value){
|
|
var data = master.data;
|
|
if (master.filter){
|
|
var id = master.locate(this._viewobj.parentNode);
|
|
data = master.getItem(id);
|
|
//XMLSerializer - FF "feature"
|
|
this.refresh();
|
|
master._active_holders_item[key][id]=this._viewobj.outerHTML||(new XMLSerializer().serializeToString(this._viewobj));
|
|
master._active_holders_values[key][id] = value;
|
|
}
|
|
if(data)
|
|
data[key] = value;
|
|
};
|
|
},
|
|
_bind_active_content:function(key){
|
|
return function(obj, common, active){
|
|
var object = common._active_holders?common:common.masterUI;
|
|
|
|
if (!object._active_holders[key]){
|
|
var d = document.createElement("DIV");
|
|
|
|
active = active || object._settings.activeContent;
|
|
var el = webix.ui(active[key], d);
|
|
|
|
d.firstChild.setAttribute("onclick", "event.processed = true; if (webix.env.isIE8) event.srcElement.w_view = '"+el._settings.id+"';");
|
|
|
|
el.getNode = object._get_active_node(el, key, object);
|
|
|
|
el.attachEvent("onChange", object._set_new_active_value(key, object));
|
|
|
|
object._active_references[key] = el;
|
|
object._active_holders[key] = d.innerHTML;
|
|
object._active_holders_item[key] = {};
|
|
object._active_holders_values[key] = {};
|
|
el.$activeEl = el.$view;
|
|
}
|
|
if (object.filter && obj[key] != object._active_holders_values[key] && !webix.isUndefined(obj[key])){
|
|
var el = object._active_references[key];
|
|
el.blockEvent();
|
|
object._setActiveContentView(el,el.$activeEl);
|
|
//in IE we can lost content of active element during parent repainting
|
|
if (!el.$view.firstChild) el.refresh();
|
|
el.setValue(obj[key]);
|
|
el.refresh();
|
|
el.unblockEvent();
|
|
|
|
object._active_holders_values[key][obj.id] = obj[key];
|
|
object._active_holders_item[key][obj.id] = el._viewobj.outerHTML||(new XMLSerializer().serializeToString(el._viewobj));
|
|
}
|
|
|
|
return object._active_holders_item[key][obj.id]||object._active_holders[key];
|
|
};
|
|
},
|
|
_setActiveContentView: function(el,view){
|
|
el._dataobj = el._viewobj = el.$view = view;
|
|
}
|
|
};
|
|
webix.ProgressBar = {
|
|
$init:function(){
|
|
if (webix.isUndefined(this._progress) && this.attachEvent){
|
|
this.attachEvent("onBeforeLoad", this.showProgress);
|
|
this.attachEvent("onAfterLoad", this.hideProgress);
|
|
this._progress = null;
|
|
}
|
|
},
|
|
showProgress:function(config){
|
|
// { position: 0 - 1, delay: 2000ms by default, css : name of css class to use }
|
|
if (!this._progress){
|
|
|
|
config = webix.extend({
|
|
position:0,
|
|
delay: 2000,
|
|
type:"icon",
|
|
icon:"refresh",
|
|
hide:false
|
|
}, (config||{}), true);
|
|
|
|
var incss = (config.type == "icon") ? ("fa-"+config.icon+" fa-spin") : "";
|
|
|
|
|
|
|
|
this._progress = webix.html.create(
|
|
"DIV",
|
|
{
|
|
"class":"webix_progress_"+config.type,
|
|
"role":"progressbar",
|
|
"aria-valuemin":"0",
|
|
"aria-valuemax":"100",
|
|
"tabindex":"0"
|
|
},
|
|
"<div class='webix_progress_state "+incss+"'></div>"
|
|
);
|
|
|
|
if(!this.setPosition)
|
|
this._viewobj.style.position = "relative";
|
|
|
|
webix.html.insertBefore(this._progress, this._viewobj.firstChild, this._viewobj);
|
|
this._viewobj.setAttribute("aria-busy", "true");
|
|
|
|
if(!webix.Touch.$active){
|
|
if(this.getScrollState){
|
|
var scroll = this.getScrollState();
|
|
if(this._viewobj.scrollWidth != this.$width){
|
|
this._progress.style.left = scroll.x +"px";
|
|
}
|
|
if(this._viewobj.scrollHeight != this.$height){
|
|
if(config.type != "bottom"){
|
|
this._progress.style.top = scroll.y +"px";
|
|
} else {
|
|
this._progress.style.top = scroll.y + this.$height - this._progress.offsetHeight +"px";
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
this._progress_delay = 1;
|
|
}
|
|
|
|
if (config && config.type != "icon")
|
|
webix.delay(function(){
|
|
if (this._progress){
|
|
var position = config.position || 1;
|
|
//check for css-transition support
|
|
if(this._progress.style[webix.env.transitionDuration] !== webix.undefined || !config.delay){
|
|
this._progress.firstChild.style.width = position*100+"%";
|
|
if (config.delay)
|
|
this._progress.firstChild.style[webix.env.transitionDuration] = config.delay+"ms";
|
|
} else{
|
|
//if animation is not supported fallback to timeouts [IE9]
|
|
var count = 0,
|
|
start = 0,
|
|
step = position/config.delay*30,
|
|
view = this;
|
|
|
|
if(this._progressTimer){
|
|
//reset the existing progress
|
|
window.clearInterval(this._progressTimer);
|
|
start = this._progress.firstChild.offsetWidth/this._progress.offsetWidth*100;
|
|
}
|
|
this._progressTimer = window.setInterval(function(){
|
|
if(count*30 == config.delay){
|
|
window.clearInterval(view._progressTimer);
|
|
}
|
|
else{
|
|
if(view._progress && view._progress.firstChild)
|
|
view._progress.firstChild.style.width = start+count*step*position*100+"%";
|
|
count++;
|
|
}
|
|
},30);
|
|
}
|
|
|
|
if (config.hide)
|
|
webix.delay(this.hideProgress, this, [1], config.delay);
|
|
|
|
}
|
|
this._progress_delay = 0;
|
|
}, this);
|
|
else if(config && config.type == "icon" && config.hide)
|
|
webix.delay(this.hideProgress, this, [1], config.delay);
|
|
},
|
|
hideProgress:function(now){
|
|
if (this._progress_delay)
|
|
now = true;
|
|
|
|
if (this._progress){
|
|
if (now){
|
|
if(this._progressTimer)
|
|
window.clearInterval(this._progressTimer);
|
|
webix.html.remove(this._progress);
|
|
this._progress = null;
|
|
this._viewobj.removeAttribute("aria-busy");
|
|
} else {
|
|
this.showProgress({ position:1.1, delay:300 , hide:true });
|
|
}
|
|
}
|
|
}
|
|
};
|
|
webix.protoUI({
|
|
name:"multitext",
|
|
$cssName:"text",
|
|
defaults:{
|
|
icon:"plus-circle",
|
|
iconWidth:25,
|
|
separator:", "
|
|
},
|
|
getValueHere:function(){
|
|
return webix.ui.text.prototype.getValue.call(this);
|
|
},
|
|
setValueHere:function(value){
|
|
return webix.ui.text.prototype.$setValue.call(this, value);
|
|
},
|
|
getValue:function(){
|
|
if (this.config.mode == "extra") return this.getValueHere();
|
|
|
|
var values = [ this.getValueHere(this) ];
|
|
for (var i=0; i<this._subs.length; i++){
|
|
var seg = webix.$$(this._subs[i]).getValueHere();
|
|
if (seg) values.push(seg);
|
|
}
|
|
return values.join(this.config.separator);
|
|
},
|
|
$setValue:function(value){
|
|
value = value || "";
|
|
|
|
if (this.config.mode == "extra") return this.setValueHere(value);
|
|
|
|
|
|
var parts = value.split(this.config.separator);
|
|
if (parts.length == this._subs.length+1){
|
|
this.setValueHere(parts[0]);
|
|
for (var i = 0; i < this._subs.length; i++)
|
|
webix.$$(this._subs[i]).setValueHere(parts[i+1]);
|
|
return;
|
|
}
|
|
|
|
this.removeSection();
|
|
this.setValueHere.call(this, parts[0]);
|
|
for (var i = 1; i<parts.length; i++){
|
|
var next = this.addSection();
|
|
webix.$$(next).setValueHere(parts[i]);
|
|
}
|
|
},
|
|
_subOnChange:function(call){
|
|
var parent = this.config.master ? webix.$$(this.config.master) : this;
|
|
var newvalue = parent.getValue();
|
|
var oldvalue = parent._settings.value;
|
|
if (newvalue !== oldvalue){
|
|
parent._settings.value = newvalue;
|
|
parent.callEvent("onChange", [newvalue, oldvalue]);
|
|
}
|
|
},
|
|
addSection:function(){
|
|
var config = this.config,
|
|
newConfig = {
|
|
labelWidth: config.labelWidth,
|
|
inputWidth: config.inputWidth,
|
|
width: config.width,
|
|
label: config.label ? " " : "",
|
|
view: this.name,
|
|
mode: "extra",
|
|
value: "",
|
|
icon: "minus-circle",
|
|
suggest: config.suggest || null,
|
|
master: config.id
|
|
};
|
|
|
|
webix.extend(newConfig, config.subConfig||{},true);
|
|
|
|
var newone = this.getParentView().addView(newConfig);
|
|
webix.$$(newone).attachEvent("onChange", this._subOnChange);
|
|
|
|
this._subs.push(newone);
|
|
this.callEvent("onSectionAdd", [newone, this._subs.length]);
|
|
return newone;
|
|
},
|
|
removeSection:function(id){
|
|
var parent = this.config.master ? webix.$$(this.config.master) : this;
|
|
for (var i = parent._subs.length - 1; i >= 0; i--){
|
|
var section = parent._subs[i];
|
|
if (!id || section == id){
|
|
parent._subs.removeAt(i);
|
|
this.getParentView().removeView(section);
|
|
parent.callEvent("onSectionRemove", [section, i+1]);
|
|
}
|
|
}
|
|
},
|
|
on_click:{
|
|
"webix_input_icon":function(ev, id, html){
|
|
if (this.config.mode == "extra"){
|
|
this.removeSection(this.config.id);
|
|
var childs = this.getParentView().getChildViews();
|
|
childs[childs.length - 1].focus();
|
|
this._subOnChange();
|
|
} else
|
|
webix.$$( this.addSection() ).focus();
|
|
|
|
return false;
|
|
}
|
|
},
|
|
$init:function(){
|
|
this._subs = webix.toArray([]);
|
|
this.attachEvent("onKeyPress", this._onKeyPress);
|
|
},
|
|
$render:function(obj){
|
|
this.$setValue(obj.value);
|
|
},
|
|
}, webix.ui.text);
|
|
/*
|
|
UI:Organogram
|
|
*/
|
|
|
|
|
|
|
|
|
|
webix.protoUI({
|
|
name:"organogram",
|
|
defaults:{
|
|
scroll: "auto",
|
|
ariaLabel:"lines"
|
|
},
|
|
$init:function(){
|
|
this._viewobj.className += " webix_organogram";
|
|
//map API of DataStore on self
|
|
this._html = document.createElement("DIV");
|
|
|
|
this.$ready.push(this._afterInit);
|
|
webix.extend(this.data, webix.TreeStore, true);
|
|
this.data.provideApi(this,true);
|
|
},
|
|
//attribute , which will be used for ID storing
|
|
_id:"webix_dg_id",
|
|
//supports custom context menu
|
|
on_click:{
|
|
webix_organogram_item:function(e,id){
|
|
if (this._settings.select){
|
|
if (this._settings.select=="multiselect" || this._settings.multiselect)
|
|
this.select(id, false, (e.ctrlKey || e.metaKey || (this._settings.multiselect == "touch")), e.shiftKey); //multiselection
|
|
else
|
|
this.select(id);
|
|
this._no_animation = false;
|
|
}
|
|
}
|
|
},
|
|
on_context:{},
|
|
on_dblclick:{},
|
|
_afterInit:function(){
|
|
this._dataobj.style.position = "relative";
|
|
this.data.attachEvent("onStoreUpdated",webix.bind(this.render,this));
|
|
},
|
|
_toHTMLItem:function(obj){
|
|
var mark = this.data._marks[obj.id];
|
|
|
|
this.callEvent("onItemRender",[obj]);
|
|
return this.type.templateStart.call(this,obj,this.type,mark)+(obj.$template?this.type["template"+obj.$template].call(this,obj,this.type,mark):this.type.template.call(this,obj,this.type,mark))+this.type.templateEnd.call(this);
|
|
},
|
|
_toHTML:function(obj){
|
|
//check if related template exist
|
|
var html=this._toHTMLItem(obj);
|
|
|
|
if (this.data.branch[obj.id])
|
|
html += this._renderBranch(obj.id);
|
|
|
|
return html;
|
|
},
|
|
_isListBlocks: function(){
|
|
return this.type.listMarginX || this.type.listMarginY;
|
|
},
|
|
_renderBranch: function(pId){
|
|
var elem, i, id,
|
|
html = "",
|
|
leaves = this.data.branch[pId],
|
|
marks = this.data._marks[pId],
|
|
pItem = this.getItem(pId),
|
|
sizes, totalWidth,
|
|
type = (pItem?pItem.$type:false);
|
|
|
|
|
|
|
|
if(!pId){
|
|
this._colHeight = [];
|
|
this.$xy = {};
|
|
totalWidth = this.$width - this.type.padding*2;
|
|
|
|
this.$xy[0] = {
|
|
totalWidth: totalWidth,
|
|
start: this.type.padding,
|
|
width: 0,
|
|
height: 0,
|
|
left: totalWidth/2,
|
|
top: this.type.padding||0
|
|
};
|
|
}
|
|
|
|
if(leaves){
|
|
sizes = this.$xy[pId];
|
|
|
|
// draw items inside list container
|
|
if(type == "list" && !this._isListBlocks()){
|
|
html += this.type.templateListStart.call(this,pItem, this.type, marks);
|
|
}
|
|
// render items and calculate heights
|
|
var sumTotalWidth = 0;
|
|
|
|
var childHeight = 0;
|
|
for( i=0; i < leaves.length; i++){
|
|
id = leaves[i];
|
|
totalWidth = this._tw[id];
|
|
var obj = this.getItem(id);
|
|
|
|
if(obj.open == webix.undefined)
|
|
obj.open = true;
|
|
|
|
if(type == "list")
|
|
this.data.addMark(id, "list_item","", 1, true);
|
|
|
|
var height = this._getItemHeight(id);
|
|
if(type == "list"){
|
|
var leftOffset = (type == "list"&&this._isListBlocks()?this.type.listMarginX:0);
|
|
var itemMargin = 0;
|
|
if(this._isListBlocks())
|
|
itemMargin = this.type.listMarginY;
|
|
else if(!i)
|
|
itemMargin = this.type.marginY;
|
|
|
|
this.$xy[id] = {
|
|
totalWidth: totalWidth,
|
|
start: sizes.start,
|
|
width: this.type.width,
|
|
height: height,
|
|
left: sizes.start + totalWidth/2 - this.type.width/2+ leftOffset,
|
|
top: i?(this.$xy[leaves[i-1]].top+this.$xy[leaves[i-1]].height+itemMargin+childHeight):(sizes.top+sizes.height+itemMargin)
|
|
};
|
|
childHeight = this.data.branch[id] ? this._getBranchHeight(id) : 0;
|
|
}
|
|
else{
|
|
this.$xy[id] = {
|
|
totalWidth: totalWidth,
|
|
start: sizes.start + sumTotalWidth,
|
|
width: this.type.width,
|
|
height: height,
|
|
left: sizes.start + sumTotalWidth + totalWidth/2 - this.type.width/2 ,
|
|
top: sizes.top + sizes.height + (pId?this.type.marginY:0)
|
|
};
|
|
|
|
}
|
|
html += this._toHTMLItem(obj);
|
|
sumTotalWidth += totalWidth;
|
|
|
|
}
|
|
if(!pId && sumTotalWidth){
|
|
this._dataobj.style.width = sumTotalWidth+this.type.padding*2+"px";
|
|
}
|
|
// draw child branches
|
|
for( i=0; i < leaves.length; i++){
|
|
id = leaves[i];
|
|
|
|
if (this.data.branch[id] && this.getItem(id).open)
|
|
html += this._renderBranch(id);
|
|
else if(pItem){
|
|
if(pItem.$type != "list")
|
|
this._colHeight.push(this.$xy[id].top+this.$xy[id].height);
|
|
else if(i == (leaves.length-1)){
|
|
this._colHeight.push(this.$xy[id].top+this.$xy[id].height);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(type == "list" && !this._isListBlocks())
|
|
html += this.type.templateListEnd(pItem, this.type, marks);
|
|
}
|
|
|
|
return html;
|
|
},
|
|
|
|
_getBranchHeight:function(id){
|
|
var items = this.data.branch[id];
|
|
var height = 0;
|
|
for (var i = 0; i < items.length; i++) {
|
|
height += this._getItemHeight(items[i])+this.type.listMarginY;
|
|
}
|
|
return height + this.type.marginY;
|
|
},
|
|
|
|
_getItemHeight: function(id){
|
|
var item = this.getItem(id);
|
|
var height = this.type.height;
|
|
if( typeof height == "function"){
|
|
height = height.call(item, this.type, this.data._marks[id]);
|
|
}
|
|
|
|
|
|
if(!this._hDiv){
|
|
this._hDiv = webix.html.create("div");
|
|
this._dataobj.appendChild(this._hDiv);
|
|
|
|
}
|
|
|
|
this._hDiv.className = this.type.classname(item,this.type,this.data._marks[id]);
|
|
this._hDiv.style.cssText="width:"+this.type.width+"px;height:"+height+(height=="auto"?"":"px")+";";
|
|
this._hDiv.innerHTML = this.type.template.call(this,item,this.type,this.data._marks[id]);
|
|
return this._hDiv.scrollHeight;
|
|
},
|
|
_calcTotalWidth: function(){
|
|
var tw = {};
|
|
var width = this.type.width;
|
|
var margin = this.type.marginX;
|
|
this.data.each(function(obj){
|
|
tw[obj.id] = width + margin;
|
|
|
|
var parentId = this.getParentId(obj.id);
|
|
if(parentId && this.getItem(parentId).$type != "list")
|
|
while(parentId){
|
|
var leaves = this.branch[parentId];
|
|
tw[parentId] = 0;
|
|
|
|
for( var i =0; i < leaves.length; i++){
|
|
tw[parentId] += tw[leaves[i]]||0;
|
|
}
|
|
parentId = this.getParentId(parentId);
|
|
}
|
|
});
|
|
this._tw = tw;
|
|
return tw;
|
|
|
|
},
|
|
getItemNode:function(searchId){
|
|
if (this._htmlmap)
|
|
return this._htmlmap[searchId];
|
|
|
|
//fill map if it doesn't created yet
|
|
this._htmlmap={};
|
|
|
|
var t = this._dataobj.childNodes;
|
|
for (var i=0; i < t.length; i++){
|
|
var id = t[i].getAttribute(this._id); //get item's
|
|
if (id)
|
|
this._htmlmap[id]=t[i];
|
|
if(t[i].className.indexOf("webix_organogram_list")!=-1 && !this._isListBlocks()){
|
|
var listNodes = t[i].childNodes;
|
|
for (var j=0; j < listNodes.length; j++){
|
|
id = listNodes[j].getAttribute(this._id); //get item's
|
|
if (id)
|
|
this._htmlmap[id]=listNodes[j];
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
//call locator again, when map is filled
|
|
return this.getItemNode(searchId);
|
|
},
|
|
_toHTMLObject:function(obj){
|
|
this._html.innerHTML = this._toHTMLItem(obj);
|
|
return this._html.firstChild;
|
|
},
|
|
render:function(id,data,type){
|
|
if (!this.isVisible(this._settings.id) || this.$blockRender)
|
|
return;
|
|
if (webix.debug_render)
|
|
webix.log("Render: "+this.name+"@"+this._settings.id);
|
|
|
|
if(type == "update"){
|
|
var cont = this.getItemNode(id); //get html element of updated item
|
|
|
|
var t = this._htmlmap[id] = this._toHTMLObject(data);
|
|
webix.html.insertBefore(t, cont);
|
|
webix.html.remove(cont);
|
|
return true;
|
|
}
|
|
else{
|
|
//full reset
|
|
if (this.callEvent("onBeforeRender",[this.data])){
|
|
this._calcTotalWidth();
|
|
this._htmlmap = null;
|
|
this._dataobj.innerHTML = this._renderBranch(0);
|
|
this._hDiv = null;
|
|
|
|
this._dataobj.style.height = Math.max.apply(Math, this._colHeight)+this.type.padding+"px";
|
|
this._renderCanvas();
|
|
this.resize();
|
|
this.callEvent("onAfterRender",[]);
|
|
}
|
|
}
|
|
return true;
|
|
},
|
|
_renderCanvas: function(){
|
|
if(this.canvas)
|
|
this.canvas.clearCanvas(true);
|
|
|
|
this.canvas = new webix.Canvas({
|
|
container:this._dataobj,
|
|
name:this._settings.ariaLabel,
|
|
width: this._dataobj.offsetWidth,
|
|
height:this._dataobj.offsetHeight
|
|
});
|
|
|
|
this._drawLines(0);
|
|
},
|
|
_drawLine:function(ctx,x1,y1,x2,y2,color,width){
|
|
ctx.strokeStyle = color;
|
|
ctx.lineCap='square';
|
|
ctx.lineWidth = width;
|
|
ctx.beginPath();
|
|
ctx.moveTo(x1,y1);
|
|
ctx.lineTo(x2,y2);
|
|
ctx.stroke();
|
|
ctx.lineWidth = 1;
|
|
},
|
|
_drawLines: function(id,ctx){
|
|
var i, item, leaves, p, s,
|
|
x12,y1,y2,
|
|
start, end;
|
|
|
|
var layout = this.config.layout;
|
|
if(!ctx)
|
|
ctx = this.canvas.getCanvas();
|
|
if(!this.$xy){
|
|
return;
|
|
}
|
|
id = id||0;
|
|
leaves = this.data.branch[id];
|
|
item = this.getItem(id);
|
|
if(leaves && leaves.length){
|
|
p = this.$xy[id];
|
|
// draw a vertical line between parent and nodes
|
|
if(id){
|
|
|
|
x12 = parseInt(p.left+ p.width/2,10) +0.5;
|
|
y1 = parseInt(p.top + p.height,10);
|
|
y2 = parseInt(p.top + p.height+ this.type.marginY/2,10);
|
|
|
|
if(item.$type == "list"){
|
|
if(!this._isListBlocks()){
|
|
y2 = parseInt(p.top + p.height+ this.type.marginY,10);
|
|
this._drawLine(ctx,x12, y1, x12, y2, this.type.lineColor);
|
|
return;
|
|
}
|
|
|
|
}
|
|
else
|
|
this._drawLine(ctx,x12, y1, x12, y2, this.type.lineColor);
|
|
}
|
|
|
|
|
|
y1 = parseInt(p.top + p.height+ this.type.marginY/2,10)+0.5;
|
|
for(i = 0; i < leaves.length; i++){
|
|
if(id){
|
|
s = this.$xy[leaves[i]];
|
|
if(item.$type == "list" && this._isListBlocks()){
|
|
x12 = parseInt(p.left + this.type.listMarginX/2,10) + 0.5;
|
|
if(!i)
|
|
start = x12;
|
|
else if(i == (leaves.length - 1))
|
|
end = x12;
|
|
y2 = parseInt(s.top + s.height/2,10);
|
|
this._drawLine(ctx,x12, y1 - this.type.marginY/2, x12, y2, this.type.lineColor);
|
|
this._drawLine(ctx,x12, y2, x12+this.type.listMarginX/2, y2, this.type.lineColor);
|
|
}
|
|
else{
|
|
x12 = parseInt(s.left+ s.width/2,10) + 0.5;
|
|
if(!i)
|
|
start = x12;
|
|
else if(i == (leaves.length - 1))
|
|
end = x12;
|
|
y2 = parseInt(s.top ,10);
|
|
this._drawLine(ctx,x12, y1, x12, y2, this.type.lineColor);
|
|
}
|
|
|
|
}
|
|
if(this.getItem(leaves[i]).open)
|
|
this._drawLines(leaves[i],ctx);
|
|
}
|
|
if(id)
|
|
this._drawLine(ctx,start, y1, end, y1,this.type.lineColor);
|
|
}
|
|
},
|
|
//autowidth, autoheight - no inner scroll
|
|
//scrollable - width, height, auto, with scroll
|
|
$getSize:function(dx,dy){
|
|
var aW = this._settings.autowidth;
|
|
var aH = this._settings.autoheight;
|
|
if(aW){
|
|
dx = this._dataobj.offsetWidth+(this._dataobj.offsetHeight>dy && !aH?webix.ui.scrollSize:0);
|
|
}
|
|
if(aH){
|
|
dy = this._dataobj.offsetHeight + (this._dataobj.offsetWidth>dx && !aW?webix.ui.scrollSize:0);
|
|
}
|
|
|
|
return webix.ui.view.prototype.$getSize.call(this, dx, dy);
|
|
},
|
|
$setSize:function(x,y){
|
|
if(webix.ui.view.prototype.$setSize.call(this,x,y)){
|
|
this._dataobj.style.width = this.$width+"px";
|
|
this._dataobj.style.height = this.$height+"px";
|
|
this.render();
|
|
}
|
|
},
|
|
//css class to action map, for dblclick event
|
|
type:{
|
|
width: 120,
|
|
height: "auto",
|
|
padding: 20,
|
|
marginX: 20,
|
|
marginY: 20,
|
|
listMarginX: 0,
|
|
listMarginY: 0,
|
|
lineColor: "#90caf9",
|
|
classname:function(obj, common, marks){
|
|
var css = "webix_organogram_item ";
|
|
if (obj.$css){
|
|
if (typeof obj.$css == "object")
|
|
obj.$css = webix.html.createCss(obj.$css);
|
|
css += " "+obj.$css;
|
|
}
|
|
|
|
if(marks && marks.list_item)
|
|
css += " webix_organogram_list_item ";
|
|
if(marks && marks.$css)
|
|
css += marks.$css;
|
|
css += " webix_organogram_level_"+obj.$level;
|
|
return css;
|
|
},
|
|
listClassName: function(obj){
|
|
var css = "webix_organogram_list webix_organogram_list_"+obj.$level;
|
|
if (obj.$listCss){
|
|
if (typeof obj.$listCss == "object")
|
|
obj.$listCss = webix.html.createCss(obj.$listCss);
|
|
css += " "+obj.$listCss;
|
|
}
|
|
return css;
|
|
},
|
|
template:webix.template("#value#"),
|
|
templateStart:function(obj,type,marks){
|
|
var style="";
|
|
if((!(marks && marks.list_item) || type.listMarginX || type.listMarginY) && this.$xy){
|
|
var xy = this.$xy[obj.id];
|
|
style += "width: "+ xy.width+"px; height: " + xy.height+"px;";
|
|
style += "top: "+ xy.top+"px; left: " + xy.left+"px;";
|
|
}
|
|
return '<div webix_dg_id="'+obj.id+'" class="'+type.classname.call(this,obj,type,marks)+'"'+(style?'style="'+style+'"':'')+'">';
|
|
},
|
|
templateEnd:webix.template("</div>"),
|
|
templateListStart:function(obj,type,marks){
|
|
var style="";
|
|
if(this.$xy){
|
|
var xy = this.$xy[obj.id];
|
|
style += "width: "+ xy.width+"px;";
|
|
style += "top: "+ (xy.top+xy.height+type.marginY)+"px; left: " + xy.left+"px;";
|
|
}
|
|
return '<div class="'+type.listClassName.call(this,obj,type,marks)+'"'+(style?'style="'+style+'"':'')+'">';
|
|
},
|
|
templateListEnd:webix.template("</div>")
|
|
}
|
|
}, webix.AutoTooltip, webix.Group, webix.TreeAPI, webix.DataMarks, webix.SelectionModel, webix.MouseEvents, webix.Scrollable, webix.RenderStack, webix.TreeDataLoader, webix.DataLoader, webix.ui.view, webix.EventSystem);
|
|
|
|
|
|
|
|
|
|
webix.protoUI({
|
|
name: "barcode",
|
|
defaults:{
|
|
type: "ean13",
|
|
height: 160,
|
|
width: 220,
|
|
paddingY: 10,
|
|
paddingX: 20,
|
|
textHeight: 20,
|
|
color: "#000",
|
|
ariaLabel:"bars"
|
|
},
|
|
$init: function(){
|
|
this.$view.className += " webix_barcode";
|
|
if (!this.types){
|
|
this.types = { "default" : this.type };
|
|
this.type.name = "default";
|
|
}
|
|
},
|
|
type:{},
|
|
render: function(){
|
|
if(this.isVisible(this._settings.id)){
|
|
if(this.canvas)
|
|
this.canvas.clearCanvas(true);
|
|
this.$view.innerHTML = "";
|
|
this._renderCanvas();
|
|
}
|
|
},
|
|
_renderCanvas: function(){
|
|
this.canvas = new webix.Canvas({
|
|
container:this.$view,
|
|
name:this._settings.ariaLabel,
|
|
width: this.$width,
|
|
height:this.$height
|
|
});
|
|
this._drawBars();
|
|
},
|
|
_drawBars: function(){
|
|
var code, ctx, i, len,
|
|
value = this._settings.value,
|
|
type = this._settings.type;
|
|
|
|
if(!type || !this.types[type] || !value)
|
|
return false;
|
|
|
|
code = this.type.encode(value);
|
|
len = code.length;
|
|
|
|
ctx = this.canvas.getCanvas();
|
|
if(len){
|
|
var unitWidth = (this.$width - this.config.paddingX*2)/len;
|
|
var unitNum = 0;
|
|
|
|
for( i = 0; i < len ; i++ ){
|
|
var ch1 = parseInt(code.charAt(i),10);
|
|
if(ch1){
|
|
unitNum++;
|
|
if(i == (len-1)){
|
|
this._drawBar(ctx,i+1,unitWidth,unitNum,len);
|
|
}
|
|
}
|
|
else if(unitNum){
|
|
this._drawBar(ctx,i,unitWidth,unitNum,len);
|
|
unitNum=0;
|
|
}
|
|
}
|
|
|
|
// add text
|
|
this._addText(value, unitWidth);
|
|
}
|
|
},
|
|
_drawBar: function(ctx,i,unitWidth,unitNum,num){
|
|
var x0, x1, y0, y1;
|
|
|
|
x1 = parseInt(i*unitWidth+this.config.paddingX,10);
|
|
x0 = parseInt(x1 - unitNum*unitWidth,10);
|
|
y0 = this.config.paddingY;
|
|
y1 = this.$height - this.config.paddingY - this.config.textHeight;
|
|
|
|
if(this._isEAN() && ( i<4 || i>(num-4) || (i < (num/2+2) && i>(num/2-2)))){
|
|
y1 += this.config.textHeight/2;
|
|
}
|
|
ctx.fillStyle = this.config.color;
|
|
ctx.beginPath();
|
|
ctx.moveTo(x0,y0);
|
|
ctx.lineTo(x1,y0);
|
|
ctx.lineTo(x1,y1);
|
|
ctx.lineTo(x0,y1);
|
|
ctx.lineTo(x0,y0);
|
|
ctx.fill();
|
|
},
|
|
_addText: function(value, barWidth){
|
|
var i, len, x;
|
|
|
|
if(this.type.template)
|
|
value = this.type.template(value);
|
|
|
|
if(this._isEAN()){
|
|
if(this.type.firstDigit){
|
|
this.canvas.renderTextAt(true,"left", this.config.paddingX,this.$height-this.config.paddingY, value.charAt(0));
|
|
value = value.slice(1);
|
|
}
|
|
|
|
len = value.length;
|
|
|
|
if(this.type.lastDigit)
|
|
len--;
|
|
|
|
if(len){
|
|
var tUnitWidth = (this.$width - this.config.paddingX*2 - barWidth*11)/len;
|
|
|
|
for( i = 0; i < len; i++ ){
|
|
x = this.config.paddingX + i*tUnitWidth + (i<len/2?3:8)*barWidth +tUnitWidth/2;
|
|
this.canvas.renderTextAt(true, true, x, this.$height - this.config.paddingY, value.charAt(i));
|
|
}
|
|
|
|
if(this.type.lastDigit){
|
|
x = this.config.paddingX + len*tUnitWidth + 11*barWidth;
|
|
this.canvas.renderTextAt(true, false, x, this.$height-this.config.paddingY, value.charAt(len));
|
|
}
|
|
}
|
|
}
|
|
else{
|
|
this.canvas.renderTextAt( true, true, this.$width/2, this.$height - this.config.paddingY, value );
|
|
}
|
|
},
|
|
setValue: function(value){
|
|
this._settings.value = value;
|
|
this.render();
|
|
return value;
|
|
},
|
|
getValue: function(){
|
|
var value = this._settings.value;
|
|
return this.type.template?this.type.template(value):value;
|
|
},
|
|
type_setter:function(value){
|
|
if(!this.types[value])
|
|
this.customize(value);
|
|
else {
|
|
this.type = webix.clone(this.types[value]);
|
|
if (this.type.css)
|
|
this._contentobj.className+=" "+this.type.css;
|
|
}
|
|
return value;
|
|
},
|
|
_isEAN: function(){
|
|
var type = this.config.type;
|
|
return (type.indexOf("ean")===0 || type.indexOf("upcA")!=-1);
|
|
},
|
|
$setSize:function(x,y){
|
|
if(webix.ui.view.prototype.$setSize.call(this,x,y)){
|
|
this.render();
|
|
}
|
|
}
|
|
},webix.ui.view);
|
|
|
|
/*
|
|
* EAN8
|
|
* */
|
|
webix.type(webix.ui.barcode, {
|
|
name:"ean8",
|
|
encodings: [
|
|
["0001101", "1110010"],
|
|
["0011001", "1100110"],
|
|
["0010011", "1101100"],
|
|
["0111101", "1000010"],
|
|
["0100011", "1011100"],
|
|
["0110001", "1001110"],
|
|
["0101111", "1010000"],
|
|
["0111011", "1000100"],
|
|
["0110111", "1001000"],
|
|
["0001011", "1110100"]
|
|
],
|
|
encode: function(value){
|
|
var code, i;
|
|
value = value.replace(/[^0-9]/g,"").substring(0, 7);
|
|
if(value.length != 7)
|
|
return "";
|
|
|
|
value = value + this.checksum(value);
|
|
|
|
code = "101";
|
|
|
|
for(i=0; i<4; i++){
|
|
code += this.encodings[parseInt(value.charAt(i),10)][0];
|
|
}
|
|
|
|
code += "01010";
|
|
|
|
for(i=4; i<8; i++){
|
|
code += this.encodings[parseInt(value.charAt(i),10)][1];
|
|
}
|
|
|
|
code += "101";
|
|
return code;
|
|
},
|
|
template: function(value){
|
|
return value.replace(/[^0-9]/g,"").substring(0, 7) + this.checksum(value);
|
|
},
|
|
checksum: function (value){
|
|
value = value.substring(0, 7);
|
|
var i,
|
|
odd = true,
|
|
sum = 0;
|
|
|
|
for(i=0; i<7; i++){
|
|
sum += (odd ? 3 : 1) * parseInt(value.charAt(i),10);
|
|
odd = !odd;
|
|
}
|
|
return ((10 - sum % 10) % 10).toString();
|
|
}
|
|
});
|
|
|
|
/*
|
|
* EAN13
|
|
* */
|
|
webix.type(webix.ui.barcode, {
|
|
name:"ean13",
|
|
firstDigit: true,
|
|
encodings: [
|
|
["0001101", "0100111", "1110010", "000000"],
|
|
["0011001", "0110011", "1100110", "001011"],
|
|
["0010011", "0011011", "1101100", "001101"],
|
|
["0111101", "0100001", "1000010", "001110"],
|
|
["0100011", "0011101", "1011100", "010011"],
|
|
["0110001", "0111001", "1001110", "011001"],
|
|
["0101111", "0000101", "1010000", "011100"],
|
|
["0111011", "0010001", "1000100", "010101"],
|
|
["0110111", "0001001", "1001000", "010110"],
|
|
["0001011", "0010111", "1110100", "011010"]
|
|
],
|
|
encode: function(value){
|
|
var code, columnIndexes, i;
|
|
|
|
value = value.replace(/[^0-9]/g,"").substring(0, 12);
|
|
|
|
if (value.length != 12)
|
|
return "";
|
|
|
|
value += this.checksum(value);
|
|
|
|
code = "101";
|
|
|
|
columnIndexes = this.encodings[parseInt(value.charAt(0),10) ][3];
|
|
|
|
for(i=1; i<7; i++){
|
|
code += this.encodings[parseInt(value.charAt(i),10)][ parseInt(columnIndexes.charAt(i-1),10)];
|
|
}
|
|
|
|
code += "01010";
|
|
|
|
for(i=7; i<13; i++){
|
|
code += this.encodings[parseInt(value.charAt(i),10)][2];
|
|
}
|
|
|
|
code += "101";
|
|
return code;
|
|
},
|
|
template: function(value){
|
|
return value.replace(/[^0-9]/g,"").substring(0, 12) + this.checksum(value);
|
|
},
|
|
checksum: function (value){
|
|
var i,
|
|
odd = false,
|
|
sum = 0;
|
|
|
|
value = value.substring(0, 12);
|
|
for(i=0; i<12; i++){
|
|
sum += (odd ? 3 : 1) * parseInt(value.charAt(i),10);
|
|
odd = !odd;
|
|
}
|
|
return ((10 - sum % 10) % 10).toString();
|
|
}
|
|
});
|
|
|
|
/*
|
|
* UPC-A
|
|
* */
|
|
webix.type(webix.ui.barcode, {
|
|
name:"upcA",
|
|
firstDigit: true,
|
|
lastDigit: true,
|
|
encode: function(value){
|
|
if (value.length < 12) {
|
|
value = '0' + value;
|
|
}
|
|
return webix.ui.barcode.prototype.types.ean13.encode(value);
|
|
},
|
|
template: function(value){
|
|
return value.replace(/[^0-9]/g,"").substring(0, 11) + this.checksum(value);
|
|
},
|
|
checksum: function (value){
|
|
if (value.length < 12) {
|
|
value = '0' + value;
|
|
}
|
|
return webix.ui.barcode.prototype.types.ean13.checksum(value);
|
|
}
|
|
});
|
|
|
|
webix.protoUI({
|
|
name:"abslayout",
|
|
$init:function(){
|
|
this.$view.className += " webix_abslayout";
|
|
delete this.rows_setter;
|
|
delete this.cols_setter;
|
|
},
|
|
cells_setter:function(cells){
|
|
this._collection = cells;
|
|
},
|
|
_parse_cells:function(){
|
|
webix.ui.baselayout.prototype._parse_cells.call(this, this._collection);
|
|
},
|
|
$getSize:function(dx, dy){
|
|
var self_size = webix.ui.baseview.prototype.$getSize.call(this, 0, 0);
|
|
var sub = null;
|
|
|
|
for (var i=0; i<this._cells.length; i++)
|
|
if (this._cells[i]._settings.relative)
|
|
sub = this._cells[i].$getSize(0,0);
|
|
|
|
if (sub){
|
|
//use child settings if layout's one was not defined
|
|
if (self_size[1] >= 100000) self_size[1]=0;
|
|
if (self_size[3] >= 100000) self_size[3]=0;
|
|
|
|
self_size[0] = Math.max(self_size[0], sub[0]);
|
|
self_size[1] = Math.max(self_size[1], sub[1]);
|
|
self_size[2] = Math.max(self_size[2], sub[2]);
|
|
self_size[3] = Math.max(self_size[3], sub[3]);
|
|
}
|
|
|
|
return self_size;
|
|
},
|
|
$setSize:function(x,y){
|
|
this._layout_sizes = [x,y];
|
|
webix.debug_size_box_start(this);
|
|
|
|
webix.ui.baseview.prototype.$setSize.call(this,x,y);
|
|
this._set_child_size(x,y);
|
|
|
|
webix.debug_size_box_end(this, [x,y]);
|
|
},
|
|
_set_child_size:function(x,y){
|
|
for (var i=0; i<this._cells.length; i++){
|
|
var view = this._cells[i];
|
|
var conf = view._settings;
|
|
|
|
if (conf.relative){
|
|
conf.left = conf.top = 0;
|
|
conf.width = x;
|
|
conf.height = y;
|
|
}
|
|
|
|
var sizes = view.$getSize(0,0);
|
|
view.$setSize(sizes[0], sizes[2]);
|
|
|
|
var node = view.$view;
|
|
var options = ["left", "right", "top", "bottom"];
|
|
|
|
for (var j = 0; j < options.length; j++) {
|
|
var key = options[j];
|
|
if (key in conf)
|
|
node.style[key] = conf[key] + "px";
|
|
}
|
|
}
|
|
}
|
|
}, webix.ui.baselayout);
|
|
|
|
webix.protoUI({
|
|
name:"datalayout",
|
|
$init:function(){
|
|
this.data.provideApi(this, true);
|
|
this.data.attachEvent("onStoreUpdated", webix.bind(this.render, this));
|
|
},
|
|
_parse_cells:function(cells){
|
|
if (!this._origin_cells){
|
|
this._origin_cells = this._collection;
|
|
this._collection = [{}];
|
|
}
|
|
|
|
return webix.ui.layout.prototype._parse_cells.call(this, this._collection);
|
|
},
|
|
setValue:function(obj){
|
|
this.parse(obj);
|
|
},
|
|
getValue:function(obj){
|
|
var subcount = this._origin_cells.length;
|
|
for (var i = 0; i < this._cells.length; i++) {
|
|
var id = this.data.order[Math.floor(i/subcount)];
|
|
var item = this.data.getItem(id);
|
|
this._save_data(this._cells[i], item);
|
|
}
|
|
return this.data.serialize();
|
|
},
|
|
_save_data:function(view, prop){
|
|
var name = view._settings.name;
|
|
if (name){
|
|
var data = null;
|
|
if (view.getValues) data = view.getValues();
|
|
else if (view.getValue) data = view.getValue();
|
|
else if (view.serialize) data = view.serialize();
|
|
|
|
if (name == "$value")
|
|
webix.extend(prop, data, true);
|
|
else
|
|
prop[name] = data;
|
|
} else {
|
|
var collection = view._cells;
|
|
if (collection)
|
|
for (var i = 0; i < collection.length; i++)
|
|
this._save_data(collection[i], prop);
|
|
}
|
|
},
|
|
_fill_data:function(view, prop){
|
|
var obj, name = view._settings.name;
|
|
if (name){
|
|
if (name == "$value")
|
|
obj = prop;
|
|
else
|
|
obj = prop[name];
|
|
|
|
if (view.setValues) view.setValues(obj);
|
|
else if (view.setValue) view.setValue(obj);
|
|
else if (view.parse){
|
|
//make copy of data for treestore parsers
|
|
if (view.openAll)
|
|
obj = webix.copy(obj);
|
|
view.parse(obj);
|
|
}
|
|
} else {
|
|
var collection = view._cells;
|
|
if (collection)
|
|
for (var i = 0; i < collection.length; i++)
|
|
this._fill_data(collection[i], prop);
|
|
}
|
|
},
|
|
render:function(id, obj, mode){
|
|
var subcount = this._origin_cells.length;
|
|
|
|
if (id && mode === "update"){
|
|
//update mode, change only part of layout
|
|
var obj = this.getItem(id);
|
|
var index = this.getIndexById(id);
|
|
|
|
for(var i=0; i<subcount; i++)
|
|
this._fill_data(this._cells[index*subcount+i], obj);
|
|
return;
|
|
}
|
|
|
|
//full repainting
|
|
var cells = this._collection = [];
|
|
var order = this.data.order;
|
|
|
|
for (var i = 0; i < order.length; i++) {
|
|
if (subcount)
|
|
for (var j = 0; j < subcount; j++)
|
|
cells.push(webix.copy(this._origin_cells[j]));
|
|
else
|
|
cells.push(this.getItem(order[i]));
|
|
}
|
|
|
|
if (!cells.length) cells.push({});
|
|
|
|
this.reconstruct();
|
|
|
|
if (subcount)
|
|
for (var i = 0; i < order.length; i++) {
|
|
var prop = this.getItem(order[i]);
|
|
for (var j = 0; j < subcount; j++) {
|
|
var view = this._cells[i*subcount + j];
|
|
this._fill_data(view, prop);
|
|
}
|
|
}
|
|
}
|
|
}, webix.DataLoader, webix.ui.layout);
|
|
|
|
webix.protoUI({
|
|
$init:function(){
|
|
webix.extend(this, webix.FlexLayout, true);
|
|
},
|
|
name:"flexdatalayout"
|
|
}, webix.ui.datalayout);
|
|
|
|
/*
|
|
UI:Video
|
|
*/
|
|
|
|
webix.protoUI({
|
|
name:"video",
|
|
$init:function(config){
|
|
if (!config.id) config.id = webix.uid();
|
|
this.$ready.push(this._init_video);
|
|
},
|
|
_init_video:function(){
|
|
var c = this._settings;
|
|
this._contentobj = webix.html.create("video",{
|
|
"class":"webix_view_video",
|
|
"style":"width:100%;height:100%;",
|
|
"autobuffer":"autobuffer"
|
|
},"");
|
|
if(c.poster)
|
|
this._contentobj.poster=c.poster;
|
|
|
|
if(c.src){
|
|
if(typeof c.src!= "object")
|
|
c.src = [c.src];
|
|
for(var i = 0; i < c.src.length;i++)
|
|
this._contentobj.innerHTML += ' <source src="'+ c.src[i]+'">';
|
|
}
|
|
|
|
if(c.controls)
|
|
this._contentobj.controls=true;
|
|
if(c.autoplay)
|
|
this._contentobj.autoplay=true;
|
|
this._viewobj.appendChild(this._contentobj);
|
|
},
|
|
getVideo:function(){
|
|
return this._contentobj;
|
|
},
|
|
defaults:{
|
|
src:"",
|
|
controls: true
|
|
}
|
|
}, webix.ui.view);
|
|
|
|
webix.protoUI({
|
|
name:"sidemenu",
|
|
defaults: {
|
|
animate: true,
|
|
position: "left",
|
|
width: 200,
|
|
borderless: true
|
|
},
|
|
$init:function(){
|
|
this.$view.className += " webix_sidemenu";
|
|
},
|
|
$skin:function(){
|
|
this.defaults.padding = 0;
|
|
},
|
|
position_setter: function(value){
|
|
var prevPosition = this._settings.position;
|
|
if(prevPosition)
|
|
webix.html.removeCss(this.$view," webix_sidemenu_"+prevPosition);
|
|
webix.html.addCss(this.$view," webix_sidemenu_"+value);
|
|
return value;
|
|
},
|
|
$getSize: function(){
|
|
var sizes = webix.ui.window.prototype.$getSize.apply(this,arguments);
|
|
this._desired_sizes = sizes;
|
|
return sizes;
|
|
},
|
|
$setSize:function(x,y){
|
|
webix.ui.view.prototype.$setSize.call(this,x,y);
|
|
x = this._content_width-this._settings.padding*2;
|
|
y = this._content_height-this._settings.padding*2;
|
|
this._contentobj.style.padding = this._settings.padding+"px";
|
|
this._headobj.style.display="none";
|
|
this._bodyobj.style.height = y+"px";
|
|
this._body_cell.$setSize(x,y);
|
|
},
|
|
show: function(){
|
|
if(!this.callEvent("onBeforeShow",arguments))
|
|
return false;
|
|
|
|
this._settings.hidden = false;
|
|
this._viewobj.style.zIndex = (this._settings.zIndex||webix.ui.zIndex());
|
|
if (this._settings.modal || this._modal){
|
|
this._modal_set(true);
|
|
this._modal = null; // hidden_setter handling
|
|
}
|
|
this._viewobj.style.display = "block";
|
|
this._render_hidden_views();
|
|
if (this._settings.position)
|
|
this._setPosition();
|
|
|
|
this._hide_timer = 1;
|
|
webix.delay(function(){ this._hide_timer = 0; }, this, [], (webix.env.touch ? 400 : 100 ));
|
|
|
|
if (this.config.autofocus){
|
|
this._prev_focus = webix.UIManager.getFocus();
|
|
webix.UIManager.setFocus(this);
|
|
}
|
|
|
|
if (-1 == webix.ui._popups.find(this))
|
|
webix.ui._popups.push(this);
|
|
|
|
this.callEvent("onShow",[]);
|
|
},
|
|
_setPosition: function(x){
|
|
var width, height, maxWidth, maxHeight,
|
|
position,
|
|
left = 0, top = 0,
|
|
state = { };
|
|
|
|
|
|
this.$view.style.position = "fixed";
|
|
|
|
maxWidth = (window.innerWidth||document.documentElement.offsetWidth);
|
|
maxHeight = (window.innerHeight||document.documentElement.offsetHeight);
|
|
|
|
width = this._desired_sizes[0] || maxWidth;
|
|
height = this._desired_sizes[2] ||maxHeight;
|
|
|
|
webix.assert(width &&height, "Attempt to show not rendered window");
|
|
|
|
position = this._settings.position;
|
|
|
|
if(position == "top"){
|
|
width = maxWidth;
|
|
} else if(position == "right"){
|
|
height = maxHeight;
|
|
left = maxWidth - width;
|
|
} else if(position == "bottom"){
|
|
width = maxWidth;
|
|
top = maxHeight - height;
|
|
} else {
|
|
height = maxHeight;
|
|
}
|
|
|
|
state = { left: left, top: top,
|
|
width: width, height: height,
|
|
maxWidth: maxWidth, maxHeight: maxHeight
|
|
};
|
|
|
|
if (typeof this._settings.state == "function")
|
|
this._settings.state.call(this, state);
|
|
|
|
this._state = state;
|
|
|
|
this.$setSize(state.width, state.height);
|
|
|
|
if (typeof x == "undefined" && this._isAnimationSupported()){
|
|
webix.html.removeCss(this.$view,"webix_animate",true);
|
|
// set initial state
|
|
this._animate[this._settings.position].beforeShow.call(this, state);
|
|
// set apply animation css
|
|
webix.delay(function(){
|
|
webix.html.addCss(this.$view,"webix_animate",true);
|
|
},this, null,1);
|
|
// animate popup
|
|
webix.delay(function(){
|
|
this._animate[this._settings.position].show.call(this, state);
|
|
},this, null,10);
|
|
|
|
}
|
|
else{
|
|
|
|
this.setPosition(state.left, state.top);
|
|
}
|
|
},
|
|
_isAnimationSupported: function(){
|
|
return webix.animate.isSupported() && this._settings.animate && !(webix.env.isIE && navigator.appVersion.indexOf("MSIE 9")!=-1);
|
|
},
|
|
hidden_setter:function(value){
|
|
if(value)
|
|
this.hide(true);
|
|
else
|
|
this.show();
|
|
return !!value;
|
|
},
|
|
_animate:{
|
|
left: {
|
|
beforeShow: function(state){
|
|
this.$view.style.left = -state.width+"px";
|
|
this.$view.style.top = state.top+"px";
|
|
},
|
|
show: function(){
|
|
this.$view.style.left = "0px";
|
|
},
|
|
hide: function(state){
|
|
this.$view.style.left = -state.width+"px";
|
|
}
|
|
},
|
|
right: {
|
|
beforeShow: function(state){
|
|
this.$view.style.left = "auto";
|
|
this.$view.style.right = -state.width+"px";
|
|
this.$view.style.top = state.top+"px";
|
|
},
|
|
show: function(){
|
|
this.$view.style.right = 0 +"px";
|
|
},
|
|
hide: function(state){
|
|
this.$view.style.right = -state.width+"px";
|
|
}
|
|
},
|
|
top: {
|
|
beforeShow: function(state){
|
|
this.setPosition(state.left,state.top);
|
|
this.$view.style.height ="0px";
|
|
this._bodyobj.style.height ="0px";
|
|
},
|
|
show: function(state){
|
|
this.$view.style.height = state.height +"px";
|
|
this._bodyobj.style.height =state.height+"px";
|
|
},
|
|
hide: function(){
|
|
this.$view.style.height = "0px";
|
|
this._bodyobj.style.height = "0px";
|
|
}
|
|
},
|
|
bottom: {
|
|
beforeShow: function(state){
|
|
this.$view.style.left = state.left + "px";
|
|
this.$view.style.top = "auto";
|
|
var bottom = (state.bottom != webix.undefined?state.bottom:(state.maxHeight-state.top -state.height));
|
|
this.$view.style.bottom = bottom +"px";
|
|
this.$view.style.height ="0px";
|
|
},
|
|
show: function(state){
|
|
this.$view.style.height = state.height +"px";
|
|
},
|
|
hide: function(){
|
|
this.$view.style.height = "0px";
|
|
}
|
|
}
|
|
},
|
|
hide:function(force){
|
|
|
|
if (this.$destructed) return;
|
|
|
|
if (this._settings.modal)
|
|
this._modal_set(false);
|
|
|
|
var maxWidth = (window.innerWidth||document.documentElement.offsetWidth);
|
|
var maxHeight = (window.innerHeight||document.documentElement.offsetHeight);
|
|
|
|
if (!force && this._isAnimationSupported() && maxWidth == this._state.maxWidth && maxHeight == this._state.maxHeight){
|
|
// call 'hide' animation handler
|
|
this._animate[this._settings.position].hide.call(this, this._state);
|
|
// hide popup
|
|
var tid = webix.event(this.$view, webix.env.transitionEnd, webix.bind(function(ev){
|
|
this._hide_callback();
|
|
webix.eventRemove(tid);
|
|
},this));
|
|
}
|
|
else{
|
|
this._hide_callback();
|
|
}
|
|
|
|
if (this._settings.autofocus){
|
|
var el = document.activeElement;
|
|
if (el && this._viewobj && this._viewobj.contains(el)){
|
|
webix.UIManager.setFocus(this._prev_focus);
|
|
this._prev_focus = null;
|
|
}
|
|
}
|
|
|
|
this._hide_sub_popups();
|
|
|
|
}
|
|
|
|
}, webix.ui.popup);
|
|
|
|
(function(){
|
|
|
|
var webixCustomScroll = webix.CustomScroll = {
|
|
scrollStep:40,
|
|
init:function(){
|
|
this._init_once();
|
|
webix.env.$customScroll = true;
|
|
webix.ui.scrollSize = 0;
|
|
webix.destructors.push({
|
|
obj:{
|
|
destructor:function(){
|
|
this._last_active_node = null;
|
|
}
|
|
}
|
|
});
|
|
webix.attachEvent("onReconstruct", webixCustomScroll._on_reconstruct);
|
|
webix.attachEvent("onResize", webixCustomScroll._on_reconstruct);
|
|
|
|
//adjusts scroll after view repainting
|
|
//for example, opening a branch in the tree
|
|
//it will be better to handle onAfterRender of the related view
|
|
webix.attachEvent("onClick", webixCustomScroll._on_reconstruct);
|
|
},
|
|
resize:function(){
|
|
this._on_reconstruct();
|
|
},
|
|
_enable_datatable:function(view){
|
|
view._body._custom_scroll_view = view._settings.id;
|
|
view.attachEvent("onAfterRender", function(){
|
|
var scroll = webixCustomScroll._get_datatable_sizes(this);
|
|
var y = Math.max(scroll.dy - scroll.py, 0);
|
|
var x = Math.max(scroll.dx - scroll.px, 0);
|
|
|
|
if (this._y_scroll && this._scrollTop > y){
|
|
this._y_scroll.scrollTo(y);
|
|
}
|
|
else if (this._x_scroll && this._scrollLeft > x){
|
|
this._x_scroll.scrollTo(x);
|
|
}
|
|
|
|
if ( webixCustomScroll._last_active_node == this._body)
|
|
webixCustomScroll._on_reconstruct();
|
|
});
|
|
webix._event(view._body, "mouseover", webixCustomScroll._mouse_in );
|
|
webix._event(view._body, "mouseout", webixCustomScroll._mouse_out );
|
|
},
|
|
enable:function(view, mode){
|
|
webixCustomScroll._init_once();
|
|
if (view.mapCells)
|
|
return this._enable_datatable(view);
|
|
|
|
var node = view;
|
|
if (view._dataobj)
|
|
node = view._dataobj.parentNode;
|
|
|
|
node._custom_scroll_mode = mode||"xy";
|
|
webix._event(node, "mouseover", webixCustomScroll._mouse_in );
|
|
webix._event(node, "mouseout", webixCustomScroll._mouse_out );
|
|
webix._event(node, "mousewheel", webixCustomScroll._mouse_wheel );
|
|
webix._event(node, "DOMMouseScroll", webixCustomScroll._mouse_wheel );
|
|
|
|
// update scroll on data change
|
|
this._setDataHandler(view);
|
|
},
|
|
_on_reconstruct:function(){
|
|
var last = webixCustomScroll._last_active_node;
|
|
if (last && last._custom_scroll_size){
|
|
webixCustomScroll._mouse_out_timed.call(last);
|
|
webixCustomScroll._mouse_in.call(last);
|
|
}
|
|
},
|
|
_init_once:function(e){
|
|
webix.event(document.body, "mousemove", function(e){
|
|
if (webixCustomScroll._active_drag_area)
|
|
webixCustomScroll._adjust_scroll(webixCustomScroll._active_drag_area, webixCustomScroll._active_drag_area._scroll_drag_pos, webix.html.pos(e));
|
|
});
|
|
webixCustomScroll._init_once = function(){};
|
|
},
|
|
_mouse_in:function(e){
|
|
webixCustomScroll._last_active_node = this;
|
|
|
|
clearTimeout(this._mouse_out_timer);
|
|
if (this._custom_scroll_size || webixCustomScroll._active_drag_area) return;
|
|
|
|
var sizes;
|
|
if (this._custom_scroll_view){
|
|
//ger related view
|
|
var view = webix.$$(this._custom_scroll_view);
|
|
//if view was removed, we need not scroll anymore
|
|
if (!view) return;
|
|
sizes = webixCustomScroll._get_datatable_sizes(view);
|
|
} else{
|
|
sizes = {
|
|
dx:this.scrollWidth,
|
|
dy:this.scrollHeight,
|
|
px:this.clientWidth,
|
|
py:this.clientHeight
|
|
};
|
|
sizes._scroll_x = sizes.dx > sizes.px && this._custom_scroll_mode.indexOf("x") != -1;
|
|
sizes._scroll_y = sizes.dy > sizes.py && this._custom_scroll_mode.indexOf("y") != -1;
|
|
}
|
|
|
|
this._custom_scroll_size = sizes;
|
|
if (sizes._scroll_x){
|
|
sizes._scroll_x_node = webixCustomScroll._create_scroll(this, "x", sizes.dx, sizes.px, "width", "height");
|
|
sizes._sx = (sizes.px - sizes._scroll_x_node.offsetWidth - 4);
|
|
sizes._vx = sizes.dx - sizes.px;
|
|
if(webixCustomScroll.trackBar)
|
|
sizes._bar_x = webixCustomScroll._create_bar(this,"x");
|
|
}
|
|
if (sizes._scroll_y){
|
|
sizes._scroll_y_node = webixCustomScroll._create_scroll(this, "y", sizes.dy, sizes.py, "height", "width");
|
|
sizes._sy = (sizes.py - sizes._scroll_y_node.offsetHeight - 4);
|
|
sizes._vy = sizes.dy - sizes.py;
|
|
|
|
if(webixCustomScroll.trackBar)
|
|
sizes._bar_y = webixCustomScroll._create_bar(this,"y");
|
|
}
|
|
|
|
webixCustomScroll._update_scroll(this);
|
|
},
|
|
_create_bar: function(node, mode){
|
|
var bar = webix.html.create("DIV", {
|
|
"webixignore":"1",
|
|
"class":"webix_c_scroll_bar_"+mode
|
|
},"");
|
|
|
|
node.appendChild(bar);
|
|
return bar;
|
|
},
|
|
_adjust_scroll:function(node, old, pos){
|
|
var config = node._custom_scroll_size;
|
|
var view = node._custom_scroll_view;
|
|
if (view) view = webix.$$(view);
|
|
|
|
if (config._scroll_x_node == node._scroll_drag_enabled){
|
|
var next = (pos.x - old.x)*config._vx/config._sx;
|
|
if (view)
|
|
view._x_scroll.scrollTo(view._scrollLeft+next);
|
|
else
|
|
webixCustomScroll._set_scroll_value(node, "scrollLeft", next);
|
|
}
|
|
if (config._scroll_y_node == node._scroll_drag_enabled){
|
|
var next = (pos.y - old.y)*config._vy/config._sy;
|
|
if (view)
|
|
view._y_scroll.scrollTo(view._scrollTop+next);
|
|
else
|
|
webixCustomScroll._set_scroll_value(node, "scrollTop", next);
|
|
}
|
|
|
|
node._scroll_drag_pos = pos;
|
|
webixCustomScroll._update_scroll(node);
|
|
},
|
|
_get_datatable_sizes:function(view){
|
|
var sizes = {};
|
|
if (view._x_scroll && view._settings.scrollX){
|
|
sizes.dx = view._x_scroll._settings.scrollWidth;
|
|
sizes.px = view._x_scroll._last_set_size || 1;
|
|
sizes._scroll_x = sizes.dx - sizes.px > 1;
|
|
}
|
|
if (view._y_scroll && view._settings.scrollY){
|
|
sizes.dy = view._y_scroll._settings.scrollHeight;
|
|
sizes.py = view._y_scroll._last_set_size || 1;
|
|
sizes._scroll_y = sizes.dy - sizes.py > 1;
|
|
}
|
|
return sizes;
|
|
},
|
|
_mouse_out:function(){
|
|
clearTimeout(this._mouse_out_timer);
|
|
this._mouse_out_timer = webix.delay(webixCustomScroll._mouse_out_timed, this, [], 200);
|
|
},
|
|
_removeScroll:function(scroll){
|
|
if (scroll){
|
|
webix.html.remove(scroll);
|
|
if (scroll._webix_event_sc1){
|
|
webix.eventRemove(scroll._webix_event_sc1);
|
|
webix.eventRemove(scroll._webix_event_sc2);
|
|
}
|
|
}
|
|
},
|
|
_mouse_out_timed:function(){
|
|
if (this._custom_scroll_size){
|
|
if (this._scroll_drag_enabled){
|
|
this._scroll_drag_released = true;
|
|
return;
|
|
}
|
|
var sizes = this._custom_scroll_size;
|
|
webixCustomScroll._removeScroll(sizes._scroll_x_node);
|
|
webixCustomScroll._removeScroll(sizes._scroll_y_node);
|
|
webix.html.removeCss(document.body,"webix_noselect");
|
|
if(sizes._bar_x){
|
|
webix.html.remove(sizes._bar_x);
|
|
}
|
|
if(sizes._bar_y){
|
|
webix.html.remove(sizes._bar_y);
|
|
}
|
|
this._custom_scroll_size = null;
|
|
}
|
|
},
|
|
_mouse_wheel:function(e){
|
|
var sizes = this._custom_scroll_size;
|
|
var delta = e.wheelDelta/-40;
|
|
var toblock = true;
|
|
if (!delta && e.detail && webix.isUndefined(e.wheelDelta))
|
|
delta = e.detail;
|
|
if (sizes){
|
|
if (sizes._scroll_x_node && (e.wheelDeltaX || ( delta && !sizes._scroll_y_node ))){
|
|
var x_dir = (e.wheelDeltaX/-40)||delta;
|
|
//see below
|
|
toblock = webixCustomScroll._set_scroll_value(this, "scrollLeft", x_dir*webixCustomScroll.scrollStep);
|
|
} else if (delta && sizes._scroll_y_node){
|
|
|
|
//lesser flickering of scroll in IE
|
|
//also prevent scrolling outside of borders because of scroll-html-elements
|
|
toblock = webixCustomScroll._set_scroll_value(this, "scrollTop", delta*webixCustomScroll.scrollStep);
|
|
}
|
|
}
|
|
|
|
|
|
webixCustomScroll._update_scroll(this);
|
|
if (toblock !== false)
|
|
return webix.html.preventEvent(e);
|
|
},
|
|
_set_scroll_value:function(node, pose, value){
|
|
var sizes = node._custom_scroll_size;
|
|
var max_scroll = (pose == "scrollLeft") ? (sizes.dx - sizes.px) : (sizes.dy - sizes.py);
|
|
var now = node[pose];
|
|
|
|
if (now+value > max_scroll)
|
|
value = max_scroll - now;
|
|
if (!value || (now+value < 0 && now === 0))
|
|
return false;
|
|
|
|
|
|
if (webix.env.isIE){
|
|
webixCustomScroll._update_scroll(node, pose, value + now);
|
|
node[pose] += value;
|
|
} else
|
|
node[pose] += value;
|
|
|
|
return true;
|
|
},
|
|
_create_scroll:function(node, mode, dy, py, dim, pos){
|
|
var scroll = webix.html.create("DIV", {
|
|
"webixignore":"1",
|
|
"class":"webix_c_scroll_"+mode
|
|
},"<div></div>");
|
|
|
|
scroll.style[dim] = Math.max((py*py/dy-7),40)+"px";
|
|
node.style.position = "relative";
|
|
node.appendChild(scroll);
|
|
node._webix_event_sc1 = webix.event(scroll, "mousedown", webixCustomScroll._scroll_drag(node));
|
|
node._webix_event_sc2 = webix.event(document.body, "mouseup", webix.bind(webixCustomScroll._scroll_drop, node));
|
|
return scroll;
|
|
},
|
|
_scroll_drag:function(node){
|
|
return function(e){
|
|
webix.html.addCss(document.body,"webix_noselect",1);
|
|
this.className += " webix_scroll_active";
|
|
webixCustomScroll._active_drag_area = node;
|
|
node._scroll_drag_enabled = this;
|
|
node._scroll_drag_pos = webix.html.pos(e);
|
|
};
|
|
},
|
|
_scroll_drop:function(node){
|
|
if (this._scroll_drag_enabled){
|
|
webix.html.removeCss(document.body,"webix_noselect");
|
|
this._scroll_drag_enabled.className = this._scroll_drag_enabled.className.toString().replace(" webix_scroll_active","");
|
|
this._scroll_drag_enabled = false;
|
|
webixCustomScroll._active_drag_area = 0;
|
|
if (this._scroll_drag_released){
|
|
webixCustomScroll._mouse_out_timed.call(this);
|
|
this._scroll_drag_released = false;
|
|
}
|
|
}
|
|
},
|
|
_update_scroll:function(node, pose, value){
|
|
var sizes = node._custom_scroll_size;
|
|
if (sizes && (sizes._scroll_x_node||sizes._scroll_y_node)){
|
|
var view = node._custom_scroll_view;
|
|
|
|
var left_scroll = pose == "scrollLeft" ? value : node.scrollLeft;
|
|
var left = view?webix.$$(view)._scrollLeft:left_scroll;
|
|
var shift_left = view?0:left;
|
|
|
|
var top_scroll = pose == "scrollTop" ? value : node.scrollTop;
|
|
var top = view?(webix.$$(view)._scrollTop):top_scroll;
|
|
var shift_top = view?0:top;
|
|
|
|
if (sizes._scroll_x_node){
|
|
sizes._scroll_x_node.style.bottom = 1 - shift_top + "px";
|
|
sizes._scroll_x_node.style.left = Math.round(sizes._sx*left/(sizes.dx-sizes.px)) + shift_left + 1 +"px";
|
|
if(sizes._bar_x){
|
|
sizes._bar_x.style.bottom = 1 - shift_top + "px";
|
|
sizes._bar_x.style.left = shift_left + "px";
|
|
}
|
|
}
|
|
if (sizes._scroll_y_node){
|
|
sizes._scroll_y_node.style.right = 0 - shift_left + "px";
|
|
sizes._scroll_y_node.style.top = Math.round(sizes._sy*top/(sizes.dy-sizes.py)) + shift_top + 1 + "px";
|
|
if(sizes._bar_y){
|
|
sizes._bar_y.style.right = 0 - shift_left + "px";
|
|
sizes._bar_y.style.top = shift_top + "px";
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
},
|
|
_setDataHandler: function(view){
|
|
if(view.data && view.data.attachEvent)
|
|
view.data.attachEvent("onStoreUpdated", function(){
|
|
var node = webixCustomScroll._last_active_node;
|
|
if(node && view.$view.contains(node))
|
|
webixCustomScroll.resize();
|
|
});
|
|
}
|
|
};
|
|
|
|
})();
|
|
webix.protoUI({
|
|
name:"portlet",
|
|
defaults:{
|
|
layoutType:"wide",
|
|
},
|
|
$init:function(config){
|
|
this._viewobj.style.position = "relative";
|
|
|
|
if (config.header && config.body)
|
|
config.body = [ { template:config.header, type:"header" }, config.body ];
|
|
|
|
this.$ready.push(this._init_drag_area);
|
|
// refresh scroll state of datatables
|
|
webix.attachEvent("onAfterPortletMove", this._refreshChildScrolls);
|
|
},
|
|
_refreshChildScrolls: function(source){
|
|
webix.ui.each(source, function(view){
|
|
if(view._restore_scroll_state)
|
|
view._restore_scroll_state();
|
|
});
|
|
},
|
|
_init_drag_area:function(){
|
|
var childs = this.getChildViews();
|
|
|
|
if (childs.length > 1)
|
|
webix.DragControl.addDrag(childs[0].$view, this);
|
|
else {
|
|
var drag = webix.html.create("div", { "class":"portlet_drag" }, "<span class='webix_icon fa-bars'></span>");
|
|
this._viewobj.appendChild(drag);
|
|
webix.DragControl.addDrag(drag, this);
|
|
}
|
|
},
|
|
body_setter:function(value){
|
|
return this.rows_setter(webix.isArray(value) ? value:[value]);
|
|
},
|
|
markDropArea:function(target, mode){
|
|
if (!target)
|
|
return webix.html.remove(this._markerbox);
|
|
|
|
target = webix.$$(target);
|
|
|
|
if (!this._markerbox)
|
|
this._markerbox = webix.html.create("div",null," ");
|
|
|
|
target.$view.appendChild(this._markerbox);
|
|
this._markerbox.className = "portlet_marker"+mode;
|
|
},
|
|
movePortlet:function(target, mode){
|
|
var parent = target.getParentView();
|
|
var source = this.getParentView();
|
|
|
|
var tindex = parent.index(target);
|
|
var sindex = source.index(this);
|
|
|
|
if (!webix.callEvent("onBeforePortletMove", [source, parent, this, target, mode])) return;
|
|
|
|
webix.ui.$freeze = true;
|
|
|
|
var shift = (source != parent ? 1 : 0);
|
|
var isv = parent._vertical_orientation;
|
|
if ((mode == "top" || mode == "bottom")){
|
|
if (isv !== 1){
|
|
parent = webix.ui({ type:target._settings.layoutType, rows:[] }, parent, tindex+shift);
|
|
webix.ui(target, parent, 0);
|
|
tindex = 0; shift = 1;
|
|
}
|
|
if (mode == "bottom") shift+=1;
|
|
} else if ((mode == "left" || mode == "right")){
|
|
if (isv !== 0){
|
|
parent = webix.ui({ type:target._settings.layoutType, cols:[] }, parent, tindex+shift);
|
|
webix.ui(target, parent, 0);
|
|
tindex = 0; shift = 1;
|
|
}
|
|
if (mode == "right") shift+=1;
|
|
}
|
|
|
|
if (sindex < tindex) shift -= 1;
|
|
webix.ui(this, parent, tindex+shift );
|
|
if (mode == "replace")
|
|
webix.ui(target, source, sindex);
|
|
|
|
this._removeEmptySource(source);
|
|
|
|
webix.ui.$freeze = false;
|
|
|
|
var tops = source.getTopParentView();
|
|
target.resize();
|
|
source.resize();
|
|
|
|
webix.callEvent("onAfterPortletMove", [source, parent, this, target, mode]);
|
|
},
|
|
_removeEmptySource:function(view){
|
|
var childview;
|
|
var maxcount = 0;
|
|
|
|
while (view.getChildViews().length <= maxcount){
|
|
childview = view;
|
|
view = view.getParentView();
|
|
|
|
maxcount = 1;
|
|
}
|
|
|
|
if (maxcount)
|
|
view.removeView(childview);
|
|
},
|
|
$drag:function(object, e){
|
|
webix.html.addCss(this._viewobj, "portlet_in_drag");
|
|
webix.DragControl._drag_context = {source:object, from:object};
|
|
return this._viewobj.innerHTML;
|
|
},
|
|
$dragDestroy:function(target, html, e){
|
|
webix.html.removeCss(this._viewobj, "portlet_in_drag");
|
|
webix.html.remove(html);
|
|
if (this._portlet_drop_target){
|
|
this.movePortlet(this._portlet_drop_target, this._portlet_drop_mode);
|
|
this.markDropArea();
|
|
this._portlet_drop_target = null;
|
|
}
|
|
},
|
|
_getDragItemPos: function(){
|
|
return webix.html.offset(this.$view);
|
|
},
|
|
$dragPos: function(pos, e, html){
|
|
html.style.left = "-10000px";
|
|
var evObj = webix.env.mouse.context(e);
|
|
var node = document.elementFromPoint(evObj.x, evObj.y);
|
|
|
|
var view = null;
|
|
if (node)
|
|
view = webix.$$(node);
|
|
|
|
this._portlet_drop_target = this._getPortletTarget(view);
|
|
this._portlet_drop_mode = this._markPortletDrag(this._portlet_drop_target, e);
|
|
|
|
pos.x = pos.x - this._content_width + 10;
|
|
pos.y = pos.y - 20;
|
|
|
|
webix.DragControl._skip = true;
|
|
},
|
|
_markPortletDrag:function(view, ev){
|
|
var drop = "";
|
|
var mode = "";
|
|
|
|
if (ev && view){
|
|
var box = webix.html.offset(view.$view);
|
|
var pos = webix.html.pos(ev);
|
|
var erx = (pos.x-box.x) - box.width/2;
|
|
var ery = (pos.y-box.y) - box.height/2;
|
|
|
|
mode = view._settings.mode;
|
|
if (!mode)
|
|
mode = Math.abs(erx)*(box.height/box.width) > Math.abs(ery) ? "cols" : "rows";
|
|
|
|
if (mode == "cols"){
|
|
drop = erx >=0 ? "right" :"left";
|
|
} else if (mode == "rows"){
|
|
drop = ery >=0 ? "bottom" : "top";
|
|
}
|
|
|
|
this.markDropArea(view, drop);
|
|
}
|
|
|
|
this.markDropArea(view, drop);
|
|
return drop || mode;
|
|
},
|
|
_getPortletTarget:function(view){
|
|
while(view){
|
|
if (view.movePortlet)
|
|
return view;
|
|
else
|
|
view = view.getParentView();
|
|
}
|
|
}
|
|
}, webix.ui.layout);
|
|
|
|
webix.UIManager.getState = function(node, children) {
|
|
children = (children||false);
|
|
node = webix.$$(node);
|
|
var state = {
|
|
id: node.config.id,
|
|
width: node.config.width,
|
|
height: node.config.height,
|
|
gravity: node.config.gravity
|
|
};
|
|
if (!webix.isUndefined(node.config.collapsed)) state.collapsed = node.config.collapsed;
|
|
if (node.name === 'tabs' || node.name === 'tabbar') state.activeCell = node.getValue();
|
|
|
|
if (children) {
|
|
state = [state];
|
|
if (node._cells) {
|
|
for (var i = 0; i < node._cells.length; i++)
|
|
state = state.concat(this.getState(node._cells[i], children));
|
|
}
|
|
}
|
|
return state;
|
|
};
|
|
|
|
webix.UIManager.setState = function(states) {
|
|
if (!webix.isArray(states)) states = [states];
|
|
|
|
for (var i = 0; i < states.length; i++) {
|
|
var state = states[i];
|
|
var node = webix.$$(state.id);
|
|
if (!node) continue;
|
|
|
|
if (!webix.isUndefined(state.collapsed)) node.define('collapsed', state.collapsed);
|
|
if (!webix.isUndefined(state.activeCell)) node.setValue(state.activeCell);
|
|
|
|
node.define('width', state.width);
|
|
node.define('height', state.height);
|
|
node.define('gravity', state.gravity);
|
|
}
|
|
var top = webix.$$(states[0].id);
|
|
if (top) top.resize();
|
|
};
|
|
|
|
|
|
(function(){
|
|
|
|
var errorMessage = "non-existing view for export";
|
|
|
|
webix.toPNG = function(id, options){
|
|
var defer = webix.promise.defer();
|
|
|
|
webix.require(webix.cdn + "/extras/html2canvas.min.js", function(){
|
|
//backward compatibility
|
|
if (typeof options === "string") options = { filename: options };
|
|
options = options || {};
|
|
|
|
var view = webix.$$(id);
|
|
if (view && view.$exportView)
|
|
view = view.$exportView(options);
|
|
webix.assert(view, errorMessage);
|
|
if(!view) return defer.reject(errorMessage);
|
|
|
|
var node = view ? view.$view : webix.toNode(id);
|
|
var filename = (options.filename||"Data")+".png";
|
|
|
|
window.html2canvas(node).then(function(canvas) {
|
|
var callback = function(data){
|
|
if(options.download !== false)
|
|
webix.html.download(data, filename);
|
|
canvas.remove();
|
|
defer.resolve(data);
|
|
};
|
|
if(canvas.msToBlob)
|
|
callback(canvas.msToBlob());
|
|
else
|
|
canvas.toBlob(callback, "image/png");
|
|
});
|
|
});
|
|
return defer;
|
|
};
|
|
|
|
webix.toExcel = function(id, options){
|
|
options = options || {};
|
|
options.export_mode = "excel";
|
|
|
|
id = webix.isArray(id)?id:[id];
|
|
var views = [];
|
|
|
|
for(var i = 0; i<id.length; i++){
|
|
var view = webix.$$(id[i]);
|
|
if (view && view.$exportView)
|
|
view = view.$exportView(options);
|
|
if(view) views = views.concat(view);
|
|
webix.assert(view, errorMessage);
|
|
|
|
//spreadsheet and excelviewer require plain data output first
|
|
if(options.dataOnly){
|
|
var scheme = getExportScheme(view, options);
|
|
views[i] = {
|
|
scheme : scheme,
|
|
exportData:getExportData(view, options, scheme),
|
|
spans:(options.spans ? getSpans(view, options) : [])
|
|
};
|
|
}
|
|
}
|
|
if(options.dataOnly) return views;
|
|
|
|
var defer = webix.promise.defer();
|
|
webix.require(webix.cdn + "/extras/xlsx.core.styles.min.js", function(){
|
|
if(!views.length) return defer.reject(errorMessage);
|
|
|
|
var wb = { SheetNames:[], Sheets:{}, Workbook:{ WBProps :{}, Names:[] }};
|
|
var name = webix.isArray(options.sheets) ? options.sheets : [options.name || "Data"];
|
|
|
|
for(var i = 0; i<views.length; i++){
|
|
var scheme = views[i].scheme || getExportScheme(views[i], options);
|
|
var result = views[i].exportData || getExportData(views[i], options, scheme);
|
|
var spans = views[i].spans ? views[i].spans: (options.spans ? getSpans(views[i], options) : []);
|
|
var ranges = views[i].ranges || [];
|
|
var styles = views[i].styles || [];
|
|
var data = getExcelData(result, scheme, spans, styles);
|
|
var sname = (name[i] || "Data"+i).replace(/[\*\?\:\[\]\\\/]/g,"").substring(0, 31);
|
|
|
|
wb.SheetNames.push(sname);
|
|
wb.Sheets[sname] = data;
|
|
wb.Workbook.Names = wb.Workbook.Names.concat(ranges);
|
|
}
|
|
|
|
var xls = XLSX.write(wb, {bookType:'xlsx', bookSST:false, type: 'binary'});
|
|
var filename = (options.filename || name.join(","))+".xlsx";
|
|
|
|
var blob = new Blob([str2array(xls)], { type: "application/xlsx" });
|
|
if(options.download !== false)
|
|
webix.html.download(blob, filename);
|
|
defer.resolve(blob);
|
|
});
|
|
return defer;
|
|
};
|
|
|
|
webix.toCSV = function(id, options){
|
|
options = options || {};
|
|
|
|
var view = webix.$$(id);
|
|
if (view && view.$exportView)
|
|
view = view.$exportView(options);
|
|
webix.assert(view, errorMessage);
|
|
if(!view) return webix.promise.reject(errorMessage);
|
|
|
|
options.export_mode = "csv";
|
|
options.filterHTML = true;
|
|
|
|
var scheme = getExportScheme(view, options);
|
|
var result = getExportData(view, options, scheme);
|
|
|
|
var data = getCsvData(result, scheme);
|
|
var filename = (options.filename || "Data")+".csv";
|
|
|
|
var blob = new Blob(["\uFEFF" + data], { type: "text/csv" });
|
|
if(options.download !== false)
|
|
webix.html.download(blob, filename);
|
|
|
|
return webix.promise.resolve(blob);
|
|
};
|
|
|
|
function getCsvData(data, scheme) {
|
|
return webix.csv.stringify(data);
|
|
}
|
|
|
|
var font;
|
|
webix.toPDF = function(id, options){
|
|
var defer = webix.promise.defer();
|
|
|
|
webix.require(webix.cdn + "/extras/pdfjs.js", function(){
|
|
options = options || {};
|
|
|
|
var view = webix.$$(id);
|
|
if (view && view.$exportView)
|
|
view = view.$exportView(options);
|
|
webix.assert(view, "non-existing view for export");
|
|
if(!view) return defer.reject();
|
|
|
|
options.export_mode = "pdf";
|
|
options._export_font = font;
|
|
options.fontName = options.fontName ||"pt-sans.regular";
|
|
|
|
var scheme = getExportScheme(view, options);
|
|
var data = getExportData(view, options, scheme);
|
|
|
|
var callback = function(pdf, options){
|
|
var filename = (options.filename || "Data")+".pdf";
|
|
var blob = new Blob([pdf.toString()], { type: "application/pdf" });
|
|
|
|
if(options.download !== false)
|
|
webix.html.download(blob, filename);
|
|
defer.resolve(blob);
|
|
};
|
|
|
|
if(options._export_font)
|
|
getPdfData(scheme, data, options, callback);
|
|
else
|
|
pdfjs.load(webix.cdn + "/extras/"+options.fontName+".ttf", function(err, buf){
|
|
if(err) throw err;
|
|
font = options._export_font = new pdfjs.TTFFont(buf);
|
|
getPdfData(scheme, data, options, callback);
|
|
});
|
|
});
|
|
return defer;
|
|
};
|
|
|
|
function getDataHelper(key, column, raw){
|
|
if (!raw && column.format)
|
|
return function(obj){ return column.format(obj[key]); };
|
|
|
|
return function(obj){ return obj[key]; };
|
|
}
|
|
function getExportScheme(view, options){
|
|
var scheme = [];
|
|
var h_count = 0, f_count = 0;
|
|
var isTable = view.getColumnConfig;
|
|
var columns = options.columns;
|
|
var raw = !!options.rawValues;
|
|
scheme.heights = {};
|
|
|
|
if (!columns){
|
|
if (isTable)
|
|
columns = [].concat(view._columns);
|
|
else {
|
|
columns = [];
|
|
var obj = view.data.pull[view.data.order[0]];
|
|
for (var key in obj)
|
|
if(key !== "id")
|
|
columns.push({id:key});
|
|
}
|
|
}
|
|
else if(!columns.length){
|
|
//export options are set as - columns:{ rank:true, title:{ header:"custom"}}
|
|
var arr = [];
|
|
for(var key in columns)
|
|
arr.push(webix.extend({ id:key}, webix.extend({}, columns[key])));
|
|
columns = arr;
|
|
}
|
|
|
|
if (options.ignore)
|
|
for (var i=columns.length-1; i>=0; i--)
|
|
if (options.ignore[columns[i].id])
|
|
columns.splice(i,1);
|
|
|
|
if (options.id)
|
|
scheme.push({ id:"id", width:50, header:" ", template:function(obj){ return obj.id; }});
|
|
|
|
if (options.flatTree){
|
|
var flatKey = options.flatTree.id;
|
|
var copy = [].concat(options.flatTree.columns);
|
|
var fill = [];
|
|
var fillMode = !!options.flatTree.fill;
|
|
for (var i = 1; i <= copy.length; i++)
|
|
copy[i-1].template = (function(i, c){
|
|
return function(obj){
|
|
return obj.$level == i ? (fill[i]=obj[flatKey]) : ((fillMode && i<obj.$level)?fill[i]:"");
|
|
};
|
|
})(i);
|
|
|
|
var index = 0;
|
|
for (var i = columns.length-1; i >= 0; i--)
|
|
if (columns[i].id === flatKey)
|
|
index = i;
|
|
|
|
columns = [].concat(columns.slice(0,index)).concat(copy).concat(columns.slice(index+1));
|
|
}
|
|
|
|
|
|
for (var j = 0; j < columns.length; j++) {
|
|
var column = columns[j];
|
|
var key = column.id;
|
|
|
|
if (column.noExport) continue;
|
|
|
|
if (isTable && view._columns_pull[key])
|
|
column = webix.extend(webix.extend({}, column), view._columns_pull[key]);
|
|
|
|
var record = {
|
|
id: column.id,
|
|
template: (( raw || !column.template) ? getDataHelper(key, column, raw) : column.template ),
|
|
width: ((column.width || 200) * (options.export_mode==="excel"?8.43/70:1 )),
|
|
header: (column.header!==false?(column.header||key) : "")
|
|
};
|
|
|
|
if(typeof record.header === "string") record.header = [{text:record.header}];
|
|
else record.header = webix.copy(record.header);
|
|
|
|
for(var i = 0; i<record.header.length; i++)
|
|
record.header[i] = record.header[i]?(record.header[i].contentId?"":record.header[i].text):"";
|
|
h_count = Math.max(h_count, record.header.length);
|
|
|
|
if(view._settings.footer){
|
|
var footer = column.footer || "";
|
|
if(typeof footer == "string") footer = [{text:footer}];
|
|
else footer = webix.copy(footer);
|
|
|
|
for(var i = 0; i<footer.length; i++){
|
|
if(footer[i]) footer[i] = footer[i].contentId?view.getHeaderContent(footer[i].contentId).getValue():footer[i].text;
|
|
else footer[i] = "";
|
|
}
|
|
record.footer = footer;
|
|
f_count = Math.max(f_count, record.footer.length);
|
|
}
|
|
scheme.push(record);
|
|
}
|
|
|
|
for(var i =0; i<scheme.length; i++){
|
|
|
|
var diff = h_count-scheme[i].header.length;
|
|
for(var d=0; d<diff; d++)
|
|
scheme[i].header.push("");
|
|
|
|
if(view._settings.footer){
|
|
diff = f_count-scheme[i].footer.length;
|
|
for(var d=0; d<diff; d++)
|
|
scheme[i].footer.push("");
|
|
}
|
|
}
|
|
|
|
return scheme;
|
|
}
|
|
|
|
|
|
function getExportData(view, options, scheme){
|
|
var filterHTML = !!options.filterHTML;
|
|
var htmlFilter = /<[^>]*>/gi;
|
|
var data = [];
|
|
var header, headers;
|
|
|
|
if(options.export_mode ==="excel" && options.docHeader){
|
|
data = [[(options.docHeader.text || options.docHeader).toString()], [""]];
|
|
if(options.docHeader.height)
|
|
scheme.heights[0] = options.docHeader.height;
|
|
}
|
|
|
|
if( options.header !== false && scheme.length){
|
|
for(var h=0; h < scheme[0].header.length; h++){
|
|
headers = [];
|
|
for (var i = 0; i < scheme.length; i++){
|
|
header = "";
|
|
if(scheme[i].header[h]){
|
|
header = scheme[i].header[h];
|
|
if (filterHTML)
|
|
header = scheme[i].header[h] = header.replace(htmlFilter, "");
|
|
}
|
|
headers.push(header);
|
|
}
|
|
|
|
if(options.export_mode =="excel" && view._columns && options.heights !==false &&
|
|
(view._headers[h] !== webix.skin.$active.barHeight || options.heights == "all")
|
|
) scheme.heights[data.length] = view._headers[h];
|
|
|
|
if (options.export_mode !== "pdf")
|
|
data[data.length] = headers;
|
|
}
|
|
}
|
|
options.yCorrection = (options.yCorrection||0)-data.length;
|
|
|
|
var isTree = (view.data.name == "TreeStore");
|
|
var treeline = (options.flatTree || options.plainOutput) ? "" : " - ";
|
|
|
|
view.data.each(function(item){
|
|
if(item){ //dyn loading
|
|
var line = [];
|
|
for (var i = 0; i < scheme.length; i++){
|
|
var column = scheme[i], cell = null;
|
|
//spreadsheet can output math
|
|
if(options.math && item["$"+column.id] && item["$"+column.id].charAt(0) =="=" && !item["$"+column.id].match(/^=(image|link|sparkline)\(/i))
|
|
cell = item["$"+column.id];
|
|
if(this._spans_pull){
|
|
var span = this.getSpan(item.id, column.id);
|
|
if(span && span[4] && span[0] == item.id && span[1] == column.id)
|
|
cell = span[4];
|
|
}
|
|
if(!cell){
|
|
cell = column.template(item, view.type, item[column.id], column, i);
|
|
if (!cell && cell !== 0) cell = "";
|
|
if (filterHTML && typeof cell === "string"){
|
|
if(isTree)
|
|
cell = cell.replace(/<div class=.webix_tree_none.><\/div>/, treeline);
|
|
cell = cell.replace(htmlFilter, "");
|
|
}
|
|
//remove end/start spaces(ex.hierarchy data)
|
|
if (typeof cell === "string" && options.export_mode === "csv")
|
|
cell = cell.trim();
|
|
//for multiline data
|
|
if (typeof cell === "string" && (options.export_mode === "excel" || options.export_mode === "csv")){
|
|
cell = cell.replace(/<br\s*\/?>/mg,"\n");
|
|
}
|
|
}
|
|
line.push(cell);
|
|
}
|
|
|
|
if(options.export_mode =="excel" && view._columns && options.heights !==false &&
|
|
((item.$height && item.$height !== webix.skin.$active.rowHeight) || options.heights =="all")
|
|
) scheme.heights[data.length] = item.$height || this.config.rowHeight;
|
|
|
|
data.push(line);
|
|
}
|
|
}, view);
|
|
|
|
if( options.footer !==false ){
|
|
var f_count = scheme[0].footer?scheme[0].footer.length:0;
|
|
for (var f = 0; f < f_count; f++){
|
|
var footers = [];
|
|
for(var i = 0; i<scheme.length; i++){
|
|
var footer = scheme[i].footer[f];
|
|
if (filterHTML) footer = scheme[i].footer[f] = footer.toString().replace(htmlFilter, "");
|
|
footers.push(footer);
|
|
}
|
|
|
|
|
|
if(options.export_mode =="excel" && view._columns && options.heights !==false &&
|
|
(view._footers[f] !== webix.skin.$active.barHeight || options.heights=="all")
|
|
) scheme.heights[data.length] = view._footers[f];
|
|
|
|
if(options.export_mode !== "pdf")
|
|
data.push(footers);
|
|
}
|
|
}
|
|
|
|
if(options.export_mode ==="excel" && options.docFooter){
|
|
data = data.concat([[], [(options.docFooter.text || options.docFooter).toString()]]);
|
|
if(options.docFooter.height)
|
|
scheme.heights[data.length-1] = options.docFooter.height;
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
function getColumnsWidths(scheme){
|
|
var wscols = [];
|
|
for (var i = 0; i < scheme.length; i++)
|
|
wscols.push({ wch: scheme[i].width });
|
|
|
|
return wscols;
|
|
}
|
|
|
|
function excelDate(date) {
|
|
return Math.round(25569 + date / (24 * 60 * 60 * 1000));
|
|
}
|
|
|
|
function getSpans(view, options){
|
|
var isTable = view.getColumnConfig;
|
|
var pull = view._spans_pull;
|
|
var spans = [];
|
|
|
|
if(isTable){
|
|
if(options.header!==false)
|
|
spans = getHeaderSpans(view, options, "header", spans);
|
|
|
|
if(pull){
|
|
var xc = options.xCorrection || 0;
|
|
var yc = options.yCorrection || 0;
|
|
for(var row in pull){
|
|
//{ s:{c:1, r:0}, e:{c:3, r:0} }
|
|
var cols = pull[row];
|
|
for(var col in cols){
|
|
var sc = view.getColumnIndex(col) - xc;
|
|
var sr = view.getIndexById(row) - yc;
|
|
var ec = sc+cols[col][0]-1;
|
|
var er = sr+(cols[col][1]-1);
|
|
|
|
spans.push({ s:{c:sc, r:sr}, e:{c:ec, r:er} });
|
|
}
|
|
}
|
|
}
|
|
if(options.footer!==false)
|
|
spans = getHeaderSpans(view, options, "footer", spans);
|
|
}
|
|
|
|
return spans;
|
|
}
|
|
|
|
function getHeaderSpans(view, options, group, spans){
|
|
var columns = view.config.columns;
|
|
var delta = (options.docHeader?2:0)+(group == "header" ? 0 :((options.header!==false?view._headers.length:0)+view.count()));
|
|
|
|
for(var i=0; i<columns.length; i++){
|
|
var header = columns[i][group];
|
|
for(var h = 0; h<header.length; h++){
|
|
if(header[h] && (header[h].colspan || header[h].rowspan)){
|
|
spans.push({
|
|
s:{ c:i, r:h+delta},
|
|
e:{ c:i+(header[h].colspan||1)-1, r:h+(header[h].rowspan ||1)-1+delta }
|
|
});
|
|
}
|
|
}
|
|
}
|
|
return spans;
|
|
}
|
|
|
|
function getStyles(r, c, styles){
|
|
//row index, column index, styles array
|
|
if(styles[r] && styles[r][c])
|
|
return styles[r][c];
|
|
return "";
|
|
}
|
|
|
|
function getRowHeights(heights){
|
|
for(var i in heights)
|
|
heights[i] = {hpx:heights[i], hpt:heights[i]*0.75};
|
|
return heights;
|
|
}
|
|
|
|
var table = "_table";
|
|
function getExcelData(data, scheme, spans, styles) {
|
|
var ws = {};
|
|
var range = {s: {c:10000000, r:10000000}, e: {c:0, r:0 }};
|
|
for(var R = 0; R != data.length; ++R) {
|
|
for(var C = 0; C != data[R].length; ++C) {
|
|
if(range.s.r > R) range.s.r = R;
|
|
if(range.s.c > C) range.s.c = C;
|
|
if(range.e.r < R) range.e.r = R;
|
|
if(range.e.c < C) range.e.c = C;
|
|
|
|
var cell = {v: data[R][C] };
|
|
if(cell.v === null) continue;
|
|
var cell_ref = XLSX.utils.encode_cell({c:C,r:R});
|
|
|
|
if(typeof cell.v === "number" || (cell.v && !isNaN(cell.v*1))){
|
|
cell.v = cell.v*1;
|
|
cell.t = "n";
|
|
}
|
|
else if(typeof cell.v === "boolean")
|
|
cell.t = "b";
|
|
else if(cell.v instanceof Date) {
|
|
cell.t = "n"; cell.z = XLSX.SSF[table][14];
|
|
cell.v = excelDate(cell.v);
|
|
}
|
|
else if(cell.v.charAt(0) == "="){
|
|
cell.t = "n";
|
|
cell.f = cell.v;
|
|
delete cell.v;
|
|
}
|
|
else cell.t = "s";
|
|
|
|
if(styles)
|
|
cell.s = getStyles(R, C, styles);
|
|
|
|
ws[cell_ref] = cell;
|
|
}
|
|
}
|
|
if(range.s.c < 10000000) ws["!ref"] = XLSX.utils.encode_range(range);
|
|
|
|
ws["!rows"] = getRowHeights(scheme.heights);
|
|
ws["!cols"] = getColumnsWidths(scheme);
|
|
if(spans.length)
|
|
ws["!merges"] = spans;
|
|
|
|
return ws;
|
|
}
|
|
|
|
function str2array(s) {
|
|
var buf = new ArrayBuffer(s.length);
|
|
var view = new Uint8Array(buf);
|
|
for (var i=0; i!=s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
|
|
return buf;
|
|
}
|
|
|
|
function getPdfData(scheme, data, options, callback){
|
|
|
|
|
|
options.header = (webix.isUndefined(options.header) || options.header === true) ? {} : options.header;
|
|
options.footer = (webix.isUndefined(options.footer) || options.footer === true) ? {} : options.footer;
|
|
options.table = options.table || {};
|
|
|
|
var width = options.width||595.296, height = options.height || 841.896;// default A4 size
|
|
|
|
if(options.orientation && options.orientation ==="landscape")
|
|
height = [width, width = height][0];
|
|
|
|
if(options.autowidth){
|
|
width = 80; //paddings
|
|
for(var i = 0; i<scheme.length; i++)
|
|
width += scheme[i].width;
|
|
}
|
|
|
|
var doc = new pdfjs.Document({
|
|
padding: 40,
|
|
font: options._export_font,
|
|
threshold:256,
|
|
width:width,
|
|
height:height
|
|
});
|
|
|
|
|
|
//render table
|
|
var h_count = options.header === false ? 0: scheme[0].header.length;
|
|
var f_count = (options.footer === false || !scheme[0].footer) ? 0: scheme[0].footer.length;
|
|
|
|
var colWidths = [];
|
|
for(var i = 0; i<scheme.length; i++)
|
|
colWidths[i] = scheme[i].width;
|
|
|
|
var tableOps = webix.extend(options.table, {
|
|
borderWidth: 1,height:20, lineHeight:1.1,
|
|
borderColor: 0xEEEEEE, backgroundColor: 0xFFFFFF, color:0x666666,
|
|
textAlign:"left", paddingRight:10, paddingLeft:10,
|
|
headerRows:h_count, widths: colWidths.length?colWidths:["100%"]
|
|
});
|
|
|
|
var table = doc.table(tableOps);
|
|
|
|
//render table header
|
|
if(h_count){
|
|
var headerOps = webix.extend(options.header, {
|
|
borderRightColor:0xB0CEE3, borderBottomColor:0xB0CEE3,
|
|
color:0x4A4A4A, backgroundColor:0xD2E3EF,
|
|
height:27, lineHeight:1.2
|
|
});
|
|
|
|
for(var i = 0; i<h_count; i++){
|
|
var header = table.tr(headerOps);
|
|
for(var s=0; s<scheme.length; s++)
|
|
header.td(scheme[s].header[i].toString());
|
|
}
|
|
}
|
|
|
|
//render table data
|
|
for(var r=0; r<data.length;r++){
|
|
var row = table.tr({});
|
|
for(var c=0; c< data[r].length; c++)
|
|
row.td(data[r][c]);
|
|
}
|
|
|
|
//render table footer
|
|
if(f_count){
|
|
var footerOps = webix.extend(options.footer, {
|
|
borderRightColor:0xEEEEEE, borderBottomColor:0xEEEEEE,
|
|
backgroundColor: 0xFAFAFA, color:0x666666,
|
|
height:27, lineHeight:1.2
|
|
});
|
|
|
|
for(var i = 0; i<f_count; i++){
|
|
var footer = table.tr(footerOps);
|
|
for(var s=0; s<scheme.length; s++)
|
|
footer.td(scheme[s].footer[i].toString());
|
|
}
|
|
}
|
|
|
|
//doc footer
|
|
if(options.docFooter !== false){
|
|
var ft = doc.footer();
|
|
ft.text({
|
|
color: 0x666666, textAlign:"center"
|
|
}).append((webix.i18n.dataExport.page||"Page")).pageNumber().append(" "+(webix.i18n.dataExport.of || "of")+" ").pageCount();
|
|
}
|
|
|
|
var horder = { text:0, image:1};
|
|
|
|
//doc header, configurable
|
|
if(options.docHeader){
|
|
if(typeof options.docHeader == "string") options.docHeader = {text:options.docHeader};
|
|
webix.extend(options.docHeader, {
|
|
color: 0x666666, textAlign:"right", order:0
|
|
});
|
|
horder.text = options.docHeader.order;
|
|
}
|
|
|
|
if (options.docHeaderImage){
|
|
if(typeof options.docHeaderImage == "string") options.docHeaderImage = {url:options.docHeaderImage};
|
|
webix.extend(options.docHeaderImage, {
|
|
align:"right", order:1
|
|
});
|
|
horder.image = options.docHeaderImage.order;
|
|
}
|
|
|
|
if(options.docHeader && horder.image > horder.text)
|
|
doc.header({paddingBottom:10}).text(options.docHeader.text, options.docHeader);
|
|
|
|
if (options.docHeaderImage){
|
|
pdfjs.load(options.docHeaderImage.url, function(err, buffer){
|
|
if (!err){
|
|
var img = new pdfjs.Image(buffer);
|
|
doc.header({paddingBottom:10}).image(img, options.docHeaderImage);
|
|
|
|
if(options.docHeader && horder.image < horder.text)
|
|
doc.header({paddingBottom:10}).text(options.docHeader.text, options.docHeader);
|
|
}
|
|
//render pdf and show in browser
|
|
var pdf = doc.render();
|
|
callback(pdf, options);
|
|
});
|
|
}
|
|
else{
|
|
//render pdf and show in browser
|
|
var pdf = doc.render();
|
|
callback(pdf, options);
|
|
}
|
|
}
|
|
|
|
})();
|
|
|
|
|
|
webix.protoUI({
|
|
name:"pdfviewer",
|
|
defaults:{
|
|
scale:"auto"
|
|
},
|
|
$init:function(config){
|
|
this.$view.className += " webix_pdf";
|
|
|
|
var elm_wrapper = document.createElement("DIV");
|
|
elm_wrapper.className="canvas_wrapper";
|
|
|
|
var elm = document.createElement("canvas");
|
|
|
|
this._currentPage = this.$view;
|
|
this._container = this.$view.appendChild(elm_wrapper);
|
|
this._canvas = this._container.appendChild(elm);
|
|
|
|
this.$pdfDoc = null;
|
|
this.$pageNum = 0;
|
|
this.$numPages = 0;
|
|
this._pageRendering = false;
|
|
this._pageNumPending = null;
|
|
this._ctx = this._canvas.getContext('2d');
|
|
|
|
this._init_scale_value = 0.1;
|
|
this._default_scale_delta = config.scaleDelta || 1.1;
|
|
this._min_scale = config.minScale || 0.25;
|
|
this._max_scale = config.maxScale || 10.0;
|
|
this._max_auto_scale = 1.25;
|
|
|
|
this._hPadding = 40;
|
|
this._vPadding = 10;
|
|
|
|
this.$ready.push(this._attachHandlers);
|
|
},
|
|
toolbar_setter:function(toolbar){
|
|
if (typeof toolbar == "string"){
|
|
var ui_toolbar = webix.$$(toolbar);
|
|
if (ui_toolbar){
|
|
ui_toolbar.$master = this;
|
|
ui_toolbar.refresh();
|
|
}
|
|
this.attachEvent("onDocumentReady", function(){
|
|
if(ui_toolbar){
|
|
ui_toolbar.setPage(this.$pageNum);
|
|
ui_toolbar.setValues(this.$numPages, this._settings.scale);
|
|
}
|
|
else
|
|
this.toolbar_setter(toolbar);
|
|
});
|
|
return toolbar;
|
|
}
|
|
},
|
|
_attachHandlers:function(){
|
|
delete this._settings.datatype; // cheat(
|
|
|
|
this.attachEvent("onScaleChange", function(scale, update){
|
|
if(update && this._settings.toolbar && webix.$$(this._settings.toolbar).setScale)
|
|
webix.$$(this._settings.toolbar).setScale(scale);
|
|
});
|
|
|
|
if(webix.env.touch){
|
|
this._touchDelta = false;
|
|
|
|
webix._event(this._viewobj, "touchstart", webix.bind(function(e){
|
|
var touches = e.targetTouches;
|
|
if(touches.length === 2){
|
|
webix.html.preventEvent(e);
|
|
this._touchDelta = Math.abs(touches[0].pageY - touches[1].pageY);
|
|
}
|
|
}, this));
|
|
|
|
webix._event(this.$view, "touchmove", webix.bind(function(e){
|
|
var touches = e.targetTouches;
|
|
|
|
if(touches.length === 2 && this._touchDelta !== false){
|
|
webix.html.preventEvent(e);
|
|
|
|
if(Math.abs(touches[0].pageY - touches[1].pageY)>this._touchDelta)
|
|
this.zoomIn();
|
|
else
|
|
this.zoomOut();
|
|
this._touchDelta = false;
|
|
}
|
|
}, this));
|
|
|
|
this.attachEvent("onSwipeX", function(start, end){
|
|
this.$view.scrollLeft = this.$view.scrollLeft - (end.x-start.x);
|
|
});
|
|
|
|
this.attachEvent("onSwipeY", function(start, end){
|
|
var ch = this.$view.clientHeight,
|
|
sh = this.$view.scrollHeight,
|
|
oh = this.$view.offsetHeight,
|
|
stop = this.$view.scrollTop,
|
|
delta = end.y-start.y;
|
|
|
|
if(ch === sh || (delta<0 && stop > (sh - oh)) || (delta>0 && stop === 0)){
|
|
var page = this.$pageNum + (delta > 0 ? -1 :1);
|
|
if(page>0 && page <=this.$numPages){
|
|
this.$pageNum = page;
|
|
this._queueRenderPage(this.$pageNum);
|
|
this.$view.scrollTop = delta > 0 ? sh : 0;
|
|
}
|
|
}
|
|
else
|
|
this.$view.scrollTop = stop - delta;
|
|
});
|
|
}
|
|
else{
|
|
var evt = webix.env.isFF?"DOMMouseScroll":"mousewheel";
|
|
webix.event(window, evt, webix.bind(function(e){
|
|
var ticks = (e.type === 'DOMMouseScroll') ? -e.detail :e.wheelDelta;
|
|
var dir = (ticks < 0) ? 'out' : 'in';
|
|
if (e.ctrlKey) { // Only zoom the pages, not the entire viewer
|
|
webix.html.preventEvent(e);
|
|
if(dir == "in")
|
|
this.zoomIn();
|
|
else
|
|
this.zoomOut();
|
|
}
|
|
}, this));
|
|
}
|
|
},
|
|
_getDocument:function(data){
|
|
if(data.name){ //File structure
|
|
var reader = new FileReader();
|
|
reader.onload = webix.bind(function (e) {
|
|
this._getDocument({data:e.target.result});
|
|
}, this);
|
|
reader.readAsArrayBuffer(data);
|
|
}
|
|
else{
|
|
PDFJS.getDocument({data:data.data}).then(webix.bind(function (pdfDoc_) {
|
|
this.clear();
|
|
this.$pdfDoc = pdfDoc_;
|
|
this.$numPages = this.$pdfDoc.numPages;
|
|
this.$pageNum = 1;
|
|
|
|
this._renderPage(this.$pageNum).then(webix.bind(function(){
|
|
this.callEvent("onDocumentReady");
|
|
}, this));
|
|
}, this));
|
|
}
|
|
},
|
|
$onLoad:function(data){
|
|
if(!window.PDFJS){
|
|
//for cross browser and compatibility
|
|
webix.require([webix.cdn + "/extras/pdfjs/compatibility.min.js", webix.cdn + "/extras/pdfjs/pdf.min.js"], function(){
|
|
PDFJS.workerSrc = webix.cdn + "/extras/pdfjs/pdf.worker.min.js";
|
|
this._getDocument(data);
|
|
}, this);
|
|
}
|
|
else
|
|
this._getDocument(data);
|
|
return true;
|
|
},
|
|
_getViewPort:function(page, scale){
|
|
var viewport = page.getViewport(scale);
|
|
this._canvas.height = viewport.height;
|
|
this._canvas.width = viewport.width;
|
|
this._container.style.width = viewport.width+"px";
|
|
this._container.style.height = viewport.height+"px";
|
|
|
|
return viewport;
|
|
},
|
|
_renderPage:function(num) {
|
|
var viewer = this;
|
|
viewer._pageRendering = true;
|
|
// Using promise to fetch the page
|
|
return this.$pdfDoc.getPage(num).then(function(page) {
|
|
//Getting 'safe' scale value
|
|
var scale = isNaN(parseFloat(viewer._settings.scale))?viewer._init_scale_value:viewer._settings.scale;
|
|
|
|
var viewport = viewer._getViewPort(page, scale);
|
|
//recalc viewport if "string" scale is set
|
|
if(scale !== viewer._settings.scale){
|
|
scale = viewer._getScale(viewer._settings.scale);
|
|
viewport = viewer._getViewPort(page, scale);
|
|
viewer._settings.scale = scale;
|
|
}
|
|
|
|
// Render PDF page into canvas context
|
|
var renderContext = {
|
|
canvasContext: viewer._ctx,
|
|
viewport: viewport
|
|
};
|
|
|
|
page.cleanupAfterRender = true;
|
|
|
|
// Wait for rendering to finish
|
|
return page.render(renderContext).promise.then(function () {
|
|
viewer.callEvent("onPageRender", [viewer.$pageNum]);
|
|
viewer._pageRendering = false;
|
|
|
|
if (viewer._pageNumPending !== null) {
|
|
// New page rendering is pending
|
|
viewer._renderPage(viewer._pageNumPending);
|
|
viewer._pageNumPending = null;
|
|
}
|
|
});
|
|
});
|
|
},
|
|
_queueRenderPage:function(num) {
|
|
if (this._pageRendering)
|
|
this._pageNumPending = num;
|
|
else
|
|
this._renderPage(num);
|
|
},
|
|
renderPage:function(num){
|
|
if(!this.$pdfDoc || num<0 || num>this.$numPages)
|
|
return;
|
|
|
|
this.$pageNum = num;
|
|
this._queueRenderPage(this.$pageNum);
|
|
},
|
|
prevPage:function() {
|
|
if (this.$pageNum <= 1)
|
|
return;
|
|
this.$pageNum--;
|
|
this._queueRenderPage(this.$pageNum);
|
|
},
|
|
nextPage:function() {
|
|
if(this.$pageNum >= this.$numPages)
|
|
return;
|
|
this.$pageNum++;
|
|
this._queueRenderPage(this.$pageNum);
|
|
},
|
|
zoomIn: function (){
|
|
var newScale = this._settings.scale;
|
|
|
|
newScale = (newScale * this._default_scale_delta).toFixed(2);
|
|
newScale = Math.ceil(newScale * 10) / 10;
|
|
newScale = Math.min(this._max_scale, newScale);
|
|
this.setScale(newScale, true);
|
|
},
|
|
zoomOut: function (){
|
|
var newScale = this._settings.scale;
|
|
|
|
newScale = (newScale / this._default_scale_delta).toFixed(2);
|
|
newScale = Math.floor(newScale * 10) / 10;
|
|
newScale = Math.max(this._min_scale, newScale);
|
|
|
|
this.setScale(newScale, true);
|
|
},
|
|
_getScale:function(value){
|
|
if(!isNaN(parseFloat(value)))
|
|
return value;
|
|
if(isNaN(parseFloat(this._settings.scale)))
|
|
this._settings.scale = this._init_scale_value;
|
|
|
|
var scale = 1; //default value
|
|
var pageWidthScale = ((this._currentPage.clientWidth - this._hPadding) * this._settings.scale/this._canvas.clientWidth).toFixed(2);
|
|
var pageHeightScale = ((this._currentPage.clientHeight - this._vPadding) * this._settings.scale/this._canvas.clientHeight).toFixed(2);
|
|
switch (value) {
|
|
case 'page-actual':
|
|
scale = 1;
|
|
break;
|
|
case 'page-width':
|
|
scale = pageWidthScale;
|
|
break;
|
|
case 'page-height':
|
|
scale = pageHeightScale;
|
|
break;
|
|
case 'page-fit':
|
|
scale = Math.min(pageWidthScale, pageHeightScale);
|
|
break;
|
|
case 'auto':
|
|
var isLandscape = (this._currentPage.clientWidth > this._currentPage.clientHeight);
|
|
var horizontalScale = isLandscape ? Math.min(pageHeightScale, pageWidthScale) : pageWidthScale;
|
|
scale = Math.min(this._max_auto_scale, horizontalScale);
|
|
break;
|
|
}
|
|
return scale;
|
|
},
|
|
setScale: function(value, update) {
|
|
if (!isNaN(parseFloat(value))) {
|
|
this._setScale(value, update);
|
|
} else {
|
|
var scale = this._getScale(value);
|
|
this._setScale(scale, update);
|
|
}
|
|
},
|
|
_setScale:function(newScale, update){
|
|
this._settings.scale = newScale;
|
|
this.renderPage(this.$pageNum);
|
|
|
|
this.callEvent("onScaleChange", [newScale, update]);
|
|
},
|
|
download:function(){
|
|
if(!this.$pdfDoc) return;
|
|
|
|
var filename = (this._settings.downloadName || "document")+".pdf";
|
|
this.$pdfDoc.getData().then(function(data){
|
|
var blob = PDFJS.createBlob(data, 'application/pdf');
|
|
webix.html.download(blob, filename);
|
|
});
|
|
},
|
|
clear:function(){
|
|
if(this.$pdfDoc){
|
|
this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
|
|
this._container.style.height = this._container.style.width = this._canvas.width = this._canvas.height = 0;
|
|
this._settings.scale = "auto";
|
|
this.$pageNum = this.$numPages = 0;
|
|
this.$pdfDoc.transport.startCleanup();
|
|
this.$pdfDoc.destroy();
|
|
this.$pdfDoc = null;
|
|
|
|
if(this._settings.toolbar && webix.$$(this._settings.toolbar))
|
|
webix.$$(this._settings.toolbar).reset();
|
|
}
|
|
}
|
|
}, webix.EventSystem, webix.AtomDataLoader, webix.ui.view);
|
|
|
|
webix.protoUI({
|
|
name: "pdfbar",
|
|
reset:function(){
|
|
this.setPage(0);
|
|
this.setValues(0, "auto");
|
|
},
|
|
$init:function(config){
|
|
this.$view.className +=" pdf_bar";
|
|
|
|
config.cols = [
|
|
{ view:"button", type:"icon", icon:"arrow-left", width:35, click:function(){ this.getParentView()._navigate("prev");}},
|
|
{ view:"text", width:70, value:"0", on:{
|
|
onBlur:function(){ this.getParentView()._navigate(this.getValue());},
|
|
onKeyPress:function(code){ if(code === 13) this.getParentView()._navigate(this.getValue());}
|
|
}},
|
|
{ template:webix.i18n.PDFviewer.of+" #limit#", width:70, data:{limit:0}, borderless:true },
|
|
{ view:"button", type:"icon", icon:"arrow-right", width:35, click:function(){ this.getParentView()._navigate("next");}},
|
|
{},
|
|
{view:"button", type:"icon", icon:"minus", width:35, click:function(){ this.getParentView().zoom("out");}},
|
|
{view:"richselect", options:[], maxWidth:195, suggest:{
|
|
padding:0, css:"pdf_opt_list", borderless:true, body:{
|
|
type:{ height:25}, scroll:false, yCount:13 }
|
|
},
|
|
on:{ onChange:function(){ this.getParentView().setMasterScale(this.getValue());}}
|
|
},
|
|
{view:"button", type:"icon", icon:"plus", width:35, click:function(){ this.getParentView().zoom("in");}},
|
|
{view:"button", type:"icon", icon:"download", width:35, click:function(){ this.getParentView().download();}}
|
|
];
|
|
this.$ready.push(this._setScaleOptions);
|
|
},
|
|
_setScaleOptions:function(){
|
|
var list = this.getChildViews()[6].getPopup().getBody();
|
|
list.clearAll();
|
|
list.parse([
|
|
{ id:"auto", value:webix.i18n.PDFviewer.automaticZoom}, { id:"page-actual", value:webix.i18n.PDFviewer.actualSize},
|
|
{ id:"page-fit", value:webix.i18n.PDFviewer.pageFit}, { id:"page-width", value:webix.i18n.PDFviewer.pageWidth},
|
|
{ id:"page-height", value:webix.i18n.PDFviewer.pageHeight},
|
|
{ id:"0.5", value:"50%"}, { id:"0.75", value:"75%"},
|
|
{ id:"1", value:"100%"}, { id:"1.25", value:"125%"},
|
|
{ id:"1.5", value:"150%"}, { id:"2", value:"200%"},
|
|
{ id:"3", value:"300%"}, { id:"4", value:"400%"}
|
|
]);
|
|
var width = 0;
|
|
list.data.each(function(obj){
|
|
width = Math.max(webix.html.getTextSize(obj.value, "webixbutton").width, width);
|
|
});
|
|
this.getChildViews()[6].define("width", width+20);
|
|
this.getChildViews()[6].resize();
|
|
},
|
|
_navigate:function(num){
|
|
this.setMasterPage(num);
|
|
this.setPage(this.$master.$pageNum);
|
|
},
|
|
setScale:function(scale){
|
|
var sel = this.getChildViews()[6];
|
|
sel.blockEvent();
|
|
if(sel.getPopup().getList().exists(scale))
|
|
sel.setValue(scale);
|
|
else{
|
|
sel.setValue("");
|
|
sel.getInputNode().innerHTML = (scale*100).toFixed(0)+"%";
|
|
}
|
|
sel.unblockEvent();
|
|
},
|
|
setMasterScale:function(value){
|
|
if(!this.$master) return;
|
|
this.$master.setScale(value);
|
|
},
|
|
setMasterPage:function(num){
|
|
if(!this.$master) return;
|
|
if(num === "prev")
|
|
this.$master.prevPage();
|
|
else if(num==="next")
|
|
this.$master.nextPage();
|
|
else if(!isNaN(parseInt(num)))
|
|
this.$master.renderPage(parseInt(num));
|
|
},
|
|
zoom:function(dir){
|
|
if(!this.$master) return;
|
|
if(dir === "out")
|
|
this.$master.zoomOut();
|
|
else if(dir === "in")
|
|
this.$master.zoomIn();
|
|
|
|
},
|
|
setPage:function(num){
|
|
this.getChildViews()[1].setValue(num);
|
|
},
|
|
setValues:function(num, scale){
|
|
this.getChildViews()[2].data.limit = num;
|
|
this.getChildViews()[2].refresh();
|
|
|
|
this.setScale(scale);
|
|
},
|
|
download:function(){
|
|
if(!this.$master) return;
|
|
this.$master.download();
|
|
}
|
|
}, webix.ui.toolbar);
|
|
webix.protoUI({
|
|
name: "excelbar",
|
|
defaults:{
|
|
padding:0,
|
|
type:"line"
|
|
},
|
|
$init:function(config){
|
|
config.cols = [
|
|
{ view:"tabbar", options:[""], optionWidth:200, on:{
|
|
onaftertabclick:function(){
|
|
this.getParentView().callEvent("onExcelSheetSelect", [this.getValue()]);
|
|
}
|
|
}}
|
|
];
|
|
},
|
|
getValue:function(){
|
|
return this.getInput().getValue();
|
|
},
|
|
setValue:function(value){
|
|
return this.getInput().setValue(value);
|
|
},
|
|
getInput:function(){
|
|
return this.getChildViews()[0];
|
|
},
|
|
setSheets:function(sheets){
|
|
var input = this.getInput();
|
|
input.config.options = sheets;
|
|
input.refresh();
|
|
}
|
|
}, webix.ui.toolbar);
|
|
|
|
webix.protoUI({
|
|
name:"excelviewer",
|
|
$init:function(){
|
|
this.$ready.push(function(){
|
|
if (this._settings.toolbar)
|
|
webix.$$(this._settings.toolbar).attachEvent("onExcelSheetSelect", webix.bind(this.showSheet, this));
|
|
});
|
|
},
|
|
defaults:{
|
|
datatype:"excel"
|
|
},
|
|
$onLoad:function(data){
|
|
if(data.sheets){
|
|
this._excel_data = data;
|
|
if (this._settings.toolbar)
|
|
webix.$$(this._settings.toolbar).setSheets(data.names);
|
|
var now = data.names[0];
|
|
this.showSheet(now.id || now);
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
$exportView:function(options){
|
|
if(options.export_mode !=="excel" || options.dataOnly) return this;
|
|
|
|
if(options.sheets === true)
|
|
options.sheets = this.getSheets();
|
|
else if(!options.sheets || !options.sheets.length)
|
|
options.sheets = [this._activeSheet];
|
|
else if(typeof options.sheets == "string")
|
|
options.sheets = [options.sheets];
|
|
|
|
options.dataOnly = true;
|
|
options.heights = webix.isUndefined(options.heights) && options.styles?"all":options.heights;
|
|
|
|
var temp = [];
|
|
var active = this._activeSheet;
|
|
|
|
for(var i = 0; i<options.sheets.length; i++){
|
|
this.showSheet(options.sheets[i]);
|
|
temp = temp.concat(webix.toExcel(this, options));
|
|
if(options.styles)
|
|
temp[i].styles = this._getExportStyles(options);
|
|
}
|
|
this.showSheet(active);
|
|
delete options.dataOnly;
|
|
return temp;
|
|
},
|
|
showSheet:function(name){
|
|
this.clearAll();
|
|
|
|
var obj = this.data.driver.sheetToArray(this._excel_data.sheets[name], {
|
|
spans:this._settings.spans
|
|
});
|
|
|
|
var header = this._settings.excelHeader;
|
|
var letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
|
|
var cols = {}, rows = {};
|
|
if(obj.sizes){
|
|
for(var i = 0; i<obj.sizes.length; i++){
|
|
if(obj.sizes[i][0] == "column") cols[obj.sizes[i][1]] = Math.round(obj.sizes[i][2]);
|
|
else if (obj.sizes[i][0] =="row") rows[obj.sizes[i][1]] = Math.round(obj.sizes[i][2]);
|
|
}
|
|
}
|
|
|
|
if (!header){
|
|
header = webix.copy(obj.data[0]);
|
|
for (var i = 0; i < header.length; i++)
|
|
header[i] = { header:letters[i], id:"data"+i, width:cols[i],adjust:!cols[i], editor:"text", sort:"string" };
|
|
} else if (header === true) {
|
|
header = obj.data.splice(0,1)[0];
|
|
for (var i = 0; i < header.length; i++)
|
|
header[i] = { header:header[i], id:"data"+i, width:cols[i], adjust:!cols[i], editor:"text", sort:"string" };
|
|
} else
|
|
header = webix.copy(header);
|
|
|
|
this.config.columns = header;
|
|
this.refreshColumns();
|
|
|
|
this.parse(obj, this._settings.datatype);
|
|
this._activeSheet = name;
|
|
|
|
var paintspans = this._paintSpans(obj.spans);
|
|
var paintrows = this._paintRowHeight(rows);
|
|
var paintstyles = this._paintStyles(obj.styles, paintspans);
|
|
|
|
if(paintspans || paintrows || paintstyles)
|
|
this.refresh();
|
|
},
|
|
getSheets:function(){
|
|
return this._excel_data.names;
|
|
},
|
|
_getSpanCss:function(spans, id, cid, style){
|
|
var found = false;
|
|
for(var s = 0; s<spans.length; s++){
|
|
if(spans[s][0] === id && spans[s][1] === cid){
|
|
spans[s][5] = webix.html.createCss(this._toCellStyle(style));
|
|
this.addSpan(spans[s][0], spans[s][1], spans[s][2], spans[s][3] ,spans[s][4], spans[s][5]);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
return found;
|
|
},
|
|
_paintStyles:function(styles, spans){
|
|
var count = 0;
|
|
if(styles && styles.length){
|
|
for(var i = 0; i<styles.length; i++){
|
|
var rind = styles[i][0]-(this.config.excelHeader?1:0);
|
|
if(rind >=0){
|
|
var id = this.getIdByIndex(rind);
|
|
if(this.exists(id)){
|
|
var item = this.getItem(id);
|
|
var cid = this.columnId(styles[i][1]);
|
|
if(cid){
|
|
if(!spans.length || !this._getSpanCss(spans, id, cid, styles[i][2])){
|
|
item.$cellCss = item.$cellCss || {};
|
|
item.$cellCss[cid] = this._toCellStyle(styles[i][2]);
|
|
}
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
return false;
|
|
},
|
|
//ARGB conversion
|
|
_safeColor:function(str){
|
|
str = str || "000000";
|
|
if(str.length === 8) str = str.substring(2);
|
|
return "#"+str;
|
|
},
|
|
_toCellStyle:function(st){
|
|
var res = {};
|
|
if(st.fill && st.fill.fgColor)
|
|
res["background-color"] = this._safeColor(st.fill.fgColor.rgb);
|
|
if(st.font){
|
|
var f = st.font;
|
|
if(f.name) res["font-family"] = f.name;
|
|
if(f.sz) res["font-size"] = f.sz/0.75+"px";
|
|
if(f.color && f.color.rgb) res["color"] = this._safeColor(f.color.rgb);
|
|
if(f.bold) res["font-weight"] = "bold";
|
|
if(f.underline) res["text-decoration"] = "underline";
|
|
if(f.italic) res["font-style"] = "italic";
|
|
if(f.strike) res["text-decoration"] = "line-through";
|
|
}
|
|
if(st.alignment){
|
|
var a = st.alignment;
|
|
if(a.vertical && a.vertical == "center"){
|
|
res["display"] = "flex";
|
|
res["justify-content"] = "flex-start";
|
|
res["align-items"] = "center";
|
|
}
|
|
if(a.vertical && a.vertical == "bottom"){
|
|
res["display"] = "flex";
|
|
res["justify-content"] = "flex-end";
|
|
res["align-items"] = "flex-end";
|
|
}
|
|
if(a.horizontal) {
|
|
if(a.vertical && (a.vertical =="center" || a.vertical =="bottom"))
|
|
res["justify-content"] = "center";
|
|
else
|
|
res["text-align"] = a.horizontal;
|
|
}
|
|
if(a.wrapText) res["white-space"] = "normal";
|
|
}
|
|
if(st.border){
|
|
var b = st.border;
|
|
if(b.top) res["border-top"] = "1px solid "+this._safeColor(b.top.color.rgb);
|
|
if(b.bottom) res["border-bottom"] = "1px solid "+this._safeColor(b.bottom.color.rgb)+" !important";
|
|
if(b.left) res["border-left"] = "1px solid "+this._safeColor(b.left.color.rgb);
|
|
if(b.right) res["border-right"] = "1px solid "+this._safeColor(b.right.color.rgb)+" !important";
|
|
}
|
|
return res;
|
|
},
|
|
_paintRowHeight:function(rows){
|
|
var count = 0;
|
|
for(var i in rows){
|
|
var index = this.config.excelHeader?i-1:i;
|
|
if(index >=0){
|
|
var id = this.getIdByIndex(index);
|
|
if(this.exists(id)){
|
|
this.getItem(id).$height = rows[i];
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
this.config.fixedRowHeight = !count;
|
|
return count;
|
|
},
|
|
_paintSpans:function(spans){
|
|
var res = [];
|
|
if(this._settings.spans && spans && spans.length){
|
|
this._spans_pull = {};
|
|
for(var i = 0; i<spans.length; i++){
|
|
if(this.config.excelHeader)
|
|
spans[i][0]--;
|
|
if(spans[i][0] >= 0){
|
|
spans[i][0] = this.getIdByIndex(spans[i][0]);
|
|
spans[i][1] = "data"+spans[i][1];
|
|
res.push(spans[i]);
|
|
}
|
|
}
|
|
this.addSpan(res);
|
|
return res;
|
|
}
|
|
return false;
|
|
}
|
|
}, webix.ui.datatable);
|
|
|
|
webix.DataDriver.excel = webix.extend({
|
|
toObject:function(data){
|
|
if(!data.excel){
|
|
var opts = data.options || {};
|
|
if (opts.dataurl)
|
|
webix.extend(opts, this._urlToOptions(opts.dataurl));
|
|
|
|
data = data.data || data;
|
|
var promise = webix.promise.defer();
|
|
|
|
if(data.name){ //file
|
|
opts.ext = data.name.split(".").pop();
|
|
var reader = new FileReader();
|
|
|
|
reader.onload = webix.bind(function (e) {
|
|
promise.resolve(this.parseData(e.target.result, opts));
|
|
}, this);
|
|
reader.readAsArrayBuffer(data);
|
|
}
|
|
else //arraybuffer
|
|
promise.resolve(this.parseData(data, opts));
|
|
|
|
return promise;
|
|
}
|
|
//plain jsarray or hash
|
|
return data;
|
|
},
|
|
parseData:function(data, options){
|
|
data = new Uint8Array(data);
|
|
var arr = [];
|
|
for(var i = 0; i != data.length; ++i)
|
|
arr[i] = String.fromCharCode(data[i]);
|
|
|
|
var ext = (options.ext || options).toLowerCase();
|
|
if (ext != "xls") ext = "xlsx";
|
|
return webix.require(webix.cdn + "/extras/xlsx.core.styles.min.js").then(webix.bind(function(){
|
|
var wb = (ext == "xls") ?
|
|
XLS.read(arr.join(""), {type: 'binary', cellStyles:true, cellDates:true}) :
|
|
XLSX.read(arr.join(""), {type: 'binary', cellStyles:true, cellDates:true});
|
|
|
|
var res = {
|
|
sheets: wb.Sheets,
|
|
names: wb.SheetNames,
|
|
options:options,
|
|
ranges:wb.Workbook?(wb.Workbook.Names ||[]):[]
|
|
};
|
|
return webix.extend(this.getSheet(res, options), res);
|
|
}, this));
|
|
},
|
|
getSheet:function(data, options){
|
|
var name = options.name || data.names[0];
|
|
data = this.sheetToArray(data.sheets[name], options);
|
|
if(options.rows && options.rows.length)
|
|
data.data = data.data.splice(options.rows[0], Math.min(options.rows[1], data.data.length)-options.rows[0]);
|
|
return data;
|
|
},
|
|
sheetToArray:function(sheet, options){
|
|
var all = [];
|
|
var spans = [];
|
|
var styles = [];
|
|
var sizes = [];
|
|
|
|
if(sheet && sheet["!ref"]){
|
|
var range = XLS.utils.decode_range(sheet["!ref"]),
|
|
row, col, cellCoord, cell,
|
|
xCorrection = range.s.c,
|
|
yCorrection = range.s.r+(options.rows?options.rows[0]:0);
|
|
|
|
for (row = range.s.r; row <= range.e.r; row++) {
|
|
var nrow = [];
|
|
for (col = range.s.c; col <= range.e.c; col++) {
|
|
cellCoord = XLS.utils.encode_cell({ r: row, c: col });
|
|
cell = sheet[cellCoord];
|
|
if(!cell)
|
|
nrow.push("");
|
|
else{
|
|
var ncell = "";
|
|
if(options.math&&cell.f) // get formula
|
|
ncell = cell.f.charAt(0)=="=" ? cell.f : "="+cell.f;
|
|
else if (cell.t =="d" && webix.isDate(cell.v))
|
|
ncell = webix.i18n.dateFormatStr(cell.v);
|
|
else
|
|
ncell = cell.v;
|
|
nrow.push(ncell);
|
|
|
|
if (cell.s)
|
|
styles.push([row-yCorrection, col-xCorrection, cell.s]);
|
|
}
|
|
}
|
|
all.push(nrow);
|
|
}
|
|
|
|
if(sheet["!merges"]){
|
|
var merges = sheet["!merges"];
|
|
for(var i = 0; i<merges.length; i++){
|
|
var s = merges[i].s;
|
|
var e = merges[i].e;
|
|
if(!options.rows || (s.r-yCorrection>=0 && e.r-yCorrection<=options.rows[1]))
|
|
spans.push([s.r-yCorrection, s.c-xCorrection, e.c-s.c+1, e.r-s.r+1]);
|
|
}
|
|
}
|
|
if(sheet["!cols"]){
|
|
var widths = sheet["!cols"];
|
|
for(var i = 0; i<widths.length; i++)
|
|
if(widths[i]) sizes.push(["column", i-xCorrection, widths[i].wch/(8.43/70)]); //mode, colind, value
|
|
}
|
|
if(sheet["!rows"]){
|
|
var heights = sheet["!rows"];
|
|
for(var i = 0; i<heights.length; i++)
|
|
if(heights[i]) sizes.push(["row", i-yCorrection, heights[i].hpx]); //mode ("row", "column"), rowind, value
|
|
}
|
|
}
|
|
return { data:all, spans: spans, styles:styles, sizes:sizes, excel: true };
|
|
},
|
|
_urlToOptions:function(details){
|
|
var parts = details.split("[");
|
|
var options = {};
|
|
options.name = parts[0];
|
|
if(parts[1]){
|
|
var rows = parts[1].split(/[^0-9]+/g);
|
|
rows[0] = rows[0]*1 || 0;
|
|
rows[1] = rows[1]*1 || 9999999;
|
|
options.rows = rows;
|
|
}
|
|
return options;
|
|
}
|
|
}, webix.DataDriver.jsarray);
|
|
webix.protoUI({
|
|
name: "treemap",
|
|
defaults: {
|
|
activeItem: false,
|
|
subRender: true,
|
|
header: true,
|
|
headerHeight: 35,
|
|
value: webix.template("#value#"),
|
|
headerTemplate: "",
|
|
navigation:true
|
|
},
|
|
value_setter: webix.template,
|
|
headerTemplate_setter: webix.template,
|
|
header_setter: function(value){
|
|
if(value && value !== true){
|
|
this.type.header = value;
|
|
}
|
|
return value;
|
|
},
|
|
$init: function(config){
|
|
this.$view.className += " webix_treemap";
|
|
this._viewobj.setAttribute("role", "tree");
|
|
|
|
this._htmlElement = document.createElement("DIV");
|
|
|
|
webix.extend(this.data, webix.TreeStore, true);
|
|
this.data.provideApi(this,true);
|
|
|
|
this.data.attachEvent("onClearAll", webix.bind(function(){
|
|
this._html = "";
|
|
this.$values = {};
|
|
this.$xy = {};
|
|
},this));
|
|
|
|
this.attachEvent("onKeyPress", this._onKeyPress);
|
|
},
|
|
_toHTMLItem:function(obj){
|
|
var mark = this.data._marks[obj.id];
|
|
this.callEvent("onItemRender",[obj]);
|
|
var template = (obj.$template?this.type["template"+obj.$template].call(this,obj,this.type,mark):this.type.template.call(this,obj,this.type,mark));
|
|
return this.type.templateStart.call(this,obj,this.type,mark) + template + this.type.templateEnd.call(this);
|
|
},
|
|
_renderHeader: function(id){
|
|
var item = this.getItem(id);
|
|
var height = this._settings.headerHeight;
|
|
var html = "<div class='webix_treemap_header' style='height:"+height+"px;line-height:"+height+"px;'>";
|
|
html += this.type.header.call(this, item, this.type);
|
|
html += "</div>";
|
|
return html;
|
|
},
|
|
_renderBranch:function(pId){
|
|
var i, id, item, sizes, row, value, sum,
|
|
leaves = [];
|
|
|
|
if(!this.$width || !this.count()){
|
|
this._html = "";
|
|
return false;
|
|
}
|
|
|
|
if(!pId){
|
|
pId = this.config.branch||0;
|
|
this._html = "";
|
|
this.$values = {};
|
|
this.$xy = {};
|
|
this.$xy[pId] = {
|
|
width: this.$width,
|
|
height: this.$height,
|
|
top: 0,
|
|
left: 0
|
|
};
|
|
// header
|
|
if(pId && this._settings.header){
|
|
this.$xy[pId].height -= this._settings.headerHeight;
|
|
this.$xy[pId].top = this._settings.headerHeight;
|
|
this._html += this._renderHeader(pId);
|
|
}
|
|
|
|
// values calculation
|
|
sum = 0;
|
|
this.data.each(function(item){
|
|
var parentId = this.getParentId(item.id);
|
|
if(!this.data.branch[item.id]){
|
|
value = this.config.value.call(this,item)*1;
|
|
if(!isNaN(value) && value){
|
|
this.$values[item.id] = value;
|
|
sum += value;
|
|
while(parentId){
|
|
if(!this.$values[parentId])
|
|
this.$values[parentId] = 0;
|
|
this.$values[parentId] += value;
|
|
parentId = this.getParentId(parentId);
|
|
}
|
|
|
|
}
|
|
}
|
|
}, this, false, pId);
|
|
}
|
|
|
|
this.data.eachChild(pId, function(item){
|
|
if(this.$values[item.id])
|
|
leaves.push(webix.copy(item));
|
|
}, this);
|
|
|
|
sum = sum || this.$values[pId];
|
|
|
|
if(leaves.length && sum){
|
|
sizes = this.$xy[pId];
|
|
row ={ top: sizes.top, left:sizes.left, dx: sizes.width, dy: sizes.height, set:[], sum:0 };
|
|
row.dim = Math.min(row.dx,row.dy);
|
|
var delta = row.dx*row.dy/sum; //total area
|
|
for ( i=0; i< leaves.length; i++)
|
|
leaves[i].$value = this.$values[leaves[i].id]*delta; //normalized value
|
|
|
|
|
|
leaves.sort(function(a,b){
|
|
return a.$value >b.$value?-1:1;
|
|
});
|
|
|
|
var bad = Infinity;
|
|
var i = 0;
|
|
while(leaves[i]){
|
|
var check=this._worst(row, leaves[i]);
|
|
if (check<bad){
|
|
row.sum += leaves[i].$value;
|
|
row.set.push(leaves[i]);
|
|
bad=check;
|
|
i++;
|
|
} else {
|
|
this._renderRow(row);
|
|
var r = { top:row.top, left:row.left, dx:row.dx, dy:row.dy, set:[], sum:0 };
|
|
var delta = row.sum/row.dim;
|
|
if (row.dx > row.dy){
|
|
r.left += delta;
|
|
r.dx -= delta;
|
|
} else {
|
|
r.top += delta;
|
|
r.dy -= delta;
|
|
}
|
|
row=r;
|
|
row.dim = Math.min(row.dx,row.dy);
|
|
bad=Infinity;
|
|
}
|
|
}
|
|
}
|
|
if(row)
|
|
this._renderRow(row);
|
|
},
|
|
_renderRow:function(row){
|
|
var i, id, item, x, y,
|
|
top=row.top,
|
|
left=row.left;
|
|
|
|
row.mode=(row.dy<row.dx);
|
|
row.contra=(row.sum/row.dim);
|
|
|
|
for (i=0; i<row.set.length; i++){
|
|
id=row.set[i].id;
|
|
if (row.mode){
|
|
x=row.contra;
|
|
y=row.set[i].$value/row.contra;
|
|
} else {
|
|
x=row.set[i].$value/row.contra;
|
|
y=row.contra;
|
|
}
|
|
this.$xy[id] = {};
|
|
this.$xy[id].top = top;
|
|
this.$xy[id].left = left;
|
|
if (row.mode)
|
|
top += y;
|
|
else
|
|
left += x;
|
|
|
|
this.$xy[id].width = x;
|
|
this.$xy[id].height = y;
|
|
|
|
this._html += this._toHTMLItem(this.getItem(id));
|
|
if(this._settings.subRender && this.data.branch[id])
|
|
this._renderBranch(id);
|
|
}
|
|
},
|
|
_worst:function(row, add){
|
|
var s = row.sum + add.$value;
|
|
var a = (s*s) /( row.dim*row.dim*add.$value);
|
|
if (row.set.length){
|
|
a=Math.max(row.dim*row.dim*row.set[0].$value/(s*s),a);
|
|
}
|
|
return a>1?a:(1/a);
|
|
},
|
|
_toHTMLObject:function(obj){
|
|
this._htmlElement.innerHTML = this._toHTMLItem(obj);
|
|
return this._htmlElement.firstChild;
|
|
},
|
|
showBranch: function(id){
|
|
this._settings.branch = id;
|
|
this.refresh();
|
|
},
|
|
render:function(id,data,type){
|
|
if (!this.isVisible(this._settings.id) || this.$blockRender)
|
|
return;
|
|
|
|
if(type == "update"){
|
|
var cont = this.getItemNode(id); //get html element of updated item
|
|
if(cont){
|
|
var t = this._htmlmap[id] = this._toHTMLObject(data);
|
|
webix.html.insertBefore(t, cont);
|
|
webix.html.remove(cont);
|
|
}
|
|
}
|
|
else if(this.data.branch && (!this._settings.branch || this.data.branch[this._settings.branch])){
|
|
this._htmlmap = null;
|
|
this.callEvent("onBeforeRender",[]);
|
|
this._renderBranch();
|
|
this._dataobj.innerHTML = this._html;
|
|
this.callEvent("onAfterRender",[]);
|
|
}
|
|
return true;
|
|
},
|
|
_id:"webix_dm_id",
|
|
on_click:{
|
|
webix_treemap_item:function(e,id){
|
|
if (this._settings.select){
|
|
if (this._settings.select=="multiselect" || this._settings.multiselect)
|
|
this.select(id, false, (e.ctrlKey || e.metaKey || (this._settings.multiselect == "touch")), e.shiftKey);
|
|
else
|
|
this.select(id);
|
|
}
|
|
if(this._settings.activeItem && this.isBranch(id)){
|
|
this.showBranch(id);
|
|
}
|
|
},
|
|
webix_treemap_header_item: function(e){
|
|
var id = webix.html.locate(e, "webix_dm_header_id");
|
|
this.define("branch", id);
|
|
this.refresh();
|
|
},
|
|
webix_treemap_reset: function(e){
|
|
this.define("branch", 0);
|
|
this.refresh();
|
|
}
|
|
},
|
|
on_dblclick:{
|
|
},
|
|
on_mouse_move:{
|
|
},
|
|
_getCssText: function(style){
|
|
var css = "";
|
|
for(var property in style){
|
|
css += property+":"+style[property]+";";
|
|
}
|
|
return css;
|
|
},
|
|
type:{
|
|
//normal state of item
|
|
template:webix.template("#value#"),
|
|
header: function(obj, common){
|
|
var id = obj.id;
|
|
var resetIcon = "<div role='button' tabindex='0' aria-label='"+webix.i18n.aria.resetTreeMap+"' class='webix_treemap_reset'></div>";
|
|
var arr = [];
|
|
while(id){
|
|
obj = this.getItem(id);
|
|
arr.push(common.headerItem.call(this, obj, common));
|
|
id = this.getParentId(id);
|
|
}
|
|
arr.reverse();
|
|
return resetIcon + arr.join("<span class='webix_icon fa-angle-right webix_treemap_path_icon'></span>");
|
|
},
|
|
headerItem: function(obj){
|
|
var template = this.config.headerTemplate(obj);
|
|
var html = '<a role="button" tabindex="0" aria-label="'+template+'" webix_dm_header_id="'+obj.id+'" class="webix_treemap_header_item">';
|
|
html += template;
|
|
html += '</a>';
|
|
return html;
|
|
},
|
|
classname:function(obj, common, marks){
|
|
var css = "webix_treemap_item";
|
|
|
|
if (common.css) css +=common.css+" ";
|
|
|
|
if (obj.$css){
|
|
if (typeof obj.$css == "object")
|
|
obj.$css = webix.html.createCss(obj.$css);
|
|
css +=" "+obj.$css;
|
|
}
|
|
|
|
var xy = this.$xy[obj.id];
|
|
|
|
if (marks && marks.$css) css +=" "+marks.$css;
|
|
|
|
css += " webix_treemap_level_" + this.getItem(obj.id).$level;
|
|
|
|
var parentId = this.getParentId(obj.id);
|
|
|
|
if(!parentId || parentId == this._settings.branch)
|
|
css += " webix_treemap_level_top";
|
|
|
|
if(this.$height - xy.top - xy.height < 1)
|
|
css += " webix_treemap_item_bottom";
|
|
|
|
if(this.$width - xy.left - xy.width < 1)
|
|
css += " webix_treemap_item_right";
|
|
|
|
if(common.cssClass){
|
|
var cssClass = common.cssClass.call(this, obj, common, marks);
|
|
if(cssClass){
|
|
if(typeof cssClass == "object"){
|
|
css += " "+ webix.html.createCss(cssClass);
|
|
}
|
|
else
|
|
css += " "+cssClass;
|
|
}
|
|
}
|
|
return css;
|
|
},
|
|
templateStart:function(obj,type,marks){
|
|
var className = "", style="";
|
|
if(this.$xy){
|
|
var xy = this.$xy[obj.id];
|
|
style += "width: "+ xy.width +"px; height: " + xy.height+"px;";
|
|
style += "top: "+ xy.top+"px; left: " + xy.left+"px;";
|
|
}
|
|
return '<div role="treeitem" aria-level="'+obj.$level+'" '+(marks && marks.webix_selected?'aria-selected="true" tabindex="0"':'')+' webix_dm_id="'+obj.id+'" class="'+type.classname.call(this,obj,type,marks)+'" style="'+style+'">';
|
|
},
|
|
templateEnd:webix.template("</div>")
|
|
}
|
|
},webix.AutoTooltip, webix.Group, webix.TreeAPI, webix.SelectionModel, webix.KeysNavigation, webix.MouseEvents, webix.Scrollable, webix.TreeDataLoader, webix.ui.proto, webix.TreeRenderStack, webix.CopyPaste, webix.EventSystem);
|
|
|
|
webix.extend(webix.ui.datatable, {
|
|
_init_areaselect: function(){
|
|
this._arSelCheckKeys = true;
|
|
this._areaSelStorage = {};
|
|
this.define("select","area");
|
|
this.attachEvent("onAfterScroll", function(){
|
|
this.refreshSelectArea();
|
|
});
|
|
this.attachEvent("onAfterRender", function(){
|
|
this.refreshSelectArea();
|
|
});
|
|
this.attachEvent("onBeforeColumnHide", function(column){
|
|
this._areaSelHiddenIndex = this.getColumnIndex(column);
|
|
});
|
|
this.attachEvent("onAfterColumnHide", function(){
|
|
this._excludeColumnFromAreas(this._areaSelHiddenIndex);
|
|
});
|
|
|
|
this._bs_do_select = function(start, end, stopped, ev){
|
|
if(start.row && end.row){
|
|
if(stopped){
|
|
this.addSelectArea(start, end, true);
|
|
this._arSelCheckKeys = true;
|
|
return false;
|
|
}
|
|
else{
|
|
if(this.callEvent("onAreaDrag",[start, end, ev])){
|
|
if(!this._activeAreaSName){
|
|
if(this._arSelCheckKeys && !(this._settings.multiselect && ev && ev.ctrlKey) ){
|
|
this.removeSelectArea();
|
|
this._arSelCheckKeys = false;
|
|
}
|
|
|
|
}else{
|
|
this._removeAreaNodes(this._activeAreaSName);
|
|
}
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
}
|
|
};
|
|
this.attachEvent("onBeforeAreaAdd", this._span_correct_range);
|
|
webix._event(this._body, "mousedown", this._ars_down, {bind:this});
|
|
},
|
|
_block_sel_flag: true,
|
|
_excludeColumnFromAreas: function(index){
|
|
var areas = this._areaSelStorage;
|
|
for(var a in areas){
|
|
var area = areas[a];
|
|
if(this.getColumnIndex(area.start.column) <0 ){
|
|
if(area.start.column == area.end.column)
|
|
this.removeSelectArea(area.name);
|
|
else{
|
|
var id = this.columnId(index+1);
|
|
if(id)
|
|
this._updateSelectArea(area.name,{row: area.start.row,column: id},null);
|
|
}
|
|
}
|
|
else if(this.getColumnIndex(area.end.column) <0 ){
|
|
var id = this.columnId(index-1);
|
|
if(id)
|
|
this._updateSelectArea(area.name,null,{row: area.end.row,column: id});
|
|
}
|
|
}
|
|
},
|
|
_extendAreaRange: function(id, area, mode, details){
|
|
var sci, eci, sri, eri, ci, ri, iri, ici;
|
|
|
|
if (area){
|
|
sci = this.getColumnIndex(area.start.column);
|
|
eci = this.getColumnIndex(area.end.column);
|
|
sri = this.getIndexById(area.start.row);
|
|
eri = this.getIndexById(area.end.row);
|
|
ci = this.getColumnIndex(id.column);
|
|
ri = this.getIndexById(id.row);
|
|
//start cell of area
|
|
iri = this.getIndexById(area.init.row);
|
|
ici = this.getColumnIndex(area.init.column);
|
|
|
|
if(sci > ci || mode == "left"){
|
|
if(mode === "left" && details.ctrl) {
|
|
sci = this._extendAreaToData(iri, sci, mode);
|
|
eci = ici;
|
|
}
|
|
else if(mode === "left" && eci > ici) eci--;
|
|
else sci = ci;
|
|
}
|
|
else if(eci <= ci || mode == "right"){
|
|
if(mode == "right" && details.ctrl){
|
|
eci = this._extendAreaToData(iri, eci, mode);
|
|
sci = ici;
|
|
}
|
|
else if(mode == "right" && sci <ici) sci ++;
|
|
else eci = ci;
|
|
}
|
|
|
|
if(sri > ri || mode =="up"){
|
|
if(mode =="up" && details.ctrl) {
|
|
sri = this._extendAreaToData(sri, ici, mode);
|
|
eri = iri;
|
|
}
|
|
else if(mode =="up" && eri > iri ) eri--;
|
|
else sri = ri;
|
|
}
|
|
else if(eri < ri || mode =="down"){
|
|
if(mode == "down" && details.ctrl) {
|
|
eri = this._extendAreaToData(eri, ici, mode);
|
|
sri = iri;
|
|
}
|
|
else if(mode == "down" && sri <iri) sri++;
|
|
else eri = ri;
|
|
}
|
|
|
|
var start = { row: this.getIdByIndex(sri), column: this.columnId(sci) };
|
|
var end = { row: this.getIdByIndex(eri), column: this.columnId(eci) };
|
|
|
|
if(this.callEvent("onBeforeBlockSelect", [start, end, true])){
|
|
this._updateSelectArea(area.name, start, end);
|
|
this.callEvent("onSelectChange", []);
|
|
this.callEvent("onAfterBlockSelect", [start, end]);
|
|
}
|
|
}
|
|
},
|
|
_extendAreaToData:function(rind, cind, mode){
|
|
var columns = this.config.columns;
|
|
var order = this.data.order;
|
|
var item = this.data.pull[order[rind]];
|
|
var column = columns[cind].id;
|
|
var res = 0;
|
|
|
|
//iterate columns
|
|
if(mode == "right"){
|
|
for(var i = cind+1; i<columns.length; i++){
|
|
if(item[columns[i].id]){ res = i; break; }
|
|
else res = i;
|
|
}
|
|
}
|
|
else if (mode =="left"){
|
|
for(var i = cind-1; i>=0; i--){ //check ss
|
|
if(item[columns[i].id]){ res = i; break;}
|
|
}
|
|
}
|
|
//iterate data
|
|
else if (mode == "down"){
|
|
for(var i = rind+1; i<order.length; i++){
|
|
if(this.getItem(order[i])[column]){ res = i;break;}
|
|
else res = i;
|
|
}
|
|
}
|
|
else if(mode =="up"){
|
|
for(var i = rind-1; i>=0; i--){
|
|
if(this.getItem(order[i])[column]){ res = i;break;}
|
|
}
|
|
}
|
|
return res;
|
|
},
|
|
_updateSelectArea: function(name, start, end){
|
|
var area = this._areaSelStorage[name];
|
|
if(!area)
|
|
return false;
|
|
|
|
var range = { start: start||area.start, end: end||area.end};
|
|
this._span_correct_range(range);
|
|
webix.extend(area, range, true);
|
|
|
|
this.refreshSelectArea();
|
|
},
|
|
areaselect_setter:function(value){
|
|
if(value){
|
|
this._init_areaselect();
|
|
this._init_areaselect = function(){};
|
|
}
|
|
this.define("blockselect",value);
|
|
return value;
|
|
},
|
|
addSelectArea: function(start, end, preserve, name, css, handle){
|
|
var i0, i1, j0, j1, temp;
|
|
i0 = this.getIndexById(start.row);
|
|
i1 = this.getIndexById(end.row);
|
|
|
|
j0 = this.getColumnIndex(start.column);
|
|
j1 = this.getColumnIndex(end.column);
|
|
|
|
|
|
if (i0>i1){
|
|
temp = i0;
|
|
i0 = i1;
|
|
i1 = temp;
|
|
}
|
|
|
|
if (j0>j1){
|
|
temp = j0;
|
|
j0 = j1;
|
|
j1 = temp;
|
|
}
|
|
|
|
name = name || this._activeAreaSName || webix.uid();
|
|
|
|
this._activeAreaSName= null;
|
|
|
|
var area = {
|
|
start: { row: this.getIdByIndex(i0), column: this.columnId(j0)},
|
|
end:{ row: this.getIdByIndex(i1), column: this.columnId(j1)}
|
|
};
|
|
|
|
if(css)
|
|
area.css = css;
|
|
if(handle || handle === false)
|
|
area.handle = handle;
|
|
|
|
if(this._areaSelStorage[name]){
|
|
return this._updateSelectArea(name,area.start,area.end);
|
|
}
|
|
else{
|
|
area.handle = true;
|
|
}
|
|
|
|
area.name = name;
|
|
|
|
area.init = area.start;
|
|
|
|
if(this.callEvent("onBeforeAreaAdd",[area])){
|
|
this._lastDefArea = name;
|
|
if(!preserve)
|
|
this.removeSelectArea();
|
|
this._areaSelStorage[area.name] = area;
|
|
this._selected_areas.push(area);
|
|
this.refreshSelectArea();
|
|
this.callEvent("onAfterAreaAdd",[area]);
|
|
this.callEvent("onSelectChange",[]);
|
|
}
|
|
},
|
|
_renderSelectAreaBox: function(){
|
|
var box = webix.html.create("DIV");
|
|
box.className = "webix_area_selection_layer";
|
|
box.style.top = this._render_scroll_shift+"px";
|
|
return box;
|
|
},
|
|
refreshSelectArea: function(){
|
|
var xr, yr, name, range,
|
|
r0, r1, c0, c1,
|
|
center = null, left=null, right = null,
|
|
prerender = this._settings.prerender;
|
|
|
|
if(!this._render_full_rows)
|
|
return;
|
|
// indexes of visible cols
|
|
xr = this._get_x_range(prerender);
|
|
// indexes of visible rows
|
|
yr = this._get_y_range(prerender === true);
|
|
|
|
if (!this._rselect_box){
|
|
this._rselect_box = this._renderSelectAreaBox();
|
|
this._body.childNodes[1].appendChild(this._rselect_box);
|
|
this._rselect_box_left = this._renderSelectAreaBox();
|
|
this._body.childNodes[0].appendChild(this._rselect_box_left);
|
|
this._rselect_box_right = this._renderSelectAreaBox();
|
|
this._body.childNodes[2].appendChild(this._rselect_box_right);
|
|
}
|
|
|
|
this._rselect_box.innerHTML = "";
|
|
this._rselect_box_left.innerHTML = "";
|
|
this._rselect_box_right.innerHTML = "";
|
|
|
|
var leftSplit = this._settings.leftSplit;
|
|
var rightSplit = this._settings.rightSplit;
|
|
|
|
for(name in this._areaSelStorage){
|
|
range = this._areaSelStorage[name];
|
|
var ind = this._calcAreaSelectIndexes(range,xr,yr);
|
|
if (ind === null){
|
|
this.removeSelectArea(name);
|
|
continue;
|
|
}
|
|
var startIndex = this.getColumnIndex(range.start.column);
|
|
var endIndex = this.getColumnIndex(range.end.column);
|
|
if(ind.r0 <= ind.r1){
|
|
if(this._settings.topSplit && r0>=this._settings.topSplit && r1< this._render_scroll_top)
|
|
return false;
|
|
if(startIndex < leftSplit)
|
|
left = this._getSelectAreaCellPositions(ind.r0, startIndex, ind.r1, Math.min(endIndex,leftSplit-1));
|
|
if(ind.c0<=ind.c1)
|
|
center = this._getSelectAreaCellPositions(ind.r0, ind.c0, ind.r1, ind.c1);
|
|
if(rightSplit && endIndex >= this._rightSplit)
|
|
right = this._getSelectAreaCellPositions(ind.r0, Math.max(startIndex,this._rightSplit), ind.r1, endIndex);
|
|
|
|
if(left || center || right)
|
|
this._setSelectAreaBorders(left,center,right, name, range.css, range.handle);
|
|
}
|
|
}
|
|
},
|
|
_calcAreaSelectIndexes: function(range, xr, yr){
|
|
var r0, r1, c0, c1;
|
|
|
|
var startIndex = this.getIndexById(range.start.row);
|
|
var endIndex = this.getIndexById(range.end.row);
|
|
|
|
var startColumn = this.getColumnIndex(range.start.column);
|
|
var endColumn = this.getColumnIndex(range.end.column);
|
|
|
|
//return null for broken select areas
|
|
if (startColumn === -1 || endColumn === -1)
|
|
return null;
|
|
if (startIndex === -1 || endIndex === -1)
|
|
return null;
|
|
|
|
r1 = Math.min(yr[1],endIndex);
|
|
if(this._settings.topSplit){
|
|
r0 = startIndex;
|
|
if(r0 >= this._settings.topSplit)
|
|
r0 = Math.max(yr[0]-this._settings.topSplit,startIndex);
|
|
if(r1 >= this._settings.topSplit){
|
|
var endPos = this._cellPosition(this.getIdByIndex(endIndex),range.end.column);
|
|
var splitPos = this._cellPosition(this.getIdByIndex(this._settings.topSplit-1),range.end.column);
|
|
if(splitPos.top+splitPos.height > (endPos.top+endPos.height))
|
|
r1 = this._settings.topSplit-1;
|
|
}
|
|
}
|
|
else
|
|
r0 = Math.max(yr[0],this.getIndexById(range.start.row));
|
|
|
|
c0 = Math.max(xr[0],startColumn);
|
|
c1 = Math.min(this._rightSplit?xr[1]-1:xr[1],endColumn);
|
|
|
|
return {r0: r0, r1: r1, c0: c0, c1: c1};
|
|
},
|
|
_getSelectAreaCellPositions: function(i0, j0, i1, j1){
|
|
var start = this._cellPosition(this.getIdByIndex(i0),this.columnId(j0));
|
|
var end = this._cellPosition(this.getIdByIndex(i1),this.columnId(j1));
|
|
return [start, end];
|
|
},
|
|
_setSelectAreaBorders: function(left, center, right, name, css, handle){
|
|
|
|
var handleBox, handlePos,
|
|
area = this._areaSelStorage[name],
|
|
offset = 0;
|
|
|
|
if(this._settings.topSplit)
|
|
offset = this._getTopSplitOffset(area.start, true);
|
|
|
|
//include split in calcs
|
|
var renderArea = function(parentNode, start, end, skipLeft, skipRight){
|
|
var bName, height, width, top, left, hor,
|
|
borders = {"top": 1, "right":1, "bottom": 1, "left": 1};
|
|
if(skipLeft)
|
|
delete borders.left;
|
|
if(skipRight)
|
|
delete borders.right;
|
|
height = end.top - start.top + end.height-1;
|
|
width = end.left - start.left + end.width;
|
|
|
|
for(bName in borders){
|
|
top = start.top + offset;
|
|
|
|
if(bName == "bottom")
|
|
top = end.top + end.height;
|
|
|
|
left = start.left;
|
|
if(bName == "right"){
|
|
left = end.left+end.width;
|
|
}
|
|
|
|
hor = (bName=="top"||bName =="bottom");
|
|
|
|
parentNode.appendChild(webix.html.create("DIV", {
|
|
"class":"webix_area_selection webix_area_selection_"+bName+(css?" "+css:"") ,
|
|
"style": "left:"+left+"px;top:"+top+"px;"+(hor?("width:"+width+"px;"):("height:"+(height-offset)+"px;")),
|
|
"webix_area_name": name
|
|
}, ""));
|
|
var elem = parentNode.lastChild;
|
|
if(bName == "right")
|
|
elem.style.left = left-elem.offsetWidth+"px";
|
|
if(bName == "bottom")
|
|
elem.style.top = top-elem.offsetHeight+"px";
|
|
}
|
|
};
|
|
|
|
if(right)
|
|
renderArea(this._rselect_box_right, right[0], right[1],!!center,false);
|
|
if(center)
|
|
renderArea(this._rselect_box, center[0], center[1],!!left,!!right);
|
|
if(left)
|
|
renderArea(this._rselect_box_left, left[0], left[1],false,!!center);
|
|
|
|
if(handle){
|
|
handlePos = right?right[1]:(center?center[1]:left[1]);
|
|
handleBox = right?this._rselect_box_right:(center?this._rselect_box:this._rselect_box_left);
|
|
handleBox.appendChild(webix.html.create("DIV", {
|
|
"class":"webix_area_selection_handle"+(css?" "+css:"") ,
|
|
"style": "left:"+(handlePos.left+handlePos.width)+"px;top:"+(handlePos.top +handlePos.height)+"px;",
|
|
"webix_area_name": name
|
|
}, ""));
|
|
}
|
|
|
|
},
|
|
_removeAreaNodes: function(name){
|
|
if(name){
|
|
var removeNodes = function(parentNode){
|
|
var nodes = parentNode.childNodes;
|
|
for(var i = nodes.length-1; i>=0; i--){
|
|
if(nodes[i].getAttribute("webix_area_name") == name){
|
|
parentNode.removeChild(nodes[i]);
|
|
}
|
|
}
|
|
};
|
|
removeNodes(this._rselect_box);
|
|
removeNodes(this._rselect_box_left);
|
|
removeNodes(this._rselect_box_right);
|
|
}
|
|
},
|
|
removeSelectArea: function(name){
|
|
if(name){
|
|
if(this.callEvent("onBeforeAreaRemove", [name])){
|
|
delete this._areaSelStorage[name];
|
|
this._removeAreaNodes(name);
|
|
//reconstruct selected areas
|
|
this._selected_areas = [];
|
|
for (var key in this._areaSelStorage)
|
|
this._selected_areas.push(this._areaSelStorage[key]);
|
|
|
|
this.callEvent("onAfterAreaRemove", [name]);
|
|
}
|
|
}
|
|
else {
|
|
for(var n in this._areaSelStorage)
|
|
this.removeSelectArea(n);
|
|
}
|
|
},
|
|
_ars_down: function(e){
|
|
var src = e.target||e.srcElement;
|
|
var css = webix.html._getClassName(src);
|
|
if(css && css.indexOf("webix_area_selection_handle")!=-1){
|
|
var name = src.getAttribute("webix_area_name");
|
|
this._activeAreaSName = name;
|
|
// show block selection
|
|
var area = this._areaSelStorage[name];
|
|
var pos0 = this._cellPosition(area.start.row,area.start.column);
|
|
var pos1 = this._cellPosition(area.end.row,area.end.column);
|
|
|
|
var prerender = this._settings.prerender;
|
|
|
|
var xCorrStart = this.getColumnIndex(area.start.column) < this._settings.leftSplit?0:this._left_width;
|
|
var xCorrEnd = this.getColumnIndex(area.end.column) < this._settings.leftSplit?0:this._left_width;
|
|
|
|
this._bs_ready = [pos0.left+1+xCorrStart-this._scrollLeft, pos0.top +1-(prerender?this._scrollTop:0),{
|
|
row:area.start.row, column:area.start.column
|
|
}];
|
|
this._bs_position = webix.html.offset(this._body);
|
|
|
|
this._bs_start(e);
|
|
this._bs_progress = [pos1.left+1+xCorrEnd-this._scrollLeft, pos1.top +1-(prerender?this._scrollTop:0)];
|
|
this._bs_select(false, false);
|
|
return webix.html.preventEvent(e);
|
|
}
|
|
},
|
|
getSelectArea: function(name){
|
|
return this._areaSelStorage[name||this._lastDefArea];
|
|
},
|
|
getAllSelectAreas: function(){
|
|
return this._areaSelStorage;
|
|
},
|
|
_span_correct_range: function(range){
|
|
if (!this.config.spans) return true;
|
|
var i, j, c0, c1, r0, r1,
|
|
span, spanR0,spanC0,
|
|
minR0, minC0,maxR1, maxC1,
|
|
changed = false,
|
|
start = range.start,
|
|
end = range.end;
|
|
|
|
minR0 = r0 = this.getIndexById(start.row);
|
|
minC0 = c0 = this.getColumnIndex(start.column);
|
|
maxR1 = r1 = this.getIndexById(end.row);
|
|
maxC1 = c1 = this.getColumnIndex(end.column);
|
|
|
|
for(i = r0; i <= r1; i++){
|
|
for(j = c0; j <= c1; j++){
|
|
span = this.getSpan(this.getIdByIndex(i), this.columnId(j));
|
|
if(span){
|
|
spanR0 = this.getIndexById(span[0]);
|
|
spanC0 = this.getColumnIndex(span[1]);
|
|
if(spanR0 < minR0){
|
|
minR0 = spanR0;
|
|
changed = true;
|
|
}
|
|
if(spanC0 < minC0){
|
|
changed = true;
|
|
minC0 = spanC0;
|
|
}
|
|
if(spanR0 + span[3]-1 > maxR1){
|
|
changed = true;
|
|
maxR1 = spanR0 + span[3]-1;
|
|
}
|
|
if(spanC0 + span[2]-1 > maxC1){
|
|
changed = true;
|
|
maxC1 = spanC0 + span[2]-1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(changed){
|
|
range.start = {row: this.getIdByIndex(minR0), column:this.columnId(minC0)};
|
|
range.end = {row: this.getIdByIndex(maxR1), column:this.columnId(maxC1)};
|
|
this._span_correct_range(range);
|
|
}
|
|
}
|
|
});
|
|
webix.protoUI({
|
|
name:"rangechart",
|
|
$init:function(){
|
|
this.attachEvent("onAfterRender", this._init_frame);
|
|
this._set_full_range();
|
|
},
|
|
_init_frame:function(){
|
|
webix.assert((this._settings.type.indexOf("pie") ===-1 && this._settings.type !=="radar" &&
|
|
this._settings.type !=="donut"), "Not suppored chart type");
|
|
|
|
if(!this._map._areas.length || this._frame){
|
|
this._setHandle(true);
|
|
return;
|
|
}
|
|
|
|
this._setMap();
|
|
this._item_radius = (this._map._areas[0].points[2]-this._map._areas[0].points[0])/2;
|
|
this._rHandle = webix.html.create("div", {"class":"webix_chart_resizer right", "tabindex":"0", "role":"button", "aria-label":webix.i18n.aria.resizeChart });
|
|
this._lHandle = webix.html.create("div", {"class":"webix_chart_resizer left", "tabindex":"0", "role":"button", "aria-label":webix.i18n.aria.resizeChart });
|
|
this._frame = webix.html.create("div",{ "class":"webix_chart_frame"});
|
|
|
|
this._viewobj.appendChild(this._lHandle);
|
|
this._viewobj.appendChild(this._frame);
|
|
this._viewobj.appendChild(this._rHandle);
|
|
|
|
this._setHandle();
|
|
|
|
webix._event(this._rHandle, webix.env.mouse.down, this._frDown, {bind:this});
|
|
webix._event(this._lHandle, webix.env.mouse.down, this._frDown, {bind:this});
|
|
webix._event(this._frame, webix.env.mouse.down, this._frDown, {bind:this});
|
|
|
|
webix._event(webix.toNode(this._rHandle), "keydown", this._keyShift, {bind:this});
|
|
webix._event(webix.toNode(this._lHandle), "keydown", this._keyShift, {bind:this});
|
|
|
|
if (this._value)
|
|
this._settings.range = this._set_full_range(this._value);
|
|
|
|
this._refresh_range();
|
|
this.callEvent("onAfterRangeChange", [this._value]);
|
|
this.data.attachEvent("onStoreUpdated", webix.bind(this._refresh_range, this));
|
|
},
|
|
$setSize:function(x, y){
|
|
if (webix.ui.chart.prototype.$setSize.call(this, x, y))
|
|
this._setMap();
|
|
},
|
|
_setHandle:function(update){
|
|
if(this._rHandle && !this._handle_radius){
|
|
this._handle_radius = this._rHandle.clientWidth/2;
|
|
if(update)
|
|
this._refresh_range();
|
|
}
|
|
},
|
|
_setMap:function(){
|
|
var bounds = this._getChartBounds(this._content_width,this._content_height);
|
|
this._mapStart = bounds.start;
|
|
this._mapEnd = bounds.end;
|
|
},
|
|
removeAllSeries: function(){
|
|
this._frame = this._rHandle = this._lHandle = null;
|
|
webix.ui.chart.prototype.removeAllSeries.apply(this,arguments);
|
|
},
|
|
_keyShift:function(e){
|
|
var code = e.which || e.keyCode;
|
|
if(code === 37 || code ===39){
|
|
webix.html.preventEvent(e);
|
|
|
|
var index = e.target.className.indexOf("right")!==-1?"eindex":"sindex";
|
|
var id = e.target.className.indexOf("right")!==-1?"end":"start";
|
|
var range = this._value;
|
|
|
|
range[index] = range[index] + (code === 37?-1:1);
|
|
if(this._map._areas[range[index]]){
|
|
range[id] = this._get_id_by_index(range[index]);
|
|
this.setFrameRange(range);
|
|
}
|
|
}
|
|
},
|
|
_frDown:function(e){
|
|
if(e.target.className.indexOf("webix_chart_resizer") !==-1)
|
|
this._activeHandle = e.target;
|
|
else if(this._map._areas.length){
|
|
var spos = this._map._areas[this._value.sindex].points[2]-this._item_radius;
|
|
var epos = this._map._areas[this._value.eindex].points[2];
|
|
|
|
this._activeFrame = {
|
|
ex:webix.html.pos(e).x,
|
|
fx:spos+this._mapStart.x,
|
|
fw:epos-spos
|
|
};
|
|
}
|
|
|
|
webix.html.addCss(this._viewobj,"webix_noselect webix_wresize_cursor");
|
|
|
|
this._frClear();
|
|
this._resizeHandlerMove = webix.event(document.body, webix.env.mouse.move, this._frMove, {bind:this});
|
|
this._resizeHandlerUp = webix.event(document.body, webix.env.mouse.up, this._frUp, {bind:this});
|
|
},
|
|
_frClear:function(){
|
|
if(webix._events[this._resizeHandlerMove]){
|
|
webix.eventRemove(this._resizeHandlerMove);
|
|
webix.eventRemove(this._resizeHandlerUp);
|
|
}
|
|
},
|
|
_frMove:function(e){
|
|
if(this._activeHandle){
|
|
var pos_x = webix.html.pos(e).x-webix.html.offset(this.$view).x;
|
|
if(pos_x>=this._mapStart.x && pos_x<=this._mapEnd.x){
|
|
if(this._activeHandle.className.indexOf("left")!==-1){
|
|
if(pos_x<this._rHandle.offsetLeft){
|
|
this._activeHandle.style.left = pos_x-this._handle_radius+"px";
|
|
this._frame.style.left = pos_x+"px";
|
|
this._frame.style.width = this._rHandle.offsetLeft-this._lHandle.offsetLeft-1+"px";
|
|
}
|
|
}
|
|
else if(pos_x>this._lHandle.offsetLeft+this._handle_radius){
|
|
this._activeHandle.style.left = pos_x-this._handle_radius+"px";
|
|
this._frame.style.width = this._rHandle.offsetLeft-this._lHandle.offsetLeft-1+"px";
|
|
}
|
|
}
|
|
}
|
|
else if(this._activeFrame){
|
|
var shift = webix.html.pos(e).x - this._activeFrame.ex;
|
|
var lx = this._activeFrame.fx+shift;
|
|
var rx = lx+this._activeFrame.fw;
|
|
|
|
if(this._mapStart.x<=lx && this._mapEnd.x>=rx){
|
|
webix.extend(this._activeFrame, {lx:lx, rx:rx}, true);
|
|
|
|
this._lHandle.style.left = lx-this._handle_radius+"px";
|
|
this._rHandle.style.left = rx-this._handle_radius+"px";
|
|
this._frame.style.left = lx+"px";
|
|
}
|
|
}
|
|
},
|
|
_frUp:function(e){
|
|
this._frClear();
|
|
|
|
webix.html.removeCss(this._viewobj,"webix_noselect");
|
|
webix.html.removeCss(this._viewobj,"webix_wresize_cursor");
|
|
|
|
if(!this.count()) return;
|
|
|
|
if(this._activeHandle){
|
|
var pos_x = webix.env.touch?e.changedTouches[0].pageX:webix.html.pos(e).x;
|
|
pos_x -= webix.html.offset(this.$view).x+this._mapStart.x;
|
|
|
|
var ind = this._get_index_by_pos(pos_x);
|
|
var id = this._get_id_by_index(ind);
|
|
|
|
if (this._activeHandle === this._lHandle){
|
|
if(ind >= this._value.eindex){
|
|
ind = this._value.eindex;
|
|
id = this._get_id_by_index(ind);
|
|
}
|
|
this._value.start = id;
|
|
this._value.sindex = ind;
|
|
} else{
|
|
if(ind <= this._value.sindex){
|
|
ind = this._value.sindex;
|
|
id = this._get_id_by_index(ind);
|
|
}
|
|
this._value.end = id;
|
|
this._value.eindex = ind;
|
|
}
|
|
|
|
this._activeHandle = null;
|
|
}
|
|
else if(this._activeFrame && this._activeFrame.lx){
|
|
var lind = this._value.sindex = this._get_index_by_pos(this._activeFrame.lx-this._mapStart.x);
|
|
var rind = this._value.eindex = this._get_index_by_pos(this._activeFrame.rx-this._mapStart.x);
|
|
this._value.start = this._get_id_by_index(lind);
|
|
this._value.end = this._get_id_by_index(rind);
|
|
|
|
this._activeFrame = null;
|
|
}
|
|
|
|
this._refresh_range();
|
|
this.callEvent("onAfterRangeChange", [this._value.start, this._value.end]);
|
|
},
|
|
_get_id_by_index:function(ind){
|
|
if (ind >= this.data.order.length)
|
|
ind = this.data.order.length-1;
|
|
return this.getItem(this.data.order[ind])[this._settings.frameId || "id"];
|
|
},
|
|
_get_index_by_pos:function(pos){
|
|
var areas = this._map._areas;
|
|
for(var i = 0; i<areas.length; i++)
|
|
if(pos <= areas[i].points[2]-this._item_radius)
|
|
return i;
|
|
|
|
return areas.length-1;
|
|
},
|
|
_get_frame_index:function(value){
|
|
var key = this._settings.frameId || "id";
|
|
|
|
for (var i=0; i<this.data.order.length; i++)
|
|
if (this.getItem(this.data.order[i])[key]==value)
|
|
return i;
|
|
|
|
return -1;
|
|
},
|
|
_set_full_range:function(value){
|
|
if(!value)
|
|
value = { start:0, end:0, sindex:0, eindex: 0 };
|
|
else{
|
|
if(value.start) value.sindex = this._get_frame_index(value.start);
|
|
if(value.end) value.eindex = this._get_frame_index(value.end);
|
|
value.start = value.start || this._get_id_by_index(value.sindex);
|
|
value.end = value.end || this._get_id_by_index(value.eindex);
|
|
}
|
|
this._value = value;
|
|
},
|
|
range_setter:function(value){
|
|
this._set_full_range(value);
|
|
return this._value;
|
|
},
|
|
getFrameData:function(){
|
|
var res = [];
|
|
for (var i=this._value.sindex; i<=this._value.eindex; i++){
|
|
var item = this.getItem(this.data.order[i]);
|
|
if(item) res.push(item);
|
|
}
|
|
return res;
|
|
},
|
|
setFrameRange:function(range){
|
|
this._set_full_range(range);
|
|
this._refresh_range();
|
|
|
|
this.callEvent("onAfterRangeChange", [range]);
|
|
},
|
|
_refresh_range:function(){
|
|
if(!this._map) return;
|
|
var areas = this._map._areas;
|
|
|
|
if (areas.length){
|
|
var sx = areas[this._value.sindex].points[0] + this._mapStart.x+this._item_radius-1;
|
|
var ex = areas[this._value.eindex].points[0] + this._mapStart.x+this._item_radius-1;
|
|
|
|
this._lHandle.style.left = sx-this._handle_radius+"px";
|
|
this._rHandle.style.left = ex-this._handle_radius+"px";
|
|
this._frame.style.left = sx+"px";
|
|
this._frame.style.width = (ex-sx)+"px";
|
|
|
|
this._settings.range = this._value;
|
|
}
|
|
},
|
|
getFrameRange:function(){
|
|
return this._settings.range;
|
|
}
|
|
}, webix.ui.chart);
|
|
webix.protoUI({
|
|
name: "richtext",
|
|
defaults:{
|
|
label:"",
|
|
labelWidth:80,
|
|
labelPosition:"left"
|
|
},
|
|
$init: function(config) {
|
|
this.$ready.unshift(this._setLayout);
|
|
},
|
|
getInputNode:function(){
|
|
return this.$view.querySelector(".webix_richtext_editor");
|
|
},
|
|
_button:function(name){
|
|
return {
|
|
view: "toggle",
|
|
type: "iconButton",
|
|
icon: name, name: name, id:name,
|
|
label: webix.i18n.richtext[name],
|
|
autowidth: true,
|
|
action:name,
|
|
click: this._add_data
|
|
};
|
|
},
|
|
_setLayout: function() {
|
|
var top = this;
|
|
|
|
var editField = {
|
|
view: "template",
|
|
css: "webix_richtext_container",
|
|
borderless: true,
|
|
template: "<div class='webix_richtext_editor' contenteditable='true'>"+this.getValue()+"</div>",
|
|
on: {
|
|
onAfterRender: function() {
|
|
webix._event(
|
|
top.getInputNode(),
|
|
"blur",
|
|
function(){
|
|
top._updateValue(this.innerHTML);
|
|
}
|
|
);
|
|
webix._event(
|
|
top.getInputNode(),
|
|
"keyup",
|
|
function(){
|
|
top._getselection();
|
|
}
|
|
);
|
|
}
|
|
},
|
|
onClick: {
|
|
webix_richtext_editor: function() {
|
|
top._getselection();
|
|
}
|
|
}
|
|
};
|
|
|
|
var editorToolbar = {
|
|
view: "toolbar",
|
|
id:"toolbar",
|
|
elements: [
|
|
this._button("underline"),
|
|
this._button("bold"),
|
|
this._button("italic"),
|
|
{}
|
|
]
|
|
};
|
|
|
|
var rows = [
|
|
editorToolbar,
|
|
editField
|
|
];
|
|
|
|
if (this.config.labelPosition === "top" || !this.config.labelWidth){
|
|
editorToolbar.elements.push({
|
|
view:"label", label: this.config.label, align:"right"
|
|
});
|
|
this.rows_setter(rows);
|
|
} else {
|
|
this.config.borderless = true;
|
|
this.cols_setter([{
|
|
template: (this.config.label || " "),
|
|
width: this.config.labelWidth
|
|
}, {
|
|
rows:rows
|
|
}]);
|
|
}
|
|
},
|
|
_getselection: function() {
|
|
var top = this;
|
|
var bar = top.$$("toolbar");
|
|
var sel;
|
|
|
|
bar.setValues({
|
|
italic:false, underline:false, bold:false
|
|
});
|
|
|
|
if(window.getSelection) {
|
|
sel = window.getSelection();
|
|
} else {
|
|
sel = document.selection.createRange();
|
|
}
|
|
|
|
for (var i = 0; i < sel.rangeCount; ++i) {
|
|
var range = sel.getRangeAt(i);
|
|
if (top.$view.contains(this.getInputNode())){
|
|
if (document.queryCommandState("bold")) {
|
|
top.$$("bold").setValue(true);
|
|
}
|
|
if (document.queryCommandState("underline")) {
|
|
top.$$("underline").setValue(true);
|
|
}
|
|
if (document.queryCommandState("italic")) {
|
|
top.$$("italic").setValue(true);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
refresh: function() {
|
|
this.getInputNode().innerHTML = this.config.value;
|
|
},
|
|
_execCommandOnElement:function(el, commandName) {
|
|
var sel, selText;
|
|
|
|
if(window.getSelection()) {
|
|
sel = window.getSelection();
|
|
selText = sel.toString().length;
|
|
} else {
|
|
sel = document.selection.createRange();
|
|
selText = sel.text.length;
|
|
}
|
|
|
|
if(selText > 0) {
|
|
for (var i = 0; i < sel.rangeCount; ++i) {
|
|
var range = sel.getRangeAt(i);
|
|
if (!sel.isCollapsed) {
|
|
document.execCommand(commandName, false, '');
|
|
} else {
|
|
var textValue = sel.focusNode.textContent;
|
|
var focusEl = sel.focusNode;
|
|
var focustext = sel.anchorOffset;
|
|
var wordBegining = textValue.substring(0, focustext).match(/[A-Za-z]*$/)[0];
|
|
var wordEnd = textValue.substring(focustext).match(/^[A-Za-z]*/)[0];
|
|
|
|
var startWord = focustext - wordBegining.length;
|
|
var endWord = focustext + wordEnd.length;
|
|
|
|
range.setStart(focusEl, startWord);
|
|
range.setEnd(focusEl, endWord);
|
|
sel.removeAllRanges();
|
|
|
|
window.getSelection().addRange(range);
|
|
document.execCommand(commandName, false, '');
|
|
}
|
|
}
|
|
}
|
|
},
|
|
_add_data:function() {
|
|
var style = this.config.action;
|
|
var top = this.getTopParentView();
|
|
var editableElement = top.getInputNode();
|
|
|
|
if(this.$view.contains(this.getInputNode())){
|
|
top._execCommandOnElement(editableElement, this.config.action);
|
|
}
|
|
},
|
|
focus: function() {
|
|
var editableElement = this.$view.querySelector(".webix_richtext_editor");
|
|
editableElement.focus();
|
|
},
|
|
_updateValue: function(value){
|
|
var old = this.config.value;
|
|
this.config.value = value || "";
|
|
|
|
if (old !== value)
|
|
this.callEvent("onChange", [value, old]);
|
|
},
|
|
setValue: function(value) {
|
|
this._updateValue(value);
|
|
this.refresh();
|
|
},
|
|
getValue: function() {
|
|
var input = this.getInputNode();
|
|
if (input)
|
|
this.config.value = this.getInputNode().innerHTML;
|
|
|
|
var value = this.config.value;
|
|
return value || (value ===0?"0":"");
|
|
}
|
|
}, webix.IdSpace, webix.ui.layout);
|
|
|
|
webix.protoUI({
|
|
name: "gage",
|
|
defaults: {
|
|
value: 0,
|
|
minRange: 0,
|
|
maxRange: 100,
|
|
minWidth:250,
|
|
minHeight:200,
|
|
smoothFlow: true,
|
|
scale:3,
|
|
stroke:7
|
|
},
|
|
$init: function() {
|
|
this.$ready.push(webix.bind(this._setDefaultView, this));
|
|
this.attachEvent("onDestruct", function(){
|
|
this._circleGradient = this._gageGradientPoint = this._gage = null;
|
|
});
|
|
},
|
|
$setSize: function(x, y) {
|
|
if (webix.ui.view.prototype.$setSize.call(this, x, y)){
|
|
this._refresh(this.config.value);
|
|
this._animate(this.config.value);
|
|
}
|
|
},
|
|
_refresh: function() {
|
|
var curves = this.$view.querySelector('.webix_gage_curves'),
|
|
gageInfo = this.$view.querySelector('.webix_gage_info'),
|
|
kx = this.config.scale,
|
|
x = this.$width;
|
|
|
|
curves.setAttribute("r", (x / kx));
|
|
curves.setAttribute("strokeDasharray", Math.round(Math.PI * x / kx));
|
|
curves.style.r = x / kx;
|
|
curves.style.strokeDasharray = Math.round(Math.PI * x / kx);
|
|
|
|
gageInfo.setAttribute('style', "width: "+Math.round((x / kx) * 2)+"px;");
|
|
this._gage.setAttribute('style', "height: "+(x / kx + 20)+"px;");
|
|
this._circleGradient.setAttribute("r", (x / kx));
|
|
this._circleGradient.setAttribute('style', "stroke-dasharray: " + Math.round(this.gradientLength * Math.PI * x / kx) + ", 1900;");
|
|
this._draw_line(curves.style.r);
|
|
},
|
|
_safeValue: function(value){
|
|
return Math.min(Math.max(value, this._settings.minRange), this._settings.maxRange);
|
|
},
|
|
_draw_line: function(radius) {
|
|
var svgCoord = this.$width,
|
|
arrowSpace = 0.05,
|
|
value = this.config.value;
|
|
|
|
value = this._safeValue(value);
|
|
|
|
var currentChartValue = value - this.config.minRange;
|
|
var degrees = Math.round(currentChartValue * 180 / (this.config.maxRange - this.config.minRange));
|
|
|
|
if(degrees === 0 || degrees === 180) {
|
|
this._gage.style.paddingTop = "3px";
|
|
}
|
|
this._gageGradientPoint.style.transformOrigin = (svgCoord / 2 - (0.5 + arrowSpace)) + 'px 0 0';
|
|
this._gageGradientPoint.setAttribute('y1', '0');
|
|
this._gageGradientPoint.setAttribute('x1', Math.round(svgCoord * (0.5 + arrowSpace)));
|
|
|
|
this._gageGradientPoint.setAttribute('y2', 0);
|
|
this._gageGradientPoint.setAttribute('x2', Math.round(svgCoord * (0.5 + arrowSpace / 2) + parseInt(radius)));
|
|
},
|
|
_animate: function(value) {
|
|
var webixGageValue = this.$view.querySelector('.webix_gage-value');
|
|
var currentChartValue = this._safeValue(value) - this.config.minRange;
|
|
var degrees = Math.round(currentChartValue * 180 / (this.config.maxRange - this.config.minRange));
|
|
var viewSize = this.$width;
|
|
|
|
viewSize = Math.floor(viewSize/10);
|
|
this.$view.style.fontSize = viewSize+'px';
|
|
webixGageValue.innerHTML = value;
|
|
|
|
this._circleGradient.style.stroke = this.color;
|
|
this._circleGradient.setAttribute("stroke", this.color);
|
|
this._gageGradientPoint.setAttribute("transform", "rotate(" + degrees + " "+ this.$width/2 +" 0)");
|
|
this._gageGradientPoint.style.transform = "rotate(" + degrees + "deg)";
|
|
},
|
|
_setDash: function() {
|
|
webix.assert(this.config.minRange < this.config.maxRange, "Invalid Range Values");
|
|
this.gradientLength = (this._safeValue(this.config.value) - this.config.minRange) / (this.config.maxRange - this.config.minRange);
|
|
|
|
var template = this.config.color;
|
|
if (template){
|
|
if (typeof template === "function")
|
|
this.color = template.call(this, this.config.value);
|
|
else
|
|
this.color = template;
|
|
} else
|
|
this.color = "hsl(" + (120 - Math.round(this.gradientLength * 120)) + ", 100%, 50%)";
|
|
|
|
if (this.config.animation === true) {
|
|
this.defaultColor = "hsl(125, 100%, 50%)";
|
|
} else {
|
|
this.defaultColor = "hsl(" + (120 - Math.round(this.gradientLength * 120)) + ", 100%, 50%)";
|
|
}
|
|
},
|
|
_setDefaultView: function() {
|
|
this.gradientLength = 0;
|
|
this._setDash();
|
|
this.$view.innerHTML = '<div class="webix_gage_label"><span>'+(this.config.label||"")+'</span></div><svg class="webix_gage" style="height:300px; position: relative;"><circle class="webix_gage_curves" r="0" cx="50%" cy="0" stroke="#EEEEEE" stroke-width="'+this.config.stroke+'%" fill="none"></circle><circle class="webix_gage_gradient" r="0" stroke="'+this.defaultColor+'" cx="50%" cy="0" stroke-width="'+this.config.stroke+'%" fill="none" style="stroke-dasharray: 0, 1900;"></circle><line class="webix_gage_gradient_point" x1="0" x2="0" y1="0" y2="0" style="stroke:#B0B0B0; stroke-width:4;"></line></svg><div class="webix_gage_info"><div class="webix_gage_min_range">'+this.config.minRange+'</div><div class="webix_gage_max_range">'+this.config.maxRange+'</div><div class="webix_gage_placeholder"><div class="webix_gage-value">'+this.config.value+'</div><div class="webix_gage_range_info">'+(this.config.placeholder||"")+'</div></div></div>';
|
|
this._circleGradient = this.$view.querySelector('.webix_gage_gradient');
|
|
this._gageGradientPoint = this.$view.querySelector('.webix_gage_gradient_point');
|
|
this._gage = this.$view.querySelector('.webix_gage');
|
|
if (this.isVisible() === true && this.config.smoothFlow === true && (webix.env.svganimation && !webix.env.isEdge)) {
|
|
this._circleGradient.setAttribute('class', 'webix_gage_gradient webix_gage_animated');
|
|
this._gageGradientPoint.setAttribute('class', 'webix_gage_gradient_point webix_gage_gradient_point_animated');
|
|
}
|
|
},
|
|
setValue: function(value) {
|
|
this.config.value = value;
|
|
this._setDash();
|
|
this._refresh();
|
|
this._animate(value);
|
|
},
|
|
getValue: function() {
|
|
return this.config.value;
|
|
}
|
|
}, webix.EventSystem, webix.ui.view);
|
|
webix.protoUI({
|
|
name: "bullet",
|
|
defaults: {
|
|
color: "#394646",
|
|
marker: false,
|
|
layout: "x",
|
|
barWidth: 40,
|
|
flowTime: 500,
|
|
labelWidth: 150,
|
|
stroke: 8,
|
|
bands:[
|
|
{ value:100, color:"#5be5d6"},
|
|
{ value:80, color:"#fff07e" },
|
|
{ value:60, color:"#fd8b8c" }
|
|
],
|
|
scale: {
|
|
step:10
|
|
}
|
|
},
|
|
label_setter:webix.template,
|
|
placeholder_setter: webix.template,
|
|
$init:function(obj){
|
|
if (obj) {
|
|
if ((!obj.layout || obj.layout === "x") && !obj.height)
|
|
obj.height = obj.scale === false ? 60: 90;
|
|
if (obj.layout === "y" && !obj.width)
|
|
obj.width = obj.scale === false ? 60: 97;
|
|
}
|
|
},
|
|
scale_setter:function(config){
|
|
config.step = config.step || 10;
|
|
config.template = webix.template(config.template||"#value#");
|
|
return config;
|
|
},
|
|
$setSize: function(x, y) {
|
|
if (webix.ui.view.prototype.$setSize.call(this, x, y)) {
|
|
this._setDefaultView(this._settings.layout === "y" ? y : x);
|
|
if (this._settings.value)
|
|
this._animate(0, this._settings.value);
|
|
}
|
|
},
|
|
_safeValue: function(value) {
|
|
return Math.min(Math.max(value, this._settings.minRange), this._settings.maxRange);
|
|
},
|
|
_animateFrame: function(timestamp) {
|
|
this._dt = timestamp - (this._time || timestamp);
|
|
this._time = timestamp;
|
|
var fps;
|
|
|
|
if(this._settings.flowTime > this._dt) {
|
|
fps = this._settings.flowTime / this._dt;
|
|
} else {
|
|
fps = this._settings.flowTime;
|
|
}
|
|
|
|
if (fps > 1000 || fps < 5) fps = 30;
|
|
|
|
var step = (this._settings.value - this._prevValue)/fps;
|
|
this._nowValue += step;
|
|
|
|
if (Math.abs(this._nowValue - this._settings.value) < Math.abs(step))
|
|
this._nowValue = this._settings.value;
|
|
|
|
if (this._nowValue != this._settings.value){
|
|
this._requestId = requestAnimationFrame(this._animateFrame.bind(this));
|
|
} else {
|
|
cancelAnimationFrame(this._requestId);
|
|
this._requestId = null;
|
|
}
|
|
this._bulletValue.setAttribute("width", Math.floor(this._nowValue * this._scale));
|
|
},
|
|
_animate: function(from, to){
|
|
this._prevValue = this._nowValue = from;
|
|
this._settings.value = to;
|
|
|
|
var label = this._settings.label;
|
|
if (label)
|
|
this.$view.querySelector(".webix_bullet_header").textContent = label(this._settings);
|
|
|
|
var placeholder = this._settings.placeholder;
|
|
if (typeof placeholder === "function")
|
|
this.$view.querySelector(".webix_bullet_subheader").textContent = placeholder(this._settings);
|
|
|
|
if (this.isVisible() === true && this._settings.smoothFlow === true && (window.requestAnimationFrame)) {
|
|
if (!this._requestId)
|
|
this._requestId = requestAnimationFrame(this._animateFrame.bind(this));
|
|
} else {
|
|
this._bulletValue.setAttribute("width", Math.floor(to * this._scale));
|
|
}
|
|
},
|
|
_setAttr: function(tag, names, values) {
|
|
for (var i = 0; i < names.length; i++)
|
|
tag.setAttribute(names[i], values[i]);
|
|
},
|
|
_createNS:function(tag, names, values){
|
|
var ns = "http://www.w3.org/2000/svg";
|
|
var el = document.createElementNS(ns, tag);
|
|
if (names)
|
|
this._setAttr(el, names, values);
|
|
|
|
return el;
|
|
},
|
|
_dom:function(data){
|
|
var top = this._createNS(data[0], data[1], data[2]);
|
|
var child = data[3];
|
|
if (child)
|
|
for (var i = 0; i < child.length; i++)
|
|
top.appendChild(this._dom(child[i]));
|
|
|
|
return top;
|
|
},
|
|
_setView: function() {
|
|
var id = "d"+webix.uid();
|
|
var svg = this._createNS("svg", ["class"], ["webix_bullet_graph_svg"]);
|
|
var container = this._createNS('g');
|
|
var containerBand = this._createNS('g');
|
|
var value = this._createNS('rect', ["x","y", "width", "height", "class", "style"], [this._leftStart, this._topStart, 100, this._settings.stroke, "webix_bullet_value","filter:url(#"+id+");fill:"+this._settings.color]);
|
|
|
|
var valueMarker = this._createNS('rect', ["x","y", "width", "height", "fill"], [0, 5, 3, (this._settings.barWidth - 10), "rgba(0,0,0,0.5)"]);
|
|
var division = this._createNS('g', ["stroke", "stroke-width", "fill"], ["#8b94ac", "2", "none"]);
|
|
var text = this._createNS('text', ["text-anchor", "stroke", "fill"], ["end", "none", "#8b94ac"]);
|
|
var left = this._settings.layout == "y" ? "50%" : this._leftStart - 10;
|
|
var top = this._settings.layout == "y" ? 11 : 17;
|
|
var textRow1 = this._createNS('tspan',["x", "y", "class"], [left, top, "webix_bullet_header"]);
|
|
var textRow2 = this._createNS('tspan',["x", "y", "class"], [left, top+17, "webix_bullet_subheader"]);
|
|
var range = this._createNS('text', ["text-anchor", "stroke", "class", "fill"], ["middle", "none", "webix_bullet_scale", "#8b94ac"]);
|
|
|
|
var filter = this._dom(
|
|
["filter", ["id","x","y", "width", "height"], [id, "0","-150%", "110%", "400%"], [
|
|
["feOffset",["in", "dx","dy"],["SourceAlpha", 0, 0 ]],
|
|
["feGaussianBlur",["stdDeviation"],["2"]],
|
|
["feComponentTransfer", 0 ,0, [
|
|
["feFuncA", ["type", "slope"], ["linear", "0.5"]]
|
|
]],
|
|
["feMerge", 0,0, [
|
|
["feMergeNode"],
|
|
["feMergeNode", ["in"], ["SourceGraphic"]]
|
|
]]
|
|
]]
|
|
);
|
|
|
|
svg.appendChild(filter);
|
|
|
|
var tempContainer = document.createElement('div');
|
|
container.appendChild(containerBand);
|
|
|
|
if(this._settings.marker !== false) {
|
|
valueMarker.setAttribute("x", (this._leftStart + this._safeValue(this._settings.marker)*this._scale - 2));
|
|
container.appendChild(valueMarker);
|
|
}
|
|
|
|
container.appendChild(value);
|
|
text.appendChild(textRow1);
|
|
text.appendChild(textRow2);
|
|
svg.appendChild(text);
|
|
|
|
var vertical = this._settings.layout === "y";
|
|
if (this._settings.scale){
|
|
for (var i = this._settings.minRange; i <= this._settings.maxRange; i+= this._settings.scale.step){
|
|
var label = this._settings.labelHeight || this._settings.labelWidth;
|
|
var left = Math.floor(label + i*this._scale-(i?0.1:-1));
|
|
var x = vertical ? (this.$width - this._settings.barWidth)/2 + -10 : left;
|
|
var y = vertical ? this._chartWidth - left + label + 44 : this._settings.barWidth + 28;
|
|
var z = vertical ? -13 : this._settings.barWidth+3;
|
|
var align = vertical ? "end" : "middle";
|
|
|
|
var bulletRangeChild = this._createNS('tspan',
|
|
["x", "y", "text-anchor"], [x, y, align]);
|
|
var bulletDivChild = this._createNS('line',
|
|
["x1", "y1", "x2", "y2", "stroke-width"], [left,z,left,z+10,1]);
|
|
|
|
tempContainer.innerHTML = this._settings.scale.template({ value: i });
|
|
bulletRangeChild.appendChild(tempContainer.childNodes[0]);
|
|
range.appendChild(bulletRangeChild);
|
|
division.appendChild(bulletDivChild);
|
|
|
|
}
|
|
|
|
container.appendChild(division);
|
|
svg.appendChild(range);
|
|
}
|
|
|
|
|
|
for (var i = 0; i < this._settings.bands.length; i++){
|
|
var obj = this._settings.bands[i];
|
|
var band = this._createNS('path');
|
|
var value = this._safeValue(obj.value)*this._scale;
|
|
band.setAttribute("d", "M "+this._leftStart+",0 l " + value + ",0 l 0,"+this._settings.barWidth+" l -" + value + ",0 z");
|
|
band.setAttribute("fill", obj.color);
|
|
containerBand.appendChild(band);
|
|
}
|
|
|
|
svg.appendChild(container);
|
|
|
|
if (this._settings.layout === "y"){
|
|
var w = this._settings.scale?(this.$width / 2 - 10):0;
|
|
var h = this.$height + this._leftStart - 28;
|
|
container.setAttribute("transform", "translate("+w+", "+h+") rotate(270)");
|
|
text.setAttribute("text-anchor", "middle");
|
|
text.childNodes[0].setAttribute("x", "55%");
|
|
text.childNodes[1].setAttribute("x", "55%");
|
|
range.setAttribute("text-anchor", "right");
|
|
}
|
|
svg.setAttribute("viewBox", "0 0 " + this.$width + " " + this.$height + "");
|
|
|
|
return svg;
|
|
},
|
|
_setDefaultView: function(size) {
|
|
if (!size) return;
|
|
webix.assert(this.config.minRange < this.config.maxRange, "Invalid Range Values");
|
|
var _view = this.$view;
|
|
_view.innerHTML = "";
|
|
|
|
var label = this._settings.labelHeight || this._settings.labelWidth;
|
|
|
|
this._leftStart = this._settings.label ? label : 7;
|
|
this._topStart = Math.floor((this._settings.barWidth - this._settings.stroke)/2);
|
|
this._chartWidth = size - this._leftStart - 30;
|
|
this._scale = this._chartWidth / (this._settings.maxRange - this._settings.minRange);
|
|
|
|
var svg = this._setView();
|
|
// scale height fix for ie
|
|
svg.setAttribute("height", this.$height);
|
|
svg.setAttribute("width", this.$width);
|
|
|
|
_view.appendChild(svg);
|
|
this._bulletValue = _view.querySelector(".webix_bullet_value");
|
|
},
|
|
setValue: function(value) {
|
|
if (this._settings.value != value){
|
|
this._animate(this._settings.value, value);
|
|
}
|
|
},
|
|
getValue: function() {
|
|
return this._settings.value;
|
|
}
|
|
}, webix.ui.gage, webix.ui.view);
|