diff --git a/examples/templ/.gitignore b/examples/templ/.gitignore
new file mode 100644
index 0000000..0725909
--- /dev/null
+++ b/examples/templ/.gitignore
@@ -0,0 +1,2 @@
+.task
+bin
\ No newline at end of file
diff --git a/examples/templ/.vscode/examples.code-snippets b/examples/templ/.vscode/examples.code-snippets
new file mode 100644
index 0000000..188136f
--- /dev/null
+++ b/examples/templ/.vscode/examples.code-snippets
@@ -0,0 +1,62 @@
+{
+ // Place your templ workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
+ // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
+ // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
+ // used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
+ // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
+ // Placeholders with the same ids are connected.
+ // Example:
+ "handler": {
+ "scope": "go",
+ "prefix": "handler",
+ "body": [
+ "package ${TM_DIRECTORY/.*\\/(.*)$/$1/}",
+ "",
+ "import \"net/http\"",
+ "",
+ "func Handler() http.Handler {",
+ " mux := http.NewServeMux()",
+ "",
+ " mux.HandleFunc(\"GET /\", demo)",
+ "",
+ " return mux",
+ "}",
+ "",
+ "func demo(w http.ResponseWriter, r *http.Request) {",
+ " component := page()",
+ " w.WriteHeader(http.StatusOK)",
+ " _ = component.Render(r.Context(), w)",
+ "}",
+ "",
+ ],
+ "description": "example handler file",
+ },
+ "page": {
+ "scope": "templ",
+ "prefix": "page",
+ "body": [
+ "package bulkupdate",
+ "",
+ "import (",
+ " \"github.com/lithammer/dedent\"",
+ "",
+ " \"github.com/will-wow/typed-htmx-go/examples/templ/internal/web/layout\"",
+ " \"github.com/will-wow/typed-htmx-go/hx\"",
+ " \"github.com/will-wow/typed-htmx-go/hx/swap\"",
+ ")",
+ "",
+ "templ page() {",
+ " @layout.Base(\"$1\") {",
+ "
$1 ",
+ " ",
+ " Desc",
+ "
",
+ " ",
+ " { dedent.Dedent(`",
+ " `) }",
+ " ",
+ " }",
+ "}",
+ ],
+ },
+}
diff --git a/examples/templ/Taskfile.yml b/examples/templ/Taskfile.yml
new file mode 100644
index 0000000..95883f2
--- /dev/null
+++ b/examples/templ/Taskfile.yml
@@ -0,0 +1,56 @@
+version: "3"
+
+tasks:
+ build:
+ desc: Build the server binary
+ deps: [gen]
+ cmds:
+ - go build -v -o ./bin/server ./cmd/server/main.go
+
+ start:
+ desc: Run built server
+ cmds:
+ - ./bin/server
+
+ dev:
+ desc: Run the server with a watch for templ, go, and static files
+ cmds:
+ - "wgo -file=.go -file=.templ -xfile=_templ.go -file=static/.+.js -file=static/.+.css task gen :: go run cmd/server/main.go"
+
+ lint:
+ desc: Run golangci-lint
+ cmds:
+ - go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.56.2 run
+
+ fmt:
+ desc: Run goimports
+ cmds:
+ - go mod tidy
+ - go run golang.org/x/tools/cmd/goimports@v0.18.0 -w -local github.com/will-wow/typed-htmx-go/examples/templ .
+
+ test:
+ desc: Run test suite
+ cmds:
+ - go test -v {{.CLI_ARGS}} ./...
+
+ test-cover:
+ desc: Run test suite with coverage
+ cmds:
+ - go test -coverpkg ./... -coverprofile=coverage.txt ./...
+ - go tool cover -html=coverage.txt
+
+ gen:
+ desc: Generate templ components
+ cmds:
+ - templ generate
+ # Re-format generated code
+ - go run golang.org/x/tools/cmd/goimports@v0.18.0 -w -local github.com/will-wow/typed-htmx-go/examples/templ ./
+ sources:
+ - "**/*.templ"
+ generates:
+ - "**/*_templ.go"
+
+ tools:
+ desc: Install tools
+ cmds:
+ - go install github.com/a-h/templ/cmd/templ@latest
diff --git a/examples/templ/api/README.md b/examples/templ/api/README.md
new file mode 100644
index 0000000..07d5baa
--- /dev/null
+++ b/examples/templ/api/README.md
@@ -0,0 +1,5 @@
+# api
+
+Exposes the templ/examples as a Vercel function.
+
+See the [Vercel docs](https://vercel.com/docs/functions/runtimes/go) for more details.
diff --git a/examples/templ/api/api.go b/examples/templ/api/api.go
new file mode 100644
index 0000000..60628f8
--- /dev/null
+++ b/examples/templ/api/api.go
@@ -0,0 +1,12 @@
+package api
+
+import (
+ "net/http"
+
+ "github.com/will-wow/typed-htmx-go/examples/templ/internal/web"
+)
+
+func Handler(w http.ResponseWriter, r *http.Request) {
+ handler := web.NewHttpHandler()
+ handler.ServeHTTP(w, r)
+}
diff --git a/examples/templ/cmd/server/main.go b/examples/templ/cmd/server/main.go
new file mode 100644
index 0000000..55f6685
--- /dev/null
+++ b/examples/templ/cmd/server/main.go
@@ -0,0 +1,28 @@
+package main
+
+import (
+ "fmt"
+ "net/http"
+ "os"
+ "time"
+
+ "github.com/will-wow/typed-htmx-go/examples/templ/internal/web"
+)
+
+func main() {
+ handler := web.NewHttpHandler()
+
+ //nolint:exhaustruct
+ server := &http.Server{
+ Addr: "localhost:8080",
+ Handler: handler,
+ ReadTimeout: time.Second * 10,
+ WriteTimeout: time.Second * 10,
+ }
+
+ fmt.Printf("Listening on %v\n", server.Addr)
+ err := server.ListenAndServe()
+ if err != nil {
+ os.Exit(1)
+ }
+}
diff --git a/examples/templ/go.mod b/examples/templ/go.mod
new file mode 100644
index 0000000..a044f0b
--- /dev/null
+++ b/examples/templ/go.mod
@@ -0,0 +1,10 @@
+module github.com/will-wow/typed-htmx-go/examples/templ
+
+go 1.22.0
+
+require (
+ github.com/a-h/templ v0.2.543
+ github.com/will-wow/typed-htmx-go v0.0.3
+)
+
+require github.com/lithammer/dedent v1.1.0
diff --git a/examples/templ/go.sum b/examples/templ/go.sum
new file mode 100644
index 0000000..ca0dbb6
--- /dev/null
+++ b/examples/templ/go.sum
@@ -0,0 +1,8 @@
+github.com/a-h/templ v0.2.543 h1:8YyLvyUtf0/IE2nIwZ62Z/m2o2NqwhnMynzOL78Lzbk=
+github.com/a-h/templ v0.2.543/go.mod h1:jP908DQCwI08IrnTalhzSEH9WJqG/Q94+EODQcJGFUA=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY=
+github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
+github.com/will-wow/typed-htmx-go v0.0.3 h1:zF0ESFMm2Ry2OEe+mQ+2zcULM1aEqWaDwdyGEZWC/1w=
+github.com/will-wow/typed-htmx-go v0.0.3/go.mod h1:vBV9acu4/cjaeLc1Z2z4ZruJYpF1KdCoFebp3CWNaPA=
diff --git a/examples/templ/internal/web/bulkupdate/bulkupdate.go b/examples/templ/internal/web/bulkupdate/bulkupdate.go
new file mode 100644
index 0000000..b7b142d
--- /dev/null
+++ b/examples/templ/internal/web/bulkupdate/bulkupdate.go
@@ -0,0 +1,65 @@
+package bulkupdate
+
+import (
+ "fmt"
+ "net/http"
+)
+
+func Handler() http.Handler {
+ mux := http.NewServeMux()
+
+ mux.HandleFunc("GET /", demo)
+ mux.HandleFunc("POST /", post)
+
+ return mux
+}
+
+type userModel struct {
+ name string
+ email string
+ active bool
+}
+
+func demo(w http.ResponseWriter, r *http.Request) {
+ users := defaultUsers()
+
+ component := page(users)
+ w.WriteHeader(http.StatusOK)
+ _ = component.Render(r.Context(), w)
+}
+
+func post(w http.ResponseWriter, r *http.Request) {
+ err := r.ParseForm()
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ users := defaultUsers()
+
+ var additions int
+ var removals int
+
+ for _, user := range users {
+ if r.Form.Has(user.email) && !user.active {
+ additions++
+ } else if !r.Form.Has(user.email) && user.active {
+ removals++
+ }
+ }
+
+ toast := fmt.Sprintf("Activated %d and deactivated %d users", additions, removals)
+
+ component := updateToast(toast)
+ w.WriteHeader(http.StatusOK)
+ _ = component.Render(r.Context(), w)
+}
+
+func defaultUsers() []userModel {
+ return []userModel{
+ {name: "Joe Smith", email: "joe@smith.org", active: true},
+ {name: "Angie MacDowell", email: "angie@macdowell.org", active: true},
+ {name: "Fuqua Tarkenton", email: "fuqua@tarkenton.org", active: true},
+ {name: "Kim Yee", email: "kim@yee.org", active: false},
+ }
+}
diff --git a/examples/templ/internal/web/bulkupdate/bulkupdate.templ b/examples/templ/internal/web/bulkupdate/bulkupdate.templ
new file mode 100644
index 0000000..b2f62d5
--- /dev/null
+++ b/examples/templ/internal/web/bulkupdate/bulkupdate.templ
@@ -0,0 +1,158 @@
+package bulkupdate
+
+import (
+ "time"
+
+ "github.com/lithammer/dedent"
+
+ "github.com/will-wow/typed-htmx-go/examples/templ/internal/web/layout"
+ "github.com/will-wow/typed-htmx-go/hx"
+ "github.com/will-wow/typed-htmx-go/hx/swap"
+)
+
+templ page(users []userModel) {
+ @layout.Base("Bulk Update", "bulk-update") {
+ Bulk Update
+
+ This demo shows how to implement a common pattern where rows are selected and then bulk updated. This is accomplished by putting a form around a table, with checkboxes in the table, and then including the checked values in the form submission (POST request)
:
+
+
+ { dedent.Dedent(`
+ templ table(users []userModel) {
+
+ }
+ `) }
+
+
+ The server will bulk-update the statuses based on the values of the checkboxes. We respond with a small toast message about the update to inform the user, and use ARIA to politely announce the update for accessibility.
+
+
+ { dedent.Dedent(`
+ #toast.htmx-settling {
+ opacity: 100;
+ }
+
+ #toast {
+ background: #E1F0DA;
+ opacity: 0;
+ transition: opacity 3s ease-out;
+ }
+ `) }
+
+
+ { dedent.Dedent(`
+ templ updateToast(toast string) {
+
+ { toast }
+
+ }
+ `) }
+
+
+ The cool thing is that, because HTML form inputs already manage their own state, we don’t need to re-render any part of the users table. The active users are already checked and the inactive ones unchecked!
+
+
+ You can see a working example of this code below.
+
+ Demo
+ @table(users)
+ }
+}
+
+templ table(users []userModel) {
+
+}
+
+templ updateToast(toast string) {
+
+ { toast }
+
+}
diff --git a/examples/templ/internal/web/bulkupdate/bulkupdate_templ.go b/examples/templ/internal/web/bulkupdate/bulkupdate_templ.go
new file mode 100644
index 0000000..1f4b106
--- /dev/null
+++ b/examples/templ/internal/web/bulkupdate/bulkupdate_templ.go
@@ -0,0 +1,314 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.2.543
+package bulkupdate
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import (
+ "bytes"
+ "context"
+ "io"
+ "time"
+
+ "github.com/a-h/templ"
+ "github.com/lithammer/dedent"
+
+ "github.com/will-wow/typed-htmx-go/hx"
+ "github.com/will-wow/typed-htmx-go/hx/swap"
+
+ "github.com/will-wow/typed-htmx-go/examples/templ/internal/web/layout"
+)
+
+func page(users []userModel) 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 {
+ templ_7745c5c3_Buffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Var2 := 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 {
+ templ_7745c5c3_Buffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("Bulk Update This demo shows how to implement a common pattern where rows are selected and then bulk updated. This is accomplished by putting a form around a table, with checkboxes in the table, and then including the checked values in the form submission (POST request)
:
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var3 string
+ templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(dedent.Dedent(`
+ templ table(users []userModel) {
+
+ }
+ `))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/bulkupdate/bulkupdate.templ`, Line: 62, Col: 5}
+ }
+ _, 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 server will bulk-update the statuses based on the values of the checkboxes. We respond with a small toast message about the update to inform the user, and use ARIA to politely announce the update for accessibility.
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var4 string
+ templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(dedent.Dedent(`
+ #toast.htmx-settling {
+ opacity: 100;
+ }
+
+ #toast {
+ background: #E1F0DA;
+ opacity: 0;
+ transition: opacity 3s ease-out;
+ }
+ `))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/bulkupdate/bulkupdate.templ`, Line: 78, Col: 5}
+ }
+ _, 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(" ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var5 string
+ templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(dedent.Dedent(`
+ templ updateToast(toast string) {
+
+ { toast }
+
+ }
+ `))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/bulkupdate/bulkupdate.templ`, Line: 92, Col: 5}
+ }
+ _, 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(" The cool thing is that, because HTML form inputs already manage their own state, we don’t need to re-render any part of the users table. The active users are already checked and the inactive ones unchecked!
You can see a working example of this code below.
Demo ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = table(users).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if !templ_7745c5c3_IsBuffer {
+ _, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer)
+ }
+ return templ_7745c5c3_Err
+ })
+ templ_7745c5c3_Err = layout.Base("Bulk Update", "bulk-update").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if !templ_7745c5c3_IsBuffer {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+func table(users []userModel) 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 {
+ templ_7745c5c3_Buffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var6 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var6 == nil {
+ templ_7745c5c3_Var6 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if !templ_7745c5c3_IsBuffer {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+func updateToast(toast string) 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 {
+ templ_7745c5c3_Buffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var9 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var9 == nil {
+ templ_7745c5c3_Var9 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var10 string
+ templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(toast)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/bulkupdate/bulkupdate.templ`, Line: 155, Col: 9}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if !templ_7745c5c3_IsBuffer {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
+ }
+ return templ_7745c5c3_Err
+ })
+}
diff --git a/examples/templ/internal/web/clicktoedit/clicktoedit.go b/examples/templ/internal/web/clicktoedit/clicktoedit.go
new file mode 100644
index 0000000..aa4a17f
--- /dev/null
+++ b/examples/templ/internal/web/clicktoedit/clicktoedit.go
@@ -0,0 +1,109 @@
+package clicktoedit
+
+import (
+ "net/http"
+ "strings"
+
+ "github.com/will-wow/typed-htmx-go/examples/templ/internal/web/ui"
+)
+
+type form struct {
+ ui.Form
+ FirstName string
+ LastName string
+ Email string
+}
+
+func (f *form) Validate() (ok bool) {
+ if f.FirstName == "" {
+ f.SetRequiredError("FirstName")
+ }
+ if f.LastName == "" {
+ f.SetRequiredError("LastName")
+ }
+ if f.Email == "" {
+ f.SetRequiredError("Email")
+ } else if !strings.Contains(f.Email, "@") {
+ f.SetError("Email", "Invalid email address")
+ }
+
+ return !f.HasErrors()
+}
+
+func newForm() *form {
+ return &form{
+ FirstName: "",
+ LastName: "",
+ Email: "",
+ Form: ui.NewForm(),
+ }
+}
+
+func Handler() http.Handler {
+ mux := http.NewServeMux()
+
+ mux.HandleFunc("GET /{$}", demo)
+ mux.HandleFunc("GET /view", view)
+ mux.HandleFunc("GET /edit", edit)
+ mux.HandleFunc("POST /edit", post)
+
+ return mux
+}
+
+func demo(w http.ResponseWriter, r *http.Request) {
+ form := newForm()
+
+ component := Page(form)
+
+ w.WriteHeader(http.StatusOK)
+ _ = component.Render(r.Context(), w)
+}
+
+func view(w http.ResponseWriter, r *http.Request) {
+ form := newForm()
+
+ component := ViewForm(form)
+ w.WriteHeader(http.StatusOK)
+ _ = component.Render(r.Context(), w)
+}
+
+func edit(w http.ResponseWriter, r *http.Request) {
+ err := r.ParseForm()
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ form := newForm()
+
+ component := EditForm(form)
+ w.WriteHeader(http.StatusOK)
+ _ = component.Render(r.Context(), w)
+}
+
+func post(w http.ResponseWriter, r *http.Request) {
+ err := r.ParseForm()
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ form := &form{
+ FirstName: r.Form.Get("firstName"),
+ LastName: r.Form.Get("lastName"),
+ Email: r.Form.Get("email"),
+ Form: ui.NewForm(),
+ }
+ ok := form.Validate()
+
+ if !ok {
+ component := EditForm(form)
+ w.WriteHeader(http.StatusUnprocessableEntity)
+ _ = component.Render(r.Context(), w)
+ return
+ }
+
+ component := ViewForm(form)
+ w.WriteHeader(http.StatusOK)
+ _ = component.Render(r.Context(), w)
+}
diff --git a/examples/templ/internal/web/clicktoedit/clicktoedit.templ b/examples/templ/internal/web/clicktoedit/clicktoedit.templ
new file mode 100644
index 0000000..9257c84
--- /dev/null
+++ b/examples/templ/internal/web/clicktoedit/clicktoedit.templ
@@ -0,0 +1,211 @@
+package clicktoedit
+
+import (
+ "cmp"
+ "github.com/lithammer/dedent"
+
+ "github.com/will-wow/typed-htmx-go/examples/templ/internal/web/layout"
+ "github.com/will-wow/typed-htmx-go/hx"
+ "github.com/will-wow/typed-htmx-go/hx/swap"
+)
+
+templ Page(form *form) {
+ @layout.Base("Click to edit") {
+ Click To Edit
+
+ The click to edit pattern provides a way to offer inline editing of all or part of a record without a page refresh.
+
+
+
+ This pattern starts with a UI that shows the details of a contact. The div has a button that will get the editing UI for the contact from /contact/1/edit
+
+
+
+ { dedent.Dedent(`
+ templ ViewForm(form *form) {
+
+
+ First Name
+ { cmp.Or(form.FirstName, "None") }
+ Last Name
+ { cmp.Or(form.LastName, "None") }
+ Email
+ { cmp.Or(form.Email, "None") }
+
+
+ Click To Edit
+
+
+
+
+ }
+ `) }
+
+
+
+ This returns a form that can be used to edit the contact
+
+
+
+ { dedent.Dedent(`
+ templ EditForm(form *form) {
+
+ }
+ `) }
+
+
+
+ The form issues a POST back to /edit, following the usual REST-ful pattern.
+
+
+ If there is an error, the form swaps the form with error messages in place of the edit form.
+
+
+ Demo
+ @ViewForm(form)
+ }
+}
+
+templ ViewForm(form *form) {
+
+
+ First Name
+ { cmp.Or(form.FirstName, "None") }
+ Last Name
+ { cmp.Or(form.LastName, "None") }
+ Email
+ { cmp.Or(form.Email, "None") }
+
+
+ Click To Edit
+
+
+
+
+}
+
+templ EditForm(form *form) {
+
+}
diff --git a/examples/templ/internal/web/clicktoedit/clicktoedit_templ.go b/examples/templ/internal/web/clicktoedit/clicktoedit_templ.go
new file mode 100644
index 0000000..84bd345
--- /dev/null
+++ b/examples/templ/internal/web/clicktoedit/clicktoedit_templ.go
@@ -0,0 +1,413 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.2.543
+package clicktoedit
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import (
+ "bytes"
+ "cmp"
+ "context"
+ "io"
+
+ "github.com/a-h/templ"
+ "github.com/lithammer/dedent"
+
+ "github.com/will-wow/typed-htmx-go/hx"
+ "github.com/will-wow/typed-htmx-go/hx/swap"
+
+ "github.com/will-wow/typed-htmx-go/examples/templ/internal/web/layout"
+)
+
+func Page(form *form) 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 {
+ templ_7745c5c3_Buffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Var2 := 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 {
+ templ_7745c5c3_Buffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("Click To Edit The click to edit pattern provides a way to offer inline editing of all or part of a record without a page refresh.
This pattern starts with a UI that shows the details of a contact. The div has a button that will get the editing UI for the contact from /contact/1/edit ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var3 string
+ templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(dedent.Dedent(`
+ templ ViewForm(form *form) {
+
+
+ First Name
+ { cmp.Or(form.FirstName, "None") }
+ Last Name
+ { cmp.Or(form.LastName, "None") }
+ Email
+ { cmp.Or(form.Email, "None") }
+
+
+ Click To Edit
+
+
+
+
+ }
+ `))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/clicktoedit/clicktoedit.templ`, Line: 41, Col: 5}
+ }
+ _, 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(" This returns a form that can be used to edit the contact ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var4 string
+ templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(dedent.Dedent(`
+ templ EditForm(form *form) {
+
+ }
+ `))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/clicktoedit/clicktoedit.templ`, Line: 114, Col: 5}
+ }
+ _, 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("The form issues a POST back to /edit, following the usual REST-ful pattern. If there is an error, the form swaps the form with error messages in place of the edit form. Demo ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = ViewForm(form).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if !templ_7745c5c3_IsBuffer {
+ _, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer)
+ }
+ return templ_7745c5c3_Err
+ })
+ templ_7745c5c3_Err = layout.Base("Click to edit").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if !templ_7745c5c3_IsBuffer {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+func ViewForm(form *form) 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 {
+ templ_7745c5c3_Buffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var5 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var5 == nil {
+ templ_7745c5c3_Var5 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("First Name ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var6 string
+ templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(cmp.Or(form.FirstName, "None"))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/clicktoedit/clicktoedit.templ`, Line: 133, Col: 39}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" Last Name ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var7 string
+ templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(cmp.Or(form.LastName, "None"))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/clicktoedit/clicktoedit.templ`, Line: 135, Col: 38}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" Email ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var8 string
+ templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(cmp.Or(form.Email, "None"))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/clicktoedit/clicktoedit.templ`, Line: 137, Col: 35}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" Click To Edit
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if !templ_7745c5c3_IsBuffer {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+func EditForm(form *form) 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 {
+ templ_7745c5c3_Buffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var9 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var9 == nil {
+ templ_7745c5c3_Var9 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if !templ_7745c5c3_IsBuffer {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
+ }
+ return templ_7745c5c3_Err
+ })
+}
diff --git a/examples/templ/internal/web/error.templ b/examples/templ/internal/web/error.templ
new file mode 100644
index 0000000..5a1c6d6
--- /dev/null
+++ b/examples/templ/internal/web/error.templ
@@ -0,0 +1,19 @@
+package web
+
+import (
+ "github.com/will-wow/typed-htmx-go/examples/templ/internal/web/layout"
+)
+
+templ notFoundPage() {
+ @layout.Base("Not Found") {
+ Not Found
+ The page you are looking for does not exist.
+ }
+}
+
+templ serverErrorPage(err string) {
+ @layout.Base("Server Error") {
+ Something went wrong
+ { err }
+ }
+}
diff --git a/examples/templ/internal/web/error_templ.go b/examples/templ/internal/web/error_templ.go
new file mode 100644
index 0000000..8450853
--- /dev/null
+++ b/examples/templ/internal/web/error_templ.go
@@ -0,0 +1,107 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.2.543
+package web
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import (
+ "bytes"
+ "context"
+ "io"
+
+ "github.com/a-h/templ"
+
+ "github.com/will-wow/typed-htmx-go/examples/templ/internal/web/layout"
+)
+
+func notFoundPage() 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 {
+ templ_7745c5c3_Buffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Var2 := 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 {
+ templ_7745c5c3_Buffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("Not Found The page you are looking for does not exist.
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if !templ_7745c5c3_IsBuffer {
+ _, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer)
+ }
+ return templ_7745c5c3_Err
+ })
+ templ_7745c5c3_Err = layout.Base("Not Found").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if !templ_7745c5c3_IsBuffer {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+func serverErrorPage(err string) 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 {
+ templ_7745c5c3_Buffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var3 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var3 == nil {
+ templ_7745c5c3_Var3 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Var4 := 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 {
+ templ_7745c5c3_Buffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("Something went wrong ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var5 string
+ templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(err)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/error.templ`, Line: 16, Col: 10}
+ }
+ _, 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("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if !templ_7745c5c3_IsBuffer {
+ _, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer)
+ }
+ return templ_7745c5c3_Err
+ })
+ templ_7745c5c3_Err = layout.Base("Server Error").Render(templ.WithChildren(ctx, templ_7745c5c3_Var4), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if !templ_7745c5c3_IsBuffer {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
+ }
+ return templ_7745c5c3_Err
+ })
+}
diff --git a/examples/templ/internal/web/examples/examples.go b/examples/templ/internal/web/examples/examples.go
new file mode 100644
index 0000000..e999703
--- /dev/null
+++ b/examples/templ/internal/web/examples/examples.go
@@ -0,0 +1,12 @@
+package examples
+
+import (
+ "net/http"
+)
+
+func Handler(w http.ResponseWriter, r *http.Request) {
+ component := page()
+ w.WriteHeader(http.StatusOK)
+
+ _ = component.Render(r.Context(), w)
+}
diff --git a/examples/templ/internal/web/examples/examples.templ b/examples/templ/internal/web/examples/examples.templ
new file mode 100644
index 0000000..5451048
--- /dev/null
+++ b/examples/templ/internal/web/examples/examples.templ
@@ -0,0 +1,59 @@
+package examples
+
+import (
+ "github.com/will-wow/typed-htmx-go/examples/templ/internal/web/layout"
+)
+
+templ page() {
+ @layout.Base("") {
+ UI Examples
+
+ Below are a set of UX patterns implemented in htmx with minimal HTML and styling.
+
+
+ These are ported from the
+
+ htmx examples
+
+ and are intended showcase the use of hx
when building HTMX applications.
+
+
+ You can copy and paste them and then adjust them for your needs.
+
+
+
+
+ Pattern
+ Description
+
+
+
+ @exampleRow(
+ "/examples/click-to-edit",
+ "Click To Edit",
+ "Demonstrates inline editing of a data object",
+ )
+ @exampleRow(
+ "/examples/bulk-update",
+ "Bulk Update",
+ "Demonstrates bulk updating of multiple rows of data",
+ )
+
+
+ }
+}
+
+templ exampleRow(link, name, description string) {
+
+
+ { name }
+
+
+ { description }
+
+
+}
diff --git a/examples/templ/internal/web/examples/examples_templ.go b/examples/templ/internal/web/examples/examples_templ.go
new file mode 100644
index 0000000..942152a
--- /dev/null
+++ b/examples/templ/internal/web/examples/examples_templ.go
@@ -0,0 +1,134 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.2.543
+package examples
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import (
+ "bytes"
+ "context"
+ "io"
+
+ "github.com/a-h/templ"
+
+ "github.com/will-wow/typed-htmx-go/examples/templ/internal/web/layout"
+)
+
+func page() 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 {
+ templ_7745c5c3_Buffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Var2 := 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 {
+ templ_7745c5c3_Buffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("UI Examples Below are a set of UX patterns implemented in htmx with minimal HTML and styling.
These are ported from the htmx examples and are intended showcase the use of hx
when building HTMX applications.
You can copy and paste them and then adjust them for your needs.
Pattern Description ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = exampleRow(
+ "/examples/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/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 = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if !templ_7745c5c3_IsBuffer {
+ _, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer)
+ }
+ return templ_7745c5c3_Err
+ })
+ templ_7745c5c3_Err = layout.Base("").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if !templ_7745c5c3_IsBuffer {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+func exampleRow(link, name, description string) 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 {
+ templ_7745c5c3_Buffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var3 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var3 == nil {
+ templ_7745c5c3_Var3 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ 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: `internal/web/examples/examples.templ`, Line: 52, Col: 41}
+ }
+ _, 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(" ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ 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: `internal/web/examples/examples.templ`, Line: 55, Col: 16}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if !templ_7745c5c3_IsBuffer {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
+ }
+ return templ_7745c5c3_Err
+ })
+}
diff --git a/examples/templ/internal/web/layout/layout.templ b/examples/templ/internal/web/layout/layout.templ
new file mode 100644
index 0000000..5d372fc
--- /dev/null
+++ b/examples/templ/internal/web/layout/layout.templ
@@ -0,0 +1,88 @@
+package layout
+
+import (
+ "github.com/will-wow/typed-htmx-go/hx"
+)
+
+templ Base(title string, className ...string) {
+
+
+
+
+
+ if title == "" {
+ HX | Examples
+ } else {
+ { title } | HX | Examples
+ }
+
+
+
+
+
+
+
+
+
+
+
+ @nav()
+ { children... }
+
+
+
+
+}
+
+templ nav() {
+
+
+
+
+}
diff --git a/examples/templ/internal/web/layout/layout_templ.go b/examples/templ/internal/web/layout/layout_templ.go
new file mode 100644
index 0000000..3da587b
--- /dev/null
+++ b/examples/templ/internal/web/layout/layout_templ.go
@@ -0,0 +1,131 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.2.543
+package layout
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import (
+ "bytes"
+ "context"
+ "io"
+
+ "github.com/a-h/templ"
+
+ "github.com/will-wow/typed-htmx-go/hx"
+)
+
+func Base(title string, className ...string) 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 {
+ templ_7745c5c3_Buffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if title == "" {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("HX | Examples ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ } else {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var2 string
+ templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/layout/layout.templ`, Line: 15, Col: 18}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" | HX | Examples ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var3 = []any{className}
+ templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var3...)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = nav().Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templ_7745c5c3_Var1.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 {
+ return templ_7745c5c3_Err
+ }
+ if !templ_7745c5c3_IsBuffer {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+func nav() 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 {
+ templ_7745c5c3_Buffer = templ.GetBuffer()
+ defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var4 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var4 == nil {
+ templ_7745c5c3_Var4 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if !templ_7745c5c3_IsBuffer {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
+ }
+ return templ_7745c5c3_Err
+ })
+}
diff --git a/examples/templ/internal/web/static/main.css b/examples/templ/internal/web/static/main.css
new file mode 100644
index 0000000..5c0dbdb
--- /dev/null
+++ b/examples/templ/internal/web/static/main.css
@@ -0,0 +1,13 @@
+/* /examples/bulk-update */
+
+.bulk-update #toast.htmx-settling {
+ opacity: 100;
+}
+
+.bulk-update #toast {
+ background: var(--pico-contrast-background);
+ color: var(--pico-contrast-inverse);
+ padding: 0.1rem 0.2rem;
+ opacity: 0;
+ transition: opacity 3s ease-out;
+}
diff --git a/examples/templ/internal/web/static/main.js b/examples/templ/internal/web/static/main.js
new file mode 100644
index 0000000..5781250
--- /dev/null
+++ b/examples/templ/internal/web/static/main.js
@@ -0,0 +1,18 @@
+function main() {
+ document.body.addEventListener("htmx:beforeSwap", function (evt) {
+ if (evt.detail.xhr.status === 404) {
+ // alert the user when a 404 occurs (maybe use a nicer mechanism than alert())
+ alert("Error: Could Not Find Resource");
+ } else if (evt.detail.xhr.status === 422) {
+ // allow 422 responses to swap as we are using this as a signal that
+ // a form was submitted with bad data and want to rerender with the
+ // errors
+ //
+ // set isError to false to avoid error logging in console
+ evt.detail.shouldSwap = true;
+ evt.detail.isError = false;
+ }
+ });
+}
+
+main();
diff --git a/examples/templ/internal/web/ui/ui.go b/examples/templ/internal/web/ui/ui.go
new file mode 100644
index 0000000..aeec17b
--- /dev/null
+++ b/examples/templ/internal/web/ui/ui.go
@@ -0,0 +1,53 @@
+// package ui holds shared ui helpers
+package ui
+
+func HasError(s string) string {
+ if s == "" {
+ return ""
+ }
+ return "true"
+}
+
+func BoolToString(b bool) string {
+ if b {
+ return "true"
+ }
+ return "false"
+}
+
+type Form struct {
+ errors map[string]string
+}
+
+func NewForm() Form {
+ return Form{
+ errors: make(map[string]string),
+ }
+}
+
+func (f *Form) HasErrors() bool {
+ return len(f.errors) > 0
+}
+
+func (f *Form) HasError(field string) bool {
+ return f.errors[field] != ""
+}
+
+func (f *Form) SetError(field string, message string) {
+ f.errors[field] = message
+}
+
+func (f *Form) GetError(field string) string {
+ return f.errors[field]
+}
+
+func (f *Form) SetRequiredError(field string) {
+ f.errors[field] = "This field is required"
+}
+
+func (f *Form) AriaInvalid(field string) string {
+ if f.errors[field] != "" {
+ return "true"
+ }
+ return ""
+}
diff --git a/examples/templ/internal/web/web.go b/examples/templ/internal/web/web.go
new file mode 100644
index 0000000..c6130d8
--- /dev/null
+++ b/examples/templ/internal/web/web.go
@@ -0,0 +1,92 @@
+package web
+
+import (
+ "embed"
+ "fmt"
+ "log/slog"
+ "net/http"
+ "os"
+
+ "github.com/will-wow/typed-htmx-go/examples/templ/internal/web/bulkupdate"
+ "github.com/will-wow/typed-htmx-go/examples/templ/internal/web/clicktoedit"
+ "github.com/will-wow/typed-htmx-go/examples/templ/internal/web/examples"
+)
+
+//go:embed "static"
+var staticFiles embed.FS
+
+type Handler struct {
+ logger *slog.Logger
+}
+
+func NewHttpHandler() http.Handler {
+ logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
+
+ handler := &Handler{
+ logger: logger,
+ }
+
+ return handler.routes()
+}
+
+func (h *Handler) routes() http.Handler {
+ mux := http.NewServeMux()
+
+ // Catch-all
+ mux.HandleFunc("/", notFound)
+
+ // Set up a in-memory file server for the embedded static files.
+ fileServer := http.FileServerFS(staticFiles)
+ mux.Handle("GET /static/", fileServer)
+
+ mux.HandleFunc("/{$}", examples.Handler)
+ delegateExample(mux, "/examples/click-to-edit", clicktoedit.Handler())
+ delegateExample(mux, "/examples/bulk-update", bulkupdate.Handler())
+
+ return h.recoverPanic(h.logRequest(mux))
+}
+
+func delegateExample(mux *http.ServeMux, path string, handler http.Handler) {
+ mux.Handle(path+"/", http.StripPrefix(path, handler))
+}
+
+func (h *Handler) logRequest(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ var (
+ ip = r.RemoteAddr
+ proto = r.Proto
+ method = r.Method
+ uri = r.URL.RequestURI()
+ )
+
+ h.logger.Info("received request", "ip", ip, "proto", proto, "method", method, "uri", uri)
+
+ next.ServeHTTP(w, r)
+ })
+}
+
+func notFound(w http.ResponseWriter, r *http.Request) {
+ component := notFoundPage()
+ w.WriteHeader(http.StatusNotFound)
+ _ = component.Render(r.Context(), w)
+}
+
+func (h *Handler) recoverPanic(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // Create a deferred function (which will always be run in the event
+ // of a panic as Go unwinds the stack).
+ defer func() {
+ // Use the builtin recover function to check if there has been a
+ // panic or not. If there has...
+ if err := recover(); err != nil {
+ // Set a "Connection: close" header on the response.
+ w.Header().Set("Connection", "close")
+ component := serverErrorPage(fmt.Sprintf("%s", err))
+ w.WriteHeader(http.StatusInternalServerError)
+ _ = component.Render(r.Context(), w)
+ }
+ }()
+
+ next.ServeHTTP(w, r)
+ })
+}
diff --git a/go.work b/go.work
new file mode 100644
index 0000000..7903165
--- /dev/null
+++ b/go.work
@@ -0,0 +1,6 @@
+go 1.22.0
+
+use (
+ .
+ ./examples/templ
+)
diff --git a/go.work.sum b/go.work.sum
new file mode 100644
index 0000000..10169a5
--- /dev/null
+++ b/go.work.sum
@@ -0,0 +1,21 @@
+github.com/a-h/lexical v0.0.53/go.mod h1:d73jw5cgKXuYypRozNBuxRNFrTWQ3y5hVMG7rUjh1Qw=
+github.com/a-h/parse v0.0.0-20230402144745-e6c8bc86e846/go.mod h1:3mnrkvGpurZ4ZrTDbYU84xhwXW2TjTKShSwjRi2ihfQ=
+github.com/a-h/protocol v0.0.0-20230224160810-b4eec67c1c22/go.mod h1:Gm0KywveHnkiIhqFSMZglXwWZRQICg3KDWLYdglv/d8=
+github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
+github.com/cli/browser v1.2.0/go.mod h1:xFFnXLVcAyW9ni0cuo6NnrbCP75JxJ0RO7VtCBiH/oI=
+github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
+github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM=
+github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
+github.com/segmentio/encoding v0.3.6/go.mod h1:n0JeuIqEQrQoPDGsjo8UNd1iA0U8d8+oHAA4E3G3OxM=
+go.lsp.dev/jsonrpc2 v0.10.0/go.mod h1:fmEzIdXPi/rf6d4uFcayi8HpFP1nBF99ERP1htC72Ac=
+go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2/go.mod h1:gtSHRuYfbCT0qnbLnovpie/WEmqyJ7T4n6VXiFMBtcw=
+go.lsp.dev/uri v0.3.0/go.mod h1:P5sbO1IQR+qySTWOCnhnK7phBx+W3zbLqSMDJNTw88I=
+go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
+go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
+go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=