Creating a Dojo dijit.Tree with checkboxes

November 21, 2008 – 12:11 pm Tags: ,

Dojo provides a useful component called dijit.Tree, which is basically a quite typical tree component. However, it doesn’t do much out of the box, and I needed it to make some tree nodes selectable with checkboxes for my Zend Framework based packageizer script. While Zend Framework has a Zend_Dojo component, it doesn’t quite do trees the way I want yet.

Let’s see how I made the tree play nice with checkboxes and some ajax tricks.

The basic idea

If you’ve checked out the packageizer, you probably already know most of this:

I wanted the tree to load its nodes from the PHP script, separately for each leaf. I also wanted to have “folders” and “files” – folders would contain things, and files would have a checkbox that can be checked to select it. I also needed to be able to track the checked items, so the script would know what files to put in the package.

The dijit.Tree is basically two main components: dijit.tree and dijit._TreeNode. Typically you’d also need a store, like dojox.data.QueryReadStore in my case, and a model, like dijit.tree.ForestStoreModel. By extending some of these, we can easily modify the tree to fit our needs.

Getting to it

You can find the complete source code here.

dijit._TreeNode

Let’s first take a look at the dijit._TreeNode class, which represents each node in a tree. We can easily modify it to contain a checkbox, if certain requirements are met:

dojo.declare('CU.dojo._ChkTreeNode', dijit._TreeNode, {
    setLabelNode: function(label) {
        if(this.item.root || this.tree.model.store.getValue(this.item, 'type') != 'class')
            return this.inherited(arguments);
 
        var chk = dojo.doc.createElement('input');
        chk.type = 'checkbox';
        this.labelNode.innerHTML = '';
        dojo.place(chk, this.expandoNode, 'after');
        this.labelNode.appendChild(dojo.doc.createTextNode(label));
        this.checkNode = chk;
        this.expandoNodeText.parentNode.removeChild(this.expandoNodeText);
    }
});

The above code checks if the item is of type “class”, and inserts a checkbox after the expando (the +/- sign). The setLabelNode function gets automatically called when the tree node gets created.

But why class? This is because the data received from the PHP script assigns type as “class” for all classes and “package” for packages, which can contain classes or other packages. If you wanted to add checkboxes to all nodes, you can simply remove the if clause.

dijit.Tree

This is going to be a bit longer, so I’m just going to explain it and show some snippets. You can get the complete code and follow from there.

In the declaration for CU.dojo.ChkTree, we can see the _createTreeNode function. This is a function in dijit.Tree, which gets called when the code needs to create a new tree node, so this is the place where we need to return our shiny new ChkTreeNode.

The getIconClass function determines the CSS class name for the icon of the tree node. This is modified to output “package” or “class” so we can use CSS styles to modify how the icons look. Same is done for the getLabelClass function.

The onNodeChecked and onNodeUnchecked function’s purprose is to serve as a base which you can dojo.connect to, or just simply override to get a notification when a node is checked or unchecked.

onClick: function(item, node) {
    if(item.root || this.model.store.getValue(item, 'type') == 'package')
    {
        this._onExpandoClick({ node: node });
    }
    else
    {
        if(!node.checkNode)
            return;
 
        if(this._clickTarget && this._clickTarget.nodeName != 'INPUT')
            node.checkNode.checked = !node.checkNode.checked;
 
        if(node.checkNode.checked)
        {
            this.onNodeChecked(node);
        }
        else
        {
            this.onNodeUnchecked(node);
        }
    }
},

The onClick is a bit more tricky. It gets called when any node is clicked, but we want to have a bit different behavior depending on the node type. If it’s a package, we want it to be expanded or minimized on click, but if it’s a class, the checkbox should be toggled.

The first block checks if it’s a package, and calls the _onExpandoClick function, which toggles the node. If it’s a class, we check if it actually has a checkbox node in it or not. Then, we need to check if what was clicked was the checkbox: If the checkbox was the click target, it was already toggled, and toggling it again would make it impossible to use. Finally, calls are made to the related event functions.

And lastly in the ChkTree, we have _onClick, which is needed for the checkbox to function correctly. Unless we have this function in, every time we click on the checkbox, it would focus the label, and it would not be possible to check it by clicking the checkbox directly, only by clicking the label and thus invoking the onClick handler for it.

dijit.tree.ForestStoreModel

And the last extended class, CU.dojo.QueryStoreModel, is required to add some custom behavior to the store so the tree works correctly. We modify getChildren to use some custom logic to fetch the child nodes from the store, as the backend PHP code requires some additional parameters to be able to correctly determine the nodes.

We also override mayHaveChildren to allow children for packages but not for classes.

Usage

Using this class is very similar to how you would normally use a dijit.Tree. Here’s a simplified example from the packageizer:

    var store = new dojox.data.QueryReadStore({ url: '/pack/1.6' });
    var model = new CU.dojo.QueryStoreModel({
        store: store,
        query: { package: '', format: 'json' },
        rootId: 'treeRoot',
        rootLabel: 'Packages',
        childrenAttrs: 'children'
    }, 'store');
    var tree = new CU.dojo.ChkTree({ model: model }, 'tree');
 
    dojo.connect(tree, 'onNodeUnchecked', function(node) {
       alert('A node was unchecked!');
    });
 
    dojo.connect(tree, 'onNodeChecked', function(node) {
        alert('A node was checked!');
    });
 
    tree.startup();

Here’s an example of the JSON sent by the PHP backend:

