I am creating an console interface,in which the program ask some questions and user answers it through console,there are questions where the user must only enter limited no of inputs,i found some ways to get console input in node js
but cant find a way to limit users inputs and ask questions one after another, after asking questions want to create a JSON
object from it.
for eg,i will ask questions like:
- What is your name?
- List 3 of your hobbies ?
- Enter a desired username ?
After asking this questions i will construct a json
object like
{"name":"codebean","hobbies":['Exploring','Coding','Trucking'],"username":'codebean'}
Difficulties that i face are :
How to ask questions one after other, ie, serially
Limit the user inputs to a particular count
Terminate the program after entering the final question's answer
I only have little experience with NodeJs
and what i was able to establish was nothing but rubbish,and here is what i established
process.stdin.setEncoding('utf8');
var input = [];
process.stdin.on('readable', function() {
console.log('What is your name');
var name = process.stdin.read();
if ((name !== null) && (name !== '/n')) {
input.push(name.split('/n')[0]);
console.log(input);
}
console.log('List 3 of your hobbies ?');
var hobbies = process.stdin.read();
});
process.stdin.on('end', function() {
console.log(input);
});
I am creating an console interface,in which the program ask some questions and user answers it through console,there are questions where the user must only enter limited no of inputs,i found some ways to get console input in node js
but cant find a way to limit users inputs and ask questions one after another, after asking questions want to create a JSON
object from it.
for eg,i will ask questions like:
- What is your name?
- List 3 of your hobbies ?
- Enter a desired username ?
After asking this questions i will construct a json
object like
{"name":"codebean","hobbies":['Exploring','Coding','Trucking'],"username":'codebean'}
Difficulties that i face are :
How to ask questions one after other, ie, serially
Limit the user inputs to a particular count
Terminate the program after entering the final question's answer
I only have little experience with NodeJs
and what i was able to establish was nothing but rubbish,and here is what i established
process.stdin.setEncoding('utf8');
var input = [];
process.stdin.on('readable', function() {
console.log('What is your name');
var name = process.stdin.read();
if ((name !== null) && (name !== '/n')) {
input.push(name.split('/n')[0]);
console.log(input);
}
console.log('List 3 of your hobbies ?');
var hobbies = process.stdin.read();
});
process.stdin.on('end', function() {
console.log(input);
});
Share
Improve this question
edited Dec 27, 2015 at 16:51
Basilin Joe
asked Dec 27, 2015 at 16:48
Basilin JoeBasilin Joe
7021 gold badge10 silver badges23 bronze badges
5
|
3 Answers
Reset to default 14Having made CLI applications in NodeJS, I would recommend using a library like prompt
to better organize your code. A library would keep it more readable than what you can do naively (in my opinion).
However, if you want some native alternative, you could use Node's EventEmitter
object to make things look a bit more organized, opposed to handling it all inside of the stdin callback:
var EventEmitter = require('events');
var prompt = new EventEmitter();
var current = null;
var result = {};
process.stdin.resume();
process.stdin.on('data', function(data){
prompt.emit(current, data.toString().trim());
});
prompt.on(':new', function(name, question){
current = name;
console.log(question);
process.stdout.write('> ');
});
prompt.on(':end', function(){
console.log('\n', result);
process.stdin.pause();
});
prompt.emit(':new', 'name', 'What is your name?');
prompt.on('name', function(data){
result.name = data;
prompt.emit(':new', 'hobbies', 'What are your hobbies?');
});
prompt.on('hobbies', function(data){
result.hobbies = data.split(/,\s?/);
prompt.emit(':new', 'username', 'What is your username?');
});
prompt.on('username', function(data){
result.username = data;
prompt.emit(':end');
});
This code uses some sort of a state-tracking method (I wouldn't know if there is an actual term).
Basically, there is a variable that keeps track of what you programming is looking for, in our case that is current
. This variable is also used to trigger our prompt
EventEmitter whenever data is received.
Inside events we can change the current
variable to ask for something else (I made a shorthand :new
event to do this), manipulate the data however we please, and also add it input our result
variable.
If you want to "tag" your input (a little marker at the beginning), you can do that simply with stdin.write
:
prompt.on(':new', function(){
// ...
process.stdin.write('> ');
});
Here is what that code looks like in action:
$ node ...
What is your name?
> Jamen Marzonie
What are your hobbies?
> programming, philosophy, petting cats
What is your username?
> jamen
{ name: 'Jamen Marzonie',
hobbies: [ 'programming', 'philosophy', 'petting cats' ],
username: 'jamen' }
I routinely use coroutines for this case, which yield questions and consume answers. Co-routines are generators which consume values to yield the next result. In our case they consume the user's responses, and yield prompts.
This makes for supremely readable and debuggable sequences of prompts and simple state management.
Note the use of function*
in the example code which runs in node 14. This syntax defines a coroutine in javascript.
The first two 'library' functions in the example code at the end expose node's native stdio capabilities so that you can write 'stateful' coroutines such as...
function* createSimpleSequence(print) {
print("Welcome to the game");
let playerName = "";
while(!playerName){
playerName = yield "What is your name? ";
}
print(`Hello, ${playerName}`);
yield "Press enter to restart the game";
}
Note that there is no explicit state management, as the co-routine allows us to 'pick up where we left off' after yielding a question, and we can use while loops and other apparently synchronous control flow primitives to decide how and when to 'progress' between states.
The full example requested by the original poster would look like...
function* createOriginalPostersSequence(print) {
let name = "";
while (!name) {
name = yield "What is your name?";
if (!name) {
print("Your name cannot be empty");
}
}
let hobbyString = "";
while (!hobbyString) {
hobbyString = yield "List three of your hobbies, separated by ','";
const hobbyCount = hobbyString.split(",").length;
if (hobbyCount !== 3) {
if (hobbyCount === 0) {
print("Your hobbies cannot be empty");
} else if (hobbyCount == 1) {
print("What! Do you really only have one hobby, think again!");
} else if (hobbyCount == 2) {
print("Two is better than one, but I asked for three!");
}
hobbyString = "";
}
}
const hobbies = hobbyString.split(",").map((hobby) => hobby.trim());
let username = "";
while (!username) {
username = yield "What is your username?";
if (!username) {
print("Your username cannot be empty!");
}
if (!username.match(/[a-z_]+/)) {
print(
"Your username can only contain lowercase letters and underscores."
);
username = "";
}
}
const data = {
name,
hobbies,
username,
};
print(`Your full data is ${JSON.stringify(data)}`);
}
Finally this is the full source code which would run both the simple sequence, and then the original poster's requested interactive prompt sequence interactively in Node 14.
// core 'library' exposing native node console capabilities for co-routines
function getAnswer() {
process.stdin.resume();
return new Promise((resolve) => {
process.stdin.once("data", function (data) {
resolve(data.toString().trim());
});
});
}
async function runSequence(sequenceFactory, clearScreen = true) {
function print(msg, end = "\n") {
process.stdout.write(msg + end);
}
let answer = undefined;
const sequence = sequenceFactory(print);
while (true) {
const { value: question } = sequence.next(answer);
if (question) {
print(question, " : ");
answer = await getAnswer();
if (clearScreen) {
console.clear();
}
} else {
break;
}
}
}
// examples using the library
function* createSimpleSequence(print) {
print("Welcome to the game");
let playerName = "";
while (!playerName) {
playerName = yield "What is your name? ";
}
print(`Hello, ${playerName}`);
yield "Press enter to restart the game";
}
function* createOriginalPostersSequence(print) {
let name = "";
while (!name) {
name = yield "What is your name?";
if (!name) {
print("Your name cannot be empty");
}
}
let hobbyString = "";
while (!hobbyString) {
hobbyString = yield "List three of your hobbies, separated by ','";
const hobbyCount = hobbyString.split(",").length;
if (hobbyCount !== 3) {
if (hobbyCount === 0) {
print("Your hobbies cannot be empty");
} else if (hobbyCount == 1) {
print("What! Do you really only have one hobby, think again!");
} else if (hobbyCount == 2) {
print("Two is better than one, but I asked for three!");
}
hobbyString = "";
}
}
const hobbies = hobbyString.split(",").map((hobby) => hobby.trim());
let username = "";
while (!username) {
username = yield "What is your username?";
if (!username) {
print("Your username cannot be empty!");
}
if (!username.match(/[a-z_]+/)) {
print(
"Your username can only contain lowercase letters and underscores."
);
username = "";
}
}
const data = {
name,
hobbies,
username,
};
print(`Your full data is ${JSON.stringify(data)}`);
}
// demo to run examples
async function run() {
await runSequence(createSimpleSequence);
await runSequence(createOriginalPostersSequence);
process.exit(0);
}
run();
Here's an easy way to do it natively in Node 18.
const readline = require('node:readline/promises'); // Requires Node 18
async function main() {
// Create interface
const rl = readline.createInterface({input:process.stdin, output:process.stdout});
// Ask questions
const name = await rl.question("What is your name? ");
rl.write("Enter three hobbies:\n");
const hobby1 = await rl.question(" Hobby #1: ");
const hobby2 = await rl.question(" Hobby #2: ");
const hobby3 = await rl.question(" Hobby #3: ");
const username = await rl.question("What username would you like? ");
// Close interface
rl.close();
return {name, username, hobbies:[hobby1, hobby2, hobby3]};
}
main();
prompt
might do. – user3117575 Commented Dec 27, 2015 at 16:53