Golang separating items with comma in template - go

I am trying to display a list of comma separated values, and don't want to display a comma after the last item (or the only item if there is only one).
My code so far:
Equipment:
{{$equipment := .Equipment}}
{{ range $index, $element := .Equipment}}
{{$element.Name}}
{{if lt $index ((len $equipment) -1)}}
,
{{end}}
{{end}}
The current output: Equipment: Mat , Dumbbell ,
How do I get rid of the trailing comma

A nice trick you can use is:
Equipment:
{{$equipment := .Equipment}}
{{ range $index, $element := .Equipment}}
{{if $index}},{{end}}
{{$element.Name}}
{{end}}
This works because the first index is 0, which returns false in the if statement. So this code returns false for the first index, and then places a comma in front of each following iteration. This results in a comma separated list without a leading or trailing comma.

Add a template function to do the work for you. strings.Join is perfect for your use case.
Assuming tmpl contains your templates, add the Join function to your template:
tmpl = tmpl.Funcs(template.FuncMap{"StringsJoin": strings.Join})
Template:
Equipment:
{{ StringsJoin .Equipment ", " }}
Playground
Docs: https://golang.org/pkg/text/template/#FuncMap

Another way to skin the cat, if you can create a new type for the field.
type MyList []string
func (list MyList) Join() string {
return strings.Join(list, ", ")
}
Then you can use the function in the template like regular:
{{ .MyList.Join }}

without index
it can be useful for any iteration, like on maps that do not have index:
{{ $first := true }}
{{ range $_, $element := .}}
{{if not $first}}, {{else}} {{$first = false}} {{end}}
{{$element.Name}}
{{end}}
Playground

If you are willing to use an external library, it seems like sprig library has a "join" function (see here):
join
Join a list of strings into a single string, with the given separator.
list "hello" "world" | join "_"
The above will produce hello_world
join will try to convert non-strings to a string value:
list 1 2 3 | join "+"
The above will produce 1+2+3

Related

How to strip leading whitespace before a Nunjucks variable only if the variable string is empty?

