custom: Improve templating of assets

This commit is contained in:
Tom Wiesing 2023-01-18 14:10:58 +01:00
parent 7d0fb60d67
commit b6bf0a8900
No known key found for this signature in database
19 changed files with 516 additions and 432 deletions

View file

@ -48,19 +48,18 @@ type BaseContextGaps struct {
Actions []component.MenuItem
}
func (bcg BaseContextGaps) Clone() BaseContextGaps {
func (bcg BaseContextGaps) clone() BaseContextGaps {
return BaseContextGaps{
Crumbs: slices.Clone(bcg.Crumbs),
Actions: slices.Clone(bcg.Actions),
}
}
// Use updates this context to use the values from the given base.
//
// The given request *must not* be nil.
//
// For convenience the passed context is also returned.
func (tc *BaseContext) use(custom *Custom, r *http.Request, gaps BaseContextGaps) *BaseContext {
// update updates an embedded BaseContext field in context.
func (custom *Custom) update(context any, r *http.Request, bcg BaseContextGaps) *BaseContext {
tc := reflect.ValueOf(context).
Elem().FieldByName(baseContextName).Addr().
Interface().(*BaseContext)
// tc.custom = custom
tc.inited = true
tc.requestWasNil = r == nil
@ -77,7 +76,7 @@ func (tc *BaseContext) use(custom *Custom, r *http.Request, gaps BaseContextGaps
tc.Menu = custom.BuildMenu(r)
// build the breadcrumbs
tc.BaseContextGaps = gaps.Clone()
tc.BaseContextGaps = bcg.clone()
last := len(tc.Crumbs) - 1
for i := range tc.Crumbs {
tc.Crumbs[i].Active = i == last
@ -97,25 +96,6 @@ func (bc BaseContext) DoInitCheck() template.HTML {
return ""
}
// NewForm is like New, but returns a new BaseFormContext
func (custom *Custom) NewForm(context httpx.FormContext, r *http.Request, bcg BaseContextGaps) (ctx BaseFormContext) {
ctx.FormContext = context
ctx.use(custom, r, bcg)
return
}
// Update updates an embedded BaseContext field in context.
//
// Assumes that context is a pointer to a struct type.
// If this is not the case, might call panic().
func (custom *Custom) Update(context any, r *http.Request, bcg BaseContextGaps) *BaseContext {
ctx := reflect.ValueOf(context).
Elem().FieldByName(baseContextName).Addr().
Interface().(*BaseContext)
ctx.use(custom, r, bcg)
return ctx
}
// BaseFormContext combines BaseContext and FormContext
type BaseFormContext struct {
BaseContext

View file

@ -0,0 +1,139 @@
package custom
import (
"html/template"
"net/http"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
)
// Parsed represents a parsed template that receives an underlying context of type C
type Parsed[C any] struct {
template *template.Template
}
// Parse creates a new Parsed from a template source.
// Parse calls panic() when parsing fails.
func Parse[C any](name string, source []byte, Assets static.Assets) Parsed[C] {
return Parsed[C]{
template: Assets.MustParseShared(name, string(source)),
}
}
// Prepare prepares this template for use inside a concrete handler.
// gaps must either be of length 0 or length 1 and may pre-fill gaps to be used when executing the template later.
func (p *Parsed[C]) Prepare(custom *Custom, gaps ...BaseContextGaps) *Template[C] {
wrap := Template[C]{
custom: custom,
template: custom.Template(p.template),
}
if len(gaps) > 1 {
panic("WrapTemplate: must provide either 1 or no gaps")
}
if len(gaps) == 1 {
wrap.gaps = gaps[0]
}
return &wrap
}
// Tempalte represents an executable template.
type Template[C any] struct {
custom *Custom
template *template.Template
gaps BaseContextGaps
}
// Template returns a template that, if executed together with the context by the Context method, produces the desired result.
func (tw *Template[C]) Template() *template.Template {
return tw.template
}
// Context generates a context for a given request that can be used to execute the provided template.
func (tw *Template[C]) Context(r *http.Request, c C, gaps ...BaseContextGaps) any {
// make the gaps something
if len(gaps) > 1 {
panic("Context: must provide either 1 or no gaps")
}
// update the context with gaps
{
g := tw.gaps
if len(gaps) == 1 {
g = gaps[0]
}
tw.custom.update(&c, r, g)
}
return c
}
// ParseForm is like Parse[BaseFormContext]
var ParseForm = Parse[BaseFormContext]
// FormTemplateContext returns a new handler for a form with the given base context
func FormTemplateContext(tw *Template[BaseFormContext]) func(ctx httpx.FormContext, r *http.Request) any {
return func(ctx httpx.FormContext, r *http.Request) any {
return tw.Context(r, BaseFormContext{FormContext: ctx})
}
}
// MappedHandler returns a new handler that maps the incoming context via f
func MappedHandler[In, Out any](tw *Template[Out], f func(ctx In, r *http.Request) (Out, BaseContextGaps)) func(ctx In, r *http.Request) any {
// TODO: Should this one be removed?
return func(ctx In, r *http.Request) any {
c, g := f(ctx, r)
return tw.Context(r, c, g)
}
}
// Hander returns a function that returns a context for the given template
func (tw *Template[C]) Handler(f func(r *http.Request) (C, error)) func(r *http.Request) (any, error) {
// TODO: Should this one be removed?
return tw.HandlerWithGaps(func(r *http.Request, gaps *BaseContextGaps) (C, error) {
return f(r)
})
}
// HTMLHandler returns a new HTMLHandler for this request
func (tw *Template[C]) HTMLHandler(f func(r *http.Request) (C, error)) httpx.HTMLHandler[any] {
return httpx.HTMLHandler[any]{
Handler: tw.Handler(f),
Template: tw.Template(),
}
}
// HandlerWithGaps works like handler, but additionally receives a gaps object to update.
func (tw *Template[C]) HandlerWithGaps(f func(r *http.Request, gaps *BaseContextGaps) (C, error)) func(r *http.Request) (any, error) {
// TODO: Drop this variant?
var zero C
return func(r *http.Request) (any, error) {
g := tw.gaps.clone()
c, err := f(r, &g)
if err != nil {
return zero, err
}
// update the context
return tw.Context(r, c, g), nil
}
}
func (tw *Template[C]) HTMLHandlerWithGaps(f func(r *http.Request, gaps *BaseContextGaps) (C, error)) httpx.HTMLHandler[any] {
return httpx.HTMLHandler[any]{
Handler: tw.HandlerWithGaps(f),
Template: tw.Template(),
}
}
// Execute executes this template with the given context
func (tw *Template[C]) Execute(w http.ResponseWriter, r *http.Request, c C, gaps ...BaseContextGaps) error {
return tw.ExecuteWithError(w, r, c, nil, gaps...)
}
// ExecuteWithError executes this template, or the default error handler if err != nil
func (tw *Template[C]) ExecuteWithError(w http.ResponseWriter, r *http.Request, c C, err error, gaps ...BaseContextGaps) error {
// TODO: Drop this variant?
// TODO: This should be removed!
return httpx.WriteHTML(tw.Context(r, c, gaps...), err, tw.template, "", w, r)
}