Saturday, April 13, 2013

One Handler Per Object

‹prev | My Chain | next›

Now that I have the code in the ICE Code Editor reasonably well factored, it is time to start adding features. I have two different classes, ICE.Full and ICE.Embedded, that support live editing of JavaScript code (mostly Three.js) either in a full-screen lite IDE or embedded in web pages. The lite IDE is more or less “done” as I spent quite a lot of time getting it ready for use in 3D Game Programming for Kids. So tonight I pick back up with adding features to the embedded version.

The first new feature that I would like to add is the ability to embed multiple instances of the ICE Code Editor. I know that I keep coming across TODOs that I have left myself on the subject, but it seems easiest to start by actually trying it. So I add a second <script> tag to my sample page:
<p>Lebowski ipsum i'll get...</p>
<script type="text/ice-code">
  // First code sample here...
</script>
<p>One a those days, huh.</p>
<script type="text/ice-code">
  // Second code sample here...
</script>
<p>I'm not Mr. Lebowski;</p>
<script src="js/ice-editor.js"></script>
<script src="js/ice-embeded.js"></script>
<script>ICE.attachEmbedded();</script>
When I load it up in the browser, it just works:



I have two different animations running at the same time—both being driven by the code embedded inside the <script> tags with the special type attribute. So problem solved, right?

Not quite.

I am not able to edit the live code in both embedded versions. If there is just a single text/ice-code entry on the page, I can edit the code and see the visualization layer change. But with two entries, I cannot reliably see the visualization layer change.

This leads me to my TODO statement, which would seem like a good candidate for the fix:
Editor.prototype.getPreviewIframe = function() {
  var iframe;
  if (this.preview_el.children.length > 0 ) {
    // TOOD: multiples
    iframe = frames[0].frameElement;
  }
  else {
    // Create a new iframe here...
  }
  return iframe;
};
This getter for the editor itself is looking up the first frame element in the entire document, not the first child element inside the preview <div>. That could certainly cause the problem. Only it does not actually get called. Thanks to last night's work, the preview layer is removed entirely before calling the getPreviewIframe() method—at least in this context.

I eventually track the problem down to the callback that I use to handle change. I am unsure how I am going to fix this, but the problem is that all instances of ICE.Editor end up using the same callback handler:
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();
};
I had done this so that the callback would remain the same, allowing removeListener() to actually remove the old handler. Only things are a little too much the same now. I am going to have to figure out how to make the function the same per instance, not throughout all instances.

Something like this should work:
Editor.prototype.setContent = function(data) {
  var that = this;
  if (!this.handle_change) {
    this.handle_change = function() {
      that.resetUpdateTimer();
    };
  }

  this.editor.getSession().removeListener('change', this.handle_change);
  this.editor.setValue(data, -1);
  this.editor.getSession().setUndoManager(new UndoManager());
  this.editor.getSession().on('change', this.handle_change);
  this.updatePreview();
};
And it does work. When I update either embedded editor, the correct preview layer is subsequently redrawn.

Looking back on why I took the original approach to the handle_change() callback, I can see where I went wrong. I was in such a desperate state to get the callback recognized by removeListener() that I simply overlooked the possibility that the callback might need to be per-object. Silly.


Day #721

No comments:

Post a Comment