Skip to content

Commit

Permalink
Merge pull request #9 from rur/v0.2.0
Browse files Browse the repository at this point in the history
v0.2.0 improve code docs and further API clarifications
  • Loading branch information
Ruaidhri Devery authored Feb 24, 2020
2 parents 9825386 + 56e53a0 commit 17227eb
Show file tree
Hide file tree
Showing 18 changed files with 494 additions and 187 deletions.
14 changes: 10 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
## [0.2.0] - 2020-01-26
## [0.2.0] - 2020-02-16

### Breaking API Changes
Add clarifications to the prototype library API and improve code docs.

### Bugfix

Taking Treetop from POC to Alpha gives me an opportunity to
execute on a wishlist of API changes.
- Writer implementation was not including query in response URL header

### Breaking API Changes

- Remove `treetop.Renderer` type, unnecessary since it only wraps `TemplateExec`
- Add TemplateExec parameter to `treetop.NewView..` API method for creating base views
- Rename `treetop.TreetopWriter` interface to `treetop.Writer` to conform to naming guidelines
- Remove `treetop.Test`, testing recipes and resources belong elsewhere
- Split `treetop.Writer` function into `treetop.NewPartialWriter` and `treetop.NewFragmentWriter` and remove the confusing `isPartial` flag
- Change `treetop.View` from an interface to a struct and expose internals to make debugging easier
- Rename `HandlePartial` method to `HandleSubView` in `treetop.Response` interface to be consistent with the view builder
- Rename `Done` method to `Finished` in `treetop.Response` interface
- Rename `HandlerFunc` signature type to `ViewHandlerFunc` to make a clearer association with the view builder


#### Defining a page with views
Expand Down
4 changes: 2 additions & 2 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ Each template filepath is paired with a data handler which is responsible for yi
Content interface{}
}{
Session: Session{"example.user"},
Content: rsp.HandlePartial("content", req),
Content: rsp.HandleSubView("content", req),
}
}

Expand All @@ -141,7 +141,7 @@ Each template filepath is paired with a data handler which is responsible for yi
Form interface{}
}{
Title: "My Contact Form",
Form: rsp.HandlePartial("form", req),
Form: rsp.HandleSubView("form", req),
}
}

Expand Down
107 changes: 107 additions & 0 deletions doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
Package treetop provides tools for handlers to support a HTML template request protocol .
So your webpage does IO; and you need to show updates without clobbering the interface.
The common approach is to expose a data API and dispatch JavaScript to micromanage the client.
That will work, but it is pretty heavy duty for what seems like a simple problem.
Conventional HTTP works very well for navigation and web forms alike, no micromanagement required.
Perhaps it could be extended to solve our dynamic update problem. That is the starting point for Treetop,
to see how far we can get with a simple protocol.
Treetop is unique because it puts the server-side hander in complete control of how the page will be updated
following a request.
For documentation and examples see https://github.com/rur/treetop and https://github.com/rur/treetop-recipes
Introduction
In the spirit of opt-in integration this package supports two use cases.
The first is the handler scoped 'Writer' which is useful for supporting
ad-hoc fragments in an existing application. The view builder abstraction is
the second, it is designed for constructing a UI with many
cooperating endpoints.
Example of ad-hoc partial writer
import (
"fmt"
"github.com/rur/treetop"
"net/http"
)
...
func MyHandlerFunc(w http.ResponseWriter, req *http.Request) {
if pw, ok := treetop.NewPartialWriter(w, req); ok {
fmt.Fprint(pw, `<p id="greeting">Hello Treetop!</p>`)
return
}
// otherwise render a full page
...
}
For the example, the 'full page' document will include: the Treetop client library, an element
with an ID of "greeting" and an anchor element with a 'treetop' attribute.
Example HTML document
<!DOCTYPE html>
<html>
<head>
<title>My Page</title>
</head>
<body>
<h1>Message</h1>
<p id="greeting">Hello Page!</p>
<div><a treetop href=".">Fetch greeting</a></div>
<script>
// use default config, global variable will signal treetop client to initialize
TREETOP_CONFIG = {}
</script>
<script src="/lib/treetop.js" async></script>
</body>
</html>
When the 'Fetch greeting' anchor is clicked, the client library will issue an XHR fetch
with the appropriate headers. Elements on the DOM will then be replaced with fragments from
the response body based upon their ID attribute.
Views and Templates
Many Go web applications take advantage of the HTML template support
in the Go standard library. Package treetop includes a view builder
that works with the template system to support pages, partials and fragments.
Example
base := treetop.NewView(
treetop.DefaultTemplateExec,
"base.html.tmpl",
baseHandler,
)
greeting := base.NewSubView(
"greeting",
"greeting.html.tmpl",
greetingHandler,
)
mymux.Handle("/", treetop.ViewHandler(greeting))
See the documentation of the View type for details.
Browser History - Partials vs Fragments
This can cause confussion, but it is a very useful distinction. A partial is a
_part_ of an HTML document, a fragment is a general purpose HTML
snippet. Both have a URL, but only the former should be considered 'navigation'
by the user agent. This allows browser history to be handled correctly so that back,
forward and refresh behavior work as expected.
Note: The client relies upon the HTML 5 history API to support Treetop partials.
*/
package treetop
72 changes: 0 additions & 72 deletions examples/greeter.go

