Forum: Ruby on Rails Ajax partial need unique ids?

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
F677fa685a2cfe8aff31f161062db3d3?d=identicon&s=25 David (Guest)
on 2009-01-26 02:44
(Received via mailing list)
Im not sure about the best way to go about dealing with the following
situation.  I have a number of popups that each contain a link "add
time" which ajaxes and renders a partial:

page.replace_html "add_appt_#{params[:day]}", :partial => 'add_appt'

and within this partial a javascript function is called which
populates an element with id "start_time".  The problem that I am
facing is that if I click and close the partial without replacing the
new partial, the javascript function finds the first element in the
DOM with id "start_time" and it doesnt work.  My question is would it
be better to rename all the start_time elements so that they are all
unique?  or would it be better to somehow replace the html upon the
closing of the popup?  or is there an even better solution that I
havent yet considered?
Aafa8848c4b764f080b1b31a51eab73d?d=identicon&s=25 Phlip (Guest)
on 2009-01-26 04:07
(Received via mailing list)
David wrote:

> DOM with id "start_time" and it doesnt work.  My question is would it
> be better to rename all the start_time elements so that they are all
> unique?  or would it be better to somehow replace the html upon the
> closing of the popup?  or is there an even better solution that I
> havent yet considered?

You should generally get in the habit of uniquifying all HTML IDs. This
is
partly because it's The Law (y'heah, right!), and partly so when you
actually
need them

Your ID should be "start_time_<%= @record.id %>", because your database
IDs are
generally unique to the system.

Further, the View Tests on your partial, and on your ajax handler,
should expect
  unique IDs. As a big digression, here's how we do that at work. (If
this code
had used the @record.id trick, it would appear in the assertions!):

    def test_render_skim_panel_cc
      site_list = sites(:doctors_site, :lawyers_site)

      render :partial => 'skim_panel',
              :locals => { :@sites => site_list,
                           :@payment_method => 'cc' }

      assert_xpath :select, :cc_skim_selection do
        site.skims.visible('cc').each do |skim|
          option = assert_xpath(:option, :value => skim.id)
          assert{ option.text == skim.name }
        end
        site.skims.visible('ach').each do |skim|
          deny_xpath :"option[ @value = '#{skim.id}' ]"
        end
      end
    end

(Parenthetically, can Merb's fixtures return a list of records from one
sites()
call like that?)

The test renders the partial that displays a panel containing a <select>
list of
skims (aka "special offers"). The panel contains only Credit Card
offers, no ACH
(Checking) offers. The first assert_xpath() call discovers the <select>
list
with the correct name. The subsequent assertions must pass inside this
list -
another option in another panel cannot cause a false positive.

The internal assert_xpath() finds each CC skim in our model, and matches
its
name. I could have written assert_xpath("option[ @id = '#{skim.id}' and
@value =
'#{skim.name}' ]"), but running this XPath as a Ruby DSL saves a lot of
ugly
string-mashing code.

Further, I can't tell from the have_xpath() documentation if you can
nest the
XPaths, or if you can use the returned node, like have_xpath().text. I
am aware
of XPath functions such as contains(), but it seems that more complex
sub-assertions, in the Ruby code, are impossible.

The last assertion denies that the wrong skims appeared in the list.
There are
also lower-level tests on the model things that the partial calls, and
there are
higher level tests on the entire page containing this partial. Any port
in a storm!

This test covers one of the Ajax handlers. It returns JavaScript that
pushes the
'skim_panel' partial into the correct container:

    def test_populate_empty_skim_list
      site_list = sites(:doctors_site, :lawyers_site)
      site_list.map(&:skims).map(&:destroy_all)
      xhr :post, :xhr_populate_skims, :sites => site_list

      assert_js_replace_html :skim_panel_cc do
        assert_xpath :select, :cc_skim_selection do
          deny_xpath '*'
        end
      end
    end

assert_js_replace_html() lexes the returned JavaScript, and finds the
Replace.element('skim_panel_cc') call. Then it finds the string payload
inside
that, evaluates this as XHTML, and preps that to work with
assert_xpath().

The assert_xpath() call detects the <select id='cc_skim_selection'>, and
the
nested deny_xpath() simply determines that it is empty.

The point of all this lexing and nesting is to pinpoint the regions that
need
test, and exclude the ones that don't. If that deny_xpath failed, its
diagnostic
would not report the entire page. It would only report the contents of
the
<select> tag around the assertion.

assert_js_replace_html() works a lot like the ARTS assert_rjs() call,
but that
only uses regular expressions. I told my colleague to use the latter
this week,
and he immediately discovered that a call like assert_rjs :replace_html,
'skim_panel_cc', /\<select id=.ach_skim_selection/ could get fooled by
an
'ach_skim_selection' from the next Replace.element() call! Our Ajax is
delightfully complex, so we don't need this kind of noise at test time.

Testing like this helps us make Ajax abuse competitive with a desktop
environment, such as forms developed with C#.

--
   Phlip
280b78a61a968391b7e07e912be102a8?d=identicon&s=25 Robert Walker (robert4723)
on 2009-01-26 18:41
Phlip wrote:
> You should generally get in the habit of uniquifying all HTML IDs. This
> is
> partly because it's The Law (y'heah, right!), and partly so when you
> actually
> need them
Well maybe it's not "The Law" as you say, but it certainly is illegal
within the context of the standard. Pages with duplicate id attributes
will not validate:

Excerpt from the W3C HTML 4.01 documentation:
---------------------------------------------
id = name [CS]
This attribute assigns a name to an element. This name must be unique in
a document.
---------------------------------------------
This topic is locked and can not be replied to.