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