Vaadin

Join Vaadin Log In

Groovy, Grails and Vaadin

by Petter Holmström

Introduction

The purpose of this article is to briefly demonstrate how a Vaadin user interface can be added to a Grails application. Prerequisites:

  • Knowledge of Groovy, Grails and Vaadin
  • A working installation of Grails 1.3.0.RC1 or later
  • A Groovy code editor, or an IDE with Grails support

Setting up the demo application

In this article, we are going to add a Vaadin user interface to the application described in this tutorial.

Start by creating a new Grails project:

$ grails create-app tripplanner

Change into the tripplanner directory and create a domain class:

$ grails create-domain-class Trip

Open grails-app/domain/tripplanner/Trip.groovy and add some fields:

package tripplanner

class Trip {

  static constraints = {
  }

  String name
  String city
  Date startDate
  Date endDate
  String purpose
  String notes
}

Then generate a controller and corresponding views:

$ grails generate-all tripplanner.Trip

You can now start the application and try it out:

$ grails run-app

Creating the Vaadin UI

Now, download the latest Vaadin jar and place it in the lib directory. Next, create a new class named tripplanner.VaadinApp and place it in the src/groovy directory. The complete implementation of the class is as follows:

package tripplanner

import com.vaadin.ui.*
import com.vaadin.data.*
import com.vaadin.data.util.*

class VaadinApp extends com.vaadin.Application {

  void init() {   
    def window = new Window("Hello Vaadin!", new SplitPanel(SplitPanel.ORIENTATION_HORIZONTAL))
    setMainWindow window
        
    // Trip editor
    
    def tripEditor = new Form()
    tripEditor.setSizeFull()
    tripEditor.layout.setMargin true
    tripEditor.immediate = true
    tripEditor.visible = false
    
    def saveButton = new Button("Save", new Button.ClickListener() {
      void buttonClick(Button.ClickEvent event) {
        Trip.withTransaction { status ->
          def tripInstance = tripEditor.itemDataSource.bean
          if (!tripInstance.save(flush:true)) {
            window.showNotification "Could not save changes"
          } else {
            window.showNotification "Changes saved"
          }
        }
      }
    })
    
    tripEditor.footer = new HorizontalLayout()
    tripEditor.footer.addComponent saveButton
    
    // Trip browser
    
    def container = new BeanItemContainer<Trip>(Trip.class)
    Trip.list().each {
      container.addBean it
    }
    
    def browser = new VerticalLayout()
    browser.setSizeFull()
    
    def table = new Table()
    table.containerDataSource = container
    table.selectable = true
    table.setSizeFull()
    table.visibleColumns = ["name", "purpose", "startDate", "endDate", "city", "notes"]
    table.immediate = true
    table.addListener new Property.ValueChangeListener() {
      void valueChange(Property.ValueChangeEvent event) {
        if (table.value) {
          tripEditor.itemDataSource = table.getItem(table.value)
          tripEditor.visibleItemProperties = ["name", "purpose", "startDate", "endDate", "city", "notes"]
        } else {
          tripEditor.itemDataSource = null
        }
        tripEditor.visible = tripEditor.itemDataSource != null
      }
    }
    
    browser.addComponent table
    
    def toolbar = new HorizontalLayout()
    
    def addButton = new Button("+", new Button.ClickListener() {
      void buttonClick(Button.ClickEvent event) {
        Trip.withTransaction { status ->
          def tripInstance = new Trip()
          tripInstance.name = "A new trip"
          tripInstance.endDate = new Date()
          tripInstance.startDate = new Date()
          tripInstance.purpose = ""
          tripInstance.notes = ""
          tripInstance.city = ""
          if (tripInstance.save(flush:true)) {
            container.addBean tripInstance
          } else {
            window.showInstance "Could not add trip"            
          }
        }
      }
    
    })
    toolbar.addComponent addButton
    
    def removeButton = new Button("-", new Button.ClickListener() {
      void buttonClick(Button.ClickEvent event) {
        if (table.value) {
          Trip.withTransaction { status ->
            def tripInstance = table.value
            tripInstance.delete(flush:true)
            container.removeItem tripInstance
            table.value = null
          }
        }
      }
    })
    toolbar.addComponent(removeButton)
    
    browser.addComponent toolbar
    browser.setExpandRatio table, 1.0f
        
    mainWindow.addComponent browser
    mainWindow.addComponent tripEditor
  }
}

Most of the code is ordinary Vaadin UI creation code, which we will not discuss in this article. However, the interesting parts are those dealing with domain objects.

We start with the code that fetches the Trips from the database and puts them in a Vaadin container:

def container = new BeanItemContainer<Trip>(Trip.class)
Trip.list().each {
  container.addBean it
}

