Passing parameters in Javascript onClick event - javascript

I'm trying to pass a parameter in the onclick event. Below is a sample code:
<div id="div"></div>
<script language="javascript" type="text/javascript">
var div = document.getElementById('div');
for (var i = 0; i < 10; i++) {
var link = document.createElement('a');
link.setAttribute('href', '#');
link.innerHTML = i + '';
link.onclick= function() { onClickLink(i+'');};
div.appendChild(link);
div.appendChild(document.createElement('BR'));
}
function onClickLink(text) {
alert('Link ' + text + ' clicked');
return false;
}
</script>
However whenever I click on any of the links the alert always shows 'Link 10 clicked'!
Can anyone tell me what I'm doing wrong?
Thanks

This happens because the i propagates up the scope once the function is invoked. You can avoid this issue using a closure.
for (var i = 0; i < 10; i++) {
var link = document.createElement('a');
link.setAttribute('href', '#');
link.innerHTML = i + '';
link.onclick = (function() {
var currentI = i;
return function() {
onClickLink(currentI + '');
}
})();
div.appendChild(link);
div.appendChild(document.createElement('BR'));
}
Or if you want more concise syntax, I suggest you use Nick Craver's solution.

This is happening because they're all referencing the same i variable, which is changing every loop, and left as 10 at the end of the loop. You can resolve it using a closure like this:
link.onclick = function(j) { return function() { onClickLink(j+''); }; }(i);
You can give it a try here
Or, make this be the link you clicked in that handler, like this:
link.onclick = function(j) { return function() { onClickLink.call(this, j); }; }(i);
You can try that version here

link.onclick = function() { onClickLink(i+''); };
Is a closure and stores a reference to the variable i, not the value that i holds when the function is created. One solution would be to wrap the contents of the for loop in a function do this:
for (var i = 0; i < 10; i++) (function(i) {
var link = document.createElement('a');
link.setAttribute('href', '#');
link.innerHTML = i + '';
link.onclick= function() { onClickLink(i+'');};
div.appendChild(link);
div.appendChild(document.createElement('BR'));
}(i));

Try this:
<div id="div"></div>
<script language="javascript" type="text/javascript">
var div = document.getElementById('div');
for (var i = 0; i < 10; i++) {
var f = function() {
var link = document.createElement('a');
var j = i; // this j is scoped to our anonymous function
// while i is scoped outside the anonymous function,
// getting incremented by the for loop
link.setAttribute('href', '#');
link.innerHTML = j + '';
link.onclick= function() { onClickLink(j+'');};
div.appendChild(link);
div.appendChild(document.createElement('br')); // lower case BR, please!
}(); // call the function immediately
}
function onClickLink(text) {
alert('Link ' + text + ' clicked');
return false;
}
</script>

or you could use this line:
link.setAttribute('onClick', 'onClickLink('+i+')');
instead of this one:
link.onclick= function() { onClickLink(i+'');};

Another simple way ( might not be the best practice) but works like charm. Build the HTML tag of your element(hyperLink or Button) dynamically with javascript, and can pass multiple parameters as well.
// variable to hold the HTML Tags
var ProductButtonsHTML ="";
//Run your loop
for (var i = 0; i < ProductsJson.length; i++){
// Build the <input> Tag with the required parameters for Onclick call. Use double quotes.
ProductButtonsHTML += " <input type='button' value='" + ProductsJson[i].DisplayName + "'
onclick = \"BuildCartById('" + ProductsJson[i].SKU+ "'," + ProductsJson[i].Id + ")\"></input> ";
}
// Add the Tags to the Div's innerHTML.
document.getElementById("divProductsMenuStrip").innerHTML = ProductButtonsHTML;

It is probably better to create a dedicated function to create the link so you can avoid creating two anonymous functions. Thus:
<div id="div"></div>
<script>
function getLink(id)
{
var link = document.createElement('a');
link.setAttribute('href', '#');
link.innerHTML = id;
link.onclick = function()
{
onClickLink(id);
};
link.style.display = 'block';
return link;
}
var div = document.getElementById('div');
for (var i = 0; i < 10; i += 1)
{
div.appendChild(getLink(i.toString()));
}
</script>
Although in both cases you end up with two functions, I just think it is better to wrap it in a function that is semantically easier to comprehend.

