Using RJS to replace innerHTML with a real live instance variable.

Posted by Steve Cotner on Stack Overflow See other posts from Stack Overflow or by Steve Cotner
Published on 2010-05-25T13:02:20Z Indexed on 2010/05/26 6:51 UTC
Read the original article Hit count: 383

I can't for the life of me get RJS to replace an element's innerHTML with an instance variable's attribute, i.e. something like @thing.name

I'll show all the code (simplified from the actual project, but still complete), and I hope the solution will be forehead-slap obvious to someone...

In RoR, I've made a simple page displaying a random Chinese character.

This is a Word object with attributes chinese and english.

Clicking on a link titled "What is this?" reveals the english attribute using RJS. Currently, it also hides the "What is this?" link and reveals a "Try Another?" link that just reloads the page, effectively starting over with a new random character.

This is fine, but there are other elements on the page that make their own database queries, so I would like to load a new random character by an AJAX call, leaving the rest of the page alone.

This has turned out to be harder than I expected: I have no trouble replacing the html using link_remote_to and page.replace_html, but I can't get it to display anything that includes an instance variable.

I have a Word resource and a Page resource, which has a home page, where all this fun takes place. In the PagesController, I've made a couple ways to get random words. Either one works fine...

Here's the code:

class PagesController < ApplicationController
  def home

    all_words = Word.find(:all)
    @random_word = all_words.rand

    @random_words = Word.find(:all, :limit => 100, :order => 'rand()')
    @random_first = @random_words[1]

  end
end

As an aside, the SQL call with :limit => 100 is just in case I think of some way to cycle through those random words. Right now it's not useful. Also, the 'rand()' is MySQL specific, as far as I know.

In the home page view (it's HAML), I have this:

#character_box
 = render(:partial => "character", :object => @random_word) if @random_word

#whatisthis
  = link_to_remote "? what is this?", :url => { :controller => 'words', :action => 'reveal_character' }, :html => { :title => "Click for the translation." }

#tryanother.{:style => "display:none"}
  = link_to "try another?", root_path

Note that the #'s in this case represent divs (with the given ids), not comments, because this is HAML.

The "character" partial looks like this (it's erb, for no real reason):

<div id="character">
  <%= "#{@random_word.chinese}" } %>
</div>

<div id="revealed" style="display:none">
  <ul>
    <li><span id="english"><%= "#{@random_word.english_name}" %></span></li>
  </ul>
</div>

The reveal_character.rjs file looks like this:

page[:revealed].visual_effect :slide_down, :duration => '.2'
page[:english].visual_effect :highlight,
                             :startcolor => "#ffff00",
                             :endcolor => "#ffffff",
                             :duration => '2.5'

page.delay(0.8) do
  page[:whatisthis].visual_effect :fade, :duration => '.3'
  page[:tryanother].visual_effect :appear
end

That all works perfectly fine.

But if I try to turn link_to "try another?" into link_to_remote, and point it to an RJS template that replaces the "character" element with something new, it only works when I replace the innerHTML with static text. If I try to pass an instance variable in there, it never works.

For instance, let's say I've defined a second random word under Pages#home...

I'll add @random_second = @random_words[2] there.

Then, in the home page view, I'll replace the "try another?" link (previously pointing to the root_path), with this:

= link_to_remote "try another?", :url => { :controller => 'words', :action => 'second_character' }, :html => { :title => "Click for a new character." }

I'll make that new RJS template, at app/views/words/second_character.rjs, and a simple test like this shows that it's working:

page.replace_html("character", "hi")

But if I change it to this:

page.replace_html("character", "#{@random_second.english}")

I get an error saying I fed it a nil object:

ActionView::TemplateError (undefined method `english_name' for nil:NilClass) on line #1 of app/views/words/second_character.rjs: 1: page.replace_html("character", "#{@random_second.english}")

Of course, actually instantiating @random_second, @random_third and so on ad infinitum would be ridiculous in a real app (I would eventually figure out some better way to keep grabbing a new random record without reloading the page), but the point is that I don't know how to get any instance variable to work here.

This is not even approaching my ideal solution of rendering a partial that includes the object I specify, like this:

page.replace_html 'character', :partial => 'new_character', :object => @random_second

As I can't get an instance variable to work directly, I obviously cannot get it to work via a partial.

I have tried various things like:

:object => @random_second

or

:locals => { :random_second => @random_second }

I've tried adding these all over the place -- in the link_to_remote options most obviously -- and studying what gets passed in the parameters, but to no avail. It's at this point that I realize I don't know what I'm doing.

This is my first question here. I erred on the side of providing all necessary code, rather than being brief. Any help would be greatly appreciated.

© Stack Overflow or respective owner

Related posts about ruby-on-rails

Related posts about partial