6

I'm trying to write a template (using html/template) and passing it a struct that has some methods attached to it, many of which return multiple values. Is there any way of accessing these from within the template? I'd like to be able to do something like:

package main

import (
        "fmt"
        "os"
        "text/template"
)

type Foo struct {
    Name string
}

func (f Foo) Baz() (int, int) {
    return 1, 5
}

const tmpl = `Name: {{.Name}}, Ints: {{$a, $b := .Baz}}{{$a}}, {{b}}`

func main() {

    f := Foo{"Foo"}

    t, err := template.New("test").Parse(tmpl)
    if err != nil {
        fmt.Println(err)
    }

    t.Execute(os.Stdout, f)

}

But obviously this doesn't work. Is there no way around it?

I've considered creating an anonymous struct in my code:

data := struct {
    Foo
    a   int
    b   int
}{
    f,
    0,
    0,
}
data.a, data.b = f.Baz()

And passing that in, but would much prefer to have something in the template. Any ideas? I also tried writing a wrapper function which I added to funcMaps but could never get that to work at all.

Thanks for any suggestions!

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
  • can't call function from template, that's true. so will you go ahead with `wrapper function`? show your current works on it and let people help you work it out. – Jiang YD Jul 08 '15 at 09:28
  • Related: [Text/template: “can't call method/function with 0 results.”](http://stackoverflow.com/questions/31221849/text-template-cant-call-method-function-with-0-results). Solution is the same: you have to create a custom function. – icza Jul 08 '15 at 09:47
  • I tried to make a function (FirstValue) that would take another function as its argument, and then return just the first value of the original output. Unfortunately it seems that I can't then pass the method through in the template so it won't work: {{FirstValue .Baz}} calls .Baz() }}rather than passes it, so it's a no go. – Andrew Charlton Jul 08 '15 at 11:37
  • Possible duplicate of [Text/template: "can't call method/function with 0 results."](https://stackoverflow.com/questions/31221849/text-template-cant-call-method-function-with-0-results) – Jonathan Hall Jul 16 '17 at 11:25

3 Answers3

6

You won't be able to call a function that returns two values in a template unless one of those values is an error. This is so that your template is guaranteed to work at runtime. There is a great answer that explains that here, if you're interested.

To solve your problem you need to either 1) break your function into two separate getter functions that you can call in the appropriate place in your template; or 2) have your function return a simple struct with the values inside.

I can't tell which would be better for you because I really have no idea what your implementation requires. Foo and Baz don't give many clues. ;)

Here is a quick-n-dirty example of option one:

type Foo struct {
    Name string
}

func (f Foo) GetA() (int) {
    return 1
}

func (f Foo) GetB() (int) {
    return 5
}

And then modify the template accordingly:

const tmpl = `Name: {{.Name}}, Ints: {{.GetA}}, {{.GetB}}`

Hopefully this is of some help. :)

Community
  • 1
  • 1
Jonathan
  • 5,495
  • 4
  • 38
  • 53
  • Thanks a lot. I though that was probably the case. My solution involves doing a set of calculations by iterating through a set of items and doing some (linked) work with them. I'm trying to avoid the overhead of iterating through multiple times, which would essentially double the calculations needed. I like the idea of passing a struct back though, that makes a lot of sense. Thanks! – Andrew Charlton Jul 08 '15 at 11:42
2

There is also possibility to return struct with multiple fields and use them.

type Result struct {
    First string
    Second string
}

func GetResult() Result {
     return Result{First: "first", Second: "second"}
}

And then use in template

{{$result := GetResult}}
{{$result.First}} - {{$result.Second}}
phonkee
  • 21
  • 1
0

I recently had a problem similar to this one and came across this question. I think this might be a little cleaner. It doesn't require you to create multiple new functions:

const tmpl = `Name: {{.Name}}, Ints: {{BazWrapper .}}`

func main() {

    f := Foo{"Foo"}

    funcMap := template.FuncMap{
        "BazWrapper": func(f Foo) string {
            a, b := f.Baz()
            return fmt.Sprintf("%d, %d", a, b)
        },
    }

    t, err := template.New("test").Funcs(funcMap).Parse(tmpl)
    if err != nil {
        fmt.Println(err)
    }

    t.Execute(os.Stdout, f)

}
Arthur Ruckman
  • 101
  • 2
  • 4