1
votes

I have an object that looks like this:

{
  "id": "someId",
  "name": "some name",
  "endpoints": [
    { "scheme": "http", "host":"localhost", "port":"8080" }
  ]
}

I want to edit this data in a Marionette app. I've defined:

  • a Backbone Model for my outermost object, let's call it Server
  • a Backbone Model for Endpoint
  • a Backbone Collection for Servers
  • a Backbone Collection for Endpoints

My goal is to have a form that allows N number of endpoints to be added such that when the form is submitted, I will persist the entire object (the server data and the list of endpoints).

To implement this I first overrode the parse function to instantiate the Endpoints collection like this:

parse: function(response) {
    var data = response;
    if (response.endpoints) {
        data.endpoints = new Entities.EndpointCollection(response.endpoints, {parse: true});            
    }
    return data;
},

Then, I created a LayoutView that holds my server related fields and contains a region for my list of endpoints. My Controller instantiates the view and passes in the Server model. My Controller also instantiates a CollectionView and passes in the Server's endpoints collection.

The form renders as expected with input controls for id, name, and repeating sets of inputs for as many endpoints as I have in my object.

However, it is not clear to me how serialization is supposed to work with this setup. If I remove an endpoint by destroying its model, the endpoint is removed when the data is persisted. However, if I change any of the endpoint data, those changes are ignored.

In my form submit handler I can iterate over the endpoints but those models do not reflect the changed form values.

submitClicked: function (e) {
    e.preventDefault();
    var data = Backbone.Syphon.serialize(this); // I know this is not enough
    var endpoints = this.model.get("endpoints");
    for (var i = 0; i < endpoints.length; i++) {
        var endpoint = endpoints.models[i];
        alert("got endpoint: " + endpoint.get("host"));
    }
    this.trigger("form:submit", data);
},

I assume there is a fundamental misunderstanding in how best to handle repeating groups within a form. What am I missing?

1

1 Answers

1
votes

My thinking had become uptight. The layout view really isn't the issue. The key is that the form needs to have IDs/names that are unique. To do this, I first send the item's index to the view:

Views.Endpoint = Marionette.ItemView.extend({
    template: endpointTpl,

    triggers: {
        "click button.btn-remove": "endpoint:remove"
    },

    initialize: function(options) {
        this.model.set("idx", options.childIndex);
    }

});

Views.Endpoints = Marionette.CollectionView.extend({
    childView: Views.Endpoint,
    childViewOptions: function(model, index) {
        return {
            childIndex: index
        }
    }
});

Then, in the view's template I set up the IDs and names using that index property:

<tr>
  <td><label for="endpoints[<%- idx %>][scheme]" class="control-label">Scheme:</label></td>
  <td><input id="endpoints[<%- idx %>][scheme]" name="endpoints[<%- idx %>][scheme]" type="text" value="<%= scheme %>"></input></td>
  <td></td>
</tr>

And, finally, on submit, Syphon will create an object for my repeating group, when what I really wanted was an array, so I address that with:

submitClicked: function (e) {
    e.preventDefault();
    var data = Backbone.Syphon.serialize(this);
    // need to convert endpoints object to array
    var endpoints = data.endpoints;
    delete data["endpoints"];
    data.endpoints = [];
    for (var idx in endpoints) {
        data.endpoints.push(endpoints[idx]);
    }
    this.trigger("form:submit", data);
}

Now I can add/remove/edit endpoints as needed and they save back to the object appropriately.