Given:
// Positions the bars relative to scale
this.gDrawBars = function() {
// Go through all the bars
for (i = 0; i < this.gData.length; i++) {
// Check part of it is within range
if (this.gDisplayFrom < this.gData[i][2] || this.gDisplayTo > this.gData[i][1]) {
// Is the entire bar showing
var isEntireBarInRange = (this.gDisplayFrom < this.gData[i][2] && this.gDisplayTo > this.gData[i][1]);
var div = document.createElement('div');
div.id = "gBar" + i;
div.className = 'gBar';
div.innerHTML = this.gData[i][0];
var self = this;
div.onmouseover = function() {
gBarHighlight(this, this.gData[i][1], this.gData[i][2]);
};
div.onmouseout = function() {
gBarUnHighlight(this, this.gData[i][1], this.gData[i][2]);
};
this.gContainer.appendChild(div);
//this.gContainer.innerHTML += "<div id=\"gBar" + i + "\" class=\"gBar\" onmouseover=\"gBarHighlight(this, '" + this.gData[i][1] + "', '" + this.gData[i][2] + "')\" onmouseout=\"gBarUnHighlight(this, '" + this.gData[i][1] + "', '" + this.gData[i][2] + "')\">" + this.gData[i][0] + "</div>";
The commented line at the bottom works fine, but I'm trying to change it to add these functions dynamically. It needs to pass this.gData[i][1] into the functions but it can't, because the i value has no meaning outside the loop.
How can I get around this? IE, make the function recognise it's being passed a value to use and not a reference.
You need to retain the value of i in a new execution context.
Place the code that assigns the handlers into a named function, and call that in the loop, passing i as an argument.
Place this function before the for loop:
function setupMouseOverOut( el, i ){
el.onmouseover = function() {
gBarHighlight(this, this.gData[i][1], this.gData[i][2]);
};
el.onmouseout = function() {
gBarUnHighlight(this, this.gData[i][1], this.gData[i][2]);
};
}
...then call it in the for loop:
setupMouseOverOut( div, i );
This way the value of i that you passed out of the for loop is retained in the new execution context of the setupMouseOverOut() function call, and the new functions you set as handlers will refer to that local variable.
It's not a function, it's an event. You need to add it as an event to the element:
div.addEventListener('mouseover', function() {
// ...
});
Note that when you do it this way you don't have that 'on' word there.
Related
When a new button is created it isn't being picked up by the rest of the code
var topics = ["dog", "cat", "pangolin", "snake", "bird", "emu", "cow", "hedgehog"]
$(document).ready(function () {
$("#btnAddSubmit").click(function() {
var newAnimal = $("#addInput").val();
topics.push(newAnimal);
newAnimal = newAnimal.toLowerCase();
$("#buttons").append('<button id="gif' + newAnimal + '">' + newAnimal + '</button>');
});
$("button").click(function() {
var currentGif = this.id;
if (this.id != "submit") {
currentGif = currentGif.replace("gif", "");
currentGif = currentGif.toLowerCase();
var topicNum = topics.indexOf(currentGif);
var myUrl = "https://api.giphy.com/v1/gifs/search?q=" + topics[topicNum] + "&api_key=oaPF55NglUdAyYKwDZ0KtuSumMrwDAK9&limit=15";
$.ajax({
method: "GET",
url: myUrl,
}).then(function(response) {
console.log(currentGif);
console.log(response);
$("#gifLocation").empty();
var gifURL = response.data[0].images.fixed_width.url;
console.log(response.data.length);
var gifNum = response.data.length
for (var i = 0; i < gifNum; i++) {
$("#gifLocation").append('<div id=gifDiv' + i + '></div>');
gifURL = response.data[i].images.fixed_width.url;
var gifRateId = "gifRate" + i;
var ratingLocString = '<p id="' + gifRateId + '"></p>'
var ratingLoc = $(ratingLocString);
var rating = response.data[i].rating;
var gifRating = "Rating: " + rating;
$("#gifDiv" + i).append(ratingLoc);
$("#" + gifRateId).text(gifRating);
var gifId = "gif" + i;
var gifImage = $('<img class=gif id=' + gifId + '>');
gifImage.attr("src", gifURL);
$("#gifDiv" + i).append(gifImage);
}
});
console.log(currentGif);
}
});
});
What I'm trying to do is when the user creates a new button, that button will then work like the premade buttons. The premade buttons are supposed to display a few gifs.
What is happening is that after I create the new button, clicking on that button won't even console log the id of that new button.
Your event listener $("#btnAddSubmit").click worked only with already created buttons. That is means your new buttons will be without this listener. If you want to add listeners to the new buttons, you must do something like:
// We are create event listener as a function for convenient use
var onButtonClick = function () {
var currentGif = this.id;
if (this.id != "submit") {
currentGif = currentGif.replace("gif", "");
// Your code here...
}
}
$("#btnAddSubmit").click(function() {
var newAnimal = $("#addInput").val();
topics.push(newAnimal);
newAnimal = newAnimal.toLowerCase();
$("#buttons").append('<button id="gif' + newAnimal + '">' + newAnimal + '</button>');
// We are remove all button's listeners and at once add new
$("button").off('click').on('click', onButtonClick);
});
// And this code will add your listener as it was originally
$("button").off('click').on('click', onButtonClick);
Be cearful if your buttons have another event listeners. If it exists, you connot use .off(). In that case is correct way will be add listener for a new specific button's id.
Based on your question and the js code provided, i guess this is because the newly added button doesn't get the event.
All events are attached to the dom on page load. The new buttons that are injected to the DOM doesn't get the events. jQuery already did the bindings to DOM elements before the new code was injected. To solve this you have to use '.on() method in jQuery
Something like this
$(document).on('click','your_button_class_here',function(){
dosomething();
});
You're using the ready callback, so all of this runs when the DOM is ready. However, you don't actually create the new button until this ready callback has already run! So when you try to add callbacks with $("button").click(function(){}), you are trying to add that callback to all the buttons on the DOM... but some of the buttons you want to add it to do not exist yet. They won't exists until that first button's click callback is executed! So the first button you make will have the callback attached, but the new ones will not.
Maybe try something like this? I expect something will be wrong with how the value of this works on your click callback, but I think it's a nudge in the right direction.
$(document).ready(function () {
$("#btnAddSubmit").click(function () {
var newAnimal = $("#addInput").val();
topics.push(newAnimal);
newAnimal = newAnimal.toLowerCase();
$("#buttons").append('<button id="gif' + newAnimal + '">' + newAnimal + '</button>');
// be wary of what the value of `this` refers to! it might refer to
// the `this` of the scope in which it was defined!
function gifCallback() {
var currentGif = this.id;
if (this.id != "submit") {
currentGif = currentGif.replace("gif", "");
currentGif = currentGif.toLowerCase();
var topicNum = topics.indexOf(currentGif);
var myUrl = "https://api.giphy.com/v1/gifs/search?q=" + topics[topicNum] + "&api_key=oaPF55NglUdAyYKwDZ0KtuSumMrwDAK9&limit=15";
$.ajax({
method: "GET",
url: myUrl,
}).then(function (response) {
console.log(currentGif);
console.log(response);
$("#gifLocation").empty();
var gifURL = response.data[0].images.fixed_width.url;
console.log(response.data.length);
var gifNum = response.data.length
for (var i = 0; i < gifNum; i++) {
$("#gifLocation").append('<div id=gifDiv' + i + '></div>');
gifURL = response.data[i].images.fixed_width.url;
var gifRateId = "gifRate" + i;
var ratingLocString = '<p id="' + gifRateId + '"></p>'
var ratingLoc = $(ratingLocString);
var rating = response.data[i].rating;
var gifRating = "Rating: " + rating;
$("#gifDiv" + i).append(ratingLoc);
$("#" + gifRateId).text(gifRating);
var gifId = "gif" + i;
var gifImage = $('<img class=gif id=' + gifId + '>');
gifImage.attr("src", gifURL);
$("#gifDiv" + i).append(gifImage);
}
});
console.log(currentGif);
}
};
// reference the new button by its ID and add your desired callback
$("#gif").click(gifCallback)
});
});
I'm experiencing some weird behavior in my code that I don't quite understand. I call a function, and inside that function there is another (anonymous) callback function it skips over and it goes to the end of the containing function, runs those lines, and then goes back into the callback function and runs those lines... Anybody have some insight, what am I doing wrong? Is it doing this because the "relatedQuery" method isn't complete yet so it hasn't hit the callback function before it runs the rest of the containing function's lines? That's the only thing I can think of, but I'm also not very skilled at JS. I've added some console.log statements that will tell you the order in which lines are being hit.
//Call the mgmtPopupContent function
mgmtTractPopupBox.setContent(mgmtPopupContent);
function mgmtPopupContent(feature) {
for (var attrb in feature.attributes) {
if (attrb == "HabitatManagement.DBO.MgmtTracts.OBJECTID") {
var OID = feature.attributes[attrb];
}
}
var relatedQuery = new RelationshipQuery();
relatedQuery.outFields = ["*"];
relatedQuery.relationshipId = 0;
relatedQuery.objectIds = [OID];
//Get data year that the map view is set to and set the definition expression on the table
viewYear = dom.byId("data-year").value;
relatedQuery.definitionExpression = "YearTreated = " + viewYear;
//Create table header that will go inside popup
var content = '<table id="mgmtPopupTable1"><tr><th>Veg Mgmt Practice</th><th>Herbicide</th><th>Month</th><th>Year</th>\
<th>Implemented By</th><th>Funded By</th><th>Farm Bill Code</th></tr>';
console.log("PRINTS FIRST");
//Do query and get the attributes of each related record for the popup
queryableMgmtTractFL.queryRelatedFeatures(relatedQuery, function (relatedRecords) {
console.log("PRINTS THIRD");
var fset = relatedRecords[OID].features;
fset.forEach(function (feature) {
var vegPractice = vegPName(feature.attributes.VegMgmtPractice);
var herbicide = herbName(feature.attributes.Herbicide);
var monthTreated = monthName(feature.attributes.MonthTreated);
var yearTreated = feature.attributes.YearTreated;
var impBy = impName(feature.attributes.ImplementedBy);
var fundBy = fundName(feature.attributes.FundedBy);
var fbc = feature.attributes.FarmBillCode;
if (fundBy == "CRP" || fundBy == "CRP - CREP") {
fbc = crpName(fbc);
}
else if (fundBy == "EQIP" || fundBy == "EQIP - RCPP") {
fbc = eqipName(fbc);
}
else {
fbc = "Not applicable";
}
row = '<tr><td>' + vegPractice + '</td><td>' + herbicide + '</td><td>' + monthTreated + '</td><td>' + yearTreated +
'</td><td>' + impBy + '</td><td>' + fundBy + '</td><td>' + fbc + '</td></tr>';
content = content + row;
});
content = content + '</table>';
});
console.log("PRINTS SECOND");
return content;
}
As mentioned in my comment, you have to wait for the queries to finish before you can render the content. So something like:
let content = '<table id="mgmtPopupTable1"><tr><th>Veg Mgmt Practice</th><th>Herbicide</th><th>Month</th><th>Year</th>\
<th>Implemented By</th><th>Funded By</th><th>Farm Bill Code</th></tr>';
const render_popup = function( content ) {
document.querySelector( '#myPopup' ).innerHTML = content;
};
// Render only the headers to begin with.
render_popup( content );
queryableMgmtTractFL.queryRelatedFeatures(relatedQuery, function (relatedRecords) {
var fset = relatedRecords[OID].features;
fset.forEach(function (feature) {
...
});
// Rerender the popup, now headers And content.
render_popup( content );
});
I am trying to make a loop that will display some images and add an event listener to each image which, when clicked will assign the appropriate value to humanGoal. I have:
var humanGoal;
function displayPicker(round){
for(var i = 0; i <= round; i++){
document.write('<img src=img/die' + i + '.png id="' + 'picker' + i + '">');
document.getElementById('picker'+i).addEventListener("click", function () {
humanGoal = i;
document.write('you picked ' + humanGoal );
});
}
}
why does humanGoal always === round+1, instead of the variable i from the for loop?
The humanGoal variable is being overwrited with every for loop iteration and holds the round + 1 value at the end. Different words speaking - it will always display a wrong index.
Solution: apply same class to the each img element, bind a click event listener and display the actual index by passing i variable inside the Array#forEach function.
function displayPicker(round){
for (var i = 0; i <= round; i++){
document.write('<img src=img/die' + i + '.png id="' + 'picker' + i + '" class="img">');
}
var elems = document.getElementsByClassName('img');
Array.from(elems).forEach((v,i) => v.addEventListener('click', function() {
console.log(`You picked ${i}`);
}));
}
displayPicker(5);
See answer to your question is simple, when you were trying to assign human goal with value of i, the loop is already been iterated over "rounds" value that why you always getting i === round inside click function.
See the below code snippet,
<html>
<script>
var humanGoal;
function displayPicker(round){
for(var i = 0; i <= round; i++){
document.write('<img src=img/die' + i + '.png id="' + 'picker' + i + '">');
document.getElementById('picker'+i).addEventListener("click", function () {
console.log("me getting called second");
humanGoal = i;
document.body.append('you picked ' + humanGoal );
});
console.log("me getting called first");
}
}
</script>
<body onload="displayPicker(4)">
</body>
</html>
for getting the correct result you can follow the approach provided by #Kind user
I'm trying to make a dropdown which is populated from an array with Javascript. Each Item needs to have an event trigger attached, but it currently only attaches the event to the last element. I have tried the examples based on fixing closures but is still only attaches to the last element.
https://jsfiddle.net/z3h1uux4/
var ArrayUName = ["A","B","C"]
var ArraySlug = ["Q","W","E"]
for (i = 0; i < ArrayUName.length; i++) {
var GoalID = ArrayUName[i] + '-' + ArraySlug[i];
document.getElementById("TheContent").innerHTML +=
'<a class="GoalIDBtn" id="' + GoalID + '">' + ArrayUName[i] + ' / ' + ArraySlug[i] + '</a></br>';
(function(_i, _GoalID)
{document.getElementById(_GoalID).addEventListener(
"click",
function() {alert("Click Made : " + _i)}
);
})(i, GoalID);
console.log("Loop #" + i);
}
That's because innerHTML is a destructive property. It recreates the set content and creates new elements, the newly generated elements do no have any click handlers bound to them. You should create a node (element) instead of using innerHTML.
You can use the document.createElement and HTMLElement.appendChild methods instead:
var a = document.createElement('a');
a.className = 'GoalIDBtn';
a.id = GoalID;
a.textContent = ArrayUName[i] + ' / ' + ArraySlug[i];
document.getElementById("TheContent").appendChild(a);
(function(_i /*, _GoalID*/) {
a.addEventListener("click", function() {
alert("Click Made : " + _i);
});
})(i);
Here is a demo on jsfiddle. Note that it doesn't add the br elements and you can use the similar DOM APIs for creating them.
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 9 years ago.
I want to add a delete option for a cookie of each img element inside for-loop. the problem is that all ids are always from the last object.
what I've tried:
function listCookies() {
var theCookies = document.cookie.split(';');
var aString = '';
for (var i = 1; i <= theCookies.length; i++) {
(function (i) {
if (theCookies[i - 1].indexOf("pauseCookie-") >= 0) {
cookieArray = theCookies[i - 1].split("=");
theCookie = $.trim(cookieArray[0]);
cookieInfo = $.cookie($.trim(theCookie));
cookieInfo = cookieInfo.split("^");
...
htmlCode = "<div id='pauseDiv-" + theCookie + "'><a href='" + cookiePath + "'>" + cookieTitle + "</a> (" + pauseInfo + ") </div><br />";
$("#cookiesContent").append(htmlCode);
header = document.createElement('img');
header.id = 'delImg' + theCookie;
header.style = 'cursor:pointer';
header.src = delImage;
header.onclick = function () {
alert("#pauseDiv-" + header.id);
//$.removeCookie(theCookie,{path:'/'});
$("#pauseDiv-" + theCookie).html("<div class=\"pauseDivResponse\">Deleted</div>");
}
document.getElementById("pauseDiv-" + theCookie).appendChild(header);
}
})(i);
}
}
listCookies();
alert("#pauseDiv-" + header.id); always prints the last id from all img elements I created.
This is not directly related to closures, but is to do with your variable header being in the global scope. When you declare a new variable, you should use the var keyword, otherwise it is assumed to be a property of the global object (window).
If you enable strict mode (by adding "use strict"; to the top of your code, or the top of the function), you will be told about these sorts of mistake.
To be clear, you need to change
header = document.createElement('img');
to
var header = document.createElement('img');
(and should do the same for all your variables)