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