#74
Oct 08

Complex Forms Part 2

See how to use Javascript and RJS to add and remove form fields dynamically. This episode will build upon the previous episode allowing you to create any number of tasks in one form the same time a project is created.
Tags: forms ajax views
Download (13.7 MB, 8:54)
alternative download for iPod & Apple TV (11 MB, 8:54)
Resources
<!-- layouts/application.rhtml -->
<%= javascript_include_tag :defaults %>

<!-- projects/new.rhtml -->
<div id="tasks">
  <%= render :partial => 'task', :collection => @project.tasks %>
</div>
<p><%= add_task_link "Add a task" %></p>

<!-- projects/_task.rhtml -->
<div class="task">
<% fields_for "project[task_attributes][]", task do |task_form| %>
  <p>
    Task: <%= task_form.text_field :name %>
    <%= link_to_function "remove", "$(this).up('.task').remove()" %>
  </p>
<% end %>
</div>
# projects_helper.rb
def add_task_link(name)
  link_to_function name do |page|
    page.insert_html :bottom, :tasks, :partial => 'task', :object => Task.new
  end
end

55 comments:

AdulteratedJedi Oct 08, 2007 at 01:13

I have Mixed thoughts about this episode, useful information as usual, however I do not like the Obtrusiveness of the javascript.

Maybe you could clean it up in a later episode using something Dan Webbs excellent Low Pro Library, or indeed UJS?


Jlehman Oct 08, 2007 at 03:41

Great episode. You teased me though. You mentioned in last episode that by moving the task creation into the projects view we lost the error handling of the task model. Will you be covering that in future episodes? Could you point in a direction to learn about that. Thank you again for your great work.


James OKelly Oct 08, 2007 at 04:21

As always a well done screencast Ryan. I have no complaints about the javascript, it is the predominant scripting language of the web after all. :)

In the case of this simple single field child model you can add errors by hand to the parent model (which the form is tied to) easily


jlehman Oct 08, 2007 at 05:47

Is it a matter of passing an errors hash back to the parent model? Can you still have the additional form change to show that errors have occurred like it does when it is on it's own view page using it's own model?


Mislav Oct 08, 2007 at 07:17

Ryan,

Why did you create a ".task" DIV to remove the task? You could have just removed the paragraph. Also, "this" needs to be enclosed in a "$()" call:

$(this).up('p').remove()

This is because of IE, in which DOM elements are not extended by default.


Ryan Bates Oct 08, 2007 at 08:11

@AdulteratedJedi, I think UJS deserves its own episode. I'll consider doing one in the future. Thanks for the suggestion!

@Jlehman, there are ways to handle the validation properly, I just haven't had enough time to show it yet. Hopefully I will get around to it in the next episode.

@Mislav, I created the surrounding ".task" div so it is easier to add more fields to the task if you need to. Thanks for the tip on $(this), I'll adjust the sample code here to reflect that.


Vinícius Oct 08, 2007 at 17:26

Nice episode. I'm waiting for Part 3! :)


John Oct 08, 2007 at 19:13

I was using RJS doing similar tasks, this is a much DRY and faster approach (although the html source code is harder to read, I can live with it by gaining that much!). Using helper function for the RJS is cool. Shared forms in different viewers become cleaner by calling RJS function in the application_helper. I can't wait to see the proper way to do validations in your complex form editing episode!


jlehman Oct 09, 2007 at 03:52

This might be a preview to Ryan's railscast. I found this tidbit: http://www.railsforum.com/viewtopic.php?id=2548.

It helped me with my validation problem. Can't wait for the railscasts though. Keep up the great work Ryan.


David Parker Oct 09, 2007 at 09:41

Great last couple of Railscasts! How do you go about grace degradation with something like this?


Ryan Bates Oct 09, 2007 at 09:59

@David, good question. You can create a more traditional TasksController which handles the CRUD operations (create, read, update, destroy). You can then link to these actions on the project show page or the tasks index page. This way the user can fallback to this approach if he doesn't have javascript enabled.

Being able to add/edit multiple models at once is more for convenience and doesn't have to be the only way it is done.


Joan Oct 09, 2007 at 12:47

Great episode as always. I am officially big fan of Ryan's casts.

If by any chance, can you make an episode about creating/consuming RESTful web services?

Big thanks for your generous contribution to rails community.


Erik Oct 09, 2007 at 15:09

I'm trying to do this but with a file_field instead of a text_field but I wont get it to work. When a user input error occurs and the new-template is rendered again my file fields are gone… I understand that I will probably have to use some other kind of techinque here, but which? Any clues?


