wuno
wuno

Reputation: 9865

Map Json Data To Html Template In GoLang

I am looping through my API response and adding it to the html template like this,

 // Following sends same information as above to the browser as html
     t, err := template.New("TopMovies").Parse(`
      {{define "TopMovies"}}
        <html>
        <ul>
        {{$ImgUrl := "http://image.tmdb.org/t/p/w185" }}
        {{range $movies := .Results}}
        <li>{{$ImgUrl}}{{$movies.PosterPath}}</li>
        <li>{{$movies.Adult}}</li>
        <li>{{$movies.Overview}}</li>
        <li>{{$movies.ReleaseDate}}</li>
        <li>{{$movies.GenreIds}}</li>
        <li>{{$movies.Id}}</li>
        <li>{{$movies.OriginalTitle}}</li>
        <li>{{$movies.OriginalLanguage}}</li>
        <li>{{$movies.Title}}</li>
        <li>{{$ImgUrl}}{{$movies.BackdropPath}}</li>
        <li>{{$movies.Popularity}}</li>
        <li>{{$movies.VoteCount}}</li>
        <li>{{$movies.Video}}</li>
        <li>{{$movies.VoteAverage}}</li>
        {{end}}
        </ul>
        </html>
      {{end}}
      `)
    err = t.ExecuteTemplate(w, "T", p) // This writes the client response
}

I am under the impression I should be able to call this in my html templates like this,

{{.TopMovies}}

But when I run the app the data does not appear in the html page I am calling it in. What am I missing here?

I create a struct like this,

//A Page structure
type Page struct {
  Title string
  TopMovies string
}

Then I create my handle like this,

func TopMoviesHandler(w http.ResponseWriter, r *http.Request) {
   res, err := http.Get(url)
      if err != nil {
        panic(err)
      }
      defer res.Body.Close()

      body, err := ioutil.ReadAll(res.Body)
      if err != nil {
        panic(err)
      }
      var p Payload

      err = json.Unmarshal(body, &p)
      if err != nil {
        panic(err)
      }

  // Following sends same information as above to the browser as html
     t, err := template.New("TopMovies").Parse(`
      {{define "TopMovies"}}
        <html>
        <ul>
        {{$ImgUrl := "http://image.tmdb.org/t/p/w185" }}
        {{range $movies := .Results}}
        <li>{{$ImgUrl}}{{$movies.PosterPath}}</li>
        <li>{{$movies.Adult}}</li>
        <li>{{$movies.Overview}}</li>
        <li>{{$movies.ReleaseDate}}</li>
        <li>{{$movies.GenreIds}}</li>
        <li>{{$movies.Id}}</li>
        <li>{{$movies.OriginalTitle}}</li>
        <li>{{$movies.OriginalLanguage}}</li>
        <li>{{$movies.Title}}</li>
        <li>{{$ImgUrl}}{{$movies.BackdropPath}}</li>
        <li>{{$movies.Popularity}}</li>
        <li>{{$movies.VoteCount}}</li>
        <li>{{$movies.Video}}</li>
        <li>{{$movies.VoteAverage}}</li>
        {{end}}
        </ul>
        </html>
      {{end}}
      `)
    err = t.ExecuteTemplate(w, "T", p) // This writes the client response
}

Then in main.go

   http.HandleFunc("/TopPicks", TopMoviesHandler)

TopPicks.html

{{define "TopPicks"}}
    {{template "header" .}}
    <div class="content">
    {{.TopMovies}}
    </div>
     {{template "footer" .}}
    {{end}}

What does work is this,

func aboutHandler(w http.ResponseWriter, r *http.Request) {
  display(w, "about", &Page{Title: "About"})
}

I can add a title to the page in the same way as I previously mentioned but using display()

And in the html template

<title>{{.Title}}</title>

How can I make this work for my json response?

Upvotes: 8

Views: 9728

Answers (2)

David Budworth
David Budworth

Reputation: 11626

Looks like you are doing {{define "body"}}, but then asking ExecuteTemplate to execute "T" which isn't defined anywhere.

I think you want: t.ExecuteTemplate(w, "body", p)

That all said, if you just want to use multiple templates, you can do it by creating a master top level template, then parsing all the parts as sub templates.

Here's an example (on Play).

Easily changed to walk your file system and load all your templates, then you just execute the template matching the http.Request path.

package main

import "html/template"
import "os"
import "log"

var mainText = `
Normal page stuff
{{ template "_header_" . }}
{{ template "body" . }}
`

var bodyText = `
 Body has: {{ .Thing }}
`
var headerText = `
 I am header text
`

type Stuff struct {
    Thing string
}

func main() {
    t := template.New("everything")

    // parse all templates you may want
    template.Must(t.New("/").Parse(mainText))
    template.Must(t.New("_header_").Parse(headerText))
    template.Must(t.New("body").Parse(bodyText))

    if err := t.ExecuteTemplate(os.Stdout, "/", Stuff{"I am a thing"}); err != nil {
        log.Fatal("Failed to execute:", err)
    }
}

Upvotes: 3

Wade73
Wade73

Reputation: 4509

Ok, I think there were two problems with the original code.

  1. Handler was calling wrong template

func TopMoviesHandler(w http.ResponseWriter, r *http.Request) { ...snip original code...

err = t.ExecuteTemplate(w, "T", p) // This writes the client response

Should be 

err = t.ExecuteTemplate(w, "TopMovies", p) // This writes the client response
  1. Incorrect context was passed to nested template

In the html file there was this code

{{define "TopPicks"}}
     {{template "header" .}}
     <div class="content">
          {{.TopMovies}}
          {{template "MyTemplate" . }}
     </div>
     {{template "footer" .}}
{{end}}

Which should be

{{define "TopPicks"}}
     {{template "header" .}}
     <div class="content">
          {{.TopMovies}}
          {{template "MyTemplate" .TopMovies }}
     </div>
     {{template "footer" .}}
{{end}}

The reason was that you were trying to pass the main context to the nested template, versus just the json result the template was expecting.

Original Answer

I made a simple example of what I think you need to do. Where I set the topMovies variable, this is where you would set the results from your api call. I hope this helps to show you the sequence you need to follow a little bit better.

package main

import (
    "net/http"
    "text/template"
)

type movie struct {
    Title string
}

type page struct {
    Title     string
    TopMovies []movie
}

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
        w.Header().Add("Content Type", "text/html")

        templates := template.New("template")
        templates.New("Body").Parse(doc)
        templates.New("List").Parse(docList)

        topMovies := []movie{{Title: "Movie 1"}, {Title: "Movie 2"}, {Title: "Movie 3"}}

        page := page{Title: "My Title", TopMovies: topMovies}
        templates.Lookup("Body").Execute(w, page)

    })

    http.ListenAndServe(":8000", nil)
}

const docList = `
<ul >
    {{range .}}
    <li>{{.Title}}</li>
    {{end}}
</ul>
`

const doc = `
<!DOCTYPE html>
<html>
    <head><title>{{.Title}}</title></head>
    <body>
        <h1>Hello Templates</h1>
        {{template "List" .TopMovies}}
    </body>
</html>
`

Upvotes: 1

Related Questions