Lightview for modal dialogs on Rails

Posted by Ric Thu, 26 Jun 2008 21:57:00 GMT

Long-time followers of this blog will know that I’ve tried various solutions for modal dialogs in rails apps (namely, Prototype-Window, and Redbox).

Prototype-Window worked fine, but uses Prototype 1.5RC3, and I’m using rails 2 with Prototype 1.6. The guys behind Prototype-Window are working on a new open source library, Prototype-UI which uses Prototype 1.6 and Script.aculo.us 1.8, but it’s still just a release candidate at the moment. This is a really exciting development and it will be interesting to see how this project progresses.

Initially, I was a big fan of Redbox, but I uncovered a few problems as time went on (including some cross-browser issues), which meant that it lost favour. To be fair to Craig Ambrose, the developer of Redbox, he did mention to me that he wasn’t currently working on it, and that it was just built to fit the exact requirements he had at the time.

So, to get down to the topic of this blog post… my current modal dialog solution du jour is Lightview by Nick Stakenburg. It’s not free, but relatively cheap, even for commercial use (€49 for one domain – if you compare this cost to the amount of developer-time it would take to write something of equal quality, it’s really a no-brainer). You can download a free-trial so that you can try it out before buying. I can’t comment on the quality of the code because it’s obfuscated but the documentation on the website is great, and the product itself works well across all major browsers.

I thought I would share how i got Lightview working in a rails application, for showing ajax content.

1. Download Lightview from here. (I’m using version 2.2.9.2)

2. Make a lightview folder in your application’s public/javascripts folder, and copy the contents (i.e. the css, images and js folders) of the lightview 2.x folder into your new folder.

3. Create a new file, lightview_helper.rb, in your helpers folder, and add the following code into it.

module LightviewHelper

# A lightview is shown with js like below (example taken from the lightview site: 
# (http://www.nickstakenburg.com/projects/lightview/)
# There are 2 levels of arguments: 
#   top-level 'parameters' (href, rel etc.)
#   2nd-level 'options' (autosize, ajax etc.)

#  Lightview.show({
#    href: '/ajax/',
#    rel: 'ajax',
#    title: 'Login',
#    caption: 'Enter your username and password to login',
#    options: {
#      autosize: true,
#      topclose: true,
#      ajax: {
#        method: 'get',
#        onComplete: function(){ $('name').focus(); }
#      }
#    }
#  });

 # link to a light view.
 # name: the text to display in the link
 # options: url options for the content to show in the lightview
 # html_options: html_options to pass to link_to 
 # lightview_params: parameters (top-level arguments) for lightview, that differ from or add to defaults
 # lightview_options: options (2nd-level arguments) for lightview, that differ from or add to the defaults
 def link_to_lightview( name, options = {}, html_options = {}, lightview_params = {}, lightview_options = {} )

    # get hold of the js for showing the lightview
    show_lightview_js = show_lightview( options, lightview_params, lightview_options )

    # merge in the javascipt on the onclick event, keeping any other onclick code intact.    
    html_options.merge!({ 
      :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{show_lightview_js} return false;" 
    })

    # return a link which points to #, but has the onclick event in the html options.
    link_to( name, "#", html_options )

  end 

  # returns javascript for showing a lightview.
  # url_options : hash of options to pass to url_for 
  # parameters: parameters (top-level arguments) for lightview, that differ from or add to defaults
  # options: options (2nd-level arguments) for lightview, that differ from or add to the defaults
  def show_lightview( url_options, parameters, options = {} )

    # get hold of the default options and merge in what is passed in to this method
    lightview_options = get_default_lightview_options
    lightview_options.merge!( options )

    # get hold of the default parameters
    lightview_params = get_default_lightview_params
    #  merge in any paramters passed in
    lightview_params.merge!(parameters)
    #merge in the href and options(from above)
    lightview_params.merge!( 
      { 
        :href => "'#{escape_javascript(url_for(url_options))}'",
        :options => options_for_javascript(lightview_options)
      }
    )

    # return js to show the lightview 
    "      
      Lightview.show(
        #{options_for_javascript(lightview_params)}   
       );
    "

  end

  # returns js to hide a lightview
  def close_lightview_js
    "Lightview.hide();"
  end

  # appends javsascript to hide a lightview to the page
  def close_lightview_rjs
    page<<close_lightview_js
  end

  private 

  # a default set of parameters for showing a lightview 
  # (top-level arguments -see comments at top of file)
  def get_default_lightview_params
    {
      :rel => "'ajax'",
    }
  end

  # a default set of options for showing a lightview 
  # (2nd-level arguments -see comments at top of file)
  def get_default_lightview_options
    {
       :autosize => true,
       :ajax => options_for_javascript({ :evalScripts => true }) # this is so any js in the displayed page is available (e.g. for autocompleter)
    }
  end

