Is it possible to access the name of current template in Golang text/html/template without passing it as a data element to the template?
Thanks!
I'm hoping this is what you meant (from http://golang.org/pkg/text/template/#Template.Name)
func (t *Template) Name() string
"Name returns the name of the template."
If you mean to access the template name from within the template, I can only think to either add a function to the template.FuncMap, or, as you suggested to add the name as a data element.
The first would probably look something like:
var t = template.Must(template.New("page.html").ParseFiles("page.html"))
t.Funcs(template.FuncMap{"name": fmt.Sprint(t.Name())})
but I can't get it to work in the quick time I've messed about with it. Hopefully it might help point you in the right direction.
It would probably be easier in the long run just to add the name as a data element.
EDIT: In case anyone wants to know how to do it using template.FuncMap, it's basically a matter of defining the function after you create the template, then adding it to the FuncMap:
Full running example:
func main() {
const text = "{{.Thingtype}} {{templname}}\n"
type Thing struct {
Thingtype string
}
var thinglist = []*Thing{
&Thing{"Old"},
&Thing{"New"},
&Thing{"Red"},
&Thing{"Blue"},
}
t := template.New("things")
templateName := func() string { return t.Name() }
template.Must(t.Funcs(template.FuncMap{"templname": templateName}).Parse(text))
for _, p := range thinglist {
err := t.Execute(os.Stdout, p)
if err != nil {
fmt.Println("executing template:", err)
}
}
}
Outputs:
Old things
New things
Red things
Blue things
Playground link: http://play.golang.org/p/VAg5Gv5hCg
Related
I know how to get the user IP from the *http.Requeststruct:
strings.Split(r.RemoteAddr, ":")[0]
And I know how to define a template.FuncMap:
funcMap = template.FuncMap{
// gets the time since the post was posted
"since": func(t time.Time) string {
s := time.Since(t).String()
return strings.Replace(s[:strings.LastIndex(s, "m")+1], "h", "h ", 1)
},
}
How would I get the users IP from a template function defined in the template.FuncMap?
The func map is intended for helper functions, rather than data, and should be defined once before parsing templates, so this isn't a good place for it. You should instead pass in the data to the view when executing the template.
This would fit better in your data/context for the view. For example if you use a map[string]interface{} for that (one of the few places I'd use interface{}), you can simply assign it there:
userIP := strings.Split(r.RemoteAddr, ":")[0]
data := map[string]interface{}{"userIP":userIP}
err := tmpl.Execute(w,data)
Template:
<p>User IP:{{.userIP}}</p>
Is there a simple way like template.ParseFiles("base.html", "home.html") but for strings to build a template from a set of strings?
I have a base template and a list of pages templates (all as strings) that I want to build on top of base template.
I figured out how to merge them, but I my solution is quite verbose and doesn't look elegant enough, even though working.
You may create a new, empty template using template.New() function. Then you may use the Template.New() method to create a new, empty, associated template. And you may parse "into" this using the Template.Parse() method.
Here's how it could look like:
func parseTemplates(templs ...string) (t *template.Template, err error) {
t = template.New("_all")
for i, templ := range templs {
if _, err = t.New(fmt.Sprint("_", i)).Parse(templ); err != nil {
return
}
}
return
}
Testing it:
t, err := parseTemplates(
`{{define "one"}}I'm #1.{{end}}`,
`{{define "two"}}I'm #2, including #1: {{template "one" .}}{{end}}`,
)
if err != nil {
panic(err)
}
if err = t.ExecuteTemplate(os.Stdout, "two", nil); err != nil {
panic(err)
}
Output (try it on the Go Playground):
I'm #2, including #1: I'm #1.
Also see related question: Go template name
Note
While we could call the Template.Parse() method on a single template multiple times, and it would parse multiple named templates properly, it is still advisable to acquire a new template.Template for each by calling Template.New(). Because if the template texts have content outside of named templates, they will be overwritten and only the last would be retained. For example: abc {{define "one"}}no 1{{end}}. The static text "abc" would be lost by a subsequent Template.Parse() call.
This is also noted in the doc of Template.Parse():
(In multiple calls to Parse with the same receiver template, only one call can contain text other than space, comments, and template definitions.)
Maybe
for _, templ := range ListOfPagesTemplates{
YourBaseTemplate.Parse(templ)
}
err check absent for simplicity of reading
I'm getting an error when I try and access a function I'm passing to my template:
Error: template: struct.tpl:3: function "makeGoName" not defined
Can anyone please let me know what I'm doing wrong?
Template file (struct.tpl):
type {{.data.tableName}} struct {
{{range $key, $value := .data.tableData}}
{{makeGoName $value.colName}} {{$value.colType}} `db:"{{makeDBName $value.dbColName}},json:"{{$value.dbColName}}"`
{{end}}
}
Calling file:
type tplData struct {
tableName string
tableData interface{}
}
func doStuff() {
t, err := template.ParseFiles("templates/struct.tpl")
if err != nil {
errorQuit(err)
}
t = t.Funcs(template.FuncMap{
"makeGoName": makeGoName,
"makeDBName": makeDBName,
})
data := tplData{
tableName: tableName,
tableData: tableInfo,
}
t.Execute(os.Stdout, data)
}
func makeGoName(name string) string {
return name
}
func makeDBName(name string) string {
return name
}
This is for a program that generates struct boilerplate code (in case anyone is wondering why I'm doing that in my template).
Custom functions need to be registered before parsing the templates, else the parser would not be able to tell whether an identifier is a valid function name or not. Templates are designed to be statically analyzable, and this is a requirement to that.
You can first create a new, undefined template with template.New(), and besides the template.ParseFiles() function, the template.Template type (returned by New()) also has a Template.ParseFiles() method, you can call that.
Something like this:
t, err := template.New("").Funcs(template.FuncMap{
"makeGoName": makeGoName,
"makeDBName": makeDBName,
}).ParseFiles("templates/struct.tpl")
Note that the template.ParseFiles() function also calls template.New() under the hood, passing the name of the first file as the template name.
Also Template.Execute() returns an error, print that to see if no output is generated, e.g.:
if err := t.Execute(os.Stdout, data); err != nil {
fmt.Println(err)
}
When registering custom functions for your template and using ParseFiles() you need to specify the name of the template when instantiating it and executing it. You also need to call ParseFiles() after you call Funcs().
// Create a named template with custom functions
t, err := template.New("struct.tpl").Funcs(template.FuncMap{
"makeGoName": makeGoName,
"makeDBName": makeDBName,
}).ParseFiles("templates/struct.tpl") // Parse the template file
if err != nil {
errorQuit(err)
}
// Execute the named template
err = t.ExecuteTemplate(os.Stdout, "struct.tpl", data)
if err != nil {
errorQuit(err)
}
When working with named templates the name is the filename without the directory path e.g. struct.tpl not templates/struct.tpl. So the name in New() and ExecuteTemplate() should be the string struct.tpl.
I have a HTML template that I execute passing a map[string]string variable. The template uses the variable to create the HTML output that I send to clients.
In addition to producing the HTML, I would like to use the very same template to generate some values that are retured to the main program, so I can use the same file to put some logic externally.
As far as I know, it is not possible to modify the variable I pass to Execute (something like {{.output = "value"}}).
So how could I get multiple output values from a template Execution?
You don't actually need to pass a funcmap, just pass the struct.
var tmpl = template.Must(template.New("test").Parse(`Before: {{.Data}}{{.Set "YY"}}, after: {{.Data}}`))
func main() {
c := &CustomData{"XX"}
tmpl.Execute(os.Stdout, c)
fmt.Println()
}
playground
You can always pass a FuncMap to the template, here's an extremely simple example:
const tmpl = `Before: {{.Data}}{{.Set "YY"}}, after: {{.Data}}`
type CustomData struct {
Data string
}
func (c *CustomData) Set(d string) string { // it has to return anything
c.Data = d
return ""
}
func main() {
c := &CustomData{"XX"}
funcMap := template.FuncMap{
"Set": c.Set,
}
t, _ := template.New("test").Funcs(funcMap).Parse(tmpl) // don't ignore errors in real code
t.Execute(os.Stdout, c)
fmt.Println()
}
playground
Say i have a data structure like this:
type Foo struct {
Bar []struct {
FooBar string
}
}
And i fill it such that Bar has 3 elements. Now, using the template library, how can i access say the 3rd element's FooBar in that slice? I have tried the following with no success:
{Foo.Bar[2].FooBar}
{Foo.Bar.2.FooBar}
Now, i know that i can use {.repeated section Foo.Bar} {FooBar} {.end}, but that gives me the value of foobar for each element, rather than just a specific one. I have googled and asked on irc to no avail...
Using the new text/template or html/template:
package main
import (
"fmt"
"text/template" // or html/template
"os"
)
func main() {
tmpl, err := template.New("name").Parse("{{index . 0}}")
if err != nil {
fmt.Println(err)
return
}
tmpl.Execute(os.Stdout, []string{"yup, that's me", "not that!"})
}
I'm fairly certain this just isn't possible. Perhaps there's a way you could restructure your data so that it's all named fields.
Or just write some more logic in your actual application. Array indexing is somewhat beyond the scope of the template package I would think.