Merge pull request #1552 from bogdan/select
Fixing select[multiple] html specification problem.
This commit is contained in:
commit
f5e1548c1b
|
@ -128,6 +128,28 @@ module ActionView
|
|||
# By default, <tt>post.person_id</tt> is the selected option. Specify <tt>:selected => value</tt> to use a different selection
|
||||
# or <tt>:selected => nil</tt> to leave all options unselected. Similarly, you can specify values to be disabled in the option
|
||||
# tags by specifying the <tt>:disabled</tt> option. This can either be a single value or an array of values to be disabled.
|
||||
#
|
||||
# ==== Gotcha
|
||||
#
|
||||
# The HTML specification says when +multiple+ parameter passed to select and all options got deselected
|
||||
# web browsers do not send any value to server. Unfortunately this introduces a gotcha:
|
||||
# if an +User+ model has many +roles+ and have +role_ids+ accessor, and in the form that edits roles of the user
|
||||
# the user deselects all roles from +role_ids+ multiple select box, no +role_ids+ parameter is sent. So,
|
||||
# any mass-assignment idiom like
|
||||
#
|
||||
# @user.update_attributes(params[:user])
|
||||
#
|
||||
# wouldn't update roles.
|
||||
#
|
||||
# To prevent this the helper generates an auxiliary hidden field before
|
||||
# every multiple select. The hidden field has the same name as multiple select and blank value.
|
||||
#
|
||||
# This way, the client either sends only the hidden field (representing
|
||||
# the deselected multiple select box), or both fields. Since the HTML specification
|
||||
# says key/value pairs have to be sent in the same order they appear in the
|
||||
# form, and parameters extraction gets the last occurrence of any repeated
|
||||
# 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)
|
||||
end
|
||||
|
@ -557,7 +579,7 @@ module ActionView
|
|||
value = value(object)
|
||||
selected_value = options.has_key?(:selected) ? options[:selected] : value
|
||||
disabled_value = options.has_key?(:disabled) ? options[:disabled] : nil
|
||||
content_tag("select", add_options(options_for_select(choices, :selected => selected_value, :disabled => disabled_value), options, selected_value), html_options)
|
||||
select_content_tag(add_options(options_for_select(choices, :selected => selected_value, :disabled => disabled_value), options, selected_value), html_options)
|
||||
end
|
||||
|
||||
def to_collection_select_tag(collection, value_method, text_method, options, html_options)
|
||||
|
@ -566,8 +588,8 @@ module ActionView
|
|||
value = value(object)
|
||||
disabled_value = options.has_key?(:disabled) ? options[:disabled] : nil
|
||||
selected_value = options.has_key?(:selected) ? options[:selected] : value
|
||||
content_tag(
|
||||
"select", add_options(options_from_collection_for_select(collection, value_method, text_method, :selected => selected_value, :disabled => disabled_value), options, value), html_options
|
||||
select_content_tag(
|
||||
add_options(options_from_collection_for_select(collection, value_method, text_method, :selected => selected_value, :disabled => disabled_value), options, value), html_options
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -575,8 +597,8 @@ module ActionView
|
|||
html_options = html_options.stringify_keys
|
||||
add_default_name_and_id(html_options)
|
||||
value = value(object)
|
||||
content_tag(
|
||||
"select", add_options(option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, value), options, value), html_options
|
||||
select_content_tag(
|
||||
add_options(option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, value), options, value), html_options
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -584,7 +606,7 @@ module ActionView
|
|||
html_options = html_options.stringify_keys
|
||||
add_default_name_and_id(html_options)
|
||||
value = value(object)
|
||||
content_tag("select",
|
||||
select_content_tag(
|
||||
add_options(
|
||||
time_zone_options_for_select(value || options[:default], priority_zones, options[:model] || ActiveSupport::TimeZone),
|
||||
options, value
|
||||
|
@ -603,6 +625,15 @@ module ActionView
|
|||
end
|
||||
option_tags.html_safe
|
||||
end
|
||||
|
||||
def select_content_tag(content, html_options)
|
||||
select = content_tag("select", content, 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
|
||||
|
|
|
@ -457,6 +457,22 @@ class FormOptionsHelperTest < ActionView::TestCase
|
|||
)
|
||||
end
|
||||
|
||||
def test_select_with_multiple_to_add_hidden_input
|
||||
output_buffer = select(:post, :category, "", {}, :multiple => true)
|
||||
assert_dom_equal(
|
||||
"<input type=\"hidden\" name=\"post[category][]\" value=\"\"/><select multiple=\"multiple\" id=\"post_category\" name=\"post[category][]\"></select>",
|
||||
output_buffer
|
||||
)
|
||||
end
|
||||
|
||||
def test_select_with_multiple_and_disabled_to_add_disabled_hidden_input
|
||||
output_buffer = select(:post, :category, "", {}, :multiple => true, :disabled => true)
|
||||
assert_dom_equal(
|
||||
"<input disabled=\"disabled\"type=\"hidden\" name=\"post[category][]\" value=\"\"/><select multiple=\"multiple\" disabled=\"disabled\" id=\"post_category\" name=\"post[category][]\"></select>",
|
||||
output_buffer
|
||||
)
|
||||
end
|
||||
|
||||
def test_select_with_blank
|
||||
@post = Post.new
|
||||
@post.category = "<mus>"
|
||||
|
@ -649,11 +665,11 @@ class FormOptionsHelperTest < ActionView::TestCase
|
|||
)
|
||||
end
|
||||
|
||||
def test_collection_select_with_multiple_option_appends_array_brackets
|
||||
def test_collection_select_with_multiple_option_appends_array_brackets_and_hidden_input
|
||||
@post = Post.new
|
||||
@post.author_name = "Babe"
|
||||
|
||||
expected = "<select id=\"post_author_name\" name=\"post[author_name][]\" multiple=\"multiple\"><option value=\"\"></option>\n<option value=\"<Abe>\"><Abe></option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>"
|
||||
expected = "<input type=\"hidden\" name=\"post[author_name][]\" value=\"\"/><select id=\"post_author_name\" name=\"post[author_name][]\" multiple=\"multiple\"><option value=\"\"></option>\n<option value=\"<Abe>\"><Abe></option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>"
|
||||
|
||||
# Should suffix default name with [].
|
||||
assert_dom_equal expected, collection_select("post", "author_name", dummy_posts, "author_name", "author_name", { :include_blank => true }, :multiple => true)
|
||||
|
|
Loading…
Reference in New Issue