Skip to content

Instantly share code, notes, and snippets.

@nijikokun
Created May 3, 2012 20:46
Show Gist options
  • Save nijikokun/2589227 to your computer and use it in GitHub Desktop.
Save nijikokun/2589227 to your computer and use it in GitHub Desktop.
Beautiful Validation... Why have I never thought of this before?!

In a recent project I had to validate a form in Javascript, I've done it thousands of times, but during this one instance I had a stroke of ... oh my god I can really do this ... moment. Essentially it cleaned up about 40 lines of code down to about five or six, and ontop of that, you can easily set variables inside your operation or outside, its just beautiful:

      return (
          (!(username += '') || username === '') ? { error: "No Username Given.", field: 'name' }
        : (!(username += '') || password === '') ? { error: "No Password Given.", field: 'pass' }
        : (username.length < 3)                  ? { error: "Username is less than 3 Characters.", field: 'name' }
        : (password.length < 4)                  ? { error: "Password is less than 4 Characters.", field: 'pass' }
        : (!/^([a-z0-9_-]+)$/i.test(username))   ? { error: "Username contains invalid characters.", field: 'name' }
        : false
      );

It's really it's own language if you think about it. It's just so concise and direct.

Basic Validation

Validate existence:

  (!username || username === '')        ? { error: "No Username Given.", field: 'name' }  // Doesn't Exist? Return this
  : false // Otherwise, nothing to return; False is good. It means it passed validation.

Complex Validation

Regular Expressions? Requirements? All easy.

  (!/^([a-z0-9-_]+)$/i.test(username))  ? { error: "Username contains invalid characters.", field: 'name' } // Test username against RegExp, return this on failure
  : false // It passed!

Nesting?

No problemo.

  (!username)   ?
  (
    (!password) ? { error: "Missing Username and Password.", field: ['name', 'pass'] }
    :             { error: "Missing Username", field: 'name' }
  )
  : false 

Complex problems are still simple to solve in the end. It's so beautiful it hurts.

Variable Setting

If you wish to setup variables in a normal manner you can do so on the lines before validation, this only works for things that are not null, undefined, false.

  (username = username.toString())      && 
  (password = password.toString())      &&

Another method in one statement:

  (
    username = username.toString()        && 
    password = password.toString()
  ) &&

Optional Variable Storage? No Problem.

  (
    username = username.toString()        || 
    password = password.toString()        // Password is now set only if username is true.
  ) &&

A better method for this scenario would be to coerce the variable into a string: !(username += "") and check it.

Or simply do it before the return ;)

var user = {
validateCredentials: function (username, password) {
return (
(!(username += '') || username === '') ? { error: "No Username Given.", field: 'name' }
: (!(username += '') || password === '') ? { error: "No Password Given.", field: 'pass' }
: (username.length < 3) ? { error: "Username is less than 3 Characters.", field: 'name' }
: (password.length < 4) ? { error: "Password is less than 4 Characters.", field: 'pass' }
: (!/^([a-z0-9_-]+)$/i.test(username)) ? { error: "Username contains invalid characters.", field: 'name' }
: false
);
}
};
var results = user.validateCredentials('Nijikokun','somepassword');
console.log(results);
@albohlabs
Copy link

Really nice work. But i think you should unescape the '-' char.

      return (
          (!(username += '') || username === '') ? { error: "No Username Given.", field: 'name' }
        : (!(username += '') || password === '') ? { error: "No Password Given.", field: 'pass' }
                 ^
        : (username.length < 3)                  ? { error: "Username is less than 3 Characters.", field: 'name' }
        : (password.length < 4)                  ? { error: "Password is less than 4 Characters.", field: 'pass' }
        : (!/^([a-z0-9-_]+)$/i.test(username))   ? { error: "Username contains invalid characters.", field: 'name' }
                      ^
        : false
      );
    }
  };

@ivasilov
Copy link

ivasilov commented May 4, 2012

Beautiful. Adding a handling for undefined and null wouldn't hurt though.

@DTrejo
Copy link

DTrejo commented May 4, 2012

I kind of like this, just so you can tell the users all the errors in one go:

function validate(username, password)
  var a = []

  username += ''
  if (!username || username === '') e.push({ error: "No Username Given.", field: 'name' })
  if (!username || password === '') e.push({ error: "No Password Given.", field: 'pass' })
  // ...

  if (a.length) return a
  else return false
}

Or even

function validate (username, password) {
  username += ''
  var errors = [
    (!username) && { error: "No Username Given.", field: 'name' }
  , (!username || password === '') && { error: "No Password Given.", field: 'pass' }
  ].filter(function(v){ return v }) // remove falsy values

  if (errors.length) return errors;
  return false;  
}

@jimrubenstein
Copy link

I tend to agree with DTrejo; I dont think it's good user experience to only show 1 error at a time. The experience to the user is that "they'll never get it right" and give up before completing the form. Also, as beautiful as this looks; it's still a lot of very specific code to write - and you end up repeating yourself a lot. Username and password existence and length are almost exactly the same code with just minor differences. Regular expression testing is something that would happen constantly. You'd be better off writing some sort of small plugin that was able to validate a form based off HTML5 data attributes or some small json definition object, imho.

@judofyr
Copy link

judofyr commented May 4, 2012

