From 59d04c496dbdac5ca523ee9dedf41abbcc5c073c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20B=C3=A4rtschi?= Date: Fri, 1 Aug 2025 11:09:51 +0200 Subject: [PATCH 1/2] feat(custom option tags): allow custom tags in options_for_select --- lib/phoenix_html/form.ex | 28 +++++++++++++++--------- test/phoenix_html/form_test.exs | 38 +++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 10 deletions(-) diff --git a/lib/phoenix_html/form.ex b/lib/phoenix_html/form.ex index 63f1a8a..b95f1e9 100644 --- a/lib/phoenix_html/form.ex +++ b/lib/phoenix_html/form.ex @@ -289,7 +289,7 @@ defmodule Phoenix.HTML.Form do an atom, string or integer to be used as the option value * simple atom, string or integer - which will be used as both label and value for the generated select - + ## Option groups If `options` is map or keyword list where the first element is a string, @@ -320,6 +320,12 @@ defmodule Phoenix.HTML.Form do #=> #=> + Custom option tags: + + options_for_select(["Admin": "admin", "User": "user"], nil, tag: "opt") + #=> Admin + #=> User + Horizontal separators can be added: options_for_select(["Admin", "User", :hr, "New"], nil) @@ -336,21 +342,22 @@ defmodule Phoenix.HTML.Form do """ - def options_for_select(options, selected_values) do + def options_for_select(options, selected_values, extra \\ []) do {:safe, escaped_options_for_select( options, - selected_values |> List.wrap() |> Enum.map(&html_escape/1) + selected_values |> List.wrap() |> Enum.map(&html_escape/1), + extra )} end - defp escaped_options_for_select(options, selected_values) do + defp escaped_options_for_select(options, selected_values, extra) do Enum.reduce(options, [], fn {:hr, nil}, acc -> [acc | hr_tag()] {option_key, option_value}, acc -> - [acc | option(option_key, option_value, [], selected_values)] + [acc | option(option_key, option_value, extra, selected_values)] options, acc when is_list(options) -> {option_key, options} = @@ -373,19 +380,19 @@ defmodule Phoenix.HTML.Form do {value, options} end - [acc | option(option_key, option_value, options, selected_values)] + [acc | option(option_key, option_value, extra ++ options, selected_values)] :hr, acc -> [acc | hr_tag()] option, acc -> - [acc | option(option, option, [], selected_values)] + [acc | option(option, option, extra, selected_values)] end) end - defp option(group_label, group_values, [], value) + defp option(group_label, group_values, extra, value) when is_list(group_values) or is_map(group_values) do - section_options = escaped_options_for_select(group_values, value) + section_options = escaped_options_for_select(group_values, value, extra) option_tag("optgroup", [label: group_label], {:safe, section_options}) end @@ -393,7 +400,8 @@ defmodule Phoenix.HTML.Form do option_key = html_escape(option_key) option_value = html_escape(option_value) attrs = extra ++ [selected: option_value in value, value: option_value] - option_tag("option", attrs, option_key) + {tag, attrs} = Keyword.pop(attrs, :tag, "option") + option_tag(tag, attrs, option_key) end defp option_tag(name, attrs, {:safe, body}) when is_binary(name) and is_list(attrs) do diff --git a/test/phoenix_html/form_test.exs b/test/phoenix_html/form_test.exs index b8b2921..5f6946d 100644 --- a/test/phoenix_html/form_test.exs +++ b/test/phoenix_html/form_test.exs @@ -295,6 +295,44 @@ defmodule Phoenix.HTML.FormTest do ~s() end + + test "with custom option tag" do + assert options_for_select(["value", "novalue", nil], "novalue", tag: "el-option") |> safe_to_string() == + ~s(value) <> + ~s(novalue) <> + ~s() + + assert options_for_select(["value", :hr, "novalue"], "novalue", tag: "el-option") |> safe_to_string() == + ~s(value) <> + ~s(
) <> + ~s(novalue) + + assert options_for_select( + [ + [value: "value", key: "Value", disabled: true], + :hr, + [value: "novalue", key: "No Value"], + [value: nil, key: nil] + ], + "novalue", + tag: "el-option" + ) + |> safe_to_string() == + ~s(Value) <> + ~s(
) <> + ~s(No Value) <> + ~s() + + assert options_for_select(~w(value novalue), ["value", "novalue"], tag: "el-option") |> safe_to_string() == + ~s(value) <> + ~s(novalue) + + assert options_for_select([Label: "value", hr: nil, New: "new"], nil, tag: "el-option") |> safe_to_string() == + ~s(Label) <> + ~s(
) <> + ~s(New) + end + test "with groups" do assert options_for_select([{"foo", ["bar", :hr, "baz"]}, {"qux", ~w(qux quz)}], "qux") |> safe_to_string() == From ce645a660536db541f320f868cb799c074d326a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20B=C3=A4rtschi?= Date: Fri, 1 Aug 2025 11:26:08 +0200 Subject: [PATCH 2/2] fix(cicd): run mix format --- test/phoenix_html/form_test.exs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/test/phoenix_html/form_test.exs b/test/phoenix_html/form_test.exs index 5f6946d..87e7416 100644 --- a/test/phoenix_html/form_test.exs +++ b/test/phoenix_html/form_test.exs @@ -295,14 +295,15 @@ defmodule Phoenix.HTML.FormTest do ~s() end - test "with custom option tag" do - assert options_for_select(["value", "novalue", nil], "novalue", tag: "el-option") |> safe_to_string() == + assert options_for_select(["value", "novalue", nil], "novalue", tag: "el-option") + |> safe_to_string() == ~s(value) <> ~s(novalue) <> ~s() - assert options_for_select(["value", :hr, "novalue"], "novalue", tag: "el-option") |> safe_to_string() == + assert options_for_select(["value", :hr, "novalue"], "novalue", tag: "el-option") + |> safe_to_string() == ~s(value) <> ~s(
) <> ~s(novalue) @@ -323,11 +324,13 @@ defmodule Phoenix.HTML.FormTest do ~s(No Value) <> ~s() - assert options_for_select(~w(value novalue), ["value", "novalue"], tag: "el-option") |> safe_to_string() == + assert options_for_select(~w(value novalue), ["value", "novalue"], tag: "el-option") + |> safe_to_string() == ~s(value) <> ~s(novalue) - assert options_for_select([Label: "value", hr: nil, New: "new"], nil, tag: "el-option") |> safe_to_string() == + assert options_for_select([Label: "value", hr: nil, New: "new"], nil, tag: "el-option") + |> safe_to_string() == ~s(Label) <> ~s(
) <> ~s(New)