Jquery setTimeout is making issues and not working properly in loop - javascript

I am working on below code snippet. Without setTimeOut(), its working perfect and displaying me the id in loaded(id) function. But with setTimeOut() this is not working properly.
var menuLink = document.getElementsByClassName("li_common_class");
for(var i = 0; i < 5; i++ ) {
var childElement = menuLink[i];
childElement.addEventListener('click',setTimeout(function(){
loaded(childElement.id);
},100), true);
}
function loaded(id){
alert(id);
}

Passing a function
You should be assigning an event handler, but instead you're invoking setTimeout immediately.
Pass a function to .addEventListener(), and use const to declare the variable.
var menuLink = document.getElementsByClassName("li_common_class");
for (var i = 0; i < 5; i++) {
const childElement = menuLink[i];
childElement.addEventListener('click', function() {
setTimeout(function() {
loaded(childElement.id);
}, 100)
}, true);
}
function loaded(id) {
alert(id);
}
So now that pass a function as the second argument to .addEventListener. That function gets assigned as the event handler for the child element. I also declared childElement using const, otherwise you'd always get the last value assigned to that variable instead of the respective value for each loop iteration.
Eliminating the need for a closure reference
However, this still isn't ideal. You really don't need childElement at all, since you have a reference to the element inside the handler already.
var menuLink = document.getElementsByClassName("li_common_class");
for (var i = 0; i < 5; i++) {
menuLink[i].addEventListener('click', function(event) {
var targ = event.currentTarget
setTimeout(function() {
loaded(targ.id);
}, 100)
}, true);
}
function loaded(id) {
alert(id);
}
See now that I added an event parameter to the handler function. This lets you grab the element to which the handler was bound.
We could have used this instead of event.currentTarget, but we actually lose that value inside the setTimeout callback. If we passed an arrow function to setTimeout, then the event handler's this would be reachable.
Reusing the function
But since there's no longer any need for a function to be associated with each iteration of the loop, we can actually move the function outside the loop, so that we're reusing it.
var menuLink = document.getElementsByClassName("li_common_class");
for (var i = 0; i < menuLink.length; i++) {
menuLink[i].addEventListener('click', handler, true);
}
function handler(event) {
var targ = event.currentTarget
setTimeout(function() {
loaded(targ.id);
}, 100)
}
function loaded(id) {
alert(id);
}
<ul>
<li class="li_common_class" id="foo">CLICK ME</li>
<li class="li_common_class" id="bar">CLICK ME</li>
<li class="li_common_class" id="baz">CLICK ME</li>
</ul>
ES6
The same code can be greatly shortened if we are to use ES6:
const menuLinkOnClick = event => setTimeout(() => alert(event.target.id), 100),
menuLinks = document.getElementsByClassName("li_common_class");
for (let menuLink of menuLinks) {
menuLink.addEventListener('click', menuLinkOnClick);
}

Try this:
var menuLink = document.getElementsByClassName("li_common_class");
for (var i = 0; i < 5; i++) {
var childElement = menuLink[i];
(function (ce) {
ce.addEventListener('click', function () {
setTimeout(function () {
loaded(ce.id);
}, 100);
}, true);
})(childElement);
}
function loaded(id) {
alert(id);
}

Try this:
var menuLink = document.getElementsByClassName("li_common_class");
for(i = 0; i < 5; i++) {
var childElement = menuLink[i];
childElement.addEventListener('click', function(){setTimeout(function(){loaded(childElement.id)},100)}, true);
}
function loaded(id){
alert(id);
}
You had a function (setTimeout) in the addEventListener() without having it inside "" or function(){}. You need it in one of those except if you want it to call a function woth no parameters by putting functionName with no quotes, or ().
Hope this works.

Related

Why can't I remove my event listener?

