OSDN Git Service

git-svn-id: https://svn.sourceforge.jp/svnroot/nucleus-jp/plugin@1020 1ca29b6e-896d...
[nucleus-jp/nucleus-plugins.git] / NP_TrackBack / branches / DOM-branch / trackback / js / rico / ricoLiveGrid.js
1 /**
2   *  (c) 2005-2007 Richard Cowin (http://openrico.org)
3   *  (c) 2005-2007 Matt Brown (http://dowdybrown.com)
4   *
5   *  Rico is 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   *   http://www.apache.org/licenses/LICENSE-2.0
8   **/
9
10
11 if(typeof Rico=='undefined') throw("LiveGrid requires the Rico JavaScript framework");
12 if(typeof RicoUtil=='undefined') throw("LiveGrid requires the RicoUtil Library");
13 if(typeof RicoTranslate=='undefined') throw("LiveGrid requires the RicoTranslate Library");
14 if(typeof Rico.TableColumn=='undefined') throw("LiveGrid requires ricoGridCommon.js");
15
16
17 Rico.Buffer = {};
18
19 /**
20  * Loads buffer with data that already exists in the document as an HTML table (no AJAX).
21  * Also serves as a base class for AJAX-enabled buffers.
22  */
23 Rico.Buffer.Base = Class.create();
24
25 Rico.Buffer.Base.prototype = {
26
27   initialize: function(dataTable, options) {
28     this.clear();
29     this.updateInProgress = false;
30     this.lastOffset = 0;
31     this.rcvdRowCount = false;  // true if an eof element was included in the last xml response
32     this.foundRowCount = false; // true if an xml response is ever received with eof true
33     this.totalRows = 0;
34     this.rowcntContent = "";
35     this.rcvdOffset = -1;
36     this.options = {
37       fixedHdrRows     : 0,
38       canFilter        : false, // does buffer object support filtering?
39       isEncoded        : true,  // is the data received via ajax html encoded?
40       acceptAttr       : []     // attributes that can be copied from original/ajax data (e.g. className, style, id)
41     }
42     Object.extend(this.options, options || {});
43     if (dataTable) {
44       this.loadRowsFromTable(dataTable);
45     } else {
46       this.clear();
47     }
48   },
49
50   registerGrid: function(liveGrid) {
51     this.liveGrid = liveGrid;
52   },
53
54   setTotalRows: function( newTotalRows ) {
55     if (this.totalRows == newTotalRows) return;
56     this.totalRows = newTotalRows;
57     if (this.liveGrid) {
58       Rico.writeDebugMsg("setTotalRows, newTotalRows="+newTotalRows);
59       if (this.liveGrid.sizeTo=='data') this.liveGrid.resizeWindow();
60       this.liveGrid.updateHeightDiv();
61     }
62   },
63
64   loadRowsFromTable: function(tableElement) {
65     this.rows = this.dom2jstable(tableElement,this.options.fixedHdrRows);
66     this.startPos = 0;
67     this.size = this.rows.length;
68     this.setTotalRows(this.size);
69     this.rowcntContent = this.size.toString();
70     this.rcvdRowCount = true;
71     this.foundRowCount = true;
72   },
73
74   dom2jstable: function(rowsElement,firstRow) {
75     var newRows = new Array();
76     var trs = rowsElement.getElementsByTagName("tr");
77     var acceptAttr=this.options.acceptAttr;
78     for ( var i=firstRow || 0; i < trs.length; i++ ) {
79       var row = new Array();
80       var cells = trs[i].getElementsByTagName("td");
81       for ( var j=0; j < cells.length ; j++ ) {
82         row[j]={};
83         row[j].content=RicoUtil.getContentAsString(cells[j],this.options.isEncoded);
84         for (var k=0; k<acceptAttr.length; k++) {
85           row[j]['_'+acceptAttr[k]]=cells[j].getAttribute(acceptAttr[k]);
86         }
87         if (Prototype.Browser.IE) row[j]._class=cells[j].getAttribute('className');
88       }
89       newRows.push( row );
90     }
91     return newRows;
92   },
93
94   _blankRow: function() {
95     var newRow=[];
96     for (var i=0; i<this.liveGrid.columns.length; i++) {
97       newRow[i]={};
98       newRow[i].content='';
99     }
100     return newRow;
101   },
102
103   insertRow: function(beforeRowIndex) {
104     this.rows.splice(beforeRowIndex,0,this._blankRow());
105   },
106
107   appendRows: function(cnt) {
108     for (var i=0; i<cnt; i++)
109       this.rows.push(this._blankRow());
110     this.size=this.rows.length;
111   },
112
113   sortBuffer: function(colnum,sortdir,coltype,getvalfunc) {
114     this.sortColumn=colnum;
115     this.getValFunc=getvalfunc;
116     var sortFunc;
117     switch (coltype) {
118       case 'number': sortFunc=this._sortNumeric.bind(this); break;
119       case 'control':sortFunc=this._sortControl.bind(this); break;
120       default:       sortFunc=this._sortAlpha.bind(this); break;
121     }
122     this.rows.sort(sortFunc);
123     if (sortdir=='DESC') this.rows.reverse();
124   },
125
126   _sortAlpha: function(a,b) {
127     var aa = this.sortColumn<a.length ? RicoUtil.getInnerText(a[this.sortColumn].content) : '';
128     var bb = this.sortColumn<b.length ? RicoUtil.getInnerText(b[this.sortColumn].content) : '';
129     if (aa==bb) return 0;
130     if (aa<bb) return -1;
131     return 1;
132   },
133
134   _sortNumeric: function(a,b) {
135     var aa = this.sortColumn<a.length ? parseFloat(RicoUtil.getInnerText(a[this.sortColumn].content)) : 0;
136     if (isNaN(aa)) aa = 0;
137     var bb = this.sortColumn<b.length ? parseFloat(RicoUtil.getInnerText(b[this.sortColumn].content)) : 0;
138     if (isNaN(bb)) bb = 0;
139     return aa-bb;
140   },
141
142   _sortControl: function(a,b) {
143     var aa = this.sortColumn<a.length ? RicoUtil.getInnerText(a[this.sortColumn].content) : '';
144     var bb = this.sortColumn<b.length ? RicoUtil.getInnerText(b[this.sortColumn].content) : '';
145     if (this.getValFunc) {
146       aa=this.getValFunc(aa);
147       bb=this.getValFunc(bb);
148     }
149     if (aa==bb) return 0;
150     if (aa<bb) return -1;
151     return 1;
152   },
153
154   clear: function() {
155     this.rows = new Array();
156     this.startPos = -1;
157     this.size = 0;
158     this.windowPos = 0;
159   },
160
161   isInRange: function(position) {
162     var lastRow=Math.min(this.totalRows, position + this.liveGrid.pageSize)
163     return (position >= this.startPos) && (lastRow <= this.endPos()); // && (this.size != 0);
164   },
165
166   endPos: function() {
167     return this.startPos + this.rows.length;
168   },
169
170   fetch: function(offset) {
171     this.liveGrid.refreshContents(offset);
172     return;
173   },
174
175   exportAllRows: function(populate,finish) {
176     populate(this.getRows(0,this.totalRows));
177     finish();
178   },
179
180   setWindow: function(start, count) {
181     this.windowStart = start - this.startPos;
182     this.windowEnd = Math.min(this.windowStart + count,this.size);
183     this.windowPos = start;
184   },
185
186   isVisible: function(bufRow) {
187     return bufRow < this.rows.length && bufRow >= this.windowStart && bufRow < this.windowEnd;
188   },
189
190   getWindowCell: function(windowRow,col) {
191     var bufrow=this.windowStart+windowRow;
192     return this.isVisible(bufrow) && col < this.rows[bufrow].length ? this.rows[bufrow][col] : null;
193   },
194
195   getWindowValue: function(windowRow,col) {
196     var cell=this.getWindowCell(windowRow,col);
197     return cell ? cell.content : null;
198   },
199
200   setWindowValue: function(windowRow,col,newval) {
201     var bufRow=this.windowStart+windowRow;
202     if (bufRow >= this.windowEnd) return false;
203     return this.setValue(bufRow,col,newval);
204   },
205
206   getCell: function(bufRow,col) {
207     return bufRow < this.size ? this.rows[bufRow][col] : null;
208   },
209
210   getValue: function(bufRow,col) {
211     var cell=this.getCell(bufRow,col);
212     return cell ? cell.content : null;
213   },
214
215   setValue: function(bufRow,col,newval,newstyle) {
216     if (bufRow>=this.size) return false;
217     if (!this.rows[bufRow][col]) this.rows[bufRow][col]={};
218     this.rows[bufRow][col].content=newval;
219     if (typeof newstyle=='string') this.rows[bufRow][col]._style=newstyle;
220     this.rows[bufRow][col].modified=true;
221     return true;
222   },
223
224   getRows: function(start, count) {
225     var begPos = start - this.startPos;
226     var endPos = Math.min(begPos + count,this.size);
227     var results = new Array();
228     for ( var i=begPos; i < endPos; i++ )
229       results.push(this.rows[i]);
230     return results
231   }
232
233 };
234
235
236 // Rico.LiveGrid -----------------------------------------------------
237
238 Rico.LiveGrid = Class.create();
239
240 Rico.LiveGrid.prototype = {
241
242   initialize: function( tableId, buffer, options ) {
243     Object.extend(this, new Rico.GridCommon);
244     Object.extend(this, new Rico.LiveGridMethods);
245     this.baseInit();
246     this.tableId = tableId;
247     this.buffer = buffer;
248     Rico.setDebugArea(tableId+"_debugmsgs");    // if used, this should be a textarea
249
250     Object.extend(this.options, {
251       visibleRows      : -1,    // -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
252       frozenColumns    : 0,
253       offset           : 0,     // first row to be displayed
254       prefetchBuffer   : true,  // load table on page load?
255       minPageRows      : 1,
256       maxPageRows      : 50,
257       canSortDefault   : true,  // can be overridden in the column specs
258       canFilterDefault : buffer.options.canFilter, // can be overridden in the column specs
259       canHideDefault   : true,  // can be overridden in the column specs
260       cookiePrefix     : 'liveGrid.'+tableId,
261
262       // highlight & selection parameters
263       highlightElem    : 'none',// what gets highlighted/selected (cursorRow, cursorCell, menuRow, menuCell, selection, or none)
264       highlightSection : 3,     // which section gets highlighted (frozen=1, scrolling=2, all=3, none=0)
265       highlightMethod  : 'class', // outline, class, both (outline is less CPU intensive on the client)
266       highlightClass   : 'ricoLG_selection',
267
268       // export/print parameters
269       maxPrint         : 1000,  // max # of rows that can be printed/exported, 0=disable print/export feature
270       exportWindow     : "height=300,width=500,scrollbars=1,menubar=1,resizable=1",
271
272       // heading parameters
273       headingSort      : 'link', // link: make headings a link that will sort column, hover: make headings a hoverset, none: events on headings are disabled
274       hdrIconsFirst    : true,   // true: put sort & filter icons before header text, false: after
275       sortAscendImg    : 'sort_asc.gif',
276       sortDescendImg   : 'sort_desc.gif',
277       filterImg        : 'filtercol.gif'
278     });
279     // other options:
280     //   sortCol: initial sort column
281
282     this.options.sortHandler = this.sortHandler.bind(this);
283     this.options.filterHandler = this.filterHandler.bind(this);
284     this.options.onRefreshComplete = this.bookmarkHandler.bind(this);
285     this.options.rowOverHandler = this.rowMouseOver.bindAsEventListener(this);
286     this.options.mouseDownHandler = this.selectMouseDown.bindAsEventListener(this);
287     this.options.mouseOverHandler = this.selectMouseOver.bindAsEventListener(this);
288     this.options.mouseUpHandler  = this.selectMouseUp.bindAsEventListener(this);
289     Object.extend(this.options, options || {});
290
291     switch (typeof this.options.visibleRows) {
292       case 'string':
293         this.sizeTo=this.options.visibleRows;
294         this.options.visibleRows=-1;
295         break;
296       case 'number':
297         switch (this.options.visibleRows) {
298           case -1: this.sizeTo='window'; break;
299           case -2: this.sizeTo='data'; break;
300           case -3: this.sizeTo='body'; break;
301         }
302         break;
303       default:
304         this.sizeTo='window';
305         this.options.visibleRows=-1;
306     }
307     this.highlightEnabled=this.options.highlightSection>0;
308     this.pageSize=0;
309     this.createTables();
310     if (this.headerColCnt==0) {
311       alert('ERROR: no columns found in "'+this.tableId+'"');
312       return;
313     }
314     this.createColumnArray();
315         if (this.options.headingSort=='hover')
316           this.createHoverSet();
317
318     this.bookmark=$(this.tableId+"_bookmark");
319     this.sizeDivs();
320     this.createDataCells(this.options.visibleRows);
321     if (this.pageSize == 0) return;
322     this.buffer.registerGrid(this);
323     if (this.buffer.setBufferSize) this.buffer.setBufferSize(this.pageSize);
324     this.scrollTimeout = null;
325     this.lastScrollPos = 0;
326     this.attachMenuEvents();
327
328     // preload the images...
329     new Image().src = Rico.imgDir+this.options.filterImg;
330     new Image().src = Rico.imgDir+this.options.sortAscendImg;
331     new Image().src = Rico.imgDir+this.options.sortDescendImg;
332     Rico.writeDebugMsg("images preloaded");
333
334     this.setSortUI( this.options.sortCol, this.options.sortDir );
335     this.setImages();
336     if (this.listInvisible().length==this.columns.length)
337       this.columns[0].showColumn();
338     this.sizeDivs();
339     this.scrollDiv.style.display="";
340     if (this.buffer.totalRows>0)
341       this.updateHeightDiv();
342     if (this.options.prefetchBuffer) {
343       if (this.bookmark) this.bookmark.innerHTML = RicoTranslate.getPhrase("Loading...");
344       if (this.options.canFilterDefault && this.options.getQueryParms)
345         this.checkForFilterParms();
346       this.buffer.fetch(this.options.offset);
347     }
348     this.scrollEventFunc=this.handleScroll.bindAsEventListener(this);
349     this.wheelEventFunc=this.handleWheel.bindAsEventListener(this);
350     this.wheelEvent=(Prototype.Browser.IE || Prototype.Browser.Opera || Prototype.Browser.WebKit) ? 'mousewheel' : 'DOMMouseScroll';
351     if (this.options.offset && this.options.offset < this.buffer.totalRows)
352       setTimeout(this.scrollToRow.bind(this,this.options.offset),50);  // Safari requires a delay
353     this.pluginScroll();
354     this.setHorizontalScroll();
355     if (this.options.windowResize)
356       setTimeout(this.pluginWindowResize.bind(this),100);
357   }
358 };
359
360 Rico.LiveGridMethods = function() {};
361
362 Rico.LiveGridMethods.prototype = {
363
364   createHoverSet: function() {
365     var hdrs=[];
366     for( var c=0; c < this.headerColCnt; c++ )
367       hdrs.push(this.columns[c].hdrCellDiv);
368           this.hoverSet = new Rico.HoverSet(hdrs);
369   },
370
371   checkForFilterParms: function() {
372     var s=window.location.search;
373     if (s.charAt(0)=='?') s=s.substring(1);
374     var pairs = s.split('&');
375     for (var i=0; i<pairs.length; i++)
376       if (pairs[i].match(/^f\[\d+\]/)) {
377         this.buffer.options.requestParameters.push(pairs[i]);
378       }
379   },
380
381 /**
382  * Create one table for frozen columns and one for scrolling columns.
383  * Also create div's to contain them.
384  */
385   createTables: function() {
386     var insertloc;
387     var result = -1;
388     var table = $(this.tableId);
389     if (!table) return result;
390     if (table.tagName.toLowerCase()=='table') {
391       var theads=table.getElementsByTagName("thead");
392       if (theads.length == 1) {
393         Rico.writeDebugMsg("createTables: using thead section, id="+this.tableId);
394         var hdrSrc=theads[0].rows;
395       } else {
396         Rico.writeDebugMsg("createTables: using tbody section, id="+this.tableId);
397         var hdrSrc=new Array(table.rows[0]);
398       }
399       insertloc=table;
400     } else if (this.options.columnSpecs.length > 0) {
401       insertloc=table;
402       Rico.writeDebugMsg("createTables: inserting at "+table.tagName+", id="+this.tableId);
403     } else {
404       alert("ERROR!\n\nUnable to initialize '"+this.tableId+"'\n\nLiveGrid terminated");
405       return result;
406     }
407
408     this.createDivs();
409     this.scrollTabs = this.createDiv("scrollTabs",this.innerDiv);
410     this.shadowDiv  = this.createDiv("shadow",this.scrollDiv);
411     this.shadowDiv.style.direction='ltr';  // avoid FF bug
412     this.messageDiv = this.createDiv("message",this.outerDiv);
413     this.messageDiv.style.display="none";
414     this.messageShadow=new Rico.Shadow(this.messageDiv);
415     this.scrollDiv.style.display="none";
416     this.scrollDiv.scrollTop=0;
417     if (this.options.highlightMethod!='class') {
418       this.highlightDiv=[];
419       switch (this.options.highlightElem) {
420         case 'menuRow':
421         case 'cursorRow':
422           this.highlightDiv[0] = this.createDiv("highlight",this.outerDiv);
423           this.highlightDiv[0].style.display="none";
424           break;
425         case 'menuCell':
426         case 'cursorCell':
427           for (var i=0; i<2; i++) {
428             this.highlightDiv[i] = this.createDiv("highlight",i==0 ? this.frozenTabs : this.scrollTabs);
429             this.highlightDiv[i].style.display="none";
430             this.highlightDiv[i].id+=i;
431           }
432           break;
433         case 'selection':
434           // create one div for each side of the rectangle
435           var parentDiv=this.options.highlightSection==1 ? this.frozenTabs : this.scrollTabs;
436           for (var i=0; i<4; i++) {
437             this.highlightDiv[i] = this.createDiv("highlight",parentDiv);
438             this.highlightDiv[i].style.display="none";
439             this.highlightDiv[i].id+=i;
440             this.highlightDiv[i].style[i % 2==0 ? 'height' : 'width']="0px";
441           }
442           break;
443       }
444     }
445
446     // create new tables
447     for (var i=0; i<2; i++) {
448       this.tabs[i] = document.createElement("table");
449       this.tabs[i].className = 'ricoLG_table';
450       this.tabs[i].border=0;
451       this.tabs[i].cellPadding=0;
452       this.tabs[i].cellSpacing=0;
453       this.tabs[i].id = this.tableId+"_tab"+i;
454       this.thead[i]=this.tabs[i].createTHead();
455       this.thead[i].className='ricoLG_top';
456       if (this.tabs[i].tBodies.length==0)
457         this.tbody[i]=this.tabs[i].appendChild(document.createElement("tbody"));
458       else
459         this.tbody[i]=this.tabs[i].tBodies[0];
460       this.tbody[i].className='ricoLG_bottom';
461       this.tbody[i].insertRow(-1);
462     }
463     this.frozenTabs.appendChild(this.tabs[0]);
464     this.scrollTabs.appendChild(this.tabs[1]);
465     insertloc.parentNode.insertBefore(this.outerDiv,insertloc);
466     if (hdrSrc)
467       this.loadHdrSrc(hdrSrc);
468     else
469       this.createHdr();
470     for( var c=0; c < this.headerColCnt; c++ )
471       this.tbody[c<this.options.frozenColumns ? 0 : 1].rows[0].insertCell(-1);
472     if (table) table.parentNode.removeChild(table);
473     Rico.writeDebugMsg('createTables end');
474   },
475
476   createDataCells: function(visibleRows) {
477     if (visibleRows < 0) {
478       this.appendBlankRow();
479       this.sizeDivs();
480       this.autoAppendRows(this.remainingHt());
481     } else {
482       for( var r=0; r < visibleRows; r++ )
483         this.appendBlankRow();
484     }
485     var s=this.options.highlightSection;
486     if (s & 1) this.attachHighlightEvents(this.tbody[0]);
487     if (s & 2) this.attachHighlightEvents(this.tbody[1]);
488     return;
489   },
490
491   createHdr: function() {
492     for (var i=0; i<2; i++) {
493       var start=(i==0) ? 0 : this.options.frozenColumns;
494       var limit=(i==0) ? this.options.frozenColumns : this.options.columnSpecs.length;
495       Rico.writeDebugMsg('createHdr: i='+i+' start='+start+' limit='+limit);
496       if (this.options.PanelNamesOnTabHdr && this.options.panels) {
497         // place panel names on first row of thead
498         var r = this.thead[i].insertRow(-1);
499         r.className='ricoLG_hdg';
500         var lastIdx=-1, span, newCell=null, spanIdx=0;
501         for( var c=start; c < limit; c++ ) {
502           if (lastIdx == this.options.columnSpecs[c].panelIdx) {
503             span++;
504           } else {
505             if (newCell) newCell.colSpan=span;
506             newCell = r.insertCell(-1);
507             span=1;
508             lastIdx=this.options.columnSpecs[c].panelIdx;
509             newCell.innerHTML=this.options.panels[lastIdx];
510           }
511         }
512         if (newCell) newCell.colSpan=span;
513       }
514       var mainRow = this.thead[i].insertRow(-1);
515       mainRow.id=this.tableId+'_tab'+i+'h_main';
516       mainRow.className='ricoLG_hdg';
517       for( var c=start; c < limit; c++ ) {
518         var newCell = mainRow.insertCell(-1);
519         newCell.innerHTML=this.options.columnSpecs[c].Hdg;
520       }
521       this.headerColCnt = this.getColumnInfo(this.thead[i].rows);
522     }
523   },
524
525   loadHdrSrc: function(hdrSrc) {
526     Rico.writeDebugMsg('loadHdrSrc start');
527     this.headerColCnt = this.getColumnInfo(hdrSrc);
528     for (var i=0; i<2; i++) {
529       for (var r=0; r<hdrSrc.length; r++) {
530         var newrow = this.thead[i].insertRow(-1);
531         newrow.className='ricoLG_hdg';
532       }
533     }
534     if (hdrSrc.length==1) {
535       var cells=hdrSrc[0].cells;
536       for (var c=0; cells.length > 0; c++)
537         this.thead[c<this.options.frozenColumns ? 0 : 1].rows[0].appendChild(cells[0]);
538     } else {
539       for (var r=0; r<hdrSrc.length; r++) {
540         var cells=hdrSrc[r].cells;
541         for (var c=0; cells.length > 0; c++) {
542           if (cells[0].className=='ricoFrozen') {
543             this.thead[0].rows[r].appendChild(cells[0]);
544             if (r==this.headerRowIdx) this.options.frozenColumns=c+1;
545           } else {
546             this.thead[1].rows[r].appendChild(cells[0]);
547           }
548         }
549       }
550     }
551     Rico.writeDebugMsg('loadHdrSrc end');
552   },
553
554   sizeDivs: function() {
555     Rico.writeDebugMsg('sizeDivs: '+this.tableId);
556     this.cancelMenu();
557     this.unhighlight();
558     this.baseSizeDivs();
559     if (this.pageSize == 0) return;
560     this.rowHeight = Math.round(this.dataHt/this.pageSize);
561     var scrHt=this.dataHt;
562     if (this.scrWi>0 || Prototype.Browser.IE || Prototype.Browser.WebKit)
563       scrHt+=this.options.scrollBarWidth;
564     this.scrollDiv.style.height=scrHt+'px';
565     this.innerDiv.style.width=(this.scrWi-this.options.scrollBarWidth+1)+'px';
566     this.resizeDiv.style.height=this.frozenTabs.style.height=this.innerDiv.style.height=(this.hdrHt+this.dataHt+1)+'px';
567     Rico.writeDebugMsg('sizeDivs scrHt='+scrHt+' innerHt='+this.innerDiv.style.height+' rowHt='+this.rowHeight+' pageSize='+this.pageSize);
568     pad=(this.scrWi-this.scrTabWi < this.options.scrollBarWidth) ? 2 : 0;
569     this.shadowDiv.style.width=(this.scrTabWi+pad)+'px';
570     this.outerDiv.style.height=(this.hdrHt+scrHt)+'px';
571     this.setHorizontalScroll();
572   },
573
574   setHorizontalScroll: function() {
575     var scrleft=this.scrollDiv.scrollLeft;
576     this.scrollTabs.style.left=(-scrleft)+'px';
577   },
578
579   remainingHt: function() {
580     var winHt=RicoUtil.windowHeight();
581     var margin=Prototype.Browser.IE ? 15 : 10;
582     switch (this.sizeTo) {
583       case 'window':
584       case 'data':
585         var divPos=Position.page(this.outerDiv);
586         var tabHt=Math.max(this.tabs[0].offsetHeight,this.tabs[1].offsetHeight);
587         Rico.writeDebugMsg("remainingHt, winHt="+winHt+' tabHt='+tabHt+' gridY='+divPos[1]);
588         return winHt-divPos[1]-tabHt-this.options.scrollBarWidth-margin;  // allow for scrollbar and some margin
589       case 'body':
590         //Rico.writeDebugMsg("remainingHt, document.height="+document.height);
591         //Rico.writeDebugMsg("remainingHt, body.offsetHeight="+document.body.offsetHeight);
592         //Rico.writeDebugMsg("remainingHt, body.scrollHeight="+document.body.scrollHeight);
593         //Rico.writeDebugMsg("remainingHt, documentElement.scrollHeight="+document.documentElement.scrollHeight);
594         var bodyHt=Prototype.Browser.IE ? document.body.scrollHeight : document.body.offsetHeight;
595         var remHt=winHt-bodyHt-margin;
596         if (!Prototype.Browser.WebKit) remHt-=this.options.scrollBarWidth;
597         Rico.writeDebugMsg("remainingHt, winHt="+winHt+' pageHt='+bodyHt+' remHt='+remHt);
598         return remHt;
599     }
600   },
601
602   adjustPageSize: function() {
603     var remHt=this.remainingHt();
604     Rico.writeDebugMsg('adjustPageSize remHt='+remHt+' lastRow='+this.lastRowPos);
605     if (remHt > this.rowHeight)
606       this.autoAppendRows(remHt);
607     else if (remHt < 0 || this.sizeTo=='data')
608       this.autoRemoveRows(-remHt);
609   },
610
611   pluginWindowResize: function() {
612     Event.observe(window, "resize", this.resizeWindow.bindAsEventListener(this), false);
613   },
614
615   resizeWindow: function() {
616     Rico.writeDebugMsg('resizeWindow '+this.tableId+' lastRow='+this.lastRowPos);
617     if (!this.sizeTo) {
618       this.sizeDivs();
619       return;
620     }
621     var oldSize=this.pageSize;
622     this.adjustPageSize();
623     if (this.pageSize > oldSize) {
624       this.isPartialBlank=true;
625       var adjStart=this.adjustRow(this.lastRowPos);
626       this.buffer.fetch(adjStart);
627     }
628     if (oldSize != this.pageSize)
629       setTimeout(this.finishResize.bind(this),50);
630     else
631       this.sizeDivs();
632     Rico.writeDebugMsg('resizeWindow complete. old size='+oldSize+' new size='+this.pageSize);
633   },
634
635   finishResize: function() {
636     this.sizeDivs();
637     this.updateHeightDiv();
638   },
639
640   topOfLastPage: function() {
641     return Math.max(this.buffer.totalRows-this.pageSize,0);
642   },
643
644   updateHeightDiv: function() {
645     var notdisp=this.topOfLastPage();
646     var ht = this.scrollDiv.clientHeight + this.rowHeight * notdisp;
647     //if (Prototype.Browser.Opera) ht+=this.options.scrollBarWidth-3;
648     Rico.writeDebugMsg("updateHeightDiv, ht="+ht+' scrollDiv.clientHeight='+this.scrollDiv.clientHeight+' rowsNotDisplayed='+notdisp);
649     this.shadowDiv.style.height=ht+'px';
650   },
651
652   autoRemoveRows: function(overage) {
653     var removeCnt=Math.ceil(overage / this.rowHeight);
654     if (this.sizeTo=='data')
655       removeCnt=Math.max(removeCnt,this.pageSize-this.buffer.totalRows);
656     Rico.writeDebugMsg("autoRemoveRows overage="+overage+" removeCnt="+removeCnt);
657     for (var i=0; i<removeCnt; i++)
658       this.removeRow();
659   },
660
661   removeRow: function() {
662     if (this.pageSize <= this.options.minPageRows) return;
663     this.pageSize--;
664     for( var c=0; c < this.headerColCnt; c++ ) {
665       var cell=this.columns[c].cell(this.pageSize);
666       this.columns[c].dataColDiv.removeChild(cell);
667     }
668   },
669
670   autoAppendRows: function(overage) {
671     var addCnt=Math.floor(overage / this.rowHeight);
672     Rico.writeDebugMsg("autoAppendRows overage="+overage+" cnt="+addCnt+" rowHt="+this.rowHeight);
673     for (var i=0; i<addCnt; i++) {
674       if (this.sizeTo=='data' && this.pageSize>=this.buffer.totalRows) break;
675       this.appendBlankRow();
676     }
677   },
678
679   // on older systems, this can be fairly slow
680   appendBlankRow: function() {
681     if (this.pageSize >= this.options.maxPageRows) return;
682     Rico.writeDebugMsg("appendBlankRow #"+this.pageSize);
683     var cls=this.defaultRowClass(this.pageSize);
684     for( var c=0; c < this.headerColCnt; c++ ) {
685       var newdiv = document.createElement("div");
686       newdiv.className = 'ricoLG_cell '+cls;
687       newdiv.id=this.tableId+'_'+this.pageSize+'_'+c;
688       this.columns[c].dataColDiv.appendChild(newdiv);
689       newdiv.innerHTML='&nbsp;';
690       if (this.columns[c]._create)
691         this.columns[c]._create(newdiv,this.pageSize);
692     }
693     this.pageSize++;
694   },
695
696   defaultRowClass: function(rownum) {
697     return (rownum % 2==0) ? 'ricoLG_evenRow' : 'ricoLG_oddRow';
698   },
699
700   handleMenuClick: function(e) {
701     //Event.stop(e);
702     if (!this.menu) return;
703     this.cancelMenu();
704     this.unhighlight(); // in case highlighting was invoked externally
705     var cell=Event.element(e);
706     if (cell.className=='ricoLG_highlightDiv') {
707       var idx=this.highlightIdx;
708     } else {
709       cell=RicoUtil.getParentByTagName(cell,'div','ricoLG_cell');
710       if (!cell) return;
711       var idx=this.winCellIndex(cell);
712       if ((this.options.highlightSection & (idx.tabIdx+1))==0) return;
713     }
714     this.highlight(idx);
715     this.highlightEnabled=false;
716     if (this.hideScroll) this.scrollDiv.style.overflow="hidden";
717     this.menuIdx=idx;
718     if (!this.menu.div) this.menu.createDiv();
719     this.menu.liveGrid=this;
720     if (this.menu.buildGridMenu) {
721       var showMenu=this.menu.buildGridMenu(idx.row, idx.column, idx.tabIdx);
722       if (!showMenu) return;
723     }
724     if (this.options.highlightElem=='selection' && !this.isSelected(idx.cell))
725       this.selectCell(idx.cell);
726     this.menu.showmenu(e,this.closeMenu.bind(this));
727   },
728
729   closeMenu: function() {
730     if (!this.menuIdx) return;
731     if (this.hideScroll) this.scrollDiv.style.overflow="";
732     this.unhighlight();
733     this.highlightEnabled=true;
734     this.menuIdx=null;
735   },
736
737 /**
738  * @return index of cell within the window
739  */
740   winCellIndex: function(cell) {
741     var a=cell.id.split(/_/);
742     var l=a.length;
743     var r=parseInt(a[l-2]);
744     var c=parseInt(a[l-1]);
745     return {row:r, column:c, tabIdx:this.columns[c].tabIdx, cell:cell};
746   },
747
748 /**
749  * @return index of cell within the buffer
750  */
751   bufCellIndex: function(cell) {
752     var idx=this.winCellIndex(cell);
753     idx.row+=this.buffer.windowPos;
754     if (idx.row >= this.buffer.size) idx.onBlankRow=true;
755     return idx;
756   },
757
758   attachHighlightEvents: function(tBody) {
759     switch (this.options.highlightElem) {
760       case 'selection':
761         Event.observe(tBody,"mousedown", this.options.mouseDownHandler, false);
762         tBody.ondrag = function () { return false; };
763         tBody.onselectstart = function () { return false; };
764         break;
765       case 'cursorRow':
766       case 'cursorCell':
767         Event.observe(tBody,"mouseover", this.options.rowOverHandler, false);
768         break;
769     }
770   },
771
772   getVisibleSelection: function() {
773     var cellList=[];
774     if (this.SelectIdxStart && this.SelectIdxEnd) {
775       var r1=Math.max(Math.min(this.SelectIdxEnd.row,this.SelectIdxStart.row),this.buffer.windowPos);
776       var r2=Math.min(Math.max(this.SelectIdxEnd.row,this.SelectIdxStart.row),this.buffer.windowEnd-1);
777       var c1=Math.min(this.SelectIdxEnd.column,this.SelectIdxStart.column);
778       var c2=Math.max(this.SelectIdxEnd.column,this.SelectIdxStart.column);
779       for (var r=r1; r<=r2; r++)
780         for (var c=c1; c<=c2; c++)
781           cellList.push({row:r-this.buffer.windowPos,column:c});
782     }
783     if (this.SelectCtrl) {
784       for (var i=0; i<this.SelectCtrl.length; i++) {
785         if (this.SelectCtrl[i].row>=this.buffer.windowPos && this.SelectCtrl[i].row<this.buffer.windowEnd)
786           cellList.push({row:this.SelectCtrl[i].row-this.buffer.windowPos,column:this.SelectCtrl[i].column});
787       }
788     }
789     return cellList;
790   },
791
792   updateSelectOutline: function() {
793     if (!this.SelectIdxStart || !this.SelectIdxEnd) return;
794     var r1=Math.max(Math.min(this.SelectIdxEnd.row,this.SelectIdxStart.row), this.buffer.windowStart);
795     var r2=Math.min(Math.max(this.SelectIdxEnd.row,this.SelectIdxStart.row), this.buffer.windowEnd-1);
796     if (r1 > r2) {
797       this.HideSelection();
798       return;
799     }
800     var c1=Math.min(this.SelectIdxEnd.column,this.SelectIdxStart.column);
801     var c2=Math.max(this.SelectIdxEnd.column,this.SelectIdxStart.column);
802     var top1=this.columns[c1].cell(r1-this.buffer.windowStart).offsetTop;
803     var cell2=this.columns[c1].cell(r2-this.buffer.windowStart);
804     var bottom2=cell2.offsetTop+cell2.offsetHeight;
805     var left1=this.columns[c1].dataCell.offsetLeft;
806     var left2=this.columns[c2].dataCell.offsetLeft;
807     var right2=left2+this.columns[c2].dataCell.offsetWidth;
808     //window.status='updateSelectOutline: '+r1+' '+r2+' top='+top1+' bot='+bottom2;
809     this.highlightDiv[0].style.top=this.highlightDiv[3].style.top=this.highlightDiv[1].style.top=(this.hdrHt+top1-1) + 'px';
810     this.highlightDiv[2].style.top=(this.hdrHt+bottom2-1)+'px';
811     this.highlightDiv[3].style.left=(left1-2)+'px';
812     this.highlightDiv[0].style.left=this.highlightDiv[2].style.left=(left1-1)+'px';
813     this.highlightDiv[1].style.left=(right2-1)+'px';
814     this.highlightDiv[0].style.width=this.highlightDiv[2].style.width=(right2-left1-1) + 'px';
815     this.highlightDiv[1].style.height=this.highlightDiv[3].style.height=(bottom2-top1) + 'px';
816     //this.highlightDiv[0].style.right=this.highlightDiv[2].style.right=this.highlightDiv[1].style.right=()+'px';
817     //this.highlightDiv[2].style.bottom=this.highlightDiv[3].style.bottom=this.highlightDiv[1].style.bottom=(this.hdrHt+bottom2) + 'px';
818     for (var i=0; i<4; i++)
819       this.highlightDiv[i].style.display='';
820   },
821
822   HideSelection: function(cellList) {
823     if (this.options.highlightMethod!='class') {
824       for (var i=0; i<4; i++)
825         this.highlightDiv[i].style.display='none';
826     }
827     if (this.options.highlightMethod!='outline') {
828       var cellList=this.getVisibleSelection();
829       for (var i=0; i<cellList.length; i++)
830         this.unhighlightCell(this.columns[cellList[i].column].cell(cellList[i].row));
831     }
832   },
833
834   ShowSelection: function() {
835     if (this.options.highlightMethod!='class')
836       this.updateSelectOutline();
837     if (this.options.highlightMethod!='outline') {
838       var cellList=this.getVisibleSelection();
839       for (var i=0; i<cellList.length; i++)
840         this.highlightCell(this.columns[cellList[i].column].cell(cellList[i].row));
841     }
842   },
843
844   ClearSelection: function() {
845     this.HideSelection();
846     this.SelectIdxStart=null;
847     this.SelectIdxEnd=null;
848     this.SelectCtrl=[];
849   },
850
851   selectCell: function(cell) {
852     this.ClearSelection();
853     this.SelectIdxStart=this.SelectIdxEnd=this.bufCellIndex(cell);
854     this.ShowSelection();
855   },
856
857   AdjustSelection: function(cell) {
858     var newIdx=this.bufCellIndex(cell);
859     if (this.SelectIdxStart.tabIdx != newIdx.tabIdx) return;
860     this.HideSelection();
861     this.SelectIdxEnd=newIdx;
862     this.ShowSelection();
863   },
864
865   RefreshSelection: function() {
866     var cellList=this.getVisibleSelection();
867     for (var i=0; i<cellList.length; i++)
868       this.columns[cellList[i].column].displayValue(cellList[i].row);
869   },
870
871   FillSelection: function(newVal,newStyle) {
872     if (this.SelectIdxStart && this.SelectIdxEnd) {
873       var r1=Math.min(this.SelectIdxEnd.row,this.SelectIdxStart.row);
874       var r2=Math.max(this.SelectIdxEnd.row,this.SelectIdxStart.row);
875       var c1=Math.min(this.SelectIdxEnd.column,this.SelectIdxStart.column);
876       var c2=Math.max(this.SelectIdxEnd.column,this.SelectIdxStart.column);
877       for (var r=r1; r<=r2; r++)
878         for (var c=c1; c<=c2; c++)
879           this.buffer.setValue(r,c,newVal,newStyle);
880     }
881     if (this.SelectCtrl) {
882       for (var i=0; i<this.SelectCtrl.length; i++)
883         this.buffer.setValue(this.SelectCtrl[i].row,this.SelectCtrl[i].column,newVal,newStyle);
884     }
885     this.RefreshSelection();
886   },
887
888   selectMouseDown: function(e) {
889     if (this.highlightEnabled==false) return true;
890     this.cancelMenu();
891     var cell=Event.element(e);
892     Event.stop(e);
893     if (!Event.isLeftClick(e)) return;
894     cell=RicoUtil.getParentByTagName(cell,'div','ricoLG_cell');
895     if (!cell) return;
896     var newIdx=this.bufCellIndex(cell);
897     if (newIdx.onBlankRow) return;
898     if (e.ctrlKey) {
899       if (!this.SelectIdxStart || this.options.highlightMethod!='class') return;
900       if (!this.isSelected(cell)) {
901         this.highlightCell(cell);
902         this.SelectCtrl.push(this.bufCellIndex(cell));
903       } else {
904         for (var i=0; i<this.SelectCtrl.length; i++) {
905           if (this.SelectCtrl[i].row==newIdx.row && this.SelectCtrl[i].column==newIdx.column) {
906             this.unhighlightCell(cell);
907             this.SelectCtrl.splice(i,1);
908             break;
909           }
910         }
911       }
912     } else if (e.shiftKey) {
913       if (!this.SelectIdxStart) return;
914       this.AdjustSelection(cell);
915     } else {
916       this.selectCell(cell);
917       this.pluginSelect();
918     }
919   },
920
921   pluginSelect: function() {
922     if (this.selectPluggedIn) return;
923     var tBody=this.tbody[this.SelectIdxStart.tabIdx];
924     Event.observe(tBody,"mouseover", this.options.mouseOverHandler, false);
925     Event.observe(this.outerDiv,"mouseup",  this.options.mouseUpHandler,  false);
926     if (this.options.highlightMethod!='class')
927     this.selectPluggedIn=true;
928   },
929
930   unplugSelect: function() {
931     var tBody=this.tbody[this.SelectIdxStart.tabIdx];
932     Event.stopObserving(tBody,"mouseover", this.options.mouseOverHandler , false);
933     Event.stopObserving(this.outerDiv,"mouseup", this.options.mouseUpHandler , false);
934     this.selectPluggedIn=false;
935   },
936
937   selectMouseUp: function(e) {
938     this.unplugSelect();
939     var cell=Event.element(e);
940     cell=RicoUtil.getParentByTagName(cell,'div','ricoLG_cell');
941     if (!cell) return;
942     if (this.SelectIdxStart && this.SelectIdxEnd)
943       this.AdjustSelection(cell);
944     else
945       this.ClearSelection();
946   },
947
948   selectMouseOver: function(e) {
949     var cell=Event.element(e);
950     cell=RicoUtil.getParentByTagName(cell,'div','ricoLG_cell');
951     if (!cell) return;
952     this.AdjustSelection(cell);
953     Event.stop(e);
954   },
955
956   isSelected: function(cell) {
957     return Element.hasClassName(cell,this.options.highlightClass);
958   },
959
960   highlightCell: function(cell) {
961     Element.addClassName(cell,this.options.highlightClass);
962   },
963
964   unhighlightCell: function(cell) {
965     if (cell==null) return;
966     Element.removeClassName(cell,this.options.highlightClass);
967   },
968
969   selectRow: function(r) {
970     for (var c=0; c<this.columns.length; c++)
971       this.highlightCell(this.columns[c].cell(r));
972   },
973
974   unselectRow: function(r) {
975     for (var c=0; c<this.columns.length; c++)
976       this.unhighlightCell(this.columns[c].cell(r));
977   },
978
979   rowMouseOver: function(e) {
980     if (!this.highlightEnabled) return;
981     var cell=Event.element(e);
982     cell=RicoUtil.getParentByTagName(cell,'div','ricoLG_cell');
983     if (!cell) return;
984     var newIdx=this.winCellIndex(cell);
985     if ((this.options.highlightSection & (newIdx.tabIdx+1))==0) return;
986     this.highlight(newIdx);
987   },
988
989   highlight: function(newIdx) {
990     if (this.options.highlightMethod!='outline') this.cursorSetClass(newIdx);
991     if (this.options.highlightMethod!='class') this.cursorOutline(newIdx);
992     this.highlightIdx=newIdx;
993   },
994
995   cursorSetClass: function(newIdx) {
996     switch (this.options.highlightElem) {
997       case 'menuCell':
998       case 'cursorCell':
999         if (this.highlightIdx) this.unhighlightCell(this.highlightIdx.cell);
1000         this.highlightCell(newIdx.cell);
1001         break;
1002       case 'menuRow':
1003       case 'cursorRow':
1004         if (this.highlightIdx) this.unselectRow(this.highlightIdx.row);
1005         var s1=this.options.highlightSection & 1;
1006         var s2=this.options.highlightSection & 2;
1007         var c0=s1 ? 0 : this.options.frozenColumns;
1008         var c1=s2 ? this.columns.length : this.options.frozenColumns;
1009         for (var c=c0; c<c1; c++)
1010           this.highlightCell(this.columns[c].cell(newIdx.row));
1011         break;
1012       default: return;
1013     }
1014   },
1015
1016   cursorOutline: function(newIdx) {
1017     switch (this.options.highlightElem) {
1018       case 'menuCell':
1019       case 'cursorCell':
1020         var div=this.highlightDiv[newIdx.tabIdx];
1021         div.style.left=(this.columns[newIdx.column].dataCell.offsetLeft-1)+'px';
1022         div.style.width=this.columns[newIdx.column].colWidth;
1023         this.highlightDiv[1-newIdx.tabIdx].style.display='none';
1024         break;
1025       case 'menuRow':
1026       case 'cursorRow':
1027         var div=this.highlightDiv[0];
1028         var s1=this.options.highlightSection & 1;
1029         var s2=this.options.highlightSection & 2;
1030         div.style.left=s1 ? '0px' : this.frozenTabs.style.width;
1031         div.style.width=((s1 ? this.frozenTabs.offsetWidth : 0) + (s2 ? this.innerDiv.offsetWidth : 0) - 4)+'px';
1032         break;
1033       default: return;
1034     }
1035     div.style.top=(this.hdrHt+newIdx.row*this.rowHeight-1)+'px';
1036     div.style.height=(this.rowHeight-1)+'px';
1037     div.style.display='';
1038   },
1039
1040   unhighlight: function() {
1041     switch (this.options.highlightElem) {
1042       case 'menuCell':
1043         this.highlightIdx=this.menuIdx;
1044       case 'cursorCell':
1045         if (this.highlightIdx) this.unhighlightCell(this.highlightIdx.cell);
1046         if (!this.highlightDiv) return;
1047         for (var i=0; i<2; i++)
1048           this.highlightDiv[i].style.display='none';
1049         break;
1050       case 'menuRow':
1051         this.highlightIdx=this.menuIdx;
1052       case 'cursorRow':
1053         if (this.highlightIdx) this.unselectRow(this.highlightIdx.row);
1054         if (this.highlightDiv) this.highlightDiv[0].style.display='none';
1055         break;
1056     }
1057   },
1058
1059   hideMsg: function() {
1060     if (this.messageDiv.style.display=="none") return;
1061     this.messageDiv.style.display="none";
1062     this.messageShadow.hide();
1063   },
1064
1065   showMsg: function(msg) {
1066     this.messageDiv.innerHTML=RicoTranslate.getPhrase(msg);
1067     this.messageDiv.style.display="";
1068     var msgWidth=this.messageDiv.offsetWidth;
1069     var msgHeight=this.messageDiv.offsetHeight;
1070     var divwi=this.outerDiv.offsetWidth;
1071     var divht=this.outerDiv.offsetHeight;
1072     this.messageDiv.style.top=parseInt((divht-msgHeight)/2)+'px';
1073     this.messageDiv.style.left=parseInt((divwi-msgWidth)/2)+'px';
1074     this.messageShadow.show();
1075     Rico.writeDebugMsg("showMsg: "+msg);
1076   },
1077
1078   resetContents: function(resetHt) {
1079     Rico.writeDebugMsg("resetContents("+resetHt+")");
1080     this.buffer.clear();
1081     this.clearRows();
1082     if (typeof resetHt=='undefined' || resetHt==true) {
1083       this.buffer.setTotalRows(0);
1084     } else {
1085       this.scrollToRow(0);
1086     }
1087     if (this.bookmark) this.bookmark.innerHTML="&nbsp;";
1088   },
1089
1090   setImages: function() {
1091     for (n=0; n<this.columns.length; n++)
1092       this.columns[n].setImage();
1093   },
1094
1095   // returns column index, or -1 if there are no sorted columns
1096   findSortedColumn: function() {
1097     for (var n=0; n<this.columns.length; n++)
1098       if (this.columns[n].isSorted()) return n;
1099     return -1;
1100   },
1101
1102   findColumnName: function(name) {
1103     for (var n=0; n<this.columns.length; n++)
1104       if (this.columns[n].fieldName == name) return n;
1105     return -1;
1106   },
1107
1108 /**
1109  * Set initial sort
1110  */
1111   setSortUI: function( columnNameOrNum, sortDirection ) {
1112     Rico.writeDebugMsg("setSortUI: "+columnNameOrNum+' '+sortDirection);
1113     var colnum=this.findSortedColumn();
1114     if (colnum >= 0) {
1115       sortDirection=this.columns[colnum].getSortDirection();
1116     } else {
1117       if (typeof sortDirection!='string') {
1118         sortDirection=Rico.TableColumn.SORT_ASC;
1119       } else {
1120         sortDirection=sortDirection.toUpperCase();
1121         if (sortDirection != Rico.TableColumn.SORT_DESC) sortDirection=Rico.TableColumn.SORT_ASC;
1122       }
1123       switch (typeof columnNameOrNum) {
1124         case 'string':
1125           colnum=this.findColumnName(columnNameOrNum);
1126           break;
1127         case 'number':
1128           colnum=columnNameOrNum;
1129           break;
1130       }
1131     }
1132     if (typeof(colnum)!='number' || colnum < 0) return;
1133     this.clearSort();
1134     this.columns[colnum].setSorted(sortDirection);
1135     this.buffer.sortBuffer(colnum,sortDirection,this.columns[colnum].format.type,this.columns[colnum]._sortfunc);
1136   },
1137
1138 /**
1139  * clear sort flag on all columns
1140  */
1141   clearSort: function() {
1142     for (var x=0;x<this.columns.length;x++)
1143       this.columns[x].setUnsorted();
1144   },
1145
1146 /**
1147  * clear filters on all columns
1148  */
1149   clearFilters: function() {
1150     for (var x=0;x<this.columns.length;x++)
1151       this.columns[x].setUnfiltered(true);
1152     if (this.options.filterHandler)
1153       this.options.filterHandler();
1154   },
1155
1156 /**
1157  * returns number of columns with a user filter set
1158  */
1159   filterCount: function() {
1160     for (var x=0,cnt=0;x<this.columns.length;x++)
1161       if (this.columns[x].isFiltered()) cnt++;
1162     return cnt;
1163   },
1164
1165   sortHandler: function() {
1166     this.cancelMenu();
1167     this.setImages();
1168     var n=this.findSortedColumn();
1169     if (n < 0) return;
1170     Rico.writeDebugMsg("sortHandler: sorting column "+n);
1171     this.buffer.sortBuffer(n,this.columns[n].getSortDirection(),this.columns[n].format.type,this.columns[n]._sortfunc);
1172     this.clearRows();
1173     this.scrollDiv.scrollTop = 0;
1174     this.buffer.fetch(0);
1175   },
1176
1177   filterHandler: function() {
1178     Rico.writeDebugMsg("filterHandler");
1179     this.cancelMenu();
1180     this.ClearSelection();
1181     this.setImages();
1182     if (this.bookmark) this.bookmark.innerHTML="&nbsp;";
1183     this.clearRows();
1184     this.buffer.fetch(-1);
1185   },
1186
1187   bookmarkHandler: function(firstrow,lastrow) {
1188     if (isNaN(firstrow) || !this.bookmark) return;
1189     var totrows=this.buffer.totalRows;
1190     if (totrows < lastrow) lastrow=totrows;
1191     if (totrows<=0) {
1192       var newhtml = RicoTranslate.getPhrase("No matching records");
1193     } else if (lastrow<0) {
1194       var newhtml = RicoTranslate.getPhrase("No records");
1195     } else {
1196       var newhtml = RicoTranslate.getPhrase("Listing records")+" "+firstrow+" - "+lastrow;
1197       var totphrase = this.buffer.foundRowCount ? "of" : "of about";
1198       newhtml+=" "+RicoTranslate.getPhrase(totphrase)+" "+totrows;
1199     }
1200     this.bookmark.innerHTML = newhtml;
1201   },
1202
1203 /**
1204  * @return array of column objects which have invisible status
1205  */
1206   listInvisible: function() {
1207     var hiddenColumns=new Array();
1208     for (var x=0;x<this.columns.length;x++)
1209       if (this.columns[x].visible==false)
1210         hiddenColumns.push(this.columns[x]);
1211     return hiddenColumns;
1212   },
1213
1214 /**
1215  * Show all columns
1216  */
1217   showAll: function() {
1218     var invisible=this.listInvisible();
1219     for (var x=0;x<invisible.length;x++)
1220       invisible[x].showColumn();
1221   },
1222
1223   clearRows: function() {
1224     if (this.isBlank==true) return;
1225     for (var c=0; c < this.columns.length; c++)
1226       this.columns[c].clearColumn();
1227     this.ClearSelection();
1228     this.isBlank = true;
1229   },
1230
1231   blankRow: function(r) {
1232      for (var c=0; c < this.columns.length; c++)
1233         this.columns[c].clearCell(r);
1234   },
1235
1236   refreshContents: function(startPos) {
1237     Rico.writeDebugMsg("refreshContents: startPos="+startPos+" lastRow="+this.lastRowPos+" PartBlank="+this.isPartialBlank+" pageSize="+this.pageSize);
1238     this.hideMsg();
1239     this.cancelMenu();
1240     this.unhighlight(); // in case highlighting was manually invoked
1241     this.highlightEnabled=this.options.highlightSection!='none';
1242     if (startPos == this.lastRowPos && !this.isPartialBlank && !this.isBlank) return;
1243     this.isBlank = false;
1244     var viewPrecedesBuffer = this.buffer.startPos > startPos
1245     var contentStartPos = viewPrecedesBuffer ? this.buffer.startPos: startPos;
1246     var contentEndPos = Math.min(this.buffer.startPos + this.buffer.size, startPos + this.pageSize);
1247     var onRefreshComplete = this.options.onRefreshComplete;
1248
1249     if ((startPos + this.pageSize < this.buffer.startPos)
1250         || (this.buffer.startPos + this.buffer.size < startPos)
1251         || (this.buffer.size == 0)) {
1252       this.clearRows();
1253       if (onRefreshComplete != null)
1254           onRefreshComplete(contentStartPos+1,contentEndPos);
1255       return;
1256     }
1257
1258     Rico.writeDebugMsg('refreshContents: contentStartPos='+contentStartPos+' contentEndPos='+contentEndPos+' viewPrecedesBuffer='+viewPrecedesBuffer);
1259     if (this.options.highlightElem=='selection') this.HideSelection();
1260     var rowSize = contentEndPos - contentStartPos;
1261     this.buffer.setWindow(contentStartPos, rowSize );
1262     var blankSize = this.pageSize - rowSize;
1263     var blankOffset = viewPrecedesBuffer ? 0: rowSize;
1264     var contentOffset = viewPrecedesBuffer ? blankSize: 0;
1265
1266     for (var r=0; r < rowSize; r++) { //initialize what we have
1267       for (var c=0; c < this.columns.length; c++)
1268         this.columns[c].displayValue(r + contentOffset);
1269     }
1270     for (var i=0; i < blankSize; i++)     // blank out the rest
1271       this.blankRow(i + blankOffset);
1272     if (this.options.highlightElem=='selection') this.ShowSelection();
1273     this.isPartialBlank = blankSize > 0;
1274     this.lastRowPos = startPos;
1275     Rico.writeDebugMsg("refreshContents complete, startPos="+startPos);
1276     // Check if user has set a onRefreshComplete function
1277     if (onRefreshComplete != null)
1278       onRefreshComplete(contentStartPos+1,contentEndPos);
1279   },
1280
1281   scrollToRow: function(rowOffset) {
1282      var p=this.rowToPixel(rowOffset);
1283      Rico.writeDebugMsg("scrollToRow, rowOffset="+rowOffset+" pixel="+p);
1284      this.scrollDiv.scrollTop = p; // this causes a scroll event
1285      if ( this.options.onscroll )
1286         this.options.onscroll( this, rowOffset );
1287   },
1288
1289   scrollUp: function() {
1290      this.moveRelative(-1);
1291   },
1292
1293   scrollDown: function() {
1294      this.moveRelative(1);
1295   },
1296
1297   pageUp: function() {
1298      this.moveRelative(-this.pageSize);
1299   },
1300
1301   pageDown: function() {
1302      this.moveRelative(this.pageSize);
1303   },
1304
1305   adjustRow: function(rowOffset) {
1306      var notdisp=this.topOfLastPage();
1307      if (notdisp == 0 || !rowOffset) return 0;
1308      return Math.min(notdisp,rowOffset);
1309   },
1310
1311   rowToPixel: function(rowOffset) {
1312      return this.adjustRow(rowOffset) * this.rowHeight;
1313   },
1314
1315 /**
1316  * @returns row to display at top of scroll div
1317  */
1318   pixeltorow: function(p) {
1319      var notdisp=this.topOfLastPage();
1320      if (notdisp == 0) return 0;
1321      var prow=parseInt(p/this.rowHeight);
1322      return Math.min(notdisp,prow);
1323   },
1324
1325   moveRelative: function(relOffset) {
1326      newoffset=Math.max(this.scrollDiv.scrollTop+relOffset*this.rowHeight,0);
1327      newoffset=Math.min(newoffset,this.scrollDiv.scrollHeight);
1328      //Rico.writeDebugMsg("moveRelative, newoffset="+newoffset);
1329      this.scrollDiv.scrollTop=newoffset;
1330   },
1331
1332   pluginScroll: function() {
1333      if (this.scrollPluggedIn) return;
1334      Rico.writeDebugMsg("pluginScroll: wheelEvent="+this.wheelEvent);
1335      Event.observe(this.scrollDiv,"scroll",this.scrollEventFunc, false);
1336      for (var t=0; t<2; t++)
1337        Event.observe(this.tabs[t],this.wheelEvent,this.wheelEventFunc, false);
1338      this.scrollPluggedIn=true;
1339   },
1340
1341   unplugScroll: function() {
1342      if (!this.scrollPluggedIn) return;
1343      Rico.writeDebugMsg("unplugScroll");
1344      Event.stopObserving(this.scrollDiv,"scroll", this.scrollEventFunc , false);
1345      for (var t=0; t<2; t++)
1346        Event.stopObserving(this.tabs[t],this.wheelEvent,this.wheelEventFunc, false);
1347      this.scrollPluggedIn=false;
1348   },
1349
1350   handleWheel: function(e) {
1351     var delta = 0;
1352     if (e.wheelDelta) {
1353       if (Prototype.Browser.Opera)
1354         delta = e.wheelDelta/120;
1355       else if (Prototype.Browser.WebKit)
1356         delta = -e.wheelDelta/12;
1357       else
1358         delta = -e.wheelDelta/120;
1359     } else if (e.detail) {
1360       delta = e.detail/3; /* Mozilla/Gecko */
1361     }
1362     if (delta) this.moveRelative(delta);
1363     Event.stop(e);
1364     return false;
1365   },
1366
1367   handleScroll: function(e) {
1368      if ( this.scrollTimeout )
1369        clearTimeout( this.scrollTimeout );
1370      this.setHorizontalScroll();
1371      var scrtop=this.scrollDiv.scrollTop;
1372      var vscrollDiff = this.lastScrollPos-scrtop;
1373      if (vscrollDiff == 0.00) return;
1374      var newrow=this.pixeltorow(scrtop);
1375      if (newrow == this.lastRowPos && !this.isPartialBlank && !this.isBlank) return;
1376      var stamp1 = new Date();
1377      //Rico.writeDebugMsg("handleScroll, newrow="+newrow+" scrtop="+scrtop);
1378      this.buffer.fetch(newrow);
1379      if (this.options.onscroll) this.options.onscroll(this, newrow);
1380      this.scrollTimeout = setTimeout(this.scrollIdle.bind(this), 1200 );
1381      this.lastScrollPos = this.scrollDiv.scrollTop;
1382      var stamp2 = new Date();
1383      //Rico.writeDebugMsg("handleScroll, time="+(stamp2.getTime()-stamp1.getTime()));
1384   },
1385
1386   scrollIdle: function() {
1387      if ( this.options.onscrollidle )
1388         this.options.onscrollidle();
1389   },
1390
1391   printAll: function(exportType) {
1392     this.exportStart();
1393     this.buffer.exportAllRows(this.exportBuffer.bind(this),this.exportFinish.bind(this,exportType));
1394   },
1395
1396 /**
1397  * Send all rows to print window
1398  */
1399   exportBuffer: function(rows) {
1400     for(var r=0; r < rows.length; r++) {
1401       this.exportText+="<tr>";
1402       for (var c=0; c<this.columns.length; c++) {
1403         if (this.columns[c].visible)
1404           this.exportText+="<td>"+this.columns[c]._format(rows[r][c].content)+"</td>";
1405       }
1406       this.exportText+="</tr>";
1407     }
1408   }
1409
1410 };
1411
1412
1413 Object.extend(Rico.TableColumn.prototype, {
1414
1415 initialize: function(liveGrid,colIdx,hdrInfo,tabIdx) {
1416   this.baseInit(liveGrid,colIdx,hdrInfo,tabIdx);
1417   Rico.writeDebugMsg(" sortable="+this.sortable+" filterable="+this.filterable+" hideable="+this.hideable+" isNullable="+this.isNullable+' isText='+this.isText);
1418   this.fixHeaders(this.liveGrid.tableId, this.options.hdrIconsFirst);
1419   if (this.format.type=='control' && this.format.control) {
1420     // copy all properties/methods that start with '_'
1421     if (typeof this.format.control=='string')
1422       this.format.control=eval(this.format.control);
1423     for (var property in this.format.control)
1424       if (property.charAt(0)=='_') {
1425         Rico.writeDebugMsg("Copying control property "+property);
1426         this[property] = this.format.control[property];
1427       }
1428   } else if (this['format_'+this.format.type]) {
1429     this._format=this['format_'+this.format.type].bind(this);
1430   }
1431 },
1432
1433 sortAsc: function() {
1434   this.setColumnSort(Rico.TableColumn.SORT_ASC);
1435 },
1436
1437 sortDesc: function() {
1438   this.setColumnSort(Rico.TableColumn.SORT_DESC);
1439 },
1440
1441 setColumnSort: function(direction) {
1442   this.liveGrid.clearSort();
1443   this.setSorted(direction);
1444   if (this.liveGrid.options.saveColumnInfo.sort)
1445     this.liveGrid.setCookie();
1446   if (this.options.sortHandler)
1447     this.options.sortHandler();
1448 },
1449
1450 isSortable: function() {
1451   return this.sortable;
1452 },
1453
1454 isSorted: function() {
1455   return this.currentSort != Rico.TableColumn.UNSORTED;
1456 },
1457
1458 getSortDirection: function() {
1459   return this.currentSort;
1460 },
1461
1462 toggleSort: function() {
1463   if (this.liveGrid.buffer && this.liveGrid.buffer.totalRows==0) return;
1464   if (this.currentSort == Rico.TableColumn.SORT_ASC)
1465     this.sortDesc();
1466   else
1467     this.sortAsc();
1468 },
1469
1470 setUnsorted: function() {
1471   this.setSorted(Rico.TableColumn.UNSORTED);
1472 },
1473
1474 /**
1475  * direction must be one of Rico.TableColumn.UNSORTED, .SORT_ASC, or .SORT_DESC
1476  */
1477 setSorted: function(direction) {
1478   this.currentSort = direction;
1479 },
1480
1481 canFilter: function() {
1482   return this.filterable;
1483 },
1484
1485 getFilterText: function() {
1486   var vals=[];
1487   for (var i=0; i<this.filterValues.length; i++) {
1488     var v=this.filterValues[i];
1489     if (v!=null && v.match(/<span\s+class=(['"]?)ricolookup\1>(.*)<\/span>/i))
1490       vals.push(RegExp.leftContext);
1491     else
1492       vals.push(v);
1493   }
1494   switch (this.filterOp) {
1495     case 'EQ':   return vals[0];
1496     case 'NE':   return 'not: '+vals.join(', ');
1497     case 'LE':   return '<= '+vals[0];
1498     case 'GE':   return '>= '+vals[0];
1499     case 'LIKE': return 'like: '+vals[0];
1500     case 'NULL': return '<empty>';
1501     case 'NOTNULL': return '<not empty>';
1502   }
1503   return '?';
1504 },
1505
1506 getFilterQueryParm: function() {
1507   if (this.filterType == Rico.TableColumn.UNFILTERED) return '';
1508   var retval='&f['+this.index+'][op]='+this.filterOp;
1509   retval+='&f['+this.index+'][len]='+this.filterValues.length
1510   for (var i=0; i<this.filterValues.length; i++)
1511     retval+='&f['+this.index+']['+i+']='+escape(this.filterValues[i]);
1512   return retval;
1513 },
1514
1515 setUnfiltered: function(skipHandler) {
1516   this.filterType = Rico.TableColumn.UNFILTERED;
1517   if (this.liveGrid.options.saveColumnInfo.filter)
1518     this.liveGrid.setCookie();
1519   if (this.removeFilterFunc)
1520     this.removeFilterFunc();
1521   if (this.options.filterHandler && !skipHandler)
1522     this.options.filterHandler();
1523 },
1524
1525 setFilterEQ: function() {
1526   if (this.userFilter=='' && this.isNullable)
1527     this.setUserFilter('NULL');
1528   else
1529     this.setUserFilter('EQ');
1530 },
1531 setFilterNE: function() {
1532   if (this.userFilter=='' && this.isNullable)
1533     this.setUserFilter('NOTNULL');
1534   else
1535     this.setUserFilter('NE');
1536 },
1537 addFilterNE: function() {
1538   this.filterValues.push(this.userFilter);
1539   if (this.liveGrid.options.saveColumnInfo.filter)
1540     this.liveGrid.setCookie();
1541   if (this.options.filterHandler)
1542     this.options.filterHandler();
1543 },
1544 setFilterGE: function() { this.setUserFilter('GE'); },
1545 setFilterLE: function() { this.setUserFilter('LE'); },
1546 setFilterKW: function() {
1547   var keyword=prompt(RicoTranslate.getPhrase("Enter keyword to search for")+RicoTranslate.getPhrase(" (use * as a wildcard):"),'');
1548   if (keyword!='' && keyword!=null) {
1549     if (keyword.indexOf('*')==-1) keyword='*'+keyword+'*';
1550     this.setFilter('LIKE',keyword,Rico.TableColumn.USERFILTER);
1551   } else {
1552     this.liveGrid.cancelMenu();
1553   }
1554 },
1555
1556 setUserFilter: function(relop) {
1557   this.setFilter(relop,this.userFilter,Rico.TableColumn.USERFILTER);
1558 },
1559
1560 setSystemFilter: function(relop,filter) {
1561   this.setFilter(relop,filter,Rico.TableColumn.SYSTEMFILTER);
1562 },
1563
1564 setFilter: function(relop,filter,type,removeFilterFunc) {
1565   this.filterValues = [filter];
1566   this.filterType = type;
1567   this.filterOp = relop;
1568   if (type == Rico.TableColumn.USERFILTER && this.liveGrid.options.saveColumnInfo.filter)
1569     this.liveGrid.setCookie();
1570   this.removeFilterFunc=removeFilterFunc;
1571   if (this.options.filterHandler)
1572     this.options.filterHandler();
1573 },
1574
1575 isFiltered: function() {
1576   return this.filterType == Rico.TableColumn.USERFILTER;
1577 },
1578
1579 format_text: function(v) {
1580   if (typeof v!='string')
1581     return '&nbsp;';
1582   else
1583     return v.stripTags();
1584 },
1585
1586 format_showTags: function(v) {
1587   if (typeof v!='string')
1588     return '&nbsp;';
1589   else
1590     return v.replace(/&/g, '&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
1591 },
1592
1593 format_number: function(v) {
1594   if (typeof v=='undefined' || v=='' || v==null)
1595     return '&nbsp;';
1596   else
1597     return v.formatNumber(this.format);
1598 },
1599
1600 format_datetime: function(v) {
1601   if (typeof v=='undefined' || v=='' || v==null)
1602     return '&nbsp;';
1603   else {
1604     var d=new Date;
1605     d.setISO8601(v);
1606     return d.formatDate(this.format.dateFmt || 'translateDateTime');
1607   }
1608 },
1609
1610 format_date: function(v) {
1611   if (typeof v=='undefined' || v==null || v=='')
1612     return '&nbsp;';
1613   else {
1614     var d=new Date;
1615     if (!d.setISO8601(v)) return v;
1616     return d.formatDate(this.format.dateFmt || 'translateDate');
1617   }
1618 },
1619
1620 fixHeaders: function(prefix, iconsfirst) {
1621   if (this.sortable) {
1622     switch (this.options.headingSort) {
1623       case 'link':
1624         var a=RicoUtil.wrapChildren(this.hdrCellDiv,'ricoSort',undefined,'a')
1625         a.href = "#";
1626         a.onclick = this.toggleSort.bindAsEventListener(this);
1627         break;
1628       case 'hover':
1629         this.hdrCellDiv.onclick = this.toggleSort.bindAsEventListener(this);
1630         break;
1631     }
1632   }
1633   this.imgFilter = document.createElement('img');
1634   this.imgFilter.style.display='none';
1635   this.imgFilter.src=Rico.imgDir+this.options.filterImg;
1636   this.imgFilter.className='ricoLG_HdrIcon';
1637   this.imgSort = document.createElement('img');
1638   this.imgSort.style.display='none';
1639   this.imgSort.className='ricoLG_HdrIcon';
1640   if (iconsfirst) {
1641     this.hdrCellDiv.insertBefore(this.imgSort,this.hdrCellDiv.firstChild);
1642     this.hdrCellDiv.insertBefore(this.imgFilter,this.hdrCellDiv.firstChild);
1643   } else {
1644     this.hdrCellDiv.appendChild(this.imgFilter);
1645     this.hdrCellDiv.appendChild(this.imgSort);
1646   }
1647 },
1648
1649 getValue: function(windowRow) {
1650   return this.liveGrid.buffer.getWindowValue(windowRow,this.index);
1651 },
1652
1653 getFormattedValue: function(windowRow) {
1654   return this._format(this.getValue(windowRow));
1655 },
1656
1657 getBufferCell: function(windowRow) {
1658   return this.liveGrid.buffer.getWindowCell(windowRow,this.index);
1659 },
1660
1661 setValue: function(windowRow,newval) {
1662   this.liveGrid.buffer.setWindowValue(windowRow,this.index,newval);
1663 },
1664
1665 _format: function(v) {
1666   return v;
1667 },
1668
1669 _display: function(v,gridCell) {
1670   gridCell.innerHTML=this._format(v);
1671 },
1672
1673 displayValue: function(windowRow) {
1674   var bufCell=this.getBufferCell(windowRow);
1675   if (!bufCell) {
1676     this.clearCell(windowRow);
1677     return;
1678   }
1679   var gridCell=this.cell(windowRow);
1680   this._display(bufCell.content,gridCell,windowRow);
1681   var acceptAttr=this.liveGrid.buffer.options.acceptAttr;
1682   for (var k=0; k<acceptAttr.length; k++) {
1683     var bufAttr=bufCell['_'+acceptAttr[k]] || '';
1684     switch (acceptAttr[k]) {
1685       case 'style': gridCell.style.cssText=bufAttr; break;
1686       case 'class': gridCell.className=bufAttr; break;
1687       default:      gridCell['_'+acceptAttr[k]]=bufAttr; break;
1688     }
1689   }
1690 }
1691
1692 });
1693
1694 Rico.TableColumn.checkbox = Class.create();
1695
1696 Rico.TableColumn.checkbox.prototype = {
1697
1698   initialize: function(checkedValue, uncheckedValue, defaultValue, readOnly) {
1699     this._checkedValue=checkedValue;
1700     this._uncheckedValue=uncheckedValue;
1701     this._defaultValue=defaultValue || false;
1702     this._readOnly=readOnly || false;
1703     this._checkboxes=[];
1704   },
1705
1706   _create: function(gridCell,windowRow) {
1707     this._checkboxes[windowRow]=RicoUtil.createFormField(gridCell,'input','checkbox',this.liveGrid.tableId+'_chkbox_'+this.index+'_'+windowRow);
1708     this._clear(gridCell,windowRow);
1709     if (this._readOnly)
1710       this._checkboxes[windowRow].disabled=true;
1711     else
1712       Event.observe(this._checkboxes[windowRow], "click", this._onclick.bindAsEventListener(this), false);
1713   },
1714
1715   _onclick: function(e) {
1716     var elem=Event.element(e);
1717     var windowRow=parseInt(elem.id.split(/_/).pop());
1718     var newval=elem.checked ? this._checkedValue : this._uncheckedValue;
1719     this.setValue(windowRow,newval);
1720   },
1721
1722   _clear: function(gridCell,windowRow) {
1723     this._checkboxes[windowRow].checked=this._defaultValue;
1724   },
1725
1726   _display: function(v,gridCell,windowRow) {
1727     this._checkboxes[windowRow].checked=(v==this._checkedValue);
1728   }
1729
1730 }
1731
1732 Rico.TableColumn.link = Class.create();
1733
1734 Rico.TableColumn.link.prototype = {
1735
1736   initialize: function(href,target) {
1737     this._href=href;
1738     this._target=target;
1739     this._anchors=[];
1740   },
1741
1742   _create: function(gridCell,windowRow) {
1743     this._anchors[windowRow]=RicoUtil.createFormField(gridCell,'a',null,this.liveGrid.tableId+'_a_'+this.index+'_'+windowRow);
1744     if (this._target) this._anchors[windowRow].target=this._target;
1745     this._clear(gridCell,windowRow);
1746   },
1747
1748   _clear: function(gridCell,windowRow) {
1749     this._anchors[windowRow].href='';
1750     this._anchors[windowRow].innerHTML='';
1751   },
1752
1753   _display: function(v,gridCell,windowRow) {
1754     this._anchors[windowRow].innerHTML=v;
1755     var getWindowValue=this.liveGrid.buffer.getWindowValue.bind(this.liveGrid.buffer);
1756     this._anchors[windowRow].href=this._href.replace(/\{\d+\}/g,
1757       function ($1) {
1758         var colIdx=parseInt($1.substr(1));
1759         return getWindowValue(windowRow,colIdx);
1760       }
1761     );
1762   }
1763
1764 }
1765
1766 Rico.TableColumn.lookup = Class.create();
1767
1768 Rico.TableColumn.lookup.prototype = {
1769
1770   initialize: function(map, defaultCode, defaultDesc) {
1771     this._map=map;
1772     this._defaultCode=defaultCode || '';
1773     this._defaultDesc=defaultDesc || '&nbsp;';
1774     this._sortfunc=this._sortvalue.bind(this);
1775     this._codes=[];
1776     this._descriptions=[];
1777   },
1778
1779   _create: function(gridCell,windowRow) {
1780     this._descriptions[windowRow]=RicoUtil.createFormField(gridCell,'span',null,this.liveGrid.tableId+'_desc_'+this.index+'_'+windowRow);
1781     this._codes[windowRow]=RicoUtil.createFormField(gridCell,'input','hidden',this.liveGrid.tableId+'_code_'+this.index+'_'+windowRow);
1782     this._clear(gridCell,windowRow);
1783   },
1784
1785   _clear: function(gridCell,windowRow) {
1786     this._codes[windowRow].value=this._defaultCode;
1787     this._descriptions[windowRow].innerHTML=this._defaultDesc;
1788   },
1789
1790   _sortvalue: function(v) {
1791     return this._getdesc(v).replace(/&amp;/g, '&').replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&nbsp;/g,' ');
1792   },
1793
1794   _getdesc: function(v) {
1795     var desc=this._map[v];
1796     return (typeof desc=='string') ? desc : this._defaultDesc;
1797   },
1798
1799   _display: function(v,gridCell,windowRow) {
1800     this._codes[windowRow].value=v;
1801     this._descriptions[windowRow].innerHTML=this._getdesc(v);
1802   }
1803
1804 }
1805
1806 Rico.includeLoaded('ricoLiveGrid.js');