How to get dynamically created inputs from html form back to


#1

Thank you in advance.

Although I have many years of experience in general, including cross-
platform processing, I am not an HTML/Javascript programmer. As a
result, I do not have certain specific baseline skills and/or knowledge
that are presumed in the Rails and Ajax documentation.

I am experienced with DOM manipulations, so the bare mechanics of
manipulating the browser GUI via Javascript and the DOM tree are self
evident. However, this does not get the input data back to the server
side of the application.

The immediate question that have is this: When I dynamically add rows
of inputs to a table, how do get the values entered by the user into
those rows back to the rails application?

The specific example is a screen for editing a question with its list of
answers as a sovereign application. Both code snips are from
views/question/_form.rhtml.

I realize that I do not have the server side of the interface
represented from controllers/question_controller.rb. In part, this is
because I have a gap in my knowledge about what the HTTP post will
provide back to the ruby app - it is presumed to be self evident in the
books I have available.

The following javascript is the code that inserts the new row into the
table in the browser context (not the backing RDBMS):

function insertAnswer () {
table = document.getElementById (“answersTable”);
body = document.getElementById (“answersBody”);

row = document.createElement(“tr”);
row.setAttribute(“id”,"");
// need to build a Ruby friendly naming scheme for
// elements so they can be passed as parameters
data = document.createElement(“td”);
row.appendChild(data);
data = document.createElement(“td”);
row.appendChild(data);
data = document.createElement(“td”);
node = document.createElement(“input”)
node.setAttribute (“type”,“checkbox”);
data.appendChild(node);
row.appendChild(data);
data = document.createElement(“td”);
node = document.createElement(“input”)
node.setAttribute (“type”,“text”);
node.setAttribute (“size”,“3”);
data.appendChild(node);
row.appendChild(data);
data = document.createElement(“td”);
node = document.createElement(“input”)
node.setAttribute (“type”,“text”);
data.appendChild(node);
row.appendChild(data);
data = document.createElement(“td”);
btn = document.createElement(“button”)
btn.setAttribute (“name”,“linkPresentation”);
btn.setAttribute (“type”,“button”);
btn.setAttribute (“onClick”,“linkAnswerToPresentation”);
node = document.createElement(“img”);
node.setAttribute (“src”,"/images/file-document.png");
btn.appendChild(node);
data.appendChild(btn);
row.appendChild(data);

data = document.createElement(“td”);
node = document.createElement(“input”)
node.setAttribute (“type”,“text”);
data.appendChild(node);
row.appendChild(data);
data = document.createElement(“td”);
btn = document.createElement(“button”)
btn.setAttribute (“name”,“linkAudio”);
btn.setAttribute (“type”,“button”);
btn.setAttribute (“onClick”,“linkPresentationToAudio()”);
node = document.createElement(“img”);
node.setAttribute (“src”,"/images/file-audio.png");
btn.appendChild(node);
data.appendChild(btn);
row.appendChild(data);

data = document.createElement(“td”);
node = document.createElement(“input”)
node.setAttribute (“type”,“text”);
data.appendChild(node);
row.appendChild(data);
data = document.createElement(“td”);
btn = document.createElement(“button”)
btn.setAttribute (“name”,“linkArt”);
btn.setAttribute (“type”,“button”);
btn.setAttribute (“onClick”,“linkPresentationToArt()”);
node = document.createElement(“img”);
node.setAttribute (“src”,"/images/file-art.png");
btn.appendChild(node);
data.appendChild(btn);
row.appendChild(data);

data = document.createElement(“td”);
btn = document.createElement(“button”)
btn.setAttribute (“name”,“answer”);
btn.setAttribute(“id”,“delete_”+table.rows.length);
btn.setAttribute (“type”,“button”);
btn.setAttribute (“onClick”,“deleteAnswer(this)”);
node = document.createElement(“img”);
node.setAttribute (“src”,"/images/trashcan_empty.png");
btn.appendChild(node);
data.appendChild(btn);
row.appendChild(data);

body.appendChild(row);
}

The equivalent RHTML fragment that builds the table from previosly
stored data is:

<%
answers = Answers.find (:all, :conditions => "parent_id = " +
@question.id.to_s)
for answer in answers
presentation = Presentations.find_by_id
(answer.presentation_id.to_s);
-%>


<%= answer.id %>
<%= answer.seq %>
<%= answer.iscorrect %>
<%= text_field_tag ‘answer.points’, answer.points %>
<%= text_field_tag ‘presentation.text’, presentation.text %
  <td><input name="loadText" type="img" value="file..."

src="/images/file-document.png" onClick=“linkPresentation()”/>

<%= presentation.audio %>

<%= presentation.visual %>



<% end %>

#2

If you name your input tags with a [] suffix, the data returned in
your ‘params’ hash will be returned in an array. For example, if the
following HTML is submitted back to your controller:

