4
\$\begingroup\$

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 => validate(input))
    .filter(result => !!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.

\$\endgroup\$
  • 1
    \$\begingroup\$ I wanted to focus on the disclaimer at the end. Why are you trying to avoid using HTML5 form validation? Yeah, it's incredibly simplistic, and doesn't handle everything, but if it's available in the browser being used (something worth keeping in mind), and it works for what you're trying to validate, then why not take advantage of it? The above code {w,sh}ould be an good fallback for browsers without validation. Finally, the key part: this is not the real validation code (unless you can deploy it to Node.js), because this is the client side logic! The server must validate all input too. \$\endgroup\$ – i336_ Oct 27 '17 at 22:49
1
+50
\$\begingroup\$

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) => 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.
\$\endgroup\$
  • 1
    \$\begingroup\$ Accepted and a bouns but no upvotes... this cannot be, you'll get the +1 from me ;-] \$\endgroup\$ – t3chb0t Dec 12 '17 at 18:57
  • \$\begingroup\$ Thanks - sometimes I feel like JS posts often aren't upvoted as often as posts in other topics but maybe the content isn't regarded as highly... and I thought I was going to get an unsung hero badge like I did in SO ;) \$\endgroup\$ – Sᴀᴍ Onᴇᴌᴀ Dec 12 '17 at 22:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.