if (!dojo._hasResource['dojox.grid._grid.scroller']) { // _hasResource checks // added by build. Do // not use _hasResource // directly in your // code. dojo._hasResource['dojox.grid._grid.scroller'] = true; dojo.provide('dojox.grid._grid.scroller'); dojo.declare('dojox.grid.scroller.base', null, { // summary: // virtual scrollbox, abstract class // Content must in /rows/ // Rows are managed in contiguous sets called /pages/ // There are a fixed # of rows per page // The minimum rendered unit is a page constructor : function() { this.pageHeights = []; this.stack = []; }, // specified rowCount : 0, // total number of rows to manage defaultRowHeight : 10, // default height of a row keepRows : 100, // maximum number of rows that should exist at one time contentNode : null, // node to contain pages scrollboxNode : null, // node that controls scrolling // calculated defaultPageHeight : 0, // default height of a page keepPages : 10, // maximum number of pages that should exists at one // time pageCount : 0, windowHeight : 0, firstVisibleRow : 0, lastVisibleRow : 0, // private page : 0, pageTop : 0, // init init : function(inRowCount, inKeepRows, inRowsPerPage) { switch (arguments.length) { case 3 : this.rowsPerPage = inRowsPerPage; case 2 : this.keepRows = inKeepRows; case 1 : this.rowCount = inRowCount; } this.defaultPageHeight = this.defaultRowHeight * this.rowsPerPage; // this.defaultPageHeight = this.defaultRowHeight * // Math.min(this.rowsPerPage, this.rowCount); this.pageCount = Math.ceil(this.rowCount / this.rowsPerPage); this.keepPages = Math.max(Math.ceil(this.keepRows / this.rowsPerPage), 2); this.invalidate(); if (this.scrollboxNode) { this.scrollboxNode.scrollTop = 0; this.scroll(0); this.scrollboxNode.onscroll = dojo.hitch(this, 'onscroll'); } }, // updating invalidate : function() { this.invalidateNodes(); this.pageHeights = []; this.height = (this.pageCount ? (this.pageCount - 1) * this.defaultPageHeight + this.calcLastPageHeight() : 0); this.resize(); }, updateRowCount : function(inRowCount) { this.invalidateNodes(); this.rowCount = inRowCount; // update page count, adjust document height oldPageCount = this.pageCount; this.pageCount = Math.ceil(this.rowCount / this.rowsPerPage); if (this.pageCount < oldPageCount) { for (var i = oldPageCount - 1; i >= this.pageCount; i--) { this.height -= this.getPageHeight(i); delete this.pageHeights[i] } } else if (this.pageCount > oldPageCount) { this.height += this.defaultPageHeight * (this.pageCount - oldPageCount - 1) + this.calcLastPageHeight(); } this.resize(); }, // abstract interface pageExists : function(inPageIndex) { }, measurePage : function(inPageIndex) { }, positionPage : function(inPageIndex, inPos) { }, repositionPages : function(inPageIndex) { }, installPage : function(inPageIndex) { }, preparePage : function(inPageIndex, inPos, inReuseNode) { }, renderPage : function(inPageIndex) { }, removePage : function(inPageIndex) { }, pacify : function(inShouldPacify) { }, // pacification pacifying : false, pacifyTicks : 200, setPacifying : function(inPacifying) { if (this.pacifying != inPacifying) { this.pacifying = inPacifying; this.pacify(this.pacifying); } }, startPacify : function() { this.startPacifyTicks = allGetServerTime().getTime(); }, doPacify : function() { var result = (allGetServerTime().getTime() - this.startPacifyTicks) > this.pacifyTicks; this.setPacifying(true); this.startPacify(); return result; }, endPacify : function() { this.setPacifying(false); }, // default sizing implementation resize : function() { if (this.scrollboxNode) { this.windowHeight = this.scrollboxNode.clientHeight; } dojox.grid.setStyleHeightPx(this.contentNode, this.height); }, calcLastPageHeight : function() { if (!this.pageCount) { return 0; } var lastPage = this.pageCount - 1; var lastPageHeight = ((this.rowCount % this.rowsPerPage) || (this.rowsPerPage)) * this.defaultRowHeight; this.pageHeights[lastPage] = lastPageHeight; return lastPageHeight; }, updateContentHeight : function(inDh) { this.height += inDh; this.resize(); }, updatePageHeight : function(inPageIndex) { if (this.pageExists(inPageIndex)) { var oh = this.getPageHeight(inPageIndex); var h = (this.measurePage(inPageIndex)) || (oh); this.pageHeights[inPageIndex] = h; if ((h) && (oh != h)) { this.updateContentHeight(h - oh) this.repositionPages(inPageIndex); } } }, rowHeightChanged : function(inRowIndex) { this.updatePageHeight(Math.floor(inRowIndex / this.rowsPerPage)); }, // scroller core invalidateNodes : function() { while (this.stack.length) { this.destroyPage(this.popPage()); } }, createPageNode : function() { var p = document.createElement('div'); p.style.position = 'absolute'; // p.style.width = '100%'; p.style.left = '0'; return p; }, getPageHeight : function(inPageIndex) { var ph = this.pageHeights[inPageIndex]; return (ph !== undefined ? ph : this.defaultPageHeight); }, // FIXME: this is not a stack, it's a FIFO list pushPage : function(inPageIndex) { return this.stack.push(inPageIndex); }, popPage : function() { return this.stack.shift(); }, findPage : function(inTop) { var i = 0, h = 0; for (var ph = 0; i < this.pageCount; i++, h += ph) { ph = this.getPageHeight(i); if (h + ph >= inTop) { break; } } this.page = i; this.pageTop = h; }, buildPage : function(inPageIndex, inReuseNode, inPos) { this.preparePage(inPageIndex, inReuseNode); this.positionPage(inPageIndex, inPos); // order of operations is key below this.installPage(inPageIndex); this.renderPage(inPageIndex); // order of operations is key above this.pushPage(inPageIndex); }, needPage : function(inPageIndex, inPos) { var h = this.getPageHeight(inPageIndex), oh = h; if (!this.pageExists(inPageIndex)) { this .buildPage( inPageIndex, (this.keepPages) && (this.stack.length >= this.keepPages), inPos); h = this.measurePage(inPageIndex) || h; this.pageHeights[inPageIndex] = h; if (h && (oh != h)) { this.updateContentHeight(h - oh) } } else { this.positionPage(inPageIndex, inPos); } return h; }, onscroll : function() { this.scroll(this.scrollboxNode.scrollTop); }, scroll : function(inTop) { this.startPacify(); this.findPage(inTop); var h = this.height; var b = this.getScrollBottom(inTop); for (var p = this.page, y = this.pageTop; (p < this.pageCount) && ((b < 0) || (y < b)); p++) { y += this.needPage(p, y); } this.firstVisibleRow = this.getFirstVisibleRow(this.page, this.pageTop, inTop); this.lastVisibleRow = this.getLastVisibleRow(p - 1, y, b); // indicates some page size has been updated if (h != this.height) { this.repositionPages(p - 1); } this.endPacify(); }, getScrollBottom : function(inTop) { return (this.windowHeight >= 0 ? inTop + this.windowHeight : -1); }, // events processNodeEvent : function(e, inNode) { var t = e.target; while (t && (t != inNode) && t.parentNode && (t.parentNode.parentNode != inNode)) { t = t.parentNode; } if (!t || !t.parentNode || (t.parentNode.parentNode != inNode)) { return false; } var page = t.parentNode; e.topRowIndex = page.pageIndex * this.rowsPerPage; e.rowIndex = e.topRowIndex + dojox.grid.indexInParent(t); e.rowTarget = t; return true; }, processEvent : function(e) { return this.processNodeEvent(e, this.contentNode); }, dummy : 0 }); dojo.declare('dojox.grid.scroller', dojox.grid.scroller.base, { // summary: // virtual scroller class, makes no assumption about shape of items // being scrolled constructor : function() { this.pageNodes = []; }, // virtual rendering interface renderRow : function(inRowIndex, inPageNode) { }, removeRow : function(inRowIndex) { }, // page node operations getDefaultNodes : function() { return this.pageNodes; }, getDefaultPageNode : function(inPageIndex) { return this.getDefaultNodes()[inPageIndex]; }, positionPageNode : function(inNode, inPos) { inNode.style.top = inPos + 'px'; }, getPageNodePosition : function(inNode) { return inNode.offsetTop; }, repositionPageNodes : function(inPageIndex, inNodes) { var last = 0; for (var i = 0; i < this.stack.length; i++) { last = Math.max(this.stack[i], last); } // var n = inNodes[inPageIndex]; var y = (n ? this.getPageNodePosition(n) + this.getPageHeight(inPageIndex) : 0); // console.log('detected height change, repositioning from #%d (%d) // @ %d ', inPageIndex + 1, last, y, this.pageHeights[0]); // for (var p = inPageIndex + 1; p <= last; p++) { n = inNodes[p]; if (n) { // console.log('#%d @ %d', inPageIndex, y, // this.getPageNodePosition(n)); if (this.getPageNodePosition(n) == y) { return; } // console.log('placing page %d at %d', p, y); this.positionPage(p, y); } y += this.getPageHeight(p); } }, invalidatePageNode : function(inPageIndex, inNodes) { var p = inNodes[inPageIndex]; if (p) { delete inNodes[inPageIndex]; this.removePage(inPageIndex, p); dojox.grid.cleanNode(p); p.innerHTML = ''; } return p; }, preparePageNode : function(inPageIndex, inReusePageIndex, inNodes) { var p = (inReusePageIndex === null ? this.createPageNode() : this .invalidatePageNode(inReusePageIndex, inNodes)); p.pageIndex = inPageIndex; p.id = 'page-' + inPageIndex; inNodes[inPageIndex] = p; }, // implementation for page manager pageExists : function(inPageIndex) { return Boolean(this.getDefaultPageNode(inPageIndex)); }, measurePage : function(inPageIndex) { return this.getDefaultPageNode(inPageIndex).offsetHeight; }, positionPage : function(inPageIndex, inPos) { this.positionPageNode(this.getDefaultPageNode(inPageIndex), inPos); }, repositionPages : function(inPageIndex) { this.repositionPageNodes(inPageIndex, this.getDefaultNodes()); }, preparePage : function(inPageIndex, inReuseNode) { this.preparePageNode(inPageIndex, (inReuseNode ? this.popPage() : null), this.getDefaultNodes()); }, installPage : function(inPageIndex) { this.contentNode.appendChild(this.getDefaultPageNode(inPageIndex)); }, destroyPage : function(inPageIndex) { var p = this .invalidatePageNode(inPageIndex, this.getDefaultNodes()); dojox.grid.removeNode(p); }, // rendering implementation renderPage : function(inPageIndex) { var node = this.pageNodes[inPageIndex]; for (var i = 0, j = inPageIndex * this.rowsPerPage; (i < this.rowsPerPage) && (j < this.rowCount); i++, j++) { this.renderRow(j, node); } }, removePage : function(inPageIndex) { for (var i = 0, j = inPageIndex * this.rowsPerPage; i < this.rowsPerPage; i++, j++) { this.removeRow(j); } }, // scroll control getPageRow : function(inPage) { return inPage * this.rowsPerPage; }, getLastPageRow : function(inPage) { return Math.min(this.rowCount, this.getPageRow(inPage + 1)) - 1; }, getFirstVisibleRowNodes : function(inPage, inPageTop, inScrollTop, inNodes) { var row = this.getPageRow(inPage); var rows = dojox.grid.divkids(inNodes[inPage]); for (var i = 0, l = rows.length; i < l && inPageTop < inScrollTop; i++, row++) { inPageTop += rows[i].offsetHeight; } return (row ? row - 1 : row); }, getFirstVisibleRow : function(inPage, inPageTop, inScrollTop) { if (!this.pageExists(inPage)) { return 0; } return this.getFirstVisibleRowNodes(inPage, inPageTop, inScrollTop, this.getDefaultNodes()); }, getLastVisibleRowNodes : function(inPage, inBottom, inScrollBottom, inNodes) { var row = this.getLastPageRow(inPage); var rows = dojox.grid.divkids(inNodes[inPage]); for (var i = rows.length - 1; i >= 0 && inBottom > inScrollBottom; i--, row--) { inBottom -= rows[i].offsetHeight; } return row + 1; }, getLastVisibleRow : function(inPage, inBottom, inScrollBottom) { if (!this.pageExists(inPage)) { return 0; } return this.getLastVisibleRowNodes(inPage, inBottom, inScrollBottom, this.getDefaultNodes()); }, findTopRowForNodes : function(inScrollTop, inNodes) { var rows = dojox.grid.divkids(inNodes[this.page]); for (var i = 0, l = rows.length, t = this.pageTop, h; i < l; i++) { h = rows[i].offsetHeight; t += h; if (t >= inScrollTop) { this.offset = h - (t - inScrollTop); return i + this.page * this.rowsPerPage; } } return -1; }, findScrollTopForNodes : function(inRow, inNodes) { var rowPage = Math.floor(inRow / this.rowsPerPage); var t = 0; for (var i = 0; i < rowPage; i++) { t += this.getPageHeight(i); } this.pageTop = t; this.needPage(rowPage, this.pageTop); var rows = dojox.grid.divkids(inNodes[rowPage]); var r = inRow - this.rowsPerPage * rowPage; for (var i = 0, l = rows.length; i < l && i < r; i++) { t += rows[i].offsetHeight; } return t; }, findTopRow : function(inScrollTop) { return this.findTopRowForNodes(inScrollTop, this.getDefaultNodes()); }, findScrollTop : function(inRow) { return this.findScrollTopForNodes(inRow, this.getDefaultNodes()); }, dummy : 0 }); dojo.declare('dojox.grid.scroller.columns', dojox.grid.scroller, { // summary: // Virtual scroller class that scrolls list of columns. Owned by grid // and used internally // for virtual scrolling. constructor : function(inContentNodes) { this.setContentNodes(inContentNodes); }, // nodes setContentNodes : function(inNodes) { this.contentNodes = inNodes; this.colCount = (this.contentNodes ? this.contentNodes.length : 0); this.pageNodes = []; for (var i = 0; i < this.colCount; i++) { this.pageNodes[i] = []; } }, getDefaultNodes : function() { return this.pageNodes[0] || []; }, scroll : function(inTop) { if (this.colCount) { dojox.grid.scroller.prototype.scroll.call(this, inTop); } }, // resize resize : function() { if (this.scrollboxNode) { this.windowHeight = this.scrollboxNode.clientHeight; } for (var i = 0; i < this.colCount; i++) { dojox.grid.setStyleHeightPx(this.contentNodes[i], this.height); } }, // implementation for page manager positionPage : function(inPageIndex, inPos) { for (var i = 0; i < this.colCount; i++) { this.positionPageNode(this.pageNodes[i][inPageIndex], inPos); } }, preparePage : function(inPageIndex, inReuseNode) { var p = (inReuseNode ? this.popPage() : null); for (var i = 0; i < this.colCount; i++) { this.preparePageNode(inPageIndex, p, this.pageNodes[i]); } }, installPage : function(inPageIndex) { for (var i = 0; i < this.colCount; i++) { this.contentNodes[i] .appendChild(this.pageNodes[i][inPageIndex]); } }, destroyPage : function(inPageIndex) { for (var i = 0; i < this.colCount; i++) { dojox.grid.removeNode(this.invalidatePageNode(inPageIndex, this.pageNodes[i])); } }, // rendering implementation renderPage : function(inPageIndex) { var nodes = []; for (var i = 0; i < this.colCount; i++) { nodes[i] = this.pageNodes[i][inPageIndex]; } // this.renderRows(inPageIndex*this.rowsPerPage, this.rowsPerPage, // nodes); for (var i = 0, j = inPageIndex * this.rowsPerPage; (i < this.rowsPerPage) && (j < this.rowCount); i++, j++) { this.renderRow(j, nodes); } } }); }