View Full Version : Ext.ux.tree.PagingNodeUI - a pagingBar for trees

10 Mar 2008, 10:12 PM
Hi there,

I am working on a toolkit for database applications and we have to provide a tree control. However each tree node can have any number of children - thousands and more.

Now the regular Ext.tree works great if every node has a reasonable number of children but for our case it might just blow up, if someone wants to use it for large amounts of data. The obvious solution is paging - but it has to work for every node that has more than a certain number of children.

So I have written an extension of TreeNodeUI to render the paging bar (it might be better to also extend TreeNode, but I didn't have time for that yet...)

When the user selects a specific page, the parent-node's children are reloaded, with the additional pageNum parameter - so that on the server you can render the correct children.

So here is the extension:

Ext.ux.PagingTreeNodeUI = Ext.extend(Ext.tree.TreeNodeUI,
renderElements : function(n, a, targetNode, bulkRender){
this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';

var attribs = n.attributes;
var currentBlock = attribs.currentBlock;
var fullCount = attribs.fullCount;
var blockSize = attribs.blockSize;
var blockCount = Math.ceil(fullCount/blockSize);
var renderBtn = function(stringStack, pageNum, text, addlClass)
stringStack.push('<div class="gs_tree_pgbtn')
stringStack.push(' ' + addlClass);
stringStack.push('"><div gs:page="');
stringStack.push('" class="x-tree-col-text">');

var buf = [
'<li class="x-tree-node gs_tree_pagingbar"><div ext:tree-node-id="',n.id,'" class="x-tree-node-el ', a.cls,'">',
'<div class="x-tree-col">',
'<span class="x-tree-node-indent">',this.indentMarkup,"</span>",
'<img src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow">',
'<img src="', this.emptyIcon, '" class="x-tree-node-icon" unselectable="on">',
'<a hidefocus="on" class="x-tree-node-anchor" href="',a.href ? a.href : "#",'" tabIndex="1">',
'<span unselectable="on">Page ',currentBlock+1, " of ", blockCount, "</span></a>",

var i;

renderBtn(buf, currentBlock-1, '<');

for(i=0; i<blockCount; i++)
renderBtn(buf, i, i+1, currentBlock==i?'gs_tree_pgbtn_sel':null);
// always render the link to the first page:
renderBtn(buf, 0, 1, currentBlock==0?'gs_tree_pgbtn_sel':null);

// render the current page link and the three links before and after:
var from = Math.max(1, currentBlock-2);
var to = Math.min(blockCount-1, currentBlock+3);

buf.push('<div class="x-tree-col"><div class="x-tree-col-text">...</div></div>');

for(i=from; i<to; i++)
renderBtn(buf, i, i+1, currentBlock==i?'gs_tree_pgbtn_sel':null);

buf.push('<div class="x-tree-col"><div class="x-tree-col-text">...</div></div>');

renderBtn(buf, blockCount-1, blockCount, currentBlock==blockCount-1?'gs_tree_pgbtn_sel':null);
renderBtn(buf, currentBlock+1, '>');

var post= ['<div class="x-tree-col"><div id="last" class="x-tree-col-text">Displaying rows ',currentBlock*blockSize+1, ' to ', Math.min((currentBlock+1)*blockSize, fullCount), " of ", fullCount, '</div></div>',
'<div class="x-clear"></div></div>',
'<ul class="x-tree-node-ct" style="display:none;"></ul>',

var nodeStr = buf.join('')+post.join('');

if(bulkRender !== true && n.nextSibling && n.nextSibling.ui.getEl()){
this.wrap = Ext.DomHelper.insertHtml("beforeBegin",
n.nextSibling.ui.getEl(), nodeStr);
this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, nodeStr);
function(node, evt)
var loader = node.getOwnerTree().getLoader();
var target = evt.target;
return false;
loader.baseParams.pageNum = target.attributes.getNamedItem('gs:page').value;
var parent = node.parentNode;
parent.getUI().beforeLoad(); //display the loading icon
loader.load(parent, function()
parent.getUI().afterLoad(); //remove the loading icon again
delete loader.baseParams.pageNum;
return false;

this.elNode = this.wrap.childNodes[0];
this.ctNode = this.wrap.childNodes[1];
var cs = this.elNode.firstChild.childNodes;
this.indentNode = cs[0];
this.ecNode = cs[1];
this.iconNode = cs[2];
this.anchor = cs[3];
this.textNode = cs[3].firstChild;

And these are the styles you need:

.gs_tree_pgbtn {
padding:0 5px;
.gs_tree_pgbtn :hover {
.gs_tree_pgbtn_sel {

To use the TreePagingBar, you have to send a special record in the JSON response containing the children of a tree like this:

{text:'My Node',uiProvider:'col',cls:'master-task',id:'root,3,0',leaf:true}

And of course you have to register the TreeNodeUI implementation when creating the treeLoader (just like for the columnTree):

new Ext.tree.TreeLoader({
'col': Ext.tree.ColumnNodeUI,
'paging': Ext.ux.PagingTreeNodeUI

The extension works fine and does the job, but there are some things that could be done to improve it - such as:
Cache the pages that have once been loaded, so the expand/collapsed status does not get lost when switching between pages
Improve the styling
When reloading it collapses and then expands the parent node - this looks a bit odd

Maybe you can think of other things - and implement them if you have a chance :) - let me know what you think about it. I did it in just a day - and for that I think its pretty cool. I trie d it only with the columnTree, but it should work with any tree....I hope

Guido Deinhammer

11 Mar 2008, 5:03 AM
Thank you, gdein! Do you plan to set up a demo page for your code? It would be nice to have something to play with.

12 Mar 2008, 4:42 AM
I don't have webspace for a demo - but I am attaching a screenshot so you can see what it is about.
It shows the pagingBar with a pageSize of 3 - just to show the concept.