Leaf Part 1 - Getting Started

Written by Tim on Sunday, November 12, 2017. Last edited on Friday, December 8, 2017.

This is the sixth tutorial of a series of tutorials on Vapor. For more, click here

Up until now, everything we have been doing has been focused on an API and all the requests we have been sending have been sent through a REST client. This is great for iOS apps (and other mobile clients) but at some point we are going to want to have a presence on the web and a way of using the application through a website. We can serve HTML files via Vapor relatively easily but we will want a way to display content from our application. The way to achieve this is to use a templating language where we can use a template to display different things of the same type - for instance, a template to display a reminder. Vapor has it's own templating language, Leaf and in this tutorial we will learn how to use it!

Adding Leaf to your Project

To be able to use Leaf we need to add it as a dependency to our project. First add it to your Package.swift as a dependency (remember you need to add it to the list of your dependencies and also add it as a dependency to the App target):

Next, edit your droplet.json configuration file in the Config directory and add in the following line (make sure you add a , to the preceding line so it is valid JSON):

{
...
"view": "leaf"
}

Now you can regenerate your Xcode project to pull in the new dependency with vapor xcode -y. Once you have your Xcode project open, edit the Config+Setup.swift file, import LeafProvider at the top and add the Leaf Provider to the list of providers:

Build and run the app to make sure everything is still working. The final thing that we need to do to be able to use Leaf is to create the directories where our templates live. By default, these live in the Resources/Views directory, so let's create those directories:

mkdir -p Resources/Views

Generating Views

Using a Template

The first thing we want to do is to create a page that will be displayed when someone visits our site at the root address - so http://localhost:8080 or https://www.brokenhands.io for example when we deploy. To do this, we will create a new controller to manage all of our public views, WebController. Because we need to render views with this controller we will use the dependency injection pattern to pass in the ViewRenderer to our controller so we can use it to generate references.

So let's create our new controller and regenerate our project like before:

touch Sources/App/Controllers/WebController.swift
vapor xcode -y

We can now create a new controller. So let's create a new controller as before with an addRoutes function and remember to pass in the ViewRenderer to the initialiser:

This is really simple - all we are doing is telling the ViewRenderer to make the view using the index template. We will come back to this in a minute as first we need to hook up the rest of our stuff. Next, register the route with the Droplet in our addRoutes function:

Here we initialise the WebController with the Droplet's view which is a ViewRenderer implementation. Now that we have hooked everything up we can head back to our index template. What we are doing with our viewRenderer.make("index") is telling it to generate a view using a template named index.leaf. So let's create that:

Save that in Resources/Views/index.leaf and then build and run the project. Hopefully, if you now go to http://localhost:8080 in your browser you should see:

Adding Parameters

We have our first web page! But currently it isn't very 'templatey', it is only rendering a static page and we don't need Leaf to do that! So let's add some dynamic stuff to our template. In the <head> element we are specifying a title, but what if we want to pass that in? We can pass parameters in to our templates easily as a [String: NodeRepresentable] dictionary, so let's pass in the title:

If you build and rerun our app, when you reload the page it should work and look exactly the same! Now admittedly, changing a static title for an injected one isn't particular impressive but you can start to see how we can generate complex views.

Loop Statements

This is a reminders app after all so it isn't unreasonable to want to display all of our reminders on our homepage! So what we can do is get all of the reminders from the database and then pass them to our template. HTML unfortunately isn't strictly typed and only really works as text files so we need a way of converting our model into something that Leaf can generate into text. Vapor uses the Node object for this, so the first thing we need to do is to make our Reminder model conform to NodeRepresentable:

For now we will only require the title and the description but we will also add in the id as it will come in useful later. Now that we can convert our Reminder model into something that Leaf can use, we can pass it through to our template in our WebController as a new parameter. To get all of the reminders from our database we can simply use Fluent and call Reminder.all() which will return us an array of all the reminders! So our new index handler will look like:

Finally we can use our reminders in our Leaf template. To display all of the reminders on the page we will use a simple table. We can use the loop tag in Leaf to loop through all of our reminders and display them in the table. So change the body of our template to look like:

If you build and run the app you should see an empty table as we don't have any data! You can use the API to add some data to the app, but you can run this bash script if you want any easy way of doing it. Once you have added some data, if you reload the page it should look something like:

Splitting Up Templates

Now that we have a page for all our reminders, next we will want a page for a single reminder. This should show all the information for a single reminder, but most of the page will actually be pretty much the same, especially since we pass the title in. We could copy the template and edit the <body> but copying code is bad - if we want to change the <head> for our site, we would have to go and do it in every template, which is not good! To avoid this we can use a base template and then just write templates for the bits that are different between each page.

First copy our index.leaf template and rename it to base.leaf. Inside base.leaf replace everything inside the <body> tags with #import("body"). Your base.leaf should now look like:

This tells Leaf to use the base.leaf template as its base and then to inject the #import("body") part in base.leaf with what we have defined in the #export. The result is the same, but it now means we can add new pages easily. So we can now add a new template for a single reminder.

Leaf's If Statement

Let's create a new template called reminder.leaf and in this we want to display the title and description of a reminder, it's creator and any categories. However, not every reminder will have a category, so we can use the #if() tag to check for this. This works by testing the parameter to see if it exists and/or if it true; we can simply use it to test for nil - if it is not nil then we will loop through the categories, otherwise we won't show anything. So our template will look something like:

So in this template we will pass in the reminder with the parameter name reminder. We will pass in the user as user and if any categories exist we will pass them in under the parameter categories. You may have noticed that we will need to conform both User and Category to NodeRepresentable, so let's do that. First, for User:

Now if we build and run our app and add some data to the database we should be able to navigate to http://localhost:8080/reminders/1/ and see something like:

However the final thing we want to do for our reminder page is to make it easy to access! We don't want users having to type in the address, we should provide links on the homepage. So in the loop for all our reminders, let's set them up as links, using the reminder's ID we pass into the Node object:

Expanding our Website

Now that we have the basics of our site we can add more pages, mainly the users and categories pages.

Users

We want two pages for our users - one to display all of them and one to display an individual user. This will be very similar to our reminders, so let's create a new template users.leaf. Inside it will simply be:

Inside this we will simply loop through the users parameter which we will pass in and display them in a list. Each user will be linked to a user's page which we will create later. So we can create our route handler in our WebController:

If you build and run the app, you should see the updated home screen with your list of users you can click through to. The final thing we can do for our new user pages is to link through to the user on the reminder page. If you change the 'Created By' part of our reminder.leaf template to include a link then users of the site can easily click through to the user:

<h2>Created by <a href="/users/#(user.id)">#(user.name)</a></h2>

Categories

We basically want to duplicate everything we have done for our users for categories! So start by creating a categories.leaf template which will look like:

Finally, edit our reminder.leaf page to link to the categories when we list them inside our loop:

<li><a href="/categories/#(category.id)">#(category.name)</a></li>

If you build and run the app now you should be able to link through to all our pages.

Conclusion

Hopefully after this tutorial you should understand the basics of Leaf and how to create dynamic pages from templates. In the next tutorial we will continue to dig deeper into Leaf and look at improving the navigation and look of our site, how to create reminders from our website and what Node's Context is for.