applying an event handler to newly created objects - javascript

So my goal is to have 5 boxes and every time one box is clicked a new box appears. The code I wrote for that is this:
window.onload = function(){
var boxList = document.getElementsByClassName("box");
for(i = 0; i< boxList.length;i++){
boxList[i].onclick = clickHandler;
}
}
function clickHandler(eo){
if(eo.target.style.backgroundColor != "black") {
eo.target.style.backgroundColor = "black";
var box = document.createElement("div");
box.setAttribute("class","box");
box.setAttribute("id",document.getElementsByClassName("box").length++);
document.getElementById("Master").appendChild(box);
}
else eo.target.style.backgroundColor = "white";
}
The class of all the divs is "box" and I just add a new id to every new box. My problem is that the event handler doesn't seem to work for the newly created boxes. How could that be solved?
Many thanks in advance!

box.onclick = clickHandler;
There are more elegant ways, but as that's what you're already doing, there's no harm doing what you're doing, now.
In a different world, you might do something like:
var master = document.querySelector("#master");
master.addEventListener("click", clickHandler);
function clickHandler (e) {
var box = e.target;
var newBox;
var totalBoxes = master.querySelectorAll(".box").length;
if (!box.classList.contains("box")) {
return; // not a box
}
if (isBlack(box)) {
changeColour(box, "white");
} else {
newBox = makeNewBox(totalBoxes + 1);
master.appendChild(newBox);
changeColour(box, "black");
}
}
I don't have to worry about further click-handling beyond that, if all of the boxes are descendants of #master.
makeNewBox here is simply separating the creation of the object from what you actually want to do with it.

You will need to add an onclick event to your newly added box.
box.onclick = clickHandler;

If you create boxes dynamically after the window.onload handler has already run, then you will have to run some additional code on those dynamically created boxes that assigns the click handler to them after they have been created.
function clickHandler(eo){
if(eo.target.style.backgroundColor != "black") {
eo.target.style.backgroundColor = "black";
var box = document.createElement("div");
box.setAttribute("class","box");
// add this line of code to assign the click handler
box.onclick = clickHandler;
box.setAttribute("id",document.getElementsByClassName("box").length++);
document.getElementById("Master").appendChild(box);
}
else eo.target.style.backgroundColor = "white";
}
Or, you can use delegated event handling and handle the events from a common parent that is not dynamically created.
Delegated event handling uses "event bubbling" where events bubble up their parent chain so you can attach a click handler to a common parent and then check e.target in that click handler to see if the click occurred on one of your box elements and then process it one place. In cases of dynamically added content, this can work very well.
Delegated event handling in your code would look something like this:
window.onload = function(){
// put click handler on common box parent and use event bubbling
document.getElementById("Master").addEventListener("click", clickHandler);
}
function clickHandler(eo){
// if this click occurred on one of my boxes
if (hasClass(eo.target, "box"))
if(eo.target.style.backgroundColor != "black") {
eo.target.style.backgroundColor = "black";
var box = document.createElement("div");
box.setAttribute("class","box");
box.setAttribute("id",document.getElementsByClassName("box").length++);
document.getElementById("Master").appendChild(box);
}
else eo.target.style.backgroundColor = "white";
}
}
// utility function for checking a class name
// could also use .classList with a polyfill for older browsers
function hasClass(elem, cls) {
var str = " " + elem.className + " ";
var testCls = " " + cls + " ";
return(str.indexOf(testCls) !== -1) ;
}

Related

Javascript - Problem with modal close on click outside [duplicate]

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.)

Button duplicate not working in jquery?

