Rendering backbone views with collection in callbacks - backbone.js

Here is a view I am working with:
class Raffler.Views.EntriesIndex extends Backbone.View
template: JST['entries/index']
initialize: ->
#collection.on('reset', #render, this)
render: ->
Raffler.entries = #collection
$(#el).html(#template(eventSource: (start, end, callback) ->
console.log #collection # = undefined
callback(Raffler.entries.events(start, end))
))
I had to assign window.Raffler property to my collection to be able to use it in the callback. Is there a nice way of using something like callback(#collection.events(start, end))?

In coffeescript, if you use the "fat arrow" (=>) operator instead of ->, your callback function will be bound to the this (#) in the scope in which it is created. This means you can use #collection within your callback and # will properly refer to your EntriesIndex, so your render function can just look like this:
render: ->
$(#el).html(#template(eventSource: (start, end, callback) =>
console.log #collection # == your EntriesIndex collection
callback(#collection.events(start, end))
))
See http://coffeescript.org/#fat_arrow
My suggestion above will only work if this (#) refers to your EntriesIndex within render, so I believe you may have to do as Abraham suggested as well and make sure to bind # to your EntriesIndex within the render function. Add this to initialize:
_.bindAll this
Someone who knows Coffeescript can correct me if I'm wrong on that syntax :)

Inside of initialize if you this.bindAll(this); then this.collection should work inside of render.

Related

Backbone.js read model attributes in initialize

In one of my view I need to recopy an attribute value into another attribute.
Here is the code in my code in coffeescript
class MyFactoryView extends Backbone.View
initialize: ->
#model.fetch reset: true
#model.set('NewStatus', #model.get('CurrentStatus'))
This code throws an undefined exception when I call get.
But if I do a console.log #model, I can see the CurrentStatus in attributes.
As mu said, fetch is async and you only have access to the attributes once it's finished.
class MyFactoryView extends Backbone.View
initialize: ->
#model.fetch
reset: true
context: #
success ->
#model.set('NewStatus', #model.get('CurrentStatus'))
For that specific line, it could be simpler to use the parse function of the model to initialise the NewStatus attribute on sync.
For the console, take a look at this answer which explains that the console contains live references and doesn't copy anything.

Backbone View, Event Handler and bindAll

I have something like this
$ ->
class MyView extends Backbone.View
initialize: ->
stuff
$("body").on "click", ".stuff", ->
#functionB()
functionA: (new_date) ->
stuff
functionB: () =>
stuff
new MyView(el: $mySelector)
I want to call functionB from the event handler.
I know that I can use fat arrows, but how would I do it with _.bindAll
First of all, I'm guessing that $("body").on "click", ".stuff", -> is actually inside your initialize method and you just have a formatting problem. Your code doesn't make a lot of sense otherwise.
The problem you'll run into is that jQuery controls what # (AKA this) is inside the callback function. So just binding functionB won't be sufficient because you can't get at functionB without having the right # already.
In your specific case, none of this really matters because functionB is defined as a bound function (using =>) and you have no need for an anonymous wrapper in the event handler, just hand on the function itself:
$("body").on "click", ".stuff", #functionB
If for some reason you insist on bind an anonymous function then you'd want to use _.bind (or Function.prototype.bind) rather than _.bindAll; _.bindAll binds functions (by name) to a specific # but you want to find a specific function (which has no name) to a specific #, see What is the difference between these Backbone/Underscore .bind() methods? for more discussion on the difference. You'd say something like:
$("body").on "click", ".stuff", _(-> #functionB()).bind(#)
# or
unbound_function = -> #functionB()
$("body").on "click", ".stuff", _.bind unbound_function, #
since you are using backbone, you can either use the native syntax for binding, => or the built in underscore methods of _bind and _bindAll. they do the same thing, its just a matter of where you declare them. If you bind them at function declaration, you can then have a reference to them if you want to dispose of the event listener.
$ ->
class MyView extends Backbone.View
initialize: ->
stuff
$("body").on "click", ".stuff", #functionB
# alternatively, inline as an anonymous function (then you can
# leave the declaration of functionB below as `->` )
$("body").on "click", ".stuff", => #functionB(arguments...)
functionA: (new_date) ->
stuff
functionB: () =>
stuff

Why it calls the original method?

I am writing a jasmine spec against my backbone app. However, I got stuck with this problem. Not sure why my spy function doesn't get invoked. I want to make sure when the model is changed, it should call #render.
Here is my backbone view:
class App.Views.Main extends Backbone.View
initialize: () ->
#model.on("change", #render, this)
render: () ->
console.log('rendering')
return
Here is my jasmine spec:
it "should render when change is triggered", ->
renderSpy = sinon.spy(#view, 'render')
#view.model.trigger('change')
expect(renderSpy.called).toBeTruthy()
Another thing that confuses me is that when this spec runs, it actually invokes the original method. The console log is always displayed. Anyone could help me?
Updated:
As answered below by Leonardo, I make changes with the following changes:
it "should render when reset is triggered", ->
renderSpy = sinon.spy(App.Views.Main.prototype, 'render')
#view.model.trigger('change')
expect(#renderSpy.called).toBeTruthy()
renderSpy.restore()
It works, but the problem is that it invokes the original method. I just wonder why?
I think this is the same that is happenning here:
https://stackoverflow.com/a/9012788/603175
Basically, you need to create the spy before you execute the constructor that executes the event listening, which binds the function to 'this' context.

Backbonejs and Custom Model Event

I am trying to create a custom event for my model but apparently the custom event get triggered no matter what unless I use "anonymous" function definition as a callback
Here is the pseudo code of my app structure
//Router
initialize: ->
this.user = new User()
this.view = new View({model:this.user})
this.view.render()
//View
initialize: ->
//This event binding get triggered no matter what
//this.model.on("custom:event", this.triggerMe(), this)
//This works properly. Only triggered when I call model.trigger("custom:event")
this.model.on("custom:event", function(){console.log("I WORK!!");}))
triggerMe: ->
//I GET TRIGGER NO MATTER WHAT
you are invoking a function here:
this.triggerMe()
it should be this.triggerMe
this.model.on("custom:event", this.triggerMe, this)
Adding () or .call() or .apply() is invoking a function not a reference to it.
By passing this.triggerMe() you automatically execute the triggerMe function (because you add parentheses, and by so invocating it).
What you need to do, is to pass a reference to the function. Like so:
this.model.on("custom:event", this.triggerMe, this)

Backbone.js extend and this

I'm using experimenting with .extend() to set up my views and initialise them with. I've found it's convenient to assign config variables to view objects nested deep within a hierarchy.
My problem is that my Views lose their this context. This becomes the ctor object which I asume is the constructor. How can I fix this?
My coffeescript is below. The first class would be nested deep within a tree, the second is at the top level where the application boots up:
# This is a child somewhere deep within a tree of views.
class View extends Backbone.View
initialize: ->
console.log # # returns object ctor
MyView = View.extend({
initialize: ->
# do config stuff then init prototype
App.Views.MyView.prototype.initialize()
})
view = new MyView
Two things:
First, and not as importantly, you can use
class MyView extends View
instead of View.extend. CoffeeScript classes and Backbone classes are interoperable.
Second—and this is the important part—instead of
App.Views.MyView.prototype.initialize()
you should simply use the CoffeeScript keyword
super
That effectively does the same thing, but also ensures that the function is called in the correct context. Bonus: It also passes in all of your function arguments for you.
If you're curious, super here compiles into
initialize.__super__.constructor.apply(this, arguments)
(where __super__ is a reference to the superclass that's set by both CoffeeScript's extends). Read about apply at MDN.
My coffe script isnt so hot but can you call the function sending the view you want to be the value for this in as the first parameter
I suppose in standardish js
var view = null;
MyView = View.extend({
initialize: function() {
// do config stuff then init prototype
App.Views.MyView.prototype.initialize.call(view)
}
})
view = new MyView;
Im not sure what you have access to at that point or the generated js either.
The point being if you have access to what should be the value for this when you call the function you should be able to pass it in.
I would check myself but i don't comprehend coffeescript :)

Resources