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]);}
}
\$\endgroup\$

migrated from stackoverflow.com Dec 18 '17 at 1:25

This question came from our site for professional and enthusiast programmers.

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>

\$\endgroup\$
  • \$\begingroup\$ You can try input:visible. not sure though \$\endgroup\$ – Rajesh Dec 7 '17 at 9:04
  • \$\begingroup\$ input:(selector1):not(selector2)... \$\endgroup\$ – Rajesh Dec 7 '17 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>

\$\endgroup\$
  • \$\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 Dec 7 '17 at 8:18
  • \$\begingroup\$ You can just change the key press to input. \$\endgroup\$ – Jhecht Dec 7 '17 at 16:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.