Using GrailsUI dataTable tag

The GUI dataTable is the most complicated tag to set up, because there is a lot of data configuration required. You have to correctly set up the column definitions in the tag, and you have to set up the controller to serve up the proper data.

Let’s start from scratch with the basic book / author demo.

Book.groovy
class Book {
String name
String isbn
static belongsTo = [author:Author]
Date publishDate
}

We want a to create a dataTable tag that will display all the Books I loaded up in Bootstrap.groovy. First off, since we know we’ll be using a dataTable GUI tag in our page, we need to declare this to the resources tag in the page head element. The resources tag will include all the YUI CSS and JavaScript files it knows it will need to build a dataTable.

Resources tag in HEAD
<gui:resources components="['dataTable']"/>

Now for the dataTable tag. We’ll set up something basic first.

Initial dataTable tag
<div class="yui-skin-sam">
<gui:dataTable
controller="book" action="dataTableJSON"
columnDefs="[
[key:'id', label:'ID'],
[key:'name', label:'Name'],
[key:'isbn', label:'ISBN'],
[key:'author', label:'Author'],
[key:'publishDate', label:'Published on']
]"
/>
</div>
Note that I’ve wrapped this whole tag in a div with the yui-skin-sam class. This is necessary if I want to take advantage of the default YUI skin. If you want another skin, it can all be provided in local CSS.

So our dataTable is now expecting at least five fields for each row of data it receives: id, name, isbn, author, and publishDate. We need to set up a controller action that will serve this data in JSON format for us. In the dataTable tag, I’ve already decided the action will be called ‘dataTableJSON’, so let’s code it.

Initial dataTableJSON action
def dataTableJSON = {
def books = Book.list(params)
def data = [
totalRecords: Book.count(),
results: books
]
render data as JSON
}

The dataTable will be calling this method to populate itself with data, so the params will be the dataTable’s filtering information, like how to sort, where to start, etc. The GUI dataTable knows only to send params that can be understood by the Grails list domain class function, so we can just send the params directly into Book.list(params).

By default, the GUI dataTable expects there to be a piece of metaData called “totalRecords” that contains the total amount of records in the entire possible dataset that could be displayed by the table. This is necessary to calculate how many pages of data there are in order to render the pagination controls. If this totalRecords is absent or incorrect, the dataTable pagination will not work properly.

The totalRecords must be the total count of records that might be displayed in the table, not the total count of records that is being passed back by the action.

In the data map above, the ‘results’ key is where the dataTable will look by default for its data. This can be changed in the dataTable tag. For example, if you added resultsList=”myResults” in the dataTable tag, and changed the controller action to return myResults: list instead of results: list, things would work fine.

At this point, we should have something that looks very much like this:

Initial Data Table

Not bad, but that date looks nasty because it is handled as a String (there is no type formatting in the dataTable yet). And I would really rather see the Author name rather than id. Also, we don’t really want to see the Book id in the table either. So we’re going to have to dig into our controller and take a firmer grip on how the JSON is being created from our domain class.

dataTableJSON action w/ custom formatting
def dataTableJSON = {
def books = Book.list(params)
// let's convert our book list into an array of maps so we can format
// each value exactly as we want...
def formattedBooks = books.collect {
[
name: it.name,
isbn: it.isbn,
publishDate: new java.text.SimpleDateFormat("MMM dd, yyyy").format(it.publishDate),
author: it.author.name
]
}
def data = [
totalRecords: Book.count(),
results: formattedBooks
]
render data as JSON
}

At this point, we are in total control of the JSON being produced. Grails isn’t converting our domain objects into JSON anymore. We added a custom date format, and we drilled down into the Author to get a name. We also omitted the id field, so we’ll have to go into our tag and omit the id field in the column definitions. If the id field was left in the tag’s columnDefs, there would be an empty column in the table. While we are at it, let’s make all the columns sortable, resizeable, and draggable.

dataTable tag, sortable, resizeable, draggable, & without id
<div class="yui-skin-sam">
<gui:dataTable
controller="book" action="dataTableJSON"
columnDefs="[
[key:'name', label:'Name', sortable: true, resizeable: true],
[key:'isbn', label:'ISBN', sortable: true, resizeable: true],
[key:'author', label:'Author', sortable: true, resizeable: true],
[key:'publishDate', label:'Published on', sortable: true, resizeable: true]
]"
sortedBy="name"
draggableColumns="true"
/>
</div>

Notice that the id column definition is removed, sortable and resizeable were added to each column def, draggableColumns is on, and we added a sortedBy attribute.

The GUI dataTable is sorted by id by default. Because we just removed the id from the column definition, we must explicitly announce how the dataTable should sort upon first render, so the sortedBy=’name’ is absolutely necessary.