This code snippet creates a standard Vaadin BeanItemContainer, fetches a list of all the Trips and adds each Trip to the container.

Next, we move on to the code for adding new Trips:

Trip.withTransaction { status ->
  def tripInstance = new Trip()
  tripInstance.name = "A new trip"
  tripInstance.endDate = new Date()
  tripInstance.startDate = new Date()
  tripInstance.purpose = ""
  tripInstance.notes = ""
  tripInstance.city = ""
  if (tripInstance.save(flush:true)) {
    container.addBean tripInstance
  } else {
    window.showInstance "Could not add trip"            
  }
}

As Vaadin is not controlled by Grails, we have to handle transactions ourselves. Fortunately, each Grails domain class has a method named withTransaction that makes this easy (see the Grails documentation for details).

While inside the transaction, we create a new Trip instance, populate it with default values (otherwise the database will not accept it), save it to the database and add it to the container so that it shows up in the UI. Please note, that a real-world application might want to ask the user for data before saving the domain object instead of using default data. Also, the validation errors could probably be handled in a more user friendly way.

Finally, we take a look at the code snippet for deleting Trips:

Trip.withTransaction { status ->
  def tripInstance = table.value
  tripInstance.delete(flush:true)
  container.removeItem tripInstance
  table.value = null
}

We fetch the currently selected Trip from the table, delete it from both the database and the container and clear the selection of the table. Again, we have to handle the transactions ourselves.

Deploying the Vaadin UI

Now when the Vaadin UI has been implemented, we have to deploy it. We start by creating a new servlet in the tripplanner package (found in the src/groovy directory):

package tripplanner

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;

import com.vaadin.Application;

class VaadinAppServlet extends com.vaadin.terminal.gwt.server.AbstractApplicationServlet {
  @Override
  protected Class<? extends Application> getApplicationClass()
      throws ClassNotFoundException {
    return VaadinApp.class
  }
	
  @Override
  protected Application getNewApplication(HttpServletRequest request)
      throws ServletException {
    return new VaadinApp()
  }
}

We have to implement the class ourselves, as the default ApplicationServlet class cannot find the VaadinApp class for some reason.

Finally, we have to configure the web.xml file. As Grails normally uses a default file based on a template, we have to start by installing the template in order to be able to change it:

$ grails install-templates

Now open the web.xml file found in the src/templates/war directory and add the following elements:

<servlet>
  <servlet-name>VaadinServlet</servlet-name>
  <servlet-class>tripplanner.VaadinAppServlet</servlet-class>
</servlet>

<servlet-mapping>
  <servlet-name>VaadinServlet</servlet-name>
  <url-pattern>/VAADIN/*</url-pattern>
</servlet-mapping>

Now you can start the application again:

$ grails run-app

If everything went as expected, you should now be able to access the new Vaadin UI at http://localhost:8080/tripplanner/VAADIN. If you like, you can also open the UI generated by Grails and verify that the Trips show up in both user interfaces (please remember, though, that you have to restart the Vaadin application in order to refresh the list of Trips).

Summary

Using Vaadin and Groovy is really no different than using Vaadin and Java, especially now when Groovy supports anonymous inner classes. Although I had never used Groovy before I wrote this article, its syntax seems to be more efficient than Java's, making it possible to do more with less lines of code. Therefore, I think Groovy and Vaadin may turn out to be a very nice combination.

As for Grails and Vaadin, I am not that convinced. I had never used Grails before I wrote this article so I may be wrong, but to me, it seems like Grails is a Groovy-based MVC framework with a built-in object persistence solution (built on top of Hibernate). As Vaadin and the Grails MVC are very different, the only reason why one would want to use Vaadin and Grails is the persistence solution.

So in conclusion, it is possible to write a Vaadin user interface for a Grails application. However, if you are starting a new project, you should ask yourself what you are winning by basing the application on Grails. If you can't answer that question, it might be a good idea to look at some alternatives (such as plain Groovy) before the coding begins.

Attention all Grails experts out there: If you can find a better way of incorporating Vaadin into grails, or better arguments for why it is a good idea to base Vaadin applications on Grails, please let me know!

0 Attachments 0 Attachments
2500 Views

Average (3 Votes)

  • Comments
Also check this out: http://code.google.com/p/vgrails/

Posted on 4/20/10 5:04 AM.

Top Top
... and this: http://www.dzone.com/links/r/creating_masterdetail_forms_with_vaadin_and_grails.html

Posted on 5/31/10 11:43 AM.

Top Top
Don't forget the grails vaadin plugin http://grails.org/plugin/vaadin

Posted on 8/24/10 2:07 PM.

Top Top