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