How does JavaScript closure work in this case? - javascript

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/

Related

How to create a function to be re-used for later within another function

I got this code:
$(document).ready(function(){
$(".nextForm").on('click',(function(){
//check criteria
if(selectedSlots.length < 1 ||$("#positionAppliedFor").get(0).value.length < 1 ||$("#maxAmountOfHours").get(0).value.length < 1){
//error messages and array
var errorForSlots= "<h5>Select at least one availability slot</h5>";
var errorForPosition = "<h5>Enter the position you wish to apply for<h5>";
var errorForHours = "<h5>Enter the amount of hours you would like to work<h5>";
var errors = [];
//add errors to array
if(selectedSlots.length < 1){errors.push(errorForSlots)};
if($("#positionAppliedFor").get(0).value.length < 1){errors.push(errorForPosition)};
if($("#maxAmountOfHours").get(0).value.length < 1){errors.push(errorForHours)};
//create message
var div = "<div id=\"sectionError\">";
if($("#sectionError").length > 0){$("#sectionError").html('')};
$(div).appendTo($(this).get(0).parentNode);
for(var i = 0; i < errors.length; i++){
$(errors[i]).appendTo($("#sectionError"));
console.log(errors[i]);}
$("</div>").appendTo($(this).get(0).parentNode);
} else {
$("#applicationDetails").slideUp();
$("#personalDetails").slideDown();
if($("#sectionError").length > 0){$("#sectionError").remove()};
}
console.log("function finished");
}));
It all works perfectly, however, I am trying to figure out how to create a function for
//create message
var div = "<div id=\"sectionError\">";
if($("#sectionError").length > 0){$("#sectionError").html('')};
$(div).appendTo($(this).get(0).parentNode);
for(var i = 0; i < errors.length; i++){
$(errors[i]).appendTo($("#sectionError"));
console.log(errors[i]);}
$("</div>").appendTo($(this).get(0).parentNode);
I am planning to re-use this for few other sections on my form and rather than copy/paste I would like to get some help on making my code tidier.
I did try:
function myFunction(){
//message code here
}
$(document).ready(function(){
$(".nextForm").on('click',(function(){
//check criteria
...
//add errors
...
//call func
myFunction();
(I also tried this.myFunction();)
...
}));
});
However, that ended up in TypeError and I don't know where to begin...
I am also concerned about the "this" in my message code so I am also not sure how to address that in my new function...
Admitedly I am a newbie at this and I do not exactly understand all the ins and outs, hopefully you will be able to help.
Maybe there is a better way of doing this?
Let me know your thought either way!
Thanks.
I have created a small reusable framework same as how jQuery is doing behind the scene to expose reusable functions. I didn't tested the append function properly,I just explaining how you can create your own reusable plugin to reuse across the project.
You can change the parameters and method name that you want to expose based on your functionality.
Also I would suggest you to move this code to a javascript file as a plugin and drag after the jquery script.
(function (global, $) {
//you can pass the jQuery object in to this IIFE
var DisplayError = function (elementId) {
return new DisplayError.init(elementId);
}
DisplayError.prototype = {
appendError: function (errors) {
var div = "<div id=\"" + this.elementId + " \">";
if ($(this.elementId).length > 0) {
$(this.elementId).html('')
};
$(div).appendTo($(this.elementId).get(0).parentNode);
for (var i = 0; i < errors.length; i++) {
$(errors[i]).appendTo($(this.elementId));
}
$("</div>").appendTo($(this.elementId).get(0).parentNode);
}
};
DisplayError.init = function (elementId) {
var self = this;
self.elementId = elementId;
}
DisplayError.init.prototype = DisplayError.prototype;
global.DisplayError = global.DisplayError = DisplayError;
}(window, jQuery));
You can write the code for clear the html directly in init function to ensure the element is clearing while initialize the instance itself.
You can invoke the method like below ,
var displayError=DisplayError("#sectionError")
displayError.appendError(["errorId"])
or
DisplayError("#sectionError").appendError(["errorId"])
Hope this helps
New Function
function generateMessage(arg1) {
//create message for each section
console.log("generating message");
var div = "<div id=\"sectionError\">";
if ($("#sectionError").length > 0) {
$("#sectionError").html('')
}
;$(div).appendTo($(arg1).parent());
for (var i = 0; i < errors.length; i++) {
$(errors[i]).appendTo($("#sectionError"));
console.log(errors[i]);
}
$("</div>").appendTo($(arg1).parent());
}
Changed old function
$(document).ready(function() {
$("#adbutnext").on('click', (function() {
//check criteria
if (selectedSlots.length < 1 || $("#positionAppliedFor").get(0).value.length < 1 || $("#maxAmountOfHours").get(0).value.length < 1) {
//error messages and array
var errorForSlots = "<h5>Select at least one availability slot</h5>";
var errorForPosition = "<h5>Enter the position you wish to apply for<h5>";
var errorForHours = "<h5>Enter the amount of hours you would like to work<h5>";
errors = [];
//add errors to array
if (selectedSlots.length < 1) {
errors.push(errorForSlots)
}
;if ($("#positionAppliedFor").get(0).value.length < 1) {
errors.push(errorForPosition)
}
;if ($("#maxAmountOfHours").get(0).value.length < 1) {
errors.push(errorForHours)
}
;
generateMessage(this);
} else {
$("#applicationDetails").slideUp();
$("#personalDetails").slideDown();
if ($("#sectionError").length > 0) {
$("#sectionError").remove()
}
;
}
console.log("function finished");
}
));
});

Event listeners and closures on HTML collection in for loop

The code is
//Logic which works when the desired element is clicked
function changeArtistPhotoAndBio(prop) {
var artistPhoto = document.getElementsByClassName("artist-photo")[0];
var artistBio = document.getElementsByClassName("artist-bio")[0];
var i = prop.getAttribute("src").indexOf(".jpg");
var photoName = prop.getAttribute("src").slice (0, i);
artistPhoto.style.background="url(" + photoName + "-large.jpg";
console.log("it happened");
};
//Setting listeners for the click event in the loop
var artists = document.getElementsByClassName("gallery")[0].getElementsByTagName("img");
for (var i = 0; i < artists.length; i++) {
artists[i].addEventListener("click", changeArtistPhotoAndBio(artists[i]));
}
And the console output is
7x it happened
And the event handler for the click function does not work. I've tried isolating handler in the closure, like this:
for (var i = 0; i < artists.length; i++) {(function(i) {
artists[i].addEventListener("click", changeArtistPhotoAndBio(artists[i]));
}(i))
}
but the output is still the same. So there are two questions:
1) Why does the console output contain results of seven handler invocations if I did not invoke the function, just set it as a handler?
2) How can I set handlers in the "for" loop for HTML collection?
You have to use closures:
var artists = document.getElementsByClassName("gallery")[0].getElementsByTagName("img");
for (var i = 0; i < artists.length; i++) {
artists[i].addEventListener("click", function(index) {
return function() {
//You can use index for the current clicked item index
// console.log(index);
var artistPhoto = document.getElementsByClassName("artist-photo")[0];
var artistBio = document.getElementsByClassName("artist-bio")[0];
var i = this.getAttribute("src").indexOf(".jpg");
var photoName = this.getAttribute("src").slice (0, i);
artistPhoto.style.background="url(" + photoName + "-large.jpg";
console.log("it happened");
}
}(i));
}
$('body *').on('mouseover',function(){console.log(this.tagName)});
$('body *') selects all elements within the body.

