Grails: alternate views for mobile (iPhone) clients

I figured out a semi-cool setup to provide alternate views if the client is found to be, for instance, a Mobile Safari browser on an iPhone. I wanted to share my findings since I’ve seen related questions asked here or there, and I have a question at the end about a potential improvement.

First, I made a filter:

detectMobile (controller:'*', action:'*') {
  before = {
    if (request.getHeader('user-agent') =~ /(?i)iphone/) {
      request['isMobile'] = true
    } else {
      request['isMobile'] = false
    }
    return true // keep processing other filters and the action
  }
  after = { }
  afterView = { }
}

Next, I added the following to a controller:

def afterInterceptor = { model, modelAndView ->
  if (request['isMobile']) {
    modelAndView.viewName = modelAndView.viewName + "_m"
  }
}

For that controller, if a request comes in from a mobile device, a view ending in “_m” is loaded, instead of the default view. The view “list.gsp” would have a counterpart mobile view “list_m.gsp”, and that’s what would be served to iPhone clients. That’s it, so far.

One bit that would be handy would be to add this closure to every controller, rather than have to copy and paste it to controllers that support mobile views. I’ve been meaning to see how the rest plugin dynamically adds methods to every controller in case that is similar to what I’m trying to do, but if anyone knows offhand what I need to do (or read) to automatically get the closure in every controller, please let me know.

6 thoughts on “Grails: alternate views for mobile (iPhone) clients”

  1. Nice post. I wonder if you could have your controllers extend a base class with the closure.

    I’ve thought about this problem too. I haven’t tried it yet – but was planning to use different sitemesh templates for mobile clients. In theory the views would be generic, and via sitemesh templates, different formatting (css includes) could be applied. The goal of my thinking is to apply a little DRY and avoid replication of code by not having diff GSPs for different clients.

  2. Nice post! I agree with the previous commenter, IMO sitemesh would be a better approach to this problem. You could add a MobileDecoratorMapper to the sitemesh.xml, and have the mapper inspect the user agent string to select different layouts. If you need different content displayed to mobile users, then your approach would be the preferable way to manage things. Great topic, thanks for sharing!

  3. Very useful post! Thanks! I definitely would recommend creating a BaseController class and having your controllers extend that class. If you don’t want to do that, and you are using the scaffolded controllers, you should look at the “grails install-templates” command and modify the controller template that is used there. Adding the method to that template will cause all newly generate controllers to have the method.

  4. Thanks for this post and thanks Rich Kroll for the sitemesh decorator mapper hint.

    I like the sitemesh way, cause there is already a sitemesh AgentDecoratorMapper.

    So I simply extended the sitemesh.xml with

    <decorator-mappers>
      <mapper class="com.opensymphony.module.sitemesh.mapper.AgentDecoratorMapper" >
        <param name="match.iPhone" value="mobile" />
      </mapper>
      <mapper class="org.codehaus.groovy.grails.web.sitemesh.GrailsLayoutDecoratorMapper" />
    </decorator-mappers>

    Thats all you have to do. Now sitemesh tries first to resolve your view-mobile.gsp and fallback to view.gsp if it does not find any view-mobile.gsp

  5. But in the meanwhile I discovered, that this only helps to resolve the layout gsp, not the view mapping convention.

    Probably you can use normal spring interceptors http://www.vaannila.com/spring/spring-interceptor.html
    That would be like your approach, but a little bit more DRY ( so without adding afterInterceptor for every controller).

    But I am still not sure about the best approach for this task.

Comments are closed.