Desktop Development - The Fun Way

Tutorial - Griffon: Building Desktop Applications with Groovy - Part 2

          

Building the UI

Now that we have seen a bit of code in the Views, let’s continue with this MVC member, we’ll cover the other two in a moment. In the spirit of keeping things simple we’ll update the UI so that it looks like Figure 3.

Figure 3: New looks for the address book application

Let’s decompose each section. On the left we see a white space with a title ‘Contacts’. This will be a list that holds all the contacts in our address book. In the middle we see a form that we may use to edit the details of a particular contact found in the list. Next on the right we discover a series of buttons that will perform operations on a contact. You may also appreciate a menu named ‘Contacts’ which contains menu items with the same names as the buttons. Listing 2 describes the updated AddressbookView.

Listing 2 - AddressbookView Updated

 

package addressbook
application(title: 'Addressbook',
  pack: true,
  resizable: false,
  locationByPlatform:true,
  iconImage: imageIcon('/griffon-icon-48x48.png').image,
  iconImages: [imageIcon('/griffon-icon-48x48.png').image,
               imageIcon('/griffon-icon-32x32.png').image,
               imageIcon('/griffon-icon-16x16.png').image]) {
    menuBar {
        menu('Contacts') {
            controller.griffonClass.actionNames.each { name ->
                menuItem(getVariable(name))
            }            
        }
    }
    migLayout(layoutConstraints: 'fill')
    list(model: eventListModel(source: model.contacts), 
         constraints: 'west, w 180! ',
         border: titledBorder(title: 'Contacts'),
         selectionMode: ListSelectionModel.SINGLE_SELECTION,
         keyReleased: { e ->  // enter/return key
             if (e.keyCode != KeyEvent.VK_ENTER) return
             int index = e.source.selectedIndex
             if (index > -1) model.selectedIndex = index
         },
         mouseClicked: { e -> // double click
             if (e.clickCount != 2) return
             int index = e.source.locationToIndex(e.point)
             if (index > -1) model.selectedIndex = index
         })
    panel(constraints: 'center', border: titledBorder(title: 'Contact')) {
        migLayout(layoutConstraints: 'fill')
        for(propName in Contact.PROPERTIES) {
            label(text: GriffonNameUtils.getNaturalName(propName) + ': ',
                        constraints: 'right')
            textField(columns: 30, constraints: 'grow, wrap',
                text: bind(propName, source: model.currentContact,
                           mutual: true))
        }
    }
    panel(constraints: 'east', border: titledBorder(title: 'Actions')) {
        migLayout()
        controller.griffonClass.actionNames.each { name ->
            button(getVariable(name), constraints: 'growx, wrap')
        }
    }
}

We can see the menuBar node we mentioned earlier. We can also see three main components: a list that will be placed on the west side; a panel in the middle that somehow creates a pair or label and textField for each property found in a list of properties. Then we see the third component, a panel that holds buttons. The code may look a bit magical at first glance but really what we’re doing is taking advantage of the conventions laid out by Griffon. A View MVC member has access to the other two members, that is, the Model and the Controller. The job of the View is to lay out the UI components. The job of the Controller is to hold the logic that reacts to user input. The job of the Model is to serve as a communication bridge between View and Controller. We can see references to a model and a controller variables in the View; these variables point to their respective MVC members.

Next we’ll update the Model found in griffon-app/models/addressbook/AddressbookModel.groovy. Here we’ll make sure the Model keeps a list of contacts in memory; it will also hold a reference to the contact currently being edited. We’ll make use of GlazedLists (a popular choice among Swing developers) to organize the list of contacts. Listing 3 shows all the code that’s required to build the list and keep a reference to the currently edited contact. Now, the contacts list has a special binding defined in relation to its elements. Whenever an element is edited it will publish a change event that the list will intercept; the list in turn will update whoever is interested in lists changes. Looking back to Listing 2 you can see that the list definition makes use of a listEventModel node. This is exactly the component that will notify the UI whenever an update is available, this repainting the affected region. And we only had to connect a pair of components for this to happen! The AddressbookModel works with two domain specific classes: Contact and ContactPresentationModel. The first can be seen as a plain domain class, as it defines what a contact should be in terms of simple properties. The latter is an observable wrapper around the Contact class. Presentation models are usually decorators that can support binding operations. We’ll see these two classes in just a moment.

Listing 3

 

package addressbook
import groovy.beans.Bindable
import ca.odell.glazedlists.*
import griffon.transform.PropertyListener
import java.beans.PropertyChangeEvent
import java.beans.PropertyChangeListener

class AddressbookModel {
    final EventList<ContactPresentationModel> contacts = 
             new ObservableElementList<ContactPresentationModel>(
        GlazedLists.threadSafeList(
        new BasicEventList<ContactPresentationModel>()),
        GlazedLists.beanConnector(ContactPresentationModel)
    )
    
    final ContactPresentationModel currentContact = new ContactPresentationModel()
    
    @PropertyListener(selectionUpdater)
    @Bindable int selectedIndex = -1
    
    private selectionUpdater = { e ->
        currentContact.contact = contacts[selectedIndex].contact
    }
    
    AddressbookModel() {
        currentContact.addPropertyChangeListener(new ModelUpdater())
    }
    
    private class ModelUpdater implements PropertyChangeListener {
        void propertyChange(PropertyChangeEvent e) {
            if(e.propertyName == ‘contact’ || selectedIndex < 0) return
            contacts[selectedIndex][e.propertyName] = e.newValue
        }
    }

    void removeContact(Contact contact) {
        currentContact.contact = null
        ContactPresentationModel toDelete = contacts.find { 
            it.contact == contact 
        }
        if(toDelete != null) contacts.remove(toDelete)
    }
}

 

But before we show the domain let’s cover the final MVC member: the Controller. It’s the job of the controller to react to user input and orchestrate the flow of information. For now we only need to fill in the blanks to make the application work again, for example by pasting the code shown in Listing 4 into griffon-app/controllers/addresbook/AddressbookController.groovy

Listing 4

 

package addressbook
class AddressbookController {
    def model
    void newAction(evt) { }
    void saveAction(evt) { }  
    void deleteAction(evt) { }

 

Do you remember that Models mediate data between Controllers and Views? That’s precisely why there’s a model property on the controller class. Griffon sports a basic dependency injection mechanism that will assure each MVC member can talk to the other two as long as certain properties are defined in their respective classes.

           

Pages

Andres Almiray
Andres Almiray

What do you think?

JAX Magazine - 2014 - 06 Exclucively for iPad users JAX Magazine on Android

Comments

Latest opinions