Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

Filtering List Data with a jQuery-searchFilter Plugin


:P
On this page:
continued from Page 1

When dealing with list based data on HTML forms, filtering that data down based on a search text expression is an extremely useful feature. We’re used to search boxes on just about anything these days and HTML forms should be no different. In this post I’ll describe how you can easily filter a list down to just the elements that match text typed into a search box. It’s a pretty simple task and it’s super easy to do, but I get a surprising number of comments from developers I work with who are surprised how easy it is to hook up this sort of behavior, that I thought it’s worth a blog post.

But Angular does that out of the Box, right?

These days it seems everybody is raving about Angular and the rich SPA features it provides. One of the cool features of Angular is the ability to do drop dead simple filters where you can specify a filter expression as part of a looping construct and automatically have that filter applied so that only items that match the filter show. I think Angular has single handedly elevated search filters to first rate, front-row status because it’s so easy.

I love using Angular myself, but Angular is not a generic solution to problems like this. For one thing, using Angular requires you to render the list data with Angular – if you have data that is server rendered or static, then Angular doesn’t work. Not all applications are client side rendered SPAs – not by a long shot, and nor do all applications need to become SPAs.

Long story short, it’s pretty easy to achieve text filtering effects using jQuery (or plain JavaScript for that matter) with just a little bit of work. Let’s take a look at an example.

Why Filter?

Client side filtering is a very useful tool that can make it drastically easier to sift through data displayed in client side lists. In my applications I like to display scrollable lists that contain a reasonably large amount of data, rather than the classic paging style displays which tend to be painful to use. So I often display 50 or so items per ‘page’ and it’s extremely useful to be able to filter this list down.

Here’s an example in my Time Trakker application where I can quickly glance at various common views of my time entries. I can see Recent Entries, Unbilled Entries, Open Entries etc and filter those down by individual customers and so forth. Each of these lists results tends to be a few pages worth of scrollable content.

The following screen shot shows a filtered view of Recent Entries that match the search keyword of CellPage:

capture-1

As you can see in this animated GIF, the filter is applied as you type, displaying only entries that match the text anywhere inside of the text of each of the list items. This is an immediately useful feature for just about any list display and adds significant value.

A few lines of jQuery

The good news is that this is trivially simple using jQuery.

To get an idea what this looks like, here’s the relevant page layout showing only the search box and the list layout:

<div id="divItemWrapper">
    <div class="time-entry">
        <div class="time-entry-right">
            May 11, 2014 - 7:20pm<br />
            <span style='color:steelblue'>0h:40min</span><br />
            <a id="btnDeleteButton" href="#" class="hoverbutton" data-id="16825">
                <img src="images/remove.gif" />
            </a>
        </div>
        <div class="punchedoutimg"></div>
        <b><a href='/TimeTrakkerWeb/punchout/16825'>Project Housekeeping</a></b><br />
        <small><i>Sawgrass</i></small>
    </div>
    ... more items here
</div>

So we have a searchbox txtSearchPage and a bunch of DIV elements with a .time-entry CSS class attached that makes up the list of items displayed.

To hook up the search filter with jQuery is merely a matter of a few lines of jQuery code hooked to the .keyup() event handler:

<script type="text/javascript">
    $("#txtSearchPage").keyup(function() {
        var search = $(this).val();
        $(".time-entry").show();
        if (search) $(".time-entry").not(":contains(" + search + ")").hide();
    });
</script>

The idea here is pretty simple: You capture the keystroke in the search box and capture the search text. Using that search text you first make all items visible and then hide all the items that don’t match.

Since DOM changes are applied after a method finishes execution in JavaScript, the show and hide operations are effectively batched up and so the view changes only to the final list rather than flashing the whole list and then removing items on a slow machine. You get the desired effect of the list showing the items in question.

Case Insensitive Filtering

But there is one problem with the solution above: The jQuery :contains filter is case sensitive, so your search text has to match expressions explicitly which is a bit cumbersome when typing. In the screen capture above I actually cheated – I used a custom filter that provides case insensitive contains behavior.

jQuery makes it really easy to create custom query filters, and so I created one called containsNoCase. Here’s the implementation of this custom filter:

$.expr[":"].containsNoCase = function(el, i, m) {
    var search = m[3];
    if (!search) return false;
    return new RegExp(search, "i").test($(el).text());
};

This filter can be added anywhere where page level JavaScript runs – in page script or a seperately loaded .js file.  The filter basically extends jQuery with a : expression. Filters get passed a tokenized array that contains the expression. In this case the m[3] contains the search text from inside of the brackets. A filter basically looks at the active element that is passed in and then can return true or false to determine whether the item should be matched. Here I check a regular expression that looks for the search text in the element’s text.

So the code for the filter now changes to:

$(".time-entry").not(":containsNoCase(" + search + ")").hide();

And voila – you now have a case insensitive search.

You can play around with another simpler example using this Plunkr:
http://plnkr.co/edit/hDprZ3IlC6uzwFJtgHJh?p=preview

Wrapping it up in a jQuery Plug-in

To make this even easier to use and so that you can more easily remember how to use this search type filter, we can wrap this logic into a small jQuery plug-in:

(function($, undefined) {
    $.expr[":"].containsNoCase = function(el, i, m) {
        var search = m[3];
        if (!search) return false;
        return new RegExp(search, "i").test($(el).text());
    };

    $.fn.searchFilter = function(options) {
        var opt = $.extend({
            // target selector
            targetSelector: "",
            // number of characters before search is applied
            charCount: 1
        }, options);

        return this.each(function() {
            var $el = $(this);
            $el.keyup(function() {
                var search = $(this).val();

                var $target = $(opt.targetSelector);
                $target.show();

                if (search && search.length >= opt.charCount)
                    $target.not(":containsNoCase(" + search + ")").hide();
            });
        });
    };
})(jQuery);

To use this plug-in now becomes a one liner:

$("#txtSearchPagePlugin").searchFilter({ targetSelector: ".time-entry", charCount: 2})

You attach the .searchFilter() plug-in to the text box you are searching and specify a targetSelector that is to be filtered. Optionally you can specify a character count at which the filter kicks in since it’s kind of useless to filter at a single character typically.

Summary

This is s a very easy solution to a cool user interface feature your users will thank you for.

Search filtering is a simple but highly effective user interface feature, and as you’ve seen in this post it’s very simple to create this behavior with just a few lines of jQuery code. While all the cool kids are doing Angular these days, jQuery is still useful in many applications that don’t embrace the ‘everything generated in JavaScript’ paradigm. I hope this jQuery plug-in or just the raw jQuery will be useful to some of you…

Resources

Posted in jQuery  HTML5  JavaScript  

The Voices of Reason


 

Eduardo
May 12, 2014

# re: Filtering List Data with a jQuery-searchFilter Plugin

How would you extend it to be accent insensitive?

Rick Strahl
May 12, 2014

# re: Filtering List Data with a jQuery-searchFilter Plugin

@Eduardo - what do you mean exactly? There can be issues because it's a regex expression and if you throw in some regEx chars it can cause issues.

Do you have an example of your specific use case/issue?

Here's more info on this issue in an older post:
http://weblog.west-wind.com/posts/2008/Oct/24/Using-jQuery-to-search-Content-and-creating-custom-Selector-Filters#522728

I found the solution in there worse than the cause though :-)

Duncan Smart
May 13, 2014

# re: Filtering List Data with a jQuery-searchFilter Plugin

If I were to use this I'd probably look into escaping special characters (quotes and other punctuation) for the jQuery selector and the regex. For simple text searches it works a treat but if a search string includes special characters it errors.

Also the eval could be avoided using
new RegExp("pattern", flags])

Eduardo
May 13, 2014

# re: Filtering List Data with a jQuery-searchFilter Plugin

In Spanish, people usually skip adding accents to words (Canción/Cancion) because bad grammar, don't know how or because it's extra work with the keyboard.

SQL Server has a great option making search accent insensitive in strings, but when adding JS filters I'm always limited.

I guess I can create a copy of the elements and replace all accents á -> a, é -> e, etc. and then search there, but I think this will take too much memory/resources.

Rick Strahl
May 13, 2014

