Posts Tagged “fsharp”

Thursday, February 9, 2023
  Page-Level JavaScript Initialization with htmx

Development with htmx focuses on generating content server-side and replacing portions of the existing page (or the entire content) with the result, with no build step and no additional JavaScript apart from the library itself. The only JavaScript required is whatever the application may need for interactivity on the page. This can be tricky, though, when the front-end uses a library that requires a JavaScript initialization call; how do we know when (or how, or whether) to initialize it?

The answer is HTML Events. For regular page loads, the DOMContentLoaded event is the earliest in the page lifecycle where the content is available; references to elements with particular ids will work. htmx offers its own events, and the one that corresponds to DOMContentLoaded is htmx:afterSwap. (Swapping is the process of updating the existing page with the new content; when it is complete, the old elements are gone and the new ones are available.) The other difference is that DOMContentLoaded is fired from the top-level Window element (accessible via the document element), while htmx:afterSwap is fired from the body tag.

All that being said, the TL;DR process is:

  • If the page loads normally, initialize it on DOMContentLoaded
  • If the page is loaded via htmx, initialize it on htmx:afterSwap

and looks something like:

document.addEventListener([event], () => { [init-code] }, { once: true })

When htmx makes a request, it includes an HX-Request header; using this as the flag, the server can know if it is preparing a response to a regular request or for htmx. (There are several libraries for different server-side technologies that integrate this into the request/response pipeline.) As the server is in control of the content, it must make the determination as to how this initialization code should be generated.

This came up recently with code I was developing. I had a set of contact information fields used for websites, e-mail addresses, and phone numbers (type, name, and value), and the user could have zero to lots of them. ASP.NET Core can bind arrays from a form, but for that to work properly, the name attributes need to have an array index in them (ex. Contact[2].Name). To add a row in the browser, I needed the next index; to know the next index, I needed to know how many I rendered from the server.

To handle this, I wrote a function to render a script tag to do the initialization. (This is in F# using Giraffe View Engine, but the pattern will be the same across languages and server technologies.)

let jsOnLoad js isHtmx =
    script [] [
        let (target, event) =
            if isHtmx then "document.body", "htmx:afterSettle"
            else "document", "DOMContentLoaded"
        rawText (sprintf """%s.addEventListener("%s", () => { %s }, { once: true })"""
                    target event js)
    ]

Then, I call it when rendering my contact form:

    jsOnLoad $"app.user.nextIndex = {m.Contacts.Length}" isHtmx

Whether the user clicks to load this page (which uses htmx), or goes there directly (or hits F5), the initialization occurs correctly.

Categorized under ,
Tagged , , , ,

Thursday, November 24, 2022
  Fragment Rendering in Giraffe View Engine

A few months back, Carson Gross, author of the outstanding htmx library, wrote an essay entitled “Template Fragments”; if you are unfamiliar with the concept, read that first.

The goal is to allow an application to render a partial view without defining that partial view as its own template, in its own file. A common pattern in hypermedia-driven applications (and listed “con” for them) is that every little piece that may need to be rendered on its own must be broken out into its own template. This pattern eliminates the “locality of behavior” benefits; you cannot even see your entire template together in one place. Fragment rendering eliminates that requirement.

Developing with Giraffe View Engine, we can do part of this out-of-the-box, as we can control the functions we use to create the nodes that will eventually be rendered. And, while there is not a concept of a “template fragment” in Giraffe View Engine, the term “fragment” as applied to an HTML document identifies its id attribute.

Giraffe.ViewEngine.Htmx, as of v1.8.4, now supports rendering these fragments based on the id attribute. Giraffe has the RenderView module where its rendering functions are defined; opening the Giraffe.ViewEngine.Htmx namespace exposes the RenderFragment module. Its functions follow the pattern of Giraffe View Engine:

  • AsString functions return a string
  • AsBytes functions return a byte array
  • IntoStringBuilder functions render into the provided StringBuilder

Each of those three modules has htmlFromNodes if the source is a node list, and htmlFromNode if the source is a single node. (As fragment rendering has little-to-no use in the XML world, XML rendering functions are not provided.)

One final thought - while this was built with htmx applications in mind, the concept does not require htmx. If you want to render fragments with Giraffe View Engine, using this package will allow that, whether your application uses htmx (you probably should!) or not.

Categorized under , ,
Tagged , , , ,