Backbone.js - View not reloading - javascript

So I've managed to figure out how to populate my collection from an external file, and render a view based on a url, but I'm running into a problem. The code below is working as intended, except on page load, I'm getting the following error:
Uncaught TypeError: Cannot call method 'get' of undefined
Getting rid of "view.render()" eliminates the error, but now the app no longer responds to ID changes in the url (e.g. going from #/donuts/1 to #/donuts/2 does not update the view)
Could someone point me in the right direction here?
The Code:
(function(){
var Donut = Backbone.Model.extend({
defaults: {
name: null,
sprinkles: null,
cream_filled: null
}
});
var Donuts = Backbone.Collection.extend({
url: 'json.json',
model: Donut,
initialize: function() {
this.fetch();
}
})
var donuts = new Donuts();
var donutView = Backbone.View.extend({
initialize: function() {
this.collection.bind("reset", this.render, this)
},
render: function() {
console.log(this.collection.models[this.id].get('name'))
}
});
var App = Backbone.Router.extend({
routes: {
"donut/:id" : 'donutName',
},
donutName: function(id) {
var view = new donutView({
collection: donuts,
id: id
});
view.render();
}
});
var app = new App();
Backbone.history.start();
})(jQuery);
The JSON:
[
{
"name": "Boston Cream",
"sprinkles" : "false",
"cream_filled": "true"
},
{
"name": "Plain",
"sprinkles": "false",
"cream_filled": "false"
},
{
"name": "Sprinkles",
"sprinkles": "true",
"cream_filled": "false"
}
]

Looks like a bit of a flow issue here. You have the view listening to the collection's "reset" event. So when there's a reset, the view will render. That is just fine. But I believe the problem is in your router. When you route, you're creating a new instance of the view, but not doing anything with the collection, so its state is the same.
Since you're already observing the collection, do nothing with the view. When you route, update the collection's url, then do a fetch. This will trigger a reset and the view should then update itself.

Related

Not fetching correct url issue

I have a backboneJS app that has a router that looks
var StoreRouter = Backbone.Router.extend({
routes: {
'stores/add/' : 'add',
'stores/edit/:id': 'edit'
},
add: function(){
var addStoresView = new AddStoresView({
el: ".wrapper"
});
},
edit: function(id){
var editStoresView = new EditStoresView({
el: ".wrapper",
model: new Store({ id: id })
});
}
});
var storeRouter = new StoreRouter();
Backbone.history.start({ pushState: true, hashChange: false });
and a model that looks like:
var Store = Backbone.Model.extend({
urlRoot: "/stores/"
});
and then my view looks like:
var EditStoresView = Backbone.View.extend({
...
render: function() {
this.model.fetch({
success : function(model, response, options) {
this.$el.append ( JST['tmpl/' + "edit"] (model.toJSON()) );
}
});
}
I thought that urlRoot when fetched would call /stores/ID_HERE, but right now it doesn't call that, it just calls /stores/, but I'm not sure why and how to fix this?
In devTools, here is the url it's going for:
GET http://localhost/stores/
This might not be the answer since it depends on your real production code.
Normally the code you entered is supposed to work, and I even saw a comment saying that it works in a jsfiddle. A couple of reasons might affect the outcome:
In your code you changed the Backbone.Model.url() function. By default the url function is
url: function() {
var base =
_.result(this, 'urlRoot') ||
_.result(this.collection, 'url') ||
urlError();
if (this.isNew()) return base;
return base.replace(/([^\/])$/, '$1/') + encodeURIComponent(this.id);
},
This is the function to be used by Backbone to generate the URL for model.fetch();.
You added a custom idAttribute when you declared your Store Model to be like the one in your DB. For example your database has a different id than id itself, but in your code you still use new Model({ id: id }); when you really should use new Model({ customId: id });. What happens behind the scenes is that you see in the url() function it checks if the model isNew(). This function actually checks if the id is set, but if it is custom it checks for that:
isNew: function() {
return !this.has(this.idAttribute);
},
You messed up with Backbone.sync ... lots of things can be done with this I will not even start unless I want to make a paper on it. Maybe you followed a tutorial without knowing that it might affect some other code.
You called model.fetch() "a la" $.ajax style:
model.fetch({
data: objectHere,
url: yourUrlHere,
success: function () {},
error: function () {}
});
This overrides the awesomeness of the Backbone automation. (I think sync takes over from here, don't quote me on that).
Reference: Backbone annotated sourcecode

Backbone returning length of 0

I'm fairly new to backbone and I'm trying to build a simple app. This is what I have so far
var Election = Backbone.Model.extend();
var Elections = Backbone.Collection.extend({
model: Election,
url: '/assets/data.json',
initialize: function() {
console.log('init col');
this.render();
return this;
},
render: function() {
console.log('rendering the collection');
return this;
},
// return this
});
var router = Backbone.Router.extend({
routes: {
'': 'root'
},
root: function(){
var collection = new Elections();
collection.fetch();
console.log(collection.length); //returns 0
}
});
var r = new router();
Backbone.history.start();
The log is this
> init col
> rendering the collection
> 0
But when I create a new collection manually in the console, it shows the right length and everything, I assume that for some reason the router call is happening too early, not sure though. This is a sample of data.json
[
{
"year": 1868,
...
},
{
"year": 1872,
...
},
fetch performs an asynchronous HTTP (Ajax) request, so you should pass fetch a success callback:
collection.fetch({
success: function(){
console.log(collection.length);
}
});
expanding on CD's answer a little bit,
a better approach would be calling fetch and then using listenTo to call the render method on change
in your initialize method do this
_.bindAll(this, 'render');
this.listenTo(this, 'change', this.render);
and you can have the fetch outside if you wish
collection.fetch()
and it will automatically update on change

Using Backbone.js Models/Collections with static JSON

I'm trying to learn Backbone by diving right in and building out a simple "question" app, but I've been banging my head against the wall trying to figure out how to use models and/or collections correctly. I've added the code up to where I've gotten myself lost. I'm able to get the collection to pull in the JSON file (doing "var list = new QuestionList; list.getByCid('c0') seems to return the first question), but I can't figure out how to update the model with that, use the current model for the view's data, then how to update the model with the next question when a "next" button is clicked.
What I'm trying to get here is a simple app that pulls up the JSON on load, displays the first question, then shows the next question when the button is pressed.
Could anyone help me connect the dots?
/questions.json
[
{
questionName: 'location',
question: 'Where are you from?',
inputType: 'text'
},
{
questionName: 'age',
question: 'How old are you?',
inputType: 'text'
},
{
questionName: 'search',
question: 'Which search engine do you use?'
inputType: 'select',
options: {
google: 'Google',
bing: 'Bing',
yahoo: 'Yahoo'
}
}
]
/app.js
var Question = Backbone.Model.Extend({});
var QuestionList = Backbone.Collection.extend({
model: Question,
url: "/questions.json"
});
var QuestionView = Backbone.View.extend({
template: _.template($('#question').html()),
events: {
"click .next" : "showNextQuestion"
},
showNextQuestion: function() {
// Not sure what to put here?
},
render: function () {
var placeholders = {
question: this.model.question, //Guessing this would be it once the model updates
}
$(this.el).html(this.template, placeholders));
return this;
}
});
As is evident, in the current setup, the view needs access to a greater scope than just its single model. Two possible approaches here, that I can see.
1) Pass the collection (using new QuestionView({ collection: theCollection })) rather than the model to QuestionView. Maintain an index, which you increment and re-render on the click event. This should look something like:
var QuestionView = Backbone.View.extend({
initialize: function() {
// make "this" context the current view, when these methods are called
_.bindAll(this, "showNextQuestion", "render");
this.currentIndex = 0;
this.render();
}
showNextQuestion: function() {
this.currentIndex ++;
if (this.currentIndex < this.collection.length) {
this.render();
}
},
render: function () {
$(this.el).html(this.template(this.collection.at(this.currentIndex) ));
}
});
2) Set up a Router and call router.navigate("questions/" + index, {trigger: true}) on the click event. Something like this:
var questionView = new QuestionView( { collection: myCollection });
var router = Backbone.Router.extend({
routes: {
"question/:id": "question"
},
question: function(id) {
questionView.currentIndex = id;
questionView.render();
}
});

backbonejs: Cannot call method 'bind' of undefined

I am currently learning backbone.js via a screencast tutorial, somewhere along the way, my code seems to stop working, throwing the error Cannot call method 'bind' of undefined in Chrome's javascript console. The erroneous line is contained in the initialize function:
window.PlaylistView = Backbone.View.extend({
tag: 'section',
className: 'playlist',
initialize: function() {
_.bindAll(this, 'render');
this.template = _.template($('#playlist-template').html());
this.collection.bind('reset', this.render); //<<<<<< THIS LINE
this.player = this.options.player;
this.library = this.options.library;
},
render: function() {
$(this.el).html(this.template(this.player.toJSON()));
this.$('button.play').toggle(this.player.isStopped());
this.$('button.pause').toggle(this.player.isPlaying());
return this;
}
});
I don't know what this.collection means, why does the view have a collection, isn't collections for models? this.collection.bind() used in other views did not seem to throw any errors. In window.LibraryAlbumView which calls this.collection.trigger('select', this.model); and extends window.AlbumView, I dont see any collection defined anywhere in window.AlbumView, yet no error is thrown. This seems to be confusing me.
JSFIDDLE
EDIT:
The error has been fixed because instead of
window.Player = Backbone.Model.extend({
defaults: {
'currentAlbumIndex': 0,
'currentTrackIndex': 0,
'state': 'stop'
},
initialize: function() {
this.playlist = new Playlist();
},
I had
window.Player = Backbone.Model.extend({
defaults: {
'currentAlbumIndex': 0,
'currentTrackIndex': 0,
'state': 'stop'
},
initialize: function() {
playlist = new Playlist(); // <<< this line changed!
},
Also previously this.collection refered to the collection here,
window.BackboneTunes = Backbone.Router.extend({
routes: {
'': 'home',
'blank': 'blank'
},
initialize: function() {
this.playlistView = new PlaylistView({
collection: window.player.playlist, // <<<< THIS ONE!
player: window.player,
library: window.library
});
this.libraryView = new LibraryView({
collection: window.library
});
},
Backbone Views contain a collection or a model, because views are meant for presenting the data contained within a model or a collections of models.
This example throws an error because this.collection has not been defined yet. To do that you need to initialize some collection and then pass it on to your view.
new PlayListView({collection: someCollection});

Backbone.js - data not being populated in collection even though fetch is successful

I am trying to populate a collection from a simple JSON file as part of learning backbone.js. But I can't get it to work.
The AJAX call is made (verified with FireBug), but the toJSON method returns undefined.
What am I doing wrong?
theModel = Backbone.Model.extend();
theCollection = Backbone.Collection.extend({
model: aModel,
url: "source.json"
});
theView = Backbone.View.extend({
el: $("#temp"),
initialize: function () {
this.collection = new theCollection();
this.collection.fetch();
this.render();
},
render : function () {
$(this.el).html( this.collection.toJSON() ); // Returns blank
}
});
var myView = new theView;
Here's my JSON:
[{
"description": "Lorem ipsum..."
},
{
"description": "Lorem ipsum..."
}]
fetch is asynchronous, your collection won't yet be populated if you immediately call render. To solve this problem, you just have to bind the collection reset event (sync event for Backbone>=1.0) to the view render :
theView = Backbone.View.extend({
el: $("#temp"),
initialize: function () {
this.collection = new theCollection();
// for Backbone < 1.0
this.collection.on("reset", this.render, this);
// for Backbone >= 1.0
this.collection.on("sync", this.render, this);
this.collection.fetch();
},
render : function () {
console.log( this.collection.toJSON() );
}
});
Note the third argument of the bind method, giving the correct context to the method:
http://documentcloud.github.com/backbone/#FAQ-this
i believe the problem lies in your json
either you override the parse method on the collection, to work with your json
or you could change the json :)
[{
"description": "Lorem ipsum..."
},
{
"description": "Lorem ipsum..."
}]
i believe this is what your json should look like, just an array of your models.

Categories