Part 1 | Part 2 | Part 3 | Part 4 | Part 5
In this section we will enhance the Product Catalogue view to display our products and provide potential customers with a way to quickly locate and view their desired product or products.
We will implement a simple Product Catalogue page that lists available products and allows the user to filter the list by category. Of course this is a far cry from a professional Product Catalogue page but it will suffice for now.
Use your favourite editor to open the products.html page in the src/main/webapp sub-directory of our project, as shown below.
Replace the <p>Product list and details...</p> with the following to create the categories column.
<div id="productCatalogue" class="column span-17 last">
<div id="catagories" class="column span-4 sidebar">
<ul>
<lift:ProductCatalogue:showCategories>
<li><a category:filter_href=""><category:name /></a></li>
</lift:ProductCatalogue:showCategories>
</ul>
</div>
</div>
This creates an area on the page to display an unordered list of categories. At this stage, however, the reference to <lift: ProductCatalogue. showCategories > is not effective because we have not added the ProductCatalogue snippet yet. We will do that in the next step.
Create the ProductCatalogue.scala file in the src/main/scala/code/snippet sub-directory of our project. Edit the new Scala file and implement the ProductCatalogue class as shown below.
package code {
package snippet {
import _root_.scala.xml.{NodeSeq, Text}
import _root_.net.liftweb.util._
import _root_.net.liftweb.common._
import _root_.net.liftweb.http._
import Helpers._
import scala.xml._
import model._
class ProductCatalogue {
def showCategories(html: NodeSeq) : NodeSeq = {
categoriesList.flatMap(category =>
bind("category", html,
"name" -> category.name,
FuncAttrBindParam("filter_href", _ =>
Text("products/filter/" + (category.primaryKeyField)),"href")
)
)
}
private def categoriesList = Category.findAll();
}
}
}
The above class is known as a snippet. The ProductCatalogue class includes a showCategories method which is triggered by the <lift:ProductCatalogue.showCategories> tag we used in the products.html page earlier. The showCategories method in the above class returns a list of all Category items (as retrieved by the call to Category.findAll() in the same class).
Notice that for each category item we have defined a name field bound through the “name” -> category.name statement and a href bound through the FuncAttrBindParam("view_href", _ => Text("products/filter/" + (category.primaryKeyField)),"href")) statement. These are both used in the HTML file we created earlier. The name field allows us to display the category name and the href provides the link details to view a filtered list of products for the selected category. We will see how these operate when we check our progress in the next section.
Let’s restart the Jetty server and refresh the application in our browser then click on the Product Catalogue menu item to view our changes as shown below.
Notice that the Product Catalogue now shows two categories, Hand Bags and Sunglasses. The items are links and are responsive when the mouse moves over them. If you click on either item you will be presented with an error page as shown below.
The error is presented because we have not yet implemented the corresponding page or action for the category filter links. We will fix this problem a little later in this lesson. Before we do that however, let’s see how we can display our products in the Product Catalogue.
Once again, open the products.html page in the src/main/webapp sub-directory of our project as shown below.
Insert the following tags immediately above the last closing </div> tag in the file.
<div id="products" class="column span-10 topMenu">
<ul>
<lift:ProductCatalogue.showProducts>
<li>
<div id="product" class="column span-3">
<a product:view_href=""><product:name /></a><br/>
<product:shortDescription /> <br/>
$<product:pricePerUnit />
</div>
</li>
</lift:ProductCatalogue.showProducts>
</ul>
</div>
The product.html file should now appear as shown below.
Make sure to save your changes.
Open the ProductCatalogue.scala file in the src/main/scala/code/snippet sub-directory that we created earlier and add the following code to create the showProducts method and product details that we referenced in our view layer.
def showProducts(html: NodeSeq) : NodeSeq = {
productList.flatMap(product =>
bind("product", html,
"name" -> product.name,
"category" -> product.category,
"shortDescription" -> product.shortDescription,
"quantityType" -> product.quantityType,
"quantityPerUnit" -> product.quantityPerUnit,
"pricePerUnit" -> product.pricePerUnit,
FuncAttrBindParam("view_href", _ =>
Text("prod/view/" + (product.primaryKeyField)),"href")
)
)
}
private def productList = Product.findAll();
The showProducts method is triggered by the <lift:ProductCatalogue.showProducts> tag we used in the products.html page earlier. It returns a list of all Product items (as retrieved by the call to Product.findAll() in the same class).
Some of the information returned in the list is not used in the products.html page. These include the quantityType and quantityPerUnit. They are included for completeness of the information sent to the HTML file.
The ProductCatalogue class should appear as follows:
Let’s restart the Jetty server and refresh the application in our browser then click on the Product Catalogue menu item to view our changes as shown below.
Notice that the Product Catalogue now shows the three products that we added in the earlier part of this lesson. Notice that the product names are links. However, we have not implemented the backing code for these links so clicking on them will return an error page as shown below.
The error is presented because we have not yet implemented the corresponding page or action for the product view links. In the next section we will fix this problem and the similar problem we experienced earlier.
Notice also that the URL ends with prod/view/1 where the 1 is the ID of the selected product. We will rewrite the URL later because we do not want to create a separate page for each product ID.
In this section we will add a detail page to display product information and fix the product view link we created above to display this page for the selected product.
This section draws heavily from the links at the assembla Lift wiki page that demonstrates how to Create the UI for Mapper Entities. In particular, the following pages from Neural Monkey provided the instruction for this section:
Please refer directly to the source material for a deeper explanation and better understanding of the instructions in this section.
We will create a new page to display the product information. However, before we do that we should create a new sub-directory in which we store our page. The new sub-directory is src/main/webapp/prod as shown below.
Create the view.html page shown below in the new sub-directory.
We will add the relevant product information to this page soon.
In order for our product view page to be accessible it must be added to the sitemap defined in the Boot.scala file. Adding the following entry to the sitemap will allow users to access the new page and any other pages located in the new src/main/webapp/prod sub-directory.
Menu(Loc("Prod", Link(List("prod"), true, "/prod/view"), "Prod", Hidden)),
The revised Boot.scala file should appear as shown below.
We must also rewrite the URL as we mentioned earlier. The rewrite is achieved by adding the following in the Boot.scala class.
LiftRules.statelessRewrite.append {
case RewriteRequest(ParsePath(List("prod", "view", id),_,_,_),_,_) =>
RewriteResponse("prod" :: "view" :: Nil, Map("id" -> id))
}
Note that we are using LiftRules.statelessRewrite because LiftRules.rewrite has been deprecated.
The revised Boot.scala file should appear as shown below.
Restart the application and go to the Product Catalogue page. Click on the name of the first product in the Product Catalogue page. The following page should be displayed.
As we noted earlier the URL ends with /product/view/1 where the 1 is the ID of the selected product. We can use this ID as a parameter to enable us to retrieve the product details for display purposes. Let’s do that in the following steps.
Let’s enhance the view.html page to display some product details by adding the following tags after the <h2>Product Details</h2> heading.
<lift:ViewProduct.view>
Name: <product:name/>
Description: <product:shortDescription/>
Price per unit: $<product:pricePerUnit/>
</lift:ViewProduct.view>
The revised view.html file is shown below.
Next, let’s add the new ViewProduct.scala snippet referenced in our view.html file. The snippet should have a view method that retrieves the product details for the selected product id and binds them to the product html object. The required code is listed below.
package code {
package snippet {
import _root_.scala.xml.{NodeSeq, Text}
import _root_.net.liftweb.util._
import _root_.net.liftweb.common._
import _root_.net.liftweb.http._
import Helpers._
import scala.xml._
import model._
class ViewProduct {
var id = S.param("id") openOr ""
var product = try {
Product.findByKey(id.toLong)
}
catch {
case e:NumberFormatException => Empty
}
def view(html: NodeSeq): NodeSeq = {
product map ({ p =>
bind("product", html,
"name" -> p.name,
"shortDescription" -> p.shortDescription,
"pricePerUnit" -> p.pricePerUnit
)
}) openOr Text("Invalid Product")
}
}
}
}
Now if we run our application and select the first product from Product Catalogue page we will see the product details as shown below.
Admittedly it is not an attractive product details page but it does show the details we instructed it to display.
As we saw earlier, an error page is displayed when a prospective customer clicks on a category in the Product Catalogue page. In this section we will implement Snippet logic to handle the customer request in an appropriate manner.
When a prospective customer or visitor to our application clicks on a category in the Product Catalogue page (shown below) they are trying to filter the list of products displayed to include only the products belonging to the selected category.
Notice that the Product Catalogue page initially shows all products in the catalogue. Ideally, when the user clicks on the category Hand Bags, for example, only the Bulga 6236 and Manzoni N193 should be displayed. Similarly, only the Oakley Grapevine should be listed if the user clicks on the Sunglasses category.
However, as we saw earlier, clicking on a category launches an error page as shown below.
Notice that the error page URL ends with /products/filter/1. Our solution will leverage this fact to use URL rewriting in order to handle the request and filter the products for the requested category.
The rewrite is achieved by adding the following in the Boot.scala class.
LiftRules.statelessRewrite.append {
case RewriteRequest(ParsePath(List("products", "filter", id),_,_,_),_,_) =>
RewriteResponse("products" :: Nil, Map("id" -> id))
}
Notice that the RewriteResponse rewrites to products and not product. This is because we want to stay on the current page, not redirect to the product details page.
The revised Boot.scala file should appear as shown below.
If you run the application now, however, you will still get the same error page as before. This is because we have not implemented the filtering logic for our Product Catalogue.
Let’s add the filtering logic in the ProductCatalogue.scala snippet referenced in our products.html file. Open the ProductCatalogue.scala snippet file and locate the productList method shown below.
private def productList = Product.findAll();
We need to enhance this method to return all products when no category is selected (as it does now) and, alternatively, return products for the selected category when a category is selected. The revised method is shown below.
private def productList = {
if(selectedCategoryId=="") Product.findAll()
else Category.productsByCategory(selectedCategoryId.toLong)
}
We will add the productsByCategory method to our Category class in the next step. In the meantime, we have some more changes to make to our ProductCatalogue.scala snippet.
Staying in the ProductCatalogue.scala snippet, we need to add the selectedCategoryId and selectedCategory variables shown below.
var selectedCategoryId = S.param("id") openOr ""
var selectedCategory = try {
Category.findByKey(selectedCategoryId.toLong)
}
catch {
case e: NumberFormatException => Empty
}
The revisions to the ProductCatalogue.scala file are shown below.
As mentioned earlier, we need to add the productsByCategory method to the Category class in our model sub-directory. Open the Category.scala file in the src/main/scala/code/model sub-directory and add the method shown below.
def productsByCategory(categoryId: Long) = Product.findAll(By(Product.category, categoryId))
The modified Category.scala file is shown below.
Let’s run the application again and go to the Product Catalogue page to confirm it operates as expected.
Click on the Manzoni N193 product name. You should see the product details page shown below.
Click the browser’s back arrow to return to the Product Catalogue page. From the Product Catalogue page, click on the Oakley Grapevine product name to view the details for that product.
Click on the Product Catalogue item in the top menu to return to the Product Catalogue page.
Click on the Hand Bags category below the Product Catalogue heading. You should now see the same page with only the hand bag products listed as shown below.
Notice that Oakley Grapevine sunglasses are not listed because we have filtered for Hand Bags only. Notice also that the URL ends with products/filter/1.
Let’s click on the Sunglasses category item below the Product Catalogue heading to filter for Sunglasses only.
Oops! We have a problem. Instead of seeing our range of sunglasses, we are seeing an error page. Notice the URL ends with products/filter/products/filter/3. It seems we have a problem in our code setting the URL.
Also, although it is not shown here, a similar error message is displayed if we click on a product name to view its detail page after having filtered by category.
We will fix both problems in the next step.
Open the ProductCatalogue.scala file in our snippet directory and locate the showCategories method as shown below.
Change the "products/filter/" in the FuncAttrBindParam statement to "/products/filter/" (that is, add a forward slash "/" at the beginning as shown). This tells Lift that we are providing the URL relative to the root.
Make the same change in the showProducts method, too.
The revised file is shown below.
Let’s restart the application again and go to the Product Catalogue page to confirm our fix.
Click on Hand Bags in the category list.
Now click on Sunglasses in the category list.
Great! We have fixed the filter URL problem. Now click on the Oakley Grapevine product name and you should see the following page.
Fantastic! Our application is working as desired.
Continue to: Part 5
Part 1 | Part 2 | Part 3 | Part 4 | Part 5
Copyright 2010 – 2011, Hani Massoud. All rights reserved. Article copyright and all rights retained by the author.
Get your business online and get more customers through the door!
© 2013 Created by Hani Massoud.
Badges | Report an Issue | Privacy Policy | Terms of Service
© Copyright Centillion Group Pty Ltd
Commerity.com Commerce Community ¦ Creator
