Sunday, May 8, 2011

How To Read User Input With NodeJS

I think it's time for me to join the NodeJS hysteria. So, I'd like to share a bit of things I was tackling today because there is not really much of docs on that matter right now.

Short things short. If you're writing a little console script in NodeJS that supposed to ask the user this and that, here's how you can get it done.

Node's global object process has two properties called .stdin and .stdout, which are essentially streams. You can write things into the stdout and listen to the 'data' event in the stdin stream. A simple example from the api-docs looks like that

process.stdin.resume();
process.stdin.setEncoding('utf8');

process.stdin.on('data', function (chunk) {
process.stdout.write('data: ' + chunk);
});

You need to call that .resume() method first, it initializes the STDIN reading process.

It is pretty straightforward, but it's almost useless when you need to ask the user several questions, more of that validate the input data and re-ask questions when something was entered wrong.

The basic trouble here is that .on('data' handler that will fire every time the user hits the Enter, so you'll have to figure out which question is currently asked an where to put the data. Given the fact that everything is happening asynchronously, it pretty quickly gets really messy.

Fortunately for us, there is another events handler in NodeJS called .once(.. it's basically same as .on( but it detaches the listener after the first event received. Knowing that, we can write a little helper function like that

function ask(question, format, callback) {
var stdin = process.stdin, stdout = process.stdout;

stdin.resume();
stdout.write(question + ": ");

stdin.once('data', function(data) {
data = data.toString().trim();

if (format.test(data)) {
callback(data);
} else {
stdout.write("It should match: "+ format +"\n");
ask(question, format, callback);
}
});
}

There are two important moments. First of all, don't use console.log to ask the user the question, otherwise it will print a new line at the end, which is kinda sucks. Secondly note that you should call .toString() and .trim() on your data, the first one to convert the data from a stream into an actual string, the second one is needed because the string will have a trailing new line symbol at the end after the user hits the Enter key.

After that, you can ask the user all sorts of questions. For example like that

ask("Name", /.+/, function(name) {
ask("Email", /^.+@.+$/, function(email) {
console.log("Your name is: ", name);
console.log("Your email is:", email);

process.exit();
});
});

And don't forget to call the process.exit() when you done, otherwise the script will just hung in there.

That's basically all of it. Enjoy!

No comments: