Did you ever remember the previous chapter. The template occurs many times.It almost handle all the html pages, such as errors, regular home page and so on.
Revel uses go official template, also a similar way to maintain the templates. It abstract a template loader. It is like a small database.
Structure - templateLoader
templateLoader
object handles loading and parsing of templates.
type TemplateLoader struct {
// This is the set of all templates under views
templateSet *template.Template
// If an error was encountered parsing the templates, it is stored here.
compileError *Error
// Paths to search for templates, in priority order.
paths []string
// Map from template name to the path from whence it was loaded.
templatePaths map[string]string
}
Method - NewTemplateLoader
To build a template loader , the only thing you should prepare is the base path of searching.
func NewTemplateLoader(paths []string) *TemplateLoader {
loader := &TemplateLoader{
paths: paths,
}
return loader
}
Method - Refresh
This method scans the views directory and parses all templates as Go templates
It go through every file under base path and builds up a template set in the end.
func (loader *TemplateLoader) Refresh() *Error {
TRACE.Println("Refresh")
loader.compileError = nil
loader.templatePaths = map[string]string{}
// Walk through the template loader's paths and build up a template set.
var templateSet *template.Template = nil
for _, basePath := range loader.paths {
// Walk only returns an error if the template loader is completely unusable
// (namely, if one of the TemplateFuncs does not have an acceptable signature).
funcErr := filepath.Walk(basePath, func(path string, info os.FileInfo, err error) error {
...
})
// If there was an error with the Funcs, set it and return immediately.
if funcErr != nil {
loader.compileError = funcErr.(*Error)
return loader.compileError
}
}
// Note: compileError may or may not be set.
loader.templateSet = templateSet
return loader.compileError
}
The callback function is to read every template file content and add it into template set
if err != nil {
ERROR.Println("error walking templates:", err)
return nil
}
// Walk into directories.
if info.IsDir() {
if !loader.WatchDir(info) {
return filepath.SkipDir
}
return nil
}
if !loader.WatchFile(info.Name()) {
return nil
}
// Convert template names to use forward slashes, even on Windows.
templateName := path[len(basePath)+1:]
if os.PathSeparator == '\\' {
templateName = strings.Replace(templateName, `\`, `/`, -1)
}
// If we already loaded a template of this name, skip it.
if _, ok := loader.templatePaths[templateName]; ok {
return nil
}
loader.templatePaths[templateName] = path
fileBytes, err := ioutil.ReadFile(path)
if err != nil {
ERROR.Println("Failed reading file:", path)
return nil
}
fileStr := string(fileBytes)
if templateSet == nil {
// Create the template set. This panics if any of the funcs do not
// conform to expectations, so we wrap it in a func and handle those
// panics by serving an error page.
var funcError *Error
func() {
defer func() {
if err := recover(); err != nil {
funcError = &Error{
Title: "Panic (Template Loader)",
Description: fmt.Sprintln(err),
}
}
}()
templateSet = template.New(templateName).Funcs(TemplateFuncs)
_, err = templateSet.Parse(fileStr)
}()
if funcError != nil {
return funcError
}
} else {
_, err = templateSet.New(templateName).Parse(fileStr)
}
// Store / report the first error encountered.
if err != nil && loader.compileError == nil {
line, description := parseTemplateError(err)
loader.compileError = &Error{
Title: "Template Compilation Error",
Path: templateName,
Description: description,
Line: line,
SourceLines: strings.Split(fileStr, "\n"),
}
ERROR.Printf("Template compilation error (In %s around line %d):\n%s",
templateName, line, description)
}
return nil
- files and dirs beginning with “.” will be ignored.
- if there is template parsing err happened, it will be logged in
compileError
. Even there is a error, the template set is still generated.
Method - Template
Return the template with given name. The name is the template’s path relative to a template loader root
func (loader *TemplateLoader) Template(name string) (Template, error) {
// Look up and return the template.
tmpl := loader.templateSet.Lookup(name)
// This is necessary.
// If a nil loader.compileError is returned directly, a caller testing against
// nil will get the wrong result. Something to do with casting *Error to error.
var err error
if loader.compileError != nil {
err = loader.compileError
}
if tmpl == nil && err == nil {
return nil, fmt.Errorf("Template %s not found.", name)
}
return GoTemplate{tmpl, loader}, err
}
Structure - GoTemplate
To overwrite go standard template, it wrapped go *template.TemplateLoader
.
type GoTemplate struct {
*template.Template
loader *TemplateLoader
}
And it has two methods for satisfying revel.Template
interface.
type Template interface {
Name() string
Content() []string
Render(wr io.Writer, arg interface{}) error
}
func (gotmpl GoTemplate) Render(wr io.Writer, arg interface{}) error {
return gotmpl.Execute(wr, arg)
}
func (gotmpl GoTemplate) Content() []string {
content, _ := ReadLines(gotmpl.loader.templatePaths[gotmpl.Name()])
return content
}
Template functions
Revel predefined some itself template functions to the go standard template functions, it help the user to write a sample template file.
function - eq
a simple "a == b"
test.
Usage:
<div class="message {{if eq .User "you"}}you{{end}}">
The eq
function:
"eq": func(a, b interface{}) bool { return a == b },
function - set
Set a variable in the given context.
Usage:
{{set . "title" "Basic Chat room"}}
<h1>{{.title}}</h1>
The set
function:
"set": func(renderArgs map[string]interface{}, key string, value interface{}) template.HTML {
renderArgs[key] = value
return template.HTML("")
},
function - append
Add a variable to an array, or start an array, in the given context.
Usage:
{{append . "moreScripts" "js/jquery-ui.js"}}
{{range .moreScripts}}
<link rel="stylesheet" type="text/css" href="/public/{{.}}">
{{end}}
The append
function:
"append": func(renderArgs map[string]interface{}, key string, value interface{}) template.HTML {
if renderArgs[key] == nil {
renderArgs[key] = []interface{}{value}
} else {
renderArgs[key] = append(renderArgs[key].([]interface{}), value)
}
return template.HTML("")
},
function - field
A helper for input fields
Usage:
{{with $field := field "booking.CheckInDate" .}}
<p class = "error">
<strong>Check In Date:</strong>
<input type="text" size="10" name="{{$field.Name}}" class="datepicker" vaule="">
* <span class="error">{{$field.Error}}</span>
</p>
{{end}}
The field
function:
"field": func(name string, renderArgs map[string]interface{}) *Field {
value, _ := renderArgs["flash"].(map[string]string)[name]
err, _ := renderArgs["errors"].(map[string]*ValidationError)[name]
return &Field{
Name: name,
Value: value,
Error: err,
}
},
The field
structure:
type Field struct {
Name, Value string
Error *ValidationError
}
It has two helper methods.
func (f *Field) ErrorClass() string {
if f.Error != nil {
return ERROR_CLASS
}
return ""
}
// Return "checked" if this field.Value matches the provided value
func (f *Field) Checked(val string) string {
if f.Value == val {
return "checked"
}
return ""
}
function - option
Assists in constructing HTML option
elements, in conjunction with the field helper.
Usage:
{{with $field := field "booking.Beds" .}}
<select name="{{$field.Name}}">
{{option $field "1" "One king-size bed"}}
{{option $field "2" "Two double beds"}}
{{option $field "3" "Three beds"}}
</select>
{{end}}
The option
function:
"option": func(f *Field, val, label string) template.HTML {
selected := ""
if f.Value == val {
selected = " selected"
}
return template.HTML(fmt.Sprintf(`<option value="%s"%s>%s</option>`, html.EscapeString(val), selected, html.EscapeString(label)))
},
function - radio
Assists in constructing HTML radio input elements, in conjunction with the field helper.
Usage:
{{with $field := field "booking.Smoking" .}}
{{radio $field "true"}} Smoking
{{radio $field "false"}} Non smoking
{{end}}
The radio
function:
"radio": func(f *Field, val string) template.HTML {
checked := ""
if f.Value == val {
checked = " checked"
}
return template.HTML(fmt.Sprintf(`<input type="radio" name="%s" value="%s"%s>`,
html.EscapeString(f.Name), html.EscapeString(val), checked))
},
function - pad
Pads the given string with ’s up to given width
Usage:
<h1>{{pad "hello" 10}}</h1>
The pad
function:
"pad": func(str string, width int) template.HTML {
if len(str) >= width {
return template.HTML(html.EscapeString(str))
}
return template.HTML(html.EscapeString(str) + strings.Repeat(" ", width-len(str)))
},
function - errorClass
If there was an error, it output the literal string “error”, else “”.
Usage:
<p class={{errorClass "error" .}}>
<h2>Some error happend</h2>
</p>
The errorClass
function:
"errorClass": func(name string, renderArgs map[string]interface{}) template.HTML {
errorMap, ok := renderArgs["errors"].(map[string]*ValidationError)
if !ok {
WARN.Println("Called 'errorClass' without 'errors' in the render args.")
return template.HTML("")
}
valError, ok := errorMap[name]
if !ok || valError == nil {
return template.HTML("")
}
return template.HTML(ERROR_CLASS)
},
function - url
Return a url capable of invoking a given controller method:
"Application.ShowApp 123" => "/app/123"
Usage:
<a href={{url "Application.ShowApp" "123"}}> Apps </a>
The url
function:
"url": ReverseUrl,
func ReverseUrl(args ...interface{}) string {
if len(args) == 0 {
ERROR.Println("Warning: no arguments provided to url function")
return "#"
}
action := args[0].(string)
actionSplit := strings.Split(action, ".")
var ctrl, meth string
if len(actionSplit) != 2 {
ERROR.Println("Warning: Must provide Controller.Method for reverse router.")
return "#"
}
ctrl, meth = actionSplit[0], actionSplit[1]
controllerType := LookupControllerType(ctrl)
methodType := controllerType.Method(meth)
argsByName := make(map[string]string)
for i, argValue := range args[1:] {
argsByName[methodType.Args[i].Name] = fmt.Sprintf("%s", argValue)
}
return MainRouter.Reverse(args[0].(string), argsByName).Url
}
Extract controller
and method
from the first string. Assert a lookup in router adding the exist args.
FIN.