Manipulating table child elements in AJAX callback considered harmful?

This is one of those small bugs that eats away entire day. You find a workaround rather quickly, but can’t let go until you know what to heck is going on.

This happened in rather large AngularJS application, which meant I had to chip away everything that is not necessary to reproduce the issue for hours.

Application screen consists of a table with several rows, with number input boxes. When user changes a value and leaves the input field, application sends a web request to perform some kind of calculation to 3rd party service. It then replaces the row that contains the changed field (performing web callbacks onblur is due to hard requirements that customer has placed. Not mine to judge). If blur event occurred due to clicking (not tabbing) into another input in same row, buttons don’t receive click events anymore. I have to stress, that there is no flicker to indicate elements moving around the page. If you click anywhere within the table again, click events start getting fired again.

You can see the issue in action here

Wait for table to load, click one input, then click second input in same row. Now go to bottom of the table and click “Can’t click this!”. It should show an alert. More often than not, it will not fire if you are using IE.

After removing almost all of the code of the application, conclusion is following:

  • happens only in IE (even version 11)
  • happens only when modifying inputs within TABLE element
  • happens only when modifying DOM within an AJAX callback
  • happens only when inputs within a table are defocused+focused
  • disappears when something is focused again after the manipulation

Why this happens?

Can’t really give you inner workings of the browser, but a hint is that AngularJS doesn’t seem to update values in inputs when you swap the object under it. It removes the inputs, and places new ones (we’re talking about ngRepeat directive to generate table rows). If you actually modify values within bound objects, this problem doesn’t happen.

Workaround? Why, there’s an ugly workaround always, of course!

Actually, there are two.

Focus something after modifying the DOM:

document.body.focus();

Or, modify the objects to which Angular model is bound. You can use angular.copy() function for that. It’s primary use is for creating copies of objects, but it takes an optional second parameter using which you can provide destination object.

angular.copy(newItem, $scope.ItemArray[idxToReplace]);
// instead of 
$scope.ItemArray[idxToReplace] = newItem;

Although second workaround seems nicer, I prefer the first one. I don’t want workarounds to seem nice. Broken line of code shown in workaround is actually more optimal, so I assume someone may want to ‘fix’ the workaround. I prefer that fixes for ugly bugs be obvious.

Update 2015-06-18:
This also happens with current version of Spartan browser.