Sunday, 31 January 2010

Scala List foldLeft and foldRight

The Scala List class introduces foldLeft and foldRight which will be unfamiliar to Java developers. The fold methods apply a function to each element in the list either starting at the first element or the last, for foldLeft and foldRight respectfully. The result of the function applied to the first element is passed to the second function. Clear as mud? Let me explain with an example

Create a List and then apply the foldLeft function to it.

val sentence = List("Mary", "had", "a", "little", "lamb")
sentence.foldLeft("start")((a,b) => a + b)                    

The result of the code above is a String startMaryhadalittlelamb. The Scala doc for the List class the foldLeft method is override def foldLeft[B](z : B)(f : (B, A) => B) : B. The function (B, A) => B gets called for each element in the list where the first parameter is the element from the list and the second parameter is z for the first element in the list and the result of the (B, A) => B function on the previous element on the list for subsequent ones. Adding some debug to the code shows this in action.

val sentence = List("Mary", "had", "a", "little", "lamb")
sentence.foldLeft("start")((a,b) => {
      println("[a:" + a + "][b:"+ b + "]");
      a + b
})
[a:start][b:Mary]
[a:startMary][b:had]
[a:startMaryhad][b:a]
[a:startMaryhada][b:little]
[a:startMaryhadalittle][b:lamb]

What about foldRight? Changing the code to example above to use foldRight looks like this.

val sentence = List("Mary", "had", "a", "little", "lamb")
sentence.foldRight("start")((a,b) => a + b)   

This gives the result Maryhadalittlelambstart. The difference between this result and the foldLeft example is that the initial value start is on the end of the String now rather than the beginning. The API docs for the List class say that foldRight Combines the elements of this list together using the binary function f, from right to left, and starting with the value z. so I expected the result to be startlamblittleahadMary. Running the code with the debug statements in shows the reason for the difference between the actual and my expected results.

val sentence = List("Mary", "had", "a", "little", "lamb")
sentence.foldRight("start")((a,b) => {
     println("[a:" + a + "][b:"+ b + "]");
     a + b
})
[a:lamb][b:start]
[a:little][b:lambstart]
[a:a][b:littlelambstart]
[a:had][b:alittlelambstart]
[a:Mary][b:hadalittlelambstart]

Where as for foldLeft the initial value/result of previous was the first parameter in the function call for foldRight it is the second parameter and the value from the list is the first. So inorder to get my expected result I would need to swap the a and b parameters around.

sentence.foldRight("start")((b,a) => a + b)

Why not see if you can solve problems four and five here using foldLeft and if you are still in need of inspiration checkout the list of foldLeft examples Matt Malones has put together here.

Saturday, 10 October 2009

Writing the Edit view in Lift

The edit component is a combination of the view and create components shown previously. The addition of the edit component lets us remove the CRUDify trait from the Item MetaMapper object as well as removing all the override menuloc methods.

object Item extends Item with LongKeyedMetaMapper[Item] {

    override def fieldOrder = List(name, amount)
}

Add a file called edit.html in the src/main/webapp/item folder and add some test content.

<lift:surround with="default" at="content">
    <p>Edit with some test content</p>
</lift:surround>

Run run mvn jetty:run to check that the edit page is working, you might need to create some Items first and then click on the edit link from the list view.

Create a file called EditItem.scala in the /src/main/scala/com/shopping/snippet directory.

The EditItem class needs to present the Item to the user on the show form request. When the form is posted back to the server the values need to be taken from the post request, set in the Item instance and then that Item must be persisted to the database.

package com.shopping.snippet

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

import SHtml._
import S._
import mapper._
import util._
import Helpers._

import scala.xml.{NodeSeq, Text}

class EditItem {

    var id = S.param("id") openOr ""

    var item = try {
        Item.findByKey(id.toLong)
    } catch {
        case e:NumberFormatException => Empty
    }

    def edit (html: NodeSeq): NodeSeq ={
        item map ({ i =>
            i.toForm(Full("save"), "/item/list")
        }) openOr Text("Invalid Item")
    }

}

The code in the EditItem snippet takes the id parameter from the request and uses the MetaMapper findByKey method to locate it in the database. Once the item has been found calling the toForm method on it provides the XML node sequence for to render on the screen. When the submit button is pressed the Item is persisted to the database and the user redirected to list page. If the Item can not be found for the id passed in Invalid Item is shown to the user.

