JavaScript without JavaScript: Intercooler.js

Intercooler.js is a wonderful way to add dynamicity to applications without bringing in the complexity of a frontend framework.

More than enough has been written about single-page applications, frontend frameworks and when they are and are not appropriate.

Instead, I'd like to talk to you about how to add a little dynamicity to those web applications that clearly need some, but the thought of pulling in MBs of dependencies and configuring webpack for what should be couple hundred lines of JavaScript rankles.

Intercooler.js is a little JavaScript library that is ideal for these use cases. I saw it several months ago on Hacker News, and earmarked it for a rainy day. That day has arrived, and this week I used intercooler.js along with Flask to write a little link shortener, the details of which I'm going to share with you.

What an intercooler actually looks like, apparently

First, the basic idea of intercooler.js is that you describe AJAX requests using HTML attributes, just like the one below:

<button ic-get-from="/images/javascript-without-javascript/data.txt">Click here!</button>

The creator of Intercooler summarizes it thus:

intercooler.js is, for the most part, just updated HTML, generalizing the anchor/form HTTP request submission mechanism across elements, events, HTTP actions and replacement targets. It is how HTML should work normally.

My goal for this was to see how far I could get without writing a line of JavaScript myself, and I am pretty happy with the result. While you obviously might need to write some JavaScript for more complex use cases, Intercooler is surprisingly expressive.

The link shortener I wrote does the following things dynamically:

  • Validate that the form fields are correct (i.e. there's a reasonably descriptive title, the URL is valid). If they aren't, an error will pop up before the full form is POSTed.
  • Return the shortened link (along with a little toast message if the link has already been shortened), and update the browser history to reflect it
  • Poll for new links, displaying a list of the most recently shortened link

It looks like this:

video of cool-links use

For example, here's the code that polls for a list of recent links:

<div ic-trigger-on="load" ic-poll="10s" ic-src="/recent"></div>

In English: when the page loads and every ten seconds afterwards, replace the contents of the div with the results of GETting /recent. This is part of what I like about Intercooler: you can basically read it out in plain language and figure out what's going on really quickly.

In addition to allowing us to describe a wide variety of requests, it can also modify state in response to HTTP headers.

When a user submits a link for shortening, we return the new link to be rendered in the existing page, but make sure to modify the browser history to reflect that the submission occurred:

res.headers['X-IC-Title'] = f'Cool Links: {title}'
res.headers['X-IC-PushURL'] = f'/view/{short_id}'

Probably the hairiest part of working with this is writing several different HTML fragments for a simple page. For example, here's part of the code for the submission, with CSS formatting stuff stripped:

<div class="form-group">
  <form ic-post-to="/submit" ic-replace-target="true" ic-target="#my-link">
  <!-- The input below is a separate input_url.html file, included in this file, form_submit.html, so it
  can either be returned as a fragment in response to ic-post-to, or as a result of the whole form
  being posted -->
  <div>
    <label for="url">URL:</label>
    <input type="url" 
      ic-post-to="/submit/validate-url" name="url" value="{% raw %}{{url}}{% endraw %}" placeholder="Site URL" 
      ic-target="closest div" ic-replace-target="true">
    {% raw %}{% if url_error %}<p>{{url_error}}</p>{% endif %}{% endraw %}
  </div>
  <!-- omitted the title input which is essentially the same -->
  <button type=submit>Submit URL</button>
</form>

When the input is blurred, it will post the form to /submit/validate-url, which will return input_url.html with any errors added, which then replaces the closest div.

When the whole form is submitted, the rendered link will replace the #my-link div on the page.

I can see this fragmentary approach becoming problematic for larger applications, but I can also see that a small library could generate most of the code and routes needed on the fly (which I might write if I end up using intercooler.js more in the future). And the above code will gracefully handle browsers with JavaScript disabled as well.

If you're wondering where the code is, you've actually already seen most of the client-side code above. The rest is just basic Python using Flask/peewee to store and fetch the links from an sqlite database.

The verdict

intercooler.js with zepto, minified and gzipped, clocks in at about 18kb.

What I really liked about using intercooler is how simple it was. I wrote the code in basically a day, and that was without ever having used it before. I think it's a breath of fresh air and look forward to using it in more projects from here on out.