I have an issue with removeEventListener, it doesn't seem to work at all, I've seen some other questions on this site but I don't get it, can you help me?
displayImg() {
console.log('img')
for (var i = 1; i <= 4; i++) {
var line = "l"+i;
var position = 0;
var addDivLine = document.createElement('div');
addDivLine.className = 'line';
addDivLine.id = line;
document.getElementById('container').appendChild(addDivLine);
for (var j = 1; j <= 7; j++) {
var block = "b"+j;
var element = line+"-"+block;
var addDivBlock = document.createElement('div');
addDivBlock.className = 'block';
addDivBlock.id = element;
document.getElementById(line).appendChild(addDivBlock);
memory.addEvent(element);
};
};
showImage(event) {
event.preventDefault();
memory.clickedBlock++;
var block = event.target.id;
memory.removeEvent(block);
}
addEvent(id){
document.getElementById(id).addEventListener('click', function(){memory.showImage(event)});
},
removeEvent(id){
console.log("remove");
document.getElementById(id).removeEventListener('click', function(){memory.showImage(event)});
},
I am creating div elements then put an eventListener on them, I call the same function to remove the event, I use the same id, is there something that I forgot? I probably don't fully understand how it really works.
Thanks a lot!
In this two lines:
.addEventListener('click', function() { memory.showImage(event) });
and
.removeEventListener('click', function() { memory.showImage(event) });
function() { memory.showImage(event) } are two different functions. You need to provide reference to the same function in both cases in order to bind/unbind listener. Save it so some variable and use in both places:
.addEventListener('click', memory.showImage);
.removeEventListener('click', memory.showImage);
For example using directly memory.showImage will work properly as it's the same function in both cases.
The function looks like the same but its reference would be different. So, define the function in a scope where it's available for both function and use the reference in both case.
var callback = function(){memory.showImage(event)};
addEvent(id){
document.getElementById(id).addEventListener('click', callback);
}
removeEvent(id){
console.log("remove");
document.getElementById(id).removeEventListener('click', callback);
}

Iterating parameters for a function within a loop

JS Nooblord here, to give some context I have recently created my first JQuery based image slider to which I'm currently trying to generate a list of control buttons dynamically when the page loads.
I have succeeded thus far in creating the buttons but when it comes to writing the onclick function I'm having issues calling another function (with a parameter) inside a for loop.
I suck at explaining things but here is the code;
function addControls(){
var x = document.getElementById('slider').childElementCount;
for (var i = 0; i < x; i++) {
var ul = document.getElementById('slider-control');
var li = document.createElement("li");
var btn = document.createElement("Button");
btn.onclick = function() {
goto(i);
};
btn.appendChild(document.createTextNode(i + 1));
ul.appendChild(li);
li.appendChild(btn);
}
}
function goto(index){
alert(index);
}
Here is the JSFiddle preview.
What I expect is for each button to call the goto function with their respective position in the loop however every generated button with the onclick function uses the last index from the loop (4).
My initial thoughts are that the buttons are being rendered after the loops are finished and not within each iteration of the loop? also if anyone has any tips and alternatives for what I'm doing I would greatly appreciate that.
Thanks,
-Dodd
As commented on Mikelis Baltruks, you will have to use .bind.
You can use
goto.bind(null, i+1)
to map only index to it. If you wish to get the button as well, you can use
goto.bind(btn, i+1)
Sample JSFiddle
Bind
.bind is used to change the context of a function. Its syntax is
functionName.bind(context, argumentList);
This will create a reference of function with a newly binded context.
You can also use .apply for this task. Difference is, apply expects arguments as array and bind expect a comma separated list.
Note: both this function will just register events and not call it.
Reference
.bind
.apply
call() & apply() vs bind()
The problem is the reference to i.
for (var i = 0; i < x; i++) {
var btn = document.createElement("Button");
btn.onclick = function() {
goto(i);
// any variable reference will use the latest value
// so when `onclick` is actually run, the loop will have continued on to completion, with i == 4
};
}
You need a separate variable to reference for each onclick handler. You can do this by creating a closure:
function makeOnclick(i) {
// `i` is now a completely separate "variable",
// so it will not be updated while the loop continues running
return function() { goto(i); };
}
for (var i = 0; i < x; i++) {
var btn = document.createElement("Button");
btn.onclick = makeOnclick(i);
}
This can be done any number of ways, as others have shown. But this should explain why it's happening. Please ask any questions.
You need to create a closure in the loop, this should work:
var x = document.getElementById('slider').childElementCount;
for (var i = 0; i < x; i++) {
(function (i) {
var ul = document.getElementById('slider-control');
var li = document.createElement("li");
var btn = document.createElement("Button");
btn.onclick = function() {
goto(i);
};
btn.appendChild(document.createTextNode(i + 1));
ul.appendChild(li);
li.appendChild(btn);
})(i);
}
function goto(index) {
alert(index);
}
https://jsfiddle.net/g8qeq29e/6/
Or with ES6 let keyword;
function addControls(){
var x = document.getElementById('slider').childElementCount;
for (let i = 0; i < x; i++) {//change var to let here
var ul = document.getElementById('slider-control');
var li = document.createElement("li");
var btn = document.createElement("Button");
btn.onclick = function() {
goto(i);
};
btn.appendChild(document.createTextNode(i + 1));
ul.appendChild(li);
li.appendChild(btn);
}
}
function goto(index){
alert(index);
}

Pass iteration variable to click events inside for loop

I have the following piece of code:
var i = 0;
for (var anchor in window.globals.html.anchors) {
var value = window.globals.html.anchors[anchor];
value.addEventListener('click', function(i) {
var currData = window.globals.data[i],
data_clientId = 0;
if (currData.client_product_id !== undefined) {
data_clientId = currData.getAttribute('data_clientId');
} else if (currData.product_id !== undefined) {
data_clientId = currData.product_id;
}
Statistics.send(data_clientId);
window.open(window.globals.data[i].url, '_blank');
}(i));
i++;
}
Which means I want to access global array by interator inside the click event listener. If I don't pass no i to the click event I get the maximum number possible in each iteration, as i would be a global variable.
But right here it seems that all iterations of click events are invoke before clicking anything, onload.
Why is that so, what's wrong?
Looks like you are calling the handler whilst trying to add it to addEventListener. You need to wrap it in a function thus creating a closure that encapsulates your i variable.
var i = 0;
for (var anchor in window.globals.html.anchors) {
var value = window.globals.html.anchors[anchor];
value.addEventListener('click', (function(i) {
return function() {
var currData = window.globals.data[i],
data_clientId = 0;
if (currData.client_product_id !== undefined) {
data_clientId = currData.getAttribute('data_clientId');
} else if (currData.product_id !== undefined) {
data_clientId = currData.product_id;
}
Statistics.send(data_clientId);
window.open(window.globals.data[i].url, '_blank');
}
}(i)));
i++;
}
Notice now that the function you are passing to addEventListener is invoked immediately and returns a function itself where your variable i is scoped.

Canot get the href value

Hi I need to valid the href is empty or not on my page using javascript. I searched the site and found some example, but it didn't worked for me. I must miss something that I didn't notice. Would someone point me the good direction and my mistake. I got the error" Unable to get property 'getattribute' of undefined or null reference. The <a> element is like that <a name="playback" href=""> on html file.
Thanks in advance.
There is my code which is run on load event:
var anchors = document.getElementsByTagName("a");
for (var i = 0; i < anchors.length; i++)
{
anchors[i].onclick = function() {
if (anchors == null) {
alert('null');
}
else {
var link = anchors[i].getAttribute("href");
//var link= anchors[i].attributes['href'] this line doesn't work too.
}
}
}
}
In your code, the call to getAttribute is inside a closure (that is, a function defined "inline" without a name) that is assigned to the onlick event handler of the link. Therefore that code isn't execxuted right away - it doesn't run before the onclick handler triggers.
When the onclick header triggers, two things are passed to the callback function: the element on which the event was triggered is assigned to the this variable of the functions context - and the event itself is passed as first parameter. anchors however is undefined in the scope of that callback.
So, use either of those:
anchors[i].onclick = function () {
var link = this.getAtrribute("href");
}
 
