Learn the tips, techniques, and pitfalls when developing Web 2.0 and Dojo applications. Wendi Nusbickel and Melissa Betancourt have worked on the Dojo application documented in this article for over a year. Having recently completed the development of a Web 2.0 Dojo prototype, they share the experience they gained when creating a custom Dojo application.

Share:

Wendi Nusbickel (wnusbick@us.ibm.com), Software Engineer, IBM

Photo of Wendi NusbickelWendi Nusbickel is a software engineer at the IBM Software Lab in Boca Raton, Florida. She currently works on the Information Management, Information Server development team as a client architect for the next generation Information Services Director application. She has been developing Web 2.0 applications with Dojo for over 2 years, and has over 10 years of technical experience in Java, J2EE, WebSphere Application Server, and Web-based technologies. Wendi has a Bachelor of Science degree in Computer Science from North Dakota State University. You can reach Wendi at wnusbick@us.ibm.com.



Melissa Betancourt (betan@us.ibm.com), Software Engineer, IBM

Photo of Melissa BetancourtMelissa Betancourt is a software engineer at the IBM Software Lab in Boca Raton, Florida. She currently works on the Information Management, Information Server development team as a lead developer for the next generation Information Services Director application. She has over five years of experience in Java programming and Web-based technologies. Melissa has a Bachelor of Science degree in Computer Science from Florida International University. You can reach Melissa at betan@us.ibm.com.



09 December 2008

Also available in Chinese Japanese

Introduction

We recently completed the development of a Web 2.0 Dojo prototype. The prototype was extensive, exposing a new function for Information Management. We worked with a user experience team to help ensure the application was usable. The screens were designed by a graphical Web designer, for a professional look and feel.

This article documents our experience with the Web 2.0 development of this prototype. Because Web 2.0 is relatively new, it can be difficult to get started or know how to do customizations when the need arises. We did not deliver an out-of-the-box look and feel with our Dojo application. We needed a graphic design that was consistent with the branding of our product line. So, we had to do customizations with Dojo. Customizations are where most developers end up spending a lot of time, especially if they don't know exactly how to tackle this problem.

Because this article focuses on the customization of a Dojo application, we do not describe the details of every widget attribute needed or shown in the examples. Having a basic knowledge of Dojo and CSS is assumed. The examples in this article are based on Dojo 1.1.0 (see Resources for a link).


Creating a Dojo application

The design methodology

As experienced software engineers, we are accustomed to using Object Oriented (OO) techniques for our development. We wanted our Web 2.0 application to follow the principles we have practiced for many years with Java™ programming. We found we were able to do this to a large degree using the Dojo widget and template pattern, along with JavaScript/Dojo Objects.

Use of Dojo widget and template pattern

We wrote custom widgets consisting of JavaScript classes with HTML templates for rendering. This allowed us to apply more OO methodologies to our application, rather than just writing HTML files that used global JavaScript functions. With the custom widgets we gained various features: object isolation with well-defined widgets, dynamic content for the HTML using widgets to update the DOM whenever needed, and multiple instances of the same HTML template by using custom widgets to generate different HTML IDs. We were also able to extend the custom widgets we wrote, adding more specialized versions.

Listing 1 shows a portion of a custom widget JavaScript file, which is built on top of dijit._Widget and dijit._Templated.

Listing 1. Custom widget JavaScript file
dojo.provide("mywidgets.MyWidget");
// put any other requires the widget needs here
dojo.declare(
  'mywidgets.MyWidget',
  [dijit._Widget, dijit._Templated],
{
  widgetsInTemplate: true,

templatePath: dojo.moduleUrl( "mywidgets", 
"../templates/mywidgets/templateMyWidget.html");
// put any other variables and methods for this widget here
// can also override methods from the base classes here

});

The widgetsInTemplate attribute tells Dojo that it needs to parse the template file because it contains Dojo widgets, as opposed to only HTML tags. The templatePath attribute tells Dojo that this widget is to use the specified HTML template for rendering. When the widget is instantiated, such as using a new mywidgets.MyWidget() instance, the template's HTML is rendered at the point where this object is inserted in the DOM..

Listing 2 shows the HTML template file for this widget.

Listing 2. HTML template
<div class="templateMyWidget">
    <!-- Other Widgets and HTML can be included here -->
<button dojoType="dijit.form.Button" id="myButton_${id}" 
label="My Button" dojoAttachPoint="myButton"></button>
</div>

In this example, variable substitution is used for the ID in case your code needs to access the button through its ID. The ${id} is replaced with the value of the ID attribute that is inherited from the dijit._Widget class. This ID is unique; therefore, using it as part of the IDs in your template makes it so that if you instantiate several of your widgets, each will have a unique ID. We also included the dojoAttachPoint attribute. This creates an attribute in your widget that points to the DOM node. So if you have access to the widget (for example, myWidgetObj), you can get to the DOM node using myWidgetObj.myButton. You would not need to know the ID and can leave out that attribute, letting the system create its own unique ID for that element. You can retrieve the system-generated ID by using myWidgetObj.myButton.id.

