Only register events once - javascript

I wrote a small script to load pages with ajax. All links that have the ajax-pls- class, should be selected.
After I add the eventlistener I remove the class, because I need to parse the included html every time.... right?
(function() {
function addEvent(element, evnt, funct){
if (element.attachEvent)
return element.attachEvent('on'+evnt, funct);
else
return element.addEventListener(evnt, funct, false);
}
var link_click = function (e) {
e.preventDefault();
if (this.getAttribute("href") == 'test1.html') {
var content = document.getElementById('content');
content.innerHTML = "<a href='test3.html' class='ajax-pls'>Test3</a>";
register_listeners();
} else {
alert(this);
}
};
function register_listeners() {
var atags = document.querySelectorAll('a.ajax-pls');
for (i = 0; i < atags.length; i++) {
addEvent(atags[i], 'click', link_click);
atags[i].classList.remove("ajax-pls");
}
}
register_listeners();
})();
It is just test-code, but do I need to do the trick with the class or could I just call register_listeners() after every include?

Yes you need to remove classes otherwise, addEvent will add same handler several times to each link.
But you can bypass this issue by using event bubbling.
If you add handler to nearest common ancestor of every link (in worst case it would be document.body). It will catch every click event on every elements inside it. You will need to filter them by checking event.target.

Related

Enable and disable onclick funtion

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;
});

How can I add an event listener for multiple buttons with same class name?

I'm building a decision tree in JavaScript. I do not have jQuery available to me for this project.
I would like to be able to have buttons, placed anywhere in the decision tree (Hidden or displayed anywhere on the page), with the same class name. The listener on the JS side would then run a function.
Here is what I am using for and ID based listener. It works well but I need to be able to have multiple buttons with the same class or name available. Although I have seen examples of this, I cannot get it to function properly.
function q1a1() {
var q1a1button = document.getElementById("q1answer1");
if(q1a1button.addEventListener){
q1a1button.addEventListener("click", function() { q1answer1();}, false);
} else if(q1a1button.attachEvent){
q1a1button.attachEvent("onclick", function() { q1answer1();});
}
};
if(window.addEventListener){
window.addEventListener("load", q1a1, false);
} else if(window.attachEvent){
window.attachEvent("onload", q1a1);
} else{
document.addEventListener("load", q1a1, false);
}
function q1answer1() {
//DO SOME STUFF
}
This also needs to work in as many versions of IE as possible. For single class handling I'm using querySelectorAll.
What you are really looking for is JavaScript Event Delegation. In your case, you have BUTTON elements, which I'm going to assume are <button> tags. Now you want to know when one of those buttons was clicked and then run a function:
if (document.addEventListener) {
document.addEventListener("click", handleClick, false);
}
else if (document.attachEvent) {
document.attachEvent("onclick", handleClick);
}
function handleClick(event) {
event = event || window.event;
event.target = event.target || event.srcElement;
var element = event.target;
// Climb up the document tree from the target of the event
while (element) {
if (element.nodeName === "BUTTON" && /foo/.test(element.className)) {
// The user clicked on a <button> or clicked on an element inside a <button>
// with a class name called "foo"
doSomething(element);
break;
}
element = element.parentNode;
}
}
function doSomething(button) {
// do something with button
}
Anywhere on the page that a <button class="foo">...</button> element appears, clicking it, or any HTML tag inside of it, will run the doSomething function.
Update: Since Event Delegation is used, only a single click handler is registered on the document object. If more <button>s are created as a result of an AJAX call, you don't have to register click handlers on those new <button>s since we take advantage of the click event bubbling up from the element the user clicked on to the document object itself.
If you don't have jquery:
if (document.body.addEventListener){
document.body.addEventListener('click',yourHandler,false);
}
else{
document.body.attachEvent('onclick',yourHandler);//for IE
}
function yourHandler(e){
e = e || window.event;
var target = e.target || e.srcElement;
if (target.className.match(/keyword/))
{
//an element with the keyword Class was clicked
}
}
If you use a cross browser library like jquery:
HTML:
<div class="myClass">sample</div>
<div class="myClass">sample 2</div>
JS:
function theFuncToCall(event){
//func code
}
$(document).on('click', '.myClass', theFuncToCall);
var buttons = document.querySelectorAll(".MyClassName");
var i = 0, length = buttons.length;
for (i; i < length; i++) {
if (document.addEventListener) {
buttons[i].addEventListener("click", function() {
// use keyword this to target clicked button
});
} else {
buttons[i].attachEvent("onclick", function() {
// use buttons[i] to target clicked button
});
};
};
This answer is a bit overkill, but it should show you ways you could structure your code in a "modern" way even if you're still targeting old browsers
Write code to add event listeners so there is minimal difference between new and old browsers
var listen = (function () { // will return the handler for use in unlisten
if (window.addEventHandler) {
return function (node, type, handler) {
node.addEventListener(type, handler);
return handler;
};
} else if (window.attachEvent) {
return function (node, type, handler) {
var fn = function (e) {
if (!e) {
e = window.event;
}
if (!e.target && e.srcElement) {
e.target = e.srcElement;
}
return handler.call(this, e);
};
node.attachEvent('on' + type, fn);
return fn;
};
} else {
throw new Error('Events not supported in this environment');
// or
// return function ... node['on' + type] = function () { ... };
}
}());
and if you'd like the reverse, too
var unlisten = (function () { // use handler given by listen
if (window.removeEventListener) {
return function (node, type, handler) {
node.removeEventListener(type, handler);
};
} else if (window.detachEvent) {
return function (node, type, handler) {
node.detachEvent('on' + type, handler);
};
} else {
throw new Error('Events not supported in this environment');
// or
// return function ... node['on' + type] = null;
}
}());
Write your click handler
function clickHandler(e) {
// do stuff
}
Wrap your click handler in a function to choose only clicks on buttons with the right class
function wrappedClickHandler(e) {
var tokens, i;
if (e.target.tagName !== 'INPUT' && e.target.tagName !== 'BUTTON') {
return;
}
tokens = (e.target.className || '').split(' ');
for (i = 0; i < tokens.length; ++i) {
if (tokens[i] === 'theClassTokenWeWant') {
return clickHandler.call(this, e);
// or
// return clickHandler.call(e.target, e);
}
}
}
Add this as a listener to a common ancestor node
var h = listen(document, 'click', wrappedClickHandler);
// .. later, if desired
unlisten(document, 'click', h);
Would the simpler way of writing the event delegation function be to add it to the container of the buttons? For example,
// Select Container Element
const questionContainer = document.querySelector(".container");
// Listen For Clicks Within Container
questionContainer.onclick = function (event) {
// Prevent default behavior of button
event.preventDefault();
// Store Target Element In Variable
const element = event.target;
// If Target Element Is a Button
if (element.nodeName === 'BUTTON') {
// Event Code
}
}

