Templates

package:

  1. text/template : when dealing with command line applications.
  2. html/template : when dealing with webapps.

In the first chapter we had a cursory glance over the concept of templates. This chapter is dedicated entirely to templates. As we said previously, a web application responds to certain URLs and gives an html page to the browser which the browser then interprets and shows to the end user. This html page which is sent to the browser is what is called templates in the backend for we have a template which stores some variables, and in real time data is provided into the template which makes it a complete html page.

Let's take a practical example. Suppose we are building a micro blogging site. We would start with creating the front end in html. Our microblogging site will show Hi User on the right corner.

In our static html we write this <p>Hi User</p>

But if we serve this page on our webserver it'll not change anything, it'll show Hi User, the name of the user won't come magically, we have to put it into the page somehow, here we use a variable so in Go we'd approach this by using a variable named {{.Name}} so our html now will be <p>Hi {{.Name}}</p>. The . is mandatory.

Now, this is the logic we apply to all our html pages, keeping such {{}} variable expansion parameters and serving the value of the parameter while executing the template. If you are wondering how we'd do that, this is the syntax

homeTemplate.Execute(w, context)

//Context is the struct passed to templates
type Context struct {
    Tasks      []Task
    Name       string
    Search     string
    Message    string
}

These are the three parts of using templates, first you need to create types like we have created the Context type, then we need to read the template, then we need to use the components of that type in our template file. So what remains now is passing an object of that type during template execution.

Every template requires the context object because that is what defines the data to be populated in the template.

Reading template:

templates, err = template.Must(template.ParseFiles(allFiles...))

template.Must

Must is a helper that panics if the error is non-nil where allFiles is populated as below:

var allFiles []string
templatesDir := "./public/templates/"
files, err := ioutil.ReadDir(templatesDir)
if err != nil {
    fmt.Println("Error reading template dir")
}
for _, file := range files {
    filename := file.Name()
    if strings.HasSuffix(filename, ".html") {
        allFiles = append(allFiles, templatesDir+filename)
    }
}

For the sake of demonstration of how to parse multiple files we have used the ParseFiles method to parse all the .html files, you can use the ParseGlob method which is available in the standard library.

template.Must(template.ParseGlob(templatesDir + "*.html"))

The definition of ParseGlob is:

func ParseGlob(pattern string) (*Template, error)

We have to specify the Pattern for the ParseGlob function, but we have passed the path and the pattern because just passing the pattern is useless, we need the path where the pattern will be applied to find the list of all files meeting the criteria.

Note

  1. ... operator : allFiles is a string slice and allFiles... passes the function a parameter as a string.

  2. ParseFiles performance:

    There is one point to note here about performance in parsing files, typically a template file won't change until there is some major change to the codebase so we should only parse the files once, rather than keep this code in each view handler and doing a

    template.ParseFiles("home.html")
    template.Execute(w, context)
    

    This block of code will unnecessarily read the html page each time while serving the response of a request, which means if ten people are using our blogging site then for each page they visit the code will read the html page, but there is no need to do things this way, we can read the html files once at the start and then use the Lookup method of the template class,

    homeTemplate = templates.Lookup("home.html")
    homeTemplate.Execute(w, context)
    

Sub templating

We learnt how to pass data to templates and display it in the html page, it so happens that a lot of code is used in all templates suppose the navigation bar or the header, then we need not write the same chunk everywhere, we can create a template to store that, and use sub templating {{template "_head.html" .}}

Here, we have identified a chunk of code which we want to replicate and put it in _head.html. Then we put the above statement in each template file where we wish to have our header. This way templates become a lot smaller and don't contain replicated code everywhere. Do note that the . before the first } is intentional and it means that all the variables which were passed to the current template are passed to the sub template.

The sub templates which we create depends on our requirement, but the basic point behind it is that if we are going to repeat a block of HTML code then we should form it as a template.

The main point to note over here is that when we are going to use our templates or sub templates, all those html files need to be parsed. In templating we have a variable which stores all templates even if we aren't going to refer to that directly in our code using the Lookup method on the template variable. The lookup method takes the name of the template. When the {{template _head.html .}} is evaluated, it goes to our template variable and tries to find out the template parsed with the exact name, if it is not present then it doesn't complain by default, we should use Must method if we want it to complain.

Example

file views/views.go

package views

import (
    "io/ioutil"
    "net/http"
    "os"
    "strconv"
    "strings"
    "html/template"
)

var (
    homeTemplate      *template.Template
    deletedTemplate   *template.Template
    completedTemplate *template.Template
    loginTemplate      *template.Template
    editTemplate      *template.Template
    searchTemplate    *template.Template
    templates         *template.Template
    message           string 
    //message will store the message to be shown as notification
    err               error
)

//PopulateTemplates is used to parse all templates present in
//the templates folder
func PopulateTemplates() {
    var allFiles []string
    templatesDir := "./public/templates/"
    files, err := ioutil.ReadDir(templatesDir)
    if err != nil {
        fmt.Println("Error reading template dir")
    }
    for _, file := range files {
        filename := file.Name()
        if strings.HasSuffix(filename, ".html") {
            allFiles = append(allFiles, templatesDir+filename)
        }
    }

    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
    templates, err = template.Must(template.ParseFiles(allFiles...))
    // templates, err := template.Must(template.ParseGlob(templatesDir+".html"))
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
    homeTemplate = templates.Lookup("home.html")
    deletedTemplate = templates.Lookup("deleted.html")

    editTemplate = templates.Lookup("edit.html")
    searchTemplate = templates.Lookup("search.html")
    completedTemplate = templates.Lookup("completed.html")
    loginTemplate = templates.Lookup("login.html")

}

