Views

Basic Usage

Views contain the HTML served by your application and separate your controller/application logic from your presentation logic. The views are parsed by the html/template package.

A view should include the four define blocks (title, head, content, and foot) and may look like this:

{{define "title"}}About Blueprint{{end}}
{{define "head"}}{{end}}
{{define "content"}}
	<div class="page-header">
		<h1>{{template "title" .}}</h1>
	</div>
	<p>Blueprint lays the foundation for your web application using the Go language.</p>
	{{template "footer" .}}
{{end}}
{{define "foot"}}{{end}}

Since this view is stored at view/about/index.tmpl, we render it using the view helper package like so:

// import "github.com/blue-jay/core/view"
c := flight.Context(w, r)
v := c.View.New("about/index")
// Variables would go here like this: v.Vars["first_name"] = session.Values["first_name"]
v.Render(w, r)

If you don’t have to pass any variables to the template, you could shorten it like this:

// import "github.com/blue-jay/core/view"
c := flight.Context(w, r)
c.View.New("about/index").Render(w, r)

Base Template

By default, the view/base.tmpl template is used as the base template (as specified in env.json). If you want to change the base template for a template, you can try this:

c := flight.Context(w, r)
v := c.View.New("about/index").Base("alternate")
v.Render(w, r)

A shorter way to specify the view with a different base template and then render is like this:

c := flight.Context(w, r)
c.View.New("about/about").Base("alternate").Render(w, r)

View Package

The core/view package is a wrapper for the Go html/template package and provides the following:

  • thread-safe template caching
  • ability to extend the list of functions available in templates
  • ability to modify the variables available in templates

The set up of the view package is handled by the lib/boot package. The config is then passed to flight so it can be accessed by controllers.

// Set up the views
config.View.SetTemplates(config.Template.Root, config.Template.Children)

// Set up the functions for the views
config.View.SetFuncMaps(
	config.Asset.Map(config.View.BaseURI),
	link.Map(config.View.BaseURI),
	noescape.Map(),
	prettytime.Map(),
	form.Map(),
)

// Set up the variables and modifiers for the views
config.View.SetModifiers(
	authlevel.Modify,
	uri.Modify,
	xsrf.Token,
	flash.Modify,
)

// Store the variables in flight
flight.StoreConfig(*config)

Organization

The HTML templates are organized in folders under the view folder:

about/index.tmpl	 - quick blurb about the app
home/index.tmpl      - public and authenticated home page
login/index.tmpl     - login page
note/create.tmpl	 - create a note
note/edit.tmpl		 - edit a note
note/index.tmpl		 - view all notes
note/show.tmpl		 - view a note
partial/favicon.tmpl - favicon metadata generated by gulpfile.js
partial/footer.tmpl	 - footer at the bottom of all pages
partial/menu.tmpl	 - menu at the top of all pages
register/index.tmpl	 - register page
base.tmpl            - base template for all pages

View Functions

The Go template packages supports passing a FuncMap which maps names to a function. This means you can add functions so they are available to the views. These functions are stored in the viewfunc folder. Here is an example of a LINK function that can be used to create hyperlinks and the code is stored in viewfunc/link/link.go:

// Package link provides a funcmap for html/template to generate a hyperlink.
package link

import (
	"fmt"
	"html/template"
)

// Map returns a template.FuncMap for LINK that returns a hyperlink tag.
func Map(baseURI string) template.FuncMap {
	f := make(template.FuncMap)

	f["LINK"] = func(path, name string) template.HTML {
		return template.HTML(fmt.Sprintf(`<a href="%v%v">%v</a>`, baseURI, path, name))
	}

	return f
}

To use this function in a template, you would write it like this:

{{LINK "register" "Create a new account."}}

And the code would render like this:

<a href="/register">Create a new account.</a>

Once you create a new funcmap, you make it available to the views by adding it to the view.SetFuncMaps() function in the lib/boot/boot.go file:

// Set up the functions for the views
config.View.SetFuncMaps(
	config.Asset.Map(config.View.BaseURI),
	link.Map(config.View.BaseURI),
	noescape.Map(),
	prettytime.Map(),
	form.Map(),
)