If you download the Dojo source code you will notice that Dojo widgets are coded this way as well. Each Dojo widget has a JavaScript file and an HTML file associated with it (usually both with the same name). The templates are found in directories named templates at the level of the widget's JavaScript file. For example, the Button widget's JavaScript file is <dojo_root>\dijit\form\Button.js and its template file is <dojo_root>\dijit\form\templates\Button.html, where <dojo_root> is the directory where the Dojo code was downloaded.

Use of JavaScript and Dojo objects

When we started the prototype, we did not have much experience with JavaScript, although, it does let one develop using OO principles using the JavaScript Object. Dojo provides a better way to define your own classes using the dojo.declare construct. We recommend the developerWorks article "Dojo concepts for Java developers" (see Resources). As with any prototype, our requirements kept creeping in past the point of initial design, so the code got messy. We wished we had employed more design patterns. There are many articles on design patterns, which include examples with JavaScript and Dojo. We recommend one called "MVC with JavaScript" (see Resources).

Error handling

Like Java code, JavaScript does have exception handling, which we recommend using for your error handling.

The try/catch handling in JavaScript, as shown in Listing 3, is much like Java code. You can see a stack trace in the exception object.

Listing 3. Try/catch handling
try {
     // your logic
}catch(e){
     // error handling
}

Exceptions can be thrown from a method in JavaScript, much like Java programming.

Listing 4. Exception thrown
if( <test for error condition> ) throw "meaningful error message";

Development environment for Dojo

  • Starting point - Dojo examples/demos and test code: Getting started with Dojo is quite fast and easy, as there are numerous samples that you can just run using any Web browser and then modify to suit your needs. Most of the samples are located in the tests directories within the Dojo source code.
  • Integrated development environment (IDE): At the time we started our project, there was little to no Ajax/Dojo support. We did all our development in an Eclipse-based IDE, because that is where we do all of our Java development. We did find some plug-ins that had some code assistance for JavaScript and Dojo. However, for cases where we needed to deviate from the sample code, we often resorted to reading the actual Dojo classes to understand how to use them. The big adjustment we faced was that there is no compile time checking with JavaScript. That means you don't find your bugs until the path with the bug is executed.
  • Help - Dojo community: Because Dojo is so popular, there is a huge community of support. There are numerous forums where other developers post their questions and code solutions to problems. We often found these posted examples helpful; if not directly, they often gave us ideas to help us tackle the problem we were trying to solve.
  • Debugging – Firebug plug-in: For our prototype, we wanted a single Web browser and chose Mozilla Firefox. To that we added the Firebug plug-in. Firebug is downloadable from many locations, and you can get a link to it in Resources. One of the things Firebug does is log the HTTP requests/responses along with timings. You can set breakpoints in a JavaScript file to debug. It also lets you inspect/edit HTML, CSS, and DOM code. Firebug is also very helpful in figuring out which style attributes are being used in the Dojo widgets.

    Additionally, Firebug adds a global variable named "console" to your Web application. This variable has many methods for log and trace including: debug, info, warn, error, and log. These methods write a message in the console along with the JavaScript file name and line number of the traced message. We enabled conditional tracing so we could use a global attribute to turn the console tracing on and off.

One tip we can offer is we discovered that when we included our own widgets using the dojo.require statement, like shown in Listing 5, the traced message file name and line numbers in the console were not available unless we also added an explicit script src statement such as the one shown in Listing 6.

Listing 5. Including widgets using the dojo.require statement
dojo.require("acme.MyWidget");
Listing 6. Adding an explicit script src statement
<script type="text/javaacript"src="widget/MyWidget.js"></script>

Customizing the Dojo app

We looked at several JavaScript frameworks, and while we actually wanted a framework that was a bit higher level than Dojo, it had all the right building blocks, customization points, and a large community of support.

How to customize

So, we decided to use Dojo, but to customize it, and there are many aspects to this process. The simplest way to customize the look and feel of Dojo widgets is with the use of custom style sheets (CSS). You can also override a Dojo widget method, subclass an existing Dojo widget for special behavior, or create your own widget. In this section, we describe a variety of customizations used by this prototype.

Note: This list represents a subset of what was actually developed for our application. Many additional customizations not included in this article are currently in the Intellectual Property (IP) process.

Restyling Dojo widgets

Dojo has three basic skins that can be used out of the box: soria, tundra, and nihilo. These skins can also be used as a base by specifying it in the class attribute of your application's overall HTML body tag. Then you can override the styles you choose with your own CSS. You could also write all your CSS from scratch; however, this process is time consuming if your application uses a lot of widgets. To view the Dojo widgets with the predefined skins, you can run the Dijit Theme Tester, which is located in <dojo_home>/dijit/themes/themeTester.html, where <dojo_home> is the directory where you downloaded the Dojo source code.

