Git Product home page Git Product logo

vally's Introduction

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

Why not just use HTML5 validation?

HTML5 validation sometimes is a bit lacking when it comes to customizability and validating with custom constraints. That's not to say it is bad - especially since it is now supported by all major browsers. In fact it is good practice to still use HTML5 type constraints on your fields (i.e. type="number") in conjunction with vally to provide the best possible experience to your users.

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()

vally's People

Contributors

on3iro avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

vally's Issues

TODO

  • Remove babel-polyfill (add object-spread if necessary)
  • enhance lib
    • refactor code
    • increase coverage
    • add imports/exports to index.js
    • add docstrings
  • releases
    • beta release for testing purposes
    • initial release
  • repo
    • set up CI (travis, coveralls, codeclimate)
    • documentation
    • importable libdefs

v2 - rewrite

Living on the fastlane.

Vally isn't even a week old and I am already thinking about a rewrite ๐Ÿ˜…

I want vally to become more functional and flexible. So DOM-manipulation should be deferred to the user as much as possible (apart from makeValidationWithBindings). Here are a few thoughts on how to achieve this:

  • Instead of handing selector strings for fields, we could directly use the HTMLInputElements
  • The validation itself could be promise based and return an array of failed fields (more on that below)
  • Validators become an object instead of a single function
type ValidatorFn = (any) => boolean
type Validator = {|
    type: string,
    fn: ValidatorFn,
    msg: string
|}

When iterating over a fields validators we can then resolve the promise with the following content

const validateValue = (
  node: HTMLInputElement,
  validators: Array<Validator>
):Promise => validators.reduce((acc, validator, i) => {
  const val = node.value

  const isValid = validator.fn(node)
  const newAcc = {
      node,
      validator,
      isValid: (acc.isValid && isValid)
  }
  const isLast = i === validators.length - 1
  if (isLast) return Promise.resolve(newAcc)

  return newAcc
}, { isValid: true })

That way the user is in charge of how to handle and react to each individual field change (#22 would be no longer necessary) :

Example

const myNameInput = document.getElementById('js-name-input')
vally.validate([
 {
    field: myNameInput,
    validators: [
        { type: 'isNotEmpty', msg: 'Please enter a name.', fn: isNotEmpty },
    ]
 }   
]).then((results) => {
    results.forEach(res => { /* user does something in response */ })   
})

Another advantage of this approach would be, that because we already get the nodes to validate
we can now merge validators, that run on the same node ( #19 ).

One problem with the promise-based approach would be, that whis would not work in IE by default.

Add way to handle all fields of the same type

Currently field selectors have to be very specific and each field is only handled one at a time.
Instead it would cool if we either generally use querySelectorAll instead of querySelector and iterate
over all fields with that type.

Alternatively we could provide a completely different function, although that would probably be overkill, considering the fact, that the other solution looks rather easy to implement and use

Create gitbook or other way of handling documentation

I am really not that satisfied with how the documentation.js docs turned out.
Furthermore I feel that these jsdoc annotations are distracting - i would prefer to write documentation externally. documentjs's flow-type extraction doesn't work reliably anyway - so manually writing out types is a must.

Add a few full fledged examples

I am not quite sure how easy vally is to understand right now.

Some usage examples would definitely be helpful to give a better understand how vally should commonly be used.

Add custom templates to validator-Functions

I am not quite sure about the interface yet.
Instead of just passing functions into the validator array, we could pass in objects like the following:

type  ValidatorFunction = (any) => boolean 

type ValidatorConf = {|
    validator: ValidatorFunction,
    template: string,
    templateTarget: string,
    position: 'beforeBegin' | 'afterBegin' | 'beforeEnd' | 'afterEnd'
|}

type Validator = ValidatorFunction | ValidatorConf

Add more validators

a few more validator functions would be cool...

...but technically not really necessary, as validators are a) easy to implement and b) there are libraries like validator.js that could easily be used in conjunction with vally, by partially applying them.

Maybe if it becomes obvious that some really common ones are missing, we could add these. Apart from that I would probably keep the API small.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.