A quality of life Stimulus.js controller
Tags: stimulus, javascript, rails, hotwire
Published: 2023-02-10 13:16:02

If you are developing any (Rails) app, you know how often have to log in to your own application. Enter your email and password for the umpteenth time isn't really 2023 is it? You could store them in your password manager, sure. But that also that is too much work. Besides you don't really want to clutter your password manager with dummy or seed data.

For the past few years, at least since [Stimulus.js was introduced](https://stimulus.hotwired.dev/), I've been using this simple controller for all the SaaS products I developed. 

Here it is, ready to be copied-and-pasted. Let's go over some of the more interesting bits afterwards.

**app/javascript/controllers/development/fill_form_controller.js**
```js
import { Controller } from '@hotwired/stimulus'

export default class extends Controller {
  static targets = ["button"]

  connect() {
    if (this.#formElementsOnPage && this.#devEnvironment) {
      this.#addComponent()
    }
  }

  analysePage() {
    if (this.#formElementsOnPage == 1) {
      this.#fillForm()
    } else {
      this.buttonTarget.text = "Too many forms on this page…"
    }
  }

  analysePageWithKeybinding(event) {
    if (event.key === "f") {
      this.analysePage()
    }
  }

  // private

  get #formElementsOnPage() {
    return this.#numberOfFormElements > 0
  }

  get #devEnvironment() {
    return process.env.NODE_ENV === "development"
  }

  #addComponent() {
    this.element.insertAdjacentHTML("beforeend", this.#buttonTag())
  }

  get #numberOfFormElements() {
    return document.getElementsByTagName("form").length
  }

  get #buttonTag() {
    return `
      <button
        data-development--fill-form-target="button"
        data-action="development--fill-form#analysePage keydown@window->development--fill-form#analysePageWithKeybinding"
      >
        Fill form
      </button>
    `
  }

  #fillForm() {
    const forms = document.getElementsByTagName("form");

    [].forEach.call(forms, (form) => {
      this.#fillInputsFor(form)
    })
  }

  #fillInputsFor(form) {
    const inputs = form.getElementsByTagName("input");

    [].forEach.call(inputs, (input) => {
      let regex = /(^.*\[|\].*$)/g;
      let inputName = input.name.replace(regex, "")

      switch (inputName) {
        case "name":
          input.value = this.#randomiseValue(this.#nameValueOptions())
        break
        case "email":
          input.value = this.#randomiseValue(this.#emailValueOptions())
        break
        case "password":
          input.value = this.#randomiseValue(this.#passwordValueOptions())
        break
      }
    })
  }

  #nameValueOptions() {
    return [
      "Chris", "Kate", "Cameron"
    ]
  }

  #emailValueOptions() {
    return [
      "test@example.com"
    ]
  }

  #passwordValueOptions() {
    return [
      "password"
    ]
  }

  #randomiseValue(items) {
    return items[Math.floor(Math.random() * items.length)]
  }
}
```

And then in **app/view/layouts/application.html.erb**

```erb
  <%= tag.div nil, data: { controller: "development--fill-form" } if Rails.env.development? %>
```

You could also only add it to the layout for your sign up/log in instead. It's what I do. 


## How it works

All this class does is check if there's one form present on the page. If there's one (and not many), and the user presses `f` or the button in the bottom-right, the form fields for **name**, **email** and **password** gets, randomly, filled with any of the values from any of the `_ValueOptions()` functions.

Let's go over some of the more interesting bits.

```js
connect() {
  if (this.#formElementsOnPage && this.#devEnvironment) {
    this.#addComponent()
  }
```

If there are form elements on the page and if the environment equals "development", run `this#addComponent()`. The environment check is also done when adding the element on the page with `Rails.env.development?`. It really is just an extra safeguard. 

_Quick side note_: the functions with `#` prefix are “[private functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields)”. Similar to Ruby's private methods. I like to expose only the functions needed for the functionality to work. The `// private` comment does nothing really, I like it for a visual cue.

```js
#buttonTag() {
return `
   <button
     data-development--fill-form-target="button"
      data-action="development--fill-form#analysePage keydown@window->development--fill-form#analysePageWithKeybinding"
      >
      Fill form
    </button>
  `
}
```

This isn't a method I use often with Stimulus.js (which works best with HTML you have), but sometimes adding the HTML from the controller makes sense. 


```js
#fillInputsFor(form) {
  // …
  
[].forEach.call(inputs, (input) => {
    let regex = /(^.*\[|\].*$)/g;
    let inputName = input.name.replace(regex, "")

    switch (inputName) {
      // …
    }

  // …
}
```

This function checks for the input's name value and inserts a random value from any of the `_ValueOptions()`. A tricky bit here is that Rails' expects input name's like `model[name]`, so this is taken care off with some regex. 

If you want to add more values to the current options, you can expand the arrays. Also if you need more fields, you can extend them easily.

Like this approach? Or do you know a better way? Do let me know.