georgedyer
georgedyer

Reputation: 2745

How to pass event data from the stage to the parent context without deconstructing/reconstructing it?

consider the following code:

window.stage = bonsai.run(document.getElementById('stage'), {
  code: function() {
    var circle;
    circle = new Circle(200, 200, 50);
    circle.stroke('green', 2);
    circle.addTo(stage);
    circle.on('click', function(ev) {
      stage.sendMessage('click', ev);
    });
  },
  width: 500,
  height: 500
});

stage.on('load', function() {
  console.log('loaded');
  stage.on('message:click', function(ev) {
      console.log('click', ev);
  });
});

So, clicking on the circle gives me the error: DATA_CLONE_ERR: DOM Exception 25

If I just send out properties like ev.x and ev.y, they pass out just fine. I can also reconstruct the object from its properties before sending and it passes fine.

How can I send the event object out intact to the parent context without deconstructing-> reconstructing it? And by the way, why does bonsai work this way?

Upvotes: 1

Views: 288

Answers (1)

klipstein
klipstein

Reputation: 111

Great that you've asked why we do the separation that way. Just went through the BonsaiJS documentation and realized that we don't explicitely say a word about why we separate the rendering from the execution thread.

BonsaiJS code is mostly executed in a worker (falls back to iframe, if workers aren't available) and uses postMessage to communicate with the context which created the worker. The DATA_CLONE_ERR: DOM Exception 25 is raised because the DOM event object can't be serialized by postMessage. To solve your problem, you could create a simple function, which removes all nested objects / functions of the object, which should be passed:

window.stage = bonsai.run(document.getElementById('stage'), {
  code: function() {
    var circle;
    var makeSerializable = function(obj) {
      var ret = {}, val;
      Object.keys(obj).forEach(function(key) {
        val = obj[key];
        if (typeof val != 'object' && typeof val != 'function') {
          ret[key] = val;
        };
      });
      return ret; 
    };
    circle = new Circle(200, 200, 50);
    circle.stroke('green', 2);
    circle.addTo(stage);
    circle.on('click', function(ev) {
      stage.sendMessage('click', makeSerializable(ev));
    });
  },
  width: 500,
  height: 500
});

stage.on('load', function() {
  console.log('loaded');
  stage.on('message:click', function(ev) {
    console.log('click', ev);
  });
});

Or you can force BonsaiJS to be executed in an iFrame. Then you'll have access to the DOM and you can serialize any object (note: see below, why I wouldn't recommend that):

window.stage = bonsai.setup({
  runnerContext: bonsai.IframeRunnerContext
}).run({...});

The main reason for putting the main code execution into a worker, is, that we don't want any computation to block the rendering "thread", so that we get more fluent animations (if the code is executed in an iFrame, rendering + code execution will happen in the same thread and it won't be as fluent as with the worker). Another advantage of executing JS code in a worker, is, that we don't rely on the DOM and can also take the same JS code and execute it in a different JS environment like Rhino or NodeJS (here is some example code, how you could execute BonsaiJS on node and send the rendering messages to the browser through SocketIO: https://github.com/uxebu/bonsai-server).

Upvotes: 2

Related Questions