From c8db468292d88e77965885c018cb69c41af92ef0 Mon Sep 17 00:00:00 2001
From: mikestefanello <552328+mikestefanello@users.noreply.github.com>
Date: Sat, 29 Mar 2025 09:40:04 -0400
Subject: [PATCH] Cleaned up conditional rendering.
---
pkg/ui/components/head.go | 10 +++-----
pkg/ui/pages/about.go | 4 ++++
pkg/ui/pages/contact.go | 41 ++++++++++++++++----------------
pkg/ui/pages/home.go | 49 +++++++++++++++++++++++++--------------
pkg/ui/pages/task.go | 23 +++++++++---------
5 files changed, 71 insertions(+), 56 deletions(-)
diff --git a/pkg/ui/components/head.go b/pkg/ui/components/head.go
index 56536b4..b767a0b 100644
--- a/pkg/ui/components/head.go
+++ b/pkg/ui/components/head.go
@@ -27,17 +27,13 @@ func JS(r *ui.Request) Node {
})
`
- var csrf Node
-
- if len(r.CSRF) > 0 {
- csrf = Script(Raw(fmt.Sprintf(htmxCSRF, r.CSRF)))
- }
-
return Group{
Script(Src("https://unpkg.com/htmx.org@2.0.0/dist/htmx.min.js")),
Script(Src("https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"), Defer()),
Script(Raw(htmxErr)),
- csrf,
+ Iff(len(r.CSRF) > 0, func() Node {
+ return Script(Raw(fmt.Sprintf(htmxCSRF, r.CSRF)))
+ }),
}
}
diff --git a/pkg/ui/pages/about.go b/pkg/ui/pages/about.go
index 5c68be8..6518423 100644
--- a/pkg/ui/pages/about.go
+++ b/pkg/ui/pages/about.go
@@ -49,6 +49,10 @@ func About(ctx echo.Context) error {
Title: "Ent",
Body: "Simple, yet powerful ORM for modeling and querying data. Visit entgo.io to learn more.",
},
+ {
+ Title: "Gomponents",
+ Body: "HTML components written in pure Go. They render to HTML 5, and make it easy for you to build reusable components. Visit gomponents.com to learn more.",
+ },
},
),
}
diff --git a/pkg/ui/pages/contact.go b/pkg/ui/pages/contact.go
index cef7f32..915a087 100644
--- a/pkg/ui/pages/contact.go
+++ b/pkg/ui/pages/contact.go
@@ -15,26 +15,27 @@ func ContactUs(ctx echo.Context, form *forms.Contact) error {
r.Title = "Contact us"
r.Metatags.Description = "Get in touch with us."
- g := make(Group, 0)
-
- if r.Htmx.Target != "contact" {
- g = append(g, components.Message(
- "is-link",
- "",
- Group{
- P(Text("This is an example of a form with inline, server-side validation and HTMX-powered AJAX submissions without writing a single line of JavaScript.")),
- P(Text("Only the form below will update async upon submission.")),
- }))
- }
-
- if form.IsDone() {
- g = append(g, components.Message(
- "is-large is-success",
- "Thank you!",
- Text("No email was actually sent but this entire operation was handled server-side and degrades without JavaScript enabled."),
- ))
- } else {
- g = append(g, form.Render(r))
+ g := Group{
+ Iff(r.Htmx.Target != "contact", func() Node {
+ return components.Message(
+ "is-link",
+ "",
+ Group{
+ P(Text("This is an example of a form with inline, server-side validation and HTMX-powered AJAX submissions without writing a single line of JavaScript.")),
+ P(Text("Only the form below will update async upon submission.")),
+ },
+ )
+ }),
+ Iff(form.IsDone(), func() Node {
+ return components.Message(
+ "is-large is-success",
+ "Thank you!",
+ Text("No email was actually sent but this entire operation was handled server-side and degrades without JavaScript enabled."),
+ )
+ }),
+ Iff(!form.IsDone(), func() Node {
+ return form.Render(r)
+ }),
}
return r.Render(layouts.Primary, g)
diff --git a/pkg/ui/pages/home.go b/pkg/ui/pages/home.go
index 51bab86..9ae14f0 100644
--- a/pkg/ui/pages/home.go
+++ b/pkg/ui/pages/home.go
@@ -18,17 +18,25 @@ func Home(ctx echo.Context, posts *models.Posts) error {
r.Metatags.Description = "This is the home page."
r.Metatags.Keywords = []string{"Software", "Coding", "Go"}
- g := make(Group, 0)
+ // This pages helps to illustrate the different options you can take when using HTMX to introduce interactivity
+ // to your web application. The following three options are available, but here, we're opting for the first one.
+ // 1) Highly-optimized and progressive enhancement:
+ // This is highly-optimized because the server is doing the least amount of work possible, only rendering
+ // the least amount possible based on the incoming request. It's possible that even your route handler would
+ // want to check the HTMX request in order to limit what it does. With HTMX, it's possible to still return a
+ // normal, full page, but use hx-select to pluck out only the part you want to re-render. It requires some extra
+ // condition checks and code but performance is improved. Progressive enhancement refers to having a fully
+ // functional web app, even if JS was disabled, but providing the enhancement if JS is enabled. All of these
+ // examples should continue to work fine without JS.
+ // 2) Not optimized and progressive enhancement:
+ // As mentioned previously, you can remove all of these conditions, re-render the entire page for every request,
+ // and rely on HTMX's hx-select to only replace what you want to (ie, the posts).
+ // 3) Optimized and partial renderings:
+ // You could have a separate route that is only for fetching posts while paging, and that would render only
+ // that partial HTML, which HTMX would then use to inject in to this page.
- if r.Htmx.Target != "posts" {
- var hello string
- if r.IsAuth {
- hello = fmt.Sprintf("Hello, %s", r.AuthUser.Name)
- } else {
- hello = "Hello"
- }
-
- g = append(g,
+ headerMsg := func() Node {
+ return Group{
Section(
Class("hero is-info welcome is-small mb-5"),
Div(
@@ -37,7 +45,10 @@ func Home(ctx echo.Context, posts *models.Posts) error {
Class("container"),
H1(
Class("title"),
- Text(hello),
+ Iff(r.IsAuth, func() Node {
+ return Text(fmt.Sprintf("Hello, %s", r.AuthUser.Name))
+ }),
+ If(!r.IsAuth, Text("Hello")),
),
H2(
Class("subtitle"),
@@ -49,20 +60,24 @@ func Home(ctx echo.Context, posts *models.Posts) error {
),
H2(Class("title"), Text("Recent posts")),
H3(Class("subtitle"), Text("Below is an example of both paging and AJAX fetching using HTMX")),
- )
+ }
}
- g = append(g, posts.Render(r.Path(routenames.Home)))
-
- if r.Htmx.Target != "posts" {
- g = append(g, Message(
+ filesMsg := func() Node {
+ return Message(
"is-small is-warning mt-5",
"Serving files",
Group{
Text("In the example posts above, check how the file URL contains a cache-buster query parameter which changes only when the app is restarted. "),
Text("Static files also contain cache-control headers which are configured via middleware."),
},
- ))
+ )
+ }
+
+ g := Group{
+ Iff(r.Htmx.Target != "posts", headerMsg),
+ posts.Render(r.Path(routenames.Home)),
+ Iff(r.Htmx.Target != "posts", filesMsg),
}
return r.Render(layouts.Primary, g)
diff --git a/pkg/ui/pages/task.go b/pkg/ui/pages/task.go
index aed83ce..52c4d5e 100644
--- a/pkg/ui/pages/task.go
+++ b/pkg/ui/pages/task.go
@@ -15,19 +15,18 @@ func AddTask(ctx echo.Context, form *forms.Task) error {
r.Title = "Create a task"
r.Metatags.Description = "Test creating a task to see how it works."
- g := make(Group, 0)
-
- if r.Htmx.Target != "task" {
- g = append(g, components.Message(
- "is-link",
- "",
- Group{
- P(Raw("Submitting this form will create an ExampleTask in the task queue. After the specified delay, the message will be logged by the queue processor.")),
- P(Text("See pkg/tasks and the README for more information.")),
- }))
+ g := Group{
+ Iff(r.Htmx.Target != "task", func() Node {
+ return components.Message(
+ "is-link",
+ "",
+ Group{
+ P(Raw("Submitting this form will create an ExampleTask in the task queue. After the specified delay, the message will be logged by the queue processor.")),
+ P(Text("See pkg/tasks and the README for more information.")),
+ })
+ }),
+ form.Render(r),
}
- g = append(g, form.Render(r))
-
return r.Render(layouts.Primary, g)
}