mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-12-26 08:38:54 +01:00
584 lines
16 KiB
JavaScript
Executable File
584 lines
16 KiB
JavaScript
Executable File
//v.2.6 build 100722
|
|
|
|
/*
|
|
Copyright DHTMLX LTD. http://www.dhtmlx.com
|
|
You allowed to use this component or parts of it under GPL terms
|
|
To use it on other terms or get Professional edition of the component please contact us at sales@dhtmlx.com
|
|
*/
|
|
/**
|
|
* @desc: constructor, data processor object
|
|
* @param: serverProcessorURL - url used for update
|
|
* @type: public
|
|
*/
|
|
function dataProcessor(serverProcessorURL){
|
|
this.serverProcessor = serverProcessorURL;
|
|
this.action_param="!nativeeditor_status";
|
|
|
|
this.object = null;
|
|
this.updatedRows = []; //ids of updated rows
|
|
|
|
this.autoUpdate = true;
|
|
this.updateMode = "cell";
|
|
this._tMode="GET";
|
|
this.post_delim = "_";
|
|
|
|
this._waitMode=0;
|
|
this._in_progress={};//?
|
|
this._invalid={};
|
|
this.mandatoryFields=[];
|
|
this.messages=[];
|
|
|
|
this.styles={
|
|
updated:"font-weight:bold;",
|
|
inserted:"font-weight:bold;",
|
|
deleted:"text-decoration : line-through;",
|
|
invalid:"background-color:FFE0E0;",
|
|
invalid_cell:"border-bottom:2px solid red;",
|
|
error:"color:red;",
|
|
clear:"font-weight:normal;text-decoration:none;"
|
|
};
|
|
|
|
this.enableUTFencoding(true);
|
|
dhtmlxEventable(this);
|
|
|
|
return this;
|
|
}
|
|
|
|
dataProcessor.prototype={
|
|
/**
|
|
* @desc: select GET or POST transaction model
|
|
* @param: mode - GET/POST
|
|
* @param: total - true/false - send records row by row or all at once (for grid only)
|
|
* @type: public
|
|
*/
|
|
setTransactionMode:function(mode,total){
|
|
this._tMode=mode;
|
|
this._tSend=total;
|
|
},
|
|
escape:function(data){
|
|
if (this._utf)
|
|
return encodeURIComponent(data);
|
|
else
|
|
return escape(data);
|
|
},
|
|
/**
|
|
* @desc: allows to set escaping mode
|
|
* @param: true - utf based escaping, simple - use current page encoding
|
|
* @type: public
|
|
*/
|
|
enableUTFencoding:function(mode){
|
|
this._utf=convertStringToBoolean(mode);
|
|
},
|
|
/**
|
|
* @desc: allows to define, which column may trigger update
|
|
* @param: val - array or list of true/false values
|
|
* @type: public
|
|
*/
|
|
setDataColumns:function(val){
|
|
this._columns=(typeof val == "string")?val.split(","):val;
|
|
},
|
|
/**
|
|
* @desc: get state of updating
|
|
* @returns: true - all in sync with server, false - some items not updated yet.
|
|
* @type: public
|
|
*/
|
|
getSyncState:function(){
|
|
return !this.updatedRows.length;
|
|
},
|
|
/**
|
|
* @desc: enable/disable named field for data syncing, will use column ids for grid
|
|
* @param: mode - true/false
|
|
* @type: public
|
|
*/
|
|
enableDataNames:function(mode){
|
|
this._endnm=convertStringToBoolean(mode);
|
|
},
|
|
/**
|
|
* @desc: enable/disable mode , when only changed fields and row id send to the server side, instead of all fields in default mode
|
|
* @param: mode - true/false
|
|
* @type: public
|
|
*/
|
|
enablePartialDataSend:function(mode){
|
|
this._changed=convertStringToBoolean(mode);
|
|
},
|
|
/**
|
|
* @desc: set if rows should be send to server automaticaly
|
|
* @param: mode - "row" - based on row selection changed, "cell" - based on cell editing finished, "off" - manual data sending
|
|
* @type: public
|
|
*/
|
|
setUpdateMode:function(mode,dnd){
|
|
this.autoUpdate = (mode=="cell");
|
|
this.updateMode = mode;
|
|
this.dnd=dnd;
|
|
},
|
|
ignore:function(code,master){
|
|
this._silent_mode=true;
|
|
code.call(master||window);
|
|
this._silent_mode=false;
|
|
},
|
|
/**
|
|
* @desc: mark row as updated/normal. check mandatory fields,initiate autoupdate (if turned on)
|
|
* @param: rowId - id of row to set update-status for
|
|
* @param: state - true for "updated", false for "not updated"
|
|
* @param: mode - update mode name
|
|
* @type: public
|
|
*/
|
|
setUpdated:function(rowId,state,mode){
|
|
if (this._silent_mode) return;
|
|
var ind=this.findRow(rowId);
|
|
|
|
mode=mode||"updated";
|
|
var existing = this.obj.getUserData(rowId,this.action_param);
|
|
if (existing && mode == "updated") mode=existing;
|
|
if (state){
|
|
this.set_invalid(rowId,false); //clear previous error flag
|
|
this.updatedRows[ind]=rowId;
|
|
this.obj.setUserData(rowId,this.action_param,mode);
|
|
if (this._in_progress[rowId])
|
|
this._in_progress[rowId]="wait";
|
|
} else{
|
|
if (!this.is_invalid(rowId)){
|
|
this.updatedRows.splice(ind,1);
|
|
this.obj.setUserData(rowId,this.action_param,"");
|
|
}
|
|
}
|
|
|
|
//clear changed flag
|
|
if (!state)
|
|
this._clearUpdateFlag(rowId);
|
|
|
|
this.markRow(rowId,state,mode);
|
|
if (state && this.autoUpdate) this.sendData(rowId);
|
|
},
|
|
_clearUpdateFlag:function(id){},
|
|
markRow:function(id,state,mode){
|
|
var str="";
|
|
var invalid=this.is_invalid(id);
|
|
if (invalid){
|
|
str=this.styles[invalid];
|
|
state=true;
|
|
}
|
|
if (this.callEvent("onRowMark",[id,state,mode,invalid])){
|
|
//default logic
|
|
str=this.styles[state?mode:"clear"]+str;
|
|
|
|
this.obj[this._methods[0]](id,str);
|
|
|
|
if (invalid && invalid.details){
|
|
str+=this.styles[invalid+"_cell"];
|
|
for (var i=0; i < invalid.details.length; i++)
|
|
if (invalid.details[i])
|
|
this.obj[this._methods[1]](id,i,str);
|
|
}
|
|
}
|
|
},
|
|
getState:function(id){
|
|
return this.obj.getUserData(id,this.action_param);
|
|
},
|
|
is_invalid:function(id){
|
|
return this._invalid[id];
|
|
},
|
|
set_invalid:function(id,mode,details){
|
|
if (details) mode={value:mode, details:details, toString:function(){ return this.value.toString(); }};
|
|
this._invalid[id]=mode;
|
|
},
|
|
/**
|
|
* @desc: check mandatory fields and varify values of cells, initiate update (if specified)
|
|
* @param: rowId - id of row to set update-status for
|
|
* @type: public
|
|
*/
|
|
checkBeforeUpdate:function(rowId){
|
|
return true;
|
|
},
|
|
/**
|
|
* @desc: send row(s) values to server
|
|
* @param: rowId - id of row which data to send. If not specified, then all "updated" rows will be send
|
|
* @type: public
|
|
*/
|
|
sendData:function(rowId){
|
|
if (this._waitMode && (this.obj.mytype=="tree" || this.obj._h2)) return;
|
|
if (this.obj.editStop) this.obj.editStop();
|
|
|
|
|
|
if(typeof rowId == "undefined" || this._tSend) return this.sendAllData();
|
|
if (this._in_progress[rowId]) return false;
|
|
|
|
this.messages=[];
|
|
if (!this.checkBeforeUpdate(rowId) && this.callEvent("onValidatationError",[rowId,this.messages])) return false;
|
|
this._beforeSendData(this._getRowData(rowId),rowId);
|
|
},
|
|
_beforeSendData:function(data,rowId){
|
|
if (!this.callEvent("onBeforeUpdate",[rowId,this.getState(rowId),data])) return false;
|
|
this._sendData(data,rowId);
|
|
},
|
|
serialize:function(data, id){
|
|
if (typeof id != "undefined")
|
|
return this.serialize_one(data,"");
|
|
else{
|
|
var stack = [];
|
|
var keys = [];
|
|
for (var key in data)
|
|
if (data.hasOwnProperty(key)){
|
|
stack.push(this.serialize_one(data[key],key+this.post_delim));
|
|
keys.push(key);
|
|
}
|
|
stack.push("ids="+this.escape(keys.join(",")));
|
|
return stack.join("&");
|
|
}
|
|
},
|
|
serialize_one:function(data, pref){
|
|
if (typeof data == "string")
|
|
return data;
|
|
var stack = [];
|
|
for (var key in data)
|
|
if (data.hasOwnProperty(key))
|
|
stack.push(this.escape((pref||"")+key)+"="+this.escape(data[key]));
|
|
return stack.join("&");
|
|
},
|
|
_sendData:function(a1,rowId){
|
|
if (!a1) return; //nothing to send
|
|
if (!this.callEvent("onBeforeDataSending",rowId?[rowId,this.getState(rowId),a1]:[null, null, a1])) return false;
|
|
|
|
if (rowId)
|
|
this._in_progress[rowId]=(new Date()).valueOf();
|
|
var a2=new dtmlXMLLoaderObject(this.afterUpdate,this,true);
|
|
|
|
var a3 = this.serverProcessor+(this._user?(getUrlSymbol(this.serverProcessor)+["dhx_user="+this._user,"dhx_version="+this.obj.getUserData(0,"version")].join("&")):"");
|
|
|
|
if (this._tMode!="POST")
|
|
a2.loadXML(a3+((a3.indexOf("?")!=-1)?"&":"?")+this.serialize(a1,rowId));
|
|
else
|
|
a2.loadXML(a3,true,this.serialize(a1));
|
|
|
|
this._waitMode++;
|
|
},
|
|
sendAllData:function(){
|
|
if (!this.updatedRows.length) return;
|
|
|
|
this.messages=[]; var valid=true;
|
|
for (var i=0; i<this.updatedRows.length; i++)
|
|
valid&=this.checkBeforeUpdate(this.updatedRows[i]);
|
|
if (!valid && !this.callEvent("onValidatationError",["",this.messages])) return false;
|
|
|
|
if (this._tSend)
|
|
this._sendData(this._getAllData());
|
|
else
|
|
for (var i=0; i<this.updatedRows.length; i++)
|
|
if (!this._in_progress[this.updatedRows[i]]){
|
|
if (this.is_invalid(this.updatedRows[i])) continue;
|
|
this._beforeSendData(this._getRowData(this.updatedRows[i]),this.updatedRows[i]);
|
|
if (this._waitMode && (this.obj.mytype=="tree" || this.obj._h2)) return; //block send all for tree
|
|
}
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_getAllData:function(rowId){
|
|
var out={};
|
|
var has_one = false;
|
|
for(var i=0;i<this.updatedRows.length;i++){
|
|
var id=this.updatedRows[i];
|
|
if (this._in_progress[id] || this.is_invalid(id)) continue;
|
|
if (!this.callEvent("onBeforeUpdate",[id,this.getState(id)])) continue;
|
|
out[id]=this._getRowData(id,id+this.post_delim);
|
|
has_one = true;
|
|
this._in_progress[id]=(new Date()).valueOf();
|
|
}
|
|
return has_one?out:null;
|
|
},
|
|
|
|
|
|
/**
|
|
* @desc: specify column which value should be varified before sending to server
|
|
* @param: ind - column index (0 based)
|
|
* @param: verifFunction - function (object) which should verify cell value (if not specified, then value will be compared to empty string). Two arguments will be passed into it: value and column name
|
|
* @type: public
|
|
*/
|
|
setVerificator:function(ind,verifFunction){
|
|
this.mandatoryFields[ind] = verifFunction||(function(value){return (value!="");});
|
|
},
|
|
/**
|
|
* @desc: remove column from list of those which should be verified
|
|
* @param: ind - column Index (0 based)
|
|
* @type: public
|
|
*/
|
|
clearVerificator:function(ind){
|
|
this.mandatoryFields[ind] = false;
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
findRow:function(pattern){
|
|
var i=0;
|
|
for(i=0;i<this.updatedRows.length;i++)
|
|
if(pattern==this.updatedRows[i]) break;
|
|
return i;
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* @desc: define custom actions
|
|
* @param: name - name of action, same as value of action attribute
|
|
* @param: handler - custom function, which receives a XMl response content for action
|
|
* @type: private
|
|
*/
|
|
defineAction:function(name,handler){
|
|
if (!this._uActions) this._uActions=[];
|
|
this._uActions[name]=handler;
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
* @desc: used in combination with setOnBeforeUpdateHandler to create custom client-server transport system
|
|
* @param: sid - id of item before update
|
|
* @param: tid - id of item after up0ate
|
|
* @param: action - action name
|
|
* @type: public
|
|
* @topic: 0
|
|
*/
|
|
afterUpdateCallback:function(sid, tid, action, btag) {
|
|
var correct=(action!="error" && action!="invalid");
|
|
if (!correct) this.set_invalid(sid,action);
|
|
if ((this._uActions)&&(this._uActions[action])&&(!this._uActions[action](btag)))
|
|
return (delete this._in_progress[sid]);
|
|
|
|
if (this._in_progress[sid]!="wait")
|
|
this.setUpdated(sid, false);
|
|
var soid = sid;
|
|
|
|
switch (action) {
|
|
case "inserted":
|
|
case "insert":
|
|
if (tid != sid) {
|
|
this.obj[this._methods[2]](sid, tid);
|
|
sid = tid;
|
|
}
|
|
break;
|
|
case "delete":
|
|
case "deleted":
|
|
this.obj.setUserData(sid, this.action_param, "true_deleted");
|
|
this.obj[this._methods[3]](sid);
|
|
|
|
return this.callEvent("onAfterUpdate", [sid, action, tid, btag]);
|
|
break;
|
|
}
|
|
|
|
if (this._in_progress[sid]!="wait"){
|
|
if (correct) this.obj.setUserData(sid, this.action_param,'');
|
|
delete this._in_progress[sid];
|
|
} else {
|
|
delete this._in_progress[sid];
|
|
this.setUpdated(tid,true,this.obj.getUserData(sid,this.action_param));
|
|
}
|
|
|
|
this.callEvent("onAfterUpdate", [sid, action, tid, btag]);
|
|
},
|
|
|
|
/**
|
|
* @desc: response from server
|
|
* @param: xml - XMLLoader object with response XML
|
|
* @type: private
|
|
*/
|
|
afterUpdate:function(that,b,c,d,xml){
|
|
xml.getXMLTopNode("data"); //fix incorrect content type in IE
|
|
if (!xml.xmlDoc.responseXML) return;
|
|
var atag=xml.doXPath("//data/action");
|
|
for (var i=0; i<atag.length; i++){
|
|
var btag=atag[i];
|
|
var action = btag.getAttribute("type");
|
|
var sid = btag.getAttribute("sid");
|
|
var tid = btag.getAttribute("tid");
|
|
|
|
that.afterUpdateCallback(sid,tid,action,btag);
|
|
}
|
|
that.finalizeUpdate();
|
|
},
|
|
finalizeUpdate:function(){
|
|
if (this._waitMode) this._waitMode--;
|
|
|
|
if ((this.obj.mytype=="tree" || this.obj._h2) && this.updatedRows.length)
|
|
this.sendData();
|
|
this.callEvent("onAfterUpdateFinish",[]);
|
|
if (!this.updatedRows.length)
|
|
this.callEvent("onFullSync",[]);
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* @desc: initializes data-processor
|
|
* @param: anObj - dhtmlxGrid object to attach this data-processor to
|
|
* @type: public
|
|
*/
|
|
init:function(anObj){
|
|
this.obj = anObj;
|
|
if (this.obj._dp_init)
|
|
this.obj._dp_init(this);
|
|
},
|
|
|
|
|
|
setOnAfterUpdate:function(ev){
|
|
this.attachEvent("onAfterUpdate",ev);
|
|
},
|
|
enableDebug:function(mode){
|
|
},
|
|
setOnBeforeUpdateHandler:function(func){
|
|
this.attachEvent("onBeforeDataSending",func);
|
|
},
|
|
|
|
|
|
|
|
/*! starts autoupdate mode
|
|
@param interval
|
|
time interval for sending update requests
|
|
*/
|
|
setAutoUpdate: function(interval, user) {
|
|
interval = interval || 2000;
|
|
|
|
this._user = user || (new Date()).valueOf();
|
|
this._need_update = false;
|
|
this._loader = null;
|
|
this._update_busy = false;
|
|
|
|
this.attachEvent("onAfterUpdate",function(sid,action,tid,xml_node){
|
|
this.afterAutoUpdate(sid, action, tid, xml_node);
|
|
});
|
|
this.attachEvent("onFullSync",function(){
|
|
this.fullSync();
|
|
});
|
|
|
|
var self = this;
|
|
window.setInterval(function(){
|
|
self.loadUpdate();
|
|
}, interval);
|
|
},
|
|
|
|
|
|
/*! process updating request answer
|
|
if status == collision version is depricated
|
|
set flag for autoupdating immidiatly
|
|
*/
|
|
afterAutoUpdate: function(sid, action, tid, xml_node) {
|
|
if (action == 'collision') {
|
|
this._need_update = true;
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
},
|
|
|
|
|
|
/*! callback function for onFillSync event
|
|
call update function if it's need
|
|
*/
|
|
fullSync: function() {
|
|
if (this._need_update == true) {
|
|
this._need_update = false;
|
|
this.loadUpdate();
|
|
}
|
|
return true;
|
|
},
|
|
|
|
|
|
/*! sends query to the server and call callback function
|
|
*/
|
|
getUpdates: function(url,callback){
|
|
if (this._update_busy)
|
|
return false;
|
|
else
|
|
this._update_busy = true;
|
|
|
|
this._loader = this._loader || new dtmlXMLLoaderObject(true);
|
|
|
|
this._loader.async=true;
|
|
this._loader.waitCall=callback;
|
|
this._loader.loadXML(url);
|
|
},
|
|
|
|
|
|
/*! returns xml node value
|
|
@param node
|
|
xml node
|
|
*/
|
|
_v: function(node) {
|
|
if (node.firstChild) return node.firstChild.nodeValue;
|
|
return "";
|
|
},
|
|
|
|
|
|
/*! returns values array of xml nodes array
|
|
@param arr
|
|
array of xml nodes
|
|
*/
|
|
_a: function(arr) {
|
|
var res = [];
|
|
for (var i=0; i < arr.length; i++) {
|
|
res[i]=this._v(arr[i]);
|
|
};
|
|
return res;
|
|
},
|
|
|
|
|
|
/*! loads updates and processes them
|
|
*/
|
|
loadUpdate: function(){
|
|
var self = this;
|
|
var version = this.obj.getUserData(0,"version");
|
|
var url = this.serverProcessor+getUrlSymbol(this.serverProcessor)+["dhx_user="+this._user,"dhx_version="+version].join("&");
|
|
url = url.replace("editing=true&","");
|
|
this.getUpdates(url, function(){
|
|
var vers = self._loader.doXPath("//userdata");
|
|
self.obj.setUserData(0,"version",self._v(vers[0]));
|
|
|
|
var upds = self._loader.doXPath("//update");
|
|
if (upds.length){
|
|
self._silent_mode = true;
|
|
|
|
for (var i=0; i<upds.length; i++) {
|
|
var status = upds[i].getAttribute('status');
|
|
var id = upds[i].getAttribute('id');
|
|
var parent = upds[i].getAttribute('parent');
|
|
switch (status) {
|
|
case 'inserted':
|
|
self.callEvent("insertCallback",[upds[i], id, parent]);
|
|
break;
|
|
case 'updated':
|
|
self.callEvent("updateCallback",[upds[i], id, parent]);
|
|
break;
|
|
case 'deleted':
|
|
self.callEvent("deleteCallback",[upds[i], id, parent]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
self._silent_mode = false;
|
|
}
|
|
|
|
self._update_busy = false;
|
|
self = null;
|
|
});
|
|
}
|
|
|
|
};
|
|
|
|
//(c)dhtmlx ltd. www.dhtmlx.com
|