Thursday, September 24, 2009

More about Object Marshaller

Should I need to know something about Object Marshaller since Grails has done such good job in this aspect? Most of the time, the answer is not. But if you are doing something about RIA or Ajax, the answer is yes.
Why? The reason is maybe:
1. You want to include some properties in JSON output which was not included in default, such as version. By the way, you can include version through three config option in grails new version. You can find more details on this issue at this or my last post.
2. You like to format some property yourself, such as date formatting
3. You want to output a object into a new JSON string, such as I want to output a relation property's toString() not its id.
4. ...
These days I was doing a RIA application using grails and ext. And, I met all the three requirements I said above. And here is what I got during this project:
1. To implement your marshaller, you can implement ObjectMarshaller or you can use closure when you register it. If you want to use closure, you can do this:

JSON.registerObjectMarshaller(Locale.class){
def rval= [:]
rval['ID']= it.toString()
rval['displayName']= it.getDisplayName()
return rval
}

For method 1, implementing ObjectMarshaller, You can check DomainClassMarshaller.java. But if what you want is just to output the return value of toString() of relation property, you really don't have to do it from the ground up. You can extend DomainClassMarshaller:

class AnotherDomainClassMarshaller extends DomainClassMarshaller{
protected void asShortObject(Object refObj, JSON json,
GrailsDomainClassProperty idProperty,
GrailsDomainClass referencedDomainClass) throws ConverterException {
JSONWriter writer = json.getWriter();
writer.object();
writer.key("class").value(referencedDomainClass.getName());
writer.key("id").value(extractValue(refObj, idProperty));
writer.key("value").value(refObj.toString());
writer.endObject();
}
}

2. After you create your marshaller, you should register it. And the best place in grails application is BootStrap.groovy:
JSON.registerObjectMarshaller(your marshaller instance)

3. If you want to register more than one marshaller, you should provide a priority. REMEMBER IT! Yes, it is a trick. If you ignore it, you will get a surprise: Your marshaller registered just now did not work! After I digged the source of grails, DefaultConverterConfiguration.java, I found the truth: Grails hold all the object marshallers in a treeset, which use priority as a comparator, if you register two marshallers with the same priority, grails will register your marshaller with default-priority(=0) if priority is missing, the second marshaller will not be registered. So my last version of BootStrap.groovy is:

JSON.registerObjectMarshaller(...)
JSON.registerObjectMarshaller(..., 1)
JSON.registerObjectMarshaller(..., 2)
But there is a problem I don't understand: the default ByteArrayMarshaller did not work, and I have to register it myself and provide a priority like this:

JSON.registerObjectMarshaller(new org.codehaus.groovy.grails.web.converters.marshaller.json.ByteArrayMarshaller(), 1)
Who can tell me why?

Wednesday, August 26, 2009

Another way to include version property in your JSON

For those want to include domain class version property in JSON, you can get help from GRAILS-4530: Chaning your grails version or Using the way provided by Sigi(See the description).
But I found another way to get that. Instead of using a new DomainClassMarshaller, I just retrieve the DomainClassMarshaller from the ConverterConfiguration. Here is my code:
import org.codehaus.groovy.grails.web.converters.configuration.ConvertersConfigurationHolder as CCH
import org.codehaus.groovy.grails.web.converters.marshaller.json.DomainClassMarshaller as DCM
import grails.converters.JSON

class BootStrap {

def init = { servletContext ->

def cfg= CCH.getConverterConfiguration(JSON.class)
def marshellers= cfg.orderedObjectMarshallers
for(m in marshellers){
if(DCM.class.isAssignableFrom(m.class)){
m.includeVersion= true
break
}
}
}
def destroy = {
}
}
Maybe it is not as simple as Sigi's solution, but I think it is the complement to this issue.

Monday, August 24, 2009

A new Grails powered site launched

