diff --git a/framework/h/lifecycle.go b/framework/h/lifecycle.go index 2535c1f..25fe5e5 100644 --- a/framework/h/lifecycle.go +++ b/framework/h/lifecycle.go @@ -119,6 +119,16 @@ func SetText(text string) SimpleJsCommand { return SimpleJsCommand{Command: fmt.Sprintf("this.innerText = '%s'", text)} } +func SetTextOnChildren(selector, text string) ComplexJsCommand { + // language=JavaScript + return EvalJs(fmt.Sprintf(` + var children = self.querySelectorAll('%s'); + children.forEach(function(child) { + child.innerText = '%s'; + }); + `, selector, text)) +} + func Increment(amount int) SimpleJsCommand { // language=JavaScript return SimpleJsCommand{Command: fmt.Sprintf("this.innerText = parseInt(this.innerText) + %d", amount)} @@ -176,6 +186,47 @@ func ToggleClassOnElement(selector, class string) ComplexJsCommand { )) } +func SetClassOnChildren(selector, class string) ComplexJsCommand { + // language=JavaScript + return EvalJs(fmt.Sprintf(` + var children = self.querySelectorAll('%s'); + children.forEach(function(child) { + child.classList.add('%s'); + }); + `, selector, class)) +} + +func SetClassOnSibling(selector, class string) ComplexJsCommand { + // language=JavaScript + return EvalJs(fmt.Sprintf(` + var siblings = self.parentElement.querySelectorAll('%s'); + siblings.forEach(function(sibling) { + sibling.classList.remove('%s'); + }); + self.classList.add('%s'); + `, selector, class, class)) +} + +func RemoveClassOnSibling(selector, class string) ComplexJsCommand { + // language=JavaScript + return EvalJs(fmt.Sprintf(` + var siblings = self.parentElement.querySelectorAll('%s'); + siblings.forEach(function(sibling) { + sibling.classList.remove('%s'); + }); + `, selector, class)) +} + +func RemoveClassOnChildren(selector, class string) ComplexJsCommand { + // language=JavaScript + return EvalJs(fmt.Sprintf(` + var children = self.querySelectorAll('%s'); + children.forEach(function(child) { + child.classList.remove('%s'); + }); + `, selector, class)) +} + func Alert(text string) SimpleJsCommand { // language=JavaScript return SimpleJsCommand{Command: fmt.Sprintf("alert('%s')", text)} diff --git a/framework/js/commands.go b/framework/js/commands.go index 5145977..d81c6ed 100644 --- a/framework/js/commands.go +++ b/framework/js/commands.go @@ -6,12 +6,17 @@ var AddAttribute = h.AddAttribute var RemoveAttribute = h.RemoveAttribute var AddClass = h.AddClass var SetText = h.SetText +var SetTextOnChildren = h.SetTextOnChildren var Increment = h.Increment var SetInnerHtml = h.SetInnerHtml var SetOuterHtml = h.SetOuterHtml var SetDisabled = h.SetDisabled var RemoveClass = h.RemoveClass var Alert = h.Alert +var SetClassOnChildren = h.SetClassOnChildren +var RemoveClassOnChildren = h.RemoveClassOnChildren +var SetClassOnSibling = h.SetClassOnSibling +var RemoveClassOnSibling = h.RemoveClassOnSibling var Remove = h.Remove var EvalJs = h.EvalJs var InjectScript = h.InjectScript diff --git a/htmgo-site/pages/examples.go b/htmgo-site/pages/examples.go index 5bbf9c2..481e365 100644 --- a/htmgo-site/pages/examples.go +++ b/htmgo-site/pages/examples.go @@ -6,10 +6,11 @@ import ( ) type Example struct { - Title string - Github string - Demo string - Image string + Title string + Github string + Demo string + Image string + Description string } var examples = []Example{ @@ -25,6 +26,12 @@ var examples = []Example{ Demo: "https://htmgo.dev", Image: "public/htmgo-site.jpg", }, + { + Title: "form with loading state", + Github: "", + Demo: "/form", + Description: "A simple form submission example with a loading state", + }, } func ExamplesPage(ctx *h.RequestContext) *h.Page { @@ -70,7 +77,7 @@ func ExampleCards() *h.Element { h.Class("text-lg text-center mb-1"), // Reduced margin at the bottom of the title h.Text(example.Title), ), - h.Div( + h.If(example.Image != "", h.Div( h.A( h.Href(example.Demo), h.Class("not-prose"), @@ -79,7 +86,8 @@ func ExampleCards() *h.Element { h.Class("md:w-full rounded-md mx-auto"), ), ), // Ensures image is centered within the card - ), + )), + h.If(example.Description != "", h.Pf(example.Description)), h.Div( h.Div( h.Class("flex gap-2 justify-center mt-2"), // Slight margin-top for spacing from the image diff --git a/htmgo-site/pages/form.go b/htmgo-site/pages/form.go new file mode 100644 index 0000000..113ad1a --- /dev/null +++ b/htmgo-site/pages/form.go @@ -0,0 +1,59 @@ +package pages + +import ( + "github.com/maddalax/htmgo/framework/h" + "github.com/maddalax/htmgo/framework/js" + "htmgo-site/pages/base" + "htmgo-site/partials" +) + +func Form(ctx *h.RequestContext) *h.Page { + return h.NewPage(base.RootPage(ctx, + h.Div( + h.Class("flex flex-col items-center justify-center p-4 gap-6"), + h.H2F("Form submission with loading state example", h.Class("text-2xl font-bold")), + h.Form( + h.TriggerChildren(), + h.PostPartial(partials.SubmitForm), + h.Class("flex flex-col gap-2"), + h.LabelFor("name", "Your Name"), + h.Input("text", + h.Class("p-4 rounded-md border border-slate-200"), + h.Name("name"), + h.Placeholder("Name")), + SubmitButton(), + ), + ), + )) +} + +func SubmitButton() *h.Element { + buttonClasses := "rounded items-center px-3 py-2 bg-slate-800 text-white w-full text-center" + return h.Div( + h.HxBeforeRequest( + js.RemoveClassOnChildren(".loading", "hidden"), + js.SetClassOnChildren(".submit", "hidden"), + ), + h.HxAfterRequest( + js.SetClassOnChildren(".loading", "hidden"), + js.RemoveClassOnChildren(".submit", "hidden"), + ), + h.Class("flex gap-2 justify-center"), + h.Button( + h.Class("loading hidden relative text-center", buttonClasses), + Spinner(), + h.Text("Submitting..."), + ), + h.Button( + h.Class("submit", buttonClasses), + h.Text("Submit"), + ), + ) +} + +func Spinner() *h.Element { + return h.Div( + h.Class("absolute left-1 spinner spinner-border animate-spin inline-block w-6 h-6 border-4 rounded-full border-slate-200 border-t-transparent"), + h.Attribute("role", "status"), + ) +} diff --git a/htmgo-site/partials/form.go b/htmgo-site/partials/form.go new file mode 100644 index 0000000..4934822 --- /dev/null +++ b/htmgo-site/partials/form.go @@ -0,0 +1,13 @@ +package partials + +import ( + "github.com/maddalax/htmgo/framework/h" + "time" +) + +func SubmitForm(ctx *h.RequestContext) *h.Partial { + time.Sleep(time.Second * 3) + return h.NewPartial( + h.Div(h.Text("Form submitted")), + ) +}