Maybe you saw that tweet: ”jQuery is a gateway drug. It leads to full-on JavaScript usage.” Part of that addiction, I contend, is learning other JavaScript frameworks. And that’s what this four-part series on the incredible Dojo Toolkit is all about: taking you to the next level of your JavaScript addiction.
In this, the final episode of our session, we’ll look at the last member of the Dojo trinity: DojoX.
What’s DojoX?
DojoX is a place where modules can grow and evolve at whatever rate they need to. But don’t get the idea that DojoX is a coding free-for-all. Hardly.
You could think of DojoX (which stands for Dojo Extensions) as a sandbox, a place where modules can grow and evolve at whatever rate they need to. DojoX modules aren’t necessarily as mature as Dojo and Dijit Modules. And while there is a DojoX leader, as there is for Dojo and Dijit, each of the subprojects (as they’re called) are individually managed.
But don’t get the idea that DojoX is a coding free-for-all. Hardly. In fact, there are a couple of strict rules. Every subproject must have a README file, which you’ll find in its top directory, under the dojox
folder. Then, every subproject also has a status (found in the README). A subproject’s status can be one of the following, based on the level of commitment, and the amount of testing and documentation available:
- experimental
- alpha
- beta
- production
Interestingly, if a subproject wants to change its status, the DojoX leader (called the BDFL) must approve it.
So, what kind of things will you find in DojoX? There are a lot of extensions to Dojo and Dijit functionality (think, plenty of UI widgets); then, there are projects for creating charts, work with feeds, building data tables, and more.
Well, there’s not much more to say about DojoX in general. So, let’s use a DojoX subproject—and many of the other Dojo chops we’ve learned—and wrap up our “Dig into Dojo” session with a little demo project.
Here’s what we’ll build: it’s an interactive table (a DojoX project called a DataGrid
) with a list of recent tutorials from the Tuts+ websites. We’ll be able to filter the tutorials via typing in a text input box.
Don't forget, if you're a Tuts+ Premium member, you'll get the accompanying screencast, in which I walk you though building this project, step by step. As a premium member, you'll also be able to download the code for this mini-project. It's always a good time to sign up!
Frame It: The HTML
Let’s start with some HTML, in index.html
, of course.
<!DOCTYPE html> <head> <title> Dig into Dojo | Episode 4 </title> </head> <body class='claro'> <div id='main'> <div id='settings'> </div> <div id='content'> </div> </div> <script data-dojo-config='parseOnLoad: true' src='http://ajax.googleapis.com/ajax/libs/dojo/1.6/dojo/dojo.xd.js'></script> <script src='script.js'></script> </body> </html>
Pretty run-of-the-mill, no doubt. We’re loading Dojo from a CDN, and setting parseOnLoad: true
. Let’s add a few more elements. Notice that we have a div#settings
; let’s fill in some settings there; we want to be able to choose which Tuts+ sites we’re seeing tutorials from. We’ll have a list of checkboxes that allows us to do just that:
<div id='settings'> <p>Choose the sites you'd like to include:</p> <ul> <li><input type='checkbox' value='aetuts' /> Aetuts+</li> <li><input type='checkbox' value='cgtuts' /> Cgtuts+</li> <li><input type='checkbox' value='wptuts' /> Wptuts+</li> <li><input type='checkbox' value='nettuts' /> Nettuts+</li> <li><input type='checkbox' value='psdtuts' /> Psdtuts+</li> <li><input type='checkbox' value='phototuts' /> Phototuts+</li> <li><input type='checkbox' value='audiotuts' /> Audiotuts+</li> <li><input type='checkbox' value='vectortuts' /> Vectortuts+</li> <li><input type='checkbox' value='flashtuts' /> Activetuts+</li> <li><input type='checkbox' value='mobiletuts' /> Mobiletuts+</li> <li><input type='checkbox' value='webdesigntuts' /> Webdesigntuts+</li> </ul> <button data-dojo-type='dijit.form.Button' data-dojo-id='update'> Update </button> </div>
Notice that we’re declaratively creating a Dijit button. We’ll be turning our checkboxes into Dijit checkboxes programatically later.
What about that div#content
?
<div id='content'> <h1> Recent Tutorial from the Tuts+ Network</h1> <input type='text' data-dojo-type='dijit.form.TextBox' data-dojo-props='intermediateChanges: true' data-dojo-id='filterBox' /> <div id='table'></div> </div>
Another declarative creation; this time, a text box. Be sure to set the property intermediateChanges
to true
; doing this ensures that the onChange
will fire after every keystroke in the text box, and not only when the text box loses focus. We’ll want this behaviour when we hook up our table filtering later.
Speaking of tables, you can probably guess that our table will show up in div#table
later.
One more thing here: we’ve got to link up a few stylesheets. In the <head>
:
<link rel='stylesheet' href='http://ajax.googleapis.com/ajax/libs/dojo/1.6/dijit/themes/claro/claro.css' /> <link rel='stylesheet' href='http://ajax.googleapis.com/ajax/libs/dojo/1.6/dojox/grid/resources/Grid.css' /> <link rel='stylesheet' href='http://ajax.googleapis.com/ajax/libs/dojo/1.6/dojox/grid/resources/claroGrid.css' /> <link rel='stylesheet' href='style.css' />
The first is a standard Dijit theme. The next two are required for the DataGrid
we’ll be using. Finally, we’ll add some styling of our own. Let’s look at that next!
Style It: The CSS
There’s nothing too groundbreaking here. We’re centering our content, and pushing our little settings panel over to the right. When we hover over the settings, they’ll pop out smoothly, with a simple CSS3 transition.
The one very important point is that we’re setting a height on #table
. This is required by the DataGrid
class we’ll be using. The other thing to note is that we’re setting .dijitTextBox
to have a width of 100%.
Of course, this goes in that style.css
file we linked up:
body { margin: 40px 0; padding: 0; font: 14px/1.5 sans-serif; overflow: hidden; background: #ccc; } #main { border: 1px solid #474747; width: 940px; margin: auto; padding: 10px; background: #fff; -webket-border-radius: 7px; -moz-border-radius: 7px; border-radius: 7px; } #settings { padding: 20px 30px; width: 240px; background: #ececec; z-index: 10; border: 1px solid #474747; -webkit-border-radius: 7px 0 0 7px; -moz-border-radius: 7px 0 0 7px; border-radius: 7px 0 0 7px; -webkit-transition: right 0.3s ease; -moz-transition: right 0.3s ease; -o-transition: right 0.3s ease; -ms-transition: right 0.3s ease; transition: right 0.3s ease; position: absolute; right: -270px; } #settings:hover { right: -1px; } .dijitTextBox { width: 100%; } #table { margin-top: 20px; height: 600px; }
Power It: The JavaScript
Now, open that script.js
file we linked to in our HTML. We’ll start by require
-ing the functionality we need:
dojo.require('dijit.form.Button'); dojo.require('dijit.form.TextBox'); dojo.require('dijit.form.CheckBox'); dojo.require('dojo.io.script'); dojo.require('dojox.grid.DataGrid'); dojo.require('dojo.data.ItemFileReadStore');
You probably aren’t familiar with the last two “classes” we’re pulling in. dojox.data.DataGrid
is the interactive table we’re going to be using. The last, dojo.data.ItemFileReadStore
, is one of Dojo’s many data stores. Really, it would take a whole tutorial to properly explain data stores, but we’ll cover enough to use them in our project today. For now, just know that our DataGrid
takes a data store—in our case, an ItemFileReadStore
—as its data source, and that’s why we’re using them.
Of course, we’ll want to start performing a few actions once these modules have been loaded. Therefore, let’s wrap most of our code with this:
dojo.ready(function () { });
Other than two functions outside of this, all our code will be in here. Let’s get started with a few variables.
var checks = dojo.query('input[type=checkbox]').map(function (el) { return new dijit.form.CheckBox({ checked: true, value: el.value}, el); }),
At first glance, you might think that checks
will be a NodeList
of the checkboxes. However, notice that we’re using the map
method to turn each regular old text box into the a Dijit checkbox. So, checks
will be an array of checkbox widgets. In our options hash, we’re checking the checkboxes, and setting the value to the value attribute on the element; for some reason, the widget class doesn’t take that by default. Of course, we’re saving references to these widgets in an array, because we’ll need to access them later, to see, which boxes are checked.
structure = [ { field: 'title', name: 'Title', width: '650px' }, { field: 'creator', name: 'Author', width: 'auto' }, { field: 'pubDate', name: 'Date', width: 'auto' } ],
Next up is a structure
. This is the structure for our DataGrid
table: each object in the array will be a column in our table. The field
property maps to the data we’ll have, so the DataGrid
will know what to put where. The name
is the human-friendly column header. The width
is the width of the column.
Now, we come to the grid itself:
grid = new dojox.grid.DataGrid({ sortInfo: '-3', structure: structure, query: { title: '*' } }, 'table'); grid.queryOptions = {ignoreCase: true};
We’re setting three properties on out DataGrid
instance. The first, sortInfo
, says that we want to sort out rows by the third column; the -
means order should be descending. Recall from our structure
variable that the third column is the date the tutorial was published: so, out table will be sorted with the most recent tutorial at the top. Of course, the grid
doesn’t know about this structure yet, so we inform it with the structure
property. Finally, we set the query
. This is important: it limits the rows from our data store that appear in the table. For example, if our query object was { creator: 'J*' }
, only rows whose creator
field starts with “J” would appear. In our case, we’re defaulting to all rows; we’ll look at how to change this later.
Finally, we’re passing the id
of the element that should house the DataGrid
as second parameter to our constructor. After that, we’re setting the queryOptions
object; we don’t want queries to be case sensitive, so we’ll tell our widget to ignoreCase
.
Excellent! Now, let’s prepare for some actions. When we type in out text box, we want the list of tutorial displaying to be filtered (yes, I know we actually don’t have any tutorials displaying yet, but we’ll get there).
filterBox.set('onChange', function () { grid.filter({ title : '*' + filterBox.get('value') + '*' }); });
If you’ll recall, we set data-dojo-id='filterBox'
when declaratively creating our Dijit text box, so that’s how we can use it here in our JavaScript. We’re setting it’s onChange
handler, it’s a super-simple change: we just call the grid.filter
method, passing it a query object. If, for instance, we type “Scr” into the text box, only tutorials whose titles match *scr *
will be displayed. The nice thing here is that when we clear the text box, the titles will be filtered by **
, which matches them all.
We have two tasks left:
- Initially fill the table with data (when the page loads).
- Load only tutorials for the checked sites when the “update” button is pressed.
To do these, we’re going to abstract some functionality into two helper functions. First, we have the getSites
function; as you might have guessed, we’ll be using YQL to get the Tuts+ sites’ feeds. So, we’ll need to create a query, based on the sites’ whose boxes are checked. Here’s the format of the query:
select creator, pubDate, title from rss where url in (URL1, URL2, ....)
So, here’s our function:
function getSites (checks) { var urls = []; dojo.forEach(checks, function (check) { if (check.get('checked') === true){ urls.push(''http://feeds.feedburner.com/' + check.get('value') + '''); } }); return 'select creator, pubDate, title from rss where url in (' + urls.join(', ') + ')'; }
It’s pretty simple, and I think you can see what’s going on: we pass in the array of check box widgets, which then gets looped over. If the box is checked, we’ll create a url for it and push it into an array. We create the final YQL query by concatenating a few string and making use of the array join
method.
That was easy enough, but this next method is a bit more complex.
function getTuts (query) { return dojo.io.script.get({ url : 'http://query.yahooapis.com/v1/public/yql', content: { q: query, format: 'json' }, callbackParamName: 'callback' }).then(function (data) { }); }
We start by accepting one parameter: the query
. So, first, we set up our YQL call via dojo.io.script.get
, as you’ve seen before (We’re not doing any caching of these request, just to keep things a little simpler). We’re using the dojo.Deferred
method then
to register our own callback here. But notice something else, right at the top: return
. This will actually return a new dojo.Deferred
object, that we can call a then
method on. This is an alternative to accepting a callback function.
But before we get to all that, we have to handle our own deferred callback. Here’s how that starts:
var items = data.query.results.item, typemap = { 'Date' : { deserialize: function (value) { var date = new Date(value), month = date.getMonth(), day = date.getDate(); month = month < 10 ? '0' + month : month; day = day < 10 ? '0' + day : day; return date.getFullYear() + '-' + month + '-' + day; } } };
Hey, come back: it isn’t that bad. You’re cool with bringing that long YQL object path down to just items
, but don’t let the typemap
scare you. This is simply an object of special types that we’re using in our DataGrid
. In this case, we’re creating a Date
type so that we can format our dates appropriately. While there can be other properties, we’re only using the deserialize
one, which is a function take receives the raw value from the store (in our case, a date string), and outputs the format that will be displayed in our table. In our case, we’re simply formatting the date as YYYY-MM-DD
.
Next, we have to make some simple changes to the data that we got back from YQL:
for ( var i = 0; items[i]; i++ ) { items[i].creator = (typeof items[i].creator === 'string') ? items[i].creator : items[i].creator.content; items[i].pubDate = { _value: items[i].pubDate, _type: 'Date' }; }
The creator
value is usually the author’s name; however, for some of the feeds, we actually want creator.content
. Our first line takes care of that.
The second line is important: remember that typemap
we created? We can tell our grid
to use a specific type this way: We change our pubDate
property from the date string to an object: that object has two properties: _value
is the value for the field, while _type
is the data type to use.
Finally, let’s create our data store:
return new dojo.data.ItemFileReadStore({ data: { items: items }, typeMap: typemap });
It’s pretty simple, in our case: the data
property takes an object, where items
is our data; then, we also hand it our typemap
. You might think returning this is pointless, because this is a dojo.Deferred
’s callback function, and we’re not assigning it to anything. But remember, we’re returning a new dojo.Deferred
object, and this data store will be passed to a callback function used on that object.
If you’re confused, a simple example will clear that up. Back up in our dojo.ready
call, let’s start with what happens when the “Update” button is clicked:
update.set('onClick', function () { getTuts(getSites(checks)) .then(function (data) { grid.setStore(data); }); });
We’re setting the onClick
attribute for our update
Dijit button. We first getSites
, and pass that query to getTuts
. Since that returns a dojo.Deferred
object, we pass our callback function to its then
method. We can use grid.setStore
to refresh the DataGrid
with new data.
Finally, when the page loads, we’ll do very:
// initially fill table getTuts(getSites(checks)) .then(function (tutsdata) { grid.set('store', tutsdata); grid.startup(); });
Notice that we call grid.startup()
; this is required to setup the UI; without this, nothing would show up on our page.
Admire It: The Finished Product
Nice job! Here’s our finished project:
Conclusion
Well, that brings us to the end of our “Dig into Dojo” session; I hope it has inspired you to really get into this incredibly library.
But this isn’t the end of Dojo tutorials here on Nettuts+; far from it, if I have anything to do with it! You’ve all had some great suggestions in the comments on the others posts; keep ‘em coming and thank you so much for reading!
Comments