I have a form which has a text input, a file input, and a submit button.
I want to modify the display of the button in response to input changes, where it should be enabled if either of the input types are present, and disabled otherwise.
Here is my current implementation. I define two flags in the Stimulus controller:
static values = {
hasAttachments: Boolean,
hasTextInput: Boolean
}
When the user enters text or selects a file, I set the flags manually based on the input change.
Next, I use two [...]valueChanged() Stimulus methods that respond to these flags being updated and are basically doing the exact same thing:
hasAttachmentsValueChanged() {
this.setSendBtnState(this.hasAttachmentsValue || this.hasTextInputValue);
}
hasTextInputValueChanged() {
this.setSendBtnState(this.hasTextInputValue || this.hasAttachmentsValue);
}
Lastly I have the setSendBtnState() method that makes changes to the display of the button:
setSendBtnState(enabled) {
if (enabled) {
this.sendBtnTarget.classList.remove("disabled")
this.sendBtnTarget.removeAttribute("disabled")
} else {
this.sendBtnTarget.classList.add("disabled")
this.sendBtnTarget.setAttribute("disabled", "disabled")
}
}
Question: How can I improve this implementation to:
- Avoid duplicating the same code in the valueChanged() methods
- Is there a way to have Simulus track the presence of form inputs without me setting those flags manually whenever inputs are changed?
- How can I avoid code duplication if I need to write another setState() method that controls display of some other part of the form, but essentially does the same thing and responds to the same flags, albeit setting a different attribute?
Before writing any more Stimulus code, I strongly recommend you read about how HTML and built in browser can help you get to your goal.
HTML forms and browsers have a substantial amount of built in functionality for making sure that forms can be submitted only when valid.
You can use
<input ... required />to indicate that a field must have a value to be submitted. You can also use styling on theformandinputelements with the:invalidpseudo-class to style things so that the user knows what do to visually, even if that means toggling the visibility of a label.Finally, you should try to avoid disabling a
submitbutton dynamically, it creates problems for accessibility and focus management and usually confuses users. It's better to let them press the button and then take them or show them the action to take that they have missed.Please have a read of these MDN pages to understand more
After that, you should have a HTML form that gets you 90% of what you want without a single line of JavaScript.
From there you can look at enhancing this to get a more fine-tuned behaviour for your users if you need. Or, you may find at this point the job is done and you can move on to your next task.
If you want to control parts of a
form(includinginputelements) with a Stimulus controller I would recommend you attach your controller to theform.Instead of adding a new value tracking for each input, think about the inputs you want to track as
targets instead.Finally, you want to listen to changes for the form as a whole, so one event listener on the outer
formshould be enough.Here's an example HTML to start with.
Here's a basic Stimulus controller that should achieve similar goals to your current approach but with a little bit of abstraction.
Whenever the controller connects AND there is a change in the form we run the
checkmethod that checks all fields for their own validity. Using the JavaScript DOM interface instead of manually checking values, see https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validationWe track a single over-arching value in the controller for
isValid, when this changes we use this...valueChangedcallback to update classes.Finally, we have a
submitmethod that allows us to block the form submit and focus on the field that has the error. You may not even need this but you can maybe dynamically show some other content. Remember the:invalidCSS selector could do some of this job also.Remember, as per the Stimulus docs, always start with the HTML first. Understand what it can do (in the browsers you have to support) and write semantic, accessible HTML from the start.
Then, let JavaScript help you fill in the gaps where needed, low touch and with a bit of your mind always thinking 'the less I write the better'. The browser cannot do everything you want usually but it can do a lot more than we can by trying to solve the whole problem in JavaScript alone.
Have fun.