end

4. Change the get_default_lightview_params and get_default_lightview_options methods to reflect what you want your defaults to be.

5. Add a method to the application helper:

def use_lightview
    # Avoid multiple inclusions
    @content_for_lightview_css = "" 
    @content_for_lightview_js = "" 
    content_for :lightview_css do
      stylesheet_link_tag "/javascripts/lightview/css/lightview.css"
    end  
    content_for :lightview_js do
      javascript_include_tag "/javascripts/lightview/js/lightview.js"
    end
  end

6. In the application layout (views/layouts/application.rhtml), yield the appropriate contents.

<head>
  <!-- ... -->
  <%= yield :lightview_css %>
  <%= javascript_include_tag "prototype" %>
  <%= javascript_include_tag "scriptaculous" %> 
  <%= javascript_include_tag "effects" %> 
  <%= javascript_include_tag "controls" %>
  <%= yield :lightview_js %> 
  <!-- ... -->
</head>

7. Now, just use the helper methods in your views. e.g.

<% use_lightview -%>
<%= link_to_lightview(
        "link text", 
        {:controller=>'my_controller', :action=>'my_action', :id=>my_id}, #url options
        {:class => "whatever"}, # html options
        {:title => "'Add type'"}, # lightview params
        {:autosize => false, :width => 100, :height => 200} # lightview options
      )%>   

See the Lightview website for details of all the configuration options. Note that the configuration in the lightview.js file (in public/javascripts/lightview/js) is for all lightviews. If you need multiple versions of these settings, just make another lightview folder with a different name, and adapt the helper methods.

If you want to open two (or more) lightviews in succession (e.g. for a wizard), just link to another lightview from inside the first. The lightview will just resize and show the new content.

Technorati Profile

16 comments | no trackbacks

