Advanced Guide to MVC in Pentaho XUL Applications
MVC in Pentaho XUL applications can be achieved by architecting your application using the pattern we will detail here. As a refresher, the purpose of the MVC pattern is separation of concerns in UI applications. The idea is to keep a clear separation between the data we would like to display and how that data is being displayed. In Pentaho XUL framework (PXF) the view layer is represented at least partially in a xul dom object (typically coming from a xul file). I say "partially" because you could consider view logic to be part of the view and view logic is not included in the XUL DOM.
The diagram below shows how Pentaho XUL MVC would operate in an example UI widget project Foo. Starting from the left we see that the XUL file foo.xul is represented in your application as a XulDom. The XulDom is what we consider to be the View of our MVC implementation PXF by way of the XulDom is responsible for instantiating and laying out UI components, however, as we mentioned earlier view logic is not handled by the XulDom. The responsiblity for handling view logic rests on the Controller. View logic is comprised of component state changes and events. These two classes of view logic are handled differently in the Controller. As depicted in the diagram, user interface events are handled directly in the controller, whereas state changes are mapped by the controller to a model.
Events vs State Changes - the story
A major motivation of introducing MVC in XUL applications is to remove all XUL dependencies from the backing model. In a rudimentary MVC implementation, the housekeeping work of wiring up XUL components to the backing model would be handled in the controller. Fortunately, we were able to take one more step in minimizing XUL dependencies by implementing XUL Binding. What this means is now it is possible for your controller to have zero XUL dependencies. I say possible because this is not always the case. There are some limitations to XUL Binding. XUL Binding is simply the synchronization of two Java Bean properties and since user interface events such as a button click are (rightly so) not represented as a bean property, they cannot be bound. So here is the important distinction, UI _events_ must be handled in their own handler methods within your controller while component _state changes_ will be bound to your model in the onLoad method of your controller. So back to my statement that it is possible for your controller to have zero XUL dependencies.. this is true if your view doesn't require any event handling, but if it does, your controller will have to handle the event explicitly. As such your controller will typically have references to XulContainer subclasses (the events will typically launch dialogs and windows). Even so, your controller should still have zero references to XulComponent subclasses since changing a component in a controller means setting bean properties on it, which would be better handled by a binding.
Data Binding and Component State Changes
It is the responsibility of your Controller implementation to setup bindings between your XUL components and your model (a POJO/Java Bean). What do we mean by "binding"? Binding is a way to associate bean properties so that when one changes, the other is kept in sync. Binding can be unidirectional or bidirectional, but we won't get into that here. For more information on this see XUL Bindings. For the purpose of this document we will assume that a binding is always bidirectional meaning when either side of the binding changes, the other will reflect the change. This is useful in UI applications because we can tie, say, a text box's value to a model bean String property. Consider the following XUL file snippet that defines a text box component by id "nameTextBox". Notice that there are no attribute specifying event handler calls; these are not needed when using data binding.
<textbox id="nameTextBox">
Here is a snippet of the corresponding controller onLoad method. Notice how you create a binding between the model's name property and the nameTextBox XUL component value property.
public class FooController extends AbstractXulEventHandler { ... onLoad() { bind(fooViewModel, "name", "nameTextbox", "value"); } ... }
Behind the scenes this bind method is retrieving a XulComponent by id "nameTextBox" and linking the name property of the model to the value property of the XulComponent instance. Now let's say in addition to binding the name property to a text box we want to wire the enabled state of an OK button to a boolean bean property on the model. We would just add another bind call that ties the two properties together, notice the "!" expression which does an in-flight negation of boolean value. (Many XUL components have a disabled attribute--which is sometimes hard to wrap your head around; typically we like to think in the positive.)
onLoad() { bind(fooViewModel, "name", "nameTextbox", "value"); bind(fooViewModel, "okEnabled", "okButton", "!disabled"); }
How do I know which properties to bind?
A helpful point to realize is that binding is not magic, it is simply a synchronizing of 2 java bean properties. Bean A is your XulComponent subclass and Bean B is your model. If you want to know what component properties you can bind simply lookup your xul component, say XulTextbox, and take a look at its bean properties. You can configure a binding to any property of your xul component object.
Handling of UI Events
In the diagram you will see that the XulDom fires "events" to the FooController. As we mentioned before, these events are not state changes of a XulComponent, rather these are launching and routing operations such as displaying a dialog. Events are declared in xul source file as tag attributes, e.g.:
<button id="searchDialog" label="Search" onclick="fooController.displaySearchDialog()" />
When PXF parses this tag, it will register an onclick property on the button component such that when the button is clicked PXF will try to find a registered event handler by the name of "fooController" and call it's displaySearchDialog method.  You register an event handler on a XulDomContainer object like so (you will likely do this during your application's initialization):
XulDomContainer container = xulLoader.loadXul("/path/to/my/xul/source/file.xul"); container.addEventHandler(fooController);
Foo Example Source
// Manages the state of the view or form. This includes component values as well as enablement, // visibility and form validation. Your ViewModel could delegate to or synchronize with a (persistent) domain // model if you require. public class FooViewModel extends ViewModel { private boolean okEnabled; private String name; public boolean isOkEnabled() { return okEnabled; } // Any bean property that is using for binding must fire property change events public void setOkEnabled(boolean okEnabled) { this.okEnabled = okEnabled; firePropertyChange("okEnabled", null, okEnabled); } public String getName() { return name; } public void setName(String name) { this.name = name; firePropertyChange("name", null, name); validate(); } // A very simple validator that allows the user to procede if there is anything in the name field. // Notice that we are not manipulating UI component here to achieve this, just a bean property. private void validate() { setOkEnabled(!StringUtils.isEmpty(name)); } }
// The Controller has two roles 1) setup bindings for view state and 2) handle UI events // A Controller should not contain references to XulComponent or subclasses. If you find yourself // referencing these, you should probably be binding to them instead. public class FooController extends AbstractXulEventHandler { private FooViewModel fooViewModel; public FooController() { fooViewModel = new FooViewModel(); //FYI we prefer to inject the models via an IOC container, but for the sake of simplicity... } // This is the name with which to register instances of this controller. Typically we would set this, but for simplicity we hardcode it. public String getName() { return "fooController"; //this name should match event handler aliases in the XUL source file } // Here we are binding two properties from the model to properties on two XUL components. public void onLoad() { bind(fooViewModel, "name", "nameTextbox", "value"); bind(fooViewModel, "okEnabled", "okButton", "!disabled"); } // The XUL framework will find and call this method when the Search button is clicked public void displaySearchDialog() { XulSearchDialog searchDialog = new XulSearchDialog(); searchDialog.show(); this.name = searchDialog.getName(); } }
Quick Reference
Model
- Represented as a Java Bean (with property change support)
- "Models" view state as bean properties
- Contains view logic such as enablement and visibility
- Applies form validation
- Contains *no* references to Xul of any kind
View
- Represented in the XUL dom (via XUL source file)
- Contains syntax for creation of UI components
- Layout of UI components
Controller
- Represented as a Java class that implements XulEventHandler
- Binds UI state to the model
- Performs UI evnt handling (not related to component state) such as launching of dialogs (XulDialog and XulWindow)
- Should *never* reference XulComponent or subclasses