7

I am trying to resort a list of dynamically created SELECT OPTIONS in javascript.

I have no access to the source, otherwise I would change this from the server code.

This is the select:

<select class="text12 width_constrained" name="/web/personalisation/request/DesignConsultationRequestFormHandler.addressItem">
<option value="1|Marsh Gardens||Southampton, Hedge End|Hampshire|SO30 2XN|United Kingdom|false|false|false|false|false|false|false">1 Marsh Gardens, Southampton, Hedge End, SO30 2XN</option>
<option value="3|Marsh Gardens||Southampton, Hedge End|Hampshire|SO30 2XN|United Kingdom|false|false|false|false|false|false|false">3 Marsh Gardens, Southampton, Hedge End, SO30 2XN</option>
<option value="5|Marsh Gardens||Southampton, Hedge End|Hampshire|SO30 2XN|United Kingdom|false|false|false|false|false|false|false">5 Marsh Gardens, Southampton, Hedge End, SO30 2XN</option>
<option value="7|Marsh Gardens||Southampton, Hedge End|Hampshire|SO30 2XN|United Kingdom|false|false|false|false|false|false|false">7 Marsh Gardens, Southampton, Hedge End, SO30 2XN</option>
<option value="9|Marsh Gardens||Southampton, Hedge End|Hampshire|SO30 2XN|United Kingdom|false|false|false|false|false|false|false">9 Marsh Gardens, Southampton, Hedge End, SO30 2XN</option>
<option value="11|Marsh Gardens||Southampton, Hedge End|Hampshire|SO30 2XN|United Kingdom|false|false|false|false|false|false|false">11 Marsh Gardens, Southampton, Hedge End, SO30 2XN</option>
<option value="13|Marsh Gardens||Southampton, Hedge End|Hampshire|SO30 2XN|United Kingdom|false|false|false|false|false|false|false">13 Marsh Gardens, Southampton, Hedge End, SO30 2XN</option>
<option value="15|Marsh Gardens||Southampton, Hedge End|Hampshire|SO30 2XN|United Kingdom|false|false|false|false|false|false|false">15 Marsh Gardens, Southampton, Hedge End, SO30 2XN</option>
<option value="17|Marsh Gardens||Southampton, Hedge End|Hampshire|SO30 2XN|United Kingdom|false|false|false|false|false|false|false">17 Marsh Gardens, Southampton, Hedge End, SO30 2XN</option>
<option value="19|Marsh Gardens||Southampton, Hedge End|Hampshire|SO30 2XN|United Kingdom|false|false|false|false|false|false|false">19 Marsh Gardens, Southampton, Hedge End, SO30 2XN</option>
<option value="21|Marsh Gardens||Southampton, Hedge End|Hampshire|SO30 2XN|United Kingdom|false|false|false|false|false|false|false">21 Marsh Gardens, Southampton, Hedge End, SO30 2XN</option>
<option value="23|Marsh Gardens||Southampton, Hedge End|Hampshire|SO30 2XN|United Kingdom|false|false|false|false|false|false|false">23 Marsh Gardens, Southampton, Hedge End, SO30 2XN</option>
<option value="25|Marsh Gardens||Southampton, Hedge End|Hampshire|SO30 2XN|United Kingdom|false|false|false|false|false|false|false">25 Marsh Gardens, Southampton, Hedge End, SO30 2XN</option>
<option value="27|Marsh Gardens||Southampton, Hedge End|Hampshire|SO30 2XN|United Kingdom|false|false|false|false|false|false|false">27 Marsh Gardens, Southampton, Hedge End, SO30 2XN</option>
<option value="2|Marsh Gardens||Southampton, Hedge End|Hampshire|SO30 2XN|United Kingdom|false|false|false|false|false|false|false">2 Marsh Gardens, Southampton, Hedge End, SO30 2XN</option>
<option value="4|Marsh Gardens||Southampton, Hedge End|Hampshire|SO30 2XN|United Kingdom|false|false|false|false|false|false|false">4 Marsh Gardens, Southampton, Hedge End, SO30 2XN</option>
<option value="6|Marsh Gardens||Southampton, Hedge End|Hampshire|SO30 2XN|United Kingdom|false|false|false|false|false|false|false">6 Marsh Gardens, Southampton, Hedge End, SO30 2XN</option>
<option value="8|Marsh Gardens||Southampton, Hedge End|Hampshire|SO30 2XN|United Kingdom|false|false|false|false|false|false|false">8 Marsh Gardens, Southampton, Hedge End, SO30 2XN</option>
<option value="10|Marsh Gardens||Southampton, Hedge End|Hampshire|SO30 2XN|United Kingdom|false|false|false|false|false|false|false">10 Marsh Gardens, Southampton, Hedge End, SO30 2XN</option>
<option value="12|Marsh Gardens||Southampton, Hedge End|Hampshire|SO30 2XN|United Kingdom|false|false|false|false|false|false|false">12 Marsh Gardens, Southampton, Hedge End, SO30 2XN</option>
<option value="14|Marsh Gardens||Southampton, Hedge End|Hampshire|SO30 2XN|United Kingdom|false|false|false|false|false|false|false">14 Marsh Gardens, Southampton, Hedge End, SO30 2XN</option>
<option value="16|Marsh Gardens||Southampton, Hedge End|Hampshire|SO30 2XN|United Kingdom|false|false|false|false|false|false|false">16 Marsh Gardens, Southampton, Hedge End, SO30 2XN</option>
<option value="18|Marsh Gardens||Southampton, Hedge End|Hampshire|SO30 2XN|United Kingdom|false|false|false|false|false|false|false">18 Marsh Gardens, Southampton, Hedge End, SO30 2XN</option>
<option value="20|Marsh Gardens||Southampton, Hedge End|Hampshire|SO30 2XN|United Kingdom|false|false|false|false|false|false|false">20 Marsh Gardens, Southampton, Hedge End, SO30 2XN</option>
<option value="22|Marsh Gardens||Southampton, Hedge End|Hampshire|SO30 2XN|United Kingdom|false|false|false|false|false|false|false">22 Marsh Gardens, Southampton, Hedge End, SO30 2XN</option>
<option value="24|Marsh Gardens||Southampton, Hedge End|Hampshire|SO30 2XN|United Kingdom|false|false|false|false|false|false|false">24 Marsh Gardens, Southampton, Hedge End, SO30 2XN</option>
<option value="26|Marsh Gardens||Southampton, Hedge End|Hampshire|SO30 2XN|United Kingdom|false|false|false|false|false|false|false">26 Marsh Gardens, Southampton, Hedge End, SO30 2XN</option>
<option value="28|Marsh Gardens||Southampton, Hedge End|Hampshire|SO30 2XN|United Kingdom|false|false|false|false|false|false|false">28 Marsh Gardens, Southampton, Hedge End, SO30 2XN</option>

I am currently using the following, but it only sorts by the first character using the natural sort, which does not support alphanumeric...

jQuery('document').ready(function() {
var opts = jQuery('form[name=consultationForm] select option');

console.log("options");
console.log(opts);

var arr = opts.map(function(_, o) {
    return {
        t: jQuery(o).text(),
        v: o.value
    };
}).get();

console.log("options post sort");
//arr.sort();


arr.sort(function(o1, o2) {
    return o1.t > o2.t ? 1 : o1.t < o2.t ? -1 : 0;
});

console.log(arr);

});

The obvious desired output is to have all the options ordered in numeric value, but also allowing for subsets e.g. 2a, 2b, 2c.

Can anyone help further?

3
  • consider using a split. Commented Oct 8, 2013 at 12:20
  • 1
    What do you mean by : "but also allowing for subsets e.g. 2a, 2b, 2c." ? Commented Oct 8, 2013 at 12:21
  • So where address are "1 Marsh Gardens", "2 Marsh Gardens"...you could have "1 Marsh Gardens", "2a Marsh Gardens", "2b Marsh Gardens" Commented Oct 8, 2013 at 12:31

6 Answers 6

11

It sounds like what you want is Natural Sort, i found this code sample here: Javascript : natural sort of alphanumerical strings This will give proper sorting for values like 2a, 2b, 2c, as well as values without a street number.

function naturalSorter(as, bs){
    var a, b, a1, b1, i= 0, n, L,
    rx=/(\.\d+)|(\d+(\.\d+)?)|([^\d.]+)|(\.\D+)|(\.$)/g;
    if(as=== bs) return 0;
    a= as.toLowerCase().match(rx);
    b= bs.toLowerCase().match(rx);
    L= a.length;
    while(i<L){
        if(!b[i]) return 1;
        a1= a[i],
        b1= b[i++];
        if(a1!== b1){
            n= a1-b1;
            if(!isNaN(n)) return n;
            return a1>b1? 1:-1;
        }
    }
    return b[i]? -1:0;
}

Then

arr.sort(function(o1, o2) {
    return naturalSorter(o1.t, o2.t);
});

I edited TJ's fiddle slightly to give an example: JSFiddle

Sign up to request clarification or add additional context in comments.

2 Comments

This sorted it for me, although you have a spelling mistake in the function call in the arr.sort. Many thanks
Thanks for the catch, all fixed up.
1

I believe localeSort should do what you want if used properly. If, for some reason, you need to implement your own sort comparison, try this:

