Inserting object values

A template is applied to a Go object. Fields from that Go object can be inserted into the template, and you can ‘dig’ into the object to find subfields, etc. The current object is represented as '.', so that to insert the value of the current object as a string, you use {{.}}. The package uses the fmt package by default to work out the string used as inserted values.

To insert the value of a field of the current object, you use the field name prefixed by '.'. For example, if the object is of type

  1. type Person struct {
  2. Name string
  3. Age int
  4. Emails []string
  5. Jobs []*Jobs
  6. }

then you insert the values of Name and Age by

  1. The name is {{.Name}}.
  2. The age is {{.Age}}.

We can loop over the elements of an array or other list using the range command. So to access the contents of the Emails array we do

  1. {{range .Emails}}
  2. ...
  3. {{end}}

if Job is defined by

  1. type Job struct {
  2. Employer string
  3. Role string
  4. }

and we want to access the fields of a Person‘s Jobs, we can do it as above with a {{range .Jobs}}. An alternative is to switch the current object to the Jobs field. This is done using the {{with ...}} ... {{end}} construction, where now {{.}} is the Jobs field, which is an array:

  1. {{with .Jobs}}
  2. {{range .}}
  3. An employer is {{.Employer}}
  4. and the role is {{.Role}}
  5. {{end}}
  6. {{end}}

You can use this with any field, not just an array. Using templates

Once we have a template, we can apply it to an object to generate a new string, using the object to fill in the template values. This is a two-step process which involves parsing the template and then applying it to an object. The result is output to a Writer, as in

  1. t := template.New("Person template")
  2. t, err := t.Parse(templ)
  3. if err == nil {
  4. buff := bytes.NewBufferString("")
  5. t.Execute(buff, person)
  6. }

An example program to apply a template to an object and print to standard output is

  1. /**
  2. * PrintPerson
  3. */
  4. package main
  5. import (
  6. "fmt"
  7. "html/template"
  8. "os"
  9. )
  10. type Person struct {
  11. Name string
  12. Age int
  13. Emails []string
  14. Jobs []*Job
  15. }
  16. type Job struct {
  17. Employer string
  18. Role string
  19. }
  20. const templ = `The name is {{.Name}}.
  21. The age is {{.Age}}.
  22. {{range .Emails}}
  23. An email is {{.}}
  24. {{end}}
  25. {{with .Jobs}}
  26. {{range .}}
  27. An employer is {{.Employer}}
  28. and the role is {{.Role}}
  29. {{end}}
  30. {{end}}
  31. `
  32. func main() {
  33. job1 := Job{Employer: "Monash", Role: "Honorary"}
  34. job2 := Job{Employer: "Box Hill", Role: "Head of HE"}
  35. person := Person{
  36. Name: "jan",
  37. Age: 50,
  38. Emails: []string{"jan@newmarch.name", "jan.newmarch@gmail.com"},
  39. Jobs: []*Job{&job1, &job2},
  40. }
  41. t := template.New("Person template")
  42. t, err := t.Parse(templ)
  43. checkError(err)
  44. err = t.Execute(os.Stdout, person)
  45. checkError(err)
  46. }
  47. func checkError(err error) {
  48. if err != nil {
  49. fmt.Println("Fatal error ", err.Error())
  50. os.Exit(1)
  51. }
  52. }

The output from this is

  1. The name is jan.
  2. The age is 50.
  3. An email is jan@newmarch.name
  4. An email is jan.newmarch@gmail.com
  5. An employer is Monash
  6. and the role is Honorary
  7. An employer is Box Hill
  8. and the role is Head of HE

Note that there is plenty of whitespace as newlines in this printout. This is due to the whitespace we have in our template. If we wish to reduce this, eliminate newlines in the template as in

  1. {{range .Emails}} An email is {{.}} {{end}}

In the example, we used a string in the program as the template. You can also load templates from a file using the function template.ParseFiles(). For some reason that I don’t understand (and which wasn’t required in earlier versions), the name assigned to the template must be the same as the basename of the first file in the list of files. Is this a bug?