Re: Multiple child models in a dynamic form

Oh it was missing quotes around the html, that's an easy one to miss ^^

Yeah Rails' generated js can be a little opaque. But I'm glad to see you got it worked out!

Re: Multiple child models in a dynamic form

Thanks for the great tutorial marsvin, a similar method has been used here by alloy in his example application http://github.com/alloy/complex-form-ex … ee/master.
My question is this, with the date hack you have to use with this method, is it actually any better than the method that Ryan Bates describes in his complex forms screencasts? Is it a step forward?

Thanks very much

Re: Multiple child models in a dynamic form

Marsvin,

Thanks for the great tutorial, especially since it brings things up to Rails 2.3. I was wondering if it is possible to implement the same thing for a child in a has_and_belongs_to_many relationship. Therefore 'tasks' in this case, could be part of multiple 'project's, and the delete facility would simply remove the association rather than delete the 'task'.

What sort of things should be changed if this can be the case?

Re: Multiple child models in a dynamic form

pingu wrote:

Thanks for the great tutorial marsvin, a similar method has been used here by alloy in his example application http://github.com/alloy/complex-form-ex … ee/master.
My question is this, with the date hack you have to use with this method, is it actually any better than the method that Ryan Bates describes in his complex forms screencasts? Is it a step forward?

Thanks very much

The example in that repository you linked actually uses the same method, just slightly more complex since it uses 2 tier nesting. But the ids are generated the same way (I actually took part of my tutorial from that example.)

It's in public/javascripts/application.js:

replace_ids = function(s){
  var new_id = new Date().getTime();
  return s.replace(/NEW_RECORD/g, new_id);
}
# ... etc

Ryan's complex forms railscasts use the old (pre-Rails 2.3) system which didn't have the accepts_nested_attributes_for helpers. His way it's not necessary to define ids for new records the same way but it does mean you have to do a lot more by hand.

pootsy wrote:

I was wondering if it is possible to implement the same thing for a child in a has_and_belongs_to_many relationship. Therefore 'tasks' in this case, could be part of multiple 'project's, and the delete facility would simply remove the association rather than delete the 'task'.

Good question, I'm not 100% sure. I know it will work with has_many :through associations, so my guess would be yes, but I haven't used habtm in a long time. In any case normally it would simply delete the related object (the task itself) rather than the record in the join table.

You can use accepts_nested_attributes_for for a join model, if you use has_many :through instead of habtm. It is after all just another has_many relationship. That said you'll have to do some trickery like an extra level of nesting (the actual task form in the join record's form) in that case of course.

Re: Multiple child models in a dynamic form

Hi Marsvin, I gave up trying to make a gracefully degrading version.
I got some way and was able to make things work for editing but I struggled with passing the params around in the controllers.

So I'm trying your way now but I'm wanting to use jQuery rather than jaascript.

As both these are new to me I find that I am struggling with the correct jQuery syntax to use for the inseting of the HTML

page << "$('maintenance_form').insert({ bottom: '#{escape_javascript(html)}'.replace(/NEW_RECORD/g, new Date().getTime()) });"

Any pointers as to how to do this would be really appreciated

James

Last edited by jamesw (2009-08-04 20:36:30)

What you want and what you need are too often not the same thing!
When your head is hurting from trying to solve a problem, stop standing on it. When you are the right way up you will see the problem differently and you just might find the solution.
(Quote by me 15th July 2009)

Re: Multiple child models in a dynamic form

That's a good question. I'm afraid I still haven't dug in to jquery though I've been meaning to do so for a while.

But after a cursory glance at the docs, I'd say .after is what you need. This is just a stab in the dark so you may have to fiddle with it but something like this?

"$('.task:last').after( '#{escape_javascript(html)}'.replace(/NEW_RECORD/g, new Date().getTime()) )"

Re: Multiple child models in a dynamic form

Thank you marsvin.

This worked nicely

  def add_address_link(form_builder)
    link_to_function 'add address' do |page|
      form_builder.fields_for :administrator_addresses, Address.new, :child_index => 'NEW_RECORD' do |f|
        html = render(:partial => 'address_form', :locals => { :f => f })
        page << "jQuery('#maintenance_form').append('#{escape_javascript(html)}'.replace(/NEW_RECORD/g, new Date().getTime()))"
      end
    end
  end

Now I have that working I need to change things around somewhat and I'm kinda hoping you might be able to point me in the right direction.
I think a more appropriate place for this problem is on the main views and controllers forum rather than cluttering up your tutorial but I have a really neat idea that you are welcome to add to this tutorial if I can get it to work (With your help :-))

-edit -
new topic started in the controllers and views forum here
http://railsforum.com/viewtopic.php?pid=105737#p105737
-edit-

Last edited by jamesw (2009-08-05 14:42:15)

What you want and what you need are too often not the same thing!
When your head is hurting from trying to solve a problem, stop standing on it. When you are the right way up you will see the problem differently and you just might find the solution.
(Quote by me 15th July 2009)

Re: Multiple child models in a dynamic form

OK there is a bug in rails 2.3.2 and 2.3.3. I don't know about other versions but at the time of posting this 2.3.3 is the current stable release.

Using accepts_nested_attributes on a has_many :grandchildren, :through => :parent, :source => :children produces a Cannot modify association 'Grandparent#grandchildren' because the source reflection class 'Child' is associated to 'Parent' via :has_many.

I'll do some more investigating and report the bug once I have some more scenarios but for the minuite if anyone is thinking \about doing this it won't work.

What you want and what you need are too often not the same thing!
When your head is hurting from trying to solve a problem, stop standing on it. When you are the right way up you will see the problem differently and you just might find the solution.
(Quote by me 15th July 2009)

Re: Multiple child models in a dynamic form

Thanks for the great tutorial. I learned a lot about nested form builders.

I wonder, is it possible to use this approach to edit a subset of an object's attribute collection? E.g. let's say you add another attribute to Task, called "performer_id", thus making a project's tasks selectable by who performs the tasks... and you want the user to be able to view/edit only tasks with the same performer_id. (Kind of a contrived example but I thought it would be best to phrase the question in your app's terms rather than mine smile )

I got my app mostly working with this approach, but not quite, and I suspect that the tasks not shown on the edit page will get blown away when @project.update_attributes(params[:project]) is called, since the tasks that come in via params are only a subset of the project's tasks in the DB. Is this correct?

Would it be better to have the app work on the full set of tasks (@project.tasks rather than, say, @project.tasks.for_performer(2)), but on the edit page, hide the ones with an "uninteresting" performer_id?

ETA: After some more experimentation I think the best approach is to work on the full set of tasks, and hide the uninteresting ones by putting the following at the top of the _task.html.erb partial:

<div class="task" <%= 'style="display:none"' if form.object.performer_id != @performer_id %>>

For this to work, one also must pass the performer id into the render call in _form.html.erb:

    <%= render :partial => 'task', :locals => { :form => task_form, :performer_id => @performer.id,  } %>

Again, many thanks!

Last edited by korinthe (2009-08-16 09:19:42)

Re: Multiple child models in a dynamic form

Fixed. Ignore this post :-)

Last edited by jamesw (2009-08-17 22:39:10)

What you want and what you need are too often not the same thing!
When your head is hurting from trying to solve a problem, stop standing on it. When you are the right way up you will see the problem differently and you just might find the solution.
(Quote by me 15th July 2009)

Re: Multiple child models in a dynamic form

Is it possible to access the :child_index variable inside the form?
something like
<%if defined?(child_index) && child_index%>
<%= puts("#### child_index = #{child_index}")%>
<%end%>

I have tried all sorts to get the value assigned to :child_index inside the actual form but have failed miserably :-)

What you want and what you need are too often not the same thing!
When your head is hurting from trying to solve a problem, stop standing on it. When you are the right way up you will see the problem differently and you just might find the solution.
(Quote by me 15th July 2009)

Re: Multiple child models in a dynamic form

For anyone who uses this code and wants to validate the set of children using a custom validator defined in the parent:

Just-deleted children will still be around when the validator runs. Use child.marked_for_destruction? in your validator to ignore them. Also, avoid using association extensions in your validator, as they will go to the database, and you want to operate on the set of pending children instead.

Hope that saves someone else the headache I have had smile

Last edited by korinthe (2009-08-31 15:38:47)

Re: Multiple child models in a dynamic form

Hi!

I've already used nested forms in my application, but this time there is a strange behavior.
It's like rails "field_for" ignores my form in the render (in which I display a select for ID attribute) and displays an hidden input!

Example:

<input id="myobj_periods_attributes_0_id" name="myobj[periods_attributes][0][id]" type="hidden" value="123456" />
<select class="large" id="myobj_periods_attributes_0_id" name="myobj[periods_attributes][0][id]"></select>

<input id="myobj_periods_attributes_1_id" name="myobj[periods_attributes][1][id]" type="hidden" value="123457" />
<select class="large" id="myobj_periods_attributes_1_id" name="myobj[periods_attributes][1][id]"></select>

The problem is that I want to fill with an Ajax call my select list and there are two HTML objects with the same id (and even without javascript this is a total nonsense).

What mecanism could insert this hidden value (just after the "fields_for" call)?

Thanks

Re: Multiple child models in a dynamic form

Thanks for the help
it was useful

Re: Multiple child models in a dynamic form

Hi All,

I have taken Marsvin's method and wrapped it into a Rails plugin.

You can find more details here. http://github.com/miletbaker/add_nested_fields

Once GitHubs Gem Server is back up I will create a Gem too. Feel free to contribute / refine.

Regards,

Jon

Re: Multiple child models in a dynamic form

thanks for the great tutorial marsvin.

I only have one thing I'm struggling to understand. I want to control the order that the tasks are displayed in, I want new tasks always at the top of the list,  so I first I did this in my Project model:

has_many :tasks, :order => 'id DESC', :dependent => :destroy

and in the add_task_link helper I changed bottom: to top:

