README

Build Status Coverage Status npm version Maintainability JavaScript Style Guide

api docs GitHub

vally

vally is a simple ES6, zero-dependency form field validation library, that helps to determine if <input>-values pass certain tests. The library just provides a set of useful helper functions. Most of the DOM-manipulation and validation handling still lies in the hands of the user to ensure as much flexibility as possible.

Installation

npm i --save vally

You can either use the build from inside the dist/ directory and include it via <script>-tag or use require / import-syntax in your code.

<html lang="en">
  <head></head>
  <body>
    <script>
      vally.isString(1) // => false
    </script>
  </body
  <script src="./vally.min.js"></script>
</html

or

import { isString } from 'vally'

isString(1) // => false

API

Docs

Configuration and API details can be found here:

API docs

FAQ

How can i specify a field as required?

Just use the regular required-Attribute on your <input>. This will ensure that vally actually validates the element if the input is empty. You still have to specify validator functions to provide the actual validation functionality. I.e. if the field should not be empty use isNoneEmptyString().

What happens to inputs that are not displayed?

Inputs that are not displayed (i.e. if display: none, type="hidden" or hidden="" is set), are simply ignored by the validation.

How can I bind vally to certain events?

vally leaves most of the DOM-manipulation to the user. For simple bindings (i.e. for 'keyup'-events) however you can use initWithBindings(). For detailed explaination have a look at our examples below.

Why does vally ship so few validator functions?

Because validators in vally are simple functions it is very easy to either write them yourself our just use a library like validator.js. Providing only a basic set of functions keeps the footprint of the library small.

How can I use a validator function with multiple arguments?

If you need multiple arguments (like some validator.js. functions need additional configuration) you can simply partially apply the function and return a validator function.

Example:

const isLessThan = (num: number) => (val: any):boolean => { /* actual implementation */ }

Examples

Simple Example

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>Simple submit</title>
    <style>
      input {
        outline: none;
        box-shadow: none;
      }

      .error {
        background: red;
      }
    </style>
  </head>
  <body>
    <form class="myform" action="" method="">
      <label for="number">Some number:</label>
      <input id="number" type="text" name="number">

      <label for="mail">Mail*:</label>
      <input id="mail" type="text" name="email" required>

      <label for="custom">Custom (start with 'T')*:</label>
      <input id="custom" type="text" name="custom">

      <button id="submit" type="submit">Submit</button>
    </form>

<script src="vally.min.js"></script>

<script>

const mail = document.getElementById('mail')
const number = document.getElementById('number')
const submit = document.getElementById('submit')
const custom = document.getElementById('custom')

if (mail && number && submit && custom) {
  submit.addEventListener('click', (e) => {
    e.preventDefault()

    // Simple custom validator function which ensures, that the value
    // starts with the character 'T'
    const startsWithT = (val) => val.charAt(0) === 'T'

    const result = vally.validate({
      fields: [
        {
          node: mail,
          validators: [ { fn: vally.isEmail } ]
        },
        {
          node: number,
          validators: [ { fn: vally.isNumberString } ]
        },
        {
          node: custom,
          validators: [ { fn: startsWithT }]
        }
      ]
    })

    // Set 'error' class to each invalid input
    result.validations.map(v => {
      if (!v.isValid) {
        v.node.classList.add('error')
      } else {
        v.node.classList.remove('error')
      }
    })
  })
}

</script>

  </body>
</html>

Complex example

The following example shows how vally can be used to use the same configuration to manually validate on the submit event and also bind it to fire on keyup triggered by individual inputs.

index.html

Lets use almost the same markup as before... . This time we ship vally bundled together with our other js resources in main.bundle.js, though. We also want to insert custom error messages into the DOM depending on which validator for a field failed. There are a lot of ways to achieve this. In this case we simply put hidden divs below each input and toggle their display on validation. Of course we also insert our custom messages into them.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>Simple submit</title>
    <style>
      input {
        outline: none;
        box-shadow: none;
      }

      .error {
        background: red;
      }

      .hidden {
        display: none;
      }
    </style>
  </head>
  <body>
    <form class="myform" action="" method="">
      <label for="number">Some number:</label>
      <input id="number" type="text" name="number">
      <div id="number-error" class="hidden"></div>

      <label for="mail">Mail(*):</label>
      <input id="mail" type="text" name="email" required>
      <div id="mail-error" class="hidden"></div>

      <label for="custom">Number below 10(*):</label>
      <input id="custom" type="text" name="custom" required>
      <div id="custom-error" class="hidden"></div>

      <button id="submit" type="submit">Submit</button>
    </form>

    <script src='./main.bundle.js'></script>
  </body>
</html>

config.js

We separate our configuraion from the actual validation logic, to make everything a bit more maintainable.

// config.js

import {
  createConfig,
  isEmail,
  isNoneEmptyString,
  isNumberString
} from 'vally'

// Custom validator
// Because we need another parameter for our function to specify the threshold,
// we simply curry our validator function. The actual invokation to get a
// real validator function would look like this: isLessThan(10)
const isLessThan = (num: number) => (val: any): boolean => {
  if (isNumberString(val)) return false

  return parseInt(val) < num
}

// Because we only want to fetch DOM-elements via document.querySelector
// we can use the createConfig helper function to create a valid configuration.
// Therefore we specify our specs with selectors, which in turn are used to
// fetch the actual DOM nodes
const specs = [
  {
    selector: '#mail',
    validators: [
      {
        fn: isNoneEmptyString,
        errorSelector: 'mail-error',
        msg: 'Please enter a value.'
      },
      {
        fn: isEmail,
        errorSelector: 'mail-error',
        msg: 'Please enter a valid email address.'
      }
    ]
  },
  {
    selector: '#number',
    validators: [{
      fn: isNumberString,
      errorSelector: 'number-error',
      msg: 'Please enter a number.'
    }]
  },
  {
    selector: '#custom',
    validators: [
      {
        fn: isNoneEmptyString,
        errorSelector: 'custom-error',
        msg: 'Please enter a value.'
      },
      {
        fn: isLessThan(10),
        errorSelector: 'custom-error',
        msg: 'Please enter a number smaller than ten.'
      }
    ]
  }
]

export config = createConfig(specs)

index.js

Here we will define our actual validation logic.

// index.js

import {
  initWithBindings,
  validate
} from 'vally'

import { config } from './config'

// Our callback will recieve a result object and act on its field validations
const callback = (e: Event, { validations }: { validations: Valiations }): void => {
  validations.forEach(v => {
    const msgNode = document.getElementById(v.validator.errorSelector)

    if (v.isValid) {
      v.node.classList.remove('error')

      // Hide msg
      if (msgNode ) msgNode.classList.add('hidden')
    } else {
      v.node.classList.add('error')

      // Show error msg
      if (msgNode) {
        msgNode.classList.remove('hidden')
        msgNode.innerHTML = v.validator.msg
      }
    }
  })
}

// Create a pre-configured partially applied validate function that we can use
// on our submit button. We technically don't need to do this
// as we only need to validate on submit. But in a real-world application you might
// need to re-use your validate function
// and this makes it easier.
const validateForm = () => validate(config)

const init = () => {
  // Bind our callback to the keyup event on each individual field
  initWithBindings(config, 'keyup', callback, 100)

  // Bind our validate function to our submit button
  const btnSubmit = document.getElementById('submit')
  if (!btnSubmit) return

  btnSubmit.addEventListener('click', (e) => {
    e.preventDefault()

    const result = validateForm()

    if (result.isValid) {
      // logic to send our form
    } else {
      // React to our validation result
      callback(e, result)
    }

  })
}

init()

Types

Vally's documentation makes heavy use of types. If certain parameters or return values are unclear, please refer to this section.

ValidatorFn

A function that checks if a value passes certain tests

type ValidatorFn = (val: any) => boolean

Node

type Node = HTMLInputElement | HTMLSelectElement

Validator

The validator object should always at least contain a valid ValidatorFn. You can add any other properties you might need for later evaluation.

type Validator = {
  fn: ValidatorFn,
  type?: string
}

Definition

A field definition. Should always contain a node to validate and an array of its validators.

type Definition = {|
  node: Node,
  validators: Array<Validator>
|}

Fields

type Fields = Array<Definition>

Config

Currently only holds FieldDefinitions. Might be enhanced in the future.

type Config = {|
  fields: Fields
|}

Validation

A validation represents the validation result of a single node. If validation fails at any point, validator will be the Validator-object that failed the test.

type Validation = {|
  isValid: boolean,
  node: Node,
  validator: ?Validator
|}

Result

Result of the validation of all field definitions (from a single function call). If any field failed, the whole validation is marked as invalid.

type Result = {|
  isValid: boolean,
  validations: Array<Validation>
|}

createConfig

This is just a simple helper/wrapper function to automatically fetch single nodes and return a valid configuration. Instead of directly passing in the nodes, the configuration will expect a selector-string for each of the specified fields. Behind the scenes createConfig will then invoke document.querySelector(selector) and add it to the returned config. If a node cannot be found, a console warning is emitted and the field will be skipped.

Parameters

  1. specs: Array<{selector: string, validators: Array<Validator>}>:  
    Specifications that describe each field and their corresponding Validators

Returns

Config a valid vally config to use with vally.validate, vally.initWithBindings etc.

initWithBindings

Adds an eventListener to each given node. The listener is wrapped with a debounce function and will invoke the callback when the specified event is triggered. An array of removeEventListener functions is returned, so listeners could be removed if necesseary.

Parameters

  1. config: Object:  
  2. config.fields: Array<Field>:  
    Array of field definitions
  3. event: string:  
    event to be bound to
  4. debounceTime: number (=0):  
    time to debounce callback invocation (in ms)

Returns

Array RemoveListeners

validate

Validates a list of HTMLInput/HTMLSelect elements and returns the result of the validation. Each element value will be piped through the specified list of validator functions. If validation fails at any point, it will be represented inside the respective validation object.

Parameters

  1. config: Object:  
  2. config.fields: Array<Field>:  
    an array of Field definitions

Returns

Result the general 'isValid' property determines if the set of fields as a whole validated successfully
  • each validation represents a single field { isValid: boolean, node: ?HTMLElement, validator: ?Validator }
  • if validation.isValid is false, the failing validator will be returned, so that it is possible to react on a specific failing validator

validateNode

Validates a single node by passing it to each of the specified validator functions.

Parameters

  1. definition: Object:  
  2. definition.validators: Array<Validator>:  
    Contains objects each representing a single validator. A validator should always have an 'fn'-property with a function of the type (val: any) => boolean. You can specify other properties on the object as needed.
  3. definition.node: any:  

Returns

{isValid: boolean, node: Node?, validator: Validator?}

validators

static

validators.isEmpty

isEmpty(val: string): boolean

Validates if a value is undefined or its length === 0

Parameters

  1. val: string:  

Returns

validators.isString

isString(val: string): boolean

Parameters

  1. val: string:  

Returns

validators.isNoneEmptyString

isNoneEmptyString(val: string): boolean

Parameters

  1. val: string:  

Returns

validators.isNumberString

isNumberString(val: string): boolean

Parameters

  1. val: string:  

Returns

validators.isEmail

isEmail(val: string): boolean

Parameters

  1. val: string:  

Returns