I created a button(dupe of existed) onclick of existed button. But the newly created button is not going to create another button when I click.
Here is my code
$('.add-more, .dropdown button').click(function(event){
var elementToBeAdded;
if(event.target.nodeName === "IMG"){
elementToBeAdded = event.target.parentElement.parentElement.parentElement.parentElement;
}
else if(event.target.nodeName === "BUTTON"){
elementToBeAdded = event.target.parentElement.parentElement.parentElement;
}
else if(event.target.nodeName === "SPAN"){
elementToBeAdded = event.target.parentElement.parentElement;
}
var newElement = elementToBeAdded.outerHTML;
newElement = newElement.slice(0, 5) + "style='margin-top:25px' " + newElement.slice(5, newElement.length);
newElement = $(newElement)
$(elementToBeAdded).parent().append(newElement);
})
The above code working fine and creates the dupe button, But the dupe is unable to run the code on click. Please help me.
Add the click handler to the new element.
It's probably easier to move the main logic into a separate function so you can easily attach that function as the click handler.
if the element has the same class ie .add-more and .dropdown and its a button then this is the solution
$('.add-more, .dropdown button').on('click', function(event){
var elementToBeAdded;
if(event.target.nodeName === "IMG"){
elementToBeAdded = event.target.parentElement.parentElement.parentElement.parentElement;
}
else if(event.target.nodeName === "BUTTON"){
elementToBeAdded = event.target.parentElement.parentElement.parentElement;
}
else if(event.target.nodeName === "SPAN"){
elementToBeAdded = event.target.parentElement.parentElement;
}
var newElement = elementToBeAdded.outerHTML;
newElement = newElement.slice(0, 5) + "style='margin-top:25px' " + newElement.slice(5, newElement.length);
newElement = $(newElement)
$(elementToBeAdded).parent().append(newElement);
})
As shown in this same, already answered, question you'll have to use event delegation like this:
$(document).on('click', '.add-more, .dropdown button', function(event){
// ...
})
Because dynamically created elemenets dosen't have any event handler unless they are attached to them after they were created. So instead of making an event handler on the elements themselves, you can have it on an element (a parent of those elements) that you know for sure it will be there always (here I used document, it could be any other element, the condition is it have to be there always). You attach the event handler to that fixed element (document) and telling it that when ever an event occur (first argument), check if the target element match the selector (second argument '.add-more, .dropdown button'), if so then call the function (third argument) on that element.
WHY DO DYNAMICALLY CREATED ELEMENT NOT HAVE EVENT LISTENER?:
Because, this code right here:
$('selector').click(function(){
// ...
})
selects all the elements that match the selector ('selector') and loop through them (THE SELECTED ELEMENTS) one by one assigning the function passed as an event listener using basic JS function (addEventListener, attachEvent...). At this point when this code is run, your future dynamically created elements do not exist so they don't get attached to that event (because they do not exist yet). And by the time they do exist, this line of code $('selector').click(...) is already been executed (because javascript execute code instruction after the other, there's no comming back to a previously executed instruction). So another check to see if there is new elements that match will not happen. To understand here is a plain java script example:
function handleClick() {
alert("Yaay! A Click Happened!");
}
// consider we have three .btn elements in DOM at this point
var btns = document.querySelectorAll('.btn'); // three elements are selected
btns.forEach(function(btn){
btn.addEventListener('click', handleClick); // here too, just three elements get the event listener attached to them
});
// now we create another .btn
var div = document.creatElement('div');
div.className = '.btn':
// we have never ever ever ever ... called .addEventListener on the last element so clicking it will have no effect at all.
I have done this on my own. It's working.
var addBtns = document.querySelectorAll('.add-more, .dropdown button');
addClick(addBtns);
function addClick(addBtns){
Array.prototype.forEach.call(addBtns, function(addBtn) {
addBtn.addEventListener('click', addClickEvent);
});
}
function addClickEvent(e){
var elementToBeAdded;
if(event.target.nodeName === "IMG"){
elementToBeAdded = event.target.parentElement.parentElement.parentElement.parentElement;
}
else if(event.target.nodeName === "BUTTON"){
elementToBeAdded = event.target.parentElement.parentElement.parentElement;
}
else if(event.target.nodeName === "SPAN"){
elementToBeAdded = event.target.parentElement.parentElement;
} else{
return false;
}
var newElement = elementToBeAdded.outerHTML;
newElement = newElement.slice(0, 5) + "style='margin-top:25px' " + newElement.slice(5, newElement.length);
newElement = $(newElement)
$(elementToBeAdded).parent().append(newElement);
addClick(newElement);
}

How to detect click outside an element with a class selector Using Pure Javascript (without using Jquery)

I want to detect clicking outside an element using class name as
selector
<div id="popOne" class="pop">...</div>
<div id="popTwo" class="pop">...</div>
...
<div id="popLast" class="pop">...</div>
<script>
var popElement = document.getElementById("pop");
document.addEventListener('click', function(event) {
var isClickInside = popElement.contains(event.target);
if (!isClickInside) {
alert("Outside");
//the click was outside the popElement, do something
}
});
</script>
As an alternative to iterating over all possible .pop elements for every click event, just traverse the DOM looking to see if the node or any ancestor thereof has the desired class:
document.addEventListener('click', function(e) {
var node = e.target;
var inside = false;
while (node) {
if (node.classList.contains('pop')) {
inside = true;
break;
}
node = node.parentElement;
}
if (!inside) {
alert('outside');
// click was outside
} else {
alert('inside');
}
});
This would make the performance scale relative to the depth of the DOM tree, rather than by the number of .pop elements.
Made the following changes to the script
var popElement = document.getElementsByClassName("pop");
document.addEventListener('click', function(event) {
for(i=0; i < popElement.length; i++){
popEl = popElement[i];
var isClickInside = popEl.contains(event.target);
if (!isClickInside) {
alert("Outside");
} else {
alert("Inside");
break;
}
}
});
First of all you are using the incorrect function to get Element. It should be getElementsByClassName("pop") and not getElementsById("pop") and also getElementsByClassName returns a HTMLCollection of elements having that class. So you need to loop over them and check whether clicked inside any of them or not. Here is the demo. Have added some style to divs so that it easy to differentiate between them. And also if need to check whether the click was on any of the divs then you need to check for that and as soon as you find that it was clicked inside a div having class pop. Break from the loop and continue with you conditions. But if for all the elements the IsClickedInside comes out to be false then you can handle it accordingly
document.addEventListener('click', function(event) {
var popElement = document.getElementsByClassName("pop");
var isClickInside;
for (var i = 0; i < popElement.length; i++) {
isClickInside = popElement[i].contains(event.target);
if (isClickInside) {
break;
//alert("Outside of" + popElement[i].id);
//the click was outside the popElement, do something
}
}
if(isClickInside){
alert("Clicked inside one of the divs.");
}else{
alert("Clicked outside of the divs.");
}
});
div {
height: 100px;
border:2px solid black;
}
<div id="popOne" class="pop">...</div>
<div id="popTwo" class="pop">...</div>
...
<div id="popLast" class="pop">...</div>
Hope it helps :)