The templates for the Dojo widgets contain class attributes that can be used in defining custom style sheets, but we have not found these style names documented anywhere. In order to find the class names for the widgets, we found two ways helpful. The first method is to look at the template .html file associated with the widget. The template file for the dijit.form.Button widget looks like Listing 7.

Listing 7. Template for dijit.form.Button widget
<div class="dijit dijitReset dijitLeft dijitInline"
    dojoAttachEvent="onclick:_onButtonClick,onmouseenter:_onMouse,onmouseleave:
  _onMouse,onmousedown:_onMouse"
    waiRole="presentation"
    ><button class="dijitReset dijitStretch dijitButtonNode 
dijitButtonContents" dojoAttachPoint="focusNode,titleNode"
        type="${type}" waiRole="button" waiState="labelledby-${id}_label"
        ><span class="dijitReset dijitInline ${iconClass}" dojoAttachPoint="iconNode" 
             ><span class="dijitReset dijitToggleButtonIconChar">✓</span 
        ></span
        ><div class="dijitReset dijitInline"><center class="dijitReset dijitButtonText"
id="${id}_label" dojoAttachPoint="containerNode">${label}</center></div
    ></button
></div>

For styling you would be concerned with the values for the class attributes of the template. Class values surrounded by ${}, like ${iconClass}, are replaced with the value of the iconClass attribute that was specified in the Button tag.

In the above example, dijitButtonText is used to style the text displayed in the button. So, if you wanted the text to be blue, you would include the following code in your style sheet:

Listing 8. Code to turn button text blue
.dijitButtonText
{
   color: blue;
}

You can be more specific and qualify the style even further. This is good in case this class name is used for other widgets as well. For example, .dijitButtonText is also used in ComboButton and DropdownButton templates. So, with the CSS example given above, all of those controls will have blue text. The JavaScript files for these controls contain a baseClass attribute that is used at the outermost layer and can be used to distinguish between them in your CSS. The baseClass for Button is dijitButton, while the baseClass for the other two are dijitComboButton and dijitDropDownButton, respectively. If you only want the regular button to have blue text, then the CSS would look like Listing 9.

Listing 9. Code for regular button to have blue text
.dijitButton .dijitButtonText
{ 
   color: blue;
}

If you are using a Dojo skin as a base, we recommend adding .soria to the front of that line, and possibly even add more levels, like .dijitButtonNode. This is because if the same CSS class is used in two different files, it may use the one you are not expecting; it will use the one that is more qualified. You can see how this calculation is done in section 6.4.3 of the CSS specification (see Resources). You can also force it to be used by adding the CSS !important qualification (explained in section 6.4.2 of the specification).

When using various skins and styles, it sometimes becomes difficult to keep track of which style definition will be used for a particular class. This is where Firebug comes in very handy. To open up the Firebug console while on a Web page, click on the icon on the lower left corner of the browser window. There is an HTML tab on the left side of the Firebug console that shows the HTML code being displayed on the screen, including the class attributes. If you click on the HTML tag, the associated CSS that affected this tag is displayed on the right side, under the Style tab. If the same class attribute was defined several times in the code, it will show you all the definitions and which ones were overridden and which are being used. If you want to see the styling for a particular area on the screen, click Inspect in the Firebug toolbar, and then click on the area/widget on the screen. This takes you to the HTML code responsible for that area and displays its associated CSS on the right.

Figure 1 shows the inspection of the Create button of the soria theme in the Dijit Theme Tester. (For a larger view, click here.) In Firebug, you can see that the padding style specified in dijits.css was overridden by the padding style specified in soria.css, which was further qualified by .soria .dijitButtonNode instead of just .dijitButtonNode.

Figure 1. Default Dojo style for our application
Default Dojo style for our application

We ran into some limitations using Dojo's base widgets. One limitation is the ability to add rounded corners to certain widgets. It is possible to have rounded corners using Mozilla-specific CSS styles, but this forces you to have a solid color background, and it only works for Mozilla Firefox. So you might not be able to have a rounded gradient image for the background of your control unless it is a fixed-size image, which is unrealistic in the case of buttons and title bars where the text contained in them is variable or they need to take up a percentage of the screen as opposed to a fixed width and height.

Dojo does provide this capability with some widgets though, like the dijit.form.Button. Dojo's template contains <div> elements with class attributes of dijitLeft and dijitRight, which can be used to show the rounded left side image and the rounded right side image, respectively. Note that the dijitRight attribute was removed from the template in the initial version of 1.1.0, so it is not shown in the Button's template example that we display above; however, this has been fixed. If you find cases like this, where the fix may be critical for your application, you can get the modified files from the nightly builds (see the Dojo toolkit link in Resources).

Figure 2 shows an early look of our application using the Dojo soria theme out of the box.

Figure 2. Default Dojo style for our application
Default Dojo style for our application

Figure 3 shows the final look of our application with styling customizations. (Click here to see a larger image.)

Figure 3. Custom Dojo style for our application
Custom Dojo style for our application

Navigation aids - breadcrumb trail