add div tags after specific amount of time repeatedly in javascript/jquery

I need to add append 10 div tags to another one but I need to wait random time before appending each one, something like this:
function start()
{
for (var i= 0; i< 10; i++)
{
var time = generateRandomWaitingTime();
sleep(time);
$('#div1').append('<div> div num' + i + '</div>');
}
}
I tried implementing my own sleep(time); function like here but it didn't work with me as it hangs any page event till the wait(time) finishes
setTimout() Seems to be exactly what you're looking for.
You should make use of setTimeout and a closure:
//closure
var addElement = function(i){
return function(){
$('#div1').append('<div> div num' + i + '</div>');
};
};
function start() {
for (var i = 0; i < 10; i++) {
var time = generateRandomWaitingTime();
setTimeout(addElement(i), time);
}
}
Living demo: http://jsfiddle.net/JaR34/
Update:
Living demo: http://jsfiddle.net/JaR34/1/
try this:
var elements = [1000,2000,3000,4000,5000,6000,7000];
function generateRandomWaitingTime(){
//will give you random index
var index = Math.floor((Math.random()*elements.length));
return (elements[index]);
}
function appendDiv(i){
var time = generateRandomWaitingTime();
setTimeout(function () {
$('#div1').append('<div> div num' + i + '</div>');
}, time);
}
for (var i = 0; i < 10; i++) {
appendDiv(i)
}
working fiddle here: http://jsfiddle.net/6neFA/1/

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?

Javascript for loop and alert

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

Categories

Resources