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.
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" }
]
}
- identifier
- label
- items
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>
<!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
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>.


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.
(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>
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.