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