Sunday, October 7, 2012

Forcing the Editor to Draw, Dammit

‹prev | My Chain | next›

Of the half dozen or so remaining issues in the code editor for Gaming JavaScript, only two of them are significant bugs. The first is that the programmer needs to click on the document after hiding the code so that events are properly recorded. The second is that code seems to disappear sometimes. The latter problem seems the more difficult of the two, so I start with that one.

The code does not actually disappear, but it sure looks as though it is gone:


It is not easy to get into this situation, but it is also not too difficult. The easiest way to reproduce is to highlight some of the code, scroll, then click somewhere in the middle of the highlighted code. At some point, a good portion of the code will vanish.

If I move the cursor up a little bit, the code is eventually revealed:


But that is clearly not a winning usability scenario—especially for kids just learning to program.

Interestingly, this seems to be a Chrome-only issue as I am unable to get Firefox to fail in the same way no matter what I try. Since I plan to require Chrome (for the built-in JavaScript console) in Gaming JavaScript, I need to solve this.

This may end being one of those weird browser edge cases that I have to solve by forcing redraw. Hopefully, I can come up with something a little more robust. Before digging in, I note that the line numbers on the side do not suffer from the bug.

Inspecting the page elements, I see that the display, the code, and the toolbar are three separate, absolutely positioned elements. The first two have the exact dimensions of the browser viewport. If I hide the code <div>, then both the code and the line numbers are hidden:


So the problem lies somewhere in that absolutely positioned <div>. Digging down a bit further, I find that, after recreating the bug, elements that Chrome thinks it is displaying, are not, in fact, displayed:


The computed style for that <span> is "inline", not "none", so it should be visible. Add the fact that Firefox does not see this problem and this begins to look very much like a Chrome bug, albeit a very hard to reproduce one.

While messing about in the JavaScript console, I notice that, if I disappear all of my code <pre> tags:
function hidePres() {
  var pres = document.getElementsByTagName('pre');
  for (var i=0; i<pres.length; i++) {
    pres[i].style.visibility = 'hidden';
  }
}
And then restore them:
function showPres() {
  var pres = document.getElementsByTagName('pre');
  for (var i=0; i<pres.length; i++) {
    pres[i].style.visibility = 'visible';
  }
}
Then my disappeared code is restored.

Since this situation seems to arise on click events, I try a document click handler to call these two in succession:
document.addEventListener( 'click', function(event) {
  console.log('Trying workardoun');
  disappearingCodeFix();
});

function disappearingCodeFix() {
  hidePres();
  showPres();
}
That kind of works. When the click event fires, it seems to do the trick, but it rarely fires. It seems that the codemirror editor is capturing click events and not allowing them to propagate. Tsk.

Instead, I switch to a simply calling my fix every 5 seconds. But that never works. It seems that I need an interval—even if only a millisecond—in between the two functions so that Chrome has time to recognize that it needs to repaint those invisible-now-visible elements:
function disappearingCodeFix() {
  hidePres();
  setTimeout(showPres, 1);
}

setInterval(disappearingCodeFix, 5*1000);
That works... except that there is a perceptible flash every 5 seconds. I will file that away as plan Z.

After much fiddling and googling, I stumble across this solution (based on a StackOverflow solution):
function disappearingCodeFix() {
  var pres = document.getElementsByTagName('pre');
  for (var i=0; i<pres.length; i++) {
    pres[i].style.display = 'none';
    pres[i].offsetHeight;
    pres[i].style.display = 'block';
  }
}

setInterval(disappearingCodeFix, 1000);
And yes, the offsetHeight reference does appear to be necessary. So in the end I am forced to use a manual redraw solution. I am still not quite satisfied, but it works. I may file this away as plan B and pursue something more robust or I may pick up with other code-editor issues tomorrow.


Day #532

No comments:

Post a Comment