2ec1975885d56ba5a179200fc2d5826ef1aae683
[infodrom/rico3] / minsrc / ricoGridCommon.js
1 /*
2  *  (c) 2005-2009 Richard Cowin (http://openrico.org)
3  *  (c) 2005-2009 Matt Brown (http://dowdybrown.com)
4  *
5  *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
6  *  file except in compliance with the License. You may obtain a copy of the License at
7  *
8  *         http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *  Unless required by applicable law or agreed to in writing, software distributed under the
11  *  License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
12  *  either express or implied. See the License for the specific language governing permissions
13  *  and limitations under the License.
14  */
15
16 if(typeof Rico=='undefined') throw("GridCommon requires the Rico JavaScript framework");
17
18 /**
19  * Define methods that are common to both SimpleGrid and LiveGrid
20  */
21 Rico.GridCommon = {
22
23   baseInit: function() {
24     this.options = {
25       saveColumnInfo   : {width:true, filter:false, sort:false},  // save info in cookies?
26       cookiePrefix     : 'RicoGrid.',
27       allowColResize   : true,      // allow user to resize columns
28       windowResize     : true,      // Resize grid on window.resize event? Set to false when embedded in an accordian.
29       click            : null,
30       dblclick         : null,
31       contextmenu      : null,
32       menuEvent        : 'dblclick',  // event that triggers menus - click, dblclick, contextmenu, or none (no menus)
33       defaultWidth     : -1,          // if -1, then use unformatted column width
34       scrollBarWidth   : 19,          // this is the value used in positioning calculations, it does not actually change the width of the scrollbar
35       minScrollWidth   : 100,         // min scroll area width when width of frozen columns exceeds window width
36       frozenColumns    : 0,
37       exportWindow     : "height=400,width=500,scrollbars=1,menubar=1,resizable=1,location=0,toolbar=0,status=0",
38       exportStyleList  : ['background-color','color','text-align','font-weight','font-size','font-family'],
39       exportImgTags    : false,       // applies to grid header and to SimpleGrid cells (not LiveGrid cells)
40       exportFormFields : true,
41       FilterLocation   : null,        // heading row number to place filters. -1=add a new heading row.
42       FilterAllToken   : '___ALL___', // select box value to use to indicate ALL
43       columnSpecs      : []
44     };
45     this.colWidths = [];
46     this.hdrCells=[];
47     this.headerColCnt=0;
48     this.headerRowIdx=0;       // row in header which gets resizers (no colspan's in this row)
49     this.tabs=new Array(2);
50     this.thead=new Array(2);
51     this.tbody=new Array(2);
52   },
53
54   attachMenuEvents: function() {
55     var i;
56     if (!this.options.menuEvent || this.options.menuEvent=='none') return;
57     this.hideScroll=navigator.userAgent.match(/Macintosh\b.*\b(Firefox|Camino)\b/i) || (Rico.isOpera && parseFloat(window.opera.version())<9.5);
58     this.options[this.options.menuEvent]=Rico.eventHandle(this,'handleMenuClick');
59     if (this.highlightDiv) {
60       switch (this.options.highlightElem) {
61         case 'cursorRow':
62           this.attachMenu(this.highlightDiv[0]);
63           break;
64         case 'cursorCell':
65           for (i=0; i<2; i++) {
66             this.attachMenu(this.highlightDiv[i]);
67           }
68           break;
69       }
70     }
71     for (i=0; i<2; i++) {
72       this.attachMenu(this.tbody[i]);
73     }
74   },
75
76   attachMenu: function(elem) {
77     if (this.options.click)
78       Rico.eventBind(elem, 'click', this.options.click, false);
79     if (this.options.dblclick) {
80       if (Rico.isWebKit || Rico.isOpera)
81         Rico.eventBind(elem, 'click', Rico.eventHandle(this,'handleDblClick'), false);
82       else
83         Rico.eventBind(elem, 'dblclick', this.options.dblclick, false);
84     }
85     if (this.options.contextmenu) {
86       if (Rico.isOpera || Rico.isKonqueror)
87         Rico.eventBind(elem, 'click', Rico.eventHandle(this,'handleContextMenu'), false);
88       else
89         Rico.eventBind(elem, 'contextmenu', this.options.contextmenu, false);
90     }
91   },
92
93 /**
94  * implement double-click for browsers that don't support a double-click event (e.g. Safari)
95  */
96   handleDblClick: function(e) {
97     var elem=Rico.eventElement(e);
98     if (this.dblClickElem == elem) {
99       this.options.dblclick(e);
100     } else {
101       this.dblClickElem = elem;
102       this.safariTimer=Rico.runLater(300,this,'clearDblClick');
103     }
104   },
105
106   clearDblClick: function() {
107     this.dblClickElem=null;
108   },
109
110 /**
111  * implement right-click for browsers that don't support contextmenu event (e.g. Opera, Konqueror)
112  * use control-click instead
113  */
114   handleContextMenu: function(e) {
115     var b;
116     if( typeof( e.which ) == 'number' )
117       b = e.which; //Netscape compatible
118     else if( typeof( e.button ) == 'number' )
119       b = e.button; //DOM
120     else
121       return;
122     if (b==1 && e.ctrlKey) {
123       this.options.contextmenu(e);
124     }
125   },
126
127   cancelMenu: function() {
128     if (this.menu) this.menu.cancelmenu();
129   },
130
131 /**
132  * gather info from original headings
133  */
134   getColumnInfo: function(hdrSrc) {
135     Rico.log('getColumnInfo: len='+hdrSrc.length);
136     if (hdrSrc.length == 0) return 0;
137     this.headerRowCnt=hdrSrc.length;
138     var r,c,colcnt;
139     for (r=0; r<this.headerRowCnt; r++) {
140       var headerRow = hdrSrc[r];
141       var headerCells=headerRow.cells;
142       if (r >= this.hdrCells.length) this.hdrCells[r]=[];
143       for (c=0; c<headerCells.length; c++) {
144         var obj={};
145         obj.cell=headerCells[c];
146         obj.colSpan=headerCells[c].colSpan || 1;  // Safari & Konqueror return default colspan of 0
147         if (this.options.defaultWidth < 0) obj.initWidth=headerCells[c].offsetWidth;
148         this.hdrCells[r].push(obj);
149       }
150       if (headerRow.id.slice(-5)=='_main') {
151         colcnt=this.hdrCells[r].length;
152         this.headerRowIdx=r;
153       }
154     }
155     if (!colcnt) {
156       this.headerRowIdx=this.headerRowCnt-1;
157       colcnt=this.hdrCells[this.headerRowIdx].length;
158     }
159     Rico.log("getColumnInfo: colcnt="+colcnt);
160     return colcnt;
161   },
162
163   addHeadingRow: function(className) {
164     var r=this.headerRowCnt++;
165     this.hdrCells[r]=[];
166     for( var h=0; h < 2; h++ ) {
167       var row = this.thead[h].insertRow(-1);
168       var newClass='ricoLG_hdg '+this.tableId+'_hdg'+r;
169       if (className) newClass+=' '+className;
170       row.className=newClass;
171       var limit= h==0 ? this.options.frozenColumns : this.headerColCnt-this.options.frozenColumns;
172       for( var c=0; c < limit; c++ ) {
173         var hdrCell=row.insertCell(-1);
174         var colDiv=Rico.wrapChildren(hdrCell,'ricoLG_col');
175         Rico.wrapChildren(colDiv,'ricoLG_cell');
176         this.hdrCells[r].push({cell:hdrCell,colSpan:1});
177       }
178     }
179     return r;
180   },
181   
182 /**
183  * create column array
184  */
185   createColumnArray: function(columnType) {
186     this.direction=Rico.getStyle(this.outerDiv,'direction').toLowerCase();  // ltr or rtl
187     this.align=this.direction=='rtl' ? ['right','left'] : ['left','right'];
188     Rico.log('createColumnArray: dir='+this.direction);
189     this.columns = [];
190     for (var c=0; c < this.headerColCnt; c++) {
191       Rico.log("createColumnArray: c="+c);
192       var tabidx=c<this.options.frozenColumns ? 0 : 1;
193       var col=new Rico[columnType](this, c, this.hdrCells[this.headerRowIdx][c], tabidx);
194       this.columns.push(col);
195       if (c > 0) this.columns[c-1].next=col;
196     }
197     this.getCookie();
198     Rico.runLater(100,this,'insertResizers');  // avoids peek-a-boo bug in column 1 in IE6/7
199   },
200
201 /**
202  * Insert resizing handles
203  */
204   insertResizers: function() {
205     if (!this.options.allowColResize) return;
206     for (var x=0;x<this.columns.length;x++) {
207       this.columns[x].insertResizer();
208     }
209   },
210
211 /**
212  * Create div structure
213  */
214   createDivs: function() {
215     Rico.log("createDivs start");
216     this.outerDiv   = this.createDiv("outer");
217     if (Rico.theme.gridContainer) Rico.addClass(this.outerDiv,Rico.theme.gridContainer);
218     if (this.outerDiv.firstChild && this.outerDiv.firstChild.tagName && this.outerDiv.firstChild.tagName.toUpperCase()=='TABLE') {
219       this.structTab=this.outerDiv.firstChild;
220       this.structTabLeft=this.structTab.rows[0].cells[0];
221       this.structTabUR=this.structTab.rows[0].cells[1];
222       this.structTabLR=this.structTab.rows[1].cells[0];
223     } else {
224       this.structTab = document.createElement("table");
225       this.structTab.border=0;
226       this.structTab.cellPadding=0;
227       this.structTab.cellSpacing=0;
228       var tr1=this.structTab.insertRow(-1);
229       tr1.vAlign='top';
230       this.structTabLeft=tr1.insertCell(-1);
231       this.structTabLeft.rowSpan=2;
232       this.structTabLeft.style.padding='0px';
233       this.structTabLeft.style.border='none';
234       var tr2=this.structTab.insertRow(-1);
235       tr2.vAlign='top';
236       this.structTabUR=tr1.insertCell(-1);
237       this.structTabUR.style.padding='0px';
238       this.structTabUR.style.border='none';
239       this.structTabLR=tr2.insertCell(-1);
240       this.structTabLR.style.padding='0px';
241       this.structTabLR.style.border='none';
242       this.outerDiv.appendChild(this.structTab);
243     }
244     Rico.addClass(this.structTab,'ricoLG_StructTab');
245     //this.structTabLR.style.overflow='hidden';
246     //if (Rico.isOpera) this.outerDiv.style.overflow="hidden";
247     this.frozenTabs = this.createDiv("frozenTabs",this.structTabLeft);
248     this.innerDiv   = this.createDiv("inner",this.structTabUR);
249     this.scrollDiv  = this.createDiv("scroll",this.structTabLR);
250     this.resizeDiv  = this.createDiv("resize",this.outerDiv,true);
251
252     this.messagePopup=new Rico.Popup();
253     this.messagePopup.createContainer({hideOnEscape:false, hideOnClick:false, parent:this.outerDiv});
254     this.messagePopup.content.className='ricoLG_messageDiv';
255     if (Rico.theme.gridMessage) Rico.addClass(this.messagePopup.content,Rico.theme.gridMessage);
256
257     this.keywordPopup=new Rico.Window('', {zIndex:-1, parent:this.outerDiv});
258     Rico.addClass(this.keywordPopup.container, 'ricoLG_keywordDiv');
259     var instructions=this.keywordPopup.contentDiv.appendChild(document.createElement("p"));
260     instructions.innerHTML=Rico.getPhraseById("keywordPrompt");
261     this.keywordBox=this.keywordPopup.contentDiv.appendChild(document.createElement("input"));
262     this.keywordBox.size=20;
263     Rico.eventBind(this.keywordBox,"keypress", Rico.eventHandle(this,'keywordKey'), false);
264     this.keywordPopup.contentDiv.appendChild(Rico.floatButton('Checkmark', Rico.eventHandle(this,'processKeyword')));
265     var s=this.keywordPopup.contentDiv.appendChild(document.createElement("p"));
266     Rico.setStyle(s,{clear:'both'});
267
268     //this.frozenTabs.style[this.align[0]]='0px';
269     //this.innerDiv.style[this.align[0]]='0px';
270     Rico.log("createDivs end");
271   },
272   
273   keywordKey: function(e) {
274     switch (Rico.eventKey(e)) {
275       case 27: this.closeKeyword(); Rico.eventStop(e); return false;
276       case 13: this.processKeyword(); Rico.eventStop(e); return false;
277     }
278     return true;
279   },
280   
281   openKeyword: function(colnum) {
282     this.keywordCol=colnum;
283     this.keywordBox.value='';
284     this.keywordPopup.setTitle(this.columns[colnum].displayName);
285     this.keywordPopup.centerPopup();
286     this.keywordBox.focus();
287   },
288   
289   closeKeyword: function() {
290     this.keywordPopup.closePopup();
291     this.cancelMenu();
292   },
293   
294   processKeyword: function() {
295     var keyword=this.keywordBox.value;
296     this.closeKeyword();
297     this.columns[this.keywordCol].setFilterKW(keyword);
298   },
299
300 /**
301  * Create a div and give it a standardized id and class name.
302  * If the div already exists, then just assign the class name.
303  */
304   createDiv: function(elemName,elemParent,hidden) {
305     var id=this.tableId+"_"+elemName+"Div";
306     var newdiv=document.getElementById(id);
307     if (!newdiv) {
308       newdiv = document.createElement("div");
309       newdiv.id = id;
310       if (elemParent) elemParent.appendChild(newdiv);
311     }
312     newdiv.className = "ricoLG_"+elemName+"Div";
313     if (hidden) Rico.hide(newdiv);
314     return newdiv;
315   },
316
317 /**
318  * Common code used to size & position divs in both SimpleGrid & LiveGrid
319  */
320   baseSizeDivs: function() {
321     this.setOtherHdrCellWidths();
322
323     if (this.options.frozenColumns) {
324       Rico.show(this.tabs[0]);
325       Rico.show(this.frozenTabs);
326       // order of next 3 lines is critical in IE6
327       this.hdrHt=Math.max(Rico.nan2zero(this.thead[0].offsetHeight),this.thead[1].offsetHeight);
328       this.dataHt=Math.max(Rico.nan2zero(this.tbody[0].offsetHeight),this.tbody[1].offsetHeight);
329       this.frzWi=this.borderWidth(this.tabs[0]);
330     } else {
331       Rico.hide(this.tabs[0]);
332       Rico.hide(this.frozenTabs);
333       this.frzWi=0;
334       this.hdrHt=this.thead[1].offsetHeight;
335       this.dataHt=this.tbody[1].offsetHeight;
336     }
337
338     var wiLimit,i;
339     var borderWi=this.borderWidth(this.columns[0].dataCell);
340     Rico.log('baseSizeDivs '+this.tableId+': hdrHt='+this.hdrHt+' dataHt='+this.dataHt);
341     Rico.log(this.tableId+' frzWi='+this.frzWi+' borderWi='+borderWi);
342     for (i=0; i<this.options.frozenColumns; i++) {
343       if (this.columns[i].visible) this.frzWi+=parseInt(this.columns[i].colWidth,10)+borderWi;
344     }
345     this.scrTabWi=this.borderWidth(this.tabs[1]);
346     this.scrTabWi0=this.scrTabWi;
347     Rico.log('scrTabWi: '+this.scrTabWi);
348     for (i=this.options.frozenColumns; i<this.columns.length; i++) {
349       if (this.columns[i].visible) this.scrTabWi+=parseInt(this.columns[i].colWidth,10)+borderWi;
350     }
351     this.scrWi=this.scrTabWi+this.options.scrollBarWidth;
352     if (this.sizeTo=='parent') {
353       if (Rico.isIE) Rico.hide(this.outerDiv);
354       wiLimit=this.outerDiv.parentNode.offsetWidth;
355       if (Rico.isIE) Rico.show(this.outerDiv);
356     }  else {
357       wiLimit=Rico.windowWidth()-this.options.scrollBarWidth-8;
358     }
359     if (this.outerDiv.parentNode.clientWidth > 0)
360       wiLimit=Math.min(this.outerDiv.parentNode.clientWidth, wiLimit);
361     var overage=this.frzWi+this.scrWi-wiLimit;
362     Rico.log('baseSizeDivs '+this.tableId+': scrWi='+this.scrWi+' wiLimit='+wiLimit+' overage='+overage+' clientWidth='+this.outerDiv.parentNode.clientWidth);
363     if (overage > 0 && this.options.frozenColumns < this.columns.length)
364       this.scrWi=Math.max(this.scrWi-overage, this.options.minScrollWidth);
365     this.scrollDiv.style.width=this.scrWi+'px';
366     //this.scrollDiv.style.top=this.hdrHt+'px';
367     //this.frozenTabs.style.width=this.scrollDiv.style[this.align[0]]=this.innerDiv.style[this.align[0]]=this.frzWi+'px';
368     this.frozenTabs.style.width=this.frzWi+'px';
369     this.outerDiv.style.width=(this.frzWi+this.scrWi)+'px';
370   },
371
372 /**
373  * Returns the sum of the left & right border widths of an element
374  */
375   borderWidth: function(elem) {
376     var l=Rico.nan2zero(Rico.getStyle(elem,'borderLeftWidth'));
377     var r=Rico.nan2zero(Rico.getStyle(elem,'borderRightWidth'));
378     Rico.log((elem.id || elem.tagName)+' borderWidth: L='+l+', R='+r);
379     return l + r;
380 //    return Rico.nan2zero(Rico.getStyle(elem,'borderLeftWidth')) + Rico.nan2zero(Rico.getStyle(elem,'borderRightWidth'));
381   },
382
383   setOtherHdrCellWidths: function() {
384     var c,i,j,r,w,hdrcell,cell,origSpan,newSpan,divs;
385     for (r=0; r<this.hdrCells.length; r++) {
386       if (r==this.headerRowIdx) continue;
387       Rico.log('setOtherHdrCellWidths: r='+r);
388       c=i=0;
389       while (i<this.headerColCnt && c<this.hdrCells[r].length) {
390         hdrcell=this.hdrCells[r][c];
391         cell=hdrcell.cell;
392         origSpan=newSpan=hdrcell.colSpan;
393         for (w=j=0; j<origSpan; j++, i++) {
394           if (this.columns[i].hdrCell.style.display=='none')
395             newSpan--;
396           else if (this.columns[i].hdrColDiv.style.display!='none')
397             w+=parseInt(this.columns[i].colWidth,10);
398         }
399         if (!hdrcell.hdrColDiv || !hdrcell.hdrCellDiv) {
400           divs=cell.getElementsByTagName('div');
401           hdrcell.hdrColDiv=(divs.length<1) ? Rico.wrapChildren(cell,'ricoLG_col') : divs[0];
402           hdrcell.hdrCellDiv=(divs.length<2) ? Rico.wrapChildren(hdrcell.hdrColDiv,'ricoLG_cell') : divs[1];
403         }
404         if (newSpan==0) {
405           cell.style.display='none';
406         } else if (w==0) {
407           hdrcell.hdrColDiv.style.display='none';
408           cell.colSpan=newSpan;
409         } else {
410           cell.style.display='';
411           hdrcell.hdrColDiv.style.display='';
412           cell.colSpan=newSpan;
413           hdrcell.hdrColDiv.style.width=w+'px';
414         }
415         c++;
416       }
417     }
418   },
419
420   initFilterImage: function(filterRowNum){
421     this.filterAnchor=document.getElementById(this.tableId+'_filterLink');
422     if (!this.filterAnchor) return;
423     this.filterRows=Rico.select('tr.'+this.tableId+'_hdg'+filterRowNum);
424     if (this.filterRows.length!=2) return;
425     for (var i=0, r=[]; i<2; i++) r[i]=Rico.select('.ricoLG_cell',this.filterRows[i]);
426     this.filterElements=r[0].concat(r[1]);
427     this.saveHeight = this.filterElements[0].offsetHeight;
428     var pt=Rico.getStyle(this.filterElements[0],'paddingTop');
429     var pb=Rico.getStyle(this.filterElements[0],'paddingBottom');
430     if (pt) this.saveHeight-=parseInt(pt,10);
431     if (pb) this.saveHeight-=parseInt(pb,10);
432     this.rowNum = filterRowNum;
433     this.setFilterImage(false);
434     //Rico.eventBind(this.filterAnchor, 'click', Rico.eventHandle(this,'toggleFilterRow'), false);
435   },
436
437   toggleFilterRow: function() {
438     if ( Rico.visible(this.filterRows[0]) )
439       this.slideFilterUp();
440     else
441       this.slideFilterDown();
442   },
443
444   setFilterImage: function(expandFlag) {
445     var altText=Rico.getPhraseById((expandFlag ? 'show' : 'hide')+'FilterRow');
446     this.filterAnchor.innerHTML = '<img src="'+Rico.imgDir+'tableFilter'+(expandFlag ? 'Expand' : 'Collapse')+'.gif" alt="'+altText+'" border="0">';
447   },
448
449 /**
450  * Returns a div for the cell at the specified row and column index.
451  * In SimpleGrid, r can refer to any row in the grid.
452  * In LiveGrid, r refers to a visible row (row 0 is the first visible row).
453  */
454   cell: function(r,c) {
455     return (0<=c && c<this.columns.length && r>=0) ? this.columns[c].cell(r) : null;
456   },
457
458 /**
459  * Returns the screen height available for a grid
460  */
461   availHt: function() {
462     var divPos=Rico.cumulativeOffset(this.outerDiv);
463     return Rico.windowHeight()-divPos.top-2*this.options.scrollBarWidth-15;  // allow for scrollbar and some margin
464   },
465
466   setHorizontalScroll: function() {
467     var newLeft=(-this.scrollDiv.scrollLeft)+'px';
468     this.hdrTabs[1].style.marginLeft=newLeft;
469   },
470
471   pluginScroll: function() {
472      if (this.scrollPluggedIn) return;
473      Rico.eventBind(this.scrollDiv,"scroll",this.scrollEventFunc, false);
474      this.scrollPluggedIn=true;
475   },
476
477   unplugScroll: function() {
478      Rico.eventUnbind(this.scrollDiv,"scroll", this.scrollEventFunc , false);
479      this.scrollPluggedIn=false;
480   },
481
482   hideMsg: function() {
483     this.messagePopup.closePopup();
484   },
485
486   showMsg: function(msg) {
487     this.messagePopup.setContent(msg);
488     this.messagePopup.centerPopup();
489     Rico.log("showMsg: "+msg);
490   },
491
492 /**
493  * @return array of column objects which have invisible status
494  */
495   listInvisible: function(attr) {
496     var hiddenColumns=[];
497     for (var x=0;x<this.columns.length;x++) {
498       if (!this.columns[x].visible)
499         hiddenColumns.push(attr ? this.columns[x][attr] : this.columns[x]);
500     }
501     return hiddenColumns;
502   },
503
504 /**
505  * @return index of left-most visibile column, or -1 if there are no visible columns
506  */
507   firstVisible: function() {
508     for (var x=0;x<this.columns.length;x++) {
509       if (this.columns[x].visible) return x;
510     }
511     return -1;
512   },
513
514 /**
515  * Show all columns
516  */
517   showAll: function() {
518     var invisible=this.listInvisible();
519     for (var x=0;x<invisible.length;x++)
520       invisible[x].showColumn();
521   },
522   
523   chooseColumns: function() {
524     this.menu.cancelmenu();
525     var x,z,col,itemDiv,span,contentDiv;\r
526     if (!this.columnChooser) {
527       Rico.log('creating columnChooser');
528       z=Rico.getStyle(this.outerDiv.offsetParent,'zIndex');
529       if (typeof z!='number') z=0;
530       this.columnChooser=new Rico.Window(Rico.getPhraseById('gridChooseCols'), {zIndex:z+2, parent:this.outerDiv});
531       Rico.addClass(this.columnChooser.container, 'ricoLG_chooserDiv');
532       contentDiv=this.columnChooser.contentDiv;
533       for (x=0;x<this.columns.length;x++) {
534         col=this.columns[x];
535         itemDiv=contentDiv.appendChild(document.createElement('div'));
536         col.ChooserBox=Rico.createFormField(itemDiv,'input','checkbox');
537         span=itemDiv.appendChild(document.createElement('span'));
538         span.innerHTML=col.displayName;
539         Rico.eventBind(col.ChooserBox, 'click', Rico.eventHandle(col,'chooseColumn'), false);
540       }
541     }
542     Rico.log('opening columnChooser');
543     this.columnChooser.openPopup(1,this.hdrHt);
544     for (x=0;x<this.columns.length;x++) {
545       this.columns[x].ChooserBox.checked=this.columns[x].visible;
546       this.columns[x].ChooserBox.disabled = !this.columns[x].canHideShow();
547     }
548   },
549
550   blankRow: function(r) {
551     for (var c=0; c < this.columns.length; c++) {
552       this.columns[c].clearCell(r);
553     }
554   },
555   
556   getExportStyles: function(chkelem) {
557     var exportStyles=this.options.exportStyleList;
558     var bgImg=Rico.getStyle(chkelem,'backgroundImage');
559     if (!bgImg || bgImg=='none') return exportStyles;
560     for (var styles=[],i=0; i<exportStyles.length; i++)
561       if (exportStyles[i]!='background-color' && exportStyles[i]!='color') styles.push(exportStyles[i]);
562     return styles;
563   },
564
565 /**
566  * Support function for printVisible()
567  */
568   exportStart: function() {
569     var r,c,i,j,hdrcell,newSpan,divs,cell;
570     var exportStyles=this.getExportStyles(this.thead[0]);
571     //alert(exportStyles.join('\n'));
572     this.exportRows=[];
573     this.exportText="<html><head></head><body><table border='1' cellspacing='0'>";\r
574     for (c=0; c<this.columns.length; c++) {\r
575       if (this.columns[c].visible) this.exportText+="<col width='"+parseInt(this.columns[c].colWidth,10)+"'>";
576     }\r
577     this.exportText+="<thead style='display: table-header-group;'>";
578     if (this.exportHeader) this.exportText+=this.exportHeader;
579     for (r=0; r<this.hdrCells.length; r++) {
580       if (this.hdrCells[r].length==0 || !Rico.visible(this.hdrCells[r][0].cell.parentNode)) continue;
581       this.exportText+="<tr>";
582       for (c=0,i=0; c<this.hdrCells[r].length; c++) {
583         hdrcell=this.hdrCells[r][c];
584         newSpan=hdrcell.colSpan;
585         for (j=0; j<hdrcell.colSpan; j++, i++) {
586           if (!this.columns[i].visible) newSpan--;
587         }
588         if (newSpan > 0) {
589           divs=Rico.select('.ricoLG_cell',hdrcell.cell);
590           cell=divs && divs.length>0 ? divs[0] : hdrcell.cell;
591           this.exportText+="<td style='"+this.exportStyle(cell,exportStyles)+"'";
592           if (hdrcell.colSpan > 1) this.exportText+=" colspan='"+newSpan+"'";
593           this.exportText+=">"+Rico.getInnerText(cell,!this.options.exportImgTags, !this.options.exportFormFields, 'NoExport')+"</td>";
594         }
595       }
596       this.exportText+="</tr>";
597     }
598     this.exportText+="</thead><tbody>";
599   },
600
601 /**
602  * Support function for printVisible().
603  */
604   exportFinish: function() {
605     if (this.hideMsg) this.hideMsg();
606     window.status=Rico.getPhraseById('exportComplete');
607     if (this.exportRows.length > 0) this.exportText+='<tr>'+this.exportRows.join('</tr><tr>')+'</tr>';
608     if (this.exportFooter) this.exportText+=this.exportFooter;
609     this.exportText+="</tbody></table></body></html>";
610     if (this.cancelMenu) this.cancelMenu();
611     var w=window.open('','_blank',this.options.exportWindow);
612     if (w == null) {
613       alert(Rico.getPhraseById('disableBlocker'));
614     } else {
615       w.document.open();
616       w.document.write(this.exportText);
617       w.document.close();
618     }
619     this.exportText=undefined;
620     this.exportRows=undefined;
621   },
622
623 /**
624  * Support function for printVisible()
625  */
626   exportStyle: function(elem,styleList) {
627     for (var i=0,s=''; i < styleList.length; i++) {
628       try {
629         var curstyle=Rico.getStyle(elem,styleList[i]);
630         if (curstyle) s+=styleList[i]+':'+curstyle+';';
631       } catch(e) {};
632     }
633     return s;
634   },
635
636 /**
637  * Gets the value of the grid cookie and interprets the contents.
638  * All information for a particular grid is stored in a single cookie.
639  * This may include column widths, column hide/show status, current sort, and any column filters.
640  */
641   getCookie: function() {
642     var c=Rico.getCookie(this.options.cookiePrefix+this.tableId);
643     if (!c) return;
644     var cookieVals=c.split(',');
645     for (var i=0; i<cookieVals.length; i++) {
646       var v=cookieVals[i].split(':');
647       if (v.length!=2) continue;
648       var colnum=parseInt(v[0].slice(1),10);
649       if (colnum < 0 || colnum >= this.columns.length) continue;
650       var col=this.columns[colnum];
651       switch (v[0].charAt(0)) {
652         case 'w':
653           col.setColWidth(v[1]);
654           col.customWidth=true;
655           break;
656         case 'h':
657           if (v[1].toLowerCase()=='true')
658             col.hideshow(true,true);
659           else
660             col.hideshow(false,true);
661           break;
662         case 's':
663           if (!this.options.saveColumnInfo.sort || !col.sortable) break;
664           col.setSorted(v[1]);
665           break;
666         case 'f':
667           if (!this.options.saveColumnInfo.filter || !col.filterable) break;
668           var filterTemp=v[1].split('~');
669           col.filterOp=filterTemp.shift();
670           col.filterValues = [];
671           col.filterType = Rico.ColumnConst.USERFILTER;
672           for (var j=0; j<filterTemp.length; j++)
673             col.filterValues.push(unescape(filterTemp[j]));
674           break;
675       }
676     }
677   },
678
679 /**
680  * Sets the grid cookie.
681  * All information for a particular grid is stored in a single cookie.
682  * This may include column widths, column hide/show status, current sort, and any column filters.
683  */
684   setCookie: function() {
685     var cookieVals=[];
686     for (var i=0; i<this.columns.length; i++) {
687       var col=this.columns[i];
688       if (this.options.saveColumnInfo.width) {
689         if (col.customWidth) cookieVals.push('w'+i+':'+col.colWidth);
690         if (col.customVisible) cookieVals.push('h'+i+':'+col.visible);
691       }
692       if (this.options.saveColumnInfo.sort) {
693         if (col.currentSort != Rico.ColumnConst.UNSORTED)
694           cookieVals.push('s'+i+':'+col.currentSort);
695       }
696       if (this.options.saveColumnInfo.filter && col.filterType == Rico.ColumnConst.USERFILTER) {
697         var filterTemp=[col.filterOp];
698         for (var j=0; j<col.filterValues.length; j++)
699           filterTemp.push(escape(col.filterValues[j]));
700         cookieVals.push('f'+i+':'+filterTemp.join('~'));
701       }
702     }
703     Rico.setCookie(this.options.cookiePrefix+this.tableId, cookieVals.join(','), this.options.cookieDays, this.options.cookiePath, this.options.cookieDomain);
704   }
705
706 };
707
708
709 Rico.ColumnConst = {
710   UNFILTERED:   0,
711   SYSTEMFILTER: 1,
712   USERFILTER:   2,
713
714   UNSORTED:  0,
715   SORT_ASC:  "ASC",
716   SORT_DESC: "DESC",
717
718   MINWIDTH: 10,
719   DOLLAR:  {type:'number', prefix:'$', decPlaces:2, ClassName:'alignright'},
720   EURO:    {type:'number', prefix:'&euro;', decPlaces:2, ClassName:'alignright'},
721   PERCENT: {type:'number', suffix:'%', decPlaces:2, multiplier:100, ClassName:'alignright'},
722   QTY:     {type:'number', decPlaces:0, ClassName:'alignright'},
723   DEFAULT: {type:"showTags"}
724 }
725
726
727 /**
728  * @class Define methods that are common to columns in both SimpleGrid and LiveGrid
729  */
730 Rico.TableColumnBase = function() {};
731
732 Rico.TableColumnBase.prototype = {
733
734 /**
735  * Common code used to initialize the column in both SimpleGrid & LiveGrid
736  */
737   baseInit: function(liveGrid,colIdx,hdrInfo,tabIdx) {
738     Rico.log("TableColumnBase.init index="+colIdx+" tabIdx="+tabIdx);
739     this.liveGrid  = liveGrid;
740     this.index     = colIdx;
741     this.hideWidth = Rico.isKonqueror || Rico.isWebKit || liveGrid.headerRowCnt>1 ? 5 : 2;  // column width used for "hidden" columns. Anything less than 5 causes problems with Konqueror. Best to keep this greater than padding used inside cell.
742     this.options   = liveGrid.options;
743     this.tabIdx    = tabIdx;
744     this.hdrCell   = hdrInfo.cell;
745     this.body = document.getElementsByTagName("body")[0];
746     this.displayName  = this.getDisplayName(this.hdrCell);
747     var divs=this.hdrCell.getElementsByTagName('div');
748     this.hdrColDiv=(divs.length<1) ? Rico.wrapChildren(this.hdrCell,'ricoLG_col') : divs[0];
749     this.hdrCellDiv=(divs.length<2) ? Rico.wrapChildren(this.hdrColDiv,'ricoLG_cell') : divs[1];
750     var sectionIndex= tabIdx==0 ? colIdx : colIdx-liveGrid.options.frozenColumns;
751     this.dataCell = liveGrid.tbody[tabIdx].rows[0].cells[sectionIndex];
752     divs=this.dataCell.getElementsByTagName('div');
753     this.dataColDiv=(divs.length<1) ? Rico.wrapChildren(this.dataCell,'ricoLG_col') : divs[0];
754
755     this.mouseDownHandler= Rico.eventHandle(this,'handleMouseDown');
756     this.mouseMoveHandler= Rico.eventHandle(this,'handleMouseMove');
757     this.mouseUpHandler  = Rico.eventHandle(this,'handleMouseUp');
758     this.mouseOutHandler = Rico.eventHandle(this,'handleMouseOut');
759
760     this.fieldName = 'col'+this.index;
761     var spec = liveGrid.options.columnSpecs[colIdx];
762     this.format=Rico.extend( {}, Rico.ColumnConst.DEFAULT);
763     switch (typeof spec) {
764       case 'object':
765         if (typeof spec.format=='string') Rico.extend(this.format, Rico.ColumnConst[spec.format.toUpperCase()]);
766         Rico.extend(this.format, spec);
767         break;
768       case 'string':
769         if (spec.slice(0,4)=='spec') spec=spec.slice(4).toUpperCase();  // for backwards compatibility
770         if (typeof Rico.ColumnConst[spec]=='object') Rico.extend(this.format, Rico.ColumnConst[spec]);
771         break;
772     }
773     Rico.addClass(this.dataColDiv, this.colClassName());
774     this.visible=true;
775     if (typeof this.format.visible=='boolean') this.visible=this.format.visible;
776     Rico.log("TableColumn.init index="+colIdx+" fieldName="+this.fieldName);
777     this.sortable     = typeof this.format.canSort=='boolean' ? this.format.canSort : liveGrid.options.canSortDefault;
778     this.currentSort  = Rico.ColumnConst.UNSORTED;
779     this.filterable   = typeof this.format.canFilter=='boolean' ? this.format.canFilter : liveGrid.options.canFilterDefault;
780     this.filterType   = Rico.ColumnConst.UNFILTERED;
781     this.hideable     = typeof this.format.canHide=='boolean' ? this.format.canHide : liveGrid.options.canHideDefault;
782
783     var wi;
784     switch (typeof this.format.width) {
785       case 'number': wi=this.format.width; break;
786       case 'string': wi=parseInt(this.format.width,10); break;
787       default:       wi=hdrInfo.initWidth; break;
788     }
789     wi=(typeof(wi)=='number') ? Math.max(wi,Rico.ColumnConst.MINWIDTH) : liveGrid.options.defaultWidth;
790     this.setColWidth(wi);
791     if (!this.visible) this.setDisplay('none');
792   },
793   
794   colClassName: function() {
795     return this.format.ClassName ? this.format.ClassName : this.liveGrid.tableId+'_col'+this.index;
796   },
797
798   insertResizer: function() {
799     //this.hdrCell.style.width='';
800     if (this.format.noResize) return;
801     var resizer=document.createElement('div');
802     resizer.className='ricoLG_Resize';
803     resizer.style[this.liveGrid.align[1]]='0px';
804     this.hdrCellDiv.appendChild(resizer);
805     Rico.eventBind(resizer,"mousedown", this.mouseDownHandler, false);
806   },
807
808 /**
809  * get the display name of a column
810  */
811   getDisplayName: function(el) {
812     var anchors=el.getElementsByTagName("A");
813     //Check the existance of A tags
814     if (anchors.length > 0)
815       return anchors[0].innerHTML;
816     else
817       return Rico.stripTags(el.innerHTML);
818   },
819
820   _clear: function(gridCell) {
821     gridCell.innerHTML='&nbsp;';
822   },
823
824   clearCell: function(rowIndex) {
825     var gridCell=this.cell(rowIndex);
826     this._clear(gridCell,rowIndex);
827     if (!this.liveGrid.buffer) return;
828     var acceptAttr=this.liveGrid.buffer.options.acceptAttr;
829     for (var k=0; k<acceptAttr.length; k++) {
830       switch (acceptAttr[k]) {
831         case 'style': gridCell.style.cssText=''; break;
832         case 'class': gridCell.className=''; break;
833         default:      gridCell['_'+acceptAttr[k]]=''; break;
834       }
835     }
836   },
837
838   dataTable: function() {
839     return this.liveGrid.tabs[this.tabIdx];
840   },
841
842   numRows: function() {
843     return this.dataColDiv.childNodes.length;
844   },
845
846   clearColumn: function() {
847     var childCnt=this.numRows();
848     for (var r=0; r<childCnt; r++)
849       this.clearCell(r);
850   },
851
852   cell: function(r) {
853     return this.dataColDiv.childNodes[r];
854   },
855
856   getFormattedValue: function(r,xImg,xForm,xClass) {
857     return Rico.getInnerText(this.cell(r),xImg,xForm,xClass);
858   },
859
860   setColWidth: function(wi) {
861     if (typeof wi=='number') {
862       wi=parseInt(wi,10);
863       if (wi < Rico.ColumnConst.MINWIDTH) return;
864       wi=wi+'px';
865     }
866     Rico.log('setColWidth '+this.index+': '+wi);
867     this.colWidth=wi;
868     this.hdrColDiv.style.width=wi;
869     this.dataColDiv.style.width=wi;
870   },
871
872   pluginMouseEvents: function() {
873     if (this.mousePluggedIn==true) return;
874     Rico.eventBind(this.body,"mousemove", this.mouseMoveHandler, false);
875     Rico.eventBind(this.body,"mouseup",   this.mouseUpHandler  , false);
876     Rico.eventBind(this.body,"mouseout",  this.mouseOutHandler , false);
877     this.mousePluggedIn=true;
878   },
879
880   unplugMouseEvents: function() {
881     Rico.eventUnbind(this.body,"mousemove", this.mouseMoveHandler, false);
882     Rico.eventUnbind(this.body,"mouseup",   this.mouseUpHandler  , false);
883     Rico.eventUnbind(this.body,"mouseout",  this.mouseOutHandler , false);
884     this.mousePluggedIn=false;
885   },
886
887   handleMouseDown: function(e) {
888     this.resizeStart=Rico.eventClient(e).x;
889     this.origWidth=parseInt(this.colWidth,10);
890     var p=Rico.positionedOffset(this.hdrCell);
891     if (this.liveGrid.direction=='rtl') {
892       this.edge=p.left+this.liveGrid.options.scrollBarWidth;
893       switch (this.tabIdx) {
894         case 0: this.edge+=this.liveGrid.innerDiv.offsetWidth; break;
895         case 1: this.edge-=this.liveGrid.scrollDiv.scrollLeft; break;
896       }
897     } else {
898       this.edge=p.left+this.hdrCell.offsetWidth;
899       if (this.tabIdx>0) this.edge+=Rico.nan2zero(this.liveGrid.tabs[0].offsetWidth);
900     }
901     this.liveGrid.resizeDiv.style.left=this.edge+"px";
902     this.liveGrid.resizeDiv.style.display="";
903     this.liveGrid.outerDiv.style.cursor='e-resize';
904     this.tmpHighlight=this.liveGrid.highlightEnabled;
905     this.liveGrid.highlightEnabled=false;
906     this.pluginMouseEvents();
907     Rico.eventStop(e);
908   },
909
910   handleMouseMove: function(e) {
911     var delta=Rico.eventClient(e).x-this.resizeStart;
912     var newWidth=(this.liveGrid.direction=='rtl') ? this.origWidth-delta : this.origWidth+delta;
913     if (newWidth < Rico.ColumnConst.MINWIDTH) return;
914     this.liveGrid.resizeDiv.style.left=(this.edge+delta)+"px";
915     this.colWidth=newWidth;
916     Rico.eventStop(e);
917   },
918
919   handleMouseUp: function(e) {
920     this.unplugMouseEvents();
921     Rico.log('handleMouseUp '+this.liveGrid.tableId);
922     this.liveGrid.outerDiv.style.cursor='';
923     this.liveGrid.resizeDiv.style.display="none";
924     this.setColWidth(this.colWidth);
925     this.customWidth=true;
926     this.liveGrid.setCookie();
927     this.liveGrid.highlightEnabled=this.tmpHighlight;
928     this.liveGrid.sizeDivs();
929     Rico.eventStop(e);
930   },
931
932   handleMouseOut: function(e) {
933     var reltg = Rico.eventRelatedTarget(e) || e.toElement;
934     while (reltg != null && reltg.nodeName.toLowerCase() != 'body')
935       reltg=reltg.parentNode;
936     if (reltg!=null && reltg.nodeName.toLowerCase() == 'body') return true;
937     this.handleMouseUp(e);
938     return true;
939   },
940
941   setDisplay: function(d) {
942     this.hdrCell.style.display=d;
943     this.hdrColDiv.style.display=d;
944     this.dataCell.style.display=d;
945     this.dataColDiv.style.display=d;
946   },
947   
948   hideshow: function(visible,noresize) {
949     this.setDisplay(visible ? '' : 'none');
950     this.liveGrid.cancelMenu();
951     this.visible=visible;
952     this.customVisible=true;
953     if (noresize) return;
954     this.liveGrid.setCookie();
955     this.liveGrid.sizeDivs();
956   },
957
958   hideColumn: function() {
959     Rico.log('hideColumn '+this.liveGrid.tableId);
960     this.hideshow(false,false);
961   },
962
963   showColumn: function() {
964     Rico.log('showColumn '+this.liveGrid.tableId);
965     this.hideshow(true,false);
966   },
967
968   chooseColumn: function(e) {
969     var elem=Rico.eventElement(e);
970     this.hideshow(elem.checked,false);
971   },
972
973   setImage: function() {
974     if ( this.currentSort == Rico.ColumnConst.SORT_ASC ) {
975        this.imgSort.style.display='inline-block';
976        this.imgSort.className=Rico.theme.sortAsc || 'rico-icon ricoLG_sortAsc';
977     } else if ( this.currentSort == Rico.ColumnConst.SORT_DESC ) {
978        this.imgSort.style.display='inline-block';
979        this.imgSort.className=Rico.theme.sortDesc || 'rico-icon ricoLG_sortDesc';
980     } else {
981        this.imgSort.style.display='none';
982     }
983     if (this.filterType == Rico.ColumnConst.USERFILTER) {
984        this.imgFilter.style.display='inline-block';
985        this.imgFilter.title=this.getFilterText();
986     } else {
987        this.imgFilter.style.display='none';
988     }
989   },
990
991   canHideShow: function() {
992     return this.hideable;
993   }
994
995 };