Tuesday, 1 May 2012

ZK Grails + MVVM

The 6.0 release of ZK introduced the MVVM pattern and they have been fairly well documented in their website. Using the new pattern with zkgrails is another story. Its major attraction to me is that it is farily easy to unit test a ViewModel, compared to testing a Composer(has anyone been able to do this?). I decided delved into it today and figured that the best way to do it was to start a brand new project and try different things. In the end, I did the hard way first and later found out a rather easier way. So, without further ado, here we go.

The source code for the sample app can be found on github. I won't explain how to install grails and the zkgrails plugin. The domain class Person is shown here: First I needed a form to create persons, so I created the corresponding zul: create-zul com.example.create This creates a zul under grails-app/zul The corresponding view model was also created: create-viewmodel com.example.Person A PersonViewModel.groovy was added to grails-app/viewmodels/com/example/ ZK has some issues keeping hibernate sessions, so as a work around, zkgrails has facades. We will use a PersonFacade to interact with GORM: The test for the PersonViewModel: And finally our PersonViewModel: A couple things to point out before we continue.
  1. @WireVariable is needed to wire a spring bean. All facades created by zkgrails are spring beans, so we need this annotation to wire it.
  2. @NotifyChange is used to notify the corresponding variables of any changes.
  3. @Command is the equivalent of an onCLick_ method. This can be used to bind an event in the UI to a method in the ViewModel.
  4. A custom validtor to ensure that the first name is added.
The corresponding zul: ZKgrails has a script caleld create-zul-viewmodel, that creates a zul template an ViewModel. However, I didn't know about this script, so I created them separately. If you do it this way, then you have to modify the composer to extend org.zkoss.bind.BindComposer instead of GrailsForwardComposer. I wasted hours before I figured this one out. Then a view model has to be assigned to the component. This requires the following assignments:
  • @id('vm') : this assigns viewmodel to a variable(vm) allowing us to access it throughout our view.
  • @init('com.example.PersonViewModel') : assigns an instance of this class to vm.
  • validationMessages="@id('vmsgs')" : all validation messages from our custom validators can be accessed with this variable(vmsgs).
There is also a label that simulates a flash message. This is bound to a variable in the viewmodel called successFlash: @load(vm.successFlash). This will assign a value to the label that corresponds to the successFlash proeprty in PersonViewModel. Each of the elements of the form are bound to the viewmodel also. In this example, the values for a person are stored in map. For example: @bind(vm.person.firstName) For custom validators we have an additional annotation : @save(vm.person.firstName, before='save') @validator(vm.firstNameValidator) This is saying that before the command 'save' is executed, we should run firstNameValidator() on person.firstName. If there are any errors, we can access those errors with the perviously declared vmsgs. In this example, it is being loaded from viewmodel and displayed in a label:
<label value="@load(vmsgs[firstNameTextbox])" style="color:red;"/>

Finally, we have to tell the form what to do when its submitted. This is done by assigning a 'command' to a button:
<button id="createButton" label="Create" onClick="@command('save'))" />

To view the form you can go to http://localhost:8080/com/example/create.zul Now, the right way to create the view model and the corresponding zul is as follows:

create-zul-viewmodel

This will create a zul and a corresponding view model wired together, without the need to have the composer extend BindComposer: That's it. The surface of MVVM in zkgrails.

7 comments:

  1. Thanks, I appreciate your effort, it helped get me going quickly!

    Mark

    ReplyDelete
    Replies
    1. You're welcome. I try to cover a little more details in my other blog: http://stumblingoncode.wordpress.com

      Delete
  2. hi, i've downloaded your sample app from github,
    when i open list.zul, i encountered this error :
    "cannot convert [] of type class java.util.ArrayList to interface org.zkoss.zul.ListModel"
    did i miss something?
    btw, i'm using grails 2.1 and zk-2.0.4

    ReplyDelete
    Replies
    1. This was created with a pretty old version. I know there have been lots of fixes seen then. What I think is happening is that persons in ListViewModel needs to be of type org.zkoss.zul.ListModel. ListModel is an interface, so you will need to use org.zkoss.zul.ListModelList.

      Delete
  3. This is what I did to have this issue resolved. Just in case someone else runs into this problem:
    1. Specify the type of the ViewModel to Set. e.g. "def persons" to "Set persons"
    2. run-app. (The problem seemed to go away already at this point, and I have only changed one of the VMs)
    3. Change the type "Set persons" back to "def persons" and the application seemed to stay intact.

    ReplyDelete
  4. Hi, thanks for sharing that! I'm still a little bit confused about the zul. You applied a Composer and a ModelView to the view, is that correct? They work together?

    ReplyDelete
  5. Yes, both composer and ModelViews are applied to the zul. The composer is a 'controller' and the ModelView is that model(data) that is passed to the view to display.

    ReplyDelete