Remove specific set of event listeners from an HTML element using JavaScript

I want to remove only the mouseup event listners from a selected HTML element.
I used the below code but it will remove all listners.
var old_element = divs[d];
var new_element = old_element.cloneNode(true);
old_element.parentNode.replaceChild(new_element, old_element);
this is how i attach event listners.
var divs = document.getElementsByTagName('body');// to enhance the preformance
for(var d in divs) {
try{
if (divs[d].addEventListener) {
divs[d].addEventListener('mouseup',callHighlight);
} else {
divs[d].attachEvent('mouseup', callHighlight);
}
}catch(err){
//alert(err.message);
}
}
You should use removeEventListener instead of replacechild which will obviously remove all events.
old_element.removeEventListener('mouseup', handler);
When cloning an element, listeners added using addEventListener or by direct property assignment (element.onclick = fn;) are removed, but in–line listeners and those added using IE's attachEvent are not.
In your scenario where listeners are added by reference and also possibly using attachEvent, you are best to remove them using removeEventListener and detachEvent. So you might like to create add and remove functions like:
function addEvent(element, event, fn) {
if (element.addEventListener) {
element.addEventListener(event, fn, false);
} else if (element.attachEvent) {
element.attachEvent('on' + event, fn);
}
}
function removeEvent(element, event, fn) {
if (element.removeEventListener) {
element.removeEventListener(event, fn);
} else if (element.detachEvent) {
element.detachEvent('on' + event, fn);
}
}
Note that there are some significant differences between addEventListener and attachEvent, the most important are that in the latter, this is not set to the element whose handler is calling the function and a reference to the event isn't passed as the first argument to the listener. So the listener function ends up looking like:
function foo(evt) {
evt = evt || window.event;
var target = evt.target || evt.srcElement;
...
}
There are ways around this, but they introduce more issues. Keep it simple if you can.
By default all event listeners are null, so simply just reset it. Problem is that all your mouseup events are registered to the body, so therefore you won't be able to drop the event without first stopping the event from bubbling to the body. You can solve that problem with, stopPropagation()
old_element.onmouseup = function (e) {
// event won't go up to the body tag
e.stopPropagation();
return null;
};
or
function kill (e) {
e.stopPropagation();
return null;
}
old_element.onmouseup = kill;
second_element.onmouseup = kill;
JSFIDDLE

