PDA

View Full Version : TreePanel with tri-state checkboxes



j.bruni
4 May 2010, 11:32 AM
I have not found a TreePanel with tri-state checkboxes the way I needed it, so I implemented it myself and I am proudly sharing with the community.


Features:

- If a parent node is checked / unchecked, all its child nodes are automatically checked / unchecked too.


- If only some childs of a node are selected, the checkbox remains checked, but with a third visual state, using a darkened background.


- A single image file defines all the three states images.


Example, with source code:

http://www.jbruni.com.br/extjs-tristate/


The example page was based on this official ExtJS example:
http://www.extjs.com/deploy/dev/examples/tree/check-tree.html


Note: This implementation can surely be improved. Use it as a base and adapt it for your own needs.

Stju
4 May 2010, 4:41 PM
Nice.
As a side effect - If the tree is not loaded fully from beginning then:
1. You click on nodes checkbox
2. It draws checked for clicked node
3. Loading of clicked node child nodes is still in progress!
4. Loading of child nodes finished
5. None of child nodes are checked :(

Otherwise Ecellent :)

j.bruni
5 May 2010, 2:03 AM
As a side effect - If the tree is not loaded fully from beginning then:
1. You click on nodes checkbox
2. It draws checked for clicked node
3. Loading of clicked node child nodes is still in progress!
4. Loading of child nodes finished
5. None of child nodes are checked :(


1) Try changing the TreePanel "animate" setting to false and see if it works.


animate:false,2) People in another thread went deep in this not-preloaded-children issue (http://www.extjs.com/forum/showthread.php?36179-TreePanel-Smart-Checkboxes&p=173742#post173742), but I have not spent time with it because in my case the tree was small and all loaded at once. You may want to check and see how they solved this, and perhaps do the necessary adaptations in the code.

Thanks for the feedback!

Stju
5 May 2010, 8:54 AM
No... That doesn't helped.. animate was initially set as false..

Stju
5 May 2010, 9:06 AM
Made some modifications according to thread You sugessted! And -voila It works :)
So here is updated function in case someone else needs it:


checkChange:function(node, checked){
if (!this.changing)
{
this.changing = true;
node.expand(true, false,function(node){
if (checked)
node.cascade( function(node){ node.getUI().toggleCheck(true) } );
else
node.cascade( function(node){ node.getUI().toggleCheck(false) } );
node.bubble( function(node){ if (node.parentNode) node.getUI().updateCheck() } );
});
this.changing = false;
}
}

fermo111
9 Jun 2010, 1:50 AM
It does not work if some of the json nodes loaded have a

checked = true

wemerson.januario
9 Jun 2010, 12:44 PM
nice work, I will test it soon in my project. Thanks

