menu

Questions & Answers

jQuery loop through form and retrieve field labels and values of visible inputs only

I have the following markup where I have a holder div <div data-name="holder"></div> for dynamic content.

<div id="main" class="agent">
  <div class="page">
  <div data-name="holder">

    <div class="child">
        <div class="area" >
           <div class="box">
              <div  class="section" >
                 <div data-type="text" class="widget_type_text hidden" >
                     <div>
                         <label for="child0name">Full name</label>
                     </div>
                     <div>
                        <div class="validationMessage">
                           Enter Name
                        </div>
                         <input id="child0name" type="text" name="child[0][name]" required="" title="Enter full name">
                     </div>
                 </div>
                 <div data-type="radio" class="widget_type_radio" >
                     <div>
                        <fieldset>
                            <legend>Gender</legend>
                            <span data-value="male"><input id="child0genderMale" type="radio" name="child[0][gender]" value="male"><label for="child0genderMale">Male</label></span>
                            <span data-value="female"><input id="child0genderFemale" type="radio" name="child[0][gender]" value="female"><label for="child0genderFemale">Female</label></span>
                        </fieldset>
                     </div>
                 </div>
             </div>
          </div>
        </div>
        <div class="area hidden">
          <div class="box">
             <div class="section">
                <div data-type="date" class="widget_type_date">
                    <div>
                       <label for="child0dob">Date of Birth</label>
                    </div>
                    <div>
                       <div class="validationMessage">
                           Enter Date of Birth
                       </div>
                       <input id="child0dob" type="date" name="child[0][dob]" required="" title="Enter date of Birth">
                    </div>
                </div>
             </div>
          </div>
        </div>
        <div class="area ">
          <div class="box">
             <div class="section" data-access="agent">
                <div data-type="text" class="widget_type_text">
                    <div>
                       <label for="child0school">School</label>
                    </div>
                    <div>
                       <div class="validationMessage">
                           Enter School
                       </div>
                       <input id="child0school" type="text" name="child[0][school]" required="" title="Enter school">
                    </div>
                </div>
             </div>
          </div>
        </div>
     </div>

  </div>
 </div>
</div>
  • The holder can have multiple child divs which has a class called child conveniently.
  • Each child div can contain multiple divs with a class called area.
  • Each div with a class called area can contain a single div with a class called section
  • Each div with a class called section can contain multiple form input widgets that sit inside a div with a data-type attribute.

Visibility of a div with class area, section or data-type attribute can be toggled by including a hidden class.

Visibility of each of these divs can also be restricted by including a data-access attribute with a value of either agent or guest - this works by adding either agent or guest class to the #main div.

So if a guest user is accessing the site, div with #main will have the guest class injected and if its an agent it will have a agent class and then the following CSS is used to toggle visibility of each div.

#main.guest [data-access="agent"] {
    display: none;
}

#main.agent [data-access="guest"] {
    display: none;
}

I need to retrieve the field labels and values for all form inputs whose visibility is not hidden, either by the hidden class or the data-access attributes and then re-display them on another page eg. summary page.

So in the above example if the #main div has class of agent then only the gender and school fields have been displayed to user so function would retrieve the field label and value of those fields only because the full name widget is hidden and the date of birth area is hidden

If the #main div had class of guest then only the gender field would have been displayed because the full name widget is hidden, the date of birth area is hidden and school section div has data-access of agent only.

So in short I need to check three divs which have either class area, section or data-type attr to see if they are not hidden. I also have to check the same divs to see if they contain a data-access attribute and ensure visibility has not been hidden by that.

So really if the area div has either a hidden class or data-access attribute value that does not match the class in <div id="main" class="agent"> there is no need to traverse any deeper as the input field will have been hidden

Similarly would be the case with the div with section class and finally the div with data-type attr.

How do I loop through such markup to pull out field labels and values of only those form element that have been displayed?

This is what I have so far:

 if ($("[data-name='holder']").children().length > 0 ) {
 
      $('.child').each(function(index, element) {

          //pseudo code
          for each div with class area 
             if div does not have class hidden or if div has data-access attribute and $( "#main" ) has class with same value then
                   if div with class section does not have class hidden or if div has data-access attribute and $( "#main" ) has class with same value then
                         for each div with data-type attribute 
                              if div does not have class hidden or if div has data-access attribute and $( "#main" ) has class with same value then
                                   save the field label and field value in array
                              end if

                         end for 
                   end if

             end if

          endfor 

      });
 }

Update

The holder div can appear on multiple pages inside <div class="page"> div and as user steps through the form, the previous pages visibility is hidden. So simply using jQuery's :visible pseudo class won't work as items on previous pages will be hidden but still need to be presented on summary page as they will have been presented to user.

Answers(2) :

jQuery's :visible pseudo class will filter only the [data-type] elements visible to the user.

So using it on the selector should suffice per your description:

<script>
$(function () {
    function fetchFormData() {
        var result = [];

        $('[data-name=holder] [data-type]:visible').each(function (idx, elem) {
            var $elem = $(elem);
            var type = $elem.data('type');
            var label, value;

            // specific case: radio input
            if (type === 'radio') {
                label = $elem.find('legend').text();
                value = $elem.find('input[type=radio]:checked').val();

                result.push({label: label, value: value});

                // done with this item, skip to next one
                return true; // continue;
            }

            // generic case, works with most inputs
            label = $elem.find('label').text();
            value = $elem.find('input').val();

            result.push({label: label, value: value});
        });

        return result;
    }


    // add this to an event handler
    console.log(fetchFormData());
});
</script>
Comments:
2023-01-22 00:20:05
this wont work. See my updated post as the holder divs are contained inside a a pages div whos visibility is hidden as user pages through the form.

