can.batch

  • page
 

can.batch.start( batchStopHandler ) and [can.batch.end can.batch.end( force, callStart )] are used to specify atomic operations. start prevents change events from being fired until stop is called.

The following listens to changes on a player:

var player = new can.Map({
  tvshow: "The Simpsons"
});

player.bind("change",function(ev, attr, how, newVal, oldVal){
  console.log("changed", attr );
});

The "change" callback handler does not get called until after tvshow is removed, song is added, and stopBatch is called.

can.batch.start();

player.removeAttr("tvshow");
player.attr("song","What makes you beautiful");

can.batch.stop();

Performance and correctness are the two most common reasons to use batch operations.

Correctness

Sometimes, an object can be temporarily in an invalid state. For example, the previous player should have a tvshow or song property, but not both. Event listeners should never be called in an intermediate state. We can make this happen with startBatch, stopBatch and the can/map/setter plugin as follows:

// Selection constructor function inherits from Observe
Player = can.Map({

  // called when setting tvshow
  setTvshow: function(newVal, success){
    can.batch.start();
    this.removeAttr("song")
    success(newVal);
    can.batch.stop();
  },
  // called when setting song
  setSong: function(newVal, success){
    can.batch.start();
    this.removeAttr("tvshow")
    success(newVal);
    can.batch.stop();
  }
});

// a new selection instance
var player =   new Player({song: "Amish Paradise"});

player.bind("change", function( ev, attr, how, newVal, oldVal ){
  console.log("changed", attr, how, player.attr() );
});

console.log("start")
player.attr("tvshow","Breaking Bad");
console.log("end")

Use startBatch and stopBatch to make sure events are triggered when an observe is in a valid state.

Performance

CanJS synchronously sends events when a property changes. This makes certain patterns easier. For example, if you are doing live-binding, and change a property, the DOM is immediately updated.

Occasionally, you may find yourself changing many properties at once. To prevent live-binding from performing unnecessary updates, write the property updates within a startBatch/stopBatch.

Consider a list of items like:

var items = new Items([
  {selected: false},
  {selected: true},
  {selected: false}
])

And a template that renders the number of selected items:

var template = can.view.mustache("{{count}}")

$("#itemCount").html(template({
  count: function(){
      var count = 0;
      items.each(function(item){
        count += item.attr('selected') ? 1 : 0
      })
      return count
   }
}))

The following updates the DOM once per click:

$("#selectAll").click(function(){
    can.batch.start()
    items.each(function(item){
      item.attr('selected', true)
    })
    can.batch.stop()
})

batchNum

All events created within a startBatch / stopBatch share the same batchNum value. To respond only once for a given batchNum, you can do it like:

var batchNum;
obs.bind("change", function( ev, attr, how, newVal, oldVal ){
 if( !ev.batchNum || ev.batchNum !== batchNum ) {
   batchNum = ev.batchNum;
   // do your code here!
 }
});

Automatic Batching

Libraries like Angular and Ember always batch operations. Set this up with:

can.batch.start();
setTimeout(function(){
  can.batch.stop(true, true);
  setTimeout(arguments.callee, 10)
},10);

This batches everything that happens within the same thread of execution and/or within 10 ms of each other.