Adjust order of error output
[misc/kostenrechnung] / lib / rico / ricoSimpleGrid.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
17 if(typeof Rico=='undefined') throw("SimpleGrid requires the Rico JavaScript framework");
18 if(typeof RicoUtil=='undefined') throw("SimpleGrid requires the RicoUtil Library");
19 if(typeof RicoTranslate=='undefined') throw("SimpleGrid requires the RicoTranslate Library");
20
21
22 Rico.SimpleGrid = Class.create(
23 /** @lends Rico.SimpleGrid# */
24 {
25 /**
26  * @class Create & manage an unbuffered grid.
27  * Supports: frozen columns & headings, resizable columns.
28  * @extends Rico.GridCommon
29  * @constructs
30  */
31   initialize: function( tableId, options ) {
32     Object.extend(this, new Rico.GridCommon);
33     this.baseInit();
34     Rico.setDebugArea(tableId+"_debugmsgs");    // if used, this should be a textarea
35     Object.extend(this.options, options || {});
36     this.tableId = tableId;
37     Rico.writeDebugMsg("SimpleGrid initialize start: "+tableId);
38     this.createDivs();
39     this.hdrTabs=new Array(2);
40     this.simpleGridInit();
41     Rico.writeDebugMsg("SimpleGrid initialize end: "+tableId);
42   },
43
44   simpleGridInit: function() {
45     var i;
46     for (i=0; i<2; i++) {
47       Rico.writeDebugMsg("simpleGridInit "+i);
48       this.tabs[i]=$(this.tableId+'_tab'+i);
49       if (!this.tabs[i]) return;
50       this.hdrTabs[i]=$(this.tableId+'_tab'+i+'h');
51       if (!this.hdrTabs[i]) return;
52       if (i==0) this.tabs[i].style.position='absolute';
53       if (i==0) this.tabs[i].style.left='0px';
54       this.hdrTabs[i].style.position='absolute';
55       this.hdrTabs[i].style.top='0px';
56       this.hdrTabs[i].style.zIndex=1;
57       this.thead[i]=this.hdrTabs[i];
58       this.tbody[i]=this.tabs[i];
59       this.headerColCnt = this.getColumnInfo(this.hdrTabs[i].rows);
60       if (i==0) this.options.frozenColumns=this.headerColCnt;
61     }
62     if (this.headerColCnt==0) {
63       alert('ERROR: no columns found in "'+this.tableId+'"');
64       return;
65     }
66     this.hdrHt=Math.max(RicoUtil.nan2zero(this.hdrTabs[0].offsetHeight),this.hdrTabs[1].offsetHeight);
67     for (i=0; i<2; i++) {
68       if (i==0) this.tabs[i].style.top=this.hdrHt+'px';
69     }
70     this.createColumnArray('SimpleGridColumn');
71     this.pageSize=this.columns[0].dataColDiv.childNodes.length;
72     this.sizeDivs();
73     if (typeof(this.options.FilterLocation)=='number')
74       this.createFilters(this.options.FilterLocation);
75     this.attachMenuEvents();
76     this.scrollEventFunc=this.handleScroll.bindAsEventListener(this);
77     this.pluginScroll();
78     if (this.options.windowResize)
79       Event.observe(window,"resize", this.sizeDivs.bindAsEventListener(this), false);
80   },
81
82   // return id string for a filter element
83   filterId: function(colnum) {
84     return 'RicoFilter_'+this.tableId+'_'+colnum;
85   },
86   
87   // create filter elements on heading row r
88   createFilters: function(r) {
89     if (r < 0) {
90       r=this.addHeadingRow();
91       this.sizeDivs();
92     }
93     for( var c=0; c < this.headerColCnt; c++ ) {
94       var col=this.columns[c];
95       var fmt=col.format;
96       if (typeof fmt.filterUI!='string') continue;
97       var cell=this.hdrCells[r][c].cell;
98       var field,name=this.filterId(c);
99       var divs=cell.getElementsByTagName('div');
100       switch (fmt.filterUI.charAt(0)) {
101         case 't':
102           field=RicoUtil.createFormField(divs[1],'input','text',name,name);
103           var size=fmt.filterUI.match(/\d+/);
104           field.maxLength=fmt.Length || 50;
105           field.size=size ? parseInt(size,10) : 10;
106           Event.observe(field,'keyup',col.filterKeypress.bindAsEventListener(col),false);
107           break;
108         case 's':
109           field=RicoUtil.createFormField(divs[1],'select',null,name);
110           RicoUtil.addSelectOption(field,this.options.FilterAllToken,RicoTranslate.getPhraseById("filterAll"));
111           this.getFilterValues(col);
112           var keys=col.filterHash.keys();
113           keys.sort();
114           for (var i=0; i<keys.length; i++)
115             RicoUtil.addSelectOption(field,keys[i],keys[i] || RicoTranslate.getPhraseById("filterBlank"));
116           Event.observe(field,'change',col.filterChange.bindAsEventListener(col),false);
117           break;
118       }
119     }
120     this.initFilterImage(r);
121   },
122   
123   getFilterValues: function(col) {
124     var h=$H();
125     var n=col.numRows();
126     for (var i=0; i<n; i++) {
127       var v=RicoUtil.getInnerText(col.cell(i));
128       var hval=h.get(v);
129       if (hval)
130         hval.push(i);
131       else
132         h.set(v,[i]);
133     }
134     col.filterHash=h;
135   },
136   
137   // hide filtered rows
138   applyFilters: function() {
139     // rows to display will be the intersection of all filterRows arrays
140     var fcols=[];
141     for (var c=0; c<this.columns.length; c++) {
142       if (this.columns[c].filterRows)
143         fcols.push(this.columns[c].filterRows);
144     }
145     if (fcols.length==0) {
146       // no filters are set
147       this.showAllRows();
148       return;
149     }
150     for (var r=0; r<this.pageSize; r++) {
151       var showflag=true;
152       for (var j=0; j<fcols.length; j++) {
153         if (fcols[j].indexOf(r)==-1) {
154           showflag=false;
155           break;
156         }
157       }
158       if (showflag)
159         this.showRow(r);
160       else
161         this.hideRow(r);
162     }
163     this.sizeDivs();
164   },
165
166   handleScroll: function(e) {
167     var newTop=(this.hdrHt-this.scrollDiv.scrollTop)+'px';
168     this.tabs[0].style.top=newTop;
169     this.setHorizontalScroll();
170   },
171
172   /**
173    * Register a menu that will only be used in the scrolling part of the grid.
174    * If submenus are used, they must be registered after the main menu.
175    */
176   registerScrollMenu: function(menu) {
177     if (!this.menu) this.menu=menu;
178     menu.grid=this;
179     menu.showmenu=menu.showSimpleMenu;
180     menu.showSubMenu=menu.showSimpleSubMenu;
181     menu.createDiv(this.outerDiv);
182   },
183
184   handleMenuClick: function(e) {
185     if (!this.menu) return;
186     this.cancelMenu();
187     this.menuCell=RicoUtil.getParentByTagName(Event.element(e),'div');
188     this.highlightEnabled=false;
189     if (this.hideScroll) this.scrollDiv.style.overflow="hidden";
190     if (this.menu.buildGridMenu) this.menu.buildGridMenu(this.menuCell);
191     this.menu.showmenu(e,this.closeMenu.bind(this));
192   },
193
194   closeMenu: function() {
195     if (this.hideScroll) this.scrollDiv.style.overflow="";
196     this.highlightEnabled=true;
197   },
198
199   sizeDivs: function() {
200     if (this.outerDiv.offsetParent.style.display=='none') return;
201     this.baseSizeDivs();
202     var maxHt=Math.max(this.options.maxHt || this.availHt(), 50);
203     var totHt=Math.min(this.hdrHt+this.dataHt, maxHt);
204     Rico.writeDebugMsg('sizeDivs '+this.tableId+': hdrHt='+this.hdrHt+' dataHt='+this.dataHt);
205     this.dataHt=totHt-this.hdrHt;
206     if (this.scrWi>0) this.dataHt+=this.options.scrollBarWidth;
207     this.scrollDiv.style.height=this.dataHt+'px';
208     var divAdjust=2;
209     this.innerDiv.style.width=(this.scrWi-this.options.scrollBarWidth+divAdjust)+'px';
210     this.innerDiv.style.height=this.hdrHt+'px';
211     totHt+=divAdjust;
212     this.resizeDiv.style.height=this.frozenTabs.style.height=totHt+'px';
213     this.outerDiv.style.height=(totHt+this.options.scrollBarWidth)+'px';
214     this.handleScroll();
215   },
216   
217   _printVisible: function(exportType) {
218     this.exportStart();
219     for(var r=0; r < this.pageSize; r++) {
220       if (this.columns[0].cell(r).style.display=='none') continue;
221       var exportText='';
222       for (var c=0; c<this.columns.length; c++) {
223         var col=this.columns[c];
224         if (col.visible) {
225           var v=col.getFormattedValue(r, !this.options.exportImgTags, !this.options.exportFormFields, 'NoExport');
226           if (col.format.exportPrefix) v=col.format.exportPrefix+v;
227           if (v=='') v='&nbsp;';
228           exportText+="<td style='"+this.exportStyle(col.cell(r))+"'>"+v+"</td>";
229         }
230       }
231       this.exportRows.push(exportText);
232     }
233     this.exportFinish(exportType);
234   },
235
236   /**
237    * Hide a row in the grid.
238    * sizeDivs() should be called after this function has completed.
239    */
240   hideRow: function(rownum) {
241     if (this.columns[0].cell(rownum).style.display=='none') return;
242     for (var i=0; i<this.columns.length; i++)
243       this.columns[i].cell(rownum).style.display='none';
244   },
245
246   /**
247    * Unhide a row in the grid.
248    * sizeDivs() should be called after this function has completed.
249    */
250   showRow: function(rownum) {
251     if (this.columns[0].cell(rownum).style.display=='') return;
252     for (var i=0; i<this.columns.length; i++)
253       this.columns[i].cell(rownum).style.display='';
254   },
255
256   /**
257    * Search for rows that contain SearchString in column ColIdx.
258    * If ShowMatch is false, then matching rows are hidden, if true then mismatching rows are hidden.
259    */
260   searchRows: function(ColIdx,SearchString,ShowMatch) {
261     if (!SearchString) return;
262     var re=new RegExp(SearchString);
263     var rowcnt=this.columns[ColIdx].numRows();
264     for(var r=0; r<rowcnt; r++) {
265       var txt=this.cell(r,ColIdx).innerHTML;
266       var matched=(txt.match(re) != null);
267       if (matched != ShowMatch) this.hideRow(r);
268     }
269     this.sizeDivs();
270     this.handleScroll();
271   },
272
273   /**
274    * Unhide all rows in the grid
275    */
276   showAllRows: function() {
277     for (var i=0; i<this.pageSize; i++)
278       this.showRow(i);
279     this.sizeDivs();
280   },
281   
282   openPopup: function(elem,popupobj) {
283     while (elem && !Element.hasClassName(elem,'ricoLG_cell'))
284       elem=elem.parentNode;
285     if (!elem) return false;
286     var td=RicoUtil.getParentByTagName(elem,'td');
287   
288     var newLeft=Math.floor(td.offsetLeft-this.scrollDiv.scrollLeft+td.offsetWidth/2);
289     if (this.direction == 'rtl') {
290       if (newLeft > this.width) newLeft-=this.width;
291     } else {
292       if (newLeft+this.width+this.options.margin > this.scrollDiv.clientWidth) newLeft-=this.width;
293     }
294     popupobj.divPopup.style.visibility="hidden";
295     popupobj.divPopup.style.display="block";
296     var contentHt=popupobj.divPopup.offsetHeight;
297     var newTop=Math.floor(elem.offsetTop-this.scrollDiv.scrollTop+elem.offsetHeight/2);
298     if (newTop+contentHt+popupobj.options.margin > this.scrollDiv.clientHeight)
299       newTop=Math.max(newTop-contentHt,0);
300     popupobj.openPopup(this.frzWi+newLeft,this.hdrHt+newTop);
301     popupobj.divPopup.style.visibility ="visible";
302     return elem;
303   }
304
305 });
306
307 if (Rico.Menu) {
308 Object.extend(Rico.Menu.prototype, {
309
310 showSimpleMenu: function(e,hideFunc) {
311   Event.stop(e);
312   this.hideFunc=hideFunc;
313   if (this.div.childNodes.length==0) {
314     this.cancelmenu();
315     return false;
316   }
317   var elem=Event.element(e);
318   this.grid.openPopup(elem,this);
319   return elem;
320 },
321
322 showSimpleSubMenu: function(a,submenu) {
323   if (this.openSubMenu) this.hideSubMenu();
324   this.openSubMenu=submenu;
325   this.openMenuAnchor=a;
326   if (a.className=='ricoSubMenu') a.className='ricoSubMenuOpen';
327   var top=parseInt(this.div.style.top,10);
328   var left=parseInt(this.div.style.left,10);
329   submenu.openPopup(left+a.offsetWidth,top+a.offsetTop);
330   submenu.div.style.visibility ="visible";
331 }
332
333 });
334 }
335
336
337 Rico.SimpleGridColumn = Class.create(
338 /** @lends Rico.SimpleGridColumn# */
339 {
340 /**
341  * @class Implements a SimpleGrid column
342  * @extends Rico.TableColumnBase
343  * @constructs
344  */
345 initialize: function(grid,colIdx,hdrInfo,tabIdx) {
346   Object.extend(this, new Rico.TableColumnBase());
347   this.baseInit(grid,colIdx,hdrInfo,tabIdx);
348 },
349
350 setUnfiltered: function() {
351   this.filterRows=null;
352 },
353
354 filterChange: function(e) {
355   var selbox=Event.element(e);
356   if (selbox.value==this.liveGrid.options.FilterAllToken)
357     this.setUnfiltered();
358   else
359     this.filterRows=this.filterHash.get(selbox.value);
360   this.liveGrid.applyFilters();
361 },
362
363 filterKeypress: function(e) {
364   var txtbox=Event.element(e);
365   if (typeof this.lastKeyFilter != 'string') this.lastKeyFilter='';
366   if (this.lastKeyFilter==txtbox.value) return;
367   var v=txtbox.value;
368   Rico.writeDebugMsg("filterKeypress: "+this.index+' '+v);
369   this.lastKeyFilter=v;
370   if (v) {
371     v=v.replace('\\','\\\\');
372     v=v.replace('(','\\(').replace(')','\\)');
373     v=v.replace('.','\\.');
374     if (this.format.filterUI.indexOf('^') > 0) v='^'+v;
375     var re=new RegExp(v,'i');
376     this.filterRows=[];
377     var n=this.numRows();
378     for (var i=0; i<n; i++) {
379       var celltxt=RicoUtil.getInnerText(this.cell(i));
380       if (celltxt.match(re)) this.filterRows.push(i);
381     }
382   } else {
383     this.setUnfiltered();
384   }
385   this.liveGrid.applyFilters();
386 }
387
388 });
389
390 Rico.includeLoaded('ricoSimpleGrid.js');