goweb/template/html/service.go
William Petit 985ce3eba3 template/html: refactor template loading
Allow customization of the template loading process by providing a
Loader implementation.

Allow auto-reloading of templates in development mode.
2020-07-07 09:01:04 +02:00

136 lines
2.9 KiB
Go

package html
import (
"errors"
"html/template"
"io"
"net/http"
"sync"
"github.com/oxtoacart/bpool"
)
var (
ErrLayoutNotFound = errors.New("layout not found")
)
// TemplateService is a template/html based templating service
type TemplateService struct {
mutex sync.RWMutex
blocks map[string]string
layouts map[string]*template.Template
loader Loader
pool *bpool.BufferPool
helpers template.FuncMap
devMode bool
}
func (t *TemplateService) AddBlock(name string, blockContent string) {
t.mutex.Lock()
defer t.mutex.Unlock()
t.blocks[name] = blockContent
}
func (t *TemplateService) LoadLayout(name string, layoutContent string) error {
tmpl := template.New(name)
tmpl.Funcs(t.helpers)
t.mutex.RLock()
for _, b := range t.blocks {
if _, err := tmpl.Parse(b); err != nil {
t.mutex.RUnlock()
return err
}
}
t.mutex.RUnlock()
tmpl, err := tmpl.Parse(layoutContent)
if err != nil {
return err
}
t.mutex.Lock()
t.layouts[name] = tmpl
t.mutex.Unlock()
return nil
}
// Load parses templates via the configured template loader.
func (t *TemplateService) Load() error {
return t.loader.Load(t)
}
// RenderPage renders a template to the given http.ResponseWriter
func (t *TemplateService) RenderPage(w http.ResponseWriter, templateName string, data interface{}) error {
if t.devMode {
if err := t.loader.Load(t); err != nil {
return err
}
}
// Ensure the template exists in the map.
tmpl, ok := t.layouts[templateName]
if !ok {
return ErrLayoutNotFound
}
// Create a buffer to temporarily write to and check if any errors were encountered.
buf := t.pool.Get()
defer t.pool.Put(buf)
if err := tmpl.ExecuteTemplate(buf, templateName, data); err != nil {
return err
}
// Set the header and write the buffer to the http.ResponseWriter
w.Header().Set("Content-Type", "text/html; charset=utf-8")
_, err := buf.WriteTo(w)
return err
}
// Render renders a layout to the given io.Writer.
func (t *TemplateService) Render(w io.Writer, layoutName string, data interface{}) error {
if t.devMode {
if err := t.loader.Load(t); err != nil {
return err
}
}
// Ensure the template exists in the map.
tmpl, ok := t.layouts[layoutName]
if !ok {
return ErrLayoutNotFound
}
// Create a buffer to temporarily write to and check if any errors were encountered.
buf := t.pool.Get()
defer t.pool.Put(buf)
if err := tmpl.ExecuteTemplate(buf, layoutName, data); err != nil {
return err
}
_, err := buf.WriteTo(w)
return err
}
// NewTemplateService returns a new Service
func NewTemplateService(loader Loader, funcs ...OptionFunc) *TemplateService {
options := defaultOptions()
for _, f := range funcs {
f(options)
}
return &TemplateService{
blocks: make(map[string]string),
layouts: make(map[string]*template.Template),
pool: bpool.NewBufferPool(options.PoolSize),
helpers: options.Helpers,
loader: loader,
devMode: options.DevMode,
}
}