View comments | RSS feed

Mach II Development Guide - Designing Views

Release 1.2.1 (4/14/2005)

« Designing Models | Contents | Designing Event Handlers »

Printable Version

Designing Views For Mach II

This section looks at design issues in the View portion of your application. The View encompasses all of the HTML user inteface in your application and is implemented as a collection of .cfm pages.

Controller / View Data Transfer

Since views should not contain any logic (beyond that necessary to iterate over data structures to display them as lists), any dynamic portions of a view should be passed in from the controller (Mach II), either as request scope variables or via the event object (accessible in a view as just plain event). The section on Transfer Objects touches on this topic and recommends that views depend on as few variables as possible to maintain encapsulation and reduce coupling.

The key thing to remember is that the request scope variables and the arguments in the event object that are used by a view define the API between the controller and the view and thus specifies a contract that should be agreed early on in the design, documented and then honored for rest of the project lifecycle if possible. This helps maintainability and stability.

You can reduce that API down to just the current event object by moving the request scope data into the event object:

<event-arg name="foo" variable="request.foo" />

Whether you do this or not depends on whether you feel the increased number of <event-arg> commands is worth the increase in encapsulation. An alternative is to use the custom invoker, EventInvoker, shown in the Invokers & Listeners section of Designing Models. Our rule of thumb is to leave data where it naturally falls rather than move it around artificially just to increase encapsulation. In other words, we use request scope in views for data that already happens to be in request scope (e.g., as set up by contentKey=) and we use the event object for data that already happens to be in the event object (e.g., URL or form parameters, values explicitly set with <event-arg> or event.setArg() inside filters, listeners and plugins or via EventInvoker notify commands).

Parameterized Views and eXit Events (XEs)

A good use of event arguments in a view is when <event-arg> is used to set values for parameterized views, such as forms that support both edit and create operations (typically their submit and cancel actions are passed in as well as the submit button label). This is much better than hard-coding the submit and cancel actions into the view itself.

Fusebox uses a similar technique and refers to it as XFAs - eXit FuseActions - so by analogy, these could be described as XEs - eXit Events. Again, it reduces coupling between views and the application control flow so it's good practice to parameterize views where appropriate so that the exit paths out of a view (links and form actions) are passed in as event arguments. For example, a paged record set view might have previous / next page links like this:

<a href="index.cfm?event=#event.getArg('XEPrevious')#">Previous</a> |
	<a href="index.cfm?event=#event.getArg('XENext')#">Next</a>

This allows the view to be invoked in different parts of the application with the previous / next events passed in from <event-arg> commands in the mach-ii.xml file.

contentKey Considerations

Mach II allows views to be rendered into contentKey variables so that a single HTML page can be constructed from multiple views (page fragments). These contentKey variables are typically in request scope so they also form part of the API between the controller and the views, as discussed above. Care therefore needs to be taken in planning these too. As a general guideline, you will produce a more maintainable application if you can arrange it so that dynamic data (request scope variables and / or the event object) is passed into simple views that are rendered into contentKey variables and then those contentKey variables are aggregated into a single layout template view. For example:

<event-handler event="showFoo" access="public">
	<notify listener="foo" method="getFoo"
			resultKey="request.fooData" />
	<view-page name="fooPage" contentKey="request.content" />
	<view-page name="mainLayout" />
</event-handler>

In this example, the API between fooPage and the controller is simply request.fooData (which might be a struct containing multiple items) and the API between mainLayout and the controller is simply request.content. There is no dependency between mainLayout and any dynamic data - all dynamic data is rendered into HTML by other views prior to rendering mainLayout.

If you have several views that go together to make up part of a layout you can sometimes simplify your event handler by appending to a contentKey (this is new in 1.0.10):

<event-handler event="showFoo" access="public">
	<notify listener="foo" method="getFoo"
			resultKey="request.fooData" />
	<view-page name="fooPod" contentKey="request.content" />
	<view-page name="barPod" contentKey="request.content" append="true" />
	<view-page name="mainLayout" />
</event-handler>

In this example, the output of barPod is appended by Mach II to the output of fooPod within request.content so you do not need additional contentKeys and layouts.

The intent here is for each view to focus on rendering just the data that it depends on and using a layout view to assemble the finished page from pre-rendered HTML fragments. This keeps each view cohesive: each view does one job well. It also reduces coupling between the views themselves and between the views and the controller. The next section discusses this principle in a bit more detail.

Portals a.k.a. Grid Layouts

In a typical application the user interface is quite complex and often has several dynamic elements. The navigation can be dynamic, for example, adapting to where you are in the flow of the application. Examine your user interface with an eye to breaking it down into smaller, simpler parts that form a grid. Most portal sites (e.g., My Yahoo!) are good examples of grid layouts. They have multiple columns, each containing multiple sections (often called pods).

Implement each of these pods as a separate view that renders a request scope variable or some event arguments into a contentKey and use a layout view to assemble those contentKey variables into the finished HTML page. This will produce small, focused (cohesive) views that are loosely coupled. You are also more likely to be able to reuse user interface elements on other pages - because you can reuse views very easily in Mach II as long as they have only narrow, well-defined dependencies. Even if these pods contain only static content, it is still usually better to implement them as views so that you can rearrange page elements more easily - because you only have to change the layout view. This is sometimes referred to as "view stacking" but is really an implementation of the Composite View design pattern (see J2EE pattern description).