Function activate after two onclicks

Hey I'm using javascript+html only.
Is there any way to activate a function after the button has been clicked two (or more) times? I want the button to do NOTHING at the first click.
For a "doubleclick", when the user quickly presses the mouse button twice (such as opening a program on the desktop), you can use the event listener dblclick in place of the click event.
https://developer.mozilla.org/en-US/docs/Web/Reference/Events/dblclick
For a quick example, have a look at the below code. http://jsfiddle.net/jzQa9/
This code just creates an event listener for the HTMLElement of "item", which is found by using getElementById.
<div id="item" style="width:15px;height:15px;background-color:black;"></div>
<script>
var item = document.getElementById('item');
item.addEventListener('dblclick',function(e) {
var target = e.target || e.srcElement;
target.style.backgroundColor = 'red';
},false);
</script>
As for wanting the user to click an element X times for it to finally perform an action, you can do the following. http://jsfiddle.net/5xbPG/
This below code works by adding a click tracker to the HTMLElement and incrementing the click count every time it's clicked. I opted to save the clicks to the HTMLElement instead of a variable, but either way is fine.
<div id="item" style="width:15px;height:15px;background-color:black;"></div>
<script>
var item = document.getElementById('item');
item.addEventListener('click',function(e) {
var target = e.target || e.srcElement;
var clicks = 0;
if(target.clicks)
clicks = target.clicks;
else
target.clicks = 0;
if(clicks >= 4) {
target.style.backgroundColor = 'red';
}
target.clicks += 1;
},false);
</script>
== UPDATE ==
Since you recently posted a comment that you want two different buttons to be clicked for an action to happen, you would want to do something like this... http://jsfiddle.net/9GJez/
The way this code works is by setting two variables (or more) to track if an element has been clicked. We change these variables when that item has been clicked. For each event listener at the end of changing the boolean values of the click state, we run the function checkClick which will make sure all buttons were clicked. If they were clicked, we then run our code. This code could be cleaned up and made to be more portable and expandable, but this should hopefully get you started.
<input type="button" id="button1">
<input type="button" id="button2">
<div id="result" style="width:15px;height:15px;background-color:black;"></div>
<script>
var result = document.getElementById('result');
var button1 = document.getElementById('button1');
var button2 = document.getElementById('button2');
var button1Clicked = false;
var button2Clicked = false;
button1.addEventListener('click',function(e) {
button1Clicked = true;
checkClick();
},false);
button2.addEventListener('click',function(e) {
button2Clicked = true;
checkClick();
},false);
function checkClick() {
if(button1Clicked && button2Clicked) {
result.style.backgroundColor = 'red';
}
}
</script>
Two ways you can do this, one would be to have a data attribute within the html button that identifies whether the click has been done.
<button id="btn">Click Me!</button>
<script>
var clickedAlready = false;
document.getElementById('btn').onclick = function() {
if (clickedAlready) {
//do something...
}
else
clickedAlready = true;
}
</script>
While global variables aren't the best way to handle it, this gives you an idea. Another option would be to store the value in a hidden input, and modify that value to identify if it's the first click or not.
Maybe something like this?
var numberTimesClicked = 0;
function clickHandler() {
if (numberTimesClicked > 0) {
// do something...
}
numberTimesClicked++;
}
document.getElementById("myBtn").addEventListener("click", clickHandler);

How to add an event Listener to multiple objects via the class name

I have a set of scrolling objects with text in them. I have surrounded said text with a div and a class name of "scrollContent". I have added an onscroll attribute that calls my method.
I have come across the .addEventListener feature and want to add a listener when any divs with the class name scrollContent is scrolled, call my function.
Is this possible and how?
function checkScroll(scrollEle) {
var topLG = getTopLG(scrollEle);
if(scrollEle.scrollTop < 10){
topLG.style.display = "none";
}else{
if(topLG.hasAttribute("style")){
topLG.removeAttribute("style");
}
}
}
var scrollDivs = document.getElementsByClassName("scrollContent");
for (var i =0; i<scrollDivs.length; i++){
scrollDivs[i].addEventListener('scroll', checkScroll(scrollDivs[i]), false)
}
Use event delegation. Something like:
// somecontainerDiv can be document, document.body or any Element
// containing your elements having class 'scrollContent'
somecontainerDiv.addEventListener('scroll', scrollHandling);
function scrollHandling(e){
var originator = e.target || e.srcElement;
if (!/scrollContent/i.test(originator.className) {return true;}
return checkScroll(e); // or insert checkScroll code here
}

Categories

Resources