With Backbone.js, I'm trying to bootstrap my data into a collection. My bootstrap looks like log.reset( <JSON> );
When I console.log my collection I see a bunch of objects full of data. The problem is when I console.log on the individual models in the loop, I see a bunch of empty objects with no data. The data is all there in the collection, but I can't get it to pass into my template on each forEach pass. I used the exact same code structure on another part of my project to render a collection view which works perfectly... I don't know why this one isn't working.
Note that I am listening for the reset event on the collection.
//Log Model
var LogItem = Backbone.Model.extend({});
var logItem = new LogItem();
//Log Collection
var Log = Backbone.Collection.extend({
model: LogItem
});
var log = new Log();
//Log Item View
var LogItemView = Backbone.View.extend({
tagName: 'tr',
template: _.template($('#log-item-template').html()),
render: function(){
var attributes = this.model.toJSON();
this.$el.html(this.template( attributes ));
console.log( this.model.toJSON() ); //This shows a bunch of empty objects
return this;
}
});
//Log View
var LogView = Backbone.View.extend({
el: '#log',
initialize: function() {
this.collection.on('reset', this.render, this);
},
render: function(){
this.addAll();
console.log( this.collection.toJSON() ); //This shows objects full of data
},
addOne: function(food) {
var logItemView = new LogItemView({model: logItem});
this.$el.append(logItemView.render().el);
},
addAll: function() {
this.$el.html('');
this.collection.forEach(this.addOne, this);
}
});
var logView = new LogView({collection: log});
How can I fix my empty models?
While building your subviews, your addOne method declares a food argument but you call your LogItemView constructor with {model: logItem}
Try
addOne: function(food) {
var logItemView = new LogItemView({model: food});
this.$el.append(logItemView.render().el);
}
Related
I have backbone.js collection and collectionview. collection view listening to its collection add event. But when I add new models to it's collection it renders mutiple times for each model.
Please Check the JSFiddle
var ImageCollectioView = Backbone.View.extend({
initialize: function() {
this.collection.bind('add', this.render, this);
},
collection: imgColection,
el: '#cont',
render: function() {
var els = [], self = this;
this.collection.each(function(image){
var imageView = new ImageView({model: image});
self.$el.append(imageView.render().el);
});
return this;
}
});
Your render method renders the entire collection. So after adding a model you should clear the existing item views:
render: function() {
var els = [], self = this;
this.$el.empty();
//------^---- clear existing
this.collection.each(function(image){
var imageView = new ImageView({model: image});
self.$el.append(imageView.render().el);
});
return this;
}
That being said, it's better to add a separate method that just appends single item view rather than rendering the entire collection:
var ImageCollectioView = Backbone.View.extend({
initialize: function() {
this.render();
this.listenTo(this.collection, 'add', this.renderItem);
},
el: '#cont',
render: function() {
this.collection.each(this.renderItem, this);
return this;
},
renderItem: function(image) {
var imageView = new ImageView({
model: image
});
this.$el.append(imageView.el);
}
});
Updated Fiddle
I have 2 views, one is a list of "timetracks" and the other is a form to create a timetrack/s
The first one has a collection attached.
The second one, the timetraks form, it defines a "create" function that makes reference to the first one to rerender timetraks view once a new timetrack is created.
timetracks code:
define(['backbone','collections/timetracks', 'views/timetracks/item'], function(Backbone, instanceTimeTracksCollection, TimeTrackView){
var TimeTrackGrid = Backbone.View.extend({
//......
});
return TimeTrackGrid;
});
The form code:
define(['backbone', 'collections/timetracks'], function(Backbone, instanceTimeTracksCollection){
//...............
//next comes my issue:
create: function(){
instanceTimeTracksCollection.create(indexed_array,{
success: function(model, response) {
console.info('model created, response = ',response);
// timeTracksGrid is out of scope, timeTracksGrid would be an instance of timetracks.
timeTracksGrid.render();
},
error: function(error){
console.info('error=',error);
},
wait:true
});
}
});
... and finally I have app.js where the instances of both views are defined:
requirejs(['backbone','views/timetracks/new','views/timetracks/list'],
function(Backbone, newTimeTrackForm, timeTracksGrid) {
var grid = new timeTracksGrid();
var formView = new newTimeTrackForm();
});
How could I render the timetracks view once a new timetrack is created?
**************************** UPDATE *************************************
This is my new version of the code. The issue now is that "this.listenTo(this.collection, "add", this.render);" is overlapping with "this.collection.fetch". As a result the timetracks records are rendered multiple times.
// timetracks view
define(['backbone','collections/timetracks', 'views/timetracks/item'], function(Backbone, timeTracksCollection, TimeTrackView){
var TimeTrackGrid = Backbone.View.extend({
//....
initialize: function(){
_.bindAll(this, 'render', 'generateTimeTracks', 'appendTimeTrack');
this.listenTo(this.collection, "add", this.render);
this.render();
}
render: function(){
$(this.el).html("<table border='1'></table>");
this.collection.fetch({
success: this.generateTimeTracks
});
},
generateTimeTracks : function(){
var self = this;
_(this.collection.models).each(function(item){
self.appendTimeTrack(item);
}, this);
},
appendTimeTrack: function(item){
var timeTrackView = new TimeTrackView({
model: item
});
$('table', this.el).append(timeTrackView.render().el);
}
}
Some other changes:
on app.js instead doing {model:myCollection} as you suggested I'm doing {collection: myCollection}
my form code creates a new model by calling this.collection.create
Thanks again !
A different solution would be to create the views and your collection seperately.
Then in your app.js you could pass the collection to both views. In the initialize function of the TimeTrackGrid you should listen to the "add" event of models on the collections. When such an event is fired you should render the view.
In the create method of your form view you could add the data to your collection. This way your views don't have to know anything about each other which better conforms the Model and View separation.
Thus:
//the grid view
define(['backbone', 'collections/timetracks', 'views/timetracks/item'], function (Backbone, instanceTimeTracksCollection, TimeTrackView) {
var TimeTrackGrid = Backbone.View.extend({
initialize: function () {
//start listening to models being added
this.listenTo(instanceTimeTracksCollection, "add", this.render)
},
render: function () {
//render your view
return this;
}
});
return TimeTrackGrid;
});
//and the form view
define(['backbone', 'collections/timetracks'], function (Backbone, instanceTimeTracksCollection) {
//...............
//next comes my issue:
create: function () {
var data = //get the data from the form
instanceTimeTracksCollection.add(data) //if you have defined a model on your collection, backbone will automatically instantiate the model
}
});
//and you app -> notice the reference to the collection definition
requirejs(['backbone','views/timetracks/new','views/timetracks/list', 'collections/timetrackcollection'],
function(Backbone, newTimeTrackForm, timeTracksGrid) {
var instanceTimeTracksCollection = new TimeTracksCollection();
var grid = new timeTracksGrid({model : instanceTimeTracksCollection});
var formView = new newTimeTrackForm(model : instanceTimeTracksCollection);
});
EDIT=========================================================
fetch the config here
requirejs(['backbone','views/timetracks/new','views/timetracks/list'],
function(Backbone, newTimeTrackForm, timeTracksGrid) {
var grid = new timeTracksGrid();
var formView = new newTimeTrackForm();
var collection = new Collection();
collection.fetch()
});
change your view to:
define(['backbone','collections/timetracks', 'views/timetracks/item'], function(Backbone, timeTracksCollection, TimeTrackView){
var TimeTrackGrid = Backbone.View.extend({
//....
initialize: function(){
_.bindAll(this, 'render', 'generateTimeTracks', 'appendTimeTrack');
// maybe backbone does not fire the add event after fetch
// I believe it does, but I might be mistaken. You will have to look that up
this.listenTo(this.collection, "add", this.render);
this.render();
}
//model is passed to the render method by backbone
render: function(model){
$(this.el).html("<table border='1'></table>");
$('table', this.el).append(new TimeTrackView({model : model}).render().el);
},
//unused now
generateTimeTracks : function(){
var self = this;
// backbone has underscore build in
// so use this instead
this.collection.each(function(item){
//do something with item
}
_(this.collection.models).each(function(item){
self.appendTimeTrack(item);
}, this);
},
//unused now
appendTimeTrack: function(item){
var timeTrackView = new TimeTrackView({
model: item
});
$('table', this.el).append(timeTrackView.render().el);
}
}
I am trying to understand the relationship between a model and a view. I've tried building a model and view to render that model.
I get the error Cannot call method 'toJSON' of undefined which I understand as the actual instance of the model is not being sent to the view.
I feel there is something missing in the initialize of the view?
The Model:
var sticky = Backbone.Model.extend({
defaults: {
title:"",
content:"",
created: new Date()
},
initialize: function() {
console.log("sticky created!");
}
});
The View:
var stickyView = Backbone.View.extend({
tagName:"div",
className:"sticky-container",
initialize: function() {
this.render();
console.log("stickyView created!");
},
render: function() {
$("#content-view").prepend(this.el);
var data = this.model.toJSON(); // Error: Cannot call method 'toJSON' of undefined
console.log(data);
var source = $("#sticky-template").html();
var template = Handlebars.compile(source);
$(this.el).html(template(data));
return this;
}
});
Creating new model and new instance of the view:
var Sticky = new sticky({title:"test"});
var StickyView = new stickyView();
You have to pass your model instance to your view, Backbone will do the rest:
constructor / initialize new View([options])
There are several special options that, if passed, will be attached directly to
the view: model, collection, el, id, className, tagName and
attributes.
which means you would create your view like this
var StickyView = new stickyView({model: Sticky});
And while you're at it, you could pass your compiled template and the DOM node you wish to set as your view element (and remove the tagName and className from your view definition) to avoid a strict coupling:
var stickyView = Backbone.View.extend({
initialize: function(opts) {
this.template = opts.template;
this.render();
console.log("stickyView created!");
},
render: function() {
var data = this.model.toJSON();
console.log(data);
this.$el.html(this.template(data));
return this;
}
});
var StickyView = new stickyView({
model: Sticky,
el: '#content-view',
template: Handlebars.compile($("#sticky-template").html())
});
I have a collection, and I'm trying to share information concerning the current model being selected between two different views
-->TO PUT IT SIMPLY, I WANT TO BE ABLE TO ACCESS THE MODEL SELECTED IN ONE VIEW FROM ANOTHER VIEW AND CHANGE/ASSIGN THE ATTRIBUTES OF THE MODEL
The first view is defined with:
App.Views.Person = Backbone.View.extend({
tagName: 'a',
template: template('personTemplate'),
initialize: function(){
this.model.on('change', this.render, this);
this.model.on('destroy', this.remove, this);
},
render: function() {
this.$el.html( this.template(this.model.toJSON()) );
this.input = this.$('.view');
return this;
},
the second view is defined with:
App.Views.App = Backbone.View.extend({
el: 'html',
initialize: function(){
_.bindAll(this,"render");
},
render: function() {
return this;
},
and I created my views with the following
addPersonView = new App.Views.AddPerson({ collection: peopleCollection });
appView = new App.Views.App({ model: person, collection: peopleCollection });
How do I make it so that the model selected in the 2nd view is the same as the model in the first view as pulled from my collection --> for example, when I type something into the input box on my bottom view, I want to be able to use: this.set.model({name:title}) and for this to set the model attribute for the element (associated with a model) that is selected in my top view, but using this.set.modelis not choosing the correct model that is selected in my first view
for further reference and confusion: my models are being added to my PeopleView with the following code which i'm loading from an array;
App.Views.People = Backbone.View.extend({
// tagName: '',
initialize: function() {
var i = 1;
while(i < size)
{
var person = new App.Models.Person({ url: jsArray[i] });// creating a new person object..
this.collection.add(person);
i++
}
this.collection.on('add', this.addOne, this);
console.log(jsArray[1]);
// listeners/anouncers for the collection on add..
},
// refactored render method...
render: function() {
this.collection.each(this.addOne, this);
return this;
},
// called from render method of collection view..
addOne: function(person) {
var personView = new App.Views.Person({ model: person, vent: vent });
this.$el.append(personView.render().el);
}
});
I resolved this by using 'vents' so to speak --> (awesome link) the vents provide ways for views to communicate etc
http://lostechies.com/derickbailey/2012/04/03/revisiting-the-backbone-event-aggregator-lessons-learned/, or an application level aggregator like so:
MyApp.vent = _.extend({}, Backbone.Events);
MyApp.vent.on("some:event", function(){
alert("some event was fired!");
});
MyApp.vent.trigger("some:event");
Here is my Model View and Collection :
window.Report = Backbone.Model.extend({});
window.ReportCollection = Backbone.Collection.extend({
model: Report,
initialize: function(properties){
this.url = properties.url;
}
});
window.ReportCollectionView = Backbone.View.extend({
initialize: function(){
this.collection.reset();
this.render();
},
render: function(){
var self = this;
this.collection.fetch({
success: function(){
self.collection.each(function(model){
//pass model to subview
});
}
}
});
}
});
in the other part of the code I use the instantiate the above objects
var reportCollection = new ReportCollection({url:someURL});
var reportCollectionView = new ReportCollectionView({collection:reportCollection});
'someURL' is a REST based URL that returns JSON list of Objects
So far everything looks good. What I am trying to achieve is:
I must be able to refresh the 'reportCollection' by changing the url and this should trigger an updated 'reportCollectionView'. Thanks for any pointers
I suppose you could add a method to your collection which changes url and forces a fetch:
window.ReportCollection = Backbone.Collection.extend({
//...
changeUrl: function(url) {
this.url = url;
this.fetch();
}
});
and then bind to the "reset" event in your view:
window.ReportCollectionView = Backbone.View.extend({
initialize: function() {
_.bindAll(this, 'render');
this.collection.on('reset', this.render);
this.collection.reset();
},
//...
});
Then if you do this:
c = new ReportCollection(...);
v = new ReportCollectionView({ collection: c, ... });
You'll get your rendered view and then later you can:
c.changeUrl(...);
to set the new URL and that will trigger a render call on v.