Dojo 1.2 Grid

July 14th, 2008 - by Bryan Forbes

With the release of Dojo 1.2 right around the corner, there’s an updated grid widget available. It offers new features and performance improvements over the existing grid including better Dojo data integration, simplified layout structures, and the ability to enable editing much more easily.

In order to make these improvements, we were forced to break backwards compatibility between the new grid and the old grid. This shouldn’t be a surprise since the grid is part of DojoX. We realized that enough people use, rely on, and extend the grid that we didn’t want to break applications based on Dojo 1.0 and 1.1, so both grids are available to you. The old grid will be available until Dojo 2.0, but it will not be improved in future versions of Dojo 1.x.

This article focuses on the use of the new grid widget. All of the examples presented in this article may be downloaded in a tarball so you can modify and follow along.

Logical File Layout

The new grid API has been organized according to the Dojo conventions. For instance, the old dojox.VirtualGrid is now dojox.grid._Grid, dojox.Grid is now dojox.grid.DataGrid, and dojox.grid.selection is now dojox.grid.Selection. The namespace that objects are in now also reflect where you can find them in the file system.

Dojo Data Integration

I mentioned that dojox.Grid is now dojox.grid.DataGrid, but why is it called DataGrid? The reason is that DataGrid natively supports dojo.data stores. In order to use dojo.data stores with the grid in previous releases, you needed the dojox.grid.data.DojoData model which would bridge the gap between the grid and the store. DataGrid has been engineered to remove that bridge. Instead of using stand-alone models to store data for the grid, any dojo.data store that implements the Dojo Data read API can be used. Additionally, DataGrid can use the write and notification API’s if they are available. Let’s look at an example (using the data from my previous grid tutorials modified to work with ItemFileReadStore):

var jsonStore = new dojo.data.ItemFileReadStore({ url: "json/gaskets.json" });

var grid = new dojox.grid.DataGrid({
        id: ‘grid’,
        query: { part_num: ‘*’ },
        store: jsonStore,
        structure: layout
}, ‘gridNode’);
 

[Live Example]

This will load our JSON file from a URL and give us access to the data through the Dojo Data API. We then pass the store to the DataGrid constructor along with the query we want the grid to run against the store. We’ll get to defining a layout in a bit, but for now let’s look at some more things we can do with this new grid.

I mentioned notification API support. This means if the store changes, the grid will update. Let’s set up an example:

var jsonStore = new dojo.data.ItemFileWriteStore({ url: "json/gaskets.json" });

var grid = new dojox.grid.DataGrid({
        id: ‘grid’,
        query: { part_num: ‘*’ },
        store: jsonStore,
        structure: layout
}, ‘gridNode’);

var updateGasketTypes = function(){
        jsonStore.fetch({
                query: { part_num: ‘??1?’ },
                onComplete: function(items, result){
                        dojo.forEach(items, function(item){
                                jsonStore.setValue(item, "type", 2);
                        });
                }
        });
}
 

<button onclick="updateGasketTypes();">Change gasket types</button>
 

[Live Example]

When the button is clicked, any item that has a ‘1′ in the third number of its part number will have its type changed to 2 and the grid will update its information.

Layout Structure Simplified

In previous versions of the grid, setting up how you wanted the grid to look was sometimes confusing. With this latest version we have tried to make setting up the look of the grid simpler while still allowing flexibility for those that need it. There are now two ways to lay out your grid: markup and programatically.

Layouts with markup

Previously the only way to set up your grid’s layout for use in markup was with a structure defined in a script block. Those days are long gone! Setting up a grid’s layout is now as simple as this:

<table id="gridNode" jsId="grid" dojoType="dojox.grid.DataGrid"
           query="{ part_num: ‘*’ }" store="jsonStore">

        <thead>
                <tr>
                        <th field="part_num">Part Number</th>
                        <th field="min_temp" width="100px">Minimum Temperature</th>
                        <th field="max_temp" width="100px">Maximum Temperature</th>
                        <th field="type" width="100px">Type</th>
                        <th field="thick" width="5em">Thickness</th>
                </tr>
        </thead>
</table>
 

[Live Example]

One of the cool features of the grid is that you can group certain columns and lock them from scrolling horizontally. You can set that up with markup as well:

<table id="gridNode" jsId="grid" dojoType="dojox.grid.DataGrid"
           query="{ part_num: ‘*’ }" store="jsonStore">

        <colgroup span="1" noscroll="true"></colgroup>
        <colgroup span="4"></colgroup>
        <thead>
                <tr>
                        <th field="part_num" width="300px">Part Number</th>
                        <th field="min_temp" width="100px">Minimum Temperature</th>
                        <th field="max_temp" width="100px">Maximum Temperature</th>
                        <th field="type" width="100px">Type</th>
                        <th field="thick" width="5em">Thickness</th>
                </tr>
        </thead>
</table>
 

[Live Example]

With colgroup we are able to lock the first column and allow the last four columns to be horizontally scrolled. You can even set up multiple rows within each row while spanning cells between rows and columns:

<table id="gridNode" jsId="grid" dojoType="dojox.grid.DataGrid"
           query="{ part_num: ‘*’ }" store="jsonStore">

        <thead>
                <tr>
                        <th field="part_num" rowspan="2">Part Number</th>
                        <th field="min_temp" width="100px">Minimum Temperature</th>
                        <th field="max_temp" width="100px">Maximum Temperature</th>
                        <th field="type" width="100px">Type</th>
                </tr>
                <tr>
                        <th field="thick" colspan="3" width="5em">Thickness</th>
                </tr>
        </thead>
</table>
 

[Live Example]

You can even define formatters and getters for columns:

function formatDegrees(value){
        return value + ‘°’;
}

function getODtoID(rowIndex, item){
        if(!item){
                return this.defaultValue;
        }
        var grid = dijit.byId(‘gridNode’);
        var od = grid.store.getValue(item, "outer");
        var id = grid.store.getValue(item, "inner");

        return od – id;
}
 

