Documentation ¶
Overview ¶
Package treetop implements tools for constructing HTTP handlers for nested templates
To read about nested template support in Go see https://tip.golang.org/pkg/text/template/#hdr-Nested_template_definitions
Multi-page web apps require a lot of endpoints. Template inheritance is commonly used to reduce HTML boilerplate and improve reuse. Treetop views incorporate request handlers into the hierarchy to gain the same advantage.
A 'View' is a template string (usually file path) paired with a handler function. Go templates can contain named nested blocks. Defining a 'SubView' associates a handler and a template with a block embedded within a parent template. HTTP handlers can then be constructed for various page configurations.
Example of a basic template hierarchy
baseHandler(...) | base.html ========================| | … | | {{ template "content" .Content }} | | … ^ | |_________________|_________________| | ______/ \______ contentAHandler(...) contentBHandler(...) | contentA.html ========== | | contentB.html ========== | | | | | | {{ block "content" . }}… | | {{ block "content" . }}… | |__________________________| |__________________________|
Example of using the library to constructs handlers for HTTP routes.
base := treetop.NewView( "base.html", baseHandler, ) contentA := base.NewSubView( "content", "contentA.html", contentAHandler, ) contentB := base.NewSubView( "content", "contentB.html", contentBHandler, ) exec := treetop.FileExecutor{} mymux.Handle("/path/to/a", exec.ViewHandler(contentA)) mymux.Handle("/path/to/b", exec.ViewHandler(contentB))
The generated handlers bind togeather related views. Thus views can be mixed and matched to create many endpoints.
GET /path/to/a > HTTP/1.1 200 OK > <!-- base.html --><html> > ... > <!-- contentA.html --><div id="content"> Content A </div> > ... > </html> GET /path/to/b > HTTP/1.1 200 OK > <!-- base.html --><html> > ... > <!-- contentB.html --><div id="content"> Content B </div> > ... > </html>
Note, many levels of nesting are possible once block names remain unique.
HTML Template Protocol ¶
The constructed handlers are capable of rendering just sections of the page depending upon the request headers. See the Treetop JS library for more details. (https://github.com/rur/treetop-client)
Index ¶
- Constants
- Variables
- func CompileViews(view *View, includes ...*View) (page, part *View, postscript []*View)
- func IsTemplateRequest(req *http.Request) bool
- func NewTemplateHandler(view *View, includes []*View, load *TemplateLoader) (*TemplateHandler, ExecutorErrors)
- func Noop(_ Response, _ *http.Request) interface{}
- func Redirect(w http.ResponseWriter, req *http.Request, location string, status int)
- func SprintViewInfo(v *View) string
- func SprintViewTree(v *View) string
- type CaptureErrors
- type DeveloperExecutor
- type ExecutorError
- type ExecutorErrors
- type FileExecutor
- type FileSystemExecutor
- type KeyedStringExecutor
- type Response
- type ResponseWrapper
- func (rsp *ResponseWrapper) Cancel()
- func (rsp *ResponseWrapper) Context() context.Context
- func (rsp *ResponseWrapper) DesignatePageURL(url string)
- func (rsp *ResponseWrapper) Finished() bool
- func (rsp *ResponseWrapper) HandleSubView(name string, req *http.Request) interface{}
- func (rsp *ResponseWrapper) NewTemplateWriter(req *http.Request) (Writer, bool)
- func (rsp *ResponseWrapper) ReplacePageURL(url string)
- func (rsp *ResponseWrapper) ResponseID() uint32
- func (rsp *ResponseWrapper) Status(status int) int
- func (rsp *ResponseWrapper) WithSubViews(subViews map[string]*View) *ResponseWrapper
- func (rsp *ResponseWrapper) Write(b []byte) (int, error)
- func (rsp *ResponseWrapper) WriteHeader(statusCode int)
- type StringExecutor
- type Template
- type TemplateHandler
- type TemplateLoader
- type View
- type ViewExecutor
- type ViewHandler
- type ViewHandlerFunc
- type Writer
Constants ¶
const (
// TemplateContentType is used for content negotiation within template requests
TemplateContentType = "application/x.treetop-html-template+xml"
)
Variables ¶
var ( // ErrNotAcceptable is produced by ServeHTTP when a request // does not contain an accept header that can be handled by this endpoint ErrNotAcceptable = errors.New( "treetop template handler: server cannot produce a response matching the list of acceptable values") )
Errors used by the TemplateHandler.
var (
ErrResponseHijacked = errors.New(
"treetop response: cannot write, HTTP response has been hijacked by another handler")
)
var ( // ServeClientLibrary TODO: add docs ServeClientLibrary http.Handler )
Functions ¶
func CompileViews ¶ added in v0.3.0
CompileViews is used to create an endpoint configuration combining supplied view definitions based upon the template names they define.
This returns:
- a full-page view instance,
- a partial page view instance, and
- any disconnect fragment views that should be appended to partial requests.
func IsTemplateRequest ¶ added in v0.3.0
IsTemplateRequest is a predicate function which will check the headers of a given request and return true if a template response is supported by the client.
func NewTemplateHandler ¶ added in v0.4.0
func NewTemplateHandler(view *View, includes []*View, load *TemplateLoader) (*TemplateHandler, ExecutorErrors)
NewTemplateHandler compiles an endpoint view hierarchy and loads corresponding HTML templates
func Noop ¶
Noop treetop handler helper is useful when a treetop.HandlerFunc instance is needed but you don't want it to do anything. Function returns `nil`.
func Redirect ¶
Redirect is a helper that will instruct the Treetop client library to direct the web browser to a new URL. If the request is not from a Treetop client, the 3xx redirect method is used.
This is necessary because 3xx HTTP redirects are opaque to XHR, when a full browser redirect is needed a 'X-Treetop-Redirect' header is used.
Example:
treetop.Redirect(w, req, "/some/other/path", http.StatusSeeOther)
func SprintViewInfo ¶ added in v0.3.0
SprintViewInfo will create a string preview of view
func SprintViewTree ¶ added in v0.3.0
SprintViewTree create a string with a tree representation of a a view hierarchy.
For example, the view definition 'v'
v := NewView("base.html", Constant("base!")) a := v.NewDefaultSubView("A", "A.html", Constant("A!")) a.NewDefaultSubView("A1", "A1.html", Constant("A1!")) a.NewDefaultSubView("A2", "A2.html", Constant("A2!")) b := v.NewDefaultSubView("B", "B.html", Constant("B!")) b.NewDefaultSubView("B1", "B1.html", Constant("B1!")) b.NewDefaultSubView("B2", "B2.html", Constant("B2!")) fmt.Println(treetop.SprintViewTree(v))
will be outputted as the string
- View("base.html", github.com/rur/treetop.Constant.func1) |- A: SubView("A", "A.html", github.com/rur/treetop.Constant.func1) | |- A1: SubView("A1", "A1.html", github.com/rur/treetop.Constant.func1) | '- A2: SubView("A2", "A2.html", github.com/rur/treetop.Constant.func1) | '- B: SubView("B", "B.html", github.com/rur/treetop.Constant.func1) |- B1: SubView("B1", "B1.html", github.com/rur/treetop.Constant.func1) '- B2: SubView("B2", "B2.html", github.com/rur/treetop.Constant.func1)
Types ¶
type CaptureErrors ¶ added in v0.4.0
type CaptureErrors struct {
Errors ExecutorErrors
}
CaptureErrors is a base type for implementing concreate view executors
func (*CaptureErrors) AddErrors ¶ added in v0.4.0
func (ce *CaptureErrors) AddErrors(errs ExecutorErrors)
AddErrors will store a list of errors to flushe later
func (*CaptureErrors) FlushErrors ¶ added in v0.4.0
func (ce *CaptureErrors) FlushErrors() ExecutorErrors
FlushErrors will return the list of template creation errors that occurred while ViewHandlers were begin created, since the last time it was called.
type DeveloperExecutor ¶ added in v0.3.0
type DeveloperExecutor struct {
ViewExecutor
}
DeveloperExecutor wraps another executor, it will re-generate the view handler for every request. This can be used to live-reload templates during development.
Example:
exec := DeveloperExecutor{FileExecutor{}} mux.Handle("/hello", exec.NewViewHandler(v))
Note: this is for development use only, it is not suitable for production systems
func (*DeveloperExecutor) NewViewHandler ¶ added in v0.3.0
func (de *DeveloperExecutor) NewViewHandler(view *View, includes ...*View) ViewHandler
NewViewHandler will create a special handler that will reload the templates for ever request. Any template errors that occur will be rendered to the client.
Note: this is for development use only, it is not suitable for production systems
type ExecutorError ¶ added in v0.3.0
ExecutorError is created within the executor when a template cannot be created for a view. Call exec.FlushErrors() to obtain a list of the template errors that occurred.
func (*ExecutorError) Error ¶ added in v0.3.0
func (te *ExecutorError) Error() string
Error implement the error interface
type ExecutorErrors ¶ added in v0.3.0
type ExecutorErrors []*ExecutorError
ExecutorErrors is a list zero or more template errors created when parsing templates
func (ExecutorErrors) Error ¶ added in v0.3.0
func (ee ExecutorErrors) Error() string
Errors implements error interface
type FileExecutor ¶ added in v0.3.0
type FileExecutor struct { CaptureErrors Funcs template.FuncMap KeyedString map[string]string }
FileExecutor loads view templates as a path from a template file.
func (*FileExecutor) NewViewHandler ¶ added in v0.3.0
func (fe *FileExecutor) NewViewHandler(view *View, includes ...*View) ViewHandler
NewViewHandler creates a ViewHandler from a View endpoint definition treating view template strings as a file path using os.Open.
type FileSystemExecutor ¶ added in v0.3.0
type FileSystemExecutor struct { CaptureErrors FS http.FileSystem Funcs template.FuncMap KeyedString map[string]string }
FileSystemExecutor loads view templates as a path from a Go HTML template file. The underlying file system is abstracted through the http.FileSystem interface to allow for in-memory use.
The optional KeyedString map will be checked before the loader attempts to use the FS instance when obtain a template string
func (*FileSystemExecutor) NewViewHandler ¶ added in v0.3.0
func (fse *FileSystemExecutor) NewViewHandler(view *View, includes ...*View) ViewHandler
NewViewHandler creates a ViewHandler from a View endpoint definition treating view template strings as keys into the string template dictionary.
type KeyedStringExecutor ¶ added in v0.3.0
type KeyedStringExecutor struct { CaptureErrors Templates map[string]string Funcs template.FuncMap }
KeyedStringExecutor builds handlers templates from a map of available templates. The view templates are treated as keys into the map for the purpose of build handlers.
func NewKeyedStringExecutor ¶ added in v0.3.0
func NewKeyedStringExecutor(templates map[string]string) *KeyedStringExecutor
NewKeyedStringExecutor is a deprecated method for constructing an instance from a template map
func (*KeyedStringExecutor) NewViewHandler ¶ added in v0.3.0
func (ks *KeyedStringExecutor) NewViewHandler(view *View, includes ...*View) ViewHandler
NewViewHandler creates a ViewHandler from a View endpoint definition treating view template strings as keys into the string template dictionary.
type Response ¶
type Response interface { http.ResponseWriter // Status allows a handler to indicate (not determine) what the HTTP status // should be for the response. // // When different handlers indicate a different status, // the code with the greater numeric value is chosen. // // For example, given: Bad Request, Unauthorized and Internal Server Error. // Status values are differentiated as follows, 400 < 401 < 500, // 'Internal Server Error' is chosen for the response header. // // The resulting response status is returned. Getting the current status // without affecting the response can be done as follows // // status := rsp.Status(0) // Status(int) int // DesignatePageURL forces the response to be handled as a navigation event with a specified URL. // The browser will have a new history entry created for the supplied URL. DesignatePageURL(string) // ReplacePageURL forces the location bar in the web browser to be updated with the supplied // URL. This should be done by *replacing* the existing history entry. (not adding a new one) ReplacePageURL(string) // Finished will return true if a handler has taken direct responsibility for writing the // response. Finished() bool // HandleSubView loads data from a named child subview handler. If no handler is available for the name, // nil will be returned. // // NOTE: Since a sub handler may have returned nil, there is no way for the parent handler to determine // whether the name resolved to a concrete view. HandleSubView(string, *http.Request) interface{} // ResponseID returns the ID treetop has associated with this request. // Since multiple handlers may be involved, the ID is useful for logging and caching. // // Response IDs avoid potential pitfalls around Request instance comparison that can affect middleware. // // NOTE: This is *not* a UUID, response IDs are incremented from zero when the server is started ResponseID() uint32 // Context returns the context associated with the treetop process. // This is a child of the http Request context. Context() context.Context }
Response extends the http.ResponseWriter interface to give ViewHandelersFunc's limited ability to control the hierarchical request handling.
Note that writing directly to the underlying ResponseWriter in the handler will cancel the treetop handling process. Taking control of response writing in this way is a very common and useful practice especially for error messages or redirects.
type ResponseWrapper ¶ added in v0.3.0
type ResponseWrapper struct { http.ResponseWriter // contains filtered or unexported fields }
ResponseWrapper is the concrete implementation of the response writer wrapper supplied to view handler functions
func BeginResponse ¶ added in v0.3.0
func BeginResponse(cxt context.Context, w http.ResponseWriter) *ResponseWrapper
BeginResponse initializes the context for a treetop request response
func (*ResponseWrapper) Cancel ¶ added in v0.3.0
func (rsp *ResponseWrapper) Cancel()
Cancel will teardown the treetop handing process
func (*ResponseWrapper) Context ¶ added in v0.3.0
func (rsp *ResponseWrapper) Context() context.Context
Context is getter for the treetop response context which will indicate when the request has been completed as was cancelled. This is derived from the request context so it can safely be used for cleanup.
func (*ResponseWrapper) DesignatePageURL ¶ added in v0.3.0
func (rsp *ResponseWrapper) DesignatePageURL(url string)
DesignatePageURL will result in a header being added to the response that will create a new history entry for the supplied URL
func (*ResponseWrapper) Finished ¶ added in v0.3.0
func (rsp *ResponseWrapper) Finished() bool
Finished will return true if the response headers have been written to the client, effectively cancelling the treetop view handler lifecycle
func (*ResponseWrapper) HandleSubView ¶ added in v0.3.0
func (rsp *ResponseWrapper) HandleSubView(name string, req *http.Request) interface{}
HandleSubView will execute the handler for a specified sub view of the current view if there is no match for the name, nil will be returned.
func (*ResponseWrapper) NewTemplateWriter ¶ added in v0.3.0
func (rsp *ResponseWrapper) NewTemplateWriter(req *http.Request) (Writer, bool)
NewTemplateWriter will return a template Writer configured to add Treetop headers based up on the state of the response. If the request is not a template request the writer will be nil and the ok flag will be false
func (*ResponseWrapper) ReplacePageURL ¶ added in v0.3.0
func (rsp *ResponseWrapper) ReplacePageURL(url string)
ReplacePageURL will instruct the client to replace the current history entry with the supplied URL
func (*ResponseWrapper) ResponseID ¶ added in v0.3.0
func (rsp *ResponseWrapper) ResponseID() uint32
ResponseID is a getter which returns a locally unique ID for a Treetop HTTP response. This is intended to be used to keep track of the request as is passes between handlers. The ID will increment by one starting at zero, every time the server is restarted.
func (*ResponseWrapper) Status ¶ added in v0.3.0
func (rsp *ResponseWrapper) Status(status int) int
Status will set a status for the treetop response headers if a response status has been set previously, the larger code value will be adopted
func (*ResponseWrapper) WithSubViews ¶ added in v0.3.0
func (rsp *ResponseWrapper) WithSubViews(subViews map[string]*View) *ResponseWrapper
WithSubViews creates a derived response wrapper for a different view, inheriting request
func (*ResponseWrapper) Write ¶ added in v0.3.0
func (rsp *ResponseWrapper) Write(b []byte) (int, error)
Write delegates to the underlying ResponseWriter while aborting the treetop executor handler.
func (*ResponseWrapper) WriteHeader ¶ added in v0.3.0
func (rsp *ResponseWrapper) WriteHeader(statusCode int)
WriteHeader delegates to the underlying ResponseWriter while setting finished flag to true
type StringExecutor ¶ added in v0.3.0
type StringExecutor struct { CaptureErrors Funcs template.FuncMap }
StringExecutor loads view templates as an inline template string.
Example:
exec := StringExecutor{} v := treetop.NewView("<p>Hello {{ . }}!</p>", Constant("world")) mux.Handle("/hello", exec.NewViewHandler(v))
func (*StringExecutor) NewViewHandler ¶ added in v0.3.0
func (se *StringExecutor) NewViewHandler(view *View, includes ...*View) ViewHandler
NewViewHandler creates a ViewHandler from a View endpoint definition treating view template strings as keys into the string template dictionary.
type Template ¶ added in v0.3.1
Template is an interface so that the concrete template implementation can be changed
type TemplateHandler ¶ added in v0.3.0
type TemplateHandler struct { Page *View PageTemplate Template Partial *View PartialTemplate Template Includes []*View IncludeTemplates []Template // optional developer defined error handler ServeTemplateError func(error, Response, *http.Request) }
TemplateHandler implements the treetop.ViewHandler interface for endpoints that support the treetop protocol
func (*TemplateHandler) FragmentOnly ¶ added in v0.3.0
func (h *TemplateHandler) FragmentOnly() ViewHandler
FragmentOnly creates a new Handler that only responds to fragment requests
func (*TemplateHandler) PageOnly ¶ added in v0.3.0
func (h *TemplateHandler) PageOnly() ViewHandler
PageOnly create a new handler that will only respond to non-fragment (full page) requests
func (*TemplateHandler) ServeHTTP ¶ added in v0.3.0
func (h *TemplateHandler) ServeHTTP(w http.ResponseWriter, req *http.Request)
ServeHTTP is responsible for directing the handing of an incoming request. Implements the procedure through which views functions and templates are to be executed.
type TemplateLoader ¶ added in v0.4.0
func NewTemplateLoader ¶ added in v0.4.0
func (TemplateLoader) ViewTemplate ¶ added in v0.4.0
func (tl TemplateLoader) ViewTemplate(view *View) (*template.Template, error)
type View ¶
type View struct { Template string HandlerFunc ViewHandlerFunc SubViews map[string]*View Defines string Parent *View }
View is used to define hierarchies of nested template-handler pairs so that HTTP endpoints can be constructed for different page configurations.
A 'View' is a template string (usually file path) paired with a handler function. Go templates can contain named nested blocks. Defining a 'SubView' associates a handler and a template with a block embedded within a parent template. HTTP handlers can then be constructed for various page configurations.
Example of a basic template hierarchy
baseHandler(...) | base.html ========================| | … | | {{ template "content" .Content }} | | … ^ | |_________________|_________________| | ______/ \______ contentAHandler(...) contentBHandler(...) | contentA.html ========== | | contentB.html ========== | | | | | | {{ block "content" . }}… | | {{ block "content" . }}… | |__________________________| |__________________________|
Pseudo request and response:
GET /path/to/a > HTTP/1.1 200 OK > ... base.html { Content: contentA.html } GET /path/to/b > HTTP/1.1 200 OK > ... base.html { Content: contentB.html }
Example of using the library to bind constructed handlers to a HTTP router.
base := treetop.NewView( "base.html", baseHandler, ) contentA := base.NewSubView( "content", "contentA.html", contentAHandler, ) contentB := base.NewSubView( "content", "contentB.html", contentBHandler, ) exec := treetop.FileExecutor{} mymux.Handle("/path/to/a", exec.ViewHandler(contentA)) mymux.Handle("/path/to/b", exec.ViewHandler(contentB))
This is useful for creating Treetop enabled endpoints because the constructed handler is capable of loading either a full page or just the "content" part of the page depending upon the request.
func NewSubView ¶ added in v0.3.0
func NewSubView(defines, tmpl string, handler ViewHandlerFunc) *View
NewSubView creates an instance of a view given a template + handler pair this view is a detached subview, in that is does not reference a parent
func NewView ¶
func NewView(tmpl string, handler ViewHandlerFunc) *View
NewView creates an instance of a view given a template + handler pair
func (*View) Copy ¶ added in v0.3.0
Copy creates a duplicate so that the original is not affected by changes. This will propegate as a 'deep copy' to all default subviews
func (*View) HasSubView ¶ added in v0.4.0
HasSubView asserts that a subview name exists without the need to define a template
func (*View) NewDefaultSubView ¶ added in v0.3.0
func (v *View) NewDefaultSubView(defines string, tmpl string, handler ViewHandlerFunc) *View
NewDefaultSubView create a new view extending a named block within the current view and updates the parent to use this view by default
func (*View) NewSubView ¶ added in v0.3.0
func (v *View) NewSubView(defines string, tmpl string, handler ViewHandlerFunc) *View
NewSubView create a new view extending a named block within the current view
type ViewExecutor ¶ added in v0.3.0
type ViewExecutor interface { NewViewHandler(view *View, includes ...*View) ViewHandler FlushErrors() ExecutorErrors }
ViewExecutor is an interface for objects that implement transforming a View definition into a ViewHandler that supports full page and template requests.
type ViewHandler ¶
type ViewHandler interface { http.Handler FragmentOnly() ViewHandler PageOnly() ViewHandler }
ViewHandler is an extension of the http.Handler interface with methods added for extra treetop endpoint configuration
type ViewHandlerFunc ¶ added in v0.2.0
ViewHandlerFunc is the interface for treetop handler functions that support hierarchical partial data loading.
func Constant ¶
func Constant(data interface{}) ViewHandlerFunc
Constant treetop handler helper is used to generate a treetop.HandlerFunc that always returns the same value.
func Delegate ¶
func Delegate(blockname string) ViewHandlerFunc
Delegate handler helper will delegate partial handling to a named block of that partial. The designated block data will be adopted as the partial template data and no other block handler will be executed.
func RequestHandler ¶
func RequestHandler(f func(*http.Request) interface{}) ViewHandlerFunc
RequestHandler handler helper is used where only the http.Request instance is needed to resolve the template data so the treetop.Response isn't part of the actual handler function.
type Writer ¶
type Writer interface { http.ResponseWriter Status(int) DesignatePageURL(string) ReplacePageURL(string) }
Writer is an interface for writing HTTP responses that conform to the Treetop protocol
func NewFragmentWriter ¶ added in v0.2.0
NewFragmentWriter will check if the client accepts one of the Treetop content types, if so it will return a wrapped response writer for a Treetop html fragment.
Example:
func MyHandler(w http.ResponseWriter, req *http.Request) { if ttW, ok := treetop.NewFragmentWriter(w, req); ok { // this is a treetop request, write a HTML fragment fmt.Fprintf(tw, `<h3 id="greeting">Hello, %s</h3>`, "Treetop") return } /* otherwise handle request in a different way (unspecified) */ }
func NewPartialWriter ¶ added in v0.2.0
NewPartialWriter will check if the client accepts the template content type. If so it will return a wrapped response writer that will add the appropriate headers.
The partial writer will include the 'X-Page-URL' response header with the URI of the request. By the protocol, this means that it must be possible for a subsequent request to load a full HTML document from that URL by varying the accept header.
Example:
func MyHandler(w http.ResponseWriter, req *http.Request) { if ttW, ok := treetop.NewPartialWriter(w, req); ok { // this is a treetop request, write a HTML fragment fmt.Fprintf(tw, `<h3 id="greeting">Hello, %s</h3>`, "Treetop") return } /* otherwise render a full HTML page as normal */ }