1

A JavaScript beginner here :) Summary: How can I make an event listener depend on theresult of an AJAX call? If the listener is added after the call, it will be added after each call, thus accumulating the listeners. And if I do it before the call, how can I pass the call result to it? Listeners don't seem to take user-defined arguments.

Details: I am trying to write a Web photo viewer where faces would be marked on the photos. To switch between photos, I'd like to use AJAX. My PHP script returns the name of the picture file and the coordinates of faces (x, y, radius) in the form of a JSON string, e.g.

{"filename":"im1.jpg","faces":[{"x":129,"y":260,"radius":40},{"x":232,"y":297,"radius":40}]} 

I want to draw circles on a canvas, based on faces, once the mouse is over the photo. Therefore I created a listener for the mouseover event. The problem is that if I add the listener after the AJAX call, the canvas gets multiple listeners and keeps drawing circles from the previous photos. So it looks like the handler needs to be defined in advance. But then I am struggling to pass the AJAX response to it. How could I achieve it?

My HTML code so far looks like this:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<canvas id="canvas" width="100" height="600"
    style="background-size: 100% 100%; border: 1px solid #FF0000;"></canvas>
<script>
    var image = new Image();
    var canvas = document.getElementById("canvas");
    var context = canvas.getContext("2d");

    canvas.addEventListener('mouseout', function(evt) {
        context.clearRect(0, 0, canvas.width, canvas.height);
        console.log("Canvas cleared");
    }, false);

    function loadXMLDoc() {
        var xmlhttp;
        if (window.XMLHttpRequest) {// code for IE7+, Firefox, Chrome, Opera, Safari
            xmlhttp = new XMLHttpRequest();
        }
        xmlhttp.onreadystatechange = function() {
            if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
                var ajaxData = JSON.parse(xmlhttp.responseText);
                image.src = ajaxData.filename;
                image.onload = function() {
                    canvas.width = Math.round(canvas.height / image.height * image.width);
                    canvas.style.backgroundImage = "url('" + image.src + "')";
                }
                var faces = ajaxData.faces;

                canvas.addEventListener('mouseover', function(evt) {
                    console.log("Canvas entered");
                    console.log("Faces to mark: " + faces.length);

                    for (i = 0; i < faces.length; i++) {
                        context.beginPath();
                        context.arc(faces[i].x, faces[i].y, 
                            faces[i].radius, 0, 2 * Math.PI);
                        context.strokeStyle = "#00BBBB";
                        context.lineWidth = 1;
                        context.stroke();
                    }
                }, false);

            }
        }
        xmlhttp.open("GET", "get_data.php", true);
        xmlhttp.send();
    }
</script>

<button type="button" onclick="loadXMLDoc()">Change Content</button>
</body>
</html>
texnic
  • 3,959
  • 4
  • 42
  • 75

1 Answers1

1

You can remove the old listener before adding a new one. But a better way would be to save the data into a variable outside the event listener, which gets updated after each AJAX response. Then the function that is called by the listener doesn't need to change at all, it just references the variable with your face data.

EDIT

You can add another variable at the top of your script: var faces;. The rest of your script will have access to it, just like your other variables. You don't need to assign a value to it initially. Now put your mouseover listener at the same level as your mouseout listener. When you get your data from the server, just assign it to faces: faces = ajaxData.faces; (don't use var here, or it will define faces as a local variable within your callback). Now the faces variable defined above will have that data, and the listener will have access to it. Every time you make an AJAX call, it will overwrite the old faces with the new. You may want to add a check within the mouseover listener to make sure the variable has data. You can check it this way:

if (typeof faces == 'object'){
    // your for loop can go here
}

Before the AJAX callback sets the value of faces, typeof faces will equal 'undefined'.

JavaScript will be confusing until you get a grasp on how the language handles scope. Check this out as a good starting place.

Community
  • 1
  • 1
bjudson
  • 4,073
  • 3
  • 29
  • 46
  • Could you give an example please? I've tried removing the listener, but got stuck at the fact that I need to remove it *before* the AJAX call, but then how do I know the listener from *after* the previous call? You see, it's my first experiment with AJAX. I like the second approach more, but again, could you give an example please? I tried it, but the listener created before the AJAX call doesn't have access to the data received during the call. – texnic Sep 30 '14 at 23:19
  • For an example of removing a listener, look at the link in the first sentence of my original answer, and go down to Example. Basically, you need to use a named function, not an anonymous function. I don't think that's the way want to solve this problem, but it is good to know about. – bjudson Sep 30 '14 at 23:46