Search code examples
bonsaijs

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?


Solution

  • 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).