Search code examples
gogo-gingo-html-template

Gin web application only rendering one template


I have a Gin web application with multiple HTML templates based off a set of partials and a base template. The base template seems to render fine along with the relevant partials, but my main views, login, index and register do not render as expected. Whenever I visit the HTTP endpoints for any of these, only the register view renders.

What is missing or misconfigured in the below files which is preventing my routes rendering the requested page?

My project has the following structure.

├── app
...
│   ├── handlers
│   │   ├── general
│   │   │   └── general.go
│   │   └── routes.go
│   ├── main.go
│   ├── reloadDev.sh
│   ├── static
│   │   ├── css
│   │   ├── img
│   │   └── js
│   └── templates
│       ├── home
│       │   ├── index.tmpl.html
│       │   ├── login.tmpl.html
│       │   └── register.tmpl.html
│       ├── layouts
│       │   └── base.tmpl.html
│       └── partials
│           ├── footer.tmpl.html
│           ├── head.tmpl.html
│           └── navbar.tmpl.html

base.tmpl.html

{{ define "base" }}
<!DOCTYPE html>
<html lang="eng" data-bs-theme="dark">
    {{ template "head" . }}
    {{template "navbar" .}}
    <body>
    {{ block "content" . }}{{ end }}
    </body>
    {{template "footer" .}}
</html>
{{end}}

register.tmpl.html

{{ template "base" . }}
{{ define "content" }}
<div class="container">
    <div class="row">
        <div class="col-md-6 offset-md-3">
            <h1>Register</h1>
            <form action="/register" method="post">
                <div class="mb-3">
                    <label for="username" class="form-label">Username</label>
                    <input type="text" name="username" id="username" class="form-control" placeholder="Username" required>
                </div>
                <div class="mb-3">
                    <label for="password" class="form-label">Password</label>
                    <input type="password" name="password" id="password" class="form-control" placeholder="Password" required>
                </div>
...SNIP...
                <button type="submit" class="btn btn-primary">Register</button>
            </form>
        </div>
    </div>
</div>
{{ end }}

index.tmpl.html (Login has the same structure as these two.)

{{ template "base" . }}
{{ define "title" }}Home{{ end }}
{{ define "content" }}
<div class="container">
    <div class="row">
        <div class="col-md-6 offset-md-3">
            <p>Welcome to Astra Porta.</p>
            <p>Click <a href="/login">here</a> to login.</p>
        </div>
    </div>
</div>
{{ end }}

The HTML templates are bundled with the binary using embed.FS.

//go:embed templates/partials/* templates/layouts/* templates/home/*
var files embed.FS

func main() {
    router := setupRouter()
    err := router.Run()
    if err != nil {
        panic(err)
    }
}

func setupRouter() *gin.Engine {
    router := gin.Default()
    subFS, e := fs.Sub(files, "templates")
    if e != nil {
        panic(e)
    }

tmpl := template.Must(template.ParseFS(
    subFS,
    "layouts/*.html",
    "partials/*.html",
    "home/*.html",
))
router.SetHTMLTemplate(tmpl)

router.StaticFS("/static", http.Dir("static"))

err := router.SetTrustedProxies(nil)
if err != nil {
    panic(err)
}
handlers.InitializeRoutes(&router.RouterGroup)
return router
}

And the pages are rendered within my application routes. The references here map to the file names of the *.tmpl.html files.

func SiteIndex(c *gin.Context) {
    c.HTML(http.StatusOK, "index.tmpl.html", nil)
}

func GetRegister(c *gin.Context) {
    c.HTML(http.StatusOK, "register.tmpl.html", nil)
}

func GetLogin(c *gin.Context) {
    c.HTML(http.StatusOK, "login.tmpl.html", nil)
}

Solution

  • For anybody else having this issue. The solution noted in mkopriva's comment was correct. I removed base.tmpl.html and composed each view using updated partials and the target page.

    header

    {{ define "header" }}
    <!DOCTYPE html>
    <html lang="eng" data-bs-theme="dark">
    {{template "navbar" .}}
        <body>
        {{ block "content" . }}{{ end }}
            <head><meta charset="UTF-8">
                <meta name="viewport" content="width=device-width, initial-scale=1.0">
               ...SNIP...
                <title>App</title>
            </head>
    {{end}}
    

    footer

    {{define "footer"}}
    
            <div class="container">
                <footer class="py-3 my-4" data-bs-theme="dark">
                    <ul class="nav justify-content-center border-bottom pb-3 mb-3">
                        <li class="nav-item"><a href="/" class="nav-link px-2 text-body-secondary">Home</a></li>
                    </ul>
                    <p class="text-center text-body-secondary">© 2024 .</p>
                </footer>
            </div>
        </body>
    </html>
    {{end}}
    

    Page in question

    {{template "header"}}
    <div class="container">
        <div class="row">
            <div class="col-md-6 offset-md-3">
                <p>Welcome to Astra Porta.</p>
                <p>Click <a href="/login">here</a> to login.</p>
            </div>
        </div>
    </div>
    {{template "footer"}}