### Define Full-Page GET Handler with h.NewPage Source: https://context7.com/maddalax/htmgo/llms.txt Wraps a `Ren` tree into a `*Page` for HTTP GET requests. Functions in `pages/` are automatically routed based on their file path. Dynamic segments use `.$param` in filenames. ```go // pages/root.go — layout wrapper used by all pages package pages import "github.com/maddalax/htmgo/framework/h" func RootPage(children ...h.Ren) *h.Page { return h.NewPage( h.Html( h.Head( h.Meta("charset", "UTF-8"), h.Meta("viewport", "width=device-width, initial-scale=1.0"), h.Link("/public/styles.css", "stylesheet"), h.Script("/public/htmx.min.js"), ), h.Body( h.Attribute("hx-ext", h.BaseExtensions()), h.Children(children...), ), ), ) } // pages/index.go — maps to GET / func IndexPage(ctx *h.RequestContext) *h.Page { name := ctx.QueryParam("name") return RootPage( h.Div( h.Class("flex flex-col items-center pt-24 min-h-screen bg-neutral-100"), h.H1( h.Text("Hello, htmgo"), h.Class("text-5xl font-bold"), ), h.If(name != "", h.Pf("Welcome back, %s!", name)), ), ) } // pages/profile.$id.go — maps to GET /profile/:id func ProfilePage(ctx *h.RequestContext) *h.Page { id := ctx.UrlParam("id") return RootPage( h.H2F("Profile: %s", id), ) } ``` -------------------------------- ### Register and Get Dependencies with Service Locator Source: https://context7.com/maddalax/htmgo/llms.txt Register dependencies with singleton or transient lifecycles in main and retrieve them in handlers using the service locator. Ensure the service locator is passed during application startup. ```go // main.go locator := service.NewLocator() service.Set[db.Queries](locator, service.Singleton, func() *db.Queries { conn, _ := sql.Open("sqlite3", "./app.db") return db.New(conn) }) service.Set[mailer.Client](locator, service.Transient, func() *mailer.Client { return mailer.New(os.Getenv("SMTP_HOST")) }) h.Start(h.AppOpts{ServiceLocator: locator, Register: func(app *h.App) { __htmgo.Register(app.Router) }}) // In a partial handler func RegisterUser(ctx *h.RequestContext) *h.Partial { queries := service.Get[db.Queries](ctx.ServiceLocator()) mail := service.Get[mailer.Client](ctx.ServiceLocator()) email := ctx.FormValue("email") id, err := queries.CreateUser(context.Background(), db.CreateUserParams{Email: email}) if err != nil { ctx.Response.WriteHeader(400) return h.NewPartial(h.Div(h.Text("Registration failed: "+err.Error()))) } _ = mail.SendWelcome(email) return h.RedirectPartial("/dashboard") } ``` -------------------------------- ### htmgo CLI Project Management Commands Source: https://context7.com/maddalax/htmgo/llms.txt Commands for scaffolding new projects, starting a development server with hot reload, building for production, formatting code, and managing project configuration. ```bash # Create a new project from the starter template htmgo template my-app # Start development server with live reload # Watches .go, .css, .js files; rebuilds routes, CSS, ent schema on change htmgo watch # Production build (outputs a single binary) htmgo build # One-shot production run (no watcher) htmgo run # Regenerate auto-discovered routes and ent schema manually htmgo generate # Compile Tailwind CSS only htmgo css # Format a single file or entire directory htmgo format ./pages/index.go htmgo format . # Print CLI version htmgo version # Output: htmgo cli version 1.0.6 ``` -------------------------------- ### Define htmx Partial Endpoint with Counter Example Source: https://context7.com/maddalax/htmgo/llms.txt Defines an interactive counter component with increment functionality. The `CounterForm` function creates the UI, and `IncrementCounter` handles the POST request to update the count. ```go package partials import "github.com/maddalax/htmgo/framework/h" // CounterForm renders an interactive counter func CounterForm(count int) *h.Element { return h.Div( h.Id("counter-form"), h.Class("flex gap-4 items-center"), h.P( h.Id("count-display"), h.TextF("Count: %d", count), h.Class("text-xl"), ), h.Button( h.Text("Increment"), h.Class("bg-rose-400 text-white px-4 py-2 rounded"), h.PostPartialWithQs( IncrementCounter, h.NewQs("count", fmt.Sprintf("%d", count+1)), ), ), ) } // IncrementCounter handles POST /partials.IncrementCounter func IncrementCounter(ctx *h.RequestContext) *h.Partial { count, _ := strconv.Atoi(ctx.QueryParam("count")) return h.SwapManyPartial(ctx, CounterForm(count), ) } ``` -------------------------------- ### Bootstrap HTTP Server with h.Start Source: https://context7.com/maddalax/htmgo/llms.txt Initializes the HTTP server, router, middleware, and static file handlers. Use `AppOpts` to configure port, service locator, live reload, and custom registrations. The `PORT` environment variable or `AppOpts.Port` can override the default port. ```go package main import ( "embed" "io/fs" "net/http" "github.com/maddalax/htmgo/framework/h" "github.com/maddalax/htmgo/framework/service" "myapp/__htmgo" // auto-generated route registration "myapp/internal/db" ) //go:embed assets/dist/* var StaticAssets embed.FS func main() { locator := service.NewLocator() // Register a singleton DB connection service.Set[db.Queries](locator, service.Singleton, func() *db.Queries { return db.Provide() }) h.Start(h.AppOpts{ ServiceLocator: locator, LiveReload: true, // enables /dev/livereload SSE endpoint in development Port: 8080, Register: func(app *h.App) { // Serve embedded static assets sub, _ := fs.Sub(StaticAssets, "assets/dist") app.Router.Handle("/public/*", http.StripPrefix("/public", http.FileServerFS(sub))) // Register all auto-discovered pages and partials __htmgo.Register(app.Router) // Optional middleware — runs before every handler app.Use(func(ctx *h.RequestContext) { // e.g. authentication check }) }, }) } ``` -------------------------------- ### Integrating go-freelru Library as htmgo Cache Store Source: https://github.com/maddalax/htmgo/blob/master/framework/h/cache/README.md Provides an adapter implementation to integrate the `go-freelru` library with htmgo's `cache.Store` interface. Note that `go-freelru` does not support per-item TTL and the `GetOrCompute` implementation lacks true atomic guarantees without additional synchronization. ```go import ( "time" "github.com/elastic/go-freelru" "github.com/maddalax/htmgo/framework/h/cache" ) type FreeLRUAdapter[K comparable, V any] struct { lru *freelru.LRU[K, V] } func NewFreeLRUAdapter[K comparable, V any](size uint32) cache.Store[K, V] { lru, err := freelru.New[K, V](size, nil) if err != nil { panic(err) } return &FreeLRUAdapter[K, V]{lru: lru} } func (s *FreeLRUAdapter[K, V]) Set(key K, value V, ttl time.Duration) { // Note: go-freelru doesn't support per-item TTL s.lru.Add(key, value) } func (s *FreeLRUAdapter[K, V]) GetOrCompute(key K, compute func() V, ttl time.Duration) V { // Check if exists in cache if val, ok := s.lru.Get(key); ok { return val } // Not in cache, compute and store // Note: This simple implementation doesn't provide true atomic guarantees // For production use, you'd need additional synchronization value := compute() s.lru.Add(key, value) return value } func (s *FreeLRUAdapter[K, V]) Delete(key K) { s.lru.Remove(key) } func (s *FreeLRUAdapter[K, V]) Purge() { s.lru.Clear() } func (s *FreeLRUAdapter[K, V]) Close() { // No-op for this implementation } ``` -------------------------------- ### Using Default TTL Cache in htmgo Source: https://github.com/maddalax/htmgo/blob/master/framework/h/cache/README.md Demonstrates how to use the default TTL-based cache in htmgo for backward compatibility. No explicit changes are needed if the default behavior is desired. ```go // No changes needed - works exactly as before UserProfile := h.CachedPerKeyT( 15*time.Minute, func(userID int) (int, h.GetElementFunc) { return userID, func() *h.Element { return h.Div(h.Text("User profile")) } }, ) ``` -------------------------------- ### htmgo.yml Project Configuration Source: https://context7.com/maddalax/htmgo/llms.txt Optional configuration file to customize watcher behavior, routing exclusions, and asset paths. Set `tailwind: true` to enable Tailwind CSS compilation. ```yaml # htmgo.yml tailwind: true # Paths the watcher ignores (default: node_modules, .git, assets/dist) watch_ignore: - node_modules - .git - assets/dist - vendor ``` -------------------------------- ### Construct HTML Elements with Tag Builders Source: https://context7.com/maddalax/htmgo/llms.txt Illustrates the use of Go functions in the `h` package to build HTML elements. These functions accept variadic `h.Ren` children, enabling composition without templates or JSX. ```go func ProductCard(p Product) *h.Element { return h.Div( h.Id("product-"+p.ID), h.Class("bg-white rounded-lg shadow p-4 flex flex-col gap-2"), h.Img( h.Src(p.ImageURL), h.Alt(p.Name), h.Class("w-full h-48 object-cover rounded"), ), h.H3( h.Text(p.Name), h.Class("text-lg font-semibold"), ), h.P( h.TextF("$%.2f", p.Price), h.Class("text-rose-500 font-bold"), ), h.If(p.Stock == 0, h.P( h.Text("Out of stock"), h.Class("text-gray-400 text-sm"), )), h.Button( h.Text("Add to Cart"), h.Class("bg-rose-400 text-white py-2 rounded"), h.PostPartialOnClickQs(AddToCart, h.NewQs("id", p.ID)), h.ElementIf(p.Stock == 0, h.Disabled()), ), ) } func ProductListPage(ctx *h.RequestContext) *h.Page { products := loadProducts() return h.NewPage( h.Body( h.Div( h.Class("grid grid-cols-3 gap-4 p-8"), h.List(products, func(p Product, i int) *h.Element { return ProductCard(p) }), ), ), ) } ``` -------------------------------- ### Memory-Bounded Cache Initialization Source: https://github.com/maddalax/htmgo/blob/master/framework/h/cache/README.md Initializes an LRUStore with a specified capacity to limit cache size. Recommended for public-facing applications to prevent DoS attacks by controlling memory usage. ```go // Limit cache to reasonable size based on your server's memory cache := cache.NewLRUStore[any, string](100_000) // Use for all user-specific caching UserContent := h.CachedPerKey( 5*time.Minute, getUserContent, h.WithCacheStore(cache), ) ``` -------------------------------- ### Handler Usage of Cached Components Source: https://context7.com/maddalax/htmgo/llms.txt Demonstrates how to use globally cached and per-key cached components within a handler function. ```go // Usage in a handler func DashboardPage(ctx *h.RequestContext) *h.Page { userId := getUserIdFromSession(ctx) return h.NewPage( h.Body( CachedNavbar(), // shared across all users CachedProfileCard(userId), // cached per userId ), ) } ``` -------------------------------- ### JavaScript Command Helpers in Go Source: https://context7.com/maddalax/htmgo/llms.txt Use these helpers to generate `hx-on:*` and `on*` attributes for DOM manipulation, class toggling, text updates, and script injection without writing raw JavaScript event listeners. ```go func InteractiveDemo() *h.Element { return h.Div( h.Class("demo"), // Inject an external script on load (only once) h.OnLoad(h.InjectScriptIfNotExist("https://cdn.example.com/chart.js")), // Toggle classes on parent and siblings h.Button( h.Text("Toggle Dark Mode"), h.OnClick( h.ToggleClassOnParent("dark"), h.ToggleText("Toggle Dark Mode", "Toggle Light Mode"), ), ), // Arbitrary JS evaluation h.Button( h.Text("Copy Link"), h.OnClick(h.EvalJs(`navigator.clipboard.writeText(window.location.href)`)), ), // Polling: re-fetch partial every 5 seconds h.Div( h.Id("live-stats"), h.GetPartial(LiveStats, hx.TriggerEvery5s), ), // Delayed removal after swap h.Div( h.Id("toast"), h.Class("notification"), h.Text("Saved!"), h.OnLoad(h.RunAfterTimeout(2500*time.Millisecond, h.AddClass("opacity-0"), h.RunAfterTimeout(300*time.Millisecond, h.Remove()), )), ), ) } ``` -------------------------------- ### Enable WebSocket Extension in htmgo App Source: https://context7.com/maddalax/htmgo/llms.txt Configure and enable the WebSocket extension by providing a WebSocket path and a session ID function during application startup. ```go // main.go — enable the extension import wsext "github.com/maddalax/htmgo/extensions/websocket" h.Start(h.AppOpts{ ServiceLocator: locator, Register: func(app *h.App) { wsext.EnableExtension(app, opts.ExtensionOpts{ WsPath: "/ws", SessionId: func(ctx *h.RequestContext) session.Id { return session.GetSessionId(ctx) }, }) __htmgo.Register(app.Router) }, }) ``` -------------------------------- ### h.Get/h.Post for HTMX Requests Source: https://context7.com/maddalax/htmgo/llms.txt Use these helpers to attach hx-get/hx-post and hx-trigger attributes to elements. The *Partial variants automatically derive URLs from Go function pointers for compile-time safety. Trigger strings use hx package constants. ```go import "github.com/maddalax/htmgo/framework/hx" func SearchBar(ctx *h.RequestContext) *h.Element { return h.Div( h.Class("flex gap-2"), // GET on every keystroke with 300ms debounce h.Input("text", h.Name("q"), h.Placeholder("Search..."), h.Class("border rounded p-2 flex-1"), h.HxTarget("#search-results"), h.GetPartial(SearchResults, hx.TriggerKeyUpEnter), ), // POST on click, no trigger needed (default for buttons) h.Button( h.Text("Search"), h.Class("bg-blue-500 text-white px-4 rounded"), h.PostPartialOnClick(SearchResults), ), // GET with query string params h.Button( h.Text("Filter: Active"), h.GetPartialWithQs( SearchResults, h.NewQs("status", "active"), hx.TriggerClick, ), ), h.Div(h.Id("search-results")), ) } func SearchResults(ctx *h.RequestContext) *h.Partial { q := ctx.QueryParam("q") status := ctx.QueryParam("status") results := db.Search(q, status) return h.NewPartial( h.Div( h.Id("search-results"), h.List(results, func(r Result, _ int) *h.Element { return h.P(h.Text(r.Title)) }), ), ) } ``` -------------------------------- ### Create an Index Page with Current Time Source: https://github.com/maddalax/htmgo/blob/master/htmgo-site/md/index.md This Go function demonstrates how to create a basic page component that displays the current time. It utilizes htmgo's `h.NewPage` and `h.Div` functions to structure the HTML content. ```go func IndexPage(ctx *h.RequestContext) *h.Page { now := time.Now() return h.NewPage( h.Div( h.Class("flex gap-2"), h.TextF("the current time is %s", now.String()) ) ) } ``` -------------------------------- ### Using Custom LRU Cache with htmgo Component Source: https://github.com/maddalax/htmgo/blob/master/framework/h/cache/README.md Shows how to integrate a custom cache implementation, such as an LRU cache, with an htmgo component using the `WithCacheStore` option. ```go package main import ( "github.com/maddalax/htmgo/framework/h" "github.com/maddalax/htmgo/framework/h/cache" "time" ) var ( // Create a memory-bounded LRU cache lruCache = cache.NewLRUStore[any, string](10_000) // Max 10,000 items // Use it with a cached component UserProfile = h.CachedPerKeyT( 15*time.Minute, func (userID int) (int, h.GetElementFunc) { return userID, func () *h.Element { return h.Div(h.Text("User profile")) } }, h.WithCacheStore(lruCache), // Pass the custom cache ) ) ``` -------------------------------- ### h.Get / h.Post / h.GetPartial / h.PostPartial Source: https://context7.com/maddalax/htmgo/llms.txt These helpers attach hx-get/hx-post and hx-trigger attributes to elements. The \"*Partial\" variants automatically derive the URL from the Go function pointer for compile-time safety. Trigger strings utilize \"hx\" package constants. ```APIDOC ## h.Get / h.Post / h.GetPartial / h.PostPartial — htmx request attributes These helpers attach `hx-get`/`hx-post` plus `hx-trigger` to an element. The `*Partial` variants derive the URL automatically from the Go function pointer, providing compile-time safety. Trigger strings use `hx` package constants. ```go import "github.com/maddalax/htmgo/framework/hx" func SearchBar(ctx *h.RequestContext) *h.Element { return h.Div( h.Class("flex gap-2"), // GET on every keystroke with 300ms debounce h.Input("text", h.Name("q"), h.Placeholder("Search..."), h.Class("border rounded p-2 flex-1"), h.HxTarget("#search-results"), h.GetPartial(SearchResults, hx.TriggerKeyUpEnter), ), // POST on click, no trigger needed (default for buttons) h.Button( h.Text("Search"), h.Class("bg-blue-500 text-white px-4 rounded"), h.PostPartialOnClick(SearchResults), ), // GET with query string params h.Button( h.Text("Filter: Active"), h.GetPartialWithQs( SearchResults, h.NewQs("status", "active"), hx.TriggerClick, ), ), h.Div(h.Id("search-results")), ) } func SearchResults(ctx *h.RequestContext) *h.Partial { q := ctx.QueryParam("q") status := ctx.QueryParam("status") results := db.Search(q, status) return h.NewPartial( h.Div( h.Id("search-results"), h.List(results, func(r Result, _ int) *h.Element { return h.P(h.Text(r.Title)) }), ), ) } ``` ``` -------------------------------- ### Handle Responses with Redirect and URL Updates Source: https://context7.com/maddalax/htmgo/llms.txt Use h.RedirectPartial for full-page redirects via HX-Redirect header. h.NewPartialWithHeaders and h.ReplaceUrlHeader allow for partial updates combined with browser URL changes in a single response. ```go func LoginUser(ctx *h.RequestContext) *h.Partial { if !ctx.IsHttpPost() { return nil } email := ctx.FormValue("email") password := ctx.FormValue("password") user, err := auth.Login(ctx, email, password) if err != nil { ctx.Response.WriteHeader(401) return h.NewPartial( h.Div( h.Id("form-error"), h.Text("Invalid credentials"), h.Class("text-red-500"), ), ) } ctx.SetCookie(&http.Cookie{ Name: "session", Value: user.Token, Path: "/", HttpOnly: true, }) // Full-page redirect via HX-Redirect header return h.RedirectPartial("/dashboard") } func UpdateProfile(ctx *h.RequestContext) *h.Partial { name := ctx.FormValue("name") db.UpdateUser(getUserId(ctx), name) // Partial update + browser URL change in one response return h.NewPartialWithHeaders( h.CombineHeaders( h.ReplaceUrlHeader("/profile"), ), h.Div( h.Id("profile-name"), h.Text(name), ), ) } ``` -------------------------------- ### Per-Key Component Caching with h.CachedPerKeyT Source: https://context7.com/maddalax/htmgo/llms.txt Cache component HTML separately per key (e.g., user ID) with a specified TTL using h.CachedPerKeyT. This is useful for personalized content. ```go // Per-user cached profile card — cached separately per user ID, 5-minute TTL var CachedProfileCard = h.CachedPerKeyT[string, string]( 5*time.Minute, func(userId string) (string, func() *h.Element) { return userId, func() *h.Element { user := db.GetUser(userId) return h.Div( h.Id("profile-"+userId), h.Class("card"), h.P(h.Text(user.Name)), h.P(h.Text(user.Email)), ) } }, ) ``` -------------------------------- ### Manage Query Parameters with htmgo Utilities Source: https://context7.com/maddalax/htmgo/llms.txt Use h.GetQueryParam to read parameters from request or browser URLs. h.PushQsHeader updates the browser address bar with new query parameters without a page reload, useful for filtering and pagination. ```go func FilteredList(ctx *h.RequestContext) *h.Partial { // Read from request OR from HX-Current-URL header (fallback) status := h.GetQueryParam(ctx, "status") page := h.GetQueryParam(ctx, "page") if page == "" { page = "1" } pageNum, _ := strconv.Atoi(page) items := db.ListItems(status, pageNum) return h.SwapManyPartialWithHeaders( ctx, // Push updated URL to browser: /items?status=active&page=2 h.PushQsHeader(ctx, h.NewQs("status", status, "page", fmt.Sprintf("%d", pageNum))), ItemList(items), PaginationControls(pageNum, len(items)), ) } func PaginationControls(page, total int) *h.Element { return h.Div( h.Id("pagination"), h.Class("flex gap-2"), h.Button( h.Text("← Prev"), h.GetPartialWithQs(FilteredList, h.NewQs("page", fmt.Sprintf("%d", page-1)), hx.TriggerClick), h.ElementIf(page <= 1, h.Disabled()), ), h.Button( h.Text("Next →"), h.GetPartialWithQs(FilteredList, h.NewQs("page", fmt.Sprintf("%d", page+1)), hx.TriggerClick), ), ) } ``` -------------------------------- ### Access Request Data with RequestContext Source: https://context7.com/maddalax/htmgo/llms.txt Demonstrates how to access various request details within a handler using `h.RequestContext`. This includes form values, URL parameters, query strings, htmx request detection, HTTP methods, browser URL, and registered services. ```go func HandleForm(ctx *h.RequestContext) *h.Partial { // Form values email := ctx.FormValue("email") password := ctx.FormValue("password") // URL parameter from dynamic route (e.g., pages/user.$id.go) userId := ctx.UrlParam("id") // Query string tab := ctx.QueryParam("tab") // htmx request detection if ctx.IsHxRequest() { // request came from htmx (HX-Request: true header) } // HTTP method checks if !ctx.IsHttpPost() { return h.RedirectPartial("/login") } // Browser URL htmx is operating on (differs from request URL) browserUrl := ctx.HxCurrentBrowserUrl() // Access a registered service from the locator queries := service.Get[db.Queries](ctx.ServiceLocator()) user, err := queries.GetUserByEmail(context.Background(), email) if err != nil { ctx.Response.WriteHeader(400) return h.NewPartial(h.Div(h.Text("Invalid credentials"))) } // Set a cookie ctx.SetCookie(&http.Cookie{Name: "session", Value: user.SessionToken, Path: "/"}) return h.RedirectPartial("/dashboard") } ``` -------------------------------- ### Globally Changing Default Cache Provider in htmgo Source: https://github.com/maddalax/htmgo/blob/master/framework/h/cache/README.md Demonstrates how to override the default cache provider for the entire application by setting `h.DefaultCacheProvider` in an `init` function. ```go package main import ( "github.com/maddalax/htmgo/framework/h" "github.com/maddalax/htmgo/framework/h/cache" ) func init() { // All cached components will use LRU by default h.DefaultCacheProvider = func () cache.Store[any, string] { return cache.NewLRUStore[any, string](50_000) } } ``` -------------------------------- ### Conditional Rendering with h.If, h.IfElse, h.ClassX, h.Ternary Source: https://context7.com/maddalax/htmgo/llms.txt Use h.ClassX for conditional CSS classes and h.If/h.IfElse for conditional elements. h.Ternary provides a generic helper for any type. ```go func TaskItem(task Task) *h.Element { return h.Div( h.Id("task-"+task.ID), // Conditional CSS classes h.ClassX("flex items-center p-4 border-b", h.ClassMap{ "opacity-50 line-through": task.Completed, "bg-yellow-50": task.Priority == "high", }), // Conditional element h.If(task.Completed, h.Span( h.Text("✓"), h.Class("text-green-500 mr-2"), )), h.P(h.Text(task.Name)), // IfElse returns one of two elements h.IfElse( task.Completed, h.Button(h.Text("Reopen"), h.Class("text-blue-500")), h.Button(h.Text("Complete"), h.Class("text-green-500")), ), // Ternary for any type (e.g., string) h.P(h.TextF("Priority: %s", h.Ternary(task.Priority != "", task.Priority, "normal"), )), // Conditional attribute h.AttributeIf(task.Locked, "disabled", ""), h.ClassIf(!task.Visible, "hidden"), ) } ``` -------------------------------- ### h.LifeCycle / OnLoad / OnClick for Client-side Event Handlers Source: https://context7.com/maddalax/htmgo/llms.txt Lifecycle functions attach hx-on:* or on* attributes that execute JavaScript commands when DOM events fire. Commands are Go values, eliminating the need for raw JS strings for common operations. Multiple commands can be combined in a single handler. ```go func AnimatedButton() *h.Element { return h.Button( h.Text("Click me"), h.Class("btn"), // Execute JS commands when element loads into DOM h.OnLoad( h.AddClass("opacity-100"), h.RemoveClass("opacity-0"), ), // Execute on click h.OnClick( h.SetText("Clicked!"), h.AddClass("bg-green-500"), h.RemoveClass("bg-blue-500"), ), ) } func LoadingButton() *h.Element { return h.Button( h.Text("Save"), h.Class("bg-blue-500 text-white px-4 py-2 rounded"), h.PostPartialOnClick(SaveData), // Disable self while htmx request is in-flight h.HxBeforeRequest( h.SetText("Saving..."), h.SetDisabled(true), ), h.HxAfterRequest( h.SetText("Save"), h.SetDisabled(false), ), ) } func NotificationBanner() *h.Element { return h.Div( h.Id("banner"), h.Class("bg-yellow-100 p-4"), h.Text("New message received"), // Auto-remove after 3 seconds h.OnLoad( h.RunAfterTimeout(3*time.Second, h.Remove()), ), ) } ``` -------------------------------- ### h.LifeCycle / OnLoad / OnClick Source: https://context7.com/maddalax/htmgo/llms.txt Lifecycle functions attach `hx-on:*` or `on*` attributes that execute JavaScript commands when DOM events fire. Commands are Go values, eliminating the need for raw JS strings for common operations. Multiple commands can be combined in a single handler. ```APIDOC ## h.LifeCycle / OnLoad / OnClick — Client-side event handlers Lifecycle functions attach `hx-on:*` or `on*` attributes that execute JavaScript commands when DOM events fire. Commands are Go values—no raw JS strings needed for common operations. Combine multiple commands in one handler. ```go func AnimatedButton() *h.Element { return h.Button( h.Text("Click me"), h.Class("btn"), // Execute JS commands when element loads into DOM h.OnLoad( h.AddClass("opacity-100"), h.RemoveClass("opacity-0"), ), // Execute on click h.OnClick( h.SetText("Clicked!"), h.AddClass("bg-green-500"), h.RemoveClass("bg-blue-500"), ), ) } func LoadingButton() *h.Element { return h.Button( h.Text("Save"), h.Class("bg-blue-500 text-white px-4 py-2 rounded"), h.PostPartialOnClick(SaveData), // Disable self while htmx request is in-flight h.HxBeforeRequest( h.SetText("Saving..."), h.SetDisabled(true), ), h.HxAfterRequest( h.SetText("Save"), h.SetDisabled(false), ), ) } func NotificationBanner() *h.Element { return h.Div( h.Id("banner"), h.Class("bg-yellow-100 p-4"), h.Text("New message received"), // Auto-remove after 3 seconds h.OnLoad( h.RunAfterTimeout(3*time.Second, h.Remove()), ), ) } ``` ``` -------------------------------- ### Connect WebSocket in Index Page Source: https://context7.com/maddalax/htmgo/llms.txt Connect the client to the WebSocket endpoint by adding the `ws-connect` attribute to the body, including the session ID in the URL. ```go // pages/index.go — connect the WebSocket func IndexPage(ctx *h.RequestContext) *h.Page { sessionId := session.GetSessionId(ctx) return h.NewPage(h.Body( h.Attribute("ws-connect", fmt.Sprintf("/ws?sessionId=%s", sessionId)), CounterWidget(ctx), )) } ``` -------------------------------- ### RedisStore Implementation for Distributed Cache Source: https://github.com/maddalax/htmgo/blob/master/framework/h/cache/README.md Defines a RedisStore struct with Set and GetOrCompute methods for interacting with a Redis cache. Use this for distributed caching needs across multiple application instances. ```go type RedisStore struct { client *redis.Client prefix string } func (s *RedisStore) Set(key any, value string, ttl time.Duration) { keyStr := fmt.Sprintf("%s:%v", s.prefix, key) s.client.Set(context.Background(), keyStr, value, ttl) } func (s *RedisStore) GetOrCompute(key any, compute func() string, ttl time.Duration) string { keyStr := fmt.Sprintf("%s:%v", s.prefix, key) ctx := context.Background() // Try to get from Redis val, err := s.client.Get(ctx, keyStr).Result() if err == nil { return val } // Not in cache, compute new value // For true atomic guarantees, use Redis SET with NX option value := compute() s.client.Set(ctx, keyStr, value, ttl) return value } // ... implement other methods ``` -------------------------------- ### Collection Rendering with h.List, h.Filter, h.GroupByOrdered Source: https://context7.com/maddalax/htmgo/llms.txt Utilize h.List for rendering slices and h.Filter/h.GroupByOrdered for data manipulation. h.IterMap iterates over a map. ```go type Category struct { Name string Tasks []Task } func TaskBoard(tasks []Task) *h.Element { // Group tasks by status byStatus := h.GroupByOrdered(tasks, func(t Task) string { return t.Status }) // Filter only high-priority highPriority := h.Filter(tasks, func(t Task) bool { return t.Priority == "high" }) return h.Div( h.Class("grid grid-cols-3 gap-4"), h.Div( h.Class("col-span-3 mb-4"), h.H3(h.TextF("High priority (%d)", len(highPriority))), h.List(highPriority, func(t Task, i int) *h.Element { return h.P(h.Text(t.Name), h.Class("text-red-500")) }), ), // Iterate ordered map h.IterMap(byStatus.ToMap(), func(status string, group []Task) *h.Element { return h.Div( h.H4(h.Text(status), h.Class("font-bold capitalize")), h.List(group, func(t Task, _ int) *h.Element { return TaskItem(t) }), ) }), ) } ``` -------------------------------- ### WebSocket Component with Server-Event Handlers Source: https://context7.com/maddalax/htmgo/llms.txt A component that uses session state for a counter and handles WebSocket events for incrementing, broadcasting resets, and reacting to server-side events. ```go // partials/counter.go — component with server-event handlers func CounterWidget(ctx *h.RequestContext) *h.Element { sessionId := session.GetSessionId(ctx) get, set := session.UseState(sessionId, "count", 0) return h.Div( h.Id("counter"), h.P(h.TextF("Count: %d", get())), h.Button( h.Text("Increment"), // ws.OnClick fires over WebSocket, pushes updated HTML back ws.OnClick(ctx, func(data ws.HandlerData) { set(get() + 1) ws.PushElement(data, CounterWidget(ctx)) }), ), h.Button( h.Text("Broadcast Reset"), ws.OnClick(ctx, func(data ws.HandlerData) { set(0) ws.BroadcastServerSideEvent("counter-reset", map[string]any{}) }), ), // React to server-side events triggered by other sessions ws.OnServerEvent(ctx, "counter-reset", func(data ws.HandlerData) { set(0) ws.PushElement(data, CounterWidget(ctx)) }), ) } ``` -------------------------------- ### Create Session ID Middleware Source: https://context7.com/maddalax/htmgo/llms.txt A middleware function to ensure a session ID is created for every visitor before processing requests. ```go // middleware — create a session ID for every visitor app.Use(func(ctx *h.RequestContext) { session.CreateSession(ctx) }) ``` -------------------------------- ### htmgo Cache Store Interface Definition Source: https://github.com/maddalax/htmgo/blob/master/framework/h/cache/README.md Defines the generic `Store` interface for cache implementations in htmgo. It includes methods for setting, getting/computing, deleting, purging, and closing cache entries. ```go package main import "time" type Store[K comparable, V any] interface { // Set adds or updates an entry in the cache with the given TTL Set(key K, value V, ttl time.Duration) // GetOrCompute atomically gets an existing value or computes and stores a new value // This prevents duplicate computation when multiple goroutines request the same key GetOrCompute(key K, compute func() V, ttl time.Duration) V // Delete removes an entry from the cache Delete(key K) // Purge removes all items from the cache Purge() // Close releases any resources used by the cache Close() } ``` -------------------------------- ### Sanitizing User Input for Cache Keys Source: https://github.com/maddalax/htmgo/blob/master/framework/h/cache/README.md Provides a function to sanitize user input intended for use as cache keys. It limits the key length and removes potentially problematic characters to ensure consistency and security. ```go func cacheKeyForUser(userInput string) string { // Limit length and remove special characters key := strings.TrimSpace(userInput) if len(key) > 100 { key = key[:100] } return regexp.MustCompile(`[^a-zA-Z0-9_-]`).ReplaceAllString(key, "") } ``` -------------------------------- ### h.SwapManyPartial / h.OobSwap Source: https://context7.com/maddalax/htmgo/llms.txt `SwapManyPartial` performs htmx out-of-band swaps for multiple elements at once, matching elements by their `id` attribute. `OobSwap` appends the `hx-swap-oob=\"true\"` marker to a single element. Both return `*h.Empty()` for non-htmx requests. ```APIDOC ## h.SwapManyPartial / h.OobSwap — Out-of-band DOM updates `SwapManyPartial` performs htmx out-of-band swaps for multiple elements at once—each element is matched by its `id` attribute. `OobSwap` appends the `hx-swap-oob="true"` marker to a single element. Both return `*h.Empty()` for non-htmx requests. ```go func ToggleCompleted(ctx *h.RequestContext) *h.Partial { id := ctx.QueryParam("id") task := db.ToggleTask(id) allTasks := db.ListTasks() // Atomically update three distinct DOM sections in one response return h.SwapManyPartial(ctx, TaskListElement(allTasks), // must have id="task-card-list" FooterElement(allTasks), // must have id="task-card-footer" CompleteAllIcon(allTasks), // must have id="complete-all-icon" ) } func CreateTask(ctx *h.RequestContext) *h.Partial { name := ctx.FormValue("name") if name == "" { return h.NewPartial(h.Empty()) } db.CreateTask(name) allTasks := db.ListTasks() // Single OOB swap — replace the entire card body return h.NewPartial( h.OobSwap(ctx, CardBody(allTasks)), ) } // SwapManyPartialWithHeaders — swap elements AND update the browser URL func ChangeTab(ctx *h.RequestContext) *h.Partial { tab := ctx.QueryParam("tab") tasks := db.ListTasks() return h.SwapManyPartialWithHeaders( ctx, h.PushQsHeader(ctx, h.NewQs("tab", tab)), // push ?tab=active to URL bar TaskListElement(tasks), FooterElement(tasks), ) } ``` ``` -------------------------------- ### h.SwapManyPartial / h.OobSwap for Out-of-Band DOM Updates Source: https://context7.com/maddalax/htmgo/llms.txt SwapManyPartial performs htmx out-of-band swaps for multiple elements at once, matching by ID. OobSwap appends the hx-swap-oob="true" marker to a single element. Both return *h.Empty() for non-htmx requests. ```go func ToggleCompleted(ctx *h.RequestContext) *h.Partial { id := ctx.QueryParam("id") task := db.ToggleTask(id) allTasks := db.ListTasks() // Atomically update three distinct DOM sections in one response return h.SwapManyPartial(ctx, TaskListElement(allTasks), // must have id="task-card-list" FooterElement(allTasks), // must have id="task-card-footer" CompleteAllIcon(allTasks), // must have id="complete-all-icon" ) } func CreateTask(ctx *h.RequestContext) *h.Partial { name := ctx.FormValue("name") if name == "" { return h.NewPartial(h.Empty()) } db.CreateTask(name) allTasks := db.ListTasks() // Single OOB swap — replace the entire card body return h.NewPartial( h.OobSwap(ctx, CardBody(allTasks)), ) } // SwapManyPartialWithHeaders — swap elements AND update the browser URL func ChangeTab(ctx *h.RequestContext) *h.Partial { tab := ctx.QueryParam("tab") tasks := db.ListTasks() return h.SwapManyPartialWithHeaders( ctx, h.PushQsHeader(ctx, h.NewQs("tab", tab)), // push ?tab=active to URL bar TaskListElement(tasks), FooterElement(tasks), ) } ``` -------------------------------- ### Component-Level HTML Caching with h.Cached Source: https://context7.com/maddalax/htmgo/llms.txt Globally cache rendered HTML for a duration using h.Cached. This is ideal for expensive computations or external API calls that don't change frequently. ```go // Globally cached — same HTML for all users for 30 seconds var CachedNavbar = h.Cached(30*time.Second, func() *h.Element { categories := db.ListCategories() // only called once per 30s return h.Nav( h.Class("flex gap-4"), h.List(categories, func(c Category, _ int) *h.Element { return h.A(h.Href("/c/"+c.Slug), h.Text(c.Name)) }), ) }) ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.