Search code examples
javascripthtmlajaxflaskevent-listener

How to prevent Event Listener triggering for multiple drop down menus?


I am trying to fetch data using a back end Flask server by triggering an AJAX request when I interact with the multi check box drop down menus. Essentially, I have two drop down menus called "Select Date" and "Select Channel". The Select Date dopdown corresponds to a particular .parquet file and "Select Channel" corresponds to the column headers for those .parquet files.

However, when I interact with one of the dropdown menus, the event for the other dropdown menu also gets triggered(wuthout even interacting with it). How do I avoid that?

I have the following JS script for creating MultiCheckbox dropdown menus which I have saved as select-checkbox.js:

        $(document).ready(function () {
            $(document).on("click", ".MultiCheckBox", function () {
                var detail = $(this).next();
                detail.show();
            });

            $(document).on("click", ".MultiCheckBoxDetailHeader input", function (e) {
                e.stopPropagation();
                var hc = $(this).prop("checked");
                $(this).closest(".MultiCheckBoxDetail").find(".MultiCheckBoxDetailBody input").prop("checked", hc);
                $(this).closest(".MultiCheckBoxDetail").next().UpdateSelect();
            });

            $(document).on("click", ".MultiCheckBoxDetailHeader", function (e) {
                var inp = $(this).find("input");
                var chk = inp.prop("checked");
                inp.prop("checked", !chk);
                $(this).closest(".MultiCheckBoxDetail").find(".MultiCheckBoxDetailBody input").prop("checked", !chk);
                $(this).closest(".MultiCheckBoxDetail").next().UpdateSelect();
            });

            $(document).on("click", ".MultiCheckBoxDetail .cont input", function (e) {
                e.stopPropagation();
                $(this).closest(".MultiCheckBoxDetail").next().UpdateSelect();

                var val = ($(".MultiCheckBoxDetailBody input:checked").length == $(".MultiCheckBoxDetailBody input").length)
                $(".MultiCheckBoxDetailHeader input").prop("checked", val);
            });

            $(document).on("click", ".MultiCheckBoxDetail .cont", function (e) {
                var inp = $(this).find("input");
                var chk = inp.prop("checked");
                inp.prop("checked", !chk);

                var multiCheckBoxDetail = $(this).closest(".MultiCheckBoxDetail");
                var multiCheckBoxDetailBody = $(this).closest(".MultiCheckBoxDetailBody");
                multiCheckBoxDetail.next().UpdateSelect();

                var val = ($(".MultiCheckBoxDetailBody input:checked").length == $(".MultiCheckBoxDetailBody input").length)
                $(".MultiCheckBoxDetailHeader input").prop("checked", val);
            });

            $(document).mouseup(function (e) {
                var container = $(".MultiCheckBoxDetail");
                if (!container.is(e.target) && container.has(e.target).length === 0) {
                    container.hide();
                }
            });
        });

        var defaultMultiCheckBoxOption = { width: '220px', defaultText: 'Select Below', height: '200px' };

        jQuery.fn.extend({
            CreateMultiCheckBox: function (options) {
                    

                var localOption = {};
                localOption.width = (options != null && options.width != null && options.width != undefined) ? options.width : defaultMultiCheckBoxOption.width;
                localOption.defaultText = (options != null && options.defaultText != null && options.defaultText != undefined) ? options.defaultText : defaultMultiCheckBoxOption.defaultText;
                localOption.height = (options != null && options.height != null && options.height != undefined) ? options.height : defaultMultiCheckBoxOption.height;
                
                this.hide();
                this.attr("multiple", "multiple");
                var divSel = $("<div class='MultiCheckBox'>" + localOption.defaultText + "<span class='k-icon k-i-arrow-60-down'><svg aria-hidden='true' focusable='false' data-prefix='fas' data-icon='sort-down' role='img' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 512' class='svg-inline--fa fa-sort-down fa-w-10 fa-2x'><path fill='currentColor' d='M41 288h238c21.4 0 32.1 25.9 17 41L177 448c-9.4 9.4-24.6 9.4-33.9 0L24 329c-15.1-15.1-4.4-41 17-41z' class=''></path></svg></span></div>").insertBefore(this);
                divSel.css({ "width": localOption.width });

                var detail = $("<div class='MultiCheckBoxDetail'><div class='MultiCheckBoxDetailHeader'><input type='checkbox' class='mulinput' value='-1982' /><div>Select All</div></div><div class='MultiCheckBoxDetailBody'></div></div>").insertAfter(divSel);
                detail.css({ "width": parseInt(options.width) + 10, "max-height": localOption.height });
                var multiCheckBoxDetailBody = detail.find(".MultiCheckBoxDetailBody");


                this.find("option").each(function () {
                    var val = $(this).attr("value");

                    if (val == undefined)
                        val = '';

                    multiCheckBoxDetailBody.append("<div class='cont'><div><input type='checkbox' class='mulinput' value='" + val + "' /></div><div>" + $(this).text() + "</div></div>");
                });

                multiCheckBoxDetailBody.css("max-height", (parseInt($(".MultiCheckBoxDetail").css("max-height")) - 28) + "px");

            },
            UpdateSelect: function () {
                var arr = [];

                this.prev().find(".mulinput:checked").each(function () {
                    arr.push($(this).val());
                });

                this.val(arr);
            },
        });`

On the HTML side, I created two dropdown menus called "Select Channel" and "Select Date" in the following way:

<head>
    <script>
        $(document).ready(function () {
            $("#test").CreateMultiCheckBox({ width: '230px', defaultText : 'Select Below', height:'250px' });
        });
    </script>
        <script>
        $(document).ready(function () {
            $("#test_2").CreateMultiCheckBox({ width: '230px', defaultText : 'Select Below', height:'250px' });
        });
    </script>
</head>`
  <body>
  
    <div class="container">
      <div class="side-container">
        <!-- First Dropdown -->
      <div class="mb-3" data-dropdown-type="channel">
    <label for="exampleFormControlSelect1" class="form-label">Select Channel</label>
    <select id="test">
      {% for column in data %}
      <option value="{{ column }}">{{ column }}</option>
    {% endfor %}

    </select>

  </div>
  <!-- Second Dropdown -->

  <div class="mb-3" data-dropdown-type="date">
    <label for="exampleFormControlSelect1" class="form-label">Select Date</label>
    <select id="test_2">
      {% for parquet_file in parquet_files %}
      <option value="{{ parquet_file }}">{{ parquet_file }}</option>
    {% endfor %}