# re: Filtering List Data with a jQuery-searchFilter Plugin

@Duncan - thanks. I updated the code.

Doogal
May 22, 2014

# re: Filtering List Data with a jQuery-searchFilter Plugin

Love it. The only minor problem I found is that hitting the x button that appears after you've started typing in the search box didn't trigger a search. And I gave up trying to find a reliable way to check for it being clicked, so got rid of type="search", which stops it appearing in most browsers. For IE, I made the button very small (hiding it may not work as well it seems)

#txtSearchPage::-ms-clear {
  width : 0;
  height: 0;
}

Rick Strahl
May 22, 2014

# re: Filtering List Data with a jQuery-searchFilter Plugin

@Doogal - good point about the clear box. I suppose we could double up the event handlers to also deal with click events at the same time.

Doogal
May 23, 2014

# re: Filtering List Data with a jQuery-searchFilter Plugin

I tried adding click and change event handlers but neither of them worked properly in all browsers so I went for the quick fix of hiding it

2bitcoder
June 26, 2014

# re: Filtering List Data with a jQuery-searchFilter Plugin

How would I have a separate searchable div to the div that is hidden. In the case where I only want a certain sub div text to be looked at in the search.

<div class="time-entry">
<div class="time-entry-right">
May 11, 2014 - 7:20pm<br />
<span style='color:steelblue'>0h:40min</span><br />
<a id="btnDeleteButton" href="#" class="hoverbutton" data-id="16825">
<img src="images/remove.gif" />
</a>
</div>
<div class="punchedoutimg"></div>
<div class="searchable">
<b><a href='/TimeTrakkerWeb/punchout/16825'>Project Housekeeping</a></b><br />
<small><i>Sawgrass</i></small></div>
</div>

$("#txtSearchPagePlugin").searchFilter({ targetSelector: ".searchable", hideSelector: ".time-entry", charCount: 1 })

Rick Strahl
June 26, 2014

# re: Filtering List Data with a jQuery-searchFilter Plugin

@2bitcoder - for that you would be better off manually writing the logic. As you're hinting in your code you would need two selectors, one to search, and one to show/hide items. What you'd do is find your targets, then walk up to the show/hide selector and show those - hide all the other ones before.

Nevin House
February 06, 2015

# re: Filtering List Data with a jQuery-searchFilter Plugin

Thanks for the post. Yes, I'm one of those people that still finds jQuery quite useful, and it's interesting that dispite the hype and popularity of the AngularJS, jQuery is indeed still relevant - and both AngularJS and jQuery are being rewritten into new libraries today, in 2015!!!

My question is in your humble opinion, if you had do create a website using only one Javascript JS file, would you use an AngularJS or a jQueryJS (yes, I know you'd rather write your own) AND what version of that JS file would you use?

Darek
July 09, 2015

# re: Filtering List Data with a jQuery-searchFilter Plugin

Hello there,
great solution, thanks for that :)

I'm wondering if u could provide example with dropdown as we can see on that .gif image. First filter by "customers" or wathever and then search by input field inside those "customers". How to merge them together actually ?

Cheers,
Darek.

Rick Strahl
July 10, 2015

# re: Filtering List Data with a jQuery-searchFilter Plugin

@Darek - in this case the other drop downs are filtered on the server - otherwise the list would be too big and would require to have all the data on the client. So any of the other filters cause the server to re-render the page with the retrieved items. In this case it's an ASP.NET app that redraws the entire page, but you could also refresh the data from the data with an AJAX call.

If you need to filter all that data via AJAX in combination I would probably just run the query on the server and feed all the data based on all the filters and you'd get the same effect albeit a bit slower since the data would come from the server.

Derek
July 29, 2015

# re: Filtering List Data with a jQuery-searchFilter Plugin

This is wonderful! Is there anyway that I could include a class on one of the div's, call it say 'searchable' and only have the script search that field, but still display all the other info in the search results.

I was thinking something like
<div class="time-entry-right searchable">


Cheers

Derek

Rick Strahl
July 29, 2015

# re: Filtering List Data with a jQuery-searchFilter Plugin

@derek - that's what the targetSelector option is supposed to provide. Doesn't that work for you?

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024