Now our table looks like this:

DataTable after some formatting

But there is no mention anywhere of how many total records there are! Sure, you can see how many pages, how many rows per page, and do the math, but who wants to do that? So now lets say we want to display 12 rows per page, and show the total amount of records. Well, this doesn’t have anything to do with our data, so the controller action that provides the data will be fine as it is. But we do need to specify some more information to the tag on how to render the paginator.

dataTable tag, sortable, resizeable, draggable, & without id
<div class="yui-skin-sam">
<gui:dataTable
controller="book" action="dataTableJSON"
columnDefs="[
[key:'name', label:'Name', sortable: true, resizeable: true],
[key:'isbn', label:'ISBN', sortable: true, resizeable: true],
[key:'author', label:'Author', sortable: true, resizeable: true],
[key:'publishDate', label:'Published on', sortable: true, resizeable: true]
]"
sortedBy="name"
draggableColumns="true"
rowsPerPage="12"
paginatorConfig="[
template:'{PreviousPageLink} {PageLinks} {NextPageLink} {CurrentPageReport}',
pageReportTemplate:'{totalRecords} total records'
]"
/>
</div>

So we’ve added rowsPerPage, and a paginatorConfig. The rowsPerPage is really a paginatorConfig value, but for the convenience of not having to define the paginatorConfig only for setting rowsPerPage, it was opened up as a main attribute. All the paginatorConfig values are passed through to the dataTable’s YUI Paginator object upon creation. More information on Paginator templates can be found on the YUI Paginator page.

Now we should have a 12-row table with total records displayed:

Final Data Table

And there you go. Sortable, resizeable, draggable columns, with a total record count and customized data formatting. If you want to have a hands-on look, here is the source code. Just unzip it, then run:

grails upgrade
grails install-plugin yui
grails install-plugin bubbling
grails install-plugin grails-ui
grails run-app
This entry was posted in Uncategorized and tagged , . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

