William Petit
985ce3eba3
Allow customization of the template loading process by providing a Loader implementation. Allow auto-reloading of templates in development mode.
136 lines
2.9 KiB
Go
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,
|
|
}
|
|
}
|