From cf76ca4f98b821a14df14337cdf0d4a243331c80 Mon Sep 17 00:00:00 2001 From: maddalax Date: Fri, 25 Oct 2024 21:59:17 -0500 Subject: [PATCH] add some comments --- framework/h/app.go | 5 ++++ framework/h/array.go | 3 +++ framework/h/attribute.go | 13 ++++++++++ framework/h/cache.go | 27 +++++++++++++++++++++ framework/h/conditionals.go | 12 ++++++++++ framework/h/lifecycle.go | 47 +++++++++++++++++++++++++++++++++++++ framework/h/qs.go | 12 ++++++++++ framework/h/render.go | 1 + framework/h/serialize.go | 1 + framework/h/xhr.go | 10 ++++++++ 10 files changed, 131 insertions(+) diff --git a/framework/h/app.go b/framework/h/app.go index 51547cf..0839672 100644 --- a/framework/h/app.go +++ b/framework/h/app.go @@ -47,6 +47,10 @@ func (c *RequestContext) Get(key string) interface{} { 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 { return c.locator } @@ -62,6 +66,7 @@ type App struct { Router *chi.Mux } +// Start starts the htmgo server func Start(opts AppOpts) { router := chi.NewRouter() instance := App{ diff --git a/framework/h/array.go b/framework/h/array.go index 94287df..f6a6154 100644 --- a/framework/h/array.go +++ b/framework/h/array.go @@ -1,5 +1,6 @@ package h +// Unique returns a new slice with only unique items. func Unique[T any](slice []T, key func(item T) string) []T { var result []T seen := make(map[string]bool) @@ -13,6 +14,7 @@ func Unique[T any](slice []T, key func(item T) string) []T { 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 { var result []T for _, v := range slice { @@ -23,6 +25,7 @@ func Filter[T any](slice []T, predicate func(item T) bool) []T { 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 { var result []U for _, v := range slice { diff --git a/framework/h/attribute.go b/framework/h/attribute.go index 7cf8fd0..d0da2da 100644 --- a/framework/h/attribute.go +++ b/framework/h/attribute.go @@ -120,27 +120,34 @@ func HxIndicator(tag string) *AttributeR { 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 { return HxExtension("trigger-children") } +// HxTriggerString Adds a hx-trigger to an element based on a string of triggers func HxTriggerString(triggers ...string) *AttributeR { trigger := hx.NewStringTrigger(strings.Join(triggers, ", ")) return Attribute(hx.TriggerAttr, trigger.ToString()) } +// HxTrigger Adds a hx-trigger to an element func HxTrigger(opts ...hx.TriggerEvent) *AttributeR { return Attribute(hx.TriggerAttr, hx.NewTrigger(opts...).ToString()) } +// HxTriggerClick Adds a hx-trigger="click" to an element func HxTriggerClick(opts ...hx.Modifier) *AttributeR { return HxTrigger(hx.OnClick(opts...)) } +// HxExtension Adds a hx-ext to an element func HxExtension(value string) *AttributeR { return Attribute(hx.ExtAttr, value) } +// HxExtensions Adds multiple hx-ext to an element, separated by commas func HxExtensions(value ...string) Ren { return Attribute(hx.ExtAttr, strings.Join(value, ",")) } @@ -149,6 +156,8 @@ func JoinExtensions(attrs ...*AttributeR) Ren { 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 { values := make([]string, 0, len(attrs)) for _, a := range attrs { @@ -193,6 +202,9 @@ func Class(value ...string) *AttributeR { 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 { builder := strings.Builder{} builder.WriteString(value) @@ -206,6 +218,7 @@ func ClassX(value string, m ClassMap) Ren { return Class(builder.String()) } +// MergeClasses merges multiple classes into a single class string func MergeClasses(classes ...string) string { if len(classes) == 1 { return classes[0] diff --git a/framework/h/cache.go b/framework/h/cache.go index 92338cb..64ed5fe 100644 --- a/framework/h/cache.go +++ b/framework/h/cache.go @@ -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 { element := &Element{ 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 { element := &Element{ tag: CachedNodeTag, @@ -94,6 +99,8 @@ type ByKeyEntry struct { 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 { element := &Element{ 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 { element := &Element{ 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 { element := &Element{ 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 { element := &Element{ 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 { element := &Element{ 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 { element := &Element{ 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 { element := &Element{ 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 { element := &Element{ 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() { c.html = "" 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() { c.mutex.Lock() defer c.mutex.Unlock() diff --git a/framework/h/conditionals.go b/framework/h/conditionals.go index 7705b26..e0b4d0b 100644 --- a/framework/h/conditionals.go +++ b/framework/h/conditionals.go @@ -1,5 +1,6 @@ package h +// If returns the node if the condition is true, otherwise returns an empty element func If(condition bool, node Ren) Ren { if condition { 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 { 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 { if condition { 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 { if condition { 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 { if condition { 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 { if condition { 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 { if ctx.isHxRequest { return node @@ -51,6 +61,7 @@ func IfHtmxRequest(ctx *RequestContext, node Ren) Ren { return Empty() } +// ClassIf returns the class attribute if the condition is true, otherwise returns an empty element func ClassIf(condition bool, value string) Ren { if condition { return Class(value) @@ -58,6 +69,7 @@ func ClassIf(condition bool, value string) Ren { 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 { if condition { return Attribute(name, value) diff --git a/framework/h/lifecycle.go b/framework/h/lifecycle.go index 295b50d..84d9dac 100644 --- a/framework/h/lifecycle.go +++ b/framework/h/lifecycle.go @@ -51,6 +51,7 @@ func (l *LifeCycle) OnEvent(event hx.Event, cmd ...Command) *LifeCycle { 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 { return NewLifeCycle().OnEvent(hx.LoadDomEvent, cmd...) } @@ -60,58 +61,73 @@ func (l *LifeCycle) HxBeforeRequest(cmd ...Command) *LifeCycle { return l } +// HxOnLoad executes the given commands when the element is loaded into the DOM. +// Deprecated: Use OnLoad instead. func HxOnLoad(cmd ...Command) *LifeCycle { return NewLifeCycle().OnEvent(hx.LoadEvent, cmd...) } +// HxOnAfterSwap executes the given commands when the element is swapped in. func HxOnAfterSwap(cmd ...Command) *LifeCycle { return NewLifeCycle().OnEvent(hx.AfterSwapEvent, cmd...) } +// OnClick executes the given commands when the element is clicked. func OnClick(cmd ...Command) *LifeCycle { 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 { 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 { 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 { return NewLifeCycle().OnEvent(hx.SseAfterMessageEvent, cmd...) } +// OnSubmit executes the given commands when the form is submitted. func OnSubmit(cmd ...Command) *LifeCycle { 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 { 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 { 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 { 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 { return NewLifeCycle().OnEvent(hx.SseConnectedEvent, cmd...) } +// HxBeforeRequest executes the given commands before the request is sent. func HxBeforeRequest(cmd ...Command) *LifeCycle { return NewLifeCycle().HxBeforeRequest(cmd...) } +// HxAfterRequest executes the given commands after the request is sent. func HxAfterRequest(cmd ...Command) *LifeCycle { return NewLifeCycle().HxAfterRequest(cmd...) } +// HxOnMutationError executes the given commands when a mutation error of a request occurs. func HxOnMutationError(cmd ...Command) *LifeCycle { return NewLifeCycle().HxOnMutationError(cmd...) } @@ -137,16 +153,19 @@ type ComplexJsCommand struct { TempFuncName string } +// NewComplexJsCommand creates a new complex JavaScript command. func NewComplexJsCommand(command string) ComplexJsCommand { name := fmt.Sprintf("__eval_%s", util.RandSeq(6)) return ComplexJsCommand{Command: command, TempFuncName: name} } +// SetText sets the inner text of the element. func SetText(text string) SimpleJsCommand { // language=JavaScript 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 { // language=JavaScript return EvalJs(fmt.Sprintf(` @@ -157,26 +176,31 @@ func SetTextOnChildren(selector, text string) ComplexJsCommand { `, selector, text)) } +// Increment increments the inner text of the element by the given amount. func Increment(amount int) SimpleJsCommand { // language=JavaScript 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 { // language=JavaScript return SimpleJsCommand{Command: fmt.Sprintf("this.innerHTML = `%s`", Render(r))} } +// SetOuterHtml sets the outer HTML of the element. func SetOuterHtml(r Ren) SimpleJsCommand { // language=JavaScript return SimpleJsCommand{Command: fmt.Sprintf("this.outerHTML = `%s`", Render(r))} } +// AddAttribute adds the given attribute to the element. func AddAttribute(name, value string) SimpleJsCommand { // language=JavaScript return SimpleJsCommand{Command: fmt.Sprintf("this.setAttribute('%s', '%s')", name, value)} } +// SetDisabled sets the disabled attribute on the element. func SetDisabled(disabled bool) SimpleJsCommand { if disabled { 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 { // language=JavaScript return SimpleJsCommand{Command: fmt.Sprintf("this.removeAttribute('%s')", name)} } +// AddClass adds the given class to the element. func AddClass(class string) SimpleJsCommand { // language=JavaScript return SimpleJsCommand{Command: fmt.Sprintf("this.classList.add('%s')", class)} } +// RemoveClass removes the given class from the element. func RemoveClass(class string) SimpleJsCommand { // language=JavaScript return SimpleJsCommand{Command: fmt.Sprintf("this.classList.remove('%s')", class)} } +// ToggleClass toggles the given class on the element. func ToggleClass(class string) SimpleJsCommand { // language=JavaScript 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 { // language=JavaScript 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 { // language=JavaScript return EvalJs(fmt.Sprintf(` @@ -223,6 +253,7 @@ func EvalJsOnParent(js string) ComplexJsCommand { `, js)) } +// EvalJsOnChildren evaluates the given JavaScript code on the children of the element. Reference the element using 'element'. func EvalJsOnChildren(selector, js string) ComplexJsCommand { // language=JavaScript return EvalJs(fmt.Sprintf(` @@ -233,6 +264,7 @@ func EvalJsOnChildren(selector, js string) ComplexJsCommand { `, 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 { // language=JavaScript return EvalJs(fmt.Sprintf(` @@ -244,66 +276,79 @@ func EvalJsOnSibling(selector, js string) ComplexJsCommand { `, selector, js)) } +// SetClassOnParent sets the given class on the parent of the element. Reference the element using 'element'. func SetClassOnParent(class string) ComplexJsCommand { // language=JavaScript 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 { // language=JavaScript 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 { // language=JavaScript 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 { // language=JavaScript 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 { // language=JavaScript 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 { // language=JavaScript return EvalJsOnChildren(selector, fmt.Sprintf("element.classList.remove('%s')", class)) } +// Alert displays an alert dialog with the given text. func Alert(text string) SimpleJsCommand { // language=JavaScript return SimpleJsCommand{Command: fmt.Sprintf("alert('%s')", text)} } +// Remove removes the element from the DOM. func Remove() SimpleJsCommand { // language=JavaScript return SimpleJsCommand{Command: "this.remove()"} } +// EvalJs evaluates the given JavaScript code. func EvalJs(js string) ComplexJsCommand { return NewComplexJsCommand(js) } +// PreventDefault prevents the default action of the event. func PreventDefault() SimpleJsCommand { // language=JavaScript return SimpleJsCommand{Command: "event.preventDefault()"} } +// ConsoleLog logs a message to the console. func ConsoleLog(text string) SimpleJsCommand { // language=JavaScript return SimpleJsCommand{Command: fmt.Sprintf("console.log('%s')", text)} } +// SetValue sets the value of the element. func SetValue(value string) SimpleJsCommand { // language=JavaScript return SimpleJsCommand{Command: fmt.Sprintf("this.value = '%s'", value)} } +// SubmitFormOnEnter submits the form when the user presses the enter key. func SubmitFormOnEnter() ComplexJsCommand { // language=JavaScript return EvalJs(` @@ -316,6 +361,7 @@ func SubmitFormOnEnter() ComplexJsCommand { `) } +// InjectScript injects a script tag into the document. func InjectScript(src string) ComplexJsCommand { // language=JavaScript return NewComplexJsCommand(fmt.Sprintf(` @@ -326,6 +372,7 @@ func InjectScript(src string) ComplexJsCommand { `, src)) } +// InjectScriptIfNotExist injects a script tag into the document if it does not already exist. func InjectScriptIfNotExist(src string) ComplexJsCommand { // language=JavaScript return EvalJs(fmt.Sprintf(` diff --git a/framework/h/qs.go b/framework/h/qs.go index 75fad83..b99d7ea 100644 --- a/framework/h/qs.go +++ b/framework/h/qs.go @@ -48,6 +48,13 @@ func (q *Qs) ToString() 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 { value, ok := ctx.Request.URL.Query()[key] if value == nil || !ok { @@ -65,6 +72,11 @@ func GetQueryParam(ctx *RequestContext, key string) string { 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 { u, err := url.Parse(href) if err != nil { diff --git a/framework/h/render.go b/framework/h/render.go index aa37dd5..4570708 100644 --- a/framework/h/render.go +++ b/framework/h/render.go @@ -8,6 +8,7 @@ type Ren interface { Render(context *RenderContext) } +// Render renders the given node recursively, and returns the resulting string. func Render(node Ren) string { builder := &strings.Builder{} context := &RenderContext{ diff --git a/framework/h/serialize.go b/framework/h/serialize.go index eb35418..0bc2c11 100644 --- a/framework/h/serialize.go +++ b/framework/h/serialize.go @@ -4,6 +4,7 @@ import ( "encoding/json" ) +// JsonSerializeOrEmpty serializes the given data as JSON, or returns an empty string if the serialization fails. func JsonSerializeOrEmpty(data any) string { serialized, err := json.Marshal(data) if err != nil { diff --git a/framework/h/xhr.go b/framework/h/xhr.go index 1c707e1..ed3e999 100644 --- a/framework/h/xhr.go +++ b/framework/h/xhr.go @@ -5,22 +5,27 @@ import ( "strings" ) +// Get adds two attributes to the element: hx-get and hx-trigger. func Get(path string, trigger ...string) *AttributeMapOrdered { 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 { 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 { 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 { 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 { path := GetPartialPath(partial) if !strings.HasPrefix(path, "/") { @@ -29,6 +34,7 @@ func PostPartial(partial PartialFunc, triggers ...string) *AttributeMapOrdered { 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 { path := GetPartialPathWithQs(partial, qs) if !strings.HasPrefix(path, "/") { @@ -41,18 +47,22 @@ func Post(url string, trigger ...string) *AttributeMapOrdered { 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 { 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 { 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 { 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 { return PostOnClick(GetPartialPathWithQs(partial, qs)) }