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