Skip to content

Commit

Permalink
chore: Experiments.
Browse files Browse the repository at this point in the history
  • Loading branch information
jfyne committed Jul 12, 2023
1 parent a0bbfd2 commit 11fca98
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 27 deletions.
21 changes: 21 additions & 0 deletions cmd/example/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package main

import (
"context"
"net/http"

"github.com/jfyne/live"
"github.com/jfyne/live/examples"
"github.com/jfyne/live/page"
)

func main() {
h := page.NewHandler(func(_ context.Context, _ *live.Handler, _ live.Socket) (page.Component, error) {
root := examples.NewApp()
return root, nil
})

http.Handle("/", live.NewHttpHandler(live.NewCookieStore("session-name", []byte("weak-secret")), h))
http.Handle("/live.js", live.Javascript{})
http.ListenAndServe(":8080", nil)
}
2 changes: 1 addition & 1 deletion engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ func (e *BaseEngine) Mount() MountHandler {
}

func (e *BaseEngine) Unmount() UnmountHandler {
return e.handler.getUnmount()
return e.handler.unmountHandler
}

func (e *BaseEngine) Params() []EventHandler {
Expand Down
31 changes: 31 additions & 0 deletions examples/app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package examples

import "github.com/jfyne/live/page"

type App struct {
Button *Button

page.BaseComponent
}

func NewApp() *App {
return &App{
Button: NewButton(10),
}
}

func (a App) Render() page.RenderFunc {
return page.HTML(`
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Live examples</title>
</head>
<body>
{{ .Button }}
<script src="/live.js"></script>
</body>
</html>
`, a)
}
21 changes: 21 additions & 0 deletions examples/button.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package examples

import (
"github.com/jfyne/live/page"
)

type Button struct {
Count int

page.BaseComponent
}

func NewButton(start int) *Button {
return &Button{Count: start}
}

func (b Button) Render() page.RenderFunc {
return page.HTML(`
{{.Count}}
`, b)
}
51 changes: 29 additions & 22 deletions page/component.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package page

import (
"bytes"
"context"
"fmt"
"io"
Expand All @@ -11,13 +12,13 @@ import (
"github.com/jfyne/live"
)

// EventHandler for a component, only needs the params as the event is scoped to both the socket and then component
// ComponentEventHandler for a component, only needs the params as the event is scoped to both the socket and then component
// itself. Returns any component state that needs updating.
type EventHandler func(ctx context.Context, p live.Params) (any, error)
type ComponentEventHandler func(ctx context.Context, p live.Params) (any, error)

// SelfHandler for a component, only needs the data as the event is scoped to both the socket and then component
// ComponentSelfHandler for a component, only needs the data as the event is scoped to both the socket and then component
// itself. Returns any component state that needs updating.
type SelfHandler func(ctx context.Context, data any) (any, error)
type ComponentSelfHandler func(ctx context.Context, data any) (any, error)

// ComponentMount describes the needed function for mounting a component.
type ComponentMount interface {
Expand All @@ -30,8 +31,8 @@ type ComponentRender interface {
Event(string) string
}

// ComponentLifecycle describes all that is neded to describe a component.
type ComponentLifecycle interface {
// Component describes all that is neded to describe a component.
type Component interface {
isComponent
componentInit
componentRegister
Expand All @@ -52,13 +53,13 @@ type isComponent interface {
_assignUploads(live.UploadContext)
}

// Component is a self contained component on the page. Components can be reused accross the application
// BaseComponent is a self contained component on the page. Components can be reused accross the application
// or used to compose complex interfaces by splitting events handlers and render logic into
// smaller pieces.
//
// Remember to use a unique ID and use the Event function which scopes the event-name
// to trigger the event in the right component.
type Component struct {
type BaseComponent struct {
// ID identifies the component on the page. This should be something stable, so that during the mount
// it can be found again by the socket.
// When reusing the same component this ID should be unique to avoid conflicts.
Expand All @@ -75,24 +76,30 @@ type Component struct {
Uploads live.UploadContext
}

func (c Component) _isComponent() {}
func (c *Component) _assignUploads(uploads live.UploadContext) {
func (c BaseComponent) String() string {
buf := &bytes.Buffer{}
c.Render().Render(buf)
return buf.String()
}

func (c BaseComponent) _isComponent() {}
func (c *BaseComponent) _assignUploads(uploads live.UploadContext) {
c.Uploads = uploads
}

func (c *Component) init(ID string, h *live.Handler, s live.Socket) {
func (c *BaseComponent) init(ID string, h *live.Handler, s live.Socket) {
c.ID = ID
c.Handler = h
c.Socket = s
}

// Mount a default component mount function.
func (c Component) Mount(ctx context.Context) error {
func (c BaseComponent) Mount(ctx context.Context) error {
return nil
}

// Render a default component render function.
func (c Component) Render() RenderFunc {
func (c BaseComponent) Render() RenderFunc {
return func(w io.Writer) error {
return nil
}
Expand All @@ -101,7 +108,7 @@ func (c Component) Render() RenderFunc {
var compMethodDetect = regexp.MustCompile(`On([A-Za-z]*)`)
var compMethodSplit = regexp.MustCompile(`[A-Z][^A-Z]*`)

func (c *Component) register(ID string, h *live.Handler, s live.Socket, t any) error {
func (c *BaseComponent) register(ID string, h *live.Handler, s live.Socket, t any) error {
c.ID = ID
c.Handler = h
c.Socket = s
Expand All @@ -117,7 +124,7 @@ func (c *Component) register(ID string, h *live.Handler, s live.Socket, t any) e
if len(parts) < 2 {
continue
}
c.HandleEvent(eventName(parts), func(ctx context.Context, p live.Params) (any, error) {
c.handleEvent(eventName(parts), func(ctx context.Context, p live.Params) (any, error) {
res := va.MethodByName(method.Name).Call([]reflect.Value{reflect.ValueOf(ctx), reflect.ValueOf(p)})
switch len(res) {
case 0:
Expand All @@ -132,7 +139,7 @@ func (c *Component) register(ID string, h *live.Handler, s live.Socket, t any) e
return t, nil
}
})
c.HandleSelf(eventName(parts), func(ctx context.Context, data any) (any, error) {
c.handleSelf(eventName(parts), func(ctx context.Context, data any) (any, error) {
res := va.MethodByName(method.Name).Call([]reflect.Value{reflect.ValueOf(ctx), reflect.ValueOf(data)})
switch len(res) {
case 0:
Expand Down Expand Up @@ -160,7 +167,7 @@ func eventName(parts []string) string {
}

// Start begins the component's lifecycle.
func Start(ctx context.Context, ID string, h *live.Handler, s live.Socket, comp ComponentLifecycle) error {
func NewComponent(ctx context.Context, ID string, h *live.Handler, s live.Socket, comp Component) error {
if err := comp.register(ID, h, s, comp); err != nil {
return fmt.Errorf("could not spawn component on register: %w", err)
}
Expand All @@ -172,12 +179,12 @@ func Start(ctx context.Context, ID string, h *live.Handler, s live.Socket, comp

// Self sends an event scoped not only to this socket, but to this specific component instance. Or any
// components sharing the same ID.
func (c *Component) Self(ctx context.Context, event string, data any) error {
func (c *BaseComponent) Self(ctx context.Context, event string, data any) error {
return c.Socket.Self(ctx, c.Event(event), data)
}

// HandleSelf handles scoped incoming events send by a components Self function.
func (c *Component) HandleSelf(event string, handler SelfHandler) {
func (c *BaseComponent) handleSelf(event string, handler ComponentSelfHandler) {
c.Handler.HandleSelf(c.Event(event), func(ctx context.Context, s live.Socket, d any) (any, error) {
_, err := handler(ctx, d)
if err != nil {
Expand All @@ -189,7 +196,7 @@ func (c *Component) HandleSelf(event string, handler SelfHandler) {
}

// HandleEvent handles a component event sent from a connected socket.
func (c *Component) HandleEvent(event string, handler EventHandler) {
func (c *BaseComponent) handleEvent(event string, handler ComponentEventHandler) {
c.Handler.HandleEvent(c.Event(event), func(ctx context.Context, s live.Socket, p live.Params) (any, error) {
_, err := handler(ctx, p)
if err != nil {
Expand All @@ -201,7 +208,7 @@ func (c *Component) HandleEvent(event string, handler EventHandler) {
}

// HandleParams handles parameter changes. Caution these handlers are not scoped to a specific component.
func (c *Component) HandleParams(handler EventHandler) {
func (c *BaseComponent) handleParams(handler ComponentEventHandler) {
c.Handler.HandleParams(func(ctx context.Context, s live.Socket, p live.Params) (any, error) {
_, err := handler(ctx, p)
if err != nil {
Expand All @@ -214,6 +221,6 @@ func (c *Component) HandleParams(handler EventHandler) {

// Event scopes an event string so that it applies to this instance of this component
// only.
func (c Component) Event(event string) string {
func (c BaseComponent) Event(event string) string {
return c.ID + "--" + event
}
4 changes: 2 additions & 2 deletions page/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
type Greeter struct {
Name string

Component
BaseComponent
}

func NewGreeter(name string) *Greeter {
Expand All @@ -27,7 +27,7 @@ func (g Greeter) Render() RenderFunc {
}

func Example() {
h := NewHandler(func(_ context.Context, _ *live.Handler, _ live.Socket) (ComponentLifecycle, error) {
h := NewHandler(func(_ context.Context, _ *live.Handler, _ live.Socket) (Component, error) {
root := NewGreeter("World!")
return root, nil
})
Expand Down
4 changes: 2 additions & 2 deletions page/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
)

// ComponentConstructor a func for creating a new component.
type ComponentConstructor func(ctx context.Context, h *live.Handler, s live.Socket) (ComponentLifecycle, error)
type ComponentConstructor func(ctx context.Context, h *live.Handler, s live.Socket) (Component, error)

// NewHandler creates a new handler for components.
func NewHandler(construct ComponentConstructor) *live.Handler {
Expand Down Expand Up @@ -47,7 +47,7 @@ func withComponentMount(construct ComponentConstructor) live.HandlerConfig {
func withComponentRenderer() live.HandlerConfig {
return func(h *live.Handler) error {
h.HandleRender(func(_ context.Context, data *live.RenderContext) (io.Reader, error) {
c, ok := data.Assigns.(ComponentLifecycle)
c, ok := data.Assigns.(Component)
if !ok {
return nil, fmt.Errorf("root render data is not a component")
}
Expand Down

0 comments on commit 11fca98

Please sign in to comment.