Backbone returning length of 0 - javascript

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

Related

Backbone js building a collection from the results returned from multiple URL's

I have a model that looks like this:
var BasicModel = Backbone.Model.extend({
defaults: {
a: '',
b: '',
c: '',
d: '',
e: ''
},
idAttribute: "f",
parse: function (data) {
return data;
},
initialize: function () {
console.log('Intialized');
},
constructor: function (attributes, options) {
Backbone.Model.apply(this, arguments);
}
});
Collections like this:
var BasicCollection = Backbone.Collection.extend({
model: BasicModel,
url: urlCode
});
var ACollection = BasicCollection.extend({
parse: function (data) {
return data.a.b.c.d;
}
});
var aCollection = new ACollection ();
And Views like this:
var BasicView = Backbone.View.extend({
tagName: 'tr',
template: _.template($('#basic-status-template').html()),
render: function () {
this.$el.html(this.template(this.model.attributes));
return this;
}
});
var BasicsView = Backbone.View.extend({
initialize: function () {
this.render();
},
});
This is how the collection fetch looks (Which builds the views):
aCollection.fetch({
success: function () {
// View
var aView = BasicsView.extend({
el: '#foobar #table-body',
render: function () {
this.$el.html('');
aCollection.each(function (model) {
var x = new BasicView({
model: model
});
this.$el.append(x.render().el);
}.bind(this));
return this;
}
});
var app = new aView();
}
});
But now I face a problem when trying to add another piece of detail to the tables that the views will populate. One of the columns will require data that will come from a seperate url. But I still want it to be part of the same process.
Is there are way to form a collection from the result of two URL's. (i.e. a, b, d and e come from URL 1, and c comes from URL 2)?
This way all I would need to change was the template and it should all work the same. Instead of having to alter a load of other stuff as well.
Thanks.
You have few options:
Update the endpoint to send required data. This is the proper way to do it. Collection should Ideally have single endpoint
Send a seperate AJAX request to get data from one URL before fetching collection, then in collection's parse method add the data to the response fetched from collection's URL
Do something like:
$.when(collection.fetch(), collection.fetchExtraData())
.done(()=> { /* create view here */ });
fetchExtraData here is a custom function that sends extra request and updates collection properly with the data. This way both requests are sent simultaneously. You need to make sure parse doesn't reset the data from other endpoint.

Backbone Collection not getting models

