Using RJS to replace innerHTML with a real live instance variable.
- by Steve Cotner
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.