dictionarygogo-templatesrevel

Iterate Go map get index


In order to use revel's even keyword in templates I would like to get the index of a map entry when iterating with range. Is there any way to do so? My map has the structure:

map[string][]string

Solution

  • You can't do this only with template actions, but you may register a function which provides the necessary help.

    You may register a function which returns a function (closure), which alternates its return value whenever called (exactly how "odd" and "even" indices alternate):

    func isEven() func() bool {
        e := false
        return func() bool {
            e = !e
            return e
        }
    }
    

    I named it isEven() to not collide with ravel's even(). Using it:

    func main() {
        t := template.Must(template.New("").Funcs(template.FuncMap{
            "isEven": isEven,
        }).Parse(templ))
    
        m := map[string]string{
            "a": "A", "b": "B", "c": "C", "d": "D",
        }
        if err := t.Execute(os.Stdout, m); err != nil {
            panic(err)
        }
    }
    
    const templ = `{{$e := isEven}}
    {{- range $k, $v := . -}}
        [even:{{call $e}}] key={{$k}}; value={{$v}}
    {{end}}`
    

    Output (try it on the Go Playground):

    [even:true] key=a; value=A
    [even:false] key=b; value=B
    [even:true] key=c; value=C
    [even:false] key=d; value=D
    

    If you want different output for odd and even iterations, you can call $e in an {{if}} action, like this:

    const templ = `{{$e := isEven}}
    {{- range $k, $v := . -}}
        [{{if call $e}}even{{else}}odd {{end}}] key={{$k}}; value={{$v}}
    {{end}}`
    

    Output of this (try it on the Go Playground):

    [even] key=a; value=A
    [odd ] key=b; value=B
    [even] key=c; value=C
    [odd ] key=d; value=D
    

    Under the hood

    This template action:

    {{$e := isEven}}
    

    Creates a new template variable named $e, and its value will be the result (return value) of the isEven() function call. isEven() returns a function value, a closure that has access to a local variable e of type bool. When later you do {{call $e}}, you're not calling the isEven() Go function, but the function it returned (the closure) and is stored in $e. That closure has a reference to the local bool variable e, it is not "freed" until the function returned by isEvent() is accessible.

    So whenever you do {{call $e}}, it calls the closure, which "has" an e variable of type bool, whose value is retained between calls of this $e.

    If you would call isEvent in the template again, that would return a new function (closure), wrapping a new instance of the local variable e, being independent of the first wrapped variable of the closure returned by the first isEvent() call.