Javascript for loop and alert - javascript

I am looping through a list of links. I can correctly get the title attribute, and want it displayed onclick. When the page is loaded and when I click on a link, all of the link titles are alerted one by one. What am I doing wrong?
function prepareShowElement () {
var nav = document.getElementById('nav');
var links = nav.getElementsByTagName('a');
for (var i = 0; i < links.length; i++) {
links[i].onclick = alert(links[i].title);
}
}

What you were doing was actually running the alert function.
enclosing the whole thing in an anonymous function will only run it when it is clicked
for (var i = 0; i < links.length; i++) {
links[i].onclick = function () {
alert(this.title);
}
}

You are assigning the onclick to the return value of alert(links[i].title); which doesn't make any sense, since onclick is supposed to be a function.
What you want instead is somethig like onclick = function(){ alert('Hi'); };
But
Since you are using a variable i in that loop you need to create a local copy of it
onclick = function(){ alert(links[i].title); }; would just use the outer scope i and all your links would alert the same message.
To fix this you need to write a function that localizes i and returns a new function specific to each link's own onclick:
onclick = (function(i){ return function(e){ alert(links[i].title); }; })(i);
Final result:
function prepareShowElement () {
var nav = document.getElementById('nav');
var links = nav.getElementsByTagName('a');
for (var i = 0; i < links.length; i++) {
links[i].onclick = (function(i){ return function(e){ alert(links[i].title); }; })(i);
}
}

You can use jquery. To display title of the link on click.
$("#nav a").click(function() {
var title = $(this).attr('title');
alert(title);
});

