Creating the UI for Mapper entities

When creating a UI on top of a Mapper-based entity model, you have several options:

  1. You can manually create templates and snippets using the Webkit library, which gives you the most flexibility, but requires you to create all the links between Mapper fields and displayed UI elements manually (e.g. using the MappedField’s asHtml and toForm methods).
  2. CRUDify is at the other end of the spectrum – simply add it as a trait to the Mapper entity, and it will provide a basic CRUD interface that you can then tweak in various places. However, these modifications will be in your Scala code; it is harder to achieve separation of code and design.
  3. The classes in the mapper.view package provide more powerful customization options for CRUD-like interfaces, and this customization is often achievable with certain tags that you place inside the a snippet tag

CRUDify

Some examples starting with the CRUDify trait and then detailing how to create the same functionality using templates and snippets.

  1. Example using CRUDify with a entity “link”
  2. Example overriding the CRUDify list entities view “link”
  3. Example overriding the CRUDify create entity view “link”
  4. Example overriding the CRUDify view entity page “link”
  5. Example overriding the CRUDify edit entity page “link” (using mapper.view.ModelSnippet)
  6. Example overriding the CRUDify delete entity page “link”

For an alternative approach and further discussion of underlying technques see: DIY Lift CRUD – an alternative to CRUDify

TableEditor

TableEditor lets you really quickly set up an editable table with pending adds/removes and sortable columns.

In Boot (after Schemification so it doesn’t choke on changed models):

TableEditor.registerTable("myId", MetaObject, "Title")

In template:

<div class="lift:TableEditor.edit?table=myId;eager_eval=true">
    <span class="lift:embed?what=/tableeditor/default"></span>
</div>

/tableeditor/default is a default markup you can use and the eager_eval is needed to have the inner embed evaluated before the content is passed to the outer snippet call.

Pagination

Here is an example of snippet-based pagination using a Mapper model called Request.

import net.liftweb.mapper.view.Paginator
class Requests extends ModelSnippet[Request] {
  // Parameters: the MetaMapper, default sort field, and (headerName, field) tuples
  val paginator = new Paginator(Request, this, Request.id,
    "id" -> Request.id,
    "nature" -> Request.nature,
    "client" -> Request.client,
    "dispatcher" -> Request.dispatcher,
    "priority" -> Request.priority,
    "comment" -> Request.comment
  )

  def list(xhtml: NodeSeq) = 
    bind("prefix", paginator.paginate(xhtml),  ... )
}

The Template

The nav: prefix is for navigation links. allpages is the list of page numbers, linked. The ’ | ’ is used as the separator in this example; you can choose something else if you like. In the table header use the sort prefix for sortable headers. For example <sort:id>..</sort:id> gets linked with a link that sorts by ascending id. When clicked a second time it sorts in descending order.

<lift:surround with="default" at="content">
  <lift:Requests.list>
    <nav:records/>
    <br/>
    <nav:first/>
    <nav:prev/>  <nav:allpages> | </nav:allpages>  <nav:next/>
    <nav:last/>
    <table rules="groups" frame="hsides">
      <thead>
        <th><sort:id>
          <lift:loc>Req#</lift:loc>
        </sort:id>
        </th>
        <th><sort:nature>
          <lift:loc>Nature</lift:loc>
        </sort:nature></th>
        <th><sort:client>
          <lift:loc>Client</lift:loc>
        </sort:client></th>
        <th><sort:dispatcher>
          <lift:loc>Dispatcher</lift:loc>
        </sort:dispatcher></th>
        <th><sort:priority>
          <lift:loc>Priority</lift:loc>
        </sort:priority></th>
        <th><sort:comment>
          <lift:loc>Comments</lift:loc>
        </sort:comment></th>
        <th><lift:loc>Assigned volunteers</lift:loc></th>
      </thead>
       .....
    </table>
  </lift:Requests.list>
</lift:surround>

MBindHelper

If CRUDify and the mapper.view classes are not flexible enough for you, and you don’t want to fall back to plain manual Snippets, the MBindHelper library may be worth considering.