Breadcrumb trails are used on many Web sites to keep track of navigation. Dojo provides a dijit.layout.StackController widget that you can use in conjunction with the dijit.layout.StackContainer widget, which lets you navigate through pages in the StackContainer. Listing 10 shows a basic example of how this would be used. This code is available for download and can be run from the <dojo_home>\dijit\tests\layout\ directory of the Dojo source code.

Listing 10. using the dijit.layout.StackController widget with the dijit.layout.StackContainer widget
<div dojoType="dijit.layout.StackController" containerId="myStackContainer"></div>
    
<div id="myStackContainer" dojoType="dijit.layout.StackContainer"
    style="width: 90%; border: 2px solid; height: 100px;">
    <div id="page1" dojoType="dijit.layout.ContentPane" 
  title="page 1">This is page 1.</div>
    <div id="page2" dojoType="dijit.layout.ContentPane" 
  title="page 2">This is page 2.</div>
    <div id="page3" dojoType="dijit.layout.ContentPane" 
  title="page 3">This is page 3.</div>
</div>

Using Dojo's soria stylesheet this code would look like Figure 4.

Figure 4. StackContainer application using soria stylesheet
StackContainer application using soria stylesheet

Clicking on each button of the StackController would show its corresponding page. However, if you want to have the typical breadcrumb behavior, where additional pages are added as you drill down and clicking on page 1 would go back to the first page and remove all the other pages from the breadcrumb trail (as well as the StackContainer), some custom code is needed.

Start with the same code from Listing 10, but without any initial static pages in the StackContainer, as shown in Listing 11.

Listing 11. Code without initial static pages
<div dojoType="dijit.layout.StackController" containerId="myStackContainer">
</div>
    
<div id="myStackContainer" dojoType="dijit.layout.StackContainer"
    style="width: 90%; border: 2px solid; height: 100px;">
</div>

In order to simulate drilling down into a page, you add a button to dynamically add pages to the StackContainer.

Listing 12. Adding a button to dynamically add pages
<button dojoType="dijit.form.Button"
    showLabel="true"    
    label="Add Page"
    onClick="loadPage">
</button>

The loadPage code creates a dijit.layout.ContentPane and adds it to the myStackContainer, as in Listing 13.

Listing 13. Creating a dijit.layout.Content.Pane
var nPages = 0;
  function loadPage()
  {
    nPages = nPages + 1;
    var container = dijit.byId('myStackContainer');
    if( container )
    {
      var pageid = "page" + nPages;
      add(container,"Page " + nPages, pageid);
      container.selectChild(pageid);
    }
  }
  
  function add(parent,name,id)
  {
    var node = document.createElement("div");
    var child = new dijit.layout.ContentPane
      ( {title: name, id: id },node );
    parent.addChild(child);

    //add content to the new page
    dojo.byId(id).innerHTML = name;    
  }

With these additions, the initial screen will now look like Figure 5.

Figure 5 Modified application using soria stylesheet - initial screen
Modified application using soria stylesheet - initial screen

After pressing the Add Page button twice, the screen would look like Figure 6.

Figure 6 Modified application using soria stylesheet - two pages added
Modified application using soria stylesheet - two pages added

Clicking on Page 1 though, will still leave Page 2 in the StackController. When a button in the Stack Controller is pressed, and that page is not already being displayed, it fires a selectChild event specific to the StackContainer. We need to subscribe to that event so that we can manipulate the Stack Controller when the user chooses to go back to a previous page in the breadcrumb trail. The code in Listing 14 does this, where selectedPage is the name of the method we will implement to handle this action.

Listing 14. Implementing the selectedPage method
dojo.subscribe("myStackContainer-selectChild","","selectedPage");

The selectedPage method will call a helper method named removeDownstream. removeDownstream gets the index of the page that was clicked on and removes all pages that are located after that given index as illustrated in Listing 15.

Listing 15. Using the removeDownstream method
function selectedPage(page)
 {
    removeDownstream(page.id) ;
 }

function removeDownstream( pageid )
{
    var widget = dijit.byId('myStackContainer');
    if(widget)
    {
      var children = widget.getChildren();
      var index = dojo.indexOf(children, dijit.byId(pageid)); 
        //get index of page that was selected from the breadcrumb trail
      index = index+1; //start removing from the page to the right
      while( children.length > index )
      {
        widget.removeChild(children[ index ]);
        index = index + 1;
       }
    }
}

With the inclusion of this code, going back to any previous page in the trail will remove all downstream pages from the breadcrumb trail as well as the StackContainer widget (this can easily be verified by downloading and running this code and inspecting the HTML source in Firebug).

To make it look more like a typical breadcrumb trail, CSS and DOM style manipulations can be used to style the breadcrumb nodes. Styling was included in the full example, which can be downloaded, but will not be explained here in detail. The final example would look and act as follows.

After adding four pages by clicking the Add Page button four times, the breadcrumb trail would look like Figure 7.

Figure 7. Modified application with breadcrumb trail - 4 pages
Modified application with breadcrumb trail - 4 pages