Balbuzar
10 Jun 2010, 3:52 AM
Nice work, but it runs only on FF :(

Doesn't work on Chrome 5, Safari 5, IE 8 maybe due to the use of the 'default' keyname used on check-tree-tristate.js line 94

uiProviders: { default: Ext.tree.TreeNodeUI, tristate: Ext.tree.TreeNodeTriStateUI }
I can't test my solution but maybe this can fix:

uiProviders: { "default": Ext.tree.TreeNodeUI, tristate: Ext.tree.TreeNodeTriStateUI }

andyghiuta
21 Jun 2010, 6:22 AM
Can you post the example on another page, maybe upload the source somewhere? because your page is not working anymore.
Thanks!

LE: works now, Thank you very much!

Stju
29 Jun 2010, 1:50 PM
This is modified version of original j.bruni version.
Things changed:
1.It is possible now to load tree content from JSON and mark node as 'checked':true. Be sure to pass boolean value not the string, otherwise checkbox will remain unchecked! Remarking partial node status is done by script, so there is no need to send info about partial status!
2. Changed css, to show correctly in Webkit browsers.
3. Resolved issue with not preloaded children nodes.
4. Comment: specify only
uiProviders: {tristate: Ext.tree.TreeNodeTriStateUI }, as UI provider!

Tested against 3.2.2 (but should be okay with 3.2.x). FF3.6.3, Opera 10.54, Safari 5.
CSS:


.styledCheckbox {
height: 13px;
width: 13px;
filter: alpha(opacity=0.0);
opacity: 0.0;
outline: 0;
}

.styledCheckboxWrap {
position: relative;
background: url('checkboxes.gif') no-repeat top left;
width: 13px;
height: 13px;
margin: 3px;
}

.ext-webkit .styledCheckboxWrap {
padding-bottom:1px;
}
.wrapChecked{ background-position: 0px -13px; }

.wrapPartial{ background-position: 0px -26px; }
TreeNodeTriStateUI.js


Ext.tree.TreeNodeTriStateUI = function() {
Ext.tree.TreeNodeTriStateUI.superclass.constructor.apply(this, arguments);
this.partial = false;
};

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

var cb = Ext.isBoolean(a.checked),
nel,
href = a.href ? a.href : Ext.isGecko ? "" : "#",
buf = ['<li class="x-tree-node"><div ext:tree-node-id="',n.id,'" class="x-tree-node-el x-tree-node-leaf x-unselectable ', a.cls,'" unselectable="on">',
'<span class="x-tree-node-indent">',this.indentMarkup,"</span>",
'<img src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow" />',
'<img src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon',(a.icon ? " x-tree-node-inline-icon" : ""),(a.iconCls ? " "+a.iconCls : ""),'" unselectable="on" />',
cb ? ('<span class="styledCheckboxWrap '+ (a.checked ? ' wrapChecked' :'')+'"><input class="x-tree-node-cb styledCheckbox" type="checkbox" ' +
(a.checked ? 'checked="checked" />' : '/>') + '</span>' ) : '', //modified for load checked nodes feature
'<a hidefocus="on" class="x-tree-node-anchor" href="',href,'" tabIndex="1" ',
a.hrefTarget ? ' target="'+a.hrefTarget+'"' : "", '><span unselectable="on">',n.text,"</span></a></div>",
'<ul class="x-tree-node-ct" style="display:none;"></ul>',
"</li>"].join('');

if(bulkRender !== true && n.nextSibling && (nel = n.nextSibling.ui.getEl())){
this.wrap = Ext.DomHelper.insertHtml("beforeBegin", nel, buf);
}else{
this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf);
}

this.elNode = this.wrap.childNodes[0];
this.ctNode = this.wrap.childNodes[1];
var cs = this.elNode.childNodes;
this.indentNode = cs[0];
this.ecNode = cs[1];
this.iconNode = cs[2];
var index = 3;
if(cb){
this.checkbox = cs[3].firstChild;
// fix for IE6
this.checkbox.defaultChecked = this.checkbox.checked;
index++;
}
this.anchor = cs[index];
this.textNode = cs[index].firstChild;
//updating partial nodes
if (a.checked) n.bubble( function(n){ if (n.parentNode) n.getUI().updateCheck(true) } );//pass in true -> inform, that this is loaded node!
},

toggleCheck: function(value, partial, isLoad){
var cb = this.checkbox;
if(cb){
cb.checked = (value === undefined ? !cb.checked : value);
cb.parentNode.className = 'styledCheckboxWrap' + (cb.checked ? (partial ? ' wrapPartial' : ' wrapChecked') : '');
this.partial = cb.checked && partial;
if(!isLoad) this.onCheckChange();
}
},

updateCheck: function(isLoad){
if ( this.node.childNodes.length == 0 )
return;
this.partial = 0;
Ext.each( this.node.childNodes, function(item){
var ui = item.getUI();
if (ui.isChecked()) {
this.partial++;
if (ui.partial)
{
this.toggleCheck(true,true,isLoad);
return false;
}
}
}, this );
if ( this.partial !== true )
this.toggleCheck( this.partial > 0, this.partial < this.node.childNodes.length, isLoad );
}
});
And in Your code:


