Is it possible to use the current value of a variable when defining a javascript function?
In the following code I'd like to add a click function to an array of divs, each with a different value for i, ie. the first div should call setCurrentStyleIndex(0). At the moment they all call whatever the value of i is at the time of the call.
Sorry if this is dumb question.
Step3.populateStyleMenu = function() {
var stylePopup = $("#stylePopup");
for(var i = 0; i < Step3.fontStyles.length; i++) {
var div = $('<div>'+Step3.fontStyles[i][0]+'</div>');
div.css({
'font-weight': Step3.fontStyles[i][1],
'font-style': Step3.fontStyles[i][2],
})
div.data('style', Step3.fontStyles[i]);
div.addClass('personalizePopupItem personalizeStyleItem');
div.click(function() {
setCurrentStyleIndex(i);
});
stylePopup.append(div);
}
}
try to create a closure (not tested):
(function (i) {
div.click(function() {
setCurrentStyleIndex(i);
});
})(i);
Yup, you can do the below:
div.click((function() {
var inx = i;
return function() {
setCurrentStyleIndex(inx);
}
})());
Related
I have the following code and I dont know why it is returning trackingIds[i] as undefined in the View and Click function... I want to fill an array and the code should go through each array index and checks if element is hovered or clicked. el_id and trackingIds[i] in the first function return the correct values. I would appreciate any help because I cant seem to figure this out.
jQuery(document).ready(function(){
var trackingIds = ["elementid"];
for(i=0; i<trackingIds.length; i++){
var el_id = jQuery('#'+trackingIds[i]);
console.log(el_id);
console.log(trackingIds[i]);
el_id.click(function() { Click(trackingIds[i]);});
el_id.mouseover(function() { View(trackingIds[i]);});
}
});
function Click(a) {
//do stuff...
console.log("Click was called from:"+a);
}
function View(b){
// do stuff..
console.log("View was called from:"+b)
}
You need to use let in your for-loop.
jQuery(document).ready(function() {
var trackingIds = ["elementid"];
for (let i = 0; i < trackingIds.length; i++) {
var el_id = jQuery('#' + trackingIds[i]);
//console.log(el_id);
//console.log(trackingIds[i]);
el_id.click(function() {
Click(trackingIds[i]);
});
el_id.mouseover(function() {
View(trackingIds[i]);
});
}
});
function Click(a) {
//do stuff...
console.log("Click was called from: " + a);
}
function View(b) {
// do stuff..
console.log("View was called from: " + b)
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h1 id='elementid'>Click me!</h1>
Resource
let
The let statement declares a block scope local variable, optionally initializing it to a value.
The variable my_sound is declared in the first, outer function. So, I should be able to use it in the nested function. However the mouseout event produces no result. What am I doing wrong? Thanks for any help.
$(document).ready(function () {
var starting_pics = ["CN.gif", "EN.gif", "GN.gif"];
var starting_sounds = ["CN.mp3", "EN.mp3", "GN.mp3"];
var i = 0;
for (i = 0; i < starting_pics.length; i++) {
$("<img/>").attr("src", "images/" + starting_pics[i]).appendTo("#main").addClass("pics");
}
$("#main").on("click", ".pics", function () {
var i = $(this).index();
var my_sound =($("<audio/>").attr("src", "audio/" + starting_sounds[i])).load().get(0).play();
$("#main").on("mouseout", ".pics", function () {
$("my_sound").animate({ volume: 0 }, 1000);
});
});
});
The problem is probably that .play() doesn't return a jQuery object (or anything, for that matter, hence undefined).
Additionally, as the other comments have said, you don't want $('my_sound').whatever but rather just my_sound.whatever if it were a jQuery object, which it is not. So maybe you could try
var $my_sound = $("<audio />").attr("suchandsuch","etc");
$my_sound.load().get(0).play();
$my_sound.whatever();
I have a hash called options. The problem that I'm facing is that options['beforeOpen'] might already be a function, in which case I don't want to overwrite it. I'd like to instead call it then call another function that needs to be called every time
In this example the method that needs to be called every time is methodThatINeedToDo. I thought the code below would accomplish this but it's not working as I expected.
function methodThatINeedToDo(){alert('maintenance');}
var options = {beforeOpen: function(){alert('first');}}
if(typeof options['beforeOpen'] == "function"){
options['beforeOpen'] = function(){options['beforeOpen'].call(); methodThatINeedToAddToDo();}
} else {
options['beforeOpen'] = methodThatINeedToDo;
}
The problem is that within the function you're defining to override options['beforeOpen'], you're using options['beforeOpen'], which by that time has been overwritten!
You need to cache it and use the cached value within your new function:
var cachedBeforeOpen = options.beforeOpen;
if (typeof cachedBeforeOpen == "function") {
options.beforeOpen = function() {
cachedBeforeOpen.call();
methodThatINeedToDo();
};
} else {
options.beforeOpen = methodThatINeedToDo;
}
Simply always call methodThatINeedToDo, since you want to and in there check to see if you should call your options method:
function methodThatINeedToDo(){
options.beforeOpen && options.beforeOpen();
alert('maintenance');
}
That really smells like the wrong solution. Why not Publish/Subscribe pattern?
Here's a little example: http://jsfiddle.net/ajyQH/
$(function() {
var yourObj = { yourFct : [] };
$('#btn').click(function() {
yourObj.yourFct.push(function() {
$('#testibert').append($('<p>').text('hallo'));
});
});
$('#btn_exec').click(function() {
var len = yourObj.yourFct.length;
for(var i = 0; i < len; i++) {
yourObj.yourFct[i]();
}
});
});
var oldCall = options['beforeOpen'];
var newCall = function(){
oldCall();
methodThatINeedToAddToDo();
};
options['beforeOpen'] = newCall;
I tried debugging my code for like a few hour but I got nothing out of it. The issue is that it makes absolutely no sense on why it reports an error every time I tried to use document.forms[0][i] (i as the iterator) in the event listener but "this" satisfies the code.
//broken
var addListeners = function() {
var i;
var formFields = document.forms[0];
var formSubmit = formFields["submit"];
for (i = 0; i < formFields.length; i++) {
if (formFields[i] != formSubmit) {
formFields[i].onblur = (function () {
checkNonEmpty(formFields[i]);
});
}
}
};
//works
var addListeners = function() {
var i;
var formFields = document.forms[0];
var formSubmit = formFields["submit"];
for (i = 0; i < formFields.length; i++) {
if (formFields[i] != formSubmit) {
formFields[i].onblur = (function () {
checkNonEmpty(this);
});
}
}
};
Wouldn't "this" refer to document.forms[0][i]?... formFields references to document.forms[0]. However the exact same code (with "this" where formFields[i] is at) works just fine.
Here is the demo: http://jsfiddle.net/PbHwy/
Cranio's answer already contains the root of the matter. To get rid of this you can either include formFields[i] by using closures
var blurCallbackGenerator = function(element){
return function () {
checkNonEmpty(element);
};
};
formFields[i].onblur = blurCallbackGenerator(formFields[i]);
/* // dense version:
formFields[i].onblur = (function(element){
return function () {
checkNonEmpty(element);
};
})(formFields[i]);
*/
or simply using this.
See also:
MDN: Creating closures in loops: A common mistake
Because you define formFields in a scope outside (or better, different than) the event listener. When the event listener is called, it is called not in the addListeners function where you define formFields, but "independently", so the reference is lost and its value is undefined (but this works because it is not dependent on that scope).
The problem is that the variable i (referred to in each of your handlers) is the exact same variable in each of them, which by the time the loop has finished has value formFields.length+1 and is therefore wrong for all of them. Try this instead [note: the below used to say something VERY WRONG before I edited it -- thanks to Zeta for pointing out my mistake]:
var addListeners = function() {
var i;
var formFields = document.forms[0];
var formSubmit = formFields["submit"];
for (i = 0; i < formFields.length; i++) {
if (formFields[i] != formSubmit) {
formFields[i].onblur = (function(j) {
return (function () {
checkNonEmpty(formFields[j]);
})(i);
});
}
}
};
and you'll find it works (unless there's another bug that I haven't noticed).
If you can afford to support only Javascript 1.7 and above, you can instead write your old code but make your for look like this: for (let i=0; i<formFields.length; i++). But you quite possibly can't.
Just about everyone has ran into this specific issue:
function addLinks () {
for (var i=0, link; i<5; i++) {
link = document.createElement("a");
link.innerHTML = "Link " + i;
link.onclick = function () {
alert(i);
};
document.body.appendChild(link);
}
}
window.onload = addLinks;
The value of i after the loop is cached. A closure can be used to create a "live" reference:
function addLinks () {
for (var i=0, link; i<5; i++) {
link = document.createElement("a");
link.innerHTML = "Link " + i;
link.onclick = function (num) {
return function () {
alert(num);
};
}(i);
document.body.appendChild(link);
}
}
window.onload = addLinks;
We can also use self executing anon fns to create closures as well, etc..
My situation
The issue I'm facing is the same, but the context is a little different. Here's a failing example:
Hit 'add more lis' a few times and click 'hit me', which will alert -1. The value of maxIndex is cached. Relevant code:
var attached = false;
function actions() {
var elements = $('body').find('li');
var maxIndex = elements.length -1;
var cycle = {
next: function() {
alert( maxIndex );
}
};
if ( !attached ) {
$('#next').click(function(e) {
cycle.next();
});
attached = true;
}
};
actions();
$('#add').click(function(e) {
e.preventDefault();
$('<li/>').text('god').appendTo('body');
actions();
});
I've tried applying closures to no avail. ( see http://jsfiddle.net/Bfcus/23/ and down to /1/ ).
I don't want to use with statements or let statements/expressions to solve this. I know another way this is solvable is by changing maxIndex to be a property of a named object. Here's a working example doing it that way.
What I'm wondering is - is there a way of having it work while keeping the elements variable definition inside of the action function, and having the maxIndex variable defined in the same scope? Basically, can http://fiddle.jshell.net/r7Ekj/2/show/ be adjusted to work almost in the same manner as it is, without relying on with/let/object property?
Weird, sometimes creating a thorough question actually helps you solve it and I swear I didn't have it solved beforehand...
I just created the maxIndex variable outside of the actions scope and actually assign it inside.. this makes it work because it isn't defined in the same function scope and so the binding behaves differently.
http://fiddle.jshell.net/r7Ekj/9/
( How embarassing? )