Click event delegation on the iPhone

From the dawn of history browsers have supported event delegation. If you click on an element, the event will bubble all the way up to the document in search of event handlers to execute.

It turns out that Safari on the iPhone does not support event delegation for click events, unless the click takes place on a link or input. That’s an annoying bug, but fortunately there’s a workaround available.

Event delegation

Let’s recap briefly. Event delegation is the technique of attaching event handlers not to the elements you actually want to read out events from, but to a higher-level element.

For instance, say you have a dropdown menu with a lot of menu items. You need mouseover and mouseout events to get the menu to work (not to mention focus and blur to make it keyboard-accessible, too).

Now you could attach a mouseover and mouseout event to every single menu item, but it’s far more efficient to attach a single mouseover and mouseout event handler to the topmost element of the dropdown menu; likely a <ul> .

<ul id="dropdown"> <li>Item one <ul> <li>Subitem one</li> <li>Subitem one</li> <li>Subitem one</li> </ul> </li> ... etc ... </ul> $('dropdown').onmouseover = openMenu; $('dropdown').onmouseout = closeMenu;

The trick here is event bubbling. If the user mouses over an <li> , the mouseover event bubbles up all the way to the document. That is, it first checks of the <li> itself has an onmouseover event handler, then the parent of the <li> , then its parent, etc. etc. all the way to the document. Any event handler that’s found is executed.

Thus the event bubbles up to the <ul id="dropdown"> , encounters an onmouseover event handler there, and executes it. This allows you to capture all mouseover and mouseout events in the dropdown menu with only one event handler for each event, which saves memory (not to mention lines and lines of code).

The iPhone bug

On the iPhone, event delegation does not work for the click event. It works fine for mouseover and mouseout, fine for the touch events, but not for click.

However, as Mark Haylock showed me, event delegation does work if the target of the event is a link or an input field.

Still, Safari has returned to the Dark Age. Netscape 3 did the same: it only captured click events that took place on links and form fields, and ignored the rest. Every single browser that was released after 1997 supported full event delegation, though.

Except, apparently, for the iPhone.

This test page contains an extremely simple script that sets an onclick event handler on the document and then waits for the user to click on the bordered div. Once that happens, the div is replaced by another div.

The second test page broadens that experiment a little: you can now also click on links and inputs. Those click events bubble up fine on the iPhone, but it ignores any click event starting on another element.

Let’s not mince words: this is a bug. Safari does not support something that has been supported since 1998 by absolutely every browser ever released.

Why doesn’t it work?

That said, I’m certain that the nifty Apple engineers did not make a mistake. There must be a reason for this behaviour. I don’t know what that reason is, but currently I think that it’s a memory management problem. Apparently, making all elements on a page clickable demands too many resources, and the Apple engineers decided to disable it.

I’m guessing here; maybe the problem is something else entirely. (Especially since one could argue that all other events would have the same memory problem, but event delegation works fine for them.)

If you know why event delegation has been disabled for the click event, please leave a comment.

The workaround

This is a serious problem for web pages that have a lot of clickable elements but where the programmer has wisely decided to handle all those click events centrally in one event handler.

Fortunately it’s pretty easy to solve: you have to make the element clickable by giving it an onclick event handler of its very own. That handler can be empty; as long as it’s present it will make any element clickable.

This works even on the iPhone:

document.onclick = function () { // change div } div.onclick = function () {}

We still handle the event on the document level, but we add an empty event handler to the div we want to click. Now all of a sudden the div is clickable, the event bubbles up to the document and is handled correctly.

The only trick here is that we have to repeat this every time we change the div. Once the old one is destroyed and a new one appended, the onclick event handler is gone, too, and we need to set a new one:

document.onclick = function () { var newDiv = document.createElement('div'); // populate div newDiv.onclick = function () {}; document.body.appendChild(newDiv); } div.onclick = function () {}

Apart from its ugliness, I see one problem with this approach: if my guess is right and the Apple engineers have in fact disabled click event delegation because of memory considerations, this workaround will create new memory problems. Lots of them if you have lots of divs that must become clickable.

But that can’t be helped. Event delegation simply must work for the click event, and if we have to brute-force it at the expense of memory that’s just too bad for Safari iPhone.

Comments are closed.