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();
});
}
}
Related
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.
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;
}
}
}
My case:
var tds = document.getElementsByTagName("td");
for(i=0;i<tds.length;i++)
{
tds[i].onclick = function()
{
alert(i);
};
}
Expected outcome: Alert the number of TD.
However if there are 6 TDs, the returned value will always be the last value of "i". (6)
How could i make the "i" value to remain at it's value when added to the function?
Thanks!
http://jsfiddle.net/nuKEK/11/
You need to make a closure to capture the i value. Something like this
function createFunction(i){
return function(){
alert(i);
};
}
var tds = document.getElementsByTagName("td");
for(i=0;i<tds.length;i++){
tds[i].onclick = createFunction(i);
}
DEMO: http://jsfiddle.net/nuKEK/12/
You can pass i to another function in order to get its value rather than a reference to it. In javascript, numbers are passed by value.
tds[i].onclick = (function(x) {
return function() {
alert(x); // alerting x, i's value
};
})(i); // passing i as parameter x
If that self-executing anonymous function looks a little hairy in the context of your loop, you could try Array.prototype.forEach() instead:
[].forEach.call(document.getElementsByTagName("td"), function(td, i) {
td.onclick = function() {
alert(i);
};
});
[edit] Have a look at these options and their performance.
This is one of the common mistakes in Javascript in that it is not block scoped like most languages. It is function-scoped.
You need to create a closure around the onclick to achieve this.
for (i = 0; i < tds.length; i++) {
(function (index) {
tds[index].onclick = function() {
alert(index);
};
})(i);
}
var tds = document.getElementsByTagName("td");
for(i=0;i<tds.length;i++)
{
addClick(tds, i);
}
function addClick(where, i) {
where[i].onclick = function()
{
alert(i);
};
}
You will have to force the value into a scope that will still exist when the callback is fired.
for(i=0;i<tds.length;i++){
(function(){
var _i = i;
tds[i].onclick = function(){
alert(_i);
};
})();
}
Otherwise, the value for i will always be the last index
I have a DownDownButton that I've populated from an array containing project names and ids. The list shows the project name, but I'd like to get the project id. The variable "projects" looks like this:
[Object { name="Project A", id="1325"}, Object { name="Project B", id="5241"}, Object { name="Project C", id="3224"}]
This code creates the MenuItem for the button correctly, but how do I set the variable projId in the onClick event?
for (i = 0; i < projects.length; i++) {
menuProjects.addChild(new MenuItem({
label: projects[i].name,
onClick: function () {
projId = ?;
}
}));
}
I've tried using "projId= projects[i].id;", but that gives me an error since i is now 3. What's the correct syntax to do this?
-- Edit --
This is how I got it to work using both cookie's and Merrick's answers.
for (i = 0; i < projects.length; i++)
(function (x) {
menuProjects.addChild(new MenuItem({
label: projects[i].name,
onClick: function () {
projId = projects[x].id;
}
}));
} (i));
Since, the onClick callback is async and javascript is functionally scoped i will be hoisted and by the time the click event happens i will be the last value of i. To maintain the scope you can simply leverage a IIFE to properly scope i.
// Block scoping will all lazily evaluate to 10
for (var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 100);
}
// Block scoping will capture the current value for each function invocation
for (var i = 0; i < 10; i++) (function(i) {
setTimeout(function() {
console.log(i);
}, 100);
})(i)
Here is an example: http://jsbin.com/eyeqiy/1/edit
I don't know if it is the correct/best way to do it, but you could create a closure to give each iteration a new variable scope like so:
for (i = 0; i < projects.length; i++) {
(function(x) {
menuProjects.addChild(new MenuItem({
label: projects[i].name,
onClick: function () {
projId = x;
}
}));
}(i));
}
This creates an anonymous function (with its own scope) that is immediatly evaluated.
on a website i want to do this: (simplified)
myHandlers = new Array();
for(var i = 0; i < 7; i++) {
myHandlers.push(new Handler({
handlerName: 'myHandler'+i, // works, e.g. ->myHandler1, 2, 3 etc.
handlerFunc: function(bla) { /*...*/ alert(i); } // doesn't work,all return 7
}
}
I could set the counter as another attribute of my Handler (which would copy the current value) and use it inside my function, but I guess, there is also a way to actually copy this value, no?
When handlerFunc is called, the i inside the function refers to the i of the for loop. But that i does probably not have the same value any more.
Use a closure to bind the current value of i in the scope of an anonymous function:
handlerFunc: (function(i) { return function(bla) { /*...*/ alert(i); }; })(i)
Here an anonymous function (function(i) { … })(i) is used and called immediately. This function binds the value of i of the for loop to the local i. That i is then independent from the i of the for loop.
var myHandlers = new Array();
for (var i = 0; i < 7; i++) {
myHandlers.push(new Handler({
handlerName: 'myHandler'+i, // works, e.g. ->myHandler1, 2, 3 etc.
handlerFunc:
(function(i) {
return function(blah) {
alert(i)
}
})(i)
}))
}
Use a closure to bind the i so the value stays intact
In your example, i in the functions is the same variable as i outside the functions. As i is incremented in the loop, so is it incremented within the functions. As a result, if the functions are called after the loop has finished, they will all alert "7".
You need to create a new variable with appropriate scope and copy the value of i into it.
Something like this would create the desired effect.
...
var pushHandler = function(i) {
myHandlers.push(new Handler({
handlerName: 'myHandler'+i, // works, e.g. ->myHandler1, 2, 3 etc.
handlerFunc: function(bla) { /*...*/ alert(i); } // doesn't work,all return 7
}
}
...
for(var i = 0; i < 7; i++) {
pushHandler(i);
}
...