Dijit Tree with Multi State Checkboxes

NOTE:

The new and fully AMD compliant version of the Dijit CheckBox Tree can be found HERE. Please refer to the updated version going forward.

Introduction

This article is a follow-up on my previous blog regarding a dijit tree with checkboxes. In the past couple of weeks I received a number of requests if it would be possible to get the checkbox tree with so called multi or triple state checkboxes. By default a checkbox has two states:

  1. True (checked)
  2. False (unchecked)

This BTW: applies to the default HTML checkbox as well as the dijit checkbox so in order to make a multi/triple state checkbox work we have to create our own checkbox that holds an additional state. Second, we have to be able to visually represent the third state. Going forward I will refer to this third state as the ‘mixed’ state.

MultiStateDemo

Click here to take a quick look at a demo. Please let me know what you think, leave a comment or ask a question. The source package for the Checkbox Tree with Multi State Checkboxes can be downloaded here.

The Basics

The default dijit checkbox uses the standard HTML checkbox controls but overlays the visual image with its own sprite and apply some CSS styling. We are going to use the same concept by extending the dijit checkbox widget, create a new sprite and associated CSS classes. To make it real fancy I’m going to create separate sprites for each dijit theme. Having said that I still want to be able to support the default HTML checkbox, the visual aspect of a mixed state will not be supported BUT at the application level you will be able to check if the HTML checkbox is in a mixed state.

For this tutorial I’m going to use the dojo version 1.4 source of our CheckBoxTree which can be downloaded here, just in case you are wondering: will there be a dojo 1.3.2 version available of the multi state checkbox tree, the answer is yes.

The Multi State Checkbox

