I wrote JS function and it must bind the buttons it generates depending on values in the array.
But it gives me the last value. I read that i had to use closure, I did, and I'm still not able to bind them right!
I'm still a beginner
I read about closure, I got the idea but still did not know what I'm missing
function addNewServices(newServicesArray){
var j=0; var x;
for (i in newServicesArray){
var html='';
html='<div style="width: 33%; float: leftt">'+newServicesArray[j].servicename+'</div>';
$("#main-menu").append(html);
$('#btn-'+newServicesArray[j].servicename).bind('click', function (){bindThis(j)});
j++;
}
var bindThis = function( j ) {
return function() {
alert(j); // gives 2 always
alert( newServicesArray[j].servicename );
};
};
}
you don't have to bind click in a loop... you can get the clicked refrence by $(this) in a function..
making it as simple as i can..
function addNewServices(newServicesArray){
var j=0;
for (i in newServicesArray){
var html='';
html='<div style="width: 33%; float: left">'+newServicesArray[j].servicename+'</div>';
$("#main-menu").append(html);
}
}
$(function(){
$(document).on('click','a[id^="btn-"]',function (){
var $this = $(this);
alert($this.attr('value'));
});
});
Because you have
function (){bindThis(j)}
Which gets called later when the value of j is 2.
You only need
bindThis(j)
which gets called with the different values
Closure is just the way function accesses variable from outer scope. The key word here is variable — variable may change, and if you access it afterwards (on click), you will access the later version of it.
So anyhow you need to store that association of j with jth button. Thanks to jQuery, bind method already have a facility just for this: its second parameter, eventData, is some user data that will be passed to event handler function.
So, changing this:
(..).bind('click',function (){bindThis(j)});
to this:
(..).bind('click', j, bindThis);
...should* work. Note that we don't need to create any wrapper-function. We simply pass bindThis function itself to bind, and tell bind that it will pass j to it when calling it.
(*) — not tested yet
Related
Kind of embarassed about this, but lets say I have this group of, 'child' elements within a parent element called iBlock, and within a function I'm adding an 'onclick' event to each child, like so...
someFunction(iBlock) {
var el, iTotal = iBlock.children.length;
for (var i= 0; i < iTotal; i++) {
el=iBlock.children[i];
el.onclick=function(event){myHandler(this, i)};
}
}
So given an onclick handler like this...
function myHandler(elem, n) {
alert("number is: " + n);
}
It was a little surprising that regardless of which child element I clicked on, the handler in this example would always display the one less than the total number of children. But I understand that the total - 1 was highest number the variable 'i' got to before the loop ended, and the handler isn't going to pass anything but the value of the variable at the time the event occurs. But understanding that doesn't help me answer these 2 questions...
If I really wanted the second argument in the onclick function to pass the value 'i' actually was at the time the onclick handler was
attached, what could I pass instead of the raw 'i' variable?
Since 'i' was declared by a 'var' keyword within the function that
added the handlers, why did it still even have a value (in this case
the total - 1), after installing function had returned? I would have been less surprised if the handler displayed 'undefined'. I realize
this is a 'scope of variables' question, but I'm sure its related.
On the first question, I've tried passing: i+'x', just to turn it into a string, hoping it would then generate a unique instance. That doesn't work either... and element 'clicked' on just triggers the handler shown to display '4x'. I've also tried creating a second variable, as a string, then extracting the numerical portion, and passing the final 'value' from the new variable to the onclick handler, like this...
someFunction(iBlock) {
var el, iTotal = iBlock.children.length;
for (var i= 0; i < iTotal; i++) {
el=iBlock.children[i];
var n= i+'x'; // make 'i' into a new longer string variable
var r = /\d+/; // regex, to capturei digits
n = n.match(r); // convert the string to only contain the digits
el.onclick=function(event){myHandler(this, n)};
}
}
No change. the handler still displays the total-1 on any element I click.
I have looked at similar posts on this subject such as Extract a number from a string (JavaScript), and I still don't understand a remedy to my first question.
PS (I've not tried creating the variable with 'let' instead of 'var', because I'm pretty committed to supporting certain older browsers). EDIT: I'd like to include IE-8 among those 'old browsers' if possible. I realize I can't always do so, but in this case I'd like to.
EDIT: I have found an interesting workaround, which is to add my own variable to each element, in the loop where I add handlers, like this...
someFunction(iBlock) {
var el, iTotal = iBlock.children.length;
for (var i= 0; i < iTotal; i++) {
el=iBlock.children[i];
el.onclick=function(event){myHandler(this, i)};
el.myIndex=i;
}
}
Then of course in my handler, where I'm passing 'this' anyway, I can properly recover the desired value of 'i' by examining 'this.myIndex. I've heard this is dangerous, because it might clash with a future DOM property.
But that seems 'kludge', so I'm still interested in the answers!
As stated in comments, the issue is that your inline click handler function is referencing the variable i in the closure, which gets mutated while looping. Each handler references that loop variable, so by the time the loop finishes, i is set to iTotal, and all handlers reference that same value.
This might be a good time to use the bind method of functions. What that does is return a new version of a function with the this context established and parameters pre-populated.
for (var i = 0; i < iTotal; i++) {
el = iBlock.children[i];
el.addEventListener('click', myHandler.bind(this, this, i));
}
Each el receives a unique function with the first two parameters pre-populated.
You need to create a closure using IIFE to capture iterator var.
More about closures
More about IIFE
function handleClick(element, n) {
alert("number is: " + n);
}
function iterateOverChildNOde(parentNode) {
var el = parentNode.children
var iTotal = parentNode.children.length;
for (var i= 0; i < iTotal; i++) {
(function(iterator){
el[i].onclick=function(event){handleClick(el[iterator], iterator)};
})(i)
}
}
iterateOverChildNOde(document.getElementById('parent'));
<div id="parent">
<div class="child">C</div>
<div class="child">L</div>
<div class="child">I</div>
<div class="child">I</div>
<div class="child">C</div>
<div class="child">L</div>
</div>
Also, you need to be aware that this is a bad practise. You should just attach event handler to parent element and then use e.target.
More about event delegation
function handleClick(element, n) {
alert("number is: " + n);
}
document.getElementById('parent').onclick=function(e){handleClick(e.target, Array.prototype.indexOf.call(this.children, e.target))}
<div id="parent">
<div class="child">C</div>
<div class="child">L</div>
<div class="child">I</div>
<div class="child">I</div>
<div class="child">C</div>
<div class="child">L</div>
</div>
el is not being assigned to anything. Try:
someFunction(iBlock) {
var iTotal = iBlock.children.length;
for (var i= 0; i < iTotal; i++) {
iBlock.children[i].onclick=function(event){myHandler(this, i)};
}
}
After careful consideration of all the answers offered, I'm going to stick with the solution I discovered and posted, in the final edit of my OP. Specifically, It is assigning a new attribute to each element as I'm adding my events...
someFunction(iBlock) {
var el, iTotal = iBlock.children.length;
for (var i= 0; i < iTotal; i++) {
el=iBlock.children[i];
el.onclick=function(event){myHandler(this)};
el.myIndex=i;
}
}
Here, I'm no longer even attempting to send the value of 'i' to the handler as a second arg, due the to problems noted. Yet for whatever reason, when the added element 'myIndex' is assigned to 'i', it retains the identifying value at the time of assignment. So when the handler is called it can be accessed via 'this.myIndex'. I don't fully understand why doing it this way causes 'i' to be passed by value rather than a reference / instance of itself, but it works.
No one has offered any reason NOT to do this, and it seems the least complex in terms of added code. I believe the only risk is choosing a name that clashes with some future 'standard' attribute. Maybe this is why I see suggestions tp call add on sttributes something like "data-myindex". But either way, it seems to work all IE platforms I've tried back to IE8, also Edge, Chrome and Firefox back to the last ones distributed for Windows-XP through the latest on Win-10. It works on Mobil Safari back to IOS-7, and all the android devices I'm able to test with. Still open to suggestions, but sometimes, as they say, "the simplest solution is the best"
I have a bit of HTML generated by PHP in the format of:
<div class=zoomButton>
<input type=hidden name=zoomURL value=*different per instance*>
</div>
I am trying to attach a listener (imageZoom(event, url)) to each of the class "zoomButton" elements, but call it with different arguments for each instance.
i.e.
var zoomButtonArray = document.getElementsByClassName('zoomButton');
for (i=0; i<zoomButtonArray.length; i++)
{
var zoomURL = zoomButtonArray[i].children[0].value;
zoomButtonArray[i].addEventListener("mousedown", function(){imageZoom(event,zoomURL);});
}
however it seems that zoomURL is always the value of the very last element. How can I change my code/approach so that the argument passed to the listener is the correct one, and not the last one in the "zoomButtonArray" array?
Thanks
You need to wrap the event listener in a closure:
function makeEventListenerForZoomURL(zoomURL) {
return function(event) {
imageZoom(event, zoomURL);
}
}
var zoomButtonArray = document.getElementsByClassName('zoomButton');
for (i=0; i<zoomButtonArray.length; i++)
{
zoomButtonArray[i].addEventListener(
"mousedown",
makeEventListenerForZoomURL(zoomButtonArray[i].children[0].value)
);
}
This can also be simplified using the ECMAScript5 forEach:
var zoomButtonArray = document.getElementsByClassName('zoomButton');
zoomButtonArray = Array.prototype.slice.call(zoomButtonArray, 0);
zoomButtonArray.forEach(function(node) {
node.addEventListener("mousedown", function(event) {
imageZoom(event node.children[0].value);
});
});
The reason is that each time the for loop executes a new function is created, this new scope references the variable i but i changes each time the loop iterates. So by the time the event listener runs it looks at the value of i only to find that it is the last value when the for loop ended. By using a closure described above the scope created is unique to each iteration of the loop so that when the event listener finally executes the value of the wrapped variable (zoomURL or node in the examples above) will not have changed.
Here is a good article explaining closures in for loops: http://trephine.org/t/index.php?title=JavaScript_loop_closures
I think you are missing quotes around attributes. I just added quotes and the tested at jsFiddle (Fiddle link in comments) and it's working see to console in developer tool. it is iterating through each element as desired. Console screen shot
So these two work fine:
$(document).on("click", "#_something", function(){
$('#vid').attr('src',video_config["something"].video);
});
$(document).on("click", "#_anotherthing", function(){
$('#vid').attr('src',video_config["anotherthing"].video);
});
However, something and nothing are properties of an object I made, so I attempted to do this:
for (var key in video_list){
$(document).on("click", "#_"+key, function(){
$('#vid').attr('src',video_list[key].video);
});
}
Which sort of messed it up, and set all the src values to the last video_list[key].video value I have. To rephrase, this assigned all of the src properties the same value.
How do I do this correctly without manually writing each of the event handlers?
This is because your Handler function captures the key variable which is scoped to the parent function. When your Handler executes, key has the last value.
The fix is to capture the current value at each iteration by using yet another function scope. Like this:
for (var k in video_list) {
function(key) {
// create your event handler here using key
}(k);
}
This is explained in this question that is basically the same as this one:
javascript closure in a for loop
In ES6 browsers, let being block scoped you can use it as a shortcut:
for (let k in video_list) {
let key = k;
// same code as your question goes here, using key.
}
Here's a simple way using one event handler, a class and a data attribute:
$(document).on("click", ".video", function(){
var key = $(this).data("key"); // in the element add data-key="xyz"
$('#vid').attr('src',video_list[key].video);
});
The quick and dirty hack:
for (var key in video_list){
(function(key){// create a new context, so not all handlers point the the same key
$(document).on("click", "#_"+key, function(){
$('#vidSrc').attr('src',video_list[key].video);
});
})(key);
}
The correct way:
$(document).on("click", ".some-new-class-you-just-defined", function() {
$(this).attr('src', video_list[$(this).attr('id').slice(1)].video);
});
EDIT: Add substring to the id. It's better to have some sort of lookup mechanism, rather than storing this in id's, as #jods suggested.
I am looping thru a JSON object and I want to set clicks on fields based on a function named in the JSON Object. The function called will by window[] will be parameterised instead of hardcoded.
for (var key in JSONOBJ) {
func = window["minRule"](this);
$('#'+ key).click(func);
}
The function being called is
function minRule(elem){
alert(elem.name);
}
and I want it applied to
<input type='text' name='FIELDINJSONOBJECT' id='FIELDINJSONOBJECT'/>
However when I refresh the page the function is being called for some reason and hasnt binded to the click. Also the alert(elem.name) is returning undefined. How do I pass this as the parameter to a function called with window[$FUNCTIONAME]
Regards,
Tom
I think what you are looking for is
$.each(JSONOBJ, function(key, value){
$('#'+ key).click(function(){
window["minRule"](value);
});
})
In your case you are invoking the function minRule in the loop with parameter this where it points to the current functions scope and the value returned by it (in this case undefined as the click handler), that is the reason it is not working
i have this scenario where im loading images using a for loop:
for (var i = 0; i < test.length; i++) {
var likeImage= Titanium.UI.createImageView({
image:'like.png',
width:17,
height:10,
left:110,
top:4
});
}
then im adding an event, if the one of the hearts is clicked it should change image.
likeImage.addEventListener("click", function(e) {
likeImage.image = 'unlike.jpg';
});
but when i click nothing is happening, can anyone shed light on this problem please, thanks guys
edit:
tableView.addEventListener('click', function(e) {
var tappedRow = e.row;
alert(tappedRow.username1.text);
});
but its giving me an erro, saying this is not an object! or unidentified!! thanks
EDIT --
After looking at all the code,
http://paste.bradleygill.com/index.php?paste_id=152785
you dont need to put a click event on the image, put the click event on the tableView and the "event.source" will be the object that received the click.
if the object that received the click is one of your "likeImages" then change it to unlike or whatever else you want to do
Try to use 'this' instead of likeImage:
likeImage.addEventListener("click", function(e) {
this.image = 'unlike.jpg';
});
refer to the image object through this. when the event handler is triggered, your likeImage may not even be defined anymore or can point to another object while the this keyword will always point to current object in a function/object. so you should do
likeImage.addEventListener("click", function(e) {
this.image = 'unlike.jpg';
});
in your code, you declare var likeImage in the for scope (not the global one) and you redeclare that variable in each loop's iteration, so your variable likeImage is holding a reference only to the last image object created.
For example, after the execution of the loop, the variable a will always be equal to 9 :
for(var i=0;i<10;i++)
var a = i;
alert(a);