Detect click outside element (vanilla JavaScript)

I have searched for a good solution everywhere, yet I can't find one which does not use jQuery.
Is there a cross-browser, normal way (without weird hacks or easy to break code), to detect a click outside of an element (which may or may not have children)?
Add an event listener to document and use Node.contains() to find whether the target of the event (which is the inner-most clicked element) is inside your specified element. It works even in IE5
const specifiedElement = document.getElementById('a')
// I'm using "click" but it works with any event
document.addEventListener('click', event => {
const isClickInside = specifiedElement.contains(event.target)
if (!isClickInside) {
// The click was OUTSIDE the specifiedElement, do something
}
})
var specifiedElement = document.getElementById('a');
//I'm using "click" but it works with any event
document.addEventListener('click', function(event) {
var isClickInside = specifiedElement.contains(event.target);
if (isClickInside) {
alert('You clicked inside A')
} else {
alert('You clicked outside A')
}
});
div {
margin: auto;
padding: 1em;
max-width: 6em;
background: rgba(0, 0, 0, .2);
text-align: center;
}
Is the click inside A or outside?
<div id="a">A
<div id="b">B
<div id="c">C</div>
</div>
</div>
You need to handle the click event on document level. In the event object, you have a target property, the inner-most DOM element that was clicked. With this you check itself and walk up its parents until the document element, if one of them is your watched element.
See the example on jsFiddle
document.addEventListener("click", function (e) {
var level = 0;
for (var element = e.target; element; element = element.parentNode) {
if (element.id === 'x') {
document.getElementById("out").innerHTML = (level ? "inner " : "") + "x clicked";
return;
}
level++;
}
document.getElementById("out").innerHTML = "not x clicked";
});
As always, this isn't cross-bad-browser compatible because of addEventListener/attachEvent, but it works like this.
A child is clicked, when not event.target, but one of it's parents is the watched element (i'm simply counting level for this). You may also have a boolean var, if the element is found or not, to not return the handler from inside the for clause. My example is limiting to that the handler only finishes, when nothing matches.
Adding cross-browser compatability, I'm usually doing it like this:
var addEvent = function (element, eventName, fn, useCapture) {
if (element.addEventListener) {
element.addEventListener(eventName, fn, useCapture);
}
else if (element.attachEvent) {
element.attachEvent(eventName, function (e) {
fn.apply(element, arguments);
}, useCapture);
}
};
This is cross-browser compatible code for attaching an event listener/handler, inclusive rewriting this in IE, to be the element, as like jQuery does for its event handlers. There are plenty of arguments to have some bits of jQuery in mind ;)
How about this:
jsBin demo
document.onclick = function(event){
var hasParent = false;
for(var node = event.target; node != document.body; node = node.parentNode)
{
if(node.id == 'div1'){
hasParent = true;
break;
}
}
if(hasParent)
alert('inside');
else
alert('outside');
}
you can use composePath() to check if the click happened outside or inside of a target div that may or may not have children:
const targetDiv = document.querySelector('#targetDiv')
document.addEventListener('click', (e) => {
const isClickedInsideDiv = e.composedPath().includes(targetDiv)
if (isClickedInsideDiv) {
console.log('clicked inside of div')
} else {
console.log('clicked outside of div')
}
})
I did a lot of research on it to find a better method. JavaScript method .contains go recursively in DOM to check whether it contains target or not. I used it in one of react project but when react DOM changes on set state, .contains method does not work. SO i came up with this solution
//Basic Html snippet
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="mydiv">
<h2>
click outside this div to test
</h2>
Check click outside
</div>
</body>
</html>
//Implementation in Vanilla javaScript
const node = document.getElementById('mydiv')
//minor css to make div more obvious
node.style.width = '300px'
node.style.height = '100px'
node.style.background = 'red'
let isCursorInside = false
//Attach mouseover event listener and update in variable
node.addEventListener('mouseover', function() {
isCursorInside = true
console.log('cursor inside')
})
/Attach mouseout event listener and update in variable
node.addEventListener('mouseout', function() {
isCursorInside = false
console.log('cursor outside')
})
document.addEventListener('click', function() {
//And if isCursorInside = false it means cursor is outside
if(!isCursorInside) {
alert('Outside div click detected')
}
})
WORKING DEMO jsfiddle
using the js Element.closest() method:
let popup = document.querySelector('.parent-element')
popup.addEventListener('click', (e) => {
if (!e.target.closest('.child-element')) {
// clicked outside
}
});
To hide element by click outside of it I usually apply such simple code:
var bodyTag = document.getElementsByTagName('body');
var element = document.getElementById('element');
function clickedOrNot(e) {
if (e.target !== element) {
// action in the case of click outside
bodyTag[0].removeEventListener('click', clickedOrNot, true);
}
}
bodyTag[0].addEventListener('click', clickedOrNot, true);
Another very simple and quick approach to this problem is to map the array of path into the event object returned by the listener. If the id or class name of your element matches one of those in the array, the click is inside your element.
(This solution can be useful if you don't want to get the element directly (e.g: document.getElementById('...'), for example in a reactjs/nextjs app, in ssr..).
Here is an example:
document.addEventListener('click', e => {
let clickedOutside = true;
e.path.forEach(item => {
if (!clickedOutside)
return;
if (item.className === 'your-element-class')
clickedOutside = false;
});
if (clickedOutside)
// Make an action if it's clicked outside..
});
I hope this answer will help you !
(Let me know if my solution is not a good solution or if you see something to improve.)

Remove event listener in JavaScript

I add an event listener to an element:
/* sitepoint.com/javascript-this-event-handlers */
function AttachEvent(element, type, handler){
if (element.addEventListener){
element.addEventListener(type, handler, false);
}else{
element.attachEvent("on"+type, handler);
}
}
window.addEventListener("load", function() {
var els = getElementsByClassName('name', 'img');
var elsnum = els.length;
if(elsnum) //found
{
var i = 0;
for(i=0; i < elsnum; i++)
{
var the_els = els[i];
AttachEvent(the_els, "click", myfunction);
}
}
}, false);
Later in myfunction, I want to remove the handler again, to prevent duplicate clicks:
function myfunction(e) {
e = e || window.event;
var target = e.target || e.srcElement;
//more code
//...
//remove click handler
target.removeEventListener('click', e, false);
//more code
//...
}
The event listener is not being removed, though. When I click on one of the elements, the code of myfunction is executed again. How can I remove the event listener to prevent the clicked element from being clicked again?
PS: I do not use jQuery.
I believe you're almost there, but you have to pass the listener to removeEventListener, not the event itself. So try:
target.removeEventListener('click', myFunction, false);
Use element.removeEventListener(type, listener, useCapture)
Remember to use that on the same element, and give it the exact same parameters as you did for adding.
One excellent way to code this would be to make a function that stores the listener details in a variable, mimicking how setTimeout() works for instance, and adding that to the element prototype. Here's an example function.
HTMLElement.prototype.eventListener=
function(type, func, capture){
if(typeof arguments[0]=="object"&&(!arguments[0].nodeType)){
return this.removeEventListener.apply(this,arguments[0]);
}
this.addEventListener(type, func, capture);
return arguments;
}
That will add a method to all HTML nodes that already can accept event listners, and allow you to do this.
var a=element.eventListener('click', myFunction, false); //to add
element.eventListener(a); //to remove
http://www.quirksmode.org/js/events_advanced.html
To remove an event handler, use the removeEventListener() method.
Also see http://help.dottoro.com/ljahxbsx.php
object.removeEventListener (eventName, listener, useCapture);
listener - Required. Reference to the event handler function to remove. You need to pass what listener you want to remove.

Categories