103 lines
2.6 KiB
Go
103 lines
2.6 KiB
Go
package rndr
|
|
|
|
import (
|
|
"html/template"
|
|
"io/fs"
|
|
"net/http"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"unicode"
|
|
|
|
log "gitlab.com/gdulai/simpleloglvl"
|
|
)
|
|
|
|
// ComponentRenderer handles the associations between templates and models, and renders the template files with the appropriate backing model.
|
|
type ComponentRenderer struct {
|
|
templates *template.Template
|
|
}
|
|
|
|
// NewRenderer creates returns the pointer of newly created ComponentRenderer, or an error.
|
|
func NewRenderer(root string) (*ComponentRenderer, error) {
|
|
templates, err := loadTemplates(root)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &ComponentRenderer{templates: templates}, nil
|
|
}
|
|
|
|
// Render, renders a template html file with a backing model.
|
|
// The template file name is derived from the model type via reflection.
|
|
func (renderer *ComponentRenderer) Render(w http.ResponseWriter, r *http.Request, model any) {
|
|
requestId := r.Context().Value("requestId")
|
|
modelTyp := reflect.TypeOf(model)
|
|
for modelTyp.Kind() == reflect.Pointer {
|
|
modelTyp = modelTyp.Elem()
|
|
}
|
|
|
|
if modelTyp == nil {
|
|
log.LogError("Could not determine model type for %s (%s)", model, requestId)
|
|
return
|
|
}
|
|
templateName := toKebabCase(modelTyp.Name()) + ".html"
|
|
|
|
w.Header().Set("Content-Type", "text/html")
|
|
err := renderer.templates.ExecuteTemplate(w, templateName, model)
|
|
|
|
if err != nil {
|
|
log.LogError("Error while serving html teplate: %s (%s)", err, requestId)
|
|
http.Error(w, "Component render error", http.StatusInternalServerError)
|
|
}
|
|
|
|
}
|
|
|
|
// RenderLayout, renders layout template file, without any backing model
|
|
func (renderer *ComponentRenderer) RenderLayout(w http.ResponseWriter, r *http.Request, templateName string) {
|
|
requestId := r.Context().Value("requestId")
|
|
err := renderer.templates.ExecuteTemplate(w, templateName, nil)
|
|
|
|
if err != nil {
|
|
log.LogError("Error while serving html teplate: %s (%s)", err, requestId)
|
|
http.Error(w, "Component render error", http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
func loadTemplates(root string) (*template.Template, error) {
|
|
var files []string
|
|
|
|
err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !d.IsDir() && filepath.Ext(path) == ".html" {
|
|
files = append(files, path)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return template.ParseFiles(files...)
|
|
}
|
|
|
|
func toKebabCase(s string) string {
|
|
var b strings.Builder
|
|
|
|
for i, r := range s {
|
|
if i > 0 && unicode.IsUpper(r) {
|
|
prev := rune(s[i-1])
|
|
if unicode.IsLower(prev) ||
|
|
(i+1 < len(s) && unicode.IsLower(rune(s[i+1]))) {
|
|
b.WriteByte('-')
|
|
}
|
|
}
|
|
|
|
b.WriteRune(unicode.ToLower(r))
|
|
}
|
|
|
|
return b.String()
|
|
}
|