links.forEach(function(link) {
link.onclick = function(event) {
alert(link.title);
};
}
Also note that your original solution suffered from this problem:
JavaScript closure inside loops – simple practical example
By passing in our iteration variable into a closure, we get to keep it. If we wrote the above using a for-loop, it would look like this:
// machinery needed to get the same effect as above
for (var i = 0; i < links.length; i++) {
(function(link){
link.onclick = function(event) {
alert(link.title);
}
})(links[i])
}
or
// machinery needed to get the same effect as above (version 2)
for (var i = 0; i < links.length; i++) {
(function(i){
links[i].onclick = function(event) {
alert(links[i].title);
}
})(i)
}

You need change .onclick for a eventlistener same:
function prepareShowElement () {
var nav = document.getElementById('nav');
var links = nav.getElementsByTagName('a');
for (var i = 0; i < links.length; i++) {
links[i].addEventListener('click',function() {
alert(links[i].title);
},false);
}
}

Related

How get next element after "this" - pure javascript

I have code:
var links = document.querySelectorAll('a[data-lightbox]');
for (var i = 0; i < links.length; i++) {
links[i].addEventListener('click', function() {
event.preventDefault();
var imgLink = this.getAttribute('href');
var imgTitle = this.getAttribute('title');
var dataLightbox= this.getAttribute('data-lightbox');
console.log(); //next element after "this." something like "links[i+1]" or i don't know...
}, false);
}
I want to get 'data-lightbox' attribute for next element which I clicked currently. How to do it?
Using a IIFE can do the trick to preserve the i scope
var links = document.querySelectorAll('a[data-lightbox]');
for (var i = 0; i < links.length; i++) {
(function(i){
links[i].addEventListener('click', function() {
event.preventDefault();
var imgLink = this.getAttribute('href');
var imgTitle = this.getAttribute('title');
var dataLightbox= this.getAttribute('data-lightbox');
console.log(links[i + 1]);
}, false);
})(i)
}
This is a scope issue.
You can use bind (which would fix the scope issue) for the onclick event binding,while this you can send i to the method and you can access the next element using i+1
check the following snippet
window.onload = function() {
var links = document.querySelectorAll('a[data-lightbox]');
for (var i = 0; i < links.length; i++) {
links[i].addEventListener('click', onclick.bind(links[i], i));
}
function onclick(i) {
var imgLink = this.getAttribute('href');
var imgTitle = this.getAttribute('title');
var dataLightbox = this.getAttribute('data-lightbox');
if(links[i+1]!=undefined){
var nextLightbox = links[i + 1].getAttribute('data-lightbox');
}
console.log(imgLink);
console.log(dataLightbox);
console.log(nextLightbox);
}
}
<a href="#" data-lightbox=10>link1</a>
<a href="#" data-lightbox=20>link2</a><a href="#" data-lightbox=30>link3</a><a href="#" data-lightbox=40>link4</a><a href="#" data-lightbox=50>link5</a>
Hope it helps
You can try to get the next element in the way you thought: links[i + 1], although the i is an unique hoisted variable by this loop. You can, however, re-generate this i in the loop body, using variable declaration of let (only supported in ES6+) or using a new function scope inside that loop.
let acts like we were in a new scope, but not. It won't affect the previous i in this example, it'll only replace its presence at the block statement.
var links = document.querySelectorAll('a[data-lightbox]');
for (var i = 0; i < links.length; i++) {
let i = i;
links[i].addEventListener('click', function() {
event.preventDefault();
var imgLink = this.getAttribute('href');
var imgTitle = this.getAttribute('title');
var dataLightbox= this.getAttribute('data-lightbox');
console.log(links[i + 1]);
}, false);
}
In addition to what others have mentioned, another way to go about this is using nextSibling on this.
var links = document.querySelectorAll('a[data-lightbox]');
for (var i = 0; i < links.length; i++) {
links[i].addEventListener('click', function() {
event.preventDefault();
var imgLink = this.getAttribute('href');
var imgTitle = this.getAttribute('title');
var dataLightbox= this.getAttribute('data-lightbox');
console.log(this.nextElementSibling);
}, false);
}

Setting onclick function to <li> element

I am trying to dynamically add onclick function to "li" tagged elements.
But the event does not fires.
Here is my code:
var arrSideNavButtons = [];
var sideNavLi = document.getElementsByClassName('side-nav')[0].getElementsByTagName('li');
var arrayOfSceneAudios = [scene1Audio, scene2Audio,...];
for (var i = 0; i < sideNavLi.length; i++) {
sideNavLi[i].onclick = function() {
arrayOfSceneAudios[i].play();
}
arrSideNavButtons.push(sideNavLi[i]);
}
Is it possible to code it this way?
If yes, what is my mistake?
Thanks a lot.
Wrap your onclick handler in a closure, else it only get assigned to the last elem in the loop:
for (var i = 0; i < sideNavLi.length; i++) {
(function(i) {
sideNavLi[i].onclick = function() {
arrayOfSceneAudios[i].play();
}
arrSideNavButtons.push(sideNavLi[i]);
})(i)
}
I think it's better to reuse one single function, instead of creating a new one at each iteration:
var arrSideNavButtons = [],
sideNavLi = document.getElementsByClassName('side-nav')[0].getElementsByTagName('li'),
arrayOfSceneAudios = [scene1Audio, scene2Audio,...],
handler = function() {
this.sceneAudio.play();
};
for (var i = 0; i < sideNavLi.length; i++) {
sideNavLi[i].sceneAudio = arrayOfSceneAudios[i];
sideNavLi[i].onclick = handler;
arrSideNavButtons.push(sideNavLi[i]);
}

Changing classes in a menu with Javascript

I am looking to create a very simple functionality of clicking on a menu tab and it changes color to let you know what page you are on. I am a novice so please take it easy on me...lol
/Menu in php header file/
<ul class="tabs" id="tabs">
<li class="selected">Home</li>
<li class="inactive">Bio</li>
<li class="inactive">Photo</li>
<li class="inactive">Thank</li>
<li class="inactive">Contact</li>
</ul>
/*This is the JavaScript file*/
window.onload = initPage;
function initPage() {
var tabs = document.getElementById("tabs").getElementsByTagName("li");
for (var i=0; i<tabs.length; i++){
var links = tabs[i];
links.onclick = tabClicked;
}
}
function tabClicked(){
var tabId = this.id;
document.getElementById(tabId).classList.toggle("selected");
var tabs = document.getElementById("tabs").getElementsByTagName("li");
for (var i=0; i < tabs.length; i++){
var currentTab = tabs[i];
if (currentTab.id !== tabId){
currentTab.class = "selected";
} else {
currentTab.class = "inactive";
}
}
}
element.setAttribute("class", "className");
You are using ids in your code but you don't have provided it in your markup. so give ids to li elements and try this.
function tabClicked(){
var tabId = this.id;
document.getElementById(tabId).classList.toggle("selected");
var tabs = document.getElementById("tabs").getElementsByTagName("li");
for (var i=0; i < tabs.length; i++){
var currentTab = tabs[i];
if (currentTab.id !== tabId){
currentTab.className = "inactive";
} else {
currentTab.className= "selected";
}
}
}
JS Fiddle Demo
Store a reference to each of the list items.
Create a variable to keep track of the current tab.
In an onclick function for each element (or you could use one onclick and just use some conditions), change the class attribute of the element by using the setAttribute() method.
Like this:
function onFirstTabClick() {
clearSelected();
tabVariable1.setAttribute("class","some-new-class");
}
function() clearSelected() {
switch(currentSelectedTrackerVariable) {
case 1: tabVariable1.setAttribute("class","some-new-class");
break;
// Do this for the amount of tabs that you have.
}
}
Working FIDDLE Demo
There is no need to define functions globally. Write all them in one package. The code below, works correctly with your HTML markup.
<script>
window.onload = function () {
var tab = document.getElementById('tabs');
var lis = tab.getElementsByTagName('li');
for (var i = 0, l = lis.length; i < l; i++) {
lis[i].onclick = function () {
for (var j = 0; j < l; j++) {
lis[j]["className"] = "inactive";
}
this["className"] = "selected";
};
}
};
</script>
If you use jQuery, then tabClicked can run:
jQuery('.selected').removeClass('selected').addClass('inactive');
jQuery(this).removeClass('inactive').addClass('selected');

Variable in wrong scope (maybe needs a closure?)

I have the following code that is in need of a closure:
var numItems = document.getElementsByClassName('l').length;
for (var i = 0; i < numItems; i++) {
document.getElementsByClassName('l')[i].onclick = function (e){
preview(this.href, i);
};
}
What happens is that whenever an item is clicked, preview always the same number for i
I suspect what I need to do is
function indexClosure(i) {
return function(e) {
preview(this.href, i);
}
}
And assign the onclick's like this:
document.getElementsByClassName('l')[i].onclick = indexClosure(i);
But then this would no longer refer to my link... how is this problem solved?
Use closure to capture the counter of the cycle:
var numItems = document.getElementsByClassName('l').length;
for (var i = 0; i < numItems; i++) {
(function(i){
document.getElementsByClassName('l')[i].onclick = function (e){
preview(this.href, i);
};
}(i))
}
doesn't onclick pass in (sender, eventArgs) allowing you to access this through sender?

How does JavaScript closure work in this case?

How does JavaScript closure work in this case and to be more specific: what does the (i) at the end do?
for(var i = 0; i < 10; i++) {
(function(e) {
setTimeout(function() {
console.log(e);
}, 1000);
})(i);
}
Also I'm trying to implement it in my code, and it seems I don't get it right
for (var i=0; i < len; i++) {
var formID = document.forms["form-" + i];
$(formID).bind("submit", validate);
$(formID).bind("change", function(i){
var divI = '#ind-' + i;
$(divI).css("background-color","green");
})(i);
}
This is a pattern used to create local scope around a variable. If this wasn't used then every call to console.log(i) would log the value of i (10) after the for loop finished.
for(var i = 0; i < 10; i++) {
// create new function
(function(e) {
// log each counter after 1 second.
setTimeout(function() {
console.log(e);
}, 1000);
// execute it with the counter
})(i);
}
The above is the same as this.
function foobar(e) {
setTimeout(function() {
console.log(e);
}, 1000);
}
for (var i = 0; i < 10; i++) {
(
foobar
)(i);
}
The real problem here is creating functions in a loop. don't do it :)
Your code
for (var i=0; i < len; i++) {
var formID = document.forms["form-" + i];
$(formID).bind("submit", validate);
// create a full closure around the block of code
(function() {
$(formID).bind("change", function(i){
var divI = '#ind-' + i;
$(divI).css("background-color","green");
})//(i); Don't call (i) here because your just trying to execute the
// jQuery element as a function. You can't do this, you need to wrap
// an entire function around it.
})(i);
}
But that is wrong, you want to delegate this job to something else.
function makeGreen(form, i) {
$(form).change(function() {
$("#ind-"+i).css("background-color", "green");
});
}
for (var i=0; i < len; i++) {
var formID = document.forms["form-" + i];
$(formID).bind("submit", validate);
// call a helper function which binds the change handler to the correct i
makeGreen(formID, i);
}
If you want to get a bit clever you can get rid of these anonymous functions
function makeGreen() {
var divId = $(this).data("div-id");
$(divId).css("background-color", "green");
}
for (var i=0; i < len; i++) {
$(document.forms["form-" + i])
.bind("submit", validate)
// store i on the form element
.data("div-id", "#ind-" + i)
// use a single event handler that gets the divId out of the form.
.change(makeGreen);
}
Edit
( // contain the function we create.
function(parameterA) {
window.alert(parameterA);
}
) // this now points to a function
("alertMessage"); // call it as a function.
Is the same as
( // contain the window.alert function
window.alert
) // it now points to a function
("alertMessage"); // call it as a function
Although not a direct answer to the closure question, here is my take on the issue.
I would re-write the logic to avoid the need for a closure (as it seems overcomplicated for the requirements)
The fact that there is a pattern in the naming of the forms makes things really easy
$('form[id^="form-"]').submit(validate)
.change(function(){
var divI = '#ind-' + this.id.replace('form-','');
$(divI).css("background-color","green");
});
demo http://jsfiddle.net/gaby/q8WxV/

Categories

Resources