{
 "numRows":3,
 "items":[
  {"name":"Zend_Auth_Storage_Exception","label":"Zend_Auth_Storage_Exception","parent":"Zend_Auth_Storage","type":"class"},
  {"name":"Zend_Auth_Storage_NonPersistent","label":"Zend_Auth_Storage_NonPersistent","parent":"Zend_Auth_Storage","type":"class"},
  {"name":"Zend_Auth_Storage_Session","label":"Zend_Auth_Storage_Session","parent":"Zend_Auth_Storage","type":"class"}
 ],
 "identity":"name"
}

In closing

In the end, this required less code than I expected in the beginning. I’ve done something similar using the Yahoo UI library, and it was a bit more complex. What Dojo lacks in documentation (at the moment of writing this), it has in design and flexibility.

You can see a live example of the tree here.

Hope you found this educational or useful =)

Share this:
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • DZone
  • email
  • LinkedIn
  • Pownce
  • Reddit
  • StumbleUpon
  • Technorati

RSS feed Subscribe to my RSS feed

  1. 18 Responses to “Creating a Dojo dijit.Tree with checkboxes”

  2. Add the checkboxes to the packages too.. and use it to allow you to select all the child classes..

    Sometimes you just want the whole bit.. like Zend_Mail.. it’s standalone, but selecting all the classes in your tree to find that out is a lot more troublesome then just selecting the Zend_Mail package and finding it out that way :P

    By Harro on Nov 21, 2008

  3. Oh.. and could you also include .xml files in the download?

    When I select Zend_Locale I don’t get all the xml files in the Zend/Locale/Data :(

    By Harro on Nov 21, 2008

  4. You’re commenting packageizer in the wrong place, this post is about dijit.Tree ;)

    Anyway – I’ve read the feedback and when I have time to work on it, that package checking will be amongst the other improvements =)

    XML files may be a bit trickier… I will need to see if there’s some good way to determine any external non-PHP files a class requires.

    By Jani Hartikainen on Nov 21, 2008

  5. I have a method,but it can’t work.Could you help me?

    –code–

    dojo.extend(dijit.Tree,{
    _createTreeNode: function(/*Object*/ args){
    args= dojo.mixin(args,{
    setLabelNode: function(label){

    this.labelNode.innerHTML = “”;
    var id = continentStore.getIdentity(this.item);
    var input=document.createElement(‘input’);
    input.setAttribute(‘type’,'checkbox’);
    input.setAttribute(‘name’,'groupid[]‘);
    input.setAttribute(‘id’,'groupid_’+id);
    input.setAttribute(‘value’,id);
    //input.setAttribute(‘checked’,'checked’);

    this.labelNode.appendChild(input);
    this.labelNode.appendChild(dojo.doc.createTextNode(label));

    }
    }
    );
    return new dijit._TreeNode(args);
    }
    });

    By xletian on Dec 8, 2008

  6. Could you please provide the code for the PHP script? Thanks

    By George on Jan 14, 2009

  7. Thanks.I have solved it.because of below code in function of _onClick.
    –code–
    dojo.stopEvent(e);
    –code–

    By xletian on Jan 14, 2009

  8. George: the code is available at http://codeutopia.net/code/packageizer/trunk/

    By Jani Hartikainen on Jan 14, 2009

  9. Jani, just wanted to let you know that the live example does not work anymore…

    regards,
    Matthias

    By Matthias Zeis on Jul 1, 2009

  10. Live Examples not working .. please fix that.

    Thanks…

    By Ramesh Krihsnan on Jul 17, 2009

  11. The live example is broken, again.

    By Tim on Aug 14, 2009

  12. The live example is back up. The script was down for a while due to some hosting change issues.

    By Jani Hartikainen on Aug 21, 2009

  13. Dear Jani,
    This tree is exactly what I need for my project, thank you a lot for providing it. I start in Zend and I’ve no experience at all with Dojo so it’s quite difficult for me to use it.
    I’ve tried to install you example on my server, I can select the 1.9 library (adding a zend lib in data/1.9) but when I click on it I’ve lot of errors I’m unable to fix.
    My question is:
    How to (and it is possible to) use this tree on a query result ? I’ve a query with return a sort of tree (family, subfamily, item) and I absolutely need the ability to set a value (a date) for selected values in the tree.
    How can it be done ? Most people need this tree checkbox for database items, so probably it would be a great example.
    Also as requested, the possibility to select all items in a folder would be great.
    Could someone help ?
    Would it be possible to create this tree as a “package” or “library” to include in Zend and pass to it the values in a desired format ?
    Thank you for helping.

    By Bedford on Nov 26, 2009

  14. You probably shouldn’t use the code from packageizer directly if you just want the tree. The code in it is specialized for this specific case.

    I may post something in the future about creating a similar tree from a database result though.

    By Jani Hartikainen on Nov 26, 2009

  15. Hi , Can you please tell me why i am getting below error –

    Could not load CU.dojo.ChkTree

    Please help.

    Ashish

    By Ashish Soni on Dec 14, 2009

  16. I have implemented this solution on my site and it is working well expect that the custom nodes do not indent.

    Did you find you had this problem and if so how did you solve it?

    Many thanks
    AJ

    By AJ on Jul 22, 2010

  17. Don’t worry – fixed the indent thing myself. I hadn’t declared the node class properly!

    By AJ on Jul 22, 2010

  18. how to include two of such independent trees in the same widget ?

    By arsh on Aug 19, 2010

  1. 1 Trackback(s)

  2. Feb 7, 2009: Zend Framework, Zend_Dojo and Dojo Tree « Ryan’s Blog

Post a Comment