34 comments

[ 2.6 ms ] story [ 69.7 ms ] thread
I reach out for HTML events instead of addEventListener all the time. I get free event addition when dynamically adding content, also removal and tidying up after elements are removed - no memory leaks from unused event listeners. Plus it's obvious from browsing the code what different elements are hooked up to. I get called out on this often, but no one can argue with the ease of use and performance.
I prefer them too for the same reasons, they're even available with a really similar syntax in React's JSX extension: <button onClick={onClickHandler}>

I'm a big fan of the data attributes for the same reason, they bind onto the actual element they belong with, they're available in JS and CSS, and they add semantic meaning to your markup when you're reading the source.

they're also much nicer for user-editable templates if you expect your users not to all be experts, or even if they are, have a full grasp of the system. they're right there where they're relevant, you see the element and right away you know that javascript will happen when you interact with it in certain ways
> I'm not sure what `$name` is but `$body` is the string passed to the handler attribute in HTML.

The name of the synthesized function is the attribute name itself. So `<button onclick="alert(arguments.callee.name)">` will alert "onclick" on click.

Yeah, the paragraph immediately preceding the one giving the signature starts with "if $name is onerror" so that's clearly referencing the name of the attribute.

I wonder if the name also reflects capitalization given that HTML element and attribute names are generally case insensitive.

> I noticed window.event which is documented in MDN and is marked as deprecated

The Event API is not deprecated: https://developer.mozilla.org/en-US/docs/Web/API/Event. In the posted examples the `event` object implements the Event interface.

But window.event is deprecated as you can see here:

https://developer.mozilla.org/en-US/docs/Web/API/Window/even...

It's been around since IE 4 and is the "current event being handled by the site's code" so it's event inside the scope of an event handler and undefined anywhere else. That's definitely an anti-feature lol, maybe IE 4 didn't pass the event object into an event handler's synthetic function as an argument and window.event was a bodge to allow for <div onclick="alert(event.button)"> ?

Actually, I bet back then the code in the event handler attribute was just wrapped in eval() and executed, so you would need to have the event object in the global scope to be usable in the eval'd code.

That is true, but also note that "deprecated" doesn't necessarily mean it's going away any time soon. The web platform is historically backwards compatible to a fault with the usual exceptions being mostly security-related (e.g. CORS) or "experimental" features that never saw widespread implementation.
Right, but it does mean “we suggest you don’t use this”.
Seems like this assumes that the form submits to location.pathname always
I think the good old form.elements is superior for getting values instead of querySelectorAll, but a lot of people have never even heard of this api. It‘s great if you‘re dealing with forms.

I‘m not sure why the author doesn‘t want to handle form/multipart. Does Go not have good ergonomics for this case?

Also the author's approach doesn't take into account dealing with multi-value form elements such as `<select multiple>` or multiple `<input type=checkbox>` with the same name which is all handled by standard HTML forms POST encoding.
When you're writing your own code for your own apps you're allowed to make your own assumptions. :)
(comment deleted)
Thanks! It had been a while since I've written vanilla JS form code so I forgot about both FormData and form.elements.
The real fun comes when you consider the interactions between setting an event HTML attribute and the DOM property. If memory serves, setting the DOM property for eg. "onclick" to some JS function overrides (but does not change) the HTML attribute "onclick", while setting the HTML attribute replaces an existing property value. Took me quite some time to reverse-engineer this behavior for use in custom elements. Check out this library if you want/need html event attibutes for custom events on your web components: https://github.com/SirPepe/OnEventMixin

Oh, any by the way, HTML attributes work with event bubbling. Add "onsubmit" to some div tag and it will catch submit events originating from any of the div's children. This is why there is support for eg. "onlick" on stuff like h1.

what's a difference between override vs replace, aren't both leading to the same outcome?
Not quite! If you have an attribute "onclick" and set the DOM property "onclick", the attribute value stays unchanged, but if you click, the function set by the property gets called. Set the attribute an both the attribute value and the DOM property change - the latter to a function as described in the original article.

Think of the actual function that gets called as residing in an internal slot on the element. Both the attribute and the property can set the slot's value, and setting and the DOM propertie's getter accesses the slot directly. Sort of, if i remember correctly.

I dunno. Why would you even try to use onclick instead of onsubmit. You’ll need to capture return keys, handle accessibility etc etc etc. It’s simply not the right thing to do.

Also. Onclick will simply execute the content as JavaScript. So if you’re not passing an argument, why would you expect an argument?

You can simply return false to prevent the defaults from happening.

The whole article has nothing to do with a rabbit hole. But more with a junior dev

> Why would you even try to use onclick instead of onsubmit

I mentioned in the post I switched to onsubmit.

> But more with a junior dev

Damn, anyone can be a junior dev these days! :)

You arguably should not be using HTML event handler attributes

(1) you should arguably disable eval to help prevent code injections vs CSP. Turning that on disables HTML event handler attributes as those are strings that are "eval"ed

https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP

(2) using event handler attributes or the JS equivalent (elem.onclick = somefunc) is not very forward thinking as only one handler can be assigned. Sure, you tell yourself "Well, I'm only using one handler" ... until you're not. For example you decide to add event tracking or similar functionality and, since you've followed your pattern of using event handler attributes, only the last one to assign its function wins.

(3) Its often much easier to add events in code

https://jsfiddle.net/jfkds5px/

Yes, you can assign by attribute (see 2) or you can create HTML text (see 1)

(4) this was mentioned in the article but in an event handler, this = the element the listener was assigned to, event.target = the element that was event was triggered on

