I am using LiveView with TailwindCSS to create an app where visitors can send a message in a component.
I have gotten almost everything to work as I would like it to:
- the form only submits if the fields are valid
- validation errors are only displayed after the user tries to submit the form for the first time, and on every change thereafter
- if the form is successfully submitted, it is replaced with a confirmation message
However, I also want color of the borders around the text_input and the textarea tags to switch from gray to red while a field fails validation.
Currently, only the error tag appears below an invalid field, but the style of the field itself does not change.
Is there a way to achieve this behaviour? E.g. by adding conditional css classes to the text_input and textarea tags, so the borders for these tags turn to red while that particular field is invalid?
I have tried googling and looking through the Phoenix/LiveView docs for a solution, but have been unable to find one.
This is my template (message_form.html.heex
):
<div id="message-form-container">
<%= if @form_open do %>
<%= f = form_for @changeset, "#",
id: "message-form",
phx_change: @change_action,
phx_submit: "save",
phx_target: @myself,
class: "grid grid-cols-2 gap-4 w-full" %>
<div class="flex flex-col">
<%= text_input f, :name, placeholder: "Name", class: "rounded border-gray-400 shadow-sm focus:ring-gray-900 focus:ring-opacity-50 focus:border-gray-900" %>
<%= error_tag f, :name %>
</div>
<div class="flex flex-col">
<%= text_input f, :email, placeholder: "Email", class: "rounded border-gray-400 shadow-sm focus:ring-gray-900 focus:ring-opacity-50 focus:border-gray-900" %>
<%= error_tag f, :email %>
</div>
<div class="col-span-2 flex flex-col">
<%= textarea f, :body, placeholder: "Message", class: "rounded border-gray-400 shadow-sm focus:ring-gray-900 focus:ring-opacity-50 focus:border-gray-900" %>
<%= error_tag f, :body %>
</div>
<%= submit "Send message", phx_disable_with: "Sending...", class: "block px-4 py-2 border-none shadow rounded font-semibold text-sm text-gray-50 hover:bg-blue-600 bg-blue-500 cursor-pointer" %>
<% else %>
<p>Message sent!</p>
<% end %>
</div>
And this is the LiveView component code (message_form.ex
)
defmodule AppWeb.Components.MessageForm do
use AppWeb, :live_component
alias App.Messages.Message
alias App.Messages
def update(_assigns, socket) do
{:ok,
socket
|> assign_message()
|> assign_changeset()
|> assign(form_open: true)
|> assign(change_action: nil)}
end
def handle_event(
"validate",
%{"message" => message_params},
%{assigns: %{message: message}} = socket
) do
changeset = Messages.validate_message(message, message_params)
{:noreply,
socket
|> assign(:changeset, changeset)}
end
def handle_event(
"save",
%{"message" => message_params},
%{assigns: %{message: message}} = socket
) do
changeset = Messages.validate_message(message, message_params)
case changeset.valid? do
true -> {:noreply, assign(socket, form_open: false)}
false -> {:noreply, assign(socket, changeset: changeset, change_action: "validate")}
end
end
def assign_message(socket) do
socket
|> assign(:message, %Message{})
end
def assign_changeset(%{assigns: %{message: message}} = socket) do
socket
|> assign(:changeset, Messages.change_message(message))
end
end