I'm trying to add a list of models to a collection to be stored locally. I don't fully understand backbone yet which is really the cause of this problem.
I basically pull in an RSS feed, assign each item in the feed to a Model and try place the list of Models into a collection so I can iterate over them later.
I am getting an error saying that I need to specify a Url for the collection.
It would be brilliant if someone could explain to me the correct process I need to follow to achieve my goal.
Currently I have:
var DetailIndividual = Backbone.Model.extend();
var DetailsIndividual = Backbone.Collection.extend({
model: DetailIndividual
});
var Search = Backbone.View.extend({
events: {
'click a.individualCast' : 'pullIndividual'
},
initialize: function() {
this.detailsIndividual = new DetailsIndividual();
_this = this;
this.detailsIndividual.bind('reset', function(collection) {
collection.each(function(item) {
//code to handle update
});
});
},
pullIndividual: function(e){
e.preventDefault();
//Logic to pull in RSS feed
for (var i = 0; i < result.feed.entries.length; i++) {
entry[i] = new DetailIndividual({ title: result.feed.entries[i].title, link: result.feed.entries[i].link, });
}
this.detailsIndividual.add(entry);
}
});
The error is reported out from here,because model must have url attribute:
http://backbonejs.org/docs/backbone.html#section-167
do you model have url attribute?
The reason why you're getting the error is because you're binding the 'reset' event. 'reset' is only fired on a collection.fetch or an explicit call to collection.reset, and in your case you're never fetching from the server with your collection - I'm assuming from your code you already have the feed in memory - so unless you're explicitly resetting, there's no need to listen for the reset.
In your code, you're not really extending Collection and Model, so it's actually not necessary to make extended objects - just use Backbone.Collection. You don't even need to create a Model extension because by default, when you add a JSON, a Backbone.Model is automatically created. It's only necessary to assign the collection.model if you're creating a truly custom model (with method overrides and additions).
Here's a way you could load your collection:
var search = Backbone.View.extend({
events: {
'click a.individualCast' : 'pullIndividual'
},
initialize: function() {
this.detailsIndividual = new Backbone.Collection();
},
pullIndividual: function(e) {
e.preventDefault();
//Logic to pull in RSS feed
for (var i = 0; i < result.feed.entries.length; i++) {
this.detailsIndividual.add({
title: result.feed.entries[i].title,
link: result.feed.entries[i].link
});
}
}
});
You didn't provide any code of how you wanted to parse collection (except in the 'reset'), but essentially you'd load the collection from the feed as shown.
Related
i am facing problem in backbone collection.
this is my router
var LanguageRouter = Backbone.Router.extend({
routes: {
'': 'defaultaction',
'section/:key': 'sectionview',
}
});
collection is
var LanguageCollection = Backbone.Collection.extend({
model: LanguageModel,
url: '/lang'
});
and app.js
var initialize = function () {
var language_router = new LanguageRouter(),
parent_view = new ParentView(),
list_collection = new LanguageCollection(),
list_collection.fetch();
language_router.on('route:defaultaction', function () {
list_view = new LanguageListView({
collection: list_collection,
template: _.template(templates.languagelistsingle)
});
});
Here , after fetching the list_collection i tried passed the collection to language_view but i am getting empty collection only. How to fix this... Thanks in advance
Fetch is not synchronous, so you cannot call it in one line and use the return on the next line. The correct way using backbone is use listenTo or On (depending on the backbone version)
You could change this part:
list_collection = new LanguageCollection();
this.listenTo(list_collection, "sync", this.someFunction());
\\here we are listening the sync event that is automatically fired by backbone when a model or collection is synced with the server.
list_collection.fetch();
someFunction:function(){
//Logic with the collection content...
}
To learn more about events, please take a read here: http://backbonejs.org/#Events-catalog
I am using backbone.js I need a very simple way to render a local json file into the users local storage only one time. I am building a cordova app and I just want to work with local storage data.
I have hard coded a decent size .json file (list of players) into my collection, and I just want to load the .json file into the local storage if local storage on that device is empty which will only be once, upon initialization of the app.
I could use ajax, but I don't know how to write it to only inject data one time as "starter" data. So if you know how to do this I can upload the json file to my server and somehow fetch it.
I can inject the data if I go through a series of tasks, I have to disable the fetch method and render this code below in an each statement, plus the json has to be hardcoded into the collection, with a certain format.
playersCollection.create({
name: player.get('name'),
team: player.get('team'),
team_id: player.get('team_id'),
number: player.get('number'),
points: player.get('points')
})
I am trying to finish this lol I need to use it tonight to keep stats, I am almost there the structure works, when data is loaded I can add stats etc, but I need to get that data loaded, I pray someone can help!
Edit: I was able to put together some sloppy code last minuet that at least worked, thanks to #VLS I will have a much better solution, but Ill post the bad code I used.
// I fire renderData method on click
events: {
'click .renderData':'renderData'
},
// Inside my render method I check if "players-backbone" is in local storage
render: function() {
var self = this;
if (localStorage.getItem("players-backbone") === null) {
alert('yup null');
//playersCollection.fetch();
this.$el.append('<button class="renderData">Dont click</button>')
} else {
alert('isnt null');
this.$el.find('.renderData').remove();
playersCollection.fetch();
}
this.teams.each(function(team) {
var teamView = new TeamView({ model: team });
var teamHtml = teamView.render().el;
console.log($(''))
var teamPlayers = this.players.where({team_id: team.get('id')})
_.each(teamPlayers, function(player) {
var playerView = new PlayerView({ model: player });
var playerHtml = playerView.render().el;
$(teamHtml).append(playerHtml);
}, this);
this.$el.append(teamHtml);
}, this);
return this;
},
// method that populates local storage and fires when you click a button with the class .renderData
renderData: function() {
var self = this;
this.teams.each(function(team) {
var teamPlayers = this.players.where({team_id: team.get('id')})
_.each(teamPlayers, function(player) {
playersCollection.create({
name: player.get('name'),
team: player.get('team'),
team_id: player.get('team_id'),
number: player.get('number'),
points: player.get('points')
})
}, this);
}, this);
playersCollection.fetch();
return this;
}
This is obviously not the best way to go about it, but it worked and I was in such a hurry. The caveats are you have to click a button that populates the data, the collection is hard coded in, it's just overall not very elegant (but it works) the app did what it needed.
So big thanks to #VLS, I appreciate the effort to explain your code, and create a fiddle. Sorry I was so late!
You can extend your collection's fetch method and use it in conjunction with Backbone.localStorage, so inside your collection you'd have something like:
localStorage: new Backbone.LocalStorage("TestCollection"),
fetch: function(options) {
// check if localStorage for this collection exists
if(!localStorage.getItem("TestCollection")) {
var self = this;
// fetch from server once
$.ajax({
url: 'collection.json'
}).done(function(response) {
$.each(response.items, function(i, item) {
self.create(item); // saves model to local storage
});
});
} else {
// call original fetch method
return Backbone.Collection.prototype.fetch.call(this, options);
}
}
Demo: http://jsfiddle.net/5nz8p/
More on Backbone.localStorage: https://github.com/jeromegn/Backbone.localStorage
I just recently started using Backbone.js and I'm working on an app now using Brunch that does a JSONP request to an external API to populate my collection and models. I'm following these previous posts (this and this) on doing JSONP requests with Backbone, but my collection still isn't getting the data for some reason.
My model (app/models/model.js):
module.exports = Backbone.Model.extend({
});
My collection (app/models/collection.js):
var Post = require('./model');
module.exports = Backbone.Collection.extend({
model: Post,
url: "http://somedata.com/api/posts/list/stuff",
sync: function(method, model, options) {
options.timeout = 10000;
options.dataType = "jsonp";
options.jsonp = "JSONPcallback";
return Backbone.sync(method, model, options);
},
parse: function(response) {
if (response) {
var parsed = [];
for(var i = 0; i < response.results.length; i++) {
parsed.push(response.results[i][0]);
}
return parsed;
}
}
});
Then, in the initialize method in app/application.js I'm calling it by:
var Category = require('models/collection');
this.cat = new Category();
this.cat.fetch();
Now, when I look at the parse function in console.log, I see the data being fetched, so the request is going through successfully. However, when my views are rendered and I do console.log(application.cat.models) in app/views/view.js, I get nothing -- why's this happening? Is there anything wrong with the code on my model/collection?
Also, the JSONP data has the following format, which is why looping through for response.results[i][0] and returning an array with all of it, that should do the trick, right?
{"results":[
{"0":{"id":xxx,"title":xxx,"link":xxx},
"description":xxx},
{"0":{"id":xxx,"title":xxx,"link":xxx},
"description":xxx},
{"0":{"id":xxx,"title":xxx,"link":xxx},
"description":xxx},...
]}
Would really appreciate any help...
I have 2 comments here :
I see that you have names both your model and collection as module.exports , a common practice is to make the model as singular (module.export) and make the collection for those models plural module.exports , just common practice , nothing "wrong" otherwise
You can have 2 callbacks in your code , when the collection is done fetching data(asynchronous event) also considering module.exports as your collection here ,
A. You could do this :
module.exports.fetch({
success : function(data){
console.log(JSON.stringiy(data));
//do remaining programming here
}
});
B. you could have a event listener for reset , from the documentation here , the collection fires a reset event when it completes the fetch , so could add an event listener on the collection like this :
module.exports.on('reset',function(data){
console.log(JSON.stringify(data));
//do remaining programming here
},this);
Below is my backbone view.
define([
'app',
'backbone',
'twig',
'templates/report',
'data/reportViewCollection',
'data/reportViewModel'
], function (app, Backbone, Twig, template, Collection, Model) {
var collection = new Collection();
var fetching;
return Backbone.View.extend({
setParams: function (rlId, viewId, categoryId, depth) {
// reset any possible pending previous repests.
fetching = false;
var model = collection.getModel(rlId, viewId, categoryId, depth);
if (!model) {
model = new Model({
rlId: rlId,
viewId: viewId,
categoryId: categoryId,
depth: depth
});
fetching = model.fetch({
success: function (model) {
collection.add(model);
},
error: function (model) {
alert('error getting report view');
}
});
}
this.model = model;
},
render: function () {
var that = this;
var done = function() {
app.vent.trigger('domchange:title', that.model.get('title'));
that.$el.html(Twig.render(template, that.model.toJSON()));
that.delegateEvents(that.events);
fetching = false;
};
if (fetching) {
app.loading(this);
fetching.done(done);
} else {
done();
}
return this;
},
events: {
'change select.view-select': 'viewSelect',
'click #dothing': function (e) {e.preventDefault(); alert('hi');}
},
viewSelect: function(e) {
var selectedView = $(e.target).val();
var rlId = this.model.get('rlId');
if (!rlId) rlId = 0;
var url = 'report/' + rlId + '/' + selectedView;
console.log(this, e, url);
Backbone.history.navigate(url, {trigger: true});
}
});
});
Description of functionality:
What happens is when a specific url is navigated to, the setParams() function is called to fetch the model from the server. When the render method is called, it checks if we are currently fetching the model and if so, uses sets a deferred callback to render the template when it gets done fetching. When the model is fetch-ed and we are ready to render, renders the template and fills in the view by that.$el.html().
Problem:
What happens is that my events work perfectly the first time I navigate to a url, but when I hit the back button, my events don't get attached.
I've stepped through the code and can't see any differences. The only real difference is that I'm loading the model from the cached collection immediately instead of doing an ajax request to fetch it.
Any clues what is going on?
try to change:
that.$el.html(Twig.render(template, that.model.toJSON()));
to
that.$el.html("");
that.$el.append(Twig.render(template, that.model.toJSON()));
had kind the same problem and this fixed it.
I resolved my issue. The comment by #mu set me in the right direction.
I am using Marionette and my view is contained in a region. In the Region's open function, it is doing this.$el.html(view.el); which wipes out the events in certain circumstances. I'm still not sure why it does in some but on in others.
The Solution proved to be to add an onShow function to my view that call's this.delegateEvents(). Everything is working smoothly now!
I eventually figured it out by stepping through the code and watching the events registered on the view's div.
I need to pass an id to a collection for use in the url (e.g. /user/1234/projects.json) but am not sure how to do this, an example would be wonderful.
The way my application is structured is on launch a collection of 'users' is pulled and rendered, I then want when a user is clicked their 'documents' are pulled from the server into a new collection and rendered in a new view. The issue is getting the user id into the documents collection to give the relevant URL for the documents.fetch().
think I've got it, here is an example:
//in the the view initialize function
this.collection = new Docs();
this.collection.project_id = this.options.project_id;
this.collection.fetch();
//in the collection
url: function() {
return '/project/api/' +this.project_id+'/docs';
}
Your user collection url should be set to /user. Once that's set, your models should utilize that url in order to do their magic. I believe (not completely positive) that if a model is in a collection, calling the 'url' method will return /user/:id. So all your typical REST-ish functionality will be utilized on '/user/:id'. If you are trying to do something with a relationship (a user has many documents) it's kind of rinse and repeat. So, for your documents collection (which belogs to user correct?) you'd set the url to 'user_instance.url/documents'.
To show a one to many relationship with a backbone model, you'd do something like this (upgrade to backbone 0.5.1 for urlRoot):
var User = Backbone.Model.extend({
initialize: function() {
// note, you are passing the function url. This is important if you are
// creating a new user that's not been sync'd to the server yet. If you
// did something like: {user_url: this.url()} it wouldn't contain the id
// yet... and any sync through docs would fail... even if you sync'd the
// user model!
this.docs = new Docs([], {user_url: this.url});
},
urlRoot: '/user'
});
var Doc = Backbone.Model.extend();
var Docs = Backbone.Collection.extend({
initialize: function(models, args) {
this.url = function() { args.user_url() + '/documents'; };
}
});
var user = new User([{id: 1234}]);
user.docs.fetch({ success: function() { alert('win') });
Why do you need to override the URL property of the collection with a function?.. you could do:
this.collection = new Docs();
this.collection.project_id = this.options.project_id;
this.collection.url = '/project/api/' + this.options.project_id + '/docs';
this.collection.fetch();
I like the answer from Craig Monson, but to get it working I needed to fix two things:
Binding the User url method before passing it to the Docs
A return statement from the url function in Docs
Updated example:
var User = Backbone.Model.extend({
initialize: function() {
// note, you are passing the function url. This is important if you are
// creating a new user that's not been sync'd to the server yet. If you
// did something like: {user_url: this.url()} it wouldn't contain the id
// yet... and any sync through docs would fail... even if you sync'd the
// user model!
this.docs = new Docs([], { user_url: this.url.bind(this) });
},
urlRoot: '/user'
});
var Doc = Backbone.Model.extend();
var Docs = Backbone.Collection.extend({
initialize: function(models, args) {
this.url = function() { return args.user_url() + '/documents'; };
}
});
var user = new User([{id: 1234}]);
user.docs.fetch({ success: function() { alert('win') });