Just when I started writing this article I noticed a new release of dojo was made available so I changed my recommendations to download the latest and greatest because newer is better right… Just to be on the save side I downloaded and installed 1.4 myself and guess what, our CheckboxTree widget stopped working. Ok, things happen so let try the release notes, it turns out several enhancements had been made to the dijit Tree widget but none of them explained why our widget stopped working, we are talking enhancements here. The only thing left to do was to open up the source but unfortunately dojo does not maintain ANY sort of change history in their code.

After some investigation it became clear why our widget no longer worked, to make a long story short: The internal event handling had changed, in dojo 1.3.2 it was the Tree instance that would listen for and catch the _onClick events whereas in 1.4 this functionality has been moved to the TreeNode. Once a TreeNode catches the _onClick event the only thing it does is calling the “legacy” _onClick method of the Tree but instead of just passing the event data, the TreeNode also passes itself (“this”) as the first argument to the _onClick method of the Tree. The sequence diagram below shows the new flow.

SequenceDiagram1.4

Dojo function declarations

Method Dojo 1.3.2 Dojo 1.4
_onClick _onClick: function( evt ) {…} _onClick: function( nodeWidget, evt ) {…}
onClick onClick: function( item, nodeWidget ) {…} onClick: function( item, nodeWidget, evt ) {…}
onDblClick onDblClick: function( item, nodeWidget ) {…} onDblClick: function( item, nodeWidget, evt ) {…}

Considering it is you who defines the external interface for your widget you will have to decide if you want to follow dojo’s lead and implement the version 1.4 public interface for onClick and onDblClick. If you are concerned about backward compatibility of your application (dojo.connect callback functions) I would recommend to stick to the 1.3.2 implementation at least for the time being.

The next issue is related to an enhancement dojo made. Remember when we talked about our _getParentItem method in step 7, I mentioned the dijit Tree did not support multiple parent references whereas the data store did. As of dojo 1.4 this issue has been fixed and as a result the internal mapping table “itemNodeMap” changed name to “itemNodesMap” in addition, in dojo 1.3.2 each entry in the mapping table held a single reference to a TreeNode instance whereas in 1.4 each entry holds an array of references.

So now that we know what the issues are let’s go ahead a make the changes to our CheckboxTree widget in order to support dojo 1.4. Make sure you keep a copy of the 1.3.2 code because the 1.4 code is NOT backward compatible. The methods we need to changes are:

_onClick tmpdir.CheckBoxTree
_onCheckboxChange tmpdir.CheckBoxTree
_getParentsItem tmpdir.CheckBoxStoreModel
_updateParentCheckbox tmpdir.CheckBoxStoreModel

Let’s start with the onClick method:

182   _onClick: function( nodeWidget,  e) {
183     var domElement = e.target;
184 
184     if(domElement.nodeName != 'INPUT') {
185       return this.inherited( arguments );
186     }
187     this._publish("execute", { item: nodeWidget.item, node: nodeWidget} );
188 
188     this.model.updateCheckbox( nodeWidget.item, nodeWidget._checkbox.checked );
189     this.onClick( nodeWidget.item, nodeWidget, e );
190     if(nodeWidget._checkbox.checked) {
191       this.onNodeChecked( nodeWidget.item, nodeWidget);
192     } else {
193       this.onNodeUnchecked( nodeWidget.item, nodeWidget);
194     }
195     this.focusNode(nodeWidget);
196   },

On line 182 we adjust the _onClick function declaration to accommodate the new nodeWidget argument  and on line 189 we update the call to onClick. As you can see I have decided to make our CheckboxTree widget dojo 1.4 compliant, again it’s up to you. Notice that because the TreeNode is now passing itself as the nodeWidget argument we no longer need to call the dijit.getEnclosingWidget function. Let’s go to the next one, _onCheckboxChange.

197   _onCheckboxChange: function( storeItem ) {
198     var model    = this.model,
199       identity = model.getIdentity(storeItem),
200       nodes    = this._itemNodesMap[identity];
201 
201     if( nodes ) {
202       dojo.forEach( nodes, function(node) {
203         if( node._checkbox != null ) {
204           node._checkbox.checked = this.model.getCheckboxState( storeItem );
205         }
206       }, this );
207     }
208   },

On line 200 we now use “nodes” (pural) as the itemNodesMap array holds an array of nodes as to only one in dojo version 1.3.2. As a result we need to start a loop on line 202 validating each possible node. This can have some interesting visual effects for example: if your tree represents a family structure (mother, father and children) checking any of the children will have an effect on both parents.

