When I went through the Vanilla JS Academy, my first project was a script that allows users to toggle the visibility of a single password field. I then refactored it to support multiple fields, and finally multiple forms. Here's how I did it.

Before we do anything else, let's look at the HTML I started with:

<h1>Toggle Password Visibility</h1>

<form>
<p>
<label for="username">Username</label>
<input id="username" type="text" name="username">
</p>

<p>
<label for="password">Password</label>
<input id="password" type="password" name="password">
</p>

<p>
<label for="show-password">
<input id="show-password" type="checkbox" name="show-password">
Show password
</label>
</p>

<p>
<button type="submit">Log In</button>
</p>
</form>

When the checkbox is checked, I want to show the password. When it's unchecked, I want to mask the password again.

Toggling a single field

First things first, I declared my variables. I needed to use the checkbox toggle and password field in my script, so I selected both of these:

// Get the password toggle
const toggle = document.querySelector('#show-password');

// Get the password field
const password = document.querySelector('#password');

Then I created a function called togglePassword(). It allows me to pass in any checkbox toggle and any password field. It will toggle the visibility of the password field based on the current state of the checkbox:

/**
* Toggle the visibility of a password field
* based on a checkbox
*
* @param {Object} checkbox The checkbox
* @param {Object} field The password field
*/

function togglePassword (checkbox, field) {
field.type = checkbox.checked ? 'text' : 'password';
}

Next, I created a function to handle any change events. I called my togglePassword() function, passing in the this keyword and the password element:

/**
* Handle change events
*/

function handleChange () {
togglePassword(this, password);
}

Finally, I set up a change event listener on my toggle element, passing in the handleChange() function as the callback.

Because I attached the event listener to the toggle element, that's what the this keyword in the handleChange() function refers to.

Learn more: What the lexical is this?

// Handle change events
toggle.addEventListener('change', handleChange);

View demo on CodePen

Toggling multiple fields

I wanted my script to be able to support multiple password fields. I updated my HTML with an extra password field so I could test this:

<h1>Toggling Multiple Password Fields</h1>

<form>
<p>
<label for="current-password">Current Password</label>
<input id="current-password" type="password" name="current-password">
</p>

<p>
<label for="new-password">New Password</label>
<input id="new-password" type="password" name="new-password">
</p>

<p>
<label for="show-passwords">
<input id="show-passwords" type="checkbox" name="show-passwords">
Show passwords
</label>
</p>

<p>
<button type="submit">Change Passwords</button>
</p>
</form>

Next, I updated my variables. My toggle variable is the same, but this time, I got an array of the password fields instead of a single element:

// Get the password fields
const passwords = Array.from(
document.querySelectorAll('[type="password"]')
);

Then I updated my functions. My togglePassword() function is the same, but in my handleChange() function, I use a forEach() loop to call the togglePassword() function once for each password field:

/**
* Handle change events
*/

function handleChange () {
passwords.forEach(password => {
togglePassword(this, password);
});
}

I'm still attaching my event listener to the toggle element, so the this keyword still refers to that. The arrow function is important here. Without it, the this keyword would refer to the global window object. You can learn why here: "this" and Arrow Functions.

View demo on CodePen

Multiple forms

Finally, I wanted my script to work with multiple password fields across multiple forms. Here's the updated markup for testing this. Notice that I added some data attributes to make my life easier:

<h1>Toggling Passwords in Multiple Forms</h1>

<h2>Change Username</h2>

<p>Enter your username and password to change your username.</p>

<form>
<p>
<label for="username">Username</label>
<input id="username" type="text" name="username">
</p>

<p>
<label for="password">Password</label>
<input id="password" type="password" name="password" data-password>
</p>

<p>
<label for="show-password">
<input id="show-password" type="checkbox" name="show-password" data-toggle>
Show password
</label>
</p>

<p>
<button type="submit">Change Username</button>
</p>
</form>

<h2>Change Password</h2>

<p>Enter your current password and new password below.</p>

<form>
<p>
<label for="current-password">Current Password</label>
<input id="current-password" type="password" name="current-password" data-password>
</p>

<p>
<label for="new-password">New Password</label>
<input id="new-password" type="password" name="new-password" data-password>
</p>

<p>
<label for="show-passwords">
<input id="show-passwords" type="checkbox" name="show-passwords" data-toggle>
Show passwords
</label>
</p>

<p>
<button type="submit">Change Passwords</button>
</p>
</form>

Each checkbox needs to toggle the password field(s) inside its own form. The approach is quite different this time.

I didn't need any variables, so I deleted those.

I kept my togglePassword() function, but my handleChange() function is quite different. I used event delegation and DOM traversal to get the password fields inside the relevant form and toggle only those:

/**
* Handle change events
* @param {Object} event The Event object
*/

function handleChange (event) {
// If this wasn't a password toggle, just bail
if (!event.target.matches('[data-toggle]')) return;

// Get all password fields inside this form
const passwords = Array.from(
event.target.form.querySelectorAll('[data-password]')
);

// Toggle the password fields
passwords.forEach(function (password) {
togglePassword(event.target, password);
});
}

I attached the handleChange() function to the body element this time. This is because it's the closest common ancestor of the checkbox toggles.

You can read more about my approach to event delegation if you want, but for now, here's the event listener:

// Handle change events
document.body.addEventListener('change', handleChange);

View demo on CodePen