Clicking on Page 2 results in what is shown in Figure 8.

Figure 8. Modified application with breadcrumb trail – go back to page 2
Modified application with breadcrumb trail – go back to page 2

Tree - enabled/disabled nodes

Dojo's dijit library contains a Tree widget with basic tree functionality. There are methods that let you expand and collapse each node. However, we needed a tree that also provided the function of disabling nodes. There are several ways to do this: a completely new widget could be created from scratch, we can use Dojo's Tree widget as a base to create our own widget, or we can use what Dojo gives us to accomplish the task. We will walk you through accomplishing this with Dojo.

Each level of the Dojo tree consists of an icon and a label. Dojo provides a way to specify callback methods in the Tree's tag to get the label and icon style classes that should be used for each item in the tree. We will use this to style our disabled nodes so they look disabled. In this example, we will disable all non-leaf nodes, because it is common that non-leaf nodes are just used for categorizing the items that you want to perform tasks on.

First, define the CSS styles as shown in Listing 16.

Listing 16. Defining the CSS styles
.enabled
 {
        color: black;
        cursor: pointer;
 }
 
 .disabled
 {
        color:gray;
        cursor: not-allowed;
       background-color: transparent !important;
 }

When the node is clicked, it gets a new class attribute, dijitTreeLabelFocused, added to it. By using !important for the disabled style, we are sure it will use the transparent background color over the background color set by dijitTreeLabelFocused. This is how we make it look like the node is not really selected when clicked.

The callback method for the label style takes two parameters that are passed in by the Tree widget, the item that was clicked on and a Boolean parameter specifying whether that tree node is currently open. Our method will look like Listing 17.

Listing 17. Prototype's callback method
function getLabelClassForMyTree(
            /*dojo.data.Item*/ item, /*Boolean*/ opened)
 {
        if( item && item.children )
                  return "disabled";
      
        return "enabled";
 }

To make use of this method, we need to specify the getLabelClass attribute of our tree widget tag, as in Listing 18.

Listing 18. Specifying the getLabelClass attribute
<div dojoType="dijit.Tree" id="myTree" model="model"
        onClick="onSelectItem"
        getIconClass="getIconClassForMyTree"
        getLabelClass="getLabelClassForMyTree">
        </div>

Note that this styling only styles the label itself. So if you hover over the icon or the space to the right of the label, you will not see the disabled cursor style. To style the icon with a custom image and show it as disabled on mouse-over for non-leaf nodes, you can use the callback method shown in Listing 19. This function is then specified in the getIconClass attribute of the tree widget tag, as shown in Listing 18.

Listing 19. Styling an icon with a custom image
function getIconClassForMyTree(
/*dojo.data.Item*/ item, /*Boolean*/ opened)
     {
          var style = "";
          if( item )
          {
               if( item.children )
               {
                    //add icon image style
          style = opened ? "customFolderOpenedIcon" : 
"customFolderClosedIcon";
                         
                    //add disabled style
                    style = style + " disabled";
                     }
                    else
{   
                          //add icon image styling and enabled styling
                    style = "noteIcon enabled";
                }
          }
          return style;
}

The rest of the row will still not appear disabled on hover, though. Because Dojo only provides callbacks for the icon class and the label, there is not an easy way to disable the look for the rest of the row. One option is to iterate through all the nodes after the tree is built and add the disabled or enabled style to the row that encapsulates the label and icon. That DOM node can be accessed using the rowNode attribute of each tree node.

To disable the disabled nodes function, we will simply override the onClick to return for the disabled nodes. The method shown in Listing 20 can be used to ignore the onClick event by simply returning if a disabled node was clicked on.

Listing 20. Overriding the onClick
function onSelectItem(item) {            
       if(item.children) //disabled node, ignore click event
           return;

       if(item) {    
              // Display basic attribute values
              dojo.byId('uLabel').value = item ? 
                  store.getLabel(item) : "";
       }
}

The method shown in Listing 20 can further be modified as shown in Listing 21, so that the selected nodes keep their style when clicking on a disabled node after an enabled node was already selected.

Listing 21. Having selected nodes keep their style
function onSelectItem(item,node){
            
        if(item.children)
        {
            if(previouslySelectedNode)
                  //keep currently selected node highlighted
dojo.addClass(previouslySelectedNode,"dijitTreeLabelFocused");                
            return;
        }

        if(item){
            if(previouslySelectedNode)
                //in case we set that ourself, remove it 
dojo.removeClass(previouslySelectedNode,"dijitTreeLabelFocused"); 
                
            //keep track of enabled selected node
            previouslySelectedNode = node.labelNode;
                
            // Display basic attribute values
            dojo.byId('uLabel').value = item ? store.getLabel(item) : "";
        }
        }

To see this example working, download test_Tree_disable_style.html into your <dojo_home>/dijit/tests/ directory and run the files in your browser. The first file demonstrates the use of disabling nodes with only the callbacks, while the second one uses the rowNode to make the nodes look disabled.

Grid editor with different options per row

