Add to backbone collection in response to event - backbone.js

I'm getting started with backbone.js but have got stuck at this point: I want to get data from a websocket and add it to a collection.
I've started with the code from http://adrianmejia.com/blog/2012/09/11/backbone-dot-js-for-absolute-beginners-getting-started/ but when I try to add in the event handling I cant figure out how to access the collection from within the event.
var AppView = Backbone.View.extend({
// el - stands for element. Every view has a element associate in with HTML content will be rendered.
el: '#container',
template: _.template("<h3>Hello <%= who %></h3>"),
// It's the first function called when this view it's instantiated.
initialize: function(){
console.log(this.collection) //<-- this.collection is OK here
MyPubSub.on('message', function (evt) {
this.collection.add(evt.data) //<-- TypeError: this.collection is undefined
});
this.render();
},
// $el - it's a cached jQuery object (el), in which you can use jQuery functions to push content. Like the Hello World in this case.
render: function(){
this.$el.html(this.template({who: 'planet!'}));
}
});
function init()
{
var websocket = new WebSocket("ws://example.com:8088");
MyPubSub = $.extend({}, Backbone.Events);
websocket.onmessage = function(evt) {
console.log("message")
MyPubSub.trigger('message',evt)
};
var appView = new AppView({collection:new AlertCollection()});
}
window.addEventListener("load", init, false);
I'm a complete newbie at this so I suspect I've made some kind of basic error in scoping. Also open to other approaches to read a websocket steam into a backbone app.

