add some comments

This commit is contained in:
maddalax 2024-10-25 21:59:17 -05:00
parent f06feffb9e
commit cf76ca4f98
10 changed files with 131 additions and 0 deletions

View file

@ -47,6 +47,10 @@ func (c *RequestContext) Get(key string) interface{} {
return c.kv[key] return c.kv[key]
} }
// ServiceLocator returns the service locator to register and retrieve services
// Usage:
// service.Set[db.Queries](locator, service.Singleton, db.Provide)
// service.Get[db.Queries](locator)
func (c *RequestContext) ServiceLocator() *service.Locator { func (c *RequestContext) ServiceLocator() *service.Locator {
return c.locator return c.locator
} }
@ -62,6 +66,7 @@ type App struct {
Router *chi.Mux Router *chi.Mux
} }
// Start starts the htmgo server
func Start(opts AppOpts) { func Start(opts AppOpts) {
router := chi.NewRouter() router := chi.NewRouter()
instance := App{ instance := App{

View file

@ -1,5 +1,6 @@
package h package h
// Unique returns a new slice with only unique items.
func Unique[T any](slice []T, key func(item T) string) []T { func Unique[T any](slice []T, key func(item T) string) []T {
var result []T var result []T
seen := make(map[string]bool) seen := make(map[string]bool)
@ -13,6 +14,7 @@ func Unique[T any](slice []T, key func(item T) string) []T {
return result return result
} }
// Filter returns a new slice with only items that match the predicate.
func Filter[T any](slice []T, predicate func(item T) bool) []T { func Filter[T any](slice []T, predicate func(item T) bool) []T {
var result []T var result []T
for _, v := range slice { for _, v := range slice {
@ -23,6 +25,7 @@ func Filter[T any](slice []T, predicate func(item T) bool) []T {
return result return result
} }
// Map returns a new slice with the results of the mapper function.
func Map[T, U any](slice []T, mapper func(item T) U) []U { func Map[T, U any](slice []T, mapper func(item T) U) []U {
var result []U var result []U
for _, v := range slice { for _, v := range slice {

View file

@ -120,27 +120,34 @@ func HxIndicator(tag string) *AttributeR {
return Attribute(hx.IndicatorAttr, tag) return Attribute(hx.IndicatorAttr, tag)
} }
// TriggerChildren Adds the hx-extension="trigger-children" to an element
// See https://htmgo.dev/docs#htmx-extensions-trigger-children
func TriggerChildren() *AttributeR { func TriggerChildren() *AttributeR {
return HxExtension("trigger-children") return HxExtension("trigger-children")
} }
// HxTriggerString Adds a hx-trigger to an element based on a string of triggers
func HxTriggerString(triggers ...string) *AttributeR { func HxTriggerString(triggers ...string) *AttributeR {
trigger := hx.NewStringTrigger(strings.Join(triggers, ", ")) trigger := hx.NewStringTrigger(strings.Join(triggers, ", "))
return Attribute(hx.TriggerAttr, trigger.ToString()) return Attribute(hx.TriggerAttr, trigger.ToString())
} }
// HxTrigger Adds a hx-trigger to an element
func HxTrigger(opts ...hx.TriggerEvent) *AttributeR { func HxTrigger(opts ...hx.TriggerEvent) *AttributeR {
return Attribute(hx.TriggerAttr, hx.NewTrigger(opts...).ToString()) return Attribute(hx.TriggerAttr, hx.NewTrigger(opts...).ToString())
} }
// HxTriggerClick Adds a hx-trigger="click" to an element
func HxTriggerClick(opts ...hx.Modifier) *AttributeR { func HxTriggerClick(opts ...hx.Modifier) *AttributeR {
return HxTrigger(hx.OnClick(opts...)) return HxTrigger(hx.OnClick(opts...))
} }
// HxExtension Adds a hx-ext to an element
func HxExtension(value string) *AttributeR { func HxExtension(value string) *AttributeR {
return Attribute(hx.ExtAttr, value) return Attribute(hx.ExtAttr, value)
} }
// HxExtensions Adds multiple hx-ext to an element, separated by commas
func HxExtensions(value ...string) Ren { func HxExtensions(value ...string) Ren {
return Attribute(hx.ExtAttr, strings.Join(value, ",")) return Attribute(hx.ExtAttr, strings.Join(value, ","))
} }
@ -149,6 +156,8 @@ func JoinExtensions(attrs ...*AttributeR) Ren {
return JoinAttributes(", ", attrs...) return JoinAttributes(", ", attrs...)
} }
// JoinAttributes joins multiple attributes into a single attribute string based on a separator
// Example: JoinAttributes(", ", Attribute("hx-extension", "one"), Attribute("hx-extension", "two")) = hx-extension="one,two"
func JoinAttributes(sep string, attrs ...*AttributeR) *AttributeR { func JoinAttributes(sep string, attrs ...*AttributeR) *AttributeR {
values := make([]string, 0, len(attrs)) values := make([]string, 0, len(attrs))
for _, a := range attrs { for _, a := range attrs {
@ -193,6 +202,9 @@ func Class(value ...string) *AttributeR {
return Attribute("class", MergeClasses(value...)) return Attribute("class", MergeClasses(value...))
} }
// ClassX conditionally renders a class based on a map of class names and boolean values
// value is any non-conditional class name you'd like to add
// m is a map of class names and boolean values
func ClassX(value string, m ClassMap) Ren { func ClassX(value string, m ClassMap) Ren {
builder := strings.Builder{} builder := strings.Builder{}
builder.WriteString(value) builder.WriteString(value)
@ -206,6 +218,7 @@ func ClassX(value string, m ClassMap) Ren {
return Class(builder.String()) return Class(builder.String())
} }
// MergeClasses merges multiple classes into a single class string
func MergeClasses(classes ...string) string { func MergeClasses(classes ...string) string {
if len(classes) == 1 { if len(classes) == 1 {
return classes[0] return classes[0]

View file

@ -49,6 +49,9 @@ func startExpiredCacheCleaner(node *CachedNode) {
}() }()
} }
// Cached caches the given element for the given duration. The element is only rendered once, and then cached for the given duration.
// Please note this element is globally cached, and not per unique identifier / user.
// Use CachedPerKey to cache elements per unqiue identifier.
func Cached(duration time.Duration, cb GetElementFunc) func() *Element { func Cached(duration time.Duration, cb GetElementFunc) func() *Element {
element := &Element{ element := &Element{
tag: CachedNodeTag, tag: CachedNodeTag,
@ -64,6 +67,8 @@ func Cached(duration time.Duration, cb GetElementFunc) func() *Element {
} }
} }
// CachedPerKey caches the given element for the given duration. The element is only rendered once per key, and then cached for the given duration.
// The element is cached by the unique identifier that is returned by the callback function.
func CachedPerKey[K comparable](duration time.Duration, cb GetElementFuncWithKey[K]) func() *Element { func CachedPerKey[K comparable](duration time.Duration, cb GetElementFuncWithKey[K]) func() *Element {
element := &Element{ element := &Element{
tag: CachedNodeTag, tag: CachedNodeTag,
@ -94,6 +99,8 @@ type ByKeyEntry struct {
parent *Element parent *Element
} }
// CachedPerKeyT caches the given element for the given duration. The element is only rendered once per key, and then cached for the given duration.
// The element is cached by the unique identifier that is returned by the callback function.
func CachedPerKeyT[K comparable, T any](duration time.Duration, cb GetElementFuncTWithKey[K, T]) func(T) *Element { func CachedPerKeyT[K comparable, T any](duration time.Duration, cb GetElementFuncTWithKey[K, T]) func(T) *Element {
element := &Element{ element := &Element{
tag: CachedNodeTag, tag: CachedNodeTag,
@ -118,6 +125,8 @@ func CachedPerKeyT[K comparable, T any](duration time.Duration, cb GetElementFun
} }
} }
// CachedPerKeyT2 caches the given element for the given duration. The element is only rendered once per key, and then cached for the given duration.
// The element is cached by the unique identifier that is returned by the callback function.
func CachedPerKeyT2[K comparable, T any, T2 any](duration time.Duration, cb GetElementFuncT2WithKey[K, T, T2]) func(T, T2) *Element { func CachedPerKeyT2[K comparable, T any, T2 any](duration time.Duration, cb GetElementFuncT2WithKey[K, T, T2]) func(T, T2) *Element {
element := &Element{ element := &Element{
tag: CachedNodeTag, tag: CachedNodeTag,
@ -142,6 +151,8 @@ func CachedPerKeyT2[K comparable, T any, T2 any](duration time.Duration, cb GetE
} }
} }
// CachedPerKeyT3 caches the given element for the given duration. The element is only rendered once per key, and then cached for the given duration.
// The element is cached by the unique identifier that is returned by the callback function.
func CachedPerKeyT3[K comparable, T any, T2 any, T3 any](duration time.Duration, cb GetElementFuncT3WithKey[K, T, T2, T3]) func(T, T2, T3) *Element { func CachedPerKeyT3[K comparable, T any, T2 any, T3 any](duration time.Duration, cb GetElementFuncT3WithKey[K, T, T2, T3]) func(T, T2, T3) *Element {
element := &Element{ element := &Element{
tag: CachedNodeTag, tag: CachedNodeTag,
@ -166,6 +177,8 @@ func CachedPerKeyT3[K comparable, T any, T2 any, T3 any](duration time.Duration,
} }
} }
// CachedPerKeyT4 caches the given element for the given duration. The element is only rendered once per key, and then cached for the given duration.
// The element is cached by the unique identifier that is returned by the callback function.
func CachedPerKeyT4[K comparable, T any, T2 any, T3 any, T4 any](duration time.Duration, cb GetElementFuncT4WithKey[K, T, T2, T3, T4]) func(T, T2, T3, T4) *Element { func CachedPerKeyT4[K comparable, T any, T2 any, T3 any, T4 any](duration time.Duration, cb GetElementFuncT4WithKey[K, T, T2, T3, T4]) func(T, T2, T3, T4) *Element {
element := &Element{ element := &Element{
tag: CachedNodeTag, tag: CachedNodeTag,
@ -190,6 +203,9 @@ func CachedPerKeyT4[K comparable, T any, T2 any, T3 any, T4 any](duration time.D
} }
} }
// CachedT caches the given element for the given duration. The element is only rendered once, and then cached for the given duration.
// Please note this element is globally cached, and not per unique identifier / user.
// Use CachedPerKey to cache elements per unqiue identifier.
func CachedT[T any](duration time.Duration, cb GetElementFuncT[T]) func(T) *Element { func CachedT[T any](duration time.Duration, cb GetElementFuncT[T]) func(T) *Element {
element := &Element{ element := &Element{
tag: CachedNodeTag, tag: CachedNodeTag,
@ -208,6 +224,9 @@ func CachedT[T any](duration time.Duration, cb GetElementFuncT[T]) func(T) *Elem
} }
} }
// CachedT2 caches the given element for the given duration. The element is only rendered once, and then cached for the given duration.
// Please note this element is globally cached, and not per unique identifier / user.
// Use CachedPerKey to cache elements per unqiue identifier.
func CachedT2[T any, T2 any](duration time.Duration, cb GetElementFuncT2[T, T2]) func(T, T2) *Element { func CachedT2[T any, T2 any](duration time.Duration, cb GetElementFuncT2[T, T2]) func(T, T2) *Element {
element := &Element{ element := &Element{
tag: CachedNodeTag, tag: CachedNodeTag,
@ -225,6 +244,9 @@ func CachedT2[T any, T2 any](duration time.Duration, cb GetElementFuncT2[T, T2])
} }
} }
// CachedT3 caches the given element for the given duration. The element is only rendered once, and then cached for the given duration.
// Please note this element is globally cached, and not per unique identifier / user.
// Use CachedPerKey to cache elements per unqiue identifier.
func CachedT3[T any, T2 any, T3 any](duration time.Duration, cb GetElementFuncT3[T, T2, T3]) func(T, T2, T3) *Element { func CachedT3[T any, T2 any, T3 any](duration time.Duration, cb GetElementFuncT3[T, T2, T3]) func(T, T2, T3) *Element {
element := &Element{ element := &Element{
tag: CachedNodeTag, tag: CachedNodeTag,
@ -242,6 +264,9 @@ func CachedT3[T any, T2 any, T3 any](duration time.Duration, cb GetElementFuncT3
} }
} }
// CachedT4 caches the given element for the given duration. The element is only rendered once, and then cached for the given duration.
// Please note this element is globally cached, and not per unique identifier / user.
// Use CachedPerKey to cache elements per unqiue identifier.
func CachedT4[T any, T2 any, T3 any, T4 any](duration time.Duration, cb GetElementFuncT4[T, T2, T3, T4]) func(T, T2, T3, T4) *Element { func CachedT4[T any, T2 any, T3 any, T4 any](duration time.Duration, cb GetElementFuncT4[T, T2, T3, T4]) func(T, T2, T3, T4) *Element {
element := &Element{ element := &Element{
tag: CachedNodeTag, tag: CachedNodeTag,
@ -259,6 +284,7 @@ func CachedT4[T any, T2 any, T3 any, T4 any](duration time.Duration, cb GetEleme
} }
} }
// ClearCache clears the cached HTML of the element. This is called automatically by the framework.
func (c *CachedNode) ClearCache() { func (c *CachedNode) ClearCache() {
c.html = "" c.html = ""
if c.byKeyCache != nil { if c.byKeyCache != nil {
@ -273,6 +299,7 @@ func (c *CachedNode) ClearCache() {
} }
} }
// ClearExpired clears all expired cached HTML of the element. This is called automatically by the framework.
func (c *CachedNode) ClearExpired() { func (c *CachedNode) ClearExpired() {
c.mutex.Lock() c.mutex.Lock()
defer c.mutex.Unlock() defer c.mutex.Unlock()

View file

@ -1,5 +1,6 @@
package h package h
// If returns the node if the condition is true, otherwise returns an empty element
func If(condition bool, node Ren) Ren { func If(condition bool, node Ren) Ren {
if condition { if condition {
return node return node
@ -8,10 +9,12 @@ func If(condition bool, node Ren) Ren {
} }
} }
// Ternary returns the first argument if the second argument is true, otherwise returns the third argument
func Ternary[T any](value bool, a T, b T) T { func Ternary[T any](value bool, a T, b T) T {
return IfElse(value, a, b) return IfElse(value, a, b)
} }
// ElementIf returns the element if the condition is true, otherwise returns an empty element
func ElementIf(condition bool, element *Element) *Element { func ElementIf(condition bool, element *Element) *Element {
if condition { if condition {
return element return element
@ -20,6 +23,7 @@ func ElementIf(condition bool, element *Element) *Element {
} }
} }
// IfElseE returns element if condition is true, otherwise returns element2
func IfElseE(condition bool, element *Element, element2 *Element) *Element { func IfElseE(condition bool, element *Element, element2 *Element) *Element {
if condition { if condition {
return element return element
@ -28,6 +32,7 @@ func IfElseE(condition bool, element *Element, element2 *Element) *Element {
} }
} }
// IfElse returns node if condition is true, otherwise returns node2
func IfElse[T any](condition bool, node T, node2 T) T { func IfElse[T any](condition bool, node T, node2 T) T {
if condition { if condition {
return node return node
@ -36,6 +41,10 @@ func IfElse[T any](condition bool, node T, node2 T) T {
} }
} }
// IfElseLazy returns node if condition is true, otherwise returns the result of cb2
// This is useful if you want to lazily evaluate a node based on a condition
// For example, If you are rendering a component that requires specific data,
// you can use this to only load the component if the data is available
func IfElseLazy[T any](condition bool, cb1 func() T, cb2 func() T) T { func IfElseLazy[T any](condition bool, cb1 func() T, cb2 func() T) T {
if condition { if condition {
return cb1() return cb1()
@ -44,6 +53,7 @@ func IfElseLazy[T any](condition bool, cb1 func() T, cb2 func() T) T {
} }
} }
// IfHtmxRequest returns the node if the request is an htmx request, otherwise returns an empty element
func IfHtmxRequest(ctx *RequestContext, node Ren) Ren { func IfHtmxRequest(ctx *RequestContext, node Ren) Ren {
if ctx.isHxRequest { if ctx.isHxRequest {
return node return node
@ -51,6 +61,7 @@ func IfHtmxRequest(ctx *RequestContext, node Ren) Ren {
return Empty() return Empty()
} }
// ClassIf returns the class attribute if the condition is true, otherwise returns an empty element
func ClassIf(condition bool, value string) Ren { func ClassIf(condition bool, value string) Ren {
if condition { if condition {
return Class(value) return Class(value)
@ -58,6 +69,7 @@ func ClassIf(condition bool, value string) Ren {
return Empty() return Empty()
} }
// AttributeIf returns the attribute if the condition is true, otherwise returns an empty element
func AttributeIf(condition bool, name string, value string) Ren { func AttributeIf(condition bool, name string, value string) Ren {
if condition { if condition {
return Attribute(name, value) return Attribute(name, value)

View file

@ -51,6 +51,7 @@ func (l *LifeCycle) OnEvent(event hx.Event, cmd ...Command) *LifeCycle {
return l return l
} }
// OnLoad executes the given commands when the element is loaded into the DOM, it also executes when the element is replaced / swapped in.
func OnLoad(cmd ...Command) *LifeCycle { func OnLoad(cmd ...Command) *LifeCycle {
return NewLifeCycle().OnEvent(hx.LoadDomEvent, cmd...) return NewLifeCycle().OnEvent(hx.LoadDomEvent, cmd...)
} }
@ -60,58 +61,73 @@ func (l *LifeCycle) HxBeforeRequest(cmd ...Command) *LifeCycle {
return l return l
} }
// HxOnLoad executes the given commands when the element is loaded into the DOM.
// Deprecated: Use OnLoad instead.
func HxOnLoad(cmd ...Command) *LifeCycle { func HxOnLoad(cmd ...Command) *LifeCycle {
return NewLifeCycle().OnEvent(hx.LoadEvent, cmd...) return NewLifeCycle().OnEvent(hx.LoadEvent, cmd...)
} }
// HxOnAfterSwap executes the given commands when the element is swapped in.
func HxOnAfterSwap(cmd ...Command) *LifeCycle { func HxOnAfterSwap(cmd ...Command) *LifeCycle {
return NewLifeCycle().OnEvent(hx.AfterSwapEvent, cmd...) return NewLifeCycle().OnEvent(hx.AfterSwapEvent, cmd...)
} }
// OnClick executes the given commands when the element is clicked.
func OnClick(cmd ...Command) *LifeCycle { func OnClick(cmd ...Command) *LifeCycle {
return NewLifeCycle().OnEvent(hx.ClickEvent, cmd...) return NewLifeCycle().OnEvent(hx.ClickEvent, cmd...)
} }
// OnEvent executes the given commands when the given event is triggered.
func OnEvent(event hx.Event, cmd ...Command) *LifeCycle { func OnEvent(event hx.Event, cmd ...Command) *LifeCycle {
return NewLifeCycle().OnEvent(event, cmd...) return NewLifeCycle().OnEvent(event, cmd...)
} }
// HxBeforeSseMessage executes the given commands when a message is received from the server via SSE, but before it is processed.
func HxBeforeSseMessage(cmd ...Command) *LifeCycle { func HxBeforeSseMessage(cmd ...Command) *LifeCycle {
return NewLifeCycle().OnEvent(hx.SseBeforeMessageEvent, cmd...) return NewLifeCycle().OnEvent(hx.SseBeforeMessageEvent, cmd...)
} }
// HxAfterSseMessage executes the given commands when a message is received from the server via SSE, and after it is processed.
func HxAfterSseMessage(cmd ...Command) *LifeCycle { func HxAfterSseMessage(cmd ...Command) *LifeCycle {
return NewLifeCycle().OnEvent(hx.SseAfterMessageEvent, cmd...) return NewLifeCycle().OnEvent(hx.SseAfterMessageEvent, cmd...)
} }
// OnSubmit executes the given commands when the form is submitted.
func OnSubmit(cmd ...Command) *LifeCycle { func OnSubmit(cmd ...Command) *LifeCycle {
return NewLifeCycle().OnEvent(hx.SubmitEvent, cmd...) return NewLifeCycle().OnEvent(hx.SubmitEvent, cmd...)
} }
// HxOnSseError executes the given commands when an error occurs while connecting to the server via SSE.
func HxOnSseError(cmd ...Command) *LifeCycle { func HxOnSseError(cmd ...Command) *LifeCycle {
return NewLifeCycle().OnEvent(hx.SseErrorEvent, cmd...) return NewLifeCycle().OnEvent(hx.SseErrorEvent, cmd...)
} }
// HxOnSseClose executes the given commands when the connection to the server via SSE is closed.
func HxOnSseClose(cmd ...Command) *LifeCycle { func HxOnSseClose(cmd ...Command) *LifeCycle {
return NewLifeCycle().OnEvent(hx.SseClosedEvent, cmd...) return NewLifeCycle().OnEvent(hx.SseClosedEvent, cmd...)
} }
// HxOnSseConnecting executes the given commands when the connection to the server via SSE is being established.
func HxOnSseConnecting(cmd ...Command) *LifeCycle { func HxOnSseConnecting(cmd ...Command) *LifeCycle {
return NewLifeCycle().OnEvent(hx.SseConnectingEvent, cmd...) return NewLifeCycle().OnEvent(hx.SseConnectingEvent, cmd...)
} }
// HxOnSseOpen executes the given commands when the connection to the server via SSE is established.
func HxOnSseOpen(cmd ...Command) *LifeCycle { func HxOnSseOpen(cmd ...Command) *LifeCycle {
return NewLifeCycle().OnEvent(hx.SseConnectedEvent, cmd...) return NewLifeCycle().OnEvent(hx.SseConnectedEvent, cmd...)
} }
// HxBeforeRequest executes the given commands before the request is sent.
func HxBeforeRequest(cmd ...Command) *LifeCycle { func HxBeforeRequest(cmd ...Command) *LifeCycle {
return NewLifeCycle().HxBeforeRequest(cmd...) return NewLifeCycle().HxBeforeRequest(cmd...)
} }
// HxAfterRequest executes the given commands after the request is sent.
func HxAfterRequest(cmd ...Command) *LifeCycle { func HxAfterRequest(cmd ...Command) *LifeCycle {
return NewLifeCycle().HxAfterRequest(cmd...) return NewLifeCycle().HxAfterRequest(cmd...)
} }
// HxOnMutationError executes the given commands when a mutation error of a request occurs.
func HxOnMutationError(cmd ...Command) *LifeCycle { func HxOnMutationError(cmd ...Command) *LifeCycle {
return NewLifeCycle().HxOnMutationError(cmd...) return NewLifeCycle().HxOnMutationError(cmd...)
} }
@ -137,16 +153,19 @@ type ComplexJsCommand struct {
TempFuncName string TempFuncName string
} }
// NewComplexJsCommand creates a new complex JavaScript command.
func NewComplexJsCommand(command string) ComplexJsCommand { func NewComplexJsCommand(command string) ComplexJsCommand {
name := fmt.Sprintf("__eval_%s", util.RandSeq(6)) name := fmt.Sprintf("__eval_%s", util.RandSeq(6))
return ComplexJsCommand{Command: command, TempFuncName: name} return ComplexJsCommand{Command: command, TempFuncName: name}
} }
// SetText sets the inner text of the element.
func SetText(text string) SimpleJsCommand { func SetText(text string) SimpleJsCommand {
// language=JavaScript // language=JavaScript
return SimpleJsCommand{Command: fmt.Sprintf("this.innerText = '%s'", text)} return SimpleJsCommand{Command: fmt.Sprintf("this.innerText = '%s'", text)}
} }
// SetTextOnChildren sets the inner text of all the children of the element that match the selector.
func SetTextOnChildren(selector, text string) ComplexJsCommand { func SetTextOnChildren(selector, text string) ComplexJsCommand {
// language=JavaScript // language=JavaScript
return EvalJs(fmt.Sprintf(` return EvalJs(fmt.Sprintf(`
@ -157,26 +176,31 @@ func SetTextOnChildren(selector, text string) ComplexJsCommand {
`, selector, text)) `, selector, text))
} }
// Increment increments the inner text of the element by the given amount.
func Increment(amount int) SimpleJsCommand { func Increment(amount int) SimpleJsCommand {
// language=JavaScript // language=JavaScript
return SimpleJsCommand{Command: fmt.Sprintf("this.innerText = parseInt(this.innerText) + %d", amount)} return SimpleJsCommand{Command: fmt.Sprintf("this.innerText = parseInt(this.innerText) + %d", amount)}
} }
// SetInnerHtml sets the inner HTML of the element.
func SetInnerHtml(r Ren) SimpleJsCommand { func SetInnerHtml(r Ren) SimpleJsCommand {
// language=JavaScript // language=JavaScript
return SimpleJsCommand{Command: fmt.Sprintf("this.innerHTML = `%s`", Render(r))} return SimpleJsCommand{Command: fmt.Sprintf("this.innerHTML = `%s`", Render(r))}
} }
// SetOuterHtml sets the outer HTML of the element.
func SetOuterHtml(r Ren) SimpleJsCommand { func SetOuterHtml(r Ren) SimpleJsCommand {
// language=JavaScript // language=JavaScript
return SimpleJsCommand{Command: fmt.Sprintf("this.outerHTML = `%s`", Render(r))} return SimpleJsCommand{Command: fmt.Sprintf("this.outerHTML = `%s`", Render(r))}
} }
// AddAttribute adds the given attribute to the element.
func AddAttribute(name, value string) SimpleJsCommand { func AddAttribute(name, value string) SimpleJsCommand {
// language=JavaScript // language=JavaScript
return SimpleJsCommand{Command: fmt.Sprintf("this.setAttribute('%s', '%s')", name, value)} return SimpleJsCommand{Command: fmt.Sprintf("this.setAttribute('%s', '%s')", name, value)}
} }
// SetDisabled sets the disabled attribute on the element.
func SetDisabled(disabled bool) SimpleJsCommand { func SetDisabled(disabled bool) SimpleJsCommand {
if disabled { if disabled {
return AddAttribute("disabled", "true") return AddAttribute("disabled", "true")
@ -185,26 +209,31 @@ func SetDisabled(disabled bool) SimpleJsCommand {
} }
} }
// RemoveAttribute removes the given attribute from the element.
func RemoveAttribute(name string) SimpleJsCommand { func RemoveAttribute(name string) SimpleJsCommand {
// language=JavaScript // language=JavaScript
return SimpleJsCommand{Command: fmt.Sprintf("this.removeAttribute('%s')", name)} return SimpleJsCommand{Command: fmt.Sprintf("this.removeAttribute('%s')", name)}
} }
// AddClass adds the given class to the element.
func AddClass(class string) SimpleJsCommand { func AddClass(class string) SimpleJsCommand {
// language=JavaScript // language=JavaScript
return SimpleJsCommand{Command: fmt.Sprintf("this.classList.add('%s')", class)} return SimpleJsCommand{Command: fmt.Sprintf("this.classList.add('%s')", class)}
} }
// RemoveClass removes the given class from the element.
func RemoveClass(class string) SimpleJsCommand { func RemoveClass(class string) SimpleJsCommand {
// language=JavaScript // language=JavaScript
return SimpleJsCommand{Command: fmt.Sprintf("this.classList.remove('%s')", class)} return SimpleJsCommand{Command: fmt.Sprintf("this.classList.remove('%s')", class)}
} }
// ToggleClass toggles the given class on the element.
func ToggleClass(class string) SimpleJsCommand { func ToggleClass(class string) SimpleJsCommand {
// language=JavaScript // language=JavaScript
return SimpleJsCommand{Command: fmt.Sprintf("this.classList.toggle('%s')", class)} return SimpleJsCommand{Command: fmt.Sprintf("this.classList.toggle('%s')", class)}
} }
// ToggleClassOnElement toggles the given class on the elements returned by the selector.
func ToggleClassOnElement(selector, class string) ComplexJsCommand { func ToggleClassOnElement(selector, class string) ComplexJsCommand {
// language=JavaScript // language=JavaScript
return EvalJs(fmt.Sprintf(` return EvalJs(fmt.Sprintf(`
@ -214,6 +243,7 @@ func ToggleClassOnElement(selector, class string) ComplexJsCommand {
)) ))
} }
// EvalJsOnParent evaluates the given JavaScript code on the parent of the element. Reference the element using 'element'.
func EvalJsOnParent(js string) ComplexJsCommand { func EvalJsOnParent(js string) ComplexJsCommand {
// language=JavaScript // language=JavaScript
return EvalJs(fmt.Sprintf(` return EvalJs(fmt.Sprintf(`
@ -223,6 +253,7 @@ func EvalJsOnParent(js string) ComplexJsCommand {
`, js)) `, js))
} }
// EvalJsOnChildren evaluates the given JavaScript code on the children of the element. Reference the element using 'element'.
func EvalJsOnChildren(selector, js string) ComplexJsCommand { func EvalJsOnChildren(selector, js string) ComplexJsCommand {
// language=JavaScript // language=JavaScript
return EvalJs(fmt.Sprintf(` return EvalJs(fmt.Sprintf(`
@ -233,6 +264,7 @@ func EvalJsOnChildren(selector, js string) ComplexJsCommand {
`, selector, js)) `, selector, js))
} }
// EvalJsOnSibling evaluates the given JavaScript code on the siblings of the element. Reference the element using 'element'.
func EvalJsOnSibling(selector, js string) ComplexJsCommand { func EvalJsOnSibling(selector, js string) ComplexJsCommand {
// language=JavaScript // language=JavaScript
return EvalJs(fmt.Sprintf(` return EvalJs(fmt.Sprintf(`
@ -244,66 +276,79 @@ func EvalJsOnSibling(selector, js string) ComplexJsCommand {
`, selector, js)) `, selector, js))
} }
// SetClassOnParent sets the given class on the parent of the element. Reference the element using 'element'.
func SetClassOnParent(class string) ComplexJsCommand { func SetClassOnParent(class string) ComplexJsCommand {
// language=JavaScript // language=JavaScript
return EvalJsOnParent(fmt.Sprintf("element.classList.add('%s')", class)) return EvalJsOnParent(fmt.Sprintf("element.classList.add('%s')", class))
} }
// RemoveClassOnParent removes the given class from the parent of the element. Reference the element using 'element'.
func RemoveClassOnParent(class string) ComplexJsCommand { func RemoveClassOnParent(class string) ComplexJsCommand {
// language=JavaScript // language=JavaScript
return EvalJsOnParent(fmt.Sprintf("element.classList.remove('%s')", class)) return EvalJsOnParent(fmt.Sprintf("element.classList.remove('%s')", class))
} }
// SetClassOnChildren sets the given class on the children of the element. Reference the element using 'element'.
func SetClassOnChildren(selector, class string) ComplexJsCommand { func SetClassOnChildren(selector, class string) ComplexJsCommand {
// language=JavaScript // language=JavaScript
return EvalJsOnChildren(selector, fmt.Sprintf("element.classList.add('%s')", class)) return EvalJsOnChildren(selector, fmt.Sprintf("element.classList.add('%s')", class))
} }
// SetClassOnSibling sets the given class on the siblings of the element. Reference the element using 'element'.
func SetClassOnSibling(selector, class string) ComplexJsCommand { func SetClassOnSibling(selector, class string) ComplexJsCommand {
// language=JavaScript // language=JavaScript
return EvalJsOnSibling(selector, fmt.Sprintf("element.classList.add('%s')", class)) return EvalJsOnSibling(selector, fmt.Sprintf("element.classList.add('%s')", class))
} }
// RemoveClassOnSibling removes the given class from the siblings of the element. Reference the element using 'element'.
func RemoveClassOnSibling(selector, class string) ComplexJsCommand { func RemoveClassOnSibling(selector, class string) ComplexJsCommand {
// language=JavaScript // language=JavaScript
return EvalJsOnSibling(selector, fmt.Sprintf("element.classList.remove('%s')", class)) return EvalJsOnSibling(selector, fmt.Sprintf("element.classList.remove('%s')", class))
} }
// RemoveClassOnChildren removes the given class from the children of the element. Reference the element using 'element'.
func RemoveClassOnChildren(selector, class string) ComplexJsCommand { func RemoveClassOnChildren(selector, class string) ComplexJsCommand {
// language=JavaScript // language=JavaScript
return EvalJsOnChildren(selector, fmt.Sprintf("element.classList.remove('%s')", class)) return EvalJsOnChildren(selector, fmt.Sprintf("element.classList.remove('%s')", class))
} }
// Alert displays an alert dialog with the given text.
func Alert(text string) SimpleJsCommand { func Alert(text string) SimpleJsCommand {
// language=JavaScript // language=JavaScript
return SimpleJsCommand{Command: fmt.Sprintf("alert('%s')", text)} return SimpleJsCommand{Command: fmt.Sprintf("alert('%s')", text)}
} }
// Remove removes the element from the DOM.
func Remove() SimpleJsCommand { func Remove() SimpleJsCommand {
// language=JavaScript // language=JavaScript
return SimpleJsCommand{Command: "this.remove()"} return SimpleJsCommand{Command: "this.remove()"}
} }
// EvalJs evaluates the given JavaScript code.
func EvalJs(js string) ComplexJsCommand { func EvalJs(js string) ComplexJsCommand {
return NewComplexJsCommand(js) return NewComplexJsCommand(js)
} }
// PreventDefault prevents the default action of the event.
func PreventDefault() SimpleJsCommand { func PreventDefault() SimpleJsCommand {
// language=JavaScript // language=JavaScript
return SimpleJsCommand{Command: "event.preventDefault()"} return SimpleJsCommand{Command: "event.preventDefault()"}
} }
// ConsoleLog logs a message to the console.
func ConsoleLog(text string) SimpleJsCommand { func ConsoleLog(text string) SimpleJsCommand {
// language=JavaScript // language=JavaScript
return SimpleJsCommand{Command: fmt.Sprintf("console.log('%s')", text)} return SimpleJsCommand{Command: fmt.Sprintf("console.log('%s')", text)}
} }
// SetValue sets the value of the element.
func SetValue(value string) SimpleJsCommand { func SetValue(value string) SimpleJsCommand {
// language=JavaScript // language=JavaScript
return SimpleJsCommand{Command: fmt.Sprintf("this.value = '%s'", value)} return SimpleJsCommand{Command: fmt.Sprintf("this.value = '%s'", value)}
} }
// SubmitFormOnEnter submits the form when the user presses the enter key.
func SubmitFormOnEnter() ComplexJsCommand { func SubmitFormOnEnter() ComplexJsCommand {
// language=JavaScript // language=JavaScript
return EvalJs(` return EvalJs(`
@ -316,6 +361,7 @@ func SubmitFormOnEnter() ComplexJsCommand {
`) `)
} }
// InjectScript injects a script tag into the document.
func InjectScript(src string) ComplexJsCommand { func InjectScript(src string) ComplexJsCommand {
// language=JavaScript // language=JavaScript
return NewComplexJsCommand(fmt.Sprintf(` return NewComplexJsCommand(fmt.Sprintf(`
@ -326,6 +372,7 @@ func InjectScript(src string) ComplexJsCommand {
`, src)) `, src))
} }
// InjectScriptIfNotExist injects a script tag into the document if it does not already exist.
func InjectScriptIfNotExist(src string) ComplexJsCommand { func InjectScriptIfNotExist(src string) ComplexJsCommand {
// language=JavaScript // language=JavaScript
return EvalJs(fmt.Sprintf(` return EvalJs(fmt.Sprintf(`

View file

@ -48,6 +48,13 @@ func (q *Qs) ToString() string {
return builder.String() return builder.String()
} }
// GetQueryParam returns the value of the given query parameter from the request URL.
// There are two layers of priority:
// 1. The query parameter in the URL
// 2. The current browser URL
// If the query parameter is not found in the URL from the *RequestContext, it will fall back to the current browser URL if set.
// The URL from the *RequestContext would normally be the url from an XHR request through htmx,
// which is not the current browser url a visitor may be on.
func GetQueryParam(ctx *RequestContext, key string) string { func GetQueryParam(ctx *RequestContext, key string) string {
value, ok := ctx.Request.URL.Query()[key] value, ok := ctx.Request.URL.Query()[key]
if value == nil || !ok { if value == nil || !ok {
@ -65,6 +72,11 @@ func GetQueryParam(ctx *RequestContext, key string) string {
return value[0] return value[0]
} }
// SetQueryParams sets the query parameters of the given URL.
// Given the *Qs passed in, it will set the query parameters of the URL to the given values.
// If the value does not exist in *QS, it will remain untouched.
// If the value is an empty string, it will be removed from the query parameters.
// If the value is not an empty string, it will be set to the given value.
func SetQueryParams(href string, qs *Qs) string { func SetQueryParams(href string, qs *Qs) string {
u, err := url.Parse(href) u, err := url.Parse(href)
if err != nil { if err != nil {

View file

@ -8,6 +8,7 @@ type Ren interface {
Render(context *RenderContext) Render(context *RenderContext)
} }
// Render renders the given node recursively, and returns the resulting string.
func Render(node Ren) string { func Render(node Ren) string {
builder := &strings.Builder{} builder := &strings.Builder{}
context := &RenderContext{ context := &RenderContext{

View file

@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
) )
// JsonSerializeOrEmpty serializes the given data as JSON, or returns an empty string if the serialization fails.
func JsonSerializeOrEmpty(data any) string { func JsonSerializeOrEmpty(data any) string {
serialized, err := json.Marshal(data) serialized, err := json.Marshal(data)
if err != nil { if err != nil {

View file

@ -5,22 +5,27 @@ import (
"strings" "strings"
) )
// Get adds two attributes to the element: hx-get and hx-trigger.
func Get(path string, trigger ...string) *AttributeMapOrdered { func Get(path string, trigger ...string) *AttributeMapOrdered {
return AttributeList(Attribute(hx.GetAttr, path), HxTriggerString(trigger...)) return AttributeList(Attribute(hx.GetAttr, path), HxTriggerString(trigger...))
} }
// GetPartial adds two attributes to the element: hx-get and hx-trigger, and uses the partial path for the hx-get attribute.
func GetPartial(partial PartialFunc, trigger ...string) *AttributeMapOrdered { func GetPartial(partial PartialFunc, trigger ...string) *AttributeMapOrdered {
return Get(GetPartialPath(partial), trigger...) return Get(GetPartialPath(partial), trigger...)
} }
// GetPartialWithQs adds two attributes to the element: hx-get and hx-trigger, and uses the partial path for the hx-get attribute. It also sets the query string parameters.
func GetPartialWithQs(partial PartialFunc, qs *Qs, trigger string) *AttributeMapOrdered { func GetPartialWithQs(partial PartialFunc, qs *Qs, trigger string) *AttributeMapOrdered {
return Get(GetPartialPathWithQs(partial, qs), trigger) return Get(GetPartialPathWithQs(partial, qs), trigger)
} }
// GetWithQs adds two attributes to the element: hx-get and hx-trigger, and uses the path for the hx-get attribute. It also sets the query string parameters.
func GetWithQs(path string, qs *Qs, trigger string) *AttributeMapOrdered { func GetWithQs(path string, qs *Qs, trigger string) *AttributeMapOrdered {
return Get(SetQueryParams(path, qs), trigger) return Get(SetQueryParams(path, qs), trigger)
} }
// PostPartial adds two attributes to the element: hx-post and hx-trigger, and uses the partial path for the hx-post attribute.
func PostPartial(partial PartialFunc, triggers ...string) *AttributeMapOrdered { func PostPartial(partial PartialFunc, triggers ...string) *AttributeMapOrdered {
path := GetPartialPath(partial) path := GetPartialPath(partial)
if !strings.HasPrefix(path, "/") { if !strings.HasPrefix(path, "/") {
@ -29,6 +34,7 @@ func PostPartial(partial PartialFunc, triggers ...string) *AttributeMapOrdered {
return Post(path, triggers...) return Post(path, triggers...)
} }
// PostPartialWithQs adds two attributes to the element: hx-post and hx-trigger, and uses the partial path for the hx-post attribute. It also sets the query string parameters.
func PostPartialWithQs(partial PartialFunc, qs *Qs, trigger ...string) *AttributeMapOrdered { func PostPartialWithQs(partial PartialFunc, qs *Qs, trigger ...string) *AttributeMapOrdered {
path := GetPartialPathWithQs(partial, qs) path := GetPartialPathWithQs(partial, qs)
if !strings.HasPrefix(path, "/") { if !strings.HasPrefix(path, "/") {
@ -41,18 +47,22 @@ func Post(url string, trigger ...string) *AttributeMapOrdered {
return AttributeList(Attribute(hx.PostAttr, url), HxTriggerString(trigger...)) return AttributeList(Attribute(hx.PostAttr, url), HxTriggerString(trigger...))
} }
// PostWithQs adds two attributes to the element: hx-post and hx-trigger, and uses the path for the hx-post attribute. It also sets the query string parameters.
func PostWithQs(url string, qs *Qs, trigger string) *AttributeMapOrdered { func PostWithQs(url string, qs *Qs, trigger string) *AttributeMapOrdered {
return Post(SetQueryParams(url, qs), trigger) return Post(SetQueryParams(url, qs), trigger)
} }
// PostOnClick adds two attributes to the element: hx-post and hx-trigger, and uses the path for the hx-post attribute. It also sets the hx-trigger to hx-click.
func PostOnClick(url string) *AttributeMapOrdered { func PostOnClick(url string) *AttributeMapOrdered {
return Post(url, hx.ClickEvent) return Post(url, hx.ClickEvent)
} }
// PostPartialOnClick adds two attributes to the element: hx-post and hx-trigger, and uses the partial path for the hx-post attribute. It also sets the hx-trigger to hx-click.
func PostPartialOnClick(partial PartialFunc) *AttributeMapOrdered { func PostPartialOnClick(partial PartialFunc) *AttributeMapOrdered {
return PostOnClick(GetPartialPath(partial)) return PostOnClick(GetPartialPath(partial))
} }
// PostPartialOnClickQs adds two attributes to the element: hx-post and hx-trigger, and uses the partial path for the hx-post attribute. It also sets the hx-trigger to hx-click. It also sets the query string parameters.
func PostPartialOnClickQs(partial PartialFunc, qs *Qs) *AttributeMapOrdered { func PostPartialOnClickQs(partial PartialFunc, qs *Qs) *AttributeMapOrdered {
return PostOnClick(GetPartialPathWithQs(partial, qs)) return PostOnClick(GetPartialPathWithQs(partial, qs))
} }