Validation

Real time server-side form validation
Please enter a value for First Name.
Please enter a value for Last Name.
Please enter a value for Email.

Explanation

Validates user input as it's entered using standard ActiveRecord validations.

Code (105 LOC)

CSS (24 LOC)
app/javascript/stylesheets/validation.scss (24 LOC)
.valid {
label, input {
color: theme-color("success");
}
input {
border-color: theme-color("success");
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: center right 40px;
background-size: calc(.75em + .375rem) calc(.75em + .375rem);
}
}
.invalid {
label, input {
color: theme-color("danger");
}
input {
border-color: theme-color("danger");
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23dc3545' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23dc3545' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E");
background-repeat: no-repeat;
background-position: center right 40px;
background-size: calc(.75em + .375rem) calc(.75em + .375rem);
}
}
JavaScript (30 LOC)
app/javascript/controllers/validations_controller.js (30 LOC)
import Turbolinks from 'turbolinks'
import { debounce } from 'lodash-es'
import ApplicationController from './application_controller'
export default class extends ApplicationController {
static targets = ['firstName', 'lastName', 'email', 'submit']
connect () {
super.connect()
this.perform = debounce(this._perform, 250)
}
_perform (event) {
this.stimulate('ValidationsReflex#perform', this.userParams)
}
reset (event) {
event.preventDefault()
Turbolinks.visit(location.href)
}
validate (event) {
this.focusElement = event.target
this.perform()
}
get userParams () {
return {
user: {
first_name: this.firstNameTarget.value,
last_name: this.lastNameTarget.value,
email: this.emailTarget.value
}
}
}
}
ERB (26 LOC)
app/views/validations/_demo.html.erb (26 LOC)
<%= form_with model: @user, url: "#", class: "mt-4", data: { controller: "validations", action: "submit->validations#reset" } do |f| %>
<div class="col-6 form-group <%= validation_status @user, :first_name %>">
<%= f.label :first_name, "First Name" %>
<%= f.text_field :first_name, class: "form-control form-control-lg",
data: { target: "validations.firstName", action: "input->validations#validate", reflex_permanent: true } %>
<%= validation_message @user, :first_name %>
</div>
<div class="col-6 form-group <%= validation_status @user, :last_name%>">
<%= f.label :last_name, "Last Name" %>
<%= f.text_field :last_name, class: "form-control form-control-lg",
data: { target: "validations.lastName", action: "input->validations#validate", reflex_permanent: true } %>
<%= validation_message @user, :last_name %>
</div>
<div class="col-6 form-group <%= validation_status @user, :email %>">
<%= f.label :email %>
<%= f.email_field :email, class: "form-control form-control-lg",
data: { target: "validations.email", action: "input->validations#validate", reflex_permanent: true } %>
<%= validation_message @user, :email %>
</div>
<div class="col-6 form-group mt-4">
<%= f.submit @user.valid? ? "Submit" : "Awaiting valid input...",
class: css("btn btn-lg", "btn-primary": @user.valid?, "btn-secondary": @user.invalid?), disabled: @user.invalid?,
data: { target: "validations.submit", disable_with: "Great Job! Form will reset shortly..." } %>
<a href="#" class="btn btn-default" data-action="click->validations#reset">Reset</a>
</div>
<% end %>
Ruby (25 LOC)
app/models/user.rb (5 LOC)
class User < ApplicationRecord
validates :first_name, length: {minimum: 3, maximum: 20}
validates :last_name, length: {minimum: 3, maximum: 20}
validates :email, email: true
end
app/controllers/validations_controller.rb (15 LOC)
class ValidationsController < ApplicationController
before_action :assign_params
def show
@user ||= User.new(user_params)
@user.validate
end
private
def assign_params
self.params = @reflex_params if @reflex_params.present?
end
def user_params
return {} unless params.key?(:user)
params.require(:user).permit(:first_name, :last_name, :email)
end
end
app/reflexes/validations_reflex.rb (5 LOC)
class ValidationsReflex < ApplicationReflex
def perform(params = {})
@reflex_params = params
end
end