From 5d5dc5442b1515ba3292261e83586e4b897e4281 Mon Sep 17 00:00:00 2001 From: sbosse Date: Mon, 21 Jul 2025 23:21:01 +0200 Subject: [PATCH] Mon 21 Jul 22:43:21 CEST 2025 --- js/ui/webui/webix/webix.js | 33933 +++++++++++++++++++++++++++++++++++ 1 file changed, 33933 insertions(+) create mode 100644 js/ui/webui/webix/webix.js diff --git a/js/ui/webui/webix/webix.js b/js/ui/webui/webix/webix.js new file mode 100644 index 0000000..6eb92c0 --- /dev/null +++ b/js/ui/webui/webix/webix.js @@ -0,0 +1,33933 @@ +/* +@license +webix UI v.3.3.17fix001 +This software is allowed to use under GPL or you need to obtain Commercial License + to use it in non-GPL project. Please contact sales@webix.com for details +*/ +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; i1){ + 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 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=0) this.splice(pos,(len||1)); + }, + //find element in collection and remove it + remove:function(value){ + this.removeAt(this.find(value)); + }, + //add element to collection at specific position + insertAt:function(data,pos){ + if (!pos && pos!==0) //add to the end by default + this.push(data); + else { + var b = this.splice(pos,(this.length-pos)); + this[pos] = data; + this.push.apply(this,b); //reconstruct array without loosing this pointer + } + }, + //return index of element, -1 if it doesn't exists + find:function(data){ + for (var i=0; i -1){ + 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"); +})(); + + +//html helpers +webix.html={ + _native_on_selectstart:0, + 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){ + var style = this._style_element; + if(!style){ + style = this._style_element = document.createElement("style"); + style.setAttribute("type", "text/css"); + style.setAttribute("media", "screen"); + document.getElementsByTagName("head")[0].appendChild(style); + } + /*IE8*/ + if (style.styleSheet) + style.styleSheet.cssText += rule; + else + style.appendChild(document.createTextNode(rule)); + }, + 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; + }, + //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; + } +}; + +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": ">", + '"': """, + "'": "'", + "`": "`" +}; +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; + 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, data, callback); + } else if (mode == "delete") { + ajax.del(url + data.id, 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.sync(data); + + this.callEvent("onBeforeLoad",[]); + this.data.driver = webix.DataDriver[type||"json"]; + this._onLoad(data,null); + }, + _parse:function(data){ + var driver = this.data.driver; + var parsed = driver.getDetails(driver.getRecords(data)[0]); + 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; inot 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); + }; + + //we want to refresh list after data loading if it has master link + //in same time we do not want such operation for dataFeed components + //as they are reloading data as response to the master link + if (!config.dataFeed && this.loadNext) + this.data.attachEvent("onStoreLoad", webix.bind(function(){ + if (this._bind_source) + webix.$$(this._bind_source)._bind_updated[this._settings.id] = false; + }, this)); + + 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") + return format.call(target, data, this); + else + return target.data.importData(data?data[format]:[]); + } + + 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 + }, + _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) { + 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:{ + + 190:46 + }, + _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.tabControl){ + // tab pressed + if (code === 9 && !ctrl && !alt && !meta) { + this._focus_logic(!shift ? "getNext" : "getPrev"); + webix.html.preventEvent(e); + } + } + 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 && next._settings.tabFocus && 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; + } + } + + }); + 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(){ + if(!webix.UIManager.tabControl) + webix.UIManager.setFocus(webix.$$(document.activeElement), true); + },1); + }); +}); + +webix.IdSpace = { + $init:function(){ + this._elements = {}; + this._translate_ids = {}; + this.getTopParentView = 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.getTopParentView; + } + + 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 "
"+content+"
"; + }; + + // 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" }[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; ix) x = sizes[0]; + //minHeight + if (sizes[2]>y) y = sizes[2]; + + //maxWidth rule + if (x>sizes[1]) x = sizes[1]; + //maxHeight rule + if (y>sizes[3]) y = sizes[3]; + + this.$setSize(x,y); + }, + resize:function(force){ + if (webix._child_sizing_active || webix.ui.$freeze) 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 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.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); + + 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.

You have missed rows|cols|cells|elements collection"); + for (var i=0; i