Gavin
Gavin

Reputation: 4333

How to define a callback function with Coffeescript and Rails 3.1?

In my view I want:

:coffeescript
  Gmap('#canvas').getAddressBounds request.term

which is defined in maps.js.coffee as

Gmap = (mapId) ->
  getAddressBounds: (address) ->
    data = []
    $(mapId).gmap3
      action: 'getAddress'
      address: address
      callback: (results) ->
        return unless results
        data = $.map results, (item) ->
          bounds: item.geometry.bounds
    data

This doesn't work though. First, there's a scope issue. The Gmap function is not visible to the script in the view. If I add the code directly to the view, Gmap is visible, but data always returns as [].

Upvotes: 3

Views: 3434

Answers (2)

Trevor Burnham
Trevor Burnham

Reputation: 77426

What's going on is that you're treating asynchronous code as if it's synchronous. Some debug output might help you visualize this:

Gmap = (mapId) ->
  getAddressBounds: (address) ->
    data = []
    console.log '1: Calling gmap3'
    $(mapId).gmap3
      action: 'getAddress'
      address: address
      callback: (results) ->
        console.log '3: Callback called'
        return unless results
        data = $.map results, (item) ->
          bounds: item.geometry.bounds
    console.log '2: Returning data'
    data

When you pass a callback, it could get called at any time. If it were called during the gmap3 function, then data would indeed be set before being returned. But the reason gmap3 uses a callback to return its result, rather than just returning, is that the function is asynchronous—in particular, it calls the callback when the server responds to your query. The way that JavaScript does events, that means your callback is guaranteed not to be called until after all code has finished executing.

There's no way to wrap an asynchronous function in a synchronous one in JavaScript (or CoffeeScript); even running an infinite loop until your callback is called wouldn't work, because, again, the JS runtime doesn't handle events like server responses (or even user input events) until all code has finished executing. So all you can do is change your function, too, to use a callback:

Gmap = (mapId) ->
  getAddressBounds: (address, cb) ->
    $(mapId).gmap3
      action: 'getAddress'
      address: address
      callback: (results) ->
        return unless results
        cb $.map results, (item) ->
          bounds: item.geometry.bounds

Then call it like so:

 Gmap('#canvas').getAddressBounds request.term, (data) -> console.log data

I talk a little bit more about the JS event model in my CoffeeScript book. Also, John Resig's How JavaScript Timers Work is a must-read. Asynchronicity takes some getting used to, but the benefits over multithreading are spectacular.

Upvotes: 5

bradgonesurfing
bradgonesurfing

Reputation: 32202

change Gmap = ... to window.Gmap = ....

will solve the problem. The reason is coffeescript wraps everything in an anonymous function. If you want a more feature full module system have a look here

https://github.com/jashkenas/coffee-script/wiki/Easy-modules-with-coffeescript

Upvotes: 2

Related Questions