Backbone.js: Collection.get() issues - backbone.js

Using Backbone.js, trying to do a simple get() on a collection after fetching items from server.
Fetch seems to work fine. However, when I try to get an item by id from the collection, I'm getting the unexpected result of undefined, instead of a model.
Here's my backbone items code:
Model.Scenario = Backbone.Model.extend({
defaults: function() {
return {
title: 'Scenario',
answer: null,
};
}
});
Collection.Scenarios = Backbone.Collection.extend({
model: Model.Scenario,
url: "data.php",
});
Then the obj creation/fetch w/in a router method:
var Router = new (Backbone.Router.extend({
routes: {
":id" : "page",
"" : "page"
},
initialize: function() {
_.bindAll(this, 'page');
this.scenarios = new Collection.Scenarios();
this.scenarios.fetch();
console.log(this.scenarios);
console.log(this.scenarios.get(1));
console.log(this.scenarios.get(2));
Backbone.history.start();
},
}))();
Finally, here's what's logged to the debugger (so you can see the Collection is being populated):
child
_byCid: Object
_byId: Object
1: child
2: child
3: child
4: child
7: child
10: child
11: child
12: child
13: child
14: child
15: child
16: child
17: child
18: child
19: child
20: child
21: child
22: child
23: child
26: child
27: child
28: child
__proto__: Object
length: 22
models: Array[22]
__proto__: ctor
undefined //first call to get(id)
undefined //2nd call to get(id)
I'm stumped. Sorry for the long post, but would appreciate any insight on what I'm missing. Thanks.