Another solution if you want all errors at the same time:

function validate() {
  var e, errors = [];
  for (var i = 0; i < arguments.length; i++)
    (e = arguments[i]) && errors.push(e);
  return errors;
}

function validateLogin(username, password) {
  return validate(
    (!(username += '') || username === '') && { error: "No Username Given.", field: 'name' },
    (!(username += '') || password === '') && { error: "No Password Given.", field: 'pass' },
    (username.length < 3)                   && { error: "Username is less than 3 Characters.", field: 'name' },
    (password.length < 4)                   && { error: "Password is less than 4 Characters.", field: 'pass' },
    (!/^([a-z0-9-_]+)$/i.test(username))    && { error: "Username contains invalid characters.", field: 'name' }
  );
}

@lericson
Copy link

lericson commented May 4, 2012

While this might seem like a good idea at first, it is often the case that huge expressions become tangly and hard to maintain.

First of all this code is very dense! You express a lot of logic multiple times, meaning that to test for some generic case you must repeat the same logic -- this is code duplication and it leads to longer development times.

Show the user how to do it, don't tell them off for doing it wrong.

@gerdr
Copy link

gerdr commented May 4, 2012

Why do you check

!username || username === ''

If the second expression evaluates to true, the first one did so as well and the || short-circuits.

You can also coerce to string via

username = String(username)

which doesn't suffer from the problems of .toString().

An actual problem is the use of logical and to set multiple variables: Use the comma operator instead so you won't short-circuit on assignment of false-y values, which includes the empty string.

@jnav
Copy link

jnav commented May 4, 2012

I know it is little bit off-topic, but for me forms are validated automatically using Nette Framework (JavasCript & server side) - see this for details http://doc.nette.org/en/forms#toc-javascript

you just define form in your code once and HTML5 data attributes are generated, so JavaScript code validate it on the client side plus you have automatically same validation on the server side (PHP)

@ChadMcCallum
Copy link

I use the jquery validation plugin for my form validation - provides a nice and simple way of using css styles to define validation requirements, provides the error messages and field highlighting, and also lets you define your own reusable rules. So all of that above turns into

<input name='username' class='required' minlength='3' />
<input name='password' type='password' class='required' minlength='4' />

It's obviously a larger script file than what you've got above, but for the apps we've created it's well worth it. See http://docs.jquery.com/Plugins/Validation

@sparecycles
Copy link

It's standard practice to put hyphens at the end of a character class ( i.e., say [a-z_-] not [a-z-_] )

I don't think I need to explain why. =)

@joeframbach
Copy link

I'd hate to see what your code looked like before. What's different from this?

if (!(username += '') || username === '') return { error: "No Username Given.", field: 'name' }
if (!(username += '') || password === '') return { error: "No Password Given.", field: 'pass' }
if (username.length < 3)                  return { error: "Username is less than 3 Characters.", field: 'name' }
if (password.length < 4)                  return { error: "Password is less than 4 Characters.", field: 'pass' }
if (!/^([a-z0-9-_]+)$/i.test(username))   return { error: "Username contains invalid characters.", field: 'name' }
return false

Copy link

ghost commented May 5, 2012

I prefer @joeframbach's version, it's perhaps not as concise, but a lot more readable and easier to debug.

@albohlabs
Copy link

@xeross I think there is not many code to debug. Thats the reason for this snippet.

Copy link

ghost commented May 5, 2012

@gorekee Yes that's true, but it's not entirely clear to someone what it does, as in it's not self-documenting, at least it wouldn't be if I came across it. It does look nice though. But using proper ifs/returns is a lot clearer.

@AdrianRossouw
Copy link

I actually like using json schema for a lot of these validation tasks. There exist other tasks which can't be handled declaratively, such as wether the email is unique etc.

@theycallmeswift
Copy link

+1 on @DTrejo and @judofyr's comments

@nijikokun
Copy link
Author

Updated the hyphen to the end of the sentence, and the coercing is just for example really.

Reasons for not utilizing external libraries / HTML5(psuedo-working) features: Bulk and not fully flexible options. Not to mention they can break at any time or change, this is not dependent on a third party library only the language it was written on and meant to be concise.

This code really isn't duplication @lericson - I can understand where you might see it as duplicating code over and over but it doesn't it goes through the checks and stops when an error is found, otherwise the rest are not ran. The form actively helps users understand but sometimes users don't follow what they are told this is just an extra measure to defer that from happening.

@DTrejo your first example has an error, e doesn't exist, change it to a ;) - It's a good example and usage of an array to return multiple errors.

They are all great examples and clever ways of doing validation on single lines but this is the most compact and concise way of doing it I have found only utilizing if statements.

Sure a json validation schema is a better alternative but for simple validation, things that you don't need robust systems for, this is perfect.

@yeshimei
Copy link

yeshimei commented Nov 7, 2017

If you like ES6, you can write like this:

function validate(...args) {
  return args.reduce((ret, res) => {
    res && ret.push(res)
    return ret
  }, [])
}

function validateLogin(username, password) {
  return validate(
    (!(username += '') || username === '') && { error: "No Username Given.", field: 'name' },
    (!(username += '') || password === '') && { error: "No Password Given.", field: 'pass' },
    // ...
  );
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment