Backbone model events/triggers not firing - backbone.js

Having seen it in a couple of tutorials, I'm trying to execute the following line of code in my view
#model.on('change', #render, this)
Unfortunately the change event is not firing and therefore my view is not re-rendering.
I've tried binding to different events and creating a couple of custom events using the trigger function but nothing seems to be firing at all on the model. Furthermore, there are no errors coming from the console. The change event is working fine on a different collection. I'm using Zepto 1.0, Backbone.js 0.9.2 and Underscore.js 1.3.1
EDIT: I'm trying to execute the following from the Router
place: (id) ->
#model = new GM.Models.Place({id: "#{id}"})
#model.fetch
view = new GM.Views.Place(model: #model)
$('#container').html(view.render().el)
And my model is set up like this:
class GM.Models.Place extends Backbone.Model
urlRoot: '/mobile/place'
I am wondering if anyone has experienced similar problems before and has a quick fix.
If not and you need more of the code to find an explanation please let me know...

You're not actually calling the #model.fetch method anywhere. This:
#model.fetch
is not a method call, you need to add parentheses or arguments if you want to call the method:
#model.fetch()
# or
#model.fetch success: -> ...
# etc.
Otherwise you're just producing this.model.fetch; in the JavaScript and that doesn't do anything useful.

Related

Unable to wrap functions assigned with events parameter in backbone model

Motivation: I want to wrap all functions within backbone model with log functionality to make it more easy to follow function calls.
I am iterating over models functions and warping them with underscores wrap().
But it looks like event function bindings from 'events' hash doesn't get wrapped as it looks like these functions are copied and stored somewhere in DOM, not sure.
Has anybody came to this problem as well or any ideas how to workaround?
Manually entering console.log() in these function is not what I am looking for.
I think you're experiencing the same problem as when people are trying to test the functionality provided with the events -hash. Spying on the event callback directly on the View doesn't work, but spying on the class prototype before instantiating works.
// This won't work
var view = new SomeView();
spyOn(view, 'onClick');
view.$el.click(); // spy isn't called
// This works
spyOn(SomeView.prototype, 'onClick');
var view = new SomeView();
view.$el.click(); // spy is called
Try iterating over the prototype of your 'class' right after creating it, because that should be where the methods called by the events -implementation should reside.

How do I iterate a Backbone Firebase Collection?

I currently started using Firebase as my backend solution for persistance.
I found it easy to create new objects and persist it to Firebase with Backfire with a simple
collection.add(obj)
The issue comes when I try to get a Firebase collection from the server.
For example when i try
console.log(collection);
I get this output:
=> {length: 0, models: Array[0], _byId: Object, _events: Object, constructor: function…}
Which result in an empty models array
console.log(collection.models);
=> []
After some searching, I figured out that Backbone Collections aren't yet loaded at the time I try to log it to the console (see this previous question).
I also tried using
Backbone.Collection.extend({
model: Todo,
firebase: new Backbone.Firebase("https://<your-namespace>.firebaseio.com")
});
To explicitly call fetch from the server and use success callback with no success either.
My question is: How can I get the Firebase Collection and populate the DOM from it?
When you call Backbone.Firebase.Collection.add, it does not get added to the collection synchronously. Rather, it sends the request to Firebase and then waits for the return event. See the code here
Thus, if you immediately try to read the collection, you will see zero elements. However, if you try something like this:
collection.once('add', function() { console.log(collection.length); });
You'll see the element you have added.
Remember that we're dealing with real-time data here, so when you want to populate the DOM, you shouldn't think in terms of a single transaction, but instead rely on events and take everything as you get it (in real time).
So to populate the DOM, do something like this in your view:
Backbone.View.extend({
render: function() {
this.listenTo(this.collection, 'add', this.rowAdded);
},
rowAdded: function(m) {
/* use `m` here to create your new DOM element */
}
});
Additionally, you'll probably want to check out a nice binding library like ModelBinder to help you deal with the constantly changing DOM, so you don't have to re-invent any wheels.
It seems you have to use a Backbone.Firebase.Collection and not a Backbone.Collection which will tell you that your calls to fetch or sync are silently ignored.
Also, Backbone.Firebase's got a read and a readall methods that should get you started. It seems Backbone.Firebase.Collection doesn't inherit this method, but I'm not sure though.
Edit:
As Kato stated in his comment, it seems you don't have to do anything. Just use Backbone.Backfire.Collection and Backbone.Backfire.Model.

Test Driving Backbone view events

I am trying to test drive a view event using Jasmine and the problem is probably best explained via code.
The view looks like:
App.testView = Backbone.View.extend({
events: { 'click .overlay': 'myEvent' },
myEvent: function(e) {
console.log('hello world')
}
The test looks something like:
describe('myEvent', function() {
it('should do something', function() {
var view = new App.testView();
view.myEvent();
// assertion will follow
});
});
The problem is that the view.myEvent method is never called (nothing logs to the console). I was trying to avoid triggering from the DOM. Has anyone had similar problems?
(Like I commented in the question, your code looks fine and should work. Your problem is not in the code you posted. If you can expand your code samples and give more info, we can take another look at it. What follows is more general advice on testing Backbone views.)
Calling the event handler function like you do is a legitimate testing strategy, but it has a couple of shortcomings.
It doesn't test that the events are wired up correctly. What you're testing is that the callback does what it's supposed to, but it doesn't test that the action is actually triggered when your user interacts with the page.
If your event handler needs to reference the event argument or the test will not work.
I prefer to test my views all the way from the event:
var view = new View().render();
view.$('.overlay').click();
expect(...).toEqual(...);
Like you said, it's generally not advisable to manipulate DOM in your tests, so this way of testing views requires that view.render does not attach anything to the DOM.
The best way to achieve this is leave the DOM manipulation to the code that's responsible for initializing the view. If you don't set an el property to the view (either in the View.extend definition or in the view constructor), Backbone will create a new, detached DOM node as view.el. This element works just like an attached node - you can manipulate its contents and trigger events on it.
So instead of...
View.extend({el: '#container'});
...or...
new View({el:'#container'});
...you should initialize your views as follows:
var view = new View();
$("#container").html(view.render().el);
Defining your views like this has multiple benefits:
Enables testing views fully without attaching them to DOM.
The views become reusable, you can create multiple instances and render them to different elements.
If your render method does some complicated DOM manipulation, it's faster to perform it on an detached node.
From a responsibility point of view you could argue that a view shouldn't know where it's placed, in the same way a model should not know what collection it should be added to. This enforces better design of view composition.
IMHO, this view rendering pattern is a general best practice, not just a testing-related special case.

Backbone.js model validate method fails to fire

I'm trying to understand how Backbone.js model validation works, but I'm seeing some odd inconsistencies. In one place in my app, the validate method is getting called as expected. In another place, however, Backbone.js seems to be passing in a { silent: true } object to the validator, even though I don't want it to.
Here's a jsFiddle that illustrates the issue. The validate method should be called When the Copy buttons are clicked or the values change, but when I step through the code it's clear that the _validate function is being passed the { silent: true } option.
What am I missing?
Update: Figured out what was going on here. I created this jsFiddle originally to replicate an issue I was having that was actually the opposite of this question--I was trying to add an empty model to a collection and validation was firing and preventing me from doing so. When I made the Fiddle, though, it worked as I wanted my app to work. Validation wasn't firing when an empty model was added. I couldn't figure out the difference.
Turns out I was using Backbone.js 0.9.0 in my application and version 0.9.1 in my jsFiddle. Jeremy made changes to validation in 0.9.1 to make it work the way I wanted it to work in my app (see this issue on GitHub). Mystery solved.
Backbone specifically does not call _validate when you're making a new model.
Jeremy suggests that you do:
var mymodel = new MyModel();
mymodel.set({params});
Here's our exchange on github: can't override silent:true
From the Backbone docs, it seems you have to either call set or save on the model in order for validate to trigger.
I updated the jsfiddle so that set is called, and the now the validation function gets triggered:
http://jsfiddle.net/J3uuH/12/

Backbone boilerplate Events

What is the best way to bind events to a Backbone boilerplate application? I've been trying to bind my events directly to the models associated with my views, in my views, but it doesn't seem to be working. I see within 'namespace.js', that there is an app key that extends Backbone.Events like so:
// Keep active application instances namespaced under an app object.
app: _.extend({}, Backbone.Events)
I don't fully understand how to use it...
I was able to get things working without the boilerplate, but it does provide some very cool functionality, so I'd love to be able to use it. Thanks!
ADDED
the code I was using was with the underscore bind method like so:
this.module.bind('change', this.render);
But then, I realized that 'this.model' is returning undefined, and so this doesn't work. I really am not sure how the boilerplate wants me to reference my model from the view.
I'm not sure if it is a typo that you copied from your code or a typo you only entered here, but I believe this.module (which IS undefined) should be this.model, which you also must be sure to pass in when you instantiate your view, of course, as so:
myView = new BBView({model: myModel});
then you can say this.model.bind('change', this.render); or this.model.on('change', this.render); for the most recent version of Backbone
I frequently bind my views to change events on my models in this way.
As for extending Backbone.Events, the way I have used it is to create an independent "event aggregator" that will help connect events between different views on your page. Let's say for example you have some action on one view that needs to set off an action on another view. In this case, you can pass your event aggregator object as an option to each of your views when you instantiate them, so that they can both trigger events or bind to events on a common object (i.e. your event aggregator).
whatsUp = _.extend({}, Backbone.Events) // the aggregator
myFirstView = new FirstBBView ({whatsUp: whatsUp});
(the aggregator shows up as this.options.whatsUp inside the view)
mySecondView = new SecondBBView2 ({whatsUp: whatsUp});
inside FirstBBView:
this.options.whatsUp.bind('specialEvent', function(arg1,arg2) {
// do stuff
});
inside SecondBBView, when something important happens:
this.options.whatsUp.trigger('specialEvent', {arg1: 'some data', arg2: 'more data'});
For a great explanation, see this great article by Derick Bailey

Resources