Eventlisteners impact? - javascript

What impact does eventlisteres have? Im talking about big numbers, here's an example:
There's only x amount of .marker at first
All children are added via JS when .marker is clicked - eventlistener
Each child does it's own thing which means each of them have their own eventlisteners
<!-- Final HTML of single .marker when it has been clicked -->
<div class="marker">
<div class="remove"></div>
<div class="change"></div>
<div class="add"></div>
<div class="drag"></div>
</div>
var count = 20 000;
for(i = 0; i < count; i++) {
var marker = document.createElement('div');
marker.className = 'marker';
someParentElement.appendChild(marker);
marker.click( function() {
//Create child elements
var remove = document.createElement('div');
remove.className = 'remove';
marker.appendChild(remove);
var change = document.createElement('div');
change.className = 'change';
marker.appendChild(change);
var add = document.createElement('div');
add.className = 'add';
marker.appendChild(add);
var drag = document.createElement('div');
drag.className = 'drag';
marker.appendChild(drag);
//Children eventlisteners
remove.click( function() {
//Do it's thing
});
change.click( function() {
//Do it's thing
});
add.click( function() {
//Do it's thing
});
drag.click( function() {
//Do it's thing
});
});
}
Please don't mind other things, e.g creating 20 000 elements programmatically. My question is this: what would be the impact of having all these eventlisteners with all this code in them? Does it even matter what or how much code is inside eventlistener as long as it hasn't been triggered?

