Go templates are not designed to support complex logic. There's the Go programming language for that. Templates have limitations as a consequence of this philosophy. One limitation is that template variables cannot be changed.
One way to handle this limitation is to structure the data in Go to match the structure of output. Create a type to hold posts for a date and render a slice of these types. The template simply ranges through PostsForDate and Posts.
type PostsForDate struct {
Date time.Time
Posts []*Post
}
var Dates []PostsForDate
{{range .Dates}}
<div class="post-date">Posts dated: {{.Date}}</div>
{{range .Posts}}
<div class="post-content">{{.Content}}</div>
{{end}}
{{end}}
A simpler option (that goes against the design philosophy to some degree) is to create a type in Go to record a current value and report changes to that value.
type change struct {
current interface{}
}
func (c *change) Changed(next interface{}) bool {
result := c.current != next
c.current = next
return result
}
func newChange() *change {
return &change{&struct{ int }{}} // initial value ensures that first change is fired.
}
and hook it into a template using a template function:
t := template.Must(template.New("").Funcs(template.FuncMap{"change": newChange}).Parse(` some template `))
Use it in a template like this:
{{ $i := change }}
{{ range $post := .Posts }}
{{ $i.Change $post.Date }}
<div class="post-date">Posts dated: {{ $post.Date }}</div>
{{ end }}
<div class="post-content">{{ $post.Content }}</div>
{{ end }}
playground example
If the post Date
field is a time.Time
and the posts have different times within a day, then the above does not work as desired. A workaround for this is to check for changes in the rendered date (for example $post.Date.Format "2006-01-02"
). Add the following method to simplify this:
func (c *change) ChangedValue(next interface{}) interface{} {
if c.current != next {
c.current = next
return next
}
return nil
}
Use it like this:
{{ $i := change }}
{{ range $post := .Posts }}
{{with $i.ChangedValue ($post.Date.Format "2006-01-02")}}
<div class="post-date">Posts dated: {{.}}</div>
{{ end }}
<div class="post-content">{{ $post.Content }}</div>
{{ end }}
This only works when the values are guaranteed to be considered true by the template package.
This solution does not require parsing the template on every use (as in solution #1 in the other answer) and it applies to arbitrary slice types (unlike both solutions in the other answer).
:=
was actually redeclaring$prevDate
. Is there a way to modify the value of$prevDate
within the template itself that is declared outside of the range loop? – Rights