Introduction

For those of you familiar with the Dijit CheckBox Tree project this is the third installment in the checkbox tree series. The first document discussed the initial project requirements and implementation details based on dojo version 1.3 and 1.4. The second document showed the addition of the so-called Multi-State checkboxes whereas in this episode I'm going to introduce you to the completely new, AMD compliant, version of the Dijit CheckBox Tree.

If you have questions about this article, the Dijit CheckBox Tree in general, or want to leave a comment please visit my blog which hosts this article as well.

CheckBox Tree

Dojo & Dijit

Since dojo 1.4 many things have changed with regards to the dojo toolkit, especially the introduction of dojo 1.6 set the wheels of Asynchronous Model Definition in motion. AMD is a new standard detailing how to write modular JavaScript code which can be loaded using an AMD compliant loader. Any AMD compliant module is fully self-contained that is, no more dependencies on global variables, a module can reference other AMD modules by explicitly declaring a dependency using the new require() syntax. This modular approach allows you to be very specific in what needs to be loaded in order to make your application shine. As a result, AMD compliant applications tend to load faster as only the modules you really need are loaded.

In addition to the dojo toolkit enhancements, I received several feature requests, most of them centered around the topic of making it easier to programmatically change the checked state of a checkbox. As of dojo 1.6 people also starting reporting rendering issues with the CheckBox Tree and finally there was of course my private nice to have list.

This first step was to solve the rendering issues, make it dojo 1.7 compliant and add functionality to make it easier to programmatically alter a checkbox checked state. The second step was to create a fully AMD compliant version of the Dijit CheckBox Tree, add some of the Nice To Have features and extend the API's. This document focuses on the second part.

Special thanks goes to Vladimir Elistratov who tirelessly perfromed code reviews and provided valuable feedback and suggestions. If you ever feel lost go checkout his great JavaScript geo library

New is better

Now that the new AMD implementation of the ChecBox Tree is available the interim version for dojo 1.7, which addressed the rendering issues, will no longer be available for download.

A New Project

Due to new dojo features, the number of enhancement I had in mind and the new AMD approach, instead of trying to refactor the existing code base I decided to start the AMD implementation as a completely new project called cbtree, short for 'CheckBox Tree'. The Dijit CheckBox Tree code repository is now available at GitHub allowing you to actively follow any new Dijit CheckBox Tree developments. The download section of the project holds the latest stable version of the project.
cbtree directory

A new project comes with a new directory layout. Please note that the cbtree directory is located at the same level as dojo and dijit. This is important as the included demo applications and unit tests all use relative paths when referencing dojo and dijit modules.

If you haven't done so already and you want to setup a local HTTP server, the instructions can be found here. However, instead of downloading dojo 1.3.2 as recommended you need to get the latest dojo toolkit instead.

When migrating

If you are planning to migrate from the original Dijit CheckBox Tree to the new AMD implementation please refer to the documentation for a detailed description of all available properties. Also see the New Properties section for additional information.

What's New

Following is a summary of some of the new features and enhancements, the table is by no means a complete overview. Please refer to the CheckBox Tree documentation for more details.

New and enhanced Feature Description
Tree Styling API The CheckBox Tree comes with a simple TreeStyling API which is loaded as a separate tree extension. The Tree Styling API allows you to dynamically manage tree node icons, labels and row styling either for the entire tree or on a per data item basis.
Store Model API The Store Model API extends the functionality of the standard CheckBox Tree Store Models. The API allows the user to programmatically build and maintain checkbox trees. For example, you can create your store starting with an empty JSON dataset or use an existing data store and use the Store Model API to add, move, remove or change store items.
Custom Widgets Instead of the default Multi-State checkbox that comes with the CheckBox Tree you are now able to specify alternative widgets to represent a store items checked state. The CheckBox Tree packages comes with two demos, tree04.html and tree05.html demonstrating this capability. Demo tree04.html uses the dojox TriStateCheckbox whereas tree05.html uses the dijit ToggleButton widget. An image of the CheckBox Tree with the ToggleButton widget is included below.
Read Only Checkboxes Checkboxes can now be marked as read-only using the tree properties branchReadOnly and leafReadOnly
Multi Model Support The new AMD CheckBox Tree implementation now handles multiple models operating on the same store correctly. Changes made by one model are automatically reflected by any tree associated with other models operating on the same store. For example: all trees on the left in the online demo are associated with just one store, checking a checkbox on one tree will automatically update all other trees.
Updated Event Handling The internal event handling system has been rewritten and optimized to even support trees with thousands of checkboxes. In addition, updates to the checked state of a data item directly in the underlaying store using, for example: store.setValue(), will result in an automatic update of any parent-child relationship keeping the store, model and tree in a synchronized state.
The new event handling also allows you to map any store item property to a tree node property. (see the advanced section in the documentation for details).

Design Principles

