f2273b5ffe9ac3f4bcfc9c0b9a7c18194e513ab5
[infodrom/rico3] / minsrc / ricoLiveGridAjax.js
1 /*
2  *  (c) 2005-2011 Richard Cowin (http://openrico.org)
3  *  (c) 2005-2011 Matt Brown (http://dowdybrown.com)
4  *
5  *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
6  *  file except in compliance with the License. You may obtain a copy of the License at
7  *
8  *         http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *  Unless required by applicable law or agreed to in writing, software distributed under the
11  *  License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
12  *  either express or implied. See the License for the specific language governing permissions
13  *  and limitations under the License.
14  */
15
16 if(typeof Rico=='undefined') throw("LiveGridAjax requires the Rico JavaScript framework");
17
18 if (!Rico.Buffer) Rico.Buffer = {};
19
20 Rico.Buffer.AjaxLoadOnce = function(url,options,ajaxOptions) {
21   this.initialize(url,options,ajaxOptions);
22 }
23
24 Rico.Buffer.AjaxLoadOnce.prototype = {
25 /**
26  * @class Implements buffer for LiveGrid. Loads data from server via a single AJAX call.
27  * @extends Rico.Buffer.Base
28  * @constructs
29  */
30   initialize: function(url,options,ajaxOptions) {
31     Rico.extend(this, new Rico.Buffer.Base());
32     Rico.extend(this, Rico.Buffer.AjaxXMLMethods);
33     this.dataSource=url;
34     this.options.bufferTimeout=20000;            // time to wait for ajax response (milliseconds)
35     this.options.requestParameters=[];
36     this.options.waitMsg=Rico.getPhraseById("waitForData");  // replace this with an image tag if you prefer
37     this.options.canFilter=true;
38     this.options.fmt='xml';
39     Rico.extend(this.options, options || {});
40     this.ajaxOptions = { parameters: null, method : 'get' };
41     Rico.extend(this.ajaxOptions, ajaxOptions || {});
42     this.requestCount=0;
43     this.processingRequest=false;
44     this.pendingRequest=-2;
45     this.fetchData=true;
46     this.sortParm={};
47   }
48 }
49
50 Rico.Buffer.AjaxXMLMethods = {
51
52 /** @lends Rico.Buffer.AjaxLoadOnce# */
53   fetch: function(offset) {
54     if (this.fetchData) {
55       this.foundRowCount=true;
56       this.fetchData=false;
57       this.processingRequest=true;
58       this.liveGrid.showMsg(this.options.waitMsg);
59       this.timeoutHandler = Rico.runLater(this.options.bufferTimeout,this,'handleTimedOut');
60       this.ajaxOptions.parameters = this.formQueryHashXML(0,-1);
61       Rico.log('sending request');
62       var self=this;
63       if (typeof this.dataSource=='string') {
64         this.ajaxOptions.onComplete = function(xhr) { self.ajaxUpdate(offset,xhr); };
65         new Rico.ajaxRequest(this.dataSource, this.ajaxOptions);
66       } else {
67         this.ajaxOptions.onComplete = function(newRows, newStyle, totalRows, errMsg) { self.jsUpdate(offset, newRows, newStyle, totalRows, errMsg); };
68         this.dataSource(this.ajaxOptions);
69       }
70     } else {
71       if (offset < 0) {
72         this.applyFilters();
73         this.setTotalRows(this.size);
74         offset=0;
75       }
76       this.liveGrid.refreshContents(offset);
77     }
78   },
79
80 /**
81  * Server did not respond in time... assume that there could have been
82  * an error, and allow requests to be processed again.
83  */
84   handleTimedOut: function() {
85     Rico.log("Request Timed Out");
86     this.liveGrid.showMsg(Rico.getPhraseById("requestTimedOut"));
87   },
88
89   formQueryHashXML: function(startPos,fetchSize) {
90     var queryHash= {
91       id: this.liveGrid.tableId,
92       page_size: (typeof fetchSize=='number') ? fetchSize : this.totalRows,
93       offset: startPos.toString()
94     };
95     if (!this.foundRowCount) queryHash['get_total']='true';
96     if (this.options.requestParameters) {
97       for ( var i=0; i < this.options.requestParameters.length; i++ ) {
98         var anArg = this.options.requestParameters[i];
99         if ( anArg.name != undefined && anArg.value != undefined ) {
100           queryHash[anArg.name]=anArg.value;
101         } else {
102           var ePos  = anArg.indexOf('=');
103           var argName  = anArg.substring( 0, ePos );
104           var argValue = anArg.substring( ePos + 1 );
105           queryHash[argName]=argValue;
106         }
107       }
108     }
109     return queryHash;
110   },
111
112   clearTimer: function() {
113     if(typeof this.timeoutHandler != "number") return;
114     window.clearTimeout(this.timeoutHandler);
115     delete this.timeoutHandler;
116   },
117
118   // used by both LoadOnce and SQL buffers
119   jsUpdate: function(startPos, newRows, newStyle, totalRows, errMsg) {
120     this.clearTimer();
121     this.processingRequest=false;
122     Rico.log("jsUpdate: "+arguments.length);
123     if (errMsg) {
124       Rico.log("jsUpdate: received error="+errMsg);
125       this.liveGrid.showMsg(Rico.getPhraseById("requestError",errMsg));
126       return;
127     }
128     this.rcvdRows = newRows.length;
129     if (typeof totalRows=='number') {
130       this.rowcntContent = totalRows.toString();
131       this.rcvdRowCount = true;
132       this.foundRowCount = true;
133       Rico.log("jsUpdate: found RowCount="+this.rowcntContent);
134     }
135     this.updateBuffer(startPos, newRows, newStyle);
136     if (this.options.onAjaxUpdate)
137       this.options.onAjaxUpdate();
138     this.updateGrid(startPos);
139     if (this.options.TimeOut && this.timerMsg)
140       this.restartSessionTimer();
141     if (this.pendingRequest>=-1) {
142       var offset=this.pendingRequest;
143       Rico.log("jsUpdate: found pending request for offset="+offset);
144       this.pendingRequest=-2;
145       this.fetch(offset);
146     }
147   },
148
149   // used by both LoadOnce and SQL buffers
150   ajaxUpdate: function(startPos,xhr) {
151     this.clearTimer();
152     this.processingRequest=false;
153     if (xhr.status != 200) {
154       Rico.log("ajaxUpdate: received http error="+xhr.status);
155       this.liveGrid.showMsg(Rico.getPhraseById("httpError",xhr.status));
156       return;
157     }
158     this._responseHandler=this['processResponse'+this.options.fmt.toUpperCase()];
159     if (!this._responseHandler(startPos,xhr)) return;
160     if (this.options.onAjaxUpdate)
161       this.options.onAjaxUpdate();
162     this.updateGrid(startPos);
163     if (this.options.TimeOut && this.timerMsg)
164       this.restartSessionTimer();
165     if (this.pendingRequest>=-1) {
166       var offset=this.pendingRequest;
167       Rico.log("ajaxUpdate: found pending request for offset="+offset);
168       this.pendingRequest=-2;
169       this.fetch(offset);
170     }
171   },
172   
173   // used by both LoadOnce and SQL buffers
174   processResponseXML: function(startPos,request) {
175     // The response text may contain META DATA for debugging if client side debugging is enabled in VS\r
176     var xmlDoc = request.responseXML;\r
177     if (request.responseText.substring(0, 4) == "<!--") {\r
178       var nEnd = request.responseText.indexOf("-->");\r
179       if (nEnd == -1) {\r
180         this.liveGrid.showMsg('Web server error - client side debugging may be enabled');\r
181         return false;\r
182       }\r
183       xmlDoc = Rico.createXmlDocument();\r
184       xmlDoc.loadXML(request.responseText.substring(nEnd+3));\r
185     }
186     
187     if (!xmlDoc) {
188       alert("Data provider returned an invalid XML response");
189       Rico.log("Data provider returned an invalid XML response");
190       return false;
191     }
192
193     // process children of <ajax-response>
194     var response = xmlDoc.getElementsByTagName("ajax-response");
195     if (response == null || response.length != 1) {
196       alert("Received invalid response from server");
197       return false;
198     }
199     Rico.log("Processing ajax-response");
200     this.rcvdRows = 0;
201     this.rcvdRowCount = false;
202     var ajaxResponse=response[0];
203     var debugtags = ajaxResponse.getElementsByTagName('debug');
204     for (var i=0; i<debugtags.length; i++)
205       Rico.log("ajaxUpdate: debug msg "+i+": "+Rico.getContentAsString(debugtags[i],this.options.isEncoded));
206     var error = ajaxResponse.getElementsByTagName('error');
207     if (error.length > 0) {
208       var msg=Rico.getContentAsString(error[0],this.options.isEncoded);
209       alert("Data provider returned an error:\n"+msg);
210       Rico.log("Data provider returned an error:\n"+msg);
211       return false;
212     }
213     var rowsElement = ajaxResponse.getElementsByTagName('rows')[0];
214     if (!rowsElement) {
215       Rico.log("ajaxUpdate: invalid response");
216       this.liveGrid.showMsg(Rico.getPhraseById("invalidResponse"));
217       return false;
218     }
219     var rowcnttags = ajaxResponse.getElementsByTagName('rowcount');
220     if (rowcnttags && rowcnttags.length==1) {
221       this.rowcntContent = Rico.getContentAsString(rowcnttags[0],this.options.isEncoded);
222       this.rcvdRowCount = true;
223       this.foundRowCount = true;
224       Rico.log("ajaxUpdate: found RowCount="+this.rowcntContent);
225     }
226
227     // process <rows>
228     this.updateUI = rowsElement.getAttribute("update_ui") == "true";
229     this.rcvdOffset = rowsElement.getAttribute("offset");
230     Rico.log("ajaxUpdate: rcvdOffset="+this.rcvdOffset);
231     var newRows = this.dom2jstable(rowsElement);
232     var newStyle = (this.options.acceptStyle) ? this.dom2jstableStyle(rowsElement) : false;
233     this.rcvdRows = newRows.length;
234     this.updateBuffer(startPos, newRows, newStyle);
235     return true;
236   },
237
238   dom2jstableStyle: function(rowsElement,firstRow) {
239     var acceptStyle=this.options.acceptStyle;
240     Rico.log("dom2jstableStyle start");
241     var newRows = [];
242     var trs = rowsElement.getElementsByTagName("tr");
243     for ( var i=firstRow || 0; i < trs.length; i++ ) {
244       var row = [];
245       var cells = trs[i].getElementsByTagName("td");
246       for ( var j=0; j < cells.length ; j++ ) {
247         row[j]=cells[j].getAttribute('style') || '';
248       }
249       newRows.push( row );
250     }
251     Rico.log("dom2jstableStyle end");
252     return newRows;
253   },
254
255   processResponseJSON: function(startPos,request) {
256     var json = Rico.getJSON(request);
257     if (!json || json == null) {
258       alert("Data provider returned an invalid JSON response");
259       Rico.log("Data provider returned an invalid JSON response");
260       return false;
261     }
262
263     if (json.debug) {
264       for (var i=0; i<json.debug.length; i++)
265         Rico.writeDebugMsg("debug msg "+i+": "+json.debug[i]);
266     }
267     if (json.error) {
268       alert("Data provider returned an error:\n"+json.error);
269       Rico.writeDebugMsg("Data provider returned an error:\n"+json.error);
270       return false;
271     }
272
273     if (json.rowcount) {
274       this.rowcntContent = json.rowcount;
275       this.rcvdRowCount = true;
276       this.foundRowCount = true;
277       Rico.writeDebugMsg("loadRows, found RowCount="+json.rowcount);
278     }
279
280     this.rcvdRows = json.rows.length;
281     this.updateBuffer(startPos, json.rows, json.styles);
282     return true;
283   },
284
285   // specific to LoadOnce buffer
286   updateBuffer: function(start, newRows, newStyle) {
287     this.baseRows = newRows;
288     this.attr = newStyle;
289     Rico.log("updateBuffer: # of rows="+this.rcvdRows);
290     this.rcvdRowCount=true;
291     this.rowcntContent=this.rcvdRows;
292     if (typeof this.delayedSortCol=='number')
293       this.sortBuffer(this.delayedSortCol);
294     this.applyFilters();
295     this.startPos = 0;
296   },
297
298   // used by both LoadOnce and SQL buffers
299   updateGrid: function(offset) {
300     Rico.log("updateGrid, size="+this.size+' rcv cnt type='+typeof(this.rowcntContent));
301     var newpos;
302     if (this.rcvdRowCount==true) {
303       Rico.log("found row cnt: "+this.rowcntContent);
304       var eofrow=parseInt(this.rowcntContent,10);
305       var lastTotalRows=this.totalRows;
306       if (!isNaN(eofrow) && eofrow!=lastTotalRows) {
307         this.setTotalRows(eofrow);
308         newpos=Math.min(this.liveGrid.topOfLastPage(),offset);
309         Rico.log("updateGrid: new rowcnt="+eofrow+" newpos="+newpos);
310         if (lastTotalRows==0 && this.liveGrid.sizeTo=='data')
311           Rico.runLater(100,this.liveGrid,'adjustPageSize');  // FF takes a long time to calc initial size
312         this.liveGrid.scrollToRow(newpos);
313         if ( this.isInRange(newpos) ) {
314           this.liveGrid.refreshContents(newpos);
315         } else {
316           this.fetch(newpos);
317         }
318         return;
319       }
320     } else {
321       var lastbufrow=offset+this.rcvdRows;
322       if (lastbufrow>this.totalRows) {
323         var newcnt=lastbufrow;
324         Rico.log("extending totrows to "+newcnt);
325         this.setTotalRows(newcnt);
326       }
327     }
328     newpos=this.liveGrid.pixeltorow(this.liveGrid.scrollDiv.scrollTop);
329     Rico.log("updateGrid: newpos="+newpos);
330     this.liveGrid.refreshContents(newpos);
331   }
332
333 };
334
335
336
337 Rico.Buffer.AjaxSQL = function(url,options,ajaxOptions) {
338   this.initialize(url,options,ajaxOptions);
339 }
340
341 Rico.Buffer.AjaxSQL.prototype = {
342 /**
343  * @class Implements buffer for LiveGrid. Loads data from server in chunks as user scrolls through the grid.
344  * @extends Rico.Buffer.AjaxLoadOnce
345  * @constructs
346  */
347   initialize: function(url,options,ajaxOptions) {
348     Rico.extend(this, new Rico.Buffer.AjaxLoadOnce());
349     Rico.extend(this, Rico.Buffer.AjaxSQLMethods);
350     this.dataSource=url;
351     this.options.canFilter=true;
352     this.options.largeBufferSize  = 7.0;   // 7 pages
353     this.options.nearLimitFactor  = 1.0;   // 1 page
354     this.options.canRefresh=true;
355     Rico.extend(this.options, options || {});
356     Rico.extend(this.ajaxOptions, ajaxOptions || {});
357   }
358 }
359
360 Rico.Buffer.AjaxSQLMethods = {
361 /** @lends Rico.Buffer.AjaxSQL# */
362
363   registerGrid: function(liveGrid) {
364     this.liveGrid = liveGrid;
365     this.sessionExpired=false;
366     this.timerMsg=document.getElementById(liveGrid.tableId+'_timer');
367     if (this.options.TimeOut && this.timerMsg) {
368       if (!this.timerMsg.title) this.timerMsg.title=Rico.getPhraseById("sessionExpireMinutes");
369       this.restartSessionTimer();
370     }
371   },
372
373   setBufferSize: function(pageSize) {
374     this.maxFetchSize = Math.max(50,parseInt(this.options.largeBufferSize * pageSize,10));
375     this.nearLimit = parseInt(this.options.nearLimitFactor * pageSize,10);
376     this.maxBufferSize = this.maxFetchSize * 3;
377   },
378
379   restartSessionTimer: function() {
380     if (this.sessionExpired==true) return;
381     this.sessionEndTime = (new Date()).getTime() + this.options.TimeOut*60000;
382     if (this.sessionTimer) clearTimeout(this.sessionTimer);
383     this.updateSessionTimer();
384   },
385
386   updateSessionTimer: function() {
387     var now=(new Date()).getTime();
388     if (now > this.sessionEndTime) {
389       this.displaySessionTimer(Rico.getPhraseById("sessionExpired"));
390       this.timerMsg.style.backgroundColor="red";
391       this.sessionExpired=true;
392     } else {
393       var timeRemaining=Math.ceil((this.sessionEndTime - now) / 60000);
394       this.displaySessionTimer(timeRemaining);
395       this.sessionTimer=Rico.runLater(10000,this,'updateSessionTimer');
396     }
397   },
398
399   displaySessionTimer: function(msg) {
400     this.timerMsg.innerHTML='&nbsp;'+msg+'&nbsp;';
401   },
402
403   /**
404    * Update the grid with fresh data from the database, maintaining scroll position.
405    * @param resetRowCount indicates whether the total row count should be refreshed as well
406    */
407   refresh: function(resetRowCount) {
408     var lastGridPos=this.liveGrid.lastRowPos;\r
409     this.clear();
410     if (resetRowCount) {
411       this.setTotalRows(0);
412       this.foundRowCount = false;
413     }
414     this.liveGrid.clearBookmark();
415     this.liveGrid.clearRows();
416     this.fetch(lastGridPos);
417   },
418
419   /**
420    * Fetch data from database.
421    * @param offset position (row) within the dataset (-1=clear existing buffer before issuing request)
422    */
423   fetch: function(offset) {
424     Rico.log("AjaxSQL fetch: offset="+offset+', lastOffset='+this.lastOffset);
425     if (this.processingRequest) {
426       Rico.log("AjaxSQL fetch: queue request");
427       this.pendingRequest=offset;
428       return;
429     }
430     if ((typeof offset == 'undefined') || (offset < 0)) {
431       this.clear();
432       this.setTotalRows(0);
433       this.foundRowCount = false;
434       offset=0;
435     }
436     var lastOffset = this.lastOffset;
437     this.lastOffset = offset;
438     if (this.isInRange(offset)) {
439       Rico.log("AjaxSQL fetch: in buffer");
440       this.liveGrid.refreshContents(offset);
441       if (offset > lastOffset) {
442         if (offset+this.liveGrid.pageSize < this.endPos()-this.nearLimit) return;
443         if (this.endPos()==this.totalRows && this.foundRowCount) return;
444       } else if (offset < lastOffset) {
445         if (offset > this.startPos+this.nearLimit) return;
446         if (this.startPos==0) return;
447       } else return;
448     }
449     if (offset >= this.totalRows && this.foundRowCount) return;
450
451     this.processingRequest=true;
452     Rico.log("AjaxSQL fetch: processing offset="+offset);
453     var bufferStartPos = this.getFetchOffset(offset);
454     var fetchSize = this.getFetchSize(bufferStartPos);
455     var partialLoaded = false;
456
457     this.liveGrid.showMsg(this.options.waitMsg);
458     this.timeoutHandler = Rico.runLater(this.options.bufferTimeout, this, 'handleTimedOut');
459     this.ajaxOptions.parameters = this.formQueryHashSQL(bufferStartPos,fetchSize,this.options.fmt);
460     this.requestCount++;
461     Rico.log('sending req #'+this.requestCount);
462     var self=this;
463     if (typeof this.dataSource=='string') {
464       this.ajaxOptions.onComplete = function(xhr) { self.ajaxUpdate(bufferStartPos, xhr); };
465       new Rico.ajaxRequest(this.dataSource, this.ajaxOptions);
466     } else {
467       this.ajaxOptions.onComplete = function(newRows, newStyle, totalRows, errMsg) { self.jsUpdate(bufferStartPos, newRows, newStyle, totalRows, errMsg); };
468       this.dataSource(this.ajaxOptions);
469     }
470   },
471
472   formQueryHashSQL: function(startPos,fetchSize,fmt) {
473     var queryHash=this.formQueryHashXML(startPos,fetchSize);
474     queryHash[this.liveGrid.actionId]="query";
475     if (fmt) queryHash._fmt=fmt;
476
477     // sort
478     Rico.extend(queryHash,this.sortParm);
479
480     // filters
481     for (var n=0; n<this.liveGrid.columns.length; n++) {
482       var c=this.liveGrid.columns[n];
483       if (c.filterType == Rico.ColumnConst.UNFILTERED) continue;
484       var colnum=typeof(c.format.filterCol)=='number' ? c.format.filterCol : c.index;
485       queryHash['f['+colnum+'][op]']=c.filterOp;
486       queryHash['f['+colnum+'][len]']=c.filterValues.length;
487       for (var i=0; i<c.filterValues.length; i++) {
488         var fval=c.filterValues[i];
489         if (c.filterOp=='LIKE' && fval.indexOf('*')==-1) fval='*'+fval+'*';
490         queryHash['f['+colnum+']['+i+']']=fval;
491       }
492     }
493     return queryHash;
494   },
495
496   getFetchSize: function(adjustedOffset) {
497     var adjustedSize = 0;
498     if (adjustedOffset >= this.startPos) { //appending
499       var endFetchOffset = this.maxFetchSize + adjustedOffset;
500       adjustedSize = endFetchOffset - adjustedOffset;
501       if(adjustedOffset == 0 && adjustedSize < this.maxFetchSize)
502         adjustedSize = this.maxFetchSize;
503       Rico.log("getFetchSize/append, adjustedSize="+adjustedSize+" adjustedOffset="+adjustedOffset+' endFetchOffset='+endFetchOffset);
504     } else { //prepending
505       adjustedSize = Math.min(this.startPos - adjustedOffset,this.maxFetchSize);
506     }
507     return adjustedSize;
508   },
509
510   getFetchOffset: function(offset) {
511     var adjustedOffset = offset;
512     if (offset > this.startPos)
513       adjustedOffset = Math.max(offset, this.endPos());  //appending
514     else if (offset + this.maxFetchSize >= this.startPos)
515       adjustedOffset = Math.max(this.startPos - this.maxFetchSize, 0);  //prepending
516     return adjustedOffset;
517   },
518
519   updateBuffer: function(start, newRows, newStyle) {
520     Rico.log("updateBuffer: start="+start+", # of rows="+this.rcvdRows);
521     if (this.rows.length == 0) { // initial load
522       this.rows = newRows;
523       this.attr = newStyle;
524       this.startPos = start;
525     } else if (start > this.startPos) { //appending
526       if (this.startPos + this.rows.length < start) {
527         this.rows =  newRows;
528         this.attr = newStyle;
529         this.startPos = start;
530       } else {
531         this.rows = this.rows.concat( newRows.slice(0, newRows.length));
532         if (this.attr) this.attr = this.attr.concat( newStyle.slice(0, newStyle.length));
533         if (this.rows.length > this.maxBufferSize) {
534           var fullSize = this.rows.length;
535           this.rows = this.rows.slice(this.rows.length - this.maxBufferSize, this.rows.length);
536           if (this.attr) this.attr = this.attr.slice(this.attr.length - this.maxBufferSize, this.attr.length);
537           this.startPos = this.startPos +  (fullSize - this.rows.length);
538         }
539       }
540     } else { //prepending
541       if (start + newRows.length < this.startPos) {
542         this.rows = newRows;
543         this.attr = newStyle;
544       } else {
545         this.rows = newRows.slice(0, this.startPos).concat(this.rows);
546         if (newStyle) this.attr = newStyle.slice(0, this.startPos).concat(this.attr);
547         if (this.maxBufferSize && this.rows.length > this.maxBufferSize) {
548           this.rows = this.rows.slice(0, this.maxBufferSize);
549           if (this.attr) this.attr = this.attr.slice(0, this.maxBufferSize);
550         }
551       }
552       this.startPos =  start;
553     }
554     this.size = this.rows.length;
555   },
556
557   sortBuffer: function(colnum) {
558     this.sortParm={};
559     var col=this.liveGrid.columns[colnum];
560     if (this.options.sortParmFmt) {
561       this.sortParm['sort_col']=col[this.options.sortParmFmt];
562       this.sortParm['sort_dir']=col.getSortDirection();
563     } else {
564       this.sortParm['s'+colnum]=col.getSortDirection();
565     }
566     this.clear();
567   },
568
569   printAllSQL: function(exportType) {
570     var parms=this.formQueryHashSQL(0,this.liveGrid.options.maxPrint,exportType);
571     parms.hidden=this.liveGrid.listInvisible('index').join(',');
572     var url=this.dataSource+'?'+Rico.toQueryString(parms);
573     window.open(url,'',this.liveGrid.options.exportWindow);
574   },
575
576   printVisibleSQL: function(exportType) {
577     var parms=this.formQueryHashSQL(this.liveGrid.contentStartPos-1, this.liveGrid.pageSize, exportType);
578     parms.hidden=this.liveGrid.listInvisible('index').join(',');
579     var url=this.dataSource+'?'+Rico.toQueryString(parms);
580     window.open(url,'',this.liveGrid.options.exportWindow);
581   },
582
583   // for datasource that is a javascript function
584   _printAll: function() {
585     this.liveGrid.exportStart();
586     this.ajaxOptions.parameters = this.formQueryHashSQL(0,this.liveGrid.options.maxPrint);
587     var self=this;
588     this.ajaxOptions.onComplete = function() { self._jsExport(); };
589     this.dataSource(this.ajaxOptions);
590   },
591
592   _jsExport: function(newRows, newStyle, totalRows, errMsg) {
593     Rico.log("_jsExport: "+arguments.length);
594     if (errMsg) {
595       Rico.log("_jsExport: received error="+errMsg);
596       this.liveGrid.showMsg(Rico.getPhraseById("requestError",errMsg));
597       return;
598     }
599     this.exportBuffer(newRows,0);
600     this.liveGrid.exportFinish();
601   }
602
603 };