JavaScript, CoffeeScript, and Closures

I’ve really liked CoffeeScript because it mimics behaviors and syntax I’m used to in Ruby; thus the speed of development for me is skyrocketing as the context switch between Ruby & JS has been minimized. But when I first started mucking around with it a few weeks ago I hit some oddities that I wanted to share with other developers since I’ve seen the question asked a few times.

CoffeeScript can, if this feature is enabled, wrap your .coffee code inside of an anonymous function that acts as a closure. Rails 3.1 enables this by default. I am also using jQuery, so the $ -> is merely the wrapper that provides the jQuery namespace context.

test.coffee

 
$ ->
  show_dialog = (title,msg) ->
    dialog = $('#dialog')
    dialog.find('h2').text(title)
    dialog.find('p').text(msg)
    dialog.modal('show')
 
  $('#about_us').click ->
    show_dialog('About Us', 'Pinning is fun!')

Becomes

test.compiled.js

(function() {
 
  $(function() {
    show_dialog = function(title, msg) {
      dialog = $('#general_modal');
      dialog.find('h3').text(title);
      dialog.find('p').text(msg);
      return dialog.modal('show');
    };
    return $('#about_d').click(function() {
      return show_dialog('About Us', 'Pinning is fun!');
    });
  });
 
}).call(this);

Uh oh. This creates a problem: if you want your view to call one of the functions you have defined in a CoffeeScript file you’re SOL unless you make those functions globally available (I don’t encourage cluttering the global namespace). Causing further headache, when you realize your functions are not being called and try to debug things you find yourself looking at reference/undefined method errors. Solution: throw your functions (JS or CS) in a namespace and have globally accessibly, albeit scoped appropriately, JavaScript. And, for added benefit, have a cleaner code base :)

(Displaying actual code to give you more context of how I use this solution)

welcome.js.coffee

 
window.Pinned ||= {}
 
$ ->
  Pinned.handle_invite_request = (json) ->
    console.log(json)
    console.log(json.status)
    if json.status == 'success'
      $('#invite_success').html("We'll e-mail <b>"+json.email+"</b> when we're ready!").fadeIn('fast')
      $('#invite_request').slideUp('fast')
    else
      $('#invite_error').html(json.message).show()
 
  Pinned.request_invite = () ->
    $.get("/welcome/request_invite",{'email':$('#invite_email').val()}, Pinned.handle_invite_request, "json")
 
  genModal = $('#general_modal')
 
  Pinned.show_dialog = (title,msg) ->
    genModal.find('h3').text(title)
    genModal.find('p').text(msg)
    genModal.modal('show')
 
  $('#about_d').click -> 
    Pinned.show_dialog('GetPinned',"We believe there is a better way to encourage sharing, collaboration, and love for all things artsy and digital. Our passion is to help you share your passion.")

Becomes

welcome.js

(function() {
 
  window.Pinned || (window.Pinned = {});
 
  $(function() {
    var genModal;
    Pinned.handle_invite_request = function(json) {
      console.log(json);
      console.log(json.status);
      if (json.status === 'success') {
        $('#invite_success').html("We'll e-mail <b>" + json.email + "</b> when we're ready!").fadeIn('fast');
        return $('#invite_request').slideUp('fast');
      } else {
        return $('#invite_error').html(json.message).show();
      }
    };
    Pinned.request_invite = function() {
      return $.get("/welcome/request_invite", {
        'email': $('#invite_email').val()
      }, Pinned.handle_invite_request, "json");
    };
    genModal = $('#general_modal');
    Pinned.show_dialog = function(title, msg) {
      genModal.find('h3').text(title);
      genModal.find('p').text(msg);
      return genModal.modal('show');
    };
    return $('#about_d').click(function() {
      return Pinned.show_dialog('GetPinned', "We believe there is a better way to encourage sharing, collaboration, and love for all things artsy and digital. Our passion is to help you share your passion.");
    });
  });
 
}).call(this);

You can now reference your functions that have been defined anywhere in your code!

One Comment

Leave a Reply

Your email address will not be published. Required fields are marked *