Wednesday, 26 August 2009

Lift: Listing the entities without CRUDify

The CRUDify Lift trait is a fast way to get CRUD functionality in to the application but often finer control is required over the functionality. The CRUDify trait provides view, edit, delete and list functionality for the Entity which mixes in the trait. Each piece of functionality can be turned on or off.

To turn off the list functionality override the showAllMenuLoc in the Item class.

override def showAllMenuLoc = Empty

This changes the menu of the application.


Before

After

To add a new entry to the Lift Site Map define a new Menu which points to to item/list in Boot.scala .

val entries = Menu(Loc("Home", List("index"), "Home")) :: Menu(Loc("ItemList", List("item", "list"), "Item List")) :: User.sitemap ::: Item.menus

Create a folder under src/main/webapp called item and create a file in it called list.html . The list.html file contains the view to display the list of items

Adding some content to list.html allows the template to be tested

 <lift:surround with="default" at="content">
  <table>
      <thead>
        <tr>
            <th>Name</th>
            <th>Amount</th>
            <th>View</th>
            <th>Edit</th>
            <th>Delete</th>
        </tr>
      </thead>

      <tbody>
              <tr>
                  <td>Apple</td>
                  <td>5</td>
                  <td><a href="#">View</a></td>
                  <td><a href="#">Edit</a></td>
                  <td><a href="#">Delete</a></td>
              </tr>
      </tbody>
  </table>
</lift:surround>

With this file in place the application check the work so far by running mvn jetty:run




To make this list dynamic Lift templates and snippets are required. Update the list.html file to have the contents shown below.

 <lift:surround with="default" at="content">
  <table>
      <thead>
        <tr>
            <th>Name</th>
            <th>Amount</th>
            <th>View</th>
            <th>Edit</th>
            <th>Delete</th>
        </tr>
      </thead>

      <tbody>
          <lift:ListItem.list >
              <tr>
                  <td>
                    <item:name />
                  </td>
                  <td>
                    <item:amount />
                  </td>
                  <td><a item:view_href="">View</a></td>
                  <td><a item:edit_href="">Edit</a></td>
                  <td><a item:delete_href="">Delete</a></td>
              </tr>
          </lift:ListItem.list>
      </tbody>
  </table>
</lift:surround>

The contents of the thead tag haven't changed but in the tbody the <lift:ListItem.list> tag has been added. The <lift:ListItem.list> tag tells Lift to look in the src/main/scala/com/shopping/snippet directory for a class called ListItem and tries to run the method list. When the ListItem snippet is called the xml contained in between the opening and closing tags is passed to the method. The xml between the opening and closing <lift:ListItem.list> tags provide tags that the snippet can replace. The tags which can be replaced are <item:name /> and <item:amount /> which both have the namesapce of item. The xml between the <lift:ListItem.list> also provides item:view_href="", item:edit_href="" and item:delete_href="" which are attributes that the snippet can replace and also have the namespace of item.

 package com.shopping.snippet

import com.shopping._
import model._
import net.liftweb._

import util._
import Helpers._
import http._

import scala.xml._

class ListItem {

    def list(html: NodeSeq) : NodeSeq = {
        toShow.flatMap(item =>
            bind("item", html,
                 "name" -> item.name,
                 "amount" -> item.amount,
                 FuncAttrBindParam("view_href", _ =>
                   Text("view/"+ (item.primaryKeyField)),"href"),
                 FuncAttrBindParam("edit_href", _ =>
                   Text("edit/"+ (item.primaryKeyField)),"href"),
                FuncAttrBindParam("delete_href", _ =>
                    Text("delete/"+ (item.primaryKeyField)),"href")
            )
        )
    }

    private def toShow =
        Item.findAll();

}

The ListItem Scala class is created in the /src/main/scala/com/shopping/snippet folder of the project. The ListItem defines the public list method which the template calls and a private method called toShow. The private method toShow uses the Item object findAll method mixed in from the MetaMapper trait which returns a list of Items. The list method calls the flatMap method on the List of items retuned by toShow method. The flatMap method calls the function for each item in the List and concatenates the results. The bind function is supplied from the net.liftweb.util.Helpers which inherits it from net.liftweb.util.BindHelpers. The first bind parameter is the namespace which in this case is "item" which matches the tag namespace used in the template. The second parameter is the node sequence passed in to the list function. Parameters three and four bind the item name and amount to the <item:name /> and <item:amount /> tags respectfully. Parameters five, six and seven take the attribute to bind and create a NodeSeq consisting of a Text node and set the attribute value of href.

The code for this example is on git hub and available here http://github.com/oliverdaff/Lift-Shopping/tree/master tagged with listingEntities.

1 comment:

  1. Hi,

    great tutorial, thanks a lot for this. I would however suggest one change :

    why not override the showAllMenuLoc method as follows ?

    override def showAllMenuLoc = Full(Menu(Loc("ItemList", List("item", "list"), "Item List")))

    This makes for a neater Boot.scala, and you're basically keeping your new implementations in the model file.

    Cheers,
    manojo

    ReplyDelete