egroupware/phpgwapi/js/dhtmlxtree/dhtmlxDataProcessor/sources/dhtmlxdataprocessor.js
2010-10-15 10:04:07 +00:00

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