i have a string objects of books which i have got from a JSON objects. This Book object has three Key value pairs, Title,Author and URL. I use a for loop to read each object and just put the title of the object as a button on the html page. But when the button is clicked i want the URL of the book to be alerted. As i read the objects i make Books objects and push it into an array for later use. but i am not able to Use .Click() method the URL is not right. Please see the code for better understanding. :-)
for (i = 0; i < 6; i++) //I know that there is only 65 Books..jsonString.books.lenght is not working.
{
var title = jsonString.books[i].title;
var classname = title.replace(/\s/g, "");
var author = jsonString.books[i].author;
var URL = jsonString.books[i].url;
var htmlString = '<div class="' + classname + '"><input type="button" value="' + title + '"></div>';
$(htmlString).appendTo(attachPoint).click(function () {
loadBook(URL);
});
OneBook = new Book(author, title, URL);
arr.push(OneBook);
}
attachpoint is a reference in the html file that i got from
var attachpoint=document.querySelector('.buttonAttachPoint');
So in the above code the URL that i get on clicking is always the last object in the jsonString. this is happening coz of the for loop. So is there a way i can get to class name of the Div that has onclick or the title of the button so that i can get the URL from the array of objects i created? Or is there an easier way. Also could any one point out why "jsonString.books.lenght" is not working? Thanks in advance.:-) all the help much appreciated. :-)
Creating a closure using an immediately invoked function expression should do the trick. Just replace this:
$(htmlString).appendTo(attachPoint).click(function () {
loadBook(URL);
});
with this:
(function(URL) {
$(htmlString).appendTo(attachPoint).click(function () {
loadBook(URL);
});
})(URL);
URL inside the scope of that anonymous function will have the value passed to it, which will be the correct value for that iteration of the for loop.
In ECMAScript, variables are scoped to their function, rather than any block.
The functions you are binding to click have a closure over URL in the context of the loop as a whole, not over URL in the context of the loop iteration.
This means that whenever any of the functions are invoked, URL will have the last value that the loop sets it to.
You need to freeze the value of URL for each loop. One way to do this is to have a function elsewhere that takes URL as an argument, and returns a function that closes over it, thus:
function getBookLoader(url) {
return function (){
loadBook(url);
};
}
You can then replace your ... .click line with the following:
$(htmlString).appendTo(attachPoint).click(getBookLoader(URL))
To answer the question in the title, the target property of an event contains the object to which the event was dispatched, and the currentTarget property contains the object whose listeners are currently being evaluated. currentTargetshould be the div in question.
Related
I'm a noob and also new to this site, so let me know if there are things I should do to improve this post. Anyway, I have a function that is re-used frequently in my site, so I stored it in a global variable and want to call it when a certain button is clicked.
The code looks like this (see below). My problem is that although I can confirm that the button click tries to call the function, it is clearly never actually called (none of my alerts fire and the changes to the text fields are not saved). All of this is contained in the $(document).read(function...
Have I made a dumb mistake somewhere, or is there something I'm doing clearly wrong?
$(document).ready(function () {
//Description:
//Global wrapper variable that contains all global functions. These include:
// 1. saveAll: Saves all values not stored in session data to hidden fields - this includes
// all added ingredient information. This allows us to manually pass values between
// client and server to save to db and also means we can eliminate Null values in table
// storage using a manual delimiter.
//----------------------------------------------------------------------------------------------
var Global = (function () {
return {
saveAll: function () {
alert("entering save");
//start by creating an array and initializing the length of the for loop
var saveValues = [];
var numVals = $('#HidRowCt').val();
alert("numVals: " + numVals);
//Now loop through each ingredient row and create a string containing all textbox values
//in this case, we'll do so by creating an array and then combining the values with a custom delimiter
//the strings will then be saved, one by one, into the saveValues array, which will be serialized as a JSON object,
//stored in a hidden field, and passed to the server
for (i = 1; i < numVals; i++) {
var TxtIngName = $('#TxtIngName' + i).val();
var TxtIngNumUnits = $('#TxtIngNumUnits' + i).val();
var SelIngUnits = $('#SelIngUnits' + i).val();
//make temporary array and string
var saveArr = new Array(TxtIngName, TxtIngNumUnits, SelIngUnits);
var saveStr = saveArr.join("-||-");
saveValues.push(saveStr);
}
alert("Save Values: " + saveValues);
//this will automatically escape quotes, delimited with ","
var jsoncvt = JSON.stringify(saveValues);
$("#HidSave").val(jsoncvt);
}
};
});
//----------------------------------------------------------------------------------------------
//Description:
//Hijack the click event for the save button. Saves values not saved in session data.
//
//Functions:
// Global.saveAll()
//----------------------------------------------------------------------------------------------
$("#SaveChanges").data.clickEvent = $("#SaveChanges").attr('onClick'); //save onclick event locally
$("#SaveChanges").removeAttr('onClick'); //and remove the onclick event
$('#SaveChanges').on('click', function (event) {
Global.saveAll();
//eval($("#SaveChanges").data.clickEvent); //now go ahead with the click event
});
Well, I never figured out why this didn't work, but....
I just removed the global variable and created a separate function for saveAll() and it works. Interestingly, I have a second application using the same code that uses the Global.saveAll (with the same innards) and works fine, so I must have something unusual in one of my earlier lines.
Thanks for your suggestions!
Try setting window.Global = ..., since declaring var Global sets the scope to be within the ready closure.
Then you should be able to use it later.
I just removed the global variable and created a separate function for saveAll() and it works.
Today while working with some JS I had a bug in my code. I was able to resolve it but I don't really understand why the change I made works. All I can guess is that it comes down to either closure or variable scope.
I was trying to build up a nested hash of arrays like so:
var maxNumberOfPairs = 2;
var container = {};
var pairsHash = {};
$.each(["nurse", "doctor", "janitor", "chef", "surgeon"], function(index, role) {
for(var i = 0; i < maxNumberOfPairs; i++){
var pairIdSubString = "attribute_" + i + "_" + role;
pairsHash["attribute_" + i] = [pairIdSubString + "_night", pairIdSubString + "_day"];
}
container [role] = pairsHash;
});
If you run this you get a nice nested output inside container but when you look at each array in the hash you get a weird behaviour with the string produced.
Each one has the last role in each string like so:
"attribute_0_surgeon_night"
If you log out the variable pairIdSubString it correctly has the role in the string, but as soon as this is added to pairHash it just uses the last element in the $.each array.
I was able to fix it by moving pairsHash inside the $.each but outside the for loop.
Can anyone explain to my why the output was different after moving it inside the each?
Thanks
It actually has to do with reference vs value. When its outside the each you are operating on the same object over and over so every time you set it to the container you are just setting a reference to the same object that is constantly changing. So every reference in container after the loop is the last state of the pairsHash because they all point to the same object.
When you put the pairsHash in the each it is reinitialized every time so they all point to different memory addresses. Not the same one since a new one is created every loop.
To further clarify all objects are just references to a memory address In JavaScript so in order to get new one you need to initialize or to pass by value to a function clone it.
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
How do I increment an integer inside a variable, every time that variable is called? Javascript.
var a=0;
var t=loadXMLDoc("http://ws.audioscrobbler.com/2.0/?method=artist.getinfo&artist="+x[a].getElementsByTagName("name")[0].childNodes[0].nodeValue+"&api_key=83e386b0ba08735e3dee9b118478e56d&lang=en").getElementsByTagName("bio");
for (i=0;i<20;i++)
{
document.write("<div><button type='button' onclick='document.getElementById("+i+").innerHTML=t[0].getElementsByTagName(\"summary\")[0].childNodes[1].nodeValue;'>Open Bio</button></div>");
}
I'm not sure how I would go about incrementing variable a. I need it to increase by 1 every time variable t is called in the for loop.
When I put all of the code in the for loop I get [object node list] returned so this method is not desired.
If I understood your question correctly, you could define your own getters and setters for the property.
var o = {}
o.__defineSetter__('property', function(value) { this._counter = 0; this._holder = value; })
o.__defineGetter__('property', function() { console.log(this._counter++); return this._holder; })
The counter would be reset every time o.property is assigned a value
o.property = 'Some value'
and then increase every time the property is accessed.
So,
console.log(o.property)
would print
0
Some value
to the console. And if you do it again, it would print
1
Some value
After your edit I think I can see your problem now. You will need to put the loadXMLDoc statement in the loop (since you want to load 20 different XML files), but you can't assign the result of every call to the same variable t - as once the button is clicked, the handler will evaluate t and get only the last value.
Instead, use an array:
var bios = []; // empty array
for (var i=0; i<20; i++) {
var artist = x[i].getElementsByTagName("name")[0].childNodes[0].nodeValue,
doc = loadXMLDoc("http://ws.audioscrobbler.com/2.0/?method=artist.getinfo&artist="+artist+"&api_key=83e386b0ba08735e3dee9b118478e56d&lang=en"),
bio = doc.getElementsByTagName("bio")[0].getElementsByTagName("summary")[0].childNodes[1].nodeValue;
bios[i] = bio; // store it in the array
document.write("<div><button type='button' onclick='document.getElementById("+i+").innerHTML=bios["+i+"];'>Open Bio</button></div>");
}
Of course, while that will work it's a bunch of bad practises, including
unsecured accessing of DOM nodes/properties. If the xml changes its format, you will get lots of exceptions here. You might be sure now that this never happens, but wrapping artist and bio in try-catch might not be a bad idea.
snychronous Ajax. One can do better than that.
loading 20 documents (and that sequentially!) even if you don't need them. It might be worth to try loading each of them only when the respective button is clicked.
document.write
Inline attribute event handlers
…and creating them even by JS.
Below is my code fragment:
<div onclick = "myClick('value 1')">
button 1
</div>
<div onclick = "myClick('value 2')">
button 2
</div>
Basically when I for each click on a different div, a different value will be passed to the JavaScript function.
My Question is how can I keep track of the value passed in the previous click?
For example, I click "button 1", and "value 1" will be passed to the function. Later, I click on "button 2", I want to be able to know whether I have clicked "button 1" before and get "value 1".
Just add it to a variable in your script:
var lastClicked;
var myClick = function(value) {
lastClicked = value;
};
You can define somekind of variable, like var lastUsed;
add additional line to your function:
var lastUsed = null;
function myClick(value){
prevClicked = lastUsed; //get the last saved value
...
lastUsed = value; //update the saved value to the new value
...
}
And here you go
You need a variable. Variables are like little boxes in which you can store values. In this case, we can store the value that was last passed to the function myClick.
In Javascript, you can define a variable like this:
var lastClickedValue;
You can "put" a value into that variable. Let's say you want to put your name in there. You would do this:
lastClickedValue = 'sams5817';
Now here's the tricky bit. Variables have "scope". You might want to think about it as their "life-time". When a variable reaches the end of its scope, you cannot read or write to it anymore. It's as if it's never been. Functions define a scope. So any variable you define in a function will disappear at the end of the function. For example:
function myClick(value)
{
var lastClickedValue;
alert('lastClickedValue is = ' + value);
lastClickedValue = value;
}
That looks almost right, doesn't it? We declared a variable, display its last value, and update it with the new value.
However, since the lastClickedValue was declared in the function myClick, once we've reached the end of that function, it's gone. So the next time we call myClick, lastClickedValue will be create all over again. It will be empty. We call that an "uninitialized" variable.
So what's the problem? We're trying to remember a value even after the end of myClick. But we declared lastClickedValue inside myClick, so it stops existing at the end of myClick.
The solution is to make sure that lastClickedValue continues to exist after myClick is done.
So we must delcare lastClickedValue in a different scope. Luckily, there's a larger scope called the "global scope". It exists from the moment your page loads, and until the user moves on to another webpage. So let's do it this way:
var lastClickedValue;
function myClick(value)
{
alert('lastClickedValue is = ' + value);
lastClickedValue = value;
}
It's a very small difference. We moved the declaration of the variable lastClickedValue to be outside the function myClick. Since it's outside, it will keep existing after myClick is done. Which means that each time we call myClick, then lastClickedValue will still be there.
This will let you know what the last value passed to myClick was.
Finally, I'd like to advise you to look for some kind of Javascript tutorials. I wish I knew of some good ones to recommend, but I'm certain you can find a few on the Internet. If you try to write programs before understanding what you're doing, you'll find yourself producing work that is less than what you're capable of. Good luck!
I suppose you need something like this
var clickedButtons = [];
function myClick(value){
...
clickedButtons.push(value);
...
}
I am surprised that no one else mentioned this, but since functions are first class objects in JavaScript, you can also assign attributes and methods to functions. So in order to remember a value between function calls you can do something like I have with this function here:
function toggleHelpDialog() {
if (typeof toggleHelpDialog.status === 'undefined')
toggleHelpDialog.status = true;
else
toggleHelpDialog.status = !toggleHelpDialog.status;
var layer = this.getLayer();
if (toggleHelpDialog.status) layer.add(helpDialog);
else helpDialog.remove();
layer.draw();
}
Here I have added an attribute named 'status' to the toggleHelpDialog function. This value is associated with the function itself and has the same scope as the toggleHelpDialog function. Values stored in the status attribute will persist over multiple calls to the function. Careful though, as it can be accessed by other code and inadvertently changed.
we can leverage javascript static variables
One interesting aspect of the nature of functions as objects is that you can create static
variables. A static variable is a variable in a function‘s local scope whose value persists across
function invocations. Creating a static variable in JavaScript is achieved by adding an instance
property to the function in question. For example, consider the code here that defines a function
doSum that adds two numbers and keeps a running sum:
function doSum(x,y){
if (typeof doSum.static==='undefined'){
doSum.static = x+y;
}else{
doSum.static += x+y;
}
if (doSum.static >= 100){doSum.static = 0;doSum.static += x+y;}
return doSum.static;
}
alert(doSum(5,15))
alert(doSum(10,10))
alert(doSum(10,30))
alert(doSum(20,30))