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

Trackbacks

Use the following link to trackback from your own site:
http://www.ricroberts.com/trackbacks?article_id=lightview-for-modal-dialogs-on-rails&day=26&month=06&year=2008

Comments

Leave a comment

  1. Avatar
    Amit 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. Avatar
    Ric 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. Avatar
    jt 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. Avatar
    Ric 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. Avatar
    jt 19 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. Avatar
    Ric 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. Avatar
    Neal 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. Avatar
    Rob Scott 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. Avatar
    Ric 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. Avatar
    Matthew 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. Avatar
    Ric 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. Avatar
    Matthew 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. Avatar
    Ric 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. Avatar
    Kris 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. Avatar
    Kris 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. Avatar
    Ric 4 months later:

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

  17. Avatar
    Pat Baudy 5 months later:

    Hi Ric, I tried to run a lightview locally exactly the way you explained but the same error always fires up: “undefined method `link_to_lightview’”

    Is this because I’m using a trial version do it’s not possible to provide any authenticity token?

    Thx for sharing all you tutorials with us.

  18. Avatar
    Ric 5 months later:

    There are 2 separate issues here.

    1) Make sure that the LightviewHelper is accessible to your controller.

    2) Authenticity tokens are to do with Rails’ built-in CSRF protection and are not covered by the scope of this article (yet). The comments above might give you some clues here, though.

  19. Avatar
    Nizar 5 months later:

    can anyone help me how do i use this Ajax functionality in ASP.NET…... Thanks in Advance

    nisar.pp@gmail.com

  20. Avatar
    Ric 5 months later:

    Nizar,

    The general idea will be the same in any framework – the client side will remain the same.

  21. Avatar
    Andy 7 months later:

    has anyone used lightview with tinymce and run into an issue with tiny’s popups (like the html one) appear below the light view popup, any thoughs on fixing this?

    thanks

  22. Avatar
    Josh 7 months later:

    Thanks for this, it worked as described. I just thought I would mention that lightview is incredible slow in IE6. The window fade, and modal resize animations are pretty much non existent.

    Works great in FF.

  23. Avatar
    James 7 months later:

    I have tried everything I can think of before posting here, though everything I can think of is somewhat limited in my noobihood. I have followed your well-written instructions very carefully, and everything made sense. However, nothing happens when I click the lightbox link in my view. I just get a # at the end of my URL. Anybody run into this? I’m boggled.

    I am using the trial of lightbox, locally on my server, with the latest in prototype and other scripts, and I added the scriptaculous tag to the top of my effects.js file.

    The actual controller call and action is correct.

    Here is what’s called in the head of my app layout:

    <link rel="stylesheet" type="text/css" href="/javascripts/lightview/css/lightview.css" />
    
    <script src="/javascripts/prototype.js?1224562481" type="text/javascript"></script>
    <script src="/javascripts/effects.js?1233906479" type="text/javascript"></script>
    <script src="/javascripts/dragdrop.js?1224562481" type="text/javascript"></script>
    <script src="/javascripts/controls.js?1224562481" type="text/javascript"></script>
    <script src="/javascripts/application.js?1224562481" type="text/javascript"></script>
    <script src="/javascripts/lightview/js/lightview.js" type="text/javascript"></script>
    

    Thanks so much!

  24. Avatar
    Dex 7 months later:

    I have the same problem as the previous poster. I double checked that all the javascripts are included and valid. Still the firefox debug console says “Error: Lightview.show is not a function”. Because lightview.js is mostly encrypted, I have no idea how to debug this further.

  25. Avatar
    dsgn 7 months later:

    Hi Ric, i use lightview in my portfolio. I launch the javascript command from flash and it’s works on explorer and firefox but dopn’t works on Safari!

    Can you help me? I’m searching for a solution but i don’t found it!

    Thanks a lot!

    This is the script that flash launch:

    javascript:Lightview.show(‘press_1’);

    or

    javascript:Lightview.show({ href:’images/big/press/img_big_01.jpg’, rel:’gallery[press]’});

    And this is the href in html page:

    a href=”images/big/press/img_big_01.jpg” id=”press_1” class=”lightview” rel=”gallery[press]

    the site is www.insidemybox.net (v2)

  26. Avatar
    Ric 7 months later:

    dsgn: What you’re doing seems to make sense. I’m sorry – not sure about why it doesn’t work cross-browser for you without seeing all of your code. Maybe you could try Nick Stakenburg’s forums?

  27. Avatar
    dsgn 8 months later:

    Thanks a lot Rich, i’ve solved with this as3 source code:

    http://fedeweb.wordpress.com/2008/05/17/flash-e-lightview-galleria-di-immagini/

  28. Avatar
    lardawge 8 months later:

    I am having a problem with your helper. Seems that if I pass a get request method in the options for the helper, I get an error saying it is a unknown command so it then sends everything as a post request. Whatever it is doing to compile the info is messing it up. I have it working fine by using it without the helper.

  29. Avatar
    James about 1 year later:

    Hi Ric. Really appreciate you sharing your work.

    I’m trying to load a confirmation box in the app layout with something like

    <% if condition %> Open the lightview <% end %>

    Any hints on what I should put where it says “Open the lightview”? I have looked everywhere and can’t find a WORKING solution for launching the lightview when the page loads, instead of with a link! Thanks for any insight you can provide!!

  30. Avatar
    Ric about 1 year later:

    Hi James. To open the lightview, you need to just run the Lightview.show() javascript (see the commented out bit at the top of my helper module). My show_lightview helper method calls that.

Comments