Backbone.js

Backbone.js is a client-side MV* framework that can do some pretty smart things with data going to and coming back from a server, and has a great event model for keeping multiple views in sync.

This little example shows how Backbone Models and Collections can work with Handsontable. Below, you'll see events firing from changes in the CarCollection by Handsontable or otherwise.

Make
Model
Year
DodgeRam2012
ToyotaCamry2012
SmartFortwo2012
Make
Model
Year


Please note that Backbone integration is a work in progress since Handsontable 0.8.14. The code presented here has 2 known issues:

  • inserting and removing rows or columns triggers errors, both when using alter method and the context menu
  • minSpareRows does not have effect directly after row was added from Backbone (as a workaround, you would need to call loadData instead of render)

Both issues will be addressed in future versions of HT. Contributions are welcome!

var
  container = document.getElementById('example1'),
  addCar = document.getElementById('add_car'),
  eventHolder = document.getElementById('example1_events'),
  CarModel = Backbone.Model.extend({}),
  CarCollection,
  cars,
  hot;

CarCollection = Backbone.Collection.extend({
  model: CarModel,
  // Backbone.Collection doesn't support `splice`, yet! Easy to add.
  splice: hackedSplice
});

cars = new CarCollection();

// since we're not using a server... make up some data. This will make
// a couple CarModels from these plain old objects
cars.add([
  {make: 'Dodge', model: 'Ram', year: 2012, weight: 6811},
  {make: 'Toyota', model: 'Camry', year: 2012, weight: 3190},
  {make: 'Smart', model: 'Fortwo', year: 2012, weight: 1808}
]);

hot = new Handsontable(container, {
  data: cars,
  dataSchema: makeCar,
  contextMenu: true,
  columns: [
    attr('make'),
    attr('model'),
    attr('year')
  ],
  colHeaders: ['Make', 'Model', 'Year']
  // minSpareRows: 1 //see notes on the left for `minSpareRows`
});

// this will log all the Backbone events getting fired!
cars.on('all', logEvents)
  .on('add', function () {
    hot.render();
  })
  .on('remove', function () {
    hot.render();
  });

// you'll have to make something like these until there is a better
// way to use the string notation, i.e. "bb:make"!

// normally, you'd get these from the server with .fetch()
function attr(attr) {
  // this lets us remember `attr` for when when it is get/set
  return {data: function (car, value) {
    if (_.isUndefined(value)) {
      return car.get(attr);
    }
    car.set(attr, value);
  }};
}

// just setting `dataSchema: CarModel` would be great, but it is non-
// trivial to detect constructors...
function makeCar() {
  return new CarModel();
}

// use the "good" Collection methods to emulate Array.splice
function hackedSplice(index, howMany /* model1, ... modelN */) {
  var args = _.toArray(arguments).slice(2).concat({at: index}),
    removed = this.models.slice(index, index + howMany);

  this.remove(removed).add.apply(this, args);

  return removed;
}

// show a log of events getting fired
function logEvents(event, model) {
  var now = new Date(),
    option = document.createElement('OPTION');

  option.innerHTML = [':', now.getSeconds(), ':', now.getMilliseconds(), '[' + event + ']',
    JSON.stringify(model)].join(' ');
  eventHolder.insertBefore(option, eventHolder.firstChild);
}

Handsontable.Dom.addEvent(addCar, 'click', function () {
  cars.add({make: "Tesla", model: "S", year: 2012, weight: 4647.3});
});