diff --git a/actionpack/lib/action_view/helpers/active_model_helper.rb b/actionpack/lib/action_view/helpers/active_model_helper.rb
index 56f15604a6..973135e2ea 100644
--- a/actionpack/lib/action_view/helpers/active_model_helper.rb
+++ b/actionpack/lib/action_view/helpers/active_model_helper.rb
@@ -16,9 +16,7 @@ module ActionView
end
end
- %w(content_tag to_date_select_tag to_datetime_select_tag to_time_select_tag).each do |meth|
- module_eval "def #{meth}(*) error_wrapping(super) end", __FILE__, __LINE__
- end
+ module_eval "def content_tag(*) error_wrapping(super) end", __FILE__, __LINE__
def tag(type, options, *)
tag_generate_errors?(options) ? error_wrapping(super) : super
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 2806348337..f5077b034a 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -213,7 +213,7 @@ module ActionView
# Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that
# all month choices are valid.
def date_select(object_name, method, options = {}, html_options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_date_select_tag(options, html_options)
+ Tags::DateSelect.new(object_name, method, self, options, html_options).render
end
# Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a
@@ -251,7 +251,7 @@ module ActionView
# Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that
# all month choices are valid.
def time_select(object_name, method, options = {}, html_options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_time_select_tag(options, html_options)
+ Tags::TimeSelect.new(object_name, method, self, options, html_options).render
end
# Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a
@@ -287,7 +287,7 @@ module ActionView
#
# The selects are prepared for multi-parameter assignment to an Active Record object.
def datetime_select(object_name, method, options = {}, html_options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_datetime_select_tag(options, html_options)
+ Tags::DatetimeSelect.new(object_name, method, self, options, html_options).render
end
# Returns a set of html select-tags (one for year, month, day, hour, minute, and second) pre-selected with the
@@ -974,66 +974,6 @@ module ActionView
end
end
- module DateHelperInstanceTag
- def to_date_select_tag(options = {}, html_options = {})
- datetime_selector(options, html_options).select_date.html_safe
- end
-
- def to_time_select_tag(options = {}, html_options = {})
- datetime_selector(options, html_options).select_time.html_safe
- end
-
- def to_datetime_select_tag(options = {}, html_options = {})
- datetime_selector(options, html_options).select_datetime.html_safe
- end
-
- private
- def datetime_selector(options, html_options)
- datetime = value(object) || default_datetime(options)
- @auto_index ||= nil
-
- options = options.dup
- options[:field_name] = @method_name
- options[:include_position] = true
- options[:prefix] ||= @object_name
- options[:index] = @auto_index if @auto_index && !options.has_key?(:index)
-
- DateTimeSelector.new(datetime, options, html_options)
- end
-
- def default_datetime(options)
- return if options[:include_blank] || options[:prompt]
-
- case options[:default]
- when nil
- Time.current
- when Date, Time
- options[:default]
- else
- default = options[:default].dup
-
- # Rename :minute and :second to :min and :sec
- default[:min] ||= default[:minute]
- default[:sec] ||= default[:second]
-
- time = Time.current
-
- [:year, :month, :day, :hour, :min, :sec].each do |key|
- default[key] ||= time.send(key)
- end
-
- Time.utc_time(
- default[:year], default[:month], default[:day],
- default[:hour], default[:min], default[:sec]
- )
- end
- end
- end
-
- class InstanceTag #:nodoc:
- include DateHelperInstanceTag
- end
-
class FormBuilder
def date_select(method, options = {}, html_options = {})
@template.date_select(@object_name, method, objectify_options(options), html_options)
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index ca2eb1ac10..10277599a0 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -3,6 +3,7 @@ require 'action_view/helpers/date_helper'
require 'action_view/helpers/tag_helper'
require 'action_view/helpers/form_tag_helper'
require 'action_view/helpers/active_model_helper'
+require 'action_view/helpers/tags'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/object/blank'
@@ -654,16 +655,7 @@ module ActionView
# 'Accept Terms.'.html_safe
# end
def label(object_name, method, content_or_options = nil, options = nil, &block)
- content_is_options = content_or_options.is_a?(Hash)
- if content_is_options || block_given?
- options = content_or_options if content_is_options
- text = nil
- else
- text = content_or_options
- end
-
- options ||= {}
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_label_tag(text, options, &block)
+ Tags::Label.new(object_name, method, self, content_or_options, options).render(&block)
end
# Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
@@ -685,7 +677,7 @@ module ActionView
# # =>
#
def text_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("text", options)
+ Tags::TextField.new(object_name, method, self, options).render
end
# Returns an input tag of the "password" type tailored for accessing a specified attribute (identified by +method+) on an object
@@ -707,7 +699,7 @@ module ActionView
# # =>
#
def password_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("password", { :value => nil }.merge!(options))
+ Tags::PasswordField.new(object_name, method, self, options).render
end
# Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object
@@ -725,7 +717,7 @@ module ActionView
# hidden_field(:user, :token)
# # =>
def hidden_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("hidden", options)
+ Tags::HiddenField.new(object_name, method, self, options).render
end
# Returns a file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object
@@ -746,7 +738,7 @@ module ActionView
# # =>
#
def file_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("file", options.update({:size => nil}))
+ Tags::FileField.new(object_name, method, self, options).render
end
# Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
@@ -774,7 +766,7 @@ module ActionView
# # #{@entry.body}
# #
def text_area(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_text_area_tag(options)
+ Tags::TextArea.new(object_name, method, self, options).render
end
# Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
@@ -836,7 +828,7 @@ module ActionView
# #
#
def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)
+ Tags::CheckBox.new(object_name, method, self, checked_value, unchecked_value, options).render
end
# Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object
@@ -858,7 +850,7 @@ 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)
+ Tags::RadioButton.new(object_name, method, self, tag_value, options).render
end
# Returns an input of type "search" for accessing a specified attribute (identified by +method+) on an object
@@ -884,20 +876,7 @@ module ActionView
# # =>
#
def search_field(object_name, method, options = {})
- options = options.stringify_keys
-
- if options["autosave"]
- if options["autosave"] == true
- options["autosave"] = request.host.split(".").reverse.join(".")
- end
- options["results"] ||= 10
- end
-
- if options["onsearch"]
- options["incremental"] = true unless options.has_key?("incremental")
- end
-
- InstanceTag.new(object_name, method, self, options.delete("object")).to_input_field_tag("search", options)
+ Tags::SearchField.new(object_name, method, self, options).render
end
# Returns a text_field of type "tel".
@@ -906,7 +885,7 @@ module ActionView
# # =>
#
def telephone_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("tel", options)
+ Tags::TelField.new(object_name, method, self, options).render
end
alias phone_field telephone_field
@@ -916,7 +895,7 @@ module ActionView
# # =>
#
def url_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("url", options)
+ Tags::UrlField.new(object_name, method, self, options).render
end
# Returns a text_field of type "email".
@@ -925,7 +904,7 @@ module ActionView
# # =>
#
def email_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("email", options)
+ Tags::EmailField.new(object_name, method, self, options).render
end
# Returns an input tag of type "number".
@@ -933,7 +912,7 @@ module ActionView
# ==== Options
# * Accepts same options as number_field_tag
def number_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_number_field_tag("number", options)
+ Tags::NumberField.new(object_name, method, self, options).render
end
# Returns an input tag of type "range".
@@ -941,7 +920,7 @@ module ActionView
# ==== Options
# * Accepts same options as range_field_tag
def range_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_number_field_tag("range", options)
+ Tags::RangeField.new(object_name, method, self, options).render
end
private
@@ -961,272 +940,6 @@ module ActionView
end
end
- class InstanceTag
- include Helpers::ActiveModelInstanceTag, Helpers::TagHelper, Helpers::FormTagHelper
-
- attr_reader :object, :method_name, :object_name
-
- DEFAULT_FIELD_OPTIONS = { "size" => 30 }
- DEFAULT_RADIO_OPTIONS = { }
- DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }
-
- def initialize(object_name, method_name, template_object, object = nil)
- @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
- @template_object = template_object
-
- @object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]")
- @object = retrieve_object(object)
- @auto_index = retrieve_autoindex(Regexp.last_match.pre_match) if Regexp.last_match
- end
-
- def to_label_tag(text = nil, options = {}, &block)
- options = options.stringify_keys
- tag_value = options.delete("value")
- name_and_id = options.dup
-
- if name_and_id["for"]
- name_and_id["id"] = name_and_id["for"]
- else
- name_and_id.delete("id")
- end
-
- add_default_name_and_id_for_value(tag_value, name_and_id)
- options.delete("index")
- options.delete("namespace")
- options["for"] ||= name_and_id["id"]
-
- if block_given?
- @template_object.label_tag(name_and_id["id"], options, &block)
- else
- content = if text.blank?
- object_name.gsub!(/\[(.*)_attributes\]\[\d\]/, '.\1')
- method_and_value = tag_value.present? ? "#{method_name}.#{tag_value}" : method_name
-
- if object.respond_to?(:to_model)
- key = object.class.model_name.i18n_key
- i18n_default = ["#{key}.#{method_and_value}".to_sym, ""]
- end
-
- i18n_default ||= ""
- I18n.t("#{object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.label").presence
- else
- text.to_s
- end
-
- content ||= if object && object.class.respond_to?(:human_attribute_name)
- object.class.human_attribute_name(method_name)
- end
-
- content ||= method_name.humanize
-
- label_tag(name_and_id["id"], content, options)
- end
- end
-
- def to_input_field_tag(field_type, options = {})
- options = options.stringify_keys
- options["size"] = options["maxlength"] || DEFAULT_FIELD_OPTIONS["size"] unless options.key?("size")
- options = DEFAULT_FIELD_OPTIONS.merge(options)
- if field_type == "hidden"
- options.delete("size")
- end
- options["type"] ||= field_type
- options["value"] = options.fetch("value"){ value_before_type_cast(object) } unless field_type == "file"
- options["value"] &&= ERB::Util.html_escape(options["value"])
- add_default_name_and_id(options)
- tag("input", options)
- end
-
- def to_number_field_tag(field_type, options = {})
- options = options.stringify_keys
- options['size'] ||= nil
-
- if range = options.delete("in") || options.delete("within")
- options.update("min" => range.min, "max" => range.max)
- end
- to_input_field_tag(field_type, options)
- end
-
- def to_radio_button_tag(tag_value, options = {})
- options = DEFAULT_RADIO_OPTIONS.merge(options.stringify_keys)
- options["type"] = "radio"
- options["value"] = tag_value
- if options.has_key?("checked")
- cv = options.delete "checked"
- checked = cv == true || cv == "checked"
- else
- checked = self.class.radio_button_checked?(value(object), tag_value)
- end
- options["checked"] = "checked" if checked
- add_default_name_and_id_for_value(tag_value, options)
- tag("input", options)
- end
-
- def to_text_area_tag(options = {})
- options = DEFAULT_TEXT_AREA_OPTIONS.merge(options.stringify_keys)
- add_default_name_and_id(options)
-
- if size = options.delete("size")
- options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
- end
-
- content_tag("textarea", ERB::Util.html_escape(options.delete('value') || value_before_type_cast(object)), options)
- end
-
- def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
- options = options.stringify_keys
- options["type"] = "checkbox"
- options["value"] = checked_value
- if options.has_key?("checked")
- cv = options.delete "checked"
- checked = cv == true || cv == "checked"
- else
- checked = self.class.check_box_checked?(value(object), checked_value)
- end
- options["checked"] = "checked" if checked
- if options["multiple"]
- add_default_name_and_id_for_value(checked_value, options)
- options.delete("multiple")
- else
- add_default_name_and_id(options)
- end
- hidden = unchecked_value ? tag("input", "name" => options["name"], "type" => "hidden", "value" => unchecked_value, "disabled" => options["disabled"]) : ""
- checkbox = tag("input", options)
- hidden + checkbox
- end
-
- def to_boolean_select_tag(options = {})
- options = options.stringify_keys
- add_default_name_and_id(options)
- value = value(object)
- tag_text = ""
- end
-
- def to_content_tag(tag_name, options = {})
- content_tag(tag_name, value(object), options)
- end
-
- def retrieve_object(object)
- if object
- object
- elsif @template_object.instance_variable_defined?("@#{@object_name}")
- @template_object.instance_variable_get("@#{@object_name}")
- end
- rescue NameError
- # As @object_name may contain the nested syntax (item[subobject]) we need to fallback to nil.
- nil
- end
-
- def retrieve_autoindex(pre_match)
- object = self.object || @template_object.instance_variable_get("@#{pre_match}")
- if object && object.respond_to?(:to_param)
- object.to_param
- else
- raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
- end
- end
-
- def value(object)
- self.class.value(object, @method_name)
- end
-
- def value_before_type_cast(object)
- self.class.value_before_type_cast(object, @method_name)
- end
-
- class << self
- def value(object, method_name)
- object.send method_name if object
- end
-
- def value_before_type_cast(object, method_name)
- unless object.nil?
- object.respond_to?(method_name + "_before_type_cast") ?
- object.send(method_name + "_before_type_cast") :
- object.send(method_name)
- end
- end
-
- def check_box_checked?(value, checked_value)
- case value
- when TrueClass, FalseClass
- value
- when NilClass
- false
- when Integer
- value != 0
- when String
- value == checked_value
- when Array
- value.include?(checked_value)
- else
- value.to_i != 0
- end
- end
-
- def radio_button_checked?(value, checked_value)
- value.to_s == checked_value.to_s
- end
- end
-
- private
- def add_default_name_and_id_for_value(tag_value, options)
- unless tag_value.nil?
- pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/[^-\w]/, "").downcase
- specified_id = options["id"]
- add_default_name_and_id(options)
- options["id"] += "_#{pretty_tag_value}" if specified_id.blank? && options["id"].present?
- else
- add_default_name_and_id(options)
- end
- end
-
- def add_default_name_and_id(options)
- if options.has_key?("index")
- options["name"] ||= tag_name_with_index(options["index"])
- options["id"] = options.fetch("id"){ tag_id_with_index(options["index"]) }
- options.delete("index")
- elsif defined?(@auto_index)
- options["name"] ||= tag_name_with_index(@auto_index)
- options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) }
- else
- options["name"] ||= tag_name + (options['multiple'] ? '[]' : '')
- options["id"] = options.fetch("id"){ tag_id }
- end
- options["id"] = [options.delete('namespace'), options["id"]].compact.join("_").presence
- end
-
- def tag_name
- "#{@object_name}[#{sanitized_method_name}]"
- end
-
- def tag_name_with_index(index)
- "#{@object_name}[#{index}][#{sanitized_method_name}]"
- end
-
- def tag_id
- "#{sanitized_object_name}_#{sanitized_method_name}"
- end
-
- def tag_id_with_index(index)
- "#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
- end
-
- def sanitized_object_name
- @sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
- end
-
- def sanitized_method_name
- @sanitized_method_name ||= @method_name.sub(/\?$/,"")
- end
- end
-
class FormBuilder
# The methods which wrap a form helper call.
class_attribute :field_helpers
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index ba9ff1d5aa..e323350608 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -154,7 +154,7 @@ module ActionView
# key in the query string, that works for ordinary forms.
#
def select(object, method, choices, options = {}, html_options = {})
- InstanceTag.new(object, method, self, options.delete(:object)).to_select_tag(choices, options, html_options)
+ Tags::Select.new(object, method, self, choices, options, html_options).render
end
# Returns and tags for the collection of existing return values of
@@ -188,10 +188,9 @@ module ActionView
#
#
def collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
- InstanceTag.new(object, method, self, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options)
+ Tags::CollectionSelect.new(object, method, self, collection, value_method, text_method, options, html_options).render
end
-
# Returns , and tags for the collection of existing return values of
# +method+ for +object+'s class. The value returned from calling +method+ on the instance +object+ will
# be selected. If calling +method+ returns +nil+, no selection is made without including :prompt
@@ -240,7 +239,7 @@ module ActionView
#
#
def grouped_collection_select(object, method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
- InstanceTag.new(object, method, self, options.delete(:object)).to_grouped_collection_select_tag(collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
+ Tags::GroupedCollectionSelect.new(object, method, self, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options).render
end
# Return select and option tags for the given object and method, using
@@ -274,7 +273,7 @@ module ActionView
#
# time_zone_select( "user", "time_zone", ActiveSupport::TimeZone.all.sort, :model => ActiveSupport::TimeZone)
def time_zone_select(object, method, priority_zones = nil, options = {}, html_options = {})
- InstanceTag.new(object, method, self, options.delete(:object)).to_time_zone_select_tag(priority_zones, options, html_options)
+ Tags::TimeZoneSelect.new(object, method, self, priority_zones, options, html_options).render
end
# Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container
@@ -573,69 +572,6 @@ module ActionView
end
end
- class InstanceTag #:nodoc:
- include FormOptionsHelper
-
- def to_select_tag(choices, options, html_options)
- selected_value = options.has_key?(:selected) ? options[:selected] : value(object)
-
- # Grouped choices look like this:
- #
- # [nil, []]
- # { nil => [] }
- #
- if !choices.empty? && choices.first.respond_to?(:last) && Array === choices.first.last
- option_tags = grouped_options_for_select(choices, :selected => selected_value, :disabled => options[:disabled])
- else
- option_tags = options_for_select(choices, :selected => selected_value, :disabled => options[:disabled])
- end
-
- select_content_tag(option_tags, options, html_options)
- end
-
- def to_collection_select_tag(collection, value_method, text_method, options, html_options)
- selected_value = options.has_key?(:selected) ? options[:selected] : value(object)
- select_content_tag(
- options_from_collection_for_select(collection, value_method, text_method, :selected => selected_value, :disabled => options[:disabled]), options, html_options
- )
- end
-
- def to_grouped_collection_select_tag(collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
- select_content_tag(
- option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, value(object)), options, html_options
- )
- end
-
- def to_time_zone_select_tag(priority_zones, options, html_options)
- select_content_tag(
- time_zone_options_for_select(value(object) || options[:default], priority_zones, options[:model] || ActiveSupport::TimeZone), options, html_options
- )
- end
-
- private
- def add_options(option_tags, options, value = nil)
- if options[:include_blank]
- option_tags = "\n" + option_tags
- end
- if value.blank? && options[:prompt]
- prompt = options[:prompt].kind_of?(String) ? options[:prompt] : I18n.translate('helpers.select.prompt', :default => 'Please select')
- option_tags = "\n" + option_tags
- end
- option_tags.html_safe
- end
-
- def select_content_tag(option_tags, options, html_options)
- html_options = html_options.stringify_keys
- add_default_name_and_id(html_options)
- select = content_tag("select", add_options(option_tags, options, value(object)), html_options)
- if html_options["multiple"]
- tag("input", :disabled => html_options["disabled"], :name => html_options["name"], :type => "hidden", :value => "") + select
- else
- select
- end
- end
- end
-
class FormBuilder
def select(method, choices, options = {}, html_options = {})
@template.select(@object_name, method, choices, objectify_options(options), @default_options.merge(html_options))
diff --git a/actionpack/lib/action_view/helpers/tags.rb b/actionpack/lib/action_view/helpers/tags.rb
new file mode 100644
index 0000000000..89b3efda5f
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags.rb
@@ -0,0 +1,28 @@
+module ActionView
+ module Helpers
+ module Tags
+ autoload :Base, 'action_view/helpers/tags/base'
+ autoload :Label, 'action_view/helpers/tags/label'
+ autoload :TextField, 'action_view/helpers/tags/text_field'
+ autoload :PasswordField, 'action_view/helpers/tags/password_field'
+ autoload :HiddenField, 'action_view/helpers/tags/hidden_field'
+ autoload :FileField, 'action_view/helpers/tags/file_field'
+ autoload :SearchField, 'action_view/helpers/tags/search_field'
+ autoload :TelField, 'action_view/helpers/tags/tel_field'
+ autoload :UrlField, 'action_view/helpers/tags/url_field'
+ autoload :EmailField, 'action_view/helpers/tags/email_field'
+ autoload :NumberField, 'action_view/helpers/tags/number_field'
+ autoload :RangeField, 'action_view/helpers/tags/range_field'
+ autoload :TextArea, 'action_view/helpers/tags/text_area'
+ autoload :CheckBox, 'action_view/helpers/tags/check_box'
+ autoload :RadioButton, 'action_view/helpers/tags/radio_button'
+ autoload :Select, 'action_view/helpers/tags/select'
+ autoload :CollectionSelect, 'action_view/helpers/tags/collection_select'
+ autoload :GroupedCollectionSelect, 'action_view/helpers/tags/grouped_collection_select'
+ autoload :TimeZoneSelect, 'action_view/helpers/tags/time_zone_select'
+ autoload :DateSelect, 'action_view/helpers/tags/date_select'
+ autoload :TimeSelect, 'action_view/helpers/tags/time_select'
+ autoload :DatetimeSelect, 'action_view/helpers/tags/datetime_select'
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/base.rb b/actionpack/lib/action_view/helpers/tags/base.rb
new file mode 100644
index 0000000000..24956beb9c
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/base.rb
@@ -0,0 +1,134 @@
+module ActionView
+ module Helpers
+ module Tags
+ class Base #:nodoc:
+ include Helpers::ActiveModelInstanceTag, Helpers::TagHelper, Helpers::FormTagHelper
+ include FormOptionsHelper
+
+ DEFAULT_FIELD_OPTIONS = { "size" => 30 }
+
+ attr_reader :object
+
+ def initialize(object_name, method_name, template_object, options = {})
+ @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
+ @template_object = template_object
+
+ @object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]")
+ @object = retrieve_object(options.delete(:object))
+ @options = options
+ @auto_index = retrieve_autoindex(Regexp.last_match.pre_match) if Regexp.last_match
+ end
+
+ def render(&block)
+ raise "Abstract Method called"
+ end
+
+ private
+
+ def value(object)
+ object.send @method_name if object
+ end
+
+ def value_before_type_cast(object)
+ unless object.nil?
+ object.respond_to?(@method_name + "_before_type_cast") ?
+ object.send(@method_name + "_before_type_cast") :
+ object.send(@method_name)
+ end
+ end
+
+ def retrieve_object(object)
+ if object
+ object
+ elsif @template_object.instance_variable_defined?("@#{@object_name}")
+ @template_object.instance_variable_get("@#{@object_name}")
+ end
+ rescue NameError
+ # As @object_name may contain the nested syntax (item[subobject]) we need to fallback to nil.
+ nil
+ end
+
+ def retrieve_autoindex(pre_match)
+ object = self.object || @template_object.instance_variable_get("@#{pre_match}")
+ if object && object.respond_to?(:to_param)
+ object.to_param
+ else
+ raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
+ end
+ end
+
+ def add_default_name_and_id_for_value(tag_value, options)
+ unless tag_value.nil?
+ pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/[^-\w]/, "").downcase
+ specified_id = options["id"]
+ add_default_name_and_id(options)
+ options["id"] += "_#{pretty_tag_value}" if specified_id.blank? && options["id"].present?
+ else
+ add_default_name_and_id(options)
+ end
+ end
+
+ def add_default_name_and_id(options)
+ if options.has_key?("index")
+ options["name"] ||= tag_name_with_index(options["index"])
+ options["id"] = options.fetch("id"){ tag_id_with_index(options["index"]) }
+ options.delete("index")
+ elsif defined?(@auto_index)
+ options["name"] ||= tag_name_with_index(@auto_index)
+ options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) }
+ else
+ options["name"] ||= tag_name + (options['multiple'] ? '[]' : '')
+ options["id"] = options.fetch("id"){ tag_id }
+ end
+ options["id"] = [options.delete('namespace'), options["id"]].compact.join("_").presence
+ end
+
+ def tag_name
+ "#{@object_name}[#{sanitized_method_name}]"
+ end
+
+ def tag_name_with_index(index)
+ "#{@object_name}[#{index}][#{sanitized_method_name}]"
+ end
+
+ def tag_id
+ "#{sanitized_object_name}_#{sanitized_method_name}"
+ end
+
+ def tag_id_with_index(index)
+ "#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
+ end
+
+ def sanitized_object_name
+ @sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
+ end
+
+ def sanitized_method_name
+ @sanitized_method_name ||= @method_name.sub(/\?$/,"")
+ end
+
+ def select_content_tag(option_tags, options, html_options)
+ html_options = html_options.stringify_keys
+ add_default_name_and_id(html_options)
+ select = content_tag("select", add_options(option_tags, options, value(object)), html_options)
+ if html_options["multiple"]
+ tag("input", :disabled => html_options["disabled"], :name => html_options["name"], :type => "hidden", :value => "") + select
+ else
+ select
+ end
+ end
+
+ def add_options(option_tags, options, value = nil)
+ if options[:include_blank]
+ option_tags = "\n" + option_tags
+ end
+ if value.blank? && options[:prompt]
+ prompt = options[:prompt].kind_of?(String) ? options[:prompt] : I18n.translate('helpers.select.prompt', :default => 'Please select')
+ option_tags = "\n" + option_tags
+ end
+ option_tags.html_safe
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/check_box.rb b/actionpack/lib/action_view/helpers/tags/check_box.rb
new file mode 100644
index 0000000000..b3bd6eb2ad
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/check_box.rb
@@ -0,0 +1,54 @@
+require 'action_view/helpers/tags/checkable'
+
+module ActionView
+ module Helpers
+ module Tags
+ class CheckBox < Base #:nodoc:
+ include Checkable
+
+ def initialize(object_name, method_name, template_object, checked_value, unchecked_value, options)
+ @checked_value = checked_value
+ @unchecked_value = unchecked_value
+ super(object_name, method_name, template_object, options)
+ end
+
+ def render
+ options = @options.stringify_keys
+ options["type"] = "checkbox"
+ options["value"] = @checked_value
+ options["checked"] = "checked" if input_checked?(object, options)
+
+ if options["multiple"]
+ add_default_name_and_id_for_value(@checked_value, options)
+ options.delete("multiple")
+ else
+ add_default_name_and_id(options)
+ end
+
+ hidden = @unchecked_value ? tag("input", "name" => options["name"], "type" => "hidden", "value" => @unchecked_value, "disabled" => options["disabled"]) : ""
+ checkbox = tag("input", options)
+ hidden + checkbox
+ end
+
+ private
+
+ def checked?(value)
+ case value
+ when TrueClass, FalseClass
+ value
+ when NilClass
+ false
+ when Integer
+ value != 0
+ when String
+ value == @checked_value
+ when Array
+ value.include?(@checked_value)
+ else
+ value.to_i != 0
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/checkable.rb b/actionpack/lib/action_view/helpers/tags/checkable.rb
new file mode 100644
index 0000000000..b97c0c68d7
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/checkable.rb
@@ -0,0 +1,16 @@
+module ActionView
+ module Helpers
+ module Tags
+ module Checkable
+ def input_checked?(object, options)
+ if options.has_key?("checked")
+ checked = options.delete "checked"
+ checked == true || checked == "checked"
+ else
+ checked?(value(object))
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/collection_select.rb b/actionpack/lib/action_view/helpers/tags/collection_select.rb
new file mode 100644
index 0000000000..f84140d8d0
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/collection_select.rb
@@ -0,0 +1,23 @@
+module ActionView
+ module Helpers
+ module Tags
+ class CollectionSelect < Base #:nodoc:
+ def initialize(object_name, method_name, template_object, collection, value_method, text_method, options, html_options)
+ @collection = collection
+ @value_method = value_method
+ @text_method = text_method
+ @html_options = html_options
+
+ super(object_name, method_name, template_object, options)
+ end
+
+ def render
+ selected_value = @options.has_key?(:selected) ? @options[:selected] : value(@object)
+ select_content_tag(
+ options_from_collection_for_select(@collection, @value_method, @text_method, :selected => selected_value, :disabled => @options[:disabled]), @options, @html_options
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/date_select.rb b/actionpack/lib/action_view/helpers/tags/date_select.rb
new file mode 100644
index 0000000000..5912598ca1
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/date_select.rb
@@ -0,0 +1,64 @@
+module ActionView
+ module Helpers
+ module Tags
+ class DateSelect < Base #:nodoc:
+ def initialize(object_name, method_name, template_object, options, html_options)
+ @html_options = html_options
+
+ super(object_name, method_name, template_object, options)
+ end
+
+ def render
+ error_wrapping(datetime_selector(@options, @html_options).send("select_#{select_type}").html_safe)
+ end
+
+ private
+
+ def select_type
+ self.class.name.split("::").last.sub("Select", "").downcase
+ end
+
+ def datetime_selector(options, html_options)
+ datetime = value(object) || default_datetime(options)
+ @auto_index ||= nil
+
+ options = options.dup
+ options[:field_name] = @method_name
+ options[:include_position] = true
+ options[:prefix] ||= @object_name
+ options[:index] = @auto_index if @auto_index && !options.has_key?(:index)
+
+ DateTimeSelector.new(datetime, options, html_options)
+ end
+
+ def default_datetime(options)
+ return if options[:include_blank] || options[:prompt]
+
+ case options[:default]
+ when nil
+ Time.current
+ when Date, Time
+ options[:default]
+ else
+ default = options[:default].dup
+
+ # Rename :minute and :second to :min and :sec
+ default[:min] ||= default[:minute]
+ default[:sec] ||= default[:second]
+
+ time = Time.current
+
+ [:year, :month, :day, :hour, :min, :sec].each do |key|
+ default[key] ||= time.send(key)
+ end
+
+ Time.utc_time(
+ default[:year], default[:month], default[:day],
+ default[:hour], default[:min], default[:sec]
+ )
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/datetime_select.rb b/actionpack/lib/action_view/helpers/tags/datetime_select.rb
new file mode 100644
index 0000000000..a32c840bce
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/datetime_select.rb
@@ -0,0 +1,8 @@
+module ActionView
+ module Helpers
+ module Tags
+ class DatetimeSelect < DateSelect #:nodoc:
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/email_field.rb b/actionpack/lib/action_view/helpers/tags/email_field.rb
new file mode 100644
index 0000000000..45cde507d7
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/email_field.rb
@@ -0,0 +1,8 @@
+module ActionView
+ module Helpers
+ module Tags
+ class EmailField < TextField #:nodoc:
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/file_field.rb b/actionpack/lib/action_view/helpers/tags/file_field.rb
new file mode 100644
index 0000000000..56442e1c14
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/file_field.rb
@@ -0,0 +1,12 @@
+module ActionView
+ module Helpers
+ module Tags
+ class FileField < TextField #:nodoc:
+ def render
+ @options.update(:size => nil)
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb b/actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb
new file mode 100644
index 0000000000..507466a57a
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb
@@ -0,0 +1,24 @@
+module ActionView
+ module Helpers
+ module Tags
+ class GroupedCollectionSelect < Base #:nodoc:
+ def initialize(object_name, method_name, template_object, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
+ @collection = collection
+ @group_method = group_method
+ @group_label_method = group_label_method
+ @option_key_method = option_key_method
+ @option_value_method = option_value_method
+ @html_options = html_options
+
+ super(object_name, method_name, template_object, options)
+ end
+
+ def render
+ select_content_tag(
+ option_groups_from_collection_for_select(@collection, @group_method, @group_label_method, @option_key_method, @option_value_method, value(@object)), @options, @html_options
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/hidden_field.rb b/actionpack/lib/action_view/helpers/tags/hidden_field.rb
new file mode 100644
index 0000000000..ea86596e0b
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/hidden_field.rb
@@ -0,0 +1,12 @@
+module ActionView
+ module Helpers
+ module Tags
+ class HiddenField < TextField #:nodoc:
+ def render
+ @options.update(:size => nil)
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/label.rb b/actionpack/lib/action_view/helpers/tags/label.rb
new file mode 100644
index 0000000000..74ac92ee18
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/label.rb
@@ -0,0 +1,65 @@
+module ActionView
+ module Helpers
+ module Tags
+ class Label < Base #:nodoc:
+ def initialize(object_name, method_name, template_object, content_or_options = nil, options = nil)
+ content_is_options = content_or_options.is_a?(Hash)
+ if content_is_options
+ options = content_or_options
+ @content = nil
+ else
+ @content = content_or_options
+ end
+
+ options ||= {}
+
+ super(object_name, method_name, template_object, options)
+ end
+
+ def render(&block)
+ options = @options.stringify_keys
+ tag_value = options.delete("value")
+ name_and_id = options.dup
+
+ if name_and_id["for"]
+ name_and_id["id"] = name_and_id["for"]
+ else
+ name_and_id.delete("id")
+ end
+
+ add_default_name_and_id_for_value(tag_value, name_and_id)
+ options.delete("index")
+ options.delete("namespace")
+ options["for"] ||= name_and_id["id"]
+
+ if block_given?
+ @template_object.label_tag(name_and_id["id"], options, &block)
+ else
+ content = if @content.blank?
+ @object_name.gsub!(/\[(.*)_attributes\]\[\d\]/, '.\1')
+ method_and_value = tag_value.present? ? "#{@method_name}.#{tag_value}" : @method_name
+
+ if object.respond_to?(:to_model)
+ key = object.class.model_name.i18n_key
+ i18n_default = ["#{key}.#{method_and_value}".to_sym, ""]
+ end
+
+ i18n_default ||= ""
+ I18n.t("#{@object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.label").presence
+ else
+ @content.to_s
+ end
+
+ content ||= if object && object.class.respond_to?(:human_attribute_name)
+ object.class.human_attribute_name(@method_name)
+ end
+
+ content ||= @method_name.humanize
+
+ label_tag(name_and_id["id"], content, options)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/number_field.rb b/actionpack/lib/action_view/helpers/tags/number_field.rb
new file mode 100644
index 0000000000..e89fdbec46
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/number_field.rb
@@ -0,0 +1,19 @@
+module ActionView
+ module Helpers
+ module Tags
+ class NumberField < TextField #:nodoc:
+ def render
+ options = @options.stringify_keys
+ options['size'] ||= nil
+
+ if range = options.delete("in") || options.delete("within")
+ options.update("min" => range.min, "max" => range.max)
+ end
+
+ @options = options
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/password_field.rb b/actionpack/lib/action_view/helpers/tags/password_field.rb
new file mode 100644
index 0000000000..6e7a4d3c36
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/password_field.rb
@@ -0,0 +1,12 @@
+module ActionView
+ module Helpers
+ module Tags
+ class PasswordField < TextField #:nodoc:
+ def render
+ @options = {:value => nil}.merge!(@options)
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/radio_button.rb b/actionpack/lib/action_view/helpers/tags/radio_button.rb
new file mode 100644
index 0000000000..8a0421f061
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/radio_button.rb
@@ -0,0 +1,31 @@
+require 'action_view/helpers/tags/checkable'
+
+module ActionView
+ module Helpers
+ module Tags
+ class RadioButton < Base #:nodoc:
+ include Checkable
+
+ def initialize(object_name, method_name, template_object, tag_value, options)
+ @tag_value = tag_value
+ super(object_name, method_name, template_object, options)
+ end
+
+ def render
+ options = @options.stringify_keys
+ options["type"] = "radio"
+ options["value"] = @tag_value
+ options["checked"] = "checked" if input_checked?(object, options)
+ add_default_name_and_id_for_value(@tag_value, options)
+ tag("input", options)
+ end
+
+ private
+
+ def checked?(value)
+ value.to_s == @tag_value.to_s
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/range_field.rb b/actionpack/lib/action_view/helpers/tags/range_field.rb
new file mode 100644
index 0000000000..47db4680e7
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/range_field.rb
@@ -0,0 +1,8 @@
+module ActionView
+ module Helpers
+ module Tags
+ class RangeField < NumberField #:nodoc:
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/search_field.rb b/actionpack/lib/action_view/helpers/tags/search_field.rb
new file mode 100644
index 0000000000..818fd4b887
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/search_field.rb
@@ -0,0 +1,24 @@
+module ActionView
+ module Helpers
+ module Tags
+ class SearchField < TextField #:nodoc:
+ def render
+ options = @options.stringify_keys
+
+ if options["autosave"]
+ if options["autosave"] == true
+ options["autosave"] = request.host.split(".").reverse.join(".")
+ end
+ options["results"] ||= 10
+ end
+
+ if options["onsearch"]
+ options["incremental"] = true unless options.has_key?("incremental")
+ end
+
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/select.rb b/actionpack/lib/action_view/helpers/tags/select.rb
new file mode 100644
index 0000000000..71fd4d04b7
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/select.rb
@@ -0,0 +1,31 @@
+module ActionView
+ module Helpers
+ module Tags
+ class Select < Base #:nodoc:
+ def initialize(object_name, method_name, template_object, choices, options, html_options)
+ @choices = choices
+ @html_options = html_options
+
+ super(object_name, method_name, template_object, options)
+ end
+
+ def render
+ selected_value = @options.has_key?(:selected) ? @options[:selected] : value(@object)
+
+ # Grouped choices look like this:
+ #
+ # [nil, []]
+ # { nil => [] }
+ #
+ if !@choices.empty? && @choices.first.respond_to?(:last) && Array === @choices.first.last
+ option_tags = grouped_options_for_select(@choices, :selected => selected_value, :disabled => @options[:disabled])
+ else
+ option_tags = options_for_select(@choices, :selected => selected_value, :disabled => @options[:disabled])
+ end
+
+ select_content_tag(option_tags, @options, @html_options)
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/tel_field.rb b/actionpack/lib/action_view/helpers/tags/tel_field.rb
new file mode 100644
index 0000000000..87c1f6b6b6
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/tel_field.rb
@@ -0,0 +1,8 @@
+module ActionView
+ module Helpers
+ module Tags
+ class TelField < TextField #:nodoc:
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/text_area.rb b/actionpack/lib/action_view/helpers/tags/text_area.rb
new file mode 100644
index 0000000000..a7db8eb437
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/text_area.rb
@@ -0,0 +1,20 @@
+module ActionView
+ module Helpers
+ module Tags
+ class TextArea < Base #:nodoc:
+ DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }
+
+ def render
+ options = DEFAULT_TEXT_AREA_OPTIONS.merge(@options.stringify_keys)
+ add_default_name_and_id(options)
+
+ if size = options.delete("size")
+ options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
+ end
+
+ content_tag("textarea", ERB::Util.html_escape(options.delete('value') || value_before_type_cast(object)), options)
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/text_field.rb b/actionpack/lib/action_view/helpers/tags/text_field.rb
new file mode 100644
index 0000000000..0f81726eb4
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/text_field.rb
@@ -0,0 +1,24 @@
+module ActionView
+ module Helpers
+ module Tags
+ class TextField < Base #:nodoc:
+ def render
+ options = @options.stringify_keys
+ options["size"] = options["maxlength"] || DEFAULT_FIELD_OPTIONS["size"] unless options.key?("size")
+ options = DEFAULT_FIELD_OPTIONS.merge(options)
+ options["type"] ||= field_type
+ options["value"] = options.fetch("value"){ value_before_type_cast(object) } unless field_type == "file"
+ options["value"] &&= ERB::Util.html_escape(options["value"])
+ add_default_name_and_id(options)
+ tag("input", options)
+ end
+
+ private
+
+ def field_type
+ @field_type ||= self.class.name.split("::").last.sub("Field", "").downcase
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/time_select.rb b/actionpack/lib/action_view/helpers/tags/time_select.rb
new file mode 100644
index 0000000000..9e97deb706
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/time_select.rb
@@ -0,0 +1,8 @@
+module ActionView
+ module Helpers
+ module Tags
+ class TimeSelect < DateSelect #:nodoc:
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/time_zone_select.rb b/actionpack/lib/action_view/helpers/tags/time_zone_select.rb
new file mode 100644
index 0000000000..0a176157c3
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/time_zone_select.rb
@@ -0,0 +1,20 @@
+module ActionView
+ module Helpers
+ module Tags
+ class TimeZoneSelect < Base #:nodoc:
+ def initialize(object_name, method_name, template_object, priority_zones, options, html_options)
+ @priority_zones = priority_zones
+ @html_options = html_options
+
+ super(object_name, method_name, template_object, options)
+ end
+
+ def render
+ select_content_tag(
+ time_zone_options_for_select(value(@object) || @options[:default], @priority_zones, @options[:model] || ActiveSupport::TimeZone), @options, @html_options
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/url_field.rb b/actionpack/lib/action_view/helpers/tags/url_field.rb
new file mode 100644
index 0000000000..1ffdfe0b3c
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/url_field.rb
@@ -0,0 +1,8 @@
+module ActionView
+ module Helpers
+ module Tags
+ class UrlField < TextField #:nodoc:
+ end
+ end
+ end
+end
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index 82e001732d..5be6a2e4e5 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -232,6 +232,10 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal('', label(:post, :title) { "The title, please:" })
end
+ def test_label_with_block_and_options
+ assert_dom_equal('', label(:post, :title, "for" => "my_for") { "The title, please:" })
+ end
+
def test_label_with_block_in_erb
assert_equal "", view.render("test/label_with_block")
end