ColdFusion Driven Dojo - Part 2: Stores, Structs and Queries

Using ColdFusion to Data-Drive Dojo

One of the biggest contributions that ColdFusion (or any application server for that matter) offers Dojo is data access and manipulation. Dojo is a client-side technology and ColdFusion is a server-side technology - and where does data live? On the server.

So how do you get the data from the server shoehorned into a format that works well with Dojo? You just cram all your data into a JavaScript object and you're good to go.
Well - it's almost that easy...

Table of Contents
Assets

Stores

Many of the advanced Dojo UI controls (the dijit.form.* packages) are data driven, and this is predominantly done through use of a Dojo class called a Store (the dojo.data.* packages). There are two common classes, ItemFileReadStore and ItemFileWriteStore, of note (which I'll refer to hereafter collectively as ItemFileXStore classes).
The ItemFileReadStore is pretty much a read-only affair which works well for immutable data presentation. If you're going to do client-side editing you'll need to use an ItemFileWriteStore, an ItemFileReadStore subclass. These classes offer read and write methods a basic record concept, wherein the record can contain an arbitrary number of fields.

More, with a few basic exceptions, the structure of each record does not need to match the structure of any other record. Obviously the more the differences the greater the difficulty in making the whole thing work properly, but a great deal of latitude is possible.

Populating ItemFileXStore

ItemFileXStore typically receive their priming data in JSON. The structure of the JSON content is important; it will follow the same format as this block:


    {
        "identifier": "id",
        "label": "name",
        "items": [
            { "id":"1", "name":"entry 1" },
            { "id":"2", "name":"entry 2" }
        ]
    }
    
Herein, the JSON block has three fields:
  • identifier
  • label
  • items
The first two fields, identifier and label, dictate two required fields that must exist in the constituents of the items field. Where identifier and label are strings, the items field is an array of objects. While each object in items can contain any number of fields, each object must contain fields that match the values of the identifier and label fields.
The block describes this exactly.
In this instance, the identifier field states that each entry in items must have a field called "id". The label field states that each entry in items must have a field called "name".
And so it does.

Using Structs

The JSON formatted structure used by Dojo to populate ItemFileXStore instances translates neatly into a CFML structure: three keys, two string values and one array (of structures).
The prior JSON block can be created with the following CFML snippet:

example snippet


    <cfset data = structNew()>
    <cfset data.identifier = "id">
    <cfset data.label = "name">
    <cfset data.items = arrayNew(1)>
    <cfloop index="idx" from="1" to="2">
        <cfset data.items[idx] = structNew()>
        <cfset data.items[idx].id = idx>
        <cfset data.items[idx].name = "entry #idx#">
    </cfloop>
    

Using Queries

It doesn't take a great deal of ColdFusion experience to see how this could be pushed a little bit further and the loop that populates the items field could be driven by a query.
Assuming a query object with two columns, id and name, and two rows, the prior CFML snippet could be altered thus:

example snippet


    <cfset data = structNew()>
    <cfset data.identifier = "id">
    <cfset data.label = "name">
    <cfset data.items = arrayNew(1)>
    <cfloop query="jsonsnippet">
        <cfset data.items[jsonsnippet.currentRow] = structNew()>
        <cfset data.items[jsonsnippet.currentRow].id = jsonsnippet.id>
        <cfset data.items[jsonsnippet.currentRow].name = jsonsnippet.name>
    </cfloop>
    

Making Light Work

While all of this conversion work is simple enough, it does make for a bit of typing. What's the point of being a developer if you can't package it all up to save some time and trouble?
To that end, I packaged up the whole thing into a UDF called dojoStoreFormat. The function takes three arguments; a string which is the name of the identifier field, a string which is the name of the label field, and finally either a query or an array of structures.
This UDF is available in a zip file from this blog along with some MXUnit tests.

Dynamically Generating the Store

The more common example one finds when learning about populating ItemFileXStore instances are usually tag-based and involve an external JSON file. The JSON file is either addressed directly, and thus static, or it resolves to a dynamic page.
Something like this (a snippet from a larger code file):

example snippet


    <div dojoType="dojo.data.ItemFileReadStore" jsId="peopleStore"
        url="cfwtdirectory.json" style="visibility: hidden;">

    </div>
    

Show Code


    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
        <head>
            <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
            <title>Insert title here</title>
    
            <style type="text/css">
                @import "http://localhost:8500/scripts/dojo/1.1.0/dijit/themes/tundra/tundra.css";
                @import "http://localhost:8500/scripts/dojo/1.1.0/dojo/resources/dojo.css";
            </style>
            
            <script type="text/javascript"
                src="http://localhost:8500/scripts/dojo/1.1.0/dojo/dojo.js"
                djConfig="parseOnLoad:true, usePlainJson:true">

            </script>
            
            <script>
                dojo.require("dojo.parser");
                dojo.require("dojo.data.ItemFileReadStore");
                dojo.require("dijit.form.FilteringSelect");
            </script>
        </head>
        <body class="tundra">
        
            <div dojoType="dojo.data.ItemFileReadStore" jsId="peopleStore"
                url="cfwtdirectory.json" style="visibility: hidden;">

            </div>
            
            <select dojoType="dijit.form.FilteringSelect" jsId="emailSelect"
                store="peopleStore" style="width:200px;">

                <script type="dojo/method" event="onChange">
                if(this.item)
                {
                    document.getElementById("emailaddress").innerHTML = this.item.email;
                }
            </script>
            </select>
            <br/><br/>
            <div id="emailDisplay">
                <span style="font-weight: bold;">Email Address: </span>
                <span id="emailaddress"></span>
            </div>
        </body>
    </html>
    

This technique is far from optimal:

  • Static files are - well, static
  • Dynamically generated pages require additional HTTP requests
The better solution, in most cases, would be to embed the JSON content directly into the page. To accomplish this the ItemFileXStore needs to be created with JavaScript. So the <div> block that creates the ItemFileXStore instance, as the snippet above, gets replaced with a ColdFusion block and a JavaScript block that looks like this:

example snippet

    <cfsilent>
        <cffile action="read" file="#expandPath('./cfwtdirectory.wddx')#" variable="packet">
        <cfwddx action="wddx2cfml" input="#packet#" output="populatedDataQuery">
        <cfset jsonPacket = dojoStoreFormat("id", "name", populatedDataQuery)>    
    </cfsilent>

    <script type="text/javascript">
        var jsonPacket = '<cfoutput>#jsonPacket#</cfoutput>';
        var peopleStoreData = dojo.fromJson(jsonPacket);
        
        var peopleStore = new dojo.data.ItemFileReadStore({data: peopleStoreData});
    </script>
    

Show Code


    <cfinclude template="dojoStoreFormat.cfm">
    
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
        <head>
            <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
            <title>Insert title here</title>
    
            <style type="text/css">
                @import "http://localhost:8500/scripts/dojo/1.1.0/dijit/themes/tundra/tundra.css";
                @import "http://localhost:8500/scripts/dojo/1.1.0/dojo/resources/dojo.css";
            </style>
            
            <script type="text/javascript"
                src="http://localhost:8500/scripts/dojo/1.1.0/dojo/dojo.js"
                djConfig="parseOnLoad:true, usePlainJson:true">

            </script>
            
            <script>
                dojo.require("dojo.parser");
                dojo.require("dojo.data.ItemFileReadStore");
                dojo.require("dijit.form.FilteringSelect");
            </script>
    
            <cfsilent>
                <cffile action="read" file="#expandPath('./cfwtdirectory.wddx')#" variable="packet">
                <cfwddx action="wddx2cfml" input="#packet#" output="populatedDataQuery">
                <cfset jsonPacket = dojoStoreFormat("id", "name", populatedDataQuery)>    
            </cfsilent>
        
            <script type="text/javascript">
                var jsonPacket = '<cfoutput>#jsonPacket#</cfoutput>';
                var peopleStoreData = dojo.fromJson(jsonPacket);
                
                var peopleStore = new dojo.data.ItemFileReadStore({data: peopleStoreData});
            </script>
            
        </head>
        <body class="tundra">
            <select dojoType="dijit.form.FilteringSelect" jsId="emailSelect"
                store="peopleStore" style="width:200px;">

                <script type="dojo/method" event="onChange">
                if(this.item)
                {
                    document.getElementById("emailaddress").innerHTML = this.item.email;
                }
            </script>
            </select>
            <br/><br/>
            <div id="emailDisplay">
                <span style="font-weight: bold;">Email Address: </span>
                <span id="emailaddress"></span>
            </div>
        </body>
    </html>
    

It's a not-inconsiderable bump in the number of lines of code, but one that is worth the trade off. Notice that the first two lines of code inside the <cfsilent> block are a <cffile> and a <cfwddx>, in a real world situation these two lines would be replaced with a <cfquery>.
Technically, the three lines of JavaScript could be collapsed to two, or even one, but I left the code more verbose so that the process was more easily discerned.

Comments
Chris M's Gravatar This is really close to what I'm trying to do, other than I'm serializing a coldfusion query object to JSON, then trying to assign the new JSON object to the ItemFileReadStore.... But, following this tutorial I can't set the JSON object to the ItemFileReadStore. I keep getting a "{_arrayOfAllItems= _arrayOfTopLevelItems= }" or "dojo.data.ItemFileReadStore is not a constructor" error in the console.
# Posted By Chris M | 9/19/08 9:20 AM
Chris M's Gravatar What is dojoStoreFormat() ? This isn't a Coldfusion tag.
# Posted By Chris M | 9/19/08 9:25 AM
rish's Gravatar @Chris: dojoStoreFormat is a new ColdFusion function I created (mentioned in the Making Light Work subsection of the Populating ItemFileXStore section of this article) and included in the sample code (which you can obtain following the Sample Code link at the beginning of the article).
There are a few "gotchas" when trying to generate ItemFileXStore instances from ColdFusion (which I'll be discussing in the next installment of this series), but if you follow the example snippets I've supplied you should be able to stir clear of most of them.
Try getting the dojoStoreFormat working with your code. If you're still running into problems drop me an email attaching your prototype code and we can walk through it together.
# Posted By rish | 9/19/08 10:44 AM
Chris M's Gravatar Rish: I've got your UDF up. I'm new to JSON so bare with me. My JSON object looks like:
(GeneralSelect Variable)
{"COLUMNS":["PARENTCATAGORY"],"DATA":[["Domestic Data Sheets"],["International Data Sheets"],["Stationary"],["Market Brochures"],["Product Posters"],["Pricing Material"]]} as it was a distinct type query.

I've even modified my JSON object to fit the field you specified in your example.
* identifier
* label
* items
(data variable)
{"LABEL":"name","IDENTIFIER":"id","ITEMS":[{"ID":1,"NAME":"Domestic Data Sheets"},{"ID":2,"NAME":"International Data Sheets"},{"ID":3,"NAME":"Stationary"},{"ID":4,"NAME":"Market Brochures"},{"ID":5,"NAME":"Product Posters"},{"ID":6,"NAME":"Pricing Material"}]}

<cfset data = structNew()>
<cfset data.identifier = "id">
<cfset data.label = "name">
<cfset data.items = arrayNew(1)>
<cfloop query="GeneralSelect">
<cfset data.items[GeneralSelect.currentrow] = structNew()>
<cfset data.items[GeneralSelect.currentrow].id = #GeneralSelect.currentrow#>
<cfset data.items[GeneralSelect.currentrow].name = "#GeneralSelect.parentcatagory#">
</cfloop>

<cfsilent>
<cfset ThisPacket = SerializeJSON(data)>
<cfset jsonPacket = dojoStoreFormat("id", "name", ThisPacket)>
</cfsilent>

<script type="text/javascript">
var jsonPacket = '<cfoutput>#ThisPacket#</cfoutput>';
var peopleStoreData = dojo.fromJson(jsonPacket);
   var continentStore = new dojo.data.ItemFileReadStore({data: peopleStoreData});
</script>
# Posted By Chris M | 9/19/08 4:35 PM
rish's Gravatar @Chris: I think you might be doing too much.
The snippet examples I provide in the article are successive refinements of a technique that is largely wrapped up in the dojoStoreFormat UDF. I think you'll find that you can do a great deal less manipulation. Take a few moments and review emailselectsample.cfm from the Zip file attached to this article.
It will definitely be a time saver for you.
# Posted By rish | 9/22/08 12:53 PM
Henry's Gravatar following this tutorial I can't set the JSON object to the ItemFileReadStore. I keep getting a "{_arrayOfAllItems= _arrayOfTopLevelItems= }" or "dojo.data.ItemFileReadStore is not a constructor" error in the console. I also searched at http://www.rapidsloth.com for a solution but no luck.
# Posted By Henry | 3/9/10 3:26 AM
labatterie's Gravatar What is dojoStoreFormat() ? This isn't a Coldfusion tag.
# Posted By labatterie | 4/28/10 4:36 AM
BlogCFC was created by Raymond Camden. This blog is running version 5.9.1. Contact Blog Owner