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);
Related
I have a page that has a number of divs on it and I am writing a function that loops over all of them, assigns them a random ID and attaches some event listeners to each. However, I am getting the problem outlined above when I check my code using JSHint. I have looked at other threads for this issue, but I do not fully understand how to apply the answers from there in my code. Below is a section that is relevant to this warning:
//global variables
var buttons = document.getElementsByClassName("btn");
var firstClick = 1; //set firstClick flag to 1 on load
var buttonIDs = [];
var clickedButtonID = null;
var clickedButtonColour = null;
var clickedButtonTitle = null;
function addListenersToAllBtns() {
//loop over all buttons on page, assign them unique IDs and attach their EventListeners
for (let i = 0; i < buttons.length; i++) {
let btn_id = guidGenerator();
buttonIDs[i] = btn_id;
buttons[i].setAttribute("id", btn_id);
//add to child node to only have the left side of the button clickable
buttons[i].childNodes[1].addEventListener("click", function () {
//save button information
clickedButtonID = btn_id;
clickedButtonTitle = buttons[i].childNodes[1].childNodes[1].innerHTML;
//button class format is ".btn colour"
clickedButtonColour = buttons[i].getAttribute("class").split(" ")[1];
console.log("The ID of the clicked button is: ", clickedButtonID, "\n",
'The colour of the clicked button is ', clickedButtonColour);
if (firstClick == 1) {
firstClick = 0;
window.addEventListener("resize", positionHabitCard);
document.addEventListener('DOMContentLoaded', positionHabitCard, false);
}
});
buttons[i].childNodes[1].addEventListener("click", showHabitCard);
}
}
I have tried taking the function definition outside the for loop like below and passing it to addEventListener but then it cannot access buttons[i] and has issues with btn_id (says it is undefined).
function getButtonData() {
//save button information
clickedButtonID = btn_id;
clickedButtonTitle = buttons[i].childNodes[1].childNodes[1].innerHTML;
//button class format is ".btn colour"
clickedButtonColour = buttons[i].getAttribute("class").split(" ")[1];
console.log("The ID of the clicked button is: ", clickedButtonID, "\n",
'The colour of the clicked button is ', clickedButtonColour);
if (firstClick == 1) {
firstClick = 0;
window.addEventListener("resize", positionHabitCard);
document.addEventListener('DOMContentLoaded', positionHabitCard, false);
}
}
Could someone more experienced take a look at this and help me out? I am very much a newbie when it comes to JS, so please excuse any glaring errors I may have missed.
Thank you very much in advance.
It's a warning, not an error. I can't really say it's an accurate warning though.
First of all, it's only really "confusing" if you are still using var declarations inside a loop. var declarations are only local to functions and the global scope. So there's no such thing as a var declaration for any loop. You can place a var declaration inside a loop but that won't create multiple variables of it. Instead any reference down the line (say like in a function body) will reference the one and only variable created by the declaration inside the loop.
That can be confusing because any one reference can update the variable. So, the first function created by the first iteration could alter a variable that is being used by every other function inside the loop.
With ES6 you have let and const which introduce block scope which also works within loops. That means a declaration inside a loop that iterates n times can create n variables and each reference inside the block will only apply to that iteration.
You are using let declarations inside the loop so you don't really have a problem there. However, if you were to change let i for var i or let btn_id for var btn_id you'd would see that all your functions created in the loop apply the logic for the last button. That is because every iteration would alter i and btn_id (as there would only be one variable per each across all functions).
I added an event listener and it created an infinite loop I really don't get what I did wrong.
What happen is it keeps clicking the images by itself
Here's my code:
function attachOnClickEvent(cases,theme,debut,capture){
var images=document.getElementsByTagName('img');
for(var i=0;i<images.length;i++){
images[i].addEventListener("click",x(cases,theme,debut,capture),false);
}
};
var x=function(cases,theme,debut,capture){newImage(this,cases,theme,debut,capture);};
function newImage(elem,cases,theme,debut,capture){
var images=preloadImages(cases,theme);
var imgTab=document.getElementsByTagName('img');
var Id=elem.id;
elem.src=images[randomTab[Id]];
};
Hope someone can find my mistake.. Thanks!
getElementsByTagName is returning a live NodeList
You are invoking your function x each time instead of adding it as a handler
A new <img> is created
the NodeList has it's length increased by 1
The next iteration is the same distance away from the end as the previous one
There are two things you need to consider, firstly as I mention in 2, you're invoking x when you aren't meaning to. Secondly, you may avoid some problems with live lists by looping downwards
One fix to get your desired result may be to rewrite attachOnClickEvent like this
function attachOnClickEvent(cases, theme, debut, capture) {
var images = document.getElementsByTagName('img'),
handler = function (e) {
return x(cases, theme, debut, capture);
},
i;
for (i = images.length - 1; i >= 0; --i) {
images[i].addEventListener("click", handler, false);
}
}
One issue would seem to be that you are not using callbacks properly.
Callbacks need to be function objects and cannot be returned as you are intending.
function (cases, theme, debut, capture) {
return function() { newImage(this, cases, theme, debut, capture); };
}
Try returning an anonymous function.
Would be better to see how you're calling these functions, but I noticed that on this line:
images[i].addEventListener("click",x(cases,theme,debut,capture),false);
You are calling your function x, rather than assigning it as the event listener. Since that function adds an image to the page, the loop never completes, since it iterates through all images on the page. This line should instead be:
images[i].addEventListener("click", x , false );
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 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