Using this 5 step process you can add a client-side JQuery filter to your MVC Index pages.
- Set up your layout page so that the JQuery pages load BEFORE the page loads.
- Add a filter bar.
- Add a <thead> and <tbody> section to your table (so we don't inadvertently filter the header row).
- Add data- tags to your <tr>s.
- Add jQuery to respond to the keyup event in your filters.
Layout page
In your Views/Shared/_Layout page, there is a block of code at the end that looks like this:
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/bootstrap")
@RenderSection("scripts", required: false)
Move these to the line just after
@Scripts.Render("~/bundles/modernizr")
...at around line 8. The JQuery library needs to be loaded before the @RenderBody line. This step only needs to happen once for your whole app, unless you're using multiple layout pages.
Filter Bar
Next we'll need a filter bar to the index page.
Let's say we want to filter by three fields:
I put this just before the <table>.
<div class="icon-bar">
<span style="font-weight:bold;">Filters</span>
State: <input name="State" id="state" type="text" style="width:35px;" maxlength="2" class="filter" />
County: <input name="County" id="county" type="text" style="width:90px;" class="filter" />
Town: <input name="Town" id="town" type="text" style="width:90px;" class="filter" />
<span id="recordCount" style="font-weight:bold;">@Model.Count().ToString() records.</span>
</div>
class="icon-bar": use this class to style the bar how you like it: I used this in my style sheet, but you do as you like:
div.icon-bar{
background:linear-gradient(#44f, #88F, #44F, #008, #000);
padding:5px;
margin-bottom:12px;
border:1px solid black;
border-radius:15px;
color:white;
}
This makes the text in the input boxes white on white, so I added this:
input.filter{
background-color:white;
color:black;
}
thead and tbody
The first row in your table is the headers and we DON'T want to ever filter that row out. So add a <thead> tag around the first <tr>
Now add a <tbody id="fbody"> around your data loop.
I use the id tag to find the tbody and access its rows in the jQuery.
Data- tags
Rather than use some wizardry to find what's in the <td> cells, I add custom tags to the <tr>s to help me hide/show them as a group.
@foreach (var item in Model){<tr data-state="@item.state" data-county="@item.county" data-town="@item.town">
This adds tags for all the fields I want to filter for.
jQuery
This is easy enough. Add a keyup function to all 3 filter fields.
<script type="text/javascript">
$(".filter").keyup(function () { //this matches the class=filter on all 3 input boxes.
//hide all trs in the tbody
var rows = $("#fbody").find("tr").hide(); //this hides every row in the table body
var workingList = rows; //makes a list of all the table rows to work on
//grab the text from the search box
var state = $("#state").val().toLowerCase();
var county = $("#county").val().toLowerCase();
var town = $("#town").val().toLowerCase();
//now show the ones where all values match
if (state.length > 0) { //if there is any text in the first filter...
//filter just returns a filtered list
workingList = workingList.filter(function () { //spawns a tiny inline function to test each row
//return true if data-state starts with var state
if ($(this).data("state").toLowerCase().indexOf(state) == 0) {
return true;
}
else {
return false;
}
})
}
if (county.length > 0) {
workingList = workingList.filter(function () {
//return true if data-state starts with var state
if ($(this).data("county").toLowerCase().indexOf(county) == 0) {
return true;
}
else {
return false;
}
})
}
if (town.length > 0) {
workingList = workingList.filter(function () {
//return true if data-state starts with var state
if ($(this).data("town").toLowerCase().indexOf(town) == 0) {
return true;
}
else {
return false;
}
})
}
$(workingList).show(); //show the rows remaining in the working list.
$("#recordCount").text(workingList.length+" records.") //update the record count in the filter bar.
});
</script>
That's it! You can see how I
- hide all the rows in the <tbody>
- grab the strings from the filter boxes.
- copy the list of all rows to workingList.
- Loop through the filters...
- check to see if there is any text in the filter strings. (if not, skip that filter.)
- enact a function on each <tr> that looks for the data- values to match the string entered by the user.
- this filters out all the <tr>s that do not match.
- the 3 filters all work together successively eliminating more rows from workingList until we are done.
- we issue a show() to all the rows that are left in the list.
- we update the count in the filter bar.
Also, you C# programmers like me: Don't freak out that there are returns in the middle of the function. Those returns are in the middle of a small inline function, and return to the main function, not from it.
Matching
I chose a "startsWith" match, that's why my functions have a .indexof(state)==0. Here's some different ways to match.
exact match
if ($(this).data("town").toLowerCase() == town) {
contains match
if ($(this).data("town").toLowerCase().indexOf(town) >= 0) { This should be a good start to filtering for any ASP.NET MVC index page using jQuery.