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="http://instantlistfilter.codeplex.com/">http://instantlistfilter.codeplex.com/</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($("td.ms-vh-group", this).size() > 0) {
   return; 
  }
  
  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>";    
   }
   
   colIndex++;
  });
  
  var tr = "<tr class='myapp-filterrow'>" + tdset + "</tr>";
  
  $(tr).insertAfter(this);
 }); 
 
 
 $("input.myappFilterField")
  .keyup(function() {
   filterColumn($(this));
  });
};

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

 var inputClosure = obj;
 
 if(window.myappFilterTimeoutHandle) {
  clearTimeout(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
   showAllRows();
  } 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) {
     $(this).show();
    } else {
     $(this).hide();
    }  
   });
  }
 }, 250);
};

function showAllRows() {
 $("tr.myapp-filterrow").closest("table").nextAll("tr").show();
}

21 Comments

  1. I got lucky one day, I guess… I had this filter on a web part page filtering a web part. I decided I wanted that web part to be a DVWP. I didn’t want to lose the filtering, so I open the page up in SPD and converted it to an XSLT Data View. Low and behold, the filter still worked.

    Reply
  2. Apologize if it’s a stupid question (and I’m sure it is):

    Which part of the code should I replace? I have the standard codeplex download working perfectly in a CE web part with a List View, but if I replace everything beginning with “jQuer.extend” on the original code with yours, it no longer works. I’m using it with jQuery 1.2.6 for compatibility.

    Reply
    • Mo:

      Note that I’ve changed the selector to match MY Web Part’s id: div#WebPartWPQ2. You’ll need to make that match the Web Part id in your own environment. jQuery 1.2.6 is so very ancient; I’d suggest upgrading.

      M.

      Reply
      • Thanks Mark – I was only using JQ 1.2.6 as I noticed it’s the only one I could get working (without customization) with the original Instant List Filter codeplex downloads. I’m having trouble figuring out how to get the DIV ID for the DV web part – I found the Web Part ID in Designer, but cannot figure out how to get the update for what you have as WPQ2.

        Any suggestions? Thanks again for your help

        Reply
      • Jaap Vossers’ excellent InstantListFilter works great. Was wondering if there is a way to use it while also totalling a column fo numbers. The sum would be at the top and would change based on the filter.

        Reply
  3. Never mind, got it from the page source. This works like a charm!

    This is fantastic – now I can have a styled, customized dataview with the search functionality everyone already loves.

    Reply
  4. Hi Marc, I’m unable to get either the original LV filter web part or your DV solution working with newer versions of jQuery. They both work great using version 1.2.6, but with current versions I get an error, and no filtering. Any ideas?

    Reply
    • Mo:

      I’m pretty sure that I was using jQuery 1.4.x when I did this. You’ll need to debug the error to see what’s going wrong in your environment. Note that the selector $(“div#WebPartWPQ2… worked in my particular case bacause that was the id for the Web Part in my page. Yours will undoubtedly be different.

      M.

      Reply
  5. Hi Marc, thanks for that great article. One question, i have a classic DVWP with a SQL datasource, is it possible to pre-filter this DVWP without using the above solution?

    Thanks in advance.

    Reply
    • Jan:

      You should be able to filter in the SQL statement, if that’s what you mean. If you’d like to provide live filtering like Jaap’s solution provides, then you can do it with script in a similar fashion to Jaap’s.

      M.

      Reply
      • Marc

        Thanks for the quit reply. What I really want to do is the following. I can filter and sort on the column headings, if I add a parameter to my SQL select statement the previous filter/sort does not work anymore. Therefore my precise question, Can I create a pre-filter on a column heading. Users can then turn the filter back off, if they want.

        Thanks in advance

        Reply
        • Jan:

          The answer has to be “yes”. Keep in mind that any client side filtering requires that all of the data is in the page, which may be inefficient depending on the volume of your data.

          M.

          Reply
          • Marc

            Thanks for the answer. My best solution so far is the ddwrt-function ddwrt:GenFireServerEvent(concat(‘NotUTF8;__filter={‘,’@year’,’=’,’2012′,’}’)) (if a user opens the page, he sees only the publications of the current year), but they have to click a button or a hyperlink. Any suggestions to fire that event automatically?

            Reply
  6. Hello Marc,

    I have been working with Mr. Vossers Javascript and with you addition. First, Is there anyway to get this to only show up on Lists? I have it showing on Lists and Documents. When I use your code with
    (“div#WebPartWPQ2 tr:first”).each(function() it doesn’t seem to find the Div cause nothing happens. I do have the same DIV also. I tried Mr. Vosser but he hasn’t got back to me from his blog. Any help would be greatly appreciated. THANKS! NewBee of Course

    Reply
  7. Hi Marc,

    Should your code go inside the document ready event or outside of it? It’s not working for me either way. I have the same selector as you.

    Reply

Have a thought or opinion?