/* Product Name: dhtmlxSuite Version: 4.0.3 Edition: Professional License: content of this file is covered by DHTMLX Commercial or Enterprise license. Usage without proper license is prohibited. To obtain it contact sales@dhtmlx.com Copyright UAB Dinamenta http://www.dhtmlx.com */ /* DHX DEPEND FROM FILE 'pager.js'*/ /* UI:paging control */ /*DHX:Depend template.js*/ dhtmlx.ui.pager=function(container){ this.name = "Pager"; if (dhtmlx.assert_enabled()) this._assert(); dhtmlx.extend(this, dhtmlx.Settings); this._parseContainer(container,"dhx_pager"); dhtmlx.extend(this, dhtmlx.EventSystem); dhtmlx.extend(this, dhtmlx.SingleRender); dhtmlx.extend(this, dhtmlx.MouseEvents); this._parseSettings(container,{ size:10, //items on page page:-1, //current page group:5, //pages in group count:0, //total count of items type:"default" }); this.data = this._settings; this.refresh(); }; dhtmlx.ui.pager.prototype={ _id:"dhx_p_id", on_click:{ //on paging button click "dhx_pager_item":function(e,id){ this.select(id); } }, select:function(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; if (this.callEvent("onBeforePageChange",[this._settings.page,id])){ this.data.page = id*1; //must be int this.refresh(); this.callEvent("onAfterPageChange",[id]); } }, types:{ "default":{ template:dhtmlx.Template.fromHTML("{common.pages()}"), //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; 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":"")}); return html; }, page:function(obj){ return obj.page+1; }, //go-to-first page button first:function(){ return this.button({ id:"first", index:" << ", selected:""}); }, //go-to-last page button last:function(){ return this.button({ id:"last", index:" >> ", selected:""}); }, //go-to-prev page button prev:function(){ return this.button({ id:"prev", index:"<", selected:""}); }, //go-to-next page button next:function(){ return this.button({ id:"next", index:">", selected:""}); }, button:dhtmlx.Template.fromHTML("
{obj.index}
") } }, //update settings and repaint self refresh:function(){ var s = this._settings; //max page number s.limit = Math.ceil(s.count/s.size); //correct page if it is out of limits if (s.limit && s.limit != s.old_limit) s.page = Math.min(s.limit-1, s.page); var id = s.page; if (id!=-1 && (id!=s.old_page) || (s.limit != s.old_limit)){ //refresh self only if current page or total limit was changed this.render(); this.callEvent("onRefresh",[]); s.old_limit = s.limit; //save for onchange check in next iteration s.old_page = s.page; } }, template_item_start:dhtmlx.Template.fromHTML("
"), template_item_end:dhtmlx.Template.fromHTML("
") }; /* DHX DEPEND FROM FILE 'dataprocessor_hook.js'*/ /* Behaviour:DataProcessor - translates inner events in dataprocessor calls @export changeId setItemStyle setUserData getUserData */ /*DHX:Depend compatibility.js*/ /*DHX:Depend dhtmlx.js*/ dhtmlx.DataProcessor={ //called from DP as part of dp.init _dp_init:function(dp){ //map methods var varname = "_methods"; dp[varname]=["setItemStyle","","changeId","remove"]; //after item adding - trigger DP this.attachEvent("onAfterAdd",function(id){ dp.setUpdated(id,true,"inserted"); }); this.data.attachEvent("onStoreLoad",dhtmlx.bind(function(driver, data){ if (driver.getUserData) driver.getUserData(data,this._userdata); },this)); //after item deleting - trigger DP this.attachEvent("onBeforeDelete",function(id){ if (dp._silent_mode) return true; var z=dp.getState(id); if (z=="inserted") { dp.setUpdated(id,false); return true; } if (z=="deleted") return false; if (z=="true_deleted") return true; dp.setUpdated(id,true,"deleted"); return false; }); //after editing - trigger DP this.attachEvent("onAfterEditStop",function(id){ dp.setUpdated(id,true,"updated"); }); this.attachEvent("onBindUpdate",function(data){ window.setTimeout(function(){ dp.setUpdated(data.id,true,"updated"); },1); }); varname = "_getRowData"; //serialize item's data in URL dp[varname]=function(id,pref){ var ev=this.obj.data.get(id); var data = {}; for (var a in ev){ if (a.indexOf("_")===0) continue; data[a]=ev[a]; } return data; }; varname = "_clearUpdateFlag"; dp[varname]=function(){}; this._userdata = {}; dp.attachEvent("insertCallback", this._dp_callback); dp.attachEvent("updateCallback", this._dp_callback); dp.attachEvent("deleteCallback", function(upd, id) { this.obj.setUserData(id, this.action_param, "true_deleted"); this.obj.remove(id); }); //enable compatibility layer - it will allow to use DP without dhtmlxcommon dhtmlx.compat("dataProcessor",dp); }, _dp_callback:function(upd,id){ this.obj.data.set(id,dhtmlx.DataDriver.xml.getDetails(upd.firstChild)); this.obj.data.refresh(id); }, //marks item in question with specific styles, not purposed for public usage setItemStyle:function(id,style){ var node = this._locateHTML(id); if (node) node.style.cssText+=";"+style; //style is not persistent }, //change ID of item changeId:function(oldid, newid){ this.data.changeId(oldid, newid); this.refresh(); }, //sets property value, not purposed for public usage setUserData:function(id,name,value){ if (id) this.data.get(id)[name]=value; else this._userdata[name]=value; }, //gets property value, not purposed for public usage getUserData:function(id,name){ return id?this.data.get(id)[name]:this._userdata[name]; } }; (function(){ var temp = "_dp_init"; dhtmlx.DataProcessor[temp]=dhtmlx.DataProcessor._dp_init; })(); /* DHX DEPEND FROM FILE 'compatibility_drag.js'*/ /* Compatibility hack for DND Allows dnd between dhtmlx.dnd and dhtmlxcommon based dnd When dnd items - related events will be correctly triggered. onDrag event must define final moving logic, if it is absent - item will NOT be moved automatically to activate this functionality , next command need to be called dhtmlx.compat("dnd"); */ /*DHX:Depend compatibility.js*/ dhtmlx.compat.dnd = function(){ //if dhtmlxcommon.js included on the page if (window.dhtmlDragAndDropObject){ var _dragged = "_dragged"; //fake for code compression utility, do not change! //wrap methods of dhtmlxcommon to inform dhtmlx.dnd logic var old_ocl = dhtmlDragAndDropObject.prototype.checkLanding; dhtmlDragAndDropObject.prototype.checkLanding=function(node,e,skip){ old_ocl.apply(this,arguments); if (!skip){ var c = dhtmlx.DragControl._drag_context = dhtmlx.DragControl._drag_context||{}; if (!c.from) c.from = this.dragStartObject; dhtmlx.DragControl._checkLand(node,e,true); } }; var old_odp = dhtmlDragAndDropObject.prototype.stopDrag; dhtmlDragAndDropObject.prototype.stopDrag=function(e,dot,skip){ if (!skip){ if (dhtmlx.DragControl._last){ dhtmlx.DragControl._active = dragger.dragStartNode; dhtmlx.DragControl._stopDrag(e,true); } } old_odp.apply(this,arguments); }; //pre-create dnd object from dhtmlxcommon var dragger = new dhtmlDragAndDropObject(); //wrap drag start process var old_start = dhtmlx.DragControl._startDrag; dhtmlx.DragControl._startDrag=function(){ old_start.apply(this,arguments); //build list of IDs and fake objects for dhtlmxcommon support var c = dhtmlx.DragControl._drag_context; if (!c) return; var source = []; var tsource = []; for (var i=0; i < c.source.length; i++){ source[i]={idd:c.source[i]}; tsource.push(c.source[i]); } dragger.dragStartNode = { parentNode:{}, parentObject:{ idd:source, id:(tsource.length == 1?tsource[0]:tsource), treeNod:{ object:c.from } } }; //prevent code compression of "_dragged" dragger.dragStartNode.parentObject.treeNod[_dragged]=source; dragger.dragStartObject = c.from; }; //wrap drop landing checker var old_check = dhtmlx.DragControl._checkLand; dhtmlx.DragControl._checkLand = function(node,e,skip){ old_check.apply(this,arguments); if (!this._last && !skip){ //we are in middle of nowhere, check old drop landings node = dragger.checkLanding(node,e,true); } }; //wrap drop routine var old_drop = dhtmlx.DragControl._stopDrag; dhtmlx.DragControl._stopDrag=function(e,skip){ old_drop.apply(this,arguments); if (dragger.lastLanding && !skip) dragger.stopDrag(e,false,true); }; //extend getMaster, so they will be able to recognize master objects from dhtmlxcommon.js var old_mst = dhtmlx.DragControl.getMaster; dhtmlx.DragControl.getMaster = function(t){ var master = null; if (t) master = old_mst.apply(this,arguments); if (!master){ master = dragger.dragStartObject; var src = []; var from = master[_dragged]; for (var i=0; i < from.length; i++) { src.push(from[i].idd||from[i].id); } dhtmlx.DragControl._drag_context.source = src; } return master; }; } }; /* DHX DEPEND FROM FILE 'move.js'*/ /* Behavior:DataMove - allows to move and copy elements, heavily relays on DataStore.move @export copy move */ dhtmlx.DataMove={ _init:function(){ dhtmlx.assert(this.data, "DataMove :: Component doesn't have DataStore"); }, //creates a copy of the item copy:function(sid,tindex,tobj,tid){ var data = this.get(sid); if (!data){ dhtmlx.log("Warning","Incorrect ID in DataMove::copy"); return; } //make data conversion between objects if (tobj){ dhtmlx.assert(tobj.externalData,"DataMove :: External object doesn't support operation"); data = tobj.externalData(data); } tobj = tobj||this; //adds new element same as original return tobj.add(tobj.externalData(data,tid),tindex); }, //move item to the new position move:function(sid,tindex,tobj,tid){ //can process an arrya - it allows to use it from onDrag if (sid instanceof Array){ 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 new_index = (tobj||this).indexById(this.move(sid[i], tindex, tobj, dhtmlx.uid())); if (sid[i+1]) tindex = new_index+(this.indexById(sid[i+1])"+s.innerHTML+""; } }; /* DHX DEPEND FROM FILE 'drag.js'*/ /* 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 */ /*DHX:Depend dnd.js*/ /*DHX:Depend move.js*/ /*DHX:Depend compatibility_drag.js*/ /*DHX:Depend dhtmlx.js*/ dhtmlx.DragItem={ _init:function(){ dhtmlx.assert(this.move,"DragItem :: Component doesn't have DataMove interface"); dhtmlx.assert(this.locate,"DragItem :: Component doesn't have RenderStack interface"); dhtmlx.assert(dhtmlx.DragControl,"DragItem :: DragControl is not included"); if (!this._settings || this._settings.drag) dhtmlx.DragItem._initHandlers(this); else if (this._settings){ //define setter, which may be triggered by config call this.drag_setter=function(value){ if (value){ this._initHandlers(this); delete this.drag_setter; //prevent double initialization } return value; }; } //if custom dnd marking logic is defined - attach extra handlers if (this.dragMarker){ this.attachEvent("onBeforeDragIn",this.dragMarker); this.attachEvent("onDragOut",this.dragMarker); } }, //helper - defines component's container as active zone for dragging and for dropping _initHandlers:function(obj){ dhtmlx.DragControl.addDrop(obj._obj,obj,true); dhtmlx.DragControl.addDrag(obj._obj,obj); }, /* 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 onDragIn:function(s,t,e){ var id = this.locate(e) || null; var context = dhtmlx.DragControl._drag_context; var to = dhtmlx.DragControl.getMaster(s); //previous target var html = (this._locateHTML(id)||this._obj); //prevent double processing of same target if (html == dhtmlx.DragControl._landing) return html; context.target = id; context.to = to; if (!this.callEvent("onBeforeDragIn",[context,e])){ context.id = null; return null; } dhtmlx.html.addCss(html,"dhx_drag_over"); //mark target return html; }, //called when drag moved out from possible target onDragOut:function(s,t,n,e){ var id = this.locate(e) || null; if (n != this._dataobj) id = null; //previous target var html = (this._locateHTML(id)||(n?dhtmlx.DragControl.getMaster(n)._obj:window.undefined)); if (html == dhtmlx.DragControl._landing) return null; //unmark previous target var context = dhtmlx.DragControl._drag_context; dhtmlx.html.removeCss(dhtmlx.DragControl._landing,"dhx_drag_over"); context.target = context.to = null; this.callEvent("onDragOut",[context,e]); return null; }, //called when drag moved on target and button is released onDrop:function(s,t,d,e){ var context = dhtmlx.DragControl._drag_context; //finalize context details context.to = this; context.index = context.target?this.indexById(context.target):this.dataCount(); context.new_id = dhtmlx.uid(); if (!this.callEvent("onBeforeDrop",[context,e])) return; //moving if (context.from==context.to){ this.move(context.source,context.index); //inside the same component } else { if (context.from) //from different component context.from.move(context.source,context.index,context.to,context.new_id); else dhtmlx.error("Unsopported d-n-d combination"); } this.callEvent("onAfterDrop",[context,e]); }, //called when drag action started onDrag:function(s,e){ var id = this.locate(e); var list = [id]; if (id){ if (this.getSelected){ //has selection model var selection = this.getSelected(); //if dragged item is one of selected - drag all selected if (dhtmlx.PowerArray.find.call(selection,id)!=-1) list = selection; } //save initial dnd params var context = dhtmlx.DragControl._drag_context= { source:list, start:id }; context.from = this; if (this.callEvent("onBeforeDrag",[context,e])) return context.html||this._toHTML(this.get(id)); //set drag representation } return null; } //returns dnd context object /*getDragContext:function(){ return dhtmlx.DragControl._drag_context; }*/ }; /* DHX DEPEND FROM FILE 'edit.js'*/ /* Behavior:EditAbility - enables item operation for the items @export edit stopEdit */ dhtmlx.EditAbility={ _init: function(id){ this._edit_id = null; //id of active item this._edit_bind = null; //array of input-to-property bindings dhtmlx.assert(this.data,"EditAbility :: Component doesn't have DataStore"); dhtmlx.assert(this._locateHTML,"EditAbility :: Component doesn't have RenderStack"); this.attachEvent("onEditKeyPress",function(code, ctrl, shift){ if (code == 13 && !shift) this.stopEdit(); else if (code == 27) this.stopEdit(true); }); this.attachEvent("onBeforeRender", function(){ this.stopEdit(); }); }, //returns id of item in edit state, or null if none isEdit:function(){ return this._edit_id; }, //switch item to the edit state edit:function(id){ //edit operation can be blocked from editStop - when previously active editor can't be closed if (this.stopEdit(false, id)){ if (!this.callEvent("onBeforeEditStart",[id])) return; var data = this.data.get(id); //object with custom templates is not editable if (data.$template) return; //item must have have "edit" template data.$template="edit"; this.data.refresh(id); this._edit_id = id; //parse templates and save input-property mapping this._save_binding(id); this._edit_bind(true,data); //fill inputs with data this.callEvent("onAfterEditStart",[id]); } }, //close currently active editor stopEdit:function(mode, if_not_id){ if (!this._edit_id) return true; if (this._edit_id == if_not_id) return false; var values = {}; if (!mode) this._edit_bind(false,values); else values = null; if (!this.callEvent("onBeforeEditStop",[this._edit_id, values])) return false; var data=this.data.get(this._edit_id); data.$template=null; //set default template //load data from inputs //if mode is set - close editor without saving if (!mode) this._edit_bind(false,data); var id = this._edit_id; this._edit_bind=this._edit_id=null; this.data.refresh(id); this.callEvent("onAfterEditStop",[id, values]); return true; }, //parse template and save inputs which need to be mapped to the properties _save_binding:function(id){ var cont = this._locateHTML(id); var code = ""; //code of prop-to-inp method var back_code = ""; //code of inp-to-prop method var bind_elements = []; //binded inputs if (cont){ var elements = cont.getElementsByTagName("*"); //all sub-tags var bind = ""; for (var i=0; i < elements.length; i++) { if(elements[i].nodeType==1 && (bind = elements[i].getAttribute("bind"))){ //if bind present //code for element accessing code+="els["+bind_elements.length+"].value="+bind+";"; back_code+=bind+"=els["+bind_elements.length+"].value;"; bind_elements.push(elements[i]); //clear block-selection for the input elements[i].className+=" dhx_allow_selection"; elements[i].onselectstart=this._block_native; } } elements = null; } //create accessing methods, for later usage code = Function("obj","els",code); back_code = Function("obj","els",back_code); this._edit_bind = function(mode,obj){ if (mode){ //property to input code(obj,bind_elements); if (bind_elements.length && bind_elements[0].select) //focust first html input, if possible bind_elements[0].select(); } else //input to propery back_code(obj,bind_elements); }; }, //helper - blocks event bubbling, used to stop click event on editor level _block_native:function(e){ (e||event).cancelBubble=true; return true; } }; /* DHX DEPEND FROM FILE 'selection.js'*/ /* Behavior:SelectionModel - manage selection states @export select unselect selectAll unselectAll isSelected getSelected */ dhtmlx.SelectionModel={ _init:function(){ //collection of selected IDs this._selected = dhtmlx.toArray(); dhtmlx.assert(this.data, "SelectionModel :: Component doesn't have DataStore"); //remove selection from deleted items this.data.attachEvent("onStoreUpdated",dhtmlx.bind(this._data_updated,this)); this.data.attachEvent("onStoreLoad", dhtmlx.bind(this._data_loaded,this)); this.data.attachEvent("onAfterFilter", dhtmlx.bind(this._data_filtered,this)); this.data.attachEvent("onIdChange", dhtmlx.bind(this._id_changed,this)); }, _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.indexById(this._selected[i]) < 0) var id = this._selected[i]; var item = this.item(id); if (item) delete item.$selected; 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 this._selected.remove(id); else if (!this.data.dataCount() && !this.data._filter_order){ //remove selection for clearAll this._selected = dhtmlx.toArray(); } }, _data_loaded:function(){ if (this._settings.select) this.data.each(function(obj){ if (obj.$selected) this.select(obj.id); }, this); }, //helper - changes state of selection for some item _select_mark:function(id,state,refresh){ if (!refresh && !this.callEvent("onBeforeSelect",[id,state])) return false; this.data.item(id).$selected=state; //set custom mark on item 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 } return true; }, //select some item select:function(id,non_modal,continue_old){ //if id not provide - works as selectAll if (!id) return this.selectAll(); //allow an array of ids as parameter if (id instanceof Array){ for (var i=0; i < id.length; i++) this.select(id[i], non_modal, continue_old); return; } if (!this.data.exists(id)){ dhtmlx.error("Incorrect id in select command: "+id); return; } //block selection mode if (continue_old && this._selected.length) return this.selectAll(this._selected[this._selected.length-1],id); //single selection mode if (!non_modal && (this._selected.length!=1 || this._selected[0]!=id)){ this._silent_selection = true; //prevent unnecessary onSelectChange event this.unselectAll(); this._silent_selection = false; } if (this.isSelected(id)){ if (non_modal) this.unselect(id); //ctrl-selection of already selected item return; } if (this._select_mark(id,true)){ //if not blocked from event this.callEvent("onAfterSelect",[id]); } }, //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){ var d = this.data.item(obj.id); if (!d.$selected){ this._selected.push(obj.id); this._select_mark(obj.id,true,refresh); } return obj.id; },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=dhtmlx.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 */ getSelected: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 } }, //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.dataCount/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.item(refresh[i]),"update"); if (!this._silent_selection) this.callEvent("onSelectChange",[refresh]); } }; /* DHX DEPEND FROM FILE 'render.js'*/ /* Renders collection of items Behavior uses plain strategy which suits only for relative small datasets @export locate show render */ dhtmlx.RenderStack={ _init:function(){ dhtmlx.assert(this.data,"RenderStack :: Component doesn't have DataStore"); dhtmlx.assert(dhtmlx.Template,"dhtmlx.Template :: dhtmlx.Template is not accessible"); //used for temporary HTML elements //automatically nulified during destruction this._html = document.createElement("DIV"); }, //convert single item to HTML text (templating) _toHTML:function(obj){ //check if related template exist dhtmlx.assert((!obj.$template || this.type["template_"+obj.$template]),"RenderStack :: Unknown template: "+obj.$template); /*mm: fix allows to call the event for all objects (PropertySheet)*/ //if (obj.$template) //custom template this.callEvent("onItemRender",[obj]); /* $template property of item, can contain a name of custom template */ return this.type._item_start(obj,this.type)+(obj.$template?this.type["template_"+obj.$template]:this.type.template)(obj,this.type)+this.type._item_end; }, //convert item to HTML object (templating) _toHTMLObject:function(obj){ this._html.innerHTML = this._toHTML(obj); return this._html.firstChild; }, //return html container by its ID //can return undefined if container doesn't exists _locateHTML: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._locateHTML(search_id); }, //return id of item from html event locate:function(e){ return dhtmlx.html.locate(e,this._id); }, //change scrolling state of top level container, so related item will be in visible part show:function(id){ var html = this._locateHTML(id); if (html) this._dataobj.scrollTop = html.offsetTop-this._dataobj.offsetTop; }, //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,after){ if (id){ var cont = this._locateHTML(id); //get html element of updated item switch(type){ case "update": //in case of update - replace existing html with updated one if (!cont) return; var t = this._htmlmap[id] = this._toHTMLObject(data); dhtmlx.html.insertBefore(t, cont); dhtmlx.html.remove(cont); break; case "delete": //in case of delete - remove related html if (!cont) return; dhtmlx.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); dhtmlx.html.insertBefore(t, this._locateHTML(this.data.next(id)), this._dataobj); break; case "move": //in case of move , simulate add - delete sequence //it will affect only rendering this.render(id,data,"delete"); this.render(id,data,"add"); break; default: dhtmlx.error("Unknown render command: "+type); break; } } else { //full reset if (this.callEvent("onBeforeRender",[this.data])){ //getRange - returns all elements this._dataobj.innerHTML = this.data.getRange().map(this._toHTML,this).join(""); this._htmlmap = null; //clear map, it will be filled at first _locateHTML } } this.callEvent("onAfterRender",[]); }, //pager attachs handler to onBeforeRender, to limit visible set of data //data.min and data.max affect result of data.getRange() pager_setter:function(value){ this.attachEvent("onBeforeRender",function(){ var s = this._settings.pager._settings; //initial value of pager = -1, waiting for real value if (s.page == -1) return false; this.data.min = s.page*s.size; //affect data.getRange this.data.max = (s.page+1)*s.size-1; return true; }); var pager = new dhtmlx.ui.pager(value); //update functor var update = dhtmlx.bind(function(){ this.data.refresh(); },this); //when values of pager are changed - repaint view pager.attachEvent("onRefresh",update); //when something changed in DataStore - update configuration of pager //during refresh - pager can call onRefresh method which will cause repaint of view this.data.attachEvent("onStoreUpdated",function(data){ var count = this.dataCount(); if (count != pager._settings.count){ pager._settings.count = count; //set first page //it is called first time after data loading //until this time pager is not rendered if (pager._settings.page == -1) pager._settings.page = 0; pager.refresh(); } }); return pager; }, //can be used only to trigger auto-height height_setter:function(value){ if (value=="auto"){ //react on resize of window and self-repainting this.attachEvent("onAfterRender",this._correct_height); dhtmlx.event(window,"resize",dhtmlx.bind(this._correct_height,this)); } return value; }, //update height of container to remove inner scrolls _correct_height:function(){ //disable scrolls - if we are using auto-height , they are not necessary this._dataobj.style.overflow="hidden"; //set min. size, so we can fetch real scroll height this._dataobj.style.height = "1px"; var t = this._dataobj.scrollHeight; this._dataobj.style.height = t+"px"; // FF has strange issue with height caculation // it incorrectly detects scroll height when only small part of item is invisible if (dhtmlx._isFF){ var t2 = this._dataobj.scrollHeight; if (t2!=t) this._dataobj.style.height = t2+"px"; } this._obj.style.height = this._dataobj.style.height; }, //get size of single item _getDimension:function(){ var t = this.type; var d = (t.border||0)+(t.padding||0)*2+(t.margin||0)*2; //obj.x - widht, obj.y - height return {x : t.width+d, y: t.height+d }; }, //x_count propery sets width of container, so N items can be placed on single line x_count_setter:function(value){ var dim = this._getDimension(); var scrfix = dhtmlx.$customScroll ? 0 : 18; this._dataobj.style.width = dim.x*value+(this._settings.height != "auto" ? scrfix : 0)+"px"; return value; }, //x_count propery sets height of container, so N items a visible in one column //column will have scroll if real count of lines is greater than N y_count_setter:function(value){ var dim = this._getDimension(); this._dataobj.style.height = dim.y*value+"px"; return value; } }; /* DHX DEPEND FROM FILE 'virtual_render.js'*/ /* Renders collection of items Always shows y-scroll Can be used with huge datasets @export show render */ /*DHX:Depend render.js*/ dhtmlx.VirtualRenderStack={ _init:function(){ dhtmlx.assert(this.render,"VirtualRenderStack :: Object must use RenderStack first"); this._htmlmap={}; //init map of rendered elements //in this mode y-scroll is always visible //it simplifies calculations a lot this._dataobj.style.overflowY="scroll"; //we need to repaint area each time when view resized or scrolling state is changed dhtmlx.event(this._dataobj,"scroll",dhtmlx.bind(this._render_visible_rows,this)); dhtmlx.event(window,"resize",dhtmlx.bind(function(){ this.render(); },this)); //here we store IDs of elemenst which doesn't loadede yet, but need to be rendered this.data._unrendered_area=[]; this.data.getIndexRange=this._getIndexRange; }, //return html object by item's ID. Can return null for not-rendering element _locateHTML:function(search_id){ //collection was filled in _render_visible_rows return this._htmlmap[search_id]; }, //adjust scrolls to make item visible show:function(id){ range = this._getVisibleRange(); var ind = this.data.indexById(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; this._dataobj.scrollTop = dy; }, _getIndexRange:function(from,to){ if (to !== 0) to=Math.min((to||Infinity),this.dataCount()-1); var ret=dhtmlx.toArray(); //result of method is rich-array for (var i=(from||0); i <= to; i++){ var item = this.item(this.order[i]); if(this.order.length>i){ if (!item){ this.order[i]=dhtmlx.uid(); item = { id:this.order[i], $template:"loading" }; this._unrendered_area.push(this.order[i]); //store item ID for later loading } else if (item.$template == "loading") this._unrendered_area.push(this.order[i]); ret.push(item); } } return ret; }, //repain self after changes in DOM //for add, delete, move operations - render is delayed, to minify performance impact render:function(id,data,type,after){ if (id){ var cont = this._locateHTML(id); //old html element switch(type){ case "update": if (!cont) return; //replace old with new var t = this._htmlmap[id] = this._toHTMLObject(data); dhtmlx.html.insertBefore(t, cont); dhtmlx.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(dhtmlx.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.data._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 = Math.max(viewport._from, 0); //max can be 0, in case of 1 row per page var max_row = (this.data.max || this.data.max === 0)?this.data.max:Infinity; 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 > max_row) break; //check that row is in virtual bounds, defined by paging var nextpoint = Math.min(base+viewport._dx-1,max_row); var node = this._create_placeholder(viewport._y); //all items in rendered row var range = this.data.getIndexRange(base, nextpoint); if (!range.length) break; node.innerHTML=range.map(this._toHTML,this).join(""); //actual rendering for (var i=0; i < range.length; i++) //register all new elements for later usage in _locateHTML this._htmlmap[this.data.idByIndex(base+i)]=node.childNodes[i]; //correct placeholders var h = parseInt(holder_row.style.height,10); var delta = (t-holder)*viewport._y; var delta2 = (h-delta-viewport._y); //add new row to the DOOM dhtmlx.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) dhtmlx.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); dhtmlx.html.insertBefore(new_space,node.nextSibling,this._dataobj); } } t++; } //when all done, check for non-loaded items if (this.data._unrendered_area.length){ //we have some data to load //detect borders var from = this.indexById(this.data._unrendered_area[0]); var to = this.indexById(this.data._unrendered_area.pop())+1; if (to>from){ //initiate data loading if (!this.callEvent("onDataRequest",[from, to-from])) return false; dhtmlx.assert(this.data.feed,"Data feed is missed"); this.data.feed.call(this,from,to-from); } } if (dhtmlx._isIE){ var viewport2 = this._getVisibleRange(); if (viewport2._from != viewport._from) this._render_visible_rows(); } }, //calculates visible view _getVisibleRange:function(){ var scrfix = dhtmlx.$customScroll ? 0 : 18; var top = this._dataobj.scrollTop; var width = Math.max(this._dataobj.scrollWidth,this._dataobj.offsetWidth) - scrfix; // opera returns zero scrollwidth for the empty object var height = this._dataobj.offsetHeight; // 18 - scroll //size of single item var t = this.type; var dim = this._getDimension(); var dx = Math.floor(width/dim.x)||1; //at least single item per row var min = Math.floor(top/dim.y); //index of first visible row var dy = Math.ceil((height+top)/dim.y)-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.dataCount(); var max = Math.ceil(count/dx)*dim.y; //size of view in rows return { _from:min, _height:dy, _top:top, _max:max, _y:dim.y, _dx:dx}; } }; /* DHX INITIAL FILE 'C:\http\legacy/dhtmlxCore/sources//dataview.js'*/ /* UI:DataView */ /*DHX:Depend dataview.css*/ /*DHX:Depend types*/ /*DHX:Depend ../imgs/dataview*/ /*DHX:Depend compatibility_layout.js*/ /*DHX:Depend compatibility_drag.js*/ /*DHX:Depend datastore.js*/ /*DHX:Depend load.js*/ /*DHX:Depend virtual_render.js*/ /*DHX:Depend selection.js*/ /*DHX:Depend mouse.js*/ /*DHX:Depend key.js*/ /*DHX:Depend edit.js*/ /*DHX:Depend drag.js*/ /*DHX:Depend dataprocessor_hook.js*/ /*DHX:Depend autotooltip.js*/ /*DHX:Depend pager.js*/ /*DHX:Depend destructor.js*/ /*DHX:Depend dhtmlx.js*/ /*DHX:Depend config.js*/ //container - can be a HTML container or it's ID dhtmlXDataView = function(container){ //next data is only for debug purposes this.name = "DataView"; //name of component this.version = "3.0"; //version of component if (dhtmlx.assert_enabled()) this._assert(); //enable configuration dhtmlx.extend(this, dhtmlx.Settings); this._parseContainer(container,"dhx_dataview"); //assigns parent container //behaviors dhtmlx.extend(this, dhtmlx.AtomDataLoader); dhtmlx.extend(this, dhtmlx.DataLoader); //includes creation of DataStore dhtmlx.extend(this, dhtmlx.EventSystem); dhtmlx.extend(this, dhtmlx.RenderStack); dhtmlx.extend(this, dhtmlx.SelectionModel); dhtmlx.extend(this, dhtmlx.MouseEvents); dhtmlx.extend(this, dhtmlx.KeyEvents); dhtmlx.extend(this, dhtmlx.EditAbility); dhtmlx.extend(this, dhtmlx.DataMove); dhtmlx.extend(this, dhtmlx.DragItem); dhtmlx.extend(this, dhtmlx.DataProcessor); dhtmlx.extend(this, dhtmlx.AutoTooltip); dhtmlx.extend(this, dhtmlx.Destruction); //render self , each time when data is updated this.data.attachEvent("onStoreUpdated",dhtmlx.bind(function(){ this.render.apply(this,arguments); },this)); //default settings this._parseSettings(container,{ drag:false, edit:false, select:"multiselect", //multiselection is enabled by default type:"default" }); //in case of auto-height we use plain rendering if (this._settings.height!="auto"&&!this._settings.renderAll) dhtmlx.extend(this, dhtmlx.VirtualRenderStack); //extends RenderStack behavior //map API of DataStore on self this.data.provideApi(this,true); if (dhtmlx.$customScroll) dhtmlx.CustomScroll.enable(this); }; dhtmlXDataView.prototype={ bind:function(){ dhx.BaseBind.legacyBind.apply(this, arguments); }, sync:function(){ dhx.BaseBind.legacySync.apply(this, arguments); }, /* Called each time when dragIn or dragOut situation occurs context - drag context object ev - native event */ dragMarker:function(context,ev){ //get HTML element by item ID //can be null - when item is not rendered yet var el = this._locateHTML(context.target); //ficon and some other types share common bg marker if (this.type.drag_marker){ if (this._drag_marker){ //clear old drag marker position this._drag_marker.style.backgroundImage=""; this._drag_marker.style.backgroundRepeat=""; } // if item already rendered if (el) { //show drag marker el.style.backgroundImage="url("+(dhtmlx.image_path||"")+this.type.drag_marker+")"; el.style.backgroundRepeat="no-repeat"; this._drag_marker = el; } } //auto-scroll during d-n-d, only if related option is enabled if (el && this._settings.auto_scroll){ //maybe it can be moved to the drag behavior var dy = el.offsetTop; var dh = el.offsetHeight; var py = this._obj.scrollTop; var ph = this._obj.offsetHeight; //scroll up or down is mouse already pointing on top|bottom visible item if (dy-dh >= 0 && dy-dh*0.75 < py) py = Math.max(dy-dh, 0); else if (dy+dh/0.75 > py+ph) py = py+dh; this._obj.scrollTop = py; } return true; }, //attribute , which will be used for ID storing _id:"dhx_f_id", //css class to action map, for onclick event on_click:{ dhx_dataview_item:function(e,id){ //click on item if (this.stopEdit(false,id)){ if (this._settings.select){ if (this._settings.select=="multiselect") this.select(id, e.ctrlKey, e.shiftKey); //multiselection else this.select(id); } } } }, //css class to action map, for dblclick event on_dblclick:{ dhx_dataview_item:function(e,id){ //dblclick on item if (this._settings.edit) this.edit(id); //edit it! } }, //css class to action map, for mousemove event on_mouse_move:{ }, types:{ "default":{ css:"default", //normal state of item template:dhtmlx.Template.fromHTML("
{obj.text}
"), //template for edit state of item template_edit:dhtmlx.Template.fromHTML("
"), //in case of dyn. loading - temporary spacer template_loading:dhtmlx.Template.fromHTML("
Loading...
"), width:210, height:115, margin:0, padding:10, border:1 } }, template_item_start:dhtmlx.Template.fromHTML("
"), template_item_end:dhtmlx.Template.fromHTML("
") }; dhtmlx.compat("layout");