Golang template variable isset
Asked Answered
W

3

15

I have created a function to check if a variable is defined:

fm["isset"] = func(a interface{}) bool {
        if a == nil || a == "" || a == 0 {
            fmt.Println("is not set")
            return false
        }
        fmt.Println("is set")
        return false
    }

tmpl :=  template.Must(template.New("").Funcs(fm).ParseFiles("templates/header.html"))

err := tmpl.ExecuteTemplate(w, "header", templateData)

In the template I have:

{{ if isset .Email }}
    email is set
{{ end }}

This function works if the variable is contained by the templateData (which is a custom struct that contains a map and a string), but it gives me an error if the variable doesn't exist.

The error is:

executing "header" at <.Email>: can't evaluate field Email in type base.customData

In my case "base.go" is the handler and "customData" is defined by: type customData struct{..}.

I want to be able to reuse templates and to display some sections only if some variables are sent from the handler. Any idea how can I implement a variable isset check on the template side?

I also tried using: {{ if .Email}} do stuff {{ end }} but this also gives me the same error.

Any idea?

Woodall answered 21/6, 2017 at 11:41 Comment(9)
Try to log a in your isset function. Do you see what you are passing in? If not, there is a problem at the template level. Maybe try passing in both the struct and the key to access it on to check if isset, and modify your function accordingly to take in both arguments.Gasolier
if my struct contains the "Email" it will work and it will log it inside the isset function. If the struct don't contain "Email" (this is my case) it will just output the error from above, no logs from the issset function will be displayed.Woodall
PHP has the isset($variable) function which returns true/false, what about GO? How can I check for a variable in the template to see if it is sent from the handler? For example, I want to display a menu only if the user is logged in.Woodall
Try simply {{ if .Email }}, that should work for you.Gasolier
I also tried using: {{ if .Email}} do stuff {{ end }} but this also gives me the same error. I'm not sending the "Email" to the template. Sholdn't the if return false instead of throwing an error?Woodall
What is base.customData?Gasolier
base is the handler, "base.go" and customData is a struct that contains a map ("Streets") and a string ("Name"). If I add a new string to the structure of "customData" called "Email" the {{ if isset .Email}} function and {{ if .Email }} will both work. So the problem occurs only when I don't send the data that I check on the template.Woodall
Take a look here.Gasolier
Very good example. I created my "isset" function based on what I saw on that page. I'm not using gohugo (static site generator) so everything you see in there is custom code that won't work on simple/plain go templates. See my answer to the question. Now I don't need to use the "isset" function anymore.Woodall
B
24

The recommended way

First, the recommended way is not to rely on whether a struct field exists. Of course there might be optional parts of the template, but the condition to decide whether to render a part should rely on fields that exist in all cases.

The issue, and avoiding it using a map

If the type of the template data is a struct (or a pointer to a struct) and there is no field or method with the given name, the template engine returns an error for that.

You could easily get rid of this error if you were to use a map, as maps can be indexed with keys they don't contain, and the result of that index expression is the zero value of the value type (and not an error).

To demonstrate, see this example:

s := `{{if .Email}}Email is: {{.Email}}{{else}}Email is NOT set.{{end}}`

t := template.Must(template.New("").Parse(s))
exec := func(name string, param interface{}) {
    fmt.Printf("\n%s:\n  ", name)
    if err := t.Execute(os.Stdout, param); err != nil {
        fmt.Println("Error:", err)
    }
}

exec("Filled map", map[string]interface{}{"Email": "as@as"})
exec("Empty map", map[string]interface{}{})

exec("Filled struct", struct {
    Email string
}{Email: "[email protected]"})
exec("Empty struct", struct{}{})

Output (try it on the Go Playground):

Filled map:
  Email is: as@as
Empty map:
  Email is NOT set.
Filled struct:
  Email is: [email protected]
Empty struct:
  Error: template: :1:5: executing "" at <.Email>: can't evaluate field Email in type struct {}

Sticking to struct and providing "isset"

If you must or want to stick to a struct, this "isset" can be implemented and provided, I'll call it avail().

This implementation uses reflection, and in order to check if the field given by its name exists (is available), the (wrapper) data must also be passed to it:

func avail(name string, data interface{}) bool {
    v := reflect.ValueOf(data)
    if v.Kind() == reflect.Ptr {
        v = v.Elem()
    }
    if v.Kind() != reflect.Struct {
        return false
    }
    return v.FieldByName(name).IsValid()
}

Example using it:

s := `{{if (avail "Email" .)}}Email is: {{.Email}}{{else}}Email is unavailable.{{end}}`

t := template.Must(template.New("").Funcs(template.FuncMap{
    "avail": avail,
}).Parse(s))
exec := func(name string, param interface{}) {
    fmt.Printf("\n%s:\n  ", name)
    if err := t.Execute(os.Stdout, param); err != nil {
        fmt.Println("Error:", err)
    }
}

exec("Filled struct", struct {
    Email string
}{Email: "[email protected]"})
exec("Empty struct", struct{}{})

Output (try it on the Go Playground):

Filled struct:
  Email is: [email protected]
Empty struct:
  Email is unavailable.
Bicknell answered 21/6, 2017 at 12:29 Comment(1)
The map approach doesn't work if the value of the entry in the map is 'false' (i.e. it prints "Email is NOT set." instead of "Email is: false")Duston
W
0

If you don't send data containing a variable to the template but you use {{ if .Variable }} you will get the error:

executing "templatename" at <.Variable>: can't evaluate field Variable in type handler.Data

My solution was to send the "Email" variable as a boolean (false) in order to pass the {{ if .Email }} function check. But this is a short term solution that I don't like.

I was inspired by: https://mcmap.net/q/824110/-hide-html-content-if-a-user-is-logged-in. In that example they show different HTML for authenticated and non-authenticated users. You will see that in both cases they send the "Logged" variable. Try removing that variable from the struct and execute the function. You will receive the error that I mentioned above.

Woodall answered 21/6, 2017 at 12:13 Comment(0)
R
-3

The simple way of doing:

{{ if .Email }}

is to use index:

{{ if index . "Email" }}
Repetitive answered 8/11, 2019 at 8:25 Comment(2)
I don't see how this works. Please explain? AFAICT index will error on non-enumerablesCheadle
index function does not work with type struct.Overcash

© 2022 - 2024 — McMap. All rights reserved.