Basically, I'm trying to send a GET request to my Node server, so that I can get back blog posts to create links. I do a collection.fetch, which successful completes the GET request (the Node server logs that it's sending the right objects). The model successfully parses the right data, but when I try to use the collection, it says that it's empty. Here's the code:
var mdm = mdm || {};
// MODEL
mdm.Post = Backbone.Model.extend({
parse: function( response ) {
response.id = response._id;
console.log(response); // logs the two documents
return response;
}
});
// COLLECTION
mdm.Posts = Backbone.Collection.extend({
model: mdm.Post,
url: '/api/posts'
});
// MODEL VIEW
mdm.LinkView = Backbone.View.extend({
template: _.template( $('#link_template').html() ),
render: function() {
this.$el.html( this.template( this.model.toJSON() ));
return this;
}
});
// COLLECTION VIEW
mdm.LinksView = Backbone.View.extend({
el: '#link_list',
initialize: function() {
this.collection = new mdm.Posts();
this.collection.fetch({reset: true});
// makes the request properly, but collection is empty
this.render();
// never gets called because the collection is empty
console.log(this.collection.length);
// logs a length of 0
},
render: function() {
// renders collection
}
});
$(function() {
new mdm.LinksView();
});
The data is being sent and is parsed in the models, so I'm not sure what the collection ends up being empty. Any help would be greatly appreciated.
The most likely reason you are not seeing the models in your view is because the render is happening before the asynchronous fetch is complete.
Something like below would work better:
mdm.LinksView = Backbone.View.extend({
el: '#link_list',
initialize: function() {
this.collection = new mdm.Posts();
this.listenTo(this.collection, 'reset', this.render);
this.collection.fetch({reset: true});
}
The above code sets a listener for the reset event on the collection and executes the render function when that happens.
Also, you could passing in success and error handlers into fetch and call the render function manually as well.
this.collection.fetch({
success: _.bind(function() {
this.render(); }, this)
});
Hope this helps!
Per #fbynite's comment, the problem was related to fetch being asynchronous. I made the following changes to the collection view, and it did the trick:
initialize: function() {
var self = this;
this.collection = new mdm.Posts();
this.collection.fetch({reset: true,
success: function() {
self.render();
console.log(self.collection.length);
}
});
},
The code is a modification from a Backbone tutorial, so other users may encounter a similar problem. http://addyosmani.github.io/backbone-fundamentals/#exercise-2-book-library---your-first-restful-backbone.js-app

Backbone reset a collection on fetch

This is my problem:
I have a container view that holds a collection.
On page load I get some models, populate this collection with them, then render the models
I fire and event
When this event fires, I want to make a call to my api (which returns models based on input parameters)
I then want to remove all existing models from the collection, repopulate with my new models, and then render the models
This is how I set up my model/collection/view
var someModel = Backbone.Model.extend({});
var someCollection = Backbone.Collection.extend({
model: someModel,
url: "api/someapi"
});
var someView = Backbone.View.extend({
events: {
"click #refresh": "refreshCollection"
},
initialize: function () {
this.collection.bind("reset", this.render, this);
},
render: function () {
// render stuff
},
refreshCollection: function (e) {
this.collection.fetch({data: {someParam: someValue});
this.render();
}
});
var app = function (models) {
this.start = function () {
this.models = new someCollection();
this.view = new someView({collection: this.models});
this.view.reset(models);
};
};
My point of interest is here:
refreshCollection: function (e) {
this.collection.fetch({data: {someParam: someValue});
this.render();
}
I pass in some paramaters, and my api returns a json array of models. I want to get rid of all existing models in the collection, and put all of my returned models into the collection, then update the view (with render())
I understand this is possible with collection.set, or collection.reset. Both of these take in an array of models. I don't have an array of models to pass in.
I tried:
this.collection.fetch({
data: {someParam: someValue},
success: function (response) {
doSomethingWith(response.models)
}
});
But I don't know what to do with the models when I get them.
Any pushed in the right direction would be appreciated!
From the fine manual:
fetch collection.fetch([options])
[...] When the model data returns from the server, it uses set to (intelligently) merge the fetched models, unless you pass {reset: true}, in which case the collection will be (efficiently) reset.
So you just need to include reset: true in the options and fetch will call reset to replace the collection's contents with the fetched models:
this.collection.fetch({
data: { ... },
reset: true
});

Backbone.js - View not reloading

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.

The best way to fetch and render a collection for a given object_id

I am trying to implement a simple app which is able to get a collection for a given object_id.
The GET response from the server looks like this:
[
{object_id: 1, text: "msg1"},
{object_id: 1, text: "msg2"},
{object_id: 1, text: "msg3"},
.......
]
My goal is:
render a collection when the user choose an object_id.
The starting point of my code is the following:
this.options = {object_id: 1};
myView = new NeView(_.extend( {el:this.$("#myView")} , this.options));
My question is:
* What is the best way:
1) to set the object_id value in the MyModel in order to
2) trigger the fetch in MyCollection and then
3) trigger the render function in myView?* or to active my goal?
P.S:
My basic code looks like this:
// My View
define([
"js/collections/myCollection",
"js/models/myFeed"
], function (myCollection, MyModel) {
var MyView = Backbone.View.extend({
initialize: function () {
var myModel = new MyModel();
_.bindAll(this, "render");
myModel.set({
object_id: this.options.object_id
}); // here I get an error: Uncaught TypeError: Object function (){a.apply(this,arguments)} has no method 'set'
}
});
return MyView;
});
// MyCollection
define([
"js/models/myModel"
], function (MyModel) {
var MyCollection = Backbone.Collection.extend({
url: function () {
return "http://localhost/movies/" + myModel.get("object_id");
}
});
return new MyCollection
});
//MyModel
define([
], function () {
var MyModel = Backbone.Model.extend({
});
return MyModel
});
There's a few, if not fundamentally things wrong with your basic understanding of Backbone's internals.
First off, define your default model idAttribute, this is what identifies your key you lookup a model with in a collection
//MyModel
define([
], function () {
var MyModel = Backbone.MyModel.extend({
idAttribute: 'object_id'
});
return MyModel
});
in your collection, there is no need to define your URL in the way you defined it, there are two things you need to change, first is to define the default model for your collection and second is to just stick with the base url for your collection
// MyCollection
define([
"js/models/myModel"
], function (MyModel) {
var MyCollection = Backbone.MyCollection.extend({
model: MyModel, // add this
url: function () {
return "http://localhost/movies
}
});
return MyCollection // don't create a new collection, just return the object
});
and then your view could be something along these lines, but is certainly not limited to this way of implementing
// My View
define([
"js/collections/myCollection",
"js/models/myFeed"
], function (MyCollection, MyModel) {
var MyView = Backbone.View.extend({
tagName: 'ul',
initialize: function () {
this.collection = new MyCollection();
this.collection.on('add', this.onAddOne, this);
this.collection.on('reset', this.onAddAll, this);
},
onAddAll: function (collection, options)
{
collection.each(function (model, index) {
that.onAddOne(model, collection);
});
},
onAddOne: function (model, collection, options)
{
// render out an individual model here, either using another Backbone view or plain text
this.$el.append('<li>' + model.get('text') + '</li>');
}
});
return MyView;
});
Take it easy and go step by step
I would strongly recommend taking a closer look at the exhaustive list of tutorials on the Backbone.js github wiki: https://github.com/documentcloud/backbone/wiki/Tutorials%2C-blog-posts-and-example-sites ... try to understand the basics of Backbone before adding the additional complexity of require.js

Categories