views:

38

answers:

1

I wrote this code that populates a <select> box with the results of an ajax call...

(function() {
    var cache = {};
    var queue = {};

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

        $provSel.empty();
        if(country == '' || country == null) {
            $provSel.trigger('change');
            return;
        }

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

function addOptions($select, options) {
    $select.append('<option value="">- Select -</option>');
    for(var i in options) {
        $select.append('<option value="{0}">{1}</option>'.format(options[i][0], options[i][1]));
    }
    $select.val($select.siblings('input:first').val()).trigger('change');
}

Works great on all the browsers I tested it on, but of course, not on my client's machine.

I essentially empty() the <option> list, and then append the new <options> one by one. In some browsers when you switch back and forth between countries the state/province list doesn't get refreshed properly... it keeps the old values and just adds the new values, or doesn't add the new values at all.

What's the most portable approach to do this? Should I build all the options as a big html string instead, and then set the whole thing at once with .html()? Should I delete the <select> altogether to ensure it gets emptied correctly?


The HTML:

<tr class="address province">
    <th class="label-cell">
        <label for="id_pickup_address-province">
            Province/State
        </label>
    </th>
    <td class="field-cell">
        <input style="display: none;" id="id_pickup_address-province" class="address province" value="BC" name="pickup_address-province" type="text"><select class="province address"><option value="">- Select -</option><option value="AB">Alberta</option><option value="BC">British Columbia</option><option value="MB">Manitoba</option><option value="NB">New Brunswick</option><option value="NF">Newfoundland and Labrador</option><option value="NT">Northwest Territories</option><option value="NS">Nova Scotia</option><option value="NU">Nunavut</option><option value="ON">Ontario</option><option value="PE">Prince Edward Island</option><option value="QC">Quebec</option><option value="SK">Saskatchewan</option><option value="YT">Yukon</option></select>
    </td>
</tr>

The province select is actually added dynamically, and it updates the hidden field when it's changed. Had to do this to get it to interface with Django correctly and to degrade nicely.

+1  A: 

Not sure, if that helps here. I experienced some problems with populating <select>s after an AJAX request (I think it was IE which had problems).

I could solve these problems then by adding a 1 millisecond delay before adding the <option>s to the <select>:

$.getJSON('/get-provinces.json', {country:country}, function(provinces) {
    setTimeout(function() {
        cache[country] = provinces;
        while(queue[country].length > 0) {
            var $select = queue[country].pop();
            $select.removeClass('loading');
            addOptions($select, cache[country]);    
        }
    }, 1); // 1 millisecond delay
});

May you give it a try...

Dave
I'll give it a try... seems like an odd hack.
Mark
Yes, I agree, but it was the only "solution" I found to overcome the weird behaviour of IE...
Dave