<table id="gridNode" jsId="grid" dojoType="dojox.grid.DataGrid"
           query="{ part_num: ‘*’ }" store="jsonStore" rowSelector="20px">

        <thead>
                <tr>
                        <th field="part_num">Part Number</th>
                        <th field="min_temp" formatter="formatDegrees" width="100px">
                                Minimum Temperature
                        </th>
                        <th field="max_temp" formatter="formatDegrees" width="100px">
                                Maximum Temperature
                        </th>
                        <th field="type" width="50px">Type</th>
                        <th field="thick" width="5em">Thickness</th>
                        <th width="5em" get="getODtoID">OD to ID</th>
                </tr>
        </thead>
</table>
 

[Live Example]

Another popular Dojo grid feature is dojox.GridRowView. It provides a view at the left side of the grid that allows you to select rows easily. For Dojo 1.2, that has been renamed to dojox.grid._RowSelector and can be enabled by passing an option to the grid:

<table id="gridNode" jsId="grid" dojoType="dojox.grid.DataGrid"
           query="{ part_num: ‘*’ }" store="jsonStore" rowSelector="20px">

        <thead>
                <tr>
                        <th field="part_num">Part Number</th>
                        <th field="min_temp" width="100px">Minimum Temperature</th>
                        <th field="max_temp" width="100px">Maximum Temperature</th>
                        <th field="type" width="100px">Type</th>
                        <th field="thick" width="5em">Thickness</th>
                </tr>
        </thead>
</table>
 

[Live Example]

By passing “20px” to the grid in the rowSelector attribute, we are telling the grid we want the row selector to be 20 pixels wide. You can also pass a boolean value (if it’s true, you get the default size) or any other valid css size.

Putting this all together, we can port our previous application to the new API. Just look at the source of here.

Programmatic Layouts

If markup isn’t your thing or you’re creating your grid on the fly you’ll be glad to know that we’ve simplified the structure you pass in to the grid to define the layout. Previously you were required to pass in a structure like this to get something similar to our first markup example:

var layout = [
        // view 0
        {
                cells: [[
                        { field: "part_num", name: "Part Number", width: ‘auto’ },
                        { field: "min_temp", name: "Minimum Temperature",
                                width: "100px" },
                        { field: "max_temp", name: "Maximum Temperature",
                                width: "100px" },
                        { field: "type", name: "Type", width: "100px" },
                        { field: "thick", name: "Thickness", width: "5em" }
                ]]
        }
];

var grid = new dojox.grid.Grid({
        structure: layout,
        model: model
}, ‘gridNode’);
 

Now, you can trim it down to something like this:

var layout = [
        { field: "part_num", name: "Part Number", width: ‘auto’ },
        { field: "min_temp", name: "Minimum Temperature", width: "100px" },
        { field: "max_temp", name: "Maximum Temperature", width: "100px" },
        { field: "type", name: "Type", width: "100px" },
        { field: "thick", name: "Thickness", width: "5em" }
];

var grid = new dojox.grid.DataGrid({
        query: { part_num: ‘*’ },
        store: jsonStore,
        structure: layout
}, ‘gridNode’);
 

[Live Example]

Multiple rows of cells is also easier. Before we had to do this:

var layout = [
        // view 0
        {
                cells: [[
                        { field: "part_num", name: "Part Number", width: ‘auto’,
                                rowSpan: 2 },
                        { field: "min_temp", name: "Minimum Temperature",
                                width: "100px" },
                        { field: "max_temp", name: "Maximum Temperature",
                                width: "100px" },
                        { field: "type", name: "Type", width: "100px" }
                ],[
                        { field: "thick", name: "Thickness", width: "5em",
                                colSpan: 3 }
                ]]
        }
];

var grid = new dojox.grid.Grid({
        structure: layout,
        model: model
}, ‘gridNode’);
 

And now:

var layout = [[
        { field: "part_num", name: "Part Number", width: ‘auto’, rowSpan: 2 },
        { field: "min_temp", name: "Minimum Temperature", width: "100px" },
        { field: "max_temp", name: "Maximum Temperature", width: "100px" },
        { field: "type", name: "Type", width: "100px" }
],[
        { field: "thick", name: "Thickness", width: "5em",
                colSpan: 3 }
]];

var grid = new dojox.grid.DataGrid({
        query: { part_num: ‘*’ },
        structure: layout,
        store: jsonStore
}, ‘gridNode’);
 

[Live Example]

If you want to add the row selector, just change the constructor call to this:

var grid = new dojox.grid.DataGrid({
        query: { part_num: ‘*’ },
        structure: layout,
        store: jsonStore,
        rowSelector: ‘20px’
}, ‘gridNode’);
 

[Live Example]

Now you can see the application from my previous post ported to the new programmatic API.

Editing redefined

In order to edit a value in the old grid, you would have to pass an editor class to your cell definition. In the new grid, each cell has a default editor defined; all you have to do is add editable: true to your cell definition and use a store that implements the Dojo Data write API:

var jsonStore = new dojo.data.ItemFileWriteStore({ url: "json/gaskets.json" });

var layout= [
        { field: "part_num", width: "auto", name: "Part Number" },
        { field: "min_temp", width: "100px", name: "Minimum Temperature" },
        { field: "max_temp", width: "100px", name: "Maximum Temperature" },
        { field: "type", width: "100px", name: "Type" },
        { field: "thick", width: "5em", name: "Thickness", editable: true }
];

var grid = new dojox.grid.DataGrid({
        query: { part_num: ‘*’ },
        store: jsonStore,
        structure: layout,
        rowsPerPage: 20
}, ‘gridNode’);
 

[Live Example]

You can now edit the thickness property of each row of data. What if you don’t want to edit the data with a text box? Let’s say we want to set up a select to edit the type of gasket (cellType for the markup attribute name):

var layout= [
        { field: "part_num", width: "auto", name: "Part Number" },
        { field: "min_temp", width: "100px", name: "Minimum Temperature" },
        { field: "max_temp", width: "100px", name: "Maximum Temperature" },
        { field: "type", width: "100px", name: "Type", editable: true,
          type: dojox.grid.cells.Select, options: [ ‘0′, ‘1′ ] },
        { field: "thick", width: "5em", name: "Thickness", editable: true }
];
 

[Live Example]

Conclusion

