Looping through elements

Posted on

Problem

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

Solution

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

JS + CSS

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>

Leave a Reply

Your email address will not be published. Required fields are marked *