I have properly coded a simple REST api and several backbone models. My parent model is called Topic and child model called Questions.
I'm trying to call a get method on the REST api and display the received Topic object to the user in a presentable manner. I am receiving the json (can be seen in the network tab on Chrome), but it is not getting sent to the view correctly.
Model:
var Topic = Backbone.Model.extend({
urlRoot: ROOT + '/topic',
idAttribute: 'topicId',
initialize: function () {
this.questions = new Questions([], {parent: this});
},
toJSON: function () {
var json = Backbone.Model.prototype.toJSON.call(this);
json.questions = this.questions.toJSON();
return json;
}
});
var Topics = Backbone.Collection.extend({
model: Topic,
url: ROOT + 'topic',
parse: function (response) {
return response.results;
}
})
REST URL:
http://localhost/Project/index.php/rest/resource/topic/
Backbone View: This is where I think the error is...(console log below prints an empty object)
var TopicListView = Backbone.View.extend({
el: '.page',
render: function () {
var that = this;
var topics = new Topics();
topics.fetch({
success: function (topics) {
console.log(topics);
var template = _.template($('#topic-list-template').html(), {topics: topics.models});
that.$el.html(template);
}
})
}
});
Using the above functions:
var topic = new Topic();
topic.fetch();
topicListView = new TopicListView();
var Router = Backbone.Router.extend({
routes: {
"": "home"
}
});
var router = new Router;
// render topic list for 'home'
router.on('route:home', function () {
topicListView.render();
});
Edit: Solution: Overriding the parse function in the collection proved to be the error. I wonder why...
The argument topics in your success handler is shadowing the variable topics.
The argument contains the parsed JSON response, not the Backbone Collection. You don't need that, so you can remove the argument.
The reference to topics will now be to your Collection, so topics.models will have the value you expect.
topics.fetch({
success: function () { // argument removed here so `topics` is no longer shadowed
var template = _.template($('#topic-list-template').html(), { topics: topics.models });
that.$el.html(template);
}
})
Related
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.
I seem to be fetching correctly from the server using backbone. A GET request is made to a MongDB collection, via the Node.js server code here:
exports.getTeams = function(req,res,next){
var system_db = req.system_db;
var user_id = req.mainUser._id;
var teams = teamModel.getNewTeam(system_db,user_id);
teams.find({}, function (err, items) {
res.json(items);
});
};
I am fetching from Backbone like so:
var teamCollection = new TeamCollection([]);
teamCollection.url = '/api/teams';
teamCollection.fetch(
{success:function(){
console.log('teamCollection length:',teamCollection.length);
console.log('teamCollection[0]:',teamCollection[0]);
}}
);
using this model and collection:
var Team = Backbone.Model.extend({
idAttribute: "_id",
urlRoot: 'http://localhost:3000/api/teams'
});
var TeamCollection = Backbone.Collection.extend({
model: Team,
initialize: function() {
this.bind('add', this.onModelAdded, this);
this.bind('remove', this.onModelRemoved, this);
this.bind("change", this.onModelChanged, this);
},
/* parse: function(data) {
//return JSON.stringify(data).objects;
//return JSON.parse(data).objects;
return data.objects;
},*/
onModelAdded: function(model, collection, options) {
console.log("added, options:", options);
},
onModelRemoved: function (model, collection, options) {
console.log("removed, options:", options);
},
onModelChanged: function (model, collection, options) {
console.log('Collection has changed.');
},
comparator: function (model) {
return model.get("_id");
}
});
the problem is that the logging statements above log the following in the browser console:
It says I am sending 4 items from the server to the Backbone client, but the first one is undefined. How could this be?
A Backbone.Collection is not an array-like object : it has a length attribute representing the number of models but you can't access individual models via indexes, hence
console.log(teamCollection[0]); //undefined
To get a model at a given position, use collection.at .Try
console.log(teamCollection.at(0));
And a demo of those behaviors http://jsfiddle.net/nikoshr/013gjpny/
I am trying to load data from an API into a view. However the data doesn't turn up in my view.
I tried getting the collection information in de router, as well as in the model.
However the date won't even console.log the data. Let alone that I can load the data into the view.
I am using require to load the JavaScript files. Can you have a look and see what I am doing wrong here?
I do see this console.log:
console.log("People Collection is initialized");
And I can also see the page loaded and the json. But not the console.log of the data in the url function... In fact I get this error in the console:
Error: A "url" property or function must be specified
In the Backbone Router:
var OF = OF || {};
OF.AdminRouter = Backbone.Router.extend({
routes: {
"users": "goToUsers",
"users/*other": "goToUsers"
},
goToUsers: function() {
require(['./models/users', './views/users_view', './views/menu_view', './collections/user_collection'], function(UsersMdl, UsersView, MenuView, UsersCollection) {
OF.usersView = new OF.UsersView;
OF.usersView.render();
});
}
});
The Collection:
var OF = OF || {};
OF.UsersCollection = Backbone.Collection.extend({
initialize: function() {
console.log("People Collection is initialized");
},
url: function() {
var that = this;
var sendObj = {
"admin": OF.login.attributes.admin,
"session": OF.login.attributes.session
};
$.ajax({
url: 'php/api/users/',
type: "POST",
dataType: "json",
data: sendObj,
success: function(data) {
console.log(data);
},
error: function(data) {
console.log("ERR: " + data);
}
});
},
model: OF.UsersMdl
});
The Model:
var OF = OF || {};
OF.UsersMdl = Backbone.Model.extend({
default: {
username: '',
homefoldersize: '',
email: ''
},
initialize: function(){
//on change functions can be done here
OF.usersCollection = new OF.UsersCollection();
OF.usersCollection.fetch();
},
result: {
success: false,
message: ''
},
validate: function(att) {
}
});
The View:
var OF = OF || {};
OF.UsersView = Backbone.View.extend({
el: '#content',
remove: function() {
this.$el.empty();
this.stopListening();
return this;
},
initialize: function() {
//set the new address variable.
OF.usersMdl = OF.usersMdl || new OF.UsersMdl();
},
render: function() {
/*
//first check if you are allowed to see this page
if (!OF.login || !OF.login.isValid()) {
OF.router.navigate('login', {trigger: true});
return;
}
*/
//save this in that
var that = this;
//when importing of login page (and fill it with info) is done
$.when(OF.template.get('users-usersField', function(data) {
var htmlSource = $(data).html();
var template = Handlebars.compile(htmlSource);
var compiled = template(OF.usersMdl.attributes);
//now place the page
that.$el.html(compiled);
//then start the menu
})).then(function(){
setTimeout(function(){
OF.menuView = new OF.MenuView;
OF.menuView.render();
}, 100);
});
$('#logout').show();
}
});
Thanks.
It seems to call the initialize of the collection twice and then continues to call the json function.
In your model's initialization you have
OF.usersCollection = new OF.UsersCollection();
OF.usersCollection.fetch();
But when you fetch your collection, it's going to initialize models for every result it gets back ... which will then trigger fresh collection fetches.
You don't need to create collections for your models inside your models, especially if the model is being created by the collection. Whenever you add a model to a collection (including when the collection creates the model after a fetch) the collection will associate itself with the model.
The general order of things should be:
You define a url function on your collection which returns the URL where you can get the (raw JSON) models of that collection.
You instantiate that collection, and then call fetch on the instance
The collection makes an AJAX call (which you can affect by overriding fetch or sync) and gets back the raw JSON for all of the models.
The collection instantiates new models for each result it gets back; those models are automatically added to the collection, and their .collection is automatically set to the collection.
Once OF.usersCollection.fetch().done(function() {... you can have your views start doing things, as your collection should now be all set.
I am trying to learn backbone and I was following along the code school backbone.js course to build my own backbone app. So far I have this code but I am having problems with rendering anything.
var PostsApp = new (Backbone.View.extend({
Collections: {},
Models: {},
Views: {},
start: function(bootstrap){
var posts = new PostsApp.Collections.Posts(bootstrap.posts);
var postsView = new PostsApp.Views.Posts({collection: posts});
this.$el.append(postsView.render().el);
}
}))({el : document.body});
PostsApp.Models.Post = Backbone.Model.extend({});
PostsApp.Collections.Posts = Backbone.Collection.extend({});
PostsApp.Views.Post = Backbone.View.extend({
template: _.template("<%= name %>"),
render: function(){
this.$el.html(this.template(this.model.toJSON()));
}
});
PostsApp.Views.Posts = Backbone.View.extend({
render: function(){
this.collection.forEach(this.addOne, this);
},
addOne: function(post){
var postView = new PostsApp.Views.Post({model:post});
this.$el.append(postView.render().el);
}
});
var bootstrap = {
posts: [
{name:"gorkem"},
{name: "janish"}
]
}
$(function(){
PostsApp.start(bootstrap);
});
I am just trying to create a very simple backbone app, CodeSchool is great but it not good at combining the pieces together and when I try to do that myself I am having problems.
So far the error I am getting is "Uncaught TypeError: Cannot read property 'el' of undefined" in the addOne function of the Posts View. Any help would be much appreciated.
edit: The answer below solved my initial problem, but I also set up an express server to send data to the front end with the code :
app.get('/tweet', function(req,res){
res.send([{ name: 'random_name' }, {name: 'diren_gezi'}] );
});
and then I am trying to fetch this data to my collection like this :
var PostsApp = new (Backbone.View.extend({
Collections: {},
Models: {},
Views: {},
start: function(bootstrap){
var posts = new PostsApp.Collections.Posts(bootstrap.posts);
posts.url = '/tweet';
posts.fetch();
var postsView = new PostsApp.Views.Posts({collection: posts});
postsView.render();
this.$el.append(postsView.el);
}
}))({el : document.body});
But in the page the initial data (name: gorkem and name: janish) is displayed instead of the recently fetched data..
This is the problem line (I see it in a few spots).
this.$el.append(postsView.render().el);
Try changing it to
postsView.render();
this.$el.append(postsView.el);
Render function doesn't return a function to self (the object with a reference to el).
When the first page of my Backbone app load I fetch a collection then iterate over it to render the page:
Page Router:
home: function ()
{
new BodyView ({page:'home'});
new HomeView();
new PostView();
postCollection = new PostCol({userId:getId(),start:0,offset:50});
postCollection.fetch();
postView = new Post({collection:postCollection});
},
Post View:
el:'.stream',
initialize: function ()
{
this.collection.on('reset',this.render.bind(this));
this.collection.on ('change',this.render.bind (this));
},
render: function ()
{
this.collection.each (function (model)
{
actor = new Actor ({model:model});
this.$el.append (actor.render().el);
},this);
return this;
},
What I am trying to accomplish now is that when a user save some data in another view it update the Post view. This is what I did but its not working.
Other View:
post = new PostModel ({userid:getId(),post:postText,objectid:0});
post.save({},
{
success: function (response)
{
postCol = new PostCol ({userId:getId(),start:0,offset:50});
postView = new Post ({collection:postCol});
postCol.fetch ().success({change:true});
},
error: function (response)
{
console.log (response);
}
}
);
It looks like you postCollection is global, so you could update the existing model instead of creating a new one.
// find existing model in the collection.
var post = postCollection.get(getId());
// save model with updated info.
post.save({post:postText},
{
success: function (response)
{
// if the save succeeded, the Post view will re-render,
// since you are listening for the 'change' event.
// so here, you don't really need to do anything.
},
error: function (response)
{
console.log (response);
}
);
Instead of this.collection.on ('change',this.render.bind (this)); in the Post view, you could do this in the individual Actor view, so the whole collection does not re-render.
this.model.on ('change',this.render, this); // 'this' as third parameter replaces the `bind`