To see how the Mapper toForm method works take a look at the Lift api http://scala-tools.org/scaladocs/liftweb/1.0/net/liftweb/mapper/Mapper.html and view the source by following the Source:Mapper.scala link to here http://scala-tools.org/scaladocs/liftweb/1.0/net/liftweb/mapper/Mapper.scala.html#Some(33). Search for def toForm(button: Box[String], onSuccess: String). The code shows toForm calls the validate method on Item, since validate hasn't been overridden in item this returns Nill, which means save is called and then S.redirect to /item/list .

Change the test content of the edit.html to call the EditItem snippet.

<lift:surround with="default" at="content">
    <lift:EditItem.edit form="POST"/>
</lift:surround>

Run the server using mvn jetty:run to use the new edit functionality that you have just added.

The code for this example is on git hub http://github.com/oliverdaff/Lift-Shopping and has been tagged with editItem. The editItem tag can be found here http://github.com/oliverdaff/Lift-Shopping/tree/editItem or downloaded from here http://github.com/oliverdaff/Lift-Shopping/downloads.

Sunday, 13 September 2009

Writing The Delete view in Lift

The delete view provided by the CRUDify trait gives a page that shows the details of the Item that is going to be deleted. The page has a button which when pressed deletes the Item from the database. The URL of the delete view is http://localhost:8080/item/delete/1 .

The delete view is very similar to the create view. The major differences are that the Item can not be edited, the view needs to query the database before presenting the initial page to the user. After deleting the Item the user will be redirected to the list item pages.

First turn off the delete view in the Item MetaMapper object by overriding the deleteMenuLoc.

override def deleteMenuLoc = Empty

With out the CRUDify trait delete view a new component is needed to handle the url. Create a new file called delete.html in the /src/main/webapp/item/delete folder. Add some content to the delete.html file.

    <lift:surround with="default" at="content">    
    Some test content in the delete html file. 
    </lift:surround>

Start the application mvn jetty:run and hit the url http://localhost:8080/item/delete/1 . The output should look like the screen shot below.

The delete view needs to have the incoming URL rewritten in the same way as the view item page. For more details see this previous post. The rewrite rule for view Item can be updated to handle all requests starting with /item. Notice that a new variable has been added called action to capture the action to send the rewrite to.

case RewriteRequest(
        ParsePath(List("item", action, id),_,_,_),_,_) =>
        RewriteResponse("item" :: action :: Nil, Map("id" -> id))

Update the contents of the delete.html file to provide binding points for the Item data and the delete button.

    <lift:surround with="default" at="content">
        <lift:DeleteItem.delete form="POST">
            <table>
                <tr>
                    <td>Name</td>
                    <td><item:name/></td>
                </tr>
                <tr>
                    <td>Amount</td>
                    <td><item:amount/></td>
                </tr>
            </table>
            <item:submit/>
        </lift:DeleteItem.delete>
    </lift:surround>

Create a new file called DeleteItem.scala in the folder /src/main/scala/com/snippet folder. This new snippet will find the Item which has the id passed in as a url parameter and bind the Items values to the incomming html as well as adding a delete button. The submit button action calls the deleteItem method which is bound to the item when it is in scope. With in the deleteItem method the item delete method is called and the view redirected back to the list view.

    package com.shopping.snippet

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

    import SHtml._
    import S._
    import mapper._
    import util._
    import Helpers._


    import scala.xml.{NodeSeq, Text}

    class DeleteItem {

        var id = S.param("id") openOr ""

        var item = try {
            Item.findByKey(id.toLong)
        } catch {
            case e:NumberFormatException => Empty
        }

        def delete (html: NodeSeq): NodeSeq ={

            item map ({ i =>
                def deleteItem() :Unit = {
                    i.delete_!
                    S.redirectTo("/item/list")
                }
                bind("item", html,
                     "name" -> i.name,
                     "amount" -> i.amount,
                     "submit" -> submit("Delete", deleteItem )
                )
            }) openOr Text("Invalid Item")
        }

    }

Run the application again mvn jetty:run and create some items using the create view and then delete them with new delete view you have just written.

The source code for this is available on Git Hub here http://github.com/oliverdaff/Lift-Shopping and has been tagged with deleteView.

