简体   繁体   中英

Sort JSON array based on value with jQuery

I've got two dropdown select dropdowns: one for regions and one for cities in the selected region. The result is loaded by AJAX and in my response i get all cities in an JSON array:

{
    1709: "Geertruidenberg", 
    1710: "Netersel", 
    1711: "Macharen", 
    1712: "Beers", 
    1713: "Hank", 
    1714: "Oudemolen", 
    1715: "Nistelrode"
}

I'm using this small plugin to load the data in the select dropdown:

(function($, window) {
    $.fn.replaceOptions = function(options) {
        var self, $option;

        this.empty();
        self = this;

        $.each(options, function(index, option) {
            $option = $("<option></option>")
                .attr("value", index)
                .text(option);
            self.append($option);
        });
    };
})(jQuery, window);

And this piece of javascript to do the AJAX request:

$('select#Profile_regionId').change(function() {
    $.post('/ajax/getcities', {regionid: $(this).val()}, function(data){
        //console.log(data.cities);
        $("select#Profile_cityId").replaceOptions(data.cities);
    }, 'json');
});

All works totally fine, except the city dropdown is automatically sorted on the JSON array key. I tried to use the sort() method for this, but it won't work because it's an Object and not an array. Then i tried to create an array of it:

var values = [];
$.each(data.cities, function(index,value)) {
    values[index] = value;
}

But for some reason, the dropdown list fills itself up from 1 to the first found id (key of array) of the city, and i don't know why it's doing that (array itself looks fine).

How can i sort the thing so my cities are ordered alphabetically in the dropdown list?

It needs to be converted to an array so that it can be sorted. Let's assume this is your object. Note that I rearranged it to be unsorted, to prove this works.

originalData = {
    1712: "Beers", 
    1709: "Geertruidenberg", 
    1710: "Netersel", 
    1713: "Hank", 
    1714: "Oudemolen", 
    1711: "Macharen", 
    1715: "Nistelrode"
};

Now to create an array version we need to create an array, and insert objects into it. I'm calling the keys "year". Note that we're calling parseInt on the keys. Keys in JavaScript (except for arrays) are always strings. For example, {foo: "bar"} has a string key "foo". This also applies to numerical looking keys.

var dataArray = [];
for (year in originalData) {
    var word = originalData[year];
    dataArray.push({year: parseInt(year), word: word});
}

There's a chance that we have our data out of sort right now, so we manually sort it. Note that this is a case sensitive sort. For example, "Qux" comes before "foo".

dataArray.sort(function(a, b){
    if (a.word < b.word) return -1;
    if (b.word < a.word) return 1;
    return 0;
});

The function now just pulls option.year and option.word from our array.

$.fn.replaceOptions = function(options) {
    var self, $option;

    this.empty();
    self = this;

    $.each(options, function(index, option) {
        $option = $("<option></option>")
            .attr("value", option.year)
            .text(option.word);
        self.append($option);
    });
};

And then you finally use the plugin, passing the array. You can put all of this code in the plugin, if that works best for you.

$('#mylist').replaceOptions(dataArray);

fiddle

This will do what you want and take care of the empty ids/undefined values:

var data = {
    1709: "Geertruidenberg", 
    1710: "Netersel", 
    1711: "Macharen", 
    1712: "Beers", 
    1713: "Hank", 
    1714: "Oudemolen", 
    1715: "Nistelrode"
};

var values = [];
$.each(data, function(index, value) {
    values[index] = value;
});

values.sort();

$.each(values, function(index, value) {
    if(value != undefined) {
        $("#Profile_cityId").append("<option>"+ value +"</option");
    }
});

Just replace the append I put in with your own function because jsFiddle was giving me trouble using that. Demo : http://jsfiddle.net/R4jBT/3/

So here is how I would do it. I assume that you are getting an AJAX response that comes like:

results: [
    {year: 1709, city: "Geertruidenberg"}, 
    {year: 1710, city: "Netersel"}, 
    {year: ..., city: ...} 
]

And, in a standard AJAX call, you would run through them like this, in a success function:

success: function(data) {
    var results = data.results;
    if (results.length > 0) {
        for (i=0; i < results.length; i++) {
         
            dataArrayOrig.push({
                "id" : results[i].year,
                "label" : results[i].city 
            });
        }
    }
},

I saw you say you do your call like this:

$.post('/ajax/getcities', {regionid: $(this).val()}, function(data){
    //console.log(data.cities);
$("select#Profile_cityId").replaceOptions(data.cities);}, 'json');

You're not doing anything tests on data to see if it has results (ex. if (data.length > 0) { ... } ), and you would need to push those results to an original array that stays pristine and another that can be sorted, then a final one that can get back the original year to the label, that the dropdown can then receive.

If you do it as I showed, above, you can integrate the lines I gave into the function(data){ ... } area.

Right after the push to the dataArrayOrig object, you can use a comparison function to determine if the label (city) should come before or after the previous entry.

    var results = data.results;
    if (results.length > 0) {
        for (i=0; i < results.length; i++) {
         
            dataArrayOrig.push({
                "id" : results[i].year,
                "label" : results[i].city
            });
            dataArraySorting.push({
                "id" : results[i].year,
                "label" : results[i].city
            });
            dataArraySorting.sort(compare);
            JSON.stringify(dataArraySorting);
        }
    }

The comparison function:

function compare (a, b) {
    if (a.label < b.label)
        return -1;
    if (b.label  < a.label)
        return 1;
    return 0;
});

You could also do this inline:

dataArraySorting.sort(function(a, b){
      if (a.label < b.label) return -1;
      if (b.label < a.label) return 1;
      return 0;
}); 

I prefer the function approach since then it can be re-used. We will see the usefulness of that in a minute.

For your arrays, declare them at the top, before your functions:

dataArrayOrig = [];
dataArraySorting = [];
dataArraySorted = [];

So after that loop that goes through the results, start another one that goes through the "sorting" array and compares its label against the one in the original array we pushed to and pulls out the original ID (Year):

for (var j=0; j < dataArraySorting.length; j++) {
    for (var k=0; k < dataArrayOrig.length; k++) {
        if (dataArraySorting[j].label == dataArrayOrig[k].label) {
            dataArraySorted.push({
                "id" : dataArrayOrig[k].id,
                "label" : dataArraySorting[j].label
            });
            console.log("Sorted ID: " + dataArrayOrig[k].id + ", Sorted label: " + dataArraySorting[j].label);
            dataArraySorted.sort(compare);
            JSON.stringify(dataArraySorted); // array of sorted cities, matched to year
        }
    }
}

You would go on to apply that dataArraySorted array to your dropdown as normal. I tested if I got more than 0 items from the original AJAX call, then I appended the options using id and label from the array's property names:

if (dataArraySorted.length > 0) {
    $.each(dataArraySorted, function() {
        $("#combobox").empty().append($("<option></option>").val(this['id']).html(this['label'));
    });
}

JSFiddle with the results.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM