Build HTML endpoints using a hierarchy of nested templates, as supported by the Go standard library [html/template].
- Lightweight by design
- No 3rd-party dependencies
- Protocol for fragment hot-swapping (see Online DEMO)
Each template is paired with an individual data loading function. Pages are constructed by combining templates in different configurations and binding endpoints to your application router.
BaseFunc(…)
|============ base.html =============|
| <html> |
| … |
| {{ template "content" .Content }} |
| … /\ |
| </html> || |
|________________ || ________________|
/ \
/ \
ContentAFunc(…) / \ ContentBFunc(…)
|==== content_a.html =====| |==== content_b.html =====|
| | | |
| <div id="content">A</… | | <div id="content">B</… |
|_________________________| |_________________________|
Basic example of a page hierarchy showing content A and B sharing the same 'base' template
Note. Nested hierarchy is supported, see Golang docs for details [doc]
The code example below is an extension of the hierarchy diagram. It binds
"/content_a"
and "/content_b"
routes with two
pages that share the same "base", "nav" and "sidebar" templates.
base := treetop.NewView("base.html", BaseHandler)
nav := base.NewSubView("nav", "nav.html", NavHandler)
_ = base.NewDefaultSubView("sidebar", "sidebar.html", SidebarHandler)
contentA := base.NewSubView("content", "content_a.html", ContentAHandler)
contentB := base.NewSubView("content", "content_b.html", ContentBHandler)
exec := treetop.FileExecutor{}
myMux.Handle("/content_a", exec.NewViewHandler(contentA, nav))
myMux.Handle("/content_b", exec.NewViewHandler(contentB, nav))
The 'Executor' is responsible for collecting related views,
configuring templates and plumbing it all together to produce a http.Handler
instance
for your router.
Example of embedded template blocks in "base.html"
,
...
<div class="layout">
{{ block "nav" .Nav }} <div id="nav">default nav</div> {{ end }}
{{ template "sidebar" .SideBar }}
{{ template "content" .Content }}
</div>
...
See text/template Nested Template Definitions for more info.
Note the loose coupling between content handlers in the outline below.
func BaseHandler(rsp treetop.Response, req *http.Request) interface{} {
// data for base template
return struct {
...
}{
...
Nav: rsp.HandleSubView("nav", req),
SideBar: rsp.HandleSubView("sidebar", req),
Content: rsp.HandleSubView("content", req),
}
}
func ContentAHandler(rsp treetop.Response, req *http.Request) interface{} {
// data for Content A template
return ...
}
The Treetop package wraps features of the Go standard library, mostly within "net/http" and "html/template".
Since views are self-contained, they can be rendered in isolation. Treetop handlers support rendering template fragments that can be applied to a loaded document. The following is an illustration of the protocol.
> GET /content_a HTTP/1.1
Accept: application/x.treetop-html-template+xml
< HTTP/1.1 200 OK
Content-Type: application/x.treetop-html-template+xml
Vary: Accept
<template>
<div id="content">...</div>
<div id="nav">...</div>
</template>
A Treetop Client Library is available. It sends template requests using XHR and applies fragments to the DOM with a simple find and replace mechanism.
Hot-swapping can be used to enhance user experience in several ways. See demo for more details.
- Demo Apps (README / Online DEMO)
- Todo *Without* MVC - Treetop implementation of TodoMVC app using the template protocol.
An 'Executor' is responsible for loading and configuring templates. It constructs a HTTP Handler instance to manage the plumbing between loading data and executing templates for a request. You can implement your own template loader [docs needed], but the following are provided:
FileExecutor
- load template files using os.OpenFileSytemExecutor
- loads templates from a supplied http.FileSystem instanceStringExecutor
- treat the view template property as an inline template stringKeyedStringExecutor
- treat the view template property is key into a template mapDeveloperExecutor
- force per request template parsing
A view handler function loads data for a corresponding 7960 Go template. Just as nested templates are embedded in a parent, nested handler data is embedded in the data of it's parent.
Example of a handler loading data for a child template,
func ParentHandler(rsp treetop.Response, req *http.Request) interface{} {
return struct {
...
Child interface{}
}{
...
Child: rsp.HandleSubView("child", req),
}
}
Data is subsequently passed down within the template like so,
<div id="parent">
...
{{ template "child" .Child }}
</div>
The treetop.Response
type implements http.ResponseWriter
. Any handler can halt the executor and
take full responsibility for the response by using one of the 'Write' methods of that interface2. This is a common and useful practice making things like error messages and redirects possible.
The Treetop Go library provides utilities for writing ad-hoc template responses when needed.
PartialWriter and FragmentWriter wrap a supplied http.ResponseWriter
.
For example,
func myHandler(w http.ResponseWriter, req *http.Request) {
// check for treetop request and construct a writer
if tw, ok := treetop.NewFragmentWriter(w, req); ok {
fmt.Fprintf(tw, `<h3 id="greeting">Hello, %s</h3>`, "Treetop")
}
}
This is useful when you want to use the template protocol with a conventional handler function.
The difference between a "Partial" and "Fragment" writer has to with navigation history in the web browser [docs needed]
The client library is used to send template requests from the browser using XHR. Response fragments are handled mechanically on the client. This avoids the need for callbacks or other IO boilerplate.
treetop.request("GET", "/some/path")
See Client Library for more information.
1. Go supports template inheritance through nested template definitions. 2. A http.ResponseWriter will flush headers when either WriteHeaders(..)
or Write(..)
methods are invoked.