Building a RESTful Ajax App With Dojo and Python Werkzeug (part 3): The front-end
Third post in a series that demonstrates how to create a small RESTful Ajax application leveraging the Dojo Toolkit and Python Werkzeug.
Dojo is a comprehensive versatile JavaScript toolkit which offers about anything you can think of when it comes to JavaScript web development. The example application takes advantage of the following components in the toolkit:
- the comprehensive widget system;
- the uniform data access layers for a wide range of formats; and
- the package system for JavaScript.
RESTing with Dojo
Dojo includes thorough support for interacting with web services. Especially, when it comes to RESTful JSON web services, it requires a minimal amount of client-side code to interact with these; naturally, because of the predefined application protocol governed by REST.
In dojo, we interact with RESTful resources through
the JsonRestStore. In the last post, we implemented tasks as RESTful resources. A transcript of a session with
a JavaScript interpreter interacting with those resources can look as
follows:
dojo.require("dojox.data.JsonRestStore");
tasks = dojox.data.JsonRestStore({target: "/tasks/"});
// create new task, and save with POST
var t = tasks.newItem({name:"a task"});
tasks.save();
// NOTE: save is async. so you need wait here for
// the save to finished (or use a deferred)
t.__id; // "/tasks/1" <- from the location header in the XHR request.
// edit task and PUT it back.
tasks.changing(t);
t.name = "the task";
tasks.save();
// GET all tasks
tasks.fetch();
// GET item with specific id
tasks.fetchItemByIdentity({identity:t.__id});
// DELETE item
tasks.deleteItem(t);
You can tryout the example by pointing your browser
to letsplantheevent,
and paste code into your JavaScript console.
Transforming data into information with Dojo
Dojo includes many different layout widgets. The cool thing with these widgets are that they support the different dojo datastores, which all have the same uniform interface. That uniform interface makes it possible to seamlessly wire together data and widgets.
For the example application, we use the data grid, which enables users to explore and edit data quickly. The grid is depicted in the image, and is defined programmatically by:
dojo.require("dojox.grid.DataGrid");
tasks_grid = new dojox.grid.DataGrid({
name: "tasks",
structure: tasks_grid_structure,
store:tasks}, 'tasks_grid');
tasks_grid.startup();
In the code, the 'tasks_grid' refers to the id of the DOM node where the grid is
injected - a standard dijit widget pattern. The structure property
defines the layout of the grid, manifested in an array of views. In
views, we take advantage of an
undocumented
field: type. type refers to the type of widget that
presents the value in the grid.
constraint is a field used by
our formatter, to convert ISO 8601 datetimes to a pretty-printed string. The structure of the grid is defined as follows:
dojo.registerModulePath("lpte", "/js/lpte");
dojo.require("lpte.formatters");
dojo.require("dojox.grid.cells.dijit");
var tasks_grid_structure = [
{
field: 'name',
editable: true
},
{
field: 'startdate',
width: '70px',
editable: true,
type: dojox.grid.cells.DateTextBox,
constraint: {formatLength: 'long', selector: "date"},
formatter: lpte.formatters.datetimeFormatter
},
{
field: 'starttime',
width: '70px',
editable: true,
type: dojox.grid.cells.TimeTextBox,
constraint: {timePattern: "HH:mm", selector: "time"},
formatter: lpte.formatters.datetimeFormatter
},
// omitted end(time|date) for brevity
{
name: 'Actions',
field: 'id',
formatter: function(id, rowIdx, cell){
if(!id){
return "<em>unsaved</em>";
}
var delete_action = "tasks.deleteById('" + id + "');";
return '<button onclick="' + delete_action + '">delete</button>';
}
}
];
tasks_grid = new dojox.grid.DataGrid({
name: "tasks",
structure: tasks_grid_structure,
store:tasks}, 'tasks_grid');
tasks_grid.startup();
The actions column shows a delete button when tasks have an id. On
press the button calls the function tasks.deleteById,
which is a function mixed into the tasks store.
dojo.require('dojox.data.JsonRestStore');
tasks = new dojox.data.JsonRestStore({
target:"/tasks/",
deleteById: function(id){
this.fetchItemByIdentity({identity: id, onItem: function(item){
tasks.deleteItem(item);
tasks.save();
}});
}
});
Left is the save button and new button. The code for the save button looks as follows:
var save_tasks_button = new dijit.form.Button({
label: "Save",
onClick: function(){
tasks.save({onComplete: function(){
// disable save button on saves.
save_tasks_button.set('disabled', true);
}});
},
disabled: true
}, 'save_tasks_button');
// enable save button when new item is added
dojo.connect(tasks, "onNew", function(event){
save_tasks_button.set('disabled', false);
});
// enable save button when some property is set
dojo.connect(tasks, "onSet", function(event){
save_tasks_button.set('disabled', false);
});
There is one caveat in the above: for some reason the TimeTextBox widget is not availble in dojox.grid.cells! Most likely, due to some oversigt. We define it ourself:
dojo.declare("dojox.grid.cells.TimeTextBox", dojox.grid.cells._Widget, {
widgetClass: dijit.form.TimeTextBox,
setValue: function(inRowIndex, inValue){
if(this.widget){
this.widget.set('value', new Date(inValue));
}else{
this.inherited(arguments);
}
},
getWidgetProps: function(inDatum){
return dojo.mixin(this.inherited(arguments), {
value: new Date(inDatum)
});
}
});
And that's it! We now have an advanced RESTful Ajax web service client. In the next post I'll take things up a notch and make the tasks grid a live collaborative grid using Comet updates.



