Showing posts with label grails. Show all posts
Showing posts with label grails. Show all posts

Thursday, July 30, 2009

Detect an AJAX request

Many web applications use AJAX nowadays. In a typical scenario, one fills out a form, which is then submitted to the server. If you don't use AJAX or JavaScript is disabled, your form should still submit with no problems. And you receive a new page. If you use unobtrusive enhancement via JavaScript, the form is submitted to the server and the page is updated with the result without a full page reload.

While developing with Grails writing Java and Groovy code, I noticed that a typical pattern is to create two actions in a controller. One to handle normal HTTP POST (e.g. save action) and one to handle HTTP POST coming from an AJAX request (e.g. saveAjax action). The two actions are almost identical. They perform the same business logic. The only difference is what is returned from each action. A normal action renders a new page. An AJAX one returns whatever your flavor is: XML, JSON, HTML, plain text.

So is there a way to tell the requests (ordinary and AJAX) apart?

You guessed it, there is. AJAX request comes with a special header - X-Requested-With. I usually have my controllers to extend my BaseController, which has a couple of useful methods that I use. isAjax(request) is one of them. And this is what it looks like.


public static boolean isAjax(request) {
return "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
}

I hope you'll find it useful. The all you need to do in your action is this:


def save = {

// business logic
// ...

if (isAjax(request)) {
render([ /* data */ ] as JSON)
}
else {
// render or redirect here
render(view: 'save', model: [ /* model data */ ])
}
}

Grails.org has a new home page design

I absolutely love the new design of the grails.org home page. It's sleek, clean, easy to find what you need. Simply brilliant!

Monday, November 17, 2008

Grails and 404 Page Not Found

Every good website should have a nice 404 page. This is the page that is displayed when your customers or clients navigate to a destination that does not exist, by typing an invalid URL.

Grails does not give you a nice implementation of 404 error (or any other than 500 error). You need to implement it yourself.

If you search the Internet for examples of how to implement 404 Page Not Found error in Grails, most of the results talk about the following scenario:

class UrlMappings {
static mappings = {
"500"(controller:"errors", action:"serverError")
"404"(controller:"errors", action:"notFound")
"403"(controller:"errors", action:"forbidden")
}
}

The previous piece of declaration resides inside UrlMappings.groovy and what it means is that if an error occurs, the request is redirected to errors controller which will execute in case of 404 the notFound action. This action could be as simple as

def notFound = {
render(view:"/notFound")
}

This is all good, but if it is all you need to do (render a view - notFound.gsp) you may as well express it declaratively inside UrlMappings.groovy

class UrlMappings {
static mappings = {
"500"(view:'/error')
"404"(view:'/notFound')
"403"(view:'/forbidden')
}
}

This way you won't need a controller class at all. All you need are GSP files in the locations declared in the mappings.

References

Wednesday, November 12, 2008

Grails Logging

Configuring logging in Grails is not as simple one would think. Grails uses Log4J as its logging framework. Log4J configuration is stored in the log4j.properties file. In Grails, this file is generated from Config.groovy source file.

The official Grails Logging documentation says that if you want to have a different logging levels for let's say a specific controller, all controllers and the rest of your application, you should define the logging levels as:

grails.'app.controller.YourOtherController'="off,stdout"
grails.'app.controller'="info,stdout"
grails.app="error,stdout"

Yes, those ticks are required, otherwise you get an error message (No such property: context for class: java.lang.String). Don't ask me why. What is also important is the order. Children need to go first.

So that is what the official documentation and few blogs say.

Grails allows you to configure your production, development and testing environments differently if required. I configured Log4J the way that common properties are grouped and placed outside the environment dependent ones. But no matter how I tweaked it, I could not make it to work. For some reason the parent's or child's definition overrides the other, or causes an error.

But there is a solution to this problem after all. Instead of dot notation, use curly braces. Like in the following example:

environments {
development {
...
log4j {
appender.stdout = ...
rootLogger="error,logfile,stdout"
logger {
grails {
app {
controller="debug"
}
}
}
}
}
production {
...
}
}

// log4j configuration
log4j {
appender.logfile=...
appender.stacktraceLog=...
rootLogger="debug,logfile"
logger {
grails="error"
StackTrace="error,stacktraceLog"
...
}
additivity.StackTrace=false
}

This works. Tested with Grails 1.0.3.

Related articles:

Friday, November 07, 2008

Fatal Error in the logs by Grails

I recently started working on a project using Grails and the following message in the logs appeared consistently and annoyed me quite a lot.

[Fatal Error] :-1:-1: Premature end of file.

So I started digging through the code and searching the web for an answer. And I found it. It is a bug that has been reported to Grails and you can find it at GRAILS-3088. It apparently affects Grails 1.0.3 (which I am using) and it should be fixed in next release, 1.0.4.

The bug is triggered with each request from Firefox 3 and Opera 9.5 and even Firefox 2 if you render the response as "text/xml" using

withFormat { xml { render(contentType:"text/xml"){ ... } } }

The current workaround suggested by Graeme Rocher is to edit Config.groovy file and remove (or comment out) "text/xml" from the Grails MIME types mapping

grails.mime.types = [ html: ['text/html','application/xhtml+xml'],
// xml: ['text/xml', 'application/xml'],
text: 'text-plain',

This works for me. No more annoying messages in the logs. I am not quite sure how or if it affects XML responses. But I do not need those for now, so I will worry about that later, if I need to


Creative Commons License This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 2.5 License.