2
<html>
<header><script src = "/jquery.js"></script></header>
<div id="cars"></div>

<script>
    $.post({
        url:"/cars.php",
        dataType:"JSON",
        success:function(cars){
                var fromidcount = Object.keys(cars).length;
                for (var i=0;i<=(fromidcount-1);i++){
                    var carname= Object.keys(cars)[i];
                    $("#cars").append("<input type='button' id='"+carname+"'>");
                    $("#"+carname).click(function(){
                        alert("selected "+carname);
                    });
                }
        }
    });
</script>
</html>

I am getting a JSON array from PHP, and I am using the values in it to dynamically create HTML buttons with Jquery. So far, it makes two buttons with ids="BMW" and "Toyota". My script is then supposed to create instructions such that when id="BMW" is clicked, it alerts "Selected BMW". When id="Toyota" is clicked, it should alert "Selected Toyota.

However, when I click on the buttons, both alert "Selected Toyota". I've checked the dev tools and confirmed that the button ids are BMW and Toyota, but the Events for each button still show alert("selected "+carname); instead of alert("selected "+"BMW"); and alert("selected "+"Toyota");

So I think what's happening is that when the buttons are clicked, only then does Jquery update the "carname" variable in alert("selected "+carname);. But by that time, the loop has already finished and so the value it grabs is the last value of the loop, which is Toyota.

You can see that I have multiple instances of the variable carname inside the for loop. And all instances of carname are replaced by their respective values EXCEPT for the one in alert("selected "+carname);. Why is this, and how can I fix it?

