Simple form validation module Thinking about form validation, I just wanted something simple that works and is extensible.

I came up with the following code, which expects a list of selectors that maps to one or more functions performing validation. On success, a validator function returns a "falsey" value (empty string or false, preferably) and on success, it returns the error text. The final result is a map from all inputs to one or more errors they produced.

Any comments on style and good practices, and also good use of ES-6 functionality, are welcome.

export default function validateForm(form, rules, baseSelector = 'input,textarea') {
  let inputs = form.querySelectorAll(baseSelector);
  let errorMap = new Map();
  for (let input of inputs) {
    errorMap.set(input, validateInput(input, rules));
  }
  return errorMap;
}

function validateInput(input, rules) {
  let errors = [];
  for (let rule of Object.keys(rules)) {
    if (input.matches(rule)) {
      let e = validateRule(input, rules[rule]);
      errors.push(...e);
    }
  }
  return errors;
}

function validateRule(input, validators) {
  // In case only a single function is provided instead of an array
  if (typeof validators === 'function') {
    validators = [validators];
  }

  return validators
    .map(validate =&gt; validate(input))
    .filter(result =&gt; !!result);
}


Sample call:

function ensureRequired(input) {
  if (!input.required || input.value) {
    return '';
  }
  return 'required field not set';
}

function ensureNumber(input) {
  if (Number.isInteger(input.value)) {
    return '';
  }
  return 'field must be number';
}

let myForm = document.getElementById('myForm');
let validationResult = validateForm(myForm, {
  'input': ensureRequired,
  'input.phoneField': [ensureNumber]
};


Disclaimer: I'm aware HTML5 has built-in form validation - I was purposely trying to avoid using it.
Here are my thoughts:


Good use of arrow functions where appropriate. If you really wanted to, you could shorten some of those small functions- e.g.:

function ensureNumber(input) {
    if (Number.isInteger(input.value)) {
        return '';
    }
    return 'field must be number';
}


Could be shortened to a single line:

const ensureNumber = (input) =&gt; Number.isInteger(input.value)?'':'field must be number';

I also like the use of the spread operator when getting the errors back from the mapped validation for a field.
Depending on how the validationResult is used, a POJO could be used instead of a Map to utilize less memory, though that might not be a concern. A POJO doesn't have .forEach like Map would (though one could iterate over the keys of a POJO). Apparently when using an ES-2015 compliant engine, the key order is maintained.
Bearing in mind that it might not be preferable/possible to update the DOM, one could apply class names to the inputs/textareas/selects/etc. and then use Document.getElementsByClassName() instead of querySelectorAll() but that might only lead to a slight performance gain.