views:

66

answers:

3

I wrote some code that filters a province/state select field after you choose a country:

    var cache = {};

    function updateProvinceOptions($select, values, initial) {
            for(i in values) {
                    $select.append('<option value="'+values[i][0]+'">'+values[i][1]+'</option>');
            }
            $select.val(initial).trigger('change');
    }

    $('<select class="province" />').insertBefore('.province').change(function() { // insert select boxes beside hidden province inputs
            $(this).siblings('input.province').val($(this).val()); // update hidden input with current value
    });

    $('.country').change(function() {
            var $countrySel = $(this);
            var $provSel = $countrySel.parent().parent().next().children('td').children('select.province');
            var $provInput = $provSel.siblings('input.province');
            var country = $countrySel.val();
            var province = $provInput.val();

            $provSel.empty();
            if(country in cache) {
                    updateProvinceOptions($provSel, cache[country], province);
            }
            else {
                    $provSel.addClass('loading');
                    $.getJSON('/get-provinces.json', {'country':country}, function(provinces) {
                            $provSel.removeClass('loading');
                            cache[country] = provinces;
                            updateProvinceOptions($provSel, provinces, province);
                    });
            }
    }).trigger('change');

It even caches the results so that if you, say, choose Canada, then US, and then Canada again, it doesn't have to hit the server for the list of Canadian provinces a second time. However, I'm displaying 3 of these on the page at the same time. When the page first loads, there's nothing in cache, so all 3 of em hit the server for a list of provinces because none of the ajax calls have returned yet.

Is there a relatively simple way to tell it to "wait" if an ajax call for that country is already in progress? Or should I even bother?

+1  A: 

I would say maybe add a property to cache or some other object outside of your method to indicate whether the cache is currently being loaded. Set the value to true before you call the ajax method and then set it to false in the success function of the ajax call once it is complete. If that is true then you skip any subsequent calls until that value is false.

Just one possible solution.

spinon
Yes, the true/false part is the easy bit (determining if the data has already been requested)...but how do you insert the delay? I guess you add a "waitingOn" queue...and when the ajax complete method fires you iterate through all the waiters....
Mark
Looks like you got it figured out. Glad I could help, if I did at all. Seems like you figured out on your own. Just needed a little pointer.
spinon
A: 

I would say it depends on the use case. Does the user always have to pass through these elements? If so I may be inclined to grab all the data when the page loads or even send the data down with the page in the first place.

Also depends how many items you are dealing with, maybe autocomplete would be a better solution.

Would really need a better feel for the app to advise more strongly

redsquare
Well, I don't want to grab all the data for all the countries.. that [could be] a lot of data... it defaults to Canada, which is our target audience... if they never switch the country, then the rest of the country data *never* needs to be loaded. Going to use this same script for an even bigger list (filters thousands of elements). Auto-complete is not really appropriate, as the resulting lists will still be relatively small (less than 50 options).
Mark
"Pass through"? He always has to choose a province/state, yes. (Unless the default was correct by chance)
Mark
A: 

Nevermind.. answered my own question again when responding to spinon... it wasn't that hard to add a "waiting" queue after all:

    var cache = {};
    var retrieving = {};

    function updateProvinceOptions($select, values, initial) {
            for(i in values) {
                    $select.append('<option value="'+values[i][0]+'">'+values[i][1]+'</option>');
            }
            $select.val(initial).trigger('change');
    }

    $('<select class="province" />').insertBefore('.province').change(function() { // insert select boxes beside hidden province inputs
            $(this).siblings('input.province').val($(this).val()); // update hidden input with current value
    });

    $('.country').change(function() {
            var $countrySel = $(this);
            var $provSel = $countrySel.closest('tr').next('tr').find('select.province');
            var $provInput = $provSel.siblings('input.province');
            var country = $countrySel.val();
            var province = $provInput.val();

            $provSel.empty();
            if(country in cache) {
                    updateProvinceOptions($provSel, cache[country], province);
            } else if(country in retrieving) {
                    retrieving[country].push($provSel);
            }
            else {
                    retrieving[country] = [$provSel]
                    $provSel.addClass('loading');
                    $.getJSON('/get-provinces.json', {'country':country}, function(provinces) {
                            $provSel.removeClass('loading');
                            cache[country] = provinces;
                            while(retrieving[country].length > 0) {
                                    $select = retrieving[country].pop();
                                    updateProvinceOptions($select, provinces, province);    
                            }                               
                    });
            }
    }).trigger('change');
Mark