page << "$('tasks').insert({ top: '#{escape_javascript(html)}'.replace(/NEW_RECORD/g, new Date().getTime()) });"

real simple, works great but on validation, say I validate existence of a project title, when the validation fails and the edit form is re-displayed so that the user can fix it, the order of the tasks changes, the new/unsaved due to valid? failure, tasks get pushed back to the bottom of the list.

Any idea how to fix/sort that out?

77

Re: Multiple child models in a dynamic form

Hello, thanks for that useful tutorial.
Is there a way to limit the number of tasks fields one can add ?
Thanks

Edit : I found the answer by myself smile Here is how I did it.

The idea was to be able to limit the number of tasks fields you can add. It might not make much sense for tasks, but I'm using that code to add pictures and I want to control the number of pictures one can add.

So the idea was first to prevent someone from adding more than 5 tasks, and I thought it would be neat to just remove the add task link when you've reached the limit, and to put it back in if you remove enough fields (well, just one in fact since you're not supposed to be able to add 6 fields tongue).

Here is the new code, I added "#new" commentaries in front of each modification.
I changed the remove_task_link with a 'do |page|' but that's only to make it more read-friendly.

module ProjectsHelper
  def add_task_link(form_builder)
    link_to_function('Add another task', nil, :id => 'add_task') do |page|
      form_builder.semantic_fields_for :tasks, Task.new, :child_index => 'NEW_RECORD' do |f|
        html = render(:partial => 'task', :locals => { :form => f })
        page << "if($('tasks').select('fieldset.task').size() < 5) {" #new
        page << "$('tasks').insert({ bottom: '#{escape_javascript(html)}'.replace(/NEW_RECORD/g, new Date().getTime()) });"
        page << "};" # new
        page << "if($('tasks').select('fieldset.task').size() >= 5) {$('add_task').hide();};" # new
      end
    end
  end
  
   # Display the remove link for a child form
  def remove_task_link(form_builder)
    if form_builder.object.new_record?
      # If the task is a new record, we can just remove the div from the dom
      link_to_function("Remove that task") do |page|
        page << "$(this).up('.task').remove();"
        page << "if($('tasks').select('fieldset.task').size() < 5) {$('add_task').show();};"  #new
      end
    else
      # However if it's a "real" record it has to be deleted from the database,
      # for this reason the new fields_for, accept_nested_attributes helpers give us _delete,
      # a virtual attribute that tells rails to delete the child record.
      form_builder.hidden_field(:_delete) +
      link_to_function("Remove that task") do |page|
        page << "$(this).up('.task').hide(); $(this).previous().value = '1';"
        page << "if($('tasks').select('fieldset.task').size() < 5) {$('add_task').show();};" #new
      end
    end
  end
end

And that's it ! Of course you'll probably want to add some validation to your project model to check that we don't add more than 5 tasks, instead of leaving that control to ajax only.

I have a side question. Before using the simple fieldset.task in the selector, I used fieldset[class=\"task\"].
While this was causing no problem for the add link, it would break the code of the remove link, and I would have to remove the quotes, for fieldset[class=task]. Any idea why ?

Last edited by Jub (2010-01-29 00:30:49)

Re: Multiple child models in a dynamic form

I'm still baffled by the unnecessary complexity of nesting models in a form in Rails.  You could do this in less than a minute in WebObjects v1 in what, 1997?

Re: Multiple child models in a dynamic form

Hi. Thanks for the great tutorial.

I just want to ask, how can I use observe_field for the text_field with dynamically generated id?

Because I want to use live validation, and (I think) to be able to do that I have to get the id of the text_field.
My problem is, how can I get it if it's dynamically generated?

Please help me.
Thanks

Cece

Re: Multiple child models in a dynamic form

@Ceann817

I use http://livevalidation.com/ like this

def add_teacher(form_builder)
  link_to_function "", :id  => "add_teacher" do |page|
    form_builder.fields_for :teachers, Teacher.new, :child_index => 'NEW_RECORD' do |teacher_form|
      html = render(:partial => 'teacher', :locals => { :f => teacher_form })
      page << "var time = new Date().getTime()"
      page << "$('add_teacher').insert({ before: '#{escape_javascript(html)}'.replace(/NEW_RECORD/g, time) });"
      
      # Allow client side validations on newly added inputs and textareas.
      page << "teacherNameValidation('college_teachers_attributes_NEW_RECORD_name'.replace(/NEW_RECORD/g, time));"
      page << "teacherDescriptionValidation('college_teachers_attributes_NEW_RECORD_description'.replace(/NEW_RECORD/g, time));"
    end
  end
end
// Called when a teacher is dynamically created, the id is based on the current time in ms to ensure the id is unique, therefore it has to be passed in.
function teacherDescriptionValidation(element) {
  var teacherDescription = new LiveValidation(element, { insertAfterWhatNode: 'description', validMessage: "", onlyOnBlur: true });
  teacherDescription.add(Validate.Presence);
  teacherDescription.add(Validate.Length, { minimum: 1, maximum: 10000 });
}