Today most Web 2.0 technologies provide widgets for complex tables called grids. These grid tables include the capability for column value selection, similar to an HTML SELECT. However, the limitations of this column selection are that they are static and that there is only one set of options for the entire table (for example, all rows for 1st column have the same options). Figure 9 shows a simple Dojo grid application where all the options for the 1st column are the same for each row (normal, note and important).

Figure 9 Dojo grid editor select - standard
Dojo grid editor select - standard

We needed dynamic options that can be different for every row of the grid table, so we must do a few customizations. There are a few different ways this problem can be solved. Minimally, you can override the formatter for the grid Select editor to return option information for the selected row. After that is done, however, the options and values for the column in the grid's model have no meaning. You must be careful about correlating the rows of the grid to any type of model data outside the grid, because the rows in a grid can be reordered at any time (for example, using a sort action). If you keep your extended model information outside the actual grid's model, the row you modified in the grid model may not be the row you think it is in your data outside the grid. To avoid this type of mismatch, we found it best to keep additional, non-visual information inside the grid model itself, such as the options and values for that row. The formatter override can then return that extended model information. Snippets are shown in Listing 22 through Listing 26.

In all, there are 3 basic steps to accomplish this:

  1. Extend the grid's data model with additional option information per row
  2. Create a custom grid editor, specializing the format method
  3. Hook in use of the custom selector into the grid layout

Extend the grid's data model with the additional information of the options per row

Listing 22 shows the grid data before the modification for different options/row.

Listing 22. Grid data before modification
var data = [ 
    ["normal",   "message-1","Priority-1"],
    ["note",     "message-2","Priority-2"],
    ["important","message-3","Priority-3"]
 ];

Listing 23 shows the grid data after the modification for different options/row in column 4 of the data array.

Listing 23. Modifying for different options/row
var data = [ 
    ["normal-1",   "message-1","Priority-1",["normal-1","note-1","important-1"]],
     "note-2",     "message-2","Priority-2",["normal-2","note-2","important-2"] ],
    ["important-3","message-3","Priority-3",["normal-3","note-3","important-3"] ]
];

Create a custom grid select editor to return the options for a selected row

We do this by creating a subclass of dojox.grid.editors.Select and specializing the format method. We set the options of the editor to be that of the current row, and let the parent do the actual formatting of this information. This results in different options for each row. Note that in this example both the options and values are the same. We could make them different by adding another column for values in the grid model data. The example in Listing 24 illustrates this with the creation of a new editor MySelect.

Listing 24. Making options and values different
dojo.declare("com.ibm.editor.MySelect", [dojox.grid.editors.Select], {
     format: function(inDatum, inRowIndex){
           var row = this.cell.grid.model.data[inRowIndex]; 
            this.options = this.values = row[OPTION_INDEX];   // could have diff values
            return this.inherited("format",arguments);        // return HTML select stmt
      }, 
});

Hook in use of the custom selector into the grid layout

The example in Listing 25 shows the grid layout using a standard dojo grid editor. Note that the options and values are contained in the grid layout because they are the same for all the rows in the table.

Listing 25. Grid layout using a standard dojo grid editor
gridLayout = [{
         type: 'dojox.GridRowView', width: '20px'
         },{
        defaultCell: { width: 8, editor: dojox.grid.editors.Input, styles: 'text-
        align: right;' },
        rows: [[
                {name: 'Source Schema: Current',styles: '',    width: '40%',    editor: 
               dojox.grid.editors.Select, options: ["normal", "note", "important"], 
               values: [0, 1, 2], formatter: function(inDatum) { return 
               this.options[inDatum]} },
               {name: 'Mapping',    styles: '',    width: '20%'}, 
               {name: 'Target Schema: ',    styles: '',    width: '40%'}
        ]]
}];

The example in Listing 26 shows how a grid layout can use a custom grid editor. Note that we do not need to define the options for the entire table, because they are different for every row.

Listing 26. Grid layout using a custom grid editor
gridLayout = [{
      type: 'dojox.GridRowView', width: '20px'
      },{
      defaultCell: { width: 8, editor: dojox.grid.editors.Input, styles: 
                               'text-align: right;' },
      rows: [[
            {name: 'Source Schema: Current',styles: '', width: '40%', 
              editor: com.ibm.editor.MySelect }
            },
            {name: 'Mapping',	styles: '',	width: '20%'}, 
            {name: 'Target Schema: ',	styles: '',	width: '40%'}

      ]]
}];

Figure 10 shows the final outcome of the Dojo widget customization for the grid table select editor, with different options per row. Note that the selections for the last row of normal-3, note-3, and important-3 are different from the selected options for the other rows of normal-1 and note-2.

Figure 10 Dojo grid editor select - customized
Dojo grid editor select - customized

The Dojox grid sample <dojo_home>/dojox/grid/tests/test_edit.html was modified to support different options per row. To see these examples working, download test_edit_simple.html and test_edit_diff_options.html into your <dojo_home>/dojox/grid/tests directory and run the files in your browser. The first file demonstrates normal grid table operation with the same options for all rows in a very simple application, while the second illustrates the use of a custom grid editor select.

