Episodes

Resources

Download Source Code

Summary

# Terminal
yarn add stimulus
yarn add validate.js

# app/javascript/packs/application.js
import 'controllers'

# app/javascript/controllers/index.js
import { Application } from "stimulus"
import { definitionsFromContext } from "stimulus/webpack-helpers"

const application = Application.start()
const context = require.context("controllers", true, /\.js$/)
application.load(definitionsFromContext(context))

# app/javascript/controllers/validate_controller.js
import { Controller } from "stimulus"
import validate from 'validate.js'

export default class extends Controller {
  static targets = ['form', 'submit', 'input']

  submitForm(event) {
    event.preventDefault()

    var errors = validate(this.formTarget, this.constraints())

    if (errors == null){
      this.formTarget.submit()
    } else {
      this.showErrors(errors || {})
    }
  }

  showErrors(errors) {
    for (let input of this.inputTargets) {
      // console.log(errors[input.name])
      this.showErrorsForInput(input, errors[input.name])
    }
  }

  constraints(){
    var constraints = {}
    for (let input of this.inputTargets) {
      constraints[input.name] = JSON.parse(input.getAttribute('data-validate'))
    }
    return constraints
  }

  showErrorsForInput(input, errors) {
    this.clearErrors(input)
    if (errors) {
      input.parentElement.classList.add('has-error')
      this.insertErrorMessages(input, errors)
    } else {
      input.parentElement.classList.remove('has-error')
      input.parentElement.classList.add('has-success')
    }
  }

  clearErrors(input) {
    if (document.getElementById(`error_${input.name}`) != null) {
      document.getElementById(`error_${input.name}`).remove()
    }
  }

  insertErrorMessages(input, errors) {
    var html = document.createElement('div')
    html.innerHTML = errors.join(' ')
    html.id = `error_${input.name}`
    input.after(html)
  }
}

# views/products/_form.html.erb
<%= form_with(model: product, local: true, data: { controller: 'validate', target: 'validate.form' }) do |form| %>
  <div class="field">
    <%= form.label :name %>
    <%= form.text_field :name, data: {  target: 'validate.input', 
                                        validate: { 
                                          presence: { message: '^Name is required' }
                                          }} %>
  </div>


  <div class="field">
    <%= form.label :price %>
    <%= form.text_field :price, data: {  target: 'validate.input', 
                                         validate: { 
                                           presence: true,
                                           numericality: {
                                             greaterThan: 0,
                                             message: '^Do not give away for free!'
                                         }}} %>
  </div>


  <div class="field">
    <%= form.label :quantity %>
    <%= form.number_field :quantity, data: {  target: 'validate.input', 
                                        validate: { 
                                          presence: { message: '^is required' },
                                          numericality: {
                                            lessThan: 100,
                                            message: '^# of Stock is too high'
                                          }}} %>
  </div>
  <div class="actions">
    <%= form.submit data: { action: 'validate#submitForm'} %>
  </div>
<% end %>