Event handler unbinded after first click - javascript

In Backbone marionette composite view, I am using following code .
events: {
'click th': 'doSort',
'click #selectAllGroups': 'allMembersSelected'
},
allMembersSelected: function(event) {
var selectAll;
if ($("#selectAllGroups").prop('checked') == true) {
selectAll = true;
} else {
selectAll = false;
}
var allCheckboxes = document.getElementsByName("selectGroupCheckBox");
for (var i = 0, n = allCheckboxes.length; i < n; i++) {
allCheckboxes[i].checked = selectAll;
}
},
where #selectAllGroups is actually checkbox to select and unselect all checkboxes in the list.
the function allMembersSelected is called only first time when the checkbox is clicked , it wouldn't be called on any subsequent clicks.
One Interesting point is that If I remove the below section from code, the click handler would be called on subsequent clicks and the issue wouldn't come.
if ($("#selectAllGroups").prop('checked') == true) {
selectAll = true;
} else {
selectAll = false;
}

You can update this function as:
allMembersSelected: function(event) {
event.stopPropagation(); // add this one too, as seems event is bubbling up.
$('input[type="checkbox"][name="selectGroupCheckBox"]')
.prop('checked', $("#selectAllGroups").prop('checked'));
},
It will mark check all the checkboxes named "selectGroupCheckBox" only when #selectAllGroups checkbox is checked.
This code block:
events: {
'click th': 'doSort', // 2. when event comes here all the elements on the row gets replaced by new rows.
'click #selectAllGroups': 'allMembersSelected' // 1. click on it bubbles up to the parent th
},
I find it that when you click on th it sorts all the rows and replaces all the elements with the new ones. So all the child elements inside every th if they have any event bound on it that will bubble up to the dom and that would cause in sort.
Same thing is happening with your #selectAllGroups checkbox as it is in the th or i would say that should be in the th so any event bound on it bubbles up to the th which causes in sorting and it feels that event is not working on checkbox but it does.

Related

Why two event listeners are triggered instead of one?

I have element which has two event listeners that are triggered depending of his class name. During click event, his className is changing, and this another class has its own different event listener. Two events should be triggered alternately by every click.
During first click listener calls function editClub, but all the next clicks calls two functions. I don't know why that removed-class-event is triggered. Maybe its because after each event function callListeners is executed, and there are multiple listeners on one object? But should be triggered just one. Later I wrote removeListeners function which remove all existing listeners and put her to call just before callListeners function. But then just editClub function is executed by every click. What's wrong with my code?
function callListeners()
{
if ( document.getElementsByClassName('editBtn') )
{
let x = document.getElementsByClassName('editBtn');
for ( let i = 0; i < x.length; i++ )
{
x[i].addEventListener('click', editClub);
}
}
if ( document.getElementsByClassName('saveBtn') )
{
let x = document.getElementsByClassName('saveBtn');
for ( let i = 0; i < x.length; i++ )
{
x[i].addEventListener('click', saveClub);
}
}
}
function editClub(event)
{
event.preventDefault();
this.setAttribute('src','img/\icon_save.png');
this.setAttribute('class','saveBtn');
//removeListeners(); <-- here I placed removeListeners function
callListeners();
}
function saveClub(event)
{
event.preventDefault();
this.setAttribute('src', 'img/\icon_edit.png');
this.setAttribute('class', 'editBtn');
//removeListeners(); <-- here I placed removeListeners function
callListeners();
}
It looks like you are not removing the classes when the click event happens so after the first click the element has both classes.
Just changing an element's class does not change the event listeners that were setup on it previously. They will still be there unless you explicitly remove them, or the elements themselves are destroyed. And you get multiple calls because you keep adding new listeners without removing the old ones.
You could on each click remove the old listener and add the new listener
function editClub(){
this.removeEventListener("click",editClub);
this.addEventListener("click",saveClub);
}
function saveClub(){
this.removeEventListener("click",saveClub);
this.addEventListener("click",editClub);
}
But that is a bit tedious. Instead you can setup a delegated event listener on a static parent like document. Doing so allows for a single event listener which will be called when either button is clicked. Then in that listener you can check the element's class, and execute the appropriate function for it:
function clubAction(event){
//get a reference to the element clicked
var element = event.target;
//call the appropriate function
//or just do the work here
if(element.classList.contains("editClub")){
editClub.call(element,event);
} else if(element.classList.contains("saveClub")) {
saveClub.call(element,event);
}
}
document.addEventListener("click",clubAction);
classList is a property that lets you get,set,remove, and other operations for the classes of the element.
Demo
function clubAction(event) {
var element = event.target;
if (element.classList.contains("editClub")) {
editClub.call(element,event);
} else if (element.classList.contains("saveClub")) {
saveClub.call(element,event);
}
}
document.addEventListener("click", clubAction);
function editClub(event) {
event.preventDefault();
this.classList.remove('editClub');
this.classList.add('saveClub');
this.innerText = "Save";
}
function saveClub(event) {
event.preventDefault();
this.classList.add('editClub');
this.classList.remove('saveClub');
this.innerText = "Edit";
}
.saveClub {
background:#0F0;
}
.editClub {
background:#FF0;
}
<button class="saveClub">Save</button>
<button class="editClub">Edit</button>

