Blog

Tutorial: Master-Detail screen in Flex, backed up by Grails application

20 Feb, 2008
Xebia Background Header Wave

In this blog I will show how to create a simple Master-Detail screen in Flex, how to back it up by an application is Grails and how to publish changes via JMS to all Flash clients. Important topics will be binding and remote java-object invocation in Flex and configuring JMS.
A Master-Detail view is a view with a master list, showing a collection of items and a detailed view, most often consisting of a form, in which the a single item can be edited. Clicking an item in the master list will display the details in the detail view.
Master-Detail
The combination of a framework for building RIA’s (Flex) and a Java based dynamic framework for building services (Grails) seems very promising. I think the high productivity that can easily be achieved by this combination will make it a very attractive choice for your next big project.

In this blog we will implement a "double" master detail, as can be seen in the image above. A person may have multiple addresses. The list is an address book and the view displays a number of attributes of the people in the address book (first name, last name and city and country of their first address). If an item is selected, it is displayed in the adjacent form and the list in that form shows all addresses of that person. If an address is clicked its details are shown in the form below.

Binding

In flex we will use an ArrayCollection to contain the list and this list will be displayed using a DataGrid.

  

...

The mechanisme that is used to get the data from the addressBook into the DataGrid is called "binding". This is done by using curly brackets around the expression, dataProvider="{addressBook}". Binding will make sure that if a change is made in the referenced object (in this case the addressBook or its content) the changes are reflected in the referrer (in this case the DataGrid). Binding works only in one direction! When the value in the referrer (DataGrid) is changed, the bound value (the addressBook) will not be updated. Some components (like the DataGrid) add this functionality and will update the underlying model. For this tutorial we will use the datagrid in editable="false" mode, since we want to edit the data by using the form.
The columns are filled by looking up the properties specified as dataField on the objects in the addressBook. But what are these objects? The are actionscript objects of the Person type.

package
{
  import mx.collections.ArrayCollection;
  [Bindable]
  public class Person
  {
    public var firstName:String;
    public var lastName:String;
    public var addresses: ArrayCollection = new ArrayCollection();
    public function get firstAddressCity():String {
      if (addresses.length == 0) {
        return "";
      }
      return addresses[0].city;
    }
    public function get firstAddressCountry():String {
      if (addresses.length == 0) {
        return "";
      }
      return addresses[0].country;
    }
  }
}

This class defines two simple properties and a collection. In addition it defines two getters that are also bound to in the DataGrid view. It is important to mark this class as [Bindable], so all properties of the class can be used in bind expressions.
Now when a row is selected in the list, we need to bind the values in the selected object to fields in a form. Then if the data in the form changes, we want to update the values in the selected item. Therefore we need Double Binding.

...

...

Now all we need to do is add some buttons and eventhandlers to the components…

...

...

...

… and the master detail view is done! The double binding will keep the fields in the form, the fields in the datagrid and the data in the model in synch.
The master-detail for the address works in much the same way and is a little different. A List is used to display the addresses in stead of a DataGrid and in the list the Address.toString() method is invoked to show the addresses. The eventhandling is also slightly different: A new Address is added to the list right away, instead of having an "Update Address" button.

Remoting

Now editing this data is surely fun, but they are not persistently stored nor shared between users. To do that, we’ll nee to add some server component and remoting. Remoting from Flex can be done using BlazeDS from Adobe Labs. This is a "Java remoting and web messaging technology that enables developers to easily connect to back-end distributed data and push data in real-time to Adobe® Flex™". So we can write a Web application in Java to communicate to the backend using BlazeDS. In stead of starting to write a web.xml and a Servlet, let’s look at Grails for a moment. This is a framework for creating web applications based on Java and Groovy and it has a flex plugin! That looks very promising.
First we create a new Grails application (grails create-app addresses) and download the flex-plugin (grails install-plugin flex). This takes some time to download and thus gives us some time to think. What do we really want to do in this Web application? We only need to domain classes (Person and Address) that should be persisted (this is standard with grails) and then have a service that exposes some methods to the flex front end. Saying this out loud takes almost as much time as writing it in Grails:

class Person {
  String firstName
  String lastName
  List addresses
  static hasMany = [addresses:Address]
  static fetchMode = [addresses:"eager"]
}

The Person class has the same two simple properties and the list of addresses. The list is declared as a hasMany relationship and the fetchmode is set to eager. We’ll need to load the list of addresses for each person that we fetch, since otherwise the client will get LazyInitializationExceptions.

class Address {
  String street;
  int Number;
  String postalCode;
  String city;
  String country;
}

The Address class is far simpler. It only contains the simple properties that the actionscript class contains.

class AddressBookService {
  static expose = ['flex-remoting'];
  def List findAllPersons() {
    return Person.createCriteria().listDistinct {};
  }
  def Person get(id) {
    return Person.get(id);
  }
  def void update(Person p) {
    p.save();
  }
  def void remove(Person p) {
    p.delete(flush:true);
  }
}

