gin-gonic / gin

Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin.
https://gin-gonic.com/
MIT License
78.03k stars 7.97k forks source link

Gin HTML rendering documentation example incorrect #3801

Open f1gjam opened 9 months ago

f1gjam commented 9 months ago

Hi, looking at the page:

https://gin-gonic.com/docs/examples/html-rendering/

specifically this example:

func main() {
    router := gin.Default()
    router.LoadHTMLGlob("templates/**/*")
    router.GET("/posts/index", func(c *gin.Context) {
        c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
            "title": "Posts",
        })
    })
    router.GET("/users/index", func(c *gin.Context) {
        c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
            "title": "Users",
        })
    })
    router.Run(":8080")
}

When you do

    router.LoadHTMLGlob("templates/**/*")

This looks for any files within the templates directory and any subsequent subdirectories.

However these templates/files are availble using their filename and NOT the path.

For example given the following directory structure

When using using these templates in code

func fileOne(c *gin.Context) {
    c.HTML(http.StatusOK, "base/file1.html", nil)
}

you CANNOT use the path "base/file1.html" you have to just call the filename (see example below)

func fileOne(c *gin.Context) {
    c.HTML(http.StatusOK, "file1.html", nil)
}

I realised this when running the gin app you get the following output on console

[GIN-debug] Loaded HTML Templates (14):

Which basically means that Gin loads the files and makes them available, however when you want to use those file you just call the filename. The example shows that you call the base path etc.. but this does not work.

This also means you cannot have templates with the same filename.

dreamjz commented 9 months ago

Do you wrap your html template in the {{define \<template-path>}} - {{end}} block?

Here is my code try to reproduce your issue:

func main() {
    router := gin.Default()
    router.LoadHTMLGlob("templates/**/*")
    router.GET("/posts/index", func(c *gin.Context) {
        c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
            "title": "Posts",
        })
    })
    router.GET("/users/index", func(c *gin.Context) {
        c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
            "title": "Users",
        })
    })

    router.GET("/base/file1", func(c *gin.Context) {
        c.HTML(http.StatusOK, "base/file1.tmpl", gin.H{
            "title": "base",
        })
    })

    router.GET("/other/file2", func(c *gin.Context) {
        c.HTML(http.StatusOK, "file2.tmpl", gin.H{
            "title": "other",
        })
    })

    router.GET("/other/file2-1", func(c *gin.Context) {
        c.HTML(http.StatusOK, "other/file2.tmpl", gin.H{
            "title": "other",
        })
    })

    router.Run(":8080")
}

The templates dir :

templates
├── base
│   └── file1.tmpl
├── other
│   └── file2.tmpl
├── posts
│   └── index.tmpl
└── users
    └── index.tmpl

file1:

{{ define "base/file1.tmpl" }}
    <html><h1>
        {{ .title }}
    </h1>
    </html>
{{ end }}

file2

    <html><h1>
        {{ .title }}
    </h1>
    </html>

Test cases:

$  curl http://localhost:8080/base/file1

    <html><h1>
        base
    </h1>
    </html>

$ curl  http://localhost:8080/other/file2-1
#  GIN Log: Error #01: html/template: "other/file2.tmpl" is undefined

$  curl http://localhost:8080/other/file2
    <html><h1>
        other
    </h1>
    </html>

If you forgot to define your template file in the {{define}}, I think add it will solve your issue.

f1gjam commented 9 months ago

@dreamjz you are correct, the templates are not wrapped {{define }} - {{end}} .

Thank you for tracking this down. The documentation should be updated to state this, dont you think?