cars is a multi-dimensional array. [ [bmw] , [toyota] ]

  • Can you please add `cars` Object to question – Jay Mar 11 '20 at 06:27
  • cars is a multi-dimensional array. [ [bmw] , [toyota] ] – yosimba2000 Mar 11 '20 at 06:30
  • But in code you are using `Object.keys(cars)` as Object but above comment you mentioned Array ? – Jay Mar 11 '20 at 06:33
  • This is simply a scope issue, please see [this question](https://stackoverflow.com/questions/46027262/functions-declared-within-loops-referencing-an-outer-scoped-variable-may-lead-to) explaining the issue in details. – Noah Boegli Mar 11 '20 at 06:34
  • you can create a single click event handler (outside) your loop using `onclick` attribute and just pass the car name as argument. this should alert the value passed, rather than latest value only – Muhammad Murad Haider Mar 11 '20 at 06:35
  • agree with @Noah Boegli comment, seems to be a scope issue, if you are okay with using ES6, replace `var` with `let` and your code should work – Muhammad Murad Haider Mar 11 '20 at 07:45

3 Answers3

2

You're bumping into JavaScript's closure/lexical scope rules here. You'd need to have a another function that closes over the carname variable where you add the event handler, but it may be easier to just read the car name from the ID since you have it there:

$("#"+carname).click(function(){
  alert("selected " + this.id);
});

If you need to use the ID for other things, you can also add the car name as a data- attribute.

AKX
  • 152,115
  • 15
  • 115
  • 172
  • This works, thank you! But I have no idea why 'this.id' works but mine doesn't. Would you be able to explain? – yosimba2000 Mar 11 '20 at 06:38
  • 1
    @yosimba2000 When you reference `carname` in your `alert(carname)` it will create an alert referencing to the value of `carname` which, when the loop is complete, will be the last value it had, in your case it's `BMW`. – Noah Boegli Mar 11 '20 at 06:39
  • But how come all other instances of ```carname``` are replaced correctly? Why is alert(carname) different? – yosimba2000 Mar 11 '20 at 06:45
  • @yosimba2000 This is linked to the mechanism of closure/lexical scope. When you create your `input` __inside the loop__ you write the id directly in the DOM element when creating it, thus being the current value of `carname`. When you pend your alert event, the alert event will take place __outside the loop__ thus not having access to each and every value of `carname` but only the last one. – Noah Boegli Mar 11 '20 at 06:52
  • Not quite getting it, but I'll have to read that closure stuff. But it seems like if Jquery needs to create the element NOW, it will access the current value, but if it needs to wait on something, like a user input, it will hold off on accessing the current value until user input is detected? – yosimba2000 Mar 11 '20 at 07:00
  • 1
    @yosimba2000 Let's try an analogy: Imagine you have a pack of `n` stickers, 2 of each: A and B. You walk in the street and at every lamp post you cross, you put A on the lamp post and B on a paper sheet. Except the `B` sticker always goes on top of the previous `B` sticker on the paper sheet. At the end of the street, each sticker `A` is visible distinctly on the lamp posts but only one `B`, the last one, is visible on your paper sheet. `A` is your `ID` and B is your `carname` variable. The lamp posts are your `inputs` and the paper sheet is you `alert` function. – Noah Boegli Mar 11 '20 at 07:50
1

you must Use $(this).attr("id") to get Carname

 $.post({
        url:"/cars.php",
        dataType:"JSON",
        success:function(cars){
                var fromidcount = Object.keys(cars).length;
                for (var i=0;i<=(fromidcount-1);i++){
                    var carname= Object.keys(cars)[i];
                    $("#cars").append("<input type='button' id='"+carname+"'>");
                    $("#"+carname).click(function(){
                        alert("selected "+$(this).attr("id"));
                    });
                }
        }
    });
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<html>
<header><script src = "/jquery.js"></script></header>
<div id="cars"></div>


</html>
MBadrian
  • 409
  • 3
  • 10
  • It works! But why does mine not work? – yosimba2000 Mar 11 '20 at 06:36
  • You could explained why he should use `$(this).attr("id")` to get the value of `carname` ? That way he can understand why he has to use it and do not reproduce the error in the future ? – Noah Boegli Mar 11 '20 at 06:37
  • you use `carname` variable in code but this variable is not defined in that part of code i use `id ` for get Carname – MBadrian Mar 11 '20 at 06:41
-1

You could define the handler for click inside the function.

new userService().getUsers().then(users => {
  users.forEach(user => {
    const inputElement = document.createElement('input');
    inputElement.value = user.name;
    inputElement.type = 'button';
    inputElement.addEventListener('click', () => alert(user.name));
    document.body.appendChild(inputElement);
  });
});

function userService() {
  const users = [{
      "id": 1,
      "name": "Leanne Graham",
      "username": "Bret",
      "email": "Sincere@april.biz",
      "address": {
        "street": "Kulas Light",
        "suite": "Apt. 556",
        "city": "Gwenborough",
        "zipcode": "92998-3874",
        "geo": {
          "lat": "-37.3159",
          "lng": "81.1496"
        }
      },
      "phone": "1-770-736-8031 x56442",
      "website": "hildegard.org",
      "company": {
        "name": "Romaguera-Crona",
        "catchPhrase": "Multi-layered client-server neural-net",
        "bs": "harness real-time e-markets"
      }
    },
    {
      "id": 2,
      "name": "Ervin Howell",
      "username": "Antonette",
      "email": "Shanna@melissa.tv",
      "address": {
        "street": "Victor Plains",
        "suite": "Suite 879",
        "city": "Wisokyburgh",
        "zipcode": "90566-7771",
        "geo": {
          "lat": "-43.9509",
          "lng": "-34.4618"
        }
      },
      "phone": "010-692-6593 x09125",
      "website": "anastasia.net",
      "company": {
        "name": "Deckow-Crist",
        "catchPhrase": "Proactive didactic contingency",
        "bs": "synergize scalable supply-chains"
      }
    },
    {
      "id": 3,
      "name": "Clementine Bauch",
      "username": "Samantha",
      "email": "Nathan@yesenia.net",
      "address": {
        "street": "Douglas Extension",
        "suite": "Suite 847",
        "city": "McKenziehaven",
        "zipcode": "59590-4157",
        "geo": {
          "lat": "-68.6102",
          "lng": "-47.0653"
        }
      },
      "phone": "1-463-123-4447",
      "website": "ramiro.info",
      "company": {
        "name": "Romaguera-Jacobson",
        "catchPhrase": "Face to face bifurcated interface",
        "bs": "e-enable strategic applications"
      }
    },
    {
      "id": 4,
      "name": "Patricia Lebsack",
      "username": "Karianne",
      "email": "Julianne.OConner@kory.org",
      "address": {
        "street": "Hoeger Mall",
        "suite": "Apt. 692",
        "city": "South Elvis",
        "zipcode": "53919-4257",
        "geo": {
          "lat": "29.4572",
          "lng": "-164.2990"
        }
      },
      "phone": "493-170-9623 x156",
      "website": "kale.biz",
      "company": {
        "name": "Robel-Corkery",
        "catchPhrase": "Multi-tiered zero tolerance productivity",
        "bs": "transition cutting-edge web services"
      }
    },
    {
      "id": 5,
      "name": "Chelsey Dietrich",
      "username": "Kamren",
      "email": "Lucio_Hettinger@annie.ca",
      "address": {
        "street": "Skiles Walks",
        "suite": "Suite 351",
        "city": "Roscoeview",
        "zipcode": "33263",
        "geo": {
          "lat": "-31.8129",
          "lng": "62.5342"
        }
      },
      "phone": "(254)954-1289",
      "website": "demarco.info",
      "company": {
        "name": "Keebler LLC",
        "catchPhrase": "User-centric fault-tolerant solution",
        "bs": "revolutionize end-to-end systems"
      }
    },
    {
      "id": 6,
      "name": "Mrs. Dennis Schulist",
      "username": "Leopoldo_Corkery",
      "email": "Karley_Dach@jasper.info",
      "address": {
        "street": "Norberto Crossing",
        "suite": "Apt. 950",
        "city": "South Christy",
        "zipcode": "23505-1337",
        "geo": {
          "lat": "-71.4197",
          "lng": "71.7478"
        }
      },
      "phone": "1-477-935-8478 x6430",
      "website": "ola.org",
      "company": {
        "name": "Considine-Lockman",
        "catchPhrase": "Synchronised bottom-line interface",
        "bs": "e-enable innovative applications"
      }
    },
    {
      "id": 7,
      "name": "Kurtis Weissnat",
      "username": "Elwyn.Skiles",
      "email": "Telly.Hoeger@billy.biz",
      "address": {
        "street": "Rex Trail",
        "suite": "Suite 280",
        "city": "Howemouth",
        "zipcode": "58804-1099",
        "geo": {
          "lat": "24.8918",
          "lng": "21.8984"
        }
      },
      "phone": "210.067.6132",
      "website": "elvis.io",
      "company": {
        "name": "Johns Group",
        "catchPhrase": "Configurable multimedia task-force",
        "bs": "generate enterprise e-tailers"
      }
    },
    {
      "id": 8,
      "name": "Nicholas Runolfsdottir V",
      "username": "Maxime_Nienow",
      "email": "Sherwood@rosamond.me",
      "address": {
        "street": "Ellsworth Summit",
        "suite": "Suite 729",
        "city": "Aliyaview",
        "zipcode": "45169",
        "geo": {
          "lat": "-14.3990",
          "lng": "-120.7677"
        }
      },
      "phone": "586.493.6943 x140",
      "website": "jacynthe.com",
      "company": {
        "name": "Abernathy Group",
        "catchPhrase": "Implemented secondary concept",
        "bs": "e-enable extensible e-tailers"
      }
    },
    {
      "id": 9,
      "name": "Glenna Reichert",
      "username": "Delphine",
      "email": "Chaim_McDermott@dana.io",
      "address": {
        "street": "Dayna Park",
        "suite": "Suite 449",
        "city": "Bartholomebury",
        "zipcode": "76495-3109",
        "geo": {
          "lat": "24.6463",
          "lng": "-168.8889"
        }
      },
      "phone": "(775)976-6794 x41206",
      "website": "conrad.com",
      "company": {
        "name": "Yost and Sons",
        "catchPhrase": "Switchable contextually-based project",
        "bs": "aggregate real-time technologies"
      }
    },
    {
      "id": 10,
      "name": "Clementina DuBuque",
      "username": "Moriah.Stanton",
      "email": "Rey.Padberg@karina.biz",
      "address": {
        "street": "Kattie Turnpike",
        "suite": "Suite 198",
        "city": "Lebsackbury",
        "zipcode": "31428-2261",
        "geo": {
          "lat": "-38.2386",
          "lng": "57.2232"
        }
      },
      "phone": "024-648-3804",
      "website": "ambrose.net",
      "company": {
        "name": "Hoeger LLC",
        "catchPhrase": "Centralized empowering task-force",
        "bs": "target end-to-end models"
      }
    }
  ];

  this.getUsers = () => Promise.resolve(users);
}
body {
  display: flex;
  flex-direction: column;
}

input[type="button"] {
  margin: 0.2rem;
}
eustatos
  • 686
  • 1
  • 10
  • 21