Repository: phoenixframework/phoenix_html
Pull request #108 (2016-08-13 18:05:25)

Support for suggested syntax by @josevalim
It seems to support the two other version that @josevalim suggested did already work. The only thing needed was to easy the guard on line 738 of lib/phoenix_html/form.ex to also allow for maps

I've added two more tests that cover the suggested syntax and merged the current master of phoenixframework/phoenix_html.

I've tested this on one of my own projects and everything seems fine.

Thank you @ciaran for the main work :) 


Patch:
diff --git a/lib/phoenix_html/form.ex b/lib/phoenix_html/form.ex
index 3f044f3..35a53df 100644
--- a/lib/phoenix_html/form.ex
+++ b/lib/phoenix_html/form.ex
@@ -657,6 +657,11 @@ defmodule Phoenix.HTML.Form do
   (like a regular list or a map) or any Enumerable where the element
   will be used both as key and value for the generated select.
 
+  ## Optgroups
+
+  When a two-item map contains a list as its value,
+  the options will be wrapped in an `<optroup>` using the key as its label.
+
   ## Examples
 
       # Assuming form contains a User schema
@@ -680,6 +685,16 @@ defmodule Phoenix.HTML.Form do
           <option value="user">User</option>
           </select>
 
+      select(form, :country, %{"Europe" => ["UK", "Sweden", "France"], …})
+      #=> <select id="user_country" name="user[country]">
+          <optgroup label="Europe">
+            <option>UK</option>
+            <option>Sweden</option>
+            <option>France</option>
+          </optgroup>
+          ...
+          </select>
+
   ## Options
 
     * `:prompt` - an option to include at the top of the options with
@@ -714,22 +729,30 @@ defmodule Phoenix.HTML.Form do
   defp options_for_select(values, options, value) do
     Enum.reduce values, options, fn
       {option_key, option_value}, acc ->
-        option_key   = html_escape(option_key)
-        option_value = html_escape(option_value)
         option(option_key, option_value, value, acc)
       option, acc ->
-        option = html_escape(option)
         option(option, option, value, acc)
     end
   end
 
-  defp option(option_key, option_value, values, acc) when is_list(values) do
-    opts = [value: option_value, selected: option_value in values]
-    html_escape [acc|content_tag(:option, option_key, opts)]
+  defp option(group_label, group_values, value, acc) when is_list(group_values) or is_map(group_values) do
+    section_options = options_for_select(group_values, [], value)
+    tag = content_tag(:optgroup, section_options, label: group_label)
+    html_escape [acc|tag]
   end
 
   defp option(option_key, option_value, value, acc) do
-    opts = [value: option_value, selected: value == option_value]
+    option_key   = html_escape(option_key)
+    option_value = html_escape(option_value)
+
+    selected =
+      if is_list(value) do
+        option_value in value
+      else
+        value == option_value
+      end
+
+    opts = [value: option_value, selected: selected]
     html_escape [acc|content_tag(:option, option_key, opts)]
   end
 


Test patch:
diff --git a/test/phoenix_html/form_test.exs b/test/phoenix_html/form_test.exs
index f1c5de7..5cb8281 100644
--- a/test/phoenix_html/form_test.exs
+++ b/test/phoenix_html/form_test.exs
@@ -448,6 +448,56 @@ defmodule Phoenix.HTML.FormTest do
            ~s(</select>)
   end
 
+  test "select/4 with groups" do
+    assert safe_form(&select(&1, :key, [{"foo", ~w(bar baz)}, {"qux", ~w(qux quz)}], value: "qux")) ==
+           ~s(<select id="search_key" name="search[key]">) <>
+           ~s(<optgroup label="foo">) <>
+           ~s(<option value="bar">bar</option>) <>
+           ~s(<option value="baz">baz</option>) <>
+           ~s(</optgroup>) <>
+           ~s(<optgroup label="qux">) <>
+           ~s(<option selected="selected" value="qux">qux</option>) <>
+           ~s(<option value="quz">quz</option>) <>
+           ~s(</optgroup>) <>
+           ~s(</select>)
+
+    assert safe_form(&select(&1, :key, [{"foo", [{"1", "One"}, {"2", "Two"}]}, {"qux", ~w(qux quz)}], value: "qux")) ==
+           ~s(<select id="search_key" name="search[key]">) <>
+           ~s(<optgroup label="foo">) <>
+           ~s(<option value="One">1</option>) <>
+           ~s(<option value="Two">2</option>) <>
+           ~s(</optgroup>) <>
+           ~s(<optgroup label="qux">) <>
+           ~s(<option selected="selected" value="qux">qux</option>) <>
+           ~s(<option value="quz">quz</option>) <>
+           ~s(</optgroup>) <>
+           ~s(</select>)
+
+    assert safe_form(&select(&1, :key, %{"foo" => %{"1" => "One", "2" => "Two"}, "qux" => ~w(qux quz)}, value: "qux")) ==
+           ~s(<select id="search_key" name="search[key]">) <>
+           ~s(<optgroup label="foo">) <>
+           ~s(<option value="One">1</option>) <>
+           ~s(<option value="Two">2</option>) <>
+           ~s(</optgroup>) <>
+           ~s(<optgroup label="qux">) <>
+           ~s(<option selected="selected" value="qux">qux</option>) <>
+           ~s(<option value="quz">quz</option>) <>
+           ~s(</optgroup>) <>
+           ~s(</select>)
+
+    assert safe_form(&select(&1, :key, %{"foo" => [{"1", "One"}, {"2", "Two"}], "qux" => ~w(qux quz)}, value: "qux")) ==
+           ~s(<select id="search_key" name="search[key]">) <>
+           ~s(<optgroup label="foo">) <>
+           ~s(<option value="One">1</option>) <>
+           ~s(<option value="Two">2</option>) <>
+           ~s(</optgroup>) <>
+           ~s(<optgroup label="qux">) <>
+           ~s(<option selected="selected" value="qux">qux</option>) <>
+           ~s(<option value="quz">quz</option>) <>
+           ~s(</optgroup>) <>
+           ~s(</select>)
+  end
+
   # multiple_select/4
 
   test "multiple_select/4" do
@@ -472,6 +522,17 @@ defmodule Phoenix.HTML.FormTest do
     assert safe_to_string(multiple_select(:search, :key, [{"foo", 1}, {"bar", 2}], selected: [1])) =~
            ~s(<option selected="selected" value="1">foo</option>)
 
+    assert safe_to_string(multiple_select(:search, :key, %{"foo" => [{"One", 1}, {"Two", 2}], "bar" => ~w(3 4)})) ==
+           ~s(<select id="search_key" multiple="" name="search[key][]">) <>
+           ~s(<optgroup label="bar">) <>
+           ~s(<option value="3">3</option>) <>
+           ~s(<option value="4">4</option>) <>
+           ~s(</optgroup>) <>
+           ~s(<optgroup label="foo">) <>
+           ~s(<option value="1">One</option>) <>
+           ~s(<option value="2">Two</option>) <>
+           ~s(</optgroup>) <>
+           ~s(</select>)
   end
 
   test "multiple_select/4 with form" do