diff --git a/.vscode/examples.code-snippets b/.vscode/examples.code-snippets index e9e6f88..18c80d5 100644 --- a/.vscode/examples.code-snippets +++ b/.vscode/examples.code-snippets @@ -89,9 +89,9 @@ "", "import (", " \"embed\"", - " \"time\"", "", - " \"github.com/lithammer/dedent\"", + "g \"github.com/maragudk/gomponents\"", + ". \"github.com/maragudk/gomponents/html\"", " g \"github.com/maragudk/gomponents\"", " \"github.com/will-wow/typed-htmx-go/examples/web/$2/shared\"", " \"github.com/will-wow/typed-htmx-go/examples/web/exprint\"", @@ -102,7 +102,7 @@ "", "var hx = htmx.NewGomponents()", "", - "//go:embed ${TM_DIRECTORY/.*\\/(.*)$/$1/}.gom.go", + "//go:embed $TM_FILENAME", "var fs embed.FS", "var ex = exprint.New(fs, \"//\", \"\")", "", @@ -110,13 +110,14 @@ " return layout.Wrapper(", " \"$1\",", " H1(g.Text(\"$1\")),", + " Class(\"$3\"),", " P(", " g.Text(\"Desc\"),", " ),", " Pre(", " Code(", " Class(\"language-go\"),", - " g.Text(ex.PrintOrErr(\"$TM_FILENAME_BASE.gom.go\", \"demo\")),", + " g.Text(ex.PrintOrErr(\"$TM_FILENAME_BASE\", \"demo\")),", " ),", " ),", " H2(g.Text(\"Demo\")),", diff --git a/examples/cmd/server/main.go b/examples/cmd/server/main.go index 84a21c4..0b376e4 100644 --- a/examples/cmd/server/main.go +++ b/examples/cmd/server/main.go @@ -16,7 +16,7 @@ func main() { server := &http.Server{ Addr: "localhost:8080", Handler: handler, - ReadTimeout: time.Second * 30, + ReadTimeout: time.Second * 10, WriteTimeout: time.Second * 10, } diff --git a/examples/vercel.json b/examples/vercel.json index 6bba80b..dc23698 100644 --- a/examples/vercel.json +++ b/examples/vercel.json @@ -2,7 +2,7 @@ "routes": [{ "src": "/(.*)", "dest": "/api" }], "functions": { "api/index.go": { - "maxDuration": 30 + "maxDuration": 10 } } } diff --git a/examples/web/examples/exgom/examples.gom.go b/examples/web/examples/exgom/examples.gom.go index 2e4ab3b..c428709 100644 --- a/examples/web/examples/exgom/examples.gom.go +++ b/examples/web/examples/exgom/examples.gom.go @@ -1,9 +1,12 @@ package exgom import ( + "fmt" + g "github.com/maragudk/gomponents" . "github.com/maragudk/gomponents/html" + "github.com/will-wow/typed-htmx-go/examples/web/examples/registry" "github.com/will-wow/typed-htmx-go/examples/web/layout/gom/layout" ) @@ -29,31 +32,13 @@ func Page() g.Node { ), ), TBody( - exampleRow( - "/examples/gomponents/click-to-edit", - "Click To Edit", - "Demonstrates inline editing of a data object", - ), - exampleRow( - "/examples/gomponents/bulk-update", - "Bulk Update", - "Demonstrates bulk updating of multiple rows of data", - ), - exampleRow( - "/examples/gomponents/active-search/", - "Active Search", - "Demonstrates the active search box pattern", - ), - exampleRow( - "/examples/gomponents/progress-bar/", - "Progress Bar", - "Demonstrates a job-runner like progress bar", - ), - exampleRow( - "/examples/gomponents/class-tools/", - "Class Tools", - "Demo of class-tools options", - ), + g.Group(g.Map(registry.Examples, func(ex registry.Example) g.Node { + return exampleRow( + fmt.Sprintf("/examples/gomponents/%s/", ex.Slug), + ex.Title, + ex.Desc, + ) + })), ), ), ) diff --git a/examples/web/examples/extempl/examples.templ b/examples/web/examples/extempl/examples.templ index 77eb722..f92d862 100644 --- a/examples/web/examples/extempl/examples.templ +++ b/examples/web/examples/extempl/examples.templ @@ -1,7 +1,9 @@ package extempl import ( + "fmt" "github.com/will-wow/typed-htmx-go/examples/web/layout/templ/layout" + "github.com/will-wow/typed-htmx-go/examples/web/examples/registry" ) templ Page() { @@ -32,31 +34,13 @@ templ Page() { - @exampleRow( - "/examples/templ/click-to-edit/", - "Click To Edit", - "Demonstrates inline editing of a data object", - ) - @exampleRow( - "/examples/templ/bulk-update/", - "Bulk Update", - "Demonstrates bulk updating of multiple rows of data", - ) - @exampleRow( - "/examples/templ/active-search/", - "Active Search", - "Demonstrates the active search box pattern", - ) - @exampleRow( - "/examples/templ/progress-bar/", - "Progress Bar", - "Demonstrates a job-runner like progress bar", - ) - @exampleRow( - "/examples/templ/class-tools/", - "Class Tools", - "Demo of class-tools options", - ) + for _, ex := range registry.Examples { + @exampleRow( + fmt.Sprintf("/examples/templ/%s/", ex.Slug), + ex.Title, + ex.Desc, + ) + } } diff --git a/examples/web/examples/extempl/examples_templ.go b/examples/web/examples/extempl/examples_templ.go index cfa2cd8..515ee2e 100644 --- a/examples/web/examples/extempl/examples_templ.go +++ b/examples/web/examples/extempl/examples_templ.go @@ -8,10 +8,12 @@ package extempl import ( "bytes" "context" + "fmt" "io" "github.com/a-h/templ" + "github.com/will-wow/typed-htmx-go/examples/web/examples/registry" "github.com/will-wow/typed-htmx-go/examples/web/layout/templ/layout" ) @@ -38,45 +40,15 @@ func Page() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = exampleRow( - "/examples/templ/click-to-edit/", - "Click To Edit", - "Demonstrates inline editing of a data object", - ).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = exampleRow( - "/examples/templ/bulk-update/", - "Bulk Update", - "Demonstrates bulk updating of multiple rows of data", - ).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = exampleRow( - "/examples/templ/active-search/", - "Active Search", - "Demonstrates the active search box pattern", - ).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = exampleRow( - "/examples/templ/progress-bar/", - "Progress Bar", - "Demonstrates a job-runner like progress bar", - ).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = exampleRow( - "/examples/templ/class-tools/", - "Class Tools", - "Demo of class-tools options", - ).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err + for _, ex := range registry.Examples { + templ_7745c5c3_Err = exampleRow( + fmt.Sprintf("/examples/templ/%s/", ex.Slug), + ex.Title, + ex.Desc, + ).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") if templ_7745c5c3_Err != nil { @@ -127,7 +99,7 @@ func exampleRow(link, name, description string) templ.Component { var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/examples/extempl/examples.templ`, Line: 68, Col: 41} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/examples/extempl/examples.templ`, Line: 52, Col: 41} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -140,7 +112,7 @@ func exampleRow(link, name, description string) templ.Component { var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(description) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/examples/extempl/examples.templ`, Line: 71, Col: 16} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/examples/extempl/examples.templ`, Line: 55, Col: 16} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { diff --git a/examples/web/examples/registry/registry.go b/examples/web/examples/registry/registry.go new file mode 100644 index 0000000..55aea5e --- /dev/null +++ b/examples/web/examples/registry/registry.go @@ -0,0 +1,58 @@ +package registry + +import ( + "net/http" + + "github.com/will-wow/typed-htmx-go/examples/web/activesearch" + "github.com/will-wow/typed-htmx-go/examples/web/bulkupdate" + "github.com/will-wow/typed-htmx-go/examples/web/classtools_ex" + "github.com/will-wow/typed-htmx-go/examples/web/clicktoedit" + "github.com/will-wow/typed-htmx-go/examples/web/progressbar" + "github.com/will-wow/typed-htmx-go/examples/web/sse_ex" +) + +type Example struct { + Title string + Desc string + Slug string + Handler func(bool) http.Handler +} + +var Examples = []Example{ + { + Title: "Click to Edit", + Desc: "Demonstrates inline editing of a data object", + Slug: "click-to-edit", + Handler: clicktoedit.NewHandler, + }, + { + Title: "Bulk Update", + Desc: "Demonstrates bulk updating of multiple rows of data", + Slug: "bulk-update", + Handler: bulkupdate.NewHandler, + }, + { + Title: "Active Search", + Desc: "Demonstrates the active search box pattern", + Slug: "active-search", + Handler: activesearch.NewHandler, + }, + { + Title: "Progress Bar", + Desc: "Demonstrates a job-runner like progress bar", + Slug: "progress-bar", + Handler: progressbar.NewHandler, + }, + { + Title: "Class Tools", + Desc: "Demo of class-tools options", + Slug: "class-tools", + Handler: classtools_ex.NewHandler, + }, + { + Title: "Server-Sent Events", + Desc: "Streaming responses with the SSE HTMX Extension", + Slug: "sse", + Handler: sse_ex.NewHandler, + }, +} diff --git a/examples/web/layout/gom/layout/layout.gom.go b/examples/web/layout/gom/layout/layout.gom.go index 6a4921e..9e0c423 100644 --- a/examples/web/layout/gom/layout/layout.gom.go +++ b/examples/web/layout/gom/layout/layout.gom.go @@ -33,8 +33,9 @@ func Wrapper(title string, children ...g.Node) g.Node { Meta(Name("htmx-config"), hx.Config( hxconfig.New().IncludeIndicatorStyles(false), )), - Script(Src("https://unpkg.com/htmx.org@1.9.10")), - Script(Src("https://unpkg.com/htmx.org@1.9.10/dist/ext/class-tools.js")), + Script(Src("https://unpkg.com/htmx.org@1.9.12")), + Script(Src("https://unpkg.com/htmx.org@1.9.12/dist/ext/class-tools.js")), + Script(Src("https://unpkg.com/htmx.org@1.9.12/dist/ext/sse.js")), Link(Rel("stylesheet"), Href("https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.min.css")), Link(Rel("stylesheet"), Href("https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/default.min.css")), Link(Rel("stylesheet"), Href("/static/main.css")), diff --git a/examples/web/layout/templ/layout/layout.templ b/examples/web/layout/templ/layout/layout.templ index badfa93..817a32a 100644 --- a/examples/web/layout/templ/layout/layout.templ +++ b/examples/web/layout/templ/layout/layout.templ @@ -30,8 +30,8 @@ templ Wrapper(title string, className ...string) { hxconfig.New().Timeout(time.Second), )... } /> - - + + hx.Swap(swap.OuterHTML)")), + g.Text("."), + ), + H2(g.Text("Demo")), + Trigger(), + ) +} + +func Trigger() g.Node { + //ex:start:trigger + return Button( + hx.Get("/examples/gomponents/sse/countdown/"), + hx.Target(htmx.TargetThis), + hx.Swap(swap.OuterHTML), + g.Text("Start Countdown"), + ) + //ex:end:trigger +} + +func Countdown() g.Node { + //ex:start:countdown + return Div( + hx.Ext(sse.Extension), + sse.Connect(hx, "/examples/gomponents/sse/countdown/feed/"), + sse.Swap(hx, shared.ResetEvent), + hx.Swap(swap.OuterHTML), + Div( + sse.Swap(hx, shared.CountdownEvent), + hx.Swap(swap.InnerHTML), + ), + ) + //ex:end:countdown +} + +//ex:start:message +func Message(msg string) g.Node { + return P(g.Text(msg)) +} + +func Blastoff() g.Node { + return P(g.Text("Blastoff!")) +} + +//ex:end:message diff --git a/examples/web/sse_ex/extempl/sse.templ b/examples/web/sse_ex/extempl/sse.templ index 1be7509..dd4d2a5 100644 --- a/examples/web/sse_ex/extempl/sse.templ +++ b/examples/web/sse_ex/extempl/sse.templ @@ -8,8 +8,7 @@ import ( "github.com/will-wow/typed-htmx-go/htmx/ext/sse" "github.com/will-wow/typed-htmx-go/htmx" "github.com/will-wow/typed-htmx-go/htmx/swap" - "github.com/will-wow/typed-htmx-go/examples/web/sse_ex/chatroom" - "github.com/will-wow/typed-htmx-go/htmx/on" + "github.com/will-wow/typed-htmx-go/examples/web/sse_ex/shared" ) var hx = htmx.NewTempl() @@ -19,29 +18,29 @@ var fs embed.FS var ex = exprint.New(fs, "//", "") templ Page() { - @layout.Wrapper("Server-Side Events") { -

Server-Side Events

+ @layout.Wrapper("Server-Sent Events", "sse") { +

Server-Sent Events

- A demo of Server-Side Events using htmx and typed-htmx-go. + A demo countdown using Server-Sent Events, with htmx and typed-htmx-go.

- When you click the button below, that fetches a new element that uses the sse extension to start a live feed. + When you click the button below, that fetches a new element that uses the sse extension to start stream a countdown.

 			
-				{ ex.PrintOrErr("sse.templ", "entry") }
+				{ ex.PrintOrErr("sse.templ", "trigger") }
 			
 		

- The new elements uses the sse.Connect attribute to connect to a server-side event stream, and allows you to POST messages to the stream. + The new elements uses the sse.Connect attribute to connect to a server-side event streaming countdown.

 			
-				{ ex.PrintOrErr("sse.templ", "chatroom") }
+				{ ex.PrintOrErr("sse.templ", "countdown") }
 			
 		

- When you or another user posts a message, it will be sent to the server and broadcast to all connected clients as a simple div. + Each second, the server sends a countdown message that updates the innerHTML of the div.

 			
@@ -49,60 +48,44 @@ templ Page() {
 			
 		

- After 25 seconds, the server will send the EndEvent, that closes removes the sse connection by replacing the sse.Connect element with the initial button using hx.Swap(swap.OuterHTML). + After the countdown is complete, the server will send a ResetEvent, that closes removes the sse connection by replacing the sse.Connect element with the initial button using hx.Swap(swap.OuterHTML).

Demo

- @Entry() + @Trigger() } } -templ Entry() { - //ex:start:entry +templ Trigger() { + //ex:start:trigger - //ex:end:entry + //ex:end:trigger } -templ Chatroom() { - //ex:start:chatroom +templ Countdown() { + //ex:start:countdown
-
- - -
- //ex:end:chatroom + //ex:end:countdown } //ex:start:message -templ ChatMessage(msg string) { -
{ msg }
+templ Message(msg string) { +

{ msg }

} //ex:end:message diff --git a/examples/web/sse_ex/extempl/sse_templ.go b/examples/web/sse_ex/extempl/sse_templ.go index 8c5a325..246abc5 100644 --- a/examples/web/sse_ex/extempl/sse_templ.go +++ b/examples/web/sse_ex/extempl/sse_templ.go @@ -14,12 +14,11 @@ import ( "github.com/a-h/templ" "github.com/will-wow/typed-htmx-go/htmx" "github.com/will-wow/typed-htmx-go/htmx/ext/sse" - "github.com/will-wow/typed-htmx-go/htmx/on" "github.com/will-wow/typed-htmx-go/htmx/swap" "github.com/will-wow/typed-htmx-go/examples/web/exprint" "github.com/will-wow/typed-htmx-go/examples/web/layout/templ/layout" - "github.com/will-wow/typed-htmx-go/examples/web/sse_ex/chatroom" + "github.com/will-wow/typed-htmx-go/examples/web/sse_ex/shared" ) var hx = htmx.NewTempl() @@ -47,50 +46,50 @@ func Page() templ.Component { templ_7745c5c3_Buffer = templ.GetBuffer() defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

Server-Side Events

A demo of Server-Side Events using htmx and typed-htmx-go.

When you click the button below, that fetches a new element that uses the sse extension to start a live feed.

")
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

Server-Sent Events

A demo countdown using Server-Sent Events, with htmx and typed-htmx-go.

When you click the button below, that fetches a new element that uses the sse extension to start stream a countdown.

")
 			if templ_7745c5c3_Err != nil {
 				return templ_7745c5c3_Err
 			}
 			var templ_7745c5c3_Var3 string
-			templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(ex.PrintOrErr("sse.templ", "entry"))
+			templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(ex.PrintOrErr("sse.templ", "trigger"))
 			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/sse_ex/extempl/sse.templ`, Line: 32, Col: 41}
+				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/sse_ex/extempl/sse.templ`, Line: 31, Col: 43}
 			}
 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
 			if templ_7745c5c3_Err != nil {
 				return templ_7745c5c3_Err
 			}
-			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

The new elements uses the sse.Connect attribute to connect to a server-side event stream, and allows you to POST messages to the stream.

")
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

The new elements uses the sse.Connect attribute to connect to a server-side event streaming countdown.

")
 			if templ_7745c5c3_Err != nil {
 				return templ_7745c5c3_Err
 			}
 			var templ_7745c5c3_Var4 string
-			templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(ex.PrintOrErr("sse.templ", "chatroom"))
+			templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(ex.PrintOrErr("sse.templ", "countdown"))
 			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/sse_ex/extempl/sse.templ`, Line: 40, Col: 44}
+				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/sse_ex/extempl/sse.templ`, Line: 39, Col: 45}
 			}
 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
 			if templ_7745c5c3_Err != nil {
 				return templ_7745c5c3_Err
 			}
