1/* 2Product Name: dhtmlxSuite 3Version: 5.1.0 4Edition: Professional 5License: content of this file is covered by DHTMLX Commercial or Enterprise license. Usage without proper license is prohibited. To obtain it contact [email protected] 6Copyright UAB Dinamenta http://www.dhtmlx.com 7*/ 8 9/** 10 * @desc: constructor, data processor object 11 * @param: serverProcessorURL - url used for update 12 * @type: public 13 */ 14function dataProcessor(serverProcessorURL){ 15 this.serverProcessor = serverProcessorURL; 16 this.action_param="!nativeeditor_status"; 17 18 this.object = null; 19 this.updatedRows = []; //ids of updated rows 20 21 this.autoUpdate = true; 22 this.updateMode = "cell"; 23 this._tMode="GET"; 24 this._headers = null; 25 this._payload = null; 26 this.post_delim = "_"; 27 28 this._waitMode=0; 29 this._in_progress={};//? 30 this._invalid={}; 31 this.mandatoryFields=[]; 32 this.messages=[]; 33 34 this.styles={ 35 updated:"font-weight:bold;", 36 inserted:"font-weight:bold;", 37 deleted:"text-decoration : line-through;", 38 invalid:"background-color:FFE0E0;", 39 invalid_cell:"border-bottom:2px solid red;", 40 error:"color:red;", 41 clear:"font-weight:normal;text-decoration:none;" 42 }; 43 44 this.enableUTFencoding(true); 45 dhx4._eventable(this); 46 47 if (this.connector_init){ 48 this.setTransactionMode("POST",true); 49 this.serverProcessor+=(this.serverProcessor.indexOf("?")!=-1?"&":"?")+"editing=true"; 50 }; 51 52 return this; 53 } 54 55dataProcessor.prototype={ 56 url:function(str){ 57 if (str.indexOf("?") != -1) 58 return "&"; 59 else 60 return "?"; 61 }, 62 setTransactionMode:function(mode,total){ 63 if (typeof mode == "object"){ 64 this._tMode = mode.mode || this._tMode; 65 this._headers = this._headers || mode.headers; 66 this._payload = this._payload || mode.payload; 67 } else { 68 this._tMode=mode; 69 this._tSend=total; 70 } 71 72 if (this._tMode == "REST"){ 73 this._tSend = false; 74 this._endnm = true; 75 } 76 if (this._tMode == "JSON"){ 77 this._tSend = false; 78 this._endnm = true; 79 this._headers = this._headers || {}; 80 this._headers["Content-type"] = "application/json"; 81 } 82 }, 83 escape:function(data){ 84 if (this._utf) 85 return encodeURIComponent(data); 86 else 87 return escape(data); 88 }, 89 /** 90 * @desc: allows to set escaping mode 91 * @param: true - utf based escaping, simple - use current page encoding 92 * @type: public 93 */ 94 enableUTFencoding:function(mode){ 95 this._utf=dhx4.s2b(mode); 96 }, 97 /** 98 * @desc: allows to define, which column may trigger update 99 * @param: val - array or list of true/false values 100 * @type: public 101 */ 102 setDataColumns:function(val){ 103 this._columns=(typeof val == "string")?val.split(","):val; 104 }, 105 /** 106 * @desc: get state of updating 107 * @returns: true - all in sync with server, false - some items not updated yet. 108 * @type: public 109 */ 110 getSyncState:function(){ 111 return !this.updatedRows.length; 112 }, 113 /** 114 * @desc: enable/disable named field for data syncing, will use column ids for grid 115 * @param: mode - true/false 116 * @type: public 117 */ 118 enableDataNames:function(mode){ 119 this._endnm=dhx4.s2b(mode); 120 }, 121 /** 122 * @desc: enable/disable mode , when only changed fields and row id send to the server side, instead of all fields in default mode 123 * @param: mode - true/false 124 * @type: public 125 */ 126 enablePartialDataSend:function(mode){ 127 this._changed=dhx4.s2b(mode); 128 }, 129 /** 130 * @desc: set if rows should be send to server automaticaly 131 * @param: mode - "row" - based on row selection changed, "cell" - based on cell editing finished, "off" - manual data sending 132 * @type: public 133 */ 134 setUpdateMode:function(mode,dnd){ 135 this.autoUpdate = (mode=="cell"); 136 this.updateMode = mode; 137 this.dnd=dnd; 138 }, 139 ignore:function(code,master){ 140 this._silent_mode=true; 141 code.call(master||window); 142 this._silent_mode=false; 143 }, 144 /** 145 * @desc: mark row as updated/normal. check mandatory fields,initiate autoupdate (if turned on) 146 * @param: rowId - id of row to set update-status for 147 * @param: state - true for "updated", false for "not updated" 148 * @param: mode - update mode name 149 * @type: public 150 */ 151 setUpdated:function(rowId,state,mode){ 152 this._log("item "+rowId+" "+(state?"marked":"unmarked")+" ["+(mode||"updated")+"]"); 153 154 if (this._silent_mode) return; 155 var ind=this.findRow(rowId); 156 157 mode=mode||"updated"; 158 var existing = this.obj.getUserData(rowId,this.action_param); 159 if (existing && mode == "updated") mode=existing; 160 if (state){ 161 this.set_invalid(rowId,false); //clear previous error flag 162 this.updatedRows[ind]=rowId; 163 this.obj.setUserData(rowId,this.action_param,mode); 164 if (this._in_progress[rowId]) 165 this._in_progress[rowId]="wait"; 166 } else{
167 if (!this.is_invalid(rowId)){ 168 this.updatedRows.splice(ind,1); 169 this.obj.setUserData(rowId,this.action_param,""); 170 } 171 } 172 173 //clear changed flag 174 if (!state) 175 this._clearUpdateFlag(rowId); 176 177 this.markRow(rowId,state,mode); 178 if (state && this.autoUpdate) this.sendData(rowId); 179 }, 180 _clearUpdateFlag:function(id){}, 181 markRow:function(id,state,mode){ 182 var str=""; 183 var invalid=this.is_invalid(id); 184 if (invalid){ 185 str=this.styles[invalid]; 186 state=true; 187 } 188 if (this.callEvent("onRowMark",[id,state,mode,invalid])){ 189 //default logic 190 str=this.styles[state?mode:"clear"]+str; 191 192 this.obj[this._methods[0]](id,str); 193 194 if (invalid && invalid.details){ 195 str+=this.styles[invalid+"_cell"]; 196 for (var i=0; i < invalid.details.length; i++) 197 if (invalid.details[i]) 198 this.obj[this._methods[1]](id,i,str); 199 } 200 } 201 }, 202 getState:function(id){ 203 return this.obj.getUserData(id,this.action_param); 204 }, 205 is_invalid:function(id){ 206 return this._invalid[id]; 207 }, 208 set_invalid:function(id,mode,details){ 209 if (details) mode={value:mode, details:details, toString:function(){ return this.value.toString(); }}; 210 this._invalid[id]=mode; 211 }, 212 /** 213 * @desc: check mandatory fields and varify values of cells, initiate update (if specified) 214 * @param: rowId - id of row to set update-status for 215 * @type: public 216 */ 217 checkBeforeUpdate:function(rowId){ 218 return true; 219 }, 220 /** 221 * @desc: send row(s) values to server 222 * @param: rowId - id of row which data to send. If not specified, then all "updated" rows will be send 223 * @type: public 224 */ 225 sendData:function(rowId){ 226 if (rowId) 227 this._log("Sending: "+rowId); 228 229 if (this._waitMode && (this.obj.mytype=="tree" || this.obj._h2)) return; 230 if (this.obj.editStop) this.obj.editStop(); 231 232 233 if(typeof rowId == "undefined" || this._tSend) return this.sendAllData(); 234 if (this._in_progress[rowId]) return false; 235 236 this.messages=[]; 237 if (this.getState(rowId) !== "deleted") 238 if (!this.checkBeforeUpdate(rowId) && this.callEvent("onValidationError",[rowId,this.messages])) return false; 239 240 this._beforeSendData(this._getRowData(rowId),rowId); 241 }, 242 _beforeSendData:function(data,rowId){ 243 if (!this.callEvent("onBeforeUpdate",[rowId,this.getState(rowId),data])) return false; 244 this._sendData(data,rowId); 245 }, 246 serialize:function(data, id){ 247 if (typeof data == "string") 248 return data; 249 if (typeof id != "undefined") 250 return this.serialize_one(data,""); 251 else{ 252 var stack = []; 253 var keys = []; 254 for (var key in data) 255 if (data.hasOwnProperty(key)){ 256 stack.push(this.serialize_one(data[key],key+this.post_delim)); 257 keys.push(key); 258 } 259 stack.push("ids="+this.escape(keys.join(","))); 260 if (window.dhtmlx && dhtmlx.security_key) 261 stack.push("dhx_security="+dhtmlx.security_key); 262 return stack.join("&"); 263 } 264 }, 265 serialize_one:function(data, pref){ 266 if (typeof data == "string") 267 return data; 268 var stack = []; 269 for (var key in data) 270 if (data.hasOwnProperty(key)){ 271 if ((key == "id" || key == this.action_param) && this._tMode == "REST") continue; 272 stack.push(this.escape((pref||"")+key)+"="+this.escape(data[key])); 273 } 274 return stack.join("&"); 275 }, 276 _applyPayload:function(url){ 277 if (this._payload) 278 for (var key in this._payload) 279 url = url + (url.indexOf("?") === -1 ? "?" : "&" ) + this.escape(key) + "=" + this.escape(this._payload[key]); 280 return url; 281 }, 282 _sendData:function(a1,rowId){ 283 284 this._log("url: "+this.serverProcessor); 285 this._log(a1) 286 287 if (!a1) return; //nothing to send 288 if (!this.callEvent("onBeforeDataSending",rowId?[rowId,this.getState(rowId),a1]:[null, null, a1])) return false; 289 290 if (rowId) 291 this._in_progress[rowId]=(new Date()).valueOf(); 292 293 var that = this; 294 var back = function(xml){ 295 var ids = []; 296 if (rowId) 297 ids.push(rowId); 298 else if (a1) 299 for (var key in a1) 300 ids.push(key); 301 302 return that.afterUpdate(that,xml,ids); 303 }; 304 305 var a3 = this.serverProcessor+(this._user?(this.url(this.serverProcessor)+["dhx_user="+this._user,"dhx_version="+this.obj.getUserData(0,"version")].join("&")):""); 306 var a4 = this._applyPayload(a3); 307 308 if (this._tMode=="GET"){ 309 dhx4.ajax.query({ 310 url:a4+((a4.indexOf("?")!=-1)?"&":"?")+this.serialize(a1,rowId
310), 311 method:"GET", 312 headers:this._headers, 313 callback:back 314 }); 315 } 316 else if (this._tMode == "POST"){ 317 dhx4.ajax.query({ 318 url:a4, 319 method:"POST", 320 headers:this._headers, 321 callback:back, 322 data:this.serialize(a1,rowId) 323 }); 324 } 325 else if (this._tMode == "JSON"){ 326 var action = a1[this.action_param]; 327 var data = {}; 328 for (var key in a1) data[key] = a1[key]; 329 delete data[this.action_param]; 330 delete data.id; 331 delete data.gr_id; 332 333 dhx4.ajax.query({ 334 url:a4, 335 method:"POST", 336 headers:this._headers, 337 callback:back, 338 data:JSON.stringify({ 339 id: rowId, 340 action: action, 341 data: data 342 }) 343 }); 344 } 345 else if (this._tMode == "REST"){ 346 var state = this.getState(rowId); 347 var url = a3.replace(/(\&|\?)editing\=true/,""); 348 var parts = url.split("?"); 349 if (parts[1]) 350 parts[1] = "?"+parts[1]; 351 352 var data = ""; 353 var method = "post"; 354 355 if (state == "inserted"){ 356 data = this.serialize(a1, rowId); 357 } else if (state == "deleted"){ 358 method = "DELETE"; 359 url = parts[0]+rowId+(parts[1]||""); 360 } else { 361 method = "PUT"; 362 data = this.serialize(a1, rowId); 363 url = parts[0]+rowId+(parts[1]||""); 364 } 365 366 this._applyPayload(url); 367 dhx4.ajax.query({ 368 url:url, 369 method:method, 370 headers:this._headers, 371 data:data, 372 callback:back 373 }); 374 } 375 376 this._waitMode++; 377 }, 378 sendAllData:function(){ 379 this._log("Sending all updated items"); 380 if (!this.updatedRows.length) return; 381 382 this.messages=[]; var valid=true; 383 for (var i=0; i<this.updatedRows.length; i++) 384 if (this.getState(this.updatedRows[i]) !== "deleted") 385 valid&=this.checkBeforeUpdate(this.updatedRows[i]); 386 387 if (!valid && !this.callEvent("onValidationError",["",this.messages])) return false; 388 389 if (this._tSend) 390 this._sendData(this._getAllData()); 391 else 392 for (var i=0; i<this.updatedRows.length; i++) 393 if (!this._in_progress[this.updatedRows[i]]){ 394 if (this.is_invalid(this.updatedRows[i])) continue; 395 this._beforeSendData(this._getRowData(this.updatedRows[i]),this.updatedRows[i]); 396 if (this._waitMode && (this.obj.mytype=="tree" || this.obj._h2)) return; //block send all for tree 397 } 398 }, 399 400 401 402 403 404 405 406 407 _getAllData:function(rowId){ 408 var out={}; 409 var has_one = false; 410 for(var i=0;i<this.updatedRows.length;i++){ 411 var id=this.updatedRows[i]; 412 if (this._in_progress[id] || this.is_invalid(id)) continue; 413 if (!this.callEvent("onBeforeUpdate",[id,this.getState(id), this._getRowData(id)])) continue; 414 out[id]=this._getRowData(id,id+this.post_delim); 415 has_one = true; 416 this._in_progress[id]=(new Date()).valueOf(); 417 } 418 return has_one?out:null; 419 }, 420 421 422 /** 423 * @desc: specify column which value should be varified before sending to server 424 * @param: ind - column index (0 based) 425 * @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 426 * @type: public 427 */ 428 setVerificator:function(ind,verifFunction){ 429 this.mandatoryFields[ind] = verifFunction||(function(value){return (value!=="");}); 430 }, 431 /** 432 * @desc: remove column from list of those which should be verified 433 * @param: ind - column Index (0 based) 434 * @type: public 435 */ 436 clearVerificator:function(ind){ 437 this.mandatoryFields[ind] = false; 438 }, 439 440 441 442 443 444 findRow:function(pattern){ 445 var i=0; 446 for(i=0;i<this.updatedRows.length;i++) 447 if(pattern==this.updatedRows[i]) break; 448 return i; 449 }, 450 451 452 453 454 455 456 457 458 459 460 461 /** 462 * @desc: define custom actions 463 * @param: name - name of action, same as value of action attribute 464 * @param: handler - custom function, which receives a XMl response content for action 465 * @type: private 466 */ 467 defineAction:function(name,handler){ 468 if (!this._uActions) this._uActions=[]; 469 this._uActions[name]=handler; 470 }, 471 472 473 474 475 /** 476* @desc: used in combination with setOnBeforeUpdateHandler to create custom client-server transport system 477* @param: sid - id of item before update 478* @param: tid - id of item after up0ate 479* @param: action - action name 480* @type: public 481* @topic: 0 482*/ 483 afterUpdateCallback:function(sid, tid, action, btag) { 484 this._log("Action: "+action+" SID:"+sid+" TID:"+tid, btag); 485 var marker = sid; 486 var correct=(action!="error" && action!="invalid"); 487 if (!correct) this.set_invalid(sid,action); 488 if ((this._uActions)&&(this._uActions[action])&&(!this._uActions[action](btag))) 489 return (delete this._in_progress[marker]); 490 491 if (this._in_progress[marker]!="wait") 492 this.setUpdated(sid, false); 493 494 var soid = sid; 495 496 switch (action) {
497 case "inserted": 498 case "insert": 499 if (tid != sid) { 500 this.obj[this._methods[2]](sid, tid); 501 sid = tid; 502 } 503 break; 504 case "delete": 505 case "deleted": 506 this.obj.setUserData(sid, this.action_param, "true_deleted"); 507 this.obj[this._methods[3]](sid); 508 delete this._in_progress[marker]; 509 return this.callEvent("onAfterUpdate", [sid, action, tid, btag]); 510 break; 511 } 512 513 if (this._in_progress[marker]!="wait"){ 514 if (correct) this.obj.setUserData(sid, this.action_param,''); 515 delete this._in_progress[marker]; 516 } else { 517 delete this._in_progress[marker]; 518 this.setUpdated(tid,true,this.obj.getUserData(sid,this.action_param)); 519 } 520 521 this.callEvent("onAfterUpdate", [soid, action, tid, btag]); 522 }, 523 enableDebug:function(){ 524 this._debug = true; 525 }, 526 _log:function(){ 527 if (this._debug && window.console && window.console.info) 528 window.console.info.apply(window.console, arguments); 529 }, 530 /** 531 * @desc: response from server 532 * @param: xml - XMLLoader object with response XML 533 * @type: private 534 */ 535 afterUpdate:function(that,xml,id){ 536 this._log("Server response received"); 537 //try to use json first 538 if (window.JSON){ 539 try{ 540 var tag = JSON.parse(xml.xmlDoc.responseText);
541 var action = tag.action || this.getState(id) || "updated"; 542 var sid = tag.sid || id[0]; 543 var tid = tag.tid || id[0]; 544 that.afterUpdateCallback(sid, tid, action, tag); 545 that.finalizeUpdate(); 546 return; 547 } catch(e){ 548 } 549 } 550 //xml response 551 var top = dhx4.ajax.xmltop("data", xml.xmlDoc); //fix incorrect content type in IE 552 if (!top || top.tagName == "DIV") return this.cleanUpdate(id); 553 var atag=dhx4.ajax.xpath("//data/action", top); 554 if (!atag.length) return this.cleanUpdate(id); 555 556 for (var i=0; i<atag.length; i++){ 557 var btag=atag[i]; 558 var action = btag.getAttribute("type"); 559 var sid = btag.getAttribute("sid"); 560 var tid = btag.getAttribute("tid"); 561 562 that.afterUpdateCallback(sid,tid,action,btag); 563 } 564 that.finalizeUpdate(); 565 }, 566 cleanUpdate:function(id){ 567 if (id) 568 for (var i = 0; i < id.length; i++) 569 delete this._in_progress[id[i]]; 570 }, 571 finalizeUpdate:function(){ 572 if (this._waitMode) this._waitMode--; 573 574 if ((this.obj.mytype=="tree" || this.obj._h2) && this.updatedRows.length) 575 this.sendData(); 576 this.callEvent("onAfterUpdateFinish",[]); 577 if (!this.updatedRows.length) 578 this.callEvent("onFullSync",[]); 579 }, 580 581 582 583 584 585 /** 586 * @desc: initializes data-processor 587 * @param: anObj - dhtmlxGrid object to attach this data-processor to 588 * @type: public 589 */ 590 init:function(anObj){ 591 this.obj = anObj; 592 if (anObj._dp_init) 593 anObj._dp_init(this); 594 if (this.connector_init) 595 anObj._dataprocessor=this; 596 }, 597 598 599 setOnAfterUpdate:function(ev){ 600 this.attachEvent("onAfterUpdate",ev); 601 }, 602 setOnBeforeUpdateHandler:function(func){ 603 this.attachEvent("onBeforeDataSending",func); 604 }, 605 606 607 608 /* starts autoupdate mode 609 @param interval 610 time interval for sending update requests 611 */ 612 setAutoUpdate: function(interval, user) { 613 interval = interval || 2000; 614 615 this._user = user || (new Date()).valueOf(); 616 this._need_update = false; 617 this._update_busy = false; 618 619 this.attachEvent("onAfterUpdate",function(sid,action,tid,xml_node){ 620 this.afterAutoUpdate(sid, action, tid, xml_node); 621 }); 622 this.attachEvent("onFullSync",function(){ 623 this.fullSync(); 624 }); 625 626 var self = this; 627 window.setInterval(function(){ 628 self.loadUpdate(); 629 }, interval); 630 }, 631 632 633 /* process updating request answer 634 if status == collision version is depricated 635 set flag for autoupdating immidiatly 636 */ 637 afterAutoUpdate: function(sid, action, tid, xml_node) { 638 if (action == 'collision') { 639 this._need_update = true; 640 return false; 641 } else { 642 return true; 643 } 644 }, 645 646 647 /* callback function for onFillSync event 648 call update function if it's need 649 */ 650 fullSync: function() { 651 if (this._need_update == true) { 652 this._need_update = false; 653 this.loadUpdate(); 654 } 655 return true; 656 }, 657 658 659 /* sends query to the server and call callback function 660 */ 661 getUpdates: function(url,callback){ 662 if (this._update_busy) 663 return false; 664 else 665 this._update_busy = true; 666 667 dhx4.ajax.get(url, callback); 668 }, 669 670 671 /* returns xml node value 672 @param node 673 xml node 674 */ 675 _v: function(node) { 676 if (node.firstChild) return node.firstChild.nodeValue; 677 return ""; 678 }, 679 680 681 /* returns values array of xml nodes array 682 @param arr 683 array of xml nodes 684 */ 685 _a: function(arr) { 686 var res = []; 687 for (var i=0; i < arr.length; i++) { 688 res[i]=this._v(arr[i]); 689 }; 690 return res; 691 }, 692 693 694 /* loads updates and processes them 695 */ 696 loadUpdate: function(){ 697 var self = this; 698 var version = this.obj.getUserData(0,"version"); 699 var url = this.serverProcessor+this.url(this.serverProcessor)+["dhx_user="+this._user,"dhx_version="+version].join("&"); 700 url = url.replace("editing=true&",""); 701 this.getUpdates(url, function(r){ 702 var top = dhx4.ajax.xmltop("updates", r.xmlDoc); 703 var vers = dhx4.ajax.xpath("//userdata", top); 704 self.obj.setUserData(0,"version",self._v(vers[0])); 705 706 var upds = dhx4.ajax.xpath("//update", top); 707 if (upds.length){ 708 self._silent_mode = true; 709 710 for (var i=0; i<upds.length; i++) { 711 var status = upds[i].getAttribute('status'); 712 var id = upds[i].getAttribute('id'); 713 var parent = upds[i].getAttribute('parent'); 714 switch (status) { 715 case 'inserted': 716 self.callEvent("insertCallback",[upds[i], id, parent]);
717 break; 718 case 'updated': 719 self.callEvent("updateCallback",[upds[i], id, parent]); 720 break; 721 case 'deleted': 722 self.callEvent("deleteCallback",[upds[i], id, parent]); 723 break; 724 } 725 } 726 727 self._silent_mode = false; 728 } 729 730 self._update_busy = false; 731 self = null; 732 }); 733 } 734 735}; 736 737//(c)dhtmlx ltd. www.dhtmlx.com
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.