Erik Oct 09, 2007 at 15:13

Actually that link jlehman posted gave me a clue.


emmanuel Oct 09, 2007 at 16:31

Hey Ryan, any reason why those video do not work on the iphone? I keep on downloading them and they either appear as audio only when getting the ipod version or the video is not compatible when using the itunes version. Any idea?

Thanks


Richard Oct 09, 2007 at 19:23

For a future topic I'd like to see how to move seamlessly between a show and edit action. This is a pretty common in the real world - users browses though content and then goes into edit mode. Can you recycle a bunch of form elements for the show view and lock them somehow?


ari gold Oct 09, 2007 at 20:11

as a high school teacher who is working with rails to develop a curricular tool (slowly but surely) your screencasts have proven invaluable. both in terms of current tasks and future ideas.

but as a high school music teacher i didnt realize how valuable the META lesson was so, if you dont mind ryan: how do you make the screencasts? do you use snapzprox and Mouseposé?

since yours look and sound so good (nice voice, btw) i thought i'd ask before i went out and spent the money.

ryan, you're the tippy tops. the service you provide is up there with farmers. no joke...


Arthur Spooner Oct 10, 2007 at 01:03

To remove the text field you should also call
$(this.parentNode).remove() and it will remove the p node.


george.anderson Oct 10, 2007 at 07:12

@ari: http://railscasts.com/about


Ryan Bates Oct 10, 2007 at 08:07

@Erik, I posted a tutorial on railsforum.com a while back which uses a different technique. It's not nearly as clean but it may handle the file field better:

http://railsforum.com/viewtopic.php?id=1065

@emmanuel, hmm, the iPod version of the videos should work on the iPhone. Are you certain that's the one you are trying? I think others have gotten it to work so I'm not sure what could be the problem.

@Arthur, I want to remove a tag higher up than the "p" tag so it is easier to add more fields to the tack if need be.


erv2 Oct 10, 2007 at 08:17

Very good episode, looking forward to the error handling. Also, how about adding draggable list to arrange the order of the tasks?


Skyblaze Oct 10, 2007 at 10:18

Amazing. I used the ajax version from the rayn's railsforum tutorial, but it really is a code mess :) This non-ajax variation is intuitive and clean. I will wait for the validation and edit tips.
Two other things...if i want to start with 0 tasks field on the view and let the user adds field as it wants how can i do that?
Is it possibile to download somewhere the episodes code?


Ryan Bates Oct 10, 2007 at 11:34

@erv2, making them draggable is probably possible, but out of the scope of this tutorial. You may want to post on railsforum.com about it.

@Skyblaze, if you don't build any tasks in the "new" action of the controller it should start with zero and still work fine. I honestly haven't tested it yet though.

I'll post the full code after the final episode.


ari gold Oct 10, 2007 at 11:45

@george: thanks a bunch..

believe it or not, i looked. not used to it on the right, under "navigation".. plus the 9th graders went nuts that day, frying my brain.


Michael Oct 10, 2007 at 11:58

I can't seem to get this to work whenever you go back and reload the page and try to add more and submit. When you go back and reload the tasks will load up correctly and will have input names of "project[task_attributes][5][name]". When when you submit this you get an error: "undefined method `stringify_keys!' for "5":String. What's worse, if you reload the page and then try and add more tasks, you get this error: "Conflicting types for parameter containers. Expected an instance of Array, but found an instance of Hash. This can be caused by passing Array and Hash based paramters qs[]=value&qs[key]=value." Any suggestions? Thanks.


Ryan Bates Oct 10, 2007 at 13:02

@Michael, this episode was just for the creation form, not for editing the project. I'll be addressing editing in the next screencast which will provide a solution to this problem.


Michael Oct 10, 2007 at 15:36

Great! Thanks for the quick reply ryan, keep up the good work!


Emmanuel Oct 11, 2007 at 10:08

@Ryan

I took the alternative download and it worked fine. but if I take the ipod RSS/Itunes subscribe, I can see all the episode as a podcast but they are audio only. So I have to download manually all the movie rather than through itune (which is fine with me)

Also, I have a question for you, How do you deal with the root controller not showing the same page for 2 different urls

If I look at rubyonrails.org I can see the following urls rendering the same page which can be annoying (especially for search engine as they see it as a different url)

rubyonrails.org/
rubyonrails.org/index
rubyonrails.org/index.html