Tree – editable with style indication

Our application allows users to do some minimal editing on a graphical tree. Users could remove (drop) a node in the tree and optionally its children, rename the label on a node, or undo/clear any editing done on a node. Editing of a node consisted of two visual steps:

  1. Define and apply a style to a selected node based on the edit action (for example, change color, strikethrough text, and so on).
  2. For some edit actions, change the node (for example, change label, collapse node, and so on)

To do this, we created a tree as normal, with the creation of the store, model (not shown) and tree in HTML template.

Listing 27. Creating a tree
<div dojoType="dijit.Tree" 
        id="current_tree_${id}" 
        class="container"
        model="current_tree_model_${id}"
        labelAttr="name"
        childrenAttr="children, items" 
        dojoAttachEvent="onClick:_onClickSelect"
        getLabel="ourCustomLabel"
        getLabelClass="getOurLabelClass"
        getIconClass="getOurIconClass">            
</div>

To the tree we added a right context menu with the edit options. The connect in Listing 28 attaches the menu to the tree.

Listing 28. Attaching the right context menu
<script type="dojo/connect">
        var menu = dijit.byId("edit_tree_menu_${id}");                
        menu.bindDomNode(this.domNode);
</script>

Note: This example assumes that the user selects the node of interest using their left mouse button.

The definition of the menu in the HTML template can be as in Listing 29.

Listing 29. Definition of the menu
<ul dojoType="dijit.Menu" 
id="edit_tree_menu_${id}" 
style="display: none;" 
iconClass="dijitMenuItemIcon">
        
        <li id="edit_clear_menu_${id}"
        dojoType="dijit.MenuItem" 
        dojoAttachEvent="onClick:_onClickClear" 
        iconClass="dijitEditorIcon dijitEditorIconUndo" 
        disabled="true">
        Clear Edit</li>
        
        <li dojoType="dijit.MenuItem" 
        iconClass="dijitEditorIcon dijitEditorIconWikiword" 
        disabled="true" >
        Create Expression</li>
        
        <li id="edit_drop_menu_${id}" 
        dojoType="dijit.MenuItem" 
        dojoAttachEvent="onClick:_onClickDrop" 
        iconClass="dijitEditorIcon dijitEditorIconDelete" 
        disabled="false" >
        Drop</li>                    
        
        <li dojoType="dijit.MenuItem" 
        dojoAttachEvent="onClick:_onClickRename" 
        iconClass="dijitEditorIcon dijitEditorIconCreateLink">
        Rename</li>
        
        <li id="edit_menu_remove_${id}" 
        dojoType="dijit.MenuItem" 
        dojoAttachEvent="onClick:_onClickRemove" 
        iconClass="dijitEditorIcon dijitEditorIconCut">
        Remove</li>                    
    
</ul>

We also added to the tree, for editable nodes, a trick we found in one of the Dojo forums.

Listing 30. Disable the keypress using an override
<script type="dojo/method" event="_onKeyPress">
        //disable keypress via override, so we can use inline editor on nodes
</script>

Finally, based on the option chosen, we created an appropriate handler for the edit action that would apply the style and possibly change the node.

A snippet of the remove node handler is shown below in Listing 31. (The user selects the node prior to the handler being called. Node selection sets the this.item.) The visual actions that occur are that a style is applied to indicate the node has been removed (red strikethrough), and the undo menu item is now enabled for this node.

Listing 31. Snippet of the remove node
/**
* User clicked on remove button or right context menu
* @param {Object} e
*/
_onClickRemove: function (/*Event*/ e) {
        . . .
        this.store.setValue(this.item,"edit","editRemove"); //add remove style
        dijit.byId("edit_clear_menu_"+this.id).setDisabled(false);//enable undo
        // process remove, e.g. report to server . . .
},

The remove tree node style is defined as shown below in Listing 32.

Listing 32. Defining the remove tree node style
#edit .editRemove
{
        color: red;
        text-decoration: line-through;
}

A snippet of the rename node handler is shown in Listing 33. It lets a user rename a label on a node in a tree.

Listing 33. Snippet of rename node handler
/**
 * User clicked on rename button or right context menu
 * Bring up editor for tree node name
 * @param {Object} e
 */
    _onClickRename: function (/*Event*/ e) {
        . . .
        this.store.setValue(this.item,"edit","editRename"); //add rename style
        
        var labelNode = tree._itemNodeMap[ this.item.ID ].labelNode;
        var txt = document.createElement('span');
        txt.innerHTML = labelNode.innerHTML;
        labelNode.innerHTML = "";
        labelNode.appendChild(txt);    
        
        var editor = new dijit.InlineEditBox({
            autoSave: true, // false=save/cancel button shown in html
            onChange: function(value) {
                this.setDisabled(true); // done, disable editing
                // process rename, e.g. report to server . . .
            },
            renderAsHtml: true,
            width: "150px"
        }, txt);
        editor.setDisabled(false); //enable editing
        editor._edit();
        
        dijit.byId("edit_clear_menu_"+this.id).setDisabled(false);//enable undo
        . . .
},

