DEV Community

Cover image for How to build your own node.js select-options from scratch.
kweku_kilu
kweku_kilu

Posted on

How to build your own node.js select-options from scratch.

Over the past weeks, I have been digging deeper into node.js and its inbuilt library system and I have learned a lot. I have always wanted to have a deeper understanding of node.js and not only how to use its popular libraries like express. After weeks of experimenting, I decided to build a select-options CLI tool entirely from scratch. What this select-options does is that it enables you to select an option from a list of options.

The core of the program is the readline.emitKeyPressEvents(stream) method of the readline module. The readline module is one of the standard(inbuilt) node.js lib that enables you to read from the console. The readline.emitKeyPressEvents(stream) enables you to listen to keyboard events on a stream.
The stream in our case is the standard input(we will be reading from the console), that is process.stdin. We can listen to input from the process.stdin by subscribing to the keypress event. That is process.stdin.on(keypress, keyPressedHandler). Below is a code sample for subscribing and listening to the keyboard event on the standard input.

    const readline = require('readline')
    const input = process.stdin
    const output = process.stdout

    const input = process.stdin
    input.setRawMode(true)
    input.resume()
    input.on('keypress', keyPressedHandler)

Now in the keyPressedHandler function, we check to see which key was pressed. The keys we check include up key, down key, escape, ctrl+c and we handle the response appropriately as indicated in the code sample.


const selectOption = {}

selectOption.selectIndex = 0
selectOption.options = ['mango', 'banana', 'apple', 'orange']
selectOption.selector = '*'
selectOption.isFirstTimeShowMenu = true

const keyPressedHandler = (_, key) => {
    if (key) {
        const optionLength = selectOption.options.length - 1 
        if ( key.name === 'down' && selectOption.selectIndex < optionLength) {
            selectOption.selectIndex += 1
            selectOption.createOptionMenu()
        }
        else if (key.name === 'up' && selectOption.selectIndex > 0 ) {
            selectOption.selectIndex -= 1
            selectOption.createOptionMenu()
        }
        else if (key.name === 'escape' || (key.name === 'c' && key.ctrl)) {
            selectOption.close()
        }
    }
}

This code sample increase/decrease the selectIndex or quit the application based on the key pressed.
The selectIndex will later be used as the index for choosing the selected option. Once we have the selectIndex we create the options Menu. The sample code for creating the option menu is indicated below.

selectOption.createOptionMenu = () => {
    const optionLength = selectOption.options.length
    if (selectOption.isFirstTimeShowMenu) {
        selectOption.isFirstTimeShowMenu = false
    }
    else {
        output.write(ansiEraseLines(optionLength))

    }
    const padding = selectOption.getPadding(20)
    const cursorColor = ansiColors(selectOption.selector, 'green')

    for (let i= 0; i < optionLength; i++) {

        const selectedOption = i === selectOption.selectIndex //1
                                ? `${cursorColor} ${selectOption.options[i]}` //2
                                : selectOption.options[i] //3
        const ending = i !== optionLength-1 ? '\n' : '' //4
        output.write(padding + selectedOption + ending) //5
    }
}

The most important part of the sample code is the part labelled in the comment from 1 -5. What we are doing is selecting the chosen option by comparing selectIndex to i, our current iteration index. If they are the same then we concatenate our selector/indicator(showing which option is currently chosen), indicated by * with the selected option else we just get the option at the current iteration. We then place each option(selected or unselected) option on a different line except the last one. We finally write to the console.

Additional code samples like

const ansiEraseLines = (count) => {
    //adapted from sindresorhus ansi-escape module
    const ESC = '\u001B['
    const eraseLine = ESC + '2K';
    const cursorUp = (count = 1) => ESC + count + 'A'
    const cursorLeft = ESC + 'G'

    let clear = '';

    for (let i = 0; i < count; i++) {
        clear += eraseLine + (i < count - 1 ? cursorUp() : '');
    }

    if (count) {
        clear += cursorLeft;
    }

    return clear;

}

is used for creating a helper function to help us clear the console.

The code sample

const ansiColors = (text, color) => {
    const colors = {
        'green': 32,
        'blue': 34,
        'yellow': 33   
    }
    if (colors[color]) `\x1b[${colors[color]}m${text}\x1b[0m`
    //default for colors not included
    return `\x1b[32m${text}\x1b[0m`


}

is used for generating console colors.

Read the full select-options source here to get a clear glimpse into how I implemented it.

Top comments (3)

Collapse
 
padrocha profile image
Joshua Elí Padrón Rocha

select-options

No sabía como subir la imagen Jajajaja

Collapse
 
padrocha profile image
Joshua Elí Padrón Rocha

Me sirvió como base para crear algo que necesitaba, muchas gracias

Collapse
 
padrocha profile image
Joshua Elí Padrón Rocha

It´s perfect