If you look in params[‘foo’] you will see [‘a’, ‘b’, ‘c’]

At a bare level that is the way it works. You can get even cleverer
by using named fields in your params to better encapsulate your data -
you might want to look in the source files
rails/action_pack/lib/action_controller/cgi* to examine how the params
hash is constructed.

Cheers!

-David F.


#3

This isn’t a direct answer to your question (someone else handled that
already), but on the javascript / DOM generation side of things, I
just wanted to point you in the direction of the super handy
Script.aculo.us Builder class:

http://wiki.script.aculo.us/scriptaculous/show/Builder

You can really cut down the lines needed to build those table rows…
its a nice thing :slight_smile:


Scott B.
Electro Interactive, Inc.
http://www.ElectroInteractive.com
Blog: http://synthesis.sbecker.net


#4

Thank you!

This is too easy … and it is beginning to make sense.

I will definitely look at the cgi* files to see what is going in
internally.


#5

Scott B. wrote:

This isn’t a direct answer to your question (someone else handled that
already), but on the javascript / DOM generation side of things, I
just wanted to point you in the direction of the super handy
Script.aculo.us Builder class:

http://wiki.script.aculo.us/scriptaculous/show/Builder

You can really cut down the lines needed to build those table rows…
its a nice thing :slight_smile:

Thanks, that’s useful to know.

A much quicker method that often works for building complex structures
is to just clone one that already exists in the page (either a visible
one or a hidden prototype), and then insert the dynamic parts in the
appropriate places.


We develop, watch us RoR, in numbers too big to ignore.


#6

Looks very interesting … I’ll certainly consider it.

Thanks!


#7

On 3-jun-2006, at 9:31, Scott B. wrote:

This isn’t a direct answer to your question (someone else handled that
already), but on the javascript / DOM generation side of things, I
just wanted to point you in the direction of the super handy
Script.aculo.us Builder class:

http://wiki.script.aculo.us/scriptaculous/show/Builder

You can really cut down the lines needed to build those table rows…
its a nice thing :slight_smile:

Holy bejezus. Never thought there is such a thing there. Many thanks,
my sunday is spent :slight_smile:


Julian ‘Julik’ Tarkhanov
please send all personal mail to
me at julik.nl


#8

On 4-jun-2006, at 4:40, David J. wrote:

Looks very interesting … I’ll certainly consider it.

Thanks!

I don’t know what is going on with insertions, but I know that an
input DELETED through the DOM in Safari is still going to get
submitted. I would excercise double alertness if I was on Safari in
your place.


Julian ‘Julik’ Tarkhanov
please send all personal mail to
me at julik.nl


#9

I would hope that it would be submitted - without a submission I would
not know to delete it from the database.

I will certainly construct a full suite of tests to ensure that oddities
of all of the browsers are being covered.


#10

HTML check box tags do not POST a value to the server if they are
unchecked, so in this case the behaviour you’re experiencing is
correct, if undesired.

