Dojo is a powerful Javascript framework, useful for building browser-based Rich User Interface Applications (RIA). I had some annoying times recently getting a simple Dojo widget to display correctly, so this article captures a few lessons learned in taming that elusive widget. 

In passing I’ll mention that this blog does have a few allusive elements, referencing fine British Acoustic music. The blog title itself refers to the Summer Before the War, a lovely Huw Williams song, and this posting refers to the chorus of  Mike Silver’s beautifiul How Many Rivers.

Project contents

The Javascript I’m dealing with is organised in my Web project as shown here. Dojo itself is in the folder dojoroot and my code is in djna/main.js. I’m also using one data file in the folder djna/datastores.

image

Additionally I have a single HTML file, in my WebContent, which references that javascript. There are three sub-sections, described next.

Note: This article builds from my flawed-original code to the finally working version. The first version you see of a code snippet might need a little enhancement …

Loading Dojo

The first sections sets up a Dojo theme and loads Dojo itself:

  <html>
  <head>
    <title>A Device UI</title>  
    <style type="text/css">
      @import "../dojoroot/dijit/themes/tundra/tundra.css";
    </style>   
    <script type="text/javascript" src="dojoroot/dojo/dojo.js">
    </script>

Initialising the application

Then this code loads the dojo module with my application code in it:

   <script type="text/javascript" > 
      dojo.registerModulePath("djna", "../../djna");
      dojo.require("djna.main");
      dojo.addOnLoad(function(){
          djna.main.startup();                    
      });

Splash screen

As there is a little initialsation to do on applicaiton startup I display a reassuring message, in real-life this might be a nice splash screen.

</head>
   <body class="tundra" id="mainbody">
      <div id="loading"><P>SPLASH</P></div>  
   </body>
  </html>

Javascript Application

My intent is to display a simple grid with some data from the javascript in my datastore. I’m using the Dojo module pattern, where an anonymous function defines the public methods of a “Class”. So my Javascript is also in three parts: the “Class” definition, a main method which sets up the overall display, and utility method that creates the grid.

Class definition

My module outline looks like this, begining with the declaration of the module, which matches the name djna/main.js location of the Javascript file and which was referenced by the 

     dojo.require("djna.main");

statement in the HTML script.

dojo.provide("djna.main");

dojo.require("dojo.parser");
dojo.require("dojo.data.ItemFileReadStore");
dojo.require("dojox.grid.DataGrid");
dojo.require("dijit.layout.BorderContainer");

(function(){

     // main goes here

     // grid creationg goes here

})();

Modules referenced by the two metho implementation are made available by the dojo.require statements.

Main entry point

The main function has the responsibility to create a BorderContainer and request the creation of the grid.

       djna.main.startup= function(args) {                
            
            var grid = djna.main.makeGrid();            
                 
            var appContainer = new dijit.layout.BorderContainer({
              style: "width: 230px; height: 320px;  border:solid 1px",
              design: "headline"
            });

            dojo._destroyElement(dojo.byId("loading"));           
            dojo.place(appContainer.domNode, dojo.body(), "first");
            appContainer.addChild(grid);          
            appContainer.startup();                              
      };

The main then positions the grid in the Border Container and replaces the Splash screen with Border Container.

Grid Creation

The grid creation method sets up a data store and layout and then creates the grid

      djna.main.makeGrid = function(){
         var theStore = new dojo.data.ItemFileReadStore(
                  {url:"djna/datastores/dataonly001.json"});
          var layout = {cels: [                         {
                field: ‘custId’, name: ‘Customer Id’                     
            },{
                field: ‘name’, name: ‘Name’              
            },
            {
                field: ‘address’, name: ‘Address’              
            },
            {
                field: ‘postcode’, name: ‘Postcode’              
            }
            ]};
         var grid = new dojox.grid.DataGrid({
                id:"grid",
                store : theStore,
                clientSort : "true",
                structure : layout,
                region: "center"
            });
      };

 

Problems(1) – Drawing a Blank

This all looked (to my eyes) pretty plausible. I loaded the page and got this:

image

An empty box. And no error messages. I was running in Firefox with Firebug enabled. The console showed nothing untoward. So let’s use FireBug’s debugging capabilities. I bring up the Script tab in Firebug, and click the left margin to set breakpoints at the loading of my module:

image

I can now step to the call to djna.main.startup and step inside. Unfortunately, what I see is less than helpful:

image 

