3
\$\begingroup\$

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]);}
}
asked Dec 7, 2017 at 7:24
\$\endgroup\$
0

2 Answers 2

2
\$\begingroup\$

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.
  • 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>

answered Dec 7, 2017 at 8:01
\$\endgroup\$
2
  • \$\begingroup\$ You can try input:visible. not sure though \$\endgroup\$ Commented Dec 7, 2017 at 9:04
  • \$\begingroup\$ input:(selector1):not(selector2)... \$\endgroup\$ Commented Dec 7, 2017 at 9:33
1
\$\begingroup\$

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>

answered Dec 7, 2017 at 8:03
\$\endgroup\$
2
  • \$\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\$ Commented Dec 7, 2017 at 8:18
  • \$\begingroup\$ You can just change the key press to input. \$\endgroup\$ Commented Dec 7, 2017 at 16:23

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.