views:

779

answers:

3

Hi there,

I'm triying to do a complex form in Ruby On Rails. It should be divided in groups, and each group should have text fields. I want groups and text fields to be added and removed dinamically.

I already managed to get Text Fields added and removed dinamically, but I am totally unable to do the same with the groups.

This is the call to the function:

<%= link_to_function 'Add group' do |page|
page.insert_html :bottom, :groups, :partial => 'group' 
end %>

So, it should add the partial named "group" to the "groups" div, right? well, it doesn't. This is the partial:

<div id="group" class='elements'>
    <p>
     <g_title>Text Group</g_title>
     <add><%= add_text_link 'Add a text field'%></add>
     <remove><%= link_to_function 'Remove text group', "this.up('.elements').remove()" %></remove>
    </p>

    <%= render :partial => 'text' %>
</div>

If I click on the "Add new group" link, Firebug tell me there is an error like some javascript code isnt valid (the error is "missing } after property list"). But, If I remove the fourth line in the partial code (the one with the "add_text_link" function) it works perfect! and that function works perfect.

So, any idea?

Thanks for your help!

EDIT: This is the generated source code:

<form action="/collect/text" method="post"><div style="margin:0;padding:0"><input name="authenticity_token" type="hidden" value="b2zqh1op9oHbjQ33mMAWEhZcEqHoYhUIMV0uPH8F2ms=" /></div>
<div id= 'main_elements' class='elements'>
    <p><g_title>Main elements</g_title></p>

    <p><input id="Title" name="Title" size="50" type="text" /> 
     <label class="h0" for="Title">Title</label> <options>(mandatory)</options></p>
    <p><input id="Subtitle" name="Subtitle" size="50" type="text" /> 
    <label class="h0" for="Subtitle">Subtitle</label> <options>(optional)</options></p>

</div>

<div id='groups' class="groups">
    <p><add><a href="#" onclick="try {