34 Comments

  1. Michael
    Posted November 2, 2008 at 8:03 am | Permalink

    Excellent tutorial for an excellent Grails plugin!

    But “just unzip and run” won’t work, because you need to install the needed plugins first:

    grails install-plugin yui
    grails install-plugin bubbling
    grails install-plugin grails-ui

    Make sure you get the order right …

  2. Posted November 2, 2008 at 8:11 am | Permalink

    @Michael Thanks for the note, I’ve updated the post.

  3. Roshan Shrestha
    Posted November 2, 2008 at 10:28 am | Permalink

    How about inline cell-editing? Does the Grails UI plugin support that out of the box?

  4. Posted November 2, 2008 at 10:33 am | Permalink

    @Roshan No it doesn’t, but that is an excellent feature request. I have not even looked into doing something like that yet, so it may even be possible using the config pass-through parameters. But I would guess not. I logged a JIRA issue for this to get into 1.1.

  5. boardtc
    Posted November 19, 2008 at 11:14 am | Permalink

    Really useful. I initially had left totalRecords out but the reason it was not working was that my domain was in a package - as a result I logged a Jira - http://jira.codehaus.org/browse/GRAILSPLUGINS-628

  6. Posted November 19, 2008 at 11:19 am | Permalink

    @boardtc Thanks for logging that JIRA. I’ve assigned it to 1.0.2. :)

  7. Roshan Shrestha
    Posted December 4, 2008 at 9:58 pm | Permalink

    How would you add a column that is not in the JSON, say a column that displays an icon, such as a “trash” icon to delete a record?

  8. Posted December 5, 2008 at 9:48 am | Permalink

    @Roshan There is really no easy way to do that currently. The best way would be for you to modify your JSON to include all the data you want displayed. You can easily add the some simple markup within the JSON that identifies the row (a span with class and id for example), to which you can add an icon too via CSS. Then in the view, attach listeners to all the spans with that class, which will call the remove URL.

  9. valmanar
    Posted December 16, 2008 at 1:20 pm | Permalink

    I have an error, “Data error”, I have not rows in table, why?
    Thanks

  10. Posted December 16, 2008 at 1:31 pm | Permalink

    @valmanar In order to help you out, you’ll have to give me more information. Can you paste the tag and the controller method that is populating it?

  11. John Rellis
    Posted December 19, 2008 at 8:53 am | Permalink

    Hi,

    I cannot seem to get pagination working. I have stripped the table down to one id field and i still cant get it to work :( My Tag is:

    My Controller action is:

    def dataTest = [[id:1],[id:2],[id:3],[id:4],[id:5],[id:6],[id:7],[id:8],[id:9],[id:10],[id:11]]
    
            def data = [
                    totalRecords: dataTest.size(),
                    results: dataTest
            ]
    
            //println data
            println data as JSON
            render data as JSON
    

    Can anyone help??
    Thanks!

  12. Posted December 19, 2008 at 9:08 am | Permalink

    The problem is that your “totalRecords” field tells the paginator how many total items can possibly be displayed, but you are telling it how many you are sending back to the table.

    For example, if you have 1000 records, but you want to display them 10 at a time, you’ll send a list of ten items in your JSON data, but the totalRecords will say ‘1000′.

  13. John Rellis
    Posted December 19, 2008 at 10:40 am | Permalink

    Ah i see, i would of never of figured it calls the controller each time. Much appreciated!!!

  14. John Rellis
    Posted January 6, 2009 at 5:35 am | Permalink

    Is it possible to conditionally highlight a row in the table, say if a column value goes over a certain value then highlight the row red??? If so, what do you think would be the best way to do this?? Thanks again!

  15. Posted January 6, 2009 at 8:47 am | Permalink

    I’m sorry I don’t have time to give a more detailed explanation, but I’m packing to leave the country today. I can offer you this advice, however. Take a look at this YUI example of conditional row coloring. You’ll have to create a javascript row formatter like this:

    // Define a custom row formatter function
    var myRowFormatter = function(elTr, oRecord) {
        if (oRecord.getData('Quantity') < 40) {
            Dom.addClass(elTr, 'mark');
        }
        return true;
    };

    … within script tags, and get the logic right for the condition. You’ll also have to add the CSS for the ‘mark’ class to format the row properly, but you can look at the example and see all that. Create this formatter before you declare your gui:dataTable, then pass it in as an attribute, like formatRow=’myRowFormatter’. This will include your formatter as a config parameter when the data table is created.

    Let me know how that works for you!

  16. John Rellis
    Posted January 6, 2009 at 9:17 am | Permalink

    Thanks Matthew, my Javascript isn’t too great but i will give it a try. Have a safe trip and Happy New Year!

  17. John Rellis
    Posted January 6, 2009 at 11:04 am | Permalink

    This doesn’t seem to work because when the tagLib renders the property it is rendered as ‘formatRow’: ‘myRowFormatter’ , and the quotes around myRowFormatter probably makes the browser think this is a String rather than a variable (i think). Just to test, I hardcoded ‘formatRow’: myRowFormatter into the tag library and it worked (after i added a reference to the yahoo DOM, “var Dom = YAHOO.util.Dom;” that is).

    Could this be considered a bug when adding properties that are JavaScript variables or have I messed up in some way?

    Thanks,
    John

  18. Posted January 6, 2009 at 2:16 pm | Permalink

    Seems like a bug to me. There may be a workaround by trying:

    YAHOO.util.Event.onDomReady(function() {
        GRAILSUI.myTableId.formatRow = myRowFormatter;
    });

    After the gui:dataTable tag. But I’m not sure if it will work.

  19. John Rellis
    Posted January 9, 2009 at 5:49 am | Permalink

    I logged the posible bug under http://jira.codehaus.org/browse/GRAILS-3806. Its my first bug report in JIRA, so please let me know if i made a hash of it! Thanks for your help!

  20. Posted February 4, 2009 at 8:28 am | Permalink

    Hi,
    Try to do it this way:
    YAHOO.util.Event.onDOMReady(function() {
    GRAILSUI.myTableId.set(”formatRow”, myFormatter);
    });
    Works pretty fine to me :)
    The “formatRow” is not a property, but a “config attribute” - these are set by the set() method of YUI widgets.
    Hope that helps!

  21. Prashant
    Posted February 11, 2009 at 8:17 am | Permalink

    I do not want to display the ID for a particular row but when I edit the row inline, I need the ID of that particular row so that I can update the correct row in the DB. Is there a way to add a column to the DataSource but not display it so that it populates the RecordSet? I tried removing the column from the ColumnDef but then it does not add that data to the underlying RecordSet.

  22. Posted February 11, 2009 at 9:10 am | Permalink

    @Prashant Add hidden=”true” to your columnDefs for id. See the YUI DataTable docs for a full list of applicable columnDef attributes.

  23. Prashant
    Posted February 12, 2009 at 12:07 am | Permalink

    Matthew, the problem with hidden=”true” is that it hides the column by reducing the breadth of the column. The table still has that column but it too narrow to display data.The table looks a little odd with such a narrow column.
    The way this would work is to provide a column in the DataSource for the table but not provide it in the columnDef. Is there a way to achieve this using GrailsUI?

  24. Posted February 12, 2009 at 8:21 am | Permalink

    @Prashant You need to do it this way:

    YAHOO.util.Event.onDOMReady(function() {
    GRAILSUI.myDataTable.getDataSource().responseSchema.fields.push(’id’);
    });

    It’ll make the responseSchema be aware of the new field (’id’), but it won’t be rendered to the dataTable itself because it’s not declared in the columnDefs.
    Of course, your controller has to export the ‘id’ field as well as other fields.
    Good luck!

  25. Prashant
    Posted February 12, 2009 at 11:59 pm | Permalink

    Thanks xis , that works.

  26. Posted February 13, 2009 at 8:49 am | Permalink

    Great solution, @xis. So do you think there is a way to better incorporate this requirement into Grails-UI?

  27. philcms1
    Posted March 11, 2009 at 3:00 pm | Permalink

    Hi all,
    A question from a beginner: I used GrailsUI before and it worked. Now when I access the controller, it doesn’t render the data but opens a pop up in Firefox (or IE) and tells me I am trying to open an object which is application/json from http://localhost:8080. If I open this with Firefox it displays the correct data. I can see a text looking like (not complete here):

    {”totalRecords”:4,”results”:[{"id":1,"class":"Patient","dob":new Date(160556400000),"firstName":"John","lastName":"Doe","sex":"Male"}, ...

    My code is fairly simple:
    import grails.converters.JSON
    def foo = {
    def mPatients = Patient.list()
    //return [ patients : patients ]
    def data = [
    totalRecords: mPatients.size(),
    results: mPatients
    ]
    render data as JSON
    }

    GSP:

    Simple GSP page

    FYI: Grails 1.0.4 and GrailsUI 1.0.2.

    Any idea? Thanks!

    Phil

  28. Posted April 2, 2009 at 8:45 am | Permalink

    @philcms1 Somehow I lost part of your comment. To find an answer to your question, your best bet is to send a message to the grails user mailing list. More info here.

  29. albertgn
    Posted June 4, 2009 at 5:50 am | Permalink

    Hi,
    very nice tutorial, thank you very much.
    I have the following question, I have a domain class with string properties and they have a maxSize:1000.
    The load to the dataTable is ok, but the they are displayed on a single line. I tried to find some settings to apply on the component to make the text (wrap like in Excel) ,but could not find anything related. Maybe making the columns not autoresizeable could do it, but i don’t know how.
    could you please suggest a hint on how to do this.
    thanks

  30. Posted June 8, 2009 at 3:19 pm | Permalink

    @alertgn You can find all the options for a DataTable at the YUI docs page. Take a look at the maxAutoWidth column definition option.

  31. albertgn
    Posted June 12, 2009 at 5:46 am | Permalink

    Matthew,
    Thanks for the response. I already read the documentation and have tried the maxAutoWidth property. This property works for the container, the component itself, but has no effect on the contents. I didn’t find anything that does it automatically, maybe it is in some javascript functions, but I am not good at using javascript.

    I achieved the desired effect using string manipulations and formatting in the controller, but I find such solution very ugly and complicated. especially with many datatables and “unpaternable” contents.
    Thanks again, I’ll keep digging

  32. Posted June 12, 2009 at 8:04 am | Permalink

    @ albertgn You don’t know javascript very well? If you are a UI developer and you trying to do something this complicated with the GUI, I think I have to defer you to a previous post of mine. ;)

  33. snowmanjack
    Posted June 29, 2009 at 9:51 am | Permalink

    @albertgn I had the same issue, large data column that was restricted to one long line. I figured out all the css uses white-space: nowrap, when we want white-space:normal. It’s amazing how many places the css is specified in all the plugins, but I finally found one that expanded the row height based on text. Under \web-app\plugins\grails-ui-1.0.4\css\grailsui\datatable.css, change the following from ‘nowrap’ to ‘normal’.
    .yui-skin-sam .yui-dt-liner { white-space:normal; }

  34. snowmanjack
    Posted June 29, 2009 at 10:05 am | Permalink

    Matthew - Excellent article. While working on the text display issue, I tried setting maxAutoWidth. That seems to cause a bug in my table, such that if maxAutoWidthis set on just one column, it adds blank padding to all columns. No matter the width or text inside any column, 60% of the field is blank. Even if I make them resizable, it will always make the columns wider and keep about 60% blank. Happens in Firefox3 and IE7. Not sure if it’s a bug in GrailsUI or YUI, but thought I’d mention it.

One Trackback

  1. [...] issue that is likely of interest to anyone using the YUI CSS foundation (Reset, Fonts, and Grids).Using GrailsUI DataTable Tag: Matt Taylor writes: “The GUI dataTable is the most complicated tag to set up, because there [...]

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*