Manzer A
Manzer A

Reputation: 3816

input type checkbox with labels firing events twice

I am trying to dynmaically create round checkboxes with tickMark which get appended to id="demo" onclick of two buttons which call get(data) method.

The issue is when two buttons called simulataneously, checkbox is not getting tick mark and causing call of getdata(idD + '_chckBox') method twice or more as seen in console.log.

However, I am using e.preventDefault(); e.stopPropagation();.

What is the issue here, the getdata(idD + '_chckBox') is called twice or more and roundcheckbox is not getting checked?

I am trying to toggle checkbox and show tick mark. If any better way of doing this possible, is also welcomed.

what is the best and easiest way to bind onclick and onscroll method in dynamic htmls which are in for loop, so that a object can be passed as a parameter in called method onclick.

index.html

   

     var data0 = [{
            "title": "a"
          },
          {
            "title": "b"
          },
          {
            "title": "c"
          },
          {
            "title": "d"
          },
        ];
        var data1 = [{
            "title": "ads"
          },
          {
            "title": "bd"
          },
          {
            "title": "fc"
          },
          {
            "title": "dg"
          },
        ];
    
     var html = "<div id='parent'   ' + 'onscroll="loadMoreContent(event)" ></div>";
     $(html ).appendTo('body');
        $(document).on('click', '#btn11', () => {
          get(data0, 'parent');
        })
        $(document).on('click', '#btn00', () => {
          get(data1,'parent');
        })
    
function loadMoreContent(event){
 // gettting server data (data01 ) on ajax call
var data01 = [{
            "title": "aaa"
          },
          {
            "title": "sdw3b"
          },
          {
            "title": "c433"
          },
          {
            "title": "34d"
          },
        ];
get(data01 , idToAppend)
}
        function get(data, idToAppend) {
    
          var html = '';
          html += '<div class="col-12 parentdiv">';
          $.each(data, function(key, msgItem) {
            var idD = msgItem.title + key;
            html += '<div class="flLeftBlock"  style="width: 30px;margin-top: 36px;">';
            html += '<div class="roundCheckboxWidget"  id="' + idD + '_roundCheckboxWidget">';
            html += '<input   id="' + idD + '_chckBox" class="" type="checkbox" tid="" title="discard">';
            html += '<label id="' + idD + '_chckBox_label" for="' + msgItem.title + '" ></label> ';
            html += "&nbsp;" + msgItem.title;
            html += '</div>';
            html += '</div>';
            html += '';
          });
          html += '</div>';
          $('#'+ idToAppend).append(html);
    
          $.each(data, function(index, element) {
            var idD = element.title + index;
            const self = this;
            $(document).on('click', '#' + idD + '_chckBox_label', (e) => {
              if (e.target.tagName === "LABEL") {
                e.preventDefault();
                e.stopPropagation();
                console.log('#' + idD + '_chckBox_label');
                getdata(idD + '_chckBox');
              }
            });
          });
        }
    
        function getdata(id) {
          console.log(id);
          $("#" + id).prop("checked", !$("#" + id).prop("checked"));
          return true;
        }
.roundCheckboxWidget {
  position: relative;
}

.roundCheckboxWidget label {
  background-color: #ffffff;
  border: 1px solid rgb(196, 196, 209);
  border-radius: 50%;
  cursor: pointer;
  height: 22px;
  left: 0;
  position: absolute;
  top: 0;
  width: 22px;
}

.roundCheckboxWidget label:after {
  border: 2px solid #fff;
  border-top: none;
  border-right: none;
  content: "";
  height: 6px;
  left: 4px;
  opacity: 0;
  position: absolute;
  top: 6px;
  transform: rotate(-45deg);
  width: 12px;
}

.roundCheckboxWidget input[type="checkbox"] {
  visibility: hidden;
}

.roundCheckboxWidget input[type="checkbox"]:checked+label {
  /* background-color: #6168e7 !important;
      border-color: 1px solid #6168e7 !important; */
}

.roundCheckboxWidget input[type="checkbox"]:checked+label:after {
  opacity: 1;
}

