NealR
NealR

Reputation: 10679

Backbone collection empty when method called via change event

Right off the bat: this is my first Backbone app so if there are any design flaws I'm all ears. The purpose of this app is to draw/re-draw a bar graph based upon changing input values.

Input values are stored in the SliderModel. Each DataPoint model in the DataSeries collection is supposed to reprent one column of data that gets drawn in the BarGraph view.

When the user changes values in the SliderModel, I've tied and event handler method in the BarGraph so that I can dynamically re-draw the graph according to the input.

When the page loads, the updateCalculations method in the BarGraph view is run. The calculations are processed correctly and the graph is drawn appropriately (all zeros).

However, once the user starts entering input (i.e. one single character) the other variable below (from updateCalculations method in the view) is evaluated as undefined

var other = this.collection;

For some reason the collection in the BarGraph view is being cleared out when the updateCalculations method is called via a change to the SliderModel properties.

Model

Model containing default values and calculation methods

var SliderModel = Backbone.Model.extend({
    initialize: function() {

    },

    defaults: {
        purchasePayment: '0',
        fixedRate: '0',
        returnSpx: '0',
        slidervalue: '0'
    },

    fixedAllocation: function () {
        return this.attributes.purchasePayment * (1 - (this.attributes.slidervalue / 100));
    },

    illustratedEoy: function() {
        return this.fixedAllocation() * Math.pow(1 + (this.attributes.fixedRate / 100), 7);
    },

    eoyContractVal: function (value) {
        return this.illustratedEoy() + parseFloat(value);
    }
});

Model/Collection

Collection and model type of collection

var DataPoint = Backbone.Model.extend({

    initialize: function (lbl, ctrct, rtrn) {
        this.set({
            label: lbl,
            contract: ctrct,
            annReturn: rtrn
        })
    },
});

var DataSeries = Backbone.Collection.extend({

    model: DataPoint,

    fetch: function () {
        this.reset();
        this.add([
            new DataPoint("1/7yrs", "111830.17", "1.63%"),
            new DataPoint("2/7yrs", "115311.17", "2.07%"),
            new DataPoint("3/7yrs", "118984.65", "2.52%"),
            new DataPoint("4/7yrs", "122859.65", "2.98%"),
            new DataPoint("5/7yrs", "126947.77", "3.46%"),
            new DataPoint("6/7yrs", "131260.74", "3.94%"),
            new DataPoint("7/7yrs", "135810.92", "4.44%")
        ])
    }
});

View

The SliderModel is the model assigned to this view. The DataSeries is the collection assigned to the view.

var BarGraph = Backbone.View.extend({

    "el": "#graph",

    options: {barDemo: ""},

    initialize: function (options) {

        _.bindAll(this, "render");
        this.collection.bind("change", this.updateCollection);

        //Bind model change event to view event handler
        this.model.bind('change:purchasePayment', this.updateCollection);
        this.model.bind('change:slidervalue', this.updateCollection);
        this.model.bind('change:purchasePayment', this.updateCollection);
        this.model.bind('change:slidervalue', this.updateCollection);
        this.model.bind('change:fixedRate', this.updateCollection);
        this.model.bind('change:returnSpx', this.updateCollection);

        //Run setup methods
        this.drawGraph();
        this.collection.fetch();
        this.updateCollection();
    },

    drawGraph: function() {
        var margin = { top: 20, right: 20, bottom: 20, left: 20 };

        this.options.barDemo = d3.selectAll($(this.el)).append("svg:svg")
            .attr("width", width + margin.left + margin.right)
            .attr("height", height + margin.top + margin.bottom + 20);
    },

    updateCollection: function () {
        var sum = 0.00;
        var that = this.model;

        //Collection shows as 'undefined' when this method is fired via change event
        var other = this.collection;

        var indexed = $.makeArray($('#IndexedTable').find('tbody tr'));

        var i = 0;
        this.collection.each(function (m) {
            var value = that.eoyContractVal(that.indexedIllustrated(i));
            sum = parseFloat(sum) + parseFloat(value);

            other.models[i].attributes.contract = value.toFixed(2);
            other.models[i].attributes.annReturn = that.annReturn(value).toFixed(2) + '%';
            i++;
        });

        this.render();
    },

    render: function () {

    },
});

jQuery

How the above code is initialized

var sliderModel = new SliderModel;
var dataSeries = new DataSeries();
new BarGraph({
    collection: dataSeries,
    model: sliderModel
});

Upvotes: 0

Views: 340

Answers (1)

CharlieBrown
CharlieBrown

Reputation: 4163

first of all it's a bad bad idea overriding original Collection methods like fetch. You don't need that.

If you want to add some test data without going to the server, use the reset method or add like you did, outside of the collection definition.

http://backbonejs.org/#Collection-reset

I would leave the collection with just the model attribute. In your "main" (your jQuery ready handler), fill the data:

var slider = new SliderModel();
var dataSeries = new DataSeries();
var view = new BarGraph({
  model: slider,
  collection: dataSeries
});


dataSeries.reset([
  new DataPoint("1/7yrs", "111830.17", "1.63%"),
  new DataPoint("2/7yrs", "115311.17", "2.07%"),
  new DataPoint("3/7yrs", "118984.65", "2.52%"),
  new DataPoint("4/7yrs", "122859.65", "2.98%"),
  new DataPoint("5/7yrs", "126947.77", "3.46%"),
  new DataPoint("6/7yrs", "131260.74", "3.94%"),
  new DataPoint("7/7yrs", "135810.92", "4.44%")
]);

Now in your View, you're listening to a change event in the collection, but that is a Model event. http://backbonejs.org/#Events-catalog

Normally it's preferred to listen to the reset event, which you can trigger either resetting the collection by yourself, like we just did, or calling collection.fetch({reset:true}) to get data from the server.

It's recommended practice to use the listenTo function for event handling, because it automatically binds the function context to the current object. http://backbonejs.org/#Events-listenTo

So your initialize method becomes:

initialize: function (options) {

  _.bindAll(this, "render");
  this.listenTo(this.collection, "reset", this.updateCollection);

  //Bind model change event to view event handler
  //instead of individually listen to every attribute change
  //just listen to any change in one line
  this.listenTo(this.model, "change", this.updateCollection);

  //Run setup methods
  this.drawGraph();
  //not needed with fake data... or save it to a JSON file and fetch it!
  //this.collection.fetch();
  this.updateCollection();
}

Upvotes: 1

Related Questions