6
\$\begingroup\$

I'm brand new to JavaScript and jQuery and I feel like there's probably a better way to do this. It seems that the way I designed it may not be very optimal once you get a thousand entries in the list but I do like the fact that it's a live search.

Note: I tried changing the style of the <option>'s to display:none but that doesn't work in all browsers.

http://jsfiddle.net/x6cfF/3/

<input type="search" id="SearchBox" />
<br />
<div class="scrollable" id="CustomerSelectDiv">
    <select size=2 class="scrollableinside" id="CustomerSelect">
        <option value=100>test</option>
        <option value=101>test1</option>
        <option value=102>test2</option>
        <option value=103>test3</option>
    </select>
</div>
<div style="display: none;">
    <select id="CustomerSelectHidden"></select>
</div>






 <script type="text/javascript">
        window.onload=function() {
            var $options = $('#CustomerSelect option');
            document.getElementById("SearchBox").onkeyup = function () {
                var $HiddenOptions = $('#CustomerSelectHidden option');
                $HiddenOptions.each(function (index, value) {
                document.getElementById('CustomerSelect').appendChild(this);
                });
                var search = $.trim($(this).val()).replace(/ +/g, ' ').toLowerCase();
                var element = $options.filter(function () {
                    var text = $(this).text().replace(/\s+/g, ' ').toLowerCase();
                    return !~text.indexOf(search);

                }).appendTo(document.getElementById('CustomerSelectHidden'));
                $('#CustomerSelect option').sort(Sort).appendTo('#CustomerSelect');
            }
        }
        function Sort(a, b)
       {
           return (a.innerHTML > b.innerHTML) ? 1 : -1;
       }
    </script>
\$\endgroup\$
6
\$\begingroup\$

First thing I notice is that you're actually not using jQuery all that much. The point of jQuery is really its convenience compared to the native DOM. You're foregoing that convenience in some places, and relying on jQuery in others. You can of course do it all directly using the DOM and skip jQuery, but you're not quite doing that either. My point is really just that you should decide one way or the other in order to keep the code consistent.

Also, while it's not enforced, convention dictates that function names are lowerCamelCase. If you use PascalCase it implies that a function is a constructor. I.e. your Sort function should be just sort.

However, the sort function is not necessary. Provided you always search in a "master list" of options, and those are already sorted, then the filtered options will retain that order.

The hidden select element isn't necessary either; you can clone the full option list into memory, and filter it from there without having to store it in the document.

In fact, by keeping a master list, you can "prepare" that list to make it faster to filter later.

Here's a demo of my take and here's the code:

$(function () {
    var customerSelect = $("#customer"),
        searchField    = $("#search"),
        options        = customerSelect.find("option").clone(); // clone into memory

    // generic function to clean text
    function sanitize(string) {
        return $.trim(string).replace(/\s+/g, ' ').toLowerCase();
    }

    // prepare the options by storing the
    // "searchable" name as data on the element
    options.each(function () {
        var option = $(this);
        option.data( "sanitized", sanitize(option.text()) );
    });

    // handle keyup
    searchField.on("keyup", function (event) {
        var term = sanitize( $(this).val() ),
            matches;

        // just show all options, if there's no search term
        if( !term ) {
            customerSelect.empty().append(options.clone());
            return;
        }

        // otherwise, show the options that match
        matches = options.filter(function () {
            return $(this).data("sanitized").indexOf(term) != -1;
        }).clone();
        customerSelect.empty().append(matches);
    });
});

You could do a few more things, but I'll leave that as an exercise:

  • If the search term contains the previous search term, you can filter the customer select directly, instead of filtering the entire "master list". I.e. if the last search was for "foo" and the next is for "foobar", you already have the results that match "foo", and only have to find a further subset that contains "foobar" rather than starting from the beginning.

  • If you have a very long list, or you're retrieving the search results via ajax, it's beneficial to have a delay between searches. I.e. on keyup wait, say, 100ms before filtering the list, to see if the user's still typing.

\$\endgroup\$
  • \$\begingroup\$ Amazing! thank you for all the feed back and tips. \$\endgroup\$ – Pr0n Mar 12 '13 at 4:00
  • 1
    \$\begingroup\$ Just fyi, the demo link is broken. \$\endgroup\$ – beporter Sep 14 '16 at 14:54
  • \$\begingroup\$ @beporter Well, darn. Should make a stack snippet I suppose, but I must admit I'm not inclined to mess with a 3-year-old answer, so I'll probably just leave it be. Thanks for the heads-up regardless. \$\endgroup\$ – Flambino Sep 14 '16 at 14:59
3
\$\begingroup\$

Try this.

/**
 * Only shows options that contain a given text.
 * @author Larry Battle <bateru.com/news>
 * @method showOnlyOptionsSimilarToText
 * @param {String|jQuery Object} selectionEl - jQuery selector or jQuery object
 * @param {String} str - String to compare
 * @param {Boolean} isCaseSensitive - is case sensitive
 */
var showOnlyOptionsSimilarToText = function (selectionEl, str, isCaseSensitive) {
    if (isCaseSensitive)
        str = str.toLowerCase();
    // cache the jQuery object of the <select> element
    var $el = $(selectionEl);
    if (!$el.data("options")) {
        // cache all the options inside the <select> element for easy recover
        $el.data("options", $el.find("option").clone());
    }
    var newOptions = $el.data("options").filter(function () {
            var text = $(this).text();
            if (isCaseSensitive)
                text = text.toLowerCase();
            return text.match(str);
        });
    $el.empty().append(newOptions);
};

$("#SearchBox").on("keyup", function () {
    var userInput = $("#SearchBox").val();
    showOnlyOptionsSimilarToText($("#CustomerSelect"), userInput);
});

Demo: http://jsfiddle.net/x6cfF/4/

\$\endgroup\$
  • \$\begingroup\$ Would add multiple +1 if I could, easy good working solution! \$\endgroup\$ – Abbas May 8 '18 at 9:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.