Select2 Custom Matcher for Non-Adjacent Keywords
Asked Answered
C

4

6

I use Select2 in my app to allow for searching a dropdown with about 1200 options.

I am currently making use of the default implementation of Select2's matcher, which works well as long as keywords are adjacent in the search results:

function(term, text) { return text.toUpperCase().indexOf(term.toUpperCase())>=0; }

For example, a search for 'stackoverflow question' returns option 'Stackoverflow question about Select2'

I would however else like the matcher to return results based on non-adjacent keywords. For instance, I would also like it to return the above option when searching for 'stackoverflow select2'.

Would anyone have an idea how to create a custom matcher to allow for this behavior?

Children answered 27/7, 2014 at 11:21 Comment(2)
Please note that tags are not keywords. Stuffing the tag list full of the same words that are in your question (search, keyword, mapper) will not help categorize it. Always be sure to read the descriptions that appear when selecting tags!Furthest
Thanks for the notice Charles, will do so in future posts!Children
H
1

Try this:

search Stackoverflow question, stackoverflow select2, select2 stackoverflow, about stackoverflow select2 question, question select2 about

<select id="e17_2" style="width:300px">
   <option alt="Stackoverflow question about Select2">Stackoverflow question about Select2</option>
   <option alt="Stackoverflow Other line ...">Stackoverflow Other line ...</option>
</select>

Copied from: https://mcmap.net/q/82910/-trying-to-understand-recursion-within-for-loops-in-javascript

function permute(input, permArr, usedChars) {
    var i, ch;
    for (i = 0; i < input.length; i++) {
        ch = input.splice(i, 1)[0];
        usedChars.push(ch);
        if (input.length == 0) {
            permArr.push(usedChars.slice());
        }
        permute(input, permArr, usedChars);
        input.splice(i, 0, ch);
        usedChars.pop();
    }
    return permArr
};


$("#e17_2").select2({
    matcher: function(term, text) { 

                if (term.length == 0) return true;
                texts = text.split(" ");

                allCombinations = permute(texts, [], []);

                for(i in allCombinations){
                    if( allCombinations[i].join(" ").toUpperCase().indexOf(term.toUpperCase())==0 ){
                        return true;
                    }
                }

                return false;

    }
});
Heman answered 27/7, 2014 at 14:9 Comment(5)
Thanks! That works like a charm when keywords are in the same order as the order in which they appear in the options. For example, this indeed returns 'Stackoverflow question about Select2' when searching for 'stackoverflow select2'. However, it does not return this when searching for 'select2 stackoverflow'. Would you have any idea how to accomplish this as well?Children
@AV86NL What you actually want is not what I have implemented. Your search should always starts from left to right, and not otherwise. To find Stackoverflow question about Select2, you may search: Stackoverflow question, question about, question Select2, but never from right to left (select2 about, about Stackoverflow). To do so, you have change the order of your option's words (from Stackoverflow question about Select2 to Select2 about question Stackoverflow) and then search it again... Do you get my point?Heman
Thanks for the explanation. Unfortunately I cannot change the order of the option content since all options are fixed product descriptions. I'm trying to allow an end-user to search for terms in these descriptions (no matter where they are located in the description) and only return those options for which all terms are included in the product description. Is this possible at all with Select2, or should I be looking at some other solution? I have marked your answer as accepted since you fully answered my initial question. Thanks!Children
You dont need to change your options, only define matcher. What are exactly your requirements? Just list them to discard/approveHeman
Reverse search works well now, thanks. Only extra requirement would be to allow searches for any amount of keywords. Current amount of keywords to search for seems limited to two :-)Children
V
11

this is what i did in Select2 4. i wanted matcher to return only options that contains all of keywords entered (assuming keywords are search term splitted by " "). Matching is case insesitive.

        matcher: function (params, data) {
                // If there are no search terms, return all of the data
                if ($.trim(params.term) === '') {
                    return data;
                }
                // `params.term` should be the term that is used for searching
                // split by " " to get keywords
                keywords=(params.term).split(" ");
                // `data.text` is the text that is displayed for the data object
                // check if data.text contains all of keywords, if some is missing, return null
                for (var i = 0; i < keywords.length; i++) {

                    if (((data.text).toUpperCase()).indexOf((keywords[i]).toUpperCase()) == -1) 
                      // Return `null` if the term should not be displayed
                      return null;

                }
                // If here, data.text contains all keywords, so return it.
                return data;
            }

