Blog

HTML event handler attributes: down the rabbit hole

Published on by

I was throwing together a license management app for DataStation and got to form submissions. DataStation has a complex user interface and benefits from React. But this license management app I wanted to keep simple using only server-side templates (Pongo2, a Go clone of Jinja templates).

But I wanted all of my Go code to just deal with JSON HTTP bodies rather than multipart/form-data.

A basic form in the app looks like this:

<form method="post">
  <div class="form-row">
    <label>Email: </label><input type="email" name="email"/>
  </div>
  <div class="form-row">
    <label>Password: </label><input type="password" name="password"/>
  </div>

  <button>Sign In</button>
</form>

I knew I'd have a bunch of forms around the app. So I wanted to just have some reusable JavaScript that would turn form submissions into a POST request and prevent the default behavior of submitting the form.

The JavaScript would look like this:

function submit(e) {
  const form = e.target.closest('form');
  if (form.method === 'get') {
    return; // default behavior is ok
  }
  e.preventDefault();

  const values = {};
  for (const el of form.querySelectorAll('input')) {
    values[el.name] = el.value;
  }

  fetch(location.pathname, {
    method: form.method,
    headers: { 'content-type': 'application/json' },
    credentials: 'same-origin',
    body: JSON.stringify(values),
  });
}

So this way each form could just call this function in some way and they'd all submit their data as JSON-encoded HTTP bodies.

In each form all I'd have to do is something like this:

<button onclick="submit">...</button>

And I'd be set. But I tried that out and nothing happened. So I switched to onclick="submit()". And that worked. But where is the event?

Let's check the internet. MDN docs on the onclick attribute. Nothing. Google "onclick html attribute access event". Nothing.

All the examples on Google/StackOverflow/MDN do show a function call. Some show something like onclick="submit(this)". But this in this context is the button element not the event. None of them show anything to do with an event.

I need to pass the event to submit to be able to prevent default. How do I do this?

Bringing in the cavalry

At this point I started a Twitter thread hoping one of my intelligent followers would illuminate me.

The debugger

Then I opened up the debugger and set a breakpoint within my submit function.

I noticed that the caller scope is the HTML attribute. And when I clicked on that I noticed the event variable in local scope.

Event variable in local scope

Well that looks like it! I set onclick="submit(event)" and it works! I get the submit event I want.

But what the heck is this variable? As I scoured the internet I noticed window.event which is documented in MDN and is marked as deprecated. Crap. I hope that's not the variable I'm using. Because if a variable isn't in scope it will get looked up in the window object.

Simon Willison chimed in and suggested I check console.log(event === window.event). It's true. But that doesn't mean event and window.event are the same.

Here's a case illustrating this fallback to window:

window.x = 1;
function dothing () {
  console.log(x === window.x); // true
}
dothing();

But here's a counter example of how they're not necessarily the same variable.

window.x = 1;
function dothing (x) {
  console.log(x === window.x); // true
}
dothing(window.x);

x is a local variable in scope in the dothing function. And it happens to be equal to window.x.

So comparing the event and window.event isn't enough to know if event is a variable in scope in the function or if it's being looked up in the window object.

And again, I saw event in the debugger in the locals section. So that kind of sounds like it's a local not a variable in the window object. Especially since no other variables in the window object are in the locals section.

The HTML spec

At this point Simon and I both started simple searches against the HTML spec (PDF). There were no examples event showing up in onclick attribute values. But we did start seeing examples of other handlers using event.

Examples of event object in event handler attribute values

Seeing examples in the spec made me feel more certain that this was not window.event since window.event wasn't something I could find in the spec. And I assumed the spec wouldn't be using examples of deprecated code.

Finally, Colin Dellow found the point in the spec where event is defined. And turns out: it's a local!

HTML spec showing event definition

Summary

The spec shows that the string passed to a handler is combined like this:

function $name (event) { $body }

I'm not sure what $name is but $body is the string passed to the handler attribute in HTML. event is an argument. This string becomes a function through the Function constructor I'm assuming.

Good to know! Thank you Simon for digging into the problem and also amplifying the questions. And thank you Colin for finding the location in the spec where these event handler attributes are described!

And as a footnote: I ended up switching from <button onclick to <form onsubmit since it seems like the latter handles more cases.

Share

With questions, criticism or ideas, email or Tweet me.

Also, check out DataStation and dsq.