Backbone/Marionette form with list of inputs Using Backbone/Marionette, I'm building a simple widget that looks like:



I'm hoping to get some feedback on my approach because I think I may be doing it wrong.

jsfiddle

// widget/invites/person.js
var Person = Backbone.Model.extend({
    defaults: {
        name: ''
    }
});

// widget/invites/people.js
var People = Backbone.Collection.extend({
    model: Person
});

// widget/invites/personField.js
var PersonView = Marionette.ItemView.extend({
    template: _.template(...),
    hasSiblings: false,
    initialize: function(opts) {
        this.hasSiblings = opts.hasSiblings || false;
    },
    templateHelpers: function() {
        return {
            id: this.model.cid,
            showDel: this.hasSiblings
        }
    },
});

// widget/invites/form.js
var FormView = Marionette.CompositeView.extend({
    template: _.template(...),
    childView: PersonView,
    childViewOptions: function() {
        return {
            hasSiblings: (this.collection.length &gt; 1)
        };
    },
    childViewContainer: '.values',
    ui: {
        'addBtn': '.add-name',
        'removeBtn': '.del-name',
        'wrap': '.values'
    },
    events: {
        'click @ui.addBtn': 'addFormRow',
        'click @ui.removeBtn': 'removeFormRow'
    },
    addFormRow: function(ev) {
        var name;
        ev.preventDefault();

        if (this.collection.length === 1) {
            // also rerender first input to include remove button
            name = this.ui.wrap.find('[name="person[]"]').get(0).value;
            this.collection.reset([{name: name}, {name: ''}]);
        }
        else {
            this.collection.add({name:''});
        }
    },
    removeFormRow: function(ev) {
        var id, model;
        ev.preventDefault();

        id = $(ev.currentTarget).data('id');
        model = this.collection.get(id);
        this.collection.remove(model);

        if (this.collection.length === 1) {
            // rerender first input so it doesn't have remove button
            name = this.ui.wrap.find('[name="person[]"]').get(0).value;
            this.collection.reset([{name: name}]);
        }
    }
});


Note, the list is saved by clicking a Submit button (not shown in mockup), as there will be other fields in the form as well.



The code works, but consider the following workflow:


User enters "John Doee" in first input


Collection: ['']

User clicks "add", and enters "Sally Something" in the second input


Collection: ['', '']

User realized they spelled "John Doe" (two e's) and fixes it


Collection: ['', '']



or consider an existing list being edited:


Collection: ['John Doe', 'Sally Something', 'Austin Powers']
User removes "Austin Powers"


Collection: ['John Doe', 'Sally Something']

User updates "John Doe" to "Dr. John Doe"


Collection: ['John Doe', 'Sally Something']



I'm using collection.add() and collection.remove() to trigger the view to update itself, but the collection doesn't always reflect the data on the screen. Is that okay? 

Additionally, I did try adding a hook for blur which grabbed every value and reset the collection, but it seemed too brute force. Hence, my doubt about this approach.

Any thoughts or other approaches?
Answering my own question. A View's Model represents "state", not data. This is a subtle distinction that wasn't apparent to me at first. The Collection should match the View, blank values and all.

When it comes time to save the list, the Collection's toJSON method can be overridden to omit any blank models.

I updated my original jsfiddle to show values being saved on blur.

var Person = Backbone.Model.extend();

var People = Backbone.Collection.extend({
  model: Person,
  toJSON: function() { /*filter out empty models*/}
});

var PersonView = Marionette.ItemView.extend({
  ...
  events: {
    'blur input': 'onChange', //calls model.set('name', input value)
  },
  triggers: {
    'click .del-name': 'remove:form:row' //calls collection.remove(...)
  },
  ...
});

var FormView = Marionette.CompositeView.extend({
  events: {
    'click @ui.addBtn': 'addFormRow' //calls collection.add({name: ''})
  },
  collectionEvents: {
    add: 'onRowAdded'  //rerender first row to show delete button
  },
  ...
});