Ari
Ari

Reputation: 6169

How to load templates in subdirectories

I currently have all my html files in a flat directory templates/ and I load everything in with

tmpl := template.Must(template.ParseGlob("templates/*.html"))

But I'd like to now bring in some structure and put templates into folders, components, base, etc. But when I do my site stops working. I'm thinking it could be the above, or could it also be that I need to reference the path in the template?

example

{{ template "navbar" }}

would become

{{ template "components/navbar" }}

Slightly confused...

I'm also using the native go library not a framework, for now.

Upvotes: 2

Views: 1869

Answers (1)

mkopriva
mkopriva

Reputation: 38223

Go's glob does not support matching files in sub-directories, i.e. ** is not supported.

You can either use a third party lib (there are a number of implementations on github), or you could invoke filepath.Glob for each "level" of sub-directories and aggregate the returned file names into a single slice and then pass the slice to template.ParseFiles:

dirs := []string{
    "templates/*.html",
    "templates/*/*.html",
    "templates/*/*/*.html",
    // ...
}

files := []string{}
for _, dir := range dirs {
    ff, err := filepath.Glob(dir)
    if err != nil {
        panic(err)
    }
    files = append(files, ff...)
}

t, err := template.ParseFiles(files...)
if err != nil {
    panic(err)
}

// ...

You also need to keep in mind how ParseFiles works: (emphasis mine)

ParseFiles creates a new Template and parses the template definitions from the named files. The returned template's name will have the (base) name and (parsed) contents of the first file. There must be at least one file. If an error occurs, parsing stops and the returned *Template is nil.

When parsing multiple files with the same name in different directories, the last one mentioned will be the one that results. For instance, ParseFiles("a/foo", "b/foo") stores "b/foo" as the template named "foo", while "a/foo" is unavailable.

This means that, if you want to load all the files, you have to ensure at least one of two things: (1) that each file's base name is unique across all template files, not just in the directory in which the file's located, or (2) provide a unique template name for each file by using the {{ define "<template_name>" }} action at the top of the file's contents (and do not forget the {{ end }} to close the define action).


As an example for the 2nd approach, let's say, in your templates you have two files that have the same base name, e.g. templates/foo/header.html and templates/bar/header.html and their contents are as follows:

templates/foo/header.html

<head><title>Foo Site</title></head>

templates/bar/header.html

<head><title>Bar Site</title></head>

Now to give the these files a unique template name you can change the contents to this:

templates/foo/header.html

{{ define "foo/header" }}
<head><title>Foo Site</title></head>
{{ end }}

templates/bar/header.html

{{ define "bar/header" }}
<head><title>Bar Site</title></head>
{{ end }}

After you do this, you can either execute them directly with t.ExecuteTemplate(w, "foo/header", nil), or indirectly by having other templates reference them using the {{ template "bar/header" . }} action.

Upvotes: 6

Related Questions