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
- Optional protocol extension for fragment hot-swapping [1]
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 shows how http.Handler
endpoints are created from
a hierarchy of templates and handler pairs.
The example binds the "/content_a"
and "/content_b"
routes shown in the diagram
with additional "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))
Note that exec := treetop.FileExecutor{}
is responsible for compiling a template tree
from your view definitions, plumbing togeather handler functions and exposing a http.Handler
interface to your application 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.html` 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 the `content_a.html` template
return struct {
...
}{
...
}
}
The Treetop package wraps features of the Go standard library, mostly within "net/http" and "html/template".
Hot-swap sections of a page with minimal boilerplate.
Since Treetop 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 Client Library handles the the client side of the protocol, passively updating the DOM based on the response.
- Demo Apps (README / 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 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.