Getting Started with Leaf (Vapor 3)

1114 Views

This tutorial is going to be an introduction to the template engine for Vapor, Leaf. Here, we'll discuss:

  • Adding the Leaf Package
  • Confguring your project to use Leaf as its default view renderer
  • How to display your model's info/variables in a Leaf file
  • Using #embed() and #set() tags to reuse content
  • Other useful Leaf tags/syntax

First, create a new Vapor project and call it LeafGettingStarted, by running:

    vapor new LeafGettingStarted
Then, cd into the project with cd LeafGettingStarted, and then run vapor xcode to generate the Xcode project.

Add the Leaf Package

The first thing we need to do is add the Leaf package to our Package.swift file. Add the following line to your dependencies array:
    .package(url: "https://github.com/vapor/leaf.git", from:"3.0.0-rc.2.2")
Then, add "Leaf" to your targets array as well:
    .target(name: "App", dependencies: ["FluentSQLite", "Vapor", "Leaf"]),

Add the Resources and Views folders

If you are reading this tutorial at a later date, and using the Vapor Web template, then you may not need to do this (nor will you need to have manually added Leaf to your project). But with the API template, you'll need to manually create a Resources and Views folder. To do so, simply do the following:



Then, close your Xcode project, run vapor update and regenerate your Xcode project. Your project directory should look like this before proceeding (they must be blue folders, not yellow directories). They should appear in your Xcode project after regenerating it.

Configuring Leaf

In order to use Leaf in our project, there are a few things we must do in configure.swift first:

  • At the top of your file, add import Leaf
  • Where you register your providers, add try services.register(LeafProvider())
  • Add config.prefer(LeafRenderer.self, for: ViewRenderer.self)

Great! Now we are ready to use Leaf!

Leaf syntax

Very briefly before starting, let's go over some basic syntax and theory. We learn best by examples, but it's good to know at least a little theory before diving in :D

  • To access variables in a leaf file, use #(variableNameHere)
  • Variables accessed in leaf must have been set from the controller
  • For loop syntax is: #for(post in posts) { The post title is #(post.title) }
  • To get the number of items in an array, use the count tag: #count(posts)
  • Other useful tags include #capitalize(name) #uppercase(name) #lowercase(name)

Syntax Coloring

If you are using Xcode, the syntax coloring can be an issue. To change the syntax coloring for a leaf file, select the file, and then go to Editor -> Syntax Coloring -> HTML. Unfortunately, Xcode seems to forget my preferences after every Xcode restart, so you'll need to get used to doing this for the time being :/

Creating our leaf files

Finally, the moment we've all been waiting for! First, under Resources > Views, create our base layout file, base.leaf. Copy and paste the following html in it:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
</head>

<body>

</body>
</html>

Then, create another file, allTodos.leaf, and just write a simple line of "Hello World" text in it for place holder purposes.

Next, add the following route in routes.swift to return a view:

    router.get("allTodos") { request -> Future in
        return try request.view().render("allTodos")
    }

The code above will return to us the allTodos.leaf file.

At this point, run your server and navigate to http://localhost:8080/allTodos, and you should see the "Hello World" text. Your first leaf view! Exciting!

Embedding

Ideally, we would like to embed our allTodos file inside our base.leaf file. This way, we could have a navigation bar, meta tags, or some other setup that is consistent across all pages. This is quite simple. First, change the content of your allTodos.leaf to be the following:
    #set("content") {
        Hello World
    }
    #embed("base")
Then, in the body tag of your base.leaf, add:
    #get(content)

Here, we embed the base HTML file in our allTodos.leaf. Then, wherever we place our #get(content) in base.leaf, our content that we set is inserted there. In this case, it is inserted in the body of the HTML.

Displaying Todo titles inside our Leaf file

Of course, the most obvious use case for a templating engine is displaying some data from our application to the user.

In order to do this, we'll need to do a few things:

  • Create a struct representing the data we wish to pass to the view
  • Change our allTodos route to use this struct to pass the data to the view
  • Change our allTodos.leaf file to use the proper syntax to print them out
  • Create some sample Todo items using a Rest client, such as Postman

So, let's create the struct. We expect to pass back an array of Todos to the template, so create the following struct:

    struct TodosContext: Encodable {
        var ourTodos: [Todo]
    }

Then, change the allTodos route in routes.swift to be the following:

    router.get("allTodos") { request -> Future in
        return Todo.query(on: request).all().flatMap(to: View.self) { allTodos in
            let todosContext = TodosContext(ourTodos: allTodos)
            return try request.view().render("allTodos", todosContext)
        }
    }

Here, we changed the route to first retrieve all our Todos, and then we use the TodosContext we created to pass them to the view.

In our file, our array of todos is accessible by whatever name we give it in our struct--in this case, ourTodos.

Changing the allTodos.leaf

Next, change allTodos.leaf to have the following implementation:

    #set("content") {
        #for(todo in ourTodos) {
            <h4>The title is #(todo.title)</h4>
        }
    }
    #embed("base")

Here, we loop through our todos array, and simply print out the name.

Finally, restart your server, and then create some sample Todos using a rest client. Once you have created a few, navigate to http://localhost:8080/allTodos.

You should see your Todos printed out on the screen. Isn't it so satisfying?

Partials

Sometimes we have a view that we want to reuse and just insert into many different places. Maybe it's a generic blog post view, a navigation bar, footer, header, etc. So, to keep our code DRY, we can use partials to keep these blocks of reusable code for us, and then embed them in our other views where needed.

To demonstrate how to do this in Leaf, create a new file, todo.leaf. Then, add the following content to it:

   <div style="background-color: grey; width: 200px; height: 80px">
        The todo is #(todo.title) and this is index #(index)
    </div>
    <br/>
(yes, you shouldnt do inline css--but I'm trying to keep this simple :) )

Notice also here that we are printing out the index. Inside a for loop, Leaf gives us access to the index of the current iteration as well

Then, in your allTodos.leaf, change the implementation to the following:

   #set("content") {
    #for(todo in todos) {
        #embed("todo")
        }
    }
    #embed("base")

It works like this: Any variable that you have defined in your original struct, or leaf file (such as ourTodos and todo), is then available in your partial under the same variable name.

Build, run, create a few todos, and then navigate to /allTodos, and you'll see it's using your new reusable view.

Further Uses

We can also use the #set() and #embed() tags for organizing stylesheets. For example, you may have some page-specific styles you'd like to define for only one page. In such a case, in your specific file you could do:

    #set("stylesheets") {
        <link rel="stylesheet" href="/styles/myStylesheet.css">
    }

And then in your base.leaf file:

    #get(stylesheets)

If statements

If statements are simple with Leaf. We can evaluate conditions such as true/false:

    #if(bool) {
        The value of boolean is #(bool)
    } else {
        It is false
    }

Or, we can do the same to see if a given variable is present:

    #if(variableHere) {
        The value of boolean is #(variableHere)
    } else {
        No variable present!
    }

That's all for this brief intro to Leaf. You can find the completed code for this demo here. Happy coding!



Enjoy this article? Consider supporting VaporForums by following us on Twitter! Get the latest Vapor articles, tutorials, and news.