Monday, 7 September 2009

Writing a View Page in Lift

The CRUDify trait adds a view url for a item. In this post we will turn off this view and build our own. To turn off the CRUDify view override the viewMenuLoc method in the Item object

 override def viewMenuLoc = Empty

The view item the url is http://localhost:8080/item/view/1 where final part of the url is the primary key (PK) of the item. To write our own version of the view functionality the url needs to rewritten to convert the 1 into a parameter.

Lift handles url rewriting using LiftRules rewrite. The rewite is a RulesSeq where the rules take a RewriteRequest and return a RewriteResponse. The RewriteRequests are a case class used in a case statement on the incoming request.

The RewriteRequest takes a ParsePath which represents the incoming url that needs rewriting. The path is the first argument in the ParsePath constructor, the second is the suffix to match on, the third is a boolean that indicates if the path is root relative i.e. starts with the context path of the app and the fourth indicates that the url ends with a /.

The instance we are interested in is the path item/view/1 so the parse path is constructed as shown below where the _ means match anything in the case statement.

ParsePath(List("item", "view", id),_,_,_)

The RewriteRequest takes as its second argument a RequestType that represents a HTTP method. The final argument it the HttpServletRequest.

For the item view rewrite there is no need to match the http request type or match on the HttpServletRequest .

RewriteRequest(
        ParsePath(List("item", "view", id),_,_,_),_,_)

The RewriteResponse takes a ParsePath and a map of parameters.

RewriteResponse("item" :: "view" :: Nil, Map("id" -> id))

Put both theRewriteRequest and RewriteResponse together in the Boot class. Notice that id from the RewriteRequest ParsePath is mapped to the id variable id which is used in the RewriteResponse.

LiftRules.rewrite.append {
    case RewriteRequest(
    ParsePath(List("item", "view", id),_,_,_),_,_) =>
    RewriteResponse("item" :: "view" :: Nil, Map("id" -> id))
}

To test the new rewrite add a new html file under /src/main/webapp/item called view.html.

Create view html file

Add some test content to the view.html file

<lift:surround with="default" at="content">
<p>My view template</p>
</lift:surround>

The view item does not need to appear in the left hand menu but unless the location is added to the sitemap access will not be allowed to the page. To allow access to all files in the item folder add a Menu to the sitemap which allow access to sub paths but is not shown.

Menu(Loc("Item", List("item") -> true, "Item", Hidden))

Run the application mvn jetty:run and view this page in your browser http://localhost:8080/item/view/1.

To display the Item in the page update the view.html.

<lift:surround with="default" at="content">
<lift:ViewItem.view >
    <table>
        <tr>
            <td>Name</td>
            <td><item:name/></td>
        </tr>
        <tr>
            <td>Amonut</td>
            <td><item:amount/></td>
        </tr>
    </table>
</lift:ViewItem.view>
</lift:surround>

Create ViewItem.scala in the snippets directory.

View item snippet

The view method on the ViewItem class should find the Item associated with the passed in id request parameter. The Lift S object provides the param method to retrieve the http parameter from the request. The MetaMapper findByKey method returns a item that has the value of the id parameter passed in. If the item is found a Full Box is returned and the values bound to the incomming NodeSequence. If the item is not found a Empty is returned and a Text node containing "Invalid Item" is returned.

package com.shopping.snippet

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

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

import scala.xml._

class ViewItem {

    var id = S.param("id") openOr ""

    var item = try {
        Item.findByKey(id.toLong)
    } catch {
        case e:NumberFormatException => Empty
    }

    def view(html : NodeSeq): NodeSeq = {
        item map ({ i =>
            bind("item", html,
                 "name" -> i.name,
                 "amount" -> i.amount
            )
        }) openOr Text("Invalid Item")

    }
}

Run the app using mvn jetty:run create some items and then go to the list view and click on the view link.

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

Sunday, 30 August 2009

Writing a Create Form in Lift

The create form is one of the other pages CRUDify trait provides. To hand roll your own form deactivate the 'Create Item' menu in the Item class by overriding the createMenuLoc method on the Item class.

     override def createMenuLoc = Empty

Add a file called create.html in the /src/main/webapp/item/ folder.

create item folder

The create.html file should contain some sample text to test the page with.

     <lift:surround with="default" at="content">
         <p>Some text to test the create page</p>
     </lift:surround>

