I started to write a Gin application and my project tree looks like
-assets
--css
---{bootstrap}
-templates
--layouts
---footer.html
---head.html
---header.html
--book.html
-main.go
In main.go I load templates and there is no error
router.LoadHTMLGlob("./templates/layouts/*.html")
I define templates
{{ define "head" }}
<head>
//Head
</head>
{{ end }}
And I nest them
{{ define "header" }}
{{ template "head.html" . }}
//HTML
{{ end }}
But when I try to use them, I get empty output
{{ template "header" . }}
<h1>{{ .Title}}</h1>
<h3>{{ .Author.Fullname}}</h3>
[Edit] Function that executes the template:
func getBook(c *gin.Context) {
//DB stuff
var book models.Book
t, err := template.ParseFiles("templates/book.html")
if err != nil {
log.Println(err)
}
t.Execute(c.Writer, book)
}
Full-code can be found on github
router.LoadHTMLGlob and template.ParseFiles are two separate approaches to deal with templates. The template returned by ParseFiles has no knowledge of the templates loaded by LoadHTMLGlob. Once you decide to use LoadHTMLGlob you should then use c.HTML to render your templates. And the name argument to this c.HTML method would be either the name specified in a {{define "name"}} action or the base name of the template file (including the extention I believe).
So in your case you should probably do something like this:
c.HTML(http.StatusOK, "book.html", book)
More examples can be found here: https://gin-gonic.com/docs/examples/html-rendering/
Keep in mind that LoadHTMLGlob relies on template.ParseGlob which states:
When parsing multiple files with the same name in different
directories, the last one mentioned will be the one that results.
That means that if you want all of your templates to be accessible through c.HTML you need to make sure that they either have unique base names or they need to contain the {{ define "name"}} action.
Moving from the default templating system where 'everything simply worked' to Gin is a bit confusing, namely, there seem to be some naming restrictions when using files for templates. I have no idea if this is the case, but, in my setup, I had to make sure that:
The name of the template (the define keyword) needs to be the filename — at least when that's the only define in the template (I haven't tested with multiple defines) — i.e. if you're using ./templates/book.html as a template, you need to have {{ define "book.html" }} at the top of that file (this is true for templates included in other templates; I didn't experiment with blocks or other more esoteric ways to jinx templates together)
Similarly, when calling c.HTML(http.StatusOK, "book.html", book), you have to put the full name of the file containing that template (as shown!)
In other words, although the manual says otherwise, and #mkopriva confirms what the manual says, I have had a different experience: it was only I started matching the filename with the define and the c.HTML() call that I stopped getting blank pages...
Also, while running from the console (in debug mode), it was clear that my programme was 'finding' far more templates than it should — namely, almost every template was duplicated (one copy for the filename, another copy for the define, etc.). This confused not only me but the application itself...
Related
I am in the process of learning fiber framework in GO language and having trouble figure out why the template engine is returning an error when the body section is an include. The following works as expected but when I add in another include for the "body section" it throws an error:
The Error:
html/template:fun: """ in attribute name: " ">Read Full Article\n "
This one works:
I am unable to add another template "partial" in the middle for body content, I have even tried the full design html in this section (without using includes), either way it throws the same error when loading. For some reason this sample body above works fine, but the error isn't telling me much.
This won't work:
Nor does this work:
{{template "includes/header" .}}
{{template "includes/navigation" .}}
// full html body text here (much longer than first working example)
{{template "includes/footer" .}}
I am not sure why the standard template engine had issues because the errors were not very clear, but I was able to fix the issue by switching over the jet engine. Jet seems to have better error handling. All template extensions must be switched to .jet extension and the includes change a little bit to {{include "directory/file_name" }}
// Loading Templates
engine := jet.New("./views", ".jet")
// Start Fiber
app := fiber.New(fiber.Config{
Views: engine,
})
I'm working on a personal project to learn some more Go and hitting a snag (or potentially going about this all wrong too so there is that).
A little background first:
I'm working on a simple web app with some form fields to do some basic network tests like curl, netcat, and traceroute. I was looking to use WASM so that I could just modify the DOM with results from the Go app, but I believe due to there being a lot of syscalls, the WASM architecture doesnt support them and I couldnt compile it after a certain point or when it did it was failing anytime I ventured into some of the network pkgs. I couldn't really find another way to use javascript without abandoning Go, so I decided to go with templates.
When a form is submitted it loads a results.html that is identical to the index.html except some Go templating to print the results of the network tests. It works great has a "meh" kinda of way to simulate JS modifications and allow you to do more tests without leaving the page BUT since there are three different network tests, and only one running at one time, I get errors when it can't fill ALL the templates.
So for example
<section id="curlOutput">
<p><strong>Executed curl to {{.URL}}</strong></p>
<p>{{.Protocol}}</p>
<p>{{.Status}}</p>
<p>{{.ContentLength}}</p>
{{range $key, $val := .Headers}}
<p><strong>{{$key}}:</strong> {{$val}}</p>
{{end}}
<br/>
</section>
<section id="ncOutput">
<p><strong>Executed netcat to {{.Host}}</strong></p>
<p>{{.Result}}</p>
</section>
The issue i hit is if I want to test Netcat, Go throws errors because it can't fill the earlier templates like for curl and such. So is there a way to define in the templates that if the value doesnt exist, just continue and leave it blank? Or does every single template have to be defined and filled? Perhaps defining defaults in the templates to be empty strings somehow? Is there a better way to go about this in general?
Sorry for the length, but i felt some background would help, thanks!
You can use a data struct better suited for the task and the with template action.
type Results struct {
CURLOutput *CURLOutput
NCOutput *NCOutput
}
type CURLOutput struct {
URL string
Protocol string
Status string
ContentLength int64
Headers map[string][]string
}
type NCOutput struct {
Host string
Result string
}
{{- with .CURLOutput -}}
<section id="curlOutput">
<p><strong>Executed curl to {{.URL}}</strong></p>
<p>{{.Protocol}}</p>
<p>{{.Status}}</p>
<p>{{.ContentLength}}</p>
{{ range $key, $val := .Headers -}}
<p><strong>{{$key}}:</strong> {{$val}}</p>
{{- end }}
<br/>
</section>
{{ end }}
{{ with .NCOutput -}}
<section id="ncOutput">
<p><strong>Executed netcat to {{.Host}}</strong></p>
<p>{{.Result}}</p>
</section>
{{ end }}
https://play.golang.org/p/dOfLc_5bl8b
I want to use the jsonQuery syntax in dockerize to parse traefik's acme.json and emit cert/key files for TLS settings in another service.
jsonQuery accepts a string, which the example gives as an environment variable {{ .Env.myJson }}
How might I get the string contents of a file:
{{with $myJsonContent := <insert magic here> }}
# extract key to file
{{end}}
Go's text/template doesn't natively support that. It looks like the dockerize tool provides a couple of extension functions but none of them allow this either.
(The nearest thing I can think of is that kubernetes-helm supports reading a file from a Helm chart, but that's implemented at the Go level by injecting a special accessor object that can provides the file-access API to template code, and it's intentionally limited to files physically located within the Helm chart directory.)
I can't see any way to add a function to the template, as dockerize doesn't expose the addition of functions to the template prior to parsing. So you'll either have to (1) get the contents of acme.json into an environment variable, or (2) modify dockerize to include a jsonFileQuery function in the templates.
Add the contents of acme.json to the environment variables before running dockerize - then access as in the example. This could be done with a small go program, added to the container and run via CMD prior to the CMD dockerize
Fork dockerize and change jsonQuery: Fork dockerize and change line 83 of template.go:
from: parser, err := gojq.NewStringQuery(jsonObj)
to: parser, err := gojq.NewFileQuery(jsonObj)
Then use:
{{with $myJsonContent := jsonQuery "/opt/traefik/acme.json" "toplevelobject" }}
# extract key to file
{{end}}
gojq.NewStringQuery() is a function behind the jsonQuery template function. The gojq.NewFileQuery() version has the same signature as the StringQuery but reads the file at the path in the input string instead of using the input string as json.
OR
Merge new jsonFileQuery template function into dockerize: submit an issue to dockerize to add jsonFileQuery to the template functions. Seems like it could be set up the same as jsonQuery but with the small difference above. In template.go, add the jsonFileQuery function and assign it to jsonFileQuery in the template.FuncMap{} within generateFile().
So I can use {{.Host}} just fine in the template file, but once inside a {{range .Items}} loop it doesn't work since it's trying to get the .Host from the .Items (array?)thing..
I get this as an error
template: listing:41:46: executing "listing" at <.Host>: can't evaluate field Host in type browse.FileInfo
I've never used Go before, I've tried reading the text template documentation page but it's all rather confusing.
ooooh, nevermind guys, I knew it was a simple fix.
{{$.Host}}
Just add the $, then you'll be using the global context again, instead of the context inside of the range loop.
Source, thanks HUGO for the clear documentation.
{{range}} changes the pipeline (the dot, .) to the current Items. You can use {{$.Host}} which will refer to the "top-level" Host.
{{$.Host}}
golang template.
How do I get nested templates like Jinja has in the python runtime. TBC what I mean is how do I have a bunch of templates inherit from a base templates, just filing in blocks of the base templates, like Jinja/django-templates does. Is it possible using just html/template in the standard library.
If that is not a possibility, what are my alternatives. Mustache seems to be an option but would I then be missing out on those nice subtle features of html/template like the context sensitive escaping etc.? What other alternatives are ther?
(Environment: Google App Engin, Go runtime v1, Dev - Mac OSx lion)
Thanks for reading.
Yes it is possible. A html.Template is actually a set of template files. If you execute a defined block in this set, it has access to all the other blocks defined in this set.
If you create a map of such template sets on your own, you have basically the same flexibility that Jinja / Django offers. The only difference is that the html/template package has no direct access to the file system, so you have to parse and compose the templates on your own.
Consider the following example with two different pages ("index.html" and "other.html") that both inherit from "base.html":
// Content of base.html:
{{define "base"}}<html>
<head>{{template "head" .}}</head>
<body>{{template "body" .}}</body>
</html>{{end}}
// Content of index.html:
{{define "head"}}<title>index</title>{{end}}
{{define "body"}}index{{end}}
// Content of other.html:
{{define "head"}}<title>other</title>{{end}}
{{define "body"}}other{{end}}
And the following map of template sets:
tmpl := make(map[string]*template.Template)
tmpl["index.html"] = template.Must(template.ParseFiles("index.html", "base.html"))
tmpl["other.html"] = template.Must(template.ParseFiles("other.html", "base.html"))
You can now render your "index.html" page by calling
tmpl["index.html"].Execute("base", data)
and you can render your "other.html" page by calling
tmpl["other.html"].Execute("base", data)
With some tricks (e.g. a consistent naming convention of your template files), it's even possible to generate the tmpl map automatically.
note, when you execute your base template, you must pass values down to the child templates, here I simply pass ".", so that everything is passed down.
template one displays {{.}}
{{define "base"}}
<html>
<div class="container">
{{.}}
{{template "content" .}}
</div>
</body>
</html>
{{end}}
template two displays {{.domains}} that's passed into the parent.
{{define "content"}}
{{.domains}}
{{end}}
Note, if we used {{template "content"}} instead of {{template "content" .}}, .domains wouldn't be accessible from the content template.
DomainsData := make(map[string]interface{})
DomainsData["domains"] = domains.Domains
if err := groupsTemplate.ExecuteTemplate(w, "base", DomainsData); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
having worked with other template packages, now a days I mostly work with standard html/template package, I guess I was naive to not appreciate the simplicity it provides and other goodies. I use a very similar approach to accepted answer with following changes
you don't need to wrap your layouts with additional base template, a template block is created for every parsed file so in this case it is redundant, I also like to use the block action provided in new version of go, which allows you to have default block content in case you don't provide one in child templates
// base.html
<head>{{block "head" .}} Default Title {{end}}</head>
<body>{{block "body" .}} default body {{end}}</body>
and you page templates can be the same as
// Content of index.html:
{{define "head"}}<title>index</title>{{end}}
{{define "body"}}index{{end}}
// Content of other.html:
{{define "head"}}<title>other</title>{{end}}
{{define "body"}}other{{end}}
now to execute the templates you need to call it like so
tmpl["index.html"].ExecuteTemplate(os.Stdout, "base.html", data)
Use Pongo, which is a super-set of Go Templates that supports the {{extends}} and {{block}} tags for template inheritance, just like Django.
I've been coming back to this answer for days, finally bit the bullet and wrote a small abstraction layer / pre processor for this. It basically:
Adds the 'extends' keyword to templates.
Allows overriding 'define' calls (thus default values for greggory are possible)
Allows non defined 'template' calls, they just give an empty string
Sets the default value of . in 'template' calls to . of the parent
https://github.com/daemonl/go_sweetpl