A new Grails powered site launched last Friday: GroovyLive, which is a simple groovy console with a simple tutorial.
Here are features of this simple Grails App:
  • Hosted on Google's GAE
  • Script history
  • Supporting hotkey. You can find these keys though the tooptips of the buttons of console.
  • A simple Groovy tutorial, although not including Groovy MOP.
  • The tutorial is intractive. You can try the script included in tutorial in a command shell, and the content will be the next chapter when the result of that script matches the answer.
It is powerd by:
Since English is not my native language, if there are some error in my tutorial, pls point it to me.
Thanks.

Friday, July 17, 2009

Using a list or a map to construct JSON dynamiclly

How do you contruct a dynamic JSON, especially when you only need to return some fields of an object not a whole object? Using a builder or constructing a string directly? Neither is my favorite.
As time playing grails goes on, I find another way to build a JSON, only two steps:
1. To fill a list or a map with your data.
2. Using "as JSON".
Here is a sample:

def toc= TutorialRepository.toc
int i=1
int j=1
def tocJson=[]
toc.each{ ch ->
def chJson= [:]
chJson['title']= ch.title
chJson['sections']= []
ch.sections.each{ se ->
def seJson= [:]
seJson['title']= se.title
seJson['pos']= "${i}:${j}"
chJson['sections'] << seJson
j++
}
tocJson << chJson
i++
j= 1
}

render tocJson as JSON

It's simple, right? And there are several advantages in this way building a JSON:
1. It makes code clean.
2. You can build more expressive JSON.
3. It is more intuitive as the structure of the list(or the map) is the structure of the final JSON.

Sunday, July 12, 2009

Enhancing your layout with YUI Layout Manager

These days, I did a little grails project. And in this project, I combined sitemesh with yui layout manager. The way is not too difficult ;)
Here is code snippet from layout gsp, eg main.gsp:

<head>
...
<script type="text/javascript">
YAHOO.util.Event.onDOMReady(function () {
var layout1 = new YAHOO.widget.Layout({
minWidth: 1000,
units: [
{ position: 'top', height: 45, body: 'top1' },
{ position: 'left', width: 190, resize: true, body: 'left1', gutter: '2px', header: leftHeader,
collapse: true, scroll: true },
{ position: 'center' }
]
});
layout1.on("render", function(){
var el = layout1.getUnitByPosition('center').get('wrap');
var layout2= new YAHOO.widget.Layout(el, {
parent: layout1,
units: [
{ position: 'top', height: 45, body: 'top2', gutter: '2px' } ,
{ position: 'center', body: 'center2', gutter: '2px' },
{ position: 'bottom', height: bottomHeight, resize: true, body: 'bottom2',
scroll: true, gutter: '2px' }
]
});
layout2.on("resize", function(){
YAHOO.util.Dom.setStyle(center2ContentId, 'height', ((layout2.getSizes().center.h - 7) + 'px'));
});
layout2.render();
});
layout1.render();
init();
});
</script>
</head>
<body class="${pageProperty(name:'body.class')}">
<div id="top1">
<div id="logo"></div>
</div>
<div id="left1">
<g:pageProperty name="page.layout1.left.content"/>
</div>
<div id="top2">
<div id="toolbar"></div>
</div>
<div id="center2">
<g:pageProperty name="page.layout2.center.content"/>
</div>
<div id="bottom2">
<g:pageProperty name="page.layout2.bottom.content"/>
</div>
<g:pageProperty name="page.other"/>
</body>

init() and <g:pageProperty> is just placeholder, you can override it in your any gsp decorated by this layout. For example:

<head>
<script type="text/javascript">
// your own js code is here
...
// define your init function
var init= function(){...};
</script>
</head>
<!-- define your page content -->
<body class="yui-skin-sam">
<content tag="layout1.left.content"></content>
<content tag="layout2.center.content">
<div id="..."></div>
</content>
<content tag="layout2.bottom.content">
<div id="...">
<div class="..."></div>
</div>
</content>
<content tag="other">
<div id="how-to" style="text-align:left;">
......
</div>
</content>
</body>

It is noted that YUI Layout manager in this sample can also be replaced with othere ajax components lib(eg extjs or dojo) without too much hard work.

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.