Updated Rico2 and Rico3 with fixes for IE9. Updated Rico2 version to 2.3. Updated...
[infodrom/rico3] / ricoClient / js / ricoLiveGrid.js
1 /*
2  *  (c) 2005-2009 Richard Cowin (http://openrico.org)
3  *  (c) 2005-2009 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("LiveGrid requires the Rico JavaScript framework");
17
18
19 /** @namespace */
20 if (!Rico.Buffer) Rico.Buffer = {};
21
22 Rico.Buffer.Base = function(dataTable, options) {
23   this.initialize(dataTable, options);
24 }
25 /** @lends Rico.Buffer.Base# */
26 Rico.Buffer.Base.prototype = {
27 /**
28  * @class Defines the static buffer class (no AJAX).
29  * Loads buffer with data that already exists in the document as an HTML table or passed via javascript.
30  * Also serves as a base class for AJAX-enabled buffers.
31  * @constructs
32  */
33   initialize: function(dataTable, options) {
34     this.clear();
35     this.updateInProgress = false;
36     this.lastOffset = 0;
37     this.rcvdRowCount = false;  // true if an eof element was included in the last xml response
38     this.foundRowCount = false; // true if an xml response is ever received with eof true
39     this.totalRows = 0;
40     this.rowcntContent = "";
41     this.rcvdOffset = -1;
42     this.options = {
43       fixedHdrRows     : 0,
44       canFilter        : true,  // does buffer object support filtering?
45       isEncoded        : true,  // is the data received via ajax html encoded?
46       acceptAttr       : []     // attributes that can be copied from original/ajax data (e.g. className, style, id)
47     };
48     Rico.extend(this.options, options || {});
49     if (dataTable) {
50       this.loadRowsFromTable(dataTable,this.options.fixedHdrRows);
51     } else {
52       this.clear();
53     }
54   },
55
56   registerGrid: function(liveGrid) {
57     this.liveGrid = liveGrid;
58   },
59
60   setTotalRows: function( newTotalRows ) {
61     if (typeof(newTotalRows)!='number') newTotalRows=this.size;
62     if (this.totalRows == newTotalRows) return;
63     this.totalRows = newTotalRows;
64     if (!this.liveGrid) return;
65     Rico.log("setTotalRows, newTotalRows="+newTotalRows);
66     switch (this.liveGrid.sizeTo) {
67       case 'data':
68         this.liveGrid.resizeWindow();
69         break;
70       case 'datamax':
71         this.liveGrid.setPageSize(newTotalRows);
72         break;
73       default:
74         this.liveGrid.updateHeightDiv();
75         break;
76     }
77   },
78
79   loadRowsFromTable: function(tableElement,firstRow) {
80     var newRows = new Array();
81     var trs = tableElement.getElementsByTagName("tr");
82     for ( var i=firstRow || 0; i < trs.length; i++ ) {
83       var row = new Array();
84       var cells = trs[i].getElementsByTagName("td");
85       for ( var j=0; j < cells.length ; j++ )
86         row[j]=cells[j].innerHTML;
87       newRows.push( row );
88     }
89     this.loadRows(newRows);
90   },
91
92   loadRowsFromArray: function(array2D) {
93     for ( var i=0; i < array2D.length; i++ ) {
94       for ( var j=0; j < array2D[i].length ; j++ ) {
95         array2D[i][j]=array2D[i][j].toString();
96       }
97     }
98     this.loadRows(array2D);
99   },
100
101   loadRows: function(jstable) {
102     this.baseRows = jstable;
103     this.startPos = 0;
104     this.size = this.baseRows.length;
105   },
106
107   dom2jstable: function(rowsElement) {
108     Rico.log('dom2jstable: encoded='+this.options.isEncoded);
109     var newRows = new Array();
110     var trs = rowsElement.getElementsByTagName("tr");
111     for ( var i=0; i < trs.length; i++ ) {
112       var row = new Array();
113       var cells = trs[i].getElementsByTagName("td");
114       for ( var j=0; j < cells.length ; j++ )
115         row[j]=Rico.getContentAsString(cells[j],this.options.isEncoded);
116       newRows.push( row );
117     }
118     return newRows;
119   },
120
121   dom2jstableAttr: function(rowsElement,firstRow) {
122     var acceptAttr=this.options.acceptAttr;
123     Rico.log("dom2jstableAttr start, # attr="+acceptAttr.length);
124     var newRows = new Array();
125     var trs = rowsElement.getElementsByTagName("tr");
126     for ( var i=firstRow || 0; i < trs.length; i++ ) {
127       var row = new Array();
128       var cells = trs[i].getElementsByTagName("td");
129       for ( var j=0; j < cells.length ; j++ ) {
130         row[j]={};
131         for (var k=0; k<acceptAttr.length; k++)
132           row[j]['_'+acceptAttr[k]]=cells[j].getAttribute(acceptAttr[k]);
133         if (Rico.isIE) row[j]._class=cells[j].getAttribute('className');
134       }
135       newRows.push( row );
136     }
137     Rico.log("dom2jstableAttr end");
138     return newRows;
139   },
140
141   _blankRow: function() {
142     var newRow=[];
143     for (var i=0; i<this.liveGrid.columns.length; i++) {
144       newRow[i]='';
145     }
146     return newRow;
147   },
148
149   deleteRows: function(rowIndex,cnt) {
150     this.baseRows.splice(rowIndex,typeof(cnt)=='number' ? cnt : 1);
151     this.liveGrid.isPartialBlank=true;
152     this.size=this.baseRows.length;
153   },
154
155   insertRow: function(beforeRowIndex) {
156     var r=this._blankRow();
157     this.baseRows.splice(beforeRowIndex,0,r);
158     this.size=this.baseRows.length;
159     this.liveGrid.isPartialBlank=true;
160     if (this.startPos < 0) this.startPos=0;
161     return r;
162   },
163
164   appendRows: function(cnt) {
165     var newRows=[];
166     for (var i=0; i<cnt; i++) {
167       var r=this._blankRow();
168       this.baseRows.push(r);
169       newRows.push(r);
170     }
171     this.size=this.baseRows.length;
172     this.liveGrid.isPartialBlank=true;
173     if (this.startPos < 0) this.startPos=0;
174     return newRows;
175   },
176   
177   sortFunc: function(coltype) {
178     switch (coltype) {
179       case 'number': return Rico.bind(this,'_sortNumeric');
180       case 'control':return Rico.bind(this,'_sortControl');
181       default:       return Rico.bind(this,'_sortAlpha');
182     }
183   },
184
185   sortBuffer: function(colnum) {
186     if (!this.baseRows) {
187       this.delayedSortCol=colnum;
188       return;
189     }
190     this.liveGrid.showMsg(Rico.getPhraseById("sorting"));
191     this.sortColumn=colnum;
192     var col=this.liveGrid.columns[colnum];
193     this.getValFunc=col._sortfunc;
194     this.baseRows.sort(this.sortFunc(col.format.type));
195     if (col.getSortDirection()=='DESC') this.baseRows.reverse();
196   },
197   
198   _sortAlpha: function(a,b) {
199     var aa = this.sortColumn<a.length ? Rico.getInnerText(a[this.sortColumn]) : '';
200     var bb = this.sortColumn<b.length ? Rico.getInnerText(b[this.sortColumn]) : '';
201     if (aa==bb) return 0;
202     if (aa<bb) return -1;
203     return 1;
204   },
205
206   _sortNumeric: function(a,b) {
207     var aa = this.sortColumn<a.length ? this.nan2zero(Rico.getInnerText(a[this.sortColumn])) : 0;
208     var bb = this.sortColumn<b.length ? this.nan2zero(Rico.getInnerText(b[this.sortColumn])) : 0;
209     return aa-bb;
210   },
211
212   nan2zero: function(n) {
213     if (typeof(n)=='string') n=parseFloat(n);
214     return isNaN(n) || typeof(n)=='undefined' ? 0 : n;
215   },
216   
217   _sortControl: function(a,b) {
218     var aa = this.sortColumn<a.length ? Rico.getInnerText(a[this.sortColumn]) : '';
219     var bb = this.sortColumn<b.length ? Rico.getInnerText(b[this.sortColumn]) : '';
220     if (this.getValFunc) {
221       aa=this.getValFunc(aa);
222       bb=this.getValFunc(bb);
223     }
224     if (aa==bb) return 0;
225     if (aa<bb) return -1;
226     return 1;
227   },
228
229   clear: function() {
230     this.baseRows = [];
231     this.rows = [];
232     this.startPos = -1;
233     this.size = 0;
234     this.windowPos = 0;
235   },
236
237   isInRange: function(position) {
238     var lastRow=Math.min(this.totalRows, position + this.liveGrid.pageSize);
239     return (position >= this.startPos) && (lastRow <= this.endPos()); // && (this.size != 0);
240   },
241
242   endPos: function() {
243     return this.startPos + this.rows.length;
244   },
245
246   fetch: function(offset) {
247     Rico.log('fetch '+this.liveGrid.tableId+': offset='+offset);
248     this.applyFilters();
249     this.setTotalRows();
250     this.rcvdRowCount = true;
251     this.foundRowCount = true;
252     if (offset < 0) offset=0;
253     this.liveGrid.refreshContents(offset);
254     return;
255   },
256
257 /**
258  * @return a 2D array of buffer data representing the rows that are currently visible on the grid
259  */
260   visibleRows: function() {
261     return this.rows.slice(this.windowStart,this.windowEnd);
262   },
263
264   setWindow: function(startrow, endrow) {
265     this.windowStart = startrow - this.startPos;  // position in the buffer of first visible row
266     Rico.log('setWindow '+this.liveGrid.tableId+': '+startrow+', '+endrow+', newstart='+this.windowStart);
267     this.windowEnd = Math.min(endrow,this.size);  // position in the buffer of last visible row containing data+1
268     this.windowPos = startrow;                    // position in the dataset of first visible row
269   },
270
271 /**
272  * @return true if bufRow is currently visible in the grid
273  */
274   isVisible: function(bufRow) {
275     return bufRow < this.rows.length && bufRow >= this.windowStart && bufRow < this.windowEnd;
276   },
277   
278 /**
279  * takes a window row index and returns the corresponding buffer row index
280  */
281   bufferRow: function(windowRow) {
282     return this.windowStart+windowRow;
283   },
284
285 /**
286  * @return buffer cell at the specified visible row/col index
287  */
288   getWindowCell: function(windowRow,col) {
289     var bufrow=this.bufferRow(windowRow);
290     return this.isVisible(bufrow) && col < this.rows[bufrow].length ? this.rows[bufrow][col] : null;
291   },
292
293   getWindowAttr: function(windowRow,col) {
294     var bufrow=this.bufferRow(windowRow);
295     return this.attr && this.isVisible(bufrow) && col < this.attr[bufrow].length ? this.attr[bufrow][col] : null;
296   },
297
298   getWindowValue: function(windowRow,col) {
299     return this.getWindowCell(windowRow,col);
300   },
301
302   setWindowValue: function(windowRow,col,newval) {
303     var bufrow=this.bufferRow(windowRow);
304     if (bufrow >= this.windowEnd) return false;
305     return this.setValue(bufrow,col,newval);
306   },
307
308   getCell: function(bufRow,col) {
309     return bufRow < this.size ? this.rows[bufRow][col] : null;
310   },
311
312   getValue: function(bufRow,col) {
313     return this.getCell(bufRow,col);
314   },
315
316   setValue: function(bufRow,col,newval,newstyle) {
317     if (bufRow>=this.size) return false;
318     if (!this.rows[bufRow][col]) this.rows[bufRow][col]={};
319     this.rows[bufRow][col]=newval;
320     if (typeof newstyle=='string') this.rows[bufRow][col]._style=newstyle;
321     this.rows[bufRow][col].modified=true;
322     return true;
323   },
324
325   getRows: function(start, count) {
326     var begPos = start - this.startPos;
327     var endPos = Math.min(begPos + count,this.size);
328     var results = new Array();
329     for ( var i=begPos; i < endPos; i++ ) {
330       results.push(this.rows[i]);
331     }
332     return results;
333   },
334
335   applyFilters: function() {
336     var newRows=[],re=[];
337     var r,c,n,i,showRow,filtercnt;
338     var cols=this.liveGrid.columns;
339     for (n=0,filtercnt=0; n<cols.length; n++) {
340       c=cols[n];
341       if (c.filterType == Rico.ColumnConst.UNFILTERED) continue;
342       filtercnt++;
343       if (c.filterOp=='LIKE') re[n]=new RegExp(c.filterValues[0],'i');
344     }
345     Rico.log('applyFilters: # of filters='+filtercnt);
346     if (filtercnt==0) {
347       this.rows = this.baseRows;
348     } else {
349       for (r=0; r<this.baseRows.length; r++) {
350         showRow=true;
351         for (n=0; n<cols.length && showRow; n++) {
352           c=cols[n];
353           if (c.filterType == Rico.ColumnConst.UNFILTERED) continue;
354           switch (c.filterOp) {
355             case 'LIKE':
356               showRow=re[n].test(this.baseRows[r][n]);
357               break;
358             case 'EQ':
359               showRow=this.baseRows[r][n]==c.filterValues[0];
360               break;
361             case 'NE':
362               for (i=0; i<c.filterValues.length && showRow; i++)
363                 showRow=this.baseRows[r][n]!=c.filterValues[i];
364               break;
365             case 'LE':
366               if (c.format.type=='number')
367                 showRow=this.nan2zero(this.baseRows[r][n])<=this.nan2zero(c.filterValues[0]);
368               else
369                 showRow=this.baseRows[r][n]<=c.filterValues[0];
370               break;
371             case 'GE':
372               if (c.format.type=='number')
373                 showRow=this.nan2zero(this.baseRows[r][n])>=this.nan2zero(c.filterValues[0]);
374               else
375                 showRow=this.baseRows[r][n]>=c.filterValues[0];
376               break;
377             case 'NULL':
378               showRow=this.baseRows[r][n]=='';
379               break;
380             case 'NOTNULL':
381               showRow=this.baseRows[r][n]!='';
382               break;
383           }
384         }
385         if (showRow) newRows.push(this.baseRows[r]);
386       }
387       this.rows = newRows;
388     }
389     this.rowcntContent = this.size = this.rows.length;
390   },
391
392   printAll: function(exportType) {
393     this.liveGrid.showMsg(Rico.getPhraseById('exportInProgress'));
394     Rico.runLater(10,this,'_printAll',exportType);  // allow message to paint
395   },
396
397 /**
398  * Support function for printAll()
399  */
400   _printAll: function(exportType) {
401     this.liveGrid.exportStart();
402     this.exportBuffer(this.getRows(0,this.totalRows));
403     this.liveGrid.exportFinish(exportType);
404   },
405
406 /**
407  * Copies visible rows to a new window as a simple html table.
408  */
409   printVisible: function(exportType) {
410     this.liveGrid.showMsg(Rico.getPhraseById('exportInProgress'));
411     Rico.runLater(10,this,'_printVisible',exportType);  // allow message to paint
412   },
413
414   _printVisible: function(exportType) {
415     this.liveGrid.exportStart();
416     this.exportBuffer(this.visibleRows());
417     this.liveGrid.exportFinish(exportType);
418   },
419
420 /**
421  * Send all rows to print/export window
422  */
423   exportBuffer: function(rows,startPos) {
424     var r,c,v,col,exportText;
425     Rico.log("exportBuffer: "+rows.length+" rows");
426     var exportStyles=this.liveGrid.getExportStyles(this.liveGrid.tbody[0]);
427     var tdstyle=[];
428     var totalcnt=startPos || 0;
429     var cols=this.liveGrid.columns;
430     for (c=0; c<cols.length; c++) {
431       if (cols[c].visible) tdstyle[c]=this.liveGrid.exportStyle(cols[c].cell(0),exportStyles);  // assumes row 0 style applies to all rows
432     }
433     for(r=0; r < rows.length; r++) {
434       exportText='';
435       for (c=0; c<cols.length; c++) {
436         if (!cols[c].visible) continue;
437         col=cols[c];
438         col.expStyle=tdstyle[c];
439         v=col._export(rows[r][c],rows[r]);
440         if (v=='') v='&nbsp;';
441         exportText+="<td style='"+col.expStyle+"'>"+v+"</td>";
442       }
443       this.liveGrid.exportRows.push(exportText);
444       totalcnt++;
445       if (totalcnt % 10 == 0) window.status=Rico.getPhraseById('exportStatus',totalcnt);
446     }
447   }
448
449 };
450
451
452 // Rico.LiveGrid -----------------------------------------------------
453
454 Rico.LiveGrid = function(tableId, buffer, options) {
455   this.initialize(tableId, buffer, options);
456 }
457
458 /** 
459  * @lends Rico.LiveGrid#
460  * @property tableId id string for this grid
461  * @property options the options object passed to the constructor extended with defaults
462  * @property buffer the buffer object containing the data for this grid
463  * @property columns array of {@link Rico.LiveGridColumn} objects
464  */
465 Rico.LiveGrid.prototype = {
466 /**
467  * @class Buffered LiveGrid component
468  * @extends Rico.GridCommon
469  * @constructs
470  */
471   initialize: function( tableId, buffer, options ) {
472     Rico.extend(this, Rico.GridCommon);
473     Rico.extend(this, Rico.LiveGridMethods);
474     this.baseInit();
475     this.tableId = tableId;
476     this.buffer = buffer;
477     this.actionId='_action_'+tableId;
478     Rico.setDebugArea(tableId+"_debugmsgs");    // if used, this should be a textarea
479
480     Rico.extend(this.options, {
481       visibleRows      : -3,    // -1 or 'window'=size grid to client window; -2 or 'data'=size grid to min(window,data); -3 or 'body'=size so body does not have a scrollbar; -4 or 'parent'=size to parent element (e.g. if grid is inside a div)
482       frozenColumns    : 0,
483       offset           : 0,     // first row to be displayed
484       prefetchBuffer   : true,  // load table on page load?
485       minPageRows      : 2,
486       maxPageRows      : 50,
487       canSortDefault   : true,  // can be overridden in the column specs
488       canFilterDefault : buffer.options.canFilter, // can be overridden in the column specs
489       canHideDefault   : true,  // can be overridden in the column specs
490
491       // highlight & selection parameters
492       highlightElem    : 'none',// what gets highlighted/selected (cursorRow, cursorCell, menuRow, menuCell, selection, or none)
493       highlightSection : 3,     // which section gets highlighted (frozen=1, scrolling=2, all=3, none=0)
494       highlightMethod  : 'class', // outline, class, both (outline is less CPU intensive on the client)
495       highlightClass   : Rico.theme.gridHighlightClass || 'ricoLG_selection',
496
497       // export/print parameters
498       maxPrint         : 5000,  // max # of rows that can be printed/exported, 0=disable print/export feature
499
500       // heading parameters
501       headingSort      : 'link', // link: make headings a link that will sort column, hover: make headings a hoverset, none: events on headings are disabled
502       hdrIconsFirst    : true,   // true: put sort & filter icons before header text, false: after
503       filterImg        : 'filtercol.gif'
504     });
505     // other options:
506     //   sortCol: initial sort column
507
508     this.options.sortHandler = Rico.bind(this,'sortHandler');
509     this.options.filterHandler = Rico.bind(this,'filterHandler');
510     this.options.onRefreshComplete = Rico.bind(this,'bookmarkHandler');
511     this.options.rowOverHandler = Rico.eventHandle(this,'rowMouseOver');
512     this.options.mouseDownHandler = Rico.eventHandle(this,'selectMouseDown');
513     this.options.mouseOverHandler = Rico.eventHandle(this,'selectMouseOver');
514     this.options.mouseUpHandler  = Rico.eventHandle(this,'selectMouseUp');
515     Rico.extend(this.options, options || {});
516
517     switch (typeof this.options.visibleRows) {
518       case 'string':
519         this.sizeTo=this.options.visibleRows;
520         switch (this.options.visibleRows) {
521           case 'data':   this.options.visibleRows=-2; break;
522           case 'body':   this.options.visibleRows=-3; break;
523           case 'parent': this.options.visibleRows=-4; break;
524           case 'datamax':this.options.visibleRows=-5; break;
525           default:       this.options.visibleRows=-1; break;
526         }
527         break;
528       case 'number':
529         switch (this.options.visibleRows) {
530           case -1: this.sizeTo='window'; break;
531           case -2: this.sizeTo='data'; break;
532           case -3: this.sizeTo='body'; break;
533           case -4: this.sizeTo='parent'; break;
534           case -5: this.sizeTo='datamax'; break;
535           default: this.sizeTo='fixed'; break;
536         }
537         break;
538       default:
539         this.sizeTo='body';
540         this.options.visibleRows=-3;
541         break;
542     }
543     this.highlightEnabled=this.options.highlightSection>0;
544     this.pageSize=0;
545     this.createTables();
546     if (this.headerColCnt==0) {
547       alert('ERROR: no columns found in "'+this.tableId+'"');
548       return;
549     }
550     this.createColumnArray('LiveGridColumn');
551     if (this.options.headingSort=='hover')
552       this.createHoverSet();
553
554     this.bookmark=document.getElementById(this.tableId+"_bookmark");
555     this.sizeDivs();
556     var filterUIrow=this.buffer.options.canFilter ? this.options.FilterLocation : false;
557     if (typeof(filterUIrow)=='number' && filterUIrow<0)
558       filterUIrow=this.addHeadingRow('ricoLG_FilterRow');
559     this.createDataCells(this.options.visibleRows);
560     if (this.pageSize == 0) return;
561     this.buffer.registerGrid(this);
562     if (this.buffer.setBufferSize) this.buffer.setBufferSize(this.pageSize);
563     this.scrollTimeout = null;
564     this.lastScrollPos = 0;
565     this.attachMenuEvents();
566
567     // preload the images...
568     new Image().src = Rico.imgDir+this.options.filterImg;
569     Rico.log("images preloaded");
570
571     this.setSortUI( this.options.sortCol, this.options.sortDir );
572     this.setImages();
573     if (this.listInvisible().length==this.columns.length)
574       this.columns[0].showColumn();
575     this.sizeDivs();
576     this.scrollDiv.style.display="";
577     if (this.buffer.totalRows>0)
578       this.updateHeightDiv();
579     if (this.options.prefetchBuffer) {
580       if (this.bookmark) this.bookmark.innerHTML = Rico.getPhraseById('bookmarkLoading');
581       if (this.options.canFilterDefault && this.options.getQueryParms)
582         this.checkForFilterParms();
583       this.scrollToRow(this.options.offset);
584       this.buffer.fetch(this.options.offset);
585     }
586     if (typeof(filterUIrow)=='number')
587       this.createFilters(filterUIrow);
588     this.scrollEventFunc=Rico.eventHandle(this,'handleScroll');
589     this.wheelEventFunc=Rico.eventHandle(this,'handleWheel');
590     this.wheelEvent=(Rico.isIE || Rico.isOpera || Rico.isWebKit) ? 'mousewheel' : 'DOMMouseScroll';
591     if (this.options.offset && this.options.offset < this.buffer.totalRows)
592       Rico.runLater(50,this,'scrollToRow',this.options.offset);  // Safari requires a delay
593     this.pluginScroll();
594     this.setHorizontalScroll();
595     Rico.log("setHorizontalScroll done");
596     if (this.options.windowResize)
597       Rico.runLater(100,this,'pluginWindowResize');
598     Rico.log("initialize complete for "+this.tableId);
599   }
600 };
601
602
603 Rico.LiveGridMethods = {
604 /** @lends Rico.LiveGrid# */
605
606   createHoverSet: function() {
607     var hdrs=[];
608     for( var c=0; c < this.headerColCnt; c++ ) {
609       if (this.columns[c].sortable) {\r
610         hdrs.push(this.columns[c].hdrCellDiv);
611       }
612     }
613     this.hoverSet = new Rico.HoverSet(hdrs);
614   },
615
616   checkForFilterParms: function() {
617     var s=window.location.search;
618     if (s.charAt(0)=='?') s=s.substring(1);
619     var pairs = s.split('&');
620     for (var i=0; i<pairs.length; i++) {
621       if (pairs[i].match(/^f\[\d+\]/)) {
622         this.buffer.options.requestParameters.push(pairs[i]);
623       }
624     }
625   },
626
627 /**
628  * Refreshes a detail grid from a master grid
629  * @returns row index on master table on success, -1 otherwise
630  */
631   drillDown: function(e,masterColNum,detailColNum) {
632     var cell=Rico.eventElement(e || window.event);
633     cell=Rico.getParentByTagName(cell,'div','ricoLG_cell');
634     if (!cell) return -1;
635     var idx=this.winCellIndex(cell);
636     if (idx.row >= this.buffer.totalRows) return -1
637     this.unhighlight();
638     this.menuIdx=idx;  // ensures selection gets cleared when menu is displayed
639     this.highlight(idx);
640     var drillValue=this.buffer.getWindowCell(idx.row,masterColNum);
641     for (var i=3; i<arguments.length; i++)
642       arguments[i].setDetailFilter(detailColNum,drillValue);
643     return idx.row;
644   },
645
646 /**
647  * set filter on a detail grid that is in a master-detail relationship
648  */
649   setDetailFilter: function(colNumber,filterValue) {
650     var c=this.columns[colNumber];
651     c.format.ColData=filterValue;
652     c.setSystemFilter('EQ',filterValue);
653   },
654
655 /**
656  * Create one table for frozen columns and one for scrolling columns.
657  * Also create div's to contain them.
658  * @returns true on success
659  */
660   createTables: function() {
661     var insertloc,hdrSrc,i;
662     var table = document.getElementById(this.tableId) || document.getElementById(this.tableId+'_outerDiv');
663     if (!table) return false;
664     if (table.tagName.toLowerCase()=='table') {
665       var theads=table.getElementsByTagName("thead");
666       if (theads.length == 1) {
667         Rico.log("createTables: using thead section, id="+this.tableId);
668         if (this.options.PanelNamesOnTabHdr && this.options.panels) {
669           var r=theads[0].insertRow(0);
670           this.insertPanelNames(r, 0, this.options.frozenColumns, 'ricoFrozen');
671           this.insertPanelNames(r, this.options.frozenColumns, this.options.columnSpecs.length);
672         }
673         hdrSrc=theads[0].rows;
674       } else {
675         Rico.log("createTables: using tbody section, id="+this.tableId);
676         hdrSrc=new Array(table.rows[0]);
677       }
678       insertloc=table;
679     } else if (this.options.columnSpecs.length > 0) {
680       if (!table.id.match(/_outerDiv$/)) insertloc=table;
681       Rico.log("createTables: inserting at "+table.tagName+", id="+this.tableId);
682     } else {
683       alert("ERROR!\n\nUnable to initialize '"+this.tableId+"'\n\nLiveGrid terminated");
684       return false;
685     }
686
687     this.createDivs();
688     this.scrollContainer = this.createDiv("scrollContainer",this.structTabLR);
689     this.scrollContainer.appendChild(this.scrollDiv); // move scrollDiv
690     this.scrollTab = this.createDiv("scrollTab",this.scrollContainer);
691     this.shadowDiv  = this.createDiv("shadow",this.scrollDiv);
692     this.shadowDiv.style.direction='ltr';  // avoid FF bug
693     this.scrollDiv.style.display="none";
694     this.scrollDiv.scrollTop=0;
695     if (this.options.highlightMethod!='class') {
696       this.highlightDiv=[];
697       switch (this.options.highlightElem) {
698         case 'menuRow':
699         case 'cursorRow':
700           this.highlightDiv[0] = this.createDiv("highlight",this.outerDiv);
701           this.highlightDiv[0].style.display="none";
702           break;
703         case 'menuCell':
704         case 'cursorCell':
705           for (i=0; i<2; i++) {
706             this.highlightDiv[i] = this.createDiv("highlight",i==0 ? this.frozenTabs : this.scrollTab);
707             this.highlightDiv[i].style.display="none";
708             this.highlightDiv[i].id+=i;
709           }
710           break;
711         case 'selection':
712           // create one div for each side of the rectangle
713           var parentDiv=this.options.highlightSection==1 ? this.frozenTabs : this.scrollTab;
714           for (i=0; i<4; i++) {
715             this.highlightDiv[i] = this.createDiv("highlight",parentDiv);
716             this.highlightDiv[i].style.display="none";
717             this.highlightDiv[i].style.overflow="hidden";
718             this.highlightDiv[i].id+=i;
719             this.highlightDiv[i].style[i % 2==0 ? 'height' : 'width']="0px";
720           }
721           break;
722       }
723     }
724
725     // create new tables
726     for (i=0; i<3; i++) {
727       this.tabs[i] = document.createElement("table");
728       this.tabs[i].className = 'ricoLG_table';
729       this.tabs[i].border=0;
730       this.tabs[i].cellPadding=0;
731       this.tabs[i].cellSpacing=0;
732       this.tabs[i].id = this.tableId+"_tab"+i;
733     }
734     // set headings
735     for (i=0; i<2; i++) {
736       this.thead[i]=this.tabs[i].createTHead();
737       Rico.addClass(this.tabs[i],'ricoLG_top');
738       //this.thead[i].className='ricoLG_top';
739       if (Rico.theme.gridheader) Rico.addClass(this.thead[i],Rico.theme.gridheader);
740     }
741     // set bodies
742     for (i=0; i<2; i++) {
743       this.tbody[i]=Rico.getTBody(this.tabs[i==0?0:2]);
744       this.tbody[i].className='ricoLG_bottom';
745       if (Rico.theme.gridcontent) Rico.addClass(this.tbody[i],Rico.theme.gridcontent);
746       this.tbody[i].insertRow(-1);
747     }
748     this.frozenTabs.appendChild(this.tabs[0]);
749     this.innerDiv.appendChild(this.tabs[1]);
750     this.scrollTab.appendChild(this.tabs[2]);
751     if (insertloc) insertloc.parentNode.insertBefore(this.outerDiv,insertloc);
752     if (hdrSrc) {
753       this.headerColCnt = this.getColumnInfo(hdrSrc);
754       this.loadHdrSrc(hdrSrc);
755     } else {
756       this.createHdr(0,0,this.options.frozenColumns);
757       this.createHdr(1,this.options.frozenColumns,this.options.columnSpecs.length);
758       if (this.options.PanelNamesOnTabHdr && this.options.panels) {
759         this.insertPanelNames(this.thead[0].insertRow(0), 0, this.options.frozenColumns);
760         this.insertPanelNames(this.thead[1].insertRow(0), this.options.frozenColumns, this.options.columnSpecs.length);
761       }
762       for (i=0; i<2; i++)
763         this.headerColCnt = this.getColumnInfo(this.thead[i].rows);
764     }
765     for( var c=0; c < this.headerColCnt; c++ )
766       this.tbody[c<this.options.frozenColumns ? 0 : 1].rows[0].insertCell(-1);
767     if (insertloc) table.parentNode.removeChild(table);
768     Rico.log('createTables end');
769     return true;
770   },
771
772   createDataCells: function(visibleRows) {
773     if (visibleRows < 0) {
774       for (var i=0; i<this.options.minPageRows; i++)
775         this.appendBlankRow();
776       this.sizeDivs();
777       this.autoAppendRows(this.remainingHt());
778     } else {
779       for( var r=0; r < visibleRows; r++ )
780         this.appendBlankRow();
781     }
782     var s=this.options.highlightSection;
783     if (s & 1) this.attachHighlightEvents(this.tbody[0]);
784     if (s & 2) this.attachHighlightEvents(this.tbody[1]);
785   },
786
787 /**
788  * @param colnum column number
789  * @return id string for a filter element
790  */
791   filterId: function(colnum) {
792     return 'RicoFilter_'+this.tableId+'_'+colnum;
793   },
794
795 /**
796  * Create filter elements in heading
797  * Reads this.columns[].filterUI to determine type of filter element for each column (t=text box, s=select list, c=custom)
798  * @param r heading row where filter elements will be placed
799  */
800   createFilters: function(r) {
801     for( var c=0; c < this.headerColCnt; c++ ) {
802       var col=this.columns[c];
803       var fmt=col.format;
804       if (typeof fmt.filterUI!='string') continue;
805       var cell=this.hdrCells[r][c].cell;
806       var field,name=this.filterId(c);\r
807       var divs=cell.getElementsByTagName('div');
808       // copy text alignment from data cell
809       var align=Rico.getStyle(this.cell(0,c),'textAlign');
810       divs[1].style.textAlign=align;
811       switch (fmt.filterUI.charAt(0)) {
812         case 't':
813           // text field
814           field=Rico.createFormField(divs[1],'input','text',name,name);
815           var size=fmt.filterUI.match(/\d+/);
816           field.maxLength=fmt.Length || 50;\r
817           field.size=size ? parseInt(size,10) : 10;
818           divs[1].appendChild(Rico.clearButton(Rico.eventHandle(col,'filterClear')));
819           if (col.filterType==Rico.ColumnConst.USERFILTER && col.filterOp=='LIKE') {
820             var v=col.filterValues[0];
821             if (v.charAt(0)=='*') v=v.substr(1);
822             if (v.slice(-1)=='*') v=v.slice(0,-1);
823             field.value=v;
824             col.lastKeyFilter=v;
825           }
826           Rico.eventBind(field,'keyup',Rico.eventHandle(col,'filterKeypress'),false);
827           col.filterField=field;\r
828           break;\r
829         case 'm':
830           // multi-select
831         case 's':
832           // drop-down select
833           field=Rico.createFormField(divs[1],'select',null,name);\r
834           Rico.addSelectOption(field,this.options.FilterAllToken,Rico.getPhraseById("filterAll"));\r
835           col.filterField=field;
836           var options={};\r
837           Rico.extend(options, this.buffer.ajaxOptions);
838           var colnum=typeof(fmt.filterCol)=='number' ? fmt.filterCol : c;
839           options.parameters = {id: this.tableId, distinct:colnum};
840           options.parameters[this.actionId]="query";
841           options.onComplete = Rico.bind(this,'filterValuesUpdate',c);
842           new Rico.ajaxRequest(this.buffer.dataSource, options);
843           break;\r
844         case 'c':
845           // custom
846           if (typeof col._createFilters == 'function')
847             col._createFilters(divs[1], name);
848           break;
849       }
850     }
851     this.initFilterImage(r);
852   },
853
854 /**
855  * update select list filter with values in AJAX response
856  * @returns true on success
857  */
858   filterValuesUpdate: function(colnum,request) {
859     var response = request.responseXML.getElementsByTagName("ajax-response");
860     Rico.log("filterValuesUpdate: "+request.status);
861     if (response == null || response.length != 1) return false;
862     response=response[0];
863     var error = response.getElementsByTagName('error');
864     if (error.length > 0) {
865       Rico.log("Data provider returned an error:\n"+Rico.getContentAsString(error[0],this.buffer.isEncoded));
866       alert(Rico.getPhraseById("requestError",Rico.getContentAsString(error[0],this.buffer.isEncoded)));
867       return false;
868     }\r
869     response=response.getElementsByTagName('response')[0];\r
870     var rowsElement = response.getElementsByTagName('rows')[0];\r
871     var col=this.columns[parseInt(colnum,10)];
872     var rows = this.buffer.dom2jstable(rowsElement);\r
873     var c,opt,v;
874     if (col.filterType==Rico.ColumnConst.USERFILTER && col.filterOp=='EQ') v=col.filterValues[0];
875     Rico.log('filterValuesUpdate: col='+colnum+' rows='+rows.length);
876     switch (col.format.filterUI.charAt(0)) {
877       case 'm':
878         // multi-select
879         col.mFilter = document.body.appendChild(document.createElement("div"));
880         col.mFilter.className = 'ricoLG_mFilter'
881         Rico.hide(col.mFilter);
882         var contentDiv = col.mFilter.appendChild(document.createElement("div"));
883         contentDiv.className = 'ricoLG_mFilter_content'
884         var buttonDiv = col.mFilter.appendChild(document.createElement("div"));
885         buttonDiv.className = 'ricoLG_mFilter_button'
886         col.mFilterButton=buttonDiv.appendChild(document.createElement("button"));
887         col.mFilterButton.innerHTML=Rico.getPhraseById("ok");
888         var eventName=Rico.isWebKit ? 'mousedown' : 'click';
889         Rico.eventBind(col.filterField,eventName,Rico.eventHandle(col,'mFilterSelectClick'));
890         Rico.eventBind(col.mFilterButton,'click',Rico.eventHandle(col,'mFilterFinish'));
891         //col.filterField.options[0].text=$('AllLabel').innerHTML;
892         tab = contentDiv.appendChild(document.createElement("table"));
893         tab.border=0;
894         tab.cellPadding=2;
895         tab.cellSpacing=0;
896         //tbody=(tab.tBodies.length==0) ? tab.appendChild(document.createElement("tbody")) : tab.tBodies[0];
897         var baseId=this.filterId(colnum)+'_';
898         this.createMFilterItem(tab,this.options.FilterAllToken,Rico.getPhraseById("filterAll"),baseId+'all',Rico.eventHandle(col,'mFilterAllClick'));
899         var handle=Rico.eventHandle(col,'mFilterOtherClick')
900         for (var i=0; i<rows.length; i++) {
901           if (rows[i].length>0) {
902             c=rows[i][0];
903             this.createMFilterItem(tab,c,c || Rico.getPhraseById("filterBlank"),baseId+i,handle);
904           }
905         }
906         col.mFilterInputs=contentDiv.getElementsByTagName('input');
907         col.mFilterLabels=contentDiv.getElementsByTagName('label');
908         col.mFilterFocus=col.mFilterInputs.length ? col.mFilterInputs[0] : col.mFilterButton;
909         break;
910
911       case 's':
912         // drop-down select
913         for (var i=0; i<rows.length; i++) {
914           if (rows[i].length>0) {
915             c=rows[i][0];
916             opt=Rico.addSelectOption(col.filterField,c,c || Rico.getPhraseById("filterBlank"));
917             if (col.filterType==Rico.ColumnConst.USERFILTER && c==v) opt.selected=true;
918           }
919         }
920         Rico.eventBind(col.filterField,'change',Rico.eventHandle(col,'filterChange'));
921         break;
922     }
923     return true;\r
924   },
925   
926   createMFilterItem: function(table,code,description,id,eventHandle) {
927     var tr=table.insertRow(-1);
928     tr.vAlign='top';
929     if (tr.rowIndex % 2 == 1) tr.className='ricoLG_mFilter_oddrow';
930     var td1=tr.insertCell(-1)
931     var td2=tr.insertCell(-1)
932     var field=Rico.createFormField(td1,'input','checkbox',id);
933     field.value=code;
934     field.checked=true;
935     var label = td2.appendChild(document.createElement("label"));
936     label.htmlFor = id;
937     label.innerHTML=description;
938     Rico.eventBind(field,'click',eventHandle);
939   },
940
941   unplugHighlightEvents: function() {
942     var s=this.options.highlightSection;
943     if (s & 1) this.detachHighlightEvents(this.tbody[0]);
944     if (s & 2) this.detachHighlightEvents(this.tbody[1]);
945   },
946
947 /**
948  * place panel names on first row of grid header (used by LiveGridForms)
949  */
950   insertPanelNames: function(r,start,limit,cellClass) {
951     Rico.log('insertPanelNames: start='+start+' limit='+limit);
952     r.className='ricoLG_hdg';
953     var lastIdx=-1, span, newCell=null, spanIdx=0;
954     for( var c=start; c < limit; c++ ) {
955       if (lastIdx == this.options.columnSpecs[c].panelIdx) {
956         span++;
957       } else {
958         if (newCell) newCell.colSpan=span;
959         newCell = r.insertCell(-1);
960         if (cellClass) newCell.className=cellClass;
961         span=1;
962         lastIdx=this.options.columnSpecs[c].panelIdx;
963         newCell.innerHTML=this.options.panels[lastIdx];
964       }
965     }
966     if (newCell) newCell.colSpan=span;
967   },
968
969 /**
970  * create grid header for table i (if none was provided)
971  */
972   createHdr: function(i,start,limit) {
973     Rico.log('createHdr: i='+i+' start='+start+' limit='+limit);
974     var mainRow = this.thead[i].insertRow(-1);
975     mainRow.id=this.tableId+'_tab'+i+'h_main';
976     mainRow.className='ricoLG_hdg';
977     for( var c=start; c < limit; c++ ) {
978       var newCell = mainRow.insertCell(-1);
979       newCell.innerHTML=this.options.columnSpecs[c].Hdg;
980     }
981   },
982
983 /**
984  * move header cells in original table to grid
985  */
986   loadHdrSrc: function(hdrSrc) {
987     var i,h,c,r,newrow,cells;
988     Rico.log('loadHdrSrc start');
989     for (i=0; i<2; i++) {
990       for (r=0; r<hdrSrc.length; r++) {
991         newrow = this.thead[i].insertRow(-1);
992         newrow.className='ricoLG_hdg '+this.tableId+'_hdg'+r;
993       }
994     }
995     if (hdrSrc.length==1) {
996       cells=hdrSrc[0].cells;
997       for (c=0; cells.length > 0; c++)
998         this.thead[c<this.options.frozenColumns ? 0 : 1].rows[0].appendChild(cells[0]);
999     } else {
1000       for (r=0; r<hdrSrc.length; r++) {
1001         cells=hdrSrc[r].cells;
1002         for (c=0,h=0; cells.length > 0; c++) {
1003           if (cells[0].className=='ricoFrozen') {
1004             if (r==this.headerRowIdx) this.options.frozenColumns=c+1;
1005           } else {
1006             h=1;
1007           }
1008           this.thead[h].rows[r].appendChild(cells[0]);
1009         }
1010       }
1011     }
1012     Rico.log('loadHdrSrc end');
1013   },
1014
1015 /**
1016  * Size div elements
1017  */
1018   sizeDivs: function() {
1019     Rico.log('sizeDivs: '+this.tableId);
1020     //this.cancelMenu();
1021     this.unhighlight();
1022     this.baseSizeDivs();
1023     var firstVisible=this.firstVisible();
1024     if (this.pageSize == 0 || firstVisible < 0) return;
1025     var totRowHt=this.columns[firstVisible].dataColDiv.offsetHeight;
1026     this.rowHeight = Math.round(totRowHt/this.pageSize);
1027     var scrHt=this.dataHt;
1028     if (this.scrTabWi0 == this.scrTabWi) {
1029       // no scrolling columns - horizontal scroll bar not needed
1030       this.innerDiv.style.height=(this.hdrHt+1)+'px';
1031       this.scrollDiv.style.overflowX='hidden';
1032     } else {
1033       this.scrollDiv.style.overflowX='scroll';
1034       scrHt+=this.options.scrollBarWidth;
1035     }
1036     this.scrollDiv.style.height=scrHt+'px';
1037     this.innerDiv.style.width=(this.scrWi)+'px';
1038     this.scrollTab.style.width=(this.scrWi-this.options.scrollBarWidth)+'px';
1039     //this.resizeDiv.style.height=this.frozenTabs.style.height=this.innerDiv.style.height=(this.hdrHt+this.dataHt+1)+'px';
1040     this.resizeDiv.style.height=(this.hdrHt+this.dataHt+1)+'px';
1041     Rico.log('sizeDivs scrHt='+scrHt+' innerHt='+this.innerDiv.style.height+' rowHt='+this.rowHeight+' pageSize='+this.pageSize);
1042     var pad=(this.scrWi-this.scrTabWi < this.options.scrollBarWidth) ? 2 : 0;
1043     this.shadowDiv.style.width=(this.scrTabWi+pad)+'px';
1044     this.outerDiv.style.height=(this.hdrHt+scrHt)+'px';
1045     this.setHorizontalScroll();
1046   },
1047
1048   setHorizontalScroll: function() {
1049     var newLeft=(-this.scrollDiv.scrollLeft)+'px';
1050     this.tabs[1].style.marginLeft=newLeft;
1051     this.tabs[2].style.marginLeft=newLeft;
1052   },
1053
1054   remainingHt: function() {
1055     var tabHt;
1056     var winHt=Rico.windowHeight();
1057     var margin=Rico.isIE ? 15 : 10;
1058     // if there is a horizontal scrollbar take it into account
1059     if (!Rico.isIE && window.frameElement && window.frameElement.scrolling=='yes' && this.sizeTo!='parent') margin+=this.options.scrollBarWidth;
1060     switch (this.sizeTo) {
1061       case 'window':
1062         var divTop=Rico.cumulativeOffset(this.outerDiv).top;
1063         tabHt=Math.max(this.tabs[0].offsetHeight,this.tabs[1].offsetHeight);
1064         Rico.log("remainingHt, winHt="+winHt+' tabHt='+tabHt+' gridY='+divTop);
1065         return winHt-divTop-tabHt-this.options.scrollBarWidth-margin;  // allow for scrollbar and some margin
1066       case 'parent':
1067         var offset=this.offsetFromParent(this.outerDiv);
1068         tabHt=Math.max(this.tabs[0].offsetHeight,this.tabs[1].offsetHeight);
1069         if (Rico.isIE) Rico.hide(this.outerDiv);
1070         var parentHt=this.outerDiv.parentNode.offsetHeight;
1071         if (Rico.isIE) Rico.show(this.outerDiv);
1072         Rico.log("remainingHt, parentHt="+parentHt+' gridY='+offset+' winHt='+winHt+' tabHt='+tabHt);
1073         return parentHt - tabHt - offset - this.options.scrollBarWidth;
1074       case 'data':
1075       case 'body':
1076         var bodyHt=Rico.isIE ? document.body.scrollHeight : document.body.offsetHeight;
1077         //alert("remainingHt\n document.height="+document.height+"\n body.offsetHeight="+document.body.offsetHeight+"\n body.scrollHeight="+document.body.scrollHeight+"\n documentElement.scrollHeight="+document.documentElement.scrollHeight);
1078         var remHt=winHt-bodyHt-margin;
1079         if (!Rico.isWebKit) remHt-=this.options.scrollBarWidth;
1080         Rico.log("remainingHt, winHt="+winHt+' pageHt='+bodyHt+' remHt='+remHt);
1081         return remHt;
1082       default:
1083         tabHt=Math.max(this.tabs[0].offsetHeight,this.tabs[1].offsetHeight);
1084         Rico.log("remainingHt, winHt="+winHt+' tabHt='+tabHt);
1085         if (this.sizeTo.slice(-1)=='%') winHt*=parseFloat(this.sizeTo)/100.0;
1086         else if (this.sizeTo.slice(-2)=='px') winHt=parseInt(this.sizeTo,10);
1087         return winHt-tabHt-this.options.scrollBarWidth-margin;  // allow for scrollbar and some margin
1088     }
1089   },
1090
1091   offsetFromParent: function(element) {
1092     var valueT = 0;
1093     var elParent=element.parentNode;
1094     do {
1095       //Rico.log("offsetFromParent: "+element.tagName+' id='+element.id+' otop='+element.offsetTop);
1096       valueT += element.offsetTop  || 0;
1097       element = element.offsetParent;
1098       if (!element || element==null) break;
1099       var p = Rico.getStyle(element, 'position');
1100       if (element.tagName=='BODY' || element.tagName=='HTML' || p=='absolute') return valueT-elParent.offsetTop;
1101     } while (element != elParent);
1102     return valueT;
1103   },
1104
1105   adjustPageSize: function() {
1106     var remHt=this.remainingHt();
1107     Rico.log('adjustPageSize remHt='+remHt+' lastRow='+this.lastRowPos);
1108     if (remHt > this.rowHeight)
1109       this.autoAppendRows(remHt);
1110     else if (remHt < 0 || this.sizeTo=='data')
1111       this.autoRemoveRows(-remHt);
1112   },
1113   
1114   setPageSize: function(newRowCount) {
1115     newRowCount=Math.min(newRowCount,this.options.maxPageRows);
1116     newRowCount=Math.max(newRowCount,this.options.minPageRows);
1117     this.sizeTo='fixed';
1118     var oldSize=this.pageSize;
1119     while (this.pageSize > newRowCount) {
1120       this.removeRow();
1121     }
1122     while (this.pageSize < newRowCount) {
1123       this.appendBlankRow();
1124     }
1125     this.finishResize(oldSize);
1126   },
1127
1128   pluginWindowResize: function() {
1129     Rico.log("pluginWindowResize");
1130     this.resizeWindowHandler=Rico.eventHandle(this,'resizeWindow');
1131     Rico.eventBind(window, "resize", this.resizeWindowHandler, false);
1132   },
1133
1134   unplugWindowResize: function() {
1135     if (!this.resizeWindowHandler) return;
1136     Rico.eventUnbind(window,"resize", this.resizeWindowHandler, false);
1137     this.resizeWindowHandler=null;
1138   },
1139
1140   resizeWindow: function() {
1141     Rico.log('resizeWindow '+this.tableId+' lastRow='+this.lastRowPos);
1142     if (this.resizeState=='finish') {
1143       Rico.log('resizeWindow postponed');
1144       this.resizeState='resize';
1145       return;
1146     }
1147     if (!this.sizeTo || this.sizeTo=='fixed') {
1148       this.sizeDivs();
1149       return;
1150     }
1151     if (this.sizeTo=='parent' && Rico.getStyle(this.outerDiv.parentNode,'display') == 'none') return;
1152     var oldSize=this.pageSize;
1153     this.adjustPageSize();
1154     this.finishResize(oldSize);
1155   },
1156
1157   finishResize: function(oldSize) {
1158     if (this.pageSize > oldSize && this.buffer.totalRows>0) {
1159       this.isPartialBlank=true;
1160       var adjStart=this.adjustRow(this.lastRowPos);
1161       this.buffer.fetch(adjStart);
1162     } else if (this.pageSize < oldSize) {
1163       if (this.options.onRefreshComplete) this.options.onRefreshComplete(this.contentStartPos,this.contentStartPos+this.pageSize-1);  // update bookmark
1164     }
1165     this.resizeState='finish';
1166     Rico.runLater(20,this,'finishResize2');
1167     Rico.log('Resize '+this.tableId+' complete. old size='+oldSize+' new size='+this.pageSize);
1168   },
1169
1170   finishResize2: function() {
1171     this.sizeDivs();
1172     this.updateHeightDiv();
1173     if (this.resizeState=='resize') {
1174       this.resizeWindow();
1175     } else {
1176       this.resizeState='';
1177     }
1178   },
1179
1180   topOfLastPage: function() {
1181     return Math.max(this.buffer.totalRows-this.pageSize,0);
1182   },
1183
1184   updateHeightDiv: function() {
1185     var notdisp=this.topOfLastPage();
1186     var ht = notdisp ? this.scrollDiv.clientHeight + Math.floor(this.rowHeight * (notdisp + 0.4)) : 1;
1187     Rico.log("updateHeightDiv, ht="+ht+' scrollDiv.clientHeight='+this.scrollDiv.clientHeight+' rowsNotDisplayed='+notdisp);
1188     this.shadowDiv.style.height=ht+'px';
1189   },
1190
1191   autoRemoveRows: function(overage) {
1192     if (!this.rowHeight) return;
1193     var removeCnt=Math.ceil(overage / this.rowHeight);
1194     if (this.sizeTo=='data')
1195       removeCnt=Math.max(removeCnt,this.pageSize-this.buffer.totalRows);
1196     Rico.log("autoRemoveRows overage="+overage+" removeCnt="+removeCnt);
1197     for (var i=0; i<removeCnt; i++)
1198       this.removeRow();
1199   },
1200
1201   removeRow: function() {
1202     if (this.pageSize <= this.options.minPageRows) return;
1203     this.pageSize--;
1204     for( var c=0; c < this.headerColCnt; c++ ) {
1205       var cell=this.columns[c].cell(this.pageSize);
1206       this.columns[c].dataColDiv.removeChild(cell);
1207     }
1208   },
1209
1210   autoAppendRows: function(overage) {
1211     if (!this.rowHeight) return;
1212     var addCnt=Math.floor(overage / this.rowHeight);
1213     Rico.log("autoAppendRows overage="+overage+" cnt="+addCnt+" rowHt="+this.rowHeight);
1214     for (var i=0; i<addCnt; i++) {
1215       if (this.sizeTo=='data' && this.pageSize>=this.buffer.totalRows) break;
1216       this.appendBlankRow();
1217     }
1218   },
1219
1220 /**
1221  * on older systems, this can be fairly slow
1222  */
1223   appendBlankRow: function() {
1224     if (this.pageSize >= this.options.maxPageRows) return;
1225     Rico.log("appendBlankRow #"+this.pageSize);
1226     var cls=this.defaultRowClass(this.pageSize);
1227     for( var c=0; c < this.headerColCnt; c++ ) {
1228       var newdiv = document.createElement("div");
1229       newdiv.className = 'ricoLG_cell '+cls;
1230       newdiv.id=this.tableId+'_'+this.pageSize+'_'+c;
1231       this.columns[c].dataColDiv.appendChild(newdiv);
1232       if (this.columns[c].format.canDrag && Rico.registerDraggable)
1233         Rico.registerDraggable( new Rico.LiveGridDraggable(this, this.pageSize, c), this.options.dndMgrIdx );
1234       newdiv.innerHTML='&nbsp;';   // this seems to be required by IE
1235       if (this.columns[c]._create) {
1236         this.columns[c]._create(newdiv,this.pageSize);
1237       }
1238     }
1239     this.pageSize++;
1240   },
1241
1242   defaultRowClass: function(rownum) {
1243     var cls
1244     if (rownum % 2==0) {
1245       cls='ricoLG_evenRow';
1246       //if (Rico.theme.primary) cls+=' '+Rico.theme.primary;
1247     } else {
1248       cls='ricoLG_oddRow';
1249       //if (Rico.theme.secondary) cls+=' '+Rico.theme.secondary;
1250     }
1251     return cls;
1252   },
1253
1254   handleMenuClick: function(e) {
1255     if (!this.menu) return;
1256     this.cancelMenu();
1257     this.unhighlight(); // in case highlighting was invoked externally
1258     var idx;
1259     var cell=Rico.eventElement(e);
1260     if (cell.className=='ricoLG_highlightDiv') {
1261       idx=this.highlightIdx;
1262     } else {
1263       cell=Rico.getParentByTagName(cell,'div','ricoLG_cell');
1264       if (!cell) return;
1265       idx=this.winCellIndex(cell);
1266       if ((this.options.highlightSection & (idx.tabIdx+1))==0) return;
1267     }
1268     this.highlight(idx);
1269     this.highlightEnabled=false;
1270     if (this.hideScroll) this.scrollDiv.style.overflow="hidden";
1271     this.menuIdx=idx;
1272     if (!this.menu.div) this.menu.createDiv();
1273     this.menu.liveGrid=this;
1274     if (this.menu.buildGridMenu) {
1275       var showMenu=this.menu.buildGridMenu(idx.row, idx.column, idx.tabIdx);
1276       if (!showMenu) return;
1277     }
1278     if (this.options.highlightElem=='selection' && !this.isSelected(idx.cell)) {
1279       this.selectCell(idx.cell);
1280     }
1281     this.menu.showmenu(e,Rico.bind(this,'closeMenu'));
1282   },
1283
1284   closeMenu: function() {
1285     if (!this.menuIdx) return;
1286     if (this.hideScroll) this.scrollDiv.style.overflow="";
1287     //this.unhighlight();
1288     this.highlightEnabled=true;
1289     this.menuIdx=null;
1290   },
1291
1292 /**
1293  * @return index of cell within the window
1294  */
1295   winCellIndex: function(cell) {
1296     var l=cell.id.lastIndexOf('_',cell.id.length);
1297     var l2=cell.id.lastIndexOf('_',l-1)+1;
1298     var c=parseInt(cell.id.substr(l+1));
1299     var r=parseInt(cell.id.substr(l2,l));
1300     return {row:r, column:c, tabIdx:this.columns[c].tabIdx, cell:cell};
1301   },
1302
1303 /**
1304  * @return index of cell within the dataset
1305  */
1306   datasetIndex: function(cell) {
1307     var idx=this.winCellIndex(cell);
1308     idx.row+=this.buffer.windowPos;
1309     idx.onBlankRow=(idx.row >= this.buffer.endPos());
1310     return idx;
1311   },
1312
1313   attachHighlightEvents: function(tBody) {
1314     switch (this.options.highlightElem) {
1315       case 'selection':
1316         Rico.eventBind(tBody,"mousedown", this.options.mouseDownHandler, false);
1317         /** @ignore */
1318         tBody.ondrag = function () { return false; };
1319         /** @ignore */
1320         tBody.onselectstart = function () { return false; };
1321         break;
1322       case 'cursorRow':
1323       case 'cursorCell':
1324         Rico.eventBind(tBody,"mouseover", this.options.rowOverHandler, false);
1325         break;
1326     }
1327   },
1328
1329   detachHighlightEvents: function(tBody) {
1330     switch (this.options.highlightElem) {
1331       case 'selection':
1332         Rico.eventUnbind(tBody,"mousedown", this.options.mouseDownHandler, false);
1333         tBody.ondrag = null;
1334         tBody.onselectstart = null;
1335         break;
1336       case 'cursorRow':
1337       case 'cursorCell':
1338         Rico.eventUnbind(tBody,"mouseover", this.options.rowOverHandler, false);
1339         break;
1340     }
1341   },
1342
1343 /**
1344  * @return array of objects containing row/col indexes (index values are relative to the start of the window)
1345  */
1346   getVisibleSelection: function() {
1347     var cellList=[];
1348     if (this.SelectIdxStart && this.SelectIdxEnd) {
1349       var r1=Math.max(Math.min(this.SelectIdxEnd.row,this.SelectIdxStart.row)-this.buffer.startPos,this.buffer.windowStart);
1350       var r2=Math.min(Math.max(this.SelectIdxEnd.row,this.SelectIdxStart.row)-this.buffer.startPos,this.buffer.windowEnd-1);
1351       var c1=Math.min(this.SelectIdxEnd.column,this.SelectIdxStart.column);
1352       var c2=Math.max(this.SelectIdxEnd.column,this.SelectIdxStart.column);
1353       //Rico.log("getVisibleSelection "+r1+','+c1+' to '+r2+','+c2+' ('+this.SelectIdxStart.row+',startPos='+this.buffer.startPos+',windowPos='+this.buffer.windowPos+',windowEnd='+this.buffer.windowEnd+')');
1354       for (var r=r1; r<=r2; r++) {
1355         for (var c=c1; c<=c2; c++)
1356           cellList.push({row:r-this.buffer.windowStart,column:c});
1357       }
1358     }
1359     if (this.SelectCtrl) {
1360       for (var i=0; i<this.SelectCtrl.length; i++) {
1361         if (this.SelectCtrl[i].row>=this.buffer.windowStart && this.SelectCtrl[i].row<this.buffer.windowEnd)
1362           cellList.push({row:this.SelectCtrl[i].row-this.buffer.windowStart,column:this.SelectCtrl[i].column});
1363       }
1364     }
1365     return cellList;
1366   },
1367
1368   updateSelectOutline: function() {
1369     if (!this.SelectIdxStart || !this.SelectIdxEnd) return;
1370     var r1=Math.max(Math.min(this.SelectIdxEnd.row,this.SelectIdxStart.row), this.buffer.windowStart);
1371     var r2=Math.min(Math.max(this.SelectIdxEnd.row,this.SelectIdxStart.row), this.buffer.windowEnd-1);
1372     if (r1 > r2) {
1373       this.HideSelection();
1374       return;
1375     }
1376     var c1=Math.min(this.SelectIdxEnd.column,this.SelectIdxStart.column);
1377     var c2=Math.max(this.SelectIdxEnd.column,this.SelectIdxStart.column);
1378     var top1=this.columns[c1].cell(r1-this.buffer.windowStart).offsetTop;
1379     var cell2=this.columns[c1].cell(r2-this.buffer.windowStart);
1380     var bottom2=cell2.offsetTop+cell2.offsetHeight;
1381     var left1=this.columns[c1].dataCell.offsetLeft;
1382     var left2=this.columns[c2].dataCell.offsetLeft;
1383     var right2=left2+this.columns[c2].dataCell.offsetWidth;
1384     //window.status='updateSelectOutline: '+r1+' '+r2+' top='+top1+' bot='+bottom2;
1385     this.highlightDiv[0].style.top=this.highlightDiv[3].style.top=this.highlightDiv[1].style.top=(this.hdrHt+top1-1) + 'px';
1386     this.highlightDiv[2].style.top=(this.hdrHt+bottom2-1)+'px';
1387     this.highlightDiv[3].style.left=(left1-2)+'px';
1388     this.highlightDiv[0].style.left=this.highlightDiv[2].style.left=(left1-1)+'px';
1389     this.highlightDiv[1].style.left=(right2-1)+'px';
1390     this.highlightDiv[0].style.width=this.highlightDiv[2].style.width=(right2-left1-1) + 'px';
1391     this.highlightDiv[1].style.height=this.highlightDiv[3].style.height=(bottom2-top1) + 'px';
1392     //this.highlightDiv[0].style.right=this.highlightDiv[2].style.right=this.highlightDiv[1].style.right=()+'px';
1393     //this.highlightDiv[2].style.bottom=this.highlightDiv[3].style.bottom=this.highlightDiv[1].style.bottom=(this.hdrHt+bottom2) + 'px';
1394     for (var i=0; i<4; i++)
1395       this.highlightDiv[i].style.display='';
1396   },
1397
1398   HideSelection: function() {
1399     var i;
1400     if (this.options.highlightMethod!='class') {
1401       for (i=0; i<this.highlightDiv.length; i++)
1402         this.highlightDiv[i].style.display='none';
1403     }
1404     if (this.options.highlightMethod!='outline') {
1405       var cellList=this.getVisibleSelection();
1406       Rico.log("HideSelection "+cellList.length);
1407       for (i=0; i<cellList.length; i++)
1408         this.unhighlightCell(this.columns[cellList[i].column].cell(cellList[i].row));
1409     }
1410   },
1411
1412   ShowSelection: function() {
1413     if (this.options.highlightMethod!='class')
1414       this.updateSelectOutline();
1415     if (this.options.highlightMethod!='outline') {
1416       var cellList=this.getVisibleSelection();
1417       for (var i=0; i<cellList.length; i++)
1418         this.highlightCell(this.columns[cellList[i].column].cell(cellList[i].row));
1419     }
1420   },
1421
1422   ClearSelection: function() {
1423     Rico.log("ClearSelection");
1424     this.HideSelection();
1425     this.SelectIdxStart=null;
1426     this.SelectIdxEnd=null;
1427     this.SelectCtrl=[];
1428   },
1429
1430   selectCell: function(cell) {
1431     this.ClearSelection();
1432     this.SelectIdxStart=this.SelectIdxEnd=this.datasetIndex(cell);
1433     this.ShowSelection();
1434   },
1435
1436   AdjustSelection: function(cell) {
1437     var newIdx=this.datasetIndex(cell);
1438     if (this.SelectIdxStart.tabIdx != newIdx.tabIdx) return;
1439     this.HideSelection();
1440     this.SelectIdxEnd=newIdx;
1441     this.ShowSelection();
1442   },
1443
1444   RefreshSelection: function() {
1445     var cellList=this.getVisibleSelection();
1446     for (var i=0; i<cellList.length; i++) {
1447       this.columns[cellList[i].column].displayValue(cellList[i].row);
1448     }
1449   },
1450
1451   FillSelection: function(newVal,newStyle) {
1452     if (this.SelectIdxStart && this.SelectIdxEnd) {
1453       var r1=Math.min(this.SelectIdxEnd.row,this.SelectIdxStart.row);
1454       var r2=Math.max(this.SelectIdxEnd.row,this.SelectIdxStart.row);
1455       var c1=Math.min(this.SelectIdxEnd.column,this.SelectIdxStart.column);
1456       var c2=Math.max(this.SelectIdxEnd.column,this.SelectIdxStart.column);
1457       for (var r=r1; r<=r2; r++) {
1458         for (var c=c1; c<=c2; c++) {
1459           this.buffer.setValue(r,c,newVal,newStyle);
1460         }
1461       }
1462     }
1463     if (this.SelectCtrl) {
1464       for (var i=0; i<this.SelectCtrl.length; i++) {
1465         this.buffer.setValue(this.SelectCtrl[i].row,this.SelectCtrl[i].column,newVal,newStyle);
1466       }
1467     }
1468     this.RefreshSelection();
1469   },
1470
1471 /**
1472  * Process mouse down event
1473  * @param e event object
1474  */
1475   selectMouseDown: function(e) {
1476     if (this.highlightEnabled==false) return true;
1477     this.cancelMenu();
1478     var cell=Rico.eventElement(e);
1479     if (!Rico.eventLeftClick(e)) return true;
1480     cell=Rico.getParentByTagName(cell,'div','ricoLG_cell');
1481     if (!cell) return true;
1482     Rico.eventStop(e);
1483     var newIdx=this.datasetIndex(cell);
1484     if (newIdx.onBlankRow) return true;
1485     Rico.log("selectMouseDown @"+newIdx.row+','+newIdx.column);
1486     if (e.ctrlKey) {
1487       if (!this.SelectIdxStart || this.options.highlightMethod!='class') return true;
1488       if (!this.isSelected(cell)) {
1489         this.highlightCell(cell);
1490         this.SelectCtrl.push(this.datasetIndex(cell));
1491       } else {
1492         for (var i=0; i<this.SelectCtrl.length; i++) {
1493           if (this.SelectCtrl[i].row==newIdx.row && this.SelectCtrl[i].column==newIdx.column) {
1494             this.unhighlightCell(cell);
1495             this.SelectCtrl.splice(i,1);
1496             break;
1497           }
1498         }
1499       }
1500     } else if (e.shiftKey) {
1501       if (!this.SelectIdxStart) return true;
1502       this.AdjustSelection(cell);
1503     } else {
1504       this.selectCell(cell);
1505       this.pluginSelect();
1506     }
1507     return false;
1508   },
1509
1510   pluginSelect: function() {
1511     if (this.selectPluggedIn) return;
1512     var tBody=this.tbody[this.SelectIdxStart.tabIdx];
1513     Rico.eventBind(tBody,"mouseover", this.options.mouseOverHandler, false);
1514     Rico.eventBind(this.outerDiv,"mouseup",  this.options.mouseUpHandler,  false);
1515     this.selectPluggedIn=true;
1516   },
1517
1518   unplugSelect: function() {
1519     if (!this.selectPluggedIn) return;
1520     var tBody=this.tbody[this.SelectIdxStart.tabIdx];
1521     Rico.eventUnbind(tBody,"mouseover", this.options.mouseOverHandler , false);
1522     Rico.eventUnbind(this.outerDiv,"mouseup", this.options.mouseUpHandler , false);
1523     this.selectPluggedIn=false;
1524   },
1525
1526   selectMouseUp: function(e) {
1527     this.unplugSelect();
1528     var cell=Rico.eventElement(e);
1529     cell=Rico.getParentByTagName(cell,'div','ricoLG_cell');
1530     if (!cell) return;
1531     if (this.SelectIdxStart && this.SelectIdxEnd)
1532       this.AdjustSelection(cell);
1533     else
1534       this.ClearSelection();
1535   },
1536
1537   selectMouseOver: function(e) {
1538     var cell=Rico.eventElement(e);
1539     cell=Rico.getParentByTagName(cell,'div','ricoLG_cell');
1540     if (!cell) return;
1541     this.AdjustSelection(cell);
1542     Rico.eventStop(e);
1543   },
1544
1545   isSelected: function(cell) {
1546     if (this.options.highlightMethod!='outline') return Rico.hasClass(cell,this.options.highlightClass);
1547     if (!this.SelectIdxStart || !this.SelectIdxEnd) return false;
1548     var r1=Math.max(Math.min(this.SelectIdxEnd.row,this.SelectIdxStart.row), this.buffer.windowStart);
1549     var r2=Math.min(Math.max(this.SelectIdxEnd.row,this.SelectIdxStart.row), this.buffer.windowEnd-1);
1550     if (r1 > r2) return false;
1551     var c1=Math.min(this.SelectIdxEnd.column,this.SelectIdxStart.column);
1552     var c2=Math.max(this.SelectIdxEnd.column,this.SelectIdxStart.column);
1553     var curIdx=this.datasetIndex(cell);
1554     return (r1<=curIdx.row && curIdx.row<=r2 && c1<=curIdx.column && curIdx.column<=c2);
1555   },
1556
1557   highlightCell: function(cell) {
1558     Rico.addClass(cell,this.options.highlightClass);
1559   },
1560
1561   unhighlightCell: function(cell) {
1562     if (cell==null) return;
1563     Rico.removeClass(cell,this.options.highlightClass);
1564   },
1565
1566   selectRow: function(r) {
1567     for (var c=0; c<this.columns.length; c++)
1568       this.highlightCell(this.columns[c].cell(r));
1569   },
1570
1571   unselectRow: function(r) {
1572     for (var c=0; c<this.columns.length; c++)
1573       this.unhighlightCell(this.columns[c].cell(r));
1574   },
1575
1576   rowMouseOver: function(e) {
1577     if (!this.highlightEnabled) return;
1578     var cell=Rico.eventElement(e);
1579     cell=Rico.getParentByTagName(cell,'div','ricoLG_cell');
1580     if (!cell) return;
1581     var newIdx=this.winCellIndex(cell);
1582     if ((this.options.highlightSection & (newIdx.tabIdx+1))==0) return;
1583     this.highlight(newIdx);
1584   },
1585
1586   highlight: function(newIdx) {
1587     if (this.options.highlightMethod!='outline') this.cursorSetClass(newIdx);
1588     if (this.options.highlightMethod!='class') this.cursorOutline(newIdx);
1589     this.highlightIdx=newIdx;
1590   },
1591
1592   cursorSetClass: function(newIdx) {
1593     switch (this.options.highlightElem) {
1594       case 'menuCell':
1595       case 'cursorCell':
1596         if (this.highlightIdx) this.unhighlightCell(this.highlightIdx.cell);
1597         this.highlightCell(newIdx.cell);
1598         break;
1599       case 'menuRow':
1600       case 'cursorRow':
1601         if (this.highlightIdx) this.unselectRow(this.highlightIdx.row);
1602         var s1=this.options.highlightSection & 1;
1603         var s2=this.options.highlightSection & 2;
1604         var c0=s1 ? 0 : this.options.frozenColumns;
1605         var c1=s2 ? this.columns.length : this.options.frozenColumns;
1606         for (var c=c0; c<c1; c++)
1607           this.highlightCell(this.columns[c].cell(newIdx.row));
1608         break;
1609       default: return;
1610     }
1611   },
1612
1613   cursorOutline: function(newIdx) {
1614     var div;
1615     switch (this.options.highlightElem) {
1616       case 'menuCell':
1617       case 'cursorCell':
1618         div=this.highlightDiv[newIdx.tabIdx];
1619         div.style.left=(this.columns[newIdx.column].dataCell.offsetLeft-1)+'px';
1620         div.style.width=this.columns[newIdx.column].colWidth;
1621         this.highlightDiv[1-newIdx.tabIdx].style.display='none';
1622         break;
1623       case 'menuRow':
1624       case 'cursorRow':
1625         div=this.highlightDiv[0];
1626         var s1=this.options.highlightSection & 1;
1627         var s2=this.options.highlightSection & 2;
1628         div.style.left=s1 ? '0px' : this.frozenTabs.style.width;
1629         div.style.width=((s1 ? this.frozenTabs.offsetWidth : 0) + (s2 ? this.innerDiv.offsetWidth : 0) - 4)+'px';
1630         break;
1631       default: return;
1632     }
1633     div.style.top=(this.hdrHt+newIdx.row*this.rowHeight-1)+'px';
1634     div.style.height=(this.rowHeight-1)+'px';
1635     div.style.display='';
1636   },
1637
1638   unhighlight: function() {
1639     switch (this.options.highlightElem) {
1640       case 'menuCell':
1641         //this.highlightIdx=this.menuIdx;
1642         /*jsl:fallthru*/
1643       case 'cursorCell':
1644         if (this.highlightIdx) this.unhighlightCell(this.highlightIdx.cell);
1645         if (!this.highlightDiv) return;
1646         for (var i=0; i<2; i++)
1647           this.highlightDiv[i].style.display='none';
1648         break;
1649       case 'menuRow':
1650         //this.highlightIdx=this.menuIdx;
1651         /*jsl:fallthru*/
1652       case 'cursorRow':
1653         if (this.highlightIdx) this.unselectRow(this.highlightIdx.row);
1654         if (this.highlightDiv) this.highlightDiv[0].style.display='none';
1655         break;
1656     }
1657   },
1658
1659   resetContents: function() {
1660     Rico.log("resetContents");
1661     this.ClearSelection();
1662     this.buffer.clear();
1663     this.clearRows();
1664     this.clearBookmark();
1665   },
1666
1667   setImages: function() {
1668     for (var n=0; n<this.columns.length; n++)
1669       this.columns[n].setImage();
1670   },
1671
1672 /**
1673  * @return column index, or -1 if there are no sorted columns
1674  */
1675   findSortedColumn: function() {
1676     for (var n=0; n<this.columns.length; n++) {
1677       if (this.columns[n].isSorted()) return n;
1678     }
1679     return -1;
1680   },
1681
1682   findColumnName: function(name) {
1683     for (var n=0; n<this.columns.length; n++) {
1684       if (this.columns[n].fieldName == name) return n;
1685     }
1686     return -1;
1687   },
1688   
1689 /**
1690  * Searches options.columnSpecs colAttr for matching colValue
1691  * @return array of matching column indexes
1692  */
1693   findColumnsBySpec: function(colAttr, colValue) {
1694     var result=[]
1695     for (var n=0; n<this.options.columnSpecs.length; n++) {
1696       if (this.options.columnSpecs[n][colAttr] == colValue) result.push(n);
1697     }
1698     return result;
1699   },
1700
1701 /**
1702  * Set initial sort
1703  */
1704   setSortUI: function( columnNameOrNum, sortDirection ) {
1705     Rico.log("setSortUI: "+columnNameOrNum+' '+sortDirection);
1706     var colnum=this.findSortedColumn();
1707     if (colnum >= 0) {
1708       sortDirection=this.columns[colnum].getSortDirection();
1709     } else {
1710       if (typeof sortDirection!='string') {
1711         sortDirection=Rico.ColumnConst.SORT_ASC;
1712       } else {
1713         sortDirection=sortDirection.toUpperCase();
1714         if (sortDirection != Rico.ColumnConst.SORT_DESC) sortDirection=Rico.ColumnConst.SORT_ASC;
1715       }
1716       switch (typeof columnNameOrNum) {
1717         case 'string':
1718           colnum=this.findColumnName(columnNameOrNum);
1719           break;
1720         case 'number':
1721           colnum=columnNameOrNum;
1722           break;
1723       }
1724     }
1725     if (typeof(colnum)!='number' || colnum < 0) return;
1726     this.clearSort();
1727     this.columns[colnum].setSorted(sortDirection);
1728     this.buffer.sortBuffer(colnum);
1729   },
1730
1731 /**
1732  * clear sort flag on all columns
1733  */
1734   clearSort: function() {
1735     for (var x=0;x<this.columns.length;x++)
1736       this.columns[x].setUnsorted();
1737   },
1738
1739 /**
1740  * clear filters on all columns
1741  */
1742   clearFilters: function() {
1743     for (var x=0;x<this.columns.length;x++) {
1744       this.columns[x].setUnfiltered(true);
1745     }
1746     if (this.options.filterHandler) {
1747       this.options.filterHandler();
1748     }
1749   },
1750
1751 /**
1752  * returns number of columns with a user filter set
1753  */
1754   filterCount: function() {
1755     for (var x=0,cnt=0;x<this.columns.length;x++) {
1756       if (this.columns[x].isFiltered()) cnt++;
1757     }
1758     return cnt;
1759   },
1760
1761   sortHandler: function() {
1762     this.cancelMenu();
1763     this.ClearSelection();
1764     this.setImages();
1765     var n=this.findSortedColumn();
1766     if (n < 0) return;
1767     Rico.log("sortHandler: sorting column "+n);
1768     this.buffer.sortBuffer(n);
1769     this.clearRows();
1770     this.scrollDiv.scrollTop = 0;
1771     this.buffer.fetch(0);
1772   },
1773
1774   filterHandler: function() {
1775     Rico.log("filterHandler");
1776     this.cancelMenu();
1777     if (this.buffer.processingRequest) {
1778       this.queueFilter=true;
1779       return;
1780     }
1781     this.unplugScroll();
1782     this.ClearSelection();
1783     this.setImages();
1784     this.clearBookmark();
1785     this.clearRows();
1786     this.buffer.fetch(-1);
1787     Rico.runLater(10,this,'pluginScroll'); // resetting ht div can cause a scroll event, triggering an extra fetch
1788   },
1789
1790   clearBookmark: function() {
1791     if (this.bookmark) this.bookmark.innerHTML="&nbsp;";
1792   },
1793
1794   bookmarkHandler: function(firstrow,lastrow) {
1795     var newhtml;
1796     if (isNaN(firstrow) || !this.bookmark) return;
1797     var totrows=this.buffer.totalRows;
1798     if (totrows < lastrow) lastrow=totrows;
1799     if (totrows<=0) {
1800       newhtml = Rico.getPhraseById('bookmarkNoMatch');
1801     } else if (lastrow<0) {
1802       newhtml = Rico.getPhraseById('bookmarkNoRec');
1803     } else if (this.buffer.foundRowCount) {
1804       newhtml = Rico.getPhraseById('bookmarkExact',firstrow,lastrow,totrows);
1805     } else {
1806       newhtml = Rico.getPhraseById('bookmarkAbout',firstrow,lastrow,totrows);
1807     }
1808     this.bookmark.innerHTML = newhtml;
1809   },
1810
1811   clearRows: function() {
1812     if (this.isBlank==true) return;
1813     for (var c=0; c < this.columns.length; c++)
1814       this.columns[c].clearColumn();
1815     this.isBlank = true;
1816   },
1817
1818   refreshContents: function(startPos) {
1819     Rico.log("refreshContents1 "+this.tableId+": startPos="+startPos+" lastRow="+this.lastRowPos+" PartBlank="+this.isPartialBlank+" pageSize="+this.pageSize);
1820     this.hideMsg();
1821     this.cancelMenu();
1822     this.unhighlight(); // in case highlighting was manually invoked
1823     if (this.queueFilter) {
1824       Rico.log("refreshContents: cancelling refresh because filter has changed");
1825       this.queueFilter=false;
1826       this.filterHandler();
1827       return;
1828     }
1829     this.highlightEnabled=this.options.highlightSection!='none';
1830     var viewPrecedesBuffer = this.buffer.startPos > startPos;
1831     var contentStartPos = viewPrecedesBuffer ? this.buffer.startPos: startPos;
1832     this.contentStartPos = contentStartPos+1;
1833     var contentEndPos = Math.min(this.buffer.startPos + this.buffer.size, startPos + this.pageSize);
1834     this.buffer.setWindow(contentStartPos, contentEndPos);
1835     Rico.log('refreshContents2 '+this.tableId+': cStartPos='+contentStartPos+' cEndPos='+contentEndPos+' vPrecedesBuf='+viewPrecedesBuffer+' b.startPos='+this.buffer.startPos);
1836     if (startPos == this.lastRowPos && !this.isPartialBlank && !this.isBlank) return;
1837     this.isBlank = false;
1838     var onRefreshComplete = this.options.onRefreshComplete;
1839
1840     if ((startPos + this.pageSize < this.buffer.startPos) ||
1841         (this.buffer.startPos + this.buffer.size < startPos) ||
1842         (this.buffer.size == 0)) {
1843       this.clearRows();
1844       if (onRefreshComplete) onRefreshComplete(this.contentStartPos,contentEndPos);  // update bookmark
1845       return;
1846     }
1847
1848     Rico.log('refreshContents: contentStartPos='+contentStartPos+' contentEndPos='+contentEndPos+' viewPrecedesBuffer='+viewPrecedesBuffer);
1849     var rowSize = contentEndPos - contentStartPos;
1850     var blankSize = this.pageSize - rowSize;
1851     var blankOffset = viewPrecedesBuffer ? 0: rowSize;
1852     var contentOffset = viewPrecedesBuffer ? blankSize: 0;
1853
1854     for (var r=0; r < rowSize; r++) { //initialize what we have
1855       for (var c=0; c < this.columns.length; c++)
1856         this.columns[c].displayValue(r + contentOffset);
1857     }
1858     for (var i=0; i < blankSize; i++)     // blank out the rest
1859       this.blankRow(i + blankOffset);
1860     if (this.options.highlightElem=='selection') this.ShowSelection();
1861     this.isPartialBlank = blankSize > 0;
1862     this.lastRowPos = startPos;
1863     Rico.log("refreshContents complete, startPos="+startPos);
1864     if (onRefreshComplete) onRefreshComplete(this.contentStartPos,contentEndPos);  // update bookmark
1865   },
1866
1867   scrollToRow: function(rowOffset) {
1868      var p=this.rowToPixel(rowOffset);
1869      Rico.log("scrollToRow, rowOffset="+rowOffset+" pixel="+p);
1870      this.scrollDiv.scrollTop = p; // this causes a scroll event
1871      if ( this.options.onscroll )
1872         this.options.onscroll( this, rowOffset );
1873   },
1874
1875   scrollUp: function() {
1876      this.moveRelative(-1);
1877   },
1878
1879   scrollDown: function() {
1880      this.moveRelative(1);
1881   },
1882
1883   pageUp: function() {
1884      this.moveRelative(-this.pageSize);
1885   },
1886
1887   pageDown: function() {
1888      this.moveRelative(this.pageSize);
1889   },
1890
1891   adjustRow: function(rowOffset) {
1892      var notdisp=this.topOfLastPage();
1893      if (notdisp == 0 || !rowOffset) return 0;
1894      return Math.min(notdisp,rowOffset);
1895   },
1896
1897   rowToPixel: function(rowOffset) {
1898      return this.adjustRow(rowOffset) * this.rowHeight;
1899   },
1900
1901 /**
1902  * @returns row to display at top of scroll div
1903  */
1904   pixeltorow: function(p) {
1905      var notdisp=this.topOfLastPage();
1906      if (notdisp == 0) return 0;
1907      var prow=parseInt(p/this.rowHeight,10);
1908      return Math.min(notdisp,prow);
1909   },
1910
1911   moveRelative: function(relOffset) {
1912      var newoffset=Math.max(this.scrollDiv.scrollTop+relOffset*this.rowHeight,0);
1913      newoffset=Math.min(newoffset,this.scrollDiv.scrollHeight);
1914      //Rico.log("moveRelative, newoffset="+newoffset);
1915      this.scrollDiv.scrollTop=newoffset;
1916   },
1917
1918   pluginScroll: function() {
1919      if (this.scrollPluggedIn) return;
1920      Rico.log("pluginScroll: wheelEvent="+this.wheelEvent);
1921      Rico.eventBind(this.scrollDiv,"scroll",this.scrollEventFunc, false);
1922      for (var t=0; t<2; t++)
1923        Rico.eventBind(this.tabs[t],this.wheelEvent,this.wheelEventFunc, false);
1924      this.scrollPluggedIn=true;
1925   },
1926
1927   unplugScroll: function() {
1928      if (!this.scrollPluggedIn) return;
1929      Rico.log("unplugScroll");
1930      Rico.eventUnbind(this.scrollDiv,"scroll", this.scrollEventFunc , false);
1931      for (var t=0; t<2; t++)
1932        Rico.eventUnbind(this.tabs[t],this.wheelEvent,this.wheelEventFunc, false);
1933      this.scrollPluggedIn=false;
1934   },
1935
1936   handleWheel: function(e) {
1937     var delta = 0;
1938     if (e.wheelDelta) {
1939       if (Rico.isOpera)
1940         delta = e.wheelDelta/120;
1941       else if (Rico.isWebKit)
1942         delta = -e.wheelDelta/12;
1943       else
1944         delta = -e.wheelDelta/120;
1945     } else if (e.detail) {
1946       delta = e.detail/3; /* Mozilla/Gecko */
1947     }
1948     if (delta) this.moveRelative(delta);
1949     Rico.eventStop(e);
1950     return false;
1951   },
1952
1953   handleScroll: function(e) {
1954      if ( this.scrollTimeout )
1955        clearTimeout( this.scrollTimeout );
1956      this.setHorizontalScroll();
1957      var scrtop=this.scrollDiv.scrollTop;
1958      var vscrollDiff = this.lastScrollPos-scrtop;
1959      if (vscrollDiff == 0.00) return;
1960      var newrow=this.pixeltorow(scrtop);
1961      if (newrow == this.lastRowPos && !this.isPartialBlank && !this.isBlank) return;
1962      var stamp1 = new Date();
1963      Rico.log("handleScroll, newrow="+newrow+" scrtop="+scrtop);
1964      if (this.options.highlightElem=='selection') this.HideSelection();
1965      this.buffer.fetch(newrow);
1966      if (this.options.onscroll) this.options.onscroll(this, newrow);
1967      this.scrollTimeout = Rico.runLater(1200,this,'scrollIdle');
1968      this.lastScrollPos = this.scrollDiv.scrollTop;
1969      var stamp2 = new Date();
1970      //Rico.log("handleScroll, time="+(stamp2.getTime()-stamp1.getTime()));
1971   },
1972
1973   scrollIdle: function() {
1974      if ( this.options.onscrollidle )
1975         this.options.onscrollidle();
1976   }
1977
1978 };
1979
1980
1981 Rico.LiveGridColumn = function(grid,colIdx,hdrInfo,tabIdx) {
1982   this.initialize(grid,colIdx,hdrInfo,tabIdx);
1983 };
1984
1985 Rico.LiveGridColumn.prototype = 
1986 /** @lends Rico.LiveGridColumn# */
1987 {
1988 /**
1989  * Implements a LiveGrid column. Also contains static properties used by SimpleGrid columns.
1990  * @extends Rico.TableColumnBase
1991  * @constructs
1992  */
1993 initialize: function(liveGrid,colIdx,hdrInfo,tabIdx) {
1994   Rico.extend(this, new Rico.TableColumnBase());
1995   this.baseInit(liveGrid,colIdx,hdrInfo,tabIdx);
1996   this.buffer=liveGrid.buffer;
1997   if (typeof(this.format.type)!='string' || this.format.EntryType=='tinyMCE') this.format.type='raw';
1998   if (typeof this.isNullable!='boolean') this.isNullable = /number|date/.test(this.format.type);
1999   this.isText = /raw|text|showTags/.test(this.format.type);
2000   Rico.log(" sortable="+this.sortable+" filterable="+this.filterable+" hideable="+this.hideable+" isNullable="+this.isNullable+' isText='+this.isText);
2001   this.fixHeaders(this.liveGrid.tableId, this.options.hdrIconsFirst);
2002   if (this['format_'+this.format.type]) {
2003     this._format=this['format_'+this.format.type];
2004     //this._format=Rico.bind(this,'format_'+this.format.type);
2005   }
2006   if (this.format.control) {
2007     // copy all properties/methods that start with '_'
2008     if (typeof this.format.control=='string') {
2009       this.format.control=eval(this.format.control);
2010     }
2011     for (var property in this.format.control) {
2012       if (property.charAt(0)=='_') {
2013         Rico.log("Copying control property "+property+ ' to ' + this);
2014         this[property] = this.format.control[property];
2015       }
2016     }
2017   }
2018 },
2019
2020 /**
2021  * Sorts the column in ascending order
2022  */
2023 sortAsc: function() {
2024   this.setColumnSort(Rico.ColumnConst.SORT_ASC);
2025 },
2026
2027 /**
2028  * Sorts the column in descending order
2029  */
2030 sortDesc: function() {
2031   this.setColumnSort(Rico.ColumnConst.SORT_DESC);
2032 },
2033
2034 /**
2035  * Sorts the column in the specified direction
2036  * @param direction must be one of Rico.ColumnConst.UNSORTED, .SORT_ASC, or .SORT_DESC
2037  */
2038 setColumnSort: function(direction) {
2039   this.liveGrid.clearSort();
2040   this.setSorted(direction);
2041   if (this.liveGrid.options.saveColumnInfo.sort)
2042     this.liveGrid.setCookie();
2043   if (this.options.sortHandler)
2044     this.options.sortHandler();
2045 },
2046
2047 /**
2048  * @returns true if this column is allowed to be sorted
2049  */
2050 isSortable: function() {
2051   return this.sortable;
2052 },
2053
2054 /**
2055  * @returns true if this column is currently sorted
2056  */
2057 isSorted: function() {
2058   return this.currentSort != Rico.ColumnConst.UNSORTED;
2059 },
2060
2061 /**
2062  * @returns Rico.ColumnConst.UNSORTED, .SORT_ASC, or .SORT_DESC
2063  */
2064 getSortDirection: function() {
2065   return this.currentSort;
2066 },
2067
2068 /**
2069  * toggle the sort sequence for this column
2070  */
2071 toggleSort: function() {
2072   if (this.buffer && this.buffer.totalRows==0) return;
2073   if (this.currentSort == Rico.ColumnConst.SORT_ASC)
2074     this.sortDesc();
2075   else
2076     this.sortAsc();
2077 },
2078
2079 /**
2080  * Flags that this column is not sorted
2081  */
2082 setUnsorted: function() {
2083   this.setSorted(Rico.ColumnConst.UNSORTED);
2084 },
2085
2086 /**
2087  * Flags that this column is sorted, but doesn't actually carry out the sort
2088  * @param direction must be one of Rico.ColumnConst.UNSORTED, .SORT_ASC, or .SORT_DESC
2089  */
2090 setSorted: function(direction) {
2091   this.currentSort = direction;
2092 },
2093
2094 /**
2095  * @returns true if this column is allowed to be filtered
2096  */
2097 canFilter: function() {
2098   return this.filterable;
2099 },
2100
2101 /**
2102  * @returns a textual representation of how this column is filtered
2103  */
2104 getFilterText: function() {
2105   var vals=[];
2106   for (var i=0; i<this.filterValues.length; i++) {
2107     var v=this.filterValues[i];
2108     vals.push(v=='' ? Rico.getPhraseById('filterBlank') : v);
2109   }
2110   switch (this.filterOp) {
2111     case 'EQ':   return '= '+vals.join(', ');
2112     case 'NE':   return Rico.getPhraseById('filterNot',vals.join(', '));
2113     case 'LE':   return '<= '+vals[0];
2114     case 'GE':   return '>= '+vals[0];
2115     case 'LIKE': return Rico.getPhraseById('filterLike',vals[0]);
2116     case 'NULL': return Rico.getPhraseById('filterEmpty');
2117     case 'NOTNULL': return Rico.getPhraseById('filterNotEmpty');
2118   }
2119   return '?';
2120 },
2121
2122 /**
2123  * @returns returns the query string representation of the filter
2124  */
2125 getFilterQueryParm: function() {
2126   if (this.filterType == Rico.ColumnConst.UNFILTERED) return '';
2127   var retval='&f['+this.index+'][op]='+this.filterOp;
2128   retval+='&f['+this.index+'][len]='+this.filterValues.length;
2129   for (var i=0; i<this.filterValues.length; i++) {
2130     retval+='&f['+this.index+']['+i+']='+escape(this.filterValues[i]);
2131   }
2132   return retval;
2133 },
2134
2135 /**
2136  * removes the filter from this column
2137  */
2138 setUnfiltered: function(skipHandler) {
2139   this.filterType = Rico.ColumnConst.UNFILTERED;
2140   if (this.liveGrid.options.saveColumnInfo.filter)
2141     this.liveGrid.setCookie();
2142   if (this.removeFilterFunc)
2143     this.removeFilterFunc();
2144   if (this.options.filterHandler && !skipHandler)
2145     this.options.filterHandler();
2146 },
2147
2148 setFilterEQ: function() {
2149   this.setUserFilter('EQ');
2150 },
2151 setFilterNE: function() {
2152   this.setUserFilter('NE');
2153 },
2154 addFilterNE: function() {
2155   this.filterValues.push(this.userFilter);
2156   if (this.liveGrid.options.saveColumnInfo.filter)
2157     this.liveGrid.setCookie();
2158   if (this.options.filterHandler)
2159     this.options.filterHandler();
2160 },
2161 setFilterGE: function() { this.setUserFilter('GE'); },
2162 setFilterLE: function() { this.setUserFilter('LE'); },
2163 setFilterKW: function(keyword) {
2164   if (keyword!='' && keyword!=null) {
2165     this.setFilter('LIKE',keyword,Rico.ColumnConst.USERFILTER);
2166   } else {
2167     this.setUnfiltered(false);
2168   }
2169 },
2170
2171 setUserFilter: function(relop) {
2172   this.setFilter(relop,this.userFilter,Rico.ColumnConst.USERFILTER);
2173 },
2174
2175 setSystemFilter: function(relop,filter) {
2176   this.setFilter(relop,filter,Rico.ColumnConst.SYSTEMFILTER);
2177 },
2178
2179 setFilter: function(relop,filter,type,removeFilterFunc) {
2180   this.filterValues = typeof(filter)=='object' ? filter : [filter];
2181   this.filterType = type;
2182   this.filterOp = relop;
2183   if (type == Rico.ColumnConst.USERFILTER && this.liveGrid.options.saveColumnInfo.filter)
2184     this.liveGrid.setCookie();
2185   this.removeFilterFunc=removeFilterFunc;
2186   if (this.options.filterHandler)
2187     this.options.filterHandler();
2188 },
2189
2190 isFiltered: function() {
2191   return this.filterType == Rico.ColumnConst.USERFILTER;
2192 },
2193
2194 filterChange: function(e) {\r
2195   var selbox=Rico.eventElement(e);
2196   if (selbox.value==this.liveGrid.options.FilterAllToken)\r
2197     this.setUnfiltered();\r
2198   else
2199     this.setFilter('EQ',selbox.value,Rico.ColumnConst.USERFILTER,function() {selbox.selectedIndex=0;});\r
2200 },
2201
2202 filterClear: function(e) {\r
2203   this.filterField.value='';
2204   this.setUnfiltered();\r
2205 },
2206
2207 filterKeypress: function(e) {\r
2208   var txtbox=Rico.eventElement(e);
2209   if (typeof this.lastKeyFilter != 'string') this.lastKeyFilter='';\r
2210   if (this.lastKeyFilter==txtbox.value) return;\r
2211   var v=txtbox.value;\r
2212   Rico.log("filterKeypress: "+this.index+' '+v);\r
2213   this.lastKeyFilter=v;
2214   if (v=='' || v=='*')\r
2215     this.setUnfiltered();\r
2216   else {
2217     this.setFilter('LIKE', v, Rico.ColumnConst.USERFILTER, function() {txtbox.value='';});
2218   }\r
2219 },\r
2220
2221 mFilterSelectClick: function(e) {
2222   Rico.eventStop(e);
2223   if (this.mFilter.style.display!='none') {
2224     this.mFilterFinish(e);
2225     if (Rico.isIE && Rico.ieVersion <= 6) {
2226       this.filterField.focus();
2227     } else {
2228       this.filterField.blur();
2229     }
2230   } else {
2231     var offset=Rico.cumulativeOffset(this.filterField);
2232     this.mFilter.style.top=(offset.top+this.filterField.offsetHeight)+'px';
2233     this.mFilter.style.left=offset.left+'px';
2234     this.mFilter.style.width=Math.min(this.filterField.offsetWidth,parseInt(this.colWidth,10))+'px';
2235     Rico.show(this.mFilter);
2236     this.mFilterFocus.focus();
2237   }
2238 },
2239
2240 mFilterFinish: function(e) {
2241   if (!this.mFilterChange) {
2242     Rico.hide(this.mFilter);
2243     return;
2244   }
2245   if (this.mFilterInputs[0].checked) {
2246     this.mFilterReset();
2247     Rico.hide(this.mFilter);
2248     this.setUnfiltered();
2249     return;
2250   }
2251   var newValues=[];
2252   var newLabels=[];
2253   for (var i=1; i<this.mFilterInputs.length; i++) {
2254     if (this.mFilterInputs[i].checked) {
2255       newValues.push(this.mFilterInputs[i].value)
2256       newLabels.push(this.mFilterLabels[i].innerHTML)
2257     }
2258   }
2259   if (newValues.length > 0) {
2260     var newText=newLabels.join(', ');
2261     this.filterField.options[0].text=newText;
2262     this.filterField.title=newText;
2263     Rico.hide(this.mFilter);
2264     this.mFilterChange=false;
2265     this.setFilter('EQ',newValues,Rico.ColumnConst.USERFILTER,Rico.bind(this,'mFilterReset'));
2266   } else {
2267     alert('Please select at least one value');
2268   }
2269 },
2270
2271 mFilterReset: function() {
2272   var newText=this.mFilterLabels[0].innerHTML;  // all
2273   this.filterField.options[0].text=newText;
2274   this.filterField.title=newText;
2275 },
2276
2277 mFilterAllClick: function(e) {
2278   var allChecked=this.mFilterInputs[0].checked;
2279   for (var i=1; i<this.mFilterInputs.length; i++) {
2280     this.mFilterInputs[i].checked=allChecked;
2281   }
2282   this.mFilterChange=true;
2283 },
2284
2285 mFilterOtherClick: function(e) {
2286   this.mFilterInputs[0].checked=false;
2287   this.mFilterChange=true;
2288 },
2289
2290 format_text: function(v) {
2291   if (typeof v!='string')
2292     return '&nbsp;';
2293   else
2294     return Rico.stripTags(v);
2295 },
2296
2297 format_showTags: function(v) {
2298   if (typeof v!='string')
2299     return '&nbsp;';
2300   else
2301     return v.replace(/&/g, '&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
2302 },
2303
2304 format_number: function(v) {
2305   if (typeof v=='undefined' || v=='' || v==null)
2306     return '&nbsp;';
2307   else
2308     return Rico.formatNumber(v,this.format);
2309 },
2310
2311 format_datetime: function(v) {
2312   if (typeof v=='undefined' || v=='' || v==null)
2313     return '&nbsp;';
2314   else {
2315     var d=Rico.setISO8601(v);
2316     if (!d) return v;
2317     return (this.format.prefix || '')+Rico.formatDate(d,this.format.dateFmt || 'translateDateTime')+(this.format.suffix || '');
2318   }
2319 },
2320
2321 // converts GMT/UTC to local time
2322 format_UTCasLocalTime: function(v) {
2323   if (typeof v=='undefined' || v=='' || v==null)
2324     return '&nbsp;';
2325   else {
2326     var tz=new Date();
2327     var d=Rico.setISO8601(v,-tz.getTimezoneOffset());
2328     if (!d) return v;
2329     return (this.format.prefix || '')+Rico.formatDate(d,this.format.dateFmt || 'translateDateTime')+(this.format.suffix || '');
2330   }
2331 },
2332
2333 format_date: function(v) {
2334   if (typeof v=='undefined' || v==null || v=='')
2335     return '&nbsp;';
2336   else {
2337     var d=Rico.setISO8601(v);
2338     if (!d) return v;
2339     return (this.format.prefix || '')+Rico.formatDate(d,this.format.dateFmt || 'translateDate')+(this.format.suffix || '');
2340   }
2341 },
2342
2343 fixHeaders: function(prefix, iconsfirst) {
2344   if (this.sortable) {
2345     var handler=Rico.eventHandle(this,'toggleSort');
2346     switch (this.options.headingSort) {
2347       case 'link':
2348         var a=Rico.wrapChildren(this.hdrCellDiv,'ricoSort',undefined,'a');
2349         a.href = "javascript:void(0)";
2350         Rico.eventBind(a,"click", handler);
2351         break;
2352       case 'hover':
2353         Rico.eventBind(this.hdrCellDiv,"click", handler);
2354         break;
2355     }
2356   }
2357   this.imgFilter = document.createElement('img');
2358   this.imgFilter.style.display='none';
2359   this.imgFilter.src=Rico.imgDir+this.options.filterImg;
2360   this.imgFilter.className='ricoLG_HdrIcon';
2361   this.imgSort = document.createElement('span');
2362   this.imgSort.style.display='none';
2363   if (iconsfirst) {
2364     this.hdrCellDiv.insertBefore(this.imgSort,this.hdrCellDiv.firstChild);
2365     this.hdrCellDiv.insertBefore(this.imgFilter,this.hdrCellDiv.firstChild);
2366   } else {
2367     this.hdrCellDiv.appendChild(this.imgFilter);
2368     this.hdrCellDiv.appendChild(this.imgSort);
2369   }
2370   if (!this.format.filterUI) {
2371     Rico.eventBind(this.imgFilter, 'click', Rico.eventHandle(this,'filterClick'), false);
2372   }
2373 },
2374
2375 filterClick: function(e) {
2376   if (this.filterType==Rico.ColumnConst.USERFILTER && this.filterOp=='LIKE') {
2377     this.liveGrid.openKeyword(this.index);
2378   }
2379 },
2380
2381 getValue: function(windowRow) {
2382   return this.buffer.getWindowCell(windowRow,this.index);
2383 },
2384
2385 getBufferAttr: function(windowRow) {
2386   return this.buffer.getWindowAttr(windowRow,this.index);
2387 },
2388
2389 setValue: function(windowRow,newval) {
2390   this.buffer.setWindowValue(windowRow,this.index,newval);
2391 },
2392
2393 _format: function(v) {
2394   return v;
2395 },
2396
2397 _display: function(v,gridCell) {
2398   gridCell.innerHTML=this._format(v);
2399 },
2400
2401 _export: function(v) {
2402   return this._format(v);
2403 },
2404
2405 exportBuffer: function(bufRow) {
2406   return this._export(this.buffer.getValue(bufRow,this.index));
2407 },
2408
2409 displayValue: function(windowRow) {
2410   var bufval=this.getValue(windowRow);
2411   if (bufval==null) {
2412     this.clearCell(windowRow);
2413     return;
2414   }
2415   var gridCell=this.cell(windowRow);
2416   this._display(bufval,gridCell,windowRow);
2417   var acceptAttr=this.buffer.options.acceptAttr;
2418   if (acceptAttr.length==0) return;
2419   var bufAttr=this.getBufferAttr(windowRow);
2420   if (bufAttr==null) return;
2421   for (var k=0; k<acceptAttr.length; k++) {
2422     bufAttr=bufAttr['_'+acceptAttr[k]] || '';
2423     switch (acceptAttr[k]) {
2424       case 'style': gridCell.style.cssText=bufAttr; break;
2425       case 'class': gridCell.className=bufAttr; break;
2426       default:      gridCell['_'+acceptAttr[k]]=bufAttr; break;
2427     }
2428   }
2429 }
2430
2431 };
2432
2433 Rico.includeLoaded('ricoLiveGrid.js');