empresa-libre/source/static/js/webix_debug.js

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 = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#x27;",
"`": "&#x60;"
};
var badChars = /[&<>"'`]/g;
var escapeChar = function(chr) {
return escape[chr] || "&amp;";
};
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 || "&nbsp;";
}
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: " &lt;&lt; ",
last: " &gt;&gt; ",
next: " &gt; ",
prev: " &lt; "
};
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 ="&nbsp;";
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===""?"&nbsp;":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||"&nbsp;")+"</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 = "&nbsp;";
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> &nbsp; #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>&nbsp;"+(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]):"&nbsp;"));
}
},
_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,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
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;'>&nbsp;</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;'>&nbsp;</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'>&nbsp;</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+"'>&nbsp;</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]+"'>&nbsp;</div>";
handles += "<div class='webix_slider_handle webix_slider_handle_1' id='"+common._handle_id[1]+"' "+aria+" aria-valuenow='"+obj.value[1]+"'>&nbsp;</div>";
var html = "<div class='webix_slider_title'></div><div class='webix_slider_box'><div class='webix_slider_right'>&nbsp;</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 ? "&nbsp;" : "",
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,"&nbsp;");
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);