Comments

  1. Amit said 19 days later:

    In my project I have been trying to create lightbox (AJAX forms) which are non-modal -> On click on submit/update the box should fade out and the update gets reflected in the parent window. I was trying to use prototype window. I was not able to get it working. I tried using RJS template with responds_to_parent rails plugin but with no success. I read your blogs and felt u might have achieved what I am trying to get.

    Can u help me with code samples to get it working?

    I would appreciate your help !!

  2. Ric said 19 days later:

    Hi Amit.

    I’ve not used the responds_to_parent plugin, but it is possible to have forms in lightviews that cause updates in the ‘owning’ page.

    Just use a form_remote_tag in your lightview. In the controller action that handles the submit, simply use rjs to close the lightview and cause whatever dom update you require.

  3. jt said 19 days later:

    I’d like to use form parameters from the ‘owning’ page in my lightview, but not sure if this is possible. Have you attempted such a thing? Redbox was unable to handle this but your lightview helper looks promising:

    1. merge in the javascipt on the onclick event, keeping any other onclick code intact. html_options.merge!({ :onclick => (html_options[:onclick] ? ”#{html_options[:onclick]}; ” : ””) + ”#{show_lightview_js} return false;” })
  4. Ric said 19 days later:

    Not sure exactly what you mean, jt.

    You can show pretty much anything in your lightview as the result comes from an ajax call. You can send whatever parameters you like to this.

  5. jt said 20 days later:

    Thanks for the response.

    It may be that what I’m trying to do isn’t possible, but basically I’m attempting to replace a form submit button with a link that opens a lightview. In the lightview page, I would like to have access to the form parameters that were submitted. Is this possible?

  6. Ric said 20 days later:

    Pretty much anything is possible if you think laterally with the tools you have at hand.

    One option would be to make the form submit to remote (i.e. an ajax form). In the controller action that the form submits to, use rjs to launch the lightview. My helper would let you do this.

    Another option would be to add some javascript to a ‘normal’ form which collects the parameters (I’m sure prototype has some handy functions that would let you do this) and sends them to the lightview (either by constructing some html for the lightview’s content. Or by building up a relevant call to an ajax call, the response to which will return your lightview content).

  7. Neal said about 1 month later:

    With this setup, is there any way you could trigger the box to close? Most of the javascript is compressed in the source so I’m not sure how to configure that action. :/

  8. Rob Scott said about 1 month later:

    Ric,

    We’re you able to resolve the issues of getting InvalidAuthenticityTokens when using Ajax calls instead of Iframe? Unless I use protect_from_forgery and skip the actions for the lightbox, the error keeps occuring.

  9. Ric said about 1 month later:

    Neal: Check out the close_lightview and close_lightview_rjs functions in my helper.

    Rob: Not sure what you mean. It works fine for me when using ajax calls. Could you explain?

  10. Matthew said 2 months later:

    Hey there Ric -

    Thanks for sharing your code with us. Unfortunately for me, I can’t seem to make Lightview in my Rails App. I am definitely a newcomer to the programming world, so I was hoping you might have some tips for me. I realize you didn’t sign on to be tech support, but any help you could offer is greatly appreciated.

    I dropped the code in exactly as you describe it above (again – I am a newbie) – changing only the controller and the action in my helper method in the view. I am trying to show another view file from my app in the modal box.

    When I run it, nothing happens, but I do get an error from Firebug:

    Lightview is not defined

    [Break on this error] Lightview.show({href: ”/stories/...ze: true}, rel: “ajax”, title: “ajax”});”

    Any thoughts on what I need to change or modify? Again, I am using your exact code, trying to show another view in the modal dialogue.

    Cheers, Matthew

  11. Ric said 2 months later:

    Hi Matthew,

    It sounds like the lightview js is not being included properly. Do a ‘view source’ on your page and check that the head contains all the relevant files. If not, check the use_lightview method and that you’re calling it correctly.

    If you can show me an example of it not working, I’ll take a look (just post the url in the comments – if you want to keep it private, just say, and I’ll remove the url when I publish your comment).

    Cheers, Ric.

  12. Matthew said 2 months later:

    Hey Ric -

    Thanks for the response. I have been able to get Lightview to work by simply dropping in non-ruby code (as Nic describes on his website), but I’d love to get a better sense of why your solution didn’t work for me (mostly as a way of improving my Ruby skills :-)

    I am working locally right now, so I don’t have a URL I can post, but one thing I think wasn’t working for me was calling the use_lightview helper: When I changed the helper call from this:

    <% use_lightview -%>

    to this:

    <= use_lightview -> #note the equal sign

    it seemed to work. Does that make sense that it would need the equal sign?

    Anyway, thanks again for your help – I am going to push ahead with the non-embedded ruby lightview solution for now, but really like the look of your solution for the future.

    Cheers, Matthew

  13. Ric said 2 months later:

    Hi Matthew,

    Glad you found a work-around.

    You shouldn’t need the equals sign because that helper just adds content to the @content_for… instance variables. You only need the equals sign when you want a call to some erb to actually return a value (which you don’t here).

    Are you yeilding the js and css correctly? (see step 6 of my post). This is where the values of the @content_for… variables should actually get inserted into the html.

    Let me know how you get on.

    Ric

    P.S. The code on this post is lifted from a working solution, so the concept definitely works. However, there is a possibility I’ve made a typo when removing superfluous irrelevant code, though.

  14. Kris said 4 months later:

    may be of help to some:

    When passing the authenticity_token add in the paramters to an ajax request. Also, be sure to put single quotes around the get method… in Nicks example this was not quoted.

    Lightview.show({
        href:'portfolios/upload',
        width:600,
        height:400,
        title:'Modal Test',
        options:{
            autosize: true,
            ajax:{
                method: 'get',
                evalScripts: true,
                parameters: '&authenticity_token=' + encodeURIComponent( window._token )
            }
        } 
        });
    return false;
  15. Kris said 4 months later:

    Hate to add a tailing comment but felt this was necessary to add:

    with in your layout, add this line above all other js calls to save your token:

    <%= javascript_tag "window._token = '#{form_authenticity_token}'" %>

    With in the helper extend the following method to whats listed below:

    def get_default_lightview_options { :autosize => true, :ajax => options_for_javascript({ :evalScripts => true, :parameters => ”’&authenticity_token=’ + encodeURIComponent( window._token )” }) # this is so any js in the displayed page is available (e.g. for autocompleter) } end

  16. Ric said 4 months later:

    Thanks, Kris. :-) I’ve been meaning to write a post on CSRF protection.

Trackbacks

Use this link to trackback from your own site.

(leave url/email »)

   Comment Markup Help Preview comment