In trying to understand what benefits AMD brings to the table and how to leverage it I decided to redesign and, as a result, rewrite the entire Dijit CheckBox Tree. As I mentioned before, AMD is, among other things, about loading only what you need without having implicit external dependencies. Therefore the practice of declaring multiple modules in a single source file is strongly discouraged and as a side effect, it truly creates cleaner code. What about the additional overhead of loading all these smaller modules you ask? Well, I haven't seen any HTTP 1.0 server in the last couple of years so I guess you won't notice the difference. Keep in mind you load a lot less code to begin with then you would have with previous dojo implementations, meaning a lot less network I/O. In addition, loading modules cross domain using a CDN, for example, has become a piece of cake.

Code Separation

Originally all modules, that is, CheckBox, CheckBoxStoreModel, _CheckBoxTreeNode and CheckBoxTree were all included in a single source file. With the new implementation however, they have been split into separate files and turned into anonymous classes. In addition, the CheckBox Tree now comes with its own dedicated store models which no longer inherit from the original dijit tree models.

Simple Accessors

Starting with dojo 1.6 simple accessors like get() and set() have been made available for all dijit widgets. These accessors are now also available for the NEW CheckBox Tree, Tree Styling API and Store Model API. For example, setting or getting the checked state of a tree node is as simple as:

nodeWiget.set("checked",true)
nodeWiget.get("checked")

New Properties

Even though the project is still referred to as The Dijit CheckBox Tree, the reality is that with the new implementation any widget capable of presenting a so-called checked state can now be used. For example, when creating a CheckBox Tree you can, by means of the tree widget property, specify a toggle button, or any other widget for that matter, to be used instead of the default checkbox.

Toggle button Tree

The example on the left demonstrates the use of the ToggleButton widget. Because of these new capabilities the CheckBox Tree store models no longer refer to a CheckBox state, instead they refer to the more generic Checked state.

Therefore all properties formerly prefixed with checkBox are now simply prefixed with checked like: checkedAll instead of checkboxAll. In addition, new properties have been added while others have been removed or have moved from the tree to the model to better decouple the tree from the model.

If you are planning to migrate from the original Dijit CheckBox Tree to the new implementation please refer to the documentation for a detailed description of all available properties including many code samples how to use them.

Tree Styling

The AMD CheckBox Tree comes with a separate Tree Styling API which allows the user to apply styling to either the tree as a whole or set styling properties on a per data item basis using simple accessors like get() and set(). Styling can be applied to the tree node row, icon or label.

Custom Icons

In contrast to the standard dijit tree, which applies tree node styling using callback functions, the Tree Styling API offers the ability to programmatically set styling properties. For example, to change the label styling for a given data item simply call:

tree.set("labelStyle",{color:red}, item)

To set an icon, or a set of icons, for a for a given data item call:

tree.set("icon", {iconClass:"myIcon"}, item)

    or

tree.set("iconClass","myIcon", item)

Alternatively, you can designate a data item property as the default icon for the given item by setting the new iconAttr property of the store model. If set, both the model and the tree will recognise the item property as an icon class. Please refer to the Tree Styling documentation for details.

Note: With the Tree Styling API loaded you can still use the original dijit tree callback style styling.

Online Demo

The CheckBox Tree package includes a set of demo applications as well as some unit tests which you can you use to verify proper installation. A comprehensive AMD CheckBox Tree demo is also available online.

Custom Icons

The online demo illustrates the CheckBox Tree configurable properties, the tree styling API, drag and drop feature and the extended store model API.

Also, the left accordion panel demostrate the multi model support, that is, click a checkbox on one tree and all other trees with checkboxes are automatically updated.

Download

The Dijit CheckBox Tree source code and demos can be downloaded straight from the GitHub download section. Note, the download section holds the latest stable version of the code. When installing the CheckBox Tree package make sure you install it according the above shown directory structure.

Documentation

All included documentation is written using the Markdown format and is also available online here.

Code Sample

