Skip to content

Commit 92cb40d

Browse files
committed
document Go guiapi, rename Response to Update to match the README
1 parent b547ff3 commit 92cb40d

10 files changed

+235
-170
lines changed

README.md

+8-8
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ page reload for every user action and a typical modern single page app that
1717
requires a REST API and lots of JavaScript to render the different HTML views.
1818

1919
You should give this framework a try, if you agree with the following principles
20-
that `guiapi` is built on:
20+
that guiapi is built on:
2121

2222
- Rendering HTML should not require a REST API
2323
- Most web apps should be multi page apps out of the box
@@ -58,7 +58,7 @@ content is still updated as if the page was visited directly.
5858
### Actions
5959

6060
Actions are events that are sent from the browser to the server. They can either be
61-
originating from a HTML element with `ga-on` attribute, or from the guiapi `action`
61+
originating from a HTML element with `ga-on` attribute, or from the guiapi `action()`
6262
JavaScript function. Actions consist of a name and optional arguments. These
6363
actions are transferred as JSON via a POST request to the endpoint that is typically
6464
called `/guiapi`. The response to an Action is an Update.
@@ -98,8 +98,8 @@ closed. This is not done via a HTTP request, but via a WebSocket connection. Sim
9898
to actions, a Stream also consists of a name and arguments.
9999

100100
> [!WARNING]
101-
> While the other concepts of guiapi (Pages, Actions, Updates) have proven useful
102-
> web applications since 2018, Streams are a new concept for server side updates and
101+
> While the other concepts of guiapi (Pages, Actions, Updates) have been proven useful
102+
> in web applications since 2018, Streams are a new concept for server sent updates and
103103
> should be considered experimental. They might change significantly in the future.
104104
105105
# API Documentation
@@ -231,12 +231,12 @@ receives an update from the server. This can be useful during development.
231231

232232
## Examples
233233

234-
Go to `./examples` and running them with `go run .` will start a webserver at
235-
localhost:8000.
234+
Go to [`./examples`](./examples/) and start the web server with `go run .` to access
235+
the 3 examples with your web browser at localhost:8000.
236236

237-
It contains 3 examples:
237+
The 3 examples are:
238238

