1
0
mirror of https://github.com/raseels-repos/golang-saas-starter-kit.git synced 2025-06-15 00:15:15 +02:00

Updated comments on template renderer

This commit is contained in:
Lee Brown
2019-05-18 18:34:39 -04:00
parent 308fee852c
commit 5879fc664f
3 changed files with 477 additions and 35 deletions

View File

@ -20,10 +20,6 @@ import (
var (
errInvalidTemplate = errors.New("Invalid template")
// Base template to support applying custom
// TODO try to remove this
//mainTmpl = `{{define "main" }} {{ template "base" . }} {{ end }}`
)
type Template struct {
@ -31,13 +27,15 @@ type Template struct {
mainTemplate *template.Template
}
// NewTemplate defines a base set of functions that will be applied to all templates
// being rendered.
func NewTemplate(templateFuncs template.FuncMap) *Template {
t := &Template{}
// these functions are used and rendered on run-time of web page so don't have to use javascript/jquery
// to for basic template formatting. transformation happens server-side instead of client-side to
// provide base-level consistency.
// Default functions are defined and available for all templates being rendered.
// These base function help with provided basic formatting so don't have to use javascript/jquery,
// transformation happens server-side instead of client-side to provide base-level consistency.
// Any defined function below will be overwritten if a matching function key is included.
t.Funcs = template.FuncMap{
// probably could provide examples of each of these
"Minus": func(a, b int) int {
@ -125,10 +123,15 @@ type TemplateRenderer struct {
enableHotReload bool
templates map[string]*template.Template
globalViewData map[string]interface{}
//mainTemplate *template.Template
mainTemplate *template.Template
errorHandler func(ctx context.Context, w http.ResponseWriter, req *http.Request, renderer web.Renderer, statusCode int, er error) error
}
// NewTemplateRenderer implements the interface web.Renderer allowing for execution of
// nested html templates. The templateDir should include three directories:
// 1. layouts: base layouts defined for the entire application
// 2. content: page specific templates that will be nested instead of a layout template
// 3. partials: templates used by multiple layout or content templates
func NewTemplateRenderer(templateDir string, enableHotReload bool, globalViewData map[string]interface{}, tmpl *Template, errorHandler func(ctx context.Context, w http.ResponseWriter, req *http.Request, renderer web.Renderer, statusCode int, er error) error) (*TemplateRenderer, error) {
r := &TemplateRenderer{
templateDir: templateDir,
@ -141,13 +144,12 @@ func NewTemplateRenderer(templateDir string, enableHotReload bool, globalViewDat
errorHandler: errorHandler,
}
//r.mainTemplate = template.New("main")
//r.mainTemplate, _ = r.mainTemplate.Parse(mainTmpl)
//r.mainTemplate.Funcs(tmpl.Funcs)
// Recursively loop through all folders/files in the template directory and group them by their
// template type. They are filename / filepath for lookup on render.
err := filepath.Walk(templateDir, func(path string, info os.FileInfo, err error) error {
dir := filepath.Base(filepath.Dir(path))
// Skip directories.
if info.IsDir() {
return nil
}
@ -168,63 +170,79 @@ func NewTemplateRenderer(templateDir string, enableHotReload bool, globalViewDat
return r, err
}
// Main template used to render execute all templates against.
r.mainTemplate = template.New("main")
r.mainTemplate, _ = r.mainTemplate.Parse( `{{define "main" }} {{ template "base" . }} {{ end }}`)
r.mainTemplate.Funcs(tmpl.Funcs)
// Ensure all layout files render successfully with no errors.
for _, f := range r.layoutFiles {
//t, err := r.mainTemplate.Clone()
//if err != nil {
// return r, err
//}
t := template.New("main")
t.Funcs(tmpl.Funcs)
t, err := r.mainTemplate.Clone()
if err != nil {
return r, err
}
template.Must(t.ParseFiles(f))
}
// Ensure all partial files render successfully with no errors.
for _, f := range r.partialFiles {
//t, err := r.mainTemplate.Clone()
//if err != nil {
// return r, err
//}
t := template.New("partial")
t.Funcs(tmpl.Funcs)
t, err := r.mainTemplate.Clone()
if err != nil {
return r, err
}
template.Must(t.ParseFiles(f))
}
// Ensure all content files render successfully with no errors.
for _, f := range r.contentFiles {
//t, err := r.mainTemplate.Clone()
//if err != nil {
// return r, err
//}
t := template.New("content")
t.Funcs(tmpl.Funcs)
t, err := r.mainTemplate.Clone()
if err != nil {
return r, err
}
template.Must(t.ParseFiles(f))
}
return r, nil
}
// Render renders a template document
// Render executes the nested templates and returns the result to the client.
// contentType: supports any content type to allow for rendering text, emails and other formats
// statusCode: the error method calls this function so allow the HTTP Status Code to be set
// data: map[string]interface{} to allow including additional request and globally defined values.
func (r *TemplateRenderer) Render(ctx context.Context, w http.ResponseWriter, req *http.Request, templateLayoutName, templateContentName, contentType string, statusCode int, data map[string]interface{}) error {
// If the template has not been rendered yet or hot reload is enabled,
// then parse the template files.
t, ok := r.templates[templateContentName]
if !ok || r.enableHotReload {
t, err := r.mainTemplate.Clone()
if err != nil {
return err
}
// Load the base template file path.
layoutFile, ok := r.layoutFiles[templateLayoutName]
if !ok {
return errors.Wrapf(errInvalidTemplate, "template layout file for %s does not exist", templateLayoutName)
}
// The base layout will be the first template.
files := []string{layoutFile}
// Append all of the partials that are defined. Not an easy way to determine if the
// layout or content template contain any references to a partial so load all of them.
// This assumes that all partial templates should be uniquely named and not conflict with
// and base layout or content definitions.
for _, f := range r.partialFiles {
files = append(files, f)
}
// Load the content template file path.
contentFile, ok := r.contentFiles[templateContentName]
if !ok {
return errors.Wrapf(errInvalidTemplate, "template content file for %s does not exist", templateContentName)
}
files = append(files, contentFile)
// Render all of template files
t = template.Must(t.ParseFiles(files...))
r.templates[templateContentName] = t
}
@ -261,7 +279,6 @@ func (r *TemplateRenderer) Render(ctx context.Context, w http.ResponseWriter, re
// to define context.Context as an argument
renderData["_Ctx"] = ctx
// Append request data map to render data last so any previous value can be overwritten.
if data != nil {
for k, v := range data {
@ -278,8 +295,9 @@ func (r *TemplateRenderer) Render(ctx context.Context, w http.ResponseWriter, re
return nil
}
// Error formats an error and returns the result to the client.
func (r *TemplateRenderer) Error(ctx context.Context, w http.ResponseWriter, req *http.Request, statusCode int, er error) error {
// If error hander was defined to support formated response for web, used it.
// If error handler was defined to support formatted response for web, used it.
if r.errorHandler != nil {
return r.errorHandler(ctx, w, req, r, statusCode, er)
}
@ -288,6 +306,8 @@ func (r *TemplateRenderer) Error(ctx context.Context, w http.ResponseWriter, req
return web.RespondError(ctx, w, er)
}
// Static serves files from the local file exist.
// If an error is encountered, it will handled by TemplateRenderer.Error
func (tr *TemplateRenderer) Static(rootDir, prefix string) web.Handler {
h := func(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
err := web.StaticHandler(ctx, w, r, params, rootDir, prefix)
@ -299,6 +319,8 @@ func (tr *TemplateRenderer) Static(rootDir, prefix string) web.Handler {
return h
}
// S3Url formats a path to include either the S3 URL or a CloudFront
// URL instead of serving the file from local file system.
func S3Url(baseS3Url, baseS3Origin, p string) string {
if strings.HasPrefix(p, "http") {
return p