//ShowAllTasksFunc is used to handle the "/" URL 
//TODO add http404 error
func ShowAllTasksFunc(w http.ResponseWriter, r *http.Request) {
    if r.Method == "GET" {
        context := db.GetTasks("pending") 
        //true when you want non deleted notes
        if message != "" {
            context.Message = message
        }
        homeTemplate.Execute(w, context)
        message = ""
    } else {
        message = "Method not allowed"
        http.Redirect(w, r, "/", http.StatusFound)
    }
}

Looping through arrays

    <div class="timeline">
        {{ if .Tasks}} 
            {{range .Tasks}}
                <div class="note">
                <p class="noteHeading">{{.Title}}</p>
                <hr>
                <p class="noteContent">{{.Content}}</p>
                </div>
            {{end}} 
        {{else}}
            <div class="note">
                <p class="noteHeading">No Tasks here</p>
                <p class="notefooter">Create new task<button> here </button> </p>
            </div>
        {{end}}
    </div>

The {{ if .Tasks}} block checks if the array is empty or not, if it is not then it'll go to the {{ .range .Tasks }} which will loop through the array, then the {{.Title}} will access the title and {{.Content}} will access the Content of that particular Task instance and we'll see all the tasks as a list.

Template variables

We have this scenario, we have a range of categories in the navigation drawer and if we visit the /category/study, then our category name should be highlighted in the navigation drawer. We do this by storing the value of the .Navigation field - which tells if it is a Edit page/Trash page/Category page

{{$nav := .Navigation}}
{{ range $index, $cat := .Categories }}
  <li class="sidebar-item">
   <a href="/category/{{$cat.Name}}" 
       {{ if eq $cat.Name $nav }} 
           class="active"
       {{end}} >
          <span class="nav-item">
          {{$cat.Name}}
      </span> 
      <span class="badge pull-right">
          {{$cat.Count}}
      </span>
    </a>
   </li>
{{end}}

Creating variables

Creating variables in templates is similar to creating variables in Go, the variable name needs to be prefixed by '$'.

For understanding why template variables are required, we need to go into the above block of code, when we are using the range operator, we are parsing the array and the range block gets the elements of the block by default.

My Context type is

type Context struct {
    Tasks      []Task
    Navigation string
    Search     string
    Message    string
    CSRFToken  string
    Categories []CategoryCount
    Referer    string
}

Hence when we do a {{range .Categories}} we will be accessing each element as {{.}} per loop. If we use any other valid variable here, like the .Navigation, then the block tries to find the .Navigation inside .Categories, which obviously isn't present.

Now we need to make the page aware of which category it is showing, should the user go to the /categories/ page.

The logic behind making that page category aware is that we create a CSS class to mark that particular category as active, but for that, we'd need to access the Category name and Navigation within the range .Categories block, thus we create two variables, one to store the category name from the .Navigation variable and use the if statement like

{{if eq $cat $nav}} class="active" {{end}}

This will mark only that particular category as active and not all of them.

In templating logic the operator is first and then the two operands.

eq: equal, le: less than equal, ge: greater than equal

You can also use if else clause like below:

{{$url := ""}}
<div class="navbar-header">
    {{if .Search}} 
           <a class="navbar-brand"> 
               Results for: {{.Search}}
           </a> {{else}} 

           {{ if eq .Navigation "pending"}}
            {{ $url:="" }} 
           {{ else if eq .Navigation "completed"}} 
                    {{ $url := "" }} 
           {{else if eq .Navigation "deleted"}}
            {{$url := ""}} 
        {{else if eq .Navigation "edit"}} 
                {{$url := ""}} 
        {{else}} 
                {{$url := "/category"}}
        {{end}}

    <p class="navbar-brand" href="{{$url}}/{{.Navigation}}">
        {{if eq .Navigation "pending"}} 
            Pending 
        {{ else if eq .Navigation "completed"}}
            Completed 
        {{ else if eq .Navigation "deleted"}}
            Deleted
        {{ else if eq .Navigation "edit"}} 
            Edit 
        {{else }} 
            {{.Navigation}} 
        {{end}} 
    </p>
    {{end}}
</div>

Here we had some complicated stuff, if our page is a search one, we had to show Results for : <query>, pending, deleted,edit, completed for respective and /category/ if we are in the category. So we defined an empty URL and assigned the URL values according to the complicated if else structure.

Homework

  1. Take the html pages from http://github.com/thewhitetulip/omninotesweb and modify them to suit our purposes We would need to create one template each for the ones we mentioned in the above variable declaration, use templating as far as possible and later check your results with http://github.com/thewhitetulip/Tasks, please do the exercise on your own first and then only check the Tasks repository.
  2. Implement a search interface. Take a query as input, search tasks for that query and return an html page with the query highlighted in the resulting page.

-Previous section -Next section

results matching ""

    No results matching ""