Adding a search feature to a select dropdown list

What you’ll be wanting to do, is to:

  • keep a cache of the available options
  • filter against that cached list for the new options
  • replace the options with the new filtered list

We can do this with some functions, called optionsArray(), filterOptions(), replaceOptions(), and an event handler called filterOptionsHandler() to bring things all together.

First we’ll want somewhere for people to type in their filter for the select field.

<p>Filter: <input id="filter"></p>

We can start with an IIFE (Imemdiately Invoked Function Expression), so that our list of cached options can be stored there.

(function () {
    // the IIFE lets us use a local variable to store information
    var optionsCache = [];
    ...
}());

When something is typed in to the filter field, we’ll want to trigger a handler to filter the options:

(function () {
    ...
    // attach filter event to trigger on keyup
    var filter = document.getElementById("filter");
    filter.addEventListener("keyup", filterOptionsHandler, false);
}());

The filterOptionsHandler() function can check if the cache has already been filled, then pass us on to filter and replace the options:

    // cache the options (if need be), and filter the options
    function filterOptionsHandler(evt) {
        var filterField = evt.target;
        var targetSelect = document.getElementById("cars");
        if (optionsCache.length < 1) {
            optionsCache = optionsArray(targetSelect);
        }
        var options = filterOptions(filterField.value, optionsCache);
        replaceOptions(targetSelect, options);
    }

The optionsArray() function just makes an array from the available options. This is preferred to using the the array-like options structure of the select field, for an actual array is easier to manipulate.

// add option values to the cache
function optionsArray(select) {
    var reduce = Array.prototype.reduce;
    return reduce.call(select.options, function addToCache(options, option) {
        options.push(option);
        return options;
    }, []);
}

I was going to just have a cacheOptions() function that updates the cache value, but when changes are happening I prefer it to be by assignment instead of as a side-effect of the function.

If we didn’t have to worry about supporting different browsers, we could have got our array by using Array.from(select.options) instead.

The filterOptions() function gives a new array of options that match the filter value:

// give a list of options matching the filter value
function filterOptions(filterValue, optionsCache) {
    var options = optionsCache.reduce(function filterCache(options, option) {
        var optionText = option.textContent;
        if (option.text.toLowerCase().match(filterValue.toLowerCase())) {
            options.push(option);
        }
        return options;
    }, []);
    return options;
}

And the replaceOptions() function just removes the existing options, before adding in all of the newly filtered options:

// replace the current options with the new options
function replaceOptions(select, options) {
    while (select.options.length > 0) {
        select.remove(0);
    }
    options.forEach(function addOption(option) {
        select.add(option);
    });
}

Putting all of the above together, we end up with the following script:

(function () {
    // the IIFE lets us use a local variable store information
    var optionsCache = [];

    // add option values to the cache
    function optionsArray(select) {
        var reduce = Array.prototype.reduce;
        return reduce.call(select.options, function addToCache(options, option) {
            options.push(option);
            return options;
        }, []);
    }
    // give a list of options matching the filter value
    function filterOptions(filterValue, optionsCache) {
        return optionsCache.reduce(function filterCache(options, option) {
            var optionText = option.textContent;
            if (option.text.toLowerCase().match(filterValue.toLowerCase())) {
                options.push(option);
            }
            return options;
        }, []);
    }
    // replace the current options with the new options
    function replaceOptions(select, options) {
        while (select.options.length > 0) {
            select.remove(0);
        }
        options.forEach(function addOption(option) {
            select.add(option);
        });
    }
    // cache the options (if need be), and filter the options
    function filterOptionsHandler(evt) {
        var filterField = evt.target;
        var targetSelect = document.getElementById("cars");
        if (optionsCache.length < 1) {
            optionsCache = optionsArray(targetSelect);
        }
        var options = filterOptions(filterField.value, optionsCache);
        replaceOptions(targetSelect, options);
    }
    // attach filter event to trigger on keyup
    var filter = document.getElementById("filter");
    filter.addEventListener("keyup", filterOptionsHandler, false);
}());

You can check out a working demo of the above code at https://jsfiddle.net/pmw57/dsopL9wj/5/

2 Likes