The AddressBookService exposes itself as a flex-remoting service. This will make sure the Grails-Flex plugin will register the object with BlazeDS and it is made available to the Flex client.
The changes to the Flex code are pretty complex:

  1. We need to annotate the actionscript classes with the RemoteClass annotation, to indicate which ActionScript class must be used for which Java object.
    [RemoteClass(alias="Person")]

    Here the alias is the Java class name, in this case the Person class in the default package.

  2. We need to change our ActionScript classes to contain the generated domain class properties id and version. These properties are very important for persisting the data, since Hibernate (used by Grails) will find out by looking at the version whether this is a new or existing persistent object and by looking at the id what the identity of the existing object is. If we omit these fields from the action script classes, they will silently get lost during remoting and every object will look like a new object to Hibernate. It will thus do an insert for each new object.
        public var id:*;
        public var version:*;

    We don’t care about the type of these properties from the ActionScript code, so we’ll just use the wild cards.

  3. A RemoteObject is introduced, along with some event handling code.
      
    
    

    The destination of the RemoteObject maps directly on the class name of our grails service. The method declarations in the remote object map to its methods and the result event handler will be called asynchronously with the result of the method call when it is called from the Flex code.

  4. The eventhandling methods for our buttons have to be changed to use the new service. A “Refresh” button is also added to get the complete addressbook from the server.
      
    
    

    Notice that the handling of the result of a call to the service is not defined with the calling code: it is defined in the method declaration of the RemoteObject.
    Since we already structured the code with these changes in mind, the structural changes are fairly minimal. We only need to change the implementation of these three methods to make it work with the Grails service.

To get the application running, the .mxml and .as files need to be copied to the web-app directory of the Grails application. Here the Flex Webtier Compiler can pick up the source code and copile the application on request. if you go to xebia.com /blog/ addresses/ addresses.mxml, the interface will show up and it will be connected to the Grails application through BlazeDS.
That’s really all there is to it! These 6 source files (addresses.mxml, Person.as, Address.as, AddressBookService.groovy, Person.groovy and Address.groovy) form a complete Rich GUI to an online AddressBook. None of the code is really complex and hardly any plumbing is needed.
While this is really great, I’m a little annoyed with this "Refresh" button and the fact that the user will only see changes made by others when he clicks this button. Lets see what we can do…

Server-push through JMS

Flex can be configured to listen to a JMS destination and Grails has a JMS plugin. We can use this to push a new data set to all clients whenever data is changed on the back-end. If we are going to use JMS, we need a JMS-provider, preferably one that can be embedded in a Grails application. Browsing the internet I found ActiveMQ from Apache, that is open source and can be embedded easily in a Spring application. After installing the JMS Plugin, downloading ActiveMQ and adding the necessary libraries to lib folder of the Grails application, we’re all set.

  1. We need to configure a connectionFactory bean in spring.
      
    
              vm://localhost
    
    

    This will create the connection factory and also start an embedded broker.

  2. We need to send the updates out to a topic whenever a change is made. This is done in the AddressBookService.
      def void update(Person p) {
        p.save();
        sendUpdate();
      }
      def void remove(Person p) {
        p.delete(flush:true);
        sendUpdate();
      }
      def private void sendUpdate() {
        try {
          sendPubSubJMSMessage("addresses", findAllPersons());
        } catch (Exception e) {
          log.error("Sending updates failed.", e);
        }
      }

    The sendPubSubJMSMessage method (added to all service classes by the Grails JMS plugin) is used to send a message to the “addresses” topic. The message will contain the new list of addresses.

  3. BlazeDS can be configured to lookup a JMS destination in JNDI. The services-config.xml needs to be changed to contain the JmsAdapter and the new destination for Flex clients to subscribe to.
        
    
                javax.jms.ObjectMessage
                ConnectionFactory
                addresses
                NON_PERSISTENT
                DEFAULT_PRIORITY
                AUTO_ACKNOWLEDGE
                false
    
                    Context.PROVIDER_URL
                    vm://localhost
    
                    Context.INITIAL_CONTEXT_FACTORY
                    org.apache.activemq.jndi.ActiveMQInitialContextFactory
    
                    topic.addresses
                    addresses
    
    

    Configuring the JNDI context for Flex is actually quite tricky. The org.apache.activemq.jndi.ActiveMQInitialContextFactory is a very basic JNDI context and it uses properties to register queues and topics. Normally the properties would reside in a jndi.properties file. Here, these properties are specified in the service-config.xml itself. The property topic.addresses</span is set to addresses to register the topic with the physical name “addresses” (the property-value) in JNDI as “addresses” (the property-key-suffix). This is really an ActiveMQ JNDI oddity.

  4. Now the Flex client can be changed to listen to the topic
    ...
    
    ...

    The Consumer is configured to listen to the “updatesJmsTopic”, that is configured in the services-config.xml. Whenever a message comes in, its body is processed by the setAddressBook method, that will copy all the data to the ArrayCollection to be displayed in the DataGrid.
    Don’t forget to subscribe the consumer at start up. We’ll also load the initial data at start up.

This completes our RIA application. Updates will now be send through JMS to all Flex clients.

Conclusion

Creating a RIA with Flex and Grails is relatively simple. Developers can really focus on developing the GUI or the server side business logic. Most plumbing can be left to the sensible defaults and if not, changes can be made using "standard" frameworks like Spring or Hibernate. Whenever an advanced feature is necessary, developers can lean on their knowledge on Java or frameworks like Sping and Hibernate to quickly intergrate the functionality into the Grails application. I think these features make the Flex/Grails combo a very suitable candidate for developing RIAs on projects of all sizes.
A few remarks:

  1. It seems like duplication to have both the Grails domain classes and ActionScript classes. The Grails Flex plugin website hints at a feature that will generate the ActionScript classes on the fly. On the other hand, it might be usefull to separate the two, since in some occasions we need client-side business logic (like the firstAddressCity and firstAddressCountry properties) on the client-side domain objects. Nevertheless, this duplication is a bit brittle and errors (like typos in field names) are easily made.
  2. Currently the application is using an AMF channel and polling to send the updates to the clients. In Flex Data Services 2, a different type of channel (RTMPChannel) could be used to push data from the server to the client side. Unfortunately BlazeDS does not contain this functionality.

The sources of this application can be downloaded:

The package with the complete grails application with the plugins was almost 60 MB, so to test the application, you’ll have to create a Grails application and add the plugins manually.

Questions?

Get in touch with us to learn more about the subject and related solutions

Explore related posts