Thursday, April 11, 2013

Functions in Methods Might Not Be What You Think They Are

‹prev | My Chain | next›

One of the things that I seem to consistently mess up in the ICE Code Editor is the initial display of the preview pane. Somehow I always seem to manage to get it drawing the preview twice.

Now that I have separated the editor from the the full-screen controls, I seem to have reverted back to this double preview page load again:



I had originally loaded a CanvasRenderer project, then opened a WebGl project (which can be seen behind the code layer). I then get two WebGlRenderer scene start messages. Since this does not occur when the editor first starts up, I guess that I have introduced the bug somewhere in the project switching code.

Looking at the project selection code, all looks OK:
var projectsDialogRow = function(doc) {
  // ...
  link.addEventListener( 'click', function ( event ) {
    store.open(doc.filename);
    editor.setContent(store.current.code);
    closeProjectsDialog();
    event.stopPropagation();
    event.preventDefault();

  }, false );
  // ...
}
When a project link in the list is clicked, the data store opens this document and the editor sets the content to the new code. This setContent() method should result in a single new preview layer, but something is behaving unexpectedly.

The problem turns out to be directly in the setContent() method of the ICE.Editor class:
Editor.prototype.setContent = function(data) {
  var that = this;
  function handleChange() {
    that.resetUpdateTimer();
  }

  this.editor.getSession().removeListener('change', handleChange);
  this.editor.setValue(data, -1);
  this.editor.getSession().setUndoManager(new UndoManager());
  this.editor.getSession().on('change', handleChange);
  this.updatePreview();
};
When I first converted this piece of code from the old procedural format into and object, I had thought that declaring the handleChange() callback inside the method would ensure that the same function would always be used each time this method is called. It is important that the same exact function be called each time so that the removeListener() call will be able to determine that it needs to remove the method on subsequent calls.

It turns out that, although handleChange() behaves the same on each call to setContent(), it is not the same exact function. That is, the function is re-declared on each call to setContent() so that removeListener() then tries to remove a newly created callback. Since this newly created callback is not on the listener list, nothing gets removed, leaving me with multiple handleChange() callbacks lingering.

Not knowing what else to try, I squirrel away the callback function so that subsequent calls to this method will re-use the original callback:
var handle_change;
Editor.prototype.setContent = function(data) {
  var that = this;
  if (!handle_change) handle_change = function() {
    that.resetUpdateTimer();
  };

  this.editor.getSession().removeListener('change', handle_change);
  this.editor.setValue(data, -1);
  this.editor.getSession().setUndoManager(new UndoManager());
  this.editor.getSession().on('change', handle_change);
  this.updatePreview();
};
Scope issues in JavaScript are always fun. And usually result in ugly lexical scoping hacks like this. But at least it works. I can now switch between projects with only a single renderer being started:



In addition to mucking with fun JavaScript scope issues, I manage to clear off all but one remaining feature that did not survive the switch to the object-oriented editor. The remaining feature is the ability to disable auto-updates of the preview pane in response to code changes. I will pick back up with that tomorrow.

Day #719

No comments:

Post a Comment