I’m going to include the extended dijit checkbox widget in our CheckBoxTree source file as I currently don’t have any plans for using the multi state checkbox anywhere else. However, it is very easy to extract it if you want to. And now for some code:

  1 dojo.provide("tmpdir.CheckBox");
  2 dojo.provide("tmpdir.CheckBoxTree");
  3 dojo.provide("tmpdir.CheckBoxStoreModel");
  4 
  5 dojo.require("dijit.Tree");
  6 dojo.require("dijit.form.CheckBox");
  7 dojo.require("dijit.tree.ForestStoreModel");
  8 
  9 dojo.declare( "tmpdir.CheckBox", dijit.form.CheckBox,
 10 {
 11   baseClass: "tmpdirCheckBox",
 12 
 13   value: "unchecked",
 14 
 15   _setStateClass: function(){
 16     var newStateClasses = this.baseClass.split(" ");
 17 
 18     function multiply(modifier){
 19       newStateClasses = newStateClasses.concat(dojo.map(newStateClasses,
 20         function(c){ return c+modifier; }),
 21         "dijit"+modifier);
 22     }
 23 
 24     if(this.state){  multiply(this.state); }
 25     if(this.attr('value') == "mixed" ){
 26       multiply("Mixed");
 27     } else if(this.checked){
 28       multiply("Checked");
 29     }
 30     if(this.disabled){
 31       multiply("Disabled");
 32     }else if(this._active){
 33       multiply(this.stateModifier+"Active");
 34     }else{
 35       if(this._focused){
 36         multiply("Focused");
 37       }
 38       if(this._hovering){
 39         multiply(this.stateModifier+"Hover");
 40       }
 41     }

On line 1 we tell dojo that our CheckBoxTree file also provides our new checkbox using the same namespace (tmpdir) as we did before. We include the dijit checkbox on line 6 and declare the new checkbox on line 9 which inherits from dijit.from.Checkbox.

On line 11 we define the base class for our checkbox widget. This will distinguish our checkbox from the default dijit checkbox allowing us to create our own CSS classes and thus introduce the third (mixed) state. On line 13 we set the value attribute to “unchecked”. We are going to use the checkbox value attribute to hold the mixed state which can be either “checked”, “unchecked” or “mixed”. The value attribute maps the standard value property of  the default HTML checkbox and therefore at the application layer you can use this value to determine if a checkbox is in a mixed state regardless if you use our dijit style checkbox or the default HTML checkbox. (more on this later).

On line 15 we overwrite the _setStateClass method which will generate the appropriate CSS classes depending on the current state of the checkbox. For example, if the checkbox is checked it will generate a CSS class “tmpdirCheckBoxMixed” if on the other hand the checkbox is in a mixed state it will generate a CSS class “tmpdirCheckBoxMixed” or we could get a state like “tmpdirCheckBoxMixedHover”.

Next we are going to overwrite the default  set and get ValueAttr functions in order to be able to use the checkbox value attribute to hold our mixed state.

 63   _setValueAttr: function( newValue){
 64 
 65     if(typeof newValue == "string"){
 66       this.value = newValue;
 67       dojo.attr(this.focusNode, 'value', newValue);
 68     }
 69   },
 70 
 71   _getValueAttr: function(){
 72     return this.value;
 73   }

The default _setValueAttr method also manipulates the ‘checked’ attribute of the checkbox and that is something we don’t want. Believe it or not but that’s all we have to do for our extended checkbox, next we will work on our CSS class definitions and create new sprites for our checkbox.

CSS Classes and Sprites

First we have to create a couple of new directories to support our creative work. In your tmpdir directory create a subdirectory called ‘themes’ and the themes directory needs another subdirectory for each dijit theme. Finally each dedicated themes directory will have a subdirectory called ‘images’. Your directory structure should look like this:

  Directory Structure 2

This directory structure follows the dojo/dijit directory structure, if you want to change it feel free to do so but for this tutorial we are going to use the above structure. In the \themes\nihilo\ directory create a new file called tmpdir.css and add the following lines:

  1 /*
  2  *  Adds cosmetic styling to tmpdir. Users may swap with a custom theme CSS file.
  3 */
  4 @import url("Checkbox.css");

In the same \themes\nihilo\ directory create a new file call Checkbox.css and add the follow class definitions:

  1 /*
  2  *  tmpdir CheckBox Widgets CSS
  3  *
  4  *  Order of images in the sprite (from L to R):
  5  *    checkbox  normal    - checked
  6  *                        - unchecked
  7  *                        - mixed
  8  *              disabled  - checked
  9  *                        - unchecked
 10  *                        - mixed
 11  *              hover     - checked
 12  *                        - unchecked
 13  *                        - mixed
 14 */
 15 .nihilo .tmpdirCheckBox,
 16 .nihilo .tmpdirCheckBoxIcon {
 17   background-image: url('images/spriteCheckbox.gif'); /* sprite image */
 18   background-repeat: no-repeat;
 19   width: 16px;
 20   height: 16px;
 21   margin: 0;
 22   padding: 0;
 23 }
 24 
 25 .nihilo .tmpdirCheckBox,
 26 .nihilo .tmpdirCheckBoxIcon {
 27   /* unchecked */
 28   background-position: -16px;
 29 }
 30 
 31 .nihilo .tmpdirCheckBoxChecked,
 32 .nihilo .tmpdirCheckBoxIcon {
 33   /* checked */
 34   background-position: 0px;
 35 }
 36 
 37 .nihilo .tmpdirCheckBoxDisabled {
 38   /* disabled */
 39   background-position: -48px;
 40 }
 41 
 42 .nihilo .tmpdirCheckBoxCheckedDisabled {
 43   /* disabled but checked */
 44   background-position: -48px;
 45 }
 46 
 47 .nihilo .tmpdirCheckBoxMixed {
 48   /* checked & mixed */
 49   background-position: -32px;
 50 }
 51 
 52 .nihilo .tmpdirCheckBoxMixedDisabled {
 53   /* mixed & Disabled*/
 54   background-position: -64px;
 55 }
 56 
 57 .nihilo .tmpdirCheckBoxHover {
 58   /* hovering over an unchecked enabled checkbox */
 59   background-position: -112px;
 60 }
 61 
 62 .nihilo .tmpdirCheckBoxCheckedHover {
 63   /* hovering over a checked enabled checkbox */
 64   background-position: -96px;
 65 }
 66 
 67 .nihilo .tmpdirCheckBoxMixedHover {
 68   /* checked & mixed */
 69   background-position: -128px;
 70 }

The only thing missing from a creative standpoint is the sprite itself which we declare as our background image on line 17. A sprite is basically a single image composed of multiple smaller images, loading a single sprite instead of multiple smaller images improves performance and creates less overhead and eliminates screen flickering. There are many, many image editors on the market you could use to create the sprite but I prefer to use IconWorkshop from Axialis because the professional version has special support for sprites or Image strips as they call them. Lets create the spriteCheckbox.gif file in the \themes\nihilo\images directory.

  spriteCheckbox

As you can tell from the above image the sprite is composed of 9 smaller images, each 16 pixels high and wide representing the different states of our checkbox. The three images on the right are the hover states which currently are the same as the enabled states on the left. Feel free to adopt the image to your own needs, both the PNG and GIF version are included in the source package.

You will have to repeat the above steps for the other dijit themes Soria and Tundra but I guess you get the picture and don’t forget to replace ‘.nihilo’ in the CSS file with the appropriate theme you are creating. (i.e: ‘.tundra’ and ‘.soria’)

Update The Checkbox Tree

To incorporate the new checkbox widget in our tree we will have to make enhancements to all three components of our tree, these components are:

  1. The Model
  2. The Tree Nodes
  3. The Tree

Update The Model

The first thing we will have to do is to introduce a new attribute for each store item which will hold the additional mixed state of our store item. As I said in the previous tutorial the data store and model have no direct relationship to any of the TreeNodes or the associated checkboxes (remember the MVC pattern). Also, as of dojo 1.4 there may actually be multiple checkboxes referring to, or should I say representing, a single store item.

  1   checkboxAll: true,
  2   checkboxState: false,
  3   checkboxRoot: false,
  4   checkboxStrict: true,
  5   checkboxIdent: "checkbox",
  6   checkboxMState: "mixed",

On line 6 we introduce a new attribute to our model called ‘checkboxMState’ which holds the name of the attribute we will be using to store the mixed state for each store item. Typically there is not need to overwrite the ‘checkboxMState’ attribute unless you are planning on using the attribute name “mixed” in your Json file. If so, you will have to overwrite the checkboxMState value when creating the model.

First we need to enhance the getCheckboxState method to support the additional mixed state.

 99   getCheckboxState: function( storeItem) {
100     var state = { checked: undefined, mixed: false };  
101 
102     if ( storeItem == this.root ) {
103       if( typeof(storeItem.checkbox) == "undefined" ) {
104         this.root.checkbox = undefined;    
105         this.root.mixedState = false;
106         if( this.checkboxRoot ) {
107           this._setCheckboxState( storeItem, this.checkboxState, false );
108           state.checked = this.checkboxState;
109         }
110       } else {
111         state.checked = this.root.checkbox;
112         state.mixed    = this.root.mixedState;
113       }
114     } else {  
115       state.checked = this.store.getValue(storeItem, this.checkboxIdent);
116       state.mixed    = this.store.getValue(storeItem, this.checkboxMState);
117       if( state.checked == undefined && this.checkboxAll) {
118         this._setCheckboxState( storeItem, this.checkboxState, false );
119         state.checked = this.checkboxState;
120       }
121     }
122     return state  
123   },

Instead of passing two states around we create a object called ‘state’ with two properties: checked and mixed. On line 111 and 112 we copy the store item states to our new object and return it at line 122. Now that we can retrieve the additional state we must also be able to store it.

125   _setCheckboxState: function( storeItem,  newState,  mixedState ) {
126     var stateChanged = true;
127 
128     if( storeItem != this.root ) {
129       var currState = this.store.getValue(storeItem, this.checkboxIdent);
130       var currMixed = this.store.getValue(storeItem, this.checkboxMState);
131       if( (currState != newState || currMixed != mixedState) && 
              (currState !== undefined || this.checkboxAll) ) {
132         this.store.setValue(storeItem, this.checkboxIdent, newState);
133         this.store.setValue(storeItem, this.checkboxMState, mixedState);
134       } else {
135         stateChanged = false;
136       }
137     } else {  
138       if(( this.root.checkbox != newState || this.root.mixedState != mixedState) &&
139          ( this.root.checkbox !== undefined || this.checkboxRoot ) ) {
140         this.root.checkbox   = newState;
141         this.root.mixedState = mixedState;
142       } else {
143         stateChanged = false;
144       }
145     }
146     if( stateChanged ) {  
147       this.onCheckboxChange(storeItem, mixedState);
148     }
149     return stateChanged;
150   },

The most important thing with the updated _setCheckboxState method is that we will now have to validate BOTH states in order to determine if an update to the actual checkbox(es) is required. For example: if a child checkbox is checked/unchecked the parent’s checked state may not change but it could alter its mixed state and thus we will have to let the tree know it needs to update the parent checkbox(es).

The next method of our model we need to address is the _updateParentCheckbox. Here is were we determine if a checkbox gets a mixed state, only parent checkboxes can get a mixed state. If any child has a mixed state the parent will automatically get the mixed state too regardless of the states of the other children.

171   _updateParentCheckbox: function( storeItem ) {
172     var parents = this._getParentsItem( storeItem );
173     dojo.forEach( parents, function( parentItem ) {
174       this.getChildren( parentItem, dojo.hitch( this,
175         function(children) {
176           var hasChecked     = false,
177             hasUnChecked   = false,
178             isMixed      = false;
179           dojo.some( children, function(child) {
180             state = this.getCheckboxState(child);
181             isMixed |= state.mixed;
182             switch( state.checked ) {  
183               case true:
184                 hasChecked = true;
185                 break;
186               case false:
187                 hasUnChecked = true;
188                 break;
189             }
190             return isMixed;
191           }, this );
192           isMixed |= !(hasChecked ^ hasUnChecked);
193           if( this._setCheckboxState( parentItem, isMixed ? true: hasChecked, 
                                                      isMixed ? true: false ) ) {
194             this._updateParentCheckbox( parentItem );
195           }
196         }),
197         function(err) {
198           console.error(this, ": fetching mixed state: ", err);
199         });
200     }, this );
201   },

If a store item gets a mixed state, by default it will also enter the checked state (line 193). We could have gone both ways but if you opt to use the default HTML checkboxes (which do not support the visual aspect of a mixed state) at least you can SEE some child is checked if the parent branch is collapsed. I guess this is like the glass is half full/empty discussion. On lines 182-189 we explicitly check for ‘true’ or ‘false’ as the checked state could also be ‘undefined’ in which case we need to ignore it. Remember, the undefined state occurs when no ‘checkbox’ attribute we specified for the store item in the Json file and the model ‘checkboxAll’ attribute is set to false.

The last changes I made to the model is how the initial store data validation takes place. During some performance testing I found that loading the entire Json file at once, instead of partial or lazy loading, offered by far the best performance. On average loading the file this way is about 5-6 times faster than partial loading. Also, keep in mind that if a strict parent-child checkbox relation is required (which is the default) we will have to load the file first anyway to perform our initial store data validation. As a result I broke the original validateData method into two pieces.

218   validateData: function( storeItem,  scope ) {
219 
220     if( scope.checkboxStrict ) {
221       try {
222         scope.store._forceLoad();    
223       } catch(e) {
224         console.log(e);
225       }
226       dojo.hitch( scope, scope._validateStore ) ( storeItem );
227     }
228   },

As before we first check if a strict parent-child checkbox relationship is required. If so, we’ll try a forced load followed by a call to the second part of our validation, now called _validateStore.

230   _validateStore: function( storeItem ) {
231     this.getChildren( storeItem, dojo.hitch( this,
232       function(children) {
233         var hasGrandChild = false,
234           oneChild    = null;
235         dojo.forEach( children, function( child ) {
236           if( this.mayHaveChildren( child )) {
237             this._validateStore( child );
238             hasGrandChild = true;
239           } else {
240             oneChild = child;
241           }
242         },this );
243         if( !hasGrandChild && oneChild ) {    
244           this._updateParentCheckbox( oneChild );
245         }
246       }),
247       function(err) {
248         console.error(this, ": validating checkbox data: ", err);
249       }
250     );
251   },

We walk down to the lowest branch of the tree and request a parent update for at least one child on the branch. The updateParentCheckbox method will go back up the tree all the way to the root.

Update The Tree Nodes

In the original version of our Checkbox Tree the Tree Node simply created a HTML checkbox and inserted it into the DOM. In this episode we are going to offer the option of using our enhanced dijit checkbox widget or use the default HTML checkbox. In addition, I wanted to offer the option to hide the Open/Closed folder icon and/or the leaf icon preceding the node label.

  OpenCloseIcon

In order to accomplish these additional requirements we are going to introduce four more attributes to our Tree:

Attribute Default
checkboxStyle “dijit”
branchIcons true
nodeIcons true
checkboxMultiState true

Lets go ahead and update our _createCheckbox method of the CheckBoxTreeNode widget.

258 dojo.declare( "tmpdir._CheckBoxTreeNode", dijit._TreeNode,
259 {
260   _checkbox: null,
261 
262   _createCheckbox: function() {
263     var  state = this.tree.model.getCheckboxState( this.item );
264     if( state.checked !== undefined ) {
265       if (this.tree.checkboxStyle == "dijit" ) {
266         this._checkbox = new tmpdir.CheckBox().placeAt(this.expandoNode,'after');
267       } else {
268         this._checkbox = dojo.doc.createElement('input');
269         this._checkbox.className = 'tmpdirHTMLCheckBox';
270         this._checkbox.type      = 'checkbox';
271         dojo.place(this._checkbox, this.expandoNode, 'after');
272       }
273       this._setCheckedState( state );
274     }
275     if( this.isExpandable ) {
276       if ( !this.tree.branchIcons ) {
277         dojo.removeClass(this.iconNode,"dijitTreeIcon");
278       }
279     } else {
280       if( !this.tree.nodeIcons ) {
281         dojo.removeClass(this.iconNode,"dijitTreeIcon");
282       }
283     }
284   },

On line 263, as before, we fetch the checkbox state from the store however, this time we get an object back instead of a simple state. If the checked state is undefined no checkbox is required for this tree node. On line 265 we check the new ‘checkboxStyle’ attribute to determine what type of checkbox is needed. On line 275 we first check if this particular tree node is expandable (is it a parent checkbox?), than if the Open/Closed Folder or leaf icon aren’t required we simply remove the appropriate CSS class from the icon Node.

Previously, when a store item changed state the model would inform the Tree and the Tree would then simply update the checkbox checked state accordingly. This time around we need a little more sophisticated approach because we are now dealing with two states and two checkbox styles each requiring different ways of storing the state information. We add a new method to the CheckBoxTreeNode called _setCheckedState which will handle the updates for the actual checkboxes. This method will be called by the Tree as soon as it receives an update notification from the model.

286   _setCheckedState: function(  state ) {
287 
288     if( this.tree.checkboxStyle == "dijit" ) {
289       if( this.tree.checkboxMultiState ) {
290         this._checkbox.attr('value',state.mixed ? "mixed" : state.checked ? "checked" : "unchecked" );
291       } else {
292         this._checkbox.attr('value',state.checked ? "checked" : "unchecked" );
293       }
294       this._checkbox.attr('checked',state.checked );
295     } else {  
296       if( this.tree.checkboxMultiState ) {
297         this._checkbox.value = state.mixed ? "mixed" : state.checked ? "checked" : "unchecked";
298       } else {
299         this._checkbox.value = state.checked ? "checked" : "unchecked";
300       }
301       this._checkbox.checked = state.checked;
302     }
303   },

On line 288 we first determine the checkbox style, next we check if multi (mixed) state support is required. On line 297 you can actually see that even if we use the default HTML checkbox we can still support mixed states. In other words, under the hood we support multi state checkboxes regardless of the checkbox style.

Update The Tree

Last but not least we have to make some minor changes to the Tree as well. I already talked about the additional attributes:

317 dojo.declare( "tmpdir.CheckBoxTree", dijit.Tree,
318 {
319   checkboxStyle: "dijit",
320 
321   checkboxMultiState: true,
322 
323   branchIcons: true,
324 
325   nodeIcons: true,

I also talked about the Tree no longer simply updating the checkbox checked state, going forward the _onCheckboxChange method of the Tree will call the _setCheckedState method of the CheckBoxTreeNode to have both states (checked and mixed) updated.

352   _onCheckboxChange: function( storeItem ) {
353 
354     var model  = this.model,
355       state    = model.getCheckboxState( storeItem ),
356       identity = model.getIdentity(storeItem),
357       nodes    = this._itemNodesMap[identity];
358 
359     if( nodes ) {
360       dojo.forEach( nodes, function(node) {
361         if( node._checkbox != null )
362           node._setCheckedState( state );
363       }, this );
364     }
365   },

Well, that’s it for our CheckboxTree and extended Checkbox widgets, the only thing left to do is to update our index.html file to include our new CSS file and add some of our new attributes. The new attributes aren’t really necessary if you are Ok with their default value. I have included them just to show how to overwrite their default values if needed.

  1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  2       "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  3 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
  4   <head>
  5     <title>Dijit Tree with Checkboxes </title>
  6     <link rel="stylesheet" href="/js/dojotoolkit/dijit/themes/nihilo/nihilo.css" />
  7     <link rel="stylesheet" href="/js/tmpdir/themes/nihilo/tmpdir.css" />
  8 
  9     <script type="text/javascript" src="/js/dojotoolkit/dojo/dojo.js"
 10         djConfig="parseOnLoad:true, isDebug:false"></script>
 11 
 12     <script type="text/javascript">
 13       dojo.registerModulePath("tmpdir","/js/tmpdir");
 14       dojo.require("dojo.data.ItemFileWriteStore");
 15       dojo.require("tmpdir.CheckBoxTree");
 16 
 17     function myTree( domLocation ) {
 18       var store = new dojo.data.ItemFileWriteStore( {
 19               url: "/js/datastore/Family.json"
 20               });
 21       var model = new tmpdir.CheckBoxStoreModel( {
 22               store: store,
 23               query: {type: 'parent'},
 24               rootLabel: 'The Family',
 25               checkboxAll:  true,
 26               checkboxRoot: true,
 27               checkboxState: true,
 28               checkboxStrict: true
 29               });
 30       var tree = new tmpdir.CheckBoxTree( {
 31               model: model,
 32               id: "MenuTree",
 33               allowMultiState: true,
 34               branchIcons: true,
 35               nodeIcons: true
 36               });
 37       tree.placeAt( domLocation );
 38     }
 39     </script>
 40   </head>
 41 
 42     <body class="nihilo">
 43     <div id="CheckboxTree">
 44       <script type="text/javascript">
 45         myTree("CheckboxTree");
 46       </script>
 47     </div>
 48   </body>
 49 </html>

The only real import change we are making to our index.html file is on line 7, the inclusion of our CSS file. Please make sure the tmpdir theme is the same as the one you select for dijit on line 6.

If the CSS themes do not match your are NOT going to see any checkboxes unless you set the checkboxStyle attribute to anything else but “dijit”.

Below you see three different representations of the same Family Tree using some of our configurable attributes. These examples are all using dojo 1.4 to demonstrate the multi parent support. For example: In our Json file Chantal is referenced by both Peter and Mary as a child. In addition, Peter is defined as a ‘parent’ and but also referenced by John as a child.

 MultiStateTreeCollection

The example on the right uses the default HTML checkboxes BUT, as mentioned before, under the hood it still maintains the mixed state and thus your application can check if any of the parent checkboxes (John, Mary, Peter, Joan and Chantal) are in a mixed state. You could disable the mixed state support by setting the checkboxMultiState to false.

Checkbox Tree Configurable Attributes

Over the course of these two tutorials we have introduced a number of attributes to our CheckBoxTree and CheckBoxStoreModel widgets. The tables below list all attributes, default value and a short description.

Note: Any attribute that has a default value assigned is considered optional.

CheckBoxStoreModel Attributes

Attribute Type Default Description
checkboxAll Boolean true If true, every node in the tree will receive a checkbox regardless if the attribute ‘checkbox’ is specified or not for a dojo.data item. If the ‘checkbox’ attribute is not specified the default value checkboxState will be applied.
checkboxIdent String “checkbox” The name of the attribute (attribute of the dojo.data.item) that specifies the items checkbox initial state.

Example: { name:’Egypt’, type:’country’, checkbox: true }

If a dojo.data.item has no ‘checkbox’ attribute specified it will depend on the attribute ‘checkboxAll’ if one will be created automatically and if so what the initial state will be as specified by ‘checkboxState’.

checkboxMState String “mixed” The name of the attribute (attribute of the dojo.data/item) that will hold the checkbox mixed state. The mixed state is separate from the default checked/unchecked state of a checkbox.
checkboxRoot Boolean false If true, the root node will receive a checkbox even though it’s not a true entry in the store.

This attribute is independent of the showRoot attribute of the Tree itself. If the Tree attribute ‘showRoot’ is set to false the checkbox for the root will not show either.

checkboxState Boolean false The default value/state applied to every checkbox unless otherwise specified for the dojo.data item.

(see also: checkboxIdent)

checkboxStrict Boolean true If true, a strict parent-child checkbox relation is maintained. For example, if all children are checked the parent will automatically be checked or if any of the children are unchecked the parent will be unchecked.

CheckBoxTree Attributes

Attribute Type Default Description
branchIcons Boolean true Determines if the FolderOpen/FolderClosed icon is displayed.
checkboxMultiState Boolean true Determines if Multi State (mixed) checkbox behavior is required. If set to false the value property of a checkbox can only be ‘checked’ or ‘unchecked’. If true the value can be ‘checked’, ‘unchecked’ or ‘mixed’
checkboxStyle String “dijit” Sets the style of the checkbox to be used. The default is “dijit” any other value will force the use of the native HTML style checkbox. The visual representation of a mixed state checkbox is only supported with a dijit style checkbox.
nodeIcons Boolean true Determines if the Leaf icon is displayed.

Other Tree/Model Attributes

Attribute Type Default Description
showRoot Boolean true Determines if the root node should be displayed or hidden.
persist Boolean true Enables/disables the use of cookies for state saving.
autoExpand Boolean false Fully expand the tree on load. Overrides `persist`
 
rootLabel String “ROOT” Label of the fabricated root item (also see showRoot)

32 Replies to “Dijit Tree with Multi State Checkboxes”

  1. Hello, Excellent blog (this one and the previous one). I’m in the process of going through the previous blog ang getting ready to understand the implementation of this one as well. For my purposes, I have the following additonal requirements and wanted to get your take on how to implement them:
    1. Root Node must have a slider control and no checkbox
    2. Leaf Nodes of a certain type do not need a checkbox nor leaf image
    3. All other leaf nodes need two checkboxes on them, the second using a different iconClass by which it does not appear to be a checkbox but still functions as one

    1. I’m guessing I can do a quick check for the root and create a slider control
    2. What are the gotchas for not having a checkbox on a node as far maintaining the parent chlid relationships, if no checkbox exist, should I return true when the node is being probed for it’s current state?
    3.I’m guessing I can follow your multi state example on how to create my own image strip that I can then use as my icon class

    Hope I’m on the right track.
    Thanks for the blog

    1. @Alex

      First of all, I’m sorry about the very late response. I’m currently working on completely different stuff AND for some reason WordPress (my blog software) stopped notifying me of any new posts. Anyway, to answer your questions:

      1. Root Node must have a slider control and no checkbox

      This would be something you would have to do yourself. I would recommend using the ‘Multistate checkbox’ as an example. Not displaying a checkboxes is already configurable on a per node basis.

      2. Leaf Nodes of a certain type do not need a checkbox nor leaf image

      Both features are currently available using either the attributes during tree creation or by means of the json data file which will allow you to specify if a checkbox is required for each individual node.

      3. All other leaf nodes need two checkboxes on them, the second using a different iconClass by which it does not appear to be a checkbox but still functions as one

      Again, use the multi-state checkbox as an example there is quit a bit of documentation in the code that should help you to understand how things work.

      2. What are the gotchas for not having a checkbox on a node as far maintaining the parent chlid relationships, if no checkbox exist, should I return true when the node is being probed for itโ€™s current state

      Please remember, the checkbox is only a visual representation of a state in the datastore. As an example: if you select the standard HTML checkbox you will only see ‘checked’ or ‘unchecked’ however, the datastore will still have the capability of maintaining all three states. In reallity this is one of the beauties of the Model-View-Controller concept. (in other words) there is no direct relation between what is maintained in the datastore and what is actually displayed. Therefore, not displaying a checkbox has no impact on the parent-child relationship.

      Hope this helps a little.

  2. Thanks for the post, very useful and great explanations.
    I used you code to setup a checkbox tree but noticed poor performance toggling checkboxes when the number of nodes starts to get high (>200). Any idea on how to improve this?

      1. Sorry to keep harping on the licensing question, but could you possibly declare the code to be under a recognized F/OSS license? Your comment “The software is freeware, no fee, use/modify it as you like but at the same time no warrenty or support.” is a little ambiguous because ‘freeware’ usually means ‘it doesn’t cost anything and can be freely redistributed but you can’t modify it’. I’d like to package something which uses this code (tt-rss) in Fedora and Fedora has strict licensing policies; the code has to be clearly under an accepted license. You could simply use the PHP License itself. Another good choice that represents your ‘use/modify it as you like’ idea is the BSD license – the “New BSD” and “2 clause BSD” variants at https://fedoraproject.org/wiki/Licensing/BSD are commonly used. All you’d have to do is put one of those licenses into the zip file for the code, or in the header of the file itself – that would probably be the best idea as the file is often stripped out and shipped on its own as part of someone else’s project, so if you put your name and the license in the header of the file, everyone would always know what terms they can use it under. thanks!

  3. Hi,

    I am trying to use your checkbox. I created checkboxes in a datagrid but the image wouldn’t render until mouse over it. Any idea that could be?

    Thanks your blog,

    Jan

  4. Hi,

    Great widget! well done! The arrow key’s provide keyboard access to navigate the tree very well, however is it possible to toggle the checkboxes on and off via the keyboard?

    Thanks,

    Richard

  5. Hi,

    Very nice widget, as a newbie to dojo it has taught me a great deal just going through the code with a fine tooth firebug comb.

    I seem to have everything working except the removal of branch and node icons.

    I’m using a modified version of dojo 1.6 included in the ESRI javascript API, so that may be part of the issue. However, the standard tree works fine. But, if I add some comments in the _createCheckbox object:

    if( this.isExpandable ) {
    if ( !this.tree.branchIcons ) {
    console.log(this.tree);
    console.log(this.iconNode);
    dojo.removeClass(this.iconNode,”dijitTreeIcon”);
    console.log(this.iconNode);
    }

    when I examine the tree I see that branchIcons and nodeIcons are false (as I set in the tree parameters) and the code successfully removes “dijitTreeIcon” from this.iconNode in the img class statement. But, “dijitIcon” remains in the image class and the icon does not disappear:

    What I’m really trying to do here is override the icon image using getIconClass:

    var tree = new dijit.Tree( {
    model: model,
    getIconClass: iconFunc,
    id: “MenuTree”});

    var iconFunc = dojo.hitch(this, function (item, opened) {
    if(item !== undefined && item !== null) {
    if (item.type==’layer’) {
    console.log(item.iconImg);
    return “iconImg”; }
    }
    });

    This works successfully in the standard dijittree.html setup at the beginning of the tutorial, but I can’t get it to work when adding it to the checkboxtree implementation. I get but not the new override css class .iconImg or the dijitLeaf class. Eventually I want to store the image css class name in the JSON so that each leaf can have an individual image to create a legend.

    Any suggestions you may have are appreciated.

    There is another option, shown here http://robrobbins.info/?tag=dojo, and I think it would be possible to override the _TreeNode attribute map, but this is way over my programming head at this point.

    Thanks,

    Susan

    1. Got it!

      I added the following in CheckBoxTree.js to override the default behavior of getIconClass in dojo.declare( “tmpdir.CheckBoxTree”, dijit.Tree:

      //override getIconClass to support custom icons stored in JSON file
      getIconClass: function(/*dojo.data.Item*/ item, /*Boolean*/ opened) {
      if( item == this.model.root ) {
      //dojo.removeClass(this.iconNode,”dijitTreeIcon”);
      //console.log(“root”);
      return (opened ? “dijitFolderOpened” : “dijitFolderClosed”);
      }
      else if( this.model.mayHaveChildren(item) ) {
      var itemName = this.model.store.getValue(item,”name”);
      //dojo.removeClass(this.iconNode,”dijitTreeIcon”);
      //console.log(“child ” + itemName);
      return (opened ? “dijitFolderOpened” : “dijitFolderClosed”);
      }
      else {
      var leafClass = this.model.store.getValue(item, “icon”);
      if(leafClass) {
      //console.log(“do I return here at leafClass”);
      return leafClass;
      }
      else {
      //console.log(“or do I return here at dijitLeaf”);
      return “dijitLeaf”;
      }
      }
      },

      Then in my JSON file I added an ICON attribute with the name of the class in a custom CSS file.

      {name:’Aeronautics_Districts’, type:’layer’, icon:’areoIcon’, id:0, visible:false, checkbox: false},

      finally – added the css tag for each icon:

      .areoIcon {background: url(‘/images/legend/areoIcon.png’) no-repeat}

      Not sure I like hard coding the icons like this….in the future an ESRI map service will let you get a URL to the swatch for the Icon, but this will do for now!

      Thanks!

      Susan

  6. Hello, nice widget. How can I submit the clicked values via form and secondly, in another form (edit), I would like to tick the nodes programmatically or have the widget ticked those nodes based on values from my json/database. Is this possible?

    1. Dean,

      Did you find a solution to tick a checkbox programmatically? I’m performing a search on data, and if the data exists I want to tick the checkbox associated with the data item.

      Susan

  7. Hi

    Thanks very much for sharing your code, I’ve learned a lot from it!

    One problem though: using the multistate checkboxes, when I check/uncheck a child checkbox, the parent checkboxes do not refresh until I hover over them. When I hover, they refresh correctly. Any ideas?

    Thanks very much

    Tony

  8. How do I query the store for the list of checked nodes and display the whole checked nodes on the checkbox tree page??
    Also I need to maintain a xml containing the list of the checked nodes and their children ?? Thanks in advance

  9. @Tony @Julio

    This took me some time, too.
    The solution is to add the following code to the “_setValueAttr” method:

    // To update the visual state, _setStateClass has to be called.
    // Unfortunately, _CssStateMixin indeed monitors a real crowd of
    // attributes and calls _setStateClass automatically, but it does
    // NOT monitor “value”.
    this._setStateClass();

  10. hi, your dijit work great in my case.
    And I know how to return the checked result.

    but I dont know how to reture the topest folder only.
    for example,

    Root
    ….folder 1
    ……..folder 1.1
    …………file A
    …………file B
    ….folder 2
    ……..folder 2.1
    ….file C
    ….file D

    if I checked on folder 1, file C and file D then
    all the things under folder 1 will be checked. so how can I return the topest folder folder 1, file C and file D only?

  11. Hi! Great control…

    Could someone advise me how to disable some of the checkboxes from the model so that a user cannot check/uncheck that checkboxes. Disabled checkboxes should be visible (disabled state) but totally ignored by parent/s checkboxes (checked/unchecked/mixed)…

    Thanks

  12. Hi, great control.

    Can someone give me advice on how to make some of tree checkboxes disabled using the checkbox tree model… also, disabled checkboxes should be ignored totally. Thanks

  13. Firstly thank you for taking the time to document this so thoroughly. Your functionality is exactly what I need for my project.
    The download code (Release date: 02/12/2010) works perfectly with Dojo 1.4.3
    With 1.7.x the code fails:
    1) branchIcons true/false doesn’t work
    2) nodeIcons true/false doesn’t work
    3) the update of the multistate dijit checkbox fails to refresh without ‘hovering’ over the check box

    There are 3 ‘DEPRECEATED – will be removed in version: 2.0’ warnings in debug mode that will become future problems

    There is no support for the ‘claro’ scheme (see ticket #7514 http://bugs.dojotoolkit.org/attachment/ticket/7514/tristatecheckboxStates.png) for a suitable icon strip (15×15 pixels so adjust the offsets)

    I can probably sort these issues out for my own use and I’m happy to document any changes back.

    Before I embark down that route, I wonder if you have updated the source at all or tackled any of the changes needed?

    Thanks again for really good article

    1. In the next two/three weeks I will finally have some time to take a look at some of the reported issues with DOJO 1.5 and beyond. I don’t think they will be hard to fix so please stay tuned.

Leave a Reply to HamptonNorth Cancel reply

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