Element.insert(&quot;groups&quot;, { bottom: &quot;&lt;p&gt;&lt;/p&gt;\n&lt;div id=\&quot;group\&quot; class='elements'&gt;\n\t&lt;p&gt;\n\t\t&lt;g_title&gt;Text Group&lt;/g_title&gt;\n\t\t&lt;add&gt;&lt;a href=\&quot;#\&quot; onclick=\&quot;try {\nElement.insert(&quot;group&quot;, { bottom: &quot;\\n&lt;div class='text'&gt;\\n\\t&lt;p&gt;\\n\\t\\tText &lt;input id=\\&quot;content\\&quot; name=\\&quot;content\\&quot; size=\\&quot;50\\&quot; type=\\&quot;text\\&quot; /&gt; Importance &lt;select id=\\&quot;t1\\&quot; name=\\&quot;t1\\&quot;&gt;&lt;option value=\\&quot;notes\\&quot;&gt;Notes&lt;/option&gt;\\n&lt;option value=\\&quot;highlighted\\&quot;&gt;Highlighted text&lt;/option&gt;\\n&lt;option value=\\&quot;normal\\&quot;&gt;Normal text&lt;/option&gt;&lt;/select&gt;\\n\\t\\t&lt;remove&gt;&lt;a href=\\&quot;#\\&quot; onclick=\\&quot;this.up('.text').remove(); return false;\\&quot;&gt;Remove text field&lt;/a&gt;&lt;/remove&gt;\\n\\t&lt;/p&gt;\\n&lt;/div&gt;&quot; });\nnew Effect.Highlight(&quot;group&quot;,{duration:0.3, startcolor:'#34d85e'});\n} catch (e) { alert('RJS error:\\n\\n' + e.toString()); alert('Element.insert(\\&quot;group\\&quot;, { bottom: \\&quot;\\\\n&lt;div class=\\'text\\'&gt;\\\\n\\\\t&lt;p&gt;\\\\n\\\\t\\\\tText &lt;input id=\\\\\\&quot;content\\\\\\&quot; name=\\\\\\&quot;content\\\\\\&quot; size=\\\\\\&quot;50\\\\\\&quot; type=\\\\\\&quot;text\\\\\\&quot; /&gt; Importance &lt;select id=\\\\\\&quot;t1\\\\\\&quot; name=\\\\\\&quot;t1\\\\\\&quot;&gt;&lt;option value=\\\\\\&quot;notes\\\\\\&quot;&gt;Notes&lt;/option&gt;\\\\n&lt;option value=\\\\\\&quot;highlighted\\\\\\&quot;&gt;Highlighted text&lt;/option&gt;\\\\n&lt;option value=\\\\\\&quot;normal\\\\\\&quot;&gt;Normal text&lt;/option&gt;&lt;/select&gt;\\\\n\\\\t\\\\t&lt;remove&gt;&lt;a href=\\\\\\&quot;#\\\\\\&quot; onclick=\\\\\\&quot;this.up(\\'.text\\').remove(); return false;\\\\\\&quot;&gt;Remove text field&lt;/a&gt;&lt;/remove&gt;\\\\n\\\\t&lt;/p&gt;\\\\n&lt;/div&gt;\\&quot; });\\nnew Effect.Highlight(\\&quot;group\\&quot;,{duration:0.3, startcolor:\\'#34d85e\\'});'); throw e }; return false;\&quot;&gt;Add a text field&lt;/a&gt;&lt;/add&gt;\n\t\t&lt;remove&gt;&lt;a href=\&quot;#\&quot; onclick=\&quot;this.up('.elements').remove(); return false;\&quot;&gt;Remove text group&lt;/a&gt;&lt;/remove&gt;\n\t&lt;/p&gt;\n\t\n\t\n&lt;div class='text'&gt;\n\t&lt;p&gt;\n\t\tText &lt;input id=\&quot;content\&quot; name=\&quot;content\&quot; size=\&quot;50\&quot; type=\&quot;text\&quot; /&gt; Importance &lt;select id=\&quot;t1\&quot; name=\&quot;t1\&quot;&gt;&lt;option value=\&quot;notes\&quot;&gt;Notes&lt;/option&gt;\n&lt;option value=\&quot;highlighted\&quot;&gt;Highlighted text&lt;/option&gt;\n&lt;option value=\&quot;normal\&quot;&gt;Normal text&lt;/option&gt;&lt;/select&gt;\n\t\t&lt;remove&gt;&lt;a href=\&quot;#\&quot; onclick=\&quot;this.up('.text').remove(); return false;\&quot;&gt;Remove text field&lt;/a&gt;&lt;/remove&gt;\n\t&lt;/p&gt;\n&lt;/div&gt;\n&lt;/div&gt;&quot; });
new Effect.Highlight(&quot;group&quot;,{duration:0.3, startcolor:'#34d85e'});
} catch (e) { alert('RJS error:\n\n' + e.toString()); alert('Element.insert(\&quot;groups\&quot;, { bottom: \&quot;&lt;p&gt;&lt;/p&gt;\\n&lt;div id=\\\&quot;group\\\&quot; class=\'elements\'&gt;\\n\\t&lt;p&gt;\\n\\t\\t&lt;g_title&gt;Text Group&lt;/g_title&gt;\\n\\t\\t&lt;add&gt;&lt;a href=\\\&quot;#\\\&quot; onclick=\\\&quot;try {\\nElement.insert(&quot;group&quot;, { bottom: &quot;\\\\n&lt;div class=\'text\'&gt;\\\\n\\\\t&lt;p&gt;\\\\n\\\\t\\\\tText &lt;input id=\\\\&quot;content\\\\&quot; name=\\\\&quot;content\\\\&quot; size=\\\\&quot;50\\\\&quot; type=\\\\&quot;text\\\\&quot; /&gt; Importance &lt;select id=\\\\&quot;t1\\\\&quot; name=\\\\&quot;t1\\\\&quot;&gt;&lt;option value=\\\\&quot;notes\\\\&quot;&gt;Notes&lt;/option&gt;\\\\n&lt;option value=\\\\&quot;highlighted\\\\&quot;&gt;Highlighted text&lt;/option&gt;\\\\n&lt;option value=\\\\&quot;normal\\\\&quot;&gt;Normal text&lt;/option&gt;&lt;/select&gt;\\\\n\\\\t\\\\t&lt;remove&gt;&lt;a href=\\\\&quot;#\\\\&quot; onclick=\\\\&quot;this.up(\'.text\').remove(); return false;\\\\&quot;&gt;Remove text field&lt;/a&gt;&lt;/remove&gt;\\\\n\\\\t&lt;/p&gt;\\\\n&lt;/div&gt;&quot; });\\nnew Effect.Highlight(&quot;group&quot;,{duration:0.3, startcolor:\'#34d85e\'});\\n} catch (e) { alert(\'RJS error:\\\\n\\\\n\' + e.toString()); alert(\'Element.insert(\\\\&quot;group\\\\&quot;, { bottom: \\\\&quot;\\\\\\\\n&lt;div class=\\\\\'text\\\\\'&gt;\\\\\\\\n\\\\\\\\t&lt;p&gt;\\\\\\\\n\\\\\\\\t\\\\\\\\tText &lt;input id=\\\\\\\\\\\\&quot;content\\\\\\\\\\\\&quot; name=\\\\\\\\\\\\&quot;content\\\\\\\\\\\\&quot; size=\\\\\\\\\\\\&quot;50\\\\\\\\\\\\&quot; type=\\\\\\\\\\\\&quot;text\\\\\\\\\\\\&quot; /&gt; Importance &lt;select id=\\\\\\\\\\\\&quot;t1\\\\\\\\\\\\&quot; name=\\\\\\\\\\\\&quot;t1\\\\\\\\\\\\&quot;&gt;&lt;option value=\\\\\\\\\\\\&quot;notes\\\\\\\\\\\\&quot;&gt;Notes&lt;/option&gt;\\\\\\\\n&lt;option value=\\\\\\\\\\\\&quot;highlighted\\\\\\\\\\\\&quot;&gt;Highlighted text&lt;/option&gt;\\\\\\\\n&lt;option value=\\\\\\\\\\\\&quot;normal\\\\\\\\\\\\&quot;&gt;Normal text&lt;/option&gt;&lt;/select&gt;\\\\\\\\n\\\\\\\\t\\\\\\\\t&lt;remove&gt;&lt;a href=\\\\\\\\\\\\&quot;#\\\\\\\\\\\\&quot; onclick=\\\\\\\\\\\\&quot;this.up(\\\\\'.text\\\\\').remove(); return false;\\\\\\\\\\\\&quot;&gt;Remove text field&lt;/a&gt;&lt;/remove&gt;\\\\\\\\n\\\\\\\\t&lt;/p&gt;\\\\\\\\n&lt;/div&gt;\\\\&quot; });\\\\nnew Effect.Highlight(\\\\&quot;group\\\\&quot;,{duration:0.3, startcolor:\\\\\'#34d85e\\\\\'});\'); throw e }; return false;\\\&quot;&gt;Add a text field&lt;/a&gt;&lt;/add&gt;\\n\\t\\t&lt;remove&gt;&lt;a href=\\\&quot;#\\\&quot; onclick=\\\&quot;this.up(\'.elements\').remove(); return false;\\\&quot;&gt;Remove text group&lt;/a&gt;&lt;/remove&gt;\\n\\t&lt;/p&gt;\\n\\t\\n\\t\\n&lt;div class=\'text\'&gt;\\n\\t&lt;p&gt;\\n\\t\\tText &lt;input id=\\\&quot;content\\\&quot; name=\\\&quot;content\\\&quot; size=\\\&quot;50\\\&quot; type=\\\&quot;text\\\&quot; /&gt; Importance &lt;select id=\\\&quot;t1\\\&quot; name=\\\&quot;t1\\\&quot;&gt;&lt;option value=\\\&quot;notes\\\&quot;&gt;Notes&lt;/option&gt;\\n&lt;option value=\\\&quot;highlighted\\\&quot;&gt;Highlighted text&lt;/option&gt;\\n&lt;option value=\\\&quot;normal\\\&quot;&gt;Normal text&lt;/option&gt;&lt;/select&gt;\\n\\t\\t&lt;remove&gt;&lt;a href=\\\&quot;#\\\&quot; onclick=\\\&quot;this.up(\'.text\').remove(); return false;\\\&quot;&gt;Remove text field&lt;/a&gt;&lt;/remove&gt;\\n\\t&lt;/p&gt;\\n&lt;/div&gt;\\n&lt;/div&gt;\&quot; });\nnew Effect.Highlight(\&quot;group\&quot;,{duration:0.3, startcolor:\'#34d85e\'});'); throw e }; return false;">Add new group</a></add></p>

    <p></p>
<div id="group" class='elements'>
    <p>
     <g_title>Text Group</g_title>
     <add><a href="#" onclick="try {
Element.insert(&quot;group&quot;, { bottom: &quot;\n&lt;div class='text'&gt;\n\t&lt;p&gt;\n\t\tText &lt;input id=\&quot;content\&quot; name=\&quot;content\&quot; size=\&quot;50\&quot; type=\&quot;text\&quot; /&gt; Importance &lt;select id=\&quot;t1\&quot; name=\&quot;t1\&quot;&gt;&lt;option value=\&quot;notes\&quot;&gt;Notes&lt;/option&gt;\n&lt;option value=\&quot;highlighted\&quot;&gt;Highlighted text&lt;/option&gt;\n&lt;option value=\&quot;normal\&quot;&gt;Normal text&lt;/option&gt;&lt;/select&gt;\n\t\t&lt;remove&gt;&lt;a href=\&quot;#\&quot; onclick=\&quot;this.up('.text').remove(); return false;\&quot;&gt;Remove text field&lt;/a&gt;&lt;/remove&gt;\n\t&lt;/p&gt;\n&lt;/div&gt;&quot; });
new Effect.Highlight(&quot;group&quot;,{duration:0.3, startcolor:'#34d85e'});
} catch (e) { alert('RJS error:\n\n' + e.toString()); alert('Element.insert(\&quot;group\&quot;, { bottom: \&quot;\\n&lt;div class=\'text\'&gt;\\n\\t&lt;p&gt;\\n\\t\\tText &lt;input id=\\\&quot;content\\\&quot; name=\\\&quot;content\\\&quot; size=\\\&quot;50\\\&quot; type=\\\&quot;text\\\&quot; /&gt; Importance &lt;select id=\\\&quot;t1\\\&quot; name=\\\&quot;t1\\\&quot;&gt;&lt;option value=\\\&quot;notes\\\&quot;&gt;Notes&lt;/option&gt;\\n&lt;option value=\\\&quot;highlighted\\\&quot;&gt;Highlighted text&lt;/option&gt;\\n&lt;option value=\\\&quot;normal\\\&quot;&gt;Normal text&lt;/option&gt;&lt;/select&gt;\\n\\t\\t&lt;remove&gt;&lt;a href=\\\&quot;#\\\&quot; onclick=\\\&quot;this.up(\'.text\').remove(); return false;\\\&quot;&gt;Remove text field&lt;/a&gt;&lt;/remove&gt;\\n\\t&lt;/p&gt;\\n&lt;/div&gt;\&quot; });\nnew Effect.Highlight(\&quot;group\&quot;,{duration:0.3, startcolor:\'#34d85e\'});'); throw e }; return false;">Add a text field</a></add>
     <remove><a href="#" onclick="this.up('.elements').remove(); return false;">Remove text group</a></remove>
    </p>



<div class='text'>
    <p>
     Text <input id="content" name="content" size="50" type="text" /> Importance <select id="t1" name="t1"><option value="notes">Notes</option>
<option value="highlighted">Highlighted text</option>
<option value="normal">Normal text</option></select>
     <remove><a href="#" onclick="this.up('.text').remove(); return false;">Remove text field</a></remove>

    </p>
</div>
</div>

</div>

<div class="button">
    <p><next><a href="#" onclick="$(this).up('form').submit(); return false;">Next Step</a></next></p>
</div>
</form>
A: 

Could you show the generated html+js source?

Lichtamberg
I've just edited the question with the code ;)
Víctor
How do you generate the other divs?The dont look well formated f.e. Element.insert("groups",..should be Element.insert(""groups"",...
Lichtamberg
A: 

You are doing it wrong. This ends up inserting an escaped partial into and escaped string in an HTML attribute. That is simply evil. And there are tons of ways it can break.

You might want to consider a simpler approach. Put the render :partial above the link_to_function 'Add a group'. Make the div#group hidden and then have the link to $('group').show().

In other words, make it hidden and then show it with JavaScript, instead of inserting it in the DOM. Simpler and less-error prone.

Stefan Kanev
But, in that way I can't add as much as I want, can I? I mean, there's only one hidden, and I want it to be able to add as many as the user wants.
Víctor
A: 

I managed to make this work. In my opinion, this is a bug in Rails because of using escape_once instead of html_escape. In other words, the first link_to_function is escaped, but the second isn't, just because the string is already escaped. This makes that the reverse operation (de-escaping or whatever) doesn't work. You could add this code to an initializer to always escape:

module ActionView
  module Helpers
    module TagHelper
      private
        def tag_options(options, escape = true)
          unless options.blank?
            attrs = []
            if escape
              options.each do |key, value|
                next unless value
                key = key.to_s
                value = BOOLEAN_ATTRIBUTES.include?(key) ? key : ERB::Util::html_escape(value)
                attrs << %(#{key}="#{value}")
              end
            else
              attrs = options.map { |key, value| %(#{key}="#{value}") }
            end
            " #{attrs.sort * ' '}" unless attrs.empty?
          end
        end
    end
  end
end

This code might break things, so use it with care. However, I've read that maybe escape_once is changed in Rails 3.0, so keep your fingers crossed ;)

Jose