anchors[i].onclick = function (event) {
var link = event.target.getAttribute("href");
}
You have got a scope problem.
The following code will output 3:
for (var i = 0; i < 3; i++) {
}
console.log(i); // 3
Similar to the example above your onclick is fired after the loop is done.
So i in your example would equal to anchors.length.
And anchors[anchors.length] === undefined.
To solve this problem you have to create a new scope.
For example you could use an Immediately-Invoked Function Expression (IIFE):
var anchors = document.getElementsByTagName("a");
for (var i = 0; i < anchors.length; i++)
{
(function(j){
anchors[j].onclick = function() {
if (anchors == null) {
alert('null');
}
else {
var link = anchors[j].getAttribute("href");
}
}
}
}(i));
}
You need to use closure if you want to do it this way since you are using the shared i variable which would have been having last value of iteration when your handler runs on click. But since you are looking at that particular anchor, try binding it with bind an event listener and access it using this.href:
You can use addEventListener and for older browser support attachEvent
var anchors = document.getElementsByTagName("a");
for (var i = 0; i < anchors.length; i++) {
anchors[i].addEventListener('click', function () {
var link = this.getAttribute("href");
})
};
Demo
Or :
var anchors = document.getElementsByTagName("a");
for (var i = 0; i < anchors.length; i++) {
anchors[i].onclick = getHandler(i);
}
function getHandler(i) {
return function () { //Now each of your handler has its own `i`
var link = anchors[i].getAttribute("href");
}
}
Demo
I have never seen getAttribute before so I performed some tests on it. It turns out that href and getAttribute("href") are quite different. Namely href is the absolute url and getAttribute("href") is the relative url to the page. See this fiddle.
The problem with your code is that the var is captured in the closure of onclick and when the onclick function runs the value will of i will be anchors.length.
Solution, Scratch that use the code from Johannes H. His is better
var anchors = document.getElementsByTagName("a");
for (var i = 0; i < anchors.length; i++) {
(function () {
var current = anchors[i]; //Capture the anchor element
current.onclick = function() {
var link = current.getAttribute("href");
};
} ());
}
See this w3 schools page for how to get the href attribute from anchor tags.
http://www.w3schools.com/jsref/prop_anchor_href.asp
var anchors = document.getElementsByTagName("a");
for (var i = 0; i < anchors.length; i++)
{
anchors[i].onclick = function() {
if (anchors == null) {
alert('null');
}
else {
var link = this.href;
}
}
}

