Use HTML With Htmx and Reduce the Amount of Code

HTML as the Center of the Universe

The internet as we know it today is largely due to HTML and CSS. Javascript (JS) could act as a glue between them and make pages more dynamic and interactive, but the history of web programming developed differently. After the emergence of client-side rendering and other similar technologies, it became much more difficult to use JS to create web applications.

What Is Htmx?

Htmx is a library that allows you to create modern and powerful user interfaces with simple markup. Thanks to it, AJAX requests, triggering CSS transitions, calling WebSocket and server-sent events can be performed directly from HTML elements.

SSR Applications

The use of htmx motivates to gradually abandon client-side rendering in favor of server-side rendering. SSR is considered to be the last resort and is used only when there is a need to quickly improve performance. However, server-side rendering can build the entire user interface of an application.

Htmx does not require any other JS packages to run, and is framework- and language-independent. Therefore, it can be used with any server platform, such as Node Express, RAILS, Django, Phoenix, Laravel, etc.

Reusing Components on the Server

Htmx allows you to reuse server-side UI elements with more familiar libraries; for example, Pug for Node or templating libraries for RAILS and Django. It helps make HTML complex and dynamic.

Here is Rentals Listing, a demo app built in Express.js and HTML. It uses the same partial both for statistical and dynamic scenarios:

ul.results
  each rental in rentals
    li
      article.rental
        button.image(type="button", _="on click toggle .large then if #view-caption.textContent === 'View Larger' then set #view-caption.textContent to 'View Smaller' else set #view-caption.textContent to 'View Larger'")
          img(src=rental.attributes.image, alt="An image of " + rental.attributes.title)
          small#view-caption View Larger
        .details
          h3
            a(href="https://dzone.com/rentals/" + rental.id) #{rental.attributes.title}
          .detail.owner
            span Owner:
            | #{rental.attributes.owner}
          .detail.type
            span Type:
            | #{rental.attributes.category}
          .detail.location
            span Location:
            | #{rental.attributes.city}
          .detail.bedrooms
            span Bedrooms:
            | #{rental.attributes.bedrooms}
        .map
          img(alt="A map of " + rental.attributes.title, src=rental.mapbox, width="150",height="150")

In the listing of the home page, I used include from the Pug library to display the partial:

extends layout
block content
  .jumbo
    .right
    h2 Welcome to Super Rentals!
    p We hope you find exactly what you're looking for in a place to stay.
    a.button(href="https://dzone.com/about") About Us
  .rentals
    label
      span Where would you like to stay?
      input.light(type="text", name="search",
       hx-post="https://dzone.com/search" ,
       hx-trigger="keyup changed delay:500ms" ,
       hx-target=".results" ,
       hx-indicator=".htmx-indicator")

include includes/rental-list.pug

Every time a user searches for a rental property on the website, I use the same partial to populate the search results. The result looks like this:

{ const { search } = req.body; const results = _rentals.data.filter(r => { const _search = search.toLowerCase(); const _title = r.attributes.title.toLowerCase(); return _title.includes(_search); }); const template = pug.compileFile(‘views/includes/rental-list.pug’); const markup = template({ rentals: results }); res.send(markup); });” data-lang=”text/javascript”>

app.post("https://dzone.com/search", (req, res) => {
  const { search } = req.body;

  const results = _rentals.data.filter(r => {
    const _search = search.toLowerCase();
    const _title = r.attributes.title.toLowerCase();
    return _title.includes(_search);
  });
  const template  = pug.compileFile('views/includes/rental-list.pug');
  const markup = template({ rentals: results });
  res.send(markup);
});

Server-Side Routing

Client-side routing poses a whole host of problems. For example, there is always a dilemma between hash-based routing and URL-based routing. Since the history API is not supported in older browsers (such as Internet Explorer 11), hash-based routing that uses fragment IDs in the URL is almost always preferred.

Most JS frameworks implement their own client-side routing logic. At the same time, all frameworks use their own browser API, such as window.history. This leads to a lot of boilerplate code in the application.

Less JS Code

In my opinion, the main advantage of htmx is the amount of JS code that we write and send to the browser. Together with hyperscript, the library allows you to create rich interactive applications without using client-side code in JS:

<!-- have a button POST a click via AJAX -->
<button hx-post="/clicked" hx-swap="outerHTML">
    Click Me
</button>

When single-page apps first started to gain popularity, the community adopted JSON as the standard for data interchange. Now, in order to reverse-engineer HTML from JSON data, you often need to process a large amount of client-side data that comes from the server through the API. API responses often contain either incomplete or redundant data.

To solve this problem, sophisticated alternatives like GraphQL have been developed, thanks to which you can get only the data you need from the server. Htmx provides a better solution: you just need to replace the HTML with the HTML response received from the server: no more client-side data.

No Building/Compilation

Another advantage of htmx is the lack of web application building tools. You can use CDN tools instead:

<!-- Load from unpkg -->
<script src="<https://unpkg.com/htmx.org@1.3.3>"></script>

Lack of building is a global trend in web application development. On one hand, the ES module specifications have been accepted by all browser developers. On the other hand, now we have the Skypack, Snowpack, and Vite tools that can be combined with the CDN and ESM approaches. All of this will ultimately lead to the fact that there will be less building for client-side JavaScript. Add to that the lack of the need to install thousands of npm packages and maintain complex building configurations.

Unified Codebase

Two codebases for one application mean additional development challenges. In particular, you need to synchronize update deployments, configure the build pipeline twice, update the framework in both bases, maintain your code, and run test packages from two sources.

Htmx allows you to combine all the code in one place: since the rendering takes place on the server-side, there is no need for a separate base for the interface. This can save you a lot of time and money in the long run. Besides, developers can act more consistently: they won’t have to check two or more repositories.

Locality of Behavior Principle (LoB)

The LoB principle was formulated by programming theorist Richard Gabriel. It states that all developers should strive to ensure that the behavior of each piece of code should be obvious when it is validated.

According to Gabriel, locality is essential to keeping your code easy to maintain. Locality is a characteristic that allows the programmer to understand which part of the architecture the code belongs to after seeing only a small part of it.

It looks like this:

The behaviour of a code unit should be as obvious as possible by looking only at that unit of code
<div hx-get="/clicked">Click Me</div>

LoB often conflicts with other principles of software development, such as separation of concerns. This conflict was resolved when React introduced HTML and CSS to JavaScript. At the same time, React creator Pete Hunt believes that this is about the separation of technologies:

Lack of Problems With Synchronization of States

Client-side state management creates more problems than it solves. Implementation of this principle leads to the fact that state management is needed both on the client-side and on the server-side. An alternative solution is to store the state on the server. In this case, the client serves as a dummy executor for rendering the state change.

This is similar to the thin-client model, an undergraduate computer with a lightweight operating system that connects to a terminal server. Such devices were used to create the first web applications for saving resources.

Htmx will help you avoid obfuscating UI code in the state management network; for example, two-way data binding, unidirectional data flow, and reactive data.

.

Leave a Comment