arr.sort(function(o1, o2) { 
    return o1.t.split(/(\d+|\D+)/)[1] - o2.t.split(/(\d+|\D+)/)[1] 
} );

The split creates an array of alternating numeric and non-numeric strings (with empty strings between them that we can ignore for this purpose). Element 0 of the array is empty, so element 1 is the first component - the house number, with no extraneous characters that will make the numeric converter treat it as NaN. The subtraction forces the conversion from string to number, and you're good to go.

That idea generalizes to a full natural sort by examining as much of each string as necessary:

arr.sort(function(o1, o2) {
           var a = o1.t.split(/(\d+|\D+)/).filter(function(s){return s!=""});
           var b = o2.t.split(/(\d+|\D+)/).filter(function(s){return s!=""});
           for (var cmp = 0, i = 0; 0 == cmp && i < a.length && i < b.length; i++) {
             var n1 = a[i] - 0, n2 = b[i] - 0;
             if (!isNaN(n1) && !isNaN(n2)) 
               cmp = n1 - n2;
             else if (a[i] < b[i]) 
               cmp = -1;
             else if (a[i] > b[i])
               cmp = 1;
           }
           return cmp;
         })

1 Comment

Close answer, but this doesn't react well to an address that has no house number and puts those randomly through the address output.
1

Your strings are probably converted to numbers because they start with a digit. You can try the following sort function:

arr.sort(function(o1, o2) {
    return o1.t.localeCompare(o2.t);
});

See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare for more information.

4 Comments

I'm getting an error when I try to implement this: Uncaught TypeError: Object #<Object> has no method 'localeCompare'
Sorry, forgot the t key. Added it to the example.
This seems to randomly sort the output list
@Kimear - the o2.t1 is a typo; it should be o2.t. Does that help?
0

I created a fiddle to demonstrate the re-ordering. I stored the values of the value and text attribute in an array as well as the parsed street number. No the sorting is easier. One could argue that my code is a bit bloated and there probably is a more elegant way.

$(document).ready(function () {

    var selectModel = []; // We use this to store all the option data

    $('select.text12 option').each(function () {
        var $this = $(this);
        selectModel.push({
            value: $this.val(),
            text: $this.text(),
            index: parseInt($this.val().split('|')[0], 10) // Make sure it's a number
        });
    });

    selectModel.sort(function (a, b) {
        return a.index - b.index;
    });

    var tempHtml = '';

    for (var i = 0, ii = selectModel.length; i < ii; i++) {
        tempHtml += '<option value="' + selectModel[i].value + '">' + selectModel[i].text + '</option>';
    }

    // Here the option html is recreated
    $('select.text12').html(tempHtml);

});

Comments

0

Incase anyone wants a sorting algorithm that doesn't include a regex. I would assume it's faster, but haven't tested that.

function alphanum(a, b) {
  function chunkify(t) {
    var tz = [], x = 0, y = -1, n = 0, i, j;

    while (i = (j = t.charAt(x++)).charCodeAt(0)) {
      var m = (i == 46 || (i >=48 && i <= 57));
      if (m !== n) {
        tz[++y] = "";
        n = m;
      }
      tz[y] += j;
    }
    return tz;
  }

  var aa = chunkify(a);
  var bb = chunkify(b);

  for (x = 0; aa[x] && bb[x]; x++) {
    if (aa[x] !== bb[x]) {
      var c = Number(aa[x]), d = Number(bb[x]);
      if (c == aa[x] && d == bb[x]) {
        return c - d;
      } else return (aa[x] > bb[x]) ? 1 : -1;
    }
  }
  return aa.length - bb.length;
}

The fiddle.

Comments

0

I Modified the script to work with objects (natural sorting by object property)

Code

function alphanumCase(a, b, prop) {
    function chunkify(t) {
        var tz = new Array();
        var x = 0, y = -1, n = 0, i, j;

        while (i = (j = t.charAt(x++)).charCodeAt(0)) {
            var m = (i == 46 || (i >=48 && i <= 57));
            if (m !== n) {
                tz[++y] = "";
                n = m;
            }
            tz[y] += j;
        }
        return tz;
    }

    var aa = chunkify(a[prop].toLowerCase());
    var bb = chunkify(b[prop].toLowerCase());

    for (x = 0; aa[x] && bb[x]; x++) {
        if (aa[x] !== bb[x]) {
            var c = Number(aa[x]), d = Number(bb[x]);
            if (c == aa[x] && d == bb[x]) {
                return c - d;
            } else return (aa[x] > bb[x]) ? 1 : -1;
        }
    }
    return aa.length - bb.length;
}

Usage

someArray.sort(function(a, b) {
    //sort naturally by the name property (a.name vs b.name)
    return alphanumCase(a, b, 'name');
});

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.