1
votes

I've been having some issues loading html templates using the Gin framework through the r.HTMLRender setting.

It would seem that the templates are not being found.

I have tried to use the following helpers:

Neither of these seem to be working when setting the default path for templates (in my case app/views); for the purposes of getting to this to work my html template file structure looks like this:

/workspace
|-app
  |-views
    |-layouts
      |-application.html
    |-test.html

Here is a sample of the Gin loading code:

import (
    "github.com/gin-contrib/location"
    "github.com/gin-gonic/gin"
    "fmt"
    "os"
    "github.com/gin-gonic/contrib/static"
    "github.com/michelloworld/ez-gin-template"
)

//CORSMiddleware ...
func CORSMiddleware() gin.HandlerFunc {
    /** CORS middleware **/
}

func Router() {

    if os.Getenv("ENVIRONMENT") == "production" {
        gin.SetMode(gin.ReleaseMode)
    }

    // Initialize Gin object
    r := gin.Default()

    // Cors Middleware
    r.Use(CORSMiddleware())

    // Rate limiting
    rl, err := helpers.RateLimiterMiddleware()
    if err != nil {
        panic("Rate Limiting Initialization error")
    }
    r.Use(rl)

    // Asset provision
    r.Use(static.ServeRoot("/public","app/assets"))

    // Get URL information
    r.Use(location.Default())

    // Attempt with EZ Template, fails
    // I ge this error: "runtime error: invalid memory address or nil pointer dereference" when calling c.HTML(...)
    render := eztemplate.New()
    render.TemplatesDir = "app/views/" // default
    render.Layout = "layouts/application"     // default
    render.Ext = ".html"               // default
    render.Debug = true               // default
    r.HTMLRender = render.Init()


    // Attempt with GinHTMLRender, fails
    // I get this error: https://gist.github.com/madhums/4340cbeb36871e227905#file-gin_html_render-go-L110
    /* 
    htmlRender := GinHTMLRender.New()
    htmlRender.TemplatesDir = "app/views/"
    htmlRender.Debug = gin.IsDebugging()
    htmlRender.Layout = "layouts/application"

    log.Println("Dir:"+htmlRender.TemplatesDir)

    r.HTMLRender = htmlRender.Create()*/

    /** Some Routes **/

    // Start web listener

    r.Run(":8009") // listen and serve on 0.0.0.0:8080
}

The corresponding render call is code is the following:

/* c is of type *gin.Context */
c.HTML(200, "test", "")

For some reason it seems like the r.HTMLRender function is not taking into account the template path; I have attempted doing this:

_, err := template.ParseFiles("app/views/test.html")
if err != nil {
    log.Println("Template Error")
} else {
    log.Println("No Template Error")
}

This code consistently displays "No Template Error", which leads me to believe that the HTMLRender assignment is not considering the TemplatesDir set variable.

I've been stuck with this issue for some time, and I am not entirely sure how to get it resolved.

Any help getting this to work would be greatly appreciated.

3
Have you tried LoadHTMLGlob() or LoadHTMLFiles() from gin-gonic/gin#html-rendering?assefamaru
I want to go the helper way to reduce loading each template manually when rendering the page. I've actually found the source of my problem with EZ Gin Template, though I haven't tried again with GinRenderHTML. I'll be posting my findings as an answer to my question. Thanks for the suggestion.autronix

3 Answers

1
votes

After doing some further research I found the source of my problem with EZ Gin Template.

I'm hoping this helps anyone experiencing the same issue.

After taking a deeper look at the helper code, I realized that the template matching pattern is strict and does not search for files recursively; ie. it expects a specific file structure to find template files:

In the default setting, EZ Gin Template requires the following file structure to work:

/workspace
- app
  - views
    - layouts
      - some_layout.html
    - some_dir
      - template_file.html
      - _partial_template.html
    - partials
      - _some_other_partial.html

In order to allow for other file patterns, the helper set of functions needs to be modified.

In my case, I forked the helper code locally to allow matching 1st level template files:

func (r Render) Init() Render {
    globalPartials := r.getGlobalPartials()

    layout := r.TemplatesDir + r.Layout + r.Ext

    // Match multiple levels of templates
    viewDirs, _ := filepath.Glob(r.TemplatesDir + "**" + string(os.PathSeparator) + "*" + r.Ext)
    // Added the following two lines to match for app/views/some_file.html as well as files on the **/*.html matching pattern
    tmp, _ := filepath.Glob(r.TemplatesDir + "*" + r.Ext)
    viewDirs = append(viewDirs, tmp...)
    // Can be extended by replicating those two lines above and adding search paths within the base template path.

    fullPartialDir := filepath.Join(r.TemplatesDir + r.PartialDir)
    for _, view := range viewDirs {
        templateFileName := filepath.Base(view)
        //skip partials
        if strings.Index(templateFileName, "_") != 0 && strings.Index(view, fullPartialDir) != 0 {
            localPartials := r.findPartials(filepath.Dir(view))

            renderName := r.getRenderName(view)
            if r.Debug {
                log.Printf("[GIN-debug] %-6s %-25s --> %s\n", "LOAD", view, renderName)
            }
            allFiles := []string{layout, view}
            allFiles = append(allFiles, globalPartials...)
            allFiles = append(allFiles, localPartials...)
            r.AddFromFiles(renderName, allFiles...)
        }
    }

    return r
}

I have not tried a similar solution with GinHTMLRenderer, but I expect that the issue might likely be related to it in terms of the expected file structure.

0
votes

You can also bind the templates into the code. The jessevdk/go-assets-builder will generate a go file that contains assets within the specified directory. Make sure that the file generated is located where the main package is. Gin also provided this as an example in their Documentation For more Info. It will also include subfolders and its files (i. e. assets) in the binary.

Get The generator tool:

go get github.com/jessevdk/go-assets-builder

Generate:

# go-assets-builder <dir> -o <generated file name>
go-assets-builder app -o assets.go

Note that <generated file name> can also be like cmd/client/assets.go to specify the destination of the file to be generated.

Load Template:

package main

// ... imports

func main() {
    r := gin.New()

    t, err := loadTemplate()
    if err != nil {
        panic(err)
    }
    r.SetHTMLTemplate(t)

    r.GET("/", func(c *gin.Context) {
        c.HTML(http.StatusOK, "app/views/layouts/application.html", nil)
    })
    r.GET("/test", func(c *gin.Context) {
        c.HTML(http.StatusOK, "app/views/test.html", nil)
    })
    r.Run(":8080")
}

// loadTemplate loads templates embedded by go-assets-builder
func loadTemplate() (*template.Template, error) {
    t := template.New("")
    // Assets is the templates
    for name, file := range Assets.Files {
        if file.IsDir() || !strings.HasSuffix(name, ".html") {
            continue
        }
        h, err := ioutil.ReadAll(file)
        if err != nil {
            return nil, err
        }
        t, err = t.New(name).Parse(string(h))
        if err != nil {
            return nil, err
        }
    }
    return t, nil
}
0
votes

Here is how I do it. This walks through the directory and collects the files marked with my template suffix which is .html & then I just include all of those. I haven't seen this answer anywhere so I thought Id post it.

// START UP THE ROUTER
router := gin.Default()

var files []string
filepath.Walk("./views", func(path string, info os.FileInfo, err error) error {
    if strings.HasSuffix(path, ".html") {
        files = append(files, path)
    }
    return nil
})

router.LoadHTMLFiles(files...)

// SERVE STATICS
router.Use(static.Serve("/css", static.LocalFile("./css", true)))
router.Use(static.Serve("/js", static.LocalFile("./js", true)))
router.Use(static.Serve("/images", static.LocalFile("./images", true)))

routers.LoadBaseRoutes(router)
routers.LoadBlog(router)

router.Run(":8080")

Now they don't have to all be nested at the exact depth like the other suggestions ... the file structure can be uneven