i know this is old topic, but maybe someone find this usefull.

Vidda answered 25/7, 2015 at 12:47 Comment(0)
T
2

If you have large amount of data or nested data then the permutation will take lot of time.

Try instead this for searching using Non-Adjacent Keywords.

Just put this function in your document.ready before initializing select2.

$(function () { 

    var keywords;

    $.fn.select2.defaults = $.extend($.fn.select2.defaults, {            
        placeholder: 'Select...',            
        matcher: function(term, text, option) {

            if ($.trim(term) === '') {
                return true;
            }             
            keywords = (term).split(" ");

            for (var i = 0; i < keywords.length; i++) {

                if ((text.toUpperCase()).indexOf((keywords[i]).toUpperCase()) == -1 )                        
                {                        
                    return false;
                }                                         
            }                
            return true;  
        }
    });

  $("#DropdownID").select2();

});

The Working example is here : http://makmilan.blogspot.in/2015/11/select2-custom-matcher-for-non-adjacent.html

Teodor answered 6/10, 2015 at 6:55 Comment(0)
H
1

Try this:

search Stackoverflow question, stackoverflow select2, select2 stackoverflow, about stackoverflow select2 question, question select2 about

<select id="e17_2" style="width:300px">
   <option alt="Stackoverflow question about Select2">Stackoverflow question about Select2</option>
   <option alt="Stackoverflow Other line ...">Stackoverflow Other line ...</option>
</select>

Copied from: https://mcmap.net/q/82910/-trying-to-understand-recursion-within-for-loops-in-javascript

function permute(input, permArr, usedChars) {
    var i, ch;
    for (i = 0; i < input.length; i++) {
        ch = input.splice(i, 1)[0];
        usedChars.push(ch);
        if (input.length == 0) {
            permArr.push(usedChars.slice());
        }
        permute(input, permArr, usedChars);
        input.splice(i, 0, ch);
        usedChars.pop();
    }
    return permArr
};


$("#e17_2").select2({
    matcher: function(term, text) { 

                if (term.length == 0) return true;
                texts = text.split(" ");

                allCombinations = permute(texts, [], []);

                for(i in allCombinations){
                    if( allCombinations[i].join(" ").toUpperCase().indexOf(term.toUpperCase())==0 ){
                        return true;
                    }
                }

                return false;

    }
});
Heman answered 27/7, 2014 at 14:9 Comment(5)
Thanks! That works like a charm when keywords are in the same order as the order in which they appear in the options. For example, this indeed returns 'Stackoverflow question about Select2' when searching for 'stackoverflow select2'. However, it does not return this when searching for 'select2 stackoverflow'. Would you have any idea how to accomplish this as well?Children
@AV86NL What you actually want is not what I have implemented. Your search should always starts from left to right, and not otherwise. To find Stackoverflow question about Select2, you may search: Stackoverflow question, question about, question Select2, but never from right to left (select2 about, about Stackoverflow). To do so, you have change the order of your option's words (from Stackoverflow question about Select2 to Select2 about question Stackoverflow) and then search it again... Do you get my point?Heman
Thanks for the explanation. Unfortunately I cannot change the order of the option content since all options are fixed product descriptions. I'm trying to allow an end-user to search for terms in these descriptions (no matter where they are located in the description) and only return those options for which all terms are included in the product description. Is this possible at all with Select2, or should I be looking at some other solution? I have marked your answer as accepted since you fully answered my initial question. Thanks!Children
You dont need to change your options, only define matcher. What are exactly your requirements? Just list them to discard/approveHeman
Reverse search works well now, thanks. Only extra requirement would be to allow searches for any amount of keywords. Current amount of keywords to search for seems limited to two :-)Children
W
0

Look for all or part of imperativements words :

element.select2({
    matcher: function(term, text){
        if (term.length < 3) { return true }
        var terms = term.split(" ");
        var count = 0;
        var nbterm = terms.length;

        for(i in terms) {
            if (text.toUpperCase().match(new RegExp(terms[i], "i"))) { count++ }
                if(nbterm == count){
                    return true;
                }
             }
             return false;
         }
});
Weasner answered 14/3, 2016 at 18:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.