https://jsfiddle.net/8efd4ghb/

(5) button in forms submit the form by default. If you don't want that behavior add type="button". No need for event.preventDefault();

> you should arguably disable eval [using CSP] to help prevent code injections

Depending on CSP is a smell, just like any time you open a GUI program from the terminal and it spams a bunch of (clearly undesirable) stuff to stdout—or, for that matter, opening the browser console on a given Web page and seeing a bunch of exceptions or other kinds of printf-style debugging spew that shouldn't have gotten checked in.

Besides that, using CSP to disable injected content is user-hostile, which is annoying given that the only "justification" for CSP only comes down to Web publishers whose development practices are such a mess that they don't have control over their content (which, due to the normalization of deviance in the world of Web development, means most of them these days).

Nowadays, I open up the browser console on pages associated with big name properties (Google, GitHub...), and it's filled with CSP and CORS violations that never should have escaped into production. I think it was better when browsers had status bars and IE put a badge in the corner, e.g. indicating when the current page encountered an error during the execution of a script. It forced publishers to confront the shoddiness of their work and deal with it if for no other reason than out of sheer embarrassment.

> button in forms submit the form by default. If you don't want that behavior add type="button". No need for event.preventDefault()

That, too, would be user-hostile. Keep your markup semantic so agents can reason about the content on a page. Don't just target incidental behavior.

Our site has mainly non tech people on it, a large amount of those have browsers that inject js/css into our HTML, I would say that is something user hostile. When we tested CSP with report-only I had to stop counting at 20 different malwares, the analysis was that even though CSP helps, those malware authors would just find another way. Thousands of our users were affected.

Do you think HTTPS is user hostile as well, not being able to read all the network traffic generated on my machines feels off for me. In the end I guess HTTPS is good for the user and so is CSP.

1. The page described is a license management page without user-generated content, so what purpose would a CSP serve besides preventing valid end-user fiddling?

2. YAGNI, in my experience.

3. That's a lot more code than onsubmit="handler(event)", and you've lost the ability to see what the form is doing by reading the HTML.

5. type=submit is semantic and helps the browser provide default behavior like submitting the form when you press enter.

To avoid the whole "what is the `event` variable and where does it come from?" problem they could use

  element.addEventListener('submit', submit)
The second argument to `addEventListener` is a callback that takes the event as its argument. (`element` should be the form and the event should be `submit` for accessibility reasons.)

Also, the author's example comparing `x === window.x` as evidence that it's insufficient to know if the in-scope variable is the same as the one on the window is wrong. The example uses a primitive value (`1`) which in JS are passed by value, so of course whenever you compare 1 with 1 it'll return true regardless of which variable the 1 is stored in. Objects, (that `event`s are an example of) are passed by reference. So you get

  const x1 = {foo: 1}
  const x2 = {foo: 1}
  
  x1 == x1 // true
  x1 == x2 // false
  x1 == {foo: 1} // false
So a simple comparison between objects is enough to determine if they are the same instance. But it won't work to see if 2 different objects contain consist of the same property-values as each other.
> To avoid the whole "what is the `event` variable and where does it come from?" problem they could use

Why avoid something when you can instead learn how it works? :)

> Also, the author's example comparing `x === window.x` as evidence that it's insufficient to know if the in-scope variable is the same as the one on the window is wrong.

I think you misunderstood it.

When you pass an object as an argument to a function it's passed as a reference. It would have been clearer if I used an object (I was trying to simplify the example) but my code is still illustrating what I meant it to even if I substituted `window.x = {a:2}` for `window.x = 1`.

The point I was trying to make is that your second example doesn't prove or disprove anything. You're simply comparing a primitive value. 1 will always equal 1, regardless of which variable it's stored in.

Your first example is enough to conclude that the locally scoped `event` is the same as the one on the window object, due to the pass-by-reference nature of JS objects. The fact that comparing the two returns true means they must be the same object.

Yes! But you're still missing the point. There is a big difference because it's a matter of which api you're using.

window.event may be a deprecated api but the thing that sends the event to the string event handler isn't. It doesn't matter that they're the same object it's about apis.

Why not just:

    document.addEventListener('submit', ev => {
        if (ev.target is a kind of a form I want json submit on) {
            ev.preventDefault();
            handle_json_submit(ev.target)
        }
    }, true);
Instead of sprinkling onsubmit="..." everywhere.
This comment has a good explanation: https://news.ycombinator.com/item?id=31173042

For myself, the biggest thing is locality. Coming into a large codebase that's been around for a while, it's a lot easier to understand if the event handlers are defined inline, right there with the form itself.

If they aren't, you first have to determine experimentally that it's not using the default form behavior. Then you end up having to hunt down where in the code someone put the `addEventListener` calls and then read them to decide which calls (and which conditional branches) apply to this form.

If the event handlers are instead defined on the forms themselves, the onsubmit behavior is immediately obvious just by looking at the form. It's a bit more code to write, but it's much easier to digest later.

> This comment has a good explanation: https://news.ycombinator.com/item?id=31173042

Not really..., neither memory leaks, nor missing the addition of event handling to newly added DOM elements is a problem for the addEventListener solution.

If the codebase is using JSON submitted forms almost everywhere, it's easier to build that feature more declaratively, by adding support for a special enctype attribute value (using one global addEventListener that would handle it), for example:

   <form enctype="application/json"> ...
That would be more in line with how other features of the <form> element work.

addEventListener solution is also better if you decide to add CSP in the future.