Step 4 – Adding Custom Events

One of the most powerful features of the dojo framework is the event system. To make a long story short, if you know the method name within a widget you can get notified when it is called. To get notified we use the dojo function connect and what it does for you is adding your application as a listener to the event whenever the method is called.

Although the documentation on the dojo event system is sketchy at best (like most of the dojo documentation) there are some basic rules I would recommend you follow. In general protected and/or private method names start with an underscore like _onClick, you can connect to them but I would not recommend doing that outside your widget for the simple reason that those methods may change between versions of dojo. As a perfect example: the _onClick method for the tree changed between 1.3.2 and 1.4. If an event is intended for public consumption there will always be a public method associated with it. So in case of the onClick event connect to the method ‘onClick’ instead of ‘_onClick’.

Lets go ahead and add some custom events incase our checkbox is checked or unchecked. Make the following changes to our widget:

 21 dojo.declare( "tmpdir.CheckBoxTree", dijit.Tree,
 22 {
 23   onNodeChecked: function(/*dojo.data.Item*/ storeItem, /*treeNode*/ nodeWidget) {
 24   },
 25 
 26   onNodeUnchecked: function(/*dojo.data.Item*/ storeItem, /* treeNode */ nodeWidget) {
 27   },
 28 
 29   _onClick: function(/*Event*/ e) {
 30     var domElement = e.target;
 31     if(domElement.nodeName != 'INPUT') {
 32       return this.inherited( arguments );
 33     }
 34     var nodeWidget = dijit.getEnclosingWidget(domElement);
 35     if(!nodeWidget || !nodeWidget.isTreeNode){
 36       return;
 37     }
 38     nodeWidget._checkbox.checked ^ true;
 39     if(nodeWidget._checkbox.checked) {
 40       this.onNodeChecked( nodeWidget.item, nodeWidget);
 41     } else {
 42       this.onNodeUnchecked( nodeWidget.item, nodeWidget);
 43     }
 44   },
 45 
 46   _createTreeNode: function( args ) {
 47     return new tmpdir._CheckBoxTreeNode(args);
 48   }
 49 });

On line 23 and 26 we declare our methods which will be called when the checkbox is either checked or unchecked. Keep in mind those are custom event callback methods so we have to make sure they get called. On line 38 we check the new state (true/false) of our checkbox and call the appropriate method. You may say: there is nothing in those methods so why call them, well that is how events are being generated with dojo, call a method and an event will be generated if anybody is listing. Also notice I did not use the underscore in the method names. For the event itself, we pass two arguments: the first being the item from our data store and the second the node widget.

Now the only thing left to do is actually catch the events, in order to do so we have to make the following changes to our index.html file.

 24       var tree = new tmpdir.CheckBoxTree( {
 25               model: model,
 26               id: "MenuTree"
 27               });
 28       tree.placeAt( domLocation );
 29 
 30       dojo.connect( tree,"onNodeChecked", function(storeItem, nodeWidget){
 31         alert( store.getValue(storeItem,"name") + " got checked..");
 32         }
 33       );
 35     }

On line 30, after we create our tree object, we are going to connect to our newly created event ‘onNodeChecked’ using dojo.connect. Dojo connect takes two arguments: the event name and a callback function to be called when the event is triggered. As you probably already know functions/methods in Javascript are objects, we can pass the entire function declaration as an argument to a method. If you don’t like this you could declare the function separately, give it a name and just pass the function name as an argument to dojo connect, whatever floats your boat.

Now go back to your browser, reload the page and click any of the checkboxes. You should see something like this:

Step-4 Event generated

And that is the dojo event system at work for you. Next we are going to use this dojo event system inside our widget so the model, which we will be creating soon, will be able to tell the tree of any updates to our data store. Remember, because of the separation between the tree and the store it is the model who will tell the tree of any updates not the store itself. The model acts as the glue between the two

Take another look at our requirement c:

The tree must be able to maintain a parent-child checkbox relationship. What I mean by this is that for example if a parent checkbox is checked all child and grandchild checkboxes will automatically be checked as well or if one child is unchecked the parent and potentially its parent is unchecked automatically.

In order to make this work we need to do 2 things:

  1. Make sure the tree is listening to update events from our model
  2. Inform the model of any changes to the tree (when a checkbox is clicked).

In the remainder of this section we will deal with some parts of the first requirement just to give you an idea of how things will work going forward. Go ahead and make the following changes to our widget:

 46   _onCheckboxChange: function(/*dojo.data.Item*/ storeItem ) {
 47     var model    = this.model,
 48       identity = model.getIdentity(storeItem),
 49       node    = this._itemNodeMap[identity];
 50 
 51     if( node ) {
 52       if( node._checkbox != null ) {
 53         node._checkbox.checked = this.model.getCheckboxState( storeItem );
 54       }
 55     }
 56   },
 57 
 58   postCreate: function() {
 59 //    this.connect(this.model, "onCheckboxChange", "_onCheckboxChange");
 60     this.inherited(arguments);
 61   },
 62 
 63   _createTreeNode: function( args ) {
 64     return new tmpdir._CheckBoxTreeNode(args);
 65   }
 66 });

We need to add two methods to our Tree, the _onCheckboxChange and postCreate. Remember, as I mentioned before the postCreate method overwrites the default method of our parent dijit.Tree widget. Lets start with the postCreate method, on line 59 we are telling our tree widget to listen to any ‘onCheckboxChange’ events generated by our model, and if triggered, call our _onCheckboxChange callback function on line 46. For the time being I have commented out line 58 just because the method _onCheckboxChange on our model doesn’t exist yet.

On line 46 we declare our callback method which will be invoked with a single argument: the storeItem whose checkbox state changed. Remember what I said before, a tree node is NOT created until it needs to be rendered therefore on line 51 we check if the tree node exists for this particular store item. At line 52 we check if the node actually has a checkbox associated with it, if so, update its state. At this point in the game, if the node exist, it will have a checkbox more on this later.

One last thing, if you are wondering why your tree appears the way you left it last (ie: expanded or not) that’s because the tree creates a cookie which captures the current state of the tree. I will tell you later when I go over some of the other useful attributes how to disable the use of a cookie.

Let’s go the next step, STEP 5.

Leave a Reply

Your email address will not be published. Required fields are marked *