Monday, April 27, 2009

How to change result of GrailsUI DataTable in an ajax way

DataTable is a good way to show data, but if you can give a way to your application users to change its result at rumtime they will get a better experience. GrailsUI DataTable taglib has an attribute named "params", which value is a query string and will be sent to your data reader since the first time DataTable sends request. So the thing becomes simple, if you can:

  1. change this attribute value at runtime

  2. change your data reader to check this query string, filter data by it and return result as JSON that DataTable needs.


Then you can change the content of DataTable as your users' wish.
There are two implement styles - one is a server-side way; the other is an ajax way, which will be this blog's topic. Since the server-side way is not complicated, I think you can do it yourself. ;)
The Domain class I will use in this example is following, and the fields used for filter are title and closed.
class Question {
String title
String details
Boolean closed
Date dateCreated
Date lastUpdated
}

First, let's see the simple part of the question: How to filter data and return the result. It's really simple, and any grails developer will finish it in no time:
def load = {
params.max = Math.min( params.max ? params.max.toInteger() : 10, 100)
def fields=[]
def fieldsCount= 0

def questionList=[]
if(!params.title && !params.closed){
fieldsCount= Question.count()
questionList= Question.list(params)
}else{
def c= Question.createCriteria()
fieldsCount= c.get{
projections{
count('id')
}
if(!params.closed params.closed=='all'){
like("title", "%${params.title?:''}%")
}else{
def closed= params.closed=='y'? true:false
and{
like("title", "%${params.title?:''}%")
eq("closed", closed)
}
}
}
questionList= Question.withCriteria{
if(!params.closed params.closed=='all'){
like("title", "%${params.title?:''}%")
}else{
def closed= params.closed=='y'? true:false
and{
like("title", "%${params.title?:''}%")
eq("closed", closed)
}
}
maxResults(params.max)
firstResult(params.offset? params.offset.toInteger():0)
}
}

questionList.each{ question ->
fields << [
id: question.id
,title: question.title
,dateCreated: g.formatDate(date:question.dateCreated, format:'yyyy-MM-hh HH:mm')
,lastUpdated: g.formatDate(date:question.lastUpdated, format:'yyyy-MM-hh HH:mm')
,closed: question.closed
,action: """<A href='/codeline/question/edit/${question.id}'>update</A>
${ if(!question.closed){
"<br/><A href='/codeline/question/delete/${question.id}'>delete</A>"
}else{
""
}
}
"""
]
}

response.setHeader("Cache-Control", "no-store")
def result= [
totalRecords: fieldsCount
, results: fields]
render result as JSON
}

Then I will show you the markup of page: a query zone and a datatable:
<div class="roundbg roundspace">
<div class="roundtl"><div class="roundbr"><div class="roundtrc"><div class="roundblc roundpad">
<h5 class="title_ico">Question List</h5>
<div class="list_toolbar"><form>
<label for="title">Title:</label>
<input type="text" maxlength="30" id="title" name="title"/>
<label for="closed">Closed?:</label>
<input type="radio" id="closed" name="closed" value="all" class="cssform_radiobutton" checked/>All
<input type="radio" id="closed" name="closed" value="y" class="cssform_radiobutton"/>Y
<input type="radio" id="closed" name="closed" value="n" class="cssform_radiobutton"/>N
<input type="button" value="submit" class="button" onclick="query">
</form></div>

<gui:dataTable id="questions" draggableColumns="true"
columnDefs="[
[key:'id', sortable:true, resizeable: true, label:'ID']
,[key:'title', sortable:true, resizeable: true, label:'Title']
,[key:'dateCreated', sortable:true, resizeable:true, label:'Created Time']
,[key:'lastUpdated', sortable:true, resizeable:true, label:'Updated Time']
,[key:'closed', label:'Is closed?']
,[key:'action', label:'Action']
]"
controller="question" action="load" rowsPerPage="1"/>
</div></div></div></div>

You may notice the submit button has a handler named "query", which will build a query string and send it to the action given above. Its code is here:
<script type="text/javascript">
var query = function(){
var str= '';
if(YAHOO.util.Dom.get('title').value.length>0){
str+= "title=" + YAHOO.util.Dom.get('title').value;
}

if(str.length>0){
str+= '&'
}
str+= "closed=" + YAHOO.util.Selector.query('#closed:checked')[0].value;
GRAILSUI.questions.customQueryString = str;

GRAILSUI.questions.loadingDialog.show();
GRAILSUI.questions.cleanup();
var sortedBy = GRAILSUI.questions.get('sortedBy');
var newState = {
startIndex: 0,
sorting: {
key: sortedBy.key,
dir: ((sortedBy.dir === YAHOO.widget.DataTable.CLASS_DESC) ? YAHOO.widget.DataTable.CLASS_DESC : YAHOO.widget.DataTable.CLASS_ASC)
},
pagination : {
recordOffset: 0,
rowsPerPage: GRAILSUI.questions.get("paginator").getRowsPerPage()
}
};
var oCallback = {
success: GRAILSUI.questions.onDataReturnInitializeTable,
failure: GRAILSUI.questions.onDataReturnInitializeTable,
scope: GRAILSUI.questions,
argument: newState
};
GRAILSUI.questions.getDataSource().sendRequest(GRAILSUI.questions.buildQueryString(newState), oCallback);
}
</script>

This code needs to be explained carefully:

  • In the example I did not use GrailsUI DataTable taglib's attribute "params", but used "customQueryString" instead. The reason is that the value of "params" will be passed to this attribute. Its purpose is to give you a way to define your own query string, its value will be a part of url that the DataSource owned by DataTable request. And this url will be built by buildQueryString()

  • The remains of query fucntion was mainly copied from GRAILSUI.DataTable.onPaginatorChangeRequest, which is in "%grailsui-home%/web-app/js/grailsui/DataTable.js". And there are two differences:


    • startIndex and recordOffser is zero

    • success handler and failure handler is not onDataReturnSetRows but onDataReturnInitializeTable

Hope you enjoy it.

Wednesday, April 15, 2009

A simple way to customize format for GrailsUI DataTable Cells

I was playing around with grails-ui these days.I really like this plugin, it makes my life easy.

After somedays digging, I find a simple way to custom formatting for DataTable cells. It is so simple that I think you will understand it quickly after reading the following controller action code. There is nothing different in GSP, so its code is omited.
def load = {
params.max = Math.min( params.max ? params.max.toInteger() : 10, 100)
def fields=[]
Question.list(params).each{
fields << [
id: it.id
,title: it.title
,dateCreated: it.dateCreated
,lastUpdated: it.lastUpdated
,closed: it.closed
,updateAction: "<A href='/codeline/question/edit/${it.id}'>update</A>"
,deleteAction: "<A href='/codeline/question/delete/${it.id}'>delete</A>"
]
}
response.setHeader("Cache-Control", "no-store")
def result= [ totalRecords: Question.count(), results: fields]
render result as JSON
}
Do you follow me? That's right! It is "returning html in the result". Be careful, if the html is complex, the best way is to check the YUI Document.