Search code examples
javascriptdom-eventsaddeventlistener

Adding Event Listeners (in a callback function) to generated elements


My objective - and I want to do this without jQuery:

  1. retrieve data from a JSON file (ajax GET)
  2. use data therein to generate a list of links
  3. when one of these links is clicked, get the value of its id (or perhaps another attribute), use it to load corresponding data (from the same JSON file, also via ajax GET)

Having rewritten this code to employ a callback, I'm getting the JSON data and creating links. However, I'm confused about two things regarding how the addEventListener works: first, why is the showProj function invoked as the event listeners are added in the for loop (so far, only alerting each link's id)? And second, why do the links not respond to clicks afterwards? I thought adding event listeners merely enables the generated links to be clickable?

function ajaxReq() {
    var request = new XMLHttpRequest();
    return request;
}

function getJsonData(makeLinks) { // makeLinks = the callback
    var request = ajaxReq();
    request.open("GET", "/json/projects.json", true);
    request.setRequestHeader("content-type", "application/json");
    request.send(null);
    request.onreadystatechange = function() {
        if (request.readyState === 4) {
            if (request.status === 200) {
                makeLinks(request.responseText);
            }
        }
    } // onreadystatechange
} // getJsonData

getJsonData(makeLinks);

function makeLinks(result) { // result = request.responseText
    var projects = JSON.parse(result);
    var projects = projects["Projects"];
    var projectList = document.getElementById("project-list"); // ul#project-list
    for (var project in projects) {
        var projectId = projects[project].id;
        var listItem = "<li><a class=\"project-link\" id=\""+projects[project].project+"\" href=\"#\">" + projects[project].project + "</a></li>";
        projectList.innerHTML += listItem;
    }

    var projLink = document.getElementsByClassName("project-link");
    for (var i = 0; i < projLink.length; i++) {
        var projId = projLink[i].id;
        projLink[i].addEventListener("click", showProject(projId), false); // ** ?? **
    }
} // makeLinks

function showProject(projId) {
    /*
        function showProject will load data corresponding to the link's id (or another attribute);
        presently there are only alerts until the links are working
    */
    alert("projId is: " + projId);
} // showProject

Again, what I'm ultimately after is simply to click on a .project-link class link, get its id (or some other attribute) and then load corresponding data, e.g. (pseudo-code):

projLink.onclick = function(){
    var projId = this.id;
    showProject(projId);
}

... and I realize I could do it with this:

$(document).ready(function() {
    $("#project-list").on("click", 'li a', function() {
        var projId = this.id;
        showProject(projId);
    })
})

... but I want to know why the event listeners aren't working in the first place (that is, without the jQuery bit).

And lastly: would it be considered evil bad practice in this scenario to preclude a scope issue by defining var projLink globally, so that I don't have to redefine it e.g., inside showProj?

Many thanks in advance for any corrections, suggestions, insights.


Solution

  • You're correct that var projLink is scoped to the makeLinks() function, but more importantly it's also inside the Ajax callback, which is a separate asynchronous scope.

    While that Ajax code is running asynchronously, the rest of your JS continues to run as well. So if you call another function to also getElementsByClassName("project-link"), most likely there aren't any yet because the Ajax callback hasn't finished doing its thing.

    Possible options include:

    1. Put everything in the Ajax request.onreadystatechange() within makeLinks() (not ideal)
    2. Adjust the code to use a separate callback function, and pass your JSON data to it. You may have to mess w/timeouts & checks to ensure the data is defined & complete before you try to act on it.

    Take a look at this previous question about Ajax response.