The Dojo 1.2 Grid offers significant performance and API simplifications over previous versions of the Dojo Grid. As of Dojo version 1.2, you will be able to continue using the existing Dojo Grid, and also use the new Grid, even on the same page of an application if necessary. We highly recommend that you upgrade to the new version of the grid as soon as possible as the new feature set is worth the investment!

Bookmark and Share

Tags: , ,

150 Responses to “Dojo 1.2 Grid”

  1. sdg says:

    hi,
    is there an example how can i use the oncellclick method?

    i am looking for this, but i didn’t found any example?

  2. Rick O'Shay says:

    It’s well known that pixel units are a disastrous choice in terms of formatting text, but most examples and books resort to this. I wish they wouldn’t. In this example, there is a mixture of pixels and em units, on the same grid. As screen resolutions continue to increase, pixels become more and more asinine, not that they ever made sense for text.

  3. Dylan says:

    @Rick: in a perfect world, yes. But CSS and ems are far from perfect in all situations… for example, text next to images… images don’t really work when set in ems, and CSS is far from great in terms of working well with layouts.

  4. Tuan says:

    Thank you for the post. I am new to Dojo too and was wondering if there is a way to show X number of rows of a grid and not depend on the div height? Does anyone know?

  5. Zladivliba says:

    Can anyone drop a piece of code here explaining how it is possible to setup the grid with JsonService ; I have tryied but it seems to be impossible to do it

  6. [...] auch die neue Grid Dokumentation und den Blog-Artikel für mehr [...]

  7. Nathan says:

    I’ve been trying to get the gasket examples to work using the Dojo 1.2 official release, and had a fair number of difficulties….

    So I eventuraly did a “diff” on the .css files in the dojox/grid/resources file in Bryan’s tarball and the .css files in the official 1.2 release dojox/grid/resources folder, and found quite a few significant differences.

    The two biggest problems I’m having is that the new 1.2 does not include an uncompressed dojo.js (so I can’t zero in on my errors very easily), and that when I do get the grid to semi-display, a nice little “Sorry, an error occured” message shows up in the grid, but no errors in firebug! So….I’m frustrated :)

    Bryan could you update your blog (or at least give me some pointers) to reflect the way your examples should work with the new 1.2 release?

  8. Kyokku says:

    Hi, I have a problem trying to insert a DataGrid into a AccordionPane programmatically, grid and pane are running well separately but this is what happens when I try to insert it:

    var utilDiv = dojo.doc.createElement(”div”); //reference for the grid
    utilDiv.setAttribute(”id”,”utilDiv”);

    dojo.place( utilDiv, newResultPane.containerNode, 0 );
    dojo.place( grid, utilDiv, 0); // and here is where it dies :´(

    containerSeguimiento.addChild(newResultPane);//doesn’t add new pane
    containerSeguimiento.startup();
    containerSeguimiento.selectChild(newResultPane);

    I get this error string in the Firefox errorConsole:
    Error: [Exception... "Node cannot be inserted at the specified point in the hierarchy" code: "3" nsresult: "0x80530003 (NS_ERROR_DOM_HIERARCHY_REQUEST_ERR)" location: "http://localhost:8080/DojoDataGrid/dojo/dojo/dojo.js Line: 21"]
    Archivo de origen: http://localhost:8080/DojoDataGrid/dojo/dojo/dojo.js
    Línea: 21

    I’ve inserted other elements and they worked well but dataGrid…
    What im doing wrong?

  9. Bryan Forbes says:

    @John: Editors in the grid have changed from 1.1.1 to 1.2. Instead of editorClass, you now specify a cell type. I’ll be going over that in my next blog post which should be out this week. About the expanding/collapsing grid, you should check out the release of 1.2 since it’s out now. I think these issues have been fixed.

    @Jesse: I’m not a dojo.data expert, but I would think you would want to use dojox.data.ItemFileWriteStore. Summary rows are a feature that we want to work on making really slick in the 1.3/1.4 release. Any feedback in Dojo’s Trac ( http://bugs.dojotoolkit.org ) would be helpful.

    @Wade: There is basic support in the form of events that are defined on the grid, but we haven’t developed drag and drop between two grids yet. We plan on implementing it in the future.

    @Dojo newbie: Yes, I used a special build. This file I used is here: http://www.sitepen.com/labs/code/grid/grid.profile.js

    @Jim: Yes the grid will eventually be fully accessible. The next two releases should bring more a11y features.

    @Lew: Since this was posted, we made the grid act more like a Dijit widget by requiring you to call .startup() on programmatic grids instead of .render() or .refresh().

    @ChriZ: You want to define an onBeforeRow function for your view. There are a few examples of it in the grid test files. I’ll try to touch on it in my next post as well.

    @Laca: rowsPerPage will specify how many rows should be fetched for each page of data.

    @Dennis Samting: You would have to parse the JSON object into something that dojo.data.ItemFileReadStore could use. Once you’ve done that, pass that to ItemFileReadStore and use that as your store for the grid.

    @Alan Xing: The basic hooks are there for DnD of grid rows, however it’s not very robust. We pushed back that issue to 1.3 so we could make sure it gets solved in a smart way.

    @Stephen D: There are a few examples in the test files for the grid that show how to do that. test_expand.html and test_subgrid.html are the pertinent files. We’re working on a simpler way to do this for 1.3/1.4.

    @jcdecker: If you just want a button element, you could set up the “get” function to return the markup for the button. If you want a widget button in there, there’s currently no easy way to do that. It’s something we’re looking into for 1.3/1.4. Regarding editable cells, my next post will include how to implement them.

    @Jon B: For now, there is no way to set up an oBeforeRow handler using the declarative approach. The tfoot idea is a good one. We’ll keep it in mind when doing summary rows!

    @rvillane: For your first problem, check out grid.filter(). That should work with QueryReadStore. The second problem is something I haven’t seen. I’ll have to look into that for the 1.2.1/1.3 release.

    @Vinod: What would the checkbox be used for? We’re planning on adding checkbox selection in 1.3.

    @kyokku: You get paging for free with the grid. Set rowsPerPage to the how many rows you want and then as you scroll through the grid, it automatically loads data as you need it. This provides for a much smoother interface.

    @Lance Spellman: This was a known problem and has been fixed for the 1.2 release.

    @sdg: How do you plan on using this event? I could touch on that in my next post if I knew how you wanted to use it.

    @Tuan: Yes, there is: set the autoHeight property to the number of rows you want to show.

    @Zladivliba: Have you looked at the test_yahoo_search.html test included with the grid? It uses dojox.rpc.Service and dojox.data.ServiceStore to populate the grid based on a search.

    @Nathan: I should have another blog post up by the end of the week. Stay tuned!

  10. Bryan Forbes says:

    @Kyokku: I see one problem: you’re using grid instead of grid.domNode in your call to dojo.place(). I think that might cause the error your seeing.

  11. Kyokku says:

    Thanks a lot Bryan, I had been working on this for days. About paging… bosses don’t like scrolls because “the grid needs to show a lot of rows (sometimes more than 20K)” ¬¬, even if paging makes slower the user interface, so they want to do it with native ajax xhr and buttons next and previous for pages. :(

    Will be good if people of dojoland makes something like.
    Thanks again for reply.
    P.D. Sorry for bad english, im just learning. =p

  12. Marc Rhodes says:

    The tarball mentioned in this post, , contains a complete copy of dojo. The demo works fine with this copy of dojo.

    If I replace that dojo with the official release dojo 1.2 the demo does not work anymore. The frame appears but the table is blank. I tried both the packed and src versions and got the same results.

  13. Bryan Forbes says:

    @Kyokku: Currently, the grid doesn’t support paging as you describe it. Perhaps we can come up with something like this for the 1.3/1.4 release.

    @Marc Rhodes: Some of the API for the grid changed between this blog post and the 1.2 release. I’m writing a new blog post with an updated tarball to be published on Friday. Stay tuned!

  14. Marc Rhodes says:

    Bryan, I am also having trouble including a grid in a contentPane loaded with setHref. Page appears fine when loaded directly, errors when loaded into a contentPane with setHref.

    Dunno if there might be something simple worth mentioning about this usage. Or if it’s too far off-topic maybe a subject for another post?

  15. What has onBeforeRow event been replaced with in grid 1.2? I’d like to implement the subgrid functionality from the 1.1 tests, test_subgrid.html, but with 1.2 DataGrid and QueryReadStore.

    It looks like grid/_View.js now has a generateCellMarkup function that doesn’t have onBeforeRow anymore like _grid/Builder.js had with its generateHtml function.

    Any hints on how to proceed?

  16. I guess, more specifically, what’s the best way to do subgrids with the new 1.2 grid? Are there examples out there?

  17. Bryan Forbes says:

    @Marc Rhodes: I’ve never tried using the grid like that in a ContentPane. I’ll check it out and see what I can come up with. Perhaps you’ve found a bug :).

    @Lance Spellman: onBeforeRow still exists. For subgrid examples, check out test_expand.html and test_subgrid.html in the grid tests.

  18. Marc Rhodes says:

    Bryan, I was able to get the grid working in the ContentPane using the markup approach but I have not got it working with the programmatic approach. This may be due to a more general problem of not understanding how to do addOnLoad when loading into a ContentPane with setHref.

  19. Thanks Bryan, you’re correct. I should have updated earlier when I discovered I was looking in the wrong place.

    I’ve worked through that and I’m trying to come up with a way that when the getDetail function is called it goes back to the data store and queries for the child entries of that entry.

    I have flexibility in setting up the data for the store, but I’m trying work through what that would be so that when the subgrid is instantiated, I can set a store and a query for the store to be something like query:{currentEntry.children: ‘*’}

    Not exactly sure how I need to structure the JSON data for the store, and then what the query would be to set the subgrid store correctly.

    Any help here appreciated. TIA, Lance

  20. Update. I have everything working to the point where grid row clicked creates subGrid. Subgrid is connected to a QueryReadStore and gets the data.

    But the subgrid doesn’t dislpay. There’s a flicker on the screen where the grid temporarily expands, then it collapses back with a tiny row (about 2px) below the row that should have expanded to a subgrid.

    Firebug shows that there is a subgrid, but no headers or data. Console.debug shows that subgrid object does in fact exist, has the right number of rows, has a datastore connected to it with the correct data.

    This is similar to the problem described in the forums of subGridProps having a “get”:getDetailData that wasn’t firing and therefore subGrid not displaying.

    Suggestions? Should I post in grid forum?

  21. Erick2red says:

    @Marc Rhodes: Bryan, I was able to get the grid working in the ContentPane using the markup approach

    i’m triyng to make it work inside a ContentPane setting href attr, but not happens, i got an error:
    “Error parsing in _ContentSetter#Setter_setter_1 TypeError: n is undefined message=n is undefined”
    and my page loaded by href use a markup defined grid, i create the tab and set href programmatically

  22. Eureka!

    Got it. For anyone else working through this, if you’re modeling after the test_subgrid.html file and you are working with a DataGrid that has a store, I found you needed to go to the buildSubgrid function and replace the line subgrid.render() with subgrid.startup().

    Shows up fine then. That was a nice 8 hr kill.

    After I get things cleaned up I will post a link to a working example in the forum.

  23. Erick2red says:

    @Bryan Forbes: i like to check your new post, cuz i’m having trouble catching onchange events from a bool cell editor, so i wanna know…

  24. rdunklau says:

    Hello.

    I’m looking for a way to insert rows in this new DataGrid. The fact is, i know how to add one at the end of the displayed rows, but i can’t figure how to insert a new one. Could you help me ?

    Thanks

  25. Marc Rhodes says:

    @Erick2red: the error you are getting comes from html.js. Maybe it is trying to parse the page you want to load. Does the page you are trying to setHref work okay when you display it standalone?

  26. Erick2red says:

    @Marc Rhodes, thanxs that was the trouble, i found it myself, but cost me like 8 hours…thanxs anyway, now killing time putting a bool cell editor to work.
    some useful tips?

  27. JbL says:

    Is there a way to allow the column widths to float naturally based on their content, like a simple HTML table? When I omit the width attributes on each column in the layout, every column gets the same default width. When I use ‘auto’ as the width in every column, it does the same — all columns are the same size.

    I’m creating the grid and layout programmatically, like this:

    var layout = [
    { field: 'id', name: 'ID', width: '40px' },
    { field: 'name', name: 'Name', width: 'auto' },
    { field: 'number', name: 'Number', width: '60px' }
    ];
    var grid = new dojox.grid.DataGrid({
    query: { id: ‘*’ },
    store: jsonStore,
    autoHeight: 5,
    structure: layout
    }, document.createElement(’div’));

  28. Dan says:

    I have been using YUI for a while, but from what I have seen, dojo seems cleaner. However, how would I populate the v1.2 grid using a javascript array. I don’t always want to use ajax.

    Have you ported your previous grid tutorials to use the new version of the grid?

    Thanks.

  29. Ivan says:

    Thanks for all the info!

    Any news about that updated blog post?

    I spent the whole weekend stupidly trying to put together something using code from your example, all the time thinking I was doing something wrong since the grid wouldn’t display. But now I see that your example won’t work with the official release.

    An update would be greatly appreciated.

    Thanks and regards,

    I.-

  30. Ivan says:

    Disregard my previous message!

    I’ve been able to fix my code by reading http://docs.dojocampus.org/dojox/grid

    Thanks again for all the hard work!

    Regards,

    I.-

  31. John says:

    The 1.2 Grid is a huge improvement over 1.1.1. I prefer to define my grid in markup rather than javascript as it seems much cleaner to me.

    I was wondering if anyone has been able to get the widgetClass (defined in markup) to work with a custom build. The following column works fine with the release build, but not with a custom build. When I try this, I see _28.split is not a function in the firebug console. Any help would be greatly appreciated

    Transaction Date

    Thanks,

    John

  32. Nathan says:

    Bryan,

    How’s that new blog post coming? I’ve been working on some other stuff, but sure would like to return to my grid work and get it nailed down properly (properly being the way you say I should do it :) )

    Thanks!
    Nathan

  33. John says:

    I found the solution to the widgetClass not working in the custom build. If you patch the source per this URL it will work.

    http://trac.dojotoolkit.org/ticket/7829

  34. [...] last article about the Dojo Grid focused on what has changed when creating a grid using Dojo 1.2. In this [...]

  35. Patch says:

    I downloaded your example source files and used the dojo 1.2 I downloaded from the dojo website. The grid only seems to work with the dojo you have in the example tarball.

    It would seem a running problem with dojo that anything built with a specific version is not backwards compatible with any other version.

    I’ve been looking for a proper ajax solution to an application I’m going to build.

  36. fancyplants says:

    I have had the same problem as Patch above – I have downloaded dojo v1.2 and tried to use the new DataGrid using my own details in json format with a slight modification to the code above.

    The DataGrid seems to be created, but it is not visible on the screen and the ItemFileReadStore._jsonData is null. Any ideas?

    Some code (abridged)
    ========================================================
    the jsp page:

    @import “http://o.aolcdn.com/dojo/1.2.0/dijit/themes/tundra/tundra.css”;
    @import “http://o.aolcdn.com/dojo/1.2.0/dojox/grid/resources/Grid.css”;
    @import “http://o.aolcdn.com/dojo/1.2.0/dojox/grid/resources/tundraGrid.css”;
    @import “http://o.aolcdn.com/dojo/1.2.0/general.css”;

    /* NOTE: for a full screen layout, must set body size equal to the viewport. */
    html, body { height: 100%; width: 100%; margin: 0; padding: 0; }

    dojo.require(”dojo.data.ItemFileReadStore”);
    dojo.require(”dojox.grid.DataGrid”);

    var jsonStore = new dojo.data.ItemFileReadStore({ url: “/appts_desktop/appointments_desktop/fetch/static.json” });
    var grid = null;

    dojo.addOnLoad(function(){
    var layout= [
    { field: "provider_type", width: "80px", name: "Type" },
    { field: "provider", width: "auto", name: "Service Provider" },
    { field: "service", width: "auto", name: "Service" },
    { field: "last_used", width: "80px", name: "Last Used" },
    { field: "rating", width: "20px", name: "Rating" }
    ];

    grid = new dojox.grid.DataGrid({
    query: { provider_type: ‘*’ },
    store: jsonStore,
    structure: layout,
    rowsPerPage: 20
    }, ‘gridNode’);
    });

    …..

    You are here: Choose Service -> Add Details -> Select Date and Time -> Confirm

    From Favourites:

    ========================================================
    static.json:

    {
    identifier: ‘provider_type’,
    label: ‘provider_type’,
    items: [
    { id: 1, provider_type: 'hairdressers', provider: 'Short Cuts', service: 'Cut and Blow Dry', last_used: '12/10/2008', rating: 3 },
    { id: 2, provider_type: 'car_servicing', provider: 'Bobs Wheels', service: 'MOT', last_used: '12/10/2008', rating: 4 },
    { id: 3, provider_type: 'car_servicing', provider: 'Bobs Wheels', service: 'Full Service and Checkup', last_used: '12/10/2008', rating: 5 },
    { id: 4, provider_type: 'restaurants', provider: 'Ricos', service: 'Evening Meal', last_used: '12/10/2008', rating: 4 }
    ]}

    Any help would be much appreciated, thanks.

  37. [...] of a model and instead it directly references the datasource with the Grid.  There’s a good article at Site Pen that will get you going with the basics of the new Grid. [...]

  38. Sharoon says:

    Hi,

    Is it possible to have some of the rows in the Grid as non-editable and some as editable?

    Any help would be appreciated.

    Thanks

  39. fancyplants says:

    Hi,
    I’ve managed to get the table to sort of appear by taking it out of the LayoutContainer, (the json data was invalid in my original post which didnt help) but it only displays the titles, and the width of the widget goes off the right side of the screen. When I go into firebug and change the height manually then sort one of the columns the data appears.
    I’m guessing this is because its nested in divs and tables, but why should this be? I can do a sort of botchy bugfix by adding the following:
    dojo.addOnLoad( function() {
    document.getElementById( ‘gridNode’ ).style.width=”530px”;
    document.getElementById( ‘gridNode’ ).style.height=”250px”;
    });
    (Putting width and height as style values in the element doesn’t work by the way)
    But why won’t it size correctly anyway? Anyone have any ideas?

  40. philou says:

    Problem:
    Filling a dojox.data.DataGrid by retrieving the data via ajax request and sending an update back to the server via ajax when the data in the grid is edited.

    Hi,
    the above states my problem pretty well. I have searched the web up and down and I haven’t been able to even get a glimpse of how to achieve this, so I’d like to ask you for a hint, please.

    I am filling a grid as you showed in your tutorial above, except that I don’t load a file, but send a request to a php script which returns the data:

    var jsonStore = new dojo.data.ItemFileWriteStore({ url: “http://www.myserver.com/return_json_data.php” });

    This works very well to fill the grid and if I change a value in the grid, this value is updated in the java jsonStore data store, as I can see with Firebug.

    Now I would like to communicate this data change back to my server. Using your setup above, lets say I have changed the “maximum temperature” to “2500″ for part number “4005″. How do I trigger this:

    http://www.myserver.com/receive_data_update.php?action=update&part_number=4005&max_temp=2500

    Could this even be done by sending this via POST?

    Many thanks ind advance,
    philou

  41. Jeremy Zerr says:

    @philou

    Before you do a jsonStore.save, why not just do an inspection of the jsonStore to see which items have modified using the isDirty() method. Then, when you determine what has been modified, send a HTTP POST request to update the server with that modified info. Just use the Dojo xhrGet or xhrPost, see the functions detailed here: http://api.dojotoolkit.org/

    Here is some sample code swiped from the book of Dojo on how to find if an item has been modified in a jsonStore.

    Code borrowed from the Write API documentation:
    http://dojotoolkit.org/book/dojo-book-0-9/part-3-programmatic-dijit-and-dojo/what-dojo-data/dojo-data-design-and-apis/write

    //Instantiate some write implementing store.
    var store = some.DataWriteStore();
    //Set our load completed hander up…
    var onCompleteFetch = function(items, request) {
    // Process the items test for modification
    for (int i = 0; i < items.length(); i++){
    var item = items[i];
    if (store.isDirty(item){
    alert(”Item with label: ” + store.getLabel(item) + ” is dirty.”);
    }
    }
    }

    //Define a fetch error handler, just in case.
    var onFetchError = function(error, request){
    alert(”Fetch failed. ” + error);
    }

    // Fetch some data… All items, in fact (no query should return everything)
    store.fetch({onComplete: onCompleteFetch});

    Jeremy Zerr
    http://www.zerrtech.com

  42. Laca says:

    @philou

    I think what you want to do is to override the _saveCustom function of ItemFileWriteStore.
    _saveCustom is called whenever the save method of the store is called AND there are not yet committed changes (inserts, deletes, updates) to the store. So ItemFileWriteStore does some of the work for you automatically.

    Here is how I do it. I am using JSON, but of course you can do whatever you prefer, it is just that you need your own formatting method (if it is needed) instead of my _getItemJSONString function:

    // Custom ItemFileWriterStore that supports add/remove/modify to my app
    dojo.declare("CustomItemFileWriteStore", dojo.data.ItemFileWriteStore, {

    _saveCustom: function(saveCompleteCallback, saveFailedCallback){
    // xhrPost/xhrPut your data back to your server (convert it to the server format first if need be)
    // 'this' keyword refers to the ItemFileWriteStore instance being extended
    var success = true;
    if(this.isDirty()) {
    if(!this._isEmpty(this._pending._newItems)) {
    success = this._postToApp("insert", this._pending._newItems);
    }
    if(success && !this._isEmpty(this._pending._deletedItems)) {
    success = this._postToApp("delete", this._pending._deletedItems);
    }
    if(success && !this._isEmpty(this._pending._modifiedItems)) {
    var modifications = [];
    for(identity in this._pending._modifiedItems){
    // find the modified items as this._pending._modifiedItems contains the
    // ORIGINAL values because the Grid must show the modified items, but
    // we need to send the modifications to Neat
    if(this._itemsByIdentity){
    modifications[identity] = this._itemsByIdentity[identity];
    }else{
    modifications[identity] = this._arrayOfAllItems[identity];
    }
    }
    success = this._postToApp("update", modifications);
    }
    }
    if(success) {
    saveCompleteCallback();
    }
    else {
    saveFailedCallback();
    }
    },

    _getItemJSONString: function(itemArr){
    var serializableStructure = {};

    var identifierAttribute = this._getIdentifierAttribute();
    if(identifierAttribute !== Number){
    serializableStructure.identifier = identifierAttribute;
    }
    if(this._labelAttr){
    serializableStructure.label = this._labelAttr;
    }
    serializableStructure.items = [];
    for(var i = 0; i < itemArr.length; ++i){
    var item = itemArr[i];
    if(item !== null){
    var serializableItem = {};
    for(var key in item){
    if(key !== this._storeRefPropName && key !== this._itemNumPropName){
    var attribute = key;
    // var valueArray = this.getValues(item, attribute);
    var valueArray = item[attribute] || [];
    if(valueArray.length == 1){
    serializableItem[attribute] = this._flatten(valueArray[0]);
    }else{
    var serializableArray = [];
    for(var j = 0; j < valueArray.length; ++j){
    serializableArray.push(this._flatten(valueArray[j]));
    serializableItem[attribute] = serializableArray;
    }
    }
    }
    }
    serializableStructure.items.push(serializableItem);
    }
    }
    var prettyPrint = false;
    return dojo.toJson(serializableStructure, prettyPrint);
    },

    _postToApp: function(operation, postItems) {
    var identity;
    var postItemsArr = [];
    for(identity in postItems){
    postItemsArr.push(postItems[identity]);
    }
    var serverURL = "appURL" + "?" + operation;
    return dojo.rawXhrPost({
    preventCache: true,
    url: serverURL ,
    handleAs: "text",
    postData: "items=" + this._getItemJSONString(postItemsArr), // dojo.toJson(postItems),
    sync: true,
    timeout: 10000,
    load: function(response, ioArgs) { // The LOAD function will be called on a successful response.
    console.error("Response: ", response);
    return true;
    },
    error: function(response, ioArgs) { // The ERROR function will be called in an error case.
    alert("HTTP status code: ", ioArgs.xhr.status);
    return false;
    }
    });
    }
    });

    Hope this helps!

    Cheers,

    Laca

  43. Laca says:

    @philou

    You may want to simply override the _saveCustom method of ItemFileWriteStore to save your changes on your server. That method is called automatically, whenever the save method of the store is called and there are uncommitted changes to the store.
    ItemFileWriteStore does this automatically for you.

    Here is my method of doing the updates (insert, delete, update) using JSON data:


    dojo.declare("CustomItemFileWriteStore", dojo.data.ItemFileWriteStore, {

    _saveCustom: function(saveCompleteCallback, saveFailedCallback){
    // xhrPost/xhrPut your data back to your server (convert it to the server format first if need be)
    // 'this' keyword refers to the ItemFileWriteStore instance being extended
    var success = true;
    if(this.isDirty()) {
    if(!this._isEmpty(this._pending._newItems)) {
    success = this._postToApp("insert", this._pending._newItems);
    }
    if(success && !this._isEmpty(this._pending._deletedItems)) {
    success = this._postToApp("delete", this._pending._deletedItems);
    }
    if(success && !this._isEmpty(this._pending._modifiedItems)) {
    var modifications = [];
    for(identity in this._pending._modifiedItems){
    // find the modified items as this._pending._modifiedItems contains the
    // ORIGINAL values because the Grid must show the modified items, but
    // we need to send the modifications to App
    if(this._itemsByIdentity){
    modifications[identity] = this._itemsByIdentity[identity];
    }else{
    modifications[identity] = this._arrayOfAllItems[identity];
    }
    }
    success = this._postToApp("update", modifications);
    }
    }
    if(success) {
    saveCompleteCallback();
    }
    else {
    saveFailedCallback();
    }
    },

    _getItemJSONString: function(itemArr){
    var serializableStructure = {};

    var identifierAttribute = this._getIdentifierAttribute();
    if(identifierAttribute !== Number){
    serializableStructure.identifier = identifierAttribute;
    }
    if(this._labelAttr){
    serializableStructure.label = this._labelAttr;
    }
    serializableStructure.items = [];
    for(var i = 0; i < itemArr.length; ++i){
    var item = itemArr[i];
    if(item !== null){
    var serializableItem = {};
    for(var key in item){
    if(key !== this._storeRefPropName && key !== this._itemNumPropName){
    var attribute = key;
    // var valueArray = this.getValues(item, attribute);
    var valueArray = item[attribute] || [];
    if(valueArray.length == 1){
    serializableItem[attribute] = this._flatten(valueArray[0]);
    }else{
    var serializableArray = [];
    for(var j = 0; j < valueArray.length; ++j){
    serializableArray.push(this._flatten(valueArray[j]));
    serializableItem[attribute] = serializableArray;
    }
    }
    }
    }
    serializableStructure.items.push(serializableItem);
    }
    }
    var prettyPrint = false;
    return dojo.toJson(serializableStructure, prettyPrint);
    },

    _postToApp: function(operation, postItems) {
    var identity;
    var postItemsArr = [];
    for(identity in postItems){
    postItemsArr.push(postItems[identity]);
    }
    var serverURL = "myURL" + "?" + operation;
    return dojo.rawXhrPost({
    preventCache: true,
    url: serverURL ,
    handleAs: "text",
    postData: "items=" + this._getItemJSONString(postItemsArr), // dojo.toJson(postItems),
    sync: true,
    timeout: 10000,
    load: function(response, ioArgs) { // The LOAD function will be called on a successful response.
    console.error("Response: ", response);
    return true;
    },
    error: function(response, ioArgs) { // The ERROR function will be called in an error case.
    alert("HTTP status code: ", ioArgs.xhr.status);
    return false;
    }
    });
    }
    });

    Replace “myURL” to your URL. If you don’t use JSON, then you need your own formatting function instead of my _getItemJSONString!

    Hope this helps!

    Cheers,

    Laca

  44. Gregg says:

    dojox.GridRowView – Is here a replacement for this when using the new grid?

  45. Patch says:

    My question still hasn’t been answered, the example above does not work with the current 1.2 release, I’ve changed nothing in the source code I see no errors. I use web dev toolbar to make sure all includes are included.

    To repeat the problem goto
    http://download.dojotoolkit.org/
    download either the latest stable or release
    and replace the dojo dojox and dijit directories in the example
    http://www.sitepen.com/labs/code/grid/dojo_grid_1.2.tar.gz
    with the release ones, it breaks.

    Any idea why?

  46. Dylan says:

    @Patch:

    1.2.0 had a couple of regressions that are being fixed in 1.2.1. That release is imminent (as in expected this week).

  47. philou says:

    @Jeremy Zerr
    @Laca

    Thanks for your suggestions. I have actually not followed exactly what you suggested, but you have put me on the right track!! For the sake of discussion, let me share my approach.

    I have looked for another event which I could hi-jack in order to do the ajax request and I have decided on the doApplyCellEdit function inside the dojox.grid.DataGrid class, which I have subclassed in order to achieve this. When the doApplyCellEdit function is called nothing has been written to the store yet, so I don’t have to do a lot of checking to see if the store has changed. My main reason is that I don’t really understand the store all that well, so I rather not mess with it. Plus, the code is pretty short – see below ;-)

    The only two things I still don’t get:
    1. Getting a handle to the item in the store in order to see if the value has changed seems very odd, is there a better way??
    2. Ideally I would not want to write changes to the store, if the ajax update failed for some reason, but if I put the code to update the store into the “ajax success” part of the script, the store is not updated on success. If anybody can tell me how/why I would be very thankful! (the non-working code is commented out).

    Here’s what I did:

    dojo.declare("custom.dojox.grid.DataGrid", dojox.grid.DataGrid, {
    doApplyCellEdit : function(inValue, inRowIndex, inAttrName) {
    var dbID = this._by_idx[inRowIndex].idty;

    // thsi seems to be to be a very odd way to get to the "currItem" of the store. Is there something better?
    var itemFound = function(item, request) {
    if (item) {
    return currItem = item;
    } else {
    return null;
    }
    };

    this.store.fetchItemByIdentity( {
    identity :this._by_idx[inRowIndex].idty,
    onItem :itemFound
    });

    var oldValue = this.store.getValue(currItem, inAttrName);

    //alert('CellEdit Row: ' + inRowIndex + ' Store ID: ' + dbID + ' Column: ' +inAttrName + ' old Value: ' + oldValue + ' new Value: ' + inValue );

    if (oldValue != inValue) { // if nothing changed don't do ajax
    if (true) { //check for all ok
    var ajaxUrl = 'receive_data_update.php?action=update&part_number=' + dbID + '&column=' + inAttrName + '&value=' + inValue;
    dojo.xhrGet( {
    url :ajaxUrl,
    handleAs :"text",
    handle : function(data, args) {
    if (typeof data == "error") {
    console.warn("error!");
    console.log(args);
    } else {
    if ('OK' == data) { // the php ajax script returns "OK" on sucess
    alert('ajax ok');
    // I would prefer to do the store update below at this point here in the script but it's not working
    // dojo.hitch(this, function(currItem) {
    // this.store.setValue(currItem, inAttrName, inValue);
    // this.onApplyCellEdit(inValue, inRowIndex, inAttrName);
    // });
    }
    }
    }
    });
    }
    }

    this.store.fetchItemByIdentity( {
    identity :this._by_idx[inRowIndex].idty,
    onItem :dojo.hitch(this, function(item) {
    this.store.setValue(item, inAttrName, inValue);
    this.onApplyCellEdit(inValue, inRowIndex, inAttrName);
    })
    });
    }
    });

  48. philou says:

    sorry if this is posted twice, but I don’t see my post…?!

    @Jeremy Zerr
    @Laca

    Thanks for your suggestions. I have actually not followed exactly what you suggested, but you have put me on the right track!! For the sake of discussion, let me share my approach.

    I have looked for another event which I could hi-jack in order to do the ajax request and I have decided on the doApplyCellEdit function inside the dojox.grid.DataGrid class, which I have subclassed in order to achieve this. When the doApplyCellEdit function is called nothing has been written to the store yet, so I don’t have to do a lot of checking to see if the store has changed. My main reason is that I don’t really understand the store all that well, so I rather not mess with it. Plus, the code is pretty short – see below ;-)

    The only two things I still don’t get:
    1. Getting a handle to the item in the store in order to see if the value has changed seems very odd, is there a better way??
    2. Ideally I would not want to write changes to the store, if the ajax update failed for some reason, but if I put the code to update the store into the “ajax success” part of the script, the store is not updated on success. If anybody can tell me how/why I would be very thankful! (the non-working code is commented out).

    Here’s what I did:

    dojo.declare("custom.dojox.grid.DataGrid", dojox.grid.DataGrid, {
    doApplyCellEdit : function(inValue, inRowIndex, inAttrName) {
    var dbID = this._by_idx[inRowIndex].idty;

    // thsi seems to be to be a very odd way to get to the "currItem" of the store. Is there something better?
    var itemFound = function(item, request) {
    if (item) {
    return currItem = item;
    } else {
    return null;
    }
    };

    this.store.fetchItemByIdentity( {
    identity :this._by_idx[inRowIndex].idty,
    onItem :itemFound
    });

    var oldValue = this.store.getValue(currItem, inAttrName);

    //alert('CellEdit Row: ' + inRowIndex + ' Store ID: ' + dbID + ' Column: ' +inAttrName + ' old Value: ' + oldValue + ' new Value: ' + inValue );

    if (oldValue != inValue) { // if nothing changed don't do ajax
    if (true) { //check for all ok
    var ajaxUrl = 'receive_data_update.php?action=update&part_number=' + dbID + '&column=' + inAttrName + '&value=' + inValue;
    dojo.xhrGet( {
    url :ajaxUrl,
    handleAs :"text",
    handle : function(data, args) {
    if (typeof data == "error") {
    console.warn("error!");
    console.log(args);
    } else {
    if ('OK' == data) { // the php ajax script returns "OK" on sucess
    alert('ajax ok');
    // I would prefer to do the store update below at this point here in the script but it's not working
    // dojo.hitch(this, function(currItem) {
    // this.store.setValue(currItem, inAttrName, inValue);
    // this.onApplyCellEdit(inValue, inRowIndex, inAttrName);
    // });
    }
    }
    }
    });
    }
    }

    this.store.fetchItemByIdentity( {
    identity :this._by_idx[inRowIndex].idty,
    onItem :dojo.hitch(this, function(item) {
    this.store.setValue(item, inAttrName, inValue);
    this.onApplyCellEdit(inValue, inRowIndex, inAttrName);
    })
    });
    }
    });

  49. Laca says:

    @philou

    Isn’t the getItem(idx) method of DataGrid that you can use to get the actual value?
    So doesn’t something like this work:

    var oldValue = this.store.getValue(getItem(inRowIndex), inAttrName);

    ?
    I have to admit that I am not a JS programmer, so I usually do everything doing “trial-and-error” :-)

    Nevertheless, I dare to say :-) that your value setting does not work because that code is not called!
    In ItemFileReadStore the fetchItemByIdentity is like (omitting the part which is executed when the store is not loaded yet):


    // Already loaded. We can just look it up and call back.
    var item = this._getItemByIdentity(keywordArgs.identity);
    if(keywordArgs.onItem){
    var scope = keywordArgs.scope?keywordArgs.scope:dojo.global;
    keywordArgs.onItem.call(scope, item);
    }

    So I assume that the function you define in dojo.hitch() is called by this line:

    keywordArgs.onItem.call(scope, item);

    So maybe you should do something like:


    this.store.fetchItemByIdentity( {
    identity :this._by_idx[inRowIndex].idty,
    onItem :dojo.hitch(this, function(currItem) {
    this.store.setValue(currItem, inAttrName, inValue);
    this.onApplyCellEdit(inValue, inRowIndex, inAttrName);
    })
    });

    But as I said I don’t really know JS, so I might have misunderstood things! You’ve been warned :-)

    Cheers,

    Laca

  50. Jason says:

    Do you know if it is possible to not only highlight the entire row when hovering over a cell but the column as well – i.e. crosshairs (ex. http://www.johnhancockfreedom529.com/public/site/performance/daily )

    Thanks

Leave a Reply