239-
- `/` is a guiapi implentation of [TodoMVC](https://todomvc.com/)
239+
- `/` contains a guiapi implentation of [TodoMVC](https://todomvc.com/)
240240
- `/counter` is a simple counter that can be increased and decreased
241241
- `/reports` is a demonstrates streams by updating the page from the server
242242

examples/counter.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func (c *CounterPage) WriteHTML(w io.Writer) error {
4646
return html.RenderMinified(w, block)
4747
}
4848

49-
func (c *CounterPage) Update() (*guiapi.Response, error) {
49+
func (c *CounterPage) Update() (*guiapi.Update, error) {
5050
out, err := html.RenderMinifiedString(c.Content)
5151
return guiapi.ReplaceContent("#page", out), err
5252
}
@@ -80,7 +80,7 @@ func (c *Counter) RenderBlock(ctx *guiapi.PageCtx) (html.Block, error) {
8080
return block, nil
8181
}
8282

83-
func (c *Counter) Increase(ctx *guiapi.ActionCtx) (*guiapi.Response, error) {
83+
func (c *Counter) Increase(ctx *guiapi.ActionCtx) (*guiapi.Update, error) {
8484
sess := c.DB.Session(ctx.Writer, ctx.Request)
8585
counter, err := c.DB.GetCounter(sess.ID)
8686
if err != nil {
@@ -96,7 +96,7 @@ func (c *Counter) Increase(ctx *guiapi.ActionCtx) (*guiapi.Response, error) {
9696
return guiapi.ReplaceContent("#count", out), err
9797
}
9898

99-
func (c *Counter) Decrease(ctx *guiapi.ActionCtx) (*guiapi.Response, error) {
99+
func (c *Counter) Decrease(ctx *guiapi.ActionCtx) (*guiapi.Update, error) {
100100
sess := c.DB.Session(ctx.Writer, ctx.Request)
101101
counter, err := c.DB.GetCounter(sess.ID)
102102
if err != nil {

examples/reports.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ func (r *ReportsPage) WriteHTML(w io.Writer) error {
232232
return html.RenderMinified(w, block)
233233
}
234234

235-
func (r *ReportsPage) Update() (*guiapi.Response, error) {
235+
func (r *ReportsPage) Update() (*guiapi.Update, error) {
236236
out, err := html.RenderMinifiedString(r.Content)
237237
res := guiapi.ReplaceElement("#reports", out)
238238
res.AddStream("Reports", r.Stream)
@@ -322,7 +322,7 @@ type ReportsArgs struct {
322322
ID string `json:"id"`
323323
}
324324

325-
func (r *Reports) Start(ctx *Action, args *ReportsArgs) (*guiapi.Response, error) {
325+
func (r *Reports) Start(ctx *Action, args *ReportsArgs) (*guiapi.Update, error) {
326326
report := &Report{
327327
ID: args.ID,
328328
Started: time.Now(),
@@ -354,7 +354,7 @@ func (r *Reports) Start(ctx *Action, args *ReportsArgs) (*guiapi.Response, error
354354
return update, err
355355
}
356356

357-
func (r *Reports) Cancel(ctx *Action, args *ReportsArgs) (*guiapi.Response, error) {
357+
func (r *Reports) Cancel(ctx *Action, args *ReportsArgs) (*guiapi.Update, error) {
358358
err := r.DB.Transaction(func() error {
359359
report := r.DB.Get(args.ID)
360360
if report.Status == ReportStatusStarted {
@@ -369,12 +369,12 @@ func (r *Reports) Cancel(ctx *Action, args *ReportsArgs) (*guiapi.Response, erro
369369
return guiapi.ReplaceElement("#all-reports", out), err
370370
}
371371

372-
func (r *Reports) Refresh(ctx *Action, args *NoArgs) (*guiapi.Response, error) {
372+
func (r *Reports) Refresh(ctx *Action, args *NoArgs) (*guiapi.Update, error) {
373373
time.Sleep(2 * time.Second)
374374
out, err := html.RenderMinifiedString(r.allReportsBlock())
375375
return guiapi.ReplaceElement("#all-reports", out), err
376376
}
377377

378-
func (r *Reports) SomeError(ctx *Action, args *NoArgs) (*guiapi.Response, error) {
378+
func (r *Reports) SomeError(ctx *Action, args *NoArgs) (*guiapi.Update, error) {
379379
return nil, errors.New("something bad happened (not really)")
380380
}

examples/stream.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
"github.com/mbertschler/html"
1010
)
1111

12-
func (r *Reports) Stream(ctx context.Context, msg json.RawMessage, res chan<- *guiapi.Response) error {
12+
func (r *Reports) Stream(ctx context.Context, msg json.RawMessage, res chan<- *guiapi.Update) error {
1313
var stream ReportsStream
1414
err := json.Unmarshal(msg, &stream)
1515
if err != nil {
@@ -24,7 +24,7 @@ func (r *Reports) Stream(ctx context.Context, msg json.RawMessage, res chan<- *g
2424
return nil
2525
}
2626

27-
func (r *Reports) overviewStream(ctx context.Context, results chan<- *guiapi.Response) error {
27+
func (r *Reports) overviewStream(ctx context.Context, results chan<- *guiapi.Update) error {
2828
listener := r.DB.AddGlobalChangeListener(func(change ChangeType, report *Report) {
2929
out, err := html.RenderMinifiedString(r.allReportsBlock())
3030
res := guiapi.ReplaceElement("#all-reports", out)
@@ -39,7 +39,7 @@ func (r *Reports) overviewStream(ctx context.Context, results chan<- *guiapi.Res
3939
return nil
4040
}
4141

42-
func (r *Reports) detailStream(ctx context.Context, id string, results chan<- *guiapi.Response) error {
42+
func (r *Reports) detailStream(ctx context.Context, id string, results chan<- *guiapi.Update) error {
4343
listener := r.DB.AddIDChangeListener(id, func(change ChangeType, report *Report) {
4444
out, err := html.RenderMinifiedString(r.singleReportBlock(id))
4545
res := guiapi.ReplaceElement("#single-report", out)

examples/todolist.go

+11-11
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ type Action struct {
2222
State TodoListState
2323
}
2424

25-
type ActionFunc[T any] func(c *Action, args *T) (*guiapi.Response, error)
25+
type ActionFunc[T any] func(c *Action, args *T) (*guiapi.Update, error)
2626

2727
func ContextAction[T any](db *DB, fn ActionFunc[T]) guiapi.ActionFunc {
28-
return func(c *guiapi.ActionCtx) (*guiapi.Response, error) {
28+
return func(c *guiapi.ActionCtx) (*guiapi.Update, error) {
2929
var input T
3030
if c.Args != nil {
3131
err := json.Unmarshal(c.Args, &input)
@@ -97,7 +97,7 @@ func (t *TodoPage) WriteHTML(w io.Writer) error {
9797
return html.RenderMinified(w, block)
9898
}
9999

100-
func (t *TodoPage) Update() (*guiapi.Response, error) {
100+
func (t *TodoPage) Update() (*guiapi.Update, error) {
101101
out, err := html.RenderMinifiedString(t.Content)
102102
if err != nil {
103103
return nil, err
@@ -315,7 +315,7 @@ type NewTodoArgs struct {
315315
Text string `json:"text"`
316316
}
317317

318-
func (t *TodoList) NewTodo(ctx *Action, input *NewTodoArgs) (*guiapi.Response, error) {
318+
func (t *TodoList) NewTodo(ctx *Action, input *NewTodoArgs) (*guiapi.Update, error) {
319319
return t.updateTodoList(ctx, func(props *TodoListProps, todos *StoredTodo) error {
320320
var highestID int
321321
for _, item := range todos.Items {
@@ -329,7 +329,7 @@ func (t *TodoList) NewTodo(ctx *Action, input *NewTodoArgs) (*guiapi.Response, e
329329
})
330330
}
331331

332-
func (t *TodoList) ToggleItem(ctx *Action, args *IDArgs) (*guiapi.Response, error) {
332+
func (t *TodoList) ToggleItem(ctx *Action, args *IDArgs) (*guiapi.Update, error) {
333333
return t.updateTodoList(ctx, func(props *TodoListProps, todos *StoredTodo) error {
334334
for i, item := range todos.Items {
335335
if item.ID == args.ID {
@@ -340,7 +340,7 @@ func (t *TodoList) ToggleItem(ctx *Action, args *IDArgs) (*guiapi.Response, erro
340340
})
341341
}
342342

343-
func (t *TodoList) ToggleAll(ctx *Action, args *NoArgs) (*guiapi.Response, error) {
343+
func (t *TodoList) ToggleAll(ctx *Action, args *NoArgs) (*guiapi.Update, error) {
344344
return t.updateTodoList(ctx, func(props *TodoListProps, todos *StoredTodo) error {
345345
allDone := true
346346
for _, item := range todos.Items {
@@ -357,7 +357,7 @@ func (t *TodoList) ToggleAll(ctx *Action, args *NoArgs) (*guiapi.Response, error
357357
})
358358
}
359359

360-
func (t *TodoList) DeleteItem(ctx *Action, args *IDArgs) (*guiapi.Response, error) {
360+
func (t *TodoList) DeleteItem(ctx *Action, args *IDArgs) (*guiapi.Update, error) {
361361
return t.updateTodoList(ctx, func(props *TodoListProps, todos *StoredTodo) error {
362362
var newItems []StoredTodoItem
363363
for _, item := range todos.Items {
@@ -373,7 +373,7 @@ func (t *TodoList) DeleteItem(ctx *Action, args *IDArgs) (*guiapi.Response, erro
373373

374374
type NoArgs struct{}
375375

376-
func (t *TodoList) ClearCompleted(ctx *Action, _ *NoArgs) (*guiapi.Response, error) {
376+
func (t *TodoList) ClearCompleted(ctx *Action, _ *NoArgs) (*guiapi.Update, error) {
377377
return t.updateTodoList(ctx, func(props *TodoListProps, todos *StoredTodo) error {
378378
var newItems []StoredTodoItem
379379
for _, item := range todos.Items {
@@ -391,7 +391,7 @@ type IDArgs struct {
391391
ID int `json:"id"`
392392
}
393393

394-
func (t *TodoList) EditItem(ctx *Action, args *IDArgs) (*guiapi.Response, error) {
394+
func (t *TodoList) EditItem(ctx *Action, args *IDArgs) (*guiapi.Update, error) {
395395
return t.updateTodoList(ctx, func(props *TodoListProps, _ *StoredTodo) error {
396396
props.EditItemID = args.ID
397397
return nil
@@ -403,7 +403,7 @@ type UpdateItemArgs struct {
403403
Text string `json:"text"`
404404
}
405405

406-
func (t *TodoList) UpdateItem(ctx *Action, args *UpdateItemArgs) (*guiapi.Response, error) {
406+
func (t *TodoList) UpdateItem(ctx *Action, args *UpdateItemArgs) (*guiapi.Update, error) {
407407
return t.updateTodoList(ctx, func(props *TodoListProps, todos *StoredTodo) error {
408408
for i, item := range todos.Items {
409409
if item.ID == args.ID {
@@ -414,7 +414,7 @@ func (t *TodoList) UpdateItem(ctx *Action, args *UpdateItemArgs) (*guiapi.Respon
414414
})
415415
}
416416

417-
func (t *TodoList) updateTodoList(ctx *Action, fn func(*TodoListProps, *StoredTodo) error) (*guiapi.Response, error) {
417+
func (t *TodoList) updateTodoList(ctx *Action, fn func(*TodoListProps, *StoredTodo) error) (*guiapi.Update, error) {
418418
props, err := t.todoListProps(ctx.Sess, ctx.State.Page)
419419
if err != nil {
420420
return nil, err

guiapi.go

+24-5
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,22 @@ import (
1010
"github.com/mbertschler/guiapi/api"
1111
)
1212

13+
// action is the sent body of a GUI API call
14+
type action struct {
15+
// Name of the action that is called
16+
Name string `json:",omitempty"`
17+
// URL is the URL of the next page that should be loaded via guiapi.
18+
URL string `json:",omitempty"`
19+
// Args as object, gets parsed by the called function
20+
Args json.RawMessage `json:",omitempty"`
21+
// State is can be passed back and forth between the server and browser.
22+
// It is held in a JavaScript variable, so there is one per browser tab.
23+
State json.RawMessage `json:",omitempty"`
24+
}
25+
1326
// handle handles HTTP requests to the GUI API.
1427
func (s *Server) handle(c *PageCtx) {
15-
var req Request
28+
var req action
1629
err := json.NewDecoder(c.Request.Body).Decode(&req)
1730
if err != nil {
1831
log.Println("guiapi: error decoding request:", err)
@@ -30,8 +43,8 @@ func (s *Server) handle(c *PageCtx) {
3043
}
3144
}
3245

33-
func (s *Server) process(p *PageCtx, req *Request) *Response {
34-
var res = Response{
46+
func (s *Server) process(p *PageCtx, req *action) *Update {
47+
var res = Update{
3548
Name: req.Name,
3649
}
3750

@@ -66,7 +79,7 @@ func (s *Server) process(p *PageCtx, req *Request) *Response {
6679
return &res
6780
}
6881

69-
func (s *Server) processURL(c *PageCtx, req *Request) {
82+
func (s *Server) processURL(c *PageCtx, req *action) {
7083
url, err := url.Parse(req.URL)
7184
if err != nil {
7285
log.Println("guiapi: error parsing url:", err)
@@ -84,11 +97,17 @@ func (s *Server) processURL(c *PageCtx, req *Request) {
8497
handle(c.Writer, c.Request, params)
8598
}
8699

100+
// ActionCtx is the context that is passed to an ActionFunc.
101+
// It extends the Request and Writer from a typical HTTP request handler with
102+
// State and Args fields from the Action call that were sent from the browser.
87103
type ActionCtx struct {
88104
Writer http.ResponseWriter
89105
Request *http.Request
90106
State json.RawMessage
91107
Args json.RawMessage
92108
}
93109

94-
type ActionFunc func(c *ActionCtx) (*Response, error)
110+
// ActionFunc is the action handler function that should return an Update in
111+
// response to the call from the client. ActionFuncs are registered with the
112+
// Server using the AddAction() function.
113+
type ActionFunc func(c *ActionCtx) (*Update, error)

0 commit comments

Comments
 (0)