dc6a05066d4091d3c9bb1b59444e43681fb93a33
[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]=document.getElementById(this.tableId+'_tab'+i);
47       if (!this.tabs[i]) return;
48       this.hdrTabs[i]=document.getElementById(this.tableId+'_tab'+i+'h');
49       if (!this.hdrTabs[i]) return;
50       //if (i==0) this.tabs[i].style.position='absolute';
51       //if (i==0) this.tabs[i].style.left='0px';
52       //this.hdrTabs[i].style.position='absolute';
53       //this.hdrTabs[i].style.top='0px';
54       //this.hdrTabs[i].style.zIndex=1;
55       this.thead[i]=this.hdrTabs[i];
56       this.tbody[i]=this.tabs[i];
57       this.headerColCnt = this.getColumnInfo(this.hdrTabs[i].rows);
58       if (i==0) this.options.frozenColumns=this.headerColCnt;
59       if (Rico.theme.gridheader) Rico.addClass(this.thead[i],Rico.theme.gridheader);
60       if (Rico.theme.gridcontent) Rico.addClass(this.tbody[i],Rico.theme.gridcontent);
61     }
62     if (this.headerColCnt==0) {
63       alert('ERROR: no columns found in "'+this.tableId+'"');
64       return;
65     }
66     //this.hdrHt=Math.max(Rico.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=Rico.eventHandle(this,'handleScroll');
77     this.pluginScroll();
78     if (this.options.windowResize)
79       Rico.eventBind(window,"resize", Rico.eventHandle(this,'sizeDivs'), 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);\r
99       var divs=cell.getElementsByTagName('div');
100       switch (fmt.filterUI.charAt(0)) {
101         case 't':
102           field=Rico.createFormField(divs[1],'input','text',name,name);
103           var size=fmt.filterUI.match(/\d+/);
104           field.maxLength=fmt.Length || 50;\r
105           field.size=size ? parseInt(size,10) : 10;
106           Rico.eventBind(field,'keyup',Rico.eventHandle(col,'filterKeypress'),false);\r
107           break;\r
108         case 's':
109           field=Rico.createFormField(divs[1],'select',null,name);\r
110           Rico.addSelectOption(field,this.options.FilterAllToken,Rico.getPhraseById("filterAll"));
111           this.getFilterValues(col);
112           var keys=Rico.keys(col.filterHash);
113           keys.sort();
114           for (var i=0; i<keys.length; i++)
115             Rico.addSelectOption(field,keys[i],keys[i] || Rico.getPhraseById("filterBlank"));\r
116           Rico.eventBind(field,'change',Rico.eventHandle(col,'filterChange'),false);\r
117           break;\r
118       }
119     }
120     this.initFilterImage(r);
121   },
122   
123   getFilterValues: function(col) {
124     var h={};
125     var n=col.numRows();
126     for (var i=0; i<n; i++) {
127       var v=Rico.getInnerText(col.cell(i));
128       var hval=h[v];
129       if (hval)
130         hval.push(i);
131       else
132         h[v]=[i];
133     }
134     col.filterHash=h;\r
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();\r
164   },
165
166   handleScroll: function(e) {
167     var newTop=(-this.scrollDiv.scrollTop)+'px';
168     this.tabs[0].style.marginTop=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=Rico.getParentByTagName(Rico.eventElement(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.log('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     this.frozenTabs.style.height=this.scrollDiv.clientHeight+'px';
209     var divAdjust=2;
210     this.innerDiv.style.width=(this.scrWi-this.options.scrollBarWidth+divAdjust)+'px';
211     //this.innerDiv.style.height=(this.hdrHt+1)+'px';
212     totHt+=divAdjust;
213     this.resizeDiv.style.height=totHt+'px';
214     
215     //this.outerDiv.style.height=(totHt+this.options.scrollBarWidth)+'px';
216     this.handleScroll();
217   },
218   
219 /**
220  * Copies all rows to a new window as a simple html table.
221  */
222   printVisible: function() {
223     this.showMsg(Rico.getPhraseById('exportInProgress'));
224     Rico.runLater(10,this,'_printVisible');  // allow message to paint
225   },
226
227   _printVisible: function() {
228     this.exportStart();
229     var exportStyles=this.getExportStyles(this.tbody[0]);
230     for(var r=0; r < this.pageSize; r++) {
231       if (this.columns[0].cell(r).style.display=='none') continue;
232       var exportText='';
233       for (var c=0; c<this.columns.length; c++) {
234         var col=this.columns[c];
235         if (col.visible) {
236           var v=col.getFormattedValue(r, !this.options.exportImgTags, !this.options.exportFormFields, 'NoExport');
237           if (col.format.exportPrefix) v=col.format.exportPrefix+v;
238           if (v=='') v='&nbsp;';
239           exportText+="<td style='"+this.exportStyle(col.cell(r),exportStyles)+"'>"+v+"</td>";
240         }
241       }
242       this.exportRows.push(exportText);
243     }
244     this.exportFinish();
245   },
246
247   /**
248    * Hide a row in the grid.
249    * sizeDivs() should be called after this function has completed.
250    */
251   hideRow: function(rownum) {
252     if (this.columns[0].cell(rownum).style.display=='none') return;
253     for (var i=0; i<this.columns.length; i++)
254       this.columns[i].cell(rownum).style.display='none';
255   },
256
257   /**
258    * Unhide a row in the grid.
259    * sizeDivs() should be called after this function has completed.
260    */
261   showRow: function(rownum) {
262     if (this.columns[0].cell(rownum).style.display=='') return;
263     for (var i=0; i<this.columns.length; i++)
264       this.columns[i].cell(rownum).style.display='';
265   },
266
267   /**
268    * Search for rows that contain SearchString in column ColIdx.
269    * If ShowMatch is false, then matching rows are hidden, if true then mismatching rows are hidden.
270    */
271   searchRows: function(ColIdx,SearchString,ShowMatch) {\r
272     if (!SearchString) return;\r
273     var re=new RegExp(SearchString);\r
274     var rowcnt=this.columns[ColIdx].numRows();\r
275     for(var r=0; r<rowcnt; r++) {\r
276       var txt=this.cell(r,ColIdx).innerHTML;\r
277       var matched=(txt.match(re) != null);\r
278       if (matched != ShowMatch) this.hideRow(r);\r
279     }
280     this.sizeDivs();
281     this.handleScroll();\r
282   },\r
283 \r
284   /**
285    * Unhide all rows in the grid
286    */
287   showAllRows: function() {
288     for (var i=0; i<this.pageSize; i++)
289       this.showRow(i);
290     this.sizeDivs();\r
291   },
292   
293   openPopup: function(elem,popupobj) {
294     while (elem && !Rico.hasClass(elem,'ricoLG_cell'))
295       elem=elem.parentNode;
296     if (!elem) return false;
297     var td=Rico.getParentByTagName(elem,'td');
298   
299     var newLeft=Math.floor(td.offsetLeft-this.scrollDiv.scrollLeft+td.offsetWidth/2);
300     if (this.direction == 'rtl') {
301       if (newLeft > this.width) newLeft-=this.width;
302     } else {
303       if (newLeft+this.width+this.options.margin > this.scrollDiv.clientWidth) newLeft-=this.width;
304     }
305     popupobj.divPopup.style.visibility="hidden";
306     popupobj.divPopup.style.display="block";
307     var contentHt=popupobj.divPopup.offsetHeight;
308     var newTop=Math.floor(elem.offsetTop-this.scrollDiv.scrollTop+elem.offsetHeight/2);
309     if (newTop+contentHt+popupobj.options.margin > this.scrollDiv.clientHeight)
310       newTop=Math.max(newTop-contentHt,0);
311     popupobj.openPopup(this.frzWi+newLeft,this.hdrHt+newTop);
312     popupobj.divPopup.style.visibility ="visible";
313     return elem;
314   }
315
316 }
317
318 if (Rico.Menu) {
319 Rico.extend(Rico.Menu.prototype, {
320
321 showSimpleMenu: function(e,hideFunc) {
322   Rico.eventStop(e);
323   this.hideFunc=hideFunc;
324   if (this.div.childNodes.length==0) {
325     this.cancelmenu();
326     return false;
327   }
328   var elem=Rico.eventElement(e);
329   this.grid.openPopup(elem,this);
330   return elem;
331 },
332
333 showSimpleSubMenu: function(a,submenu) {
334   if (this.openSubMenu) this.hideSubMenu();
335   this.openSubMenu=submenu;
336   this.openMenuAnchor=a;
337   if (a.className=='ricoSubMenu') a.className='ricoSubMenuOpen';
338   var top=parseInt(this.div.style.top,10);
339   var left=parseInt(this.div.style.left,10);
340   submenu.openPopup(left+a.offsetWidth,top+a.offsetTop);
341   submenu.div.style.visibility ="visible";
342 }
343
344 });
345 }
346
347
348 Rico.SimpleGridColumn = function(grid,colIdx,hdrInfo,tabIdx) {
349   this.initialize(grid,colIdx,hdrInfo,tabIdx);
350 }
351
352 Rico.SimpleGridColumn.prototype = {
353 /**
354  * @class Implements a SimpleGrid column
355  * @extends Rico.TableColumnBase
356  * @constructs
357  */
358 initialize: function(grid,colIdx,hdrInfo,tabIdx) {
359   Rico.extend(this, new Rico.TableColumnBase());
360   this.baseInit(grid,colIdx,hdrInfo,tabIdx);
361 },
362
363 setUnfiltered: function() {
364   this.filterRows=null;
365 },
366
367 filterChange: function(e) {\r
368   var selbox=Rico.eventElement(e);
369   if (selbox.value==this.liveGrid.options.FilterAllToken)\r
370     this.setUnfiltered();\r
371   else
372     this.filterRows=this.filterHash[selbox.value];
373   this.liveGrid.applyFilters();
374 },
375
376 filterKeypress: function(e) {\r
377   var txtbox=Rico.eventElement(e);
378   if (typeof this.lastKeyFilter != 'string') this.lastKeyFilter='';\r
379   if (this.lastKeyFilter==txtbox.value) return;\r
380   var v=txtbox.value;\r
381   Rico.log("filterKeypress: "+this.index+' '+v);\r
382   this.lastKeyFilter=v;
383   if (v) {
384     v=v.replace('\\','\\\\');
385     v=v.replace('(','\\(').replace(')','\\)');
386     v=v.replace('.','\\.');
387     if (this.format.filterUI.indexOf('^') > 0) v='^'+v;
388     var re=new RegExp(v,'i');\r
389     this.filterRows=[];
390     var n=this.numRows();
391     for (var i=0; i<n; i++) {
392       var celltxt=Rico.getInnerText(this.cell(i));
393       if (celltxt.match(re)) this.filterRows.push(i);
394     }
395   } else {
396     this.setUnfiltered();\r
397   }
398   this.liveGrid.applyFilters();
399 }
400
401 }