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!

Rails, jQuery UI (Sortable), and Ordering of Slides

I am partly sharing this issue and solution to the world, but mostly just recording somewhere what I came up with. Now, onto the quick read.

A web design client recently asked me to build a web site for him that would allow him to create slide shows of his art work. One of the criteria was to create a way to set the order and, at a later date, re-arrange slides in the slide shows. Turning to jQuery UI, specifically the Sortable (doc), and a simple rails controller this task was pretty trivial. My initial concern was that a slide show could consist of hundreds of slides and doing any sort of ajax updating of the slides would be too slow. Turns out, this was a real concern and after reading all kinds of blogs I was unable to find a work-able ajaxively awesome solution. So, I turned back to the non web2.0 design of just having a Save Order button. The methodology of my solution is straight forward: allow the user to drag and drop to any order configuration they please and then save that order. Upon clicking save via the user interface, invoke the following javascript command that will build the query string and do a window relocation. I’d rather use GET than POST for no real reason outside of having to hack around the authenticity token. After all, what’s the point of having an auth token if you’re just going to override it in a members-only section. Here is the complete code for the view, broken into pieces for clarity.

CSS:

  #sortable { list-style-type: none; margin: 0; padding: 0; width: 100%; }
  #sortable li { margin: 0 3px 3px 3px; padding: 0.4em; padding-left: 1.5em; font-size: 1.4em; height: 18px; }
  #sortable li span { position: absolute; margin-left: -1.3em; }

JS:

<script type="text/javascript">
// Initialize sortiable on #sortable div
$(function() {
	$("#sortable").sortable();
	$("#sortable").disableSelection();
});
// Prepare and go-to proper url for updating order of slides.
// Called via html anchor tag by user
function update_order() {
  window.location.href = "/slides/order?"+$('#sortable').sortable('serialize');
}

Sample #sortable:

<ul id="sortable">
	<li id="slide_2" class="ui-state-default full-width"><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>Lack of a better Title</li>
	<li id="slide_3" class="ui-state-default full-width"><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>A fireplace for two</li>
	<li id="slide_1" class="ui-state-default full-width"><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>My Super Sweet Villa</li>
	<li id="slide_4" class="ui-state-default full-width"><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>A view from above</li>
</ul>

Anchor link:

<a href="#" onClick='update_order(); return false()'>Save Order</a>

Controller:

  def order
    if params[:slide]
      params[:slide].each_with_index { |slide, index| Slide.update(slide.to_i, :slide_position => (index +1)) }
      flash[:notice] = "Slide order has been updated."
      redirect_to(slides_path)
    else
      @slides = Slide.ordered
    end
  end

Now, for some disclaimers… You should add in error handling to all of these pieces! For brevity, I’ve included the main pieces of the task and not my error handling code. You can also use post and add in (instead of looking for params[:slide]) if request.post?… Just don’t forget to put in the authenticity_token to your ajax or form post (forms do this automatically in rails if protect_from_forgery is enabled).

Efficiency: This is a O(n) algorithm, because it has to iterate over each element being sorted and do three things: fetch from the database, update the attribute, and save. The last two steps can be combined. I’m using the handy ActiveRecord::Base extension in Rails called update to do this. Here’s that code so you don’t have to look it up:

     # File vendor/rails/activerecord/lib/active_record/base.rb, line 744
744:       def update(id, attributes)
745:         if id.is_a?(Array)
746:           idx = -1
747:           id.collect { |one_id| idx += 1; update(one_id, attributes[idx]) }
748:         else
749:           object = find(id)
750:           object.update_attributes(attributes)
751:           object
752:         end
753:       end

I was unable to locate any kind of conditional MySQL update that would allow different values to be updated for different rows all in one swoop. I’ll admit my research on this topic was about 30 minutes of blog and StackOverflow reading… If you have something more efficient please reply.

Here are the link(s) to relevant information:
jQuery UI Demos (and download) — I’m using the latest code base as of this post. The styles you see within the bullet list items are from the jQuery UI theme.