I'd like to support construction of Nunjucks templates where leading whitespace before variables is only stripped if the variable is empty.
A simple example could begin:
Hello {{ firstname }}, how are you?
If firstname is "Dave", this renders to "Hello Dave, how are you?". But if not set, it renders to "Hello , how are you?" with the extra space.
How can templates be constructed, in a general way (without fore-knowledge of the surrounding text), such that leading whitespace is only stripped before variables if the variable string is empty?
Using {{- firstname }} would always strip leading whitespace (so doesn't work if the variable is not empty, resulting in "HelloDave" in the example above
).
Something like the following works for this specific case (or better, the approaches suggested by #AikonMogwai in the comments), but is overly complex and relies on knowledge of the surrounding text (e.g., inserts a space if the variable is non-empty):
Hello
{%- if firstname %}
{{- " " + firstname -}}
{% else %}
{{- "" -}}
{% endif %},
Here's a fiddle that demonstrates the above examples: https://jsfiddle.net/davebeyer/3L5146jg/ (Click "Run" then "Render".)
Ideally, I'd like to create/use a custom tag (like {{=) or custom filter (like condlstrip for conditional left-strip) so that templates can be constructed using something like:
{{= firstname }}
or
{{ firstname | condlstrip }}
But it's not clear to me how to create a custom {{= tag, and also not clear whether filters, like condlstrip, can affect rendering outside of the tags.
Regarding the requirement "without fore-knowledge of the surrounding text," this specifically means that the solution should not assume that there will always be a space in front of the variable. For instance, it should also work in a case like the following (using my wished-for {{= tag), and where the user is fairly confident that school and studentId are set, but firstname may or may not be set):
Hi {{= firstname }}, your user ID is {{= school }}-{{= studentId }} and you can reach your profile at http://{{= school }}.example.com/profiles/{{= studentId }}
to produce:
Hi Dave, your user ID is CentralValley-1234 and you can reach your profile at http://CentralValley.example.com/profiles/1234
I.e., it would be great if our system could use the pattern {{= variable_name }} to produce reasonable default behavior for inserted variables in templates created by users (who are actually using a graphical, higher-level UI).
A simpler solution:
<li>Hello{{ " " if user.firstname }}{{ user.firstname }}, how are you?</li>
https://jsfiddle.net/pn8koeg3/
var nunjucks = require('nunjucks');
var env = nunjucks.configure();
env.addFilter('ws', val => !val || val == 0 ? val : ' ' + val);
var html = env.renderString(`
Hi {{- firstname | ws -}}, your user ID ...
`, { firstname: 'Dave' });
console.log(html);

How to have short circuit with and/or in text/template

I have this Go template:
{{ if and $b.Trigger $b.Trigger.Name }}
Name is {{ $b.Trigger.Name }}.
{{ else }}
...other stuff...
{{ end }}
I'm trying to get this template to do:
if b.Trigger != nil && $b.Trigger.Name != "" { ...
however it doesn't work, because as text/template godoc says, both arguments to and/or functions are evaluated.
When the $b.Trigger.Name is evaluated, it errors out because $b.Trigger can be nil. So it returns error:
template: builds.html:24:46: executing "content" at <$b.Trigger.Name>: can't evaluate field Name in type *myType
I tried refactoring this to:
{{ if and (ne $b.Trigger nil) (ne $b.Trigger.Name "") }}
and weird enough, this fails as well, it says I can't compare $b.Trigger with nil, which doesn't make sense because that field is a pointer type:
template: builds.html:24:31: executing "content" at <ne $b.Trigger nil>: error calling ne: invalid type for comparison
Any ideas?
As Volker noted above, nest the ifs:
{{if $b.Trigger}}{{if $b.Trigger.Name}}
Name is {{ $b.Trigger.Name }}.
{{end}}{{end}}
Or, more succinctly:
{{with $b.Trigger}}{{with .Name}}
Name is {{.}}.
{{end}}{{end}}
Unfortunately, the above won't handle else clauses. Here's one (rather ugly) possibility:
{{$bTriggerName := ""}}
{{with $b.Trigger}}{{$bTriggerName = .Name}}{{end}}
{{with $bTriggerName}}
Name is {{.}}.
{{else}}
...other stuff...
{{end}}
I looked to see if gobuffalo/plush could do this more elegantly, but as of 2019-04-30 it cannot.
Here explains why all arguments are evaluated in a pipeline.
It is evaluated through go template functions
https://golang.org/pkg/text/template/#hdr-Functions

How to get a field by index in template?

I send a slice of articles into template. Each articlestruct is like:
type Article struct {
ID uint32 `db:"id" bson:"id,omitempty"`
Content string `db:"content" bson:"content"`
Author string `db:"author" bson:"author"`
...
}
I can loop over articles slice in a {{range $n := articles}} and get each {{$n.Content}} but what I want is to have only the first one (outside the range loop) to use in headline.
What I tried is:
{{index .articles.Content 0}}
But I get:
Template File Error: template: articles_list.tmpl:14:33: executing
"content" at <.articles.Content>: can't evaluate field Content in type
interface {}
If I just invoke
{{index .articles 0}}
It shows the whole article[0] object.
How can I fix this?
The index function access the nth element of the specified array, so writing
{{ index .articles.Content 0 }}
is essentially trying to write articles.Content[0]
You would want something akin to
{{ with $n := index .articles 0 }}{{ $n.Content }}{{ end }}
A more concise way is:
{{(index .articles.Content 0).Content }}
Which would be the equivalent of articles[0].Content.
{{(index .articles 0).Content}}

Golang: Ordering map by slice in Go templates

I have a question about how to order a map by a slice in Go Templates and if it is possible.
Problem: I have a slice of ordered variable names that I want to display on a website, accompanying them I have a map of metadata of variable information that I would like to display together with the variable.
If I have the following struct that I pass in to the template:
type Data struct {
Variables []string
Information map[string]int
}
I would iterate over the slice and pass the variable name in to the map
{{ range $v := .Variables }} {{ index .Information $v }} {{ end }} // Doesn't work.
Here's a Go Playground with the example.
https://play.golang.org/p/AL2csnXdoU
Question: How can I do this?
I am fairly new to Golang. Thankful for any input.
The following should work. To access .Information inside range, you should use $, which is basically d in your Playground example.
{{ range .Variables }} {{ index $.Information . }} {{ end }}

Combining first function with index in golang/html template

I'm creating a blog with Hugo.
i would like to list the first 3 Blog entries. That is not a problem so far.
{{ range first 3 .Data.Pages.ByPublishDate }}
But i need the index for adding css classes. I do that with this line
{{ range $index, $element := .Data.Pages.ByPublishDate }}
My problem now is how following. How do I get the index like in the second line of code but still limit the result to 3.
Unfortunately this doesn't seem to work.
{{ range first 3 $index, $element := .Data.Pages.ByPublishDate }}
Any ideas?
I think what you are looking for based on your examples is the following:
{{ range $index, $element := (first 3 .Data.Pages.ByPublishDate) }}

Resources