3

I have a Backbone Marionette application with a module defined below. When I run this application, the console log statement prints out @ as the window object. When the list method is run, I thought that this (@) would refer to the List.Controller object. What am I missing?

###
The Header list controller.
###
define [
    'cs!app',
    'cs!./view',
    'cs!modules/header/entities'
], (
  App,
  View
) ->
  App.module 'Header.List', (List, App, Backbone, Marionette, $, _) ->
    List.Controller =
      list: ->
        console.log(@)
        headers = App.request 'header:entities'
        view = new View.Headers {collection: headers}
        App.headerRegion.show view

      setActiveHeader: (headerUrl) ->
        headers = App.request 'header:entities'
        header = headers.find (header) -> (header.get 'url') == headerUrl
        header.select()
        headers.trigger 'reset'

    App.commands.setHandler 'header:setActive', (headerUrl) ->
      List.Controller.setActiveHeader headerUrl

  App.Header.List.Controller

Update

This is the module that calls the list method:

###
The Header module.
###

define [
    'cs!app',
    'cs!./list/controller'
], (
  App,
  listController
) ->
  App.module 'Header', (Module, App, Backbone, Marionette, $, _) ->
    Module.startWithParent = false

  App.module 'Routers.Header', (ModuleRouter, App, Backbone, Marionette, $, _) ->
    class ModuleRouter.Router extends Marionette.AppRouter
      appRoutes: {}

      executeAction = (action, args) ->
        action(args)

      API =
        list: ->
          executeAction listController.list

      App.Header.on 'start', ->
        API.list()

      App.addInitializer ->
        new ModuleRouter.Router {listController: API}

  App.Header
Erik
  • 7,479
  • 8
  • 62
  • 99

3 Answers3

2

The Problem is calling method list of object listController with context of window (global).

This happened because you called method this way: executeAction listController.list and from executeAction this is just calling method normal way: action(args)

You can bind method to the class (with _.bind) or use call or apply method of js Function (bind way is easier):

Bind (_.bind(action, context):

executeAction _.bind(listController.list, listController)

Calling (or applying) with another context (method.call(context, arg1, ..) or method.apply(context, argsArray))

  executeAction = (context,action, args) ->
    action.call(context, args)

  API =
    list: ->
      executeAction listController, listController.list
KiT O
  • 867
  • 6
  • 21
  • I used your last example and it works like a charm -- and I think I understand what is going on now. The only thing I am looking at is ekerens option. – Erik Dec 12 '13 at 16:51
  • I went with this option. I also am looking at the requirement for the extra level of indirection I am using with the executeAction piece. Thanks for the help to all. – Erik Dec 12 '13 at 17:37
1

You should use _.bindAll(this) in initialize function, just add:

initialize:->
  _.bindAll(this, "list") // Like @mu mentioned in the comment, function name is required

EDIT:

Although @KiT-O is correct and the caller can bind the function to the Controller using _.bind function. This should not be the caller responsibility, The function needs to be binded to its correct context and the caller shouldn't care about/know it.

That is why I prefer the _.bindAll solution although it adds more boilerplate code to Backbone

ekeren
  • 3,408
  • 3
  • 35
  • 55
  • I implemented this by adding App.Header.List.addInitializer -> _.bindAll(App.Header.List.Controller, 'list', 'setActiveHeader') into my Header list controller. This works, but seems cumbersome compared to KiT O's solution -- I'd have to maintain the list of functions. – Erik Dec 12 '13 at 17:24
  • This won't work with newer versions of Underscore: ["**methodNames** are required."](http://underscorejs.org/#bindAll) – mu is too short Dec 12 '13 at 17:32
  • @Erik - I disagree :), see Edit where I explain why – ekeren Dec 13 '13 at 09:22
  • @ekeren I'd agree with you if it wasn't for the way in which I was calling the method. If you call the method in context via App.Header.List.Controller.list then everything works fine. As I'm doing it, the caller is adding a layer of indirection via the executeAction part and therefore has accepted the responsibility of setting the context as well. – Erik Dec 13 '13 at 17:30
  • Also, my controller is just a dictionary, so there is no initialize function called. – Erik Dec 13 '13 at 21:13
  • Right, I missed it, I thought you are using marionette controller... cool – ekeren Dec 14 '13 at 12:03
0

this is not bound until the function is called and is dependent on how the function is called. You could think of it as an extra parameter implicitly passed to the function.

In this case, I think you're calling your function using controller(). The this value is set either by calling a function as a method (as in foo.bar() or foo["bar"]()) or by setting this explicitly via call() or apply(). Your call is doing neither so this reverts to the global object.

From here.

Community
  • 1
  • 1
user2503775
  • 4,267
  • 1
  • 23
  • 41
  • I added my call in the update. Since I am using listController.list, I thought that `this` would be bound to listController. – Erik Dec 12 '13 at 08:42
  • I was just notice that you are using `requirejs`. It seems that `requirejs` doesn't do any binding for 'this' keyword. For same code without requirejs, you will get your controller obj. – user2503775 Dec 12 '13 at 09:27
  • 1
    Reed more here: http://stackoverflow.com/questions/14650746/can-the-this-keyword-be-used-inside-a-requirejs-module – user2503775 Dec 12 '13 at 09:28
  • Interesting -- something I did not know. I need to use requirejs though as it is how I assemble my asset pipeline. – Erik Dec 12 '13 at 17:03