1 if(!dojo._hasResource['dojox.grid._grid.scroller']){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2 dojo._hasResource['dojox.grid._grid.scroller'] = true;
3 dojo.provide('dojox.grid._grid.scroller');
5 dojo.declare('dojox.grid.scroller.base', null, {
7 // virtual scrollbox, abstract class
8 // Content must in /rows/
9 // Rows are managed in contiguous sets called /pages/
10 // There are a fixed # of rows per page
11 // The minimum rendered unit is a page
12 constructor: function(){
13 this.pageHeights = [];
17 rowCount: 0, // total number of rows to manage
18 defaultRowHeight: 10, // default height of a row
19 keepRows: 100, // maximum number of rows that should exist at one time
20 contentNode: null, // node to contain pages
21 scrollboxNode: null, // node that controls scrolling
23 defaultPageHeight: 0, // default height of a page
24 keepPages: 10, // maximum number of pages that should exists at one time
33 init: function(inRowCount, inKeepRows, inRowsPerPage){
34 switch(arguments.length){
35 case 3: this.rowsPerPage = inRowsPerPage;
36 case 2: this.keepRows = inKeepRows;
37 case 1: this.rowCount = inRowCount;
39 this.defaultPageHeight = this.defaultRowHeight * this.rowsPerPage;
40 //this.defaultPageHeight = this.defaultRowHeight * Math.min(this.rowsPerPage, this.rowCount);
41 this.pageCount = Math.ceil(this.rowCount / this.rowsPerPage);
42 this.setKeepInfo(this.keepRows);
44 if(this.scrollboxNode){
45 this.scrollboxNode.scrollTop = 0;
47 this.scrollboxNode.onscroll = dojo.hitch(this, 'onscroll');
50 setKeepInfo: function(inKeepRows){
51 this.keepRows = inKeepRows;
52 this.keepPages = !this.keepRows ? this.keepRows : Math.max(Math.ceil(this.keepRows / this.rowsPerPage), 2);
55 invalidate: function(){
56 this.invalidateNodes();
57 this.pageHeights = [];
58 this.height = (this.pageCount ? (this.pageCount - 1)* this.defaultPageHeight + this.calcLastPageHeight() : 0);
61 updateRowCount: function(inRowCount){
62 this.invalidateNodes();
63 this.rowCount = inRowCount;
64 // update page count, adjust document height
65 oldPageCount = this.pageCount;
66 this.pageCount = Math.ceil(this.rowCount / this.rowsPerPage);
67 if(this.pageCount < oldPageCount){
68 for(var i=oldPageCount-1; i>=this.pageCount; i--){
69 this.height -= this.getPageHeight(i);
70 delete this.pageHeights[i]
72 }else if(this.pageCount > oldPageCount){
73 this.height += this.defaultPageHeight * (this.pageCount - oldPageCount - 1) + this.calcLastPageHeight();
78 pageExists: function(inPageIndex){
80 measurePage: function(inPageIndex){
82 positionPage: function(inPageIndex, inPos){
84 repositionPages: function(inPageIndex){
86 installPage: function(inPageIndex){
88 preparePage: function(inPageIndex, inPos, inReuseNode){
90 renderPage: function(inPageIndex){
92 removePage: function(inPageIndex){
94 pacify: function(inShouldPacify){
99 setPacifying: function(inPacifying){
100 if(this.pacifying != inPacifying){
101 this.pacifying = inPacifying;
102 this.pacify(this.pacifying);
105 startPacify: function(){
106 this.startPacifyTicks = new Date().getTime();
108 doPacify: function(){
109 var result = (new Date().getTime() - this.startPacifyTicks) > this.pacifyTicks;
110 this.setPacifying(true);
114 endPacify: function(){
115 this.setPacifying(false);
117 // default sizing implementation
119 if(this.scrollboxNode){
120 this.windowHeight = this.scrollboxNode.clientHeight;
122 dojox.grid.setStyleHeightPx(this.contentNode, this.height);
124 calcLastPageHeight: function(){
128 var lastPage = this.pageCount - 1;
129 var lastPageHeight = ((this.rowCount % this.rowsPerPage)||(this.rowsPerPage)) * this.defaultRowHeight;
130 this.pageHeights[lastPage] = lastPageHeight;
131 return lastPageHeight;
133 updateContentHeight: function(inDh){
137 updatePageHeight: function(inPageIndex){
138 if(this.pageExists(inPageIndex)){
139 var oh = this.getPageHeight(inPageIndex);
140 var h = (this.measurePage(inPageIndex))||(oh);
141 this.pageHeights[inPageIndex] = h;
143 this.updateContentHeight(h - oh)
144 this.repositionPages(inPageIndex);
148 rowHeightChanged: function(inRowIndex){
149 this.updatePageHeight(Math.floor(inRowIndex / this.rowsPerPage));
152 invalidateNodes: function(){
153 while(this.stack.length){
154 this.destroyPage(this.popPage());
157 createPageNode: function(){
158 var p = document.createElement('div');
159 p.style.position = 'absolute';
160 //p.style.width = '100%';
161 p.style[dojo._isBodyLtr() ? "left" : "right"] = '0';
164 getPageHeight: function(inPageIndex){
165 var ph = this.pageHeights[inPageIndex];
166 return (ph !== undefined ? ph : this.defaultPageHeight);
168 // FIXME: this is not a stack, it's a FIFO list
169 pushPage: function(inPageIndex){
170 return this.stack.push(inPageIndex);
173 return this.stack.shift();
175 findPage: function(inTop){
177 for(var ph = 0; i<this.pageCount; i++, h += ph){
178 ph = this.getPageHeight(i);
186 buildPage: function(inPageIndex, inReuseNode, inPos){
187 this.preparePage(inPageIndex, inReuseNode);
188 this.positionPage(inPageIndex, inPos);
189 // order of operations is key below
190 this.installPage(inPageIndex);
191 this.renderPage(inPageIndex);
192 // order of operations is key above
193 this.pushPage(inPageIndex);
195 needPage: function(inPageIndex, inPos){
196 var h = this.getPageHeight(inPageIndex), oh = h;
197 if(!this.pageExists(inPageIndex)){
198 this.buildPage(inPageIndex, this.keepPages&&(this.stack.length >= this.keepPages), inPos);
199 h = this.measurePage(inPageIndex) || h;
200 this.pageHeights[inPageIndex] = h;
202 this.updateContentHeight(h - oh)
205 this.positionPage(inPageIndex, inPos);
209 onscroll: function(){
210 this.scroll(this.scrollboxNode.scrollTop);
212 scroll: function(inTop){
214 this.findPage(inTop);
216 var b = this.getScrollBottom(inTop);
217 for(var p=this.page, y=this.pageTop; (p<this.pageCount)&&((b<0)||(y<b)); p++){
218 y += this.needPage(p, y);
220 this.firstVisibleRow = this.getFirstVisibleRow(this.page, this.pageTop, inTop);
221 this.lastVisibleRow = this.getLastVisibleRow(p - 1, y, b);
222 // indicates some page size has been updated
223 if(h != this.height){
224 this.repositionPages(p-1);
228 getScrollBottom: function(inTop){
229 return (this.windowHeight >= 0 ? inTop + this.windowHeight : -1);
232 processNodeEvent: function(e, inNode){
234 while(t && (t != inNode) && t.parentNode && (t.parentNode.parentNode != inNode)){
237 if(!t || !t.parentNode || (t.parentNode.parentNode != inNode)){
240 var page = t.parentNode;
241 e.topRowIndex = page.pageIndex * this.rowsPerPage;
242 e.rowIndex = e.topRowIndex + dojox.grid.indexInParent(t);
246 processEvent: function(e){
247 return this.processNodeEvent(e, this.contentNode);
252 dojo.declare('dojox.grid.scroller', dojox.grid.scroller.base, {
254 // virtual scroller class, makes no assumption about shape of items being scrolled
255 constructor: function(){
258 // virtual rendering interface
259 renderRow: function(inRowIndex, inPageNode){
261 removeRow: function(inRowIndex){
263 // page node operations
264 getDefaultNodes: function(){
265 return this.pageNodes;
267 getDefaultPageNode: function(inPageIndex){
268 return this.getDefaultNodes()[inPageIndex];
270 positionPageNode: function(inNode, inPos){
271 inNode.style.top = inPos + 'px';
273 getPageNodePosition: function(inNode){
274 return inNode.offsetTop;
276 repositionPageNodes: function(inPageIndex, inNodes){
278 for(var i=0; i<this.stack.length; i++){
279 last = Math.max(this.stack[i], last);
282 var n = inNodes[inPageIndex];
283 var y = (n ? this.getPageNodePosition(n) + this.getPageHeight(inPageIndex) : 0);
284 //console.log('detected height change, repositioning from #%d (%d) @ %d ', inPageIndex + 1, last, y, this.pageHeights[0]);
286 for(var p=inPageIndex+1; p<=last; p++){
289 //console.log('#%d @ %d', inPageIndex, y, this.getPageNodePosition(n));
290 if(this.getPageNodePosition(n) == y){
293 //console.log('placing page %d at %d', p, y);
294 this.positionPage(p, y);
296 y += this.getPageHeight(p);
299 invalidatePageNode: function(inPageIndex, inNodes){
300 var p = inNodes[inPageIndex];
302 delete inNodes[inPageIndex];
303 this.removePage(inPageIndex, p);
304 dojox.grid.cleanNode(p);
309 preparePageNode: function(inPageIndex, inReusePageIndex, inNodes){
310 var p = (inReusePageIndex === null ? this.createPageNode() : this.invalidatePageNode(inReusePageIndex, inNodes));
311 p.pageIndex = inPageIndex;
312 p.id = (this._pageIdPrefix || "") + 'page-' + inPageIndex;
313 inNodes[inPageIndex] = p;
315 // implementation for page manager
316 pageExists: function(inPageIndex){
317 return Boolean(this.getDefaultPageNode(inPageIndex));
319 measurePage: function(inPageIndex){
320 return this.getDefaultPageNode(inPageIndex).offsetHeight;
322 positionPage: function(inPageIndex, inPos){
323 this.positionPageNode(this.getDefaultPageNode(inPageIndex), inPos);
325 repositionPages: function(inPageIndex){
326 this.repositionPageNodes(inPageIndex, this.getDefaultNodes());
328 preparePage: function(inPageIndex, inReuseNode){
329 this.preparePageNode(inPageIndex, (inReuseNode ? this.popPage() : null), this.getDefaultNodes());
331 installPage: function(inPageIndex){
332 this.contentNode.appendChild(this.getDefaultPageNode(inPageIndex));
334 destroyPage: function(inPageIndex){
335 var p = this.invalidatePageNode(inPageIndex, this.getDefaultNodes());
336 dojox.grid.removeNode(p);
338 // rendering implementation
339 renderPage: function(inPageIndex){
340 var node = this.pageNodes[inPageIndex];
341 for(var i=0, j=inPageIndex*this.rowsPerPage; (i<this.rowsPerPage)&&(j<this.rowCount); i++, j++){
342 this.renderRow(j, node);
345 removePage: function(inPageIndex){
346 for(var i=0, j=inPageIndex*this.rowsPerPage; i<this.rowsPerPage; i++, j++){
351 getPageRow: function(inPage){
352 return inPage * this.rowsPerPage;
354 getLastPageRow: function(inPage){
355 return Math.min(this.rowCount, this.getPageRow(inPage + 1)) - 1;
357 getFirstVisibleRowNodes: function(inPage, inPageTop, inScrollTop, inNodes){
358 var row = this.getPageRow(inPage);
359 var rows = dojox.grid.divkids(inNodes[inPage]);
360 for(var i=0,l=rows.length; i<l && inPageTop<inScrollTop; i++, row++){
361 inPageTop += rows[i].offsetHeight;
363 return (row ? row - 1 : row);
365 getFirstVisibleRow: function(inPage, inPageTop, inScrollTop){
366 if(!this.pageExists(inPage)){
369 return this.getFirstVisibleRowNodes(inPage, inPageTop, inScrollTop, this.getDefaultNodes());
371 getLastVisibleRowNodes: function(inPage, inBottom, inScrollBottom, inNodes){
372 var row = this.getLastPageRow(inPage);
373 var rows = dojox.grid.divkids(inNodes[inPage]);
374 for(var i=rows.length-1; i>=0 && inBottom>inScrollBottom; i--, row--){
375 inBottom -= rows[i].offsetHeight;
379 getLastVisibleRow: function(inPage, inBottom, inScrollBottom){
380 if(!this.pageExists(inPage)){
383 return this.getLastVisibleRowNodes(inPage, inBottom, inScrollBottom, this.getDefaultNodes());
385 findTopRowForNodes: function(inScrollTop, inNodes){
386 var rows = dojox.grid.divkids(inNodes[this.page]);
387 for(var i=0,l=rows.length,t=this.pageTop,h; i<l; i++){
388 h = rows[i].offsetHeight;
390 if(t >= inScrollTop){
391 this.offset = h - (t - inScrollTop);
392 return i + this.page * this.rowsPerPage;
397 findScrollTopForNodes: function(inRow, inNodes){
398 var rowPage = Math.floor(inRow / this.rowsPerPage);
400 for(var i=0; i<rowPage; i++){
401 t += this.getPageHeight(i);
404 this.needPage(rowPage, this.pageTop);
405 var rows = dojox.grid.divkids(inNodes[rowPage]);
406 var r = inRow - this.rowsPerPage * rowPage;
407 for(var i=0,l=rows.length; i<l && i<r; i++){
408 t += rows[i].offsetHeight;
412 findTopRow: function(inScrollTop){
413 return this.findTopRowForNodes(inScrollTop, this.getDefaultNodes());
415 findScrollTop: function(inRow){
416 return this.findScrollTopForNodes(inRow, this.getDefaultNodes());
421 dojo.declare('dojox.grid.scroller.columns', dojox.grid.scroller, {
423 // Virtual scroller class that scrolls list of columns. Owned by grid and used internally
424 // for virtual scrolling.
425 constructor: function(inContentNodes){
426 this.setContentNodes(inContentNodes);
429 setContentNodes: function(inNodes){
430 this.contentNodes = inNodes;
431 this.colCount = (this.contentNodes ? this.contentNodes.length : 0);
433 for(var i=0; i<this.colCount; i++){
434 this.pageNodes[i] = [];
437 getDefaultNodes: function(){
438 return this.pageNodes[0] || [];
440 scroll: function(inTop) {
442 dojox.grid.scroller.prototype.scroll.call(this, inTop);
447 if(this.scrollboxNode){
448 this.windowHeight = this.scrollboxNode.clientHeight;
450 for(var i=0; i<this.colCount; i++){
451 dojox.grid.setStyleHeightPx(this.contentNodes[i], this.height);
454 // implementation for page manager
455 positionPage: function(inPageIndex, inPos){
456 for(var i=0; i<this.colCount; i++){
457 this.positionPageNode(this.pageNodes[i][inPageIndex], inPos);
460 preparePage: function(inPageIndex, inReuseNode){
461 var p = (inReuseNode ? this.popPage() : null);
462 for(var i=0; i<this.colCount; i++){
463 this.preparePageNode(inPageIndex, p, this.pageNodes[i]);
466 installPage: function(inPageIndex){
467 for(var i=0; i<this.colCount; i++){
468 this.contentNodes[i].appendChild(this.pageNodes[i][inPageIndex]);
471 destroyPage: function(inPageIndex){
472 for(var i=0; i<this.colCount; i++){
473 dojox.grid.removeNode(this.invalidatePageNode(inPageIndex, this.pageNodes[i]));
476 // rendering implementation
477 renderPage: function(inPageIndex){
479 for(var i=0; i<this.colCount; i++){
480 nodes[i] = this.pageNodes[i][inPageIndex];
482 //this.renderRows(inPageIndex*this.rowsPerPage, this.rowsPerPage, nodes);
483 for(var i=0, j=inPageIndex*this.rowsPerPage; (i<this.rowsPerPage)&&(j<this.rowCount); i++, j++){
484 this.renderRow(j, nodes);