Ok, now for our CheckboxStoreModel. In order to support the multi parent references we will no longer break out of our _getParentsItem (notice Parents vs Parent in 1.3.2) method after fetching the first parent. We actually return an array of parents.

118   _getParentsItem: function( storeItem ) {
119     var parents = [];
120 
120     if( storeItem != this.root ) {
121       var references = storeItem[this.store._reverseRefMap];
122       for(itemId in references ) {
123         parents.push(this.store._itemsByIdentity[itemId]);
124       }
125       if (!parents.length) {
126         parents.push(this.root);
127       }
128     }
129     return parents 
130   },

On line 119 we declare parents as an array and on line 123 we add every individual parent to our array which is returned on line 129. This implies that _updateParentCheckbox will get an array back instead of a single instance.

 93   _updateParentCheckbox: function( storeItem,  newState ) {
 94     var parents = this._getParentsItem(storeItem);
 95     dojo.forEach( parents, function( parentItem ) {
 96       if( newState ) { 
 97         this.getChildren( parentItem, dojo.hitch( this,
 98           function(siblings) {
 99             var allChecked  = true;
100             dojo.some( siblings, function(sibling) {
101               return !(allChecked = this.store.getValue(sibling,this.checkboxIdent));
102             }, this );
103             if( allChecked ) {
104               this._setCheckboxState( parentItem, true );
105               this._updateParentCheckbox( parentItem, true );
106             }
107           }),
108           function(err) {
109             console.error(_this, ": updating parent checkboxes: ", err);
110           }
111         );
112       } else {   
113         this._setCheckboxState( parentItem, false );
114         this._updateParentCheckbox( parentItem, false );
115       }
116     }, this );
117   },

If you take a look at _updateParentCheckbox you will notice that the original function (line 195 thru 214) is basically wrapped in a dojo.forEach loop. And that my friends should do it for dojo version 1.4

Child Items with Multiple Parents

The last thing I want to do is to demonstrate the Checkbox Tree where child items have multiple parent. As I mentioned before this is new functionality in dojo version 1.4. The first thing we need to do is to create a new data file in our datastore directory called family: \js\datastore\family.json

  1 { identifier: 'name',
  2   label: 'name',
  3   items: [
  4      { name:'John', type:'parent', checkbox: true,
  5          children:[{_reference:'Chuck'}, {_reference:'Melissa'}, {_reference:'Nancy'}] },
  6      { name:'Mary', type:'parent', checkbox: true,
  7          children:[{_reference:'Chuck'}, {_reference:'Melissa'}, {_reference:'Nancy'}] },
  8      { name:'Chuck', type:'child', checkbox: true },
  9      { name:'Melissa', type:'child', checkbox: true },
 10      { name:'Nancy', type:'chidl', checkbox: true }
 11 ]}

Notice that I changed the type on all entries in our file to either “parent” or “child”. I haven’t talked much about the content of our Json file so this is probably not a bad time. In Step 6 when talking about adding attributes to our model, we added a so called key value pair to the Countries Json file. If you take a look at our new file above you see, for example on line 4, other “key: value” pairs such as “name:John”, “type:parent” and “checkbox:true”. Name, type and checkbox are attributes of the datastore items and if you like you can add additional attributes and use the store method getValue to retrieve their content similar to what we did in our own method getCheckboxState.  Because I changed the values of our attribute “type” we will also have to change the query attribute when we create our CheckBoxStoreModel so let’s do that next. Open you index.html file and make the following changes:

 13     function myTree( domLocation ) {
 14       var store = new dojo.data.ItemFileWriteStore( {
 15               url: "/js/datastore/Family.json"
 16               });
 17       var model = new tmpdir.CheckBoxStoreModel( {
 18               store: store,
 19               query: {type: 'parent'},
 20               rootLabel: 'The Family',
 21               checkboxAll: true,
 22               checkboxRoot: false,
 23               checkboxState: true
 24               });

On line 15 change the URL to point to our new file “Family.Json”, the next thing is to change the query attribute at line 19 the value associate with the key “type” is now going to be “parent”. I also changed the root label on line 20 to stay with our new family theme. Fire-up your browser and see what we got:

Step-8-1
Step-8-2

The picture on the left is what you should see as soon as the Checkbox Tree gets loaded. Now uncheck Mary’s child Chuck and notice what happens to his father John and John’s child Chuck.

This ends our 1.4 tutorial..