Skip to main content
net8.0

HTMX Bindings for the Falco web toolkit.

Published
Nov 15, 2024
1y ago
Total Downloads
5.8K
5,816

Install

dotnet add package Falco.Htmx --version 1.0.0-beta1
ProductVersions
.NET
net8.0

README

Falco.Htmx

NuGet Version build

open Falco.Markup
open Falco.Htmx

let demo =
    Elem.button
        [ Hx.get "/click-me"
          Hx.swapOuterHtml
          Hx.targetCss "#wrapper" ]
        [ Text.raw "Reset" ]

Falco.Htmx brings type-safe htmx support to Falco. It provides a complete mapping of all attributes, typed request data and ready-made response modifiers.

Key Features

  • Idiomatic mapping of htmx attributes (i.e., hx-get, hx-post, hx-target etc.).
  • Typed access to htmx request headers.
  • Prepared response modifiers for common use-cases (i.e., HX-location, HX-Push-Url).

Design Goals

  • Create a self-documenting way to integrate htmx into Falco applications.
  • Match the specification of htmx as closely as possible, ideally one-to-one.
  • Provide type safety without over-abstracting.

Getting Started

This guide assumes you have a Falco project setup. If you don't, you can create a new Falco project using the following commands. The full code for this guide can be found in the Hello World example.

> dotnet new web -lang F# -o HelloWorld
> cd HelloWorldApp

Install the nuget package:

> dotnet add package Falco
> dotnet add package Falco.Htmx

Remove any *.fs files created automatically, create a new file named Program.fs and set the contents to the following:

open Falco
open Falco.Htmx
open Falco.Markup
open Falco.Routing
open Microsoft.AspNetCore.Builder

let bldr = WebApplication.CreateBuilder()
let wapp = bldr.Build()

let endpoints =
    [
    ]

wapp.UseFalco(endpoints)
    .Run()

Now, let's incorporate htmx into our Falco application. First we'll define a simple route that returns a button that, when clicked, will swap the inner HTML of a target element with the response from a GET request.

let handleIndex : HttpHandler =
    let html =
        Elem.html [] [
            Elem.head [] [
                Elem.script [ Attr.src HtmxScript.cdnSrc ] [] ]
            Elem.body [] [
                Text.h1 "Example: Hello World"
                Elem.button
                    [ Hx.get "/click"
                      Hx.swapOuterHtml ]
                    [ Text.raw "Click Me" ] ] ]

    Response.ofHtml html

Next, we'll define a handler for the click event that will return HTML from the server to replace the outer HTML of the button.

let handleClick : HttpHandler =
    let html =
        Text.h2 "Hello, World from the Server!"

    Response.ofHtml html

And lastly, we'll make Falco aware of these routes by adding them to the endpoints list.

let endpoints =
    [
        get "/" handleIndex
        get "/click" handleClick
    ]

Save the file and run the application:

> dotnet run

Navigate to https://localhost:5001 in your browser and click the button. You should see the text "Hello, World from the Server!" appear in place of the button.

htmx Attributes

hx-[get|post|put|patch|delete]

Elem.button [ Hx.put "/messages" ] [
    Text.raw "Put to Messages" ]

hx-trigger

Elem.div [ Hx.post "/mouse-enter"; Hx.trigger "mouseenter" ] [
    Text.raw "Here mouse, mouse!" ]

// Trigger modifiers
Elem.div [ Hx.post "/mouse-enter"; Hx.trigger ("mouseenter", [HxTrigger.Once]) ] [
    Text.raw "Here mouse, mouse!" ]

// Trigger filters
Elem.div [ Hx.post "/mouse-enter"; Hx.trigger ("mouseenter", [HxTrigger.Once], "ctrlKey") ] [
    Text.raw "Here mouse, mouse!" ]

hx-target

Elem.form [] [
    Elem.input [ Hx.get "/search"; Hx.target "#search-results" ]
]
Elem.div [ Attr.id "search-results" ] []

hx-swap

Elem.button [ Hx.post "/like"; Hx.swapOuterHtml ] [
    Text.raw "Like" ]

hx-swap-oob

Elem.div [ Attr.id "message"; Hx.swapOobOn ] [
    Text.raw "Swap me directly" ]

// Equivalent to:
Elem.div [ Attr.id "message"; Hx.swapOob HxSwap.OuterHTML ] [
    Text.raw "Swap me directly" ]

// With a selector:
Elem.div [ Attr.id "message"; Hx.swapOob (HxSwap.InnerHTML, "#falco") ] [
    Text.raw "Swap me directly" ]

hx-select

Elem.button [ Hx.get "/info"; Hx.select "#info-detail"; Hx.swapOuterHtml ] [
    Text.raw "Get Info" ]

hx-select-oob

Elem.div [ Attr.id "alert" ] []
Elem.button [ Hx.get "/info"; Hx.select "#info-detail"; Hx.swapOuterHtml; Hx.selectOob "#alert" ] [
    Text.raw "Get Info" ]

hx-boost

Elem.div [ Hx.boostOn ] [
    Elem.a [ Attr.href "/blog" ] [ Text.raw "Blog" ] ]

hx-push-url

Elem.div [ Hx.get "/account"; Hx.pushUrl true ] [
    Text.raw "Go to My Account" ]

