Falco.Htmx
1.0.0-beta1
See the version list below for details.
dotnet add package Falco.Htmx --version 1.0.0-beta1
NuGet\Install-Package Falco.Htmx -Version 1.0.0-beta1
<PackageReference Include="Falco.Htmx" Version="1.0.0-beta1" />
paket add Falco.Htmx --version 1.0.0-beta1
#r "nuget: Falco.Htmx, 1.0.0-beta1"
// Install Falco.Htmx as a Cake Addin #addin nuget:?package=Falco.Htmx&version=1.0.0-beta1&prerelease // Install Falco.Htmx as a Cake Tool #tool nuget:?package=Falco.Htmx&version=1.0.0-beta1&prerelease
Falco.Htmx
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
<div hx-get="/example" hx-vals='{"myVal": "My Value"}'>Get Some HTML, Including A Value in the Request</div> <div hx-get="/example" hx-vals='js:{myVal: calculateValue()}'>Get Some HTML, Including a Dynamic Value from Javascript in the Request</div>
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
<button hx-delete="/account" hx-confirm="Are you sure you wish to delete your account?"> Delete My Account </button>
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
<div hx-boost="true" hx-select="#content" hx-target="#content" hx-disinherit="hx-target"> <button hx-get="/test"></button> </div>
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
<div hx-get="/example" hx-headers='{"myHeader": "My Value"}'>Get Some HTML, Including A Custom Header in the Request</div>
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.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
-
net8.0
- Falco (>= 5.0.0-beta1)
- FSharp.Core (>= 8.0.403)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
1.0.0-beta2 | 55 | 11/20/2024 |
1.0.0-beta1 | 73 | 11/15/2024 |
0.0.5 | 180 | 3/19/2024 |
0.0.4 | 197 | 1/10/2024 |