.roundCheckboxWidget input[type="checkbox"]:checked+label {
  background-color: #ff5b6a;
  border-color: #ff5b6a !important;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<p>Alternate click on two buttons without refreshing causing getdata() method call twice. </p>

<button id="btn11" onclick="get()">Try It</button>
<button id="btn00" onclick="get()">Try It2</button>
<div id="demo"></div>

Upvotes: 8

Views: 3752

Answers (4)

Jayendra Sharan
Jayendra Sharan

Reputation: 1018

TL;DR

Unbind the event handlers before binding a new one. Use this code:

$(document).off('click', '#' + idD + '_chckBox_label').on('click', '#' + idD + '_chckBox_label', (e) => {

Long Answer

You cannot use e.preventDefault(); e.stopPropagation(); to stop different function calls. Here is the thing,

let's say you have an element in your dom and you added event handlers twice, in this case, those two event handlers will be executed no matter what.

What you need here is,

"remove the existing click handler before adding a new one".

This line:

$(document).on('click', '#' + idD + '_chckBox_label', (e) => { adds event handler to a label. But when you click on a button again, this runs again and adds another event handler.

So the number of times you click on your button, that's the same number of event handlers you are adding to your element.

But when you refresh, it's fixed. Reason: refreshing the page loads an entirely new page, and all previous handlers are gone. You need to do this programmatically, and here is how you can do it.

$(document).off('click', '#' + idD + '_chckBox_label').on('click', '#' + idD + '_chckBox_label', (e) => {

This code tells that, switch off any previous click handler and add a new one.

Bonus:

If you have named functions, you can remove the specific event handlers, for example:

$(document).off(<event_type>, <el_selector>, <event_handler>).on(<event_type>, <el_selector>, <event_handler>);

Hope this helps. Let me know if you have any counter question.

PS: There were a couple of typos in the code when I copy pasted, but I think running the code is not the agenda here :)

Upvotes: 2

Louys Patrice Bessette
Louys Patrice Bessette

Reputation: 33933

There is several mistakes here.

  1. You use some id that aren't unique. There is an attempt to make them unique, but you used the array key, which is static. So on every button click, duplicate ids are produced. You need a counter.
  2. You use an .each() loop to define a delegated event handler... Which is a misunderstanding of delegation.
  3. You use a function to toggle the checkbox state when the for= attribute on the label can do it without a single line of code.
  4. The loadMoreContent() function is unclear... I assumed you want an "infinite scroll"... So you have to check if the bottom of the page is reached to call the Ajax request... Else, you will fire tons of request on each scroll... up and down!

So here is how to do it:
   (See comment within code)

// checkbox title arrays
var data0 = [{ "title": "a" }, { "title": "b" }, { "title": "c" }, { "title": "d" }];
var data1 = [{ "title": "ads" }, { "title": "bd" }, { "title": "fc" }, { "title": "dg" }];

// Appends th "parent" div on load.
var html = "<div id='parent'></div>";
$(html).appendTo('body');

// Button handlers
$(document).on('click', '#btn11', () => {
  get(data0,'parent');
})
$(document).on('click', '#btn00', () => {
  get(data1,'parent');
})

// Simulated Ajax request... I assume.
function loadMoreContent(idToAppend){
  // gettting server data (data01 ) on ajax call
  var data01 = [{ "title": "aaa" }, { "title": "sdw3b" }, { "title": "c433" }, { "title": "34d" } ];
  get(data01 , idToAppend)
}

// Main function. It needs a counter to create UNIQUE ids.
var checkbox_counter = 0;
function get(data, idToAppend) {

  var html = '';
  html += '<div class="col-12 parentdiv">';
  $.each(data, function(key, msgItem) {
    html += '<div class="flLeftBlock" style="width: 30px;margin-top: 36px;">';
    html += '<div class="roundCheckboxWidget">';
    html += '<input id="checkbox_'+checkbox_counter+'" type="checkbox" tid="" title="discard">';
    html += '<label for="checkbox_'+checkbox_counter+'"></label> ';
    html += "&nbsp;" + msgItem.title;
    html += '</div>';
    html += '</div>';
    html += '';
    
    // Increment the checkbox counter
    checkbox_counter++;
  });
  html += '</div>';
  $('#'+ idToAppend).append(html);
}

// Just to console log the id of the checkbox...
$(document).on('click', 'label', function(){
  var checkbox_id = $(this).prev("[type='checkbox']").attr("id");
  console.log(checkbox_id);
});

// On scroll handler, check if the bottom of the page is reached to load some more...
$(document).on('scroll', function(){
  var scrolled = Math.ceil($(window).scrollTop());
  var viewport_height = $(window).outerHeight();
  var window_full_height = $(document).outerHeight();
  //console.log(scrolled +" "+ viewport_height +" "+ window_full_height);
  
  if(scrolled + viewport_height == window_full_height){
    console.log("reached the bottom... Loading some more!");
    
    // Call the Ajax request
    loadMoreContent("parent");
  }
});
.roundCheckboxWidget {
  position: relative;
}

.roundCheckboxWidget label {
  background-color: #ffffff;
  border: 1px solid rgb(196, 196, 209);
  border-radius: 50%;
  cursor: pointer;
  height: 22px;
  left: 0;
  position: absolute;
  top: 0;
  width: 22px;
}

.roundCheckboxWidget label:after {
  border: 2px solid #fff;
  border-top: none;
  border-right: none;
  content: "";
  height: 6px;
  left: 4px;
  opacity: 0;
  position: absolute;
  top: 6px;
  transform: rotate(-45deg);
  width: 12px;
}

.roundCheckboxWidget input[type="checkbox"] {
  visibility: hidden;
}

.roundCheckboxWidget input[type="checkbox"]:checked+label {
  /* background-color: #6168e7 !important;
  border-color: 1px solid #6168e7 !important; */
}

.roundCheckboxWidget input[type="checkbox"]:checked+label:after {
  opacity: 1;
}

.roundCheckboxWidget input[type="checkbox"]:checked+label {
  background-color: #ff5b6a;
  border-color: #ff5b6a !important;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<p>Alternate click on two buttons without refreshing causing getdata() method call twice. </p>

<button id="btn11" onclick="get()">Try It</button>
<button id="btn00" onclick="get()">Try It2</button>
<div id="demo"></div>

CodePen

Upvotes: 4

Maheer Ali
Maheer Ali

Reputation: 36574

There are two problems with code:

  • You are setting onclick="get()" and also adding click event using jQuery so it will trigger twice. remove onclick ="get()"
  • Second problem is that on every get() call it will add new click listeners to checkboxes. To prevent that use a boolean in your array only apply click event once

var data0 = [{
    "title": "a"
  },
  {
    "title": "b"
  },
  {
    "title": "c"
  },
  {
    "title": "d"
  },
  false
];
var data1 = [{
    "title": "ads"
  },
  {
    "title": "bd"
  },
  {
    "title": "fc"
  },
  {
    "title": "dg"
  },
  false
];
$(document).on('click', '#btn11', () => {
  get(data0);
})
$(document).on('click', '#btn00', () => {
  get(data1);
})

function get(data) {
  var html = '';
  html += '<div class="col-12 parentdiv"';
  $.each(data, function(key, msgItem) {
    var idD = msgItem.title + key;
    html += '<div class="flLeftBlock"  style="width: 30px;margin-top: 36px;">';
    html += '<div class="roundCheckboxWidget"  id="' + idD + '_roundCheckboxWidget">';
    html += '<input   id="' + idD + '_chckBox" class="" type="checkbox" tid="" title="discard">';
    html += '<label id="' + idD + '_chckBox_label" for="' + msgItem.title + '" ></label> ';
    html += "&nbsp;" + msgItem.title;
    html += '</div>';
    html += '</div>';
    html += '';
  });
  html += '</div>';
  $('#demo').html(html);
  if(data[data.length - 1]) return false;
  
  $.each(data, function(index, element) {
    var idD = element.title + index;
    const self = this;
    if(index === data.length - 1) return false;
    $(document).on('click', '#' + idD + '_chckBox_label', (e) => {
      if (e.target.tagName === "LABEL") {
        e.preventDefault();
        e.stopPropagation();
        //console.log('#' + idD + '_chckBox_label');
        getdata(idD + '_chckBox');
      }
    });
    
  });
  data[data.length -1] = true;
}

function getdata(id) {
  //console.log(id);
  $("#" + id).prop("checked", !$("#" + id).prop("checked"));
  return true;
}
.roundCheckboxWidget {
  position: relative;
}

.roundCheckboxWidget label {
  background-color: #ffffff;
  border: 1px solid rgb(196, 196, 209);
  border-radius: 50%;
  cursor: pointer;
  height: 22px;
  left: 0;
  position: absolute;
  top: 0;
  width: 22px;
}

.roundCheckboxWidget label:after {
  border: 2px solid #fff;
  border-top: none;
  border-right: none;
  content: "";
  height: 6px;
  left: 4px;
  opacity: 0;
  position: absolute;
  top: 6px;
  transform: rotate(-45deg);
  width: 12px;
}

.roundCheckboxWidget input[type="checkbox"] {
  visibility: hidden;
}

.roundCheckboxWidget input[type="checkbox"]:checked+label {
  /* background-color: #6168e7 !important;
      border-color: 1px solid #6168e7 !important; */
}

.roundCheckboxWidget input[type="checkbox"]:checked+label:after {
  opacity: 1;
}

.roundCheckboxWidget input[type="checkbox"]:checked+label {
  background-color: #ff5b6a;
  border-color: #ff5b6a !important;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<p>Alternate click on two buttons without refreshing causing getdata() method call twice. </p>

<button id="btn11">Try It</button>
<button id="btn00">Try It2</button>
<div id="demo"></div>

Upvotes: 2

BugsArePeopleToo
BugsArePeopleToo

Reputation: 2996

You have 2 click event listeners here:

$(document).on('click', '#btn11', () => {
  get(data0);
})
$(document).on('click', '#btn00', () => {
  get(data1);
})

Your get() function conatins two $.each() loops, the second of which is adding another event listener here:

$(document).on('click', '#' + idD + '_chckBox_label', (e) => {
  if (e.target.tagName === "LABEL") {
    e.preventDefault();
    e.stopPropagation();
    console.log('#' + idD + '_chckBox_label');
    getdata(idD + '_chckBox');
  }
});

From what I can tell, by the time you click the second button there are potentially 3 event listeners responding to that event.

Upvotes: 0

Related Questions