4

In this example, I have a series of 3 select drop-downs. The same class ("group-1") is applied to each to indicate that they are related (and to allow for multiple select groups on the same page). The "data-parent" attribute is applied to the select to establish a 1:1 parent/child relationship. The "data-parent" attribute is applied to the option to establish a many:many relationship.

My goal is to create a piece of dynamic jQuery code so that I don't have to explicitly identify behavior by id or class, but rather by option value and data-parent value.

My issues:

  1. The third drop-down doesn't update at all. I thought it might be that some of them have more than one data-parent value (separated by a space), but even if I change them to all have just one, it still doesn't update with changes to the second drop-down.
  2. I'm not sure how to implement the "many" data-parent values in the options for the third drop-down. Split the string, create an array, and check to see if the value is in the array?
  3. How do I reset the second and third drop-downs to the "default" value with a change to the parent drop-down? For example, if I choose "Cookies" from the first, "1 dozen" and "2 dozen" show up in the second. But if I select "1 dozen" and then change the first box to "Cakes", "1 dozen" remains selected even though "Sheet" and "Round" are the only available options in the drop-down. I'd like for it to reset to the default ("Desserts...").

Code is below, and here's my working jsfiddle: https://jsfiddle.net/rwh4z623/20/

$(document).ready(function(){
    $(".hide").children("option").hide();
    $("select").on('change', function(){
        var selClass = $(this).attr("class");
        var selName = $(this).attr("name");
        var selVal = $(this).children("option:selected").val();
        $("select."+selClass+"[data-parent='"+selName+"']").each(function(){
            $(this).children("option[data-parent='"+selVal+"']").show();
            $(this).children("option[data-parent!='"+selVal+"']").hide();
        });
    });
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<select class="group-1" name="categories">
    <option value="" selected="selected">Please select...</option>
    <option value="cookie">Cookies</option>
    <option value="cake">Cakes</option>
    <option value="icecream">Ice Cream</option>
</select>

<select class="group-1 hide" name="desserts" data-parent="categories">
    <option value="" selected="selected">Desserts...</option>
    <option value="1-dozen" data-parent="cookie">1 dozen</option>
    <option value="2-dozen" data-parent="cookie">2 dozen</option>
    <option value="sheet" data-parent="cake">Sheet</option>
    <option value="round" data-parent="cake">Round</option>
    <option value="pint" data-parent="icecream">Pint</option>
</select>

<select class="group-1 hide" name="flavors" data-parent="desserts">
    <option value="" selected="selected">Flavors...</option>
    <option value="choc-chip" data-parent="1-dozen 2-dozen">Chocolate Chip</option>
    <option value="oatmeal" data-parent="1-dozen 2-dozen">Oatmeal</option>
    <option value="yellow" data-parent="sheet round">Yellow</option>
    <option value="red-velvet" data-parent="sheet">Red Velvet</option>
</select>

Thanks in advance for any and all help! Also, suggestions on improvements are appreciated.

aynber
  • 22,380
  • 8
  • 50
  • 63
penteratap
  • 43
  • 3

2 Answers2

1

I don't know the context of this code so I have no idea, if this pattern of "nested" options and selects is the best solution. But here is my version of the javascript that seems to achieve what was asked in the question. I'll comment the relevant changes so my thought process is clear. I also added the css for a hide class, as that wasn't in the jsfiddle.

$(document).ready(function () {
    $("select").on('change', function () {
        var selClass = $(this).attr("class");
        var selName = $(this).attr("name");
        var selVal = $(this).children("option:selected").val();

        //Call the update function with the data of the changed Select
        updateRecursivly(selClass, selName, selVal);

        //Recursive function to update all selects, this allows for multiple "child" selects per select and an in theory infinite "tree" of selects
        function updateRecursivly(selClass, selName, selVal) {
            //Search "children" of the parent select
            var children = $("select." + selClass + "[data-parent='" + selName + "']");
            if (children.length) {
                children.each(function () {
                    //Hide all options in the "child" select
                    $(this).children("option[data-parent]").hide();
                    //if selVal is an empty string, the default option is selected and we should just hide the "child" select
                    if (selVal !== "") {
                        //Get all options that contain (*=) selVal in "data-parent"
                        var options = $(this).children("option[data-parent*='" + selVal + "']");
                        //If there are possible options show the select and the possible options. Else hide select
                        if (options.length) {
                            $(this).removeClass('hide');
                            options.show();
                        } else {
                            $(this).addClass('hide');
                        }
                    } else {
                        $(this).addClass('hide');
                    }
                    //If the select is updated, the options should be reset. Any selected is reset and the first is selected
                    //From here https://stackoverflow.com/a/16598989/14040328 this is apparently safer against reset events than other solutions
                    $(this).children("option:selected").prop("selected", false);
                    $(this).children("option:first").prop("selected", "selected");

                    //Get the name of the select
                    var childName = $(this).attr("name");

                    //Update the Child select
                    updateRecursivly(selClass, childName, "");
                });
            }
        }
    });
});
.hide {
  display: none;
}

I'm not sure if I overdid the comments or not, if anything is unclear feel free to comment.

  • This is perfect! I just did a quick test adding more "chained" drop-downs, as well as additional groups of select boxes, and it's working exactly as I had envisioned. Thanks! – penteratap Aug 05 '20 at 18:49
1

You can use attributeContainsWord (~) to matches mutliple value against a word. So , in below code i have use $(this).children("option[data-parent~='" + selVal + "']").show(); to show options which match with the selVal .Also , i have use prop('selectedIndex', 0); to reset select to default .

Demo Code :

$(document).ready(function() {
  $(".hide").children("option").hide();
  $("select").on('change', function() {
    //checking if dropdown is categories
    if ($(this).attr("name") == "categories") {
      //reset other to default
      $('select[name=desserts]').prop('selectedIndex', 0);
      $('select[name=flavors]').prop('selectedIndex', 0);
    }

    var selClass = $(this).attr("class");
    var selName = $(this).attr("name");
    var selVal = $(this).children("option:selected").val();
    //check if name != desserts
    if ($(this).attr("name") != "desserts") {
      //hide option of flavour
      $("select[name=flavors]").children("option").hide();
      //loop and show desire values
      $("select." + selClass + "[data-parent='" + selName + "']").each(function() {
        $(this).children("option[data-parent='" + selVal + "']").show();
        $(this).children("option[data-parent!='" + selVal + "']").hide();
      });

    } else {
      //hide options
      $("select[name=flavors]").children("option").hide();
      //loop through flavours
      $("select[name=flavors]").each(function() {
        //use " ~ " to see if the selval matches with values 
        $(this).children("option[data-parent~='" + selVal + "']").show();

      });

    }
  });
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<select class="group-1" name="categories">
  <option value="" selected="selected">Please select...</option>
  <option value="cookie">Cookies</option>
  <option value="cake">Cakes</option>
  <option value="icecream">Ice Cream</option>
</select>

<select class="group-1 hide" name="desserts" data-parent="categories">
  <option value="" selected="selected">Desserts...</option>
  <option value="1-dozen" data-parent="cookie">1 dozen</option>
  <option value="2-dozen" data-parent="cookie">2 dozen</option>
  <option value="sheet" data-parent="cake">Sheet</option>
  <option value="round" data-parent="cake">Round</option>
  <option value="pint" data-parent="icecream">Pint</option>
</select>

<select class="group-1 hide" name="flavors" data-parent="desserts">
  <option value="" selected="selected">Flavors...</option>
  <option value="choc-chip" data-parent="1-dozen 2-dozen">Chocolate Chip</option>
  <option value="oatmeal" data-parent="1-dozen 2-dozen">Oatmeal</option>
  <option value="yellow" data-parent="sheet round">Yellow</option>
  <option value="red-velvet" data-parent="sheet">Red Velvet</option>
</select>
Swati
  • 28,069
  • 4
  • 21
  • 41
  • The problem with this solution (for me) is that I'm trying to avoid using specific identifiers in the JavaScript because there will be 6 or 7 "groups" on the page. I was hoping to use the "group-#" class and data-parent attribute to create the relationships on the page, letting the JS be dynamic. – penteratap Aug 05 '20 at 18:49