diff --git a/main.go b/main.go index d2a8126..4b0aa79 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/phc-dm/phc-server/config" + "github.com/phc-dm/phc-server/templates" "github.com/phc-dm/phc-server/util" ) @@ -25,7 +26,11 @@ func main() { r.Handle("/public/*", http.StripPrefix("/public", http.FileServer(http.Dir("./public")))) // Templates & Renderer - renderer := NewTemplateRenderer("base.html") + renderer := templates.NewRenderer( + "./views/", + templates.File("./views/base.html"), + templates.Pattern("./views/partials/*.html"), + ) newsArticlesRegistry := NewArticleRenderer("./news") // Routes diff --git a/templates.go b/templates.go deleted file mode 100644 index 37d6cb5..0000000 --- a/templates.go +++ /dev/null @@ -1,52 +0,0 @@ -package main - -import ( - "io" - "strings" - "text/template" - - "github.com/phc-dm/phc-server/config" - "github.com/phc-dm/phc-server/util" -) - -// TemplateRenderer holds cached templates for rendering -type TemplateRenderer struct { - baseFile string - baseTemplate *template.Template - templateMap map[string]*template.Template -} - -// NewTemplateRenderer constructs a template renderer with a base file -func NewTemplateRenderer(baseFile string) *TemplateRenderer { - return &TemplateRenderer{ - baseFile: baseFile, - baseTemplate: template.Must(template.ParseFiles("./views/" + baseFile)), - templateMap: make(map[string]*template.Template), - } -} - -// Render the template -func (t *TemplateRenderer) Render(w io.Writer, name string, data util.H) error { - tmpl := t.templateMap[name] - - if config.Mode == "development" || tmpl == nil { - if config.Mode == "development" { - tmpl = template.Must(template.ParseFiles("./views/" + t.baseFile)) - } else { - tmpl = template.Must(t.baseTemplate.Clone()) - } - - tmpl.ParseFiles("./views/" + name) - t.templateMap[name] = tmpl - } - - newData := util.H{} - newData.Apply(data) - newData["Page"] = util.H{ - // Used to inject a page specific class on - "Name": strings.TrimSuffix(name, ".html"), - } - newData["Config"] = config.Object() - - return tmpl.ExecuteTemplate(w, "base", newData) -} diff --git a/templates/templates.go b/templates/templates.go new file mode 100644 index 0000000..79d8f0c --- /dev/null +++ b/templates/templates.go @@ -0,0 +1,117 @@ +package templates + +import ( + "html/template" + "io" + "path" + "strings" + + "github.com/phc-dm/phc-server/config" + "github.com/phc-dm/phc-server/util" +) + +type LoadTemplate interface { + Load(t *template.Template) (*template.Template, error) +} + +type File string + +func (file File) Load(t *template.Template) (*template.Template, error) { + return t.ParseFiles(string(file)) +} + +type Pattern string + +func (pattern Pattern) Load(t *template.Template) (*template.Template, error) { + return t.ParseGlob(string(pattern)) +} + +type CachedTemplate struct { + Loaders []LoadTemplate + template *template.Template +} + +func NewCacheTemplate(loaders ...LoadTemplate) *CachedTemplate { + cachedTemplate := &CachedTemplate{ + Loaders: loaders, + template: nil, + } + template.Must(cachedTemplate.Load(template.New(""))) + return cachedTemplate +} + +func (ct *CachedTemplate) Load(t *template.Template) (*template.Template, error) { + if ct.template != nil { + return ct.template, nil + } + + ct.template = t + + for _, loader := range ct.Loaders { + var err error + ct.template, err = loader.Load(ct.template) + if err != nil { + return nil, err + } + } + + return ct.template, nil +} + +func (ct *CachedTemplate) Reload() { + ct.template = nil + template.Must(ct.Load(nil)) +} + +func (ct *CachedTemplate) Template() *template.Template { + return ct.template +} + +// Renderer holds cached templates for rendering +type Renderer struct { + rootPath string + baseLoaders []LoadTemplate + routes map[string]*CachedTemplate +} + +// NewRenderer constructs a template renderer with a base file +func NewRenderer(rootPath string, loaders ...LoadTemplate) *Renderer { + return &Renderer{ + rootPath, + loaders, + map[string]*CachedTemplate{}, + } +} + +func (r *Renderer) Load(name string) *CachedTemplate { + cachedTemplate, present := r.routes[name] + + if !present { + loaders := []LoadTemplate{} + loaders = append(loaders, r.baseLoaders...) + loaders = append(loaders, File(path.Join(r.rootPath, name))) + cachedTemplate = NewCacheTemplate(loaders...) + r.routes[name] = cachedTemplate + } + + return cachedTemplate +} + +// Render the template, also injects "Page" and "Config" values in the template +func (r *Renderer) Render(w io.Writer, name string, data util.H) error { + cachedTemplate := r.Load(name) + + if config.Mode == "development" { + cachedTemplate.Reload() + } + + newData := util.H{} + newData.Apply(data) + newData["Page"] = util.H{ + // Used to inject a page specific class on + "Name": strings.TrimSuffix(path.Base(name), ".html"), + } + newData["Config"] = config.Object() + + return cachedTemplate.Template().ExecuteTemplate(w, "base", newData) +} diff --git a/views/base.html b/views/base.html index 3ca7355..055afc2 100644 --- a/views/base.html +++ b/views/base.html @@ -62,89 +62,7 @@ - + {{ template "navbar" . }}
{{ template "body" . }} diff --git a/views/partials/navbar.html b/views/partials/navbar.html new file mode 100644 index 0000000..4b09352 --- /dev/null +++ b/views/partials/navbar.html @@ -0,0 +1,85 @@ +{{ define "navbar" }} + +{{ end }} \ No newline at end of file