Backbone.js reinstantiating collection does not update corresponding view - backbone.js

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.

Related

Backbone.js collection view render multiple times for single trigger

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

backbone + requirejs: scope issue

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);
}
}

Backbone collection full of data, but models are empty

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);
}

getting model data to a view backbone.js

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())
});

Backbone.js not rendering

My collection is not rendering for some reason. Cannot find out why.
TreeItem = Backbone.Model.extend({
});
TreeList = Backbone.Collection.extend({
model: TreeItem,
url: "/get_tree_list"
});
window.tree_list = new TreeList();
// VIEW
window.TreeItemView = Backbone.View.extend({
tagName: 'li',
initialize: function(){
_.bindAll(this, 'render');
},
render: function(){
$(this.el).html('<span>'+this.model.get('title')+'</span>');
return this;
}
});
window.TreeListView = Backbone.View.extend({
el: "#tree-structure",
events: {
},
initialize: function() {
_.bindAll(this, 'appendItem', 'render');
tree_list.bind('add', this.appendItem);
tree_list.fetch();
this.render();
},
render: function() {
tree_list.each(this.appendItem);
return this;
},
appendItem: function(item){
var tree_item_view = new TreeItemView({
model: item
});
$(this.el).append(tree_item_view.render().el);
}
});
var tree_list_view = new TreeListView;
Backbone.js provides a lot to be interpreted that's where people new go wrong. Your mistake is fundamental in nature. You tie the View directly to the model
see initialize function where a instance of collection is rendered!!
Always and anywhere you create model, collection pass then as parameters to the constructor of views. Check my fiddle
Never call render inside model, view or collection. They must be inside application file
JsFiddle
http://jsfiddle.net/35QGM/

Resources