coen.bakker

coen.bakker

Writing validation logic only once: how to get ALL passed and failed form validations to client in realtime

I can’t quite figure out whether this is or isn’t possible with Phoenix:

WHAT I’M MAKING
A user registration form in which the user sees, underneath each input field and at all time, the list of field validations that apply to that field. As the user interacts with the form, the list items should, in real time, give some sort of visual cue (e.g. green check mark) to indicate whether the validation has passed or not.

REQUIREMENT
I am trying to make this work without having to write the validation logic in multiple places. I could write a client side, Javascript solution, but then I would end up writing the validation logic twice, for example.

I thought it would be a matter of getting the info about the errors and validations – including the :message info that can optionally be provided when dealing with changeset validation functions – from the server to the client on page load and each time the form is changed by the user.

However, errors are only added to a changeset if there isn’t an error for that field yet. So you don’t get a list of all errors for a field in the changeset as it returns from the server.

Also, validations/1 returns information about all the validations of each field (except required), which is great, but it does not return the messages that you (as the programmer) can add to the changeset validation functions.

Am I missing something, or is writing the validation logic twice the way to go?

Most Liked

LostKobrakai

LostKobrakai

I think the mismatch here is that you’re trying to build a “list of requirements” from error messages. Error messages are not meant to be requirements. I’d suggest building the shown list only from the validations, have a mapping from validation to requirement. Add the checkmark based on if there’s an error present for the validation or not.

validate_required data is documented (on the validations/1 function) to be part of changeset.required list rather than changeset.validations.

mindok

mindok

Have you inspected your Changeset in iex (or IO.inspect from you form change event) to confirm that you have all the errors you expect?
Have you inspected the raw html to see if errors make it to the browser, but are hidden?

From the looks of things, there is smart handling around form errors in LiveView - see Form bindings — Phoenix LiveView v1.2.5

Take a look in you app.css and search for phx-no-feedback - you should see a class that hides the feedback if the field has not yet been touched.

coen.bakker

coen.bakker

Let me walk through the %Ecto.changeset{} and validations/1 data that is being passed around in my app.

To start, this here is what the registration form could look like. In this example only the password field has all requirements listed. Those requirements will change, by the way, but just to give a better idea of what UI I am referring to.

At page load I get this data:

#Ecto.Changeset<
  action: nil,
  changes: %{},
  errors: [
    password: {"can't be blank", [validation: :required]},
    email: {"can't be blank", [validation: :required]},
    username: {"can't be blank", [validation: :required]}
  ],
  data: #TodayForum.Accounts.User<>,
  valid?: false
>

# validations/1
[
  password: {:format, ~r/[!?@#$%^&*_0-9]/},
  password: {:format, ~r/[A-Z]/},
  password: {:format, ~r/[a-z]/},
  password: {:length, [min: 12, max: 72]},
  email: {:unsafe_unique, [fields: [:email]]},
  email: {:length, [max: 160]},
  email: {:format, ~r/^[^\s]+@[^\s]+$/}
]

The errors and validations here are indeed correct, but there are two things missing.

  1. The errors of the other validation requirements that are not met.

For example, at page load password requires at least one capital letter requirement is also not met, but that error is not present in the changeset. I understand that this is per design, but in my use case I require all errors to show.

  1. The :message data that has been bound to the changeset.

So for example here:

  defp validate_password(changeset, opts) do
    changeset
    |> validate_required([:password])
    |> validate_length(:password, min: 12, max: 72)
    |> validate_format(:password, ~r/[a-z]/, message: "at least one lower case character") # HERE
    |> validate_format(:password, ~r/[A-Z]/, message: "at least one upper case character") # HERE
    |> validate_format(:password, ~r/[!?@#$%^&*_0-9]/, message: "at least one digit or punctuation character") # HERE
    |> maybe_hash_password(opts)
  end

If I enter an invalid password, like:

I get the following data:

#Ecto.Changeset<
  action: nil,
  changes: %{password: "**redacted**"},
  errors: [
    password: {"at least one digit or punctuation character",
     [validation: :format]},
    password: {"at least one upper case character", [validation: :format]},
    password: {"should be at least %{count} character(s)",
     [count: 12, validation: :length, kind: :min, type: :string]},
    email: {"can't be blank", [validation: :required]},
    username: {"can't be blank", [validation: :required]}
  ],
  data: #TodayForum.Accounts.User<>,
  valid?: false
>

# validations/1
[
  password: {:format, ~r/[!?@#$%^&*_0-9]/},
  password: {:format, ~r/[A-Z]/},
  password: {:format, ~r/[a-z]/},
  password: {:length, [min: 12, max: 72]},
  email: {:unsafe_unique, [fields: [:email]]},
  email: {:length, [max: 160]},
  email: {:format, ~r/^[^\s]+@[^\s]+$/}
]

So for the password I get the right errors.

So here is the problem. I want to dynamically create the list of requirements underneath the password field. And dynamically update the styling of these list items depending on whether these requirements are passed or not. With the current changeset and validations info I cannot get it done. Because I need all the errors and all the validations (including the messages) to be sent to the client at page load and any change to the form.

I could only show the list of requirements after the first form change by the user. This eliminates the need for all errors to be sent to the client on page load. That’s fine. But then I still need to validation messages to be passed to the client.

Where Next?

Popular in Questions Top

nobody
How to bind a phoenix app to a specific ip address? could not find anything about that, nowhere, unfortunately, but for me this is quite...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
JulienCorb
I am trying to implement my new.html.eex file to create new posts on my website. new.html.eex: &lt;h1&gt;Create Post&lt;/h1&gt; &lt;%= ...
New
ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list. ...
New

Other popular topics Top

New
sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
985 43487 311
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 52673 488
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New

Latest on Elixir Forum

Elixir Forum

We're in Beta

About us Mission Statement