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.
17 if (!Rico.Buffer) Rico.Buffer = {};
19 Rico.Buffer.Base = function(dataTable, options) {
20 this.initialize(dataTable, options);
22 /** @lends Rico.Buffer.Base# */
23 Rico.Buffer.Base.prototype = {
25 * @class Defines the static buffer class (no AJAX).
26 * Loads buffer with data that already exists in the document as an HTML table or passed via javascript.
27 * Also serves as a base class for AJAX-enabled buffers.
30 initialize: function(dataTable, options) {
32 this.updateInProgress = false;
34 this.rcvdRowCount = false; // true if an eof element was included in the last response
35 this.foundRowCount = false; // true if a response is ever received with eof true
37 this.rowcntContent = "";
41 canFilter : true, // does buffer object support filtering?
42 isEncoded : true, // is the data received via ajax html encoded?
43 acceptStyle : false, // copy style from original/ajax data?
44 canRefresh : false // should "refresh" be shown on filter menu?
46 Rico.extend(this.options, options || {});
48 this.loadRowsFromTable(dataTable,this.options.fixedHdrRows);
49 dataTable.parentNode.removeChild(dataTable); // delete the data once it has been loaded
53 registerGrid: function(liveGrid) {
54 this.liveGrid = liveGrid;
57 setTotalRows: function( newTotalRows ) {
58 if (typeof(newTotalRows)!='number') newTotalRows=this.size;
59 if (this.totalRows == newTotalRows) return;
60 this.totalRows = newTotalRows;
61 if (!this.liveGrid) return;
62 Rico.log("setTotalRows, newTotalRows="+newTotalRows);
63 switch (this.liveGrid.sizeTo) {
65 this.liveGrid.resizeWindow();
68 this.liveGrid.setPageSize(newTotalRows);
71 this.liveGrid.updateHeightDiv();
76 loadRowsFromTable: function(tableElement,firstRow) {
78 var trs = tableElement.getElementsByTagName("tr");
79 for ( var i=firstRow || 0; i < trs.length; i++ ) {
81 var cells = trs[i].getElementsByTagName("td");
82 for ( var j=0; j < cells.length ; j++ )
83 row[j]=cells[j].innerHTML;
86 this.loadRows(newRows);
89 loadRowsFromArray: function(array2D) {
90 for ( var i=0; i < array2D.length; i++ ) {
91 for ( var j=0; j < array2D[i].length ; j++ ) {
92 array2D[i][j]=array2D[i][j].toString();
95 this.loadRows(array2D);
98 loadRows: function(jstable) {
99 this.baseRows = jstable;
101 this.size = this.baseRows.length;
104 dom2jstable: function(rowsElement) {
105 Rico.log('dom2jstable: encoded='+this.options.isEncoded);
107 var trs = rowsElement.getElementsByTagName("tr");
108 for ( var i=0; i < trs.length; i++ ) {
110 var cells = trs[i].getElementsByTagName("td");
111 for ( var j=0; j < cells.length ; j++ )
112 row[j]=Rico.getContentAsString(cells[j],this.options.isEncoded);
118 _blankRow: function() {
120 for (var i=0; i<this.liveGrid.columns.length; i++) {
126 deleteRows: function(rowIndex,cnt) {
127 this.baseRows.splice(rowIndex,typeof(cnt)=='number' ? cnt : 1);
128 this.liveGrid.isPartialBlank=true;
129 this.size=this.baseRows.length;
132 insertRow: function(beforeRowIndex) {
133 var r=this._blankRow();
134 this.baseRows.splice(beforeRowIndex,0,r);
135 this.size=this.baseRows.length;
136 this.liveGrid.isPartialBlank=true;
137 if (this.startPos < 0) this.startPos=0;
141 appendRows: function(cnt) {
143 for (var i=0; i<cnt; i++) {
144 var r=this._blankRow();
145 this.baseRows.push(r);
148 this.size=this.baseRows.length;
149 this.liveGrid.isPartialBlank=true;
150 if (this.startPos < 0) this.startPos=0;
154 sortFunc: function(coltype) {
157 case 'number': return function(a,b) { return self._sortNumeric(a,b); };
158 case 'control':return function(a,b) { return self._sortControl(a,b); };
159 default: return function(a,b) { return self._sortAlpha(a,b); };
163 sortBuffer: function(colnum) {
164 if (!this.baseRows) {
165 this.delayedSortCol=colnum;
168 this.liveGrid.showMsg(Rico.getPhraseById("sorting"));
169 this.sortColumn=colnum;
170 var col=this.liveGrid.columns[colnum];
171 this.getValFunc=col._sortfunc;
172 this.baseRows.sort(this.sortFunc(col.format.type));
173 if (col.getSortDirection()=='DESC') this.baseRows.reverse();
176 _sortAlpha: function(a,b) {
177 var aa = this.sortColumn<a.length ? Rico.getInnerText(a[this.sortColumn]) : '';
178 var bb = this.sortColumn<b.length ? Rico.getInnerText(b[this.sortColumn]) : '';
179 if (aa==bb) return 0;
180 if (aa<bb) return -1;
184 _sortNumeric: function(a,b) {
185 var aa = this.sortColumn<a.length ? this.nan2zero(Rico.getInnerText(a[this.sortColumn])) : 0;
186 var bb = this.sortColumn<b.length ? this.nan2zero(Rico.getInnerText(b[this.sortColumn])) : 0;
190 nan2zero: function(n) {
191 if (typeof(n)=='string') n=parseFloat(n);
192 return isNaN(n) || typeof(n)=='undefined' ? 0 : n;
195 _sortControl: function(a,b) {
196 var aa = this.sortColumn<a.length ? Rico.getInnerText(a[this.sortColumn]) : '';
197 var bb = this.sortColumn<b.length ? Rico.getInnerText(b[this.sortColumn]) : '';
198 if (this.getValFunc) {
199 aa=this.getValFunc(aa);
200 bb=this.getValFunc(bb);
202 if (aa==bb) return 0;
203 if (aa<bb) return -1;
217 isInRange: function(position) {
218 var lastRow=Math.min(this.totalRows, position + this.liveGrid.pageSize);
219 return (position >= this.startPos) && (lastRow <= this.endPos()); // && (this.size != 0);
223 return this.startPos + this.rows.length;
226 fetch: function(offset) {
227 Rico.log('fetch '+this.liveGrid.tableId+': offset='+offset);
230 this.rcvdRowCount = true;
231 this.foundRowCount = true;
232 if (offset < 0) offset=0;
233 this.liveGrid.refreshContents(offset);
238 * @return a 2D array of buffer data representing the rows that are currently visible on the grid
240 visibleRows: function() {
241 return this.rows.slice(this.windowStart,this.windowEnd);
244 setWindow: function(startrow, endrow) {
245 this.windowStart = startrow - this.startPos; // position in the buffer of first visible row
246 Rico.log('setWindow '+this.liveGrid.tableId+': '+startrow+', '+endrow+', newstart='+this.windowStart);
247 this.windowEnd = Math.min(endrow,this.size); // position in the buffer of last visible row containing data+1
248 this.windowPos = startrow; // position in the dataset of first visible row
252 * @return true if bufRow is currently visible in the grid
254 isVisible: function(bufRow) {
255 return bufRow < this.rows.length && bufRow >= this.windowStart && bufRow < this.windowEnd;
259 * takes a window row index and returns the corresponding buffer row index
261 bufferRow: function(windowRow) {
262 return this.windowStart+windowRow;
266 * @return buffer cell at the specified visible row/col index
268 getWindowCell: function(windowRow,col) {
269 var bufrow=this.bufferRow(windowRow);
270 return this.isVisible(bufrow) && col < this.rows[bufrow].length ? this.rows[bufrow][col] : null;
273 getWindowStyle: function(windowRow,col) {
274 var bufrow=this.bufferRow(windowRow);
275 return this.attr && this.isVisible(bufrow) && this.attr[bufrow] && col < this.attr[bufrow].length ? this.attr[bufrow][col] : '';
278 getWindowValue: function(windowRow,col) {
279 return this.getWindowCell(windowRow,col);
282 setWindowValue: function(windowRow,col,newval) {
283 var bufrow=this.bufferRow(windowRow);
284 if (bufrow >= this.windowEnd) return false;
285 return this.setValue(bufrow,col,newval);
288 getCell: function(bufRow,col) {
289 return bufRow < this.size ? this.rows[bufRow][col] : null;
292 getValue: function(bufRow,col) {
293 return this.getCell(bufRow,col);
296 setValue: function(bufRow,col,newval,newstyle) {
297 if (bufRow>=this.size) return false;
298 if (!this.rows[bufRow][col]) this.rows[bufRow][col]={};
299 this.rows[bufRow][col]=newval;
300 if (this.options.acceptStyle && typeof newstyle=='string') {
301 if (!this.attr) this.attr=[];
302 if (!this.attr[bufRow]) this.attr[bufRow]=[];
303 this.attr[bufRow][col]=newstyle;
305 if (!this.modified[bufRow]) this.modified[bufRow]=[];
306 this.modified[bufRow][col]=true;
310 getRows: function(start, count) {
311 var begPos = start - this.startPos;
312 var endPos = Math.min(begPos + count,this.size);
314 for ( var i=begPos; i < endPos; i++ ) {
315 results.push(this.rows[i]);
320 applyFilters: function() {
321 var newRows=[],re=[];
322 var r,c,n,i,showRow,filtercnt;
323 var cols=this.liveGrid.columns;
324 for (n=0,filtercnt=0; n<cols.length; n++) {
326 if (c.filterType == Rico.ColumnConst.UNFILTERED) continue;
328 if (c.filterOp=='LIKE') re[n]=new RegExp(c.filterValues[0],'i');
330 Rico.log('applyFilters: # of filters='+filtercnt);
332 this.rows = this.baseRows;
334 for (r=0; r<this.baseRows.length; r++) {
336 for (n=0; n<cols.length && showRow; n++) {
338 if (c.filterType == Rico.ColumnConst.UNFILTERED) continue;
339 switch (c.filterOp) {
341 showRow=re[n].test(this.baseRows[r][n]);
344 showRow=this.baseRows[r][n]==c.filterValues[0];
347 for (i=0; i<c.filterValues.length && showRow; i++)
348 showRow=this.baseRows[r][n]!=c.filterValues[i];
351 if (c.format.type=='number')
352 showRow=this.nan2zero(this.baseRows[r][n])<=this.nan2zero(c.filterValues[0]);
354 showRow=this.baseRows[r][n]<=c.filterValues[0];
357 if (c.format.type=='number')
358 showRow=this.nan2zero(this.baseRows[r][n])>=this.nan2zero(c.filterValues[0]);
360 showRow=this.baseRows[r][n]>=c.filterValues[0];
363 showRow=this.baseRows[r][n]=='';
366 showRow=this.baseRows[r][n]!='';
370 if (showRow) newRows.push(this.baseRows[r]);
374 this.rowcntContent = this.size = this.rows.length;
377 printAll: function() {
378 this.liveGrid.showMsg(Rico.getPhraseById('exportInProgress'));
379 Rico.runLater(10,this,'_printAll'); // allow message to paint
383 * Support function for printAll()
385 _printAll: function() {
386 this.liveGrid.exportStart();
387 this.exportBuffer(this.getRows(0,this.totalRows));
388 this.liveGrid.exportFinish();
392 * Copies visible rows to a new window as a simple html table.
394 printVisible: function() {
395 this.liveGrid.showMsg(Rico.getPhraseById('exportInProgress'));
396 Rico.runLater(10,this,'_printVisible'); // allow message to paint
399 _printVisible: function() {
400 this.liveGrid.exportStart();
401 this.exportBuffer(this.visibleRows());
402 this.liveGrid.exportFinish();
406 * Send all rows to print/export window
408 exportBuffer: function(rows,startPos) {
409 var r,c,v,col,exportText;
410 Rico.log("exportBuffer: "+rows.length+" rows");
411 var exportStyles=this.liveGrid.getExportStyles(this.liveGrid.tbody[0]);
413 var totalcnt=startPos || 0;
414 var cols=this.liveGrid.columns;
415 for (c=0; c<cols.length; c++) {
416 if (cols[c].visible) tdstyle[c]=this.liveGrid.exportStyle(cols[c].cell(0),exportStyles); // assumes row 0 style applies to all rows
418 for(r=0; r < rows.length; r++) {
420 for (c=0; c<cols.length; c++) {
421 if (!cols[c].visible) continue;
423 col.expStyle=tdstyle[c];
424 v=col._export(rows[r][c],rows[r]);
425 if (v=='') v=' ';
426 exportText+="<td style='"+col.expStyle+"'>"+v+"</td>";
428 this.liveGrid.exportRows.push(exportText);
430 if (totalcnt % 10 == 0) window.status=Rico.getPhraseById('exportStatus',totalcnt);
437 // Rico.LiveGrid -----------------------------------------------------
439 Rico.LiveGrid = function(tableId, buffer, options) {
440 this.initialize(tableId, buffer, options);
444 * @lends Rico.LiveGrid#
445 * @property tableId id string for this grid
446 * @property options the options object passed to the constructor extended with defaults
447 * @property buffer the buffer object containing the data for this grid
448 * @property columns array of {@link Rico.LiveGridColumn} objects
450 Rico.LiveGrid.prototype = {
452 * @class Buffered LiveGrid component
453 * @extends Rico.GridCommon
456 initialize: function( tableId, buffer, options ) {
457 Rico.extend(this, Rico.GridCommon);
458 Rico.extend(this, Rico.LiveGridMethods);
460 this.tableId = tableId;
461 this.buffer = buffer;
462 this.actionId='_action_'+tableId;
463 Rico.setDebugArea(tableId+"_debugmsgs"); // if used, this should be a textarea
465 Rico.extend(this.options, {
466 visibleRows : -3, // -1 or 'window'=size grid to client window; -2 or 'data'=size grid to min(window,data); -3 or 'body'=size so body does not have a scrollbar; -4 or 'parent'=size to parent element (e.g. if grid is inside a div)
468 offset : 0, // first row to be displayed
469 prefetchBuffer : true, // load table on page load?
472 canSortDefault : true, // can be overridden in the column specs
473 canFilterDefault : buffer.options.canFilter, // can be overridden in the column specs
474 canHideDefault : true, // can be overridden in the column specs
476 // highlight & selection parameters
477 highlightElem : 'none',// what gets highlighted/selected (cursorRow, cursorCell, menuRow, menuCell, selection, or none)
478 highlightSection : 3, // which section gets highlighted (frozen=1, scrolling=2, all=3, none=0)
479 highlightMethod : 'class', // outline, class, both (outline is less CPU intensive on the client)
480 highlightClass : Rico.theme.gridHighlightClass || 'ricoLG_selection',
482 // export/print parameters
483 maxPrint : 5000, // max # of rows that can be printed/exported, 0=disable print/export feature
485 // heading parameters
486 headingSort : 'link', // link: make headings a link that will sort column, hover: make headings a hoverset, none: events on headings are disabled
487 hdrIconsFirst : true // true: put sort & filter icons before header text, false: after
490 // sortCol: initial sort column
493 this.options.sortHandler = function() { self.sortHandler(); };
494 this.options.filterHandler = function() { self.filterHandler(); };
495 this.options.onRefreshComplete = function(firstrow,lastrow) { self.bookmarkHandler(firstrow,lastrow); };
496 this.options.rowOverHandler = Rico.eventHandle(this,'rowMouseOver');
497 this.options.mouseDownHandler = Rico.eventHandle(this,'selectMouseDown');
498 this.options.mouseOverHandler = Rico.eventHandle(this,'selectMouseOver');
499 this.options.mouseUpHandler = Rico.eventHandle(this,'selectMouseUp');
500 Rico.extend(this.options, options || {});
502 switch (typeof this.options.visibleRows) {
504 this.sizeTo=this.options.visibleRows;
505 switch (this.options.visibleRows) {
506 case 'data': this.options.visibleRows=-2; break;
507 case 'body': this.options.visibleRows=-3; break;
508 case 'parent': this.options.visibleRows=-4; break;
509 case 'datamax':this.options.visibleRows=-5; break;
510 default: this.options.visibleRows=-1; break;
514 switch (this.options.visibleRows) {
515 case -1: this.sizeTo='window'; break;
516 case -2: this.sizeTo='data'; break;
517 case -3: this.sizeTo='body'; break;
518 case -4: this.sizeTo='parent'; break;
519 case -5: this.sizeTo='datamax'; break;
520 default: this.sizeTo='fixed'; break;
525 this.options.visibleRows=-3;
528 this.highlightEnabled=this.options.highlightSection>0;
531 if (this.headerColCnt==0) {
532 alert('ERROR: no columns found in "'+this.tableId+'"');
535 this.createColumnArray('LiveGridColumn');
536 if (this.options.headingSort=='hover')
537 this.createHoverSet();
539 this.bookmark=document.getElementById(this.tableId+"_bookmark");
542 if (this.buffer.options.canFilter && this.options.AutoFilter)
543 filterUIrow=this.addHeadingRow('ricoLG_FilterRow');
544 this.createDataCells(this.options.visibleRows);
545 if (this.pageSize == 0) return;
546 this.buffer.registerGrid(this);
547 if (this.buffer.setBufferSize) this.buffer.setBufferSize(this.pageSize);
548 this.scrollTimeout = null;
549 this.lastScrollPos = 0;
550 this.attachMenuEvents();
552 this.setSortUI( this.options.sortCol, this.options.sortDir );
554 if (this.listInvisible().length==this.columns.length)
555 this.columns[0].showColumn();
557 this.scrollDiv.style.display="";
558 if (this.buffer.totalRows>0)
559 this.updateHeightDiv();
560 if (this.options.prefetchBuffer) {
561 if (this.bookmark) this.bookmark.innerHTML = Rico.getPhraseById('bookmarkLoading');
562 if (this.options.canFilterDefault && this.options.getQueryParms)
563 this.checkForFilterParms();
564 this.scrollToRow(this.options.offset);
565 this.buffer.fetch(this.options.offset);
567 if (filterUIrow >= 0)
568 this.createFilters(filterUIrow);
569 this.scrollEventFunc=Rico.eventHandle(this,'handleScroll');
570 this.wheelEventFunc=Rico.eventHandle(this,'handleWheel');
571 this.wheelEvent=(Rico.isIE || Rico.isOpera || Rico.isWebKit) ? 'mousewheel' : 'DOMMouseScroll';
572 if (this.options.offset && this.options.offset < this.buffer.totalRows)
573 Rico.runLater(50,this,'scrollToRow',this.options.offset); // Safari requires a delay
575 this.setHorizontalScroll();
576 Rico.log("setHorizontalScroll done");
577 if (this.options.windowResize)
578 Rico.runLater(100,this,'pluginWindowResize');
579 Rico.log("initialize complete for "+this.tableId);
580 //alert('clientLeft='+this.scrollDiv.clientLeft);
581 if (this.direction=='rtl' && (!Rico.isWebKit || this.scrollDiv.clientLeft > 0)) {
582 this.scrollTab.style.right='0px';
584 this.scrollTab.style.left='0px';
585 Rico.setStyle(this.tabs[1], {'float': 'left'});
591 Rico.LiveGridMethods = {
592 /** @lends Rico.LiveGrid# */
594 createHoverSet: function() {
596 for( var c=0; c < this.headerColCnt; c++ ) {
597 if (this.columns[c].sortable) {
\r
598 hdrs.push(this.columns[c].hdrCellDiv);
601 this.hoverSet = new Rico.HoverSet(hdrs);
604 checkForFilterParms: function() {
605 var s=window.location.search;
606 if (s.charAt(0)=='?') s=s.substring(1);
607 var pairs = s.split('&');
608 for (var i=0; i<pairs.length; i++) {
609 if (pairs[i].match(/^f\[\d+\]/)) {
610 this.buffer.options.requestParameters.push(pairs[i]);
616 * Refreshes a detail grid from a master grid
617 * @returns row index on master table on success, -1 otherwise
619 drillDown: function(e,masterColNum,detailColNum) {
620 var cell=Rico.eventElement(e || window.event);
621 cell=Rico.getParentByTagName(cell,'div','ricoLG_cell');
622 if (!cell) return -1;
623 var idx=this.winCellIndex(cell);
624 if (idx.row >= this.buffer.totalRows) return -1
626 this.menuIdx=idx; // ensures selection gets cleared when menu is displayed
628 var drillValue=this.buffer.getWindowCell(idx.row,masterColNum);
629 for (var i=3; i<arguments.length; i++)
630 arguments[i].setDetailFilter(detailColNum,drillValue);
635 * set filter on a detail grid that is in a master-detail relationship
637 setDetailFilter: function(colNumber,filterValue) {
638 var c=this.columns[colNumber];
639 c.format.ColData=filterValue;
640 c.setSystemFilter('EQ',filterValue);
644 * Create one table for frozen columns and one for scrolling columns.
645 * Also create div's to contain them.
646 * @returns true on success
648 createTables: function() {
649 var insertloc,hdrSrc,i;
650 var table = document.getElementById(this.tableId) || document.getElementById(this.tableId+'_outerDiv');
651 if (!table) return false;
652 if (table.tagName.toLowerCase()=='table') {
653 var theads=table.getElementsByTagName("thead");
654 if (theads.length == 1) {
655 Rico.log("createTables: using thead section, id="+this.tableId);
656 if (this.options.ColGroupsOnTabHdr && this.options.ColGroups) {
657 var r=theads[0].insertRow(0);
658 this.insertPanelNames(r, 0, this.options.frozenColumns, 'ricoFrozen');
659 this.insertPanelNames(r, this.options.frozenColumns, this.options.columnSpecs.length);
661 hdrSrc=theads[0].rows;
663 Rico.log("createTables: using tbody section, id="+this.tableId);
664 hdrSrc=new Array(table.rows[0]);
667 } else if (this.options.columnSpecs.length > 0) {
668 if (!table.id.match(/_outerDiv$/)) insertloc=table;
669 Rico.log("createTables: inserting at "+table.tagName+", id="+this.tableId);
671 alert("ERROR!\n\nUnable to initialize '"+this.tableId+"'\n\nLiveGrid terminated");
676 this.scrollContainer = this.createDiv("scrollContainer",this.structTabLR);
677 this.scrollContainer.appendChild(this.scrollDiv); // move scrollDiv
678 this.scrollTab = this.createDiv("scrollTab",this.scrollContainer);
679 this.shadowDiv = this.createDiv("shadow",this.scrollDiv);
680 this.shadowDiv.style.direction='ltr'; // avoid FF bug
681 this.scrollDiv.style.display="none";
682 this.scrollDiv.scrollTop=0;
683 if (this.options.highlightMethod!='class') {
684 this.highlightDiv=[];
685 switch (this.options.highlightElem) {
688 this.highlightDiv[0] = this.createDiv("highlight",this.outerDiv);
689 this.highlightDiv[0].style.display="none";
693 for (i=0; i<2; i++) {
694 this.highlightDiv[i] = this.createDiv("highlight",i==0 ? this.frozenTabs : this.scrollTab);
695 this.highlightDiv[i].style.display="none";
696 this.highlightDiv[i].id+=i;
700 // create one div for each side of the rectangle
701 var parentDiv=this.options.highlightSection==1 ? this.frozenTabs : this.scrollTab;
702 for (i=0; i<4; i++) {
703 this.highlightDiv[i] = this.createDiv("highlight",parentDiv);
704 this.highlightDiv[i].style.display="none";
705 this.highlightDiv[i].style.overflow="hidden";
706 this.highlightDiv[i].id+=i;
707 this.highlightDiv[i].style[i % 2==0 ? 'height' : 'width']="0px";
714 for (i=0; i<3; i++) {
715 this.tabs[i] = document.createElement("table");
716 this.tabs[i].className = (i < 2) ? 'ricoLG_table' : 'ricoLG_scrollTab';
717 this.tabs[i].border=0;
718 this.tabs[i].cellPadding=0;
719 this.tabs[i].cellSpacing=0;
720 this.tabs[i].id = this.tableId+"_tab"+i;
723 for (i=0; i<2; i++) {
724 this.thead[i]=this.tabs[i].createTHead();
725 this.thead[i].className='ricoLG_top';
726 if (Rico.theme.gridheader) Rico.addClass(this.thead[i],Rico.theme.gridheader);
729 for (i=0; i<2; i++) {
730 this.tbody[i]=Rico.getTBody(this.tabs[i==0?0:2]);
731 this.tbody[i].className='ricoLG_bottom';
732 if (Rico.theme.gridcontent) Rico.addClass(this.tbody[i],Rico.theme.gridcontent);
733 this.tbody[i].insertRow(-1);
735 this.frozenTabs.appendChild(this.tabs[0]);
736 this.innerDiv.appendChild(this.tabs[1]);
737 this.scrollTab.appendChild(this.tabs[2]);
738 if (insertloc) insertloc.parentNode.insertBefore(this.outerDiv,insertloc);
740 this.headerColCnt = this.getColumnInfo(hdrSrc);
741 this.loadHdrSrc(hdrSrc);
743 this.createHdr(0,0,this.options.frozenColumns);
744 this.createHdr(1,this.options.frozenColumns,this.options.columnSpecs.length);
745 if (this.options.ColGroupsOnTabHdr && this.options.ColGroups) {
746 this.insertPanelNames(this.thead[0].insertRow(0), 0, this.options.frozenColumns);
747 this.insertPanelNames(this.thead[1].insertRow(0), this.options.frozenColumns, this.options.columnSpecs.length);
750 this.headerColCnt = this.getColumnInfo(this.thead[i].rows);
752 for( var c=0; c < this.headerColCnt; c++ )
753 this.tbody[c<this.options.frozenColumns ? 0 : 1].rows[0].insertCell(-1);
754 if (insertloc) table.parentNode.removeChild(table);
755 Rico.log('createTables end');
759 createDataCells: function(visibleRows) {
760 if (visibleRows < 0) {
761 for (var i=0; i<this.options.minPageRows; i++)
762 this.appendBlankRow();
764 this.autoAppendRows(this.remainingHt());
766 for( var r=0; r < visibleRows; r++ )
767 this.appendBlankRow();
769 var s=this.options.highlightSection;
770 if (s & 1) this.attachHighlightEvents(this.tbody[0]);
771 if (s & 2) this.attachHighlightEvents(this.tbody[1]);
775 * @param colnum column number
776 * @return id string for a filter element
778 filterId: function(colnum) {
779 return 'RicoFilter_'+this.tableId+'_'+colnum;
783 * Create filter elements in heading
784 * Reads this.columns[].filterUI to determine type of filter element for each column (t=text box, s=select list, c=custom)
785 * @param r heading row where filter elements will be placed
787 createFilters: function(r) {
788 for( var c=0; c < this.headerColCnt; c++ ) {
789 var col=this.columns[c];
791 if (typeof fmt.filterUI!='string') continue;
792 var cell=this.hdrCells[r][c].cell;
793 var field,name=this.filterId(c);
\r
794 var divs=cell.getElementsByTagName('div');
795 // copy text alignment from data cell
796 var align=Rico.getStyle(this.cell(0,c),'textAlign');
797 divs[1].style.textAlign=align;
798 switch (fmt.filterUI.charAt(0)) {
801 field=Rico.createFormField(divs[1],'input',Rico.inputtypes.search ? 'search' : 'text',name,'RicoFilter');
802 var size=fmt.filterUI.match(/\d+/);
803 field.maxLength=fmt.Length || 50;
\r
804 field.size=size ? parseInt(size,10) : 10;
805 if (field.type != 'search') divs[1].appendChild(Rico.clearButton(Rico.eventHandle(col,'filterClear')));
806 if (col.filterType==Rico.ColumnConst.USERFILTER && col.filterOp=='LIKE') {
807 var v=col.filterValues[0];
808 if (v.charAt(0)=='*') v=v.substr(1);
809 if (v.slice(-1)=='*') v=v.slice(0,-1);
813 Rico.eventBind(field,'keyup',Rico.eventHandle(col,'filterKeypress'),false);
814 col.filterField=field;
\r
820 field=Rico.createFormField(divs[1],'select',null,name,'RicoFilter');
\r
821 Rico.addSelectOption(field,this.options.FilterAllToken,Rico.getPhraseById("filterAll"));
\r
822 col.filterField=field;
824 Rico.extend(options, this.buffer.ajaxOptions);
825 var colnum=typeof(fmt.filterCol)=='number' ? fmt.filterCol : c;
826 options.parameters = this.buffer.formQueryHashXML(0,-1);
827 options.parameters.distinct = colnum;
828 options.onComplete = this.filterValuesUpdateFunc(c);
829 new Rico.ajaxRequest(this.buffer.dataSource, options);
832 field=Rico.createFormField(divs[1],'select',null,name,'RicoFilter');
833 Rico.addSelectOption(field,this.options.FilterAllToken,Rico.getPhraseById("filterAll"));
834 col.filterField=field;
835 var choices=fmt.filterUI.length == 1 ? "-0+" : fmt.filterUI.substr(1);
836 if (choices.indexOf("-") >= 0) Rico.addSelectOption(field,"LT0","< 0");
837 if (choices.indexOf("0") >= 0) Rico.addSelectOption(field,"EQ0","= 0");
838 if (choices.indexOf("+") >= 0) Rico.addSelectOption(field,"GT0","> 0");
839 Rico.eventBind(col.filterField,'change',Rico.eventHandle(col,'nFilterChange'));
843 if (typeof col._createFilters == 'function')
844 col._createFilters(divs[1], name);
848 this.initFilterImage(r);
851 filterValuesUpdateFunc: function(colnum) {
853 return function (request) { self.filterValuesUpdate(colnum,request); };
857 * update select list filter with values in AJAX response
858 * @returns true on success
860 filterValuesUpdate: function(colnum,request) {
861 var response = request.responseXML.getElementsByTagName("ajax-response");
862 Rico.log("filterValuesUpdate: "+request.status);
863 if (response == null || response.length != 1) return false;
864 response=response[0];
865 var error = response.getElementsByTagName('error');
866 if (error.length > 0) {
867 Rico.log("Data provider returned an error:\n"+Rico.getContentAsString(error[0],this.buffer.isEncoded));
868 alert(Rico.getPhraseById("requestError",Rico.getContentAsString(error[0],this.buffer.isEncoded)));
871 response=response.getElementsByTagName('response')[0];
\r
872 var rowsElement = response.getElementsByTagName('rows')[0];
\r
873 var col=this.columns[parseInt(colnum,10)];
874 var rows = this.buffer.dom2jstable(rowsElement);
\r
875 var found = !col.filterValues || !col.filterValues.length;
876 if (!found) for (var i=0; i<rows.length; i++) {
877 if (rows[i][0] == col.filterValues[0]) {
883 col.setUnfiltered(true);
884 if (this.options.filterHandler)
885 this.options.filterHandler();
888 if (col.filterType==Rico.ColumnConst.USERFILTER && col.filterOp=='EQ') v=col.filterValues[0];
889 Rico.log('filterValuesUpdate: col='+colnum+' rows='+rows.length);
890 switch (col.format.filterUI.charAt(0)) {
893 col.mFilter = document.body.appendChild(document.createElement("div"));
894 col.mFilter.className = 'ricoLG_mFilter'
895 Rico.hide(col.mFilter);
896 var contentDiv = col.mFilter.appendChild(document.createElement("div"));
897 contentDiv.className = 'ricoLG_mFilter_content'
898 var buttonDiv = col.mFilter.appendChild(document.createElement("div"));
899 buttonDiv.className = 'ricoLG_mFilter_button'
900 col.mFilterButton=buttonDiv.appendChild(document.createElement("button"));
901 col.mFilterButton.innerHTML=Rico.getPhraseById("apply");
902 var eventName=Rico.isWebKit ? 'mousedown' : 'click';
903 Rico.eventBind(col.filterField,eventName,Rico.eventHandle(col,'mFilterSelectClick'));
904 Rico.eventBind(col.mFilterButton,'click',Rico.eventHandle(col,'mFilterFinish'));
905 //col.filterField.options[0].text=$('AllLabel').innerHTML;
906 tab = contentDiv.appendChild(document.createElement("table"));
910 //tbody=(tab.tBodies.length==0) ? tab.appendChild(document.createElement("tbody")) : tab.tBodies[0];
911 var baseId=this.filterId(colnum)+'_';
912 this.createMFilterItem(tab,this.options.FilterAllToken,Rico.getPhraseById("filterAll"),baseId+'all',Rico.eventHandle(col,'mFilterAllClick'));
913 var handle=Rico.eventHandle(col,'mFilterOtherClick')
914 for (var i=0; i<rows.length; i++) {
915 if (rows[i].length>0) {
917 this.createMFilterItem(tab,c,c || Rico.getPhraseById("filterBlank"),baseId+i,handle);
920 col.mFilterInputs=contentDiv.getElementsByTagName('input');
921 col.mFilterLabels=contentDiv.getElementsByTagName('label');
922 col.mFilterFocus=col.mFilterInputs.length ? col.mFilterInputs[0] : col.mFilterButton;
927 for (var i=0; i<rows.length; i++) {
928 if (rows[i].length>0) {
931 if (col._getdesc) ctrans = col._getdesc(c);
932 opt=Rico.addSelectOption(col.filterField,c,ctrans || Rico.getPhraseById("filterBlank"));
933 if (col.filterType==Rico.ColumnConst.USERFILTER && c==v) opt.selected=true;
936 Rico.eventBind(col.filterField,'change',Rico.eventHandle(col,'filterChange'));
942 createMFilterItem: function(table,code,description,id,eventHandle) {
943 var tr=table.insertRow(-1);
945 if (tr.rowIndex % 2 == 1) tr.className='ricoLG_mFilter_oddrow';
946 var td1=tr.insertCell(-1)
947 var td2=tr.insertCell(-1)
948 var field=Rico.createFormField(td1,'input','checkbox',id);
951 var label = td2.appendChild(document.createElement("label"));
953 label.innerHTML=description;
954 Rico.eventBind(field,'click',eventHandle);
957 unplugHighlightEvents: function() {
958 var s=this.options.highlightSection;
959 if (s & 1) this.detachHighlightEvents(this.tbody[0]);
960 if (s & 2) this.detachHighlightEvents(this.tbody[1]);
964 * place panel names on first row of grid header (used by LiveGridForms)
966 insertPanelNames: function(r,start,limit,cellClass) {
967 Rico.log('insertPanelNames: start='+start+' limit='+limit);
968 r.className='ricoLG_hdg';
969 var lastIdx=-1, span, newCell=null, spanIdx=0;
970 for( var c=start; c < limit; c++ ) {
971 if (lastIdx == this.options.columnSpecs[c].ColGroupIdx) {
974 if (newCell) newCell.colSpan=span;
975 newCell = r.insertCell(-1);
976 if (cellClass) newCell.className=cellClass;
978 lastIdx=this.options.columnSpecs[c].ColGroupIdx;
979 newCell.innerHTML=this.options.ColGroups[lastIdx];
982 if (newCell) newCell.colSpan=span;
986 * create grid header for table i (if none was provided)
988 createHdr: function(i,start,limit) {
989 Rico.log('createHdr: i='+i+' start='+start+' limit='+limit);
990 var mainRow = this.thead[i].insertRow(-1);
991 mainRow.id=this.tableId+'_tab'+i+'h_main';
992 mainRow.className='ricoLG_hdg';
993 for( var c=start; c < limit; c++ ) {
994 var newCell = mainRow.insertCell(-1);
995 newCell.innerHTML=this.options.columnSpecs[c].Hdg;
1000 * move header cells in original table to grid
1002 loadHdrSrc: function(hdrSrc) {
1003 var i,h,c,r,newrow,cells;
1004 Rico.log('loadHdrSrc start');
1005 for (i=0; i<2; i++) {
1006 for (r=0; r<hdrSrc.length; r++) {
1007 newrow = this.thead[i].insertRow(-1);
1008 newrow.className='ricoLG_hdg '+this.tableId+'_hdg'+r;
1011 if (hdrSrc.length==1) {
1012 cells=hdrSrc[0].cells;
1013 for (c=0; cells.length > 0; c++)
1014 this.thead[c<this.options.frozenColumns ? 0 : 1].rows[0].appendChild(cells[0]);
1016 for (r=0; r<hdrSrc.length; r++) {
1017 cells=hdrSrc[r].cells;
1018 for (c=0,h=0; cells.length > 0; c++) {
1019 if (Rico.hasClass(cells[0],'ricoFrozen')) {
1020 if (r==this.headerRowIdx) this.options.frozenColumns=c+1;
1024 this.thead[h].rows[r].appendChild(cells[0]);
1028 Rico.log('loadHdrSrc end');
1034 sizeDivs: function() {
1035 Rico.log('sizeDivs: '+this.tableId);
1036 //this.cancelMenu();
1038 this.baseSizeDivs();
1039 var firstVisible=this.firstVisible();
1040 if (this.pageSize == 0 || firstVisible < 0) return;
1041 var totRowHt=this.columns[firstVisible].dataColDiv.offsetHeight;
1042 this.rowHeight = Math.round(totRowHt/this.pageSize);
1043 var scrHt=this.dataHt;
1044 if (this.scrTabWi0 == this.scrTabWi) {
1045 // no scrolling columns - horizontal scroll bar not needed
1046 this.innerDiv.style.height=(this.hdrHt+1)+'px';
1047 this.scrollDiv.style.overflowX='hidden';
1049 this.scrollDiv.style.overflowX='scroll';
1050 scrHt+=this.options.scrollBarWidth;
1052 this.scrollDiv.style.height=scrHt+'px';
1053 this.innerDiv.style.width=(this.scrWi)+'px';
1054 this.scrollTab.style.width=(this.scrWi-this.options.scrollBarWidth)+'px';
1055 //this.resizeDiv.style.height=this.frozenTabs.style.height=this.innerDiv.style.height=(this.hdrHt+this.dataHt+1)+'px';
1056 this.resizeDiv.style.height=(this.hdrHt+this.dataHt+1)+'px';
1057 Rico.log('sizeDivs scrHt='+scrHt+' innerHt='+this.innerDiv.style.height+' rowHt='+this.rowHeight+' pageSize='+this.pageSize);
1058 var pad=(this.scrWi-this.scrTabWi < this.options.scrollBarWidth) ? 2 : 0;
1059 this.shadowDiv.style.width=(this.scrTabWi+pad)+'px';
1060 this.outerDiv.style.height=(this.hdrHt+scrHt)+'px';
1061 this.setHorizontalScroll();
1064 setHorizontalScroll: function() {
1065 var newLeft=(-this.scrollDiv.scrollLeft)+'px';
1066 this.tabs[1].style.marginLeft=newLeft;
1067 this.tabs[2].style.marginLeft=newLeft;
1070 remainingHt: function() {
1071 var tabHt=this.outerDiv.offsetHeight;
1072 var winHt=Rico.windowHeight();
1073 var margin=Rico.isIE ? 15 : 10;
1074 // if there is a horizontal scrollbar take it into account
1075 if (!Rico.isIE && window.frameElement && window.frameElement.scrolling=='yes' && this.sizeTo!='parent') margin+=this.options.scrollBarWidth;
1076 switch (this.sizeTo) {
1078 var divTop=Rico.cumulativeOffset(this.outerDiv).top;
1079 Rico.log("remainingHt/window, winHt="+winHt+' tabHt='+tabHt+' gridY='+divTop);
1080 return winHt-divTop-tabHt-margin; // allow for scrollbar and some margin
1082 var offset=this.offsetFromParent(this.outerDiv);
1083 if (Rico.isIE) Rico.hide(this.outerDiv);
1084 var parentHt=this.outerDiv.parentNode.clientHeight;
1085 if (Rico.isIE) Rico.show(this.outerDiv);
1086 Rico.log("remainingHt/parent, parentHt="+parentHt+' offset='+offset+' tabHt='+tabHt);
1087 return parentHt-tabHt-offset-margin;
1090 var bodyHt=Rico.isIE ? document.body.scrollHeight : document.body.offsetHeight;
1091 //alert("remainingHt\n document.height="+document.height+"\n body.offsetHeight="+document.body.offsetHeight+"\n body.scrollHeight="+document.body.scrollHeight+"\n documentElement.scrollHeight="+document.documentElement.scrollHeight);
1092 var remHt=winHt-bodyHt-margin;
1093 if (!Rico.isWebKit) remHt-=this.options.scrollBarWidth;
1094 Rico.log("remainingHt, winHt="+winHt+' pageHt='+bodyHt+' remHt='+remHt);
1097 Rico.log("remainingHt, winHt="+winHt+' tabHt='+tabHt);
1098 if (this.sizeTo.slice(-1)=='%') winHt*=parseFloat(this.sizeTo)/100.0;
1099 else if (this.sizeTo.slice(-2)=='px') winHt=parseInt(this.sizeTo,10);
1100 return winHt-tabHt-margin; // allow for scrollbar and some margin
1104 offsetFromParent: function(element) {
1106 var elParent=element.parentNode;
1108 //Rico.log("offsetFromParent: "+element.tagName+' id='+element.id+' otop='+element.offsetTop);
1109 valueT += element.offsetTop || 0;
1110 element = element.offsetParent;
1111 if (!element || element==null) break;
1112 var p = Rico.getStyle(element, 'position');
1113 if (element.tagName=='BODY' || element.tagName=='HTML' || p=='absolute') return valueT-elParent.offsetTop;
1114 } while (element != elParent);
1118 adjustPageSize: function() {
1119 Rico.log('adjustPageSize start');
1120 var remHt=this.remainingHt();
1121 Rico.log('adjustPageSize remHt='+remHt+' lastRow='+this.lastRowPos);
1122 if (remHt > this.rowHeight)
1123 this.autoAppendRows(remHt);
1124 else if (remHt < 0 || this.sizeTo=='data')
1125 this.autoRemoveRows(-remHt);
1126 Rico.log('adjustPageSize end');
1129 setPageSize: function(newRowCount) {
1130 Rico.log('setPageSize '+this.tableId+' newRowCount='+newRowCount);
1131 newRowCount=Math.min(newRowCount,this.options.maxPageRows);
1132 newRowCount=Math.max(newRowCount,this.options.minPageRows);
1133 this.sizeTo='fixed';
1134 var oldSize=this.pageSize;
1135 while (this.pageSize > newRowCount) {
1138 while (this.pageSize < newRowCount) {
1139 this.appendBlankRow();
1141 this.finishResize(oldSize);
1144 pluginWindowResize: function() {
1145 Rico.log("pluginWindowResize");
1146 this.resizeWindowHandler=Rico.eventHandle(this,'resizeWindow');
1147 Rico.eventBind(window, "resize", this.resizeWindowHandler, false);
1150 unplugWindowResize: function() {
1151 if (!this.resizeWindowHandler) return;
1152 Rico.eventUnbind(window,"resize", this.resizeWindowHandler, false);
1153 this.resizeWindowHandler=null;
1156 resizeWindow: function() {
1157 Rico.log('resizeWindow '+this.tableId+' lastRow='+this.lastRowPos+' resizeState='+this.resizeState);
1158 if (this.resizeState=='finish') {
1159 Rico.log('resizeWindow postponed');
1160 this.resizeState='resize';
1163 if (!this.sizeTo || this.sizeTo=='fixed') {
1167 if (this.sizeTo=='parent' && Rico.getStyle(this.outerDiv.parentNode,'display') == 'none') return;
1168 Rico.log('resizeWindow: about to adjustPageSize')
1169 var oldSize=this.pageSize;
1170 this.adjustPageSize();
1171 this.finishResize(oldSize);
1174 finishResize: function(oldSize) {
1175 Rico.log('finishResize '+this.tableId);
1176 if (this.pageSize > oldSize && this.buffer.totalRows>0) {
1177 this.isPartialBlank=true;
1178 var adjStart=this.adjustRow(this.lastRowPos);
1179 this.buffer.fetch(adjStart);
1180 } else if (this.pageSize < oldSize) {
1181 if (this.options.onRefreshComplete) this.options.onRefreshComplete(this.contentStartPos,this.contentStartPos+this.pageSize-1); // update bookmark
1183 this.resizeState='finish';
1184 Rico.runLater(20,this,'finishResize2');
1185 Rico.log('Resize '+this.tableId+' complete. old size='+oldSize+' new size='+this.pageSize);
1188 finishResize2: function() {
1189 Rico.log('finishResize2 '+this.tableId+': resizeState='+this.resizeState);
1191 this.updateHeightDiv();
1192 if (this.resizeState=='resize') {
1193 this.resizeWindow();
1195 this.resizeState='';
1199 topOfLastPage: function() {
1200 return Math.max(this.buffer.totalRows-this.pageSize,0);
1203 updateHeightDiv: function() {
1204 var notdisp=this.topOfLastPage();
1205 var ht = notdisp ? this.scrollDiv.clientHeight + Math.floor(this.rowHeight * (notdisp + 0.4)) : 1;
1206 Rico.log("updateHeightDiv, ht="+ht+' scrollDiv.clientHeight='+this.scrollDiv.clientHeight+' rowsNotDisplayed='+notdisp);
1207 this.shadowDiv.style.height=ht+'px';
1210 autoRemoveRows: function(overage) {
1211 if (!this.rowHeight) return;
1212 var removeCnt=Math.ceil(overage / this.rowHeight);
1213 if (this.sizeTo=='data')
1214 removeCnt=Math.max(removeCnt,this.pageSize-this.buffer.totalRows);
1215 Rico.log("autoRemoveRows overage="+overage+" removeCnt="+removeCnt);
1216 for (var i=0; i<removeCnt; i++)
1220 removeRow: function() {
1221 if (this.pageSize <= this.options.minPageRows) return;
1223 for( var c=0; c < this.headerColCnt; c++ ) {
1224 var cell=this.columns[c].cell(this.pageSize);
1225 this.columns[c].dataColDiv.removeChild(cell);
1229 autoAppendRows: function(overage) {
1230 if (!this.rowHeight) return;
1231 var addCnt=Math.floor(overage / this.rowHeight);
1232 Rico.log("autoAppendRows overage="+overage+" cnt="+addCnt+" rowHt="+this.rowHeight);
1233 for (var i=0; i<addCnt; i++) {
1234 if (this.sizeTo=='data' && this.pageSize>=this.buffer.totalRows) break;
1235 this.appendBlankRow();
1240 * on older systems, this can be fairly slow
1242 appendBlankRow: function() {
1243 if (this.pageSize >= this.options.maxPageRows) return;
1244 Rico.log("appendBlankRow #"+this.pageSize);
1245 var cls=this.defaultRowClass(this.pageSize);
1246 for( var c=0; c < this.headerColCnt; c++ ) {
1247 var newdiv = document.createElement("div");
1248 newdiv.className = 'ricoLG_cell '+cls;
1249 newdiv.id=this.tableId+'_'+this.pageSize+'_'+c;
1250 this.columns[c].dataColDiv.appendChild(newdiv);
1251 if (this.columns[c]._create) {
1252 this.columns[c]._create(newdiv,this.pageSize);
1254 newdiv.innerHTML=' '; // this seems to be required by IE
1256 if (this.columns[c].format.canDrag && Rico.registerDraggable) {
1257 Rico.registerDraggable( new Rico.LiveGridDraggable(this, this.pageSize, c), this.options.dndMgrIdx );
1263 defaultRowClass: function(rownum) {
1265 if (rownum % 2==0) {
1266 cls='ricoLG_evenRow';
1267 //if (Rico.theme.primary) cls+=' '+Rico.theme.primary;
1269 cls='ricoLG_oddRow';
1270 //if (Rico.theme.secondary) cls+=' '+Rico.theme.secondary;
1275 handleMenuClick: function(e) {
1276 if (!this.menu) return;
1278 this.unhighlight(); // in case highlighting was invoked externally
1280 var cell=Rico.eventElement(e);
1281 if (cell.className=='ricoLG_highlightDiv') {
1282 idx=this.highlightIdx;
1284 cell=Rico.getParentByTagName(cell,'div','ricoLG_cell');
1286 idx=this.winCellIndex(cell);
1287 if ((this.options.highlightSection & (idx.tabIdx+1))==0) return;
1289 this.highlight(idx);
1290 this.highlightEnabled=false;
1291 if (this.hideScroll) this.scrollDiv.style.overflow="hidden";
1293 if (!this.menu.div) this.menu.createDiv();
1294 this.menu.liveGrid=this;
1295 if (this.menu.buildGridMenu) {
1296 var showMenu=this.menu.buildGridMenu(idx.row, idx.column, idx.tabIdx);
1297 if (!showMenu) return;
1299 if (this.options.highlightElem=='selection' && !this.isSelected(idx.cell)) {
1300 this.selectCell(idx.cell);
1303 this.menu.showmenu(e,function() { self.closeMenu(); });
1307 closeMenu: function() {
1308 if (!this.menuIdx) return;
1309 if (this.hideScroll) this.scrollDiv.style.overflow="";
1310 //this.unhighlight();
1311 this.highlightEnabled=true;
1316 * @return index of cell within the window
1318 winCellIndex: function(cell) {
1319 var l=cell.id.lastIndexOf('_',cell.id.length);
1320 var l2=cell.id.lastIndexOf('_',l-1)+1;
1321 var c=parseInt(cell.id.substr(l+1));
1322 var r=parseInt(cell.id.substr(l2,l));
1323 return {row:r, column:c, tabIdx:this.columns[c].tabIdx, cell:cell};
1327 * @return index of cell within the dataset
1329 datasetIndex: function(cell) {
1330 var idx=this.winCellIndex(cell);
1331 idx.row+=this.buffer.windowPos;
1332 idx.onBlankRow=(idx.row >= this.buffer.endPos());
1336 attachHighlightEvents: function(tBody) {
1337 switch (this.options.highlightElem) {
1339 Rico.eventBind(tBody,"mousedown", this.options.mouseDownHandler, false);
1341 tBody.ondrag = function () { return false; };
1343 tBody.onselectstart = function () { return false; };
1347 Rico.eventBind(tBody,"mouseover", this.options.rowOverHandler, false);
1352 detachHighlightEvents: function(tBody) {
1353 switch (this.options.highlightElem) {
1355 Rico.eventUnbind(tBody,"mousedown", this.options.mouseDownHandler, false);
1356 tBody.ondrag = null;
1357 tBody.onselectstart = null;
1361 Rico.eventUnbind(tBody,"mouseover", this.options.rowOverHandler, false);
1367 * @return array of objects containing row/col indexes (index values are relative to the start of the window)
1369 getVisibleSelection: function() {
1371 if (this.SelectIdxStart && this.SelectIdxEnd) {
1372 var r1=Math.max(Math.min(this.SelectIdxEnd.row,this.SelectIdxStart.row)-this.buffer.startPos,this.buffer.windowStart);
1373 var r2=Math.min(Math.max(this.SelectIdxEnd.row,this.SelectIdxStart.row)-this.buffer.startPos,this.buffer.windowEnd-1);
1374 var c1=Math.min(this.SelectIdxEnd.column,this.SelectIdxStart.column);
1375 var c2=Math.max(this.SelectIdxEnd.column,this.SelectIdxStart.column);
1376 //Rico.log("getVisibleSelection "+r1+','+c1+' to '+r2+','+c2+' ('+this.SelectIdxStart.row+',startPos='+this.buffer.startPos+',windowPos='+this.buffer.windowPos+',windowEnd='+this.buffer.windowEnd+')');
1377 for (var r=r1; r<=r2; r++) {
1378 for (var c=c1; c<=c2; c++)
1379 cellList.push({row:r-this.buffer.windowStart,column:c});
1382 if (this.SelectCtrl) {
1383 for (var i=0; i<this.SelectCtrl.length; i++) {
1384 if (this.SelectCtrl[i].row>=this.buffer.windowStart && this.SelectCtrl[i].row<this.buffer.windowEnd)
1385 cellList.push({row:this.SelectCtrl[i].row-this.buffer.windowStart,column:this.SelectCtrl[i].column});
1391 updateSelectOutline: function() {
1392 if (!this.SelectIdxStart || !this.SelectIdxEnd) return;
1393 var r1=Math.max(Math.min(this.SelectIdxEnd.row,this.SelectIdxStart.row), this.buffer.windowStart);
1394 var r2=Math.min(Math.max(this.SelectIdxEnd.row,this.SelectIdxStart.row), this.buffer.windowEnd-1);
1396 this.HideSelection();
1399 var c1=Math.min(this.SelectIdxEnd.column,this.SelectIdxStart.column);
1400 var c2=Math.max(this.SelectIdxEnd.column,this.SelectIdxStart.column);
1401 var top1=this.columns[c1].cell(r1-this.buffer.windowStart).offsetTop;
1402 var cell2=this.columns[c1].cell(r2-this.buffer.windowStart);
1403 var bottom2=cell2.offsetTop+cell2.offsetHeight;
1404 var left1=this.columns[c1].dataCell.offsetLeft;
1405 var left2=this.columns[c2].dataCell.offsetLeft;
1406 var right2=left2+this.columns[c2].dataCell.offsetWidth;
1407 //window.status='updateSelectOutline: '+r1+' '+r2+' top='+top1+' bot='+bottom2;
1408 this.highlightDiv[0].style.top=this.highlightDiv[3].style.top=this.highlightDiv[1].style.top=(this.hdrHt+top1-1) + 'px';
1409 this.highlightDiv[2].style.top=(this.hdrHt+bottom2-1)+'px';
1410 this.highlightDiv[3].style.left=(left1-2)+'px';
1411 this.highlightDiv[0].style.left=this.highlightDiv[2].style.left=(left1-1)+'px';
1412 this.highlightDiv[1].style.left=(right2-1)+'px';
1413 this.highlightDiv[0].style.width=this.highlightDiv[2].style.width=(right2-left1-1) + 'px';
1414 this.highlightDiv[1].style.height=this.highlightDiv[3].style.height=(bottom2-top1) + 'px';
1415 //this.highlightDiv[0].style.right=this.highlightDiv[2].style.right=this.highlightDiv[1].style.right=()+'px';
1416 //this.highlightDiv[2].style.bottom=this.highlightDiv[3].style.bottom=this.highlightDiv[1].style.bottom=(this.hdrHt+bottom2) + 'px';
1417 for (var i=0; i<4; i++)
1418 this.highlightDiv[i].style.display='';
1421 HideSelection: function() {
1423 if (this.options.highlightMethod!='class') {
1424 for (i=0; i<this.highlightDiv.length; i++)
1425 this.highlightDiv[i].style.display='none';
1427 if (this.options.highlightMethod!='outline') {
1428 var cellList=this.getVisibleSelection();
1429 Rico.log("HideSelection "+cellList.length);
1430 for (i=0; i<cellList.length; i++)
1431 this.unhighlightCell(this.columns[cellList[i].column].cell(cellList[i].row));
1435 ShowSelection: function() {
1436 if (this.options.highlightMethod!='class')
1437 this.updateSelectOutline();
1438 if (this.options.highlightMethod!='outline') {
1439 var cellList=this.getVisibleSelection();
1440 for (var i=0; i<cellList.length; i++)
1441 this.highlightCell(this.columns[cellList[i].column].cell(cellList[i].row));
1445 ClearSelection: function() {
1446 Rico.log("ClearSelection");
1447 this.HideSelection();
1448 this.SelectIdxStart=null;
1449 this.SelectIdxEnd=null;
1453 selectCell: function(cell) {
1454 this.ClearSelection();
1455 this.SelectIdxStart=this.SelectIdxEnd=this.datasetIndex(cell);
1456 this.ShowSelection();
1459 AdjustSelection: function(cell) {
1460 var newIdx=this.datasetIndex(cell);
1461 if (this.SelectIdxStart.tabIdx != newIdx.tabIdx) return;
1462 this.HideSelection();
1463 this.SelectIdxEnd=newIdx;
1464 this.ShowSelection();
1467 RefreshSelection: function() {
1468 var cellList=this.getVisibleSelection();
1469 for (var i=0; i<cellList.length; i++) {
1470 this.columns[cellList[i].column].displayValue(cellList[i].row);
1474 FillSelection: function(newVal,newStyle) {
1475 if (this.SelectIdxStart && this.SelectIdxEnd) {
1476 var r1=Math.min(this.SelectIdxEnd.row,this.SelectIdxStart.row);
1477 var r2=Math.max(this.SelectIdxEnd.row,this.SelectIdxStart.row);
1478 var c1=Math.min(this.SelectIdxEnd.column,this.SelectIdxStart.column);
1479 var c2=Math.max(this.SelectIdxEnd.column,this.SelectIdxStart.column);
1480 for (var r=r1; r<=r2; r++) {
1481 for (var c=c1; c<=c2; c++) {
1482 this.buffer.setValue(r,c,newVal,newStyle);
1486 if (this.SelectCtrl) {
1487 for (var i=0; i<this.SelectCtrl.length; i++) {
1488 this.buffer.setValue(this.SelectCtrl[i].row,this.SelectCtrl[i].column,newVal,newStyle);
1491 this.RefreshSelection();
1495 * Process mouse down event
1496 * @param e event object
1498 selectMouseDown: function(e) {
1499 if (this.highlightEnabled==false) return true;
1501 var cell=Rico.eventElement(e);
1502 if (!Rico.eventLeftClick(e)) return true;
1503 cell=Rico.getParentByTagName(cell,'div','ricoLG_cell');
1504 if (!cell) return true;
1506 var newIdx=this.datasetIndex(cell);
1507 if (newIdx.onBlankRow) return true;
1508 Rico.log("selectMouseDown @"+newIdx.row+','+newIdx.column);
1510 if (!this.SelectIdxStart || this.options.highlightMethod!='class') return true;
1511 if (!this.isSelected(cell)) {
1512 this.highlightCell(cell);
1513 this.SelectCtrl.push(this.datasetIndex(cell));
1515 for (var i=0; i<this.SelectCtrl.length; i++) {
1516 if (this.SelectCtrl[i].row==newIdx.row && this.SelectCtrl[i].column==newIdx.column) {
1517 this.unhighlightCell(cell);
1518 this.SelectCtrl.splice(i,1);
1523 } else if (e.shiftKey) {
1524 if (!this.SelectIdxStart) return true;
1525 this.AdjustSelection(cell);
1527 this.selectCell(cell);
1528 this.pluginSelect();
1533 pluginSelect: function() {
1534 if (this.selectPluggedIn) return;
1535 var tBody=this.tbody[this.SelectIdxStart.tabIdx];
1536 Rico.eventBind(tBody,"mouseover", this.options.mouseOverHandler, false);
1537 Rico.eventBind(this.outerDiv,"mouseup", this.options.mouseUpHandler, false);
1538 this.selectPluggedIn=true;
1541 unplugSelect: function() {
1542 if (!this.selectPluggedIn) return;
1543 var tBody=this.tbody[this.SelectIdxStart.tabIdx];
1544 Rico.eventUnbind(tBody,"mouseover", this.options.mouseOverHandler , false);
1545 Rico.eventUnbind(this.outerDiv,"mouseup", this.options.mouseUpHandler , false);
1546 this.selectPluggedIn=false;
1549 selectMouseUp: function(e) {
1550 this.unplugSelect();
1551 var cell=Rico.eventElement(e);
1552 cell=Rico.getParentByTagName(cell,'div','ricoLG_cell');
1554 if (this.SelectIdxStart && this.SelectIdxEnd)
1555 this.AdjustSelection(cell);
1557 this.ClearSelection();
1560 selectMouseOver: function(e) {
1561 var cell=Rico.eventElement(e);
1562 cell=Rico.getParentByTagName(cell,'div','ricoLG_cell');
1564 this.AdjustSelection(cell);
1568 isSelected: function(cell) {
1569 if (this.options.highlightMethod!='outline') return Rico.hasClass(cell,this.options.highlightClass);
1570 if (!this.SelectIdxStart || !this.SelectIdxEnd) return false;
1571 var r1=Math.max(Math.min(this.SelectIdxEnd.row,this.SelectIdxStart.row), this.buffer.windowStart);
1572 var r2=Math.min(Math.max(this.SelectIdxEnd.row,this.SelectIdxStart.row), this.buffer.windowEnd-1);
1573 if (r1 > r2) return false;
1574 var c1=Math.min(this.SelectIdxEnd.column,this.SelectIdxStart.column);
1575 var c2=Math.max(this.SelectIdxEnd.column,this.SelectIdxStart.column);
1576 var curIdx=this.datasetIndex(cell);
1577 return (r1<=curIdx.row && curIdx.row<=r2 && c1<=curIdx.column && curIdx.column<=c2);
1580 highlightCell: function(cell) {
1581 Rico.addClass(cell,this.options.highlightClass);
1584 unhighlightCell: function(cell) {
1585 if (cell) Rico.removeClass(cell,this.options.highlightClass);
1588 selectRow: function(r) {
1589 for (var c=0; c<this.columns.length; c++)
1590 this.highlightCell(this.columns[c].cell(r));
1593 unselectRow: function(r) {
1594 for (var c=0; c<this.columns.length; c++)
1595 this.unhighlightCell(this.columns[c].cell(r));
1598 rowMouseOver: function(e) {
1599 if (!this.highlightEnabled) return;
1600 var cell=Rico.eventElement(e);
1601 cell=Rico.getParentByTagName(cell,'div','ricoLG_cell');
1603 var newIdx=this.winCellIndex(cell);
1604 if ((this.options.highlightSection & (newIdx.tabIdx+1))==0) return;
1605 this.highlight(newIdx);
1608 highlight: function(newIdx) {
1609 if (this.options.highlightMethod!='outline') this.cursorSetClass(newIdx);
1610 if (this.options.highlightMethod!='class') this.cursorOutline(newIdx);
1611 this.highlightIdx=newIdx;
1614 cursorSetClass: function(newIdx) {
1615 switch (this.options.highlightElem) {
1618 if (this.highlightIdx) this.unhighlightCell(this.highlightIdx.cell);
1619 this.highlightCell(newIdx.cell);
1623 if (this.highlightIdx) this.unselectRow(this.highlightIdx.row);
1624 var s1=this.options.highlightSection & 1;
1625 var s2=this.options.highlightSection & 2;
1626 var c0=s1 ? 0 : this.options.frozenColumns;
1627 var c1=s2 ? this.columns.length : this.options.frozenColumns;
1628 for (var c=c0; c<c1; c++)
1629 this.highlightCell(this.columns[c].cell(newIdx.row));
1635 cursorOutline: function(newIdx) {
1637 switch (this.options.highlightElem) {
1640 div=this.highlightDiv[newIdx.tabIdx];
1641 div.style.left=(this.columns[newIdx.column].dataCell.offsetLeft-1)+'px';
1642 div.style.width=this.columns[newIdx.column].colWidth;
1643 this.highlightDiv[1-newIdx.tabIdx].style.display='none';
1647 div=this.highlightDiv[0];
1648 var s1=this.options.highlightSection & 1;
1649 var s2=this.options.highlightSection & 2;
1650 div.style.left=s1 ? '0px' : this.frozenTabs.style.width;
1651 div.style.width=((s1 ? this.frozenTabs.offsetWidth : 0) + (s2 ? this.innerDiv.offsetWidth : 0) - 4)+'px';
1655 div.style.top=(this.hdrHt+newIdx.row*this.rowHeight-1)+'px';
1656 div.style.height=(this.rowHeight-1)+'px';
1657 div.style.display='';
1660 unhighlight: function() {
1661 switch (this.options.highlightElem) {
1663 //this.highlightIdx=this.menuIdx;
1666 if (this.highlightIdx) this.unhighlightCell(this.highlightIdx.cell);
1667 if (!this.highlightDiv) return;
1668 for (var i=0; i<2; i++)
1669 this.highlightDiv[i].style.display='none';
1672 //this.highlightIdx=this.menuIdx;
1675 if (this.highlightIdx) this.unselectRow(this.highlightIdx.row);
1676 if (this.highlightDiv) this.highlightDiv[0].style.display='none';
1681 resetContents: function() {
1682 Rico.log("resetContents");
1683 this.ClearSelection();
1684 this.buffer.clear();
1686 this.clearBookmark();
1689 setImages: function() {
1690 for (var n=0; n<this.columns.length; n++)
1691 this.columns[n].setImage();
1695 * @return column index, or -1 if there are no sorted columns
1697 findSortedColumn: function() {
1698 for (var n=0; n<this.columns.length; n++) {
1699 if (this.columns[n].isSorted()) return n;
1705 * Searches options.columnSpecs colAttr for matching colValue
1706 * @return array of matching column indexes
1708 findColumnsBySpec: function(colAttr, colValue) {
1710 for (var n=0; n<this.options.columnSpecs.length; n++) {
1711 if (this.options.columnSpecs[n][colAttr] == colValue) result.push(n);
1719 setSortUI: function( columnIdOrNum, sortDirection ) {
1720 Rico.log("setSortUI: "+columnIdOrNum+' '+sortDirection);
1721 var colnum=this.findSortedColumn();
1723 sortDirection=this.columns[colnum].getSortDirection();
1725 if (typeof sortDirection!='string') {
1726 sortDirection=Rico.ColumnConst.SORT_ASC;
1728 sortDirection=sortDirection.toUpperCase();
1729 if (sortDirection != Rico.ColumnConst.SORT_DESC) sortDirection=Rico.ColumnConst.SORT_ASC;
1731 switch (typeof columnIdOrNum) {
1733 colnum=this.findColumnsBySpec('id',columnIdOrNum);
1736 colnum=columnIdOrNum;
1740 if (typeof(colnum)!='number' || colnum < 0) return;
1742 this.columns[colnum].setSorted(sortDirection);
1743 this.buffer.sortBuffer(colnum);
1747 * clear sort flag on all columns
1749 clearSort: function() {
1750 for (var x=0;x<this.columns.length;x++)
1751 this.columns[x].setUnsorted();
1755 * clear filters on all columns
1757 clearFilters: function() {
1758 for (var x=0;x<this.columns.length;x++) {
1759 this.columns[x].setUnfiltered(true);
1761 if (this.options.filterHandler) {
1762 this.options.filterHandler();
1767 * returns number of columns with a user filter set
1769 filterCount: function() {
1770 for (var x=0,cnt=0;x<this.columns.length;x++) {
1771 if (this.columns[x].isFiltered()) cnt++;
1776 sortHandler: function() {
1778 this.ClearSelection();
1780 var n=this.findSortedColumn();
1782 Rico.log("sortHandler: sorting column "+n);
1783 this.buffer.sortBuffer(n);
1785 this.scrollDiv.scrollTop = 0;
1786 this.buffer.fetch(0);
1789 filterHandler: function() {
1790 Rico.log("filterHandler");
1792 if (this.buffer.processingRequest) {
1793 this.queueFilter=true;
1796 this.unplugScroll();
1797 this.ClearSelection();
1799 this.clearBookmark();
1801 this.buffer.fetch(-1);
1802 Rico.runLater(10,this,'pluginScroll'); // resetting ht div can cause a scroll event, triggering an extra fetch
1805 clearBookmark: function() {
1806 if (this.bookmark) this.bookmark.innerHTML=" ";
1809 bookmarkHandler: function(firstrow,lastrow) {
1811 if (isNaN(firstrow) || !this.bookmark) return;
1812 var totrows=this.buffer.totalRows;
1813 if (totrows < lastrow) lastrow=totrows;
1815 newhtml = Rico.getPhraseById('bookmarkNoMatch');
1816 } else if (lastrow<0) {
1817 newhtml = Rico.getPhraseById('bookmarkNoRec');
1818 } else if (this.buffer.foundRowCount) {
1819 newhtml = Rico.getPhraseById('bookmarkExact',firstrow,lastrow,totrows);
1821 newhtml = Rico.getPhraseById('bookmarkAbout',firstrow,lastrow,totrows);
1823 this.bookmark.innerHTML = newhtml;
1826 clearRows: function() {
1827 if (this.isBlank==true) return;
1828 for (var c=0; c < this.columns.length; c++)
1829 this.columns[c].clearColumn();
1830 this.isBlank = true;
1833 refreshContents: function(startPos) {
1834 Rico.log("refreshContents1 "+this.tableId+": startPos="+startPos+" lastRow="+this.lastRowPos+" PartBlank="+this.isPartialBlank+" pageSize="+this.pageSize);
1837 this.unhighlight(); // in case highlighting was manually invoked
1838 if (this.queueFilter) {
1839 Rico.log("refreshContents: cancelling refresh because filter has changed");
1840 this.queueFilter=false;
1841 this.filterHandler();
1844 this.highlightEnabled=this.options.highlightSection!='none';
1845 var viewPrecedesBuffer = this.buffer.startPos > startPos;
1846 var contentStartPos = viewPrecedesBuffer ? this.buffer.startPos: startPos;
1847 this.contentStartPos = contentStartPos+1;
1848 var contentEndPos = Math.min(this.buffer.startPos + this.buffer.size, startPos + this.pageSize);
1849 this.buffer.setWindow(contentStartPos, contentEndPos);
1850 Rico.log('refreshContents2 '+this.tableId+': cStartPos='+contentStartPos+' cEndPos='+contentEndPos+' vPrecedesBuf='+viewPrecedesBuffer+' b.startPos='+this.buffer.startPos);
1851 if (startPos == this.lastRowPos && !this.isPartialBlank && !this.isBlank) return;
1852 this.isBlank = false;
1853 var onRefreshComplete = this.options.onRefreshComplete;
1855 if ((startPos + this.pageSize < this.buffer.startPos) ||
1856 (this.buffer.startPos + this.buffer.size < startPos) ||
1857 (this.buffer.size == 0)) {
1859 if (onRefreshComplete) onRefreshComplete(this.contentStartPos,contentEndPos); // update bookmark
1863 Rico.log('refreshContents: contentStartPos='+contentStartPos+' contentEndPos='+contentEndPos+' viewPrecedesBuffer='+viewPrecedesBuffer);
1864 var rowSize = contentEndPos - contentStartPos;
1865 var blankSize = this.pageSize - rowSize;
1866 var blankOffset = viewPrecedesBuffer ? 0: rowSize;
1867 var contentOffset = viewPrecedesBuffer ? blankSize: 0;
1869 for (var r=0; r < rowSize; r++) { //initialize what we have
1870 for (var c=0; c < this.columns.length; c++)
1871 this.columns[c].displayValue(r + contentOffset);
1873 for (var i=0; i < blankSize; i++) // blank out the rest
1874 this.blankRow(i + blankOffset);
1875 if (this.options.highlightElem=='selection') this.ShowSelection();
1876 this.isPartialBlank = blankSize > 0;
1877 this.lastRowPos = startPos;
1878 Rico.log("refreshContents complete, startPos="+startPos);
1879 if (onRefreshComplete) onRefreshComplete(this.contentStartPos,contentEndPos); // update bookmark
1882 scrollToRow: function(rowOffset) {
1883 var p=this.rowToPixel(rowOffset);
1884 Rico.log("scrollToRow, rowOffset="+rowOffset+" pixel="+p);
1885 this.scrollDiv.scrollTop = p; // this causes a scroll event
1886 if ( this.options.onscroll )
1887 this.options.onscroll( this, rowOffset );
1890 scrollUp: function() {
1891 this.moveRelative(-1);
1894 scrollDown: function() {
1895 this.moveRelative(1);
1898 pageUp: function() {
1899 this.moveRelative(-this.pageSize);
1902 pageDown: function() {
1903 this.moveRelative(this.pageSize);
1906 adjustRow: function(rowOffset) {
1907 var notdisp=this.topOfLastPage();
1908 if (notdisp == 0 || !rowOffset) return 0;
1909 return Math.min(notdisp,rowOffset);
1912 rowToPixel: function(rowOffset) {
1913 return this.adjustRow(rowOffset) * this.rowHeight;
1917 * @returns row to display at top of scroll div
1919 pixeltorow: function(p) {
1920 var notdisp=this.topOfLastPage();
1921 if (notdisp == 0) return 0;
1922 var prow=parseInt(p/this.rowHeight,10);
1923 return Math.min(notdisp,prow);
1926 moveRelative: function(relOffset) {
1927 var newoffset=Math.max(this.scrollDiv.scrollTop+relOffset*this.rowHeight,0);
1928 newoffset=Math.min(newoffset,this.scrollDiv.scrollHeight);
1929 //Rico.log("moveRelative, newoffset="+newoffset);
1930 this.scrollDiv.scrollTop=newoffset;
1933 pluginScroll: function() {
1934 if (this.scrollPluggedIn) return;
1935 Rico.log("pluginScroll: wheelEvent="+this.wheelEvent);
1936 Rico.eventBind(this.scrollDiv,"scroll",this.scrollEventFunc, false);
1937 for (var t=0; t<2; t++)
1938 Rico.eventBind(this.tabs[t],this.wheelEvent,this.wheelEventFunc, false);
1939 this.scrollPluggedIn=true;
1942 unplugScroll: function() {
1943 if (!this.scrollPluggedIn) return;
1944 Rico.log("unplugScroll");
1945 Rico.eventUnbind(this.scrollDiv,"scroll", this.scrollEventFunc , false);
1946 for (var t=0; t<2; t++)
1947 Rico.eventUnbind(this.tabs[t],this.wheelEvent,this.wheelEventFunc, false);
1948 this.scrollPluggedIn=false;
1951 handleWheel: function(e) {
1955 delta = e.wheelDelta/120;
1956 else if (Rico.isWebKit)
1957 delta = -e.wheelDelta/12;
1959 delta = -e.wheelDelta/120;
1960 } else if (e.detail) {
1961 delta = e.detail/3; /* Mozilla/Gecko */
1963 if (delta) this.moveRelative(delta);
1968 handleScroll: function(e) {
1969 if ( this.scrollTimeout )
1970 clearTimeout( this.scrollTimeout );
1971 this.setHorizontalScroll();
1972 var scrtop=this.scrollDiv.scrollTop;
1973 var vscrollDiff = this.lastScrollPos-scrtop;
1974 if (vscrollDiff == 0.00) return;
1975 var newrow=this.pixeltorow(scrtop);
1976 if (newrow == this.lastRowPos && !this.isPartialBlank && !this.isBlank) return;
1977 var stamp1 = new Date();
1978 Rico.log("handleScroll, newrow="+newrow+" scrtop="+scrtop);
1979 if (this.options.highlightElem=='selection') this.HideSelection();
1980 this.buffer.fetch(newrow);
1981 if (this.options.onscroll) this.options.onscroll(this, newrow);
1982 this.scrollTimeout = Rico.runLater(1200,this,'scrollIdle');
1983 this.lastScrollPos = this.scrollDiv.scrollTop;
1984 var stamp2 = new Date();
1985 //Rico.log("handleScroll, time="+(stamp2.getTime()-stamp1.getTime()));
1988 scrollIdle: function() {
1989 if ( this.options.onscrollidle )
1990 this.options.onscrollidle();
1996 Rico.LiveGridColumn = function(grid,colIdx,hdrInfo,tabIdx) {
1997 this.initialize(grid,colIdx,hdrInfo,tabIdx);
2000 Rico.LiveGridColumn.prototype =
2001 /** @lends Rico.LiveGridColumn# */
2004 * Implements a LiveGrid column. Also contains static properties used by SimpleGrid columns.
2005 * @extends Rico.TableColumnBase
2008 initialize: function(liveGrid,colIdx,hdrInfo,tabIdx) {
2009 Rico.extend(this, new Rico.TableColumnBase());
2010 this.baseInit(liveGrid,colIdx,hdrInfo,tabIdx);
2011 this.buffer=liveGrid.buffer;
2012 if (typeof(this.format.type)!='string' || this.format.EntryType=='tinyMCE') this.format.type='html';
2013 if (typeof this.isNullable!='boolean') this.isNullable = /number|date/.test(this.format.type);
2014 this.isText = /html|text/.test(this.format.type);
2015 Rico.log(" sortable="+this.sortable+" filterable="+this.filterable+" hideable="+this.hideable+" isNullable="+this.isNullable+' isText='+this.isText);
2016 this.fixHeaders(this.liveGrid.tableId, this.options.hdrIconsFirst);
2017 if (this['format_'+this.format.type]) {
2018 this._format=this['format_'+this.format.type];
2020 if (this.format.control) {
2021 // copy all properties/methods that start with '_'
2022 if (typeof this.format.control=='string') {
2023 this.format.control=eval(this.format.control);
2025 for (var property in this.format.control) {
2026 if (property.charAt(0)=='_') {
2027 Rico.log("Copying control property "+property+ ' to ' + this);
2028 this[property] = this.format.control[property];
2035 * Sorts the column in ascending order
2037 sortAsc: function() {
2038 this.setColumnSort(Rico.ColumnConst.SORT_ASC);
2042 * Sorts the column in descending order
2044 sortDesc: function() {
2045 this.setColumnSort(Rico.ColumnConst.SORT_DESC);
2049 * Sorts the column in the specified direction
2050 * @param direction must be one of Rico.ColumnConst.UNSORTED, .SORT_ASC, or .SORT_DESC
2052 setColumnSort: function(direction) {
2053 this.liveGrid.clearSort();
2054 this.setSorted(direction);
2055 if (this.liveGrid.options.saveColumnInfo.sort)
2056 this.liveGrid.setCookie();
2057 if (this.options.sortHandler)
2058 this.options.sortHandler();
2062 * @returns true if this column is allowed to be sorted
2064 isSortable: function() {
2065 return this.sortable;
2069 * @returns true if this column is currently sorted
2071 isSorted: function() {
2072 return this.currentSort != Rico.ColumnConst.UNSORTED;
2076 * @returns Rico.ColumnConst.UNSORTED, .SORT_ASC, or .SORT_DESC
2078 getSortDirection: function() {
2079 return this.currentSort;
2083 * toggle the sort sequence for this column
2085 toggleSort: function() {
2086 if (this.buffer && this.buffer.totalRows==0) return;
2087 if (this.currentSort == Rico.ColumnConst.SORT_ASC)
2094 * Flags that this column is not sorted
2096 setUnsorted: function() {
2097 this.setSorted(Rico.ColumnConst.UNSORTED);
2101 * Flags that this column is sorted, but doesn't actually carry out the sort
2102 * @param direction must be one of Rico.ColumnConst.UNSORTED, .SORT_ASC, or .SORT_DESC
2104 setSorted: function(direction) {
2105 this.currentSort = direction;
2109 * @returns true if this column is allowed to be filtered
2111 canFilter: function() {
2112 return this.filterable;
2116 * @returns a textual representation of how this column is filtered
2118 getFilterText: function() {
2120 for (var i=0; i<this.filterValues.length; i++) {
2121 var v=this.filterValues[i];
2122 vals.push(v=='' ? Rico.getPhraseById('filterBlank') : v);
2124 switch (this.filterOp) {
2125 case 'EQ': return '= '+vals.join(', ');
2126 case 'NE': return Rico.getPhraseById('filterNot',vals.join(', '));
2127 case 'LT': return '< '+vals[0];
2128 case 'GT': return '> '+vals[0];
2129 case 'LE': return '<= '+vals[0];
2130 case 'GE': return '>= '+vals[0];
2131 case 'LIKE': return Rico.getPhraseById('filterLike',vals[0]);
2132 case 'NULL': return Rico.getPhraseById('filterEmpty');
2133 case 'NOTNULL': return Rico.getPhraseById('filterNotEmpty');
2139 * @returns returns the query string representation of the filter
2141 getFilterQueryParm: function() {
2142 if (this.filterType == Rico.ColumnConst.UNFILTERED) return '';
2143 var retval='&f['+this.index+'][op]='+this.filterOp;
2144 retval+='&f['+this.index+'][len]='+this.filterValues.length;
2145 for (var i=0; i<this.filterValues.length; i++) {
2146 retval+='&f['+this.index+']['+i+']='+escape(this.filterValues[i]);
2152 * removes the filter from this column
2154 setUnfiltered: function(skipHandler) {
2155 this.filterType = Rico.ColumnConst.UNFILTERED;
2156 if (this.liveGrid.options.saveColumnInfo.filter)
2157 this.liveGrid.setCookie();
2158 if (this.removeFilterFunc)
2159 this.removeFilterFunc();
2160 if (this.options.filterHandler && !skipHandler)
2161 this.options.filterHandler();
2164 setFilterEQ: function() {
2165 this.setUserFilter('EQ');
2167 setFilterNE: function() {
2168 this.setUserFilter('NE');
2170 addFilterNE: function() {
2171 this.filterValues.push(this.userFilter);
2172 if (this.liveGrid.options.saveColumnInfo.filter)
2173 this.liveGrid.setCookie();
2174 if (this.options.filterHandler)
2175 this.options.filterHandler();
2177 setFilterGE: function() { this.setUserFilter('GE'); },
2178 setFilterLE: function() { this.setUserFilter('LE'); },
2179 setFilterKW: function(keyword) {
2180 if (keyword!='' && keyword!=null) {
2181 this.setFilter('LIKE',keyword,Rico.ColumnConst.USERFILTER);
2183 this.setUnfiltered(false);
2187 setUserFilter: function(relop) {
2188 this.setFilter(relop,this.userFilter,Rico.ColumnConst.USERFILTER);
2191 setSystemFilter: function(relop,filter) {
2192 this.setFilter(relop,filter,Rico.ColumnConst.SYSTEMFILTER);
2195 setFilter: function(relop,filter,type,removeFilterFunc) {
2196 this.filterValues = typeof(filter)=='object' ? filter : [filter];
2197 this.filterType = type;
2198 this.filterOp = relop;
2199 if (type == Rico.ColumnConst.USERFILTER && this.liveGrid.options.saveColumnInfo.filter)
2200 this.liveGrid.setCookie();
2201 this.removeFilterFunc=removeFilterFunc;
2202 if (this.options.filterHandler)
2203 this.options.filterHandler();
2206 isFiltered: function() {
2207 return this.filterType == Rico.ColumnConst.USERFILTER;
2210 filterChange: function(e) {
2211 var selbox=Rico.eventElement(e);
2212 if (selbox.value==this.liveGrid.options.FilterAllToken)
2213 this.setUnfiltered();
2215 this.setFilter('EQ',selbox.value,Rico.ColumnConst.USERFILTER,function() {selbox.selectedIndex=0;});
2218 nFilterChange: function(e) {
2219 var selbox=Rico.eventElement(e);
2220 if (selbox.value==this.liveGrid.options.FilterAllToken) {
2221 this.setUnfiltered();
2223 var op=selbox.value.substr(0,2);
2224 var value=selbox.value.substr(2);
2225 this.setFilter(op,value,Rico.ColumnConst.USERFILTER,function() {selbox.selectedIndex=0;});
2229 filterClear: function(e) {
\r
2230 this.filterField.value='';
2231 this.setUnfiltered();
\r
2234 filterKeypress: function(e) {
\r
2235 var txtbox=Rico.eventElement(e);
2236 if (typeof this.lastKeyFilter != 'string') this.lastKeyFilter='';
\r
2237 if (this.lastKeyFilter==txtbox.value) return;
\r
2238 var v=txtbox.value;
\r
2239 Rico.log("filterKeypress: "+this.index+' '+v);
\r
2240 this.lastKeyFilter=v;
2241 if (v=='' || v=='*')
\r
2242 this.setUnfiltered();
\r
2244 this.setFilter('LIKE', v, Rico.ColumnConst.USERFILTER, function() {txtbox.value='';});
2248 mFilterSelectClick: function(e) {
2250 if (this.mFilter.style.display!='none') {
2251 this.mFilterFinish(e);
2252 if (Rico.isIE && Rico.ieVersion <= 6) {
2253 this.filterField.focus();
2255 this.filterField.blur();
2258 var offset=Rico.cumulativeOffset(this.filterField);
2259 this.mFilter.style.top=(offset.top+this.filterField.offsetHeight)+'px';
2260 this.mFilter.style.left=offset.left+'px';
2261 this.mFilter.style.width=Math.min(this.filterField.offsetWidth,parseInt(this.colWidth,10))+'px';
2262 Rico.show(this.mFilter);
2263 this.mFilterFocus.focus();
2267 mFilterFinish: function(e) {
2268 if (!this.mFilterChange) {
2269 Rico.hide(this.mFilter);
2272 if (this.mFilterInputs[0].checked) {
2273 this.mFilterReset();
2274 Rico.hide(this.mFilter);
2275 this.setUnfiltered();
2280 for (var i=1; i<this.mFilterInputs.length; i++) {
2281 if (this.mFilterInputs[i].checked) {
2282 newValues.push(this.mFilterInputs[i].value)
2283 newLabels.push(this.mFilterLabels[i].innerHTML)
2286 if (newValues.length > 0) {
2287 var newText=newLabels.join(', ');
2288 this.filterField.options[0].text=newText;
2289 this.filterField.title=newText;
2290 Rico.hide(this.mFilter);
2291 this.mFilterChange=false;
2293 this.setFilter('EQ',newValues,Rico.ColumnConst.USERFILTER,function() { self.mFilterReset(); });
2295 alert('Please select at least one value');
2299 mFilterReset: function() {
2300 var newText=this.mFilterLabels[0].innerHTML; // all
2301 this.filterField.options[0].text=newText;
2302 this.filterField.title=newText;
2305 mFilterAllClick: function(e) {
2306 var allChecked=this.mFilterInputs[0].checked;
2307 for (var i=1; i<this.mFilterInputs.length; i++) {
2308 this.mFilterInputs[i].checked=allChecked;
2310 this.mFilterChange=true;
2313 mFilterOtherClick: function(e) {
2314 this.mFilterInputs[0].checked=false;
2315 this.mFilterChange=true;
2318 format_text: function(v) {
2319 if (typeof v!='string')
2322 return v.replace(/&/g, '&').replace(/</g,'<').replace(/>/g,'>');
2325 format_number: function(v) {
2326 if (typeof v=='undefined' || v=='' || v==null)
2329 return Rico.formatNumber(v,this.format);
2332 format_datetime: function(v) {
2333 if (typeof v=='undefined' || v=='' || v==null)
2336 var d=Rico.setISO8601(v);
2338 return (this.format.prefix || '')+Rico.formatDate(d,this.format.dateFmt || 'translateDateTime')+(this.format.suffix || '');
2342 // converts GMT/UTC to local time
2343 format_utcaslocaltime: function(v) {
2344 if (typeof v=='undefined' || v=='' || v==null)
2348 var d=Rico.setISO8601(v,-tz.getTimezoneOffset());
2350 return (this.format.prefix || '')+Rico.formatDate(d,this.format.dateFmt || 'translateDateTime')+(this.format.suffix || '');
2354 format_date: function(v) {
2355 if (typeof v=='undefined' || v==null || v=='')
2358 var d=Rico.setISO8601(v);
2360 return (this.format.prefix || '')+Rico.formatDate(d,this.format.dateFmt || 'translateDate')+(this.format.suffix || '');
2364 fixHeaders: function(prefix, iconsfirst) {
2365 if (this.sortable) {
2366 var handler=Rico.eventHandle(this,'toggleSort');
2367 switch (this.options.headingSort) {
2369 var a=Rico.wrapChildren(this.hdrCellDiv,'ricoSort',undefined,'a');
2370 a.href = "javascript:void(0)";
2371 Rico.eventBind(a,"click", handler);
2374 Rico.eventBind(this.hdrCellDiv,"click", handler);
2378 this.imgFilter = document.createElement('span');
2379 this.imgFilter.style.display='none';
2380 this.imgFilter.className='rico-icon ricoLG_filterCol';
2381 this.imgSort = document.createElement('span');
2382 this.imgSort.style.display='none';
2383 this.imgSort.style.verticalAlign='top';
2385 this.hdrCellDiv.insertBefore(this.imgSort,this.hdrCellDiv.firstChild);
2386 this.hdrCellDiv.insertBefore(this.imgFilter,this.hdrCellDiv.firstChild);
2388 this.hdrCellDiv.appendChild(this.imgFilter);
2389 this.hdrCellDiv.appendChild(this.imgSort);
2391 if (!this.format.filterUI) {
2392 Rico.eventBind(this.imgFilter, 'click', Rico.eventHandle(this,'filterClick'), false);
2396 filterClick: function(e) {
2397 if (this.filterType==Rico.ColumnConst.USERFILTER && this.filterOp=='LIKE') {
2398 this.liveGrid.openKeyword(this.index);
2402 getValue: function(windowRow) {
2403 return this.buffer.getWindowCell(windowRow,this.index);
2406 getBufferStyle: function(windowRow) {
2407 return this.buffer.getWindowStyle(windowRow,this.index);
2410 setValue: function(windowRow,newval) {
2411 this.buffer.setWindowValue(windowRow,this.index,newval);
2414 _format: function(v) {
2418 _display: function(v,gridCell) {
2419 gridCell.innerHTML=this._format(v);
2422 _export: function(v) {
2423 return this._format(v);
2426 exportBuffer: function(bufRow) {
2427 return this._export(this.buffer.getValue(bufRow,this.index));
2430 displayValue: function(windowRow) {
2431 var bufval=this.getValue(windowRow);
2433 this.clearCell(windowRow);
2436 var gridCell=this.cell(windowRow);
2437 this._display(bufval,gridCell,windowRow);
2438 if (this.buffer.options.acceptStyle) {
2439 gridCell.style.cssText=this.getBufferStyle(windowRow);