From 9474fe72dd388559aa013717e9e91b5a87aa872c Mon Sep 17 00:00:00 2001 From: Nicholas Seckar Date: Sat, 4 Feb 2006 19:58:45 +0000 Subject: Add :builder => option to form_for and friends. Closes 3268. git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@3536 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- actionpack/lib/action_view/helpers/form_helper.rb | 60 +++++++++++++++++----- .../lib/action_view/helpers/prototype_helper.rb | 13 ++--- 2 files changed, 50 insertions(+), 23 deletions(-) (limited to 'actionpack/lib') diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index c896ace862..75c09dfe87 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -97,12 +97,36 @@ module ActionView # # Note: This also works for the methods in FormOptionHelper and DateHelper that are designed to work with an object as base. # Like collection_select and datetime_select. + # + # You can also build forms using a customized FormBuilder class. Subclass FormBuilder and override or define some more helpers, + # then use your custom builder like so: + # + # <% form_for :person, @person, :url => { :action => "update" }, :builder => LabellingFormBuilder do |f| %> + # <%= f.text_field :first_name %> + # <%= f.text_field :last_name %> + # <%= text_area :person, :biography %> + # <%= check_box_tag "person[admin]", @person.company.admin? %> + # <% end %> + # + # In many cases you will want to wrap the above in another helper, such as: + # + # def labelled_form_for(name, object, options, &proc) + # form_for(name, object, options.merge(:builder => LabellingFormBuiler), &proc) + # end + # def form_for(object_name, object, options = {}, &proc) - url_for_options = options[:url] - additional_options = options.reject { |k, v| ![ :method, :multipart ].include?(k) } - concat(form_tag(url_for_options, additional_options), proc.binding) - fields_for(object_name, object, &proc) - concat(end_form_tag, proc.binding) + raise ArgumentError, "form_for requires a block!" unless proc + + url_options = options.delete :url + form_tag_selector = options.delete(:form_tag_selector) || :form_tag + form_options = {} + [:method, :multipart].each { |key| form_options[key] = options.delete(key) if options.key? key } + + fields_for(object_name, object, options.merge(:proc => proc)) do |builder| + concat send(form_tag_selector, url_options, form_options), proc.binding + builder.build_form(url_options, form_options) { proc.call builder } + concat end_form_tag, proc.binding + end end # Creates a scope around a specific model object like form_for, but doesn't create the form tags themselves. This makes @@ -119,8 +143,11 @@ module ActionView # # Note: This also works for the methods in FormOptionHelper and DateHelper that are designed to work with an object as base. # Like collection_select and datetime_select. - def fields_for(object_name, object, &proc) - form_builder = FormBuilder.new(object_name, object, self, proc) + def fields_for(object_name, object, options = {}, &proc) + raise ArgumentError, "fields_for requires a block!" unless proc + + builder = options[:builder] || FormBuilder + form_builder = builder.new(object_name, object, self, options, options[:proc] || proc) proc.call(form_builder) end @@ -350,13 +377,20 @@ module ActionView end class FormBuilder - def initialize(object_name, object, template, proc) - @object_name, @object, @template, @proc = object_name, object, template, proc + # The methods which wrap a form helper call. + class_inheritable_accessor :field_helpers + self.field_helpers = (FormHelper.instance_methods - ['form_for']) + + def initialize(object_name, object, template, options, proc) + @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc end - - (FormHelper.instance_methods - [ :check_box, :radio_button ]).each do |selector| - next if selector == "form_for" - + + # An empty stub that can be overriden to wrap a form created by form_for. + def build_form(url_options, form_options, &proc) + proc.call self + end + + (field_helpers - %w(check_box radio_button)).each do |selector| src = <<-end_src def #{selector}(method, options = {}) @template.send(#{selector.inspect}, @object_name, method, options.merge(:object => @object)) diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb index 27dc9c93cf..2cd7550c65 100644 --- a/actionpack/lib/action_view/helpers/prototype_helper.rb +++ b/actionpack/lib/action_view/helpers/prototype_helper.rb @@ -170,18 +170,11 @@ module ActionView tag("form", options[:html], true) end - def form_remote_for(object_name, object, options = {}, &proc) - concat(form_remote_tag(options), proc.binding) - fields_for(object_name, object, &proc) - concat(end_form_tag, proc.binding) - end - # Works like form_remote_tag, but uses form_for semantics. - def form_remote_for(object_name, object, options = {}, &proc) - concat(form_remote_tag(options), proc.binding) - fields_for(object_name, object, &proc) - concat(end_form_tag, proc.binding) + def remote_form_for(object_name, object, options = {}, &proc) + form_for(object_name, object, options.merge(:form_for_select => :form_remote_tag), &proc) end + alias_method :form_remote_for, :remote_form_for # Returns a button input tag that will submit form using XMLHttpRequest # in the background instead of regular reloading POST arrangement. -- cgit v1.2.3