[First time on stackoverflow.] I am trying to dynamically add html buttons to my page and then give them a javascript function to run when they are clicked, using jQuery's click. I want to have one button for each element in an array, so I used a for loop. My code looks like this (simplified)
for (var i = 0; i < results.length; i++) {
$("#" + place[i].place_id).click(function(){console.log("Test");})
$("#" + place[i].place_id).click();
}
(I inject buttons with the right id's in the same loop.) This code, when run, console logs "Test" the right number of times, but afterwards, only the last button responds "Test" when clicked. (This situation is a little absurd.) So, I think the event handler ends up using only the final value of i to assign the event handler. I think the problem has to do with closures, but I am not sure how to make a closure out of a jQuery Selector (and in general am not familiar with them).
In contrast, as a hack solution, I "manually" wrote code like the below right below and outside the for loop, and it works as expected, in that clicking causes the console log.
$("#" + place[0].place_id).click(function(){console.log("Test"););
$("#" + place[1].place_id).click(function(){console.log("Test");});
etc.
(Of course, this all occurs within a larger context - specifically a Google Maps Places API call's callback.)
First, am I understanding the problem correctly? Second, what would work? Should I take a different approach altogether, like use a .each()?
(I later would want to display a property of place[i] when clicked, which I would think would need another callback
My final hack code looks like this:
$("#" + place[0].place_id).click(function(){google.maps.event.trigger(placeMarkers[0], "click"); repeated 20 times
To do this, you can simply create a self executing function inside the for loop, like this:
for (var i = 0; i < results.length; i++) {
(function(index) {
$("#" + place[index].place_id).click(function() {
//Do something with place[index] here
});
})(i);
}
Related
I have a javascript function with two parameters : results which is an object array and i which is the index.
The function displays item from that array. I also want to to build links to show other entries in the array.
My code is:
function renderNews(results, i) {
$('.articleTitle').text(results[i].Title);
$('.articleBody').text(results[i].newsBody);
// Build links
var linkHtml = '';
for (var i = 0; i < results.length; i++) {
linkHtml += '' + (i + 1) + ' ';
}
$('.articleActions').html(linkHtml);
}
As you can see I am setting my onclick for the function to call itself to redraw the results. I get a "function not defined error".
I'm still very much learning as I go. Is it bad idea for a function to call itself? I wonder if anyone can advise on the right way of doing this.
If I understand, renderNews will be called when the page gets loaded, right? Actually, your links would be put inside a component with articleActions class. By your idea, clicking any link would call this function again, and all links would be replaced by a new links. This sounds strange. Also, I can't tell what do you expect when passing that results to the onclick event. Actually, if your idea was to always reuse the same results array, passing it undefinitely to the same function over and over again, you could make things much simpler:
function renderNews(results) {
if (results.length == 0)
return;
// Build links
var linkHtml = '';
for (var i = 0; i < results.length; i++)
linkHtml += '' + (i + 1) + ' ';
$('.articleActions').html(linkHtml);
$('.articleTitle').text(results[0].Title);
$('.articleBody').text(results[0].newsBody);
}
$('.article-link').click(function(){
$('.articleTitle').text($(this).data('articletitle'));
$('.articleBody').text($(this).data('articlebody'));
});
As far as I understand, whenever you want to update the current articles, you call renderNews which will build/rebuild a lot of links for each article in the array holding their data (title and body), and will load the first item. So renderNews is going to be called once the page loads (I don't know how you intend to do this).
There is a click event for any component with article-link class, in this case all your links (anchors). When one is clicked, it updates the screen (article's title and body) with its data.
You could improve the code to keep track of the selected item, and once renderNews is called, you load that article instead of the first one. Or you could keep passing the article's index as parameter, like your example.
Since I don't know how do you call renderNews function, it's hard to make a better code, but this might clear something to you.
Simple JSFiddle demo
I'm attempting to add a class for highlighting certain elements on my web page. I'm using the jQuery("selector").addClass("class") function to do that. However, it does not work.
function toggleHighlight(selector, on) {
if (on) {
$(selector).addClass("highlighted");
} else {
$(selector).removeClass("highlighted");
}
}
When the on argument is true and the selector argument is a valid selector, it enters the if clause and exits. Unfortunately the classes of the elements the selector refers to, remain unchanged. I seem to missing something fairly fundamental. Help would be appreciated.
EDIT:
I left out the console.log($(selector)); and console.log($(".highlighted")); statements earlier, for clarity. The first returns the right elements, the second, none (called after .addClass()). The elements themselves are <path> elements created by leaflet for a map (from GeoJSON). As such, the HTML is rather hard to reproduce.
I have called the function form a button on the page: <input type="button" id="toggle-0702" class="btn btn-default" onClick="toggleHighlight('.node-0112', true)" value="turn on" \> as well as directly from the JS console. Same results.
EDIT 2:
Apparently this is an issue with either <path> elements, or (more likely) leaflet. To demonstrate this: http://jsfiddle.net/mrz40jgw/. Notice the shy class on the second circle.
We don't have enough information to say exactly what's going on, but unless you've interferred with jQuery, we can rule out that addClass is broken. Thus, one of the following is likely true:
toggleHighlight is not being called when you think it is. Use console.log to debug this.
$(selector) is not yielding what you think it is. Use console.log to debug this.
Or perhaps most likely:
Adding the class highlight does not affect the targeted elements in the manner you think it ought to, perhaps due to a selector of greater specificity. Debug this by manually setting the class and verify how that affects the visual properties of the element.
Note that jQuery already has a toggle for classes, so your code can be simplified to:
function toggleHighlight(selector, on) {
$(selector).toggleClass("highlighted", on);
}
It would appear that the problem lies with jQuery's inability to change classes on SVG elements: jQuery SVG, why can't I addClass?
For the record, here's my solution in light of the new information:
function toggleHighlight(selector, on) {
var oldClasses = [];
var newClasses = "";
var elements = $(selector)
for (var i = 0; i < elements.length; i++) {
oldClasses = $(elements[i]).attr("class").split(" ");
newClasses = "";
for (var j in oldClasses) {
if (oldClasses[j] != "highlighted") {
newClasses += " " + oldClasses[j];
}}
if (on) {
newClasses += " highlighted";
}
$(elements[i]).attr("class", newClasses.trim());
}}
I'm trying to make a loop in jQuery that finds all 'img' elements and places a caption below them, according to the value of the element's 'caption' attribute. Whenever I run the loop below, I am left with no captions under any of the images.
for (var i = 0; i < $('.myimage').length; i++) {
$('.myimage')[i].after('<h6>' + $('.myimage').attr('caption') + '</h6>');
};
However, when I run this code
$('.myimage').after('<h6>TEST</h6>');
the word 'TEST' appears below all of the images. Therefore I know my html is correct, I have no typos, and the selector is working, I just cannot get the for loop to work... What have I done wrong?
$('.myimage')[i] returns a DOM element (not a jQuery object) so there is no after method. If you want to loop, simply use .each
$(".myimage").each(function() {
//this refers to each image
$(this).after('<h6>' + $(this).attr('caption') + '</h6>');
});
You can loop through the .myimage elements like this, using .after()'s callback function
$('.myimage').after(function(){
return '<h6>' + $(this).attr('caption') + '</h6>';
});
One minor note, don't make up your own attributes. use the custom data attribute instead, like data-caption="something".
jsFiddle example
I am trying to run a loop over a few elements in JQuery. Before anyone says it, I do not need .each(). I am trying to run through the elements as a genuine loop- once a successful iteration runs, the loop will break and prevent the same action being done on other elements. I looked briefly at the straight JavaScript version, with the .getElement... methods, but it is my understanding that this won't satisfy my other requirement- the list of elements to be iterated over is created via a partial-string JQuery identifier:
rows = $('tr[id^="am_assetRow_' + parentAsset.replace(/ /, "_") + '_' + type + '"]');
Does anyone know of anything that might help me get this working?
EDIT: Just a bit more information on the application: I am checking to see if a value can be inserted into an existing row of a table, and if not, creating a new row and inserting it there. Thus, I need the loop to exit if a suitable fit is found, and after the loop terminates, I need to know whether it terminated in success (placing the value) or failure (no available locations- time to create a new row).
In jquery, if you want a $.each() loop to end immediately, just return false from the function call.
Do do a normal loop without using each() but still using jquery to select the items based on partial string etc...
rows = $('tr[id^="am_assetRow_' + parentAsset.replace(/ /, "_") + '_' + type + '"]');
for (var i = 0; i < rows.length; ++i) {
rows[i]; // The raw element at this index.
$(rows[i]); // jquery collection for this one element.
if (someCondition) {
break; // Break the loop early.
}
}
I'm using jquery to add elements to a blank list.
on the page I have:
<ul id="myList">
</ul>
and I go through a loop like this in the script that's called from a dynamically created event handler. (It's "onDrop" of a list item having been sorted with a drag operation)
var myListItemHTML;
for (var i = 0 ; i < 5 ; i++)
{
myListItemHTML += '<li id=listItem'+i+'>This is item number'+i+'</li>';
}
$('#myList').append(myListItemHTML);
and if I check after...
if ($('#myList li').length == 0 )
{
alert('Going to crash now since I'm expecting list items')
}
roughly 95% of the time the list is populated, but about 5% of the time I hit my alert that's going to cause an exception later.
Has anyone run into this? Is there a callback or way to know when/if the append really happens?
var myListItemHTML;
for (var i = 0 ; i < 5 ; i++)
{
$('#myList').append('<li id=listItem'+i+'>This is item number'+i+'</li>');
}
Try just appending inside the for loop.
if ($('#myList li').length == 0 )
{
alert('Going to crash now since I\'m expecting list items')
}
You need a \ before the ' so it doesn't conflict.
edit: jsfiddle that shows "undefined" http://jsfiddle.net/gyEre/1/
This is a bit of a longshot, because I'm making an assumption about your code beyond what was posted. Here goes:
I had a problem with some code once, which worked perfectly in all Browsers except Chrome, wherein it would fail randomly and seemingly without cause.
My problem, ultimately, was that Chrome was actually executing my JavaScript too fast, and that it was throwing off some of the timing in some of the AJAX calls that were being made earlier.
My code was such that AJAX event A triggered, which then passed data to AJAX event B. In Chrome only, though, I found that event B was on occasion occurring before event A, and that was the error condition.
If I recall, I think the solution was to force one key AJAX request to be made synchronously, though that should be used with care. Please see: http://api.jquery.com/jQuery.ajaxSetup/
I hope that's not too vague to be helpful. Good luck!
What I've learned is that I can't trust jquery for DOM manipulation called through dynamically created events. I welcome someone to prove me wrong, but this approach:
addListItem = function (itemID, itemText)
{
var li = document.createElement('li');
li.setAttribute("id", itemID);
var liText = document.createTextNode(itemText);
li.appendChild(goalTextNode);
document.getElementById('myList').appendChild(li);
}
for (var i = 0 ; i < 5 ; i++)
{
addListItem('listItem'+i, 'Item Text'+i);
}
will work 100% of the time and never fail.
In my experience, if the script in which the prepend function is used in the -tag, it will not work because the script is loaded before the DOM is ready.
To avoid this kind frustration, is a good habit to place your javascript before the tag.