My first thought is that the fetch() has not returned by the time you're doing your .get().
But, if what you posted is exactly what is happening, then the first console.log(this.scenarios) is showing that is IS loaded.
At any rate, you may want to try putting the console.logs into the success: event of the fetch, just to make sure that is not the issue.
My second thought is that the id for the models is not being set right.
Backbone.Model defaults to using the property "id" as the ID for a model, Collection.get(id) returns the model with the id you passed in.
So, does the response from data.php have an id property?
Try instead:
var Router = new (Backbone.Router.extend({
routes: {
":id" : "page",
"" : "page"
},
initialize: function() {
_.bindAll(this, 'page');
this.scenarios = new Collection.Scenarios();
this.scenarios.fetch({
success: function(coll, resp) {
console.log(coll);
console.log(coll.first());
console.log(coll.last());
});
Backbone.history.start();
},
}))();
Which should return the first and last model, and take the ID issue out of the picture.
If that works, what do the models show for the id property (coll.first().id)?

Related

Adding a model to a parent view from a child view

I'm trying to add a new model to my parent view from a child view.
In my parent view, I render the child view like this:
renderEngineOptions: function () {
var view = new EngineOptionsView({ model: this.model });
this.$('.engineOptions').append(view.render().el);
},
In my child view(engineOptionsView), I define the parent as 'this.engine' in the initialize method:
initialize: function (args) {
this.engine = args.model;
}
Then I have an html button, that when clicked, fires this event:
addTurbo: function() {
this.$('.turboOption').show();
this.engine.turbo = new Turbo();
}
Turbo is a new model that I'm trying to add to the engine. But nothing happens. I get no error in the console, but when I write out the engine model in the console, no turbo was ever added.
How can I add the turbo model to the parent, from the child?
Thanks!
When you pass a model with the model option, the view automatically apply it to itself, so it's available through this.model directly.
var view = new EngineOptionsView({ model: this.model });
in the child:
initialize: function (args) {
this.engine = args.model; // not necessary
console.log(this.model === this.engine); // same thing
}
You are adding the turbo as a property of the model
this.engine.turbo = new Turbo();
So if you want to get the turbo from the parent:
this.model.turbo;
If you want to add the turbo as an attribute of the model from the child view:
this.model.set('turbo', new Turbo());
And to get it from the parent:
this.model.get('turbo);
Without more details, it's hard to guess where the problem is coming from.

.push not working for angularjs and pubnub

I am working on pubnub chat. And trying to push new message to the array of messages.
I have an array of message objects like
Object {0: Object, 1: Object, 2: Object, 3: Object, 4: Object, 5: Object, 6: Object, 7: Object, 8: Object, 9: Object, 10: Object, 11: Object, 12: Object, 13: Object, 14: Object, 15: Object, 16: Object, 17: Object, 18: Object, 19: Object}
Each object contains data like
Object {content: "asd", date: "2016-08-29T05:10:41.208Z", sender_username: "siehyvar", sender_uuid: "1294553"}
now I am trying to push another object
Object {content: "hi", sender_uuid: "1294553", sender_username: "siehyvar", date: "2016-08-29T05:47:40.232Z"}
with the following code
$scope.messages: [];
scope.$on(API.getMessageEventNameFor(), function(ngEvent, m) {
scope.$apply(function() {
$scope.messages.push(m);
});
});
And the error I am getting is
messages.push is not a function
I suppose messages here is an array of objects but the .push function is not working. Can somebody please help?
Let me explain the code. So I have one chat Api where I am putting all pubnub code, two controllers(for online users and chat) and two twig templates(for online users and chat)
Messages declaration for current channel ChatApi :
current_channel: {
channel_name: null,
channel_label: null,
messages: []
},
Join chat event from chatApi
joinChat: function (chat_channel,scope) {
var channel = chat_channel.channel;
var channel_name = chat_channel.name;
//Join chatroom Channel with the room name
API.chatrooms.addMe(channel);
//Get online users in chatroom
API.chatrooms.onlineMembers(channel, scope);
//API.current_channel.channel_id = channel;
API.current_channel.channel_name = channel;
API.current_channel.channel_label = channel_name;
console.log(API.current_channel.channel_name);
API.getPresenceEventNameFor();
// Listening to the callbacks for getting messages for current channel
scope.$on(API.getMessageEventNameFor(), function(ngEvent, m) {
scope.$apply(function() {
if (angular.isArray(scope.current_channel.messages)) {
scope.current_channel.messages.push(m);
}else {
console.log("Not is array");
}
});
scroller.down(500);
});
API.getHistory(channel, scope);
Twig for joining chat with user
<ul class="all-chats-list" ng-repeat="onlineUser in online.users">
<li class="col-xs-12 nopadding" ng-click="joinChat(onlineUser.chat_channel); selectMe($event);">
and messages are being displayed as
<li ng-repeat="message in current_channel.messages">
Controller for joining chat
$scope.joinChat = function(chat_channel) {
chatApi.joinChat(chat_channel, $scope);
};
Now when I am calling send message as
Twig:
<form ng-submit="sendMessage()">
<div class="col-xs-9 col-md-10 form-group nopadding">
<input style="width: 100%" ng-model="message.content" class="form-control" type="text" placeholder="Type your message" />
</div>
<div class="col-xs-3 col-md-2 form-group nopadding">
<button type="submit" class="form-control btn site-btn" id="send-message">Send</button>
</div>
</form>
chatApi:
sendMessage: function() {
console.log('PUBLISH TO: ' + API.current_channel.channel_name);
// Don't send an empty message
if (!API.message.content ||
API.message.content === '') {
return;
}
Pubnub.publish({
channel: API.current_channel.channel_name,
message: {
content: API.message.content,
sender_uuid: models.user.uuid,
sender_username: models.user.username,
date: new Date()
},
callback: function(m) {
//console.log(m)
}
});
// Reset the messageContent input
API.message.content = '';
},
Chat controller:
$scope.sendMessage = chatApi.sendMessage;
I am getting the error messages.push is not a function.
I guess it is because it is treating it as an object and not an array.
Hope it is clear now.
Adding to what others have mentioned,
The obvious issue is here:
$scope.messages: [];
am getting the error messages.push is not a function.
Just replace the declaration of $scope.messages with:
$scope.messages = [];
Now you will be able to call push method on $scope.messages array.
In your example $scope.messages was of type object (it should've been of type array).
You can read more about what methods are available to array and object types in javascript here.
The reason why you can't call push on an object an JS is
In case you want to take the tacky road and NOT define an array, then be my guest and create a prototype method push for object types. That way you can call push on objects. That'll be really silly though and you'd run in loads of issues like when trying to pop() or unshift() elements in your object (that you want to treat like an array).
Replacing the line
$scope.messages: [];
by
$scope.messages = [];
may fix your issue.

Maintain Backbone Collection References

I have been running into the problem of stale collection references. So, I have the following model:
ProcessModel = Backbone.Model.extend({
initialize: function() {
this.set('steps', new StepsCollection());
}
...
});
When the ProcessModel is fetched from the server, the StepsCollection is returned as well. Previously, I had the following parse method:
parse: function(response) {
...
response.steps = new StepsCollection(response.steps, {parse: true});
}
...however, this was creating a brand new collection object, rather than reusing the existing one. This was causing a view which was bound to the previous "steps" collection to become stale.
I've tried the following:
response.steps = this.get('steps').reset(response.steps);
But I get a long stacktrace in Object.Marionette.bindEntityEvents. What am I doing wrong?
Try this. This will create single collection and then we'll reset same collection instance with new data-set inside parse method.
ProcessModel = Backbone.Model.extend({
initialize: function() {
this.myCollection = new StepsCollection();
this.set('steps', this.myCollection);
...
},
parse: function(response) {
this.myCollection.reset(response.steps);
this.set('steps', this.myCollection);
...
}
});

How to call fetch method of Backbone Collection passing Id

I want to fire fetch method on Backbone Collection which would pass an Id parameter similar to what happens in Model.fetch(id)
E.g.
var someFoo= new Foo({id: '1234'});// Where Foo is a Backbone Model
someFoo.fetch();
My Backbone collection:-
var tasks = backbone.Collection.extend({
model: taskModel,
url: '/MyController/GetTasks',
initialize: function () {
return this;
}
});
In my View when I try to fetch data:-
var _dummyId = 10; //
// Tried approach 1 && It calls an api without any `id` parameter, so I get 500 (Internal Server Error).
this.collection.fetch(_dummyId);
// Tried approach 2 && which fires API call passing Id, but just after that
// I am getting error as below:- Uncaught TypeError: object is not a function
this.collection.fetch({
data: {
id: _dummyId
}
});
Found it very late : To cut short the above story I want something like Get /collection/id in backbone.
Thank you for your answers, finally I got the solution from Backbone.js collection options.
Apologies that I couldn't explain the question properly while for same requirement others have done brilliantly and smartly.
Solution : I can have something like :-
var Messages = Backbone.Collection.extend({
initialize: function(models, options) {
this.id = options.id;
},
url: function() {
return '/messages/' + this.id;
},
model: Message,
});
var collection = new Messages([], { id: 2 });
collection.fetch();
Thanks to nrabinowitz. Link to the Answer
As mentioned by Matt Ball, the question doesn't make sense: either you call fetch() on a Collection to retrieve all the Models from the Server, or you call fetch() on a Model with an ID to retrieve only this one.
Now, if for some reason you'd need to pass extra parameters to a Collection.fetch() (such as paging information), you could always add a 'data' key in your options object, and it may happen that one of this key be an id (+add option to add this fetched model rather than replace the collection with just one model)... but that would be a very round-about way of fetching a model. The expected way is to create a new Model with the id and fetch it:
this.collection = new taskCollection();
newTask = this.collection.add({id: 15002});
newTask.fetch();
In your code however, I don't see where the ID is coming from, so I am wondering what did you expect to be in the 'ID' parameter that you wanted the collection.fetch() to send?

Backbone.js error - Uncaught TypeError: Object [object Object] has no method 'set'

My Code:
I am new to Backbone.js and trying to build an app with Backbone.js and PHP. When I am trying to call add in the router, I am getting error:
Uncaught TypeError: Object [object Object] has no method 'set'.
Please help me to find my mistake.
Thanks.
// Models
window.Users = Backbone.Model.extend({
urlRoot:"./bb-api/users",
defaults:{
"id":null,
"name":"",
"email":"",
"designation":""
}
});
window.UsersCollection = Backbone.Collection.extend({
model:Users,
url:"./bb-api/users"
});
// Views
window.AddUserView = Backbone.View.extend({
template:_.template($('#new-user-tpl').html()),
initialize:function(){
this.model.bind("click", this.render, this);
},
render:function(){
$(this.el).html(this.template(this.model.toJSON()));
return this;
},
events:{
"click .add":"saveUser"
},
saveUser:function(){ alert('saveUser');
this.model.set({
name:$("#name").val(),
email:$("#email").val(),
designation:$("#designation").val()
});
if(this.model.isNew()){
this.model.create(this.model);
}
return false;
}
});
// Router
var AppRouter = Backbone.Router.extend({
routes:{
"":"welcome",
"users":"list",
"users/:id":"userDetails",
"add":"addUser"
},
addUser:function(){
this.addUserModel = new UsersCollection();
this.addUserView = new AddUserView({model:this.addUserModel});
$('#content').html(this.addUserView.render().el);
}
});
var app = new AppRouter();
Backbone.history.start();
As suggested in the comments, the problem starts here here:
this.addUserModel = new UsersCollection();
this.addUserView = new AddUserView({model:this.addUserModel});
and finishes here:
saveUser:function(){ alert('saveUser');
this.model.set({
By passing a collection in place of a model you create confusion, and as a result later in the saveUser function you try to call a Backbone.Model method (set) on a Backbone.Collection instance.
Note: As of version 1.0.0 Backbone.Collection now has a set method. In previous versions, such as the one used by the question's author, that method was instead called update.
There are several steps you can take to clarify this code. For starters, I would rename your model and collection classes so that it's clear that the model is the singular form and the collection is the plural form:
window.Users => window.User
window.UsersCollection => window.Users
Next, I would create a new User model, instead of a Users collection, and pass that to your view:
this.addUserModel = new User();
this.addUserView = new AddUserView({model:this.addUserModel});
Finally, I'd remove these lines:
if(this.model.isNew()){
this.model.create(this.model);
}
For one thing, the model will always be new (as you just created it before passing it in), but more importantly you don't need to call the Collection's create method because that method creates a new model, when you already have one created. Perhaps what you should add instead is :
this.model.save();
if your intent is to save the model to your server.
Since you already specified a urlRoot for the model, that should be all you need to create a new model, pass it to your view, have your view fill in its attributes based on DOM elements, and finally save that model's attributes to your server.
I think you are facing problem with object scope. When event fired it send to event object to that function. Just try this it may work
Declare global variable with the current view inside the initialize
initialize : function(){ self = this; }
then change this to self,
saveUser:function(){ alert('saveUser');
self.model.set({
name:$("#name").val(),
email:$("#email").val(),
designation:$("#designation").val()
});
if(self.model.isNew()){
self.model.create(this.model);
}
return false;
}

Resources