Show most relevant search result at the top


#1

Hi there

I’ve been presented with a business case where our users want to see the most relevant match to their search at the top of the Select2 results.

So for example if I have the following results on screen…

Electric Utilities [Utilities]
Gas Utilities [Utilities]
Utilities
Water Utilities [Utilities]

(Anything in [] is a synonym of the main phrase shown on the left-hand side)

… and my searched phrase is just the word Utilities they want to see the results in this order:

Utilities
Electric Utilities [Utilities]
Gas Utilities [Utilities]
Water Utilities [Utilities]

If I then moved the cursor back to the start of the phrase and start prepending the phrase Gas then the results should reset to the original order until I’ve completed the adding of the word Gas. At that point it should show the order as:

Gas Utilities [Utilities]
Electric Utilities [Utilities]
Utilities
Water Utilities [Utilities]

What they’re essentially asking is a top-suggested result feature. It maybe that the top result is also in bold to help make it stand out.

How would I go about adding this kind of feature to Select2?

Thanks
James


#2

Is the data in your Select2 widget retrieved via AJAX, or is it local (hard-coded in either the HTML or JavaScript)? If it’s coming in via AJAX, the you should let the server handle the relevance ranking. It it’s local then the task will be more difficult. A custom matcher function would seem to be the way to go, but it only receives each item (<option> value) once, in the order the items are defined in the HTML or JS array.

The API documentation mentions a sorter callback, but there is no further information about it. However, I played around with it a bit, and here’s how I think it works:
The sorter callback receives an array of all the items (in the Select2 data format) that match the current search term, and is expected to return a copy of that array sorted into the desired order. Some notes:

  • Importantly, the callback does not receive a copy of the current search term, so if you need that value in order to rank the results you’ll have to get it on your own. You could, for example, use jQuery to target the input field (which is an input with a class of select2-search__field) and retrieve its current value.
  • If you are using a custom matcher, that callback is called (once for every item in the Select2) before the sorter callback is called. Importantly, the custom matcher callback does receive the current search term, so you could use it just to capture the current search term (in an “outer” variable that your custom sorter can access). However, it’s a bit less efficient than just grabbing it once as described above, since the custom matcher is called on every item in the Select2. For small lists either approach would work, but for large lists (say more than a few hundred items) you’d want to use the jQuery approach above.

I hope this information is helpful to you.


#3

That’s brilliant! Thanks a lot John that was really helpful.

Doing it server-side makes sense however I’m working with an API I don’t have access to so it’ll need to be client-side on this occasion.

With that in mind I’ve gone ahead this morning and managed to get the sorter method working. Was pretty simple as well! Here’s the code I’ve setup, the only thing I now need to work out is how I format this exact match to the user which I’ve found I can do through the templateResult.

var Term = “”

var matchCustom = function (params, data) {
    var optionText = data.text.toLowerCase();
    var optionId = data.id.toLowerCase();

    params.term = $.trim(params.term);
    Term = params.term;

.
.
.
.
.

}

var sortByRelevance = function (results) {
    function array_move(arr, old_index, new_index) {
        if (new_index >= arr.length) {
            var k = new_index - arr.length + 1;
            while (k--) {
                arr.push(undefined);
            }
        }
        arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
    };

    for (var i = 0; i <= results.length; i++) {
        var result = results[i];

        if (typeof result !== "undefined" && result.text === Term) {
            array_move(results, i, 0);
        }
    }

    return results;
}