Access sibling of an attribute within an object, onclick javascript - javascript

I'm creating 5 objects, each one with a div attached as source attribute, at which onclick should open the relative html page in an iframe.
Need to access the simple value of i==[i].num for each element on onclick function, so to select actor[i].num from within actor[i].source, thought it should be simple.
// html
<div id="op0" class="op"><img src="gfx/0.svg"></div>
<div id="op1" class="op"><img src="gfx/1.svg"></div>
<div id="op2" class="op"><img src="gfx/2.svg"></div>
<div id="op3" class="op"><img src="gfx/3.svg"></div>
<div id="op4" class="op"><img src="gfx/4.svg"></div>
// js
var i=0;
let actor = {};
for (i = 0; i < 5; i++) {
actor[i] = {
num: i,
source: document.getElementById('op'+[i]),
clicked: 0
}
actor[i].source.addEventListener( 'click', function() {
console.log(this) // the source attribute <div>
console.log(this.num) // undefined
console.log(parent.num) // undefined
console.log(actor[i].num) // TypeError
clickActor(i) // returns 5 at the time of click
});
}
function clickActor(num){
if (actor[num].open == 0){
actor[num].source.classList.add("scaleUp");
document.getElementById("iFrame").src = "op"+num+".html";
actor[num].open = 1;
// div to move and scale
} else {
actor[num].source.classList.remove("scaleUp");
document.getElementById("iFrame").src = "";
document.getElementById("iFrame").style.pointerEvents = "none";
actor[num].open = 0;
// div to move and scale back
}
}

var variables are function scopes, so your i variable is being reused over and over again in your loop, so that when you actually click the button, i will be the last number of the loop. You can bypass this by simply creating a new function scope by inserting a self-executing function in place of your callback function.
actor[i].source.addEventListener( 'click', (function(i) {
return (function() {
console.log(actor[i].num) // this is should print out the object assign above it
clickActor(i) // i is now the index of the actor
});
})(i));

Related

Div element undefined for addEventListener inside loop

Here is the HTML and JS code:
let sides = document.getElementsByClassName("sides");
for (var i=0;i<sides.length;i++){
console.log(sides[i].innerHTML); // top left
sides[i].addEventListener("click", function(){
console.log(sides[i]); //Undefined
})
}
<div class="sides">top</div>
<div class="sides">left</div>
When I console.log(sides[i]) I'm getting the div elements but when I'm adding the addEventListener it's showing undefined.
Please someone help.
Change sides[i] to this so that it can refer to the clicked element itself. The reason your solution doesn't work is because your variable i gets incremented to 2 after the last iteration because of i++. So when the event listener is triggered, sides[i] is now sides[2]. Another way is to instantiate the i using let as let i = 0, then you can access sides[i]
<div class="sides">top</div>
<div class="sides">left</div>
<script>
let sides = document.getElementsByClassName("sides");
for (let i=0;i<sides.length;i++){
console.log(sides[i].innerHTML); // top left
sides[i].addEventListener("click", function(){
console.log(this);
console.log(sides[i])
})
}
</script>
You can also console.log e.target
<div class="sides">Top</div>
<div class="sides">Left</div>
<script>
let sides = document.getElementsByClassName("sides");
for (let i=0;i<sides.length;i++){
console.log(sides[i].innerHTML); // top left
sides[i].addEventListener("click", function(e) {
console.log(e.target);
})
}
</script>
You can also assign an onclick() method to the element like below, but inside of the function you would still refer to the element with this so that console.log() knows which element's innerHTML you are trying to print. In this case it's own.
let sides = document.getElementsByClassName("sides");
for (var i=0; i<sides.length; i++) {
console.log(sides[i].innerHTML); // top left
sides[i].onclick = function() {
console.log(this.innerHTML); // Not undefined
};
}
<div class="sides">top</div>
<div class="sides">left</div>
Or you could also create a function to handle the click event, in which case you would again use this to refer to the clicked element:
function someFunc() {
console.log(this.innerHTML); // Not undefined
}
let sides = document.getElementsByClassName("sides");
for (var i=0; i<sides.length; i++) {
console.log(sides[i].innerHTML); // top left
sides[i].addEventListener("click", someFunc);
}
<div class="sides">top</div>
<div class="sides">left</div>

Binding an event listener to multiple elements with the same class