Have a look at the API call for check_box
(http://ap.rubyonrails.com/classes/ActionView/Helpers/FormHelper.html#M000193)
to see how you can work around this. The cliff notes version is that
you add a second hidden input field to post a value for unchecked
checkboxes like so:

This will solve your problem - instead of [“on”] you will get
[“off”,“on”,“off”]

Cheers!

-David F.


#11

I spoke too soon. This works for all text input elements, but it
appears that this does not work if the element is a checkbox.

I’ll have to check the cgi* source code still, but someone might be able
to answer this more quickly.

Is this a bug in rails or “by design” behavior?

logged output of submission:

note that iscorrect should contain ["",“on”,""], at the very least.

{“iscorrect”=>[“on”], “audiofilename”=>[“xxx”, “”, “yyy”],
“commit”=>“Save”, “imagefilename”=>["", “”, “”], “points”=>["", “”, “”],
“action”=>“update”, “id”=>“16”,
“question”=>{“explanation_presentation”=>{“textvalue”=>""},
“tip_presentation”=>{“textvalue”=>“Matthew 1:18”}, “points”=>“5”,
“time_allowed”=>“30”, “presentation”=>{“textvalue”=>“Who was found to be
with child through the Holy Spirit?”}, “parent”=>{“name”=>“Matthew (red
level)”}}, “controller”=>“question”, “presentationtext”=>["", “aaa”,
“”], “parent_id”=>{“1”=>""}}

[“on”]
["", “aaa”, “”]
[“xxx”, “”, “yyy”]
["", “”, “”]

My environment is:
WEBrick 1.3.1
ruby 1.8.4 (2005-12-24) [i386-linux]

excerpt from Ruby controller:

def update

p params

isCorrect=params['iscorrect']
presentationText = params['presentationtext']
audioFileName = params['audiofilename']
imageFileName = params['imagefilename']
p isCorrect
p presentationText
p audioFileName
p imageFileName

end

javascript excerpt:

function insertAnswer () {
table = document.getElementById (“answersTable”);
body = document.getElementById (“answersBody”);

row = document.createElement(“tr”);
row.setAttribute(“id”,"");
// need to build a Ruby friendly naming scheme for
// elements so they can be passed as parameters
data = document.createElement(“td”);
row.appendChild(data);
data = document.createElement(“td”);
row.appendChild(data);
data = document.createElement(“td”);
node = document.createElement(“input”)
node.setAttribute (“type”,“checkbox”);
node.setAttribute (“name”,“iscorrect[]”)
data.appendChild(node);
row.appendChild(data);
data = document.createElement(“td”);
node = document.createElement(“input”)
node.setAttribute (“type”,“text”);
node.setAttribute (“size”,“3”);
node.setAttribute (“name”,“points[]”)
data.appendChild(node);
row.appendChild(data);
data = document.createElement(“td”);
node = document.createElement(“input”)
node.setAttribute (“type”,“text”);
node.setAttribute (“name”,“presentationtext[]”)
data.appendChild(node);
row.appendChild(data);
data = document.createElement(“td”);
btn = document.createElement(“button”)
btn.setAttribute (“name”,“linkPresentation”);
btn.setAttribute (“type”,“button”);
btn.setAttribute (“onClick”,“linkAnswerToPresentation”);
node = document.createElement(“img”);
node.setAttribute (“src”,"/images/file-document.png");
btn.appendChild(node);
data.appendChild(btn);
row.appendChild(data);

data = document.createElement(“td”);
node = document.createElement(“input”)
node.setAttribute (“type”,“text”);
node.setAttribute (“name”,“audiofilename[]”)
data.appendChild(node);
row.appendChild(data);
data = document.createElement(“td”);
btn = document.createElement(“button”)
btn.setAttribute (“name”,“linkAudio”);
btn.setAttribute (“type”,“button”);
btn.setAttribute (“onClick”,“linkPresentationToAudio()”);
node = document.createElement(“img”);
node.setAttribute (“src”,"/images/file-audio.png");
btn.appendChild(node);
data.appendChild(btn);
row.appendChild(data);

data = document.createElement(“td”);
node = document.createElement(“input”)
node.setAttribute (“type”,“text”);
node.setAttribute (“name”,“imagefilename[]”)
data.appendChild(node);
row.appendChild(data);
data = document.createElement(“td”);
btn = document.createElement(“button”)
btn.setAttribute (“name”,“linkArt”);
btn.setAttribute (“type”,“button”);
btn.setAttribute (“onClick”,“linkPresentationToArt()”);
node = document.createElement(“img”);
node.setAttribute (“src”,"/images/file-art.png");
btn.appendChild(node);
data.appendChild(btn);
row.appendChild(data);

data = document.createElement(“td”);
btn = document.createElement(“button”)
btn.setAttribute (“name”,“answer”);
btn.setAttribute(“id”,“delete_”+table.rows.length);
btn.setAttribute (“type”,“button”);
btn.setAttribute (“onClick”,“deleteAnswer(this)”);
node = document.createElement(“img”);
node.setAttribute (“src”,"/images/trashcan_empty.png");
btn.appendChild(node);
data.appendChild(btn);
row.appendChild(data);

body.appendChild(row);
}
a

1
)

h<

()");
y

Ù
);

t")

")

t")

}

utt
butt

d");
Q

taÁ

h<

;


}


#12

I was beginning to suspect that it might be a function of the HTML
checkbox standard - thanks for the confirmation.

I have seen this idiom frequently, and it seemed like an odd thing to
not catch in Rails testing. It makes sense now.

Thanks again for the feedback.


#13

The idiom suggested resulted in extra values being returned under
Firefox (all of the hidden values were always returned).

Here is the idiom I ended up using for checkboxes:

function checkboxClick(element) {
inputValue = element.firstChild

if (inputValue.getAttribute (“value”) == “off”)
{
inputValue.setAttribute (“value”, “on”)
element.setAttribute (“background”,"/images/cbChecked.png");
}
else {
inputValue.setAttribute (“value”, “off”)
element.setAttribute (“background”,"/images/cbUnchecked.png");
}
}

function insertRow () {
table = document.getElementById (“answersTable”);
body = document.getElementById (“answersBody”);
row = document.createElement(“tr”);

// other non-checkbox stuff not shown …

data = document.createElement(“td”);
data.setAttribute(“id”,“correct_”+table.rows.length);
data.setAttribute (“background”,"/images/cbUnchecked.png");
node = document.createElement(“input”)
node.setAttribute (“type”,“hidden”);
node.setAttribute (“name”,“iscorrect[]”)
node.setAttribute (“value”,“off”)
node.setAttribute (“style”, “background-repeat: no-repeat”)
data.appendChild(node);
data.setAttribute (“onClick”,“checkboxClick(this)”);
row.appendChild(data);

// other non-checkbox stuff not shown …

body.appendChild (row)
}