If the classes (eg: agent, guest) are fixed and you know what are all the possible combination you can do the selecting like this

var $main = $('#main');
    
// filter all the class that are not in the main
var aClasses = ['agent', 'guest'].filter(function(cl) {
    return !$main.hasClass(cl)
});

// build the selector
var selector = ':not(.hidden)';
aClasses.forEach(function(cl) {
    selector += ':not([data-access="' + cl + '"])'
})

$('div.area' + selector).each(function(i, el) {
    $('div.section' + selector, el).each(function(_i, _el) {
        $('div[data-type]' + selector, _el).each(function(__i, __el) {
            // you are inside the visible 'div[data-type]' here; do your stuff
        });
    });
});

or, do it in one big swoop like:

$(
    'div.area' + selector 
    + ' div.section' + selector 
    + ' div[data-type]' + selector
).each( function(i, el) {
    // your stuff
}

Or if you really wan't to do the selecting based on the main div having a class (eg: agent, guest) and checking the exact same, you could try

var $main = $('#main');
    
// get the main div's class
var sClass = (['agent', 'guest'].filter(function(cl) {
    return $main.hasClass(cl)
})[0];

// make the two selector combination
var s1 = ':not(.hidden):not([data-access])';
    s2 = '[data-access="' + sClass + '"]:not(.hidden)';
    
$('div.area' + s1 + ',div.area' + s2).each(function(i, el) {
    $('div.section' + s1 + ',div.section' + s2, el).each(function(_i, _el) {
        $('div[data-type]' + s1 + ',div[data-type]' + s2, el).each(function(__i, __el) {
            // your stuff
        });
    });
});

but here, to write everything in one big swoop, you would have to use 8 different combination

eg:

// area-s1 sect-s1 div-s1, 
// area-s1 sect-s1 div-s2, 
// area-s1 sect-s2 div-s1,
// area-s1 sect-s2 div-s2,
// area-s2 sect-s1 div-s1,
// area-s2 sect-s1 div-s2, 
// area-s2 sect-s2 div-s1,
// area-s2 sect-s2 div-s2,

// ie:

$(
    'div.area' + s1 + ' div.section' + s1 + ' div[data-type]' + s1
    + ',div.area' + s1 + ' div.section' + s1 + ' div[data-type]' + s2
    + ',div.area' + s1 + ' div.section' + s2 + ' div[data-type]' + s1
    + ',div.area' + s1 + ' div.section' + s2 + ' div[data-type]' + s2
    + ',div.area' + s2 + ' div.section' + s1 + ' div[data-type]' + s1
    + ',div.area' + s2 + ' div.section' + s1 + ' div[data-type]' + s2
    + ',div.area' + s2 + ' div.section' + s2 + ' div[data-type]' + s1
    + ',div.area' + s2 + ' div.section' + s2 + ' div[data-type]' + s2
).each(function(i, el) {
    // your stuff
})

so it would be best to use the nested loop itself.

Example

// assume the classes are (agent, guest) and main div is having class 'agent' then

/* First approch */
$('div.area:not(.hidden):not([data-access="guest"] div.section:not(.hidden):not([data-access="guest"] div[data-type]:not(.hidden):not([data-access="guest"]').each(function(index, elem) {
    //your stuff
})

// using nested loops
$('div.area:not(.hidden):not([data-access="guest"]').each(function(i, el) {
    $('div.section:not(.hidden):not([data-access="guest"'], el).each(function(_i, _el) {
        $('div[data-type]:not(.hidden):not([data-access="guest"'], _el).each(function(__i, __el) {
            // you are inside the visible 'div[data-type]' here; do your stuff
        });
    });
});


/* Second approch */
$(
    'div.area:not(.hidden):not([data-access]) div.section:not(.hidden):not([data-access]) div[data-type]:not(.hidden):not([data-access]), '
    + 'div.area:not(.hidden):not([data-access]) div.section:not(.hidden):not([data-access]) div[data-type][data-access="agent"]:not(.hidden), '
    + ...
).each(function(i, el) {
    //your stuff
})

// using nested loops
$('div.area:not(.hidden):not([data-access]), div.area[data-access="agent"]:not(.hidden)').each(function(i, el) {
    $('div.section:not(.hidden):not([data-access]), div.section[data-access="agent"]:not(.hidden)', el).each(function(_i, _el) {
        $('div[data-type]:not(.hidden):not([data-access]), div[data-type][data-access="agent"]:not(.hidden)', _el).each(function(__i, __el) {
            // your stuff
        });
    });
});
Comments:
2023-01-22 00:20:06
do you have example without using ES6 as my jquery is very basic.?
2023-01-22 00:20:06
@adam78 also added examples.
2023-01-22 00:20:06
does the following mean its and AND condition $('div.area:not(.hidden):not([data-access="guest"] i.e. area is not hidden AND data access is not guest? How do you make it an OR condition i.e. area is not hidden OR data-access is not guest?
2023-01-22 00:20:06
By using comma. See the second approch. eg: $('div.area:not(.hidden), div.area:not([data-access="guest"])
2023-01-22 00:20:06
the second approach works but I've hit another issue, what if I have other <div class="area"><div class="section"><div data-type="text"></div></div></div> nested inside a parent section div. I've noticed in this case the second approach duplicates the results i.e. its looping the same div multiple time?
2023-01-22 00:20:06
You can limit the scope by passing the element as second parameter to the JQuery function. eg: Start the first query using $('div-area-s1, div-area-s2', holder-div)