Invalid Form Fields and StimulusJS
Tags: stimulusjs, html
I wanted to give a quick overview on a simple method to illustrate some interesting StimulusJS and JavaScript concepts.
When submitting forms we use Rails’ built in disable_with
functionality to prevent double clicks when forms are submitted. When a form is submitted, the submit button becomes disabled, preventing the user from submitting the form multiple times. Also, the text of the button is replaced with text value that is passed to the disable_with
attribute.
To enable disable_with
, all you need to do is attach it as a parameter to the element being clicked or submitted along with a string. The string represents the content that gets displayed after the button is clicked. Like so: data: { disable_with: "Saving" }
, resulting with the following data attribute: data-disable-with="Saving..."
. When the form is submitted, Rails adds the disabled
attribute to the button and replaces the text with the value of data-disable-with
.
Before submitting form:
After submitting form:
disable_with
is helpful and effective but there is a (relatively minor) limitation. We felt, in addition to disabling, we wanted to enhance the user experience by placing a spinner on the submit button to give the user more visual feedback. Presumably the simplest way to do this would be for disable_with
to have the ability to pass the class name as an additional value. Currently you can’t do this.
We needed a simple way of adding a CSS class name that shows a spinner on the submit button via JavaScript using StimulusJS. In Stimulus we attach methods to DOM elements by using using data-action
attributes. The action that gets added to the submit button in our scenario looks like this: data-action=“click->forms#showLoading”
. When the user clicks the submit button the showLoading
method within the forms
controller is called.
All that happens in the method is that a class name of button--loading
gets added to the button and the spinner appears. Whatever styles are applied to a button with the disabled
attribute are overritten by the button--loading
styles. Also, the button--loading
styles were done in a way so that they will work with an enabled or disabled button.
Pretty straightforward - except we had one problem. The solution didn’t account for when the submit button was clicked but the form didn’t actually submit because one of the HTML5 inputs (like a number type) was invalid. This scenario resulted in rendering a submit button that was not disabled (because the form had not been submitted and disable_with
had not been triggered) but the button was still active because the click
event had been triggered and the spinner never went away. This made for a very confusing user experience. In order to prevent this from happening I needed to add an if
condition that checked for this scenario to prevent the loading class from being added to the button prematurely.
This method is the end result:
showLoading(event) {
if (!this.element.querySelector(‘:invalid’)) {
event.target.classList.add(‘button--loading’)
}
}
I got a question from one of my colleagues about the if
condition in the showLoading
method. The question was:
What is
this
,this.element
and how does that query the entire form for:invalid
?
So, here’s my attempt to answer this question:
In this context, this
is an instance of the forms
Stimulus controller. In itself this
on its own isn’t especially useful but this.element
is very useful. In Stimulus, a controller is always attached to a DOM element, like so <div data-controller="name-of-controller">[some more markup]</div>
. In this instance of our scenario this.element
is always the attached form
element (which is why it is called forms
). The name of the file that the controller is contained in is forms_controller.js
.
The :invalid
pseudo-class is automatically added by the browser to an element that doesn’t pass whatever validations are relevant to the HTML5 element. For example, if you have a form that uses a number
input and you enter a non-numeric character in to that input the browser will automatically add an :invalid
pseudo-class to that input. Also, if there’s an element within the form that is invalid, the browser won’t let the form be submitted. Therefore if a form is not being submitted we don’t need to add the spinner/loading class to the submit button. We just do a quick check to see if any of the form fields have the invalid pseudo-class and if none have been added to any of the form elements we can be confident that the form is going to be submitted and we can add the spinner/loading class to the submit button.
I’m hope that in the future Rails’ disable_with
functionality can be augmented to allow for a class name to be passed to the submit button without having to add any extra JavaScript.