Context - Contextual UI Framework
Context is a contextual UI framework. It is based on the Model View Presentor model. The idea is that you have model objects that represent the core data in your application. You also have views that represent the user interface input and output. Finally you have “contexts” that represent a user situation in the application. The logic that ties the models and views resides in the contexts. The main advantages to this model are that you can easily write UI unit tests and you can easily create bridge patterns for supporting multiple widget sets (although only GTK+ is supported at the moment). Context is intended to be extremely minimal. Only the top level abstract classes are included. It is not a widget set! You have to write your own models, views and contexts.
Below you will find some usage examples and a detailed discussion of the design rationale.
Probably the most common design structure for user interface design is Model, View, Controller (MVC). While most people are familiar with this idiom, before I discuss Context I will explain MVC. Hopefully this discussion will dispel possible sources of confusion.
MVC is a common idiom intended to separate UI objects from non-UI objects so as to create a cleaner design that's easy to work with. In MVC there are three main types of objects:
Model - Objects that represent the problem domain abstracted from the UI.
View - UI objects that represent how a user will see information.
Controller - Objects that represent the handling of input from the user.
Exactly how the object model decomposition works, and where functionality is put is a source of much confusion amongst programmers. And in fact, many so called MVC widget sets make it impossible to create controller objects at all. The following is a description of my view of good MVC practice. Keep in mind that it may differ from your own experience or practice. But to keep the discussion relevant it helps to have a shared understanding of these issues.
The easiest place to start discussing is the view. The view represents the visual representation of the data in the UI. So if you have some text, it is the text pane and the character glyphs that the user can see. But it becomes more confusing when we talk about views which contain things like buttons. What data does a button represent?
Let's sidestep that issue for a second and discuss the model. The model contains the problem domain objects. So, if we have some data, the data objects are the model. For textual data, we can create a view consisting of text panes and text that display that data. The situation gets more complicated when we discuss something like a text editor. The text buffer is clearly a model object, but in most widget sets there is a text input widget that contains a text buffer. Can we use the widget's text input buffer for our model? If so, how does that affect the separation of model from UI? Again, let's defer this discussion for a few moments.
Finally, we have the controller. The controller represents the input. I like to think of the controller as a state pattern with commands. The UI sends user input to the controller. This creates commands (e.g., delete a character) and the state pattern interprets the commands, updating the model and views in doing so. However, as with the view and model we run into confusion. In most widget sets the input is handled by the individual widgets (or worse some arbitrary code in the widget library). This input can not be funnelled easily to a controller object to create commands. Instead it updates it's own internal model and view.
As you can see, in each case there is some confusion that can lead to poor separation of model from UI. But I think there is an even greater problem. Where does the problem domain logic go in an MVC program? It might seem obvious that it will go into the model. But from experience I have found this is rarely the case. And with a little thought we can see why.
When we think of model objects, it's quite easy to see where the data will go. And we can also put methods on that data to create objects. But the real user requirements of the program (i.e., the problem domain logic) often ends up in the controller and views. Consider a user requirement for a fictitious program:
When the user presses "delete", the user is asked if they really want to delete the record. If they respond "yes", then the record is deleted. In this case, the table display turns red and the next record is displayed. If they respond "no", the next record is displayed.
Where would you write this code? In the record object (which is clearly a model object)? If so, how would you get the record object to create the view asking the user if they really want to delete the record? Then the controller receives responses from the UI. How does it interrupt the logic of the model to tell it what to do? And how/when do we update the view for the table?
This is virtually impossible. And keep in mind that the model almost certainly shouldn't know about the view and controller, since it represents only problem domain information and not UI.
So the reality is that in complex MVC code, the problem domain data is kept in the model objects, while the logic is kept in the views and controllers. So if you wanted, for instance, to adapt your program for a different widget library, you're pretty much out of luck – the logic is spread all through the UI code.
The thesis of Context is that most problems with MVC arise from it's inappropriate use. MVC works extremely well for simple problems that don't contain a lot of logic. But it scales poorly. Where MVC works exceptionally well is for building widgets themselves.
A widget, for example a text input widget, is self contained. You can have a model object representing a collection of strings. You can have a view object representing how the widget is displayed on the window. And you can have a controller object that takes input and updates the model and view.
Context's idea is to implement these UI widgets using MVC and use a slightly different idiom for problem domain logic and data. The following is Context's design for doing this.
There are again 3 different types of objects:
Model – These are true problem domain objects that represent the state of the running program.
Context – This describes a user scenario with related UI. It creates the appropriate Views, handles input, modifies the Model objects appropriatly and generates output. It contains the user domain logic.
View – Though related, this is not a traditional View at all. It creates and maintains the MVC widgets for a visual context. It takes output from the context and updates the widgets. It takes input from the widgets, and sends it to the context.
Often there is a single View for each context. However, there is no reason that a context can't have several Views, or that several contexts can't share a View. Also, there is usually an abstract View (which defines the input and output that it handles) and a concrete View (which implements the real widgets).
Model objects are pretty self explanatory. The only issue is that the Model objects should not reference any of the contexts nor should they reference any of the Views or widgets. They are truly Model domain objects.
Views are not really like MVC Views at all. The abstract Views simply define the interface for both input and output. Output is generally Model objects that need to be Viewed by the user. In the concrete Views, the data from the Model objects is displayed in widgets. Input consists of commands generated from input from the widgets. Under no circumstances should the Views update a real Model object. Instead they should create a command which it passed back to the context which updates the Model.
You can think of the abstract View as the conduit for I/O. The concrete View implements the endpoint for that conduit. As much as possible, the View should contain no problem domain logic. Anything that is a requirement for the user should be implemented in the context. Automated acceptance tests should be written against the context without regard to the concrete
The Context contains the problem domain logic. It is reposonsible for creating and destroying the views, sending data for the View to display and interpreting commands from the View's input. However, because the Context has references only to the abstract Views, it is shielded from the widget library implementation details. Thus, if you wish to port the program to another widget library you simply need to rewrite concrete View classes.
Note that the Model should never reference eithe the Context or the View. The View may reference the Model objects to display them, but it should not update them directly. If the View needs to update a model object, it should create a command and send it to the context. The Context can reference both the Model objects (including updating them) and it can reference the abstract Views. It should not, however, reference the concrete Views.
Note: In practice I rarely make actual command objects for the Views to send to the Context. Instead I have them simply call a method on the Context. In truth this somewhat breaks the model that the View know nothing about the Context. However, since ruby is a message passing language, a method call is a command of a sort. So I'll do this for efficiency. In this case, you should implement method_missing on the Context object.