2
votes

I'm a total Backbone noob, so bear with me…

I've got a table which is populated by json data. When I load the page, the table renders correctly with the data, and I am able to update the existing models.

When I try to add a new model and row to the table by using an "Add" button outside of the table, I'm getting this error:

TypeError: 'undefined' is not a function (evaluating 'this.model.on('change', this.render, this)')

It seems to be breaking in my IncomeView initialize function, but I can not for the life of me figure out why.

window.IncomeView = Backbone.View.extend({
  tagName: 'tr',
  className: 'income-row',
  template: _.template($('#income-template').html()),
  events: {
    'change .name': 'updateName',
    'change .cash': 'updateCash',
    'change .qty': 'updateQty',
    'change .recur': 'updateRecur',
    'change .start': 'updateStart',
    'click .ss-subtract': 'clear'
  },
  initialize: function() {
    this.model.on('change', this.render, this);
    this.model.on('destroy', this.clear, this);
  },
  render: function() {
    var attributes;
    attributes = this.model.toJSON();
    this.$el.html(this.template(attributes));
    this.$('.cash').formatCurrency();
    return this;
  },
  remove: function() {
    this.$el.remove();
  },
  updateName: function(e) {
    this.model.updateName(e);
  },
  updateCash: function(e) {
    this.model.updateCash(e);
  },
  updateQty: function(e) {
    this.model.updateQty(e);
  },
  updateRecur: function(e) {
    this.model.updateRecur(e);
  },
  updateStart: function(e) {
    this.model.updateStart(e);
  },
  clear: function() {
    this.model.clear();
    return false;
  }
});

Here is my Collection View:

window.IncomeTallyView = Backbone.View.extend({
  el: $('#incomeTable'),
  events: {
    'click #addIncome': 'addOne'
  },
  initialize: function() {
    this.collection.on('add', this.addOne, this);
    this.collection.on('reset', this.addAll, this);
  },
  render: function() {
    this.addAll();
    return this;
  },
  addAll: function() {
    $('#income').empty();
    this.collection.forEach(this.addOne, this);
  },
  addOne: function(incomeItem) {
    var incomeView;
    event.preventDefault();
    incomeView = new IncomeView({
      model: incomeItem
    });
    this.$('#income').append(incomeView.render().el);
  }
});

Here are my templates:

<table class="add" id="incomeTable">
    <thead>
        <tr>
            <th class="move">move</th>
            <th>Name</th>
            <th class="money">Unit cost</th>
            <th class="qty">Qty</th>
            <th class="selects">Recurring?</th>
            <th class="selects startTitle">Starting</th>
        </tr>
    </thead>
    <tbody class="sortable" id="income">
    </tbody>
    <tfoot class="table-nav">
        <tr>
            <td></td>
            <td colspan="5"><a href="#" title="" class="ss-add ss-icon button green" id="addIncome">Add</a> <a href="#" title="" class="ss-subtract ss-icon button red">Remove</a></td>
        </tr>
    </tfoot>
</table>
<script type="text/template" id="income-template">
    <td class="ss-rows"></td>
    <td class="title">
        <label for="<%= _.uniqueId('income-') %>">Name</label>
        <input type="text" name="<%= _.uniqueId('income-') %>" value="<%= name %>" class="name" placeholder="" />
    </td>
    <td class="money">
        <label for="<%= _.uniqueId('unit-cost-') %>">Unit Cost</label>
        <input type="text" name="<%= _.uniqueId('unit-cost-') %>" value="<%= cash %>" class="cash" placeholder="" />
    </td>
    <td class="quantity">
        <label for="<%= _.uniqueId('unit-qty-') %>">Unit Quantity</label>
        <input type="text" name="<%= _.uniqueId('unit-qty-') %>" class="qty" value="<%= qty %>" placeholder="" />
    </td>
    <td class="selects recurring">
        <label for="<%= _.uniqueId('recurring-') %>">Recurring</label>
        <select name="<%= _.uniqueId('recurring-') %>" class="recur">
            <option value="no">No</option>
            <option value="monthly">Monthly</option>
            <option value="yearly">Yearly</option>
        </select>
    </td>
    <td class="starting selects">
        <label for="<%= _.uniqueId('month-') %>">When&hellip;</label>
        <select name="<%= _.uniqueId('month-') %>" class="start">
            <option value="0">January</option>
            <option value="1">February</option>
            <option value="2">March</option>
            <option value="3">April</option>
            <option value="4">May</option>
            <option value="5">June</option>
            <option value="6">July</option>
            <option value="7">August</option>
            <option value="8">September</option>
            <option value="9">October</option>
            <option value="10">November</option>
            <option value="11">December</option>
        </select>
    </td>
</script>

Any ideas? Thanks!

1

1 Answers

2
votes

You've got the addOne function wired up as a handler to two different events -- this is probably a bad idea, because the arguments are going to be different depending on which event is triggered.

  1. it handles the collection's "add" event
  2. it handles the click on the "#addIncome" button

The first sends the parameter you're expecting (the model added), but the second doesn't -- it sends the element clicked (#addIncome).


I'd suggest adding a new handler "addNew" for the click event:

events: {
    'click #addIncome': 'addNew'
}

This should create a new model and pass it to 'addOne`:

addNew: function(e) {
    e.preventDefault();
    this.addOne( new IncomeModel() );
}