// Or short form:
Elem.div [ Hx.get "/account"; Hx.pushUrlOn ] [
    Text.raw "Go to My Account" ]

// Or specify URL:
Elem.div [ Hx.get "/account"; Hx.pushUrl "/my-account" ] [
    Text.raw "Go to My Account" ]

hx-sync

Elem.form [ Hx.post "/store" ] [
    Elem.input [ Attr.name "title"; Hx.post "/validate"; Hx.trigger "change"; Hx.sync ("form", HxSync.Abort) ] ]

hx-include

Elem.button [ Hx.post "/register"; Hx.includeCss "[name=email]" ] [
    Text.raw "Register!" ]
Elem.span [] [
    Text.raw "Enter email: "
    Elem.input [ Attr.name "email"; Attr.type' "email" ] [] ]

// Hx.includeCss "[name=email]" is equivalent to:
Elem.button [ Hx.post "/register"; Hx.include' (HxTarget.Css "[name=email]") ] [
    Text.raw "Register!" ]
Elem.span [] [
    Text.raw "Enter email: "
    Elem.input [ Attr.name "email"; Attr.type' "email" ] [] ]

hx-params

Elem.div [ Hx.get "/example"; Hx.params "*" ] [
    Text.raw "Get Some HTML, Including Params" ]

hx-vals

Get Some HTML, Including A Value in the Request Get Some HTML, Including a Dynamic Value from Javascript in the Request

Elem.div [ Hx.get "/example"; Hx.vals """{"myVal": "My Value"}""" ] [
    Text.raw "Get Some HTML, Including A Value in the Request" ]

// Or with a dynamic value:
Elem.div [ Hx.get "/example"; Hx.vals "js:{myVal: calculateValue()}" ] [
    Text.raw "Get Some HTML, Including a Dynamic Value from Javascript in the Request" ]

hx-confirm

Delete My Account

Elem.button [ Hx.delete "/account"; Hx.confirm "Are you sure you wish to delete your account?" ] [
    Text.raw "Delete My Account" ]

hx-disable

Elem.div [ Hx.disable ] []

hx-disabled-elt

Elem.button [ Hx.post "/example"; Hx.disabledThis ] [
    Text.raw "Post It!" ]

// Equivalent to:
Elem.button [ Hx.post "/example"; Hx.disabled HxTarget.This ] [
    Text.raw "Post It!" ]

hx-inherit

Elem.div [ Hx.targerCss "#tab-container"; Hx.inherit' "hx-target" ] [
    Elem.a [ Hx.boostOn; Attr.href "/tab1" ] [ Text.raw "Tab 1" ]
    Elem.a [ Hx.boostOn; Attr.href "/tab2" ] [ Text.raw "Tab 2" ]
    Elem.a [ Hx.boostOn; Attr.href "/tab3" ] [ Text.raw "Tab 3" ] ]

hx-disinherit

Elem.div [ Hx.boostOn; Hx.select "#content"; Hx.targetCss "#content"; Hx.disinherit "hx-target" ] [
    Elem.button [ Hx.get "/test" ] [] ]

hx-encoding

Elem.form [ Hx.encodingMultipart ] [
    (* ... form controls ... *) ]

hx-ext

Elem.div [ Hx.ext "example" ] [
    Text.raw "Example extension is used in this part of the tree..."
    Elem.div [ Hx.ext "ignore:example" ] [
        Text.raw "... but it will not be used in this part." ] ]

hx-headers

Get Some HTML, Including A Custom Header in the Request

Elem.div [ Hx.get "/example"; Hx.headers [ "myHeader", "My Value" ] ] [
    Text.raw "Get Some HTML, Including A Custom Header in the Request" ]

// Or to evaluate a dynamic value:
Elem.div [ Hx.get "/example"; Hx.headers ([ "myHeader", "calculateValue()" ], true) ] [
    Text.raw "Get Some HTML, Including A Custom Header in the Request" ]
// ^-- produces hx-headers='js:{"myHeader": calculateValue()}'

hx-history

Elem.div [ Hx.historyOff ] []

hx-history-elt

Elem.div [ Hx.historyElt ] []

hx-indicator

Elem.div [] [
    Elem.button [ Hx.post "/example"; Hx.indicator "#spinner" ] [
        Text.raw "Post It!" ]
    Elem.img [ Attr.id "spinner"; Attr.class' "htmx-indicator"; Attr.src "/img/bars.svg" ] ]

Kudos

Big thanks and kudos to @dpraimeyuu for their collaboration in starting this repo!

Find a bug?

There's an issue for that.

License

Built with ♥ by Pim Brouwers and Damian Plaza. Licensed under Apache License 2.0.

Changes from Previous Version

v0.0.5v1.0.0-beta1
Added Frameworks
net8.0
Removed Frameworks
net6.0
Added Dependencies
FSharp.Core(net8.0)
Falco(net8.0)
Removed Dependencies
FSharp.Core(net6.0)
Falco(net6.0)

Adoption Assessment

Assess this package against its supported frameworks, license, maintenance signals and dependency graph before making it a project standard.

This version is marked prerelease. Validate its API and upgrade path before production use.Compatibility metadata lists 1 target framework; confirm the exact target used by your application.

Related Guides & Workflows

Learn how to evaluate, integrate, or audit this package in your .NET project.