Included Functions

There are a few functions that are included to make working with the templates and static files easier:

<!-- CSS file with media attribute -->
{{CSS "static/css/normalize3.0.0.min.css" "all"}}
<!-- parses with timestamp to -->
<link media="all" rel="stylesheet" type="text/css" href="/static/css/normalize3.0.0.min.css?1435528339" />

<!-- JS file -->
{{JS "static/js/jquery1.11.0.min.js"}}
<!-- parses with timestamp to -->
<script type="text/javascript" src="/static/js/jquery1.11.0.min.js?1435528404"></script>

<!-- Hyperlinks -->
{{LINK "register" "Create a new account."}}
<!-- parses to -->
<a href="/register">Create a new account.</a>

<!-- Output an unescaped variable (not a safe idea, but it is useful when troubleshooting) -->
{{.SomeVariable | NOESCAPE}}

<!-- Time format for mysql.NullTime -->
{{NULLTIME .SomeTime}}
<!-- parses to format -->
3:04 PM 01/02/2006

<!-- Time format helper for mysql.NullTime that shows the latest of the two variables -->
{{PRETTYTIME .CreatedAt .UpdatedAt}}
<!-- parses to format -->
3:04 PM 01/02/2006

View Variables

There is an easy way to add variables so they are available in the views. The viewmodify folder contains packages that define variables in the view.Vars map. Since you are editing the map right before it renders, it will overwrite any other variables that were set in the controllers so it’s best to choose names or pick a naming convention for your variables.

You can also modify the view.Info struct before it renders if you need to display a different view or use a different base template.

In the viewmodify/authlevel/authlevel.go file, the AuthLevel variable is made available so the views can determine if the user is authenticated or not:

Source

// Package authlevel adds an AuthLevel variable to the view template.
package authlevel

import (
	"net/http"

	"github.com/blue-jay/blueprint/lib/flight"
	"github.com/blue-jay/core/view"
)

// Modify sets AuthLevel in the template to auth if the user is authenticated.
// Sets AuthLevel to anon if not authenticated.
func Modify(w http.ResponseWriter, r *http.Request, v *view.Info) {
	c := flight.Context(w, r)

	// Set the AuthLevel to auth if the user is logged in
	if c.Sess.Values["id"] != nil {
		v.Vars["AuthLevel"] = "auth"
	} else {
		v.Vars["AuthLevel"] = "anon"
	}
}

To use the variable, you could write this type of logic into your view:

{{if eq .AuthLevel "auth"}}
You are logged in.
{{else}}
You are not logged in.
{{end}}

Once you create a new package, you make it available to the views by adding it to the view.SetModifiers() function in the lib/boot/boot.go file:

// Set up the variables and modifiers for the views
config.View.SetModifiers(
	authlevel.Modify,
	uri.Modify,
	xsrf.Token,
	flash.Modify,
)

Included Variables

There are a few included variables you can use in templates:

<!-- Use AuthLevel=auth to determine if a user is logged in (if session.Values["id"] != nil) -->
{{if eq .AuthLevel "auth"}}
You are logged in.
{{else}}
You are not logged in.
{{end}}

<!-- Use BaseURI to print the base URL specified in the env.json file, ends in slash -->
<li><a href="{{.BaseURI}}about">About</a></li>
<!-- Use CurrentURI to print the current URL, does not end in slash -->
<li><a href="{{.CurrentURI}}">Current Page</a></li>
<!-- Use ParentURI to print the URL up one level, does not end in slash -->
<li><a href="{{.ParentURI}}">Parent Page</a></li>
<!-- Use GrandparentURI to print the URL up two levels, does not end in slash -->
<li><a href="{{.GrandparentURI}}">Grandparent Page</a></li>

<!-- Use token to output the CSRF token in a form -->
<input type="hidden" name="token" value="{{.token}}">

Repopulate Form Fields

When a form is submitted and there are errors like when a required field is missing, the same web page should reload. Unfortunately, forms are not refilled so there are a few helpers from the form package that will help you refill, select, and check.

