8

Inside a $.getJSON success function, I first trigger another element's click event:

$('#' + data[0].ID).trigger('click');

The triggered click event has its own $.getJSON method to load a bunch of data into divs. The next line after the triggered event:

$.each(data[0].ListEntries, function (key, val) {
        //this relies on the triggered click event 
        //having completely loaded all data!
}

At first the $.each didn't appear to be doing anything, but I added an alert right after the triggered event. After responding to the alert, the code in $.each shows what it's supposed to.

I'm guessing $.each is running before the click event finishes loading the data.

setTimeout pauses long enough for the click event to load data, but I'd rather not set an arbitrary time:

setTimeout(function() { 
    $.each(data[0].ListEntries, function (key, val) {
        //this relies on the triggered click event 
        //having completely loaded all data!
    }
}, 1000);

I also tried $.when and $.then to no avail (although adding alert before $.each inside $.then creates delay for $.each to work):

$.when($('#' + data[0].ID).trigger('click')).then(function () {
    $.each(data[0].ListEntries, function (key, val) {
        //this relies on the triggered click event 
        //having completely loaded all data!
    })
Chris M
  • 107
  • 1
  • 1
  • 8

3 Answers3

6

You could use a custom event for this. You could put your $.each in a listener for the event and then your $.getJSON success handler could trigger that event:

$('#x').click(function() {
    var _this = this;
    $.getJSON(url, data, function(data) {
        // do things to data...
        _this.trigger('data-loaded');
    });
});
$('#x').on('data-loaded', function() {
    $.each(...)
});
$('#x').click();

Demo: http://jsfiddle.net/ambiguous/FeYTB/

mu is too short
  • 426,620
  • 70
  • 833
  • 800
  • I'm not sure if I'm doing something wrong, but I can't get your suggestion to work. I did find this however which does work... basically setting async to false before the $.getJSON and then setting it back to true afterward. http://stackoverflow.com/questions/2765411/is-it-possible-to-set-asyncfalse-to-getjson-call – Chris M May 19 '12 at 07:18
  • 1
    Remember that `async false` is not a cross-browser solution. – Beetroot-Beetroot May 19 '12 at 07:24
  • @ChrisM: asynch is evil and rarely suitable. What are you doing differently than the demo and what does "not working" mean? – mu is too short May 19 '12 at 08:08
6

Tidied up for greater clarity

.trigger() returns a jQuery object so you are denied the option of doing $.when() ... $.then().

On the other hand, .triggerHandler(), will return an object of your choice, thus making it possible to do the deferred trick.

Your code is organised in three functions, simplified below. The call path is 1,2,3 and the all-important return path is 3,2,1.

(1) The highest level (JSON success) function will include the following lines :

function() {
    ...
    $.when($('#' + data[0].ID).triggerHandler('click')).done(function() {
        $.each(data[0].ListEntries, function (key, val) {
            ...
        });
    });
    ...
};

(2) The click handler triggered by (1), will be as follows :

$("img.Button").on("click", function () {
    return GetModels(this.id);//here `return` passes on the jqXHR object, up the return path.
});

(3) And the lowest level function containing the JSON, on which (1) depends, will be of this general form :

function GetModels(id) {
    ...
    var jqXHR = getJSON(function(){...});
    ...
    return jqXHR;//it is vital to retutn the jqXHR object arising from the json call.
}

Notes :

  • The return path delivers, as the "promise" argument to the .when() method in (1), the jqXHR object arising from the .getJSON() call in (3). The chained .done() in (1) is thus forced to wait for the jqXHR to be resolved (ie. complete) before firing the function that is provided as its argument.
  • The click handler makes no assumptions about how it is invoked. It simply returns the jqXHR object. Thus when invoked with .triggerHandler(), the click handler can have additional behaviour appended to it without affecting the normal click action.
  • It would be simpler to call GetModels() directly from (1), cutting out the middle-man (2), which is fine if GetModels() behaviour is uniquely wanted. If, however, (1) needs to be responsive to any future changes to the triggered click handler (2), then the above approach needs to be adopted.
Beetroot-Beetroot
  • 18,022
  • 3
  • 37
  • 44
  • event.preventDefault() in the triggered click event is saying "Object doesn't support this property or method". The code setting up the click event is: $("img.Button").on("click", function () { GetModels(this.id); }); This is what populates the divs with data needed for the OP code. Does something here need to change? – Chris M May 19 '12 at 07:57
  • OK, for an img `.preventDefault()` isn't necessary as an img has no default action, so that line can be deleted. For completeness above, I have left it in but added some safety to avoid the error being thrown. – Beetroot-Beetroot May 19 '12 at 08:11
  • Dang, still no go. The dependent $.each still isn't waiting for the click to finish. I think I'll have to find another method of populating this dependent data. – Chris M May 19 '12 at 08:44
  • Ultimately, this can be made to work. It's hard to advise without seeing all the necessary code. Does `GetModels()` perform the `getJSON()` for which you need to wait? If so then `GetModels()` needs to return its jqXHR object. – Beetroot-Beetroot May 19 '12 at 11:30
  • I added the return jqXHR last night, and the last step was to add the return on the img.Button click function's GetModels(this.id). That's great, I really appreciate the help! Does adding that return in the img.Button event simply force callers to wait for a return value before continuing? – Chris M May 19 '12 at 17:08
  • No, at least not in itsef. The return forms part of a return path up which a jqXHR object (arising from a `.getJSON()` statement) travels, eventually appearing as the argument to the `.when()` method, whose chained `.done()` is thus forced to wait for the jqXHR to bs resolved before firing the function that is provided as its argument. This is fairly advanced stuff; deferreds are tricky in themselves, then there's the added complication of a two stage call path, the first stage of which employs `.triggerHandler()`. – Beetroot-Beetroot May 20 '12 at 00:03
  • Answer above severely tidied for greater clarity. – Beetroot-Beetroot May 20 '12 at 06:28
  • the docs for `triggerHandler` don't state what the return value is and for me what comes back in *undefined* instead of a promise so the above doesn't work – ekkis Jun 08 '17 at 21:54
0

Why don't you try a

$('#' + data[0].ID).live('click', function (){
   $.each(data[0].ListEntries, function (key, val) {
            // your stuff here 
    });
});

So the $.each would run only after the click has been triggered.

karth
  • 635
  • 3
  • 13
  • 25
  • The click event usually loads data during its element's click without needing to iterate through additional data. Another event, described above, gets server data for itself, then needs to load the same data from the first click event before iterating the additional data. – Chris M May 19 '12 at 05:53
  • Hm.. I have probably misunderstood your question as "Running the $each only after the trigger click is complete". My bad. – karth May 19 '12 at 05:58
  • Thanks for the suggestion anyway, I didn't know about .live; I'll be able to use that elsewhere for sure. – Chris M May 19 '12 at 06:14
  • 1
    @ChrisM: [`live`](http://api.jquery.com/live/) has been deprecated in favor of [`on`](http://api.jquery.com/on/). – mu is too short May 19 '12 at 06:49