This sort of follows on from another question I asked but the guy I think was trying to explain something else, or more likely I did not explain my self right.
So all my backbone code is working, and displaying out the output right data. Now for this explain with my test data, I have 17 rows of inputs, but even without making a _.each
loop, its looping around 17 times, which I just don't understand why?
So my Backbone code,
var TimeSheetModel = Backbone.Model.extend({
defaults: {
Timesheetrow: "",
}
}); //End of Timesheet Model
var TimeSheetCollection = Backbone.Collection.extend({
model: TimeSheetModel,
url: '/dashboard/jsondata/' + TimesheetID()
}); //End of Timesheet Collection
var TimeSheetView = Backbone.View.extend({
el:'#testarea', //HTML loading area for the data
template: _.template( $('#TimesheetData').html() ), //Template to load the JSON data into
initialize: function(){
var NewTimeSheetCollection = new TimeSheetCollection(); //New Instance Of Collecttion
this.listenTo(NewTimeSheetCollection, "add", this.AddMyModel);
NewTimeSheetCollection.fetch();
},
AddMyModel: function(TimeSheetModel) { //apply model data to view template and append to view element
this.$el.append(this.template(TimeSheetModel.toJSON()));
//$(this.el).html(this.template(TimeSheetRowModel.toJSON()));
}
}); //End of Timesheet View
//New Instance & render call for the set Timesheet View.
NewTimeSheetVew = new TimeSheetView();
//NewTimeSheetVew.render(); <- do I need this? seems to work, without it?
});
And my Underscore template code
<script type="text/template" id="TimesheetData">
<% console.log(Timesheetrow) %>
<% _(1).times(function(n){ n=2; }); //just a test bit of code %>
<% if(Timesheetrow.jobtitle) { %>
<form action="#" method="post" id="TimesheetDataList">
<div class="TimesheetRowData">
<input type="hidden" name="data[Timesheetrow][0][id]" value="<%= Timesheetrow.id %>">
<input type="type" name="data[Timesheetrow][0][jobtitle]" value="<%= Timesheetrow.jobtitle %>">
</div>
</form>
<% }; %>
</script>
so my goal is to use backbone, to have one master form, and to loop around 17 times (or X, depending on user given rows). So, for example, I would have an output like:
<form id="ID-HERE">
<div><input value="XXXXX"></div>
<div><input value="XXXXX"></div>
<div><input value="XXXXX"></div>
<div><input value="XXXXX"></div>
<div><input value="XXXXX"></div>
</form>
But what I am getting is that is loops 17 times, so each input as its own form tag, so I currently have 17 forms on my page.
Any help most welcome.
Please, let me know if I have not explained myself right, I am dyslexic, so my spelling, grammar, may be a little off, sorry.
Thanks,
You are listening to the add event in the collection, which will be fired after a fetch operation for each model retrieved from the server:
this.listenTo(NewTimeSheetCollection, "add", this.AddMyModel);
NewTimeSheetCollection.fetch();
So your server is returning 17 rows, the add event is fired 17 times, and every time your AddMyModel
function is rendering the template. (If you have a look at the documentation, the fetch uses internally the set
method which will fire the add events).
Your problem is that the template you are using to render each model contains the form tag, which shouldn't be repeated for every model.
You have a few ways of fixing this, basically splitting what has to be rendered once and what has to be rendered for each model:
Update your template to render an array of models, manually looping inside the template. Do not listen to the add events in the collection and instead pass a success callback to the fetch method, where you render your template passing to it the array of models.
Split your template in 2: a master one with the form loop and a child one with the inputs for a single TimesheetRow
. Render the master template on view initialize, and keep listening to collection add events. On AddMyModel
, render the child template and append it inside the form.
Split your view into a master view and a child view. The master view will render the form and fetch the collection, listening to add events. For every added model, it will create a new instance of the child view passing to it the added model, rendering it and appending its el
inside the form.
EDIT
I have created a jsfiddle with the third option.
There are 2 views TimeSheetRowView
and TimeSheetCollectionView
:
var TimeSheetRowView = Backbone.View.extend({
className:'TimesheetRowData',
template: _.template($('#TimesheetData').html()),
render: function() {
this.$el.append(this.template(this.model.toJSON()));
return this.$el;
}
});
var TimeSheetCollectionView = Backbone.View.extend({
el:'#MasterContainer',
template: _.template($('#TimesheetForm').html()),
initialize: function(){
//render master template
this.$el.append(this.template());
//keep element so we insert child views before it, inside the form
this.submitButton = this.$(".actionSubmit");
//initialize collection and listen to events
this.collection = new TimeSheetCollection();
this.listenTo(this.collection, "add", this.AddTimesheetRow);
//simulate a fetch just setting the models
var hardcodedModels = [{Timesheetrow: {id: 1, index: 0, jobtitle: 'Foo'}},
{Timesheetrow: {id: 2, index: 1, jobtitle: 'Bar'}}];
this.collection.set(hardcodedModels);
},
AddTimesheetRow: function(model) {
//render a single row view and insert it inside the form, right before the submit button
var view = new TimeSheetRowView({model: model});
//view.render() returns its $el so we can chain the insertBefore method
view.render().insertBefore(this.submitButton);
}
});
And the templates have been divided as:
<script type="text/template" id="TimesheetForm">
<form action="#" method="post" id="TimesheetDataList">
<input type="submit" class="actionSubmit" value="Send"/>
</form>
</script>
<script type="text/template" id="TimesheetData">
<input type="hidden" name="data[Timesheetrow][<%= Timesheetrow.index %>][id]" value="<%= Timesheetrow.id %>">
<input type="type" name="data[Timesheetrow][<%= Timesheetrow.index %>][jobtitle]" value="<%= Timesheetrow.jobtitle %>">
</script>