2 * (c) 2005-2011 Richard Cowin (http://openrico.org)
3 * (c) 2005-2011 Matt Brown (http://dowdybrown.com)
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 if(typeof Rico=='undefined') throw("SimpleGrid requires the Rico JavaScript framework");
18 Rico.SimpleGrid = function(tableId, options) {
19 this.initialize(tableId, options);
22 Rico.SimpleGrid.prototype = {
24 * @class Create & manage an unbuffered grid.
25 * Supports: frozen columns & headings, resizable columns.
26 * @extends Rico.GridCommon
29 initialize: function( tableId, options ) {
30 Rico.extend(this, Rico.GridCommon);
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
37 this.hdrTabs=new Array(2);
38 this.simpleGridInit();
39 Rico.log("SimpleGrid initialize end: "+tableId);
\r
42 simpleGridInit: function() {
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);
57 if (this.headerColCnt==0) {
58 alert('ERROR: no columns found in "'+this.tableId+'"');
61 this.createColumnArray('SimpleGridColumn');
62 this.pageSize=this.columns[0].dataColDiv.childNodes.length;
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');
71 if (this.options.windowResize)
72 Rico.eventBind(window,"resize", Rico.eventHandle(this,'sizeDivs'), false);
75 // return id string for a filter element
76 filterId: function(colnum) {
77 return 'RicoFilter_'+this.tableId+'_'+colnum;
80 // create filter elements on heading row r
81 createFilters: function(r) {
83 r=this.addHeadingRow();
86 for( var c=0; c < this.headerColCnt; c++ ) {
87 var col=this.columns[c];
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)) {
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
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);
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
113 this.initFilterImage(r);
116 getFilterValues: function(col) {
119 for (var i=0; i<n; i++) {
120 var v=Rico.getInnerText(col.cell(i));
130 // hide filtered rows
131 applyFilters: function() {
132 // rows to display will be the intersection of all filterRows arrays
134 for (var c=0; c<this.columns.length; c++) {
135 if (this.columns[c].filterRows)
136 fcols.push(this.columns[c].filterRows);
138 if (fcols.length==0) {
139 // no filters are set
143 for (var r=0; r<this.pageSize; r++) {
145 for (var j=0; j<fcols.length; j++) {
146 if (fcols[j].indexOf(r)==-1) {
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;
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;
174 handleScroll: function(e) {
175 this.frozenTabs.scrollTop=this.scrollDiv.scrollTop;
176 this.innerDiv.scrollLeft=this.scrollDiv.scrollLeft;
180 * Frozen columns scrolled due to user invoking browser find
182 handleScrollFrozen: function(e) {
183 this.scrollDiv.scrollTop=this.frozenTabs.scrollTop;
187 * Heading scrolled due to user invoking browser find
189 handleScrollHeading: function(e) {
190 this.scrollDiv.scrollLeft=this.innerDiv.scrollLeft;
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.
197 registerScrollMenu: function(menu) {
198 if (!this.menu) this.menu=menu;
200 menu.showmenu=menu.showSimpleMenu;
201 menu.showSubMenu=menu.showSimpleSubMenu;
202 menu.createDiv(this.outerDiv);
205 handleMenuClick: function(e) {
206 if (!this.menu) return;
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));
215 closeMenu: function() {
216 if (this.hideScroll) this.scrollDiv.style.overflow="";
217 this.highlightEnabled=true;
220 sizeDivs: function() {
221 if (this.outerDiv.offsetParent.style.display=='none') return;
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';
231 this.innerDiv.style.width=(this.scrWi-this.options.scrollBarWidth+divAdjust)+'px';
232 //this.innerDiv.style.height=(this.hdrHt+1)+'px';
234 this.resizeDiv.style.height=totHt+'px';
236 //this.outerDiv.style.height=(totHt+this.options.scrollBarWidth)+'px';
241 * Copies all rows to a new window as a simple html table.
243 printVisible: function() {
244 this.showMsg(Rico.getPhraseById('exportInProgress'));
245 Rico.runLater(10,this,'_printVisible'); // allow message to paint
248 _printVisible: function() {
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;
254 for (var c=0; c<this.columns.length; c++) {
255 var col=this.columns[c];
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=' ';
260 exportText+="<td style='"+this.exportStyle(col.cell(r),exportStyles)+"'>"+v+"</td>";
263 this.exportRows.push(exportText);
269 * Hide a row in the grid.
270 * sizeDivs() should be called after this function has completed.
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';
279 * Unhide a row in the grid.
280 * sizeDivs() should be called after this function has completed.
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='';
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.
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
302 this.handleScroll();
\r
306 * Unhide all rows in the grid
308 showAllRows: function() {
309 for (var i=0; i<this.pageSize; i++)
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');
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;
324 if (newLeft+this.width+this.options.margin > this.scrollDiv.clientWidth) newLeft-=this.width;
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";
340 Rico.extend(Rico.Menu.prototype, {
342 showSimpleMenu: function(e,hideFunc) {
344 this.hideFunc=hideFunc;
345 if (this.div.childNodes.length==0) {
349 var elem=Rico.eventElement(e);
350 this.grid.openPopup(elem,this);
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";
369 Rico.SimpleGridColumn = function(grid,colIdx,hdrInfo,tabIdx) {
370 this.initialize(grid,colIdx,hdrInfo,tabIdx);
373 Rico.SimpleGridColumn.prototype = {
375 * @class Implements a SimpleGrid column
376 * @extends Rico.TableColumnBase
379 initialize: function(grid,colIdx,hdrInfo,tabIdx) {
380 Rico.extend(this, new Rico.TableColumnBase());
381 this.baseInit(grid,colIdx,hdrInfo,tabIdx);
384 setUnfiltered: function() {
385 this.filterRows=null;
388 filterChange: function(e) {
\r
389 var selbox=Rico.eventElement(e);
390 if (selbox.value==this.liveGrid.options.FilterAllToken)
\r
391 this.setUnfiltered();
\r
393 this.filterRows=this.filterHash[selbox.value];
394 this.liveGrid.applyFilters();
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;
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
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);
417 this.setUnfiltered();
\r
419 this.liveGrid.applyFilters();