1. Introduction
In this tutorial, we’ll learn how to use templates in the Play Framework with Scala. We’ll define a template file, pass parameters from a controller, and learn how to use dependency injection with the templates.
2. Defining a Template
A Play Framework template works like a function that accepts template variables as parameters and returns HTML code. Let’s assume we want to create a page with links to some of the Baeldung Play Framework tutorials.
To start, we’ll create the index.scala.html file in the views.Baeldung package:
@(articles: List[(String, String)])
<h1>Baeldung Play tutorials</h1>
<ul>
@for(article <- articles) {
<li><a href="@article._2">@article._1</a></li>
}
</ul>
The first line of the template file contains the parameters. Our template expects a list of tuples as input.
The rest of the file is HTML code with the template operations. The template engine provides everything that starts with the @ character. In this example, we use the @for operator to iterate over the list of articles, the @article._1 variable to access the article title, and @article._2 to access the article URL.
3. Use a Template in a Controller
Now, let’s define a controller that uses the template to generate a response:
class ViewTemplateController @Inject()(cc: ControllerComponents)
extends AbstractController(cc) {
def index = Action { implicit request =>
val articles = List(
("Introduction to Play Framework", "https://www.baeldung.com/scala/play-framework-intro"),
("Building REST API in Scala with Play Framework", "https://www.baeldung.com/scala/play-rest-api")
)
Ok(views.html.Baeldung.index(articles))
}
}
We see that the Play Framework automatically creates a class when we define a template file, and we can use it as a function that returns the HTML output.
4. Using Custom Classes in Templates
In this article, we have a simple example with two parameters in the template. Therefore, it is easy to use tuples without making mistakes. However, what would happen if we needed 15 parameters? Fortunately, we don’t have to use only built-in types when we define a template.
If we define a class, we can use it both in the template and in the controller:
case class Article(title: String, url: String)
def withClass = Action { implicit request =>
val articles = List(
Article("Introduction to Play Framework", "https://www.baeldung.com/scala/play-framework-intro"),
Article("Building REST API in Scala with Play Framework", "https://www.baeldung.com/scala/play-rest-api")
)
Ok(views.html.Baeldung.with_class(articles))
}
Now, the template can accept a list of Articles as a parameter:
@(articles: List[Article])
<h1>Baeldung Play tutorials</h1>
<ul>
@for(article <- articles) {
<li><a href="@article.url">@article.title</a></li>
}
</ul>
5. Dependency Injection in Templates
In addition to parameters, we can also inject objects into the templates. This is useful when we want to use a helper function inside the template. In the next example, we’ll define a page with a short menu and use dependency injection to pass a price formatting to the template. Let’s begin with the controller:
case class Product(productName: String, price: Double, isAvailable: Boolean)
class MenuController @Inject()(template: views.html.Baeldung.menu, cc: ControllerComponents)
extends AbstractController(cc) {
def availableProducts = Action { implicit request =>
val products = List(
Product("coffee", 8.99, true),
Product("cake", 12.00, true),
Product("pancake", 3.00, false)
)
Ok(template(products))
}
}
Note that, in the code above, we inject an instance of the template into the controller object. This is necessary because we’re also injecting dependencies into the template:
@this(priceFormatter: PriceFormatter)
@(products: List[Product])
<ul>
@for(product <- products) {
<li>@product.productName @priceFormatter(product.price)</li>
}
</ul>
The first line of the template is a template constructor. Play Framework automatically injects the dependency when we define a constructor in the template and use the dependency injection to pass the template into a controller.
6. Conditional Logic
Our previous example has a boolean variable indicating whether the product is available or not. What if we want to change the appearance of unavailable products on the menu?
In the Play Framework templates, we can use the conditional operator to render the unavailable products differently:
@if(product.isAvailable) {
<li>@product.productName @priceFormatter(product.price)</li>
} else {
<li><strike>@product.productName @priceFormatter(product.price)</strike></li>
}
7. Defining Functions in Templates
If we add many loops and conditional statements to the template, its code will quickly become unreadable and difficult to maintain. We can solve this problem by defining functions in the template files.
Such functions accept parameters and return parts of the template. Therefore, we can rewrite the two previous examples using this approach:
@display(product: Product) = {
@if(product.isAvailable) {
<li>@product.productName @priceFormatter(product.price)</li>
} else {
<li><strike>@product.productName @priceFormatter(product.price)</strike></li>
}
}
<ul>
@for(product <- products) {
@display(product)
}
</ul>
The @display keyword defines a function called “display” with a single Product parameter. The body of the function is the returned template. Note that we can use the previously injected helper function in the template function as well.
8. Conclusion
This article demonstrated how to define templates in Play Framework, use them in controllers, and pass dependencies into the template.
The implementation of the PriceFormatter and the rest of the code is available over on GitHub.