Add the create page to the SiteMap in the Boot.scala using a new Menu item.

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

Run the app and click on the new 'Create Item' on the left to view the test page.

Add a snippet to src/scala/com/shopping/snippet called CreateItem.

package com.shopping.snippet

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

import util._

import scala.xml._

class CreateItem {

    def create(html : NodeSeq): NodeSeq = {
        val item = new Item()
        item.toForm(Full("save"), {_.save})
    }

}

The CreateItem snippet creates a new instance of the Item class can calls the toForm method on the new instance which is inherited from the Mapper class. The toForm method uses the Item class fields to generate input fields. The first parameter on the toForm method takes a Lift Box and the second a function. A Lift Box is a container which can declare if it is full or not, in a similar way to the Scala Option class but adds several features. The String passed into the constructor of the Full class (a subclass of Box) is the text that appears on the submit button. The function parameter is called when the submit button is pressed. To use the CreateItem snippet add <lift:CreateItem.create form="POST"/> to the create.html file.

<lift:surround with="default" at="content">
    <lift:CreateItem.create form="POST"/>
</lift:surround>

Run the app using mvn jetty:run and try creating some shopping list items and viewing them in the list view.

Correct the order the fields appear on the form by overriding the fieldOrder method in the Item MetaMapper object.

override def fieldOrder = List(name, amount)

The code for this application is available on Git hub here http://github.com/oliverdaff/Lift-Shopping/tree/master and has been tagged with createItem.

While I was writing this post I discovered that the CRUDify trait should be applied to the Item MetaMapper rather than the mapper class. This has been updated in Git hub against this tag.

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.

Wednesday, 19 August 2009

Simple Lift CRUD

Building a CRUD application in Lift is straight forward. First use maven to create a new Lift application .

mvn archetype:generate -U \
-DarchetypeGroupId=net.liftweb \
-DarchetypeArtifactId=lift-archetype-basic \
-DarchetypeVersion=1.0 \
-DremoteRepositories=http://scala-tools.org/repo-releases \
-DgroupId=com.shopping \
-DartifactId=shopping \
-Dversion=0.1-SNAPSHOT

After maven has finished examine the folder it has created called shopping. The folder shopping should contain a new pom.xml file and a direcotry structure like this

|-src
|---main
|-----resources
|-----scala
|-------bootstrap
|---------liftweb
|-------com
|---------shopping
|-----------comet
|-----------model
|-----------snippet
|-----------view
|-----webapp
|-------WEB-INF
|-------templates-hidden
|---test
|-----resources
|-----scala
|-------com
|---------shopping

Navigate to the shopping directory on the command line and try running the app using mvn jetty:run

The Lift domain classes are found in the src/main/scala/com/shopping/ model folder. For this simple app we will add a Item class.

package com.shopping.model

import net.liftweb._
import mapper._

class Item extends LongKeyedMapper[Item]
with IdPK with CRUDify[Long, Item]{
    def getSingleton
= Item
    object name extends MappedPoliteString
(this, 128)
    
object amount extends MappedInt(this)
}

object Item extends Item
with LongKeyedMetaMapper[Item]

The imports for this class pull in the Mapper framework. The Item class extends the LongKeyedMapper and mixes in the IdPK and CRUDify Lift traits. Each Mapper class requires a MetaMapper object in Lift and Item MetaMapper object is defined at the bottom of the file. In the Item class the getSingleton method returns the the Item MetaMapper object. There are also two attributes defined for the Item one is the name of the item and the other is the amount of that item to buy. The name extends MappedPoliteString which means the default value is "" and the contents will be cropped to fit the column. The amount extends MappedInt which simply maps the value to a Int database field.

In Lift the Schemifier is responsible for creating the database schema for the defined domain objects. To include the Item class in the databse create update the file /Boot/main/scala/bootstrap/liftweb/Boot.scala and update the call to Schemifier.schemify to include the Item MetaMapper object

Schemifier.schemify(true, Log.infoF _, User, Item)

The final thing to do is to add the Item CRUD operations to the menu. Since the Item class mixes in the CRUDify trait the Item object inherits the menus field and this this list can be concatenated with the list of menus all ready defined in the Boot.scala file

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

All that is left to do now is to run the web app using mvn jetty:run and create, delete and update some items on the shopping list

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