diff --git a/CHANGELOG.md b/CHANGELOG.md index 267a080..5f9c46e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## v3.3.3 (2023-10-09) + +* Enhancements + * Allow string fields on `input_changed?` + ## v3.3.2 (2023-08-10) * Enhancements diff --git a/lib/phoenix_html/form.ex b/lib/phoenix_html/form.ex index ddf2177..53a7eec 100644 --- a/lib/phoenix_html/form.ex +++ b/lib/phoenix_html/form.ex @@ -74,7 +74,7 @@ defmodule Phoenix.HTML.Form do params: %{binary => term}, hidden: Keyword.t(), options: Keyword.t(), - errors: Keyword.t(), + errors: [{field, term}], impl: module, id: String.t(), index: nil | non_neg_integer, @@ -100,7 +100,7 @@ defmodule Phoenix.HTML.Form do defp fetch(%{errors: errors} = form, field, field_as_string) do {:ok, %Phoenix.HTML.FormField{ - errors: for({^field, value} <- errors, do: value), + errors: field_errors(errors, field), field: field, form: form, id: input_id(form, field_as_string), @@ -193,15 +193,15 @@ defmodule Phoenix.HTML.Form do changed. This is mostly used for optimization engines as an extension of the `Access` behaviour. """ - @spec input_changed?(t, t, atom) :: boolean() + @spec input_changed?(t, t, field()) :: boolean() def input_changed?( %Form{impl: impl1, id: id1, name: name1, errors: errors1, source: source1} = form1, %Form{impl: impl2, id: id2, name: name2, errors: errors2, source: source2} = form2, field ) - when is_atom(field) do + when is_atom(field) or is_binary(field) do impl1 != impl2 or id1 != id2 or name1 != name2 or - Keyword.get_values(errors1, field) != Keyword.get_values(errors2, field) or + field_errors(errors1, field) != field_errors(errors2, field) or impl1.input_value(source1, form1, field) != impl2.input_value(source2, form2, field) end @@ -261,8 +261,16 @@ defmodule Phoenix.HTML.Form do ## Examples options_for_select(["Admin": "admin", "User": "user"], "admin") - #=> + #=> + #=> + + Multiple selected values: + + options_for_select(["Admin": "admin", "User": "user", "Moderator": "moderator"], + ["admin", "moderator"]) + #=> #=> + #=> Groups are also supported: @@ -330,8 +338,9 @@ defmodule Phoenix.HTML.Form do defimpl Phoenix.HTML.Safe do def to_iodata(%{action: action, options: options}) do IO.warn( - "form_for/3 without an anonymous function is deprecated. " <> - "If you are using Phoenix.LiveView, use the new Phoenix.Component.form/1 component" + "rendering a Phoenix.HTML.Form as part of HTML is deprecated, " <> + "please extract the component you want to render instead. " <> + "If you want to build a form, use form_for/3 or <.form> in LiveView" ) {:safe, contents} = form_tag(action, options) @@ -344,7 +353,7 @@ defmodule Phoenix.HTML.Form do def form_for(form_data, action, options) when is_list(options) do IO.warn( "form_for/3 without an anonymous function is deprecated. " <> - "If you are using Phoenix.LiveView, use the new Phoenix.Component.form/1 component" + "If you are using HEEx templates, use the new Phoenix.Component.form/1 component" ) %{Phoenix.HTML.FormData.to_form(form_data, options) | action: action} @@ -399,6 +408,11 @@ defmodule Phoenix.HTML.Form do We will explore all them below. + Note that if you are using HEEx templates, `form_for/4` is no longer + the preferred way to generate a form tag, and you should use + [`Phoenix.Component.form/1`](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#form/1) + instead. + ## With changeset data The entry point for defining forms in Phoenix is with @@ -605,7 +619,7 @@ defmodule Phoenix.HTML.Form do when (is_atom(field) or is_binary(field)) and is_list(options) do IO.warn( "inputs_for/3 without an anonymous function is deprecated. " <> - "If you are using Phoenix.LiveView, use the new Phoenix.Component.inputs_for/1 component" + "If you are using HEEx templates, use the new Phoenix.Component.inputs_for/1 component" ) options = @@ -1813,4 +1827,10 @@ defmodule Phoenix.HTML.Form do # Normalize field name to string version defp field_to_string(field) when is_atom(field), do: Atom.to_string(field) defp field_to_string(field) when is_binary(field), do: field + + # Helper for getting field errors, handling string fields + defp field_errors(errors, field) + when is_list(errors) and (is_atom(field) or is_binary(field)) do + for {^field, error} <- errors, do: error + end end diff --git a/lib/phoenix_html/form_data.ex b/lib/phoenix_html/form_data.ex index 0d672f0..1444d35 100644 --- a/lib/phoenix_html/form_data.ex +++ b/lib/phoenix_html/form_data.ex @@ -1,9 +1,9 @@ defprotocol Phoenix.HTML.FormData do @moduledoc """ Converts a data structure into a [`Phoenix.HTML.Form`](`t:Phoenix.HTML.Form.t/0`) struct. - + ## Ecto integration - + Phoenix provides integration of forms with Ecto changesets and data structures via the [phoenix_ecto](https://hex.pm/packages/phoenix_ecto) package. If a project was generated without Ecto support that dependency will need to be diff --git a/mix.exs b/mix.exs index d8f53ca..5e44310 100644 --- a/mix.exs +++ b/mix.exs @@ -3,7 +3,7 @@ defmodule PhoenixHTML.Mixfile do # Also change package.json version @source_url "https://github.com/phoenixframework/phoenix_html" - @version "3.3.2" + @version "3.3.3" def project do [ diff --git a/mix.lock b/mix.lock index db163fc..273e659 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ - "earmark_parser": {:hex, :earmark_parser, "1.4.33", "3c3fd9673bb5dcc9edc28dd90f50c87ce506d1f71b70e3de69aa8154bc695d44", [:mix], [], "hexpm", "2d526833729b59b9fdb85785078697c72ac5e5066350663e5be6a1182da61b8f"}, - "ex_doc": {:hex, :ex_doc, "0.30.4", "e8395c8e3c007321abb30a334f9f7c0858d80949af298302daf77553468c0c39", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "9a19f0c50ffaa02435668f5242f2b2a61d46b541ebf326884505dfd3dd7af5e4"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.37", "2ad73550e27c8946648b06905a57e4d454e4d7229c2dafa72a0348c99d8be5f7", [:mix], [], "hexpm", "6b19783f2802f039806f375610faa22da130b8edc21209d0bff47918bb48360e"}, + "ex_doc": {:hex, :ex_doc, "0.30.6", "5f8b54854b240a2b55c9734c4b1d0dd7bdd41f71a095d42a70445c03cf05a281", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bd48f2ddacf4e482c727f9293d9498e0881597eae6ddc3d9562bd7923375109f"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, diff --git a/package.json b/package.json index 50e83ef..03bf9bd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "phoenix_html", - "version": "3.3.2", + "version": "3.3.3", "main": "./priv/static/phoenix_html.js", "repository": {}, "files": [ diff --git a/test/phoenix_html/form_test.exs b/test/phoenix_html/form_test.exs index 4259ac5..da2e9df 100644 --- a/test/phoenix_html/form_test.exs +++ b/test/phoenix_html/form_test.exs @@ -102,8 +102,10 @@ defmodule Phoenix.HTML.FormTest do assert normalize_value("datetime-local", ~N[2017-09-21 20:21:53]) == {:safe, ["2017-09-21", ?T, "20:21"]} - assert normalize_value("datetime-local", "2017-09-21 20:21:53") == "2017-09-21 20:21:53" - assert normalize_value("datetime-local", "other") == "other" + assert normalize_value("datetime-local", "2017-09-21 20:21:53") == + {:safe, "2017-09-21 20:21:53"} + + assert normalize_value("datetime-local", "other") == {:safe, "other"} end test "for textarea" do @@ -118,7 +120,7 @@ defmodule Phoenix.HTML.FormTest do end end - test "input_changed?" do + test "input_changed? with atom fields" do form = form(%{}) refute input_changed?(form, form, :foo) assert input_changed?(form, %{form | errors: [foo: "bar"]}, :foo) @@ -127,6 +129,15 @@ defmodule Phoenix.HTML.FormTest do assert input_changed?(form, form(%{"foo" => "bar"}), :foo) end + test "input_changed? with string fields" do + form = form(%{}) + refute input_changed?(form, form, "foo") + assert input_changed?(form, %{form | errors: [{"foo", "bar"}]}, "foo") + assert input_changed?(form, %{form | name: "another"}, "foo") + assert input_changed?(form, %{form | id: "another"}, "foo") + assert input_changed?(form, form(%{"foo" => "bar"}), "foo") + end + describe "access" do test "without name and atom keys" do form =