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