aboutsummaryrefslogtreecommitdiffstats
path: root/actionview/lib/action_view/helpers/tags
diff options
context:
space:
mode:
Diffstat (limited to 'actionview/lib/action_view/helpers/tags')
-rw-r--r--actionview/lib/action_view/helpers/tags/base.rb74
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_check_boxes.rb32
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_helpers.rb28
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb10
-rw-r--r--actionview/lib/action_view/helpers/tags/file_field.rb15
-rw-r--r--actionview/lib/action_view/helpers/tags/label.rb58
-rw-r--r--actionview/lib/action_view/helpers/tags/placeholderable.rb22
-rw-r--r--actionview/lib/action_view/helpers/tags/search_field.rb1
-rw-r--r--actionview/lib/action_view/helpers/tags/select.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/text_area.rb4
-rw-r--r--actionview/lib/action_view/helpers/tags/text_field.rb4
-rw-r--r--actionview/lib/action_view/helpers/tags/translator.rb40
12 files changed, 197 insertions, 93 deletions
diff --git a/actionview/lib/action_view/helpers/tags/base.rb b/actionview/lib/action_view/helpers/tags/base.rb
index 8607da301c..d57f26ba4f 100644
--- a/actionview/lib/action_view/helpers/tags/base.rb
+++ b/actionview/lib/action_view/helpers/tags/base.rb
@@ -14,7 +14,7 @@ module ActionView
@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
+ @auto_index = Regexp.last_match ? retrieve_autoindex(Regexp.last_match.pre_match) : nil
end
# This is what child classes implement.
@@ -25,19 +25,26 @@ module ActionView
private
def value(object)
- object.send @method_name if object
+ object.public_send @method_name if object
end
def value_before_type_cast(object)
unless object.nil?
method_before_type_cast = @method_name + "_before_type_cast"
- object.respond_to?(method_before_type_cast) ?
- object.send(method_before_type_cast) :
+ if value_came_from_user?(object) && object.respond_to?(method_before_type_cast)
+ object.public_send(method_before_type_cast)
+ else
value(object)
+ end
end
end
+ def value_came_from_user?(object)
+ method_name = "#{@method_name}_came_from_user?"
+ !object.respond_to?(method_name) || object.public_send(method_name)
+ end
+
def retrieve_object(object)
if object
object
@@ -72,35 +79,30 @@ module ActionView
end
def add_default_name_and_id(options)
- if options.has_key?("index")
- options["name"] ||= options.fetch("name"){ tag_name_with_index(options["index"], options["multiple"]) }
- options["id"] = options.fetch("id"){ tag_id_with_index(options["index"]) }
- options.delete("index")
- elsif defined?(@auto_index)
- options["name"] ||= options.fetch("name"){ tag_name_with_index(@auto_index, options["multiple"]) }
- options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) }
- else
- options["name"] ||= options.fetch("name"){ tag_name(options["multiple"]) }
- options["id"] = options.fetch("id"){ tag_id }
+ index = name_and_id_index(options)
+ options["name"] = options.fetch("name"){ tag_name(options["multiple"], index) }
+ options["id"] = options.fetch("id"){ tag_id(index) }
+ if namespace = options.delete("namespace")
+ options['id'] = options['id'] ? "#{namespace}_#{options['id']}" : namespace
end
-
- options["id"] = [options.delete('namespace'), options["id"]].compact.join("_").presence
- end
-
- def tag_name(multiple = false)
- "#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}"
- end
-
- def tag_name_with_index(index, multiple = false)
- "#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}"
end
- def tag_id
- "#{sanitized_object_name}_#{sanitized_method_name}"
+ def tag_name(multiple = false, index = nil)
+ # a little duplication to construct less strings
+ if index
+ "#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}"
+ else
+ "#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}"
+ end
end
- def tag_id_with_index(index)
- "#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
+ def tag_id(index = nil)
+ # a little duplication to construct less strings
+ if index
+ "#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
+ else
+ "#{sanitized_object_name}_#{sanitized_method_name}"
+ end
end
def sanitized_object_name
@@ -118,7 +120,12 @@ module ActionView
def select_content_tag(option_tags, options, html_options)
html_options = html_options.stringify_keys
add_default_name_and_id(html_options)
- options[:include_blank] ||= true unless options[:prompt] || select_not_required?(html_options)
+
+ if placeholder_required?(html_options)
+ raise ArgumentError, "include_blank cannot be false for a required field." if options[:include_blank] == false
+ options[:include_blank] ||= true unless options[:prompt]
+ end
+
value = options.fetch(:selected) { value(object) }
select = content_tag("select", add_options(option_tags, options, value), html_options)
@@ -129,8 +136,9 @@ module ActionView
end
end
- def select_not_required?(html_options)
- !html_options["required"] || html_options["multiple"] || html_options["size"].to_i > 1
+ def placeholder_required?(html_options)
+ # See https://html.spec.whatwg.org/multipage/forms.html#attr-select-required
+ html_options["required"] && !html_options["multiple"] && html_options.fetch("size", 1).to_i == 1
end
def add_options(option_tags, options, value = nil)
@@ -142,6 +150,10 @@ module ActionView
end
option_tags
end
+
+ def name_and_id_index(options)
+ options.key?("index") ? options.delete("index") || "" : @auto_index
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
index 6242a2a085..3256d44e18 100644
--- a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
@@ -9,29 +9,13 @@ module ActionView
class CheckBoxBuilder < Builder # :nodoc:
def check_box(extra_html_options={})
html_options = extra_html_options.merge(@input_html_options)
+ html_options[:multiple] = true
@template_object.check_box(@object_name, @method_name, html_options, @value, nil)
end
end
def render(&block)
- rendered_collection = render_collection do |item, value, text, default_html_options|
- default_html_options[:multiple] = true
- builder = instantiate_builder(CheckBoxBuilder, item, value, text, default_html_options)
-
- if block_given?
- @template_object.capture(builder, &block)
- else
- render_component(builder)
- end
- end
-
- # Append a hidden field to make sure something will be sent back to the
- # server if all check boxes are unchecked.
- if @options.fetch(:include_hidden, true)
- rendered_collection + hidden_field
- else
- rendered_collection
- end
+ render_collection_for(CheckBoxBuilder, &block)
end
private
@@ -39,18 +23,6 @@ module ActionView
def render_component(builder)
builder.check_box + builder.label
end
-
- def hidden_field
- hidden_name = @html_options[:name]
-
- hidden_name ||= if @options.has_key?(:index)
- "#{tag_name_with_index(@options[:index])}[]"
- else
- "#{tag_name}[]"
- end
-
- @template_object.hidden_field_tag(hidden_name, "", id: nil)
- end
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/collection_helpers.rb b/actionview/lib/action_view/helpers/tags/collection_helpers.rb
index 8050638363..b87b4281d6 100644
--- a/actionview/lib/action_view/helpers/tags/collection_helpers.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_helpers.rb
@@ -19,6 +19,8 @@ module ActionView
def label(label_html_options={}, &block)
html_options = @input_html_options.slice(:index, :namespace).merge(label_html_options)
+ html_options[:for] ||= @input_html_options[:id] if @input_html_options[:id]
+
@template_object.label(@object_name, @sanitized_attribute_name, @text, html_options, &block)
end
end
@@ -79,6 +81,32 @@ module ActionView
yield item, value, text, default_html_options.merge(additional_html_options)
end.join.html_safe
end
+
+ def render_collection_for(builder_class, &block) #:nodoc:
+ options = @options.stringify_keys
+ rendered_collection = render_collection do |item, value, text, default_html_options|
+ builder = instantiate_builder(builder_class, item, value, text, default_html_options)
+
+ if block_given?
+ @template_object.capture(builder, &block)
+ else
+ render_component(builder)
+ end
+ end
+
+ # Append a hidden field to make sure something will be sent back to the
+ # server if all radio buttons are unchecked.
+ if options.fetch('include_hidden', true)
+ rendered_collection + hidden_field
+ else
+ rendered_collection
+ end
+ end
+
+ def hidden_field #:nodoc:
+ hidden_name = @html_options[:name] || "#{tag_name(false, @options[:index])}[]"
+ @template_object.hidden_field_tag(hidden_name, "", id: nil)
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb b/actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb
index 20be34c1f2..21aaf122f8 100644
--- a/actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb
@@ -14,15 +14,7 @@ module ActionView
end
def render(&block)
- render_collection do |item, value, text, default_html_options|
- builder = instantiate_builder(RadioButtonBuilder, item, value, text, default_html_options)
-
- if block_given?
- @template_object.capture(builder, &block)
- else
- render_component(builder)
- end
- end
+ render_collection_for(RadioButtonBuilder, &block)
end
private
diff --git a/actionview/lib/action_view/helpers/tags/file_field.rb b/actionview/lib/action_view/helpers/tags/file_field.rb
index 476b820d84..e6a1d9c62d 100644
--- a/actionview/lib/action_view/helpers/tags/file_field.rb
+++ b/actionview/lib/action_view/helpers/tags/file_field.rb
@@ -2,6 +2,21 @@ module ActionView
module Helpers
module Tags # :nodoc:
class FileField < TextField # :nodoc:
+
+ def render
+ options = @options.stringify_keys
+
+ if options.fetch("include_hidden", true)
+ add_default_name_and_id(options)
+ options[:type] = "file"
+ tag("input", name: options["name"], type: "hidden", value: "") + tag("input", options)
+ else
+ options.delete("include_hidden")
+ @options = options
+
+ super
+ end
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/label.rb b/actionview/lib/action_view/helpers/tags/label.rb
index a5bcaf8153..b31d5fda66 100644
--- a/actionview/lib/action_view/helpers/tags/label.rb
+++ b/actionview/lib/action_view/helpers/tags/label.rb
@@ -2,6 +2,29 @@ module ActionView
module Helpers
module Tags # :nodoc:
class Label < Base # :nodoc:
+ class LabelBuilder # :nodoc:
+ attr_reader :object
+
+ def initialize(template_object, object_name, method_name, object, tag_value)
+ @template_object = template_object
+ @object_name = object_name
+ @method_name = method_name
+ @object = object
+ @tag_value = tag_value
+ end
+
+ def translation
+ method_and_value = @tag_value.present? ? "#{@method_name}.#{@tag_value}" : @method_name
+
+ content ||= Translator
+ .new(object, @object_name, method_and_value, scope: "helpers.label")
+ .translate
+ content ||= @method_name.humanize
+
+ content
+ end
+ end
+
def initialize(object_name, method_name, template_object, content_or_options = nil, options = nil)
options ||= {}
@@ -32,33 +55,24 @@ module ActionView
options.delete("namespace")
options["for"] = name_and_id["id"] unless options.key?("for")
- if block_given?
- content = @template_object.capture(&block)
- else
- method_and_value = tag_value.present? ? "#{@method_name}.#{tag_value}" : @method_name
- content = if @content.blank?
- @object_name.gsub!(/\[(.*)_attributes\]\[\d+\]/, '.\1')
-
- 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
+ builder = LabelBuilder.new(@template_object, @object_name, @method_name, @object, tag_value)
- content ||= if object && object.class.respond_to?(:human_attribute_name)
- object.class.human_attribute_name(method_and_value)
- end
-
- content ||= @method_name.humanize
+ content = if block_given?
+ @template_object.capture(builder, &block)
+ elsif @content.present?
+ @content.to_s
+ else
+ render_component(builder)
end
label_tag(name_and_id["id"], content, options)
end
+
+ private
+
+ def render_component(builder)
+ builder.translation
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/placeholderable.rb b/actionview/lib/action_view/helpers/tags/placeholderable.rb
new file mode 100644
index 0000000000..cf7b117614
--- /dev/null
+++ b/actionview/lib/action_view/helpers/tags/placeholderable.rb
@@ -0,0 +1,22 @@
+module ActionView
+ module Helpers
+ module Tags # :nodoc:
+ module Placeholderable # :nodoc:
+ def initialize(*)
+ super
+
+ if tag_value = @options[:placeholder]
+ placeholder = tag_value if tag_value.is_a?(String)
+ method_and_value = tag_value.is_a?(TrueClass) ? @method_name : "#{@method_name}.#{tag_value}"
+
+ placeholder ||= Tags::Translator
+ .new(object, @object_name, method_and_value, scope: "helpers.placeholder")
+ .translate
+ placeholder ||= @method_name.humanize
+ @options[:placeholder] = placeholder
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actionview/lib/action_view/helpers/tags/search_field.rb b/actionview/lib/action_view/helpers/tags/search_field.rb
index c09e2f1be7..a848aeabfa 100644
--- a/actionview/lib/action_view/helpers/tags/search_field.rb
+++ b/actionview/lib/action_view/helpers/tags/search_field.rb
@@ -16,6 +16,7 @@ module ActionView
options["incremental"] = true unless options.has_key?("incremental")
end
+ @options = options
super
end
end
diff --git a/actionview/lib/action_view/helpers/tags/select.rb b/actionview/lib/action_view/helpers/tags/select.rb
index 00881d9978..180900cc8d 100644
--- a/actionview/lib/action_view/helpers/tags/select.rb
+++ b/actionview/lib/action_view/helpers/tags/select.rb
@@ -3,7 +3,7 @@ module ActionView
module Tags # :nodoc:
class Select < Base # :nodoc:
def initialize(object_name, method_name, template_object, choices, options, html_options)
- @choices = block_given? ? template_object.capture { yield } : choices
+ @choices = block_given? ? template_object.capture { yield || "" } : choices
@choices = @choices.to_a if @choices.is_a?(Range)
@html_options = html_options
diff --git a/actionview/lib/action_view/helpers/tags/text_area.rb b/actionview/lib/action_view/helpers/tags/text_area.rb
index 9ee83ee7c2..69038c1498 100644
--- a/actionview/lib/action_view/helpers/tags/text_area.rb
+++ b/actionview/lib/action_view/helpers/tags/text_area.rb
@@ -1,7 +1,11 @@
+require 'action_view/helpers/tags/placeholderable'
+
module ActionView
module Helpers
module Tags # :nodoc:
class TextArea < Base # :nodoc:
+ include Placeholderable
+
def render
options = @options.stringify_keys
add_default_name_and_id(options)
diff --git a/actionview/lib/action_view/helpers/tags/text_field.rb b/actionview/lib/action_view/helpers/tags/text_field.rb
index e0b80d81c2..5c576a20ca 100644
--- a/actionview/lib/action_view/helpers/tags/text_field.rb
+++ b/actionview/lib/action_view/helpers/tags/text_field.rb
@@ -1,7 +1,11 @@
+require 'action_view/helpers/tags/placeholderable'
+
module ActionView
module Helpers
module Tags # :nodoc:
class TextField < Base # :nodoc:
+ include Placeholderable
+
def render
options = @options.stringify_keys
options["size"] = options["maxlength"] unless options.key?("size")
diff --git a/actionview/lib/action_view/helpers/tags/translator.rb b/actionview/lib/action_view/helpers/tags/translator.rb
new file mode 100644
index 0000000000..8b6655481d
--- /dev/null
+++ b/actionview/lib/action_view/helpers/tags/translator.rb
@@ -0,0 +1,40 @@
+module ActionView
+ module Helpers
+ module Tags # :nodoc:
+ class Translator # :nodoc:
+ def initialize(object, object_name, method_and_value, scope:)
+ @object_name = object_name.gsub(/\[(.*)_attributes\]\[\d+\]/, '.\1')
+ @method_and_value = method_and_value
+ @scope = scope
+ @model = object.respond_to?(:to_model) ? object.to_model : nil
+ end
+
+ def translate
+ translated_attribute = I18n.t("#{object_name}.#{method_and_value}", default: i18n_default, scope: scope).presence
+ translated_attribute || human_attribute_name
+ end
+
+ protected
+
+ attr_reader :object_name, :method_and_value, :scope, :model
+
+ private
+
+ def i18n_default
+ if model
+ key = model.model_name.i18n_key
+ ["#{key}.#{method_and_value}".to_sym, ""]
+ else
+ ""
+ end
+ end
+
+ def human_attribute_name
+ if model && model.class.respond_to?(:human_attribute_name)
+ model.class.human_attribute_name(method_and_value)
+ end
+ end
+ end
+ end
+ end
+end