1

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.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
user1613163
  • 127
  • 1
  • 3
  • 16
  • You're using `addEventListener` which is only supported in IE9 and above, yet you're using `ActiveXObject("Microsoft.XMLHTTP")` which is necessary only for IE<=7? –  Oct 14 '14 at 18:48
  • @torazaburo Thx, point taken. I'm still a novice with ajax. But I don't suppose this has any bearing on my inquiry. For this (personal) project, I'm only interested in best practices for modern browser. – user1613163 Oct 14 '14 at 18:54
  • Best practice for modern browsers would be to use jQuery `.ajax()` or `.getJSON()`. But you're right - the crux of your particular issue appears to be the async nature of Ajax in general, not the specific syntax. – mc01 Oct 14 '14 at 19:06
  • @mc01 Thank you. I'm studying javascript, trying to do as much as possible w/out jQuery :) – user1613163 Oct 15 '14 at 15:48

2 Answers2

0

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.

Community
  • 1
  • 1
mc01
  • 3,750
  • 19
  • 24
  • Thank you. I've read https://stackoverflow.com/questions/14220321/how-to-return-the-response-from-an-ajax-call/14220323 and https://stackoverflow.com/questions/23667086/why-is-my-variable-unaltered-after-i-modify-it-inside-of-a-function-asynchron (among others), and re-written my code with a callback. However my links are still not 'hearing' clicks. I'll either edit the OP or ask a new one. cheers! – user1613163 Oct 15 '14 at 16:02
0

Having read up a little further on event listeners, I have discovered the answer to my initial two questions and solved my current issue so if it's of interest to anyone:

projLink[i].addEventListener("click", showProject(projId), false);

The 'showProj' function is invoked in the above statement because i) it's (also) a callback, and - if I understand correctly - ii) because an argument is provided; therefore it's invoked as each of the elements in the for loop has the click event added. Evidently if no argument is provided to the addEventListener callback, then the callback function will indeed be invoked on click. [ more insight on this would be welcome ]

Furthermore, I learn that the third argument (boolean) pertains to capture & bubbling, however I shall not presently sidetrack myself on the finer points of capture & bubbling. Suffice to say that in my case, I'm fairly certain I can achieve my needs thusly:

projLink[i].addEventListener("click", showProject, false);

... (and perhaps even without the optional boolean altogether, though my understanding is that it's better practice to include it (?)

svs, over & out.

user1613163
  • 127
  • 1
  • 3
  • 16