This file was deleted.

25 changes: 25 additions & 0 deletions examples/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package main

import (
"fmt"
"log"
"net/http"

"github.com/rur/treetop"
"github.com/rur/treetop/examples/shared"

"github.com/rur/treetop/examples/view"
"github.com/rur/treetop/examples/writer"
)

func main() {
mux := http.NewServeMux()
mux.HandleFunc("/favicon.ico", func(_ http.ResponseWriter, _ *http.Request) { /* noop */ })
mux.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) {
treetop.StringTemplateExec(w, []string{shared.BaseTemplate}, nil)
})
view.SetupGreeter(mux)
writer.SetupGreeter(mux)
fmt.Println("serving on http://0.0.0.0:3000/")
log.Fatal(http.ListenAndServe(":3000", mux))
}
28 changes: 28 additions & 0 deletions examples/shared/templates.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package shared

// BaseTemplate is the document markup shared by all of the example applications
var BaseTemplate = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Treetop Examples</title>
</head>
<body>
<nav>
<ul>
<li><a href="/view" title="View Greeter">View Greeter</a></li>
<li><a href="/writer" title="Writer Greeter">Writer Greeter</a></li>
</ul>
</nav>
{{ block "content" . }}
<p id="content">↑ Choose a demo ↑</p>
{{ end }}
<script>TREETOP_CONFIG={/*defaults*/}</script>
<script src="https://rawgit.com/rur/treetop-client/master/treetop.js" async></script>
</body>
</html>
`
76 changes: 76 additions & 0 deletions examples/view/greeter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package view

import (
"net/http"

"github.com/rur/treetop"
"github.com/rur/treetop/examples/shared"
)

var (
content = `
{{ block "content" . }}
<div id="content" style="text-align: center;">
<h1>Treetop View Greeter</h1>
<div>
<form action="/view/greet" treetop>
<span>Greet, </span><input placeholder="Name" type="text" name="name">
</form>
</div>
{{ template "message" .Message}}
</div>
{{ end }}
`
landing = `
{{ block "message" . }}
<p id="message"><i>Give me someone to say hello to!</i></p>
{{ end }}
`
greeting = `
{{ block "message" . }}
<div id="message">
<h2>Hello, {{ . }}!</h2>
<p><a href="/view" treetop>Clear</a></p>
</div>
{{ end }}
`
)

// SetupGreeter add routes for /view example endpoint
func SetupGreeter(mux *http.ServeMux) {
page := treetop.NewView(
treetop.StringTemplateExec,
shared.BaseTemplate,
treetop.Delegate("content"), // adopt "content" handler as own template data
)
content := page.SubView("content", content, contentHandler)

greetForm := content.SubView("message", landing, treetop.Noop)
greetMessage := content.SubView("message", greeting, greetingHandler)

mux.Handle("/view", treetop.ViewHandler(greetForm))
mux.Handle("/view/greet", treetop.ViewHandler(greetMessage))
}

// contentHandler loads data for the content template
func contentHandler(rsp treetop.Response, req *http.Request) interface{} {
return struct {
// In the content template the .Message field is passed explicitly to the
// "message" template block.
Message interface{}
}{
// HandleSubView triggers the sub handler call and returns the sub template data.
// `who` in the case of the greetingHandler, or nil in the case of Noop
Message: rsp.HandleSubView("message", req),
}
}

// greetingHandler obtains the name for the greeting from the request query
func greetingHandler(_ treetop.Response, req *http.Request) interface{} {
who := req.URL.Query().Get("name")
if who == "" {
return "World"
}
// NOTE: relying on Go package html/template for input escaping
return who
}
Loading

0 comments on commit 17227eb

Please sign in to comment.