This raises the question of whether headers and footers should be implemented as separate pod views and assembled by the layout view? If the header and footer are site-wide elements, the chances are that they will only change if the whole look and feel of the site changes. Implementing them as separate views is possible but then every single event-handler that generates HTML will look like this:

<event-handler event="..." access="...">
	...
	<view-page name="..." contentKey="request.content" />
	<view-page name="header" contentKey="request.header" />
	<view-page name="footer" contentKey="request.footer" />
	<view-page name="mainLayout" />
</event-handler>

Or, to reduce code duplication in mach-ii.xml, you could do this:

<event-handler event="..." access="...">
	...
	<view-page name="..." contentKey="request.content" />
	<announce event="layoutPage" />
</event-handler>


<event-handler event="layoutPage" access="private">
	<view-page name="header" contentKey="request.header" />
	<view-page name="footer" contentKey="request.footer" />
	<view-page name="mainLayout" />
</event-handler>

Every event-handler that needs to generate HTML would announce the layoutPage event. If you are comfortable with this style, feel free to implement headers and footers as separate views. If you feel that headers and footers are so closely tied to the overall page layout then either implement them directly inside the layout view or implement them as separate files and include them into the layout view:

<!--- doctype etc goes here --->
<html>
<!--- head / title etc go here --->
<body>
<cfinclude template="header.cfm" />
#request.content#
<cfinclude template="footer.cfm" />
</body>
</html>

At Macromedia, we use custom tags to control the overall rendering of pages so our layout view looks somewhat simpler:

<!--- set up page settings etc --->
<cfimport taglib="/customtags/mmlf/" prefix="mmlf" />
<mmlf:renderpage>
#request.content#
</mmlf:renderpage>

Views And Scopes

If you've followed the recommendations above, your view will contain references to a few request scope variables and / or the current event object (event). That is effectively the interface between the controller and that view. What about using other scopes inside views?

« Designing Models | Contents | Designing Event Handlers »

Comments


lanfeustdetroy said on Nov 14, 2003 at 5:58 AM :
How do we have to manage caching of the view or pods ?
By the use of filters ? is there some specific methodology to use ?
SeanCorfield said on Nov 25, 2003 at 12:22 PM :
You can cache full page views using a variety of content caching
techniques. We're currently looking at Brandon Purcell's cf_accelerate
tag as one option - and using a filter to abort the current event if the
result is already in cache. It's not quite as straightforward to cache
just a single pod because the framework doesn't currently allow for a
plugin to abort a view-page directive (so you can't avoid rendering the
view even if you have it cached).
No screen name said on Dec 1, 2003 at 11:58 AM :
What is the best method to cache queries stored as request.variable name for display in the views?

<cffunction name="getPressRelease" returntype="query">
<cfquery name="qryPressRelease" datasource="#request.dsn#" cachedwithin="#CreateTimeSpan(0,24,0,0)#" blockfactor="2">
EXEC spPR
</cfquery>
<cfreturn qryPressRelease />
</cffunction>

However, <notify listener="PressReleaseManager" method="getPressRelease" resultKey="request.PRs" />
<view-page name="pressRelease" contentKey="request.pressRelease" />

The variable request.pressRelease is re-created... unlike the query which is cached... How can I cache request.pressRelease just like the query?
SeanCorfield said on Dec 1, 2003 at 12:06 PM :
There is no support within the framework for caching a single view (as
the comment above indicates, you can cache an entire page).

You could probably write your own view cache management using a
filter, depending on how complex your view structure is.

Remember: don't optimize unless you already proved you have a
problem using load testing!
schipperheyn said on Feb 22, 2004 at 5:54 AM :
How does the copying of large HTML fragments to the request scope on a continuous basis affect memory management and performance on high traffic sites?
SeanCorfield said on Mar 5, 2004 at 11:23 AM :
While there is probably some performance hit in doing this, we have found performance to be perfectly acceptable, even under heavy load.
No screen name said on Jun 16, 2004 at 7:29 AM :
about cache.
as caching is important in buseness apps such as those machii is indicated to develop with, we revert the order of events handling, i.e. when a page including any dinamic contents is requested, an event to displaying it is generated: a plugin in this event handler can verify if the page is cached: if yes, the handler continues display the page, if not, plugin stops current event handling and announce another event corrisponding to the sequence of operations to reconstruct the page.
the layout page containing dinamic contents, the pods, can be designed as a portlet or pagelet, so we can manage pods caching in this page, without involving the framework.
what ado you think about?
SeanCorfield said on Jun 21, 2004 at 4:55 PM :
That is pretty much what we do with cf_accelerate on macromedia.com except we use a filter instead of a plugin (so I think your approach is just fine).

 

RSS feed | Send me an e-mail when comments are added to this page | Comment Report

Current page: http://livedocs.adobe.com/wtg/public/machiidevguide/views.html