I'm trying to apply the onclick event with JavaScript to the following elements:
<div class="abc">first</div>
<div class="abc">second</div>
<div class="abc">third</div>
If I click on the first element (with index [0]) then this works, but I
need this event applicable for all classes:
document.getElementsByClassName('abc')[0].onclick="function(){fun1();}";
function fun1(){
document.getElementsByClassName('abc').style.color="red";
}
.onclick does not expect to receive a string, and in fact you don't need an extra function at all.
However, to assign it to each element, use a loop, like I'm sure you must have learned about in a beginner tutorial.
var els = document.getElementsByClassName('abc');
for (var i = 0; i < els.length; i++) {
els[i].onclick = fun1;
}
function fun1() {
this.style.color = "red";
}
<div class="abc">first</div>
<div class="abc">second</div>
<div class="abc">third</div>
To expand on the solution provided by #rock star I added two small additions to the function. First it is better to add / reemove a class (with an associated style rule) to an element than directly applying the stylerule to the element.
Secondly - on the click event - this will now remove the red class (and therefore style) from the previously selected element and add it to the new element. This will allow only one element to be red at a time (in the original solution any element that was clicked would become red).
var els = document.getElementsByClassName('abc');
for (var i = 0; i < els.length; i++) {
els[i].onclick = fun1;
}
function fun1() {
var oldLink = document.getElementsByClassName('red')[0];
if(oldLink) {oldLink.classList.remove('red')};
this.classList.add('red');
}
.red {
color:red;
}
<div class="abc">first</div>
<div class="abc">second</div>
<div class="abc">third</div>
This works:
<body>
<div class="abc">first</div>
<div class="abc">second</div>
<div class="abc">third</div>
<script>
var elements = document.getElementsByClassName('abc');
for(var i = 0, max = elements.length; i < max; i += 1) {
var clickedElement = elements[i];
clickedElement.onclick=function (){
fun1(this);
};
}
function fun1(element){
element.style.color="red";
}
</script>
</body>

Using Mouseenter / MouseLeave to Change Div in JavaScript