Try using event delegation , single event handler. See switch , .is()
var count = 100;
for (i = 0; i < count; i++) {
var marker = document.createElement('div');
marker.className = 'marker';
marker.innerHTML = marker.className + " " + i;
document.body.appendChild(marker);
//Create child elements
var remove = document.createElement('div');
remove.className = 'remove';
remove.innerHTML = "remove" + i;
marker.appendChild(remove);
var change = document.createElement('div');
change.className = 'change';
change.innerHTML = "change" + i;
marker.appendChild(change);
var add = document.createElement('div');
add.className = 'add';
add.innerHTML = "add" + i;
marker.appendChild(add);
var drag = document.createElement('div');
drag.className = 'drag';
drag.innerHTML = "drag" + i;
marker.appendChild(drag);
//Create child elements
}
var check = function(args) {
alert(args.innerHTML.replace(/[^\d+]/g, ""))
}
var obj = {
remove: check,
change: check,
add: check,
drag: check
}
var message = function(name) {
console.log(name)
}
$("body").on("click", ".marker", function(event) {
var name = event.target.className;
switch (name) {
case "remove":
/* do stuff */
message(name);
break;
case "change":
/* do stuff */
message(name);
break;
case "add":
/* do stuff */
message(name);
break;
case "drag":
/* do stuff */
message(name);
break;
default:
/* do stuff */
alert(name);
break;
}
// utilizing `.is()`
if ($(event.target).is(".remove")) {
// do stuff
event.target.innerHTML += "clicked"
}
if ($(event.target).is(".change")) {
// do stuff
event.target.innerHTML += "clicked"
}
if ($(event.target).is(".add")) {
// do stuff
event.target.innerHTML += "clicked"
}
if ($(event.target).is(".drag")) {
// do stuff
event.target.innerHTML += "clicked"
}
if (!$(event.target).is(".marker")) {
// utilizing an object
obj[event.target.className](event.target)
}
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js">
</script>

Creating event handlers for so many things which are repeated feels like a waste of CPU cycles, plus memory to manage so many event listeners.
Instead, it would be preferrable to use event bubbling/delegation to listen to the click events from a parent node (as close an ancestor element as possible would be ideal) and see what element triggered the event and call the appropriate code accordingly.
This would be a one-time bind, and should also catch dynamically added elements if done right.
Examples with jQuery that are also explained quite well include the following
https://learn.jquery.com/events/event-delegation/
http://api.jquery.com/on/
Though you are not limited to just jQuery to implement this.
Hope that helps.

Related

jquery .addClass not doing what it's supposed to

I'm trying to highlight a list element using:
element.id = 'ACT_3'
$('#' + element.id).addClass('highlight');
Javascript
function selectItemId(element) {
$('#' + element.id).addClass('highlight');
var eleID = element.id;
//highlight selection(s)
selectItemId
if (document.getElementById(eleID).className == "smallfieldchecked") {
document.getElementById(eleID).className = "smallfield";
}
else {
document.getElementById(eleID).className = "smallfieldchecked";
}
Items.selectedForRemoval.length = 0;
// build a list of Selected Items
if (elementInDocument(document.getElementById(mainGrid))) {
var ul = document.getElementById(mainGrid);
var lis = ul.childNodes;
for (var x = 0; x < lis.length; x++) {
if (document.getElementById(lis[x].id)) {
if (document.getElementById(lis[x].id).className == "smallfieldchecked") {
Items.selectedForRemoval.push(lis[x].id.replace(rowPrefix, ''));
}
}
}
}
}
CSS
.highlight {
background-color: cyan;
font-weight: bold;
}
I think the addClass() is working but it's being overridden a few lines below by document.getElementById(eleID).className = "...."
Try to put the addClass line after the if ... else block
Here are a few variations of how to fix the issue. Sélim Achour is correct in the cause of the problem and how to fix it.
Every time you do a query, you have to search the DOM for it, so these examples will put the element into a reuseable var.
function selectItemIDFromMainGrid(element) {
var $element = $('#' + element.id);
$element.addClass('highlight');
//highlight selection(s)
selectItemIDFromMainGrid // <-- not sure what this is
if ($element.hasClass("smallfieldchecked")) {
$element.removeClass('smallfieldchecked').addClass("smallfield");
}
else {
$element.removeClass('smallfield').addClass("smallfieldchecked");
}
// remaining code removed for brevity.
}
This version also works, as Sélim Achour pointed out. However, you will have to debug a similar situation if you have more classes used in the future.
function selectItemIDFromMainGrid(element) {
// jquery-wrapped element
var $element = $('#' + element.id);
// actual DOM element.
var domElement = $element[0];
//highlight selection(s)
selectItemIDFromMainGrid // <-- not sure what this is
if ($element.hasClass("smallfieldchecked")) {
domElement.className = "smallfield";
}
else {
domElement.className = "smallfieldchecked";
}
// adding the class down here after setting the class above
// makes this get appended. the assignments above make a single class assignment.
$element.addClass('highlight');
// remaining code removed for brevity.
}
Or you can skip the jQuery for adding the highlight class.
function selectItemIDFromMainGrid(element) {
// jquery-wrapped element
var $element = $('#' + element.id);
// actual DOM element.
var domElement = $element[0];
//highlight selection(s)
selectItemIDFromMainGrid // <-- not sure what this is
if ($element.hasClass("smallfieldchecked")) {
domElement.className = "smallfield highlight";
}
else {
domElement.className = "smallfieldchecked highlight";
}
// remaining code removed for brevity.
}

.addEventListener - long list

I found few answer on my issue but probably I'm not so experienced to processes it to my case.
I have list of items generated to .html
<div id="grid">
by JavaScript
var div_block = "";
for (i = 0; i < 70; i++) {
div_block = div_block + '<div id="c' + i + '" class="card"></div>';
}
document.getElementById("grid").innerHTML = div_block;
for (i = 0; i < 70; i++) {
'var c' + i + ' = document.getElementById(c' + i + ');'
}
and it works fine.
I want to chose .addEventListner method to chose proper element but for 70 and more elements code:
c0.addEventListener("click", function () {revealCard(0);});
c1.addEventListener("click", function () {revealCard(1);});
...
cn.addEventListener("click", function () {revealCard(n);});
is huge and not elegant. Method I've tried didn't work
for (i = 0; i < 70; i++) {
'c'+i+'.addEventListener("click", function() { revealCard('+i+'); });'
}
How to build working addEventListener() for this?
Thanks a lot.
The problem you are facing can be solved by using the card class that you add on each of your card. Then, to refer to the right card, you can use the keyword this, which in the context of an addEventListener will refer to whichever DOM element received the click. You also won't need to generate a unique Id for each one of your div, which I think is a big plus.
Your code would look like this:
let div_block = "";
for (i = 0; i < 70; i++) {
div_block = div_block + '<div class="card"></div>';
}
const cards = querySelectorAll(".card");
cards.forEach(card => {
card.addEventListener("click", revealCard)
})
function revealCard(){
// Here, `this` refers to the card that was clicked
// So you can do what you want with it
console.log(this);
}
Slight modification to brk's answer, using a single event listener on the parent that will trigger for the events on the children
var div_block = "";
for (let i = 0; i < 5; i++) {
div_block += `<div data-attr="${i}" id="c${i}" class="card">Hello</div>`;
}
var grid = document.getElementById("grid");
grid.innerHTML = div_block;
grid.addEventListener('click', function (e) {
if (e.target.getAttribute('class') === 'card') {
revealCard(e.target.dataset.attr);
}
});
function revealCard(num) {
console.log(num)
}
<div id='grid'></div>
You can use dataset, that is while creating the dom add a dataset property.
Then use querySelectorAll to get all the div with class card and iterate over it to add event using addEventListener. On click of the element get the dataset value and pass to revealCard function
var div_block = "";
for (let i = 0; i < 5; i++) {
div_block += `<div data-attr="${i}" id="c${i}" class="card">Hello</div>`;
}
document.getElementById("grid").innerHTML = div_block;
document.querySelectorAll('.card').forEach(function(item) {
item.addEventListener('click', function(e) {
revealCard(item.dataset.attr)
})
})
function revealCard(num) {
console.log(num)
}
<div id='grid'></div>
There are multiple ways to do this, but I wouldn't use IDs and I wouldn't bind X event listeners. I would use event delegation and data-* attributes:
Build your list of elements:
const grid = document.getElementById("grid");
for (var i = 0; i < 70; i++) {
const child = document.createElement('div');
child.className = 'card';
child.dataset.index = i;
grid.appendChild(child);
}
Add an event listener to the grid element:
grid.addEventListener('click', function(event) {
let target = event.target;
// traverse up if clicked inside element
while (target.className !== 'card') {
target = target.parentNode;
}
if (target) {
revealCard(target.dataset.index);
}
});
const grid = document.getElementById("grid");
for (var i = 0; i < 70; i++) {
const child = document.createElement('div');
child.className = 'card';
child.dataset.index = i;
child.innerText = `Card ${i}`;
grid.appendChild(child);
}
grid.addEventListener('click', function(event) {
let target = event.target;
// traverse up if clicked inside element
while (target.className !== 'card') {
target = target.parentNode;
}
if (target) {
revealCard(target.dataset.index);
}
});
function revealCard(i) {
console.log(`Card ${i} revealed`);
}
<div id="grid"></div>
I found probably the easiest (shortest) solution:
for(var i=0;i<boardSize;i++){
document.getElementById('c'+i).addEventListener("click",function(){
revealCard(this.id.substring(1));
});
}
What do you thing?

Event fires in another event

On the first click on each section I create an li which contains the content of that section h2 and it has to be an anchor to that section. The problem is that item.onclick fires in the doSomething method instead of when I click the li.
How can I fix it?
PS: I know that the first child of each section is a h2.
var list = document.getElementById('cuprins');
var sections = document.getElementsByTagName('section');
var l = sections.length;
var viz = new Array(l).fill(0);
for(var i = 0; i < l; i++)
sections[i].addEventListener('click', doSomething);
function jump(h){
var top = h.offsetTop;
window.scrollTo(0, top);
console.log(h);
}
function doSomething(e){
var index;
for(var i = 0; i < l; i++)
if(e.currentTarget == sections[i]){
index = i;
break;
}
if(viz[index] == 0){
var text = sections[index].children[0].textContent;
var item = document.createElement('li');
item.innerHTML = text;
item.onclick = jump(sections[index]);
list.appendChild(item);
viz[index] = 1;
}
}
The problem is that jump(sections[index]); is not being assigned as the callback function for item.click. Instead it is being executed anytime doSomething is called and viz[index] == 0.
What then happens is that the return value from calling jump(sections[index]); is being assigned as the onclick of item, but jump doesn't return any value, so there is no function registered with onclick.
You need to have jump invoked when item is clicked, so you need the line to be:
item.onclick = function() { jump(sections[index]); };
So that the outer function is assigned as the event callback and the contents of the function aren't invoked until the event occurs.
Having said that, I would move away from onXyz event properties and move to modern, standards-based code with addEventListener():
item.addEventListener("click", function() { jump(sections[index]); });
You're executing function and assign the result to item.onlick event
Unstead, using the code below will assign function declaration to item.onclick event
if(viz[index] == 0){
var text = sections[index].textContent;
var item = document.createElement('li');
item.innerHTML = text;
item.onclick = function() {
jump(sections[index])
};
list.appendChild(item);
viz[index] = 1;
}
}

How to change text content in DIV (with DOM) to add SPANs elements instead?

I'm confused on how to change text content of div with the DOM. When event is triggered, I see that the new text replace the old but it is in a new div. I want to keep it in "transcriptText" to keep all attributes.`How can I do that?
This is my old div with text inside:
var transcriptText = document.getElementById("transcriptText");
these are my new text SPAN elements
var newTranscript = document.createElement("div");
This is how I handle the event
function EventHandler() {
transcriptText.parentNode.replaceChild(newTranscript, transcriptText);
}
Here is the JSFiddle on how it currently works:
http://jsfiddle.net/b94DG/
What you're doing now is creating a new div, newTranscript, which you create by appending a bunch of spans based on the old text. Then in your event handler you replace the old one with the new one. Instead of that, you could still copy the text from the old one, but then clear it and append the children on the old div, replacing line 36 with:
transcriptText.appendChild(newSpan);
To clear the old element, it might work to just set innerHTML to "", or if necessary you could remove all the children with removeChild as described at https://developer.mozilla.org/en-US/docs/Web/API/Node.removeChild
EDIT:
I modified your fiddle to reflect this:
http://jsfiddle.net/b94DG/1/
You can change the innerHTML of transcriptText instead of creating a new div.
var transcriptText = document.getElementById("transcriptText");
var divideTranscript = document.getElementById("divideTranscript");
divideTranscript.onclick = function() {
var sArr = transcriptText.innerHTML.split(" ");
var newInnerHTML = "";
for (var i = 0; i < sArr.length; i++) {
var item = sArr[i];
var newText = "<span class='highlight' id='word" + i + "'>" + item + " </span>";
newInnerHTML += newText;
}
transcriptText.innerHTML = newInnerHTML;
var mouseOverFunction = function () {
this.style.backgroundColor = 'yellow';
};
var mouseOutFunction = function () {
this.style.backgroundColor = '';
};
var highlight = document.getElementsByClassName("highlight");
for (i = 0; i < highlight.length; i++) {
highlight[i].onmouseover = mouseOverFunction;
highlight[i].onmouseout = mouseOutFunction;
}
};
Here's the fiddle: http://jsfiddle.net/khnGN/

Dynamic div elements and associating events with these div

I am creating dynamically multiple div elements using JavaScript. Below is the code I have used:
for(i=0;i<3;i++)
{
var d1 = document.createElement('div');
d1.id = "div" + i;
var l1 = document.createElement('label');
l1.innerHtml = "Hello";
d1.appendChild(l1);
var line1 = document.createElement('hr');
d1.appendChild(line1);
document.body.appendChild(d1);
}
Output:
Hello
Hello
Hello
Now I want to dynamically append event say onmouseover and onmouseout so that whenever I move cursor over a particular div element say div1, div2 or div3, that respective div section should change color (say blue) and when cursor moves out it should return to its original color.
I have tried working out but I am unable to retrieve the div id's which I have created dynamically.
You don't need to select by ID. You already have the element, so go ahead and add handlers to it.
for(i=0;i<3;i++) {
var d1 = document.createElement('div');
d1.id = "div" + i;
var l1 = document.createElement('label');
l1.innerHtml = "Hello";
d1.appendChild(l1);
var line1 = document.createElement('hr');
d1.appendChild(line1);
document.body.appendChild(d1);
d1.onmouseover = function() {
this.style.color = "#00F";
};
d1.onmouseout = function() {
this.style.color = "";
};
}
Just build your handlers and then assign them in the loop:
var mouseover = function() {
this.style.backgroundColor = '#AAF';
},
mouseout = function() {
this.style.backgroundColor = '';
};
for (i = 0; i < 3; i++) {
var d1 = document.createElement('div'),
l1 = document.createElement('label'),
line1 = document.createElement('hr');
d1.id = "div" + i;
l1.innerHtml = "Hello";
d1.appendChild(l1);
d1.appendChild(line1);
d1.onmouseover = mouseover;
d1.onmouseout = mouseout;
document.body.appendChild(d1);
}​
Demo at: http://jsfiddle.net/vfVCm/
If your elements are created dynamically it may be better to use event delegation and attach the event listener to the parent element instead of every element that you're dynamically creating. This way your listener will work even for elements created after it's attached. DOM events bubble, and an actual target of an event is available as event.target. Here is an example:
<div id="parent">
<p id="event1">A</p>
<p id="event2">B</p>
<p id="event3">C</p>
</div>
// call on DOMContentLoaded
document.getElementById("parent").addEventListener("click", function(e) {
if(e.target && e.target.nodeName.toLowerCase() == "p") {
alert("Clicked element " + e.target.id);
}
});
​
See jsfiddle.

Categories