Monorail peeves II

Part II in a series of… several.

In Part I I whined on about static type checking in views. Here I moan about composability of page elements.

Composability of Page Elements

This isn’t exclusively a Monorail problem. Composability—the ability to construct software from (potentially reusable) elements, and moreover,

  • to create a software artifact which may be easily composed;
  • to be able to create ‘first class’ components—components which are as fully-featured as the ones provided by the programming framework; and
  • to be able to create an artifact and not to care whether it is used as a top-level element or as a component

—is often neglected in software frameworks. For example, database APIs often do not make transactions easily composable.

Monorail does support some degree of composability:

Layouts are the common elements between pages, standard header, footer, and navigation areas, for example. Views represent the content area of a page. View components are reusable elements within pages. Helpers are simple objects whose methods return fragments of HTML as stings.

The main problem with this model is that it’s not uniform. Let’s leave layouts to one side for the moment.

  1. Views and view components are implemented in the templating language; helpers are implemented in programming code.
  2. Views and view components may be further composed of view components and helpers.
  3. Views may not be contained inside other views. Helpers may not (easily) contain view components, though they can (sort of) call other helpers.

Helpers and view components are—sorta, kinda—at the same level of abstraction, but are very different in implementation and invoked by different syntax, even though both essentially take parameters and return HTML (which is essentially what views do too). Helpers, since they cannot call views or view components, have to hard-code any presentation (HTML) which they emit, rather than delegating (or being written in) the template language.

What I want

Views, view-components and helpers are (or at least should be), simple functions which take arguments and return HTML. They may benefit from additional context to be more useful, but boiled down to that, the only difference between them is what language they’re written in.

Like any other functions, they should be able to intercall each other arbitrarily. That is: views should be able to contain other views, nested to any degree.

I’ve called them ‘functions’, but in an object oriented environment, this is ambiguous. A ‘function’ could be a closure object (‘delegate’ in C#, Runnable in Java) or a method call. In MonoRail, ‘view components’ are conventional objects, whereas ‘helpers’ exist only for their methods to be called.

Use cases (in templates)

Most obviously, the templating language must allow use of ‘components’, passing parameters to them, naturally and efficiently. For example, if a component represents a particular image, wrapped in a link, we might invoke it thus:

<p>Some text $imagelink</p>

We must be able to pass parameters to a component too. Perhaps:

<p>See: $externallink($url)</p>

Must be able to pass blocks of template content to a component. For example, let’s imagine a component called ‘paginator’ which splits up a collection into fixed-sized pages for display:

<table>
#paginator($orderlines)
#section(line)
<tr><td>$line.Number</td><td>$line.Name</td>
#end
#end
</table>

‘Section’ here is a function which is passed to the ‘paginator’ by the template, and which takes an object, ‘line’, and returns markup.

I’m assuming here that everything is type-safe, and therefore that the static type of ‘line’ in the above code is inferred by the compiler.

More importantly, the ‘paginator’ component above could be implemented in the templating language, or in a conventional programming language*. In C#, it might be represented as:

public delegate Html PageSection<T>(T line);
public Html Paginator<T>(ICollection<T> lines,
[Named] PageSection<T> section){
// ...Code...
}

…which is both simpler than the existing MonoRail ‘view component’ way of doing things, and less framework-specific.

I’ve invented the attribute ‘Named’ to indicate that a parameter is accessible by name.

Actually, in the example above, this would be better coded as:

public delegate R PageSection<T, R>(T line);
public R Paginator<T, R>(ICollection<T> lines,
[Named] PageSection<T, R> section){
// ...Code...
}

That is, the return type is the generic type parameter, R. (The template language would be required to infer the generic type parameters automatically.)

Use cases (from code)

We also want to be able to call template language views from code.

As long as templates are compiled down to bytecodes, and have sensibly-generated names, this would not be a problem. So for example:

Customer c = FindCustomer(custname);
Html result = new Project.Views.Customers.Edit(c).Body();

…presuming that ‘Project’ is the name of the project into which the views were compiled, and that the view’s source code was in a file called, say, “Views/Customers/Edit.vm”

However, sometimes we may wish to choose the view dynamically. Say the code wishes to call one of two views with compatible interfaces—one view for web browsers, say; another for WAP. To accommodate that, we propose that the template language automatically generates a ViewFactory object for each view. Assuming that both compatible views implement ICustomerView:

ICustomerViewFactory v = ChooseView(mode);
Customer c = FindCustomer(custname);
Html result = v.GetView(c).Body();

We might obtain the view factory as follows:

return Project.Views.Customers.Edit.Factory;

Conclusion

The current distinction in MonoRail between ‘helpers’, ‘view components’ and ‘views’ is unwieldy and confusing. In particular, it is not always clear when to implement a ‘view component’ over a ‘helper’ (or vice versa). Also there is an artificial distinction between views components (which may be composed) and views (which may not be).

By simplifying and unifying the component model underlying MVC views, we can make compound view logic simpler to write and maintain, encourage reuse of fragments of view logic, and discourage view logic within code.

Further issues

We have not solved all the problems here. In particular:

  1. View templates now require a richer type system. In my previous article I proposed adding type annotations to template parameters. However, we will also need to allow templates to declare interfaces, and possibly supertypes. Also views should be able to specify their own class name, overriding the convention. This should all be possible without making views look too complicated.
  2. It is not clear how views should acquire references to ‘helpers’, types or to other views.
  3. Sometimes our components need to refer to the page controller in some way, or to the page request state. This is not currently possible in the revised scheme.

Stay tuned.

*At least as far as the calling code is concerned. Templating languages are intentionally kept simple and there are some things, like complex logic, that the templating language may not be able to express.

One thought on “Monorail peeves II

  1. Gauthier Segay

    It worth mentioning that viewcomponents are implemented at least as code (a class inheriting ViewComponent) and can (it’s optional) rely on dedicated view templates (found in ~/views/components/componentname/viewname.ext).

    Your point about the use of “macro-like” function is interesting, I know nvelocity support some kind of custom macro (which will use # as prefix instead of variable resolution prefix: $), also the use of delegate would be possible with some view engines: your controller would put a delegate in the propertybag, and your view will use it for some purpose.

    However I’m unsure it will work with nvelocity (you may try the macro way) or brail, I think it’s supported in aspview.

    For brail, you may look at common scripts:
    http://castleproject.org/monorail/documentation/trunk/viewengines/brail/usersguide.html#CommonFunctionality

    and there is also extension method for brail:
    http://www.ayende.com/Blog/archive/2007/05/21/Brails-Extension-Methods.aspx

    In the end, this is all hacky and view engine specific, obviously you can’t Controls.Add() in your controller (avoiding asp.net mayhem), I think the view components are the best way to extend / implement advanced features for composability, there is some great exemples (smartgrid, pagination) in the default monorail distribution.

    As yet, I didn’t encounter composability problem that I wasn’t able to overcome bit my use cases may have been simplier than yours.

Leave a Reply

Your email address will not be published. Required fields are marked *