This question already exists:
Closed 10 years ago.
Possible Duplicate:
Backbone.js - add parameter sending to fetch not trigger the collection
In my function, eventhough i added the reset to trigger my render function, nothing is happening... any clue me the wrong what i did..?
code:
$(document).ready(function(){
var school = {};
school.model = Backbone.Model.extend({
defaults:{
name:'name yet to decide'
}
});
school.collect = Backbone.Collection.extend({
model:school.model,
url:'js/school.json',
parse:function(response){
return response;
}
});
school.view = Backbone.View.extend({
tagName:'div',
className:'division',
template:$('#newTemp').html(),
render:function(){
var temp = _.template(this.template);
this.$el.html(temp(this.model.toJSON()));
return this;
}
});
school.views = Backbone.View.extend({
el:$('#content'),
initialize:function(){
_.bindAll(this);
this.collection = new school.collect;
this.collection.bind("reset", this.render);
this.collection.fetch({
update:true
})
},
render:function(){
var that = this;
_.each(this.collection.models, function(item){
that.addItems(newItem);
})
},
resets:function(){
console.log(this.collection.models)
console.log('calling from resets')
},
addItems:function(item){
var newItem = new school.collect({model:item});
this.$el.append(newItem.render().el);
},
changed:function(){
console.log('calling from changed')
}
});
var newSchool = new school.views;
})
As on Backbone.js 1.0.0 You listen to reset but pass {reset:true} option.
When the model data returns from the server, the collection will be
(efficiently) reset, unless you pass {update: true}, in which case it
will use update to (intelligently) merge the fetched models
http://backbonejs.org/#Collection-fetch
Related
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 have an Backbone App where I fetch different collections by clicking a Letter from a list. So, I want to add a Progressbar or some kind of rotating image but I dont know how to do this.
My View looks like this
function (App, Backbone) {
var Artists = App.module();
var ArtistView = Backbone.View.extend({
tagName : 'li',
template: 'artistItem',
serialize: function() {
var data = this.model.toJSON();
data.letter = this.model.collection.letter;
return data;
},
});
Artists.View = Backbone.View.extend({
tagName : 'ul',
className : 'artistList',
initialize: function() {
this.listenTo(this.collection, 'all', this.render);
this.listenTo(App, 'navigateLetter', this.updateState);
},
beforeRender: function() {
var self = this;
this.collection.each(function(item) {
self.insertView(new ArtistView({model: item}))
})
},
updateState: function(letter) {
this.collection.letter = letter;
this.stopListening(this.collection);
this.collection.fetch();
this.listenTo(this.collection, 'all', this.render);
}
});
Artists.ArtistsCollection = Backbone.Collection.extend({
url: function() {
return '/projects/mdk/index.php/api/artists/' + this.letter;
}
});
return Artists;
});
So does anyone have an idea how to do this? I could imagine I should do something in initialize or beforeRender?
Thanks in advance
You can use a spinner for the loading effect. For that you need
spin.js
Add entry of that spin.js into main file.
To use that spinner.
var yourSpinner = new Spinner();
var target = document.getElementById('spinHere');
yourSpinner.spin(target);
e.g in your case take updateState:function(){} :
updateState: function(letter) {
this.collection.letter = letter;
this.stopListening(this.collection);
var yourSpinner = new Spinner();
var target = document.getElementById('spinHere');
yourSpinner.spin(target);
this.collection.fetch();
yourSpinner.stop();
this.listenTo(this.collection, 'all', this.render);
}
Take a look at this: https://github.com/davidsulc/marionette-gentle-introduction/blob/master/assets/js/apps/contacts/list/list_controller.js#L4
It's from my Marionette book app, where the idea is to immediately display a loading view, and when the collection is fetched, render the actual view (and closing the loading view, which is Handled by Marionette). It would give something like (pseudocode):
var loadingView = new ContactManager.Common.Views.Loading();
ContactManager.mainRegion.show(loadingView);
var fetchingContacts = myCollection.fetch();
$.when(fetchingContacts).done(function(contacts){
ContactManager.mainRegion.show(new MyCollView({ collection: contacts }));
});
The code uses a deferred to determine when the collection has been fetched (and therefore the new view should be displayed). You can learn more about using deferreds here:
http://davidsulc.com/blog/2013/04/01/using-jquery-promises-to-render-backbone-views-after-fetching-data/
http://davidsulc.com/blog/2013/04/02/rendering-a-view-after-multiple-async-functions-return-using-promises/
So my issue is that the collection in my function that is being fired from my router vent/event aggregator does not have access to my main collection's fetched models.
My guess is that it's an asynchronous call issue, but how can I make it so the vented function call WAITS until the collection/models are fetched before executing? Or is that even my issue?
Here's is my relevant code. I'm using require.js and backbone to create a modular AMD app. Thank you so much in advance:
main.js
require(['views/app'], function (AppView) {
window.App = {
Vent : _.extend({}, Backbone.Events)
};
new AppView();
router.js
define([
'backbone',
], function(Backbone){
var MainRouter = Backbone.Router.extend({
routes: {
'levelone/:id':'showWork'
},
showWork: function (index){
App.Vent.trigger('addressChange', {
index: index
});
}
});
return MainRouter;
});
App.js
define([
'backbone',
'views/levelone/LevelOneView',
'views/leveltwo/LevelTwoView',
'views/static/StaticView',
'router'
],
function(Backbone, LevelOneView, LevelTwoView, StaticView, MainRouter){
var AppView = Backbone.View.extend({
el: $("body"),
events: {
...
},
initialize: function(){
new LevelOneView();
App.router = new MainRouter();
Backbone.history.start();
},
.............
LevelOneView.js
initialize:function() {
this.getCollection();
this.domSetup();
App.Vent.on('addressChange', this.addressChange, this);
},
getCollection : function(){
var self = this;
onDataHandler = function(collection) {
self.LevelTwoCollectionGrab();
};
this.collection = new LevelOneCollection([]);
this.collection.fetch({ success : onDataHandler, dataType: "jsonp" });
},
// We grab a Level Two Collection here so we can take the ids from it and add them to our Level One collection.
// This is necessary so we can create links between the two levels.
LevelTwoCollectionGrab: function(){
var self = this;
this.leveltwocollection = new LevelTwoCollectionBase([]);
onDataHandler = function(collection){
self.render();
self.$el.animate({
'opacity': 1
}, 1200);
self.renderLevelTwoIds();
self.setLevelTwoids();
self.attachLevelTwoLink();
}
this.leveltwocollection.fetch({success : onDataHandler, dataType: "jsonp"});
},
renderLevelTwoIds: function(){
return this;
},
render: function(){
var pathname = window.location.hash;
this.setModelId(this.collection.models);
this.addPositionsToIndex();
this.determineModels();
this.attachLevelTwoLink();
.......
},
addressChange: function(opts){
console.log(this.collection.models)
//returns a big fat empty array. WHY?!
}
You could use the jQuery Promises returned by fetch to help you know when both collections are fetched.
initialize:function() {
this.getCollection();
this.domSetup();
App.Vent.on('addressChange', this.addressChange, this);
},
getCollection : function(){
var self = this;
console.log('should be first');
this.collection = new LevelOneCollection([]);
this.fetchingLevelOne = this.collection.fetch({ dataType: "jsonp" });
this.fetchingLevelTwo = this.leveltwocollection.fetch({ dataType: "jsonp"});
// wait for both collections to be done fetching.
// this one will always be called before the one in addressChange
$.when(this.fetchingCollectionOne, this.fetchingCollectionTwo).done(function(){
console.log('should be second');
self.render();
self.$el.animate({
'opacity': 1
}, 1200);
self.renderLevelTwoIds();
self.setLevelTwoids();
self.attachLevelTwoLink();
});
},
renderLevelTwoIds: function(){
return this;
},
render: function(){
var pathname = window.location.hash;
this.setModelId(this.collection.models);
this.addPositionsToIndex();
this.determineModels();
this.attachLevelTwoLink();
.......
},
addressChange: function(opts){
var self = this;
// wait for both collections to be done fetching.
// this one will always be called AFTER the one in getCollection
$.when(this.fetchingCollectionOne, this.fetchingCollectionTwo).done(function(){
console.log('should be third');
console.log(self.collection.models);
});
}
A nice thing about this, if the user is very very fast at typing in the address bar, and several addressChange calls are made, they will all wait until the collections are fetched and will execute in the proper order.
I think I solved it. Basically, I'm now calling the function inside of $.when function--
Like so:
$.when(this.collection.fetch(), this.leveltwocollection.fetch()).done(function(){
$.when(self.render()).done(function(){
_.each(self.collection.models, function(model){
var wpid = model.get('id'),
bbid = model.id;
if (wpid == index){
window.App.InfoPos.pos5 = bbid;
var modelinfo = model.toJSON();
$('.box5').empty();
$('.box5').html(tmplOne(modelinfo));
self.$el.animate({
'opacity': 1
}, 1200);
}
});
});
});
The function launches from inside the when call and then waits until completed before executing anything in the done function. Works now! Thanks for the help all, especially you Paul.
In my backbone function, all works fine, even the filter. but the issue is, whenever i click on the filter type and switch to another filter type, it is filtering from the existing filtered data, instead of fetching new from server and filter...
in case if i add fetch call over my filter function, it fetch applying all data, without filtering... how can i fix this..?
my code :
$(document).ready(function(){
var school = {};
school.model = Backbone.Model.extend({
defaults:{
name:'no name',
age:'no age'
}
});
school.collect = Backbone.Collection.extend({
model:school.model,
url:'js/school.json',
initialize:function(){
this.sortVar = "name"
}
});
school.view = Backbone.View.extend({
tagName:'div',
className:'member',
template:$('#newTemp').html(),
render:function(){
var temp = _.template(this.template);
this.$el.html(temp(this.model.toJSON()));
return this;
}
});
school.views = Backbone.View.extend({
el:$('#content'),
events:{
'click #newData' : 'newArrival',
'click #showName' : 'showByName',
'click #showAge' : 'showByAge'
},
initialize:function(){
_.bindAll(this);
this.collection = new school.collect;
this.collection.bind('reset', this.render);
this.collection.fetch();
this.childViews = [];
},
newArrival:function(){
this.collection.fetch(); //it works fine, i use this for update
},
showByName:function(){
// this.collection.fetch(); //but i can't do this, it removes filtered data..
this.sortVar = 'name';
var filterType = _.filter(this.collection.models, function(item){
return item.get('name') != '';
})
this.collection.reset(filterType); //resets without fetching (filtering from existing datas..)
},
showByAge:function(){
// this.collection.fetch(); //but i can't do this, it removes filtered data..
this.sortVar = 'age';
var filterType = _.filter(this.collection.models,function(item){
return item.get('age') != 0;
})
this.collection.reset(filterType); //resets without fetching (filtering from existing datas..)
},
render:function(){
_.each(this.childViews, function(old){
old.remove();
});
this.childViews = [];
var that = this;
_.each(this.collection.models, function(item){
that.renderItem(item);
});
},
renderItem:function(item){
var newItem = new school.view({model:item});
this.$el.append(newItem.render().el);
this.childViews.push(newItem);
}
});
var newSchool = new school.views;
});
thanks in advance, as well i do have another 2 methods to add which is sorting the name and age while show the datas.
This is works for me.. thanks all.
showByAge:function(){
var that = this;
this.sortVar = 'age';
this.collection.fetch()
.done(function(){ // i am proceeding after i finish the fetch!
var filterType = _.filter(that.collection.models,function(item){
return item.get(that.sortVar) != 0;
})
that.collection.reset(filterType);
})
},
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.