Monday, December 2, 2013

Rails, Ajax Requests, and Playing Nicely with respond_to do |format|

So this morning I'm working on making a highly interactive dialog for +Rentables. The dialog is initially loaded via ajax using the very familiar respond_to block where render_ajax_dialog is a helper specific to my project.
respond_to do |format|
  format.html {}
  format.js { render_ajax_dialog }
end
Now when the user clicks certain buttons on the dialog, I want to refresh the dialog from the server (i.e. Ajax) without reloading the underlying page and without the updating controller having to know anything about the dialog - basically the blind interacting with the blind.
Pop quiz: How do the blind interact in the stateless hyper text transfer protocol world?
My solution is to pass a parameter to the updating controller (:redisplay) with the URL to the action which renders the dialog. It seems a bit messy, right? Maybe someone reading this has a better idea.
class DialogLoadingController < ApplicationController
  def show
    # load some foo

    respond_to do |format|
      format.html
      format.js { render_ajax_dialog }
    end
  end
end


class UpdatingThingsController < ApplicationController
  def update
    # update some foo

    respond_to do |format|
      format.html { redirect_to pop_return_to_or(foo_path) }
      format.js { 
        if (redisplay = params[:redisplay])
          show_dialog_url = ensure_format_js(redisplay)
          render :js => "$.ajax({url: '#{show_dialog_url}', cache: false});"
        end
      }
    end
  end


  private

  # In order for +respond_to+ to pick JS we need to ensure the URL
  # includes '.js'. The first call will not but subsequent calls will.
  #   ensure_format_js('/path/to/foo?are_you_awesome=true')
  #   # => /path/to/foo.js?are_you_awesome=true
  #
  #   ensure_format_js('/path/to/foo.js?are_you_awesome=true')
  #   # => /path/to/foo.js?are_you_awesome=true
  #
  # @param [String] url the redisplay URL
  # @return [String] Url with format .js specified
  def ensure_format_js(url)
    return nil unless url

    # split off query string, if any
    url = url.split '?'

    # check if JS specified
    if url.first.match(/.*\.js$/)
      # JS specified, re-join URL and query
      url.join('?')
    else
      # add JS and re-join
      url.first + '.js?' + url.last
    end
  end
end