The Go documentation on the text/template package is so abstract that I'm having trouble figuring out how to actually range over a slice of objects. Here's my attempt so far (This outputs nothing for me):
package main
import (
"os"
templ "text/template"
)
type Context struct {
people []Person
}
type Person struct {
Name string //exported field since it begins with a capital letter
Senior bool
}
func main() {
// Range example
tRange := templ.New("Range Example")
ctx2 := Context{people: []Person{Person{Name: "Mary", Senior: false}, Person{Name: "Joseph", Senior: true}}}
tRange = templ.Must(
tRange.Parse(`
{{range $i, $x := $.people}} Name={{$x.Name}} Senior={{$x.Senior}} {{end}}
`))
tRange.Execute(os.Stdout, ctx2)
}
The range is correct. The problem is that the Context people field is not exported. The template package ignores unexported fields. Change the type definition to:
type Context struct {
People []Person // <-- note that People starts with capital P.
}
and the template to:
{{range $i, $x := $.People}} Name={{$x.Name}} Senior={{$x.Senior}} {{end}}
playground
Related
I have a struct with two fields. The Msg string will be fmt.Sprintf("%s %s"), and I need to parse templates to those two variables in the string
type DataDB struct {
ID int
Msg string
}
Trying to parse template here, and the expected result should be -
Hello Justin, my name is abc
var name = "justin"
var msg = "abc"
justin := DataDB{ID: 1, Msg: fmt.Sprintf("%s %s", name, msg)}
s := []DataDB{justin}
tpl, err := template.New("msgs").Parse(` {{range .}}
Hello {{.name}}, my name is {{.msg}}
{{end}}
`)
if err != nil {
panic(err)
}
tpl.Execute(os.Stdout, s)
First of all, your DataDB struct doesn't have a name field, so you won't be able to reference it in your template.
Update your template to be the following:
type DataDB struct {
ID int
Name string
Msg string
}
and then set a name in your declaration
justin := DataDB{ID: 1, Name: name, Msg: msg}
Now you can reference the .Name field in your template
tpl, err := template.New("msgs").Parse(`
{{range .}}
Hello {{.Name}}, my name is {{.Msg}}
{{end}}
`)
Finally, when referencing fields in templates, the fields need to be exported, so they should all start with an upper case letter. See my version of the DataDB struct above.
EDIT, you don't need the fmt.Sprintf(...) now.
Go playground
It seems your Msg contains Name + " " + Message, so you need to split it on " ".
Go templates are logicless. But you can use helper functions to add logic.
For example:
type DataDB struct {
ID int
Msg string
}
func (d *DataDB) Name() string {
return strings.SplitN(d.Msg, " ", 2)[0]
}
func (d *DataDB) Message() string {
res := strings.SplitN(d.Msg, " ", 2)
if len(res) < 2 {
return ""
}
return res[1]
}
And use them in the template like this:
{{range .}}
Hello {{.Name}}, my name is {{.Message}}
{{end}}
Working example
Note: the code above is slightly inefficient because it does the splitting twice, so it might be better to create a separate struct just for display purposes and fill it before rendering the template.
I have two golang html templates, as follows:
var m map[string]string
m = make(map[string]string)
m["First"] = `<html>
<body>First template type {{.First}}
</html>`
m["Second"] = `<html>
<body>Second template type {{.SecondF1}} {{.SecondF2}}
</html>`
The first html template takes only one argument, named First whereas the second template needs two arguments, named SecondF1 and SecondF2.
Now I have a struct which has two fields, one for receiving a template name and another for receiving the template arguments.
type tmplReceiver struct {
TmplName string
TmplArgs string // Receives JSON string
}
Now, examples of instances for the above structs could be:
var i, j tmplReceiver
i.TmplName = "First"
i.TmplArgs = `{"Field1": "First Template Argument"}`
j.TmplName = "Second"
j.TmplArgs = `{
"SecondF1": "Second template First Argument",
"SecondF2": "Second template Second Argument"
}`
Now I can get the Template string by using the map, for example:
tmplStr := m[i.TmplName] (or)
tmplStr := m[j.TmplName]
tmpl, _ = template.New("email").Parse(tmplStr)
However, how do I get the template to be executed for all the possible template types, with a single tmpl.Execute statement. In other words, if I want to have the following code:
var buff bytes.Buffer
if err := tmpl.Execute(&buff, tmplPtr); err != nil {
log.Fatal(err)
}
log.Println(buff.String())
How do I get the tmplPtr to be valid, irrespective of how many templates I have (First, Second, etc.) and each of these templates can have a variable number of arguments (First has only one arg, whereas Second has two args, etc.)
I do not want to write N different tmpl.Execute statements with an if block for each template name. Is there any other alternative approach to solve this ? Thanks.
Neither json.Unmarshal nor template.Execute cares about the actual type of the data, this will all be handled at runtime. So you can just parse the json to an interface{} and pass that to your templates. Provided that the json data contains the fields that are expected by the template to which you pass the data, this will just work fine.
Playground link
package main
import (
"bytes"
"encoding/json"
"fmt"
"html/template"
)
var templates = map[string]*template.Template{
"A": template.Must(template.New("A").Parse("{{ .A }}")),
"B": template.Must(template.New("B").Parse("{{ .B }} and {{ .C.D }}")),
}
type tmplReceiver struct {
TmplName string
TmplArgs string
}
func main() {
receivers := []tmplReceiver{
tmplReceiver{"A", `{"A": "Value for A"}`},
tmplReceiver{"B", `{"B": "Value for B", "C": { "D": "Value for D" }}`},
}
for _, receiver := range receivers {
var data interface{}
json.Unmarshal([]byte(receiver.TmplArgs), &data)
var buffer bytes.Buffer
templates[receiver.TmplName].Execute(&buffer, data)
fmt.Println(buffer.String())
}
}
Which prints
Value for A
Value for B and Value for D
I have created a function to check if a variable is defined:
fm["isset"] = func(a interface{}) bool {
if a == nil || a == "" || a == 0 {
fmt.Println("is not set")
return false
}
fmt.Println("is set")
return false
}
tmpl := template.Must(template.New("").Funcs(fm).ParseFiles("templates/header.html"))
err := tmpl.ExecuteTemplate(w, "header", templateData)
In the template I have:
{{ if isset .Email }}
email is set
{{ end }}
This function works if the variable is contained by the templateData (which is a custom struct that contains a map and a string), but it gives me an error if the variable doesn't exist.
The error is:
executing "header" at <.Email>: can't evaluate field Email in type base.customData
In my case "base.go" is the handler and "customData" is defined by: type customData struct{..}.
I want to be able to reuse templates and to display some sections only if some variables are sent from the handler. Any idea how can I implement a variable isset check on the template side?
I also tried using: {{ if .Email}} do stuff {{ end }} but this also gives me the same error.
Any idea?
The recommended way
First, the recommended way is not to rely on whether a struct field exists. Of course there might be optional parts of the template, but the condition to decide whether to render a part should rely on fields that exist in all cases.
The issue, and avoiding it using a map
If the type of the template data is a struct (or a pointer to a struct) and there is no field or method with the given name, the template engine returns an error for that.
You could easily get rid of this error if you were to use a map, as maps can be indexed with keys they don't contain, and the result of that index expression is the zero value of the value type (and not an error).
To demonstrate, see this example:
s := `{{if .Email}}Email is: {{.Email}}{{else}}Email is NOT set.{{end}}`
t := template.Must(template.New("").Parse(s))
exec := func(name string, param interface{}) {
fmt.Printf("\n%s:\n ", name)
if err := t.Execute(os.Stdout, param); err != nil {
fmt.Println("Error:", err)
}
}
exec("Filled map", map[string]interface{}{"Email": "as#as"})
exec("Empty map", map[string]interface{}{})
exec("Filled struct", struct {
Email string
}{Email: "as#as.com"})
exec("Empty struct", struct{}{})
Output (try it on the Go Playground):
Filled map:
Email is: as#as
Empty map:
Email is NOT set.
Filled struct:
Email is: as#as.com
Empty struct:
Error: template: :1:5: executing "" at <.Email>: can't evaluate field Email in type struct {}
Sticking to struct and providing "isset"
If you must or want to stick to a struct, this "isset" can be implemented and provided, I'll call it avail().
This implementation uses reflection, and in order to check if the field given by its name exists (is available), the (wrapper) data must also be passed to it:
func avail(name string, data interface{}) bool {
v := reflect.ValueOf(data)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.Kind() != reflect.Struct {
return false
}
return v.FieldByName(name).IsValid()
}
Example using it:
s := `{{if (avail "Email" .)}}Email is: {{.Email}}{{else}}Email is unavailable.{{end}}`
t := template.Must(template.New("").Funcs(template.FuncMap{
"avail": avail,
}).Parse(s))
exec := func(name string, param interface{}) {
fmt.Printf("\n%s:\n ", name)
if err := t.Execute(os.Stdout, param); err != nil {
fmt.Println("Error:", err)
}
}
exec("Filled struct", struct {
Email string
}{Email: "as#as.com"})
exec("Empty struct", struct{}{})
Output (try it on the Go Playground):
Filled struct:
Email is: as#as.com
Empty struct:
Email is unavailable.
If you don't send data containing a variable to the template but you use {{ if .Variable }} you will get the error:
executing "templatename" at <.Variable>: can't evaluate field Variable in type handler.Data
My solution was to send the "Email" variable as a boolean (false) in order to pass the {{ if .Email }} function check. But this is a short term solution that I don't like.
I was inspired by: https://stackoverflow.com/a/31527618/1564840. In that example they show different HTML for authenticated and non-authenticated users. You will see that in both cases they send the "Logged" variable. Try removing that variable from the struct and execute the function. You will receive the error that I mentioned above.
The simple way of doing:
{{ if .Email }}
is to use index:
{{ if index . "Email" }}
What is the namespace of variables inside html/text templates? I thought that a variable $x can change value inside a template, but this example shows me that I cannot.
I failed when I tried to group tournaments according year - something like this (http://play.golang.org/p/EX1Aut_ULD):
package main
import (
"fmt"
"os"
"text/template"
"time"
)
func main() {
tournaments := []struct {
Place string
Date time.Time
}{
// for clarity - date is sorted, we don't need sort it again
{"Town1", time.Date(2015, time.November, 10, 23, 0, 0, 0, time.Local)},
{"Town2", time.Date(2015, time.October, 10, 23, 0, 0, 0, time.Local)},
{"Town3", time.Date(2014, time.November, 10, 23, 0, 0, 0, time.Local)},
}
t, err := template.New("").Parse(`
{{$prev_year:=0}}
{{range .}}
{{with .Date}}
{{$year:=.Year}}
{{if ne $year $prev_year}}
Actions in year {{$year}}:
{{$prev_year:=$year}}
{{end}}
{{end}}
{{.Place}}, {{.Date}}
{{end}}
`)
if err != nil {
panic(err)
}
err = t.Execute(os.Stdout, tournaments)
if err != nil {
fmt.Println("executing template:", err)
}
}
Edit: See https://stackoverflow.com/a/52925780/1685538 for a more up-to-date answer.
Original answer:
https://golang.org/pkg/text/template/#hdr-Variables:
A variable's scope extends to the "end" action of the control
structure ("if", "with", or "range") in which it is declared, or to
the end of the template if there is no such control structure.
So the $prev_year you define with {{$prev_year:=$year}} only lives until.. the next line ({{end}}).
It seems there is no way of going around that.
The "right" way to do this is to take that logic out of your template, and do the grouping in your Go code.
Here is a working example : https://play.golang.org/p/DZoSXo9WQR
package main
import (
"fmt"
"os"
"text/template"
"time"
)
type Tournament struct {
Place string
Date time.Time
}
type TournamentGroup struct {
Year int
Tournaments []Tournament
}
func groupTournamentsByYear(tournaments []Tournament) []TournamentGroup {
if len(tournaments) == 0 {
return nil
}
result := []TournamentGroup{
{
Year: tournaments[0].Date.Year(),
Tournaments: make([]Tournament, 0, 1),
},
}
i := 0
for _, tournament := range tournaments {
year := tournament.Date.Year()
if result[i].Year == year {
// Add to existing group
result[i].Tournaments = append(result[i].Tournaments, tournament)
} else {
// New group
result = append(result, TournamentGroup{
Year: year,
Tournaments: []Tournament{
tournament,
},
})
i++
}
}
return result
}
func main() {
tournaments := []Tournament{
// for clarity - date is sorted, we don't need sort it again
{"Town1", time.Date(2015, time.November, 10, 23, 0, 0, 0, time.Local)},
{"Town2", time.Date(2015, time.October, 10, 23, 0, 0, 0, time.Local)},
{"Town3", time.Date(2014, time.November, 10, 23, 0, 0, 0, time.Local)},
}
t, err := template.New("").Parse(`
{{$prev_year:=0}}
{{range .}}
Actions in year {{.Year}}:
{{range .Tournaments}}
{{.Place}}, {{.Date}}
{{end}}
{{end}}
`)
if err != nil {
panic(err)
}
err = t.Execute(os.Stdout, groupTournamentsByYear(tournaments))
if err != nil {
fmt.Println("executing template:", err)
}
}
In go1.11 text/template and hence html/template became able to set the value of existing variables, which means that the original code can be made to work with one very small modification.
Change
{{$prev_year:=$year}}
To
{{$prev_year = $year}}
Playground
As mentioned by this answer, the scope of that variable "re-assignment" ends with the {{end}} block. Therefore using standard variables only there's no way around the problem and it should be solved inside the Go program executing the template.
In some frameworks however this is not that easy (e.g. protoc-gen-gotemplate).
The Sprig library adds additional functionality to the standard template language. One of them are mutable maps that can be used in the following way:
// init the dictionary (you can init it without initial key/values as well)
{{$myVar := dict "key" "value"}}
// getting the "key" from the dictionary (returns array) and then fetching the first element from that array
{{pluck "key" $myVar | first}}
// conditional update block
{{if eq "some" "some"}}
// the $_ seems necessary because Go template functions need to return something
{{$_ := set $myVar "key" "newValue"}}
{{end}}
// print out the updated value
{{pluck "key" $myVar | first}}
This little example prints out:
value
newValue
A pragmatic approach would be to use a single dictionary for all mutable variables and store them under their corresponding variable name as key.
Reference:
http://masterminds.github.io/sprig/dicts.html
https://github.com/Masterminds/sprig
This one is seemingly simple but it's driving me insane.
How does one go about referencing a struct element higher in the scope within a nested range in golang templates?
Example:
type Foo struct {
Id string
Name string
}
type Bar struct {
Id string
Name string
}
var foos []Foo
var bars []Bar
// logic to populate both foos and bars
In the template:
{{range .foos}}
<div>Foo {{.Name}}</div>
<div>
{{range ..bars}}
<div>Bar {{.Name}} <input type="text" name="ids_{{..Id}}_{{.Id}}" /></div>
{{end}}
</div>
{{end}}
Obviously ..bars and ..Id don't work, but hopefully my intent is clear. I'd like to iterate through all combinations of Foo and Bar and generate a form element with a name build by both the Foo's Id and the Bar's Id.
The problem is that it seems it is impossible to:
Access bars from inside the scope of the foos range scope
Access Foo's Id from inside the bar's range scope
I have a temporary workaround to this by putting a bunch of redundant fields in both structs, but this seems very ugly to me, violates DRY, and in general feels very wrong.
Is there any way with golang templates to do what I'd like to do?
Yes. I feel as if not finding a solution comes from not reading the text/template package closely enough. If you are using html/template, the syntax is the same (and they tell you to read text/template ;)). Here is a complete working solution for what you might want to do.
Go file:
package main
import (
"bytes"
"io/ioutil"
"os"
"strconv"
"text/template"
)
type Foo struct {
Id string
Name string
}
type Bar struct {
Id string
Name string
}
var foos []Foo
var bars []Bar
func main() {
foos = make([]Foo, 10)
bars = make([]Bar, 10)
for i := 0; i < 10; i++ {
foos[i] = Foo{strconv.Itoa(i), strconv.Itoa(i)} // just random strings
bars[i] = Bar{strconv.Itoa(10 * i), strconv.Itoa(10 * i)}
}
tmpl, err := ioutil.ReadFile("so.tmpl")
if err != nil {
panic(err)
}
buffer := bytes.NewBuffer(make([]byte, 0, len(tmpl)))
output := template.Must(template.New("FUBAR").Parse(string(tmpl)))
output.Execute(buffer, struct {
FooSlice []Foo
BarSlice []Bar
}{
FooSlice: foos,
BarSlice: bars,
})
outfile, err := os.Create("output.html")
if err != nil {
panic(err)
}
defer outfile.Close()
outfile.Write(buffer.Bytes())
}
Note: You can probably do something to not load the file into an intermediate buffer (use ParseFiles), I just copied and pasted some code that I had written for one of my projects.
Template file:
{{ $foos := .FooSlice }}
{{ $bars := .BarSlice }}
{{range $foo := $foos }}
<div>Foo {{$foo.Name}}</div>
<div>
{{range $bar := $bars}}
<div>Bar {{$bar.Name}} <input type="text" name="ids_{{$foo.Id}}_{{$bar.Id}}" /></div>
{{end}}
</div>
{{end}}
The two morals of this story are
a) use variables in templates judiciously, they are beneficial
b) range in templates also can set variables, you do not need to rely solely on $ or .