this.myTreeStateTree = new Ext.tree.TreePanel({
height:270,
autoScroll: true,
animate: false,
containerScroll: true,
loader: new Ext.tree.TreeLoader({
directFn: XXX.treeLoadFunction,
paramOrder: ['compid'], //param order
baseParams: { //parameters to send along with tree load
compid : 0 // specify default value
},
uiProviders: {tristate: Ext.tree.TreeNodeTriStateUI }
}),
root: {
nodeType: 'async',
text: 'Node title goes here',
draggable: false,
isTarget:false,
editable :false,
id: 'src',
uiProvider:false,
iconCls:"ico-application-share"
},
listeners:{
scope:this,
checkchange:{
fn:this.checkChange
}
}
});
.
.
.
checkChange:function(node, checked){
if (!this.changing)
{
this.changing = true;
node.expand(true, false,function(node){
if (checked)
node.cascade( function(node){ node.getUI().toggleCheck(true) } );
else
node.cascade( function(node){ node.getUI().toggleCheck(false) } );
node.bubble( function(node){ if (node.parentNode) node.getUI().updateCheck(false) } );
});
this.changing = false;
}
},
.
.

trcampion
8 Jul 2010, 9:48 AM
stju,

Could you zip up a working example of this and post the file? I tried getting mine to work with the above info but am having no luck. If I could see a complete working example that may help me. I'm not sure where I'm going wrong. I can get the tree to load with my data but I cant get the check boxes sorted out.

Thanks in advance,

Tom C

Stju
8 Jul 2010, 3:49 PM
@Tom Post #10 have everything You need..
To have checkboxes You need to pass that info in Your JSON data for coming from the server like this:


..
{"id":"123","text":"Hiiumaa","leaf":true,"checked":false,"uiProvider":"tristate","iconCls":"ico-map-pin"}
..

As you can see, some extra field present here - checked, uiProvider and iconCls. iconCls is used to change default icons, the rest should be self understandable :)
Just one more note - property names are CASE SENSITIVE!

Denaks
8 Jul 2010, 8:46 PM
Hi, there is an error at code:

" Uncaught TypeError: Object #<an Object> has no method 'apply' "

At string: Ext.tree.TreeNodeTriStateUI.superclass.constructor.apply(this, arguments); ?

Stju
8 Jul 2010, 8:49 PM
Hi, there is an error at code:

" Uncaught TypeError: Object #<an Object> has no method 'apply' "

At string: Ext.tree.TreeNodeTriStateUI.superclass.constructor.apply(this, arguments); ?

Please post Your code... Or test case.

bedwer
19 Aug 2010, 1:35 PM
Hello, I know how to make some elements of the trees are checked at all by default

kubens
28 Sep 2010, 1:53 AM
With regards that the expand callback function is called without waiting for deep expand completion I would use following checkChange function:


checkChange:function(node, checked)
{
if (!this.changing)
{
this.changing = true;

/* expand callback does not wait for deep expand to complete */
node.expand(false, false, function(node) {
node.cascade( function(node){
node.expand(false, false, function(node) {
node.getUI().toggleCheck(checked)
});
});

node.bubble( function(node){ if (node.parentNode) node.getUI().updateCheck(false) } );

});

this.changing = false;
}

This ensures that all child nodes will checked correctly if a loaded tree was not expanded with deep option after initial load.

Additional I have seen that the TriState-Checkbox is not rendered correctly inside IE6. Unfortunately I did not find time to go in detail regarding this. May someone else have find an solution for this problem.

ttbgwt
31 Dec 2010, 6:53 AM
I'd like to toggle the checkbox state by clicking on the node as well, and not just directly on the checkbox. How can I do this?

ttbgwt
31 Dec 2010, 7:12 AM
figured it out...


listeners: {
click: function (node) {
node.getUI().toggleCheck(!node.getUI().isChecked());
}
}



I'd like to toggle the checkbox state by clicking on the node as well, and not just directly on the checkbox. How can I do this?

ttbgwt
31 Dec 2010, 11:56 AM
I would like to enhance the functionality so that if a nodes children are not loaded, it will simply mark the node, and not error if the children nodes are not loaded. But if that node is expanded, then I would like to dynamically load the children and mark their checked state appropriately... Any ideas?

sosy
2 Jan 2011, 10:23 PM
This is a nice one!