February 1, 2009

dereferencing object properties in dojox.grid.DataGrid

i'm working on a demo for Persevere and the interface is basically editable tables that represent the data that is persisted by Persevere. the editable tables are the dojox.grid.DataGrid and the demo is a primitive calendar application.

Persevere supports lazy loading and so if you have an Event object that has a calendar property to indicate which Calendar the event belongs in, that calendar is stored as a reference in the event. similarly, if the calendar has an owner that is a User object, then the owner property will reference a User. these objects that are stored as references have to be loaded before they can be referenced.

in my grid that displays the list of events, i want my columns to show:
  • Calendar - the name of the calendar that the event belongs to
  • Date - the date of the event
  • Event Name - the name/title of the event
  • Description - some extra details about the event


the way i have my Event class, this would mean that the ideal layout for my grid would be something like this:
var layout = [
{width: "20%", name: "Calendar", field: "calendar.name"},
{width: "15%", editable: true, field: "date", name: "Date", type: dojox.grid.cells.DateTextBox},
{width: "30%", editable: true, field: "name", name: "Event Name"},
{width: "35%", editable: true, field: "description", name: "Description"}
];

unfortunately, the grid (or the store - depending on where you want to post the blame) doesn't support drilling down into the item by using "calendar.name" and so the first iteration of our solution is to use a custom get function.
var getCalendar = function(row, item){
if(!item){
return this.defaultValue;
}
var store = this.grid.store;
var isItem = store && store.isItem(item, true);
if(isItem){
var calendar = store.getValue(item, "calendar");
var name = store.getValue(calendar, "name");
return name;
}
return this.defaultValue;
}
var layout = [
{width: "20%", name: "Calendar", get: getCalendar},
{width: "15%", editable: true, field: "date", name: "Date", type: dojox.grid.cells.DateTextBox},
{width: "30%", editable: true, field: "name", name: "Event Name"},
{width: "35%", editable: true, field: "description", name: "Description"}
];

this is fine for this one column but what about if this grid had more columns that i wanted to do this to and then what if i had more grids with more columns like this... that's a lot of repeating and we don't like repeating. so, the final iteration of our solution would ultimately be a subclass of dojox.grid.DataGrid that would allow us to use field names like "calendar.name"

i haven't taken this all the way through to a subclass yet but i have gone as far as overriding the default get method in the grid for each instance that i use this in.

WARNING: what i'm about to show here is only valid for reading and extra work probably needs to be done if you want to be able to edit these fields and have them write back to the store properly. if i have the need for it then i'll work on the code for writing back to the store and at that point i feel it would be worth a complete subclass and possibly a request to a dojo committer to include this as part of the grid.

var layout = [
{width: "20%", name: "Calendar", field: "calendar.name"},
{width: "15%", editable: true, field: "date", name: "Date", type: dojox.grid.cells.DateTextBox},
{width: "30%", editable: true, field: "name", name: "Event Name"},
{width: "35%", editable: true, field: "description", name: "Description"}
];
var grid = new dojox.grid.DataGrid({
structure: layout,
region: 'center',
store: this.store,
get: function(rowIndex, inItem){
var grid = this.grid;
var dereference = function(item, field){
var props = field.concat().split('.');
var val = item;
dojo.forEach(props, function(prop){
val = grid.store.getValue(val, prop);
});
return val;
}
return (!inItem ? this.defaultValue : (!this.field ? this.value : dereference(inItem, this.field)));
}
}, dojo.doc.createElement('div')).placeAt(this);


this code is taken from a custom widget that is a subclass of dijit.layout.BorderContainer that i'm using so take that into consideration when looking at lines like the last line of code that places the grid at this.

something worth noting is that inside the get function, the context is a cell.

to conclude, i'm certainly looking for any improvements to this and help with writing back to the store properly would be appreciated too. ultimately, i would like this functionality to be included in the grid.