JavaScript: create a password generator

JavaScript: create a password generator

In this tutorial we will see how to create a password generator in JavaScript.

In this tutorial we will see how to create a password generator in JavaScript.

We will use the Object-Oriented (OO) approach and choose not to use any framework or library in order to maximize the didactic aspect of this tutorial.

Basically we have to generate a random string of length n starting from some user choices, that is:

  1. The string must contain only lowercase letters.
  2. The string must contain uppercase and lowercase letters.
  3. The string must contain numbers.
  4. The string must contain special characters.

Whether the string produced contains all options is up to the user. The user must be able to adjust the length of the password through an input field of type range and make your choices using 4 specific checkbox controls. Finally, the user must be able to copy the generated password to the clipboard using a specific control.

Let's start with the structure of our class.

'use strict';

class PasswordGenerator {
    constructor({ input = null, createButton = null,
                    copyButton = null, lengthInput = null,
                    lettersControl = null,
                    mixedControl = null,
                    punctControl = null, numberControl = null }) {
        this.letters = {
            uppercase: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
            lowercase: 'abcdefghijklmnopqrstuvwxyz'
        };
        this.digits = '0123456789';
        this.symbols = '!$%&()=?*+><:;.-@[]{}';

        this.input = input;
        this.createButton = createButton;
        this.copyButton = copyButton;
        this.lengthInput = lengthInput;
        this.lettersControl = lettersControl;
        this.mixedControl = mixedControl;
        this.punctControl = punctControl;
        this.numberControl = numberControl;

        if(this.canInit()) {
            this.init();
        }
    }

    canInit() {
        return this.input && this.createButton && this.copyButton && this.lengthInput && this.lettersControl && this.mixedControl && this.punctControl
                 && this.numberControl;
    }
    
    //...
}    

In the constructor we define the characters that will make up the password and the references to the controls to which we will associate the events. The canInit() method verifies that the necessary elements exist: if these elements do not exist, the class code will not be executed.

Since we won't always need all the characters defined, let's create a method to make a selection of the type required at the moment.

getCharacters(kind = 'all') {
        switch(kind) {
            case 'uppercase':
                return this.letters.uppercase;
            case 'lowercase':
                return this.letters.lowercase;
            case 'letters':
                return this.letters.uppercase + this.letters.lowercase;
            case 'symbols':
                return this.symbols;
            case 'numbers':
                return this.digits;
            default:
                return this.letters.uppercase + this.letters.lowercase + this.digits + this.symbols;
        }

    }

We now define the method which, given a set of characters and a length n, creates a string of length n by randomly accessing the character set within a loop.

create(chars, length) {
        let password = '';
        for(let i = 1; i <= length; i++) {
            let char = Math.floor(Math.random() * chars.length + 1);
            password += chars.charAt(char);
        }
        return password;
    }

However, our character sets have a predetermined order, so we need to create a method that shuffles these characters.

shuffle(str) {
        return str.split('').sort((a, b) => { return Math.random() - 0.5; }).join('');
    }

Since the user chooses which character sets to include, we need to create a method that keeps track of the status of the checkboxes in use.

getControlState() {
        let state = {
            letters: false,
            mixed: false,
            punctuation: false,
            numbers: false
        };

        state.letters = (this.lettersControl.checked);
        state.mixed = (this.mixedControl.checked);
        state.punctuation = (this.punctControl.checked);
        state.numbers = (this.numberControl.checked);

        return state;
    }

The returned object will then be used by the method that will generate the password.

 build(state) {
        let characters = '';
        if(state.letters) {
            characters += this.getCharacters('lowercase');
        }
        if(state.mixed) {
            characters = this.getCharacters('letters');
        }

        if(state.punctuation) {
            characters += this.getCharacters('symbols');
        }

        if(state.numbers) {
            characters += this.getCharacters('numbers');
        }

        if(characters.length === 0) {
            characters = this.getCharacters();
        }

        return this.shuffle(characters);
    }

We now define the events() method which will be invoked in the init() method. In this method we bind the events to the elements of the UI. The main event is the click on the password creation button.

const self = this;
        self.createButton.addEventListener('click', evt => {
            evt.preventDefault();
            let state = self.getControlState();
            self.input.value = self.create(self.build(state), parseInt(self.lengthInput.value, 10));
        }, false);

The event reads the value of the input of type range and uses it to set the length of the generated password.

At this point since many actions depend on this event, in order not to duplicate the code, we create a method that will trigger the event on the button.

triggerEvent(element, name = 'click') {
        const evt = new Event(name);
        return element.dispatchEvent(evt);
    }

So we use it on the elements on which we want to trigger the generation of the password.

self.lengthInput.addEventListener('change', () => {
            self.triggerEvent(self.createButton, 'click');
        }, false);

        self.lettersControl.addEventListener('change', () => {
            self.triggerEvent(self.createButton, 'click');
        }, false);


        self.mixedControl.addEventListener('change', () => {
            self.triggerEvent(self.createButton, 'click');
        }, false);

        self.punctControl.addEventListener('change', () => {
            self.triggerEvent(self.createButton, 'click');
        }, false);

        self.numberControl.addEventListener('change', () => {
            self.triggerEvent(self.createButton, 'click');
        }, false);

The last action to set is the copy action to the clipboard.

self.copyButton.addEventListener('click', evt => {
            evt.preventDefault();
            self.input.select();
            document.execCommand('copy');
            self.copyButton.innerText = 'Copied!';
            setTimeout(() => {
                self.copyButton.innerText = 'Copy Password';
            }, 1000);
        }, false);


        self.triggerEvent(self.createButton, 'click');

The last line triggers the main event so that when the page is loaded the password is already present as the value of the input field.

Finally, we can use our class as follows:

const passwordGenerator = new PasswordGenerator({
            input: document.getElementById('password'),
            createButton: document.getElementById('create-password'),
            copyButton: document.getElementById('copy-password'),
            lengthInput: document.getElementById('password-length'),
            lettersControl: document.getElementById('password-letters'),
            mixedControl: document.getElementById('password-mixed'),
            punctControl: document.getElementById('password-punctuation'),
            numberControl: document.getElementById('password-numbers')
        });

Demo

JavaScript: password generator