DavyJonesLocker/client_side_validations

View on GitHub
src/index.js

Summary

Maintainability
A
0 mins
Test Coverage
import jQuery from 'jquery'
import ClientSideValidations from './core'

import { absenceLocalValidator, presenceLocalValidator } from './validators/local/absence_presence'
import { acceptanceLocalValidator } from './validators/local/acceptance'
import { formatLocalValidator } from './validators/local/format'
import { numericalityLocalValidator } from './validators/local/numericality'
import { lengthLocalValidator } from './validators/local/length'
import { exclusionLocalValidator, inclusionLocalValidator } from './validators/local/exclusion_inclusion'
import { confirmationLocalValidator } from './validators/local/confirmation'
import { uniquenessLocalValidator } from './validators/local/uniqueness'

// Validators will run in the following order
ClientSideValidations.validators.local = {
  absence: absenceLocalValidator,
  presence: presenceLocalValidator,
  acceptance: acceptanceLocalValidator,
  format: formatLocalValidator,
  numericality: numericalityLocalValidator,
  length: lengthLocalValidator,
  inclusion: inclusionLocalValidator,
  exclusion: exclusionLocalValidator,
  confirmation: confirmationLocalValidator,
  uniqueness: uniquenessLocalValidator
}

jQuery.fn.disableClientSideValidations = function () {
  ClientSideValidations.disable(this)

  return this
}

jQuery.fn.enableClientSideValidations = function () {
  const selectors = { forms: 'form', inputs: 'input' }

  for (const selector in selectors) {
    const enablers = selectors[selector]

    this.filter(ClientSideValidations.selectors[selector]).each(function () {
      ClientSideValidations.enablers[enablers](this)
    })
  }

  return this
}

jQuery.fn.resetClientSideValidations = function () {
  this.filter(ClientSideValidations.selectors.forms).each(function () {
    ClientSideValidations.reset(this)
  })

  return this
}

jQuery.fn.validate = function () {
  this.filter(ClientSideValidations.selectors.forms).each(function () {
    jQuery(this).enableClientSideValidations()
  })

  return this
}

jQuery.fn.isValid = function (validators) {
  const obj = jQuery(this[0])

  if (obj.is('form')) {
    return validateForm(obj, validators)
  } else {
    return validateElement(obj, validatorsFor(this[0].name, validators))
  }
}

const cleanNestedElementName = (elementName, nestedMatches, validators) => {
  for (const validatorName in validators) {
    if (validatorName.match(`\\[${nestedMatches[1]}\\].*\\[\\]\\[${nestedMatches[2]}\\]$`)) {
      elementName = elementName.replace(/\[[\da-z_]+\]\[(\w+)\]$/g, '[][$1]')
    }
  }

  return elementName
}

const cleanElementName = (elementName, validators) => {
  elementName = elementName.replace(/\[(\w+_attributes)\]\[[\da-z_]+\](?=\[(?:\w+_attributes)\])/g, '[$1][]')

  const nestedMatches = elementName.match(/\[(\w+_attributes)\].*\[(\w+)\]$/)

  if (nestedMatches) {
    elementName = cleanNestedElementName(elementName, nestedMatches, validators)
  }

  return elementName
}

const validatorsFor = (elementName, validators) => {
  if (Object.prototype.hasOwnProperty.call(validators, elementName)) {
    return validators[elementName]
  }

  return validators[cleanElementName(elementName, validators)] || {}
}

const validateForm = ($form, validators) => {
  let valid = true

  $form.trigger('form:validate:before.ClientSideValidations')

  $form.find(ClientSideValidations.selectors.validate_inputs).each(function () {
    if (!jQuery(this).isValid(validators)) {
      valid = false
    }

    return true
  })

  if (valid) {
    $form.trigger('form:validate:pass.ClientSideValidations')
  } else {
    $form.trigger('form:validate:fail.ClientSideValidations')
  }

  $form.trigger('form:validate:after.ClientSideValidations')

  return valid
}

const passElement = ($element) => {
  $element.trigger('element:validate:pass.ClientSideValidations').data('valid', null)
}

const failElement = ($element, message) => {
  $element.trigger('element:validate:fail.ClientSideValidations', message).data('valid', false)
}

const afterValidate = ($element) => {
  return $element.trigger('element:validate:after.ClientSideValidations').data('valid') !== false
}

const executeValidator = (validatorFunctions, validatorFunction, validatorOptions, $element) => {
  for (const validatorOption in validatorOptions) {
    if (!validatorOptions[validatorOption]) {
      continue
    }

    const message = validatorFunction.call(validatorFunctions, $element, validatorOptions[validatorOption])

    if (message) {
      failElement($element, message)
      return false
    }
  }

  return true
}

const executeValidators = (validatorFunctions, $element, validators) => {
  for (const validator in validators) {
    if (!validatorFunctions[validator]) {
      continue
    }

    if (!executeValidator(validatorFunctions, validatorFunctions[validator], validators[validator], $element)) {
      return false
    }
  }

  return true
}

const isMarkedForDestroy = ($element) => {
  if ($element.attr('name').search(/\[([^\]]*?)\]$/) >= 0) {
    const destroyInputName = $element.attr('name').replace(/\[([^\]]*?)\]$/, '[_destroy]')

    if (jQuery(`input[name="${destroyInputName}"]`).val() === '1') {
      return true
    }
  }

  return false
}

const executeAllValidators = ($element, validators) => {
  if ($element.data('changed') === false || $element.prop('disabled')) {
    return
  }

  $element.data('changed', false)

  if (executeValidators(ClientSideValidations.validators.all(), $element, validators)) {
    passElement($element)
  }
}

const validateElement = ($element, validators) => {
  $element.trigger('element:validate:before.ClientSideValidations')

  if (isMarkedForDestroy($element)) {
    passElement($element)
  } else {
    executeAllValidators($element, validators)
  }

  return afterValidate($element)
}

if (!window.ClientSideValidations) {
  window.ClientSideValidations = ClientSideValidations

  if (!isAMD() && !isCommonJS()) {
    ClientSideValidations.start()
  }
}

function isAMD () {
  return typeof define === 'function' && define.amd // eslint-disable-line no-undef
}

function isCommonJS () {
  return typeof exports === 'object' && typeof module !== 'undefined' // eslint-disable-line no-undef
}

export default ClientSideValidations