This question already has answers here:
Calling a template with several pipeline parameters
(10 answers)
Closed 5 years ago.
I have simple case, where a templates (text/templates) includes another like this
`index.html`
{{ template "image_row" . }}
`image_row.html`
{{ define "image_row" }}
To stuff here
{{ end }}
Now I want to reuse the image row template. Let's say I would like to pass a simple number, so that the image_row template builds up rows according to this number
I'd like to have something like that (where 5 is the additional argument)
index.html
{{ template "image_row" . | 5 }}
How could I achieve that in this case?
I'm not sure whether there exists a builtin solution for passing multiple arguments to a template invocation but, in case there isn't one, you could define a function that merges its arguments and returns them as a single slice value, then you can register that function and use it in the template invocation.
Something like:
func args(vs ...interface{}) []interface{} { return vs }
t, err := template.New("t").Funcs(template.FuncMap{"args":args}).Parse...
Then, in your index.html, you would do this:
{{ template "image_row" args . 5 }}
And then inside your image_row template you can access the arguments with the builtin index function like this:
{{ define "image_row" }}
To stuff here {{index . 0}} {{index . 1}}
{{ end }}
https://play.golang.org/p/gkdtvvJ1bb
There is no builtin for this. You can add a function that creates a map and use that in the child template:
func argsfn(kvs ...interface{}) (map[string]interface{}, error) {
if len(kvs)%2 != 0 {
return nil, errors.New("args requires even number of arguments.")
}
m := make(map[string]interface{})
for i := 0; i < len(kvs); i += 2 {
s, ok := kvs[i].(string)
if !ok {
return nil, errors.New("even args to args must be strings.")
}
m[s] = kvs[i+1]
}
return m, nil
}
Add it the function to the template like this:
t := template.Must(template.New("").Funcs(template.FuncMap{"args": argsfn}).Parse(......
Use it like this:
{{template "image_row" args "row" . "a" 5}}{{end}}
{{define "image_row"}}
{{$.row}} {{$.a}}
{{end}}
Run it in the playground
The advantage of using a map is that the arguments are "named". The advantage of using a slice as described in another answer is that the code is much simpler.
Related
Given the below template:
{{ range $item := . }}
{{ if $item.IsA}}
Ok
{{ else }}
Fine
{{ end }}
{{ end }}
Done!
When I render it using:
t := template.New("test").Parse(_types)
text, err := t.Execute(&buffer, strct)
The output is something like:
!empty line
!empty line
Ok
!empty line
!empty line
Done!
This means that if I want to format the text correctly, I have to re-write it as
{{ range $item := .}}{{ if $item.IsA }}OK{{ else }}{{ end }}{{ end }}
Done!
Then I get something like:
Ok
Done!
Which is the desired output.
Writing the template the second way is very unreadable and messy. Is there any way that we can write the template with proper indentation but somehow configure rendering in such a way that the template placeholders would not be converted to new lines, or their indentation would be ignored (so that the desired output would be generated)?
Edit: using {- ""} even makes the whole thing worse! Why? Please consider the following:
{{- range $item := . }}
{{- if $item.IsA }}
{{- "How many spaces??" -}}OK
...
So let me put it in another way, is there any built-in post-processor available in golang for templates?
I came across this solution after playing with templates for a while. In order to make the template cleaner, we can do one trick. Templates do accepts custom functions and the trick is to define an indentation function:
const _indent = "___START___"
_funcs = template.FuncMap {
"Indent": func(indent int) string {
str := bytes.NewBufferString(_indent)
for i := 0; i < indent; i++ {
str.WriteString(" ")
}
return str.String()
},
}
We can include a character as indentation as well but here I only need spaces. Then we need a post processor:
func PostProcess(strBuffer bytes.Buffer) bytes.Buffer {
buffer := bytes.NewBufferString("")
lines := strings.Split(strBuffer.String(), "\n")
for _, line := range lines {
tmp := strings.TrimLeftFunc(line, func(r rune) bool {
return r == ' '
})
tmp = strings.TrimPrefix(tmp, _indent)
buffer.WriteString(tmp)
}
return *buffer
}
Now we can write the markup as:
{{ range $i := . }}
{{ if $i.IsOk }}
{{ 4 | Indent }}OK
...
and we can run the PostProcess function on the rendered text. My PostProcess function does not eliminate empty lines, but we can add it easily as well.
This is way more readable than the {{- " " -}} syntax.
In my template, I use a sub-template that generates a piece of output.
The template output must be shifted though (because the output is in YAML format).
Is there any possibility to post-process template output?
{{ template "subtemplate" | indent 10 }}
This indent 10 is fictional, just to explain what I need.
It is possible (as #icza suggested) to save the output
into a variable and then work with it,
but maybe there is a better, more elegant approach?
{{$var := execTempl "subtemplate"}}
{{$var}}
The closest you can get to {{ template "subtemplate" | indent 10 }} is to define a function that parses and executes the subtemplate and outputs the result as string.
var externalTemplates = map[string]*template.Template{
"subtemplate": template.Must(template.New("subtemplate").Parse(sub_template)),
}
// Executes external template, must be registered with FuncMap in the main template.
func xtemplate(name string) (string, error) {
var b bytes.Buffer
if err := externalTemplates[name].ExecuteTemplate(&b, name, nil); err != nil {
return "", err
}
return b.String(), nil
}
t := template.Must(template.New("t").Funcs(template.FuncMap{
"xtemplate": xtemplate, // register func
}).Parse(main_template))
In the main template you can then use the function like this:
{{ xtemplate "subtemplate" | indent 10 }}
https://play.golang.org/p/brolOLFT4xL
How can you calculate something inside a html template of go?
For example:
{{ $length := len . }}
<p>The last index of this map is: {{ $length -1 }} </p>
Were the . is a map.
The code {{ $length -1 }} is not working, is there a way to achieve this?
You can't. Templates are not a scripting language. By design philosophy, complex logic should be outside of templates.
Either pass the calculated result as a parameter (preferred / easiest), or register custom functions which you can call during template execution, pass values to them and which may perform calculations and return any values (e.g. return param - 1).
For examples of registering and using custom functions, see:
Golang templates (and passing funcs to template)
How do I access object field by variable in template?
Iterate Go map get index.
The other answers are correct, you can't do it in the template themselves. However, here's a working example of how to use Funcs:
package main
import (
"fmt"
"html/template"
"os"
)
type MyMap map[string]string
func LastMapIndex(args ...interface{}) string {
if m, ok := args[0].(MyMap); ok && len(args) == 1 {
return fmt.Sprintf("%d", len(m) - 1)
}
return ""
}
func main() {
myMap := MyMap{}
myMap["foo"] = "bar"
t := template.New("template test")
t = t.Funcs(template.FuncMap{"LastMapIndex": LastMapIndex})
t = template.Must(t.Parse("Last map index: {{.|LastMapIndex}}\n"))
t.Execute(os.Stdout, myMap)
}
Playground: https://play.golang.org/p/YNchaHc5Spz
You can use a FuncMap like this. Once you define a function within a funcmap, you can use it in the HTML. In your case you could define a MapLength function or something similar that calculates the length of a given map and returns it for you. You can then call it in the template a bit like this:
<p>The last index of this map is: {{ .MapLength . }} </p>
This question already has an answer here:
Capture or assign golang template output to variable
(1 answer)
Closed 5 years ago.
I have two templates T1 and T2. I want to get the output of T1 and do a some extra processing on it inside T2. My question is:
how do I store the output of T1 in a variable inside T2? Is this even possible?
Here's some pseudo-template:
{{define "T1"}}
{{ printf "%s-%s" complex stuff }}
{{end}}
{{define "T2"}}
{{ $some_var := output_from_template "T1"}} <<<<<<<<<<<
{{ etc }}
{{end}}
There is no builtin support for storing the result of a template in a template variable, only for the inclusion of the result.
But you can register custom functions with any complex functionality you want. You may register a GetOutput function which would execute a template identified by its name, and it could return the result as a string, which you can store in a template variable.
Example doing this:
func main() {
t := template.New("")
t = template.Must(t.Funcs(template.FuncMap{
"GetOutput": func(name string) (string, error) {
buf := &bytes.Buffer{}
err := t.ExecuteTemplate(buf, name, nil)
return buf.String(), err
},
}).Parse(src))
if err := t.ExecuteTemplate(os.Stdout, "T2", nil); err != nil {
panic(err)
}
}
const src = `
{{define "T1"}}{{ printf "%s-%s" "complex" "stuff" }}{{end}}
{{define "T2"}}
{{ $t1Out := (GetOutput "T1")}}
{{ printf "%s-%s" "even-more" $t1Out }}
{{end}}`
Output will be (try it on the Go Playground):
even-more-complex-stuff
The "T1" template simply outputs "complex-stuff", and the "T2" template gets the output of "T1", and concatenates the static text "even-more-" and the result of "T1".
The registered GetOutput function gets the name of a template to execute, executes it by directing its output to a local buffer, and returns the content of the buffer (along with the optional error of its execution).
Edit: I've found an exact duplicate: Capture or assign golang template output to variable
How can I slice strings in a template using the text/template package? Of course, something like {{ $myString[0:5] }} is not working.
Define your own slicing function with template.Funcs.
Code:
t.Funcs(template.FuncMap{
"stringSlice": func(s string, i, j int) string {
return s[i:j]
}
})
Template:
{{ stringSlice .MyString 0 5 }}
See also: Template and custom function; panic: function not defined
PS: As #dyoo correctly noted in the comments; this minimal stringSlice function does nothing to prevent you from slicing UTF-8 characters in half. You should probably handle that in a live environment.