but on railscast ( I am not sure how you named your default controller, I cannot see a page for railscasts.com/index.html


Ryan Bates Oct 12, 2007 at 08:22

@Emmanuel, thanks for letting me know. I'll try to fix the ipod feed soon.

Regarding the root URL, I just have a home controller with this route:

map.home '', :controller => 'home'

I think what you're seeing is how Rails behaves. I'm guessing rubyonrails.org either isn't running on Rails or has the root index.html page cached and their web server is interpreting that.


scott tallarida Oct 12, 2007 at 09:46

LOVE the casts...so helpful. I got this one working great but am having trouble getting the update to work since the model assumes the tasks are being "created". Any advice?


TheCompWiz Oct 12, 2007 at 12:17

I wish I could get a sneak peek into the next episode... I've tried to implement the editing functionality... and got only so far before hitting a brick wall. HOW DO YOU MAKE EVERYTHING LOOK SO EASY!?!!?! The moment I watch a rails casts... I slap my forehead & say... why didn't I think about that?


Ryan Bates Oct 12, 2007 at 14:53

@scott & TheCompWiz, there's some gotchas involved in getting the editing part to work. I'll explain everything in the next episode coming to you on Monday. :)


piter Oct 15, 2007 at 05:02

Hello,

Great as always, but I have suggestion to all who care about W3C standards. If you use "project[task_attributes][]" in fields_for, you will get autogenerated id for tags with brackets which is not allowed as id. It is possible to assign own id by :id => "project_task_attributes" or something similar. Just remember --- id must be uniq.


firefly Oct 15, 2007 at 08:10

thanks for this awesome screencast.


nicolash Oct 15, 2007 at 09:50

hey piter was faster then me ...
ok, about the ids... If you think about it giving unique ids will get really difficult as soon as you start adding and removing new rows. You could remove the id totaly but the other problem is that you should use labels for your fields. If you go for label followed by field you would need the id for the for-attribute. So the only decent way to go (beside some complicated js that guaranties unique ids) is to go by the seldom used possibility of putting the field inside of the label - so you don't have a need for the for-attribute <label><input/></label>


piter Oct 16, 2007 at 04:44

Indeed, putting field inside of the label is good practice, but AFAIR Internet Explorer doesn't respect such syntax. Correct me if i'm wrong...


Tal Oct 22, 2007 at 22:02

