Using Jaap Vossers’ InstantListFilter with a Data View Web Part (DVWP)

On a recent client project, I was able to take advantage of Jaap Vossers‘ excellent InstantListFilter project on Codeplex. It’s just the sort of thing that you need to improve the user experience with SharePoint, and you can do it with minimal work because Jaap’s done all of the heavy lifting for you. It’s a jQuery-based solution, too, so it runs entirely client-side and you can adapt it to your heart’s content.

I’ve stolen Jaap’s animation to show you how the InstantListFilter works. The idea is to give the user a better option for filtering  than the standard column headers in a List View Web Part (LVWP) -based view. As you can see in the animation, whenver the user types characters in the input boxes at the top of each column, the list items are instantly filtered based on that value.

Click on the image to see the animation in a new window

While the code is quite elegant, it will only work with content that is in the page (not with paged content). This is because it is simply changing what the user sees in the Document Object model (DOM) based on what is typed.

In my case, I wanted to use the capability with a Data View Web Part (DVWP) rather than a LVWP. The key tweak to make it work with a DVWP is highlighted below. I could have generalized this further, of course, but what I did was change the selector to match the id of the div containing the DVWP in my specific page. I also added some CSS of my own and did a few other tweaks, but making it work with the DVWP was the change I wanted to highlight here.

// This function is adapted from Jaap Vossers' excellent InstantListFilter <a href=""></a>
function addFilters() {

 jQuery.extend(jQuery.expr[':'], {
  containsIgnoreCase: function(a,i,m) {return (a.textContent||a.innerText||jQuery(a).text()||'').toLowerCase().indexOf((m[3]||'').toLowerCase())>=0}

 $("div#WebPartWPQ2 tr:first").each(function() {
  if($("", this).size() > 0) {
  var tdset = "";
  var colIndex = 0;
  $(this).children("th,td").each(function() {
   if($(this).hasClass("ms-vh-icon")) {
    // attachment
    tdset += "<td></td>";
   } else {
    // filterable
    tdset += "<td><input type='text' class='myappFilterField' filtercolindex='" + colIndex + "' /></td>";    
  var tr = "<tr class='myapp-filterrow'>" + tdset + "</tr>";
  .keyup(function() {

// Do the column filtering
function filterColumn(obj) {

 var inputClosure = obj;
 if(window.myappFilterTimeoutHandle) {
 window.myappFilterTimeoutHandle = setTimeout(function() {
  var filterValues = new Array();
  // Gather up the filter values and check to see if they are all empty (so that we can display everything)
  var notEmptyCount = 0;
  $("input.myappFilterField", $(inputClosure).parents("tr:first")).each(function() {    
   if($(this).val() != "") {
    filterValues[$(this).attr("filtercolindex")] = $(this).val();
    notEmptyCount ++;

  if(notEmptyCount == 0) {
   // If all of the filters are empty, then show everything
  } else {
   // Otherwise, do the filtering
   $(inputClosure).parents("tr.myapp-filterrow").nextAll("tr").each(function() {
    var thisMatches = false;
    $(this).children("td").each(function(colIndex) {
     if(filterValues[colIndex]) {
      var val = filterValues[colIndex];
      // replace double quote character with 2 instances of itself
      val = val.replace(/"/g, String.fromCharCode(34) + String.fromCharCode(34));       
      var valArray = val.split(",");
      for(var i=0; i < valArray.length; i++) {
       if($(this).is(":containsIgnoreCase('" + valArray[i] + "')")) {
        thisMatches = true;
        return false;
    if(thisMatches) {
    } else {
 }, 250);

function showAllRows() {