The following is a simple application which creates a CheckBox Tree and connect to the onCheckBoxClick event. Whenever a checkbox on the tree is clicked the function checkBoxClicked() is called displaying the updated checked state for the associated tree node.

  1 <html lang="en">
  2   <head>
  3     <title>Dijit Tree with Checkboxes</title>
  4     <style type="text/css">
  5       @import "../../dijit/themes/claro/claro.css";
  6       @import "../themes/claro/claro.css";
  7     </style>
  8     <script type="text/javascript">
  9       var dojoConfig = {
 10             async: true,
 11             parseOnLoad: true,
 12             isDebug: true,
 13             baseUrl: "../../",
 14             packages: [
 15               { name: "dojo",  location: "dojo" },
 16               { name: "dijit", location: "dijit" },
 17               { name: "cbtree",location: "cbtree" }
 18             ]
 19       };
 20     </script>
 21     <script type="text/javascript" src="../../dojo/dojo.js"></script>
 22   </head>
 23   <body class="claro">
 24     <h1 class="DemoTitle">Dijit Tree with Multi State CheckBoxes</h1>
 25     <div id="CheckboxTree">
 26       <script type="text/javascript">
 27         require([
 28           "dojo/_base/connect",
 29           "dojo/data/ItemFileWriteStore",
 30           "dojo/domReady",
 31           "cbtree/Tree",                    
 32           "cbtree/models/ForestStoreModel"  
 33           ], function( connect, ItemFileWriteStore, domReady, Tree, ForestStoreModel) {
 34             function checkBoxClicked( item, nodeWidget, evt ) {
 35               alert( "The new state for " + this.getLabel(item) + " is: " + nodeWidget.get("checked") );
 36             }
 37             var store = new ItemFileWriteStore( { url: "../datastore/Family-1.7.json" });
 38             var model = new ForestStoreModel( {
 39                                 store: store,
 40                                 query: {type: 'parent'},
 41                                 rootLabel: 'The Family'
 42                              });
 43             var tree = new Tree( {
 44                               model: model,
 45                               id: "MenuTree",
 46                               branchIcons: true,
 47                               branchReadOnly: false,
 48                               checkBoxes: true,
 49                               nodeIcons: true
 50                             });
 51             connect.connect( tree, "onCheckBoxClick", model, checkBoxClicked );
 52             domReady( function() {
 53               tree.placeAt('CheckboxTree');
 54             });
 55         });
 56       </script>
 57     </div>
 58     <h2>Click a checkbox</h2>
 59   </body>
 60 </html>
        

The CheckBox Tree package and documentation include many more examples.

How Does It Work

The easiest way to exaplain how the Dijit CheckBox Tree works is by using some simplified sequence diagrams and a high level overview of some of the internal functions. If you want all the details then the source code is going to be your best friend. In this How To section I'm going to use two diagrams to explain how the above sample code works and what happens under the hood.

Creating the CheckBox Tree

On lines 37-50 we instantiate the store, model and tree. When the model is created it connects, or better yet subscribes, to the onSet event of the store. Whenever a property of a data item changes, the store will generate an onSet event. The onSet event holds information about the data item, the property that changed and what the new property value is.
When the tree is created on line 43, it internally connects to the onChange event of the model. As part of the post create stage of the tree it starts loading the data store by calling validateData() and for each data item a tree node and checkbox is created.

The models validateData() is an essential part of the CheckBox Tree, it loads the store data items and synchronizes their checked state when the parent-child checkbox relationship is requested.

The diagram below shows the sequence of events involved in creating a CheckBox Tree.

Custom Icons

If you take a closer look at the sequence diagram you will notice that the tree never interact with the store directly, this is part of the Model-View-Controller design pattern.

Getting notified

In order for our sample application to get notified whenever the user clicks a tree checkbox it needs to connect to the onCheckBoxClick event of the tree. On line 51 the application connects to the event specifying checkBoxClicked as the callback function.

connect.connect( tree, "onCheckBoxClick", model, checkBoxClicked );
        

The function checkBoxClicked() on line 34 will be called in the context of the model, that is, the "this" object equates to model.
So what happens when a user clicks a checkbox? The following diagram show the sequence of events that take place each time a user clicks a tree checkbox. The first thing the tree node will do is to verify the click event took place on a DOM element of type "input", if so, it gets the new checked state from the checkbox.

Custom Icons

Next, the tree node will tell the store model to update the checked state for the data item associated with the node by calling setChecked(). Simply speaking, the model will take the request and in turn asks the underlying store to do the same. On completion the store will generate an onSet event.

Because the model subscribed to the onSet event of the store, as shown in the first diagram above, it gets notified about the store update and this is were the interesting stuff starts to happen. Any data item in the store may have any number of parent items so, assuming the parent-child relationship for the model is enabled, the model will take the onSet event and updates the checked state of all parent items according to the new state of the checkbox that just got updated.
Although not visible on the sequence diagram, any update to a parent item results in another call to the setChecked() function untill all have been updated. So a single click on a tree checkbox can result in several store updates. Also, if the tree node whose checkbox was clicked has any children those children will also be updated.

Being the system architect that you are, you are wondering: why not update the parent items and potential children as soon as the original setChecked() call comes in? It would make things alot easier as you don't have to handle the onSet event from the store.
The answer is: there can be multiple models operating on the same store. For example, lets assume you have two trees A and B, and each tree has its own model A and B. If a checkbox gets clicked on tree A, the tree will call setChecked() of model A, therefore model B has no notion of any click event. However, because both models are subscribed to the onSet event of the common store, both models will be notified of the store update thus allowing both model A and B to update their own tree.

Finally, when the model receives the onSet event it will generate a matching onChange event which is caught by all trees associated with the model. Note, you can associate multiple trees with a single model. The tree will then search for all tree nodes associated with the data item and send a set("checked", newValue) request to each of them.