The debugging difficuty is caused by Dojo’s implementaiton of modules, using evals. However we can enable debugging.

Enabling Module Debugging

We adjust the Dojo configuration like this:

    <script type="text/javascript" src="dojoroot/dojo/dojo.js"
         djConfig="parseOnLoad: true, debugAtAllCosts: true">
    </script>

Now when we step into the module we can see our code:

image

and as we step through the code to line

     appContainer.addChild(grid);

we see this error:

image

Which suggests that a variable has not been intialised correctly. [The rather hard to understand variable name _17c  is shown because we are not using adevelopment version of Dojo.] A quick examination of the Watch tab shows that the grid variable is undefined:

image

despite the call to

    var grid = djna.main.makeGrid();

and so, we inspect the makeGrid() method more carefully and discover that a line of code is missing:

     var grid = new dojox.grid.DataGrid({
            id:"grid",
            store : theStore,
            clientSort : "true",
            structure : layout,
            region: "center"
        });
    return grid;  // <==== need this
  };

Now when we re-run we don’t see the “undefined” error, but unfortunately, we still see an empty page.

Problem(2) – Lay Me Low

The cause of this problem is a tiny type in the layout. I don’t know how I could have solve this problem efficiently, but in the end I obtained a working example and gradually modified it to match my broken code until I spotted the mistake.

     var layout = {cels: [                         {
                field: ‘custId’, name: ‘Customer Id’                     
            },{

The one letter change to:

     var layout = {cells: [                         {
                field: ‘custId’, name: ‘Customer Id’                     
            },{

gave some progress but yielded another problem …

Problem(3) – Just Got No Style

I’ll admit that solving this one took me an unreasonable amount of time. In retrospect when you see this:

image

it probably should be obvious that this is a style-sheet problem. However things were made a little more difficult by the fact that I had two errors.

Style sheet path

First, a simple path error in the html file:

   <style type="text/css">
      @import "../dojoroot/dijit/themes/tundra/tundra.css";  
    </style>

should be

    <style type="text/css">
      @import "dojoroot/dijit/themes/tundra/tundra.css";  
    </style>

That changes things slightly, but still we have a very similar bad screen:

image 

So there’s a further change:

Extra style sheet

It’s not obvious to a Dojo novice but you need a specific extra style sheet:

<style type="text/css">
@import "dojoroot/dijit/themes/tundra/tundra.css";
@import "dojoroot/dojox/grid/resources/tundraGrid.css";  

</style>

With that in place we can now see the grid taking shape.

image

But strangely, loading never completes, we never see any data. The final problem was probably the most annoying of the lot …

Problem(4) – Too Much Information

I suppose that if I had had the Dojo development source available it might have been possible to figure out what was happening by step-wise debugging. I took the approach of downloading a working example and comparing it with mine. Eventually I saw the error in my data file:

{
    identifier: "name",
    label: "name",
    items: [{
      custId: "123",   
      name: "Arnold Arkwright",
      address: "1, A Avenue; Accrington",
      postcode: "AA1 1AA"
    },{
        custId: "234",   
        name: "Bingley Bradford",
        address: "2, B Bridge; Batley",
        postcode: "BB2 2BB"
    }]
};

Can you see it? Yes, it’s that trailing semi-colon! Remove that and we get:

image

Conclusion

Well, I got there in the end and the journey taught me quite a lot. Seems like the tools such as Firebug do help quite a lot, but there are still quite a few gotchas.

Advertisements

Introduction

In the previous posting I used the Jackson JSON Processor libraries to parse and format JSON strings. The Java representation of the JSON String being a HashMap whose contents are named attributes some of which are themselves HashMaps, or indeed arrays of HashMaps. This is a very flexible representation, but writing client code that works with the data is (to my eyes) quite cumbersome:

        HashMap<String,Object> payload
                    = mapper.readValue(from, typeRef);

        Boolean success = (Boolean) payload.get("success");
        System.out.println("success " + success);

        ArrayList<HashMap<String,Object>> artists
              = (ArrayList<HashMap<String,Object>>)
                          payload.get("artists");
        System.out.println("artist count " + artists.size());

        for (HashMap<String,Object> oneArtist: artists){
            System.out.println("artist name " + oneArtist.get("name"));
        }

We see access to attributes such as “success” and “artists” and the requirement for the coder to cast the values to the appropriate type. Note that any errors in either the names of the attributes or their types will not be discovered until runtime.

Coming from an Object Oriented background my personal preference would be to have Object representations of attributes. I would like to have objects such as Artist and Albumn – I feel uncomfortable having

    map.get(“interestingField”)

code scattered across my business logic. (Your tastes may differ!) This article shows how to use Jackson to populate such objects, and in particular how to customize the mapping to Java objects, which is useful in more complex cases.

Sparse Object

Let’s begin with a simple object representing the payload.

    package djna.jackson.eg;

    public class CatalogueResponse {
        public boolean success;
    }

I’m creating this object on the basis of my understanding of the JSON string to be parsed. [For simplicity I’ve made the attribute public, in real life it would be private with a suitable accessor method.]

   {
      "success": true,
      "artists": [
         {
            "name": "Ashley Hutchings",

and to keep things simple I just consider one attribute: “success”.

I adjust my application to specify that CatalogueResponse is the class the parser should produce.

    CatalogueResponse catalogue
             = mapper.readValue(from, CatalogueResponse.class);
    System.out.println("Catalogue " + catalogue);

This code runs but produces the exception:

   Exception org.codehaus.jackson.map.JsonMappingException:
   Unrecognized field "artists"

At development time this is potentially a helpful error message, telling us that our payload object does not match all the fields in the JSON String. When deployed in a production environment we would probably prefer to decouple the client from upgrades to the server that produce new attributes. We’d request that such unmapped fields are silently ignored. This is specified using another configuration feature:

    mapper.getDeserializationConfig().set(    

    DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,
            false

    );

Running the application now produces the expected (if minimal) output:

    Catalogue djna.jackson.eg.CatalogueResponse@3d0a3d0a
    {
       "success" : true
    }
    Complete.

Objects, Names and Values

We can extend our object to allow access to the artist array. A simple possibility is to add that as an array of Objects:

    public class CatalogueResponse {
        public boolean success;    
        public Object[] artists;
    }

This works, and can be convenient if we have no specific interest in artists, we just get an array of HashMaps as before. More likely we want to create suitable classes for artist and albumnre

    public class Artist {
        public String name;
        public Albumn albumns[];
    }

   public class Albumn {
       public String title;
       public Object[] properties;
   }

and our response class can now refer to Artists

  public class CatalogueResponse {
      public boolean success;    
      public Artist[] artists;
  }

The creation of these payload classes is a little tedious and error-prone but is up-front work producing classes that can be re-used many times.

Custom Mapping

Looking at the Albumn class we see that the “properties” attribute is representing a set of descriptive fields that may be attached to any albumn. This approach gives a flexible data structure that can be extended in the future.

       "properties": [
                     { "name": "artist",
                       "value" : "Michael Chapman"
                     },
                     { "name": "genre",
                       "value" : "instrumental"
                     },
                     { "name": "id",
                       "value" : "BP315CD"
                     }                 

The default mapping produced by Jackson would be an array of HashMaps, each HashMap containing a “name” entry and a “value” entry. It would be much more convenient from the client developer’s point of view to represent the properties  by a single HashMap with keys such as “artist”, “genre” etc. In effect transforming the above structure to this:

      "properties": [
                     { "artist" : "Michael Chapman" },
                     { "genre: "instrumental" },
                     { "id : "BP315CD" } 

This is achieved by using custom serialize/deserialize code.        

Serialization annotation

The first step is to annotate the payload class to specify that we are using a custom mapping.

    public class Albumn {
       public String title;
       @JsonSerialize(using=PropertyMapSerializer.class)
       @JsonDeserialize(using=PropertyMapDeserializer.class)
       public Map<String, String> properties;
    }

Here I’m annotating the attribute directly, specifying the JsonSerialize and JsonDeserialize information. I would usually have a private attribute with getter and setter methods and in which case I would annotate the getter with the JsonSerialize and the setter with JsonDeserialize.

We now need to write the custom Serialize classes as referenced in the annotations.

The Deserializer Class

We create a class

    package djna.jackson.eg;

    import java.io.IOException;
    import java.util.HashMap;
    import java.util.Map;

    import org.codehaus.jackson.JsonParser;
    import org.codehaus.jackson.JsonProcessingException;
    import org.codehaus.jackson.map.DeserializationContext;
    import org.codehaus.jackson.map.JsonDeserializer;
    import org.codehaus.jackson.map.ObjectMapper;
    import org.codehaus.jackson.type.TypeReference;

    public class PropertyMapDeserializer
         extends JsonDeserializer<
              Map<String, String>
        >
   {

The key point here is that this class extends the Jackson class JsonDeserialize, which is specialised with the type corresponding to our property field. So

     extends JsonDeserializer<
              Map<String, String>
        >
matches

       public Map<String, String> properties;

The class then also must implement the deserializer method

        @Override
       public Map<String, String> deserialize(
            JsonParser parser, DeserializationContext context)
            throws IOException,
                JsonProcessingException {

The purpose of this method is to read data from the parser and construct and return the value that will be placed into our property.

Implementing the Deserializer

The implementation is in three steps: initialisation of some variables, reading the raw data and constructing the property hash map.

Initialisation

We initialise two variables

    Map<String, String>  retMap =
              new HashMap<String, String>();

    TypeReference<HashMap<String,String>[]>  typeRef
            = new TypeReference<
               HashMap<String,String>[]
             >() {};

The retMap is the return value we are populating. The typeRef is going to be used when reading the JSON string to specify the type of the raw data to be produced. [This is a common Jackson idiom that I used in the previous article.]

Reading Raw Data

We read the raw data into a natural representation of the JSON string using a Jackson ObjectMapper.

       ObjectMapper mapper = new ObjectMapper();
       HashMap<String, String>[] maps
           = mapper.readValue(parser, typeRef);

In this case we’re reading an array of HashMaps. Each HashMap in the array contains the “name” and “value” corresponding to a property:

 

       "properties": [
                     { "name": "artist",
                       "value" : "Michael Chapman"
                     },

We then transfer this data into the single hash map we are creating

Populate Returned Hash Map

We simply iterate the array and extract the name and value. (Yes, there should be some error handling.)

    for (HashMap<String, String> oneMap : maps){
             String name = oneMap.get("name");
             String value = oneMap.get("value");
             retMap.put(name, value);   
       }       
       return retMap;

 

Using the HashMap

The client code can now access the Albumn properties in quite a natural way:

    albumn.properties.get(“genre”);

Next we need to provide the inverse functionality, to serialize to a JSON string.

Implementing the Serializer

The serialization code is also quite simple. First we create the class, which must extend an appropriate specialisation of the Jackson JsonSerializer

    package djna.jackson.eg;

    import java.io.IOException;
    import java.util.HashMap;
    import java.util.Map;

    import org.codehaus.jackson.JsonGenerator;
    import org.codehaus.jackson.JsonParser;
    import org.codehaus.jackson.JsonProcessingException;
    import org.codehaus.jackson.map.JsonSerializer;
    import org.codehaus.jackson.map.ObjectMapper;
    import org.codehaus.jackson.map.SerializerProvider;

    public class PropertyMapSerializer extends JsonSerializer<
              Map<String, String>
        >
    {

Note that the specialisation matches the type of the field we are serialising.

   @JsonSerialize(using=PropertyMapSerializer.class)
   @JsonDeserialize(using=PropertyMapDeserializer.class)
   public Map<String, String> properties;

We then need to implement the serialisation method:

@Override
    public void serialize(Map<String, String> data,
            JsonGenerator generator,
            SerializerProvider provider) throws IOException,
            JsonProcessingException {

The requirement is to write to the JsonGenerator an appropriate representation of the Map<String, String> data received. We could do this by constructing an array of HashMaps, but instead I decided to create an array of a simple Property class. I think that this make for more readable code.

The Property Class

The class is a simple representation of the property data with a convenient constructor.

    public class Property {
       
       public Property(String newName, String newValue){
          name = newName;
          value = newValue;
       }
       public String name;
       public String value;

    }

The payload we are writing is a representation of an array of these objects.

Preparing the Payload

The payload is obtained from the data paramter passed by Jackson to our serialisation method

        Property values[] = new Property[data.size()];
        int i = 0;
        for ( String key: data.keySet() ){
            values[i++] = new Property(key, data.get(key));
        }

we now have a payload to write:

Writing the payload

Once again we construct a mapper object and use it to write our property array to the generator, which Jackson supplied to our serlialisation method.

       ObjectMapper mapper = new ObjectMapper();
        mapper.writeValue(generator, values);

And that completes the custom mapping.

Summary

Although it has taken a while to explain each step in preparing a mapping between Java Classes and JSON representations I wrote the code with very little effort, the Jackson programming model seems very effective.

One other note: You might feel that the custom mapping example I chose is somewhat contrived. In fact it is a simplified form of a real-life system I have been working with. The capability to provide a suitable custom mapping of the JSON payload is extremely useful in my real-world example.