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) }