I am dynamically creating a table where i am adding onclick function to each column.
for (var x = 0; x < r.length; x++) {
//Setting the columns
if (i === 1) {
var headerCell = document.createElement("TH");
headerCell.innerHTML = r[x];
headerCell.id = x;
headerCell.onclick = function () {
sortTable(this.id, name);
}
row.appendChild(headerCell);
}
}
In a specific situation I want to disable the onclick function. Here is the code and it works.
$('#errorTable TH').prop("onclick", null).off("click");
and in another situation i want to reattach the onclick function. And that doesn't work. I want to enable the original function....
Any ideas ?
The way you created your table and adding/removing events are not easily maintainable. I also have some suggestions:
Review your code and define code click handler separately.
If you use jQuery in your project use it every where, if not, do not use it anywhere.
In your code i is undefined.
Add Remove Event Listener with jQuery
First define your handler function:
var myClickHandler = function(){
// this is your click handler
alert('Yes!!!');
}
Select your element and assign to a variable. <div id="clickable">Click Me!</div> must be in the DOM at the time of below script executed.
var element = $('#clickable');
// assign event listener
element.on('click',myClickHandler);
// remove event listener:
element.off('click',myClickHandler);
note that you must have to inform jQuery which handler should be removed.
See a sample https://codepen.io/softberry/pen/BEpove
An alternative is to build a click handler that checks a "kill switch".
var tableClickable = true;
headerCell.onclick = function () {
if (tableClickable) {
sortTable(this.id, name);
}
}
//In a specific situation I want to disable the onclick function.
something.addEventListener('someEvent', function () {
tableClickable = false;
});
//and in another situation i want to reattach the onclick function.
something.addEventListener('someOtherEvent', function () {
tableClickable = true;
});
Related
I am having some trouble understanding what is happening in a piece of vanilla JS for the Isotope filter. The original code is here: https://codepen.io/desandro/pen/VWLJEb
var buttonGroups = document.querySelectorAll('.button-group');
for (var i = 0; i < buttonGroups.length; i++) {
var buttonGroup = buttonGroups[i];
var onButtonGroupClick = getOnButtonGroupClick(buttonGroup);
buttonGroup.addEventListener('click', onButtonGroupClick);
}
function getOnButtonGroupClick(buttonGroup) {
return function(event) {
// check for only button clicks
var isButton = event.target.classList.contains('button');
if (!isButton) {
return;
}
var checkedButton = buttonGroup.querySelector('.is-checked');
checkedButton.classList.remove('is-checked')
event.target.classList.add('is-checked');
}
}
What is happening between the getOnButtonGroupClick function and it being assigned to a variable in the for loop preceding it?
getButtonGroupClick returns a closure that saves the value of buttonGroup. When you click on a button in the button group, it uses that closure variable to search for the checked button in the group, uncheck it, and then check the button you clicked on.
This complexity isn't really needed. When an event listener is called, event.currentTarget is set to the element that the listener was attached to, so you could just use that.
var buttonGroups = document.querySelectorAll('.button-group');
for (var i = 0; i < buttonGroups.length; i++) {
var buttonGroup = buttonGroups[i];
buttonGroup.addEventListener('click', onButtonGroupClick);
}
function OnButtonGroupClick(event) {
// check for only button clicks
var isButton = event.target.classList.contains('button');
if (!isButton) {
return;
}
var checkedButton = event.currentTarget.querySelector('.is-checked');
checkedButton.classList.remove('is-checked')
event.target.classList.add('is-checked');
}
The for loop is used to iterate over all of the elements with the class of button-group and and a click event listener to them. getOnButtonGroupClick returns a function to be used as the function to be used as the event listener for the element i.e. the function that is run when the element is clicked on.
var buttonGroups = document.querySelectorAll('.button-group');
//get all elements within the document with a class of button-group
//buttonGroups is a NodeList
for (var i = 0; i < buttonGroups.length; i++) {
//loop through all of the elements with a class of button-group matched by the above query selector
var buttonGroup = buttonGroups[i];
//get the element in the NodeList with the index i
var onButtonGroupClick = getOnButtonGroupClick(buttonGroup);
//get the function to be run when the element is clicked on
buttonGroup.addEventListener('click', onButtonGroupClick);
//add a click event listener to the element
}
function getOnButtonGroupClick(buttonGroup) {
return function(event) {
// check for only button clicks
var isButton = event.target.classList.contains('button');
//check if the element has a class of button
if (!isButton) {
//if the element does not have a class of button, do nothing
return;
}
var checkedButton = buttonGroup.querySelector('.is-checked');
checkedButton.classList.remove('is-checked')
event.target.classList.add('is-checked');
}
}
If I understood your question correctly, It means that a click event is being added to every button in the buttonGroups there is. Although, if you ask me, it would be way better and cleaner to just use a forEach, like so:
const buttonGroups = document.querySelectorAll('.button-group');
buttonGroups.forEach(button => button.addEventListener("click", OnButtonGroupClick)
function OnButtonGroupClick(event) {
// check for only button clicks
let isButton = event.target.classList.contains('button');
if (!isButton) {
return;
}
let checkedButton = event.currentTarget.querySelector('.is-checked');
checkedButton.classList.remove('is-checked')
event.target.classList.add('is-checked');
}
So, you add a click event to ALL the buttons in the buttonGroups that will run the function onButtonGroupClick.
EDIT: And there's no really need to assign the function like that... at all. Just call it on the click event and that's it.
This question already has answers here:
Javascript removeEventListener not working
(13 answers)
Closed 6 years ago.
I have 1 div (a message) which appears on the page when the page is loaded. When a user clicks somewhere on a page outside this message I want this message to disappear and show (ONCE) a log in the console that the message has disappeared. The problem is I continue receiving my console messages every time I click everywhere on my page though the message is already gone. I.E. I cannot detach 'click' event from my page. The code is following:
var elems = document.querySelectorAll(':not(#my-widget)'); //all elements in my page except message
var promptwidget = document.getElementById('my-widget');
console.log('WIDGET==> ' + promptwidget);
if (typeof(promptwidget) != 'undefined' && promptwidget != 'null') {
for (var i = 0; i < elems.length; i++) //add click eventlistener to the rest document
{
elems[i].addEventListener("click", function(e) {
e.preventDefault(),
removeWidget(["my-widget"]), //parentNode.removeChild wrapper, works OK
console.log('widget removed'), //received everytime I click on a page but I need only ONCE
promptwidget = document.getElementById('my-widget'); //tried to reassign a null value to my promptwidget var and call removeEventListener but no work
});
}
} else //this code never called, but I want it after my-widget removal
{
for (var i = 0; i < elems.length; i++) {
elems[i].removeEventListener("click", function(e) {
e.preventDefault(),
console.log("clickevent removed")
});
}
}
Any help would be appreciated.
EDIT_1:
Thank you everyone, the problem was solved as follows:
var elems = document.querySelectorAll(':not(#my-widget)');
var promptwidget = document.getElementById('my-widget');
for(var i = 0; i<elems.length; i++)
{
elems[i].addEventListener("click", function(e)
{
e.preventDefault();
if(typeof(promptwidget) != 'undefined' && promptwidget != null)
{
removeWidget(["my-widget"]),
console.log('widget removed'), //now showed once
promptwidget = undefined
}
});
}
This code was very helpful
addEventListener allows you to specify more than one event handler for each event type, so to remove a specific event handler you need to specify not only the event type, but also which handler you want to remove:
addEventListener(eventType, eventHandler);
removeEventListener(eventType, eventHandler);
// arguments passed to removeEventListener must be exactly the same
// as in addEventListener so you cannot pass an anonymous function
It doesn't make sense to pass a newly declared anonymous function as the 2nd parameter to removeEventListener. You need to pass a reference to the actual function to be removed. You will have to define this function with a name outside of the scope, and then you can use the name as a reference to remove it.
function removeWidgetFn (e) {
e.preventDefault(),
removeWidget(["my-widget"]),
console.log('widget removed'),
promptwidget = document.getElementById('my-widget');
});
And then,
elems[i].addEventListener("click", removeWidgetFn);
And then,
elems[i].removeEventListener("click", removeWidgetFn);
An element can have many click handlers, so you need to specify which click handler you want to remove.
I have a list of controls contained in a parent div called overlay-controls.
There is many list controls that each have their own overlay-controls.
I am using a for loop to add the event listener to each button that contains the class delete.
Before the user can delete the item, they must confirm. I am trying to attach this to every delete button found in overlay-controls.
I got it to work using a for loop but I know there is a better way using bubbling and capturing. I am having trouble targeting only the delete class inside overlay-controls by bubbling up to parent div.
See the live demo here by clicking on each delete button: http://jsfiddle.net/8qqfeoa2/1/
Here is my code using the for loop:
(function() {
function getConfirmation(e){
var retVal = confirm("Are you sure you want to delete this request?");
if( retVal == true ){
return true;
}else{
e.preventDefault();
return false;
}
}
var del = document.querySelectorAll('.delete');
for(var i = 0, len = del.length; i < len; i++){
del[i].addEventListener('click', function(e) {
getConfirmation(e);
}, false);
}
}());
You dont event need the For / .each loop
Jquery takes care of it internally
$('.delete').on('click', function(e){
getConfirmation(e);
});
Provided you are using jQuery and in getConfirmation method you may also get that specific (clicked) element by using e.target which returns the target on which click happened.
Only Javascript solution
As you requested one
var deletebuttons = document.getElementsByClassName('delete');
for(var button in deletebuttons) {
button.onclick = getConfirmation;
}
I have the following Javascript that on a single mouse click in a table cell with id="freq-table" populates consecutive <input> form fields with id="searchTerm(x)" with the cell's value. It's referenced in the <body> tag as:
<body onload="populateFields()>
and <table> tag as:
<table onclick="populateFields()>
var index=0;
function populateFields(){
var ft_id = document.getElementById("freq-table");
var alltds = ft_id.getElementsByTagName("td");
for (var i in alltds) {
alltds[i].onclick = function () {
if(index==0) {
searchTerm1.value = this.innerHTML;
} else {
setThis(this.innerHTML);
}
}
}
if (index<2) {
index++;
} else {
index = 1;
}
}
function setThis(value) {
document.getElementById("searchTerm"+index).value = value;
}
When trying to make the function more universal by passing the element id (as follows), it now takes a SECOND mouse click to start populating the fields.
<table onclick="populateFields(this)" id="freq-table">
function populateFields(element){
var alltds = element.getElementsByTagName("td");
What is it about the revision that's changing the behavior? Am I just incorrectly passing the id? Or is revised function now expecting a variable to be passed to it in <body> tag? It's confusing because: If I am incorrectly passing the id, why would the function work consecutively AFTER the first mouse click? What is the fix for this, please?
You have some heavy code here, where the first table click (or body onload) sets additional click event handlers.
What you should do instead is use event delegation. With event delegation, the click event handler is attached to the table but knows which cell was clicked (the target).
[Update] Code sample based on the above article:
var index=0;
var tableIds=["freq-table1","freq-table2","freq-table3"];
for (var i=0;i<tableIds.length;i++) {
var currentId=tableIds[i];
var table=document.getElementById(currentId);
table.onclick = function(event) {
event = event || window.event;
var target = event.target || event.srcElement;
while(target != this) {
if (target.nodeName == 'TD') {
// target is our cell
setThis(target.innerHTML);
}
target = target.parentNode;
}
// increment index modulo 3
index=(index+1)%3;
}; // end of onclick function
} // end of for loop
Live demo: http://jsfiddle.net/srVmF/2/
I think the call can come from the TD or the TR element. So, the first time the id will be 'undefined'.
Why not call the function with the event and verify the tag name:
<table onclick="populateFields(event)" id="freq-table">
Javascript
function populateFields(e) {
var source = e.target || e.srcElement;
if (e.tagName == 'table') {
var ft_id = document.getElementById(source.id);
Instead of being populated on page load, now you have to click on the table before it populates the fields.
You could leave the page load handler:
<body onload="populateAllFields()">
For every table you add a class:
<table class="mytable">
Then, the code:
function populateAllFields()
{
[].forEach.call(document.getElementsByClassName('mytable'), populateFields);
}
Your <body onload="populateFields()> isn't passing the element you want, so the initial set that would be done when the page loads is no longer happening.
You can fix it by passing the ID instead, and give the onload handler the ID.
function populateFields(id){
var ft_id = document.getElementById(id);
var alltds = ft_id.getElementsByTagName("td");
// and so on...
}
<body onload="populateFields('freq-table')">
<table onclick="populateFields(this.id)">
I'll keep this short - I've got a list of buttons, that I create using a loop, and when one of them gets clicked I want to be able to pass its id attribute to another file in order to dynamically generate a new page.
Here's the code:
for (var i in data.contacts) {
var temp = document.createElement('div');
temp.className = "contacts";
var dude = document.createElement('input');
dude.type = "button";
dude.value = data.contacts[i];
dude.id = data.contacts[i];
dude.className = "dude_button" + data.contacts[i];
dude.addEventListener('click', function(event) { gotoProfile(dude.id); }, false);
temp.appendChild(dude);
temp.appendChild(document.createElement('br'));
theDiv.appendChild(temp);
}
// and now in another file, there's gotoProfile():
function gotoProfile(x) {
var username = document.getElementById(x).value;
if (xmlHttp) {
try {
.... etc.
Now see this works, sort of, but the problem is that when I click any button, it only passes the last dude.id value from the list data.contacts. Obviously I want every button's addEventListener to pass its own data.contacts[i] value, instead of just the last one.
Help appreciated, thanks guys.
Because JavaScript has no block scope, dude will refer to the last assigned element (because the loop finished) when the event handler is called. You have to capture the reference to the current dude by e.g. using an immediate function:
dude.addEventListener('click', (function(d) {
return function(event) {
gotoProfile(d.id);
}
}(dude)), false);
This is a common error when creating functions in a loop.
But you can make it even easier. The event object has a property target that points to the element the event was raised on. So you can just do:
dude.addEventListener('click', function(event) {
gotoProfile(event.target.id);
}, false);
And with that said, you don't need to add a handler for every button. As you are doing the same for every button, you could attach the same event handler above to the parent of the buttons (or a common ancestor) and it would still work. You just have to filter out the clicks that don't happen on a button:
parent.addEventListener('click', function(event) {
if(event.target.nodeName == 'INPUT' && event.target.type == "button") {
gotoProfile(event.target.id);
}
}, false);