Radio button event not firing

I have 2 modal dialogs:
Contains items
Contains the versions of those items
When the user selects an item to view, the current versions of that item are retrieved. These versions are displayed in a table on modal dialog 2. Each row in this table holds 1 version. There are 2 columns:
Radio button to select the version
Version name
So, this table of the 2nd modal dialog is produced at run time. This is my code:
jQuery/JS
function showComponentVersionModal(assocComs ,p, comVersions, comVersionArray) {
$('#component-versions-modal .modal-body tbody').find('tr:gt(0)').remove();
var winW = window.innerWidth;
for (var j in comVersions) {
if (comVersions[j].title === p.text()) {
var newRow = '<tr>' +
'<td><input name="versionCheck" type="radio"/></td> ' +
'<td></td>' +
'</tr>';
if ($('#component-versions-modal .modal-body tbody tr:last td:nth-child(2)').text() !== comVersions[j].version) {
$('#component-versions-modal .modal-body tbody tr:last').after(newRow);
$('#component-versions-modal .modal-body tbody tr:last td:nth-child(2)').text(comVersions[j].version);
}
}
}
}
}
This function is executed when the user selects an item and, therefore, produces the 2nd modal dialog with it's versions.
$('input[name="versionCheck"]').on('change', function() {
if ($(this).prop('checked').length <= 0) {
...
} else {
...
}
});
When I click/change any of these radio buttons, it does not go into this function. Any help appreciated.
delegate the event to static parent:
$('#component-versions-modal').on('change', 'input[name="versionCheck"]', function() {
When the elements are dynamically created/added in the DOM then direct event binding doesn't register the events bound on it.
So, in this case you need to delegate the event to the closest static parent #component-version-modal which is in your case. Or $(document) which is always available to delegate the events.
try doing like this
$(document.body).on('change','input[name="versionCheck"]', function() {
//your code
});

Deselect a radio option

I'm using the bootstrap radio buttons and would like to allow deselection of a radio group. This can be done using an extra button (Fiddle). Instead of an extra button, however, I would like to deselect a selected radio option if the option is clicked when it's active.
I have tried this
$(".btn-group label").on("click", function(e) {
var clickedLabel = $(this);
if ($(clickedLabel).hasClass("active"))
{
// an active option was clicked => deselect it
$(clickedLabel).children("input:radio").prop("checked", false)
$(clickedLabel).removeClass("active");
}
}
)
but there seems to be a race condition: the event of clicking the label that I use seems to be used by bootstrap.js to set the clicked label option to "active". If I introduce a timeout, the class "active" is removed successfully:
$(".btn-group label").on("click", function(e) {
var clickedLabel = $(this);
if ($(clickedLabel).hasClass("active"))
{
setTimeout(function() {
// an active option was clicked => deselect it
$(clickedLabel).children("input:radio").prop("checked", false)
$(clickedLabel).removeClass("active");
}, 500)
}
}
)
How can I toggle a selected option successfully without using a timeout?? Thank you for help.
Instead of using two method's preventDefault & stopPropagation, use return false, will work same.
The difference is that return false; takes things a bit further in
that it also prevents that event from propagating (or "bubbling up")
the DOM. The you-may-not-know-this bit is that whenever an event
happens on an element, that event is triggered on every single parent
element as well.
$(".btn-group label").on("click", function(e) {
var clickedLabel = $(this);
if ($(clickedLabel).hasClass("active"))
{
// an active option was clicked => deselect it
$(clickedLabel).children("input:radio").prop("checked", false)
$(clickedLabel).removeClass("active");
return false;
}
});
After messing with your code in jsfiddle for a while I figured out that a combination of preventDefault() and stopPropagation() does the trick.
Here's a fiddle
and the code:
$(".btn-group label").on("click", function(e) {
var clickedLabel = $(this);
if ($(clickedLabel).hasClass("active"))
{
// an active option was clicked => deselect it
$(clickedLabel).children("input:radio").prop("checked", false)
$(clickedLabel).removeClass("active");
e.preventDefault();
e.stopPropagation();
}
}
);

Avoid clicking twice to begin editing boolean (checkbox) cell in Backgrid

We are using Backgrid and have discovered that to begin editing a "boolean" (checkbox) cell in Backgrid, you must click twice: the first click is ignored and does not toggle the state of the checkbox. Ideally we would get to the root of what is causing this behavior (e.g. is preventDefault being called) and solve it there, but I at first I tried a different approach with the following extension of BooleanCell's enterEditMode method which seemed like a logical place since it was upon entering edit mode that the checkbox click was being ignored.
Problem is my attempt also toggles the state of the previously edited checkbox. Here is the code.
var BooleanCell = Backgrid.BooleanCell.extend({
/*
* see https://github.com/wyuenho/backgrid/issues/557
*/
enterEditMode: function () {
Backgrid.BooleanCell.prototype.enterEditMode.apply(this, arguments);
var checkbox = this.$('input');
checkbox.prop('checked', !checkbox.prop('checked'));
}
});
The following seems to work:
var BooleanCell = Backgrid.BooleanCell.extend({
editor: Backgrid.BooleanCellEditor.extend({
render: function () {
var model = this.model;
var columnName = this.column.get("name");
var val = this.formatter.fromRaw(model.get(columnName), model);
/*
* Toggle checked property since a click is what triggered enterEditMode
*/
this.$el.prop("checked", !val);
model.set(columnName, !val);
return this;
}
})
});
This is because the render method gets called by Backgrid.BooleanCell's enterEditMode method on click, and said method destroys and re-creates the checkbox as follows but in so doing loses the checked state (after the click) of the original "non-edit-mode" checkbox
this.$el.empty();
this.$el.append(this.currentEditor.$el);
this.currentEditor.render();
A simpler approach:
var OneClickBooleanCell = Backgrid.BooleanCell.extend({
events: {
'change input': function(e) {
this.model.set(this.column.get('name'), e.target.checked);
},
},
});
This bypasses the CellEditor mechanism entirely and just reacts to the input event on the checkbox by updating the model.

Angular - Kendo treeview checkbox change event firing multiple times

I am trying to implement the checkbox change event using angular but seeing the event firing multiple times when a checkbox is clicked.
I have created a plnkr for this, please help. Ideally it should be fired only once.
$scope.treeOptions = {
checkboxes: {
checkChildren: true
},
dataBound: function(e) {
$scope.attachChangeEvent(e);
}
};
Event change code:
$scope.attachChangeEvent = function(e) {
var dataSource = e.sender.dataSource;
// issue : change event is getting called multiple times for every click on checkbox
dataSource.bind("change", function(e) {
var selectedNodes = 0;
var checkedNodes = [];
$scope.checkedNodeIds(dataSource.view(), checkedNodes);
for (var i = 0; i < checkedNodes.length; i++) {
var nd = checkedNodes[i];
if (nd.checked) {
selectedNodes++;
}
}
// check the console - this is called multiple times for one checkbox click
console.log(selectedNodes);
});
};
Is there another event to attach to to have only one event fired after all checkboxes are updated? Or maybe there is a way to group all those 'change' events and fire just one instead?
The dataBound event is triggered multiple times because you're dealing with a hierarchical data source (each child DS triggers the event, and it bubbles up). As a result, you're binding the change handler multiple times.
You should instead bind the change event from your initTree method; snippet:
var knobj = new kendo.data.HierarchicalDataSource({
data: arrayObj
});
//setting heirarchial data to scope
$scope.treeObj = knobj;
$scope.attachChangeEvent();
(updated demo)
Alternatively, you can use the dataBound event and check e.node, which is apparently undefined for the last time dataBound is triggered (demo):
dataBound: function(e) {
if (!e.node) {
$scope.attachChangeEvent();
}
}

Categories

Resources