onclick vs addEventListener. A matter of preference perhaps (where IE>9).
// Using closures
function onClickLink(e, index) {
alert(index);
return false;
}
var div = document.getElementById('div');
for (var i = 0; i < 10; i++) {
var link = document.createElement('a');
link.setAttribute('href', '#');
link.innerHTML = i + '';
link.addEventListener('click', (function(e) {
var index = i;
return function(e) {
return onClickLink(e, index);
}
})(), false);
div.appendChild(link);
div.appendChild(document.createElement('BR'));
}
How abut just using a plain data-* attribute, not as cool as a closure, but..
function onClickLink(e) {
alert(e.target.getAttribute('data-index'));
return false;
}
var div = document.getElementById('div');
for (var i = 0; i < 10; i++) {
var link = document.createElement('a');
link.setAttribute('href', '#');
link.setAttribute('data-index', i);
link.innerHTML = i + ' Hello';
link.addEventListener('click', onClickLink, false);
div.appendChild(link);
div.appendChild(document.createElement('BR'));
}

This will work from JS without coupling to HTML:
document.getElementById("click-button").onclick = onClickFunction;
function onClickFunction()
{
return functionWithArguments('You clicked the button!');
}
function functionWithArguments(text) {
document.getElementById("some-div").innerText = text;
}

Related

How scope works in a for loop?

When I created 5 buttons in a loop and click the value of i is always 6
function createButtons() {
for (var i = 1; i <= 5; i++) {
var body = document.getElementsByTagName("BODY")[0];
var button = document.createElement("BUTTON");
button.innerHTML = 'Button ' + i;
(function(num) {
button.onclick = function() {
alert('This is button ' + num);
}
})(i);
body.appendChild(button);
}
}
but when I change the scope of i to block scope(using IIFE or let keyword) it gives the right value of i. How does it working under the hood of javascript?
I have seperated your functions into incorrect and correct one.
The incorrect version is what you're asking. The correct version is what you've already figured out but don't know why it work.
In the incorrect version, the value of i will always be updated to the most recent value because i belongs to the createButtons function and is shared with all onclick handler, and it is changing with the loop.
In the correct version, the value of i is given to the IIFE as num, and num belongs to the IIFE and not to createButtons.
Because of that, num is fixed because a new num is created for every loop thus is not shared with the other onclick handler.
Why? It is how closure works in JavaScript.
Read this for deeper understanding on JavaScript closure.
function createButtons_incorrect() {
for (var i = 1; i <= 5; i++) {
var body = document.getElementsByTagName("BODY")[0];
var button = document.createElement("BUTTON");
button.innerHTML = 'Bad ' + i;
button.onclick = function() {
alert('This is button ' + i);
}
body.appendChild(button);
}
}
function createButtons_correct() {
for (var i = 1; i <= 5; i++) {
var body = document.getElementsByTagName("BODY")[0];
var button = document.createElement("BUTTON");
button.innerHTML = 'Good ' + i;
(function(num){
button.onclick = function() {
alert('This is button ' + num);
}
})(i);
body.appendChild(button);
}
}
createButtons_incorrect();
document.body.appendChild(document.createElement('br'));
createButtons_correct();

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);
}

Add onfocus listener instead of onload

