views:

324

answers:

2

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.

A: 

The method replace_html takes a hash as its second param. I think you need to say something like:

page.replace_html "character", :text => "#{@random_second.english}"
Synthlabs
A: 

You seem to mix things up.

Either you use link_to_function, which does not go to the controller again, and can use the instance variables your initial controller has set (and just execute the rjs or javascript the manipulate the page).

Either you use link_to_remote, but then you have to define the corresponding controller-action, in your case second_character and that has got to set the object @random_second. And then each time it is called (clicked), it can set a new value.

I hope this helps.

nathanvda
This cleared it up for me. I'd been using "link_to_remote" for everything. I'll replace that in some places with "link_to_function." Here, I just needed to define a corresponding controller action, as you said, and use link_to_remote.I guess I should have realized that ":action => ..." also needed an action defined in the controller (not just an rjs template), and that that's where the instance variable could go. But I hadn't seen this explained anywhere in all the literature I've been poring over. So thanks very much for reading through my long post and pointing the way.
Steve Cotner