In the initialize function, add var myCollection = this.collection; and use myCollection within the MyPubSub.on(....

Related

Bug while creating object in View

I'm working on a backbone.js project which is mainly to learn backbone framework itself.
However I'm stuck at this problem which i can't figure out but might have an idea about the problem...
I've got an Create View looking like this...
define(['backbone', 'underscore', 'jade!templates/addAccount', 'models/accountmodel', 'common/serializeObject'],
function(Backbone, underscore, template, AccountModel, SerializeObject){
return Backbone.View.extend({
//Templates
template: template,
//Constructor
initialize: function(){
this.accCollection = this.options.accCollection;
},
//Events
events: {
'submit .add-account-form': 'saveAccount'
},
//Event functions
saveAccount: function(ev){
ev.preventDefault();
//Using common/serializeObject function to get a JSON data object from form
var myObj = $(ev.currentTarget).serializeObject();
console.log("Saving!");
this.accCollection.create(new AccountModel(myObj), {
success: function(){
myObj = null;
this.close();
Backbone.history.navigate('accounts', {trigger:true});
},
error: function(){
//show 500?
}
});
},
//Display functions
render: function(){
$('.currentPage').html("<h3>Accounts <span class='glyphicon glyphicon-chevron-right'> </span> New Account</h3>");
//Render it in jade template
this.$el.html(this.template());
return this;
}
});
});
The problem is that for every single time I visit the create page and go to another and visit it again. It remebers it, it seems. And when i finally create a new account I get that many times I've visited total number of accounts...
So console.log("Saving!"); in saveAccount function is called x times visited page...
Do I have to close/delete current view when leaving it or what is this?
EDIT
Here's a part of the route where i init my view..
"account/new" : function(){
var accCollection = new AccountCollection();
this.nav(new CreateAccountView({el:'.content', accCollection:accCollection}));
console.log("new account");
},
/Regards
You have zombie views. Every time you do this:
new CreateAccountView({el:'.content', accCollection:accCollection})
you're attaching an event listener to .content but nothing seems to be detaching it. The usual approach is to call remove on a view to remove it from the DOM and tell it to clean up after itself. The default remove does things you don't want it to:
remove: function() {
this.$el.remove();
this.stopListening();
return this;
}
You don't want that this.$el.remove() call since your view is not responsible for creating its own el, you probably want:
remove: function() {
this.$el.empty(); // Remove the content we added.
this.undelegateEvents(); // Unbind your event handler.
this.stopListening();
return this;
}
Then your router can keep track of the currently open view and remove it before throwing up another one with things like this:
if(this.currentView)
this.currentView.remove();
this.currentView = new CreateAccountView({ ... });
this.nav(this.currentView);
While I'm here, your code will break as soon as you upgrade your Backbone. As of version 1.1:
Backbone Views no longer automatically attach options passed to the constructor as this.options, but you can do it yourself if you prefer.
So your initialize:
initialize: function(){
this.accCollection = this.options.accCollection;
},
won't work in 1.1+. However, some options are automatically copied:
constructor / initialize new View([options])
There are several special options that, if passed, will be attached directly to the view: model, collection, el, id, className, tagName, attributes and events.
so you could toss out your initialize, refer to this.collection instead of this.accCollection inside the view, and instantiate the view using:
new CreateAccountView({el: '.content', collection: accCollection})

Type Error on Backbone/Marionette Single Model Fetch

I am getting used to using Backbone and Marionette and run into a little snag that I am sure I am overlooking something. I am trying to populate my ItemView with a model from my API and I can see the request and data coming back ok but I get a Type Error:obj is undefined in what appears to be my listener:
TypeError: obj is undefined
var id = obj._listenerId || (obj._listenerId = _.uniqueId('l'));
Here is my Model/View
var MyDetailView = Marionette.ItemView.extend({
template: '#my-item-detail',
initialize: function () {
_.bindAll(this, 'render');
// bind the model change to re-render this view
this.listenTo(this.model, 'change', this.render);
},
tagName: "div"
})
var MyModel= Backbone.Model.extend({ urlRoot: '/api/model', intialize: function () { } });
And my code to execute:
var m = new MyModel({ id: 123});
m.fetch({
success: function (model, response) {
var view = new MyDetailView (model);
layout.content.show(view);
}
});
You'll need to pass the model in as an options hash and not just the first parameter to MyDetailView like so:
var view = new MyDetailView({ model: model });
Also for future reference Marionette does _.bindAll with render in the Marionette.View constructor.

backbone collections' population with server data

I have a backbone collection. I fetch. The server comes back with a JSON. How do I populate the collection witht he fresh data? Here's my code:
var Todos = Backbone.Collection.extend({
url: "server/todos-service.php", // this does what it is supposed to do so no worries!
model: Todo,
initialize: function(attributes, options) {
// IN HERE WHAT DO I HAVE TO DO?
// WHAT EVENT SHALL I BIND TO TO REACT THE DATA DELIVERY?
// AND WHAT SHALL I DO NEXT TO POPULATE THE DAMN THINGS
}
});
// CLIENT CODE
new Todos().fetch();
Can anyone tell me how this is supposed to be done?
Cheers
The fetch will start the ajax call to your server. When the data is returned it is automatically put the data into your collection, and the collection will fire a reset being like "I'm done fetching data bro, you can use it now."
You would usually call fetch() from inside your view, and have the view listen to the reset event. When the reset event is trigger, the view renders the collection.
var Todo = Backbone.Model.extend({
// my model
});
var TodoList = Backbone.Collection.extend({
url: myurl
model: Todo
// my collection (usually nothing in the initialize function)
});
var AppView = Backbone.View.extend({
initialize: function() {
// instantiate the collection
this.collection = new TodoList();
//listen to the reset event on the collection. When the reset event trigger, call the render function.
this.collection.on('reset', this.render, this);
// get the data from the backend
this.collection.fetch();
},
render: function() {
// render the collection like a boss
}
});
//instantiate the view
var App = new AppView;
Also as a resource, I found this tutorial to be really helpful in understanding the basics of backbone http://net.tutsplus.com/sessions/build-a-contacts-manager-using-backbone-js/

Rendering Handlebars template with Backbone

I have a Backbone view (see below) that I believe to be doing the right thing
Index = Backbone.View.extend({
render: function() {
var activities = new Activities();
activities.fetch();
var tpl = Handlebars.compile($("#activities-template").html());
$(this.el).html(tpl({activities: activities.toJSON()}));
return this;
}
});
If execute each line in the render() function with Chrome JS console I get the expected result with the element I pass in getting populated with the template output. However, when I run this using the following
var i = new Index({el: $("body")})
i.render()
"i.$el" is completely empty--the HTML is not getting rendered like it does in console. Any ideas why?
fetch is an AJAX call so there's no guarantee that activities.toJSON() will give you any data when you do this:
activities.fetch();
var tpl = Handlebars.compile($("#activities-template").html());
$(this.el).html(tpl({activities: activities.toJSON()}));
Executing the code in the console probably gives the AJAX call time to return with something before you try to use activities.
You should do two things:
Fix your template to do something sensible (such as show a loading... message of some sort) if activities is empty.
Attach your view's render to the collection's "reset" event:
initialize: function() {
// Or, more commonly, create the collection outside the view
// and say `new View({ collection: ... })`
this.collection = new Activities();
this.collection.on('reset', this.render, this);
this.collection.fetch();
},
render: function() {
var tpl = Handlebars.compile($("#activities-template").html());
this.$el.html(tpl({activities: this.collection.toJSON()}));
return this;
}
I also switched to this.$el, there's no need to $(this.el) when Backbone already gives you this.$el.

Backbone.js: retrieving a collection from the server in PHP

I'm having a look at Backbone.js, but I'm stuck. The code until now is as simple as is possible, but I seem not to get it. I use Firebug and this.moments in the render of MomentsView is an object, but all the methods from a collection don't work (ie this.moments.get(1) doesn't work).
The code:
var Moment = Backbone.Model.extend({
});
var Moments = Backbone.Collection.extend({
model: Moment,
url: 'moments',
initialize: function() {
this.fetch();
}
});
var MomentsView = Backbone.View.extend({
el: $('body'),
initialize: function() {
_.bindAll(this, 'render');
this.moments = new Moments();
},
render: function() {
_.each(this.moments, function(moment) {
console.log(moment.get('id'));
});
return this;
}
})
var momentsview = new MomentsView();
momentsview.render();
The (dummy) response from te server:
[{"id":"1","title":"this is the moment","description":"another descr","day":"12"},{"id":"2","title":"this is the mament","description":"onother dascr","day":"14"}]
The object has two models according to the DOM in Firebug, but the methods do not work. Does anybode have an idea how to get the collection to work in the view?
The problem here is that you're fetching the data asynchronously when you initialize the MomentsView view, but you're calling momentsview.render() synchronously, right away. The data you're expecting hasn't come back from the server yet, so you'll run into problems. I believe this will work if you call render in a callback to be executed once fetch() is complete.
Also, I don't think you can call _.each(this.moments) - to iterate over a collection, use this.moments.each().
Try removing the '()' when instantiate the collection.
this.moments = new Moments;
Also, as it's an asynchronous call, bind the collection's 'change' event with the rendering.
I hope it helps you.

Resources