</select>
  </div>`

The options for the drop down menus have been generated based on a back end Flask server. Now I am trying to trigger an Ajax request using event listeners to fetch the corresponding data for the selected option on the drop down menuie. I want to get the Channel data for the selected Data from the "Select Date" dropdown menu. However, when I interact with one of the drop down menus, I also get a console.log printed for the other dropdown even without interacting with it. I'm assuming this is because the change event listener is attached to the entire document (document.addEventListener("change", ...)). This means that whenever any change event occurs anywhere in the document (not just on the dropdowns), the event listener is triggered. How do I fix this? Following is the JS script for the fetchData function and making the AJAX request:

 function fetchData(selectedChannels, selectedFiles) {
    console.log("Fetching data...");
    console.log(selectedChannels)
    console.log(selectedFiles)
    // Make an AJAX request to the backend with selectedChannel and selectedFile
    $.ajax({
      url: "/fetch_data",
      type: "POST",
      data: JSON.stringify({ channels: selectedChannels, files: selectedFiles }),
      contentType: "application/json",
      success: function (response) {
        console.log("Data received successfully:", response);
        // Render the data received from the backend on the webpage
        displayData(response);
      },
      error: function(xhr, status, error) {
        console.error("Error fetching data:", error);
      },
    });
  }


  // Function to display the data on the webpage
  function displayData(data) {
    // You can update the DOM elements with the data received
    // For example, create tables or charts to visualize the data
    console.log(data);
  }

And here is the event listener:

  document.addEventListener("change", function (event) {
      const target = event.target;
      console.log("Event triggered.")
      console.log(target)

      var array = []
      $("#test option").each(function(){
                        if($(this).is(":selected"))
                        {array.push($(this).val());}  
                    }); 
      console.log(array)
      var array1 = []      
      $("#test_2 option").each(function(){
                        if($(this).is(":selected"))
                        {array1.push($(this).val());}    
                    }); 
      console.log(array1)
      if (array.length >=1 && array1.length>=1) {
        const selectedChannels = Array.from(array)//.map(checkbox => checkbox.value);
        console.log(selectedChannels)
        const selectedFiles = Array.from(array1)//.map(checkbox => checkbox.value);
        console.log(selectedFiles)
        fetchData(selectedChannels,selectedFiles);
        
      }
      else{
        console.log("No match found")
      }
    });

  </script>

Solution

  • Register the event listener directly to the select elements.

    <script>
        $(document).ready(function () {
            $("#test").CreateMultiCheckBox({ 
                defaultText: 'Select Below', 
                height:'250px', 
                width: '230px' 
            });
            $("#test_2").CreateMultiCheckBox({ 
                defaultText: 'Select Below', 
                height:'250px', 
                width: '230px' 
            });
    
            $('#test, #test_2').change(function(evt) {
                // Your listener code here!
            });
        });
    </script>
    

    It may be that the events are not fired because you are updating the select element via JavaScript within UpdateSelect. In this case fire the event manually when the select-element is updated.

    const event = new Event('change');
    selectElem.dispatchEvent(event);