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
Related
would adding event methods override the existing backbone view methods. I am using layout manager which add some more methods..I wanted to avoid override the methods..what is the best way for naming my method
$ ->
class Overlay extends Backbone.View
events:
'click .close': 'close'
close: (e)=>
#remove()
The close Method will be added to the view..if there is a close method in backbone view it will be overridden...??
yes, same reason how implementing your own render function will overwrite backbone's default render function.
You can always prefix your methods with underscore if you are worried that you might overwrite some default functions.
Example:
'click .close': '_close'
It's a common practice to prefix private methods with underscore anyways. That or just give it a more specific name... You can also define the function directly:
'click .close': function(){
...
}
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)
I'm getting the error:
Uncaught TypeError: Cannot read property 'constructor' of undefined
When declaring the following class:
class ViewHelpers extends Backbone.Events
I can use the same syntax to extend Backbone.Router, Views, Model etc. Here is the compiled javascript which I wrote in a quick log to make sure Backbone.Events was there
__t('views').ViewHelpers = (function(_super) {
#how i know it is definied here
console.log(_super.trigger)
__extends(ViewHelpers, _super);
function ViewHelpers() {
return ViewHelpers.__super__.constructor.apply(this, arguments);
}
return ViewHelpers;
})(Backbone.Events);
So the line causing the error is
ViewHelpers.__super__.constructor.apply(this, arguments);
What is different about __extends() method that it would work for Backbone.View and not Backbone.Events?
That's because Backbone.Events is not a "class", so it cannot be extended, it's a "module" that can be mixed-in into other objects (see docs here). In JavaScript terms that means that it's not a Function, that can be called as a constructor (i.e. new Backbone.Events will throw an error), it's just a plain JS object whose properties (methods) can be assigned to other objects to make them event dispatchers.
In CoffeeScript, you can mix-in the Backbone.Events into your objects when they are created:
class ViewHelpers
constructor: ->
_.extend #, Backbone.Events
Or you can just extend the class' prototype and avoid having those methods as (own) properties of all ViewHelpers instances:
class ViewHelpers
_.extend #prototype, Backbone.Events
These two approaches should work and let you instantiate and use ViewHelpers as event dispatchers:
vh = new ViewHelpers
vh.on 'foo', -> alert 'bar'
vh.trigger 'foo'
There's another way (from what #epidemian answered), which doesn't involve copying Backbone.Events into a new object to use as your prototype - instead, use Object.create to create a new object to use as your prototype, using Backbone.Events as its prototype.
class ViewHelpers
#prototype = Object.create(Backbone.Events)
Now ViewHelpers' prototype is a new, empty object whose prototype is Backbone.Events. You can define methods on ViewHelpers' prototype without affecting Backbone.Events, but all the Backbone.Events methods are still available to ViewHelpers, without having to copy them into a new object. This not only saves (a miniscule amount of) memory, but if you ended up adding on to Backbone.Events later, all ViewHelperss would see the change.
For this, you'll need either a browser that has ES5's Object.create function, or an Object.create polyfill.
To build on the excellent answer by #epidemian I would add this, it's a bit of a hack but it allows you to write your class with the extends statement as you specified in the question (which lets you call super on all the Backbone.Events methods):
class Events
_.extend Events.prototype, Backbone.Events
class CustomEvents extends Events
trigger: (event, etc...) ->
# You can add overrides before
super "custom:#{event}", etc...
# or after the super class methods
It would be neat to work the _.extend call into the Events.constructor function but I couldn't make it work...
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 :)
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.