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