/* @license webix UI v.4.1.0 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="4.1.0"; 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 (!base[method] || 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"){ 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; }; //dom helpers //hash of attached events webix._events = {}; //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); webix._events[id]=[node,event,handler]; //store event info, for detaching //use IE's of FF's way of event's attaching if (node.addEventListener) node.addEventListener(event, handler, !!context.capture); else if (node.attachEvent) node.attachEvent("on"+event, webix._events[id][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],false); else if (ev[0].detachEvent) ev[0].detachEvent("on"+ev[1],ev[2]); delete this._events[id]; //delete all traces }; //debugger helpers //anything starting from error or log will be removed during code compression //add message in the log 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]; return (stack && stack.length) ? true : 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:"; if (navigator.userAgent.indexOf("Mobile")!=-1 || navigator.userAgent.indexOf("Windows Phone")!=-1) webix.env.mobile = true; if (webix.env.mobile || navigator.userAgent.indexOf("iPad")!=-1 || navigator.userAgent.indexOf("Android")!=-1) webix.env.touch = true; if (navigator.userAgent.indexOf('Opera')!=-1) webix.env.isOpera=true; else{ //very rough detection, but it is enough for current goals webix.env.isIE=!!document.all || (navigator.userAgent.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=(navigator.userAgent.indexOf("Edge")!=-1); webix.env.isFF=(navigator.userAgent.indexOf("Firefox")!=-1); webix.env.isWebKit=(navigator.userAgent.indexOf("KHTML")!=-1); webix.env.isSafari=webix.env.isWebKit && (navigator.userAgent.indexOf('Mac')!=-1); } if(navigator.userAgent.toLowerCase().indexOf("android")!=-1){ webix.env.isAndroid = true; if(navigator.userAgent.toLowerCase().indexOf("trident")){ 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(navigator.userAgent) !== 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){ var d = webix.html.create("DIV",{"class":"webix_view webix_measure_size "+(css||"")},""); d.style.cssText = "width:1px; height:1px; visibility:hidden; position:absolute; top:0px; left:0px; overflow:hidden; 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.scrollWidth); height = Math.max(height, d.scrollHeight); } 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 if (webix.env.isSafari){ var reader = new FileReader(); reader.onloadend = function() { var base64Data = reader.result; webix.html.download("data:attachment/file" + base64Data.slice(base64Data.search(/[,;]/)), filename); }; reader.readAsDataURL(data); return; } 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 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* jshint ignore:start */ (function (self) { var now = typeof setImmediate !== 'undefined' ? setImmediate : function(cb) { setTimeout(cb, 0) } /** * @constructor */ function promise(fn, er) { var self = this self.promise = self self.state = 'pending' self.val = null self.fn = fn || null self.er = er || null self.next = []; } promise.prototype.resolve = function (v) { var self = this if (self.state === 'pending') { self.val = v self.state = 'resolving' now(function () { self.fire() }) } } promise.prototype.reject = function (v) { var self = this if (self.state === 'pending') { self.val = v self.state = 'rejecting' now(function () { self.fire() }) } } promise.prototype.then = function (fn, er) { var self = this var p = new promise(fn, er) self.next.push(p) if (self.state === 'resolved') { p.resolve(self.val) } if (self.state === 'rejected') { p.reject(self.val) } return p } promise.prototype.fail = function (er) { return this.then(null, er) } promise.prototype.finish = function (type) { var self = this self.state = type if (self.state === 'resolved') { for (var i = 0; i < self.next.length; i++) self.next[i].resolve(self.val); } if (self.state === 'rejected') { for (var i = 0; i < self.next.length; i++) self.next[i].reject(self.val); if (webix.assert && !self.next.length) throw(self.val); } } // ref : reference to 'then' function // cb, ec, cn : successCallback, failureCallback, notThennableCallback promise.prototype.thennable = function (ref, cb, ec, cn, val) { var self = this val = val || self.val if (typeof val === 'object' && typeof ref === 'function') { try { // cnt protects against abuse calls from spec checker var cnt = 0 ref.call(val, function(v) { if (cnt++ !== 0) return cb(v) }, function (v) { if (cnt++ !== 0) return ec(v) }) } catch (e) { ec(e) } } else { cn(val) } } promise.prototype.fire = function () { var self = this // check if it's a thenable var ref; try { ref = self.val && self.val.then } catch (e) { self.val = e self.state = 'rejecting' return self.fire() } self.thennable(ref, function (v) { self.val = v self.state = 'resolving' self.fire() }, function (v) { self.val = v self.state = 'rejecting' self.fire() }, function (v) { self.val = v if (self.state === 'resolving' && typeof self.fn === 'function') { try { self.val = self.fn.call(undefined, self.val) } catch (e) { self.val = e return self.finish('rejected') } } if (self.state === 'rejecting' && typeof self.er === 'function') { try { self.val = self.er.call(undefined, self.val) self.state = 'resolving' } catch (e) { self.val = e return self.finish('rejected') } } if (self.val === self) { self.val = TypeError() return self.finish('rejected') } self.thennable(ref, function (v) { self.val = v self.finish('resolved') }, function (v) { self.val = v self.finish('rejected') }, function (v) { self.val = v self.state === 'resolving' ? self.finish('resolved') : self.finish('rejected') }) }) } promise.prototype.done = function () { if (this.state = 'rejected' && !this.next) { throw this.val } return null } promise.prototype.nodeify = function (cb) { if (typeof cb === 'function') return this.then(function (val) { try { cb(null, val) } catch (e) { setImmediate(function () { throw e }) } return val }, function (val) { try { cb(val) } catch (e) { setImmediate(function () { throw e }) } return val }) return this } promise.prototype.spread = function (fn, er) { return this.all().then(function (list) { return typeof fn === 'function' && fn.apply(null, list) }, er) } promise.prototype.all = function() { var self = this return this.then(function(list){ var p = new promise() if(!(list instanceof Array)) { p.reject(TypeError) return p } var cnt = 0 var target = list.length function done() { if (++cnt === target) p.resolve(list) } for(var i=0, l=list.length; i<l; i++) { var value = list[i] var ref; try { ref = value && value.then } catch (e) { p.reject(e) break } (function(i){ self.thennable(ref, function(val){ list[i] = val done() }, function(val){ p.reject(val); }, function(){ done() }, value) })(i) } return p }) } // self object gets globalalized/exported var promiz = { all:function(list){ var p = new promise(null, null); p.resolve(list); return p.all(); }, // promise factory defer: function () { return new promise(null, null) }, // calls a function and resolved as a promise fcall: function() { var def = new promise() var args = Array.apply([], arguments) var fn = args.shift() try { var val = fn.apply(null, args) def.resolve(val) } catch(e) { def.reject(e) } return def }, // calls a node-style function (eg. expects callback as function(err, callback)) nfcall: function() { var def = new promise() var args = Array.apply([], arguments) var fn = args.shift() try { // Add our custom promise callback to the end of the arguments args.push(function(err, val){ if(err) { return def.reject(err) } return def.resolve(val) }) fn.apply(null, args) } catch (e) { def.reject(e) } return def } } self.promise = promiz })(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, //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, 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, //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, 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, //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, 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, //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, 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, //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, 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, //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, 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, //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, 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, //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, 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, //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, 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, //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, 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, //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, 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, //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, 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, //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, 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(){ //register self in global list of destructors webix.destructors.push(this); }, //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",[]); this.destructor=function(){}; //destructor can be called only once //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++) webix.destructors[i].destructor(); webix.destructors = []; webix.ui._popups = webix.toArray(); //detach all known DOM events for (var a in webix._events){ var ev = webix._events[a]; if (ev[0].removeEventListener) ev[0].removeEventListener(ev[1],ev[2],false); else if (ev[0].detachEvent) ev[0].detachEvent("on"+ev[1],ev[2]); delete webix._events[a]; } }); /* Behavior:Settings @export customize config */ /* Template - handles html templates */ (function(){ var _cache = {}; var _csp_cache = {}; var newlines = new RegExp("(\\r\\n|\\n)","g"); var quotes = new RegExp("(\\\")","g"); var slashes = new RegExp("(\\\\)","g"); var escape = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'", "`": "`" }; var badChars = /[&<>"'`]/g; var escapeChar = function(chr) { return escape[chr] || "&"; }; webix.template = function(str){ if (typeof str == "function") return str; if (_cache[str]) return _cache[str]; str=(str||"").toString(); if (str.indexOf("->")!=-1){ var teststr = str.split("->"); switch(teststr[0]){ case "html": //load from some container on the page str = webix.html.getValue(teststr[1]); break; case "http": //load from external file str = new webix.ajax().sync().get(teststr[1],{uid:webix.uid()}).responseText; break; default: //do nothing, will use template as is break; } } //supported idioms // {obj.attr} => named attribute or value of sub-tag in case of xml str=(str||"").toString(); // Content Security Policy enabled if(webix.env.strict){ if (!_csp_cache[str]){ _csp_cache[str] = []; // get an array of objects (not sorted by position) var temp_res = []; str.replace(/\{obj\.([^}?]+)\?([^:]*):([^}]*)\}/g,function(search,s1,s2,s3,pos){ temp_res.push({pos: pos, str: search, fn: function(obj,common){ return obj[s1]?s2:s3; }}); }); str.replace(/\{common\.([^}\(]*)\}/g,function(search,s,pos){ temp_res.push({pos: pos, str: search, fn: function(obj,common){ return common[s]||''; }}); }); str.replace(/\{common\.([^\}\(]*)\(\)\}/g,function(search,s,pos){ temp_res.push({pos: pos, str: search, fn: function(obj,common){ return (common[s]?common[s].apply(this, arguments):""); }}); }); str.replace(/\{obj\.([^:}]*)\}/g,function(search,s,pos){ temp_res.push({pos: pos, str: search, fn: function(obj,common){ return obj[s]; }}); }); str.replace("{obj}",function(search,s,pos){ temp_res.push({pos: pos, str: search, fn: function(obj,common){ return obj; }}); }); str.replace(/#([^#'";, ]+)#/gi,function(search,s,pos){ if(s.charAt(0)=="!"){ temp_res.push({pos: pos, str: search, fn: function(obj,common){ s = s.substr(1); if(s.indexOf(".")!= -1) obj = webix.CodeParser.collapseNames(obj); // apply complex properties return webix.template.escape(obj[s.substr(1)]); }}); } 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, 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){ 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; }, /* 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"){ 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(){ try{ return JSON.parse(x.responseText); } catch(e){ webix.log(x.responseText); webix.log(e.toString()); webix.assert_error("Invalid json data for parsing"); } } }; }, //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; this.callEvent("onBeforeLoad",[]); 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); this.callEvent("onBeforeLoad",[]); 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); this.callEvent("onBeforeLoad",[]); 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{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.assert_error(e); 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(){ if (this.validate && !this.validate()) return false; webix.$$(this._bind_source).setBindData((this.getValue?this.getValue:this.getValues()),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) document.body.addEventListener("focus", webix.bind(this._focus_tab, this), true); webix.destructors.push(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; 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; if (view == this._view) return; if (!dont_clear) this._focus_was_there = null; if (view){ view = webix.$$(view); if (this.canFocus(view)) 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.setFocus(this.getPrev(check_view)); else 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; } }, 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", function(view, e){ if (view && view._custom_tab_handler && !view._custom_tab_handler(false, e)) return false; if (view && view._in_edit_mode){ if (view.editNext) return view.editNext(false); else if (view.editStop){ view.editStop(); return true; } } else webix.delay(function(){ webix.UIManager.setFocus(webix.$$(document.activeElement), true); },1); }); webix.UIManager.addHotKey("tab", function(view, e){ if (view && view._custom_tab_handler && !view._custom_tab_handler(true, e)) return false; if (view && view._in_edit_mode){ if (view.editNext) return view.editNext(true); else if (view.editStop){ view.editStop(); return true; } } else webix.delay(function(){ webix.UIManager.setFocus(webix.$$(document.activeElement), true); },1); }); }); 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); 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(){ // check for virtual keyboard if(webix.env.touch && ( webix.edit_open_time && (new Date())-webix.edit_open_time < 500 || webix._focus_time && (new Date())-webix._focus_time < 500)) return; webix.UIManager.applyChanges(); webix.callEvent("onClick",[]); if (!webix.ui.$freeze) for (var i=resize.length - 1; i>=0; i--){ //remove destroyed views from resize list if (resize[i].$destructed) resize.splice(i,1); else resize[i].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", 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){ 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 []; }, 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, s.width || s.maxWidth || 100000, s.height || s.minHeight || 0, s.height || s.maxHeight || 100000, s.gravity ]; 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(); if (force && show) parent.show(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" || role == "heading") && !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.offsetWidth||0; var y = this._viewobj.parentNode.offsetHeight||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); 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; 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"; }, 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; }, $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 = x; dy = y; } dx = this._desired_size[0] || dx; dy = this._desired_size[2] || dy; 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; 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 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 (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 while (trg && trg.parentNode && 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(); 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()) 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.body,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.body,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); webix.event( this._modal_cover, "click", webix.bind(this._ignore_clicks, this)); } } else { if (this._modal_cover){ webix.html.remove(this._modal_cover); //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(!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 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; //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 < 0){ fin_x = 0; //popup exceed the right border of screen } else if (x-pos.x > size[0]){ fin_x = pos.x; //aligned } else{ fin_x = x-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 if (point_x > fin_x + size[0]) point_x = fin_x + size[0]/2; } //if height is not fixed - use default position var 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); this.setPosition(fin_x+deltax, fin_y+deltay); if (this._set_point){ if (point) this._set_point(point,point_x+deltax, point_y+deltay); 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); }, _setPosition:function(x,y){ 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); 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; } 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 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); }, 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(){}, defaults:{ padding:8 }, head_setter:function(){ }, _set_point:function(mode, left, top){ 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.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 && master.options_setter){ text = this.getItemText(data.id); if (webix.isUndefined(node.value)) node.innerHTML = text; else node.value = text.replace(/<[^>]*>/g,""); } else if(node && data.value && master.$setValue){ master.$setValue(data.value); } } } 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; }, 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("onSelect", 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()) this._suggestions(e); },{bind:this, id: "webix_suggest_keydown_"+node.webix_master_id}); 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(); if( list.select && list.showItem ){ var value = this.getMasterValue(); if (value && list.exists && list.exists(value)){ list.select(value); list.showItem(value); } else{ list.unselect(); list.showItem(list.getFirstId()); } } }, $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); if(list.count){ var selected = list.getSelectedId(); if (!selected && list.count) list.select(list.getFirstId()); } if (visible) list.moveSelection("down"); }// other arrows else { var master = this._settings.master; 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); } 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; }, 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' style='background-image:url(#image#);'><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){ var oldvalue = this._settings.value; if (oldvalue == value) return false; value = this._pattern(value, false); this._settings.value = value; if (this._rendered_input) this.$setValue(value); this.callEvent("onChange", [value, oldvalue]); }, _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; //-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_inp_combo:function(e, obj, node){ node.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>":""); }, _get_event_id: function(evName){ return "webix_"+evName+"_"+this._settings.id; } }, 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(){} }, 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){ //define event id to prevent memory leak var evId = "webix_onchange_"+this._settings.id; webix.event(this.getInputNode(),"change",this._applyChanges,{bind:this, id: evId}); 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; }, $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); }, $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 && !this.getFormView()._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; //textarea if (!config.height) 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)- 4 - (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; var text = (obj.text||obj.value||this._get_div_placeholder(obj)); var html = "<div class='webix_inp_static' role='combobox' aria-label='"+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._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 { html += this._baseInputHTML("input")+"id='" + id + "' type='"+(config.type||this.name)+"'"+(config.editable?" role='combobox'":"")+" value='" + webix.template.escape(config.text || this._pattern(config.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){ 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='"+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+"' >"; 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"); } }, 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); 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.callEvent("onOptionRemove", [id, this._settings.value]); this.refresh(); }, _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", $skin:function(){ this.defaults.inputPadding = webix.skin.$active.inputPadding; }, on_click:{ "webix_input_icon":function(e){ return this.callEvent("onSearchIconClick", [e]); } }, 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; input.setAttribute("aria-pressed", value?"true":false); input.lastChild.nodeValue = " "+((value ? obj.onLabel : obj.offLabel) || obj.label); //icon or image button if(input.firstChild.nodeName ==="SPAN" && obj.onIcon && obj.offIcon && obj.onIcon !==obj.offIcon) input.firstChild.className = input.firstChild.className.replace((value?obj.offIcon:obj.onIcon), (value?obj.onIcon:obj.offIcon)); var parent = input.parentNode; if(value) 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 css = obj.value ? " webix_pressed" : ""; obj.label = (obj.value ? obj.onLabel : obj.offLabel) || obj.label; obj.icon = (obj.value ? 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='"+(obj.value?"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", $skin:function(){}, 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||"")+"</textarea>"; return common.$renderInput(obj, html, id); }, height:0, minHeight:60 }, _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:( (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='"+config.label+"' aria-valuemin='"+config.min+"' aria-valuemax='"+config.max+"' aria-valuenow='"+config.value+"' class='webix_el_group' style='width:"+common._get_input_width(config)+"px'>"; html += "<button type='button' class='webix_inp_counter_prev' tabindex='-1' aria-label='"+webix.i18n.aria.decreaseValue+"'>-</button>"; html += common._baseInputHTML("input")+" id='"+id+"' type='text' class='webix_inp_counter_value' aria-live='assertive'"+" value='"+value+"'></input>"; html += "<button type='button' class='webix_inp_counter_next' tabindex='-1' aria-label='"+webix.i18n.aria.increaseValue+"'>+</button></div>"; return common.$renderInput(config, html, id); }, min:0, max:Infinity, step:1 }, $init:function(){ webix.event(this.$view, "keydown", this._keyshift, {bind:this}); }, _keyshift:function(e){ var code = e.which || e.keyCode, c = this._settings, value = c.value || c.min; if(code>32 && code <41){ if(code === 35) value = c.min; else if(code === 36) value = c.max === Infinity? 1000000 :c.max; else if(code === 33) this.next(); else if(code === 34) this.prev(); else value = value+(code === 37 || code ===40?-1:1); if(code>34 && value>=c.min && value <=c.max) this.setValue(value); } }, $setValue:function(value){ this.getInputNode().value = value; }, getInputNode:function(){ return this._dataobj.getElementsByTagName("input")[0]; }, getValue:function(obj){ return webix.ui.button.prototype.getValue.apply(this,arguments)*1; }, next:function(step){ step = this._settings.step; this.shift(step); }, prev:function(step){ step = (-1)*this._settings.step; this.shift(step); }, shift:function(step){ var min = this._settings.min; var max = this._settings.max; var new_value = this.getValue() + step; if (new_value >= min && new_value <= max) this.setValue(new_value); } }, webix.ui.text); webix.protoUI({ name:"checkbox", defaults:{ checkValue:1, uncheckValue:0, template:function(config, common) { var id = "x"+webix.uid(); var rightlabel = ""; if (config.labelRight){ rightlabel = "<label class='webix_label_right'>"+config.labelRight+"</label>"; //user clearly attempts to hide the label, help him if (config.labelWidth) config.label = config.label || " "; } var checked = (config.checkValue == config.value); var margin = Math.floor((common._settings.aheight-16)/2); var ch = common._baseInputHTML("input")+"style='margin-top:"+margin+"px;"+(config.customCheckbox?"display:none":"")+"' id='"+id+"' type='checkbox' "+(checked?"checked='1'":"")+(config.labelRight?" aria-label='"+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"+(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.$view.getElementsByTagName(this._settings.customCheckbox?"button":"input")[0]; if(input) input.focus(); }, blur: function(){ var input = this.$view.getElementsByTagName(this._settings.customCheckbox?"button":"input")[0]; if(input) input.blur(); }, _init_onchange: function(){}, $setValue:function(value){ var isChecked = (value == this._settings.checkValue); var parentNode = this.getInputNode()?this.getInputNode().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)); } this.getInputNode().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; }, $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"+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+"' 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 = 0; 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); } 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" }, 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; if(!webix.isUndefined(value) && !this.getPopup().isVisible() && !this._settings.text) 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):""; return typeof node.value == "undefined" ? (this.getValue()?node.innerHTML:"") : 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){ 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", $init:function(){ this.attachEvent("onBlur", webix.bind(function(e){ if (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._settings.editable){ value = this.getValue(); this.$setValue(webix.isUndefined(value)?"":value); } },this)); }, getInputNode:function(){ return this._dataobj.getElementsByTagName('input')[0]; }, $render:function(obj){ if (webix.isUndefined(obj.value)) return; this.$setValue(obj.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" }, $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; this.$setValue(obj.value); }, _parse_value: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_date: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 || ""; } }, $setValue:function(value){ var popup = webix.$$(this._settings.popup.toString()); var calendar = popup._body_cell; //convert string or array to date value = this._parse_value(value); //select date in calendar-popup calendar.selectDate(value,true); //save value this._settings.value = (value)?calendar.config.date:""; //set visible date this._settings.text = (value?this._get_visible_date(this._settings.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._parse_value(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; this.$setValue(obj.value); }, getValue:function(){ if (this._rendered_input && this._settings.editable) return this.getInputNode().value; else return this._settings.value; }, _getColorNode: function(){ return this.$view.getElementsByTagName("DIV")[this._settings.editable?1:2]; }, $setValue:function(value){ var popup = webix.$$(this.config.popup.toString()); var colorboard = popup.getBody(); colorboard.setValue(value); this.config.value = value; this._getColorNode().style.backgroundColor = 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); }, 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; if (all) for (var key in obj){ if(hidden[key]) continue; var subresult = this._validate(all, obj[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], obj[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 (/^[^@]+@[^@]+\.[^@]+$/).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(); 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 ""; }; } } }; /* 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.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.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, 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) params.push("filter["+key+"]="+encodeURIComponent(state.filter[key])); } url += params.join("&"); this.load(url, final_callback); } }, _feed_callback:function(){ //after loading check if we have some ignored requests var temp = this._load_count; var last = this._feed_last; this._load_count = false; if (typeof temp =="object" && (temp[0]!=last[0] || temp[1]!=last[1])) this.data.feed.apply(this, temp); //load last ignored request }, //loads data from external URL load:function(url,call){ var 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){ if (last[0]<=from && (last[1]+last[0] >= 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(){ 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(); } 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){ var data = this.getItem(id); 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){ webix.extend(data, update, true); data.id = id; } if (this._scheme_update) this._scheme_update(data); this.callEvent("onStoreUpdated",[id, data, "update"]); this.callEvent("onDataUpdate", [id, data]); }, //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)); 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") 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){ 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; this.importData(source, true); if (filter) this.silent(filter); if (this._on_sync) this._on_sync(); 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(){ //instead of deleting one by one - just reset inner collections this.pull = {}; this._marks = {}; this.order = webix.toArray(); //this.feed = null; this._filter_order = this.url = null; this.callEvent("onClearAll",[]); 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); } }, _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 = x + "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; 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(); } }, _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; if (this.$master._body){ //datatable 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); //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 }, base); //run animation line = webix.animate.formLine(snode, onode, aniset); webix.animate([ snode, onode ], aniset ); this._pgInAnimation = true; } }, webix.MouseEvents, webix.SingleRender, webix.ui.view, webix.EventSystem); webix.locale.pager = { first: " << ", last: " >> ", next: " > ", prev: " < " }; webix.PagingAbility = { pager_setter:function(pager){ if (typeof pager == "string"){ var ui_pager = webix.$$(pager); if (!ui_pager){ this.$blockRender = true; webix.delay(function(){ var obj = webix.$$(pager); this._settings.pager = this.pager_setter(obj); var s = obj._settings; s.count = this.data._count_pager_total(s.level); obj.refresh(); this.$blockRender = false; this.render(); }, this); return null; } pager = ui_pager; } function check_pager_sizes(repeat){ if (pager.config.autosize && this.getVisibleCount){ var count = this.getVisibleCount(); if (isNaN(count)){ pager.config.size = 1; webix.delay(check_pager_sizes, this, [true]); } else if (count != pager.config.size){ pager.config.size = count; pager.refresh(); if (repeat === true) this.refresh(); } } var s = this._settings.pager; //initial value of pager = -1, waiting for real value if (s.page == -1) return false; this.data.$min = this._count_pager_index(0, s.page*s.size); //affect data.getRange this.data.$max = this._count_pager_index(this.data.$min, s.size); this.data.$pagesize = this.data.$max - this.data.$min; return true; } this.attachEvent("onBeforeRender",check_pager_sizes); if (!pager.$view){ pager.view = "pager"; pager = webix.ui(pager); } this._pager = pager; pager.$master = this; this.data.attachEvent("onStoreUpdated", function(){ var s = pager._settings; s.count = this._count_pager_total(s.level); pager.refresh(); }); this.data._count_pager_total = this._count_pager_total; return pager._settings; }, _count_pager_total:function(level){ if (level && level !== 0){ var count = 0; this.each(function(obj){ if (obj.$level == level) count++; }); return count; } else return this.count(); }, _count_pager_index:function(start, count){ var s = this._settings.pager; if (s.level && s.level !== 0){ var end = start; var max = this.data.order.length; if (count) while (end < max){ if (this.data.getItem(this.data.order[end]).$level == s.level){ if (count === 0) break; else count--; } end++; } return end; } else return start+count; }, setPage:function(value){ if (this._pager) this._pager.select(value); }, getPage:function(){ return this._pager._settings.page; }, getPager:function(){ return this._pager; } }; /* Behavior: AutoTooltip - links tooltip to data driven item */ /* UI: Tooltip @export show hide */ webix.protoUI({ name:"tooltip", defaults:{ dy:0, dx:20 }, $init:function(container){ if (typeof container == "string"){ container = { template:container }; } this.type = webix.extend({}, this.type); //create container for future tooltip this._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); webix.attachEvent("onClick", webix.bind(function(e){ if (this._visible && webix.$$(e) != this) this.hide(); }, this)); }, 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 this._contentobj.style.top = pos.y+this._settings.dy+"px"; this._contentobj.style.left = pos.x+this._settings.dx+"px"; this._contentobj.style.display="block"; } 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),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; }); this.setDirty(false); }, _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; this.callEvent("onBeforeLoad",[]); 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; this._probably_render_me(); var padding = webix.skin.$active.layoutPadding.space; 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){ this.getIframe().src = value; this.callEvent("onBeforeLoad",[]); 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("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 for (var i = this._selected.length - 1; i >= 0; i--) if (!this.exists(this._selected[i])) this._selected.splice(i,1); } 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); }, //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 = this.data.url || 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; }, changeId:function(old, newid){ if (this.branch[old]){ var branch = this.branch[newid] = this.branch[old]; for (var i = 0; i < branch.length; i++) this.getItem(branch[i]).$parent = newid; delete this.branch[old]; } var parent = this.getItem(old).$parent; if (parent !== "0"){ var index = webix.PowerArray.find.call(this.branch[parent], old); this.branch[parent][index] = newid; } return webix.DataStore.prototype.changeId.call(this, old, newid); }, clearAll:function(){ this.branch = { 0:[] }; webix.DataStore.prototype.clearAll.call(this); }, 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; obj.$parent = 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++; } 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); 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); 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); return id?webix.html.offset(this.getItemNode(id)):null; } }, //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){ return data.length; }, 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); } 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}; 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(this); // creates new textarea this._area = document.createElement('textarea'); this._area.className = "webix_clipbuffer"; this._area.setAttribute("webixignore", 1); 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(); } }); 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(){ if(!document.activeElement || !this.$view.contains(document.activeElement)){ 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", 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"); } }); this.attachEvent("onAfterSelect", webix.once(function(){ var node = this._dataobj.querySelector("["+this._id+"]"); if(node) node.setAttribute("tabindex", "-1"); })); } }, _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, e.shiftKey); } return true; }; }, moveSelection:function(mode, shift){ 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){ 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)); 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("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("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")); webix.UIManager.addHotKey("right", this._navigation_helper("right")); webix.UIManager.addHotKey("left", this._navigation_helper("left")); } 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); } }, //css class to action map, for onclick event on_click:{ webix_tree_item:function(e,id){ 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 (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); } } }; webix.protoUI({ name:"list", _listClassName : "webix_list", $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.type.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; return Math.max(this.type.height * count,this._settings.minHeight||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.type.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.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(); // adding undo support 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); 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(); //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) 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) this.updateItem(this._set_new_value(editor, state.value)); } 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){ this.getItem(editor.id)[this._settings.editValue||"value"] = new_value; return editor.id; } }; (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){ return config.header && config.header[0]?config.header[0].text:config.editValue || config.label; } /* 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.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(popup); } else pobj = webix.$$(popup); if (pobj.linkInput) pobj.linkInput(document.body); else if(this.linkInput) this.linkInput(document.body); return pobj; } var type = webix.editors.$popup[this.popupType]; if (typeof type != "string"){ 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(code === 40){ if(list.moveSelection) list.moveSelection("down"); webix.UIManager.setFocus(list); } else if(code === 13) 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("onSelect", 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.getValue(); 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._initial_text = this.getInputNode(this.node).value = sobj.getItemText(value); } }, getValue:function(){ var value = this.getInputNode().value; if (this.config.suggest){ if (value == this._initial_text) return this._initial_value; return webix.$$(this.config.suggest).getSuggestion(); } else 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){ 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", 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.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; }, _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 == "%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 "%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]; 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 == "%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])); return new Date(set[0],set[1],set[2],set[3],set[4],set[5]); }; } 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 "%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]= t[2]||0;}"; break; default: break; } } var code ="set[0],set[1],set[2],set[3],set[4],set[5]"; 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]; "+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:{ increaseValue:"Increase value", decreaseValue:"Decrease value", navMonth:["Previous month", "Next month"], navYear:["Previous year", "Next year"], navDecade:["Previous decade", "Next decade"], 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" } }; webix.i18n.setLocale("en-US"); 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"); } }); // edit, add, remove this.attachEvent("onBeforeEditStop", function(state, editor){ this._editedItem = webix.copy(this.getItem(editor.id||editor.row)); }); 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("onStoreUpdated", function(id, item, mode){ var data = null; if(id){ if(mode == "add"){ data = webix.copy(item); } else if( mode == "delete"){ data = view._deletedItem; } else if(mode == "update"){ // update can also be called due to custom updateItem call if(view._editedItem && view._editedItem.id == id){ data = view._editedItem; view._editedItem = null; } } 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); }*/ } }; 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; item[id.column] = value; this.callEvent("onCheck", [id.row, id.column, value]); this.data.callEvent("onDataUpdate", [id, item]); this.data.callEvent("onStoreUpdated", [id.row, item, (this._settings.checkboxRefresh?"update":"save")]); 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._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.attachEvent("onDestruct", this._clean_config_struct); this.attachEvent("onKeyPress", this._onKeyPress); 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); }, _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; } 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>"; for (var i = start; i < end; i++) html += "<th style='width:"+this._columns[i].width+"px'></th>"; html += "</tr>"; } var count = this._columns[0][name].length; var block_evs = []; for (var j = 0; j < count; j++){ html += "<tr section='"+name+"' role='row'>"; for (var i = start; i < end; i++){ var header = this._columns[i][name][j]; if (header === null) continue; if (header.content){ header.contentId = header.contentId||webix.uid(); header.columnId = this._columns[i].id; header.format = this._columns[i].format; webix.assert(webix.ui.datafilter, "Filtering extension was not included"); webix.assert(webix.ui.datafilter[header.content], "Unknown content type: "+header.content); header.text = webix.ui.datafilter[header.content].render(this, header); this._active_headers[header.contentId] = header; this._has_active_headers = true; } html += "<td role='presentation' column='"+(header.colspan?(header.colspan-1+i):i)+"'"; var hcss = ''; if (i==start) hcss+="webix_first"; var column_pos = i + (header.colspan?header.colspan-1:0); if (column_pos>=end-1) hcss+=" webix_last"; if (hcss) html+=' class="'+hcss+'"'; var cell_height = heights[j]; var sheight=""; if (header.contentId) html+=" active_id='"+header.contentId+"'"; if (header.colspan) html+=" colspan='"+header.colspan+"'"; if (header.rowspan){ html+=" rowspan='"+header.rowspan+"'"; cell_height = this._summ_next(this._headers, j, header.rowspan); } if (cell_height != this._settings.headerRowHeight) sheight =" style='line-height:"+cell_height+"px; height:"+cell_height+"px;'"; var css ="webix_hcell"; var header_css = header.css; if (header_css){ if (typeof header_css == "object") header.css = header_css = webix.html.createCss(header_css); css+=" "+header_css; } if (this._columns[i].$selected) css += " webix_sel_hcell"; html+="><div role='columnheader' class='"+css+"'"+sheight+">"; var text = (header.text===""?" ":header.text); if (header.rotate) text = "<div class='webix_rotate' style='width:"+(cell_height-10)+"px; transform-origin:center "+(cell_height-15)/2+"px;-webkit-transform-origin:center "+(cell_height-15)/2+"px;'>"+text+"</div>"; html += text + "</div></td>"; } html += "</tr>"; } html+="</tr></table>"; return html; }, showItemByIndex:function(row_ind, column_ind){ var pager = this._settings.pager; if (pager){ var target = Math.floor(row_ind/pager.size); if (target != pager.page) webix.$$(pager.id).select(target); } //parameter will be set to -1, to mark that scroll need not to be adjusted if (row_ind != -1){ var state = this._get_y_range(); if (row_ind < state[0]+1 || row_ind >= state[1]-1 ){ //not visible currently var summ = this._getHeightByIndexSumm((pager?this.data.$min:0),row_ind); if (row_ind < state[0]+1){ //scroll top - show row at top of screen summ = Math.max(0, summ-1) - this._top_split_height; } else { //scroll bottom - show row at bottom of screen summ += this._getHeightByIndex(row_ind) - this._dtable_offset_height; //because of row rounding we neet to scroll some extra //TODO: create a better heuristic if (row_ind>0) summ += this._getHeightByIndex(row_ind-1)-1; } this._y_scroll.scrollTo(summ); } } if (column_ind != -1){ //ignore split columns - they are always visible if (column_ind < this._settings.leftSplit) return; if (column_ind >= this._rightSplit) return; //very similar to y-logic above var state = this._get_x_range(); if (column_ind < state[0]+1 || column_ind >= state[1]-1 ){ //not visible currently var summ = 0; for (var i=this._settings.leftSplit; i<column_ind; i++) summ += this._columns[i].width; /*jsl:ignore*/ if (column_ind < state[0]+1){ //scroll to left border } else { //scroll to right border summ += this._columns[column_ind].width - this._center_width; } /*jsl:end*/ this._x_scroll.scrollTo(summ); } } }, showCell:function(row, column){ if (!column || !row){ //if column or row not provided - take from current selection var t=this.getSelectedId(true); if (t.length == 1){ column = column || t[0].column; row = row || t[0].row; } } //convert id to index column = column?this.getColumnIndex(column):-1; row = row?this.getIndexById(row):-1; this.showItemByIndex(row, column); }, scrollTo:function(x,y){ if (!this._x_scroll) return; if (this._scrollTo_touch) return this._scrollTo_touch(x,y); if (x !== null) this._x_scroll.scrollTo(x); if (y !== null) this._y_scroll.scrollTo(y); }, getScrollState:function(){ if (this._getScrollState_touch) return this._getScrollState_touch(); var diff = this._render_scroll_shift?0:(this._render_scroll_diff||0); return {x:(this._scrollLeft||0), y:(this._scrollTop + diff)}; }, showItem:function(id){ this.showItemByIndex(this.getIndexById(id), -1); }, _render_header_section:function(sec, name, heights){ sec.childNodes[0].innerHTML = this._render_subheader(0, this._settings.leftSplit, this._left_width, name, heights); sec.childNodes[1].innerHTML = this._render_subheader(this._settings.leftSplit, this._rightSplit, this._dtable_width, name, heights); sec.childNodes[1].onscroll = webix.bind(this._scroll_with_header, this); sec.childNodes[2].innerHTML = this._render_subheader(this._rightSplit, this._columns.length, this._right_width, name, heights); }, _scroll_with_header:function(){ var active = this.getScrollState().x; var header = this._header.childNodes[1].scrollLeft; if (header != active) this.scrollTo(header, null); }, _refresh_tracking_header_content:function(){ this.refreshHeaderContent(true, true); }, _refresh_any_header_content:function(){ this.refreshHeaderContent(false, true); }, //[DEPRECATE] - v3.0, move to private refreshHeaderContent:function(trackedOnly, preserve, id){ if (this._settings.header){ if (preserve) this._refreshHeaderContent(this._header, trackedOnly, 1, id); this._refreshHeaderContent(this._header, trackedOnly, 0, id); } if (this._settings.footer){ if (preserve) this._refreshHeaderContent(this._footer, trackedOnly, 1, id); this._refreshHeaderContent(this._footer, trackedOnly, 0, id); } }, refreshFilter:function(id){ if (id && !this._active_headers[id]) return; this.refreshHeaderContent(false, true, id); }, _refreshHeaderContent:function(sec, cellTrackOnly, getOnly, byId){ if (this._has_active_headers && sec){ var alltd = sec.getElementsByTagName("TD"); for (var i = 0; i < alltd.length; i++){ if (alltd[i].getAttribute("active_id")){ var obj = this._active_headers[alltd[i].getAttribute("active_id")]; if (byId && byId != obj.columnId) continue; var content = webix.ui.datafilter[obj.content]; if (getOnly){ if (content.getValue) obj.value = content.getValue(alltd[i]); } else if (!cellTrackOnly || content.trackCells){ content.refresh(this, alltd[i], obj); } } } } }, headerContent:[], _set_size_scroll_area:function(obj, height, hdx){ if (this._scrollSizeY){ obj.style.height = Math.max(height,1)-1+"px"; obj.style.width = (this._rightSplit?0:hdx)+this._scrollSizeY-1+"px"; // temp. fix: Chrome [DIRTY] if (webix.env.isWebKit) var w = obj.offsetWidth; } else obj.style.display = "none"; }, _size_header_footer_fix:function(){ if (this._settings.header) this._set_size_scroll_area(this._header_scroll, this._header_height, this._header_fix_width); if (this._settings.footer) this._set_size_scroll_area(this._footer_scroll, this._footer_height, this._header_fix_width); }, _update_scroll:function(x,y){ var hasX = !(this._settings.autowidth || this._settings.scrollX === false); this._scrollSizeX = hasX ? webix.ui.scrollSize : 0; var hasY = !(this._settings.autoheight || this._settings.scrollY === false); this._scrollSizeY = hasY ? webix.ui.scrollSize : 0; if(webix.env.touch) hasX = hasY = false; if (this._x_scroll){ this._x_scroll._settings.scrollSize = this._scrollSizeX; this._x_scroll._settings.scrollVisible = hasX; } if (this._y_scroll){ this._y_scroll._settings.scrollSize = this._scrollSizeY; this._y_scroll._settings.scrollVisible = hasY; } }, _create_scrolls:function(){ this._scrollTop = 0; this._scrollLeft = 0; var scrx, scry; scrx = scry = 1; if (this._settings.autoheight || this._settings.scrollY === false) scry = this._scrollSizeY = 0; if (this._settings.autowidth || this._settings.scrollX === false) scrx = this._scrollSizeX = 0; if (webix.env.touch) scrx = scry = 0; if (!this._x_scroll){ this._x_scroll = new webix.ui.vscroll({ container:this._footer.previousSibling, scrollWidth:this._dtable_width, scrollSize:this._scrollSizeX, scrollVisible:scrx }); //fix for scroll space on Mac if (scrx && !this._scrollSizeX && !webix.env.$customScroll) this._x_scroll._viewobj.style.position="absolute"; this._x_scroll.attachEvent("onScroll", webix.bind(this._onscroll_x, this)); } if (!this._y_scroll){ this._header_scroll = this._footer.nextSibling; var vscroll_view = this._header_scroll.nextSibling; this._footer_scroll = vscroll_view.nextSibling; this._y_scroll = new webix.ui.vscroll({ container:vscroll_view, scrollHeight:100, scroll:"y", scrollSize:this._scrollSizeY, scrollVisible:scry }); this._y_scroll.activeArea(this._body); this._x_scroll.activeArea(this._body, true); this._y_scroll.attachEvent("onScroll", webix.bind(this._onscroll_y, this)); } if (this._content_width) this.callEvent("onResize",[this._content_width, this._content_height]); if (webix.env.$customScroll) webix.CustomScroll.enable(this); this._create_scrolls = function(){}; }, columnId:function(index){ return this._columns[index].id; }, getColumnIndex:function(id){ for (var i = 0; i < this._columns.length; i++) if (this._columns[i].id == id) return i; return -1; }, _getNodeBox:function(rid, cid){ var xs=0, xe=0, ye=0, ys=0; var i; var zone = 0; for (i = 0; i < this._columns.length; i++){ if (this._rightSplit == i || this._settings.leftSplit == i){ xs=0; zone++; } if (this._columns[i].id == cid) break; xs+=this._columns[i].width; } xe+=this._columns[i].width; for (i = 0; i < this.data.order.length; i++){ if (this.data.order[i] ==rid) break; ys+=this._getHeightByIndex(i); } ye+=this._getHeightByIndex(i); return [xs,xe,ys-this._scrollTop,ye, this._body.childNodes[zone]]; }, _id_to_string:function(){ return this.row; }, locate:function(node, idOnly){ if (this._settings.subview && this != webix.$$(node)) return null; node = node.target||node.srcElement||node; while (node && node.getAttribute){ if (node.getAttribute("view_id")) 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 }; }, _updateColsSizeSettings:function(silent){ if (!this._dtable_fully_ready) return; 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)) 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){ 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); this.callEvent("onScrollY",[]); this.callEvent("onAfterScroll",[]); }, _onscroll_x:function(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); 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){ 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 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++; } var topSplit = this._settings.topSplit; if (topSplit) xind += topSplit; //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; if (xend>end) xend = end; } else while (t>0 && xend<end){ t-=this._getHeightByIndex(xend); xend++; } 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(); //row not visible if (rowindex < state[0] || rowindex >= state[1]) return; //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-state[0]]; var value = this._getValue(item, this._columns[i], 0); node.innerHTML = value; node.className = this._getCss(this._columns[i], value, item, id); } } }, _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=[]; }, _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) 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"); 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"; row.style.top = info.top + (item.$subopen ? height-1 : -1) + "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]; if (yr[2] != col._yr2){ 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){ state.total += state.row; return "<div"+aria+" class='webix_cell'></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 margin = item.$subopen ? "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 (margin) 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._settings.rowHeight; 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){ 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.loadNext(0, 0, { "before":function(){ var url = this.data.url; this.clearAll(); this.data.url = url; } }, 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") 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.firstChild; }, 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; var evId = "webix_keydown_filter_"+master._settings.id+"_"+value.columnId; webix.event(node, "keydown", this._on_key_down, {id: evId}); }, 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(){ webix.$$(id).filterByAll(); },webix.ui.datafilter.textWaitDelay); } }, "selectFilter":{ getInputNode:function(node){ return node.firstChild.firstChild; }, 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; var evId = "webix_change_filter_"+master._settings.id+"_"+value.columnId; webix.event(select, "change", this._on_change, {id: evId}); }, 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 //jshint -W083:true 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 = true; return this._runServerFilter(); } 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; } }, this); if (!server){ this.refresh(); this.callEvent("onAfterFilter",[]); } }, _multi_compare: function(key, compare){ var separator = this.getColumnConfig(key).optionslist; //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); values.sort(function(a,b){ return a.value > b.value ? 1 : -1; }); }, _runServerFilter: function(name){ this.loadNext(0,0,{ before:function(){ var url = this.data.url; if (this.editStop) this.editStop(); this.clearAll(); this.data.url = url; }, 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("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); }, _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_rows.length; i++){ var item = this.getItem(this._selected_rows[i]); if (item) this.data.removeMark(item.id, "webix_selected", 0, 1); } var cols = this._settings.columns; if (cols) for (var i = 0; i < cols.length; i++) { cols[i].$selected = null; } this._reinit_selection(); 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++) this.select(this.getIdByIndex(i), 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(); } }, 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); 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.removeSelectArea(); }, _unselect: function() { this.removeSelectArea(); }, 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) 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; } } } }); 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; if (this._settings.fixedRowHeight){ row = rows[Math.floor(y/this._settings.rowHeight)]; } else for (var i=0; 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"?"e":"n")+"-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; 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 = "n-resize"; } } else if (dy-pos.y<resizeRow+1){ this._rs_ready = ["y", dy, node]; mode = "n-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 = "e-resize"; } else if (dx-pos.x<resizeColumn+1){ this._rs_ready = ["x", dx, node]; mode = "e-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; }, _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 = []; this.mapSelection(function(value, row, col, row_ind, col_ind) { if (!data[row_ind]) data[row_ind] = []; data[row_ind].push(value); 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, window.JSON.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, window.JSON.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(window.JSON.stringify(data)) + (( expires && (expires instanceof Date)) ? ";expires=" + expires.toUTCString() : "" ) + (( domain ) ? ";domain=" + domain : "" ) + (( webix.env.https ) ? ";secure" : ""); } }, _get_cookie: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._get_cookie(name); if(!json) return null; return webix.DataDriver.json.toObject(unescape(json)); }else return null; }, remove:function(name, domain){ if(name && this._get_cookie(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++){ settings.ids.push(columns[i].id); settings.size.push(columns[i].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, !!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++){ if(columns[i] && columns[i].width != obj.size[i]) this._setColumnWidth( i, obj.size[i], true); } } if (obj.filter){ for (var key in this._filter_elements){ var f = this._filter_elements[key]; f[2].setValue(f[0], ""); } } this.unblockEvent(); this._updateColsSizeSettings(true); 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){ 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; } 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"; 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() }; } }; webix.ready(function(){ 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; }"); } }); 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){ mouse.down = "touchstart"; mouse.move = "touchmove"; mouse.up = "touchend"; mouse.context = t._get_context; } })(); webix.attachEvent("onDataTable", function(table, config){ if (webix.env.touch){ webix.Touch.$init(); config.scrollSize = 0; webix.extend(table, (config.prerender===true)?table._touchNative:table._touch); // needed to show datatable scroll if(webix.Touch._disabled) webix.Touch.limit(); table.defaults.scrollAlignY = false; table._body.setAttribute("touch_scroll","xy"); table.$ready.push(function(){ var scrollMode = ""; var config = this._settings; 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(table._body.childNodes[1].firstChild); webix.Touch._set_matrix(table._body.childNodes[1].firstChild, 0,0,"0ms"); table._sync_scroll(0,0,"0ms"); } }); webix.extend(webix.ui.datatable, { _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(){ 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.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; //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(result.e, 0, "0ms"); webix.Touch.config.translate = temp; this._scrollLeft = - result.e; this._scrollTop = -result.f; this.render(); if(isScrollX) this.callEvent("onScrollX",[]); if(isScrollY) this.callEvent("onScrollY",[]); return false; }); }, _sync_scroll:function(x,y,t){ y += this._scrollTop; 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; } } }); 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(){ if (!this.count()) return; var resize = false; var cols = this._columns; for (var i = 0; i < cols.length; i++) if (cols[i].adjust) 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 d = webix.html.create("DIV",{"class":"webix_view webix_table_cell webix_measure_size webix_cell"},""); d.style.cssText = "width:1px; visibility:hidden; position:absolute; top:0px; left:0px; overflow:hidden;"; document.body.appendChild(d); var config = this._settings.columns[ind]; var max = config.minColumnWidth || -Infinity; //iterator other all loaded data is required if (headers != "header") this.data.each(function(obj){ if (obj){ var text = this._getValue(obj, config, 0); d.innerHTML = text; max = Math.max(d.scrollWidth, max); } }, this); if (headers && headers != "data"){ for (var i=0; i<config.header.length; i++){ var header = config.header[i]; if (header){ d.innerHTML = header.text; max = Math.max(d.scrollWidth, max); } } } d = webix.html.remove(d); //1px to compensate scrollWidth 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){ var config = this.getColumnConfig(id); var count = this.data.count(); 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); 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 ((typeof(item[this._math_pref + col]) === 'undefined') || (_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){ var evId = "webix_keyup_edit_"+this._settings.id+"_"+row+"_"+column; webix.event(type.node, "keyup", this._bind_live_validation(id, this),{id: evId}); this.validateEditor(id); } return node; }, _bind_live_validation:function(id, that){ return function(){ that.validateEditor(id); }; }, _set_new_value:function(editor, new_value){ var parser = this.getColumnConfig(editor.column).editParse; var item = this.getItem(editor.row); item[editor.column] = parser?parser(new_value):new_value; if (this._settings.editMath) delete item["$"+editor.column]; return editor.row; }, //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; var selection = this.getSelectedId(true); if (selection.length == 1){ this.editNext(tab, selection[0]); 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(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; this._getInitialSpans(cols); 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){ for(var i = 0; i<cols.length; i++) for(var h = 0; h<cols[i].header.length;h++){ var line = cols[i].header[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, mode, silent){ var cols = this._settings.columns; var horder = this._hidden_column_order; var hhash = this._hidden_column_hash; var column; 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 (index<this._settings.leftSplit) this._settings.leftSplit--; if (index>=this._rightSplit) this._settings.rightSplit--; else this._rightSplit--; this._hideColumn(index); column = hhash[id] = cols.splice(index, 1)[0]; column._yr0 = -1; delete this._columns_pull[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; for (; i<horder.length; i++){ if (horder[i] == id) break; if (!hhash[horder[i]]) prev = horder[i]; } var index = prev?this.getColumnIndex(prev)+1:0; webix.PowerArray.insertAt.call(cols,column, index); delete column.hidden; if (i<this._hidden_split[0]) this._settings.leftSplit++; if (i>=this._hidden_split[1]) this._settings.rightSplit++; else this._rightSplit++; delete hhash[id]; this._columns_pull[id] = column; this.callEvent("onAfterColumnShow", [id]); } this._fixColspansHidden(column, mode !== false ? 0 : 1); if (!silent) this._refresh_columns(); }, _fixColspansHidden:function(config, mod){ for (var i = config.header.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 header = config.header[i]; if (!this.isColumnVisible(ind[j])){ //hidden column if (header && header.$colspan && spanSize <= 0){ //start of colspan in hidden spanSize = header.colspan = header.$colspan; isHidden = spanSource = header; } 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 header = config.header[i] = spanSource; spanSource = header; } else if (header && header.$colspan && spanSize <= 0){ //visible start of colspan spanSize = header.colspan = header.$colspan; spanSource = header; } 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){ return this.hideColumn(id, 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, !mode, true); else if(!preserve && col.batch!=batch && !hidden) this.hideColumn(col.id, mode, true); } }, true); this._refresh_columns(); } }); webix.extend(webix.ui.datatable, { $init:function(){ this.attachEvent("onAfterRender", function(){ var sel = this.getSelectedId(true); if(!sel.length || !this.getItemNode(sel[0])){ var node = this._dataobj.querySelector(".webix_cell"); if(node) node.setAttribute("tabindex", "0"); } }); }, moveSelection:function(mode, shift){ if(this._settings.disabled) return; //get existing selection as array var t = this.getSelectedId(true); var index = t.length-1; if (index < 0){ 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; var preserve = this._settings.multiselect ? shift : false; 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 var 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>=this.config.columns.length) return; 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); } this._select({ row:row, column:column }, preserve); var node = this.getItemNode({ row:row, column:column }); 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 >= 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){ var ind = this.getColumnIndex(column_id); row_index = row_index || 0; var nodes = this._header.childNodes[1].getElementsByTagName("TR")[row_index+1].childNodes; for (var i=0; i<nodes.length; i++) if (nodes[i].getAttribute("column") == ind) return nodes[i].firstChild; return null; }, getItemNode:function(id, e){ if (id && !id.header){ var row = id.row || id; var rowindex = this.getIndexById(row); var state = this._get_y_range(); var minRow = state[0]-this._settings.topSplit; //row not visible if (rowindex < minRow && rowindex > state[1]) return; //get visible column var x_range = this._get_x_range(); var colindex = this._settings.leftSplit ? 0 : x_range[0]; if (id.column){ colindex = this.getColumnIndex(id.column); //column not visible if (colindex < this._rightSplit && colindex >= this._settings.leftSplit && ( colindex<x_range[0] || colindex > x_range[1])) return; } var column = this._settings.columns[colindex]; if (column.attached && column.node){ var nodeIndex = rowindex < this._settings.topSplit?rowindex:(rowindex-minRow); return column.node.childNodes[nodeIndex]; } } }, dragColumn_setter:function(value){ var control; //will be defined below if (value == "order"){ control = { $drag:webix.bind(function(s,e){ var id = this.locate(e); if (this._rs_process || !id || !this.callEvent("onBeforeColumnDrag", [id.column, e])) return false; webix.DragControl._drag_context = { from:control, start:id, custom:"column_dnd" }; var column = this.getColumnConfig(id.column); this._relative_column_drag = webix.html.posRelative(e); this._limit_column_drag = column.width; return "<div class='webix_dd_drag_column' style='width:"+column.width+"px'>"+(column.header[0].text||" ")+"</div>"; }, this), $dragPos:webix.bind(function(pos, e, node){ var context = webix.DragControl.getContext(); var box = webix.html.offset(this.$view); node.style.display = 'none'; var html = document.elementFromPoint(pos.x, box.y+1); var id = (html?this.locate(html):null); var start = webix.DragControl.getContext().start.column; if (id && id.column != start && (!this._column_dnd_temp_block || id.column != this._last_sort_dnd_node )){ //ignore normal dnd , and dnd from other components if (context.custom == "column_dnd" && webix.$$(html) == this){ if (!this.callEvent("onBeforeColumnDropOrder",[start, id.column,e])) return; var start_index = this.getColumnIndex(start); var end_index = this.getColumnIndex(id.column); //on touch devices we need to preserve drag-start element till the end of dnd if(e.touches){ this._dragTarget = e.target; this._dragTarget.style.display = "none"; this.$view.parentNode.appendChild(this._dragTarget); } this.moveColumn(start, end_index+(start_index<end_index?1:0)); this._last_sort_dnd_node = id.column; this._column_dnd_temp_block = true; } } if (id && id.column == start){ //flag prevent flickering just after column move this._column_dnd_temp_block = false; } node.style.display = 'block'; pos.x = pos.x - this._relative_column_drag.x; pos.y = box.y; if (pos.x < box.x) pos.x = box.x; else { var max = box.x + this.$view.offsetWidth - this._scrollSizeY-this._limit_column_drag; if (pos.x > max) pos.x = max; } webix.DragControl._skip = true; }, this), $dragDestroy:webix.bind(function(a, node){ webix.html.remove(node); //clean dnd source element if(this._dragTarget) webix.html.remove(this._dragTarget); var id = webix.DragControl.getContext().start; this.callEvent("onAfterColumnDropOrder",[id.column, this._last_sort_dnd_node, a]); }, this), $drop: function(){} }; } else if (value) { control = { _inner_drag_only:true, $drag:webix.bind(function(s,e){ var id = this.locate(e); if (this._rs_process || !id || !this.callEvent("onBeforeColumnDrag", [id.column, e])) return false; webix.DragControl._drag_context = { from:control, start:id, custom:"column_dnd" }; var header = this.getColumnConfig(id.column).header; var text = " "; for (var i = 0; i < header.length; i++) if (header[i]){ text = header[i].text; break; } return "<div class='webix_dd_drag_column'>"+text+"</div>"; }, this), $drop:webix.bind(function(s,t,e){ var target = e; //on touch devices event doesn't point to the actual drop target if(e.touches && this._drag_column_last) target = this._drag_column_last; var id = this.locate(target); if (!id) return false; var start = webix.DragControl.getContext().start.column; if (start != id.column){ if (!this.callEvent("onBeforeColumnDrop",[start, id.column ,e])) return; var start_index = this.getColumnIndex(start); var end_index = this.getColumnIndex(id.column); this.moveColumn(start, end_index+(start_index<end_index?1:0)); this.callEvent("onAfterColumnDrop",[start, id.column, e]); } }, this), $dragIn:webix.bind(function(s,t,e){ var context = webix.DragControl.getContext(); //ignore normal dnd , and dnd from other components if (context.custom != "column_dnd" || context.from != control) return false; var target = (e.target||e.srcElement); while ((target.className||"").indexOf("webix_hcell") == -1){ target = target.parentNode; if (!target) return; } if (target != this._drag_column_last){ //new target if (this._drag_column_last) webix.html.removeCss(this._drag_column_last, "webix_dd_over_column"); webix.html.addCss(target, "webix_dd_over_column"); } return (this._drag_column_last = target); }, this), $dragDestroy:webix.bind(function(a,h){ if (this._drag_column_last) webix.html.removeCss(this._drag_column_last, "webix_dd_over_column"); webix.html.remove(h); }, this) }; } if (value){ webix.DragControl.addDrag(this._header, control); webix.DragControl.addDrop(this._header, control, true); } } }); webix.extend(webix.ui.datatable,webix.DragItem); webix.extend(webix.ui.datatable, { _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; 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); 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 = 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); 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 = 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, { 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(){ webix.html.remove(this._spans_areas); for (var i=0; i<3; i++){ var 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], x, 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],this._rightSplit,this._columns.length); this._paint_spans_area(this._spans_areas[1],this._settings.leftSplit,(this._rightSplit || this._columns.length)); }, _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; } }); webix.extend(webix.ui.datatable, { subrow_setter:function(value){ if (value){ this._settings.fixedRowHeight = false; this._init_subrow_once(); 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(){ webix.html.addStyle("#"+this._top_id +" .webix_cell.webix_dtable_subview { line-height:normal;}"); 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; 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:1px; 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.scrollHeight; } }, this); d = webix.html.remove(d); }, closeSub:function(id){ var obj = this.getItem(id); if (!obj.$subopen) return; obj.$row = false; obj.$subopen = false; var sub = this._subViewStorage[obj.$subContent]; if (sub) webix.$$(sub).config.hidden = true; this.refresh(id); this.callEvent("onSubViewClose", [id]); } }); webix.extend(webix.ui.datatable, { headermenu_setter:function(value){ if (value){ if (value.data) this._preconfigured_hmenu = true; value = this._init_hmenu_once(value); } return value; }, _init_hmenu_once:function(value){ var menuobj = { view:"contextmenu", template:"<span class='webix_icon {common.hidden()}'></span> #value#", type:{ hidden:function(obj){ if (obj.hidden) return "fa-empty"; else return "fa-eye"; } }, on:{ onMenuItemClick:webix.bind(function(id, ev){ var menu = webix.$$(this._settings.headermenu); var state = menu.getItem(id).hidden; menu.getItem(id).hidden = !state; menu.refresh(id); menu.$blockRender = true; if (state) this.showColumn(id); else this.hideColumn(id); 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.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); }, getValue:function(node){ return webix.$$(node.$webix).getValue(); }, setValue:function(node, value){ webix.$$(node.$webix).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(); this.refresh(master, node, value); }, this)); } else data = options; } else data = master.collectValues(value.columnId); if ((!this.$noEmptyOption && value.emptyOption !== false) || value.emptyOption){ var emptyOption = { id:"", value: value.emptyOption||"", $empty: true }; if(data.add) data.add(emptyOption,0); //DataStore else data.unshift(emptyOption); //array } 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); } //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.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.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(){ var mark = this.firstChild.firstChild; if (config.closed){ config.closed = false; mark.className = "webix_tree_open"; } else { config.closed = true; mark.className = "webix_tree_close"; } webix.delay(function(){ master.callEvent("onColumnGroupCollapse", [config.columnId, config.batch, !config.closed]); master.showColumnBatch(config.batch, !config.closed); }); }; if (!config.firstRun){ config.firstRun = 1; if (config.closed) master.showColumnBatch(config.batch, false); } }, render:function(master, config){ return "<div role='button' tabindex='0' aria-label='"+webix.i18n.aria[config.closed?"openGroup":"closeGroup"]+"' class='"+(config.closed?"webix_tree_close":"webix_tree_open")+"'></div> "+(config.groupText||""); } }; webix.ui.datafilter.dateRangeFilter = webix.extend({ prepare:function(value){ return webix.ui.daterange.prototype._correct_value(value); }, compare:function(a, b){ return ((!b.start || a>=b.start) && (!b.end || a<=b.end)); }, inputtype:"daterangepicker" }, webix.ui.datafilter.richSelectFilter); webix.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; for(i=0; i < this._canvas_labels.length;i++) this._obj.removeChild(this._canvas_labels[i]); 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; }, 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); }, 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){ if(webix.ui.view.prototype.$setSize.call(this,x,y)){ for(var c in this.canvases){ this.canvases[c]._resizeCanvas(this._content_width, this._content_height); } this.render(); } }, 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}; }, _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 = ((nmax-nmin)/8)||1; var power = Math.floor(this._log10(step)); var calculStep = Math.pow(10,power); var stepVal = step/calculStep; stepVal = (stepVal>5?10:5); step = parseInt(stepVal,10)*calculStep; 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; } end = start; while(end<nmax){ end += step; end = parseFloat((end*1.0).toFixed(Math.abs(power))); } 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?tag.getAttribute("userdata"):this._getActiveSeries(e); 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(); } }, 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(); } }, /** * 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 = 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(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(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>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 = 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) 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; if(config.yAxis&&value===0){ this.canvases["y"].renderTextAt(true, true, x0+Math.floor(barWidth/2),y0,this._settings.label(data[i])); 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 = 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<0||(this._settings.yAxis&&value===0)){ this.canvases["y"].renderTextAt("middle", true, x0+10,y0+barWidth/2,this._settings.label(data[i])); 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,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]); } return {a:a,b:b,c:c,d:d}; }, /*returns the y position of the spline point */ _getSplineYPoint:function(x,xi,i,a,b,c,d){ return a[i] + (x - xi)*(b[i] + (x-xi)*(c[i]+(x-xi)*d[i])); } }); webix.extend(webix.ui.chart,{ /** * renders an area chart * @param: ctx - canvas object * @param: data - object those need to be displayed * @param: width - the width of the container * @param: height - the height of the container * @param: sIndex - index of drawing chart */ $render_area:function(ctx, data, point0, point1, sIndex, map){ var align, config, i, mapRect, obj, params, path, res1, res2, x0, x1, y1, x2, y2, y0; params = this._calculateLineParams(ctx,data,point0,point1,sIndex); config = this._settings; //the size of map area mapRect = (config.eventRadius||Math.floor(params.cellWidth/2)); if (data.length) { // area points path = []; //the x position of the first item x0 = (!config.offset?point0.x:point0.x+params.cellWidth*0.5); /* iterates over all data items: calculates [x,y] for area path, adds rect to chart map and renders labels */ for(i=0; i < data.length;i ++){ obj = data[i]; res2 = this._getPointY(obj,point0,point1,params); x2 = x0 + params.cellWidth*i ; if(res2){ y2 = (typeof res2 == "object"?res2.y0:res2); if(i && this._settings.fixOverflow){ res1 = this._getPointY(data[i-1],point0,point1,params); if(res1.out && res1.out == res2.out){ continue; } x1 = params.cellWidth*(i-1) - 0.5 + x0; y1 = (typeof res1 == "object"?res1.y0:res1); if(res1.out){ y0 = (res1.out == "min"?point1.y:point0.y); path.push([this._calcOverflowX(x1,x2,y1,y2,y0),y0]); } if(res2.out){ y0 = (res2.out == "min"?point1.y:point0.y); path.push([this._calcOverflowX(x1,x2,y1,y2,y0),y0]); if(i == (data.length-1) && y0 == point0.y) path.push([x2,point0.y]); } } if(!res2.out){ path.push([x2,y2]); //map map.addRect(obj.id,[x2-mapRect-point0.x,y2-mapRect-point0.y,x2+mapRect-point0.x,y2+mapRect-point0.y],sIndex); } //labels if(!config.yAxis){ align = (!config.offset&&(i == data.length-1)?"left":"center"); this.canvases[sIndex].renderTextAt(false, align, x2, y2-config.labelOffset,config.label(obj)); } } } if(path.length){ path.push([x2,point1.y]); path.push([path[0][0],point1.y]); } //filling area ctx.globalAlpha = this._settings.alpha.call(this,data[0]); ctx.fillStyle = this._settings.color.call(this,data[0]); ctx.beginPath(); this._path(ctx,path); ctx.fill(); ctx.lineWidth = 1; ctx.globalAlpha =1; //border if(config.border){ ctx.lineWidth = config.borderWidth||1; if(config.borderColor) ctx.strokeStyle = config.borderColor.call(this,data[0]); else this._setBorderStyles(ctx,ctx.fillStyle); ctx.beginPath(); this._path(ctx,path); ctx.stroke(); } } }, /** * renders an area chart * @param: ctx - canvas object * @param: data - object those need to be displayed * @param: width - the width of the container * @param: height - the height of the container * @param: sIndex - index of drawing chart */ $render_stackedArea:function(ctx, data, point0, point1, sIndex, map){ var a0, a1, align, config, i, j, lastItem, mapRect, obj, params, path, x, y, yPos; params = this._calculateLineParams(ctx,data,point0,point1,sIndex); config = this._settings; /*the value that defines the map area position*/ mapRect = (config.eventRadius||Math.floor(params.cellWidth/2)); /*drawing all items*/ if (data.length) { // area points path = []; // y item positions yPos = []; //the x position of the first item x = (!config.offset?point0.x:point0.x+params.cellWidth*0.5); var setOffset = function(i,y){ return sIndex?(data[i].$startY?y-point1.y+data[i].$startY:0):y; }; var solveEquation = function(x,p0,p1){ var k = (p1.y - p0.y)/(p1.x - p0.x); return k*x + p0.y - k*p0.x; }; /* iterates over all data items: calculates [x,y] for area path, adds rect to chart map and renders labels */ for(i=0; i < data.length;i ++){ obj = data[i]; if(!i){ y = setOffset(i,point1.y); path.push([x,y]); } else{ x += params.cellWidth ; } y = setOffset(i,this._getPointY(obj,point0,point1,params)); yPos.push((isNaN(y)&&!i)?(data[i].$startY||point1.y):y); if(y){ path.push([x,y]); //map map.addRect(obj.id,[x-mapRect-point0.x,y-mapRect-point0.y,x+mapRect-point0.x,y+mapRect-point0.y],sIndex); //labels if(!config.yAxis){ align = (!config.offset&&lastItem?"left":"center"); this.canvases[sIndex].renderTextAt(false, align, x, y-config.labelOffset,config.label(obj)); } } } // bottom right point path.push([x,setOffset(i-1,point1.y)]); // lower border from the end to start if(sIndex){ for(i=data.length-2; i > 0; i --){ x -= params.cellWidth ; y = data[i].$startY; if(y) path.push([x,y]); } } // go to start point path.push([path[0][0],path[0][1]]); // filling path ctx.globalAlpha = this._settings.alpha.call(this,data[0]); ctx.fillStyle = this._settings.color.call(this,data[0]); ctx.beginPath(); this._path(ctx,path); ctx.fill(); // set y positions of the next series for(i=0; i < data.length;i ++){ y = yPos[i]; if(!y){ if(i == data.length-1){ y = data[i].$startY; } for(j =i+1; j< data.length; j++){ if(yPos[j]){ a0 = {x:point0.x,y:yPos[0]}; a1 = {x:(point0.x+params.cellWidth*j),y:yPos[j]}; y = solveEquation(point0.x+params.cellWidth*i,a0,a1); break; } } } data[i].$startY = y; } } } }); //+stackedArea webix.extend(webix.ui.chart, { $render_radar:function(ctx,data,x,y,sIndex,map){ this._renderRadarChart(ctx,data,x,y,sIndex,map); }, /** * renders a pie chart * @param: ctx - canvas object * @param: data - object those need to be displayed * @param: x - the width of the container * @param: y - the height of the container * @param: ky - value from 0 to 1 that defines an angle of inclination (0<ky<1 - 3D chart) */ _renderRadarChart:function(ctx,data,point0,point1,sIndex,map){ if(!data.length) return; var coord = this._getPieParameters(point0,point1); /*scale radius*/ var radius = (this._settings.radius?this._settings.radius:coord.radius); /*scale center*/ var x0 = (this._settings.x?this._settings.x:coord.x); var y0 = (this._settings.y?this._settings.y:coord.y); /*angles for each unit*/ var ratioUnits = []; for(var i=0;i<data.length;i++) ratioUnits.push(1); var ratios = this._getRatios(ratioUnits,data.length); this._mapStart = point0; if(!sIndex) this._drawRadarAxises(ratios,x0,y0,radius,data); this._drawRadarData(ctx,ratios,x0,y0,radius,data,sIndex,map); }, _drawRadarData:function(ctx,ratios,x,y,radius,data,sIndex,map){ var alpha0 ,alpha1, config, i, min, max, pos0, pos1, posArr, r0, r1, relValue, startAlpha, value, value0, value1, valueFactor, unit, unitArr; config = this._settings; /*unit calculation (item_radius_pos = value*unit)*/ min = config.yAxis.start; max = config.yAxis.end; unitArr = this._getRelativeValue(min,max); relValue = unitArr[0]; unit = (relValue?radius/relValue:radius/2); valueFactor = unitArr[1]; startAlpha = -Math.PI/2; alpha0 = alpha1 = startAlpha; posArr = []; pos1 = 0; for(i=0;i<data.length;i++){ if(!value1){ value = config.value(data[i]); if(this._logScaleCalc) value = this._log10(value); /*a relative value*/ value0 = (parseFloat(value||0) - min)*valueFactor; } else value0 = value1; r0 = Math.floor(unit*value0); value = config.value((i!=(data.length-1))?data[i+1]:data[0]); if(this._logScaleCalc) value = this._log10(value); value1 = (parseFloat(value||0) - min)*valueFactor; r1 = Math.floor(unit*value1); alpha0 = alpha1; alpha1 = ((i!=(data.length-1))?(startAlpha+ratios[i]-0.0001):startAlpha); pos0 = (pos1||this._getPositionByAngle(alpha0,x,y,r0)); pos1 = this._getPositionByAngle(alpha1,x,y,r1); /*creates map area*/ /*areaWidth = (config.eventRadius||(parseInt(config.item.radius.call(this,data[i]),10)+config.item.borderWidth)); map.addRect(data[i].id,[pos0.x-areaWidth,pos0.y-areaWidth,pos0.x+areaWidth,pos0.y+areaWidth],sIndex);*/ //this._drawLine(ctx,pos0.x,pos0.y,pos1.x,pos1.y,config.line.color.call(this,data[i]),config.line.width) posArr.push(pos0); } if(config.fill) this._fillRadarChart(ctx,posArr,data); if(!config.disableLines && data.length>2) this._strokeRadarChart(ctx,posArr,data); if(!config.disableItems || data.length<3) this._drawRadarItemMarkers(ctx,posArr,data,sIndex,map); posArr = null; }, _drawRadarItemMarkers:function(ctx,points,data,sIndex,map){ for(var i=0;i < points.length;i++){ this._drawItem(ctx,points[i].x,points[i].y,data[i],this._settings.label.call(this,data),sIndex,map); } }, _fillRadarChart:function(ctx,points,data){ var pos0,pos1; ctx.globalAlpha= this._settings.alpha.call(this,{}); ctx.beginPath(); for(var i=0;i < points.length;i++){ ctx.fillStyle = this._settings.fill.call(this,data[i]); pos0 = points[i]; pos1 = (points[i+1]|| points[0]); if(!i){ ctx.moveTo(pos0.x,pos0.y); } ctx.lineTo(pos1.x,pos1.y); } ctx.fill(); ctx.globalAlpha=1; }, _strokeRadarChart:function(ctx,points,data){ var pos0,pos1; for(var i=0;i < points.length;i++){ pos0 = points[i]; pos1 = (points[i+1]|| points[0]); this._drawLine(ctx,pos0.x,pos0.y,pos1.x,pos1.y,this._settings.line.color.call(this,data[i]),this._settings.line.width); } }, _drawRadarAxises:function(ratios,x,y,radius,data){ var configY = this._settings.yAxis; var configX = this._settings.xAxis; var start = configY.start; var end = configY.end; var step = configY.step; var scaleParam= {}; var config = this._configYAxis; if(typeof config.step =="undefined"||typeof config.start=="undefined"||typeof config.end =="undefined"){ var limits = this._getLimits(); scaleParam = this._calculateScale(limits.min,limits.max); start = scaleParam.start; end = scaleParam.end; step = scaleParam.step; configY.end = end; configY.start = start; } var units = []; var i,j,p; var c=0; var stepHeight = radius*step/(end-start); /*correction for small step*/ var power,corr; if(step<1){ power = Math.min(this._log10(step),(start<=0?0:this._log10(start))); corr = Math.pow(10,-power); } var angles = []; if(!this.canvases["scale"]) this.canvases["scale"] = this._createCanvas("radar_scale"); var ctx = this.canvases["scale"].getCanvas(); for(i = end; i>=start; i -=step){ var value = this._logScaleCalc?Math.pow(10,i):i; if(scaleParam.fixNum) value = parseFloat(i).toFixed(scaleParam.fixNum); units.push(Math.floor(c*stepHeight)+ 0.5); if(corr && !this._logScaleCalc){ value = Math.round(value*corr)/corr; i = value; } var unitY = y-radius+units[units.length-1]; this.canvases["scale"].renderTextAt("middle","left",x,unitY, configY.template(value.toString()), "webix_axis_item_y webix_radar" ); if(ratios.length<2){ this._drawScaleSector(ctx,"arc",x,y,radius-units[units.length-1],-Math.PI/2,3*Math.PI/2,i); return; } var startAlpha = -Math.PI/2;/*possibly need to moved in config*/ var alpha0 = startAlpha; var alpha1; for(j=0;j< ratios.length;j++){ if(!c) angles.push(alpha0); alpha1 = startAlpha+ratios[j]-0.0001; this._drawScaleSector(ctx,(ratios.length>2?(config.lineShape||"line"):"arc"),x,y,radius-units[units.length-1],alpha0,alpha1,i,j,data[i]); alpha0 = alpha1; } c++; } /*renders radius lines and labels*/ for(i=0;i< angles.length;i++){ p = this._getPositionByAngle(angles[i],x,y,radius); if(configX.lines.call(this,data[i],i)) this._drawLine(ctx,x,y,p.x,p.y,(configX?configX.lineColor.call(this,data[i]):"#cfcfcf"),1); this._drawRadarScaleLabel(ctx,x,y,radius,angles[i],(configX?configX.template.call(this,data[i]):" ")); } }, _drawScaleSector:function(ctx,shape,x,y,radius,a1,a2,i,j){ var pos1, pos2; if(radius<0) return false; pos1 = this._getPositionByAngle(a1,x,y,radius); pos2 = this._getPositionByAngle(a2,x,y,radius); var configY = this._settings.yAxis; if(configY.bg){ ctx.beginPath(); ctx.moveTo(x,y); if(shape=="arc") ctx.arc(x,y,radius,a1,a2,false); else{ ctx.lineTo(pos1.x,pos1.y); ctx.lineTo(pos2.x,pos2.y); } ctx.fillStyle = configY.bg(i,j); ctx.moveTo(x,y); ctx.fill(); ctx.closePath(); } if(configY.lines.call(this,i)){ ctx.lineWidth = 1; ctx.beginPath(); if(shape=="arc") ctx.arc(x,y,radius,a1,a2,false); else{ ctx.moveTo(pos1.x,pos1.y); ctx.lineTo(pos2.x,pos2.y); } ctx.strokeStyle = configY.lineColor.call(this,i); ctx.stroke(); } }, _drawRadarScaleLabel:function(ctx,x,y,r,a,text){ if(!text) return false; var t = this.canvases["scale"].renderText(0,0,text,"webix_axis_radar_title",1); var width = t.scrollWidth; var height = t.offsetHeight; var delta = 0.001; var pos = this._getPositionByAngle(a,x,y,r+5); var corr_x=0,corr_y=0; if(a<0||a>Math.PI){ corr_y = -height; } if(a>Math.PI/2){ corr_x = -width; } if(Math.abs(a+Math.PI/2)<delta||Math.abs(a-Math.PI/2)<delta){ corr_x = -width/2; } else if(Math.abs(a)<delta||Math.abs(a-Math.PI)<delta){ corr_y = -height/2; } t.style.top = pos.y+corr_y+"px"; t.style.left = pos.x+corr_x+"px"; t.style.width = width+"px"; t.style.whiteSpace = "nowrap"; } }); webix.extend(webix.ui.chart, { /** * renders a graphic * @param: ctx - canvas object * @param: data - object those need to be displayed * @param: point0 - top left point of a chart * @param: point1 - right bottom point of a chart * @param: sIndex - index of drawing chart * @param: map - map object */ $render_scatter:function(ctx, data, point0, point1, sIndex, map){ if(!this._settings.xValue) return webix.log("warning","Undefined propery: xValue"); /*max in min values*/ var limitsY = this._getLimits(); var limitsX = this._getLimits("h","xValue"); /*render scale*/ if(!sIndex){ if(!this.canvases["x"]) this.canvases["x"] = this._createCanvas("axis_x"); if(!this.canvases["y"]) this.canvases["y"] = this._createCanvas("axis_y"); this._drawYAxis(this.canvases["y"].getCanvas(),data,point0,point1,limitsY.min,limitsY.max); this._drawHXAxis(this.canvases["x"].getCanvas(),data,point0,point1,limitsX.min,limitsX.max); } limitsY = {min:this._settings.yAxis.start,max:this._settings.yAxis.end}; limitsX = {min:this._settings.xAxis.start,max:this._settings.xAxis.end}; var params = this._getScatterParams(ctx,data,point0,point1,limitsX,limitsY); this._mapStart = point0; for(var i=0;i<data.length;i++){ this._drawScatterItem(ctx,map,point0, point1, params,limitsX,limitsY,data[i],sIndex); } }, _getScatterParams:function(ctx, data, point0, point1,limitsX,limitsY){ var params = {}; /*available space*/ params.totalHeight = point1.y-point0.y; /*available width*/ params.totalWidth = point1.x-point0.x; /*unit calculation (y_position = value*unit)*/ this._calcScatterUnit(params,limitsX.min,limitsX.max,params.totalWidth,"X"); this._calcScatterUnit(params,limitsY.min,limitsY.max,params.totalHeight,"Y"); return params; }, _drawScatterItem:function(ctx,map,point0, point1,params,limitsX,limitsY,obj,sIndex){ var x0 = this._calculateScatterItemPosition(params, point1, point0, limitsX, obj, "X"); var y0 = this._calculateScatterItemPosition(params, point0, point1, limitsY, obj, "Y"); this. _drawItem(ctx,x0,y0,obj,this._settings.label.call(this,obj),sIndex,map); }, _calculateScatterItemPosition:function(params, point0, point1, limits, obj, axis){ /*the real value of an object*/ var value = this._settings[axis=="X"?"xValue":"value"].call(this,obj); /*a relative value*/ var valueFactor = params["valueFactor"+axis]; var v = (parseFloat(value||0) - limits.min)*valueFactor; /*a vertical coordinate*/ var unit = params["unit"+axis]; var pos = point1[axis.toLowerCase()] - (axis=="X"?(-1):1)*Math.floor(unit*v); /*the limit of the minimum value is the minimum visible value*/ if(v<0) pos = point1[axis.toLowerCase()]; /*the limit of the maximum value*/ if(value > limits.max) pos = point0[axis.toLowerCase()]; /*the limit of the minimum value*/ if(value < limits.min) pos = point1[axis.toLowerCase()]; return pos; }, _calcScatterUnit:function(p,min,max,size,axis){ var relativeValues = this._getRelativeValue(min,max); axis = (axis||""); p["relValue"+axis] = relativeValues[0]; p["valueFactor"+axis] = relativeValues[1]; p["unit"+axis] = (p["relValue"+axis]?size/p["relValue"+axis]:10); } }); /*chart presents*/ webix.extend(webix.ui.chart, { presets:{ "simple":{ item:{ borderColor: "#ffffff", color: "#2b7100", shadow: false, borderWidth:2 }, line:{ color:"#8ecf03", width:2 } }, "plot":{ color:"#1293f8", item:{ borderColor:"#636363", borderWidth:1, color: "#ffffff", type:"r", shadow: false }, line:{ color:"#1293f8", width:2 } }, "diamond":{ color:"#b64040", item:{ borderColor:"#b64040", color: "#b64040", type:"d", radius:3, shadow:true }, line:{ color:"#ff9000", width:2 } }, "point":{ color:"#fe5916", disableLines:true, fill:false, disableItems:false, item:{ color:"#feb916", borderColor:"#fe5916", radius:2, borderWidth:1, type:"r" }, alpha:1 }, "line":{ line:{ color:"#3399ff", width:2 }, item:{ color:"#ffffff", borderColor:"#3399ff", radius:2, borderWidth:2, type:"d" }, fill:false, disableItems:false, disableLines:false, alpha:1 }, "area":{ fill:"#3399ff", line:{ color:"#3399ff", width:1 }, disableItems:true, alpha: 0.2, disableLines:false }, "round":{ item:{ radius:3, borderColor:"#3f83ff", borderWidth:1, color:"#3f83ff", type:"r", shadow:false, alpha:0.6 } }, "square":{ item:{ radius:3, borderColor:"#447900", borderWidth:2, color:"#69ba00", type:"s", shadow:false, alpha:1 } }, /*bar*/ "column":{ color:"RAINBOW", gradient:false, barWidth:45, radius:0, alpha:1, border:true }, "stick":{ barWidth:5, gradient:false, color:"#67b5c9", radius:2, alpha:1, border:false }, "alpha":{ color:"#b9a8f9", barWidth:70, gradient:"falling", radius:0, alpha:0.5, border:true } } }); webix.extend(webix.ui.chart,{ /** * renders an splineArea chart * @param: ctx - canvas object * @param: data - object those need to be displayed * @param: width - the width of the container * @param: height - the height of the container * @param: sIndex - index of drawing chart */ $render_splineArea:function(ctx, data, point0, point1, sIndex, map){ var color, i,items,j,mapRect,params,sParams, x,x0,x1,x2,y,y1,y2, config = this._settings, path = []; params = this._calculateLineParams(ctx,data,point0,point1,sIndex); mapRect = (config.eventRadius||Math.floor(params.cellWidth/2)); /*array of all points*/ items = []; if (data.length) { /*getting all points*/ x0 = point0.x; for(i=0; i < data.length;i ++){ y = this._getPointY(data[i],point0,point1,params); if(y || y=="0"){ x = ((!i)?x0:params.cellWidth*i - 0.5 + x0); items.push({x:x,y:y,index:i}); map.addRect(data[i].id,[x-mapRect-point0.x,y-mapRect-point0.y,x+mapRect-point0.x,y+mapRect-point0.y],sIndex); } } sParams = this._getSplineParameters(items); for(i =0; i< items.length; i++){ x1 = items[i].x; y1 = items[i].y; if(i<items.length-1){ x2 = items[i+1].x; y2 = items[i+1].y; for(j = x1; j < x2; j++){ var sY1 = this._getSplineYPoint(j,x1,i,sParams.a,sParams.b,sParams.c,sParams.d); if(sY1<point0.y) sY1=point0.y; if(sY1>point1.y) sY1=point1.y; var sY2 = this._getSplineYPoint(j+1,x1,i,sParams.a,sParams.b,sParams.c,sParams.d); if(sY2<point0.y) sY2=point0.y; if(sY2>point1.y) sY2=point1.y; path.push([j,sY1]); path.push([j+1,sY2]); } path.push([x2,y2]); } } color = this._settings.color.call(this,data[0]); if(path.length){ path.push([x2,point1.y]); path.push([path[0][0],point1.y]); } //filling area ctx.globalAlpha = this._settings.alpha.call(this,data[0]); ctx.fillStyle = color; ctx.beginPath(); this._path(ctx,path); ctx.fill(); ctx.lineWidth = 1; ctx.globalAlpha =1; // draw line if(config.border){ ctx.lineWidth = config.borderWidth||1; if(config.borderColor) ctx.strokeStyle = config.borderColor.call(this,data[0]); else this._setBorderStyles(ctx,color); ctx.beginPath(); path.splice(path.length-3); this._path(ctx,path); ctx.stroke(); } } } }); (function(){ var animateDuration = 400, cellWidth = 30; webix.extend(webix.ui.chart, { dynamic_setter: function(value){ if(value) init(this); return value; } }); /** * Sets event handlers and properties for a stock chart * @param {object} chart - chart view */ function init(chart){ if(chart._stockRenderHandler) return; var config = chart._settings; if(!config.cellWidth) config.cellWidth = cellWidth; if(!config.animateDuration) config.animateDuration = animateDuration; config.offset = false; chart._stockRenderHandler = chart.attachEvent("onBeforeRender", function(data, type){ var bounds = chart._getChartBounds(chart._content_width, chart._content_height); resizeStockCanvases(chart); filterStockData(data, bounds.start, bounds.end, config.cellWidth); if(type == "add") startAnimation(chart); }); chart._stockXAxisHandler = chart.attachEvent("onBeforeXAxis", function(ctx,data,point0,point1,cellWidth,y){ drawXAxis(chart,ctx,data,point0,point1,cellWidth,y); return false; }); } /** * Starts stock animation * @param {object} chart - chart view */ function startAnimation(chart){ var cellWidth = chart._settings.cellWidth; if(chart._stockAnimationOffset != cellWidth){ chart._stockAnimationOffset = cellWidth; chart.render(); } chart._stockAnimationOffset = 0; chart._stockAnimationStart = null; if(window.requestAnimationFrame) 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; // 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); // 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) labels[labels.length-1].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 setLabelsOffset(labels, offset){ if(labels.length){ labels[0].style.display = "none"; for(var i = 1; i< labels.length; i++) 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; }, $init: function() { this._viewobj.className += " webix_calendar"; this._viewobj.setAttribute("role", "grid"); this._viewobj.setAttribute("aria-readonly", "true"); //special dates this._special_dates = {}; this._selected_date_part = this._selected_date = null; this._zoom_level = 0; this.attachEvent("onKeyPress", this._onKeyPress); }, 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){ if(this.config.master) return; //in daterange var date = webix.Date.copy(this.getSelectedDate() || this.getVisibleDate()); var css = this._zoom_logic[this._zoom_level]._keyshift(date, mode, this); 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='heading' 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' role='row'>"+this._week_template(width)+"</div>"; html += "<div class='webix_cal_body' role='rowgroup'>"+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' role='columnheader' 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 role='columnheader' 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' role='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' role='rowheader' 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); html += "<div day='"+x+"' role='gridcell' tabindex='"+(sel?"0":"-1")+"' aria-selected='"+(sel?"true":"false")+"' class='"+css+"' style='width:"+widths[x]+"px'><span 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); } 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; 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; 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); }, _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; 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; 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' role='row'>"+this._timeHeaderTemplate(width,enLocale)+"</div>"; html += "<div class='webix_cal_body' role='rowgroup' style='height:"+this._reserve_box_height+"px'>"; // check and change blocked selected time this._correctBlockedTime(); html += "<div class='webix_hours' role='row'>"; selected = config.date.getHours(); for (i= 0; i< 24; i++){ css=""; if(enLocale){ if(i%4===0){ var label = (!i?"am":(i==12?"pm":"")); html += "<div "+(label?"role='row'":"")+"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"; var value = webix.Date.toFixed(enLocale?(!i||i==12?12:i%12):i); html += "<div aria-label='"+(value+(enLocale?(i<12?" am":" pm"):""))+"' role='gridcell'"+(!i?" colspan='24'":"")+" rowindex='1' 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;":"")+"'>"+value+"</div>"; } html += "</div>"; html += "<div class='webix_minutes' role='row'>"; selected = config.date.getMinutes(); 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"; html += "<div role='gridcell'"+(!i?" colspan='12'":"")+" rowindex='1' 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"; } html+="<div role='gridcell' 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"; sections[index].setAttribute("role", "row"); } }, _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' role='columnheader' style='width:"+w1+"px'>"+webix.i18n.calendar.hours+"</div><div class='webix_cal_minutes' role='columnheader' 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(value){ var now = this._settings.date; var next = webix.Date.copy(now); this._zoom_logic[this._zoom_level]._setContent(next, value, this); 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]); } }, 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 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()); } 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 level = (trg.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, trg.getAttribute("data-value")*1, this); this._update_zoom_level(next); } else{ if(trg.className.indexOf('webix_cal_day_disabled')==-1) this._mode_selected(trg.getAttribute("data-value")*1); } } }, _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(){ 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("onSelect", [this._settings.value, e]); }, 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) return; if(value.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){ 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]); 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, calendarHeight:250 }, $skin:function(){ if( webix.skin.$active.calendar && webix.skin.$active.calendar.height) this.defaults.calendarHeight = webix.skin.$active.calendar.height; }, $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 = [], calendar = webix.extend({ view:"calendar" }, 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) ]; config.height = this.defaults.calendarHeight+(config.icons || config.button?35:0); config.type = "line"; this.$ready.push(this._after_init); }, 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(); } } }, _footer_row:function(config){ //center ok button var iconWidth = config.icons?(webix.html.getTextSize(webix.i18n.calendar.today+webix.i18n.calendar.clear).width+30):0; var button = { view:"button", value:webix.i18n.calendar.done, inputWidth:(config.calendarCount > 1 ? 230 : 0), align:"center", height:30, click:function(){ this.getParentView().getParentView().hide(); }}; var icons = { css:"webix_cal_footer ", borderless:true, height:30, template: function(){ return "<div class='webix_cal_icons'><span class='webix_cal_icon_today webix_cal_icon'>"+webix.i18n.calendar.today+"</span>"+ "<span class='webix_cal_icon_clear webix_cal_icon'>"+webix.i18n.calendar.clear+"</span></div>"; }, width: iconWidth, onClick:{ "webix_cal_icon_today": webix.bind(function(){ this.setValue(this._add_date(new Date())); this.callEvent("onTodaySet",[this.getValue()]); }, this), "webix_cal_icon_clear": webix.bind(function(){ this.setValue(""); this.callEvent("onDateClear", []); }, this) } }; var row = { css:"webix_range_footer", cols:[ { width:config.button?iconWidth:0 } ]}; row.cols.push(config.button ? button : {}); row.cols.push(config.icons ? icons : {width:iconWidth}); 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 = 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 = 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; } 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.getMonth()>vis.getMonth()? 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(value); }, getRange:function(){ return this.getBody(); }, getButton:function(){ return this.getBody().getChildViews()[1].getChildViews()[1]; }, _show_selection:function(){ var list = this.getList(); var value = this.getMasterValue(); if (value) this.setValue(webix.copy(value)); }, _setValue:function(value, hide){ var master = webix.$$(this._settings.master); if(master){ master._selecting = true; 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_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(){}; }, $setValue:function(value){ value = value || {}; var popup = webix.$$(this._settings.popup.toString()); var daterange = popup.getRange(); value.start = this._parse_value(value.start?value.start:null); value.end = this._parse_value(value.end?value.end:null); if(!this._selecting) daterange.setValue(value); this._selecting = false; this._settings.value = webix.copy(daterange._settings.value); this._settings.text = (value.start?this._get_visible_date(value.start):"")+(value.end?(" - "+ this._get_visible_date(value.end)):""); this._set_visible_text(); }, $render:function(obj){ 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 = 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.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){ 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){ 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){ var value = this.getValue(); var values = webix.toArray(value?this.getValue().split(this._settings.separator):[]); if(values.find(id)<0){ values.push(id); } else values.remove(id); var master = webix.$$(this._settings.master); if(master){ master.setValue(values.join(this._settings.separator)); } else this.setValue(values); 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(); } }, 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 (typeof value == "string") value = value.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, body:{ rows:[ { 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._toggleOption(id, e); } } }, { view:"button", click:function(){ var suggest = this.getParentView().getParentView(); suggest.setMasterValue({ id:suggest.getValue() }); suggest.hide(); }} ] } }, $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]; }; } }, $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 (typeof value == "string") value = value.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: true, 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("onBlur", webix.bind(function(){ var value = this.getInputNode().value; if(value && this._settings.newValues){ this._addNewValue(value); } this._inputValue = ""; this.refresh(); },this)); 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; } }, _removeValue: function(value){ var values = this._settings.value; 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)); }, _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(); if(!list.exists(value) && value.replace(/^\s+|\s+$/g,'')){ list.add({id: value, value: value}); } this._addValue(value); }, _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); 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='"+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); }, _getSelectedOptions: function(){ var i, item, popup, options = [], value = this._settings.value; if (!value) return []; if(typeof value == "string") value = value.split(this._settings.separator); popup = this.getPopup(); for(i = 0; i < value.length; i++){ item = popup.getList().getItem(value[i]) || (popup._valueHistory?popup._valueHistory[value[i]]:null); if(item) options.push(item); } return options; }, $setSize:function(x,y){ var config = this._settings; if(webix.ui.view.prototype.$setSize.call(this,x,y)){ if (!x || !y) return; if (config.labelPosition == "top"){ config.labelWidth = 0; } this.render(); } }, _calcInputWidth: function(value){ var tmp = document.createElement("span"); tmp.className = "webix_multicombo_input"; tmp.style.visibility = "visible"; tmp.style.height = "0px"; tmp.innerHTML = value.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); document.body.appendChild(tmp); var width = tmp.offsetWidth+10; document.body.removeChild(tmp); return width; }, _getMultiComboInputWidth: function(){ var listbox = this._getValueListBox(); return listbox.offsetWidth - listbox.firstChild.offsetWidth - 17; }, _init_onchange:function(){ // input focus and focus styling webix.event(this._getBox(),"click",function(){ this.getInputNode().focus(); },{bind:this, id: this._get_event_id("click")}); webix.event(this.getInputNode(),"focus",function(){ if(this._getBox().className.indexOf("webix_focused") == -1) this._getBox().className += " webix_focused"; },{bind:this, id: this._get_event_id("focus")}); webix.event(this.getInputNode(),"blur",function(){ this._getBox().className = this._getBox().className.replace(" webix_focused",""); },{bind:this, id: this._get_event_id("blur")}); // 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, id: this._get_event_id("input")}); // 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()); } },{bind:this, id: this._get_event_id("keyup")}); // 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) > 800)){ this._typing = true; this._removeValue(node.getAttribute("value")); } 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); } 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; } } } },{bind:this, id:this._get_event_id("keydown")}); webix.$$(this._settings.suggest).linkInput(this); } }, webix.ui.richselect); webix.protoUI({ name:"menu", _listClassName:"webix_menu", $init:function(config){ 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(){ if (this._menu_was_activated() && this._settings.openAction == "click") return; if (!this._child_menu_active) 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(){ if (this.getTopMenu()._settings.openAction == "click") return; if (!this._child_menu_active && !this._show_on_mouse_out) 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.detachEvent) 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)); obj.tabbarPopup = view._settings.id; this._destroy_with_me.push(view); } this._init_popup = function(){}; }, getPopup: function(){ this._init_popup(); return webix.$$(this._settings.tabbarPopup); }, moreTemplate_setter: webix.template, popupTemplate_setter: webix.template, defaults:{ popupWidth: 200, popupTemplate: "#value#", yCount: 7, moreTemplate: '<span class="webix_icon fa-ellipsis-h"></span>', template:function(obj,common) { var contentWidth, html, i, leafWidth, resultHTML, style, sum, tabs, verticalOffset, width; common._tabs = tabs = common._filterOptions(obj.options); if (!tabs.length){ html = "<div class='webix_tab_filler' style='width:"+common._input_width+"px; border-right:0px;'></div>"; } else { common._check_options(tabs); if (!obj.value && tabs.length) obj.value = tabs[0].id; html = ""; if (obj.tabOffset) html += "<div class='webix_tab_filler' style='width:"+obj.tabOffset+"px;'> </div>"; contentWidth = common._input_width - obj.tabOffset*2-(!obj.type?(obj.tabMargin)*(tabs.length-1):0); verticalOffset = obj.topOffset+obj.bottomOffset; var sizes = common._getTabbarSizes(); if(sizes.max && sizes.max < tabs.length){ //we need popup var popup = common.getPopup(); popup.hide(); var list = (popup.getBody()||null); if(list){ if(sizes.max){ var found = false; for( i = 0; i < tabs.length && !found; i++) if(tabs[i].id== obj.value){ found = true; if((i+1) > sizes.max){ var selectedTab = tabs.splice(i, 1); var displayTabs = tabs.splice(0, sizes.max-1).concat(selectedTab); tabs = displayTabs.concat(tabs); } } list.clearAll(); list.parse(tabs.slice(sizes.max)); } else{ list.clearAll(); } } } else if (common._settings.tabbarPopup) webix.$$(common._settings.tabbarPopup).hide(); sum = obj.tabOffset; var lastTab = false; for(i = 0; (i<tabs.length) && !lastTab; i++) { // tab width if(sizes && sizes.max){ if(sizes.max == (i + 1)){ lastTab = true; } contentWidth = common._input_width - obj.tabOffset*2-(!obj.type&&(sizes.max>1)?(obj.tabMargin)*(sizes.max-1):0); width = (contentWidth - obj.tabMoreWidth)/sizes.max ; } else width = sizes.width; width = (tabs[i].width||obj.optionWidth||width); sum += width + (i&&!obj.type?obj.tabMargin:0); if(obj.tabMargin>0&&i&&!obj.type) html += "<div class='webix_tab_filler' style='width:"+obj.tabMargin+"px;'></div>"; // tab innerHTML html += common._getTabHTML(tabs[i],width); if(lastTab){ html += '<div role="button" tabindex="0" aria-label="'+webix.i18n.aria.showTabs+'" class="webix_tab_more_icon" style="width:'+obj.tabMoreWidth+'px;">'+obj.moreTemplate(obj,common)+'</div>'; sum += obj.tabMoreWidth; } } leafWidth = common._content_width - sum; if (leafWidth>0 && !obj.type) html += "<div class='webix_tab_filler' style='width:"+leafWidth+"px;'> </div>"; } resultHTML = ""; // consider top and bottom offset in tabs height (top tabbar) style = (verticalOffset&& !obj.type)?"height:"+(common._content_height-verticalOffset)+"px":""; //space above tabs (top tabbar) if(obj.topOffset && !obj.type) resultHTML += "<div class='webix_before_all_tabs' style='width:100%;height:"+obj.topOffset+"px'></div>"; // tabs html resultHTML += "<div style='"+style+"' role='tablist' class='webix_all_tabs "+(obj.type?("webixtype_"+obj.type):"")+"'>"+html+"</div>"; //space below to tabs (top tabbar) if(obj.bottomOffset && !obj.type) resultHTML += "<div class='webix_after_all_tabs' style='width:100%;height:"+obj.bottomOffset+"px'></div>"; return resultHTML; } }, _getInputNode:function(){ return this.$view.getElementsByClassName("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); t.refresh(); 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) view.destructor(); }); } }, webix.ui.layout); (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; 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._map = webix.promise.defer(); this.data.provideApi(this, true); this.$ready.push(this.render); }, getMap:function(){ return this._map; }, render:function(){ if(typeof window.google=="undefined"||typeof window.google.maps=="undefined"){ var name = "webix_callback_"+webix.uid(); window[name] = webix.bind(function(){ google = window.google; this._initMap.call(this,true); },this); var script = document.createElement("script"); script.type = "text/javascript"; var src = "//maps.google.com/maps/api/js?callback="+name; if (this.config.key) src += "&key="+this.config.key; if (this.config.libraries) src += "&libraries="+this.config.libraries; script.src = src; document.getElementsByTagName("head")[0].appendChild(script); } else this._initMap(); }, _initMap:function(define){ 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._map.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._map.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(){ this.data.each(function(obj){ obj.$marker.setMap(null); }); this.data.clearAll(); }, 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]); } }, _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); })(); 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); webix.jsonp = function(url, params, callback, master){ 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(){ callback.apply(master||window, arguments); script.parentNode.removeChild(script); callback = head = master = script = null; delete webix.jsonp[id]; }; 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); }; 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); } } } if (document.attachEvent) document.attachEvent("onkeydown", modal_key); else document.addEventListener("keydown", modal_key, 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); 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: {} }, $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); }, _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()) 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"; 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); }, // adding files by drag-n-drop _drop: function(e) { var files = e.dataTransfer.files; if (this.callEvent('onBeforeFileDrop', [files, e])) this._add_files(files); 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(); formData.append(this.config.inputName, item.file); 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 == 200) 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, //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){ 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.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); } }, _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; //web webix.event(node,"dragover", webix.html.preventEvent); webix.event(node,"dragover", function(e){ webix.html.addCss(node, fullcss, true); }); webix.event(node,"dragleave", function(e){ webix.html.removeCss(node, fullcss); }); webix.event(node,"drop", webix.bind(function(e){ webix.html.removeCss(node, fullcss); var data=e.dataTransfer; if(data&&data.files.length) for (var i = 0; i < data.files.length; i++) this.addFile(data.files[i]); 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, 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); }; })(); (function(){ if (window.jQuery){ var $ = jQuery; var methods = []; var get_id = function(node){ if (node && node.getAttribute) return node.getAttribute("view_id"); }; var get_helper = function(name){ return function(config){ if (typeof(config) === 'string') { if (methods[config] ) { return methods[config].apply(this, []); }else { $.error('Method ' + config + ' does not exist on jQuery.'.name); } } else { var views = []; this.each(function() { var view; var id; //if target a webix component - return it var id = get_id(this) || get_id(this.firstChild); if (id) view = webix.$$(id); if (!view){ //do not include data in copy as it can be massive var temp_data = config?config.data:0; if (temp_data) config.data = null; var copy = webix.copy(config||{ autoheight:true, autowidth:true }); copy.view = name; if (temp_data) config.data = copy.data = temp_data; if (this.tagName.toLowerCase() === 'table') { var div = webix.html.create("div",{ id:(this.getAttribute("id")||""), "class":(this.getAttribute("class")||"") },""); this.parentNode.insertBefore(div, this); copy.container = div; view = webix.ui(copy); view.parse(this, "htmltable"); } else { copy.container = this; view = webix.ui(copy); } } views.push(view); }); if (views.length === 1) return views[0]; return views; } }; }; var run = function(){ for (var key in webix.ui){ var name = "webix_"+key; if (!$.fn[name]) $.fn[name] = get_helper(key); } }; run(); $(run); } })(); /* Behavior:History - change multiview state on 'back' button */ webix.history = { track:function(id, url){ this._init_state(id, url); var view = webix.$$(id); var handler = function(){ if (webix.history._ignored) return; if (view.getValue) webix.history.push(id, view.getValue()); }; if (view.getActiveId) view.attachEvent("onViewChange", handler); else 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:"fieldset", defaults:{ borderless:true }, $init:function(obj){ webix.assert(obj.body, "fieldset must have not-empty body"); this._viewobj.className += " webix_fieldset"; 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; }, $getSize:function(x,y){ webix.debug_size_box_start(this, true); x+=18; y+=30; var t = this._last_body_size = this._body_view.$getSize(x,y); webix.debug_size_box_end(this, t); return t; }, $setSize:function(x,y){ if (webix.ui.view.prototype.$setSize.call(this, x,y)){ y = Math.min(this._last_body_size[3], y); x = Math.min(this._last_body_size[1], x); this._body_view.$setSize(x-18,y-30); } } }, webix.ui.view); webix.protoUI({ name:"slider", $touchCapture:true, defaults:{ min:0, max:100, value:50, step:1, title:false, template:function(obj, common){ var id = common._handle_id = "x" +webix.uid(); var html = "<div class='webix_slider_title'></div><div class='webix_slider_box'><div class='webix_slider_left'> </div><div class='webix_slider_right'></div><div class='webix_slider_handle' role='slider' aria-label='"+obj.label+(obj.title?(" "+obj.title(obj)):"")+"' aria-valuemax='"+obj.max+"' aria-valuemin='"+obj.min+"' aria-valuenow='"+obj.value+"' tabindex='0' id='"+id+"'> </div></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; //10 - padding of webix_slider_box ( 20 = 10*2 ) //8 - width of handle / 2 if(handle){ //view is rendered for sure var width = this._get_input_width(config); var value = config.value%config.step?(Math.round(config.value/config.step)*config.step):config.value; value = Math.max(Math.min(value,config.max),config.min); var max = config.max - config.min; var left = Math.ceil((width - 20) * (value-config.min) / max); var right = width-20-left; handle.style.left = 10 + left - 8 + "px"; handle.parentNode.style.width = width+"px"; //1px border right = Math.min(Math.max(right,2),width-22); left = Math.min(Math.max(left,2),width-22); //width for left and right bars var part = handle.previousSibling; part.style.width = right + "px"; var last = part.previousSibling; last.style.width = left + "px"; if (this._settings.title){ handle.parentNode.previousSibling.innerHTML = this._settings.title(this._settings, this); } } }, _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(){ 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)); }, _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; if(webix.env.touch){ pos = touchContext?touchContext.x: event.x; } else pos = webix.html.pos(event).x; return this._get_value_from_pos(pos); }, _get_value_from_pos:function(pos){ var config = this._settings; //10 - padding of slider box var max = config.max - config.min; var left = webix.html.offset(this._get_slider_handle().parentNode).x+10; var width = this._get_input_width(config)-20; var newvalue = (width?(pos-left) * max / width:0); newvalue = Math.round((newvalue+config.min)/config.step) * config.step; return Math.max(Math.min(newvalue, config.max), config.min); }, _init_onchange:function(){} //need not ui.text logic }, webix.ui.text); webix.protoUI({ name:"rangeslider", $cssName:"slider webix_rangeslider", defaults:{ separator: ",", value: "20,80", template:function(obj, common){ var id = "x" + webix.uid(); common._handle_id = [id+"_0",id+"_1"]; var aria = "role='slider' aria-label='"+obj.label+(obj.title?(" "+obj.title(obj)):"")+"' aria-valuemax='"+obj.max+"' aria-valuemin='"+obj.min+"' tabindex='0'"; var handles = "<div class='webix_slider_handle webix_slider_handle_0' id='"+common._handle_id[0]+"' "+aria+" aria-valuenow='"+obj.value[0]+"'> </div>"; handles += "<div class='webix_slider_handle webix_slider_handle_1' id='"+common._handle_id[1]+"' "+aria+" aria-valuenow='"+obj.value[1]+"'> </div>"; var html = "<div class='webix_slider_title'></div><div class='webix_slider_box'><div class='webix_slider_right'> </div><div class='webix_slider_left'></div>"+handles+"</div>"; return common.$renderInput(obj, html, id); } }, value_setter: function(value){ if(!webix.isArray(value)){ value = value.toString().split(this._settings.separator); } if(value.length <2) value[1] = value[0]; value[0] = parseFloat(value[0]); value[1] = parseFloat(value[1]); return value; }, _get_slider_handle:function(index){ index = index && index>=0?index:0; return this.$view.querySelector(".webix_slider_handle_"+(index||0)); }, _get_left_pos: function(width,index){ var config, max, value; config = this._settings; max = config.max - config.min; value = config.value[index]%config.step?(Math.round(config.value[index]/config.step)*config.step):config.value[index]; value = Math.max(Math.min(value,config.max),config.min); return Math.ceil((width - 20) * (value-config.min) / max); }, _set_inner_size:function(){ var config, handle0, handle1, left0, left1, parentBox, width; handle0 =this._get_slider_handle(0); handle1 = this._get_slider_handle(1); config = this._settings; if(!webix.isArray(config.value)){ this.define("value",config.value); } //10 - padding of webix_slider_box ( 20 = 10*2 ) //8 - width of handle / 2 if (handle0){ width = this._get_input_width(config); parentBox = handle0.parentNode; parentBox.style.width = width+"px"; left0 = this._get_left_pos(width, 0); left1 = this._get_left_pos(width, 1); handle0.style.left = 10 + left0 - 8 + "px"; handle1.style.left = 10 + left1 - 8 + "px"; parentBox.firstChild.style.width = width - 22+ "px"; parentBox.childNodes[1].style.width = left1 - left0 + "px"; parentBox.childNodes[1].style.left = left0+12 + "px"; if (this._settings.title){ handle0.parentNode.previousSibling.innerHTML = this._settings.title(this._settings, this); } } }, _set_value_now:function(){ for(var i=0; i<2; i++){ this._get_slider_handle(i).setAttribute("aria-valuenow", this._settings.value[i]); } }, _mouse_down_process: function(e){ var trg = e.target || e.srcElement; var match = /webix_slider_handle_(\d)/.exec(trg.className); this._activeIndex = match?parseInt(match[1],10):-1; if(match) this._set_handle_active(this._activeIndex); }, setValue:function(value){ var oldvalue = this._settings.value; var temp = (typeof value == "object"?value.join(this._settings.separator):value); if (oldvalue.join(this._settings.separator) == temp) return false; this._settings.value = value; if (this._rendered_input) this.$setValue(value); this.callEvent("onChange", [value, oldvalue]); }, $getValue:function(){ var value = this._settings.value; return this._settings.stringResult?value.join(this._settings.separator):value; }, _set_handle_active: function(index){ var hActive = this._get_slider_handle(index); var h = this._get_slider_handle(1-index); if(hActive.className.indexOf("webix_slider_active") == -1) hActive.className += " webix_slider_active"; h.className = h.className.replace(" webix_slider_active",""); }, _get_value_from_pos:function(pos){ var config = this._settings; var value = config.value; //10 - padding of slider box var max = config.max - config.min; var left = webix.html.offset(this._get_slider_handle().parentNode).x; var newvalue = Math.ceil((pos-left) * max / this._get_input_width(config)); newvalue = Math.round((newvalue+config.min)/config.step) * config.step; var index = null; var pos0 = webix.html.offset(this._get_slider_handle(0)).x; var pos1 = webix.html.offset(this._get_slider_handle(1)).x; if(pos0==pos1 && (config.value[0] == config.min || config.value[0] == config.max) ){ index = (config.value[0] == config.min?1:0); this._set_handle_active(index); } else{ if(this._activeIndex >=0){ index = this._activeIndex; }else{ if(pos0==pos1){ index = (pos < pos0?0:1); } else{ var dist0 = Math.abs(pos0-pos); var dist1 = Math.abs(pos1-pos); index = dist0<dist1?0:1; this._activeIndex = index; } } } if(index){ value[index] = Math.max(Math.min(newvalue, config.max), value[0]); } else{ value[index] = Math.max(Math.min(newvalue, value[1]), config.min); } return value; } }, webix.ui.slider); /* view.load("offline->some.php") or view.load( webix.proxy("offline", "some.php") ); or view.load( webix.proxy("offline", "post->url.php") ); */ webix.proxy.offline = { $proxy:true, storage: webix.storage.local, cache:false, data:"", _is_offline : function(){ if (!this.cache && !webix.env.offline){ webix.callEvent("onOfflineMode",[]); webix.env.offline = true; } }, _is_online : function(){ if (!this.cache && webix.env.offline){ webix.env.offline = false; webix.callEvent("onOnlineMode", []); } }, load:function(view, callback){ var mycallback = { error:function(){ //assuming offline mode var text = this.getCache() || this.data; var loader = { responseText: text }; var data = webix.ajax.prototype._data(loader); this._is_offline(); webix.ajax.$callback(view, callback, text, data, loader); }, success:function(text, data, loader){ this._is_online(); webix.ajax.$callback(view, callback, text, data, loader); this.setCache(text); } }; //in cache mode - always load data from cache if (this.cache && this.getCache()) mycallback.error.call(this); else { //else try to load actual data first if (this.source.$proxy) this.source.load(this, mycallback); else webix.ajax(this.source, mycallback, this); } }, getCache:function(){ return this.storage.get(this._data_name()); }, clearCache:function(){ this.storage.remove(this._data_name()); }, setCache:function(text){ this.storage.put(this._data_name(), text); }, _data_name:function(){ if (this.source.$proxy) return this.source.source + "_$proxy$_data"; else return this.source + "_$proxy$_data"; }, saveAll:function(view, update, dp, callback){ this.setCache(view.serialize()); webix.ajax.$callback(view, callback, "", update); }, result:function(id, master, dp, text, data){ for (var i = 0; i < data.length; i++) dp.processResult({ id: data[i].id, status: data[i].operation }, {}, {}); } }; webix.proxy.cache = { init:function(){ webix.extend(this, webix.proxy.offline); }, cache:true }; webix.proxy.local = { init:function(){ webix.extend(this, webix.proxy.offline); }, cache:true, data:[] }; if (window.angular) (function(){ function id_helper($element){ //we need uniq id as reference var id = $element.attr("id"); if (!id){ id = webix.uid(); $element.attr("id", id); } return id; } function locate_view_id($element){ if (typeof $element.attr("webix-ui") != "undefined") return $element.attr("id"); return locate_view_id($element.parent()); } //creates webix ui components angular.module("webix", []) .directive('webixUi', [ "$parse", function($parse) { return { restrict: 'A', scope: false, link:function ($scope, $element, $attrs, $controller){ var dataname = $attrs["webixUi"]; var callback = $attrs["webixReady"]; var watch = $attrs["webixWatch"]; var wxRoot = null; var id = id_helper($element); $element.ready(function(){ if (wxRoot) return; if (callback) callback = $parse(callback); //destruct components $element.bind('$destroy', function() { if(wxRoot) wxRoot.destructor(); }); //webix-ui attribute has some value - will try to use it as configuration if (dataname){ //configuration var watcher = function(data){ if (wxRoot) wxRoot.destructor(); if ($scope[dataname]){ var config = webix.copy($scope[dataname]); config.$scope =$scope; $element[0].innerHTML = ""; wxRoot = webix.ui(config, $element[0]); if (callback) callback($scope, { root: wxRoot }); } }; if (watch !== "false") $scope.$watch(dataname, watcher); watcher(); } else { //if webix-ui is empty - init inner content as webix markup if (!$attrs["view"]) $element.attr("view", "rows"); var ui = webix.markup; var tmp_a = ui.attribute; ui.attribute = ""; //FIXME - memory leaking, need to detect the moment of dom element removing and destroy UI if (typeof $attrs["webixRefresh"] != "undefined") wxRoot = ui.init($element[0], $element[0], $scope); else wxRoot = ui.init($element[0], null, $scope); ui.attribute = tmp_a; if (callback) callback($scope, { root: wxRoot }); } //size of ui $scope.$watch(function() { return $element[0].offsetWidth + "." + $element[0].offsetHeight; }, function() { if (wxRoot) wxRoot.adjust(); }); }); } }; }]) .directive('webixShow', [ "$parse", function($parse) { return { restrict: 'A', scope: false, link:function ($scope, $element, $attrs, $controller){ var attr = $parse($attrs["webixShow"]); var id = id_helper($element); if (!attr($scope)) $element.attr("hidden", "true"); $scope.$watch($attrs["webixShow"], function(){ var view = webix.$$(id); if (view){ if (attr($scope)){ webix.$$(id).show(); $element[0].removeAttribute("hidden"); } else webix.$$(id).hide(); } }); } }; }]) .directive('webixEvent', [ "$parse", function($parse) { var wrap_helper = function($scope, view, eventobj){ var ev = eventobj.split("="); var action = $parse(ev[1]); var name = ev[0].trim(); view.attachEvent(name, function(){ return action($scope, { id:arguments[0], details:arguments }); }); }; return { restrict: 'A', scope: false, link:function ($scope, $element, $attrs, $controller){ var events = $attrs["webixEvent"].split(";"); var id = id_helper($element); setTimeout(function(){ var first = $element[0].firstChild; if (first && first.nodeType == 1) id = first.getAttribute("view_id") || id; var view = webix.$$(id); for (var i = 0; i < events.length; i++) { wrap_helper($scope, view, events[i]); } }); } }; }]) .directive('webixElements', [ "$parse", function($parse) { return { restrict: 'A', scope: false, link:function ($scope, $element, $attrs, $controller){ var data = $attrs["webixElements"]; var id = id_helper($element); if ($scope.$watchCollection) $scope.$watchCollection(data, function(collection){ setTimeout(function(){ var view = webix.$$(id); if (view){ view.define("elements", collection); view.refresh(); } },1); }); } }; }]) .directive('webixData', [ "$parse", function($parse) { return { restrict: 'A', scope: false, link:function ($scope, $element, $attrs, $controller){ var data = $attrs["webixData"]; var id = id_helper($element); if ($scope.$watchCollection) $scope.$watchCollection(data, function(collection){ if (collection){ setTimeout(function(){ loadData($element, id, collection, 0); },1); } }); } }; }]); function loadData($element, id, collection, num){ if (num > 10) return; var first = $element[0].firstChild; if (first && first.nodeType == 1) id = first.getAttribute("view_id") || id; var view = webix.$$(id); if (view){ if (view.options_setter){ view.define("options", collection); view.refresh(); }else{ if (view.clearAll) view.clearAll(); view.parse(collection); } } else { webix.delay(loadData, this, [$element, id, collection], 100, num+1); } } })(); if (window.Backbone) (function(){ var cfg = { use_id : false }; function _start_ext_load(cal){ cal._backbone_loading = true; cal.callEvent("onBeforeLoad", []); cal.blockEvent(); } function _finish_ext_load(cal){ cal.unblockEvent(); cal._backbone_loading = false; cal.refresh(); } webix.attachEvent("onUnSyncUnknown", function(wData, bData){ var whandlers = wData._sync_events; var handlers = wData._sync_backbone_events; for (var i = 0; i < whandlers.length; i++) wData.detachEvent(whandlers[i]); for (var i = 0; i < handlers.length; i++) bData.off.apply(bData, handlers[i]); }); webix.attachEvent("onSyncUnknown", function(wData, bData, config){ if (config) cfg = config; if (cfg.get && typeof cfg.get == "string") cfg.get = cfg.get.split(","); //remove private properties function sanitize(ev){ if (cfg.use_id) return ev; var obj = {}; for (var key in ev) if (key != "id") obj[key] = ev[key]; return obj; } function _get_id(model){ return cfg.use_id ? model.id : model.cid; } function datareset(wData, bData){ var data = []; for (var i = 0; i < bData.models.length; i++){ var model = bData.models[i]; var cid = _get_id(model); var ev = copymodel(model); ev.id = cid; data.push(ev); } wData.clearAll(); wData._parse(data); } function copymodel(model){ if (cfg.get){ var data = {}; for (var i = 0; i < cfg.get.length; i++){ var key = cfg.get[i]; data[key] = model.get(key); } return data; } return model.toJSON(); } var handlers = [ ["change", function(model, info){ var cid = _get_id(model); var ev = wData.pull[cid] = copymodel(model); ev.id = cid; if (wData._scheme_update) wData._scheme_update(ev); wData.refresh(ev.id); }], ["remove", function(model, changes){ var cid = _get_id(model); if (wData.pull[cid]) wData.remove(cid); }], ["add", function(model, changes){ var cid = _get_id(model); if (!wData.pull[cid]){ var ev = copymodel(model); ev.id = cid; if (wData._scheme_init) wData._scheme_init(ev); wData.add(ev); } }], ["reset", function(model, changes){ datareset(wData, bData); }], ["request", function(obj){ if (obj instanceof Backbone.Collection) _start_ext_load(wData); }], ["sync", function(obj){ if (obj instanceof Backbone.Collection) _finish_ext_load(wData); }], ["error", function(obj){ if (obj instanceof Backbone.Collection) _finish_ext_load(wData); }] ]; for (var i = 0; i < handlers.length; i++) bData.bind.apply(bData, handlers[i]); var whandlers = [ wData.attachEvent("onAfterAdd", function(id){ if (!bData.get(id)){ var data = sanitize(wData.getItem(id)); var model = new bData.model(data); var cid = _get_id(model); if (cid != id) this.changeId(id, cid); bData.add(model); bData.trigger("webix:add", model); } return true; }), wData.attachEvent("onDataUpdate", function(id){ var ev = bData.get(id); var upd = sanitize(wData.getItem(id)); ev.set(upd); bData.trigger("webix:change", ev); return true; }), wData.attachEvent("onAfterDelete", function(id){ var model = bData.get(id); if (model){ bData.trigger("webix:remove", model); bData.remove(id); } return true; }) ]; wData._sync_source = bData; wData._sync_events = whandlers; wData._sync_backbone_events = handlers; if (bData.length || wData.count()){ datareset(wData, bData); } }); window.WebixView = Backbone.View.extend({ //starting from backbone 1.1, this.options is not saved automatically initialize : function (options) { this.options = options || {}; }, render:function(){ if (this.beforeRender) this.beforeRender.apply(this, arguments); var config = this.config || this.options.config; var el; if (!config.view || !webix.ui.hasMethod(config.view, "setPosition")){ el = window.$ ? $(this.el)[0] : this.el; //clear previous content if any if (el && !el.config) el.innerHTML = ""; } var ui = webix.copy(config); ui.$scope = this; this.root = webix.ui(ui, el); if (this.afterRender) this.afterRender.apply(this, arguments); return this; }, destroy:function(){ if (this.root) this.root.destructor(); }, getRoot:function(){ return this.root; }, getChild:function(id){ webix.assert(this.root.$$, "You need to set isolate property for top view"); return this.root.$$(id); } }); })(); 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 !== 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._known_value == value) return; this._known_value = value; if (this.config.mode == "extra") return this.setValueHere(value); this.removeSection(); var parts = value.split(this.config.separator); this.setValueHere.call(this, parts[0]); for (var i = 1; i<parts.length; i++){ var next = this.addSection(); webix.$$(next).setValueHere(parts[i]); } }, _subOnChange:function(call){ var parent = this.config.master ? webix.$$(this.config.master) : this; var newvalue = parent.getValue(); var oldvalue = parent._settings.value; if (newvalue !== oldvalue){ parent._settings.value = newvalue; parent.callEvent("onChange", [newvalue, oldvalue]); } }, addSection:function(){ var config = this.config, newConfig = { labelWidth: config.labelWidth, inputWidth: config.inputWidth, width: config.width, label: config.label ? " " : "", view: this.name, mode: "extra", value: "", icon: "minus-circle", suggest: config.suggest || null, master: config.id }; webix.extend(newConfig, config.subConfig||{},true); var newone = this.getParentView().addView(newConfig); webix.$$(newone).attachEvent("onChange", this._subOnChange); this._subs.push(newone); 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); } } }, 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; 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):(sizes.top+sizes.height+itemMargin) }; } 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; }, _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){ return webix.ui.baseview.prototype.$getSize.call(this, 0, 0); }, $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 sizes = view.$getSize(0,0); view.$setSize(sizes[0], sizes[2]); var node = view.$view; node.style.left = view._settings.left + "px"; node.style.top = view._settings.top + "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); }, _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){ if (id && mode === "update"){ //update mode, change only part of layout var obj = this.getItem(id); var index = this.getIndexById(id); this._fill_data(this._cells[index], obj); return; } //full repainting var cells = this._collection = []; var order = this.data.order; var subcount = this._origin_cells.length; 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(this._contentobj.addEventListener) this._contentobj.addEventListener('click', function(videoNode) { return function() { videoNode.play(); }; }(this._contentobj),false); */ 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: { padding:0, animate: true, position: "left", width: 200, borderless: true }, $init:function(){ this.$view.className += " webix_sidemenu"; }, 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({ destructor:function(){ this._last_active_node = null; } }); webix.attachEvent("onReconstruct", webixCustomScroll._on_reconstruct); webix.attachEvent("onResize", webixCustomScroll._on_reconstruct); //adjusts scroll after view repainting //for example, opening a branch in the tree //it will be better to handle onAfterRender of the related view webix.attachEvent("onClick", webixCustomScroll._on_reconstruct); }, resize:function(){ this._on_reconstruct(); }, _enable_datatable:function(view){ view._body._custom_scroll_view = view._settings.id; view.attachEvent("onAfterRender", function(){ var scroll = webixCustomScroll._get_datatable_sizes(this); var y = Math.max(scroll.dy - scroll.py, 0); var x = Math.max(scroll.dx - scroll.px, 0); if (this._y_scroll && this._scrollTop > y){ this._y_scroll.scrollTo(y); } else if (this._x_scroll && this._scrollLeft > x){ this._x_scroll.scrollTo(x); } if ( webixCustomScroll._last_active_node == this._body) webixCustomScroll._on_reconstruct(); }); webix.event(view._body, "mouseover", webixCustomScroll._mouse_in ); webix.event(view._body, "mouseout", webixCustomScroll._mouse_out ); }, enable:function(view, mode){ webixCustomScroll._init_once(); if (view.mapCells) return this._enable_datatable(view); var node = view; if (view._dataobj) node = view._dataobj.parentNode; node._custom_scroll_mode = mode||"xy"; webix.event(node, "mouseover", webixCustomScroll._mouse_in ); webix.event(node, "mouseout", webixCustomScroll._mouse_out ); webix.event(node, "mousewheel", webixCustomScroll._mouse_wheel ); webix.event(node, "DOMMouseScroll", webixCustomScroll._mouse_wheel ); // update scroll on data change this._setDataHandler(view); }, _on_reconstruct:function(){ var last = webixCustomScroll._last_active_node; if (last && last._custom_scroll_size){ webixCustomScroll._mouse_out_timed.call(last); webixCustomScroll._mouse_in.call(last); } }, _init_once:function(e){ webix.event(document.body, "mousemove", function(e){ if (webixCustomScroll._active_drag_area) webixCustomScroll._adjust_scroll(webixCustomScroll._active_drag_area, webixCustomScroll._active_drag_area._scroll_drag_pos, webix.html.pos(e)); }); webixCustomScroll._init_once = function(){}; }, _mouse_in:function(e){ webixCustomScroll._last_active_node = this; clearTimeout(this._mouse_out_timer); if (this._custom_scroll_size || webixCustomScroll._active_drag_area) return; var sizes; if (this._custom_scroll_view){ //ger related view var view = webix.$$(this._custom_scroll_view); //if view was removed, we need not scroll anymore if (!view) return; sizes = webixCustomScroll._get_datatable_sizes(view); } else{ sizes = { dx:this.scrollWidth, dy:this.scrollHeight, px:this.clientWidth, py:this.clientHeight }; sizes._scroll_x = sizes.dx > sizes.px && this._custom_scroll_mode.indexOf("x") != -1; sizes._scroll_y = sizes.dy > sizes.py && this._custom_scroll_mode.indexOf("y") != -1; } this._custom_scroll_size = sizes; if (sizes._scroll_x){ sizes._scroll_x_node = webixCustomScroll._create_scroll(this, "x", sizes.dx, sizes.px, "width", "height"); sizes._sx = (sizes.px - sizes._scroll_x_node.offsetWidth - 4); sizes._vx = sizes.dx - sizes.px; if(webixCustomScroll.trackBar) sizes._bar_x = webixCustomScroll._create_bar(this,"x"); } if (sizes._scroll_y){ sizes._scroll_y_node = webixCustomScroll._create_scroll(this, "y", sizes.dy, sizes.py, "height", "width"); sizes._sy = (sizes.py - sizes._scroll_y_node.offsetHeight - 4); sizes._vy = sizes.dy - sizes.py; if(webixCustomScroll.trackBar) sizes._bar_y = webixCustomScroll._create_bar(this,"y"); } webixCustomScroll._update_scroll(this); }, _create_bar: function(node, mode){ var bar = webix.html.create("DIV", { "webixignore":"1", "class":"webix_c_scroll_bar_"+mode },""); node.appendChild(bar); return bar; }, _adjust_scroll:function(node, old, pos){ var config = node._custom_scroll_size; var view = node._custom_scroll_view; if (view) view = webix.$$(view); if (config._scroll_x_node == node._scroll_drag_enabled){ var next = (pos.x - old.x)*config._vx/config._sx; if (view) view._x_scroll.scrollTo(view._scrollLeft+next); else webixCustomScroll._set_scroll_value(node, "scrollLeft", next); } if (config._scroll_y_node == node._scroll_drag_enabled){ var next = (pos.y - old.y)*config._vy/config._sy; if (view) view._y_scroll.scrollTo(view._scrollTop+next); else webixCustomScroll._set_scroll_value(node, "scrollTop", next); } node._scroll_drag_pos = pos; webixCustomScroll._update_scroll(node); }, _get_datatable_sizes:function(view){ var sizes = {}; if (view._x_scroll && view._settings.scrollX){ sizes.dx = view._x_scroll._settings.scrollWidth; sizes.px = view._x_scroll._last_set_size || 1; sizes._scroll_x = sizes.dx - sizes.px > 1; } if (view._y_scroll && view._settings.scrollY){ sizes.dy = view._y_scroll._settings.scrollHeight; sizes.py = view._y_scroll._last_set_size || 1; sizes._scroll_y = sizes.dy - sizes.py > 1; } return sizes; }, _mouse_out:function(){ clearTimeout(this._mouse_out_timer); this._mouse_out_timer = webix.delay(webixCustomScroll._mouse_out_timed, this, [], 200); }, _removeScroll:function(scroll){ if (scroll){ webix.html.remove(scroll); if (scroll._webix_event_sc1){ webix.eventRemove(scroll._webix_event_sc1); webix.eventRemove(scroll._webix_event_sc2); } } }, _mouse_out_timed:function(){ if (this._custom_scroll_size){ if (this._scroll_drag_enabled){ this._scroll_drag_released = true; return; } var sizes = this._custom_scroll_size; webixCustomScroll._removeScroll(sizes._scroll_x_node); webixCustomScroll._removeScroll(sizes._scroll_y_node); webix.html.removeCss(document.body,"webix_noselect"); if(sizes._bar_x){ webix.html.remove(sizes._bar_x); } if(sizes._bar_y){ webix.html.remove(sizes._bar_y); } this._custom_scroll_size = null; } }, _mouse_wheel:function(e){ var sizes = this._custom_scroll_size; var delta = e.wheelDelta/-40; var toblock = true; if (!delta && e.detail && webix.isUndefined(e.wheelDelta)) delta = e.detail; if (sizes){ if (sizes._scroll_x_node && (e.wheelDeltaX || ( delta && !sizes._scroll_y_node ))){ var x_dir = (e.wheelDeltaX/-40)||delta; //see below toblock = webixCustomScroll._set_scroll_value(this, "scrollLeft", x_dir*webixCustomScroll.scrollStep); } else if (delta && sizes._scroll_y_node){ //lesser flickering of scroll in IE //also prevent scrolling outside of borders because of scroll-html-elements toblock = webixCustomScroll._set_scroll_value(this, "scrollTop", delta*webixCustomScroll.scrollStep); } } webixCustomScroll._update_scroll(this); if (toblock !== false) return webix.html.preventEvent(e); }, _set_scroll_value:function(node, pose, value){ var sizes = node._custom_scroll_size; var max_scroll = (pose == "scrollLeft") ? (sizes.dx - sizes.px) : (sizes.dy - sizes.py); var now = node[pose]; if (now+value > max_scroll) value = max_scroll - now; if (!value || (now+value < 0 && now === 0)) return false; if (webix.env.isIE){ webixCustomScroll._update_scroll(node, pose, value + now); node[pose] += value; } else node[pose] += value; return true; }, _create_scroll:function(node, mode, dy, py, dim, pos){ var scroll = webix.html.create("DIV", { "webixignore":"1", "class":"webix_c_scroll_"+mode },"<div></div>"); scroll.style[dim] = Math.max((py*py/dy-7),40)+"px"; node.style.position = "relative"; node.appendChild(scroll); node._webix_event_sc1 = webix.event(scroll, "mousedown", webixCustomScroll._scroll_drag(node)); node._webix_event_sc2 = webix.event(document.body, "mouseup", webix.bind(webixCustomScroll._scroll_drop, node)); return scroll; }, _scroll_drag:function(node){ return function(e){ webix.html.addCss(document.body,"webix_noselect",1); this.className += " webix_scroll_active"; webixCustomScroll._active_drag_area = node; node._scroll_drag_enabled = this; node._scroll_drag_pos = webix.html.pos(e); }; }, _scroll_drop:function(node){ if (this._scroll_drag_enabled){ webix.html.removeCss(document.body,"webix_noselect"); this._scroll_drag_enabled.className = this._scroll_drag_enabled.className.toString().replace(" webix_scroll_active",""); this._scroll_drag_enabled = false; webixCustomScroll._active_drag_area = 0; if (this._scroll_drag_released){ webixCustomScroll._mouse_out_timed.call(this); this._scroll_drag_released = false; } } }, _update_scroll:function(node, pose, value){ var sizes = node._custom_scroll_size; if (sizes && (sizes._scroll_x_node||sizes._scroll_y_node)){ var view = node._custom_scroll_view; var left_scroll = pose == "scrollLeft" ? value : node.scrollLeft; var left = view?webix.$$(view)._scrollLeft:left_scroll; var shift_left = view?0:left; var top_scroll = pose == "scrollTop" ? value : node.scrollTop; var top = view?(webix.$$(view)._scrollTop):top_scroll; var shift_top = view?0:top; if (sizes._scroll_x_node){ sizes._scroll_x_node.style.bottom = 1 - shift_top + "px"; sizes._scroll_x_node.style.left = Math.round(sizes._sx*left/(sizes.dx-sizes.px)) + shift_left + 1 +"px"; if(sizes._bar_x){ sizes._bar_x.style.bottom = 1 - shift_top + "px"; sizes._bar_x.style.left = shift_left + "px"; } } if (sizes._scroll_y_node){ sizes._scroll_y_node.style.right = 0 - shift_left + "px"; sizes._scroll_y_node.style.top = Math.round(sizes._sy*top/(sizes.dy-sizes.py)) + shift_top + 1 + "px"; if(sizes._bar_y){ sizes._bar_y.style.right = 0 - shift_left + "px"; sizes._bar_y.style.top = shift_top + "px"; } } } }, _setDataHandler: function(view){ if(view.data && view.data.attachEvent) view.data.attachEvent("onStoreUpdated", function(){ var node = webixCustomScroll._last_active_node; if(node && view.$view.contains(node)) webixCustomScroll.resize(); }); } }; })(); webix.protoUI({ name:"portlet", defaults:{ layoutType:"wide", }, $init:function(config){ this._viewobj.style.position = "relative"; if (config.header && config.body) config.body = [ { template:config.header, type:"header" }, config.body ]; this.$ready.push(this._init_drag_area); // refresh scroll state of datatables webix.attachEvent("onAfterPortletMove", this._refreshChildScrolls); }, _refreshChildScrolls: function(source){ webix.ui.each(source, function(view){ if(view._restore_scroll_state) view._restore_scroll_state(); }); }, _init_drag_area:function(){ var childs = this.getChildViews(); if (childs.length > 1) webix.DragControl.addDrag(childs[0].$view, this); else { var drag = webix.html.create("div", { "class":"portlet_drag" }, "<span class='webix_icon fa-bars'></span>"); this._viewobj.appendChild(drag); webix.DragControl.addDrag(drag, this); } }, body_setter:function(value){ return this.rows_setter(webix.isArray(value) ? value:[value]); }, markDropArea:function(target, mode){ if (!target) return webix.html.remove(this._markerbox); target = webix.$$(target); if (!this._markerbox) this._markerbox = webix.html.create("div",null," "); target.$view.appendChild(this._markerbox); this._markerbox.className = "portlet_marker"+mode; }, movePortlet:function(target, mode){ var parent = target.getParentView(); var source = this.getParentView(); var tindex = parent.index(target); var sindex = source.index(this); if (!webix.callEvent("onBeforePortletMove", [source, parent, this, target, mode])) return; webix.ui.$freeze = true; var shift = (source != parent ? 1 : 0); var isv = parent._vertical_orientation; if ((mode == "top" || mode == "bottom")){ if (isv !== 1){ parent = webix.ui({ type:target._settings.layoutType, rows:[] }, parent, tindex+shift); webix.ui(target, parent, 0); tindex = 0; shift = 1; } if (mode == "bottom") shift+=1; } else if ((mode == "left" || mode == "right")){ if (isv !== 0){ parent = webix.ui({ type:target._settings.layoutType, cols:[] }, parent, tindex+shift); webix.ui(target, parent, 0); tindex = 0; shift = 1; } if (mode == "right") shift+=1; } if (sindex < tindex) shift -= 1; webix.ui(this, parent, tindex+shift ); if (mode == "replace") webix.ui(target, source, sindex); this._removeEmptySource(source); webix.ui.$freeze = false; var tops = source.getTopParentView(); var topt = target.getTopParentView(); topt.resize(); if (tops != topt) tops.resize(); // //fix issue in Chrome [TEMP-FIX] // var topparent = parent && parent.$view.parentNode; // if (topparent && topparent.style.position != "absolute") // topparent.style.position = "relative"; 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(){ webix.toPNG = function(id, name){ var defer = webix.promise.defer(); webix.require(webix.cdn + "/extras/html2canvas.min.js", function(){ var view = webix.$$(id); if (view && view.$exportView) view = view.$exportView({}); var node = view ? view.$view : webix.toNode(id); var filename = (name||"data")+".png"; window.html2canvas(node).then(function(canvas) { var data = canvas.msToBlob?canvas.msToBlob():canvas.toDataURL("image/png"); webix.html.download(data, filename); canvas.remove(); defer.resolve(); }); }); return defer; }; webix.toExcel = function(id, options){ var defer = webix.promise.defer(); var view = webix.$$(id); options = options || {}; if (view.$exportView) view = view.$exportView(options); webix.require(webix.cdn + "/extras/xlsx.core.min.js", function(){ options._export_mode = "excel"; var scheme = getExportScheme(view, options); var result = getExportData(view, options, scheme); var spans = options.spans ? getSpans(view, options) : []; var data = getExcelData(result, scheme, spans); var wb = { SheetNames:[], Sheets:[]}; var name = options.name || "Data"; name = name.replace(/[\*\?\:\[\]\\\/]/g,"").substring(0, 31); wb.SheetNames.push(name); wb.Sheets[name] = data; var xls = XLSX.write(wb, {bookType:'xlsx', bookSST:false, type: 'binary'}); var filename = (options.filename || name)+".xlsx"; var blob = new Blob([str2array(xls)], { type: "application/xlsx" }); webix.html.download(blob, filename); defer.resolve(); }); return defer; }; var font; webix.toPDF = function(id, options){ var defer = webix.promise.defer(); webix.require(webix.cdn + "/extras/pdfjs.js", function(){ var view = webix.$$(id); options = options || {}; if (view.$exportView) view = view.$exportView(options); 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" }); webix.html.download(blob, filename); defer.resolve(); }; 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 getExportScheme(view, options){ var scheme = []; var h_count = 0, f_count = 0; var isTable = view.getColumnConfig; var columns = options.columns; var raw = !!options.rawValues; if (!columns){ if (isTable){ columns = view._columns_pull; } else { columns = webix.copy(view.data.pull[view.data.order[0]]); for (var key in columns) columns[key] = true; delete columns.id; } } if (options.id) scheme.push({ id:"id", width:50, header:" ", template:function(obj){ return obj.id; } }); for (var key in columns){ var column = columns[key]; 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 ? null : column.template) || function(key){return function(obj){ return obj[key]; };}(key)), 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); } //normalize headers and footers 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.header !== false && scheme.length && options._export_mode === "excel"){ 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 = header.replace(htmlFilter, ""); headers.push(header); } data.push(headers); } } var isTree = (view.data.name == "TreeStore"); view.data.each(function(item){ if(item){ //dyn loading var line = []; for (var i = 0; i < scheme.length; i++){ var column = scheme[i]; var 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>/, " - "); cell = cell.replace(htmlFilter, ""); } line.push(cell); } 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 = footer.replace(htmlFilter, ""); footers.push(footer); } if(options._export_mode === "excel") data.push(footers); } } 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 pull = view._spans_pull; var spans = []; if(pull){ //correction for spreadsheet 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); //+1 to exclude excel header spans.push({ s:{c:sc, r:sr+1}, e:{c:ec, r:er+1} }); } } } return spans; } var table = "_table"; function getExcelData(data, scheme, spans) { 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.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 cell.t = 's'; ws[cell_ref] = cell; } } if(range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range); 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(); } //doc header, configurable if(options.docHeader){ if(typeof options.docHeader == "string") options.docHeader = {text:options.docHeader}; var docHeaderOps = webix.extend(options.docHeader, { color: 0x666666, textAlign:"right" }); var hd = doc.header({paddingBottom:10}); hd.text(docHeaderOps.text, docHeaderOps); } if (options.docHeaderImage){ if(typeof options.docHeaderImage == "string") options.docHeaderImage = {url:options.docHeaderImage}; var hd = doc.header({paddingBottom:10}); var docImageOps = webix.extend(options.docHeaderImage, { align:"right" }); pdfjs.load(options.docHeaderImage.url, function(err, buffer){ if (!err){ var img = new pdfjs.Image(buffer); var image = hd.image(img, docImageOps); } //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.$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 }; // 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(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(){ 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 = 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; }, 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"; if (!header){ header = webix.copy(obj.data[0]); for (var i = 0; i < header.length; i++) header[i] = { header:letters[i], id:"data"+i, adjust:true, 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, adjust:true, editor:"text", sort:"string" }; } else header = webix.copy(header); this.config.columns = header; this.refreshColumns(); this.parse(obj.data, this._settings.datatype); } }, webix.ui.datatable); webix.DataDriver.excel = webix.extend({ toObject:function(data){ if(!data.length){ 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 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/"+ext+".core.min.js").then(webix.bind(function(){ var workbook = (ext == "xls") ? XLS.read(arr.join(""), {type: 'binary'}) : XLSX.read(arr.join(""), {type: 'binary'}); var res = { sheets: workbook.Sheets, names: workbook.SheetNames, options:options }; 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 = []; if(sheet["!ref"]){ var range = XLS.utils.decode_range(sheet["!ref"]), row, col, cellCoord, cell; 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 nrow.push(options.math&&cell.f ? (cell.f.charAt(0)=="="?cell.f:"="+cell.f) : cell.v); } all.push(nrow); } } if(sheet["!merges"]){ var merges = sheet["!merges"]; var full = {data:all, spans:[]}; for(var i = 0; i<merges.length; i++){ var s = merges[i].s; var e = merges[i].e; var sl = options.rows?options.rows[0]:0; spans.push([s.r+sl, s.c, e.c-s.c+1, e.r-s.r+1]); } } return { data:all, spans: spans, 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] || 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._renderSelectAreas(); }); this.attachEvent("onAfterRender", function(){ this._renderSelectAreas(); }); 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){ var sci, eci, sri, eri, ci, ri; 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); if(sci > ci) sci = ci; else if(eci < ci) eci = ci; if(sri > ri) sri = ri; else if(eri < ri) eri = ri; this._updateSelectArea(area.name, {row: this.getIdByIndex(sri),column: this.columnId(sci)}, {row: this.getIdByIndex(eri),column: this.columnId(eci)} ); } }, _updateSelectArea: function(name, start, end){ var area = this._areaSelStorage[name]; if(!area) return false; webix.extend(area, { start: start||area.start, end: end||area.end }, true); this._renderSelectAreas(); }, 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; if(this.callEvent("onBeforeAreaAdd",[area])){ this._lastDefArea = name; if(!preserve) this.removeSelectArea(); this._areaSelStorage[area.name] = area; this._selected_areas.push(area); this._renderSelectAreas(); 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; }, _renderSelectAreas: 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); 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 startIndex, endIndex, r0, r1, c0, c1; startIndex = this.getIndexById(range.start.row); endIndex = this.getIndexById(range.end.row); 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)); startIndex = this.getColumnIndex(range.start.column); endIndex = this.getColumnIndex(range.end.column); c0 = Math.max(xr[0],startIndex); c1 = Math.min(this._rightSplit?xr[1]-1:xr[1],endIndex); 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_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"), "Not suppored chart type"); if(!this._map._areas.length || this._frame) return; var bounds = this._getChartBounds(this._content_width,this._content_height); this._mapStart = bounds.start; this._mapEnd = bounds.end; 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._handle_radius = this._rHandle.clientWidth/2; 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)); }, 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(){ 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(), "input", function(){ this.config.value = this.getInputNode().innerHTML; }, { bind: top, id:"_rtxt_"+this.config.id } ); 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.getTopParentView(); 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 (this.$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; 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 (!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(); }, setValue: function(value) { var old = this.config.value; this.config.value = value || ""; if (old !== value) this.callEvent("onChange", [value, old]); this.refresh(); }, getValue: function() { return this.config.value || ""; } }, webix.IdSpace, webix.ui.layout); webix.protoUI({ name: "gage", defaults: { value: 0, minRange: 0, maxRange: 100, minWidth:250, minHeight:200, label: "", placeholder: "", 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; 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); }, _draw_line: function(radius) { var svgCoord = this.$width, arrowSpace = 0.05, currentChartValue = this.config.value - this.config.minRange, 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 = 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() { if (this.config.value >= this.config.minRange && this.config.value <= this.config.maxRange) { webix.assert(this.config.minRange < this.config.maxRange, "Invalid Range Values"); this.gradientLength = (this.config.value - this.config.minRange) / (this.config.maxRange - this.config.minRange); } else { webix.assert(this.config.minRange < this.config.maxRange, "Invalid Range Values"); } 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);