Password visibility toggle using stimulus.js and Rails 6
Tags: rails, webdev, javascript, stimulus
Published: 2020-10-04 19:07:07

Recently I wanted to implement password show hide feature in devise forms using Stimulus.js in Rails 6. Being a beginner in stimulus. I had to struggle a lot to find a right way to do it. 

There are two ways of implementation and two scenarios which I will be covering in this article.

1. When you want to implement toggle just in one password field.

2. When you want to implement toggle in two password fields for e.g. in signup form we will have two password fields.

I am assuming whoever is reading this they have basic idea of Stimulus.js

**Scenario 1.**

This is how my basic devise password field looks like

```
<div class="form-group my-4" data-controller="password-toggle">
 <%= f.label :password %>
 <%= f.password_field :password, autocomplete: "current-password", class: 'form-control p-2 my-2 unhide', placeholder: "Password", "data-target": "password-toggle.unhide"%>
 <a data-action="click->password-toggle#password" class="password-field-icon-1 far fa-eye-slash"></a>
</div>
```

There are three things which I have added which converts my field into accepting stimulus events.

1. **Defining stimulus controller:** `data-controller="password-toggle"` `password_toggle_controller.js` is my stimulus controller file.

2. **Defining target field:** `"data-target": "password-toggle.unhide"`

3. **Defining click event:** Click event to be defined on icon tag. `data-action="click->password-toggle#password"`

Now, this is how our `password_toggle_controller.js` will look like. 

```
import { Controller } from "stimulus";

export default class extends Controller {

  static targets = ["unhide"]

  password(e) {
    if (this.input.type === "password") {
      e.target.classList.remove('fa-eye-slash');
      e.target.classList.add('fa-eye');
      this.input.type = "text";
    } else {
      e.target.classList.remove('fa-eye');
      e.target.classList.add('fa-eye-slash');
      this.input.type = "password";
    }
  }
}
```

The basic idea of this piece of code is, if input field type is `password` and you click on visibility icon it will change the field type to `text` and toggle the icon and vice versa.

Now, if this works for you and this is only your requirement then great! 

**Scenario 2**

But if your requirement is not this and you want to enable the toggle on both password fields. Then we have to use more of jquery. 

And in my code as I have used `SVG` type of icon. `e.target` doesn't always gives me correct target to make the changes in icon. This [article](https://medium.com/@schalkneethling/preventing-svg-from-becoming-the-event-target-3e9c039dfdd0) briefly explains why does click event on `SVG` icon behaves like that.

So, I had to change my way of approaching to this problem.

```
<div class="form-group pb-1" data-controller="password-toggle">
  <label>Password</label>
  <%= f.password_field :password, autocomplete: "off", class: 'form-control p-2', required: true, placeholder: 'Password' %>
  <a data-action="click->password-toggle#password" class="password-field-icon-2 far fa-eye-slash"</a> 
</div>

<div class="form-group pb-1" data-controller="password-toggle">
  <label>Password Confirmation</label>
  <%= f.password_field :password_confirmation, autocomplete: "off", class: 'form-control p-2', required: true, placeholder: 'Confirm Password' %>
  <a data-action="click->password-toggle#password" class="password-field-icon-2 far fa-eye-slash">	   </a>
</div>
```

As, you can see I removed the `data-target` as I am not going to use that, instead I will use the `event` object, in my stimulus controller.

```
import { Controller } from "stimulus";

export default class extends Controller {

  password(e) {

    var password_field = $(e.currentTarget).prev("input")[0];
    var icon = $(e.currentTarget).closest("svg")[0];

    if (password_field.type === "password") {
      icon.classList.remove('fa-eye-slash');
      icon.classList.add('fa-eye');
      password_field.type = "text";
    } else {
      icon.classList.remove('fa-eye');
      icon.classList.add('fa-eye-slash');
      password_field.type = "password";
    }
  }
 }
}
```
The basic algo of the code remains same, the only change is how do we fetch the `input field` and how do we fetch the `icon`. Instead of using `e.target` I used `e.currentTarget` which correctly gives me the icon tag.

And instead of using `this.target` I am using same `e.currentTarget` to get the input field just previous to it, which is basically gives me the input password field for that icon event. 

Thank you! For reading it out. I hope it helps someone. If you have better idea than this. Please let me know. I would love implement it in my project. Don't forget to show some love. ❤️