Jquery Scope Issue

I can't figure out this scope issue:
var menuLinks = new Array("about.php", "contact.php");
function setClickListeners()
{
for(var i=0; i<menuItems.length; i++)
{
$("#" + menuItems[i]).click( function () {
window.alert(menuLinks[i]);
});
}
}
Notes: menuItems and menuLink is the same length. This code is stripped down to make understanding it easier.
The outcome of this code when an item is clicked is an alert "undefined". It should be the data from menuLinks.
Help!!!!
Frankie
for (var i=0; i < menuItems.length; i++) {
(function(i) {
$("#"+menuItems[i]).click(function() {
alert(menuLinks[i]);
});
}(i));
}
You need to make the current value of i local to your anonymous function in .click.
JavaScript only has function scope. So if you don't make i local then whenever you press click the value of i is the current value which in this case is menuItems.length - 1.
What your doing above is creating a new functional scope and passing the value of i into it so that the current value of i stays constant in that function scope. That way your click function picks up the constant value of i from the closure.
jslint
Let's over complicate the code and satisfy jslint.
var wrapper = function(i) {
$("#"+menuItems[i]).click(function() {
alert(menuLinks[i]);
});
};
for (var i=0; i < menuItems.length; i++) {
wrapper(i);
}
A cleaner code:
var menuLinks = new Array("about.php", "contact.php");
function setClickListeners()
{
$.each(menuLinks, function(i, element)
{
$("#" + menuItems[i]).click( function (e) {
alert(menuItems[i]);
e.preventDefault();
});
}
}

Categories

Resources