Product Catalog Application using Play and Scala Part 7 – View and template

Thu, Nov 20, 2014

Play Programming #Play #programming #scala

Before we continue developing all the pages, we need to first create a page template for this apps. The reason of using a page template is to standardize the look and feel throughout the apps and to reduce duplicated HTML code. Less code, less maintenance issues. Our page template will consist of a header navigator and content. We will be using Bootstrap for this.

Before that, let’s look at the default template created by Play framework, which is in app/views/main.scala.html .

@(title: String)(content: Html)

<!DOCTYPE html>

<html>
    <head>
        <title>@title</title>
        <link rel="stylesheet" media="screen" href="@routes.Assets.at("stylesheets/main.css")">
        <link rel="shortcut icon" type="image/png" href="@routes.Assets.at("images/favicon.png")">
        <script src="@routes.Assets.at("javascripts/hello.js")" type="text/javascript"></script>
    </head>
    <body>
        @content
    </body>
</html>

The first line, @(title: String)(content: Html), means that this template takes 2 parameter, first is a String type param and second is a Html case class. Every page template acts like a function, that let we call it anywhere we want, and that includes the Play console. That makes testing much easier.

Here is the template after I’ve modified. I am using the template provided in Bootstrap documentation.

@(title: String)(content: Html)
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>@title</title>

        <!-- Bootstrap -->
        <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">
        <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css" rel="stylesheet">
        <link rel="stylesheet" href="@routes.Assets.at("stylesheets/main.css")">
        <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
        <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
        <!--[if lt IE 9]>
          <script src="//oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
          <script src="//oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
        <![endif]-->
    </head>
    <body role="document">
        <div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
            <div class="container">
                <div class="navbar-header">
                    <a class="navbar-brand" href="#">Product Catalog</a>
                </div>
            </div>
        </div>
        <div class="container theme-showcase" role="main">
            @content
        </div>

        <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
        <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
        <!-- Include all compiled plugins (below), or include individual files as needed -->
        <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
    </body>
</html>

So basically I just integrated the Bootstrap theme into the apps. And how do we use this template? We will now create a product listing page and save it as app/views/products/list.scala.html .

@(products: List[Product])

@main("Products") {
    <div class="page-header">
        <h3>Products</h3>
    </div>
    <div class="row">
        <div class="col-md-12">
            <a href="@routes.Products.add">Add a new product</a>
            @Option(products).filterNot(_.isEmpty).map { product =>
                <table class="table table-striped">
                    <thead>
                        <tr>
                            <th>Id</th>
                            <th>Name</th>
                            <th>Category</th>
                            <th> </th>
                        </tr>
                    </thead>

                    <tbody>
                        @products.map { product =>
                            <tr>
                                <td><a href="@routes.Products.edit(product.id)">@product.id</a></td>
                                <td>@product.name</td>
                                <td>@product.categoryName</td>

                                <td>
                                <a href="@routes.Products.delete(product.id)" onclick="return confirm('Delete this record?');">Delete</a>
                                </td>
                            </tr>
                        }
                    </tbody>
                </table>
            }.getOrElse {
                <div class="well">
                    <em>No records.</em>
                </div>
            }
        </div>
    </div>
}

I will explain on how the looping and some of the codes works in the next article. As per mentioned, every page is a function, and so is this. It’s taking a list of Product from the caller, loop it and display it. If product list is empty, it will display a “No records” message. Look at the line with @main(“Products”) , here the page will actually call the main.scala.html, then pass the content to it to be displayed in the template.

And in the ProductController, we will updating the list function to the following.

def list = Action{
  Ok(html.products.list(Product.list))
}

The outcome of the listing page will be like the following.