Search code examples
gogo-html-template

Golang template global dot within a template that's given a value from range


My setup:

base.tmpl

{{define "base"}} ...basic hmtl boilerplate ...{{end}}
{{define "post"}}
    {{.ID}} {{.Username}} etc... 
    {{if $.User.Admin}}
        <...admin controls...>
    {{end}}
{{end}}

index.tmpl

{{template "base" . }}
{{define "content"}}
    Stuff......
    {{range .Posts }}
        {{template "post" . }}
    {{end}}
{{end}}

But I get

$.User.Admin is not a field of db.Post

How can I get the values from the "global" dot value within a template that isn't given it? $. clearly doesn't work.

I was just having the post within the range, but I added a new page to view individual posts and would like to not update each area that posts are displayed individually.

Update: Templates are executed like so

func Index(rw http.ResponseWriter, req *http.Request) {
    g := GetGlobal(rw, req) // Gets logged in user info, adds branch/version info etc
    ... Get posts from the DB ...
    if err := Templates["index.tmpl"].Execute(rw, g); err != nil {
        Log.Println("Error executing template", err.Error())
    }
}

The Global struct looks like:

type Global struct {
    User     db.User
    Users    []db.User
    EditUser db.User
    Session  *sessions.Session
    Error    error
    Posts    []db.Post
    Post     db.Post

    DoRecaptcha bool
    Host        string
    Version     string
    Branch      string
}

Templates are loaded like so:

Templates[filename] = template.Must(template.ParseFiles("templates/"+filename, "templates/base.tmpl"))

Solution

  • Invoking a template creates a new scope:

    A template invocation does not inherit variables from the point of its invocation.

    So you have three options:

    1. Add a reference to the parent in the post (as you said you're doing in your last comment)
    2. Create a function (isAdmin) that references the variable through closure:

      template.New("test").Funcs(map[string]interface{}{
          "isAdmin": func() bool {
              return true // you can get it here
          },
      }).Parse(src)
      

      The downside of this approach is you have to parse the template every time.

    3. Create a new function which can construct a post that has a reference to the parent without having to modify the original data. For example:

      template.New("test").Funcs(map[string]interface{}{
          "pair": func(x, y interface{}) interface{} {
              return struct { First, Second interface{} } { x, y }
          },
      }).Parse(src)
      

      And you could use it like this:

      {{range .Posts }}
          {{template "post" pair $ . }}
      {{end}}
      

      Another option is to make a dict function: Calling a template with several pipeline parameters