Views in Zope3
are implemented as multi-adapters.
This is pretty much a summary of the Zope3 presentation stack, so let us
dig into it.
History: Views as in M-V-C
First of all, the term
View is
borrowed from the
Model-View-Controller design terminology
(a.k.a MVC).
From http://www.enode.com/x/markup/tutorial/mvc.html:
Model-View-Controller (MVC) is a classic design pattern often used by
applications that need the ability to maintain multiple views of the same
data. The MVC pattern hinges on a clean separation of objects into one of
three categories — models for maintaining data, views for displaying all or
a portion of the data, and controllers for handling events that affect the
model or view(s).
The pattern is 25 years old (cf
Applications
Programming in Smalltalk-80(TM): How to use
Model-View-Controller).
Simplified MVC (the UI-delegate)
In Zope3 however,
View and
Controller are combined inside a same
element called ...
a view, this is
a simplified model of MVC, as implemented in the Swing framework. In Swing
however to avoid a confusion with the MVC term "View", this element is
called the
UI-delegate (see
).
The UI-delegate (i.e. the Zope3
view) manages both
interaction and
presentation, so that in the end we only
have
two elements:
- a model
- a view (UI-delegate)
In the case of a browser view for instance, the View (as in MVC) would
correspond to the HTML and javascript code of a web page and the Controller
(as in MVC) would be all the methods (Form POST, GET, and other RESTful
URLs) associated to the View that allow user interaction with the
page.
For instance when some form data gets posted to the "/folder/addItems" URL,
the 'addItems' method will be called on the server.
Hence a Zope3 view needs to implement:
- controller methods (e.g. 'addItems(...)')
- the actually rendering of the view (this is done with the __call__()
method). Usually the __call__ method delegates the rendering to the Page
Template engine (ZPT), but it could as well be implemented as:
def __call__(self):
return "hello world."
The pattern is classic in web application frameworks since the view and the
controller are usually tightly coupled. In an application server environment
the Controller is usually tied to the publisher's REQUEST - RESPONSE
lifecycle since user actions initiate a new request. Also at the end of the
request, either the same page is refreshed or a new page is displayed after
a redirection has occured. Hence the view (as in MVC) is very much tied to
the response phase.
Why using multi-adapters?
A view in Zope3 is a multi-adapter. It adapts the
contextual object (a.k.a the
context) AND the
request. The reason why an adapter is
used is because the publisher needs to find a "suitable view" for the object
depending on the type of request (HTTP, FTP, XMLRPC, ..), and this is done
in Zope3 by looking up the view in the adapter registry based on the
"context + request" signature.
In fact both the contextual object and the request implement interfaces,
hence there is a unique type signature for each combination of object types
(e.g. IFolder, ICalenderItem, ICatalog, ...) and request types
(IHTTPRequest, IXMLRPCRequest, ... see zope.publisher.interfaces for the
list of available request types).
Hence the Zope3 view has access to:
- the contextual object (the model as one part of the adaptee)
- the request (as the other part of the adaptee)
- the session (bound to the request)
- the application's context
This is pretty neat.
However it only solves the "lookup" part of the problem. Indeed this
guarantees that the right view will be used in a given context.
Ian Bicking in its recent "
Zope3 critique"
article remarks that this it feels too much like a model-driven approach,
and the presentation layer is not the one that drives the data.
In fact I recently
asked
myself this question, so let us see why this is really happening
...
"pull"-style vs. "push"-style MVC
It is clear that the question is:
- is it the presentation layer that
should pull the data that it needs or
- is it the model that should push what
data will be presented?
- or both?
In one case we have a "
model-driven" approach (push-style MVC)
as in Zope3 (or I would say an
object-driven approach).
In the other case we have a "
template-driven" or "
presentation-driven" approach (pull-style
MVC).
Seam
implements both, but mostly uses the
"pull"-style approach. The reason is that
you often don't know for sure what model(s) you will need to pull data from
until the page has actually been displayed.
Basically Seam will see that the presentation needs a given component and
it will pull it for you automatically and manage its entire lifecyle. So
eventually a view may pull 5 or 10 different models.
In the case of Zope3 views however there is place for only
one adapted object, so practically you
have to choose one contextual object before you start creating the view and
the template.
Often you end up with a view class that needs to pull data from other
models. This is done by storing model keys in the request (session, cookies,
...) and the view implementation soon becomes spaghetti code, because it
needs to manage session objects, cookies, request parameters and do
component lookups.
Of course one may consider that this is a problem that exists with all
object-oriented approaches (i.e. the view is derived from the object), and
in fact if you look at
Zope CMF you will see that the only
really contextual object is the folder or the document that is being
displayed.
However in reality the presentation stack
must be able to accommodate many contextual
objects and the
adaptation from
object to view does not allow that.