When, and only when, I add the JS link to ADD the partial i get this error from my partial:
`@raid[attendee_attributes]' is not allowed as an instance variable name


Bu®majaM Oct 23, 2007 at 05:25

Hi Ryan,

I want to thank you a lot for your railscasts. They helped me quick start with Rails.

Regarding this episode, I have problem with remove js call in IE6 (FF works just fine). Actually, I think insert_html makes problem because even alert('something') doesn't work when it is put instead of 'this.up('something').remove()'. Error that IE 'raises' is 'Object doesn't support this property or method'.

Do you have any idea how to solve this problem please?


Bu®majaM Oct 23, 2007 at 05:29

Cancel that. It works now with code from this page '$(this).up('something').remove()'

Tnx anyway for these casts and code snippets :)


Nathan Youngman Nov 05, 2007 at 14:07

@Ryan Bates: Thanks for showing how to do this client-side. The idea of doing server requests just seemed silly. You make me luv Rails again. :-)


ben Nov 22, 2007 at 23:54

This is great, but I'm only getting one record in my child table added, and the second one is not - can you explain somewhere how the tables are related?

Sorry, I'm very new to rails, and only testing really, but I'm a little bit stuck - I will move onto part 3 in the meantime.


Anks Jan 03, 2008 at 09:50

Hello,
I have been trrying to make this work but will forms for people ans organisation instead of projects and tasks.
<%fields_for "person[organisation_attributes][]",organisation do |org_form|%>

<div id = "organisation"><%=render :partial=>'organisation_with_index_nil_for_person',:collection=>@person.organisations,:locals=>{:org_form=>org_form}%>

In the fields_for section i am getting an error message "undefined local variable or method organisation."

in my Person model i have
class Person < ActiveRecord::Base
has_and_belongs_to_many :organisations

My Organisation model is as
class Organisation < ActiveRecord::Base
has_and_belongs_to_many :people

Please can anyone help me to figure out the mistake.Have been trying since long.


gbonny Feb 03, 2008 at 11:32

@Anks
I had the same problem.
Apparently render :partial passes the partial name as the variable name, not the child model name in the collection, as one would assume.

render :partial => 'organisation_with_index_nil_for_person'

is sending a variable named organisation_with_index_nil_for_person to your partial, but your are expecting a variable named organisation.

Try renaming your partial to _organisation.rhtml and call it with:

<%= render :partial => 'organisation', :collection => @person.organisations %>

It worked for me. Further reference, see http://dev.rubyonrails.org/ticket/5524


Sigfrid Feb 07, 2008 at 17:44

Hi Ryan (and all the rails guys out there)
after some trouble I have finally gotten it and now I can add and remove articles. What I would like to know now is showing the remove link only if the article is not the first so I surrounded the link_to_function helper with a if but basically I don't know how I can check out if it is the first element of the collection.

Could you please help me with this?

Thanks and have a nice day!


Joe Feb 11, 2008 at 05:42

Hi,

i'm having problems with the remove function. i can add new items, but when i click on the remove, i get a javascript error: $(this).up is not a function.

am i missing something?

thanks for your help.


Joe Feb 12, 2008 at 18:52

Oops, my bad. realised i was using an old version of prototype. i used Element.remove(this.parentNode) instead.

cheers!


Harrel Mar 02, 2008 at 07:15

Is there any reason the code you have on this screen cast shouldn't work with rails 2.0?

My code matches your code to the character and i get an rjs error.


nacho Mar 09, 2008 at 12:52

Hi Harrel,
I get an rjs error too using rails 2.0. Can anybody help? Mine says ReferenceError: Insertion is not defined


nacho Mar 09, 2008 at 14:20

I found in a german forum that you only need to add this to the application.rhtml file:
<%= javascript_include_tag :defaults %>
And it works!!


Sig Mar 09, 2008 at 22:20

Hi guys,
anyone has used a dynamic field text with pagination for display. I mean after adding N fields a pagination system starts working in order to keep everything ordered within the view.

THANKS.


Brian Mar 11, 2008 at 21:42

For those getting the error on IE: 'Object doesn't support this property or method'. As you probably saw it is being caused by this line: 'this.up('.something').remove()'

I spent about 3 hours tracking down this bug and its occurring because the scriptaculous prototype library (which provides functions like up, down, remove, etc) doesn't work the same way in IE.

For details read the bottom of this page:
http://prototypejs.org/learn/extensions

I can't believe this isn't documented better?!? Clearly lots of people are using embedding their own javascript into RJS.

It seems you have to use the Element.extend(something) function or the shorthand version of it $('something') so get those DOM objects to use scriptaculous functions.

So you can write the above line of code like this:
'Element.extend(this).up($('.something')).remove()'

And it works in all browsers. This seems pretty ridiculous so if someone has a better solution to this please post it.


j Mar 29, 2008 at 06:57

I was wondering if anyone else was having issues with the nested fields for? if i use a boolean in the nested fields it creates multiple records (and thus causes validation issues)


jetmedia Apr 15, 2008 at 07:02

Somehow everyone that has gotten it to work has decided to move on without sharing their discoveries. If only there were more people like Ryan


Lauren May 09, 2008 at 17:59

Hi Brian...like everyone else, those Railscasts are awesome.

When I try to add a task, it works except that for some reason, the HTML that's inserted is putting escape characters for the first < and the last >, which results in showing the HTML code (as if displaying a text paragraph) in the browser rather than the input field.

Here is an example of what's inserted:

&lt;input id="continuum_stage_attributes__name"
name="continuum[stage_attributes][][name]" size="30" type="text" /&gt;

Any ideas?


Mike May 16, 2008 at 20:30

Great webcast, as usual. I following along successfully until the sequence that moves the fields_for statement to the partial and creates the link_to_function statement in the view. Now I an RJS error. Under IE6 I get an unspecified object error; under Firefox I get "Reference error: Insertion is not defined". In the interpolated javascript statement, all the html brackets are translated to escape sequences, e.g. <a href="#" onclick="try {
new Insertion.Bottom(&quot;tasks&quot;, &quot;\n \u003Cp\u003ETask: \u003Cinput id=\&quot;project_task_attributes__name\&quot; name=\&quot;project[task_attributes][][name]\&quot; size=\&quot;30\&quot; type=\&quot;text\&quot; /\u003E \u003C/p\u003E\n\n&quot;);
} ...

I'm using Rails 2.0.2 and Ruby 1.8.6. Can someone suggest where I may have lost my way or how I can go about tracking down the problem, please?

Add your comment:

(required)

(not displayed)

(SKIP THIS ONE)


(required)

subscribe:
sponsored by:
if you want to help:
required:
Get Quicktime Player