These functions actually do both form population (filling in a form from a database record) and form repopulation (filling in a form from a form submission where something went wrong like a required field was left blank). These functions will fill the form with values from the form submission over the values from the database record. If you refresh the page, the form submission values will be forgotten and the values from the database record will return.

For instance, say your form has two fields: name and age. Both are required fields. You type in your name, John Doe, forget to type in your age, and click the submit button. The page will reload, show an error message, and the functions will automatically refill the name field with your name. The record has not been saved in the database, but the name field is remembered from the form submission (form repopulation). You enter in your age as 30, click the submit button, and the record is successfully added to the database.

Let’s say you want to edit the database record and you want to enter in a new age. You load the edit page and the functions will automatically refill the name field and the age field with the values from the database (form population). You try to be sneaky and change your age to the word “thirty”, instead of the number 30. You click the submit button, the page reloads, shows an error message, and refills the name field and the age. The age field will be filled with the word “thirty” because the form submission values take priority over the values from the database. If you clicked the refresh button in your browser, the age field would then load the number 30 from the database instead.

There are two views that benefit from these functions: create.tmpl and edit.tmpl. You don’t have to use these names, but it’s just an example to show you these functions will primarily be used in the the template to create a new record in the database (create.tmpl) and the template to edit an existing record in the database (edit.tmpl).

In the create.tmpl view, there may be required fields. If the form requires typing in a long block of text for a couple answers and the user forgets one of the required fields, when the page reloads, all the text will not be there. These functions will refill, select, and check all the form inputs specified in the controller. The create.tmpl does not use the “default value” field in these functions so you can set them to an empty string (“”) or set them to a view variable so you can copy and paste the same code between create.tmpl and edit.tmpl. The edit.tmpl view requires the same logic, but also needs the “default value” to prefill, select, and check the form values from view variables (which will probably be pulled from a model struct).

Check out the Repopulate Form Fields section on the Controllers page. It will show you the single line of code needed in your controller so these fields repopulate on form submission.

These are the functions/blocks to use in your templates. Notice that some of the HTML attributes are missing like name, value, and type from the elements. The blocks will automatically fill these in for you so you don’t have the write the name of the element multiple times. By the way, it took very little code to add this functionality so check out the form package to see how it was accomplished.

<!-- TEXT accepts the element name, a default value, and then a period -->
<input {{TEXT "email" .item.Email .}} type="email" class="form-control" id="email" />
<!-- then parses to a name attribute when no repopulation value is passed -->
<input name="email" type="email" class="form-control" id="email" />
<!-- and parses to a name and a value when a repopulation value is passed -->
<input name="email" value="me@example.com" type="email" class="form-control" id="email" />

<!-- TEXTAREA accepts the element name, a default value, and then a period -->
<textarea rows="5" class="form-control" id="name" name="name" />{{TEXTAREA "name" .item.Name .}}</textarea>
<!-- then parses to nothing when no repopulation value is passed -->
<textarea rows="5" class="form-control" id="name" name="name" /></textarea>
<!-- and parses to a value when a repopulation value is passed -->
<textarea rows="5" class="form-control" id="name" name="name" />Sample text</textarea>

<!-- CHECKBOX accepts the element name, value, default value, and then a period -->
<label><input {{CHECKBOX "rememberme" "r1" .item.RememberMe .}}> Remember me</label>
<!-- then parses to a type, name, and value attribute when no repopulation value is passed -->
<label><input type="checkbox" name="rememberme" value="r1"> Remember me</label>
<!-- and parses to a type, name, value, and the word 'checked' when a repopulation value is passed -->
<label><input type="checkbox" name="rememberme" value="r1" checked> Remember me</label>

<!-- RADIO accepts the element name, value, default value, and then a period -->
<label><input {{RADIO "options" "burger" .item.Option .}}> Burger</label>
<!-- then parses to a type, name, and value attribute when no repopulation value is passed -->
<label><input type="radio" name="options" value="burger"> Burger</label>
<!-- and parses to a type, name, value attribute, and the word 'checked' when a repopulation value is passed -->
<label><input type="radio" name="options" value="burger" checked> Burger</label>

