Consider the following simplified HTML code:
<body>
<form>
<textarea></textarea>
<input type="email">
<button type="submit"></button>
</form>
<form>
<textarea></textarea>
<input class="optional" type="email">
<button type="submit"></button>
</form>
<form>
<input type="email">
<button type="submit"></button>
</form>
... etc ...
</body>
It is desirable the for following functionality, if both textarea and email of a form are empty, then:
- The respective button should be disabled
- The respective optional class element should not be displayed
To achieve that, you might include onload
and oninput
events as follows:
<body onload="empty_check()">
<form>
<textarea oninput="empty_check()"></textarea>
<input type="email" oninput="empty_check()">
<button type="submit"></button>
</form>
<form>
<textarea oninput="empty_check()"></textarea>
<input class="optional" type="email" oninput="empty_check()">
<button type="submit"></button>
</form>
<form>
<input type="email" oninput="empty_check()">
<button type="submit"></button>
</form>
... etc ...
</body>
Where the empty_check()
function is the following JavaScript code:
function empty_check() {
var list = document.getElementsByTagName("form");
var n = list.length;
for(var i=0; i<n; i++) {
if(list[i].getElementsByTagName("textarea").length != 0) {
if( (list[i].getElementsByTagName("textarea")[0].value.length != 0) || (list[i].querySelector('input[type=email]').value.length != 0) ) {
list[i].getElementsByTagName("button")[0].disabled = false;
if(list[i].getElementsByClassName("optional").length != 0){
list[i].getElementsByClassName("optional")[0].style.display = 'block';
}
} else {
list[i].getElementsByTagName("button")[0].disabled = true;
if(list[i].getElementsByClassName("optional").length != 0){
list[i].getElementsByClassName("optional")[0].style.display = 'none';
}
}
} else {
if(list[i].querySelector('input[type=email]').value.length != 0) {
list[i].getElementsByTagName("button")[0].disabled = false;
if(list[i].getElementsByClassName("optional").length != 0){
list[i].getElementsByClassName("optional")[0].style.display = 'block';
}
} else {
list[i].getElementsByTagName("button")[0].disabled = true;
if(list[i].getElementsByClassName("optional").length != 0){
list[i].getElementsByClassName("optional")[0].style.display = 'none';
}
}
}
}
}
Issue
The JavaScript code for this simple task seems to be over-complicated:
- Too many lines
- Too many if-else conditions
- Needs some unusual functions such as
querySelector
Therefore, is it possible to simplify that?
Follow-up
Based on comments and answers, I'm now using the following HTML code:
<body onload="handleAllFormState()">
<form>
<textarea oninput="handleFormState(forms[0])"></textarea>
<input type="email" oninput="handleFormState(forms[0])">
<button type="submit"></button>
</form>
<form>
<textarea oninput="handleFormState(forms[1])"></textarea>
<input class="optional" type="email" oninput="handleFormState(forms[1])">
<button type="submit"></button>
</form>
<form>
<input type="email" oninput="handleFormState(forms[2])">
<button type="submit"></button>
</form>
... etc ...
</body>
And the following JavaScript code:
forms = document.getElementsByTagName("form");
function isFormEmpty(form) {
var list = form.querySelectorAll('textarea, input[type=email]');
var empty = true;
for (var i = 0; i < list.length; i++) {empty = empty && !list[i].value;}
return empty;
}
function handleFormState(form) {
var empty = isFormEmpty(form);
var optional = form.querySelector('.optional');
if (optional) {optional.style.display = empty? 'none' : 'block';}
form.querySelector('button').disabled = empty;
}
function handleAllFormState() {
for (var i = 0; i < forms.length; i++) {handleFormState(forms[i]);}
}
2 Answers 2
You can try something like this:
- Create a function that validates a form.
- This function will fetch all mandatory elements
:not(.optional)
. - Each one should have a valid value. If not, return false.
- This function will fetch all mandatory elements
- Pass this validation value to another function that handles UI state.
- Here, if the value is true, enable button and show optional fields
- If false, disable buttons and hide optional fields.
Just JS
function empty_check() {
var forms = document.getElementsByTagName("form");
for (var i = 0; i < forms.length; i++) {
handleUIState(forms[i], validateForm(forms[i]));
}
}
function validateForm(form) {
var list = form.querySelectorAll('textarea, input:not(.optional)');
var valid = true;
for (var i = 0; i < list.length; i++) {
valid = valid && !!list[i].value;
}
return valid;
}
function handleUIState(form, valid) {
var optional = form.querySelector('.optional');
if (optional) {
optional.style.display = valid ? 'block' : 'none';
}
form.querySelector('button').disabled = !valid
}
<body onload="empty_check()">
<form>
<textarea oninput="empty_check()"></textarea>
<input type="email" oninput="empty_check()">
<button type="submit">Submit</button>
</form>
<form>
<textarea oninput="empty_check()"></textarea>
<input class="optional" type="email" oninput="empty_check()">
<button type="submit">Submit</button>
</form>
<form>
<input type="email" oninput="empty_check()">
<button type="submit">Submit</button>
</form>
... etc ...
</body>
JS + CSS
function empty_check() {
var forms = document.getElementsByTagName("form");
for (var i = 0; i < forms.length; i++) {
handleUIState(forms[i], validateForm(forms[i]));
}
}
function validateForm(form) {
var list = form.querySelectorAll('textarea, input:not(.optional)');
var valid = true;
for (var i = 0; i < list.length; i++) {
valid = valid && !!list[i].value;
}
return valid;
}
function handleUIState(form, valid) {
form.classList.toggle('valid', valid)
form.querySelector('button').disabled = !valid
}
.valid{}
.optional{
display: none;
}
.valid .optional {
display: block;
}
<body onload="empty_check()">
<form>
<textarea oninput="empty_check()"></textarea>
<input type="email" oninput="empty_check()">
<button type="submit">Submit</button>
</form>
<form>
<textarea oninput="empty_check()"></textarea>
<input class="optional" type="email" oninput="empty_check()">
<button type="submit">Submit</button>
</form>
<form>
<input type="email" oninput="empty_check()">
<button type="submit">Submit</button>
</form>
... etc ...
</body>
-
\$\begingroup\$ You can try
input:visible
. not sure though \$\endgroup\$Rajesh Dixit– Rajesh Dixit2017年12月07日 09:04:06 +00:00Commented Dec 7, 2017 at 9:04 -
\$\begingroup\$
input:(selector1):not(selector2)...
\$\endgroup\$Rajesh Dixit– Rajesh Dixit2017年12月07日 09:33:07 +00:00Commented Dec 7, 2017 at 9:33
EDIT
Changed the event on the form elements to be the input
event instead of keypress
event
Original
You'll have to forgive any bugs -- I'm on cold medicine right now due to an unfortunate malady.
That being said, I'd like to explain [].slice.call(document.querySelectorAll('<selector here>')
: this is a way to turn the result from .querySelectorAll()
from a NodeList to an Array so that I could use any/all of the methods available to array objects. In this case, I used .forEach
.
that being said, the code here just adds some events to the inputs using javascript, and will only check the parent form that the event is triggered (this way you don't have to loop through all the elements in the DOM every time someone changes a field value)
Quick Edit Rajesh just posted an answer, and I feel like his is much simpler, but I spent the time to write this answer out so I'm going to post it anyway.
window.addEventListener('load', function(e){
var forms = document.querySelectorAll('form');
//get the forms
[].slice.call(document.querySelectorAll('input.optional')).forEach(function(optional){
optional.style.display='none';
});
//Hide all optional fields on load really quick
for(let i = 0, form; form = forms[i]; i++){
form.querySelector('[type="submit"]').setAttribute('disabled','disabled');
//Since we are firing onload, let's get any submit buttons to be disabled.
[].slice.call(form.querySelectorAll('textarea, input[type="email"]'))
.forEach(function(field){
field.addEventListener('input', function(e){
if([].slice.call(this.form.querySelectorAll('textarea, input[type="email"]:not(.optional)')).every(function(input){ return input.value.length > 0; })){
//if every field that is not optional has a length > 0, continue
this.form.querySelector('[type="submit"]').removeAttribute('disabled');
//Remove the disabled attribute from the submit button
[].slice.call(this.form.querySelectorAll('.optional')).forEach(function(optional){
optional.style.display='';
});
//Show all optional fields
}
});
});
}
});
<form>
<textarea></textarea>
<input type="email">
<button type="submit">Submit</button>
</form>
<form>
<textarea></textarea>
<input class="optional" type="email">
<button type="submit">Submit</button>
</form>
<form>
<textarea></textarea>
<input type="email">
<button type="submit">Submit</button>
</form>
-
\$\begingroup\$ "will only check the parent form that the event is triggered" Ok, agree with you. However, the way you've implemented seems that the events will only trigger with keystrokes, which is a little bit different than
oninput
(ex: the user might copy and paste with the mouse). Does it make sense or did I miss something? \$\endgroup\$Mark Messa– Mark Messa2017年12月07日 08:18:22 +00:00Commented Dec 7, 2017 at 8:18 -
\$\begingroup\$ You can just change the key press to input. \$\endgroup\$Jhecht– Jhecht2017年12月07日 16:23:20 +00:00Commented Dec 7, 2017 at 16:23