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 =