<!-- OPTION accepts the element name, value, default value, and then a period -->
<select name="select"><option {{OPTION "select" "Apple" .item.Select .}}>Apple</option></select>
<!-- then parses to a value attribute when no repopulation value is passed -->
<select name="select"><option value="Apple">Apple</option></select>
<!-- and parses to a value attribute and the word 'selected' when a repopulation value is passed -->
<select name="select"><option value="Apple" selected>Apple</option></select>

Here are examples of all the fields with the Bootrap structure and classes:

<div class="form-group">
	<label for="email">Email Address</label>
	<div><input {{TEXT "email" .item.Email .}} type="email" class="form-control" id="email" maxlength="48" placeholder="Email" /></div>
</div>

<div class="form-group">
	<label for="name">Item</label>
	<div><textarea rows="5" class="form-control" id="name" name="name" placeholder="Type your text here..." />{{TEXTAREA "name" .}}</textarea></div>
</div>

<div class="checkbox">
    <label>
        <input {{CHECKBOX "rememberme" "r1" .item.RememberMe .}}> Remember me
    </label>
</div>
<div class="checkbox">
    <label>
        <input {{CHECKBOX "rememberme" "r2" .item.RememberMe .}}> Remember me
    </label>
</div>

<div class="radio">
    <label>
        <input {{RADIO "options" "burger" .item.Option .}}> Burger
    </label>
</div>
<div class="radio">
    <label>
        <input {{RADIO "options" "taco" .item.Option .}}> Taco
    </label>
</div>

<select class="form-control" name="select">
    <option {{OPTION "select" "Apple" .item.Select .}}>Apple</option>
    <option {{OPTION "select" "Banana" .item.Select .}}>Banana</option>
    <option {{OPTION "select" "cherry" .item.Select .}}>Cherry</option>
</select>

<select multiple class="form-control" name="mselect">
    <option {{OPTION "mselect" "red" .item.Select .}}>Red</option>
    <option {{OPTION "mselect" "green" .item.Select .}}>Green</option>
    <option {{OPTION "mselect" "blue" .item.Select .}}>Blue</option>
</select>

Change HTTP Methods

When you submit a form a a website, the site most likely sends a POST request to the server. In order for us to make our application more RESTful, we can use utilize the simple rest package to change the HTTP method from a URL query string. The rest middleware is already applied to every request which is configured in the boot package.

To change the form method, add this line to your form action and change the value to match a method like DELETE or PATCH. It will automatically be converted to uppercase.

The query string key should be: _method.

<!-- Example of a PATCH request -->
<form method="post" action="{{$.CurrentURI}}?_method=patch">

<!-- Example of a DELETE request -->
<form class="button-form" method="post" action="{{$.GrandparentURI}}/{{.item.ID}}?_method=delete">

This is an example of a form that is updating and email and a password using the PATCH HTTP method:

<form method="post" action="{{$.CurrentURI}}?_method=patch">
	<div class="form-group">
		<label for="email">Email Address</label>
		<div><input {{TEXT "email" .item.Email .}} type="email" class="form-control" id="email" /></div>
	</div>

	<div class="form-group">
		<label for="password">Password</label>
		<div><input {{TEXT "password" .item.Password .}} type="password" class="form-control" id="password" /></div>
	</div>

	<input type="submit" class="btn btn-primary" value="Change" class="button" />

	<input type="hidden" name="_token" value="{{$.token}}">
</form>

The routes for this page would look something like this:

// Display the Update Page - typical GET HTTP method
router.Get("/user/edit/:id", Edit, c...)

// Handle the Update Page Form Submissions - PATCH HTTP method
router.Patch("user/edit/:id", Update, c...)

It’s also easy to add template-specific code before the closing and tags:

<!-- Code is added before the closing </head> tag -->
{{define "head"}}<meta name="robots" content="noindex">{{end}}

...

<!-- Code is added before the closing </body> tag -->
{{define "foot"}}{{JS "//www.google.com/recaptcha/api.js"}}{{end}}

JavaScript

There are a few built-in functions that you can use to trigger a flash notification using JavaScript.

flashError("An error occurred on the server.");

flashSuccess("Item added!");

flashNotice("Item deleted.");

flashWarning("Field missing: email");