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