Home

2 Methods for Binding JavaScript Events with 11ty

When using raw JavaScript with 11ty, there are multiple approaches you can take for binding events, all without complicating the JavaScript you’re using.

11ty is a classic static site generator in the sense that the code gets executed on the server during build time, producing static assets that get deployed to production. And one of the reasons I love 11ty is that it encourages minimizing the amount of JavaScript used.

Often, JavaScript becomes necessary at some point — e.g. for real-time data, interactive elements, etc. When that happens, there isn't a clear path to the best approach with 11ty. Here are two approaches you can take.

The approaches below both assume a simple example of a button with a click counter.

Call JavaScript Inline

The first is to call JavaScript inline using HTML attributes. This is the old-school approach. One that was largely frowned upon until React came around and now makes it feel natural again.

<button onclick="incrementCount('#btn-01-count')">Click Me</button>
<span id="btn-01-count">0</span> clicks

<script>
function incrementCount(counterSelector) {
const counterEl = document.querySelector(counterSelector);
const count = parseInt(counterEl.innerText);
counterEl.innerText = count + 1;
}
</script>

One thing I really like about this approach is that it's declarative from the markup. Your JavaScript is defined, but the JS code doesn't need to initiate any action. It just needs to be ready when the click happens.

Avoid Naming Conflicts

An issue I've always had with this approach is that it's very easy to run into naming conflicts. As your project grows, it's difficult to keep a handle on all the function names you've used throughout the application.

Therefore, I tend to wrap everything (or at least parts) of my JavaScript code into a global object, from which I can then call these methods.

In my JavaScript file, I have the function, defined as a property on a global object:

window.App = {
incrementCount: function (counterSelector) {
const counterEl = document.querySelector(counterSelector);
const count = parseInt(counterEl.innerText);
counterEl.innerText = count + 1;
},
};

And then I call App.incrementCount rather than incrementCount being available on the global window object.

<button onclick="App.incrementCount('#btn-01-count')">Click Me</button>
<span id="btn-01-count">0</span> clicks

Look for Data Attributes

Another approach is to bind events in the JS code directly to elements. I usually like to use data attributes to do this.

Consider this markup:

<button data-click-increment="#btn-02-count">Click Me</button>
<p><span id="btn-02-count">0</span> clicks</p>

When JavaScript is loaded and the DOM is ready, we can target and loop through all elements with the appropriate data attribute (data-click-increment) and bind the incrementCount function.

function incrementCount(counterSelector) {
const counterEl = document.querySelector(counterSelector);
const count = parseInt(counterEl.innerText);
counterEl.innerText = count + 1;
}

const clickableEls = document.querySelectorAll("[data-click-increment]");
for (const el of clickableEls) {
el.addEventListener("click", function () {
incrementCount(el.dataset.clickIncrement);
});
}

What I don't like about this approach is that it runs automatically on every page load, even when you may not need it.

I also don't love that the event binding is in the JavaScript and not the HTML, which I've found makes issues more difficult to debug.

JavaScript Event Playground

See these examples in action.

Let's Connect

Keep Reading

6 Reasons I (Still) Love 11ty

Two years after launching my site with 11ty, it’s grown considerably, and yet I’m still in love with the tool.

Aug 05, 2022

JavaScript for 11ty with esbuild

As your 11ty application evolves, you’ll want more organization with your JavaScript. Here’s a method for bundling together using esbuild.

Sep 30, 2022

3 Ways to Render Server-Side Components with Eleventy

While Eleventy doesn't appear to be built for today's component-driven landscape, here are three approaches we can take to get closer.

Jan 10, 2021