I am trying to use an array index to allow a set of div IDs to change from one ID to another when the mouseenter and mouseleave functions are triggered.
I know there are other ways to do this - toggle, hover, or CSS hover. This is just learning for me, and I am very new.
The code below is commented, and the basic problem is related to why an array variable of "largeID" (or smallID) outputs the proper values, but trying to use an index value doesn't. For each for statement, I want the smallID[i] value to be replaced with the largeID[i] value when the mouse enters the div, but I don't want to write the code for each one, i.e. "largeID[1], largeID[2].
Thanks for any pointers!!
$(document).ready(function() {
var smallID = [];
var largeID = [];
var divList = document.getElementsByTagName('div')[1]; //get the second (radialMenu) div in the document
var radialDivList = divList.getElementsByTagName('div'); // get all divs under the second (radialMenu) div
for(var i = 0; i < radialDivList.length; i++) {
if (!radialDivList[i]) continue; //skip null, undefined and non-existent elements
if (!radialDivList.hasOwnProperty(i)) continue; //skip inherited properties
smallID[i] = radialDivList[i].id; //assign the list of four divs to the smallID array;
largeID[i] = smallID[i] + 'Full'; // add "Full" to each smallID element to make a largeID element
alert(smallID[i]); // alerts for smallID / largeID and smallID[i] / largeID[i]
alert(largeID[i]); // give rational and expected output
$('#' + smallID[i]).mouseenter(function () { //works for all four radial menu divs when mouse enters
//BUT largeID[i] is undefined
alert(largeID[i]); // undefined
alert(largeID); // gives expected output of full array
}).mouseleave(function () { //mouseleave function not working
});
}
The reason your largeID[i] is undefined in your mouseenter function is because the last value of i is remembered and used on your mouseenter events.
When you use a variable and it goes "out of scope" a closure is automatically created to allow the variable to still exist for the function that still needs it, and your mouseenter functions all reference the same variable.
Your for loop stops when i is more than the amount of divs you have using radialDivList.length. Every attempt to use i to reference an index in your array will now be out of bounds.
The first answer on this page explains it well:
JavaScript closure inside loops – simple practical example
I've modified your example to create a new function with it's own copy of "i". So when hovering over the first div i will equal 0, when hovering over the second div it will equal 1, etc.
$(document).ready(function() {
var smallID = [];
var largeID = [];
var divList = document.getElementsByTagName('div')[1]; //get the second (radialMenu) div in the document
var radialDivList = divList.getElementsByTagName('div'); // get all divs under the second (radialMenu) div
var funcs = [];
for (var i = 0; i < radialDivList.length; i++) {
if (!radialDivList[i]) continue; //skip null, undefined and non-existent elements
if (!radialDivList.hasOwnProperty(i)) continue; //skip inherited properties
smallID[i] = radialDivList[i].id; //assign the list of four divs to the smallID array;
largeID[i] = smallID[i] + 'Full'; // add "Full" to each smallID element to make a largeID element
alert(smallID[i]); // alerts for smallID / largeID and smallID[i] / largeID[i]
alert(largeID[i]); // give rational and expected output
funcs[i] = function(i) {
$('#' + smallID[i]).mouseenter(function() { //works for all four radial menu divs when mouse enters
//BUT largeID[i] is undefined
alert(largeID[i]); // undefined
alert(largeID); // gives expected output of full array
}).mouseleave(function() { //mouseleave function not working
});
}.bind(this, i);
}
for (var i = 0; i < funcs.length; i++) {
funcs[i]();
}
});
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<title>Example</title>
</head>
<body>
<div>
<div>
<div id="one" style="background:green;height:40px">
</div>
<div id="two" style="background:red;height:40px">
</div>
<div id="three" style="background:blue;height:40px">
</div>
</div>
</div>
</body>
</html>

How to use closures to create event listeners in a Javascript for loop?

HTML
<span class="char" id="0">?</span>
<span class="char" id="1">!</span>
<span class="char" id="2">"</span>
<span class="char" id="3">/</span>
<span class="char" id="4">%</span>
<span class="char" id="5">$</span>
...
JavaScript
var charElems = document.getElementsByClassName('char');
for (var i=0; i < charElems.length; i++) {
charElems[i].addEventListener('mouseover',function() {
(function(j) {mouseoverCheck(j);}(i));
});
}
I have a bunch (hundreds) of span elements with numbers as IDs (starting from 0 and incrementing by 1). What this loop is supposed to do is create mouseover event listeners for all the span elements (which all have a class of char). Once moused over, it should excecute the mouseoverCheck() function and pass in whatever i was when that event listener was created. So the 203rd event listener should pass in 203. But it does not. Right now, it passes in what I believe is the last value i was before the loop completed.
I was attempting to use an IIFE and closure to make sure each event listener got i's value at the time it was created, instead of it's value when the function is called. Clearly, I didn't do it correctly, but I am fairly certain closure is the key to my problem. Can anyone shed some light on how to do this properly? I thought I understood closure, but clearly I do not...
it doesn't work because
charElems[i].addEventListener('mouseover',function() {
(function(j) {mouseoverCheck(j);}(i));
});
addEventListener() is just assigning a handler and by the time that handler is called i will be 6.
you should return a handler from an IIFE
var charElems = document.getElementsByClassName('char');
for (var i=0; i < charElems.length; i++) {
charElems[i].addEventListener('mouseover', (function(temp) {
return function(){
var j = temp;
//mouseoverCheck(j);
console.log(temp);
}
}(i)));
}
Here is a fiddle: https://jsfiddle.net/qshnfv3q/
var charElems = document.getElementsByClassName('char');
for (var i = 0; i < charElems.length; i++) {
//close...
//charElems[i].addEventListener('mouseover',function() {
// (function(j) {mouseoverCheck(j);}(i));
//});
//like this
(function(el, x) {
el.addEventListener("mouseover", function() {
mouseoverCheck(x);
});
})(charElems[i], i)
}

AJAX: Applying effect to CSS class

I have a snippet of code that applies a highlighting effect to list items in a menu (due to the fact that the menu items are just POST), to give users feedback. I have created a second step to the menu and would like to apply it to any element with a class of .highlight. Can't get it to work though, here's my current code:
[deleted old code]
The obvious work-around is to create a new id (say, '#highlighter2) and just copy and paste the code. But I'm curious if there's a more efficient way to apply the effect to a class instead of ID?
UPDATE (here is my updated code):
The script above DOES work on the first ul. The second ul, which appears via jquery (perhaps that's the issue, it's initially set to hidden). Here's relevant HTML (sort of a lot to understand, but note the hidden second div. I think this might be the culprit. Like I said, first list works flawlessly, highlights and all. But the second list does nothing.)?
//Do something when the DOM is ready:
<script type="text/javascript">
$(document).ready(function() {
$('#foo li, #foo2 li').click(function() {
// do ajax stuff
$(this).siblings('li').removeClass('highlight');
$(this).addClass('highlight');
});
//When a link in div is clicked, do something:
$('#selectCompany a').click(function() {
//Fade in second box:
//Get id from clicked link:
var id = $(this).attr('id');
$.ajax({
type: 'POST',
url: 'getFileInfo.php',
data: {'id': id},
success: function(msg){
//everything echoed in your PHP-File will be in the 'msg' variable:
$('#selectCompanyUser').html(msg)
$('#selectCompanyUser').fadeIn(400);
}
});
});
});
</script>
<div id="selectCompany" class="panelNormal">
<ul id="foo">
<?
// see if any rows were returned
if (mysql_num_rows($membersresult) > 0) {
// yes
// print them one after another
while($row = mysql_fetch_object($membersresult)) {
echo "<li>"."".$row->company.""."</li>";
}
}
else {
// no
// print status message
echo "No rows found!";
}
// free result set memory
mysql_free_result($membersresult);
// close connection
mysql_close($link);
?>
</ul>
</div>
<!-- Second Box: initially hidden with CSS "display: none;" -->
<div id="selectCompanyUser" class="panelNormal" style="display: none;">
<div class="splitter"></div>
</div>
You could just create #highlighter2 and make your code block into a function that takes the ID value and then just call it twice:
function hookupHighlight(id) {
var context = document.getElementById(id);
var items = context.getElementsByTagName('li');
for (var i = 0; i < items.length; i++) {
items[i].addEventListener('click', function() {
// do AJAX stuff
// remove the "highlight" class from all list items
for (var j = 0; j < items.length; j++) {
var classname = items[j].className;
items[j].className = classname.replace(/\bhighlight\b/i, '');
}
// set the "highlight" class on the clicked item
this.className += ' highlight';
}, false);
}
}
hookupHighlight("highliter1");
hookupHighlight("highliter2");
jQuery would make this easier in a lot of ways as that entire block would collapse to this:
$("#highlighter1 li, #highlighter2 li").click(function() {
// do ajax stuff
$(this).siblings('li').removeClass('highlight');
$(this).addClass('highlight');
});
If any of the objects you want to click on are not initially present when you run this jQuery code, then you would have to use this instead:
$("#highlighter1 li, #highlighter2 li").live("click", function() {
// do ajax stuff
$(this).siblings('li').removeClass('highlight');
$(this).addClass('highlight');
});
change the replace in /highlight/ig, it works on http://jsfiddle.net/8RArn/
var context = document.getElementById('highlighter');
var items = context.getElementsByTagName('li');
for (var i = 0; i < items.length; i++) {
items[i].addEventListener('click', function() {
// do AJAX stuff
// remove the "highlight" class from all list items
for (var j = 0; j < items.length; j++) {
var classname = items[j].className;
items[j].className = classname.replace(/highlight/ig, '');
}
// set the "highlight" class on the clicked item
this.className += ' highlight';
}, false);
}
So all those guys that are saying just use jQuery are handing out bad advice. It might be a quick fix for now, but its no replacement for actually learning Javascript.
There is a very powerful feature in Javascript called closures that will solve this problem for you in a jiffy:
var addTheListeners = function (its) {
var itemPtr;
var listener = function () {
// do AJAX stuff
// just need to visit one item now
if (itemPtr) {
var classname = itemPtr.className;
itemPtr.className = classname.replace(/\bhighlight\b/i, '');
}
// set the "highlight" class on the clicked item
this.className += ' highlight';
itemPtr = this;
}
for (var i = 0; i < its.length; i++) {
its[i].addEventListener ('click', listener, false);
}
}
and then:
var context = document.getElementById ('highlighter');
var items = context.getElementsByTagName ('li');
addTheListeners (items);
And you can call add the listeners for distinct sets of doc elements as many times as you want.
addTheListeners works by defining one var to store the list's currently selected item each time it is called and then all of the listener functions defined below it have shared access to this variable even after addTheListeners has returned (this is the closure part).
This code is also much more efficient than yours for two reasons:
You no longer iterate through all the items just to remove a class from one of them
You aren't defining functions inside of a for loop (you should never do this, not only for efficiency reasons but one day you are going to be tempted to use that i variable and its going to cause you some problems because of the closures thing I mentioned above)

Categories

Resources