Episode #352

Nesting Templates and Partials

Series: Server-side Swift with Vapor

8 minutes
Published on September 1, 2018

This video is only available to subscribers. Get access to this video and 572 others.

When working with web pages, you will almost certainly want to share a considerable amount of HTML. By nesting templates inside of master templates, we can share common HTML structure, layout, and share styles and scripts. We will see how to define sections that can be customized inside of your templates, as well as how to extract common components into partials that you can embed inside of other templates.

Use Functions for Multiple Routes

To share logic between multiple routes, we can extract our handler code into a function.

func getHello(_ req: Request) throws -> Future<View> {
    let user = try? req.parameters.next(String.self)
    let context = UserPage(user: user, categories:  [
        "Articles", "Recipes", "Reviews"
        ])
    return try req.view().render("hello", context)
}

This function needs to match the signature that route blocks expect. It must accept a single Request parameter and define the return value explicitly. It also must be a throws function.

Then we can define multiple routes that use this function.

router.get("hello", use: getHello)

// hello/ben
router.get("hello", String.parameter, use: getHello)

Looping over Content in Leaf Templates

To display our list of categories in Leaf, we can use the #for statement:

<ul>
  #for(category in categories) {
    <li>#(category)</li>
  }
</ul>

Defining a Master Template

Our pages probably want to share quite a bit of layout, styles, and javascript. For this we can define a master template and cut out sections that we can inject the current page's content into.

First, we create a master.leaf template:

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title>#get(title)</title>

    <style media="screen">
      body {
        font-family: 'Helvetica', sans-serif;
        font-size: 18px;
        color: #444;
        background-color: #eee;
      }
    </style>
  </head>
  <body>
    #get(content)
  </body>
</html>

Note that we have defined 2 dynamic sections here. Leaf will look for variables called title and content that our templates must provide.

We can update our hello.leaf template to use this master template like this:

#set("title") { Hello Leaf Templates }

#set("content") {
  <h1>Hello from Leaf!</h1>

  #if(user) {
    <h2>You are logged in as #(user)</h2>

    <ul>
       #for(category in categories) {
         <li>#(category)</li>
      }
    </ul>
  } else {
    <h2>You are not logged in</h2>
  }
}

#embed("master")

Extracting Partials

If we want to break down our template into reusable sections, we can move those into their own templates, which are called partials.

I'm going to use a Rails convention and prefix partials with an underscore to make it obvious which templates are intended to be rendered by the router, and which ones are just embedded within other templates.

Let's create _categories.leaf:

<h3>Categories</h3>
<ul>
  #for(category in categories) {
    <li>#(category)</li>
  }
</ul>

Then we can update hello.leaf to embed this:

...
#if(user) {
  <h2>You are logged in as #(user)</h2>

  #embed("_categories")
}
...

This episode uses Leaf 3.0.1, Vapor 3.0.8.