Unpoly Notes

Last weekend I spent some time playing with HTMX and created this app. While HTMX significantly simplifies the server and client interactions, for example I no longer need to create separate REST APIs to serve the client, but I feel like the DevX is not great. There are 2 things I had to deal with while working on that simple vacabulary app are:

1 - No REST API endpoints needed, but new endpoints to return partial HTMLs are needed. Technically, you can just reuse the existing endpoint, and conditionally return partial HTMLs, but I consider both approaches are pretty much the same in terms of engineering cost.

2 - It’s cumbersome to wire js handles with events. You have to be careful when to register the events. For example you need to make sure the target elements have been injected to the page. Also, if you have a non trivial JS logic, putting them inline is not ideal. For example, in the Vocabulary app, user can click a button to add the corresponding definition to Anki. This action requires a POST request on the client side to a local server that is started by the AnkiConnect add-on. Writing the fetch call with request body inline seems like a bad idea to me.

I was unhappy.

Until I found Unpoly.

Unpoly is similar to HTMX, but solves the two problems above perfectly.

With unpoly, you do not have to write HTML partials. You can keep your old endpoint and still return the full page. Unpoly only replaces specied target element and discards the rest. In a way it feels simimilar to the DOM diffing thing that SPA frameworks have. Only in this case, you have to tell it specifically where the diff is, which is pretty easy to do because you already know what changes you want to make in the UI in response to an event.

Wiring js script to an event is also easy. I don’t have to check if the element is on the page or not. I was plesantly surprised to find that out. Also the syntax to associate data + js handler with an element is very clean. It makes it super easy to move JS out of HTML into a seperate file. For example: in order to handle the click event to add new definition to Anki, I do the following:

<button class="js-add-to-anki" data-definition="{{deinitionText}}>
    Add to Anki
</button>

and in a seperate js file:

up.on("click", ".js-add-to-anki", (_event, _element, data) => {
  fetch("https://localhost:<port>/", {
    method: "POST",
    body: JSON.stringify({
      prop1: {
        nestedProp: {
          text: data.definition, // <--- data binding
          value2: "test2",
        },
      },
      prop2: {
        // etc
      },
    }),
  });
});

That is it! I also appreciate the fact that I don’t have to worry about the possibility of having multiple buttons on the same page (a word might have multiple definitions). Unpoly correctly attaches the event handler to each button with the class js-add-to-anki. The js prefix is a convention that I use to remind me which class is being used in JS. Classes without js prefix are for styling purposes.

I love Unpoly! Now I need to find another toy project to play with this delightful library!!!