Skip to content
tromberg edited this page Sep 14, 2010 · 22 revisions

Goals and Background

For data-oriented display and input forms, right now there are two choices within the Mapper framework: CRUDify and custom snippets.

CRUDify provides a lot out of the box and can then be tweaked in a few places. But I’ve found it to be best for Prototyping and a few administrative functions where a precise design is not important and workflows are simple. When trying to use it more intensively I found myself having to rely heavily on the CRUDify source code for documentation. A typical white-box framework situation.

The other alternative are custom snippets. They allow every conceivable HTML to be produced, and have no dependence on the Mapper library themselves. I used this approach a lot, but ended up with a lot of boilerplate code; e.g. if you add a field to a Mapper class, you have to also add it in the edit and view template, and in the bind statements in each Snippet.

The code really threatened to become repetitive and complicated when I needed to integrate further aspects:

  • Dealing with empty field values (e.g. hide the field altogether with its caption)
  • Displaying field errors/warnings

Finally, with the classic bind, a snippet only lends itself to reuse in different templates in a limited way. All the bind parameters are created before making the call to bind, which means that lookups etc. have to be performed even if the corresponding tag is not bound in the current template.

Binding Deluxe – binding a single Mapper instance with MBindHelper.bindMapper

With the MBindHelper trait and object provided here, you can now write more concise templates and Snippets that have a lot of standard built-in features and only do the work that is actually required in the current context.

Showing fields

Let’s say you want to display a given user profile (your User class extends Lift’s MegaProtoUser). For example, you might want to do it table-style, with a field in each row, captions in the first column, values in the second column. Using MBindHelper, you would use a template like this:

<lift:Users.myDisplayUserSnippet>
  <template:show>
    <tr><td><field:caption />:</td><td><field:value /></td></tr>
  </template:show>
  <table>
    <tbody>
      <show:firstName />
      <show:lastName />
      <show:email />
      ....
    </tbody>
  </table>
</lift:Users.myDisplayUserSnippet>

The tag introduces a per-field template. This template is then applied every time you use a tag. It remains active until you change it to something else. (This may not be the optimal solution when working with very complex and nested templates, so it might work a bit differently in the future.) The default show template will simply display the value (whatever the field returns from its asHtml method).

The Snippet code for this case couldn’t be simpler:

import be.romberg.liftweb.util.MBindHelper._

def myDisplayUserSnippet(content:NodeSeq):NodeSeq = {
  val userToDisplay: User = ... // fetch the user to display
  bindMapper(content, user, NoSpecialBinding)
}

In this case, for every tag in your template there has to be a corresponding field in the User class.

However, usually things are not so simple. For example, let’s say instead of first and last name on a separate line, we want to display the niceName. In this case we could simply change our template to:

<lift:Users.myDisplayUserSnippet>
  <template:show>
    <tr><td><field:caption />:</td><td><field:value /></td></tr>
  </template:show>
  <table>
    <tbody>
      <show:niceName/>
      ....
    </tbody>
  </table>
</lift:Users.myDisplayUserSnippet>

and modify our Snippet as follows:


def myDisplayUserSnippet(content:NodeSeq):NodeSeq = {
  val userToDisplay: User = ...
  bindMapper(content, user, {
    case "niceName" => showValue("Name", user.niceName)
  })
}

The third parameter to the bindMapper method works actually almost exactly like the classic bind parameters in BindHelpers.bind. Only that, with bind, one uses a map (“niceName” → someNodeSeq) and here we use a PartialFunction[String, NodeSeq]. showValue is a convenience function that applies the currently active per-field ‘show’ template to the caption and field value you supply to it. So our niceName will end up in a table row just like a regular field. But in many cases, you will not want to use the current per-field template at all. If you need access to the current node, it is available via the currentNode method.

The special bindings can also be used to override the default behaviour for existing fields. bindMapper will check the special bindings first, and only looks for the field if the partial function doesn’t match.

bindMapper will hide empty fields by default. But what is an empty field? The Mapper API doesn’t really have that concept. For now we use a combined approach that deals with many cases. First, MBindHelper defines an isFieldEmpty method that decides for various field types whether a field is empty. This is one of the cases where you might want to override the MBindHelper trait and produce your own MBindHelper singleton. The second technique is that it checks whether the text content of the produced NodeSeq (for a field that’s whatever asHtml delivers) is empty.

You can override how empty fields are displayed by defining a showEmpty field template, e.g.:

<template:showEmpty>
  <tr><td><field:caption/>:</td><td>EMPTY</td></tr>
</template:showEmpty>

Finally, if you only need a different show template for one field, you can simply define it inside the show tag, e.g.

<show:niceName>
  <p><field:caption />:<br/><field:value /></p>
</show:niceName>

Generating input forms

Generating input forms works almost the same. Instead of tags, you simply use tags. By default, bindMapper will look up the field in the Mapper instance you provided, and use the toForm method to generate a field. However, the per-field template is a bit more complex. Here’s the default per-field template:


<p><label><field:caption /></label>{"\u2003"}<field:hint><span class="fieldhint"><hint /></span></field:hint><br /><field:input />{"\u2002"}<field:message errorClass="errorBox" warningClass="warningBox" noticeClass="noticeBox" /></p>

The template binds 4 parameters. ‘caption’ is already well-known. ‘input’ binds to the input field as generated by the toForm method. ‘message’ will generate a tag with the uniqueFieldId of the field. The ‘hint’ is an additional field hint that is only needed in input forms, such as “Enter up to 5 authors”. Putting that into the displayName would look bizarre when the field is simply viewed.

With the standard MappedField classes as provided by Lift, the hint remains empty. Your field will need to extend the HintedField trait provided here and override the fieldHint method.

Again, the specialBindings are checked first. If you want to use the current per-field input template for your special binding, you can use the makeInputField method.

Clone this wiki locally