-			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

When you or another user posts a message, it will be sent to the server and broadcast to all connected clients as a simple div.

")
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

Each second, the server sends a countdown message that updates the innerHTML of the div.

")
 			if templ_7745c5c3_Err != nil {
 				return templ_7745c5c3_Err
 			}
 			var templ_7745c5c3_Var5 string
 			templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(ex.PrintOrErr("sse.templ", "message"))
 			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/sse_ex/extempl/sse.templ`, Line: 48, Col: 43}
+				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/sse_ex/extempl/sse.templ`, Line: 47, Col: 43}
 			}
 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
 			if templ_7745c5c3_Err != nil {
 				return templ_7745c5c3_Err
 			}
-			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

After 25 seconds, the server will send the EndEvent, that closes removes the sse connection by replacing the sse.Connect element with the initial button using hx.Swap(swap.OuterHTML).

Demo

") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

After the countdown is complete, the server will send a ResetEvent, that closes removes the sse connection by replacing the sse.Connect element with the initial button using hx.Swap(swap.OuterHTML).

Demo

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = Entry().Render(ctx, templ_7745c5c3_Buffer) + templ_7745c5c3_Err = Trigger().Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -99,7 +98,7 @@ func Page() templ.Component { } return templ_7745c5c3_Err }) - templ_7745c5c3_Err = layout.Wrapper("Server-Side Events").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = layout.Wrapper("Server-Sent Events", "sse").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -110,7 +109,7 @@ func Page() templ.Component { }) } -func Entry() templ.Component { +func Trigger() templ.Component { return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) if !templ_7745c5c3_IsBuffer { @@ -127,7 +126,7 @@ func Entry() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ.RenderAttributes(ctx, templ_7745c5c3_Buffer, hx.Get("/examples/templ/sse/chatroom/")) + templ_7745c5c3_Err = templ.RenderAttributes(ctx, templ_7745c5c3_Buffer, hx.Get("/examples/templ/sse/countdown/")) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -139,7 +138,7 @@ func Entry() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(">Enter Chat") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(">Start Countdown") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -150,7 +149,7 @@ func Entry() templ.Component { }) } -func Chatroom() templ.Component { +func Countdown() templ.Component { return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) if !templ_7745c5c3_IsBuffer { @@ -171,11 +170,11 @@ func Chatroom() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ.RenderAttributes(ctx, templ_7745c5c3_Buffer, sse.Connect(hx, "/examples/templ/sse/chatroom/feed/")) + templ_7745c5c3_Err = templ.RenderAttributes(ctx, templ_7745c5c3_Buffer, sse.Connect(hx, "/examples/templ/sse/countdown/feed/")) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ.RenderAttributes(ctx, templ_7745c5c3_Buffer, sse.Swap(hx, chatroom.EndEvent)) + templ_7745c5c3_Err = templ.RenderAttributes(ctx, templ_7745c5c3_Buffer, sse.Swap(hx, shared.ResetEvent)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -183,23 +182,15 @@ func Chatroom() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(">
") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var9 string templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(msg) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/sse_ex/extempl/sse.templ`, Line: 105, Col: 11} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/sse_ex/extempl/sse.templ`, Line: 88, Col: 9} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/examples/web/sse_ex/shared/shared.go b/examples/web/sse_ex/shared/shared.go new file mode 100644 index 0000000..a45df90 --- /dev/null +++ b/examples/web/sse_ex/shared/shared.go @@ -0,0 +1,4 @@ +package shared + +const CountdownEvent = "countdown" +const ResetEvent = "reset" diff --git a/examples/web/sse_ex/sse_ex.go b/examples/web/sse_ex/sse_ex.go index b7016d2..222f0e9 100644 --- a/examples/web/sse_ex/sse_ex.go +++ b/examples/web/sse_ex/sse_ex.go @@ -1,105 +1,93 @@ package sse_ex import ( - "context" "fmt" "net/http" + "strconv" "time" - "github.com/will-wow/typed-htmx-go/examples/web/sse_ex/chatroom" + "github.com/will-wow/typed-htmx-go/examples/web/sse_ex/exgom" "github.com/will-wow/typed-htmx-go/examples/web/sse_ex/extempl" + "github.com/will-wow/typed-htmx-go/examples/web/sse_ex/shared" ) type example struct { - gom bool - room *chatroom.Chatroom + gom bool } func NewHandler(gom bool) http.Handler { mux := http.NewServeMux() - ctx := context.TODO() - - c := chatroom.New(ctx) - - ex := example{gom: gom, room: c} + ex := example{gom: gom} mux.HandleFunc("GET /{$}", ex.demo) - mux.HandleFunc("GET /chatroom/{$}", ex.chatroom) - mux.HandleFunc("GET /chatroom/feed/{$}", ex.feed) - mux.HandleFunc("POST /chatroom/post/{$}", ex.newPost) + mux.HandleFunc("GET /countdown/{$}", ex.countdown) + mux.HandleFunc("GET /countdown/feed/{$}", ex.feed) return mux } func (ex *example) demo(w http.ResponseWriter, r *http.Request) { - component := extempl.Page() - _ = component.Render(r.Context(), w) + ctx := r.Context() + if ex.gom { + _ = exgom.Page().Render(w) + } else { + _ = extempl.Page().Render(ctx, w) + } } -func (ex *example) chatroom(w http.ResponseWriter, r *http.Request) { - component := extempl.Chatroom() - _ = component.Render(r.Context(), w) +func (ex *example) countdown(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + if ex.gom { + _ = exgom.Countdown().Render(w) + } else { + _ = extempl.Countdown().Render(ctx, w) + } } func (ex *example) feed(w http.ResponseWriter, r *http.Request) { rc := http.NewResponseController(w) - - feedTimeout := time.NewTimer(25 * time.Second) - ctx := r.Context() - room := ex.room.Join() - defer func() { - // Stop and drain the timer - feedTimeout.Stop() - - // Leave the room - ex.room.Leave(room) - }() w.Header().Set("Content-Type", "text/event-stream") w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Connection", "keep-alive") - for { - select { - case <-feedTimeout.C: - fmt.Println("end feed") - component := extempl.Entry() - - _, _ = fmt.Fprintf(w, "event: %s\n", chatroom.EndEvent) - _, _ = fmt.Fprint(w, "data: ") - _ = component.Render(ctx, w) - _, _ = fmt.Fprint(w, "\n\n") - _ = rc.Flush() - return - case <-ctx.Done(): - fmt.Println("ctx done") - return - case msg := <-room: - fmt.Println("msg", msg.Message) - component := extempl.ChatMessage(msg.Message) + for i := range 5 { + countMessage := strconv.Itoa(5 - i) + ex.sendEvent(w, shared.CountdownEvent, func() { + if ex.gom { + _ = exgom.Message(countMessage).Render(w) + } else { + _ = extempl.Message(countMessage).Render(ctx, w) + } + }) + _ = rc.Flush() + time.Sleep(time.Second) + } - _, _ = fmt.Fprintf(w, "event: %s\n", chatroom.ChatEvent) - _, _ = fmt.Fprint(w, "data: ") - _ = component.Render(ctx, w) - _, _ = fmt.Fprint(w, "\n\n") - _ = rc.Flush() + ex.sendEvent(w, shared.CountdownEvent, func() { + if ex.gom { + _ = exgom.Message("Blastoff!").Render(w) + } else { + _ = extempl.Message("Blastoff!").Render(ctx, w) } - } + }) + _ = rc.Flush() + time.Sleep(2 * time.Second) + + ex.sendEvent(w, shared.ResetEvent, func() { + if ex.gom { + _ = exgom.Trigger().Render(w) + } else { + _ = extempl.Trigger().Render(ctx, w) + } + }) } -func (ex *example) newPost(w http.ResponseWriter, r *http.Request) { - message := r.FormValue("message") - if message == "" { - w.WriteHeader(http.StatusBadRequest) - return - } - if len([]rune(message)) > 140 { - w.WriteHeader(http.StatusRequestEntityTooLarge) - return - } - - ex.room.Send(message) - w.WriteHeader(http.StatusNoContent) +func (ex *example) sendEvent(w http.ResponseWriter, event string, render func()) { + _, _ = fmt.Fprintf(w, "event: %s\n", event) + _, _ = fmt.Fprint(w, "data: ") + render() + _, _ = fmt.Fprint(w, "\n\n") } diff --git a/examples/web/web.go b/examples/web/web.go index dee8b7c..80b632d 100644 --- a/examples/web/web.go +++ b/examples/web/web.go @@ -7,13 +7,8 @@ import ( "net/http" "os" - "github.com/will-wow/typed-htmx-go/examples/web/activesearch" - "github.com/will-wow/typed-htmx-go/examples/web/bulkupdate" - "github.com/will-wow/typed-htmx-go/examples/web/classtools_ex" - "github.com/will-wow/typed-htmx-go/examples/web/clicktoedit" "github.com/will-wow/typed-htmx-go/examples/web/examples" - "github.com/will-wow/typed-htmx-go/examples/web/progressbar" - "github.com/will-wow/typed-htmx-go/examples/web/sse_ex" + "github.com/will-wow/typed-htmx-go/examples/web/examples/registry" ) //go:embed "static" @@ -53,12 +48,10 @@ func (h *Handler) routes() http.Handler { mux.HandleFunc("/{$}", templIndexRoutes.NewIndexHandler) mux.HandleFunc("/examples/gomponents/{$}", gomIndexRoutes.NewIndexHandler) - delegateExample(mux, "click-to-edit", clicktoedit.NewHandler) - delegateExample(mux, "bulk-update", bulkupdate.NewHandler) - delegateExample(mux, "active-search", activesearch.NewHandler) - delegateExample(mux, "progress-bar", progressbar.NewHandler) - delegateExample(mux, "class-tools", classtools_ex.NewHandler) - delegateExample(mux, "sse", sse_ex.NewHandler) + + for _, ex := range registry.Examples { + delegateExample(mux, ex.Slug, ex.Handler) + } return h.recoverPanic(h.logRequest(mux)) }