I have such script in js file wich I'm calling from jsp, and I need to add listener instead of "onload".
What important for me:
1) It must be pure js without jQuery or anything
2) Input tags would be created dynamically(Maybe this is important)
3) It must be external js file(<script src="<c:url value="/js/focus.js" />"></script>), but not the tag <script>function.....</script> inside jsp page
onload = function () {
var allInput = document.getElementsByTagName("input");
for (i = 0; i < allInput.length; i++) {
allInput[i].onfocus = showHint
}
};
function showHint() {
var hint = document.getElementById("hint");
hint.innerHTML = this.name + " " + this.value;
hint.style.display = "block";
};
The page can get focus before is't loaded. And if the page is not loaded your inputs don't exist so window.onfocus can't set allInput[i].onfocus. When the page is refreshed with the focus on the devtools the page gets a chance to create inputs before window.onfocus call.
Put your window.onfocus inside window.onload so that it is always called after the page is loaded. If you don't want to override window.onload use addEventListener instead:
addEventListener('load', function () {
addEventListener('focus', function() {
var allInput = document.getElementsByTagName("input")
for (i = 0; i < allInput.length; i++) {
allInput[i].addEventListener('focus', showHint)
}
}
})
function showHint() {
var hint = document.getElementById("hint")
hint.innerHTML = this.name + " " + this.value
hint.style.display = "block"
}
replace onload with onfocus
onfocus = function () {
var allInput = document.getElementsByTagName("input");
for (i = 0; i < allInput.length; i++) {
allInput[i].onfocus = showHint
}
};
That works for me
window.addEventListener('load', function () {
var allInput = document.getElementsByTagName("input");
for (i = 0; i < allInput.length; i++) {
allInput[i].onfocus = showHint
}
});
function showHint() {
var hint = document.getElementById("hint");
hint.innerHTML = this.name + " " + this.value;
hint.style.display = "block";
};

Stop output printing multiple times if function activated more than once

I'm activating a javascript function with a Jquery onclick button:
$('#on').click(function() {
var a = document.getElementsByTagName('a');
for (i = 0; i < a.length; i++) {
a[i].addEventListener('click', function() {
var span = document.createElement('span');
var text = document.createTextNode(this.innerHTML + " ");
span.appendChild(text);
document.getElementsByClassName('output')[0].appendChild(span);
})
}
});
The problem is if the button is clicked more than once the function will repeat more than once. In this case it will print the output multiple times. How can I modify the javascript function to only print one character per click?
Example:
http://jsfiddle.net/874Ljaq1/
Use the jQuery event binding method one
$('#on').one("click", function() {
var a = document.getElementsByTagName('a');
for (i = 0; i < a.length; i++) {
a[i].addEventListener('click', function() {
var span = document.createElement('span');
var text = document.createTextNode(this.innerHTML + " ");
span.appendChild(text);
document.getElementsByClassName('output')[0].appendChild(span);
})
}
});
You can use the jQuery .data() function to set a flag when the button has been clicked once, and only proceed if the flag is not set.
The code:
$('#on').click(function () {
// if we have a flag that indicates this button has been clicked before,
// don't do anything.
if ($(this).data('clicked'))
return;
$(this).data('clicked', true); // set the flag
var a = document.getElementsByTagName('a');
for (i = 0; i < a.length; i++) {
a[i].addEventListener('click', function () {
var span = document.createElement('span');
var text = document.createTextNode(this.innerHTML + " ");
span.appendChild(text);
document.getElementsByClassName('output')[0].appendChild(span);
})
}
});

Unable to define a click event on a href tag

I am trying to write a click event for an anchor tag in my tampermonkey script.
var contentTag = document.getElementsByTagName("pre")[0];
var fileContents = contentTag.innerHTML;
contentTag.innerHTML = "";
var lines = fileContents.split("\n");
window.alert("Number of lines:"+lines.length);
for(var i=0; i<20; i++) {
if(i!==15)
contentTag.innerHTML+=(lines[i]+"<br>");
else {
contentTag.innerHTML+=("<a id=link1>Click me</a>");
var link = document.getElementById('link1');
link.addEventListener("click", function() {
window.alert('I am clicked');
}, false);
}
}
The alert message never gets triggered when I click on the link in the page dispalyed, even though I have a a click event listener defined. What am I doing wrong here?
It's the way you're adding HTML, you're reappending the link when you do this in the next iteration.
link.innerHTML += something
So the event handler is lost, and you can actually prove that by adding the event handler to the last element instead.
If you do it the proper way, creating elements and appending them, it works fine
var contentTag = document.getElementsByTagName("pre")[0];
var fileContents = contentTag.innerHTML;
contentTag.innerHTML = "";
var lines = fileContents.split("\n");
for (var i = 0; i < 20; i++) {
if (i !== 15) {
var txt = document.createTextNode(lines[i] || ''),
br = document.createElement('br');
contentTag.appendChild(txt);
contentTag.appendChild(br);
} else {
var link = document.createElement('a');
link.id = 'link1';
link.innerHTML = 'Click me';
link.addEventListener("click", function () {
alert('clicked')
}, false);
contentTag.appendChild(link)
}
}
FIDDLE
Shoud be contentTag.innerHTML+=("<a id='link1'>Click me</a>");
Try this:
<script>
var contentTag = document.getElementsByTagName("pre")[0];
var fileContents = contentTag.innerHTML;
contentTag.innerHTML = "";
var lines = fileContents.split("\n");
window.alert("Number of lines:"+lines.length);
for(var i=0; i<20; i++) {
if(i!==15)
contentTag.innerHTML+=(lines[i]+"<br>");
else {
contentTag.innerHTML+=("<a id=link"+i+">Click me</a>");
var link = document.getElementById('link'+i);
var att=document.createAttribute('onclick');
att.value="alert('Clicked !')";
link.setAttributeNode(att);
}
}
</script>
Demo: http://jsfiddle.net/TmJ38/

Categories

Resources