From 1b20d98b25241ab4a947dc1315638ed01dd71cb3 Mon Sep 17 00:00:00 2001 From: Elad Meidar Date: Mon, 26 Oct 2009 00:45:44 -0400 Subject: [PATCH] Added FormHelper#fields_for_with_index and tests --- actionpack/lib/action_view/helpers/form_helper.rb | 109 ++++++++++++++++++++- actionpack/test/template/form_helper_test.rb | 42 ++++++++ 2 files changed, 148 insertions(+), 3 deletions(-) diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 994fac0..f3cbedf 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -482,6 +482,18 @@ module ActionView # Delete: <%= project_fields.check_box :_delete %> # <% end %> # <% end %> + # + # You will also be able to use #fields_for_with_index, with 2 arguments: first one + # is the form builder, and the second one is an index counter for child elements (first one will be 0) + # + # <% form_for @person, :url => { :action => "update" } do |person_form| %> + # ... + # <% person_form.fields_for_with_index :projects do |project_fields, index| %> + #
+ # Name: <%= project_fields.text_field :name %> + #
+ # <% end %> + # <% end %> def fields_for(record_or_name_or_array, *args, &block) raise ArgumentError, "Missing block" unless block_given? options = args.extract_options! @@ -496,9 +508,27 @@ module ActionView end builder = options[:builder] || ActionView::Base.default_form_builder + yield builder.new(object_name, object, self, options, block) end - + + def fields_for_with_index(record_or_name_or_array, *args, &block) + raise ArgumentError, "Missing block" unless block_given? + options = args.extract_options! + + case record_or_name_or_array + when String, Symbol + object_name = record_or_name_or_array + object = args.first + else + object = record_or_name_or_array + object_name = ActionController::RecordIdentifier.singular_class_name(object) + end + + builder = options[:builder] || ActionView::Base.default_form_builder + yield builder.new(object_name, object, self, options, block), next_child_index + end + # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object # assigned to the template (identified by +object+). The text of label will default to the attribute name unless you specify # it explicitly. Additional options on the label tag can be passed as a hash with +options+. These options will be tagged @@ -707,6 +737,13 @@ module ActionView def radio_button(object_name, method, tag_value, options = {}) InstanceTag.new(object_name, method, self, options.delete(:object)).to_radio_button_tag(tag_value, options) end + + private + + def next_child_index + @index ||= -1 + @index += 1 + end end class InstanceTag #:nodoc: @@ -939,7 +976,7 @@ module ActionView end end - (field_helpers - %w(label check_box radio_button fields_for)).each do |selector| + (field_helpers - %w(label check_box radio_button fields_for fields_for_with_index)).each do |selector| src = <<-end_src def #{selector}(method, options = {}) # def text_field(method, options = {}) @template.send( # @template.send( @@ -986,7 +1023,43 @@ module ActionView @template.fields_for(name, *args, &block) end + + def fields_for_with_index(record_or_name_or_array, *args, &block) + + "in Builder#fields_For_with_index" + if options.has_key?(:index) + index = "[#{options[:index]}]" + elsif defined?(@auto_index) + self.object_name = @object_name.to_s.sub(/\[\]$/,"") + index = "[#{@auto_index}]" + else + index = "" + end + if options[:builder] + args << {} unless args.last.is_a?(Hash) + args.last[:builder] ||= options[:builder] + end + + case record_or_name_or_array + when String, Symbol + if nested_attributes_association?(record_or_name_or_array) + return fields_for_with_index_with_nested_attributes(record_or_name_or_array, args, block) + else + name = "#{object_name}#{index}[#{record_or_name_or_array}]" + end + when Array + object = record_or_name_or_array.last + name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]" + args.unshift(object) + else + object = record_or_name_or_array + name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]" + args.unshift(object) + end + @template.fields_for_with_index(name, *args, &block) + end + def label(method, text = nil, options = {}) @template.label(@object_name, method, text, objectify_options(options)) end @@ -1039,7 +1112,27 @@ module ActionView fields_for_nested_model(name, association, args, block) end end + + def fields_for_with_index_with_nested_attributes(association_name, args, block) + name = "#{object_name}[#{association_name}_attributes]" + association = args.first + + if association.respond_to?(:new_record?) + association = [association] if @object.send(association_name).is_a?(Array) + elsif !association.is_a?(Array) + association = @object.send(association_name) + end + if association.is_a?(Array) + explicit_child_index = args.last[:child_index] if args.last.is_a?(Hash) + association.map do |child| + fields_for_with_index_nested_model("#{name}[#{explicit_child_index || nested_child_index(name)}]", child, args, block) + end.join + elsif association + fields_for_with_index_nested_model(name, association, args, block) + end + end + def fields_for_nested_model(name, object, args, block) if object.new_record? @template.fields_for(name, object, *args, &block) @@ -1050,7 +1143,17 @@ module ActionView end end end - + + def fields_for_with_index_nested_model(name, object, args, block) + if object.new_record? + @template.fields_for_with_index(name, object, *args, &block) + else + @template.fields_for_with_index(name, object, *args) do |builder, child_index| + @template.concat builder.hidden_field(:id) + block.call(builder, child_index) + end + end + end def nested_child_index(name) @nested_child_index[name] ||= -1 @nested_child_index[name] += 1 diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index 357c36d..9785623 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -909,6 +909,48 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end + def test_method_fields_for_with_index + fields_for_with_index(:post, @post) do |f, index| + concat f.text_field(:title) + concat f.text_area(:body) + concat f.check_box(:secret) + concat "
This is child index #{index}
" + end + + expected = + "" + + "" + + "" + + "" + + "
This is child index 0
" + + assert_dom_equal expected, output_buffer + end + + def test_method_fields_for_with_index_with_nested_attributes_collection_association + + @post.comments = [Comment.new(321), Comment.new] + + form_for(:post, @post) do |f| + concat f.text_field(:title) + f.fields_for_with_index(:comments) do |cf, index| + concat cf.text_field(:name) + concat "
This is item index #{index}
" + end + end + + expected = '
' + + '' + + '' + + '' + + "
This is item index 0
" + + '' + + "
This is item index 1
" + + '
' + + assert_dom_equal expected, output_buffer + end + def test_fields_for_with_index fields_for("post[]", @post) do |f| concat f.text_field(:title) -- 1.6.0.2