سؤال

Me and my loops again... I'm trying to run a for loop across several divs, each being in the class "tooltipBox" but with different ids. In each of these divs is an input text field with class "tttFalloutOrder". What I want to do in the for loop is to attach a click-event-listener on each .tttFalloutOrder input field.

This is my code so far:

function installListener(elementId) {
    $( "div#" + elementId + " > .tttFalloutOrder" ).on("click", function() {
        alert("clicked on " + elementId);
    });
}

function runSimulation() {
    alert("running simulation...");

    $( "#lContent h2" ).html("Simulation <b>in progress...</b>");

    var agents = $( "div.tooltipBox" );
    var rFOs = $( ".rFO" );

    var i, j = 0;

    for(i = 0, j = 0; i < agents.length, j < rFOs.length; i++, j++) {
        var ttl = rFOs[j].value;

        if((ttl == "undefined") || (ttl == "n")) {
            continue;
        } else {
            // function-factory als gscheite closure,
            // siehe http://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example
            // und http://stackoverflow.com/questions/3572480/please-explain-the-use-of-javascript-closures-in-loops?answertab=votes#tab-top
            (function(i, ttl) {
                var agentId = agents[i].id;

                installListener(agentId);

                /*
                $( "div#" + agentId + " > .tttFalloutOrder" ).on("change keypress paste focus textInput input", function() {
                    alert(agentId + "just got changed!");
                });
                */
                setTimeout(function() {
                    $("div#" + agentId + " > div.offlineCover").fadeIn(500);
                }, ttl*1000);
            })(i, ttl);
        }
    }
    $( "#lContent h2" ).html("Simulation <b>complete</b>");
}

As you can see, I am using a closure and even delegated the actual task of attaching the listener to another function, after reading in several SO-answers related to event-listeners in loops that this would help...though I honestly don't quite see how that would make any difference. Anyway, the click listeners still won't fire and frankly I don't understand what is - or rather what is not - happening here.

Thanks in advance - you people at SO have always found a way to point unknowing souls like me into the right direction, and I really appreciate that.

Update Case closed due to my own stupidity... First off, yes, I had an undefined member sitting in my installListener() function. Second, the jQuery selector $( "div#" + elementId + " > .tttFalloutOrder" ) returned undefined, since the > operator selects the second element, which has the first element as a direct parent. However, since .tttFalloutOrder is an input field sitting inside a <form> tag, that is not the case...

I now scrapped the function installListener() and solved the issue with the following code:

function runSimulation() {
    alert("running simulation...");

    $( "#lContent h2" ).html("Simulation <b>in progress...</b>");

    var agents = $( "div.tooltipBox" );
    var rFOs = $( ".rFO" );

    var waitUntilEvaluate = 0;

    var i, j = 0;

    for(i = 0, j = 0; i < agents.length, j < rFOs.length; i++, j++) {
        var ttl = rFOs[j].value;

        if((ttl == "undefined") || (ttl == "n")) {
            continue;
        } else {
            // function-factory als gscheite closure,
            // siehe http://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example
            // und http://stackoverflow.com/questions/3572480/please-explain-the-use-of-javascript-closures-in-loops?answertab=votes#tab-top
            (function(i, ttl) {
                var agentId = agents[i].id;

                $( "div#" + agentId + " .tttFalloutOrder" ).on("input", function() {
                    alert(agentId + "just got changed!");
                    $( "div#" + agentId + " .wasChanged" ).prop("checked", true);
                });

                setTimeout(function() {
                    $("div#" + agentId + " > div.offlineCover").fadeIn(500);
                }, ttl*1000);

                waitUntilEvaluate = waitUntilEvaluate + ttl * 1000;
            })(i, ttl);
        }
    }

    console.log(waitUntilEvaluate);
    setTimeout(function() {
        $( "#lContent h2" ).html("Simulation <b>complete</b>");
        evaluate();
    }, waitUntilEvaluate);
}
هل كانت مفيدة؟

المحلول

You will find it easier to loop with jQuery.each() rather than for(). The .each() callback function will automatically trap the var you need, so there's no need to make another, inner closure.

The most likely thing that's preventing the click handler from working is listenerKind. If no such member exists, then an error will be thrown and the event thread will die.

Your biggest issue is knowing when to change the "in progress" message to "complete". As it stands, the message will change back immediately without waiting for any of the setTimeouts to complete, let alone all of them.

Personally, I would do something like this (see comments in code) :

function runSimulation() {
    var $agents = $("div.tooltipBox"),
        $rFOs = $(".rFO"),
        $message = $("#lContent h2");
    if($agents.filter('.running').length > 0) {
        //Inhibit simulation if any part of an earlier simulation is still running.
        return;
    }
    $message.html("Simulation <b>in progress...</b>");
    $agents.each(function(i, agent) {
        var ttl, $agent;
        if(i >= $rFOs.length) {
            return false;//break out of .each()
        }
        ttl = Number($rFOs.eq(i).val());//Any failure to cast as Number will result in NaN.
        if(isNaN(ttl)) {
            return true;//continue with .each()
        }
        $agent = $(agent).addClass('running');//Provide a testable state (see below and above)
        $agent.children(".tttFalloutOrder").on('click.sim', function() {//Note: namespaced click event allows .off('click.sim') without affecting any other click handlers that might be attached.
            alert("click on " + $agent.attr('id'));
        });
        setTimeout(function() {
            $agent.children(".tttFalloutOrder").off('click.sim');//detach the handler attached with .on('click.sim') .
            $agent.removeClass('running').children(".offlineCover").fadeIn(500);
            if($agents.filter('.running').length == 0) {//if neither this nor any other agent is "running"
                $message.html("Simulation <b>complete</b>");//Signify complete when all parts are complete
            }
        }, ttl*1000);
    });
}

untested

If it the click actions still don't work, then I would suspect the $agent.children(".tttFalloutOrder") selector to be incorrect.

There are other ways of doing this type of thing, notably ways that exploit Deferreds/promises and jQuery.when(), but the above code (suitably debugged) should suffice.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top