The rename tree node style is defined as shown in Listing 34.

Listing 34. Rename tree node style
#edit .editRename
{
        color: blue;
}

Figure 11 shows what the tree node editor looked like, for the node rename action.

Figure 11. Editing a tree node - rename
Editing a tree node - rename

The end result looked something like Figure 12. A right context menu is shown with the different editing options that are available. The tree nodes that were removed or dropped are styled with red-strikethrough, and the tree nodes that were renamed are shown in blue.

Figure 12. Right context tree menu and edited nodes with styling
Right context tree menu and edited nodes with styling

Tree - with right context menus and right click selection

In the previous section, it was required to left click on the node to store the selected node in a variable that could be used when an option from the right-click context menu was chosen. This was done because the right-click context menu is bound to the entire tree as a whole, as opposed to one for each node of the tree. To get specific behavior depending on which node is right-clicked, you could connect to the _openMyself method of the menu as shown in Listing 35.

Listing 35. Connecting to the _openMyself method
<div dojoType="dijit.Tree" … >

        <script type="dojo/connect">
            var menu = dijit.byId("tree_menu"); 
            
            dojo.connect(menu, "_openMyself", this, function(e){

                // get the tree node that was the source of this open event
                var tn = dijit.getEnclosingWidget(e.target);

                // if this tree node does not have any children,
        // disable all of the menu items
        // note: these lines are not related to the above section, just 
        // shown to illustrate how menu items would be disabled 
        // depending on which node was clicked.
                menu.getChildren().forEach(
            function(i){ 
                i.setDisabled(!tn.item.children);
            });

                // IMPLEMENT CUSTOM MENU BEHAVIOR HERE
            });
        </script>
</div>

The tn variable can be stored as a global variable that can be accessed after the menu options are clicked. This way, the user is not forced to first left-click on the node that the menu action should be applied to.


Common mistakes

This section explains a number of common mistakes we encountered. These include:

  • More than one global JavaScript method with the same name. No errors are reported, and you don't know which method will be the one called.
  • Not enough object isolation, therefore, you can use too many global functions. This makes the code harder to maintain and can also lead to the common mistake mentioned above.
  • Too much log/trace or too little filtering of log messages causes information overload in the logs.
  • Inefficient error handling makes debugging difficult. There needs to be more exception handling and more thought put into error paths.
  • Misuse of the scope for object attributes (global versus local).
  • Not using dojo.hitch for callbacks where needed. This can cause late detection that the callback was not running in the correct object context (for example, incorrect object instance variable values).

Testing the Dojo app

Local and remote

Web 2.0 applications use RESTful services to get their information. REST is the acronym for REpresentational State Transfer. It is the architectural model on which the World Wide Web is based. Principles of REST include: a resource-centric approach, all relevant resources are addressable using URIs, uniform access using HTTP – GET, POST, PUT, DELETE, and so on. We wanted to be able to test in a disconnected, local mode. Therefore, we saved the RESTful service results into test files, and created an abstraction method to obtain the URL for a given REST call.

Automatic detection and switch example

The abstraction determined whether the access was local or remote using the JavaScript value of document.location.protocol, and returned the appropriate URL. This was accomplished by placing the local test files into a directory structure similar to the server URI. For example, for the URI: myui/catalog/types on server http://<server>:<port>, we simply placed the URI under a local base test directory <local-test-base-dir>/myui/catalog/types. Only the base portion of the URI changes, depending on whether the access is local or remote. We set a configuration object BASE_URL attribute as shown below.

config.BASE_URL = (document.location.protocol=="file:") ? "data" : "..";

Conclusion

In general, we found that the learning curve to start Dojo development was minimal, with help from a large set of samples available in the Dojo toolkit and from the community surrounding Dojo, along with a plethora of JavaScript information on the Internet. But as Java developers, we missed strong IDE support, good API documentation, lack of strong typing, different runtime behavior per Web browser differences, and compile time checking with JavaScript development. Learning how to do customizations were sometimes painful, and often a trial and error process. However, after we learned the processes, they were repeatable. The end result of our prototype was a professional-looking application, with all the performance benefits expected from an Ajax-enabled Web application.


Download

DescriptionNameSize
Source codeDownload_Samples.zip9KB

Resources

Learn

  • You can find help from other Dojo developers at the main Dojo site.
  • Read the article "Dojo concepts for Java developers" (developerWorks, October 2008) to bridge the gap from Java code to Dojo so that you can get up to speed quickly and use the toolkit when developing your applications.
  • Learn more on using the MVC design pattern with JavaScript. The example in the article uses a down-level Dojo, but it is still applicable with minor modifications.
  • Take a look at the CSS2 Specification.

Get products and technologies

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Web development on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development
ArticleID=357652
ArticleTitle=Writing a custom Dojo application
publish-date=12092008