Content Search Webpart Dynamic Filtering

Wednesday, August 13, 2014

13

The Content Search webpart (CSWP) introduced in SharePoint 2013 is an extremely powerful and flexible tool for displaying dynamic content.  It can surface any content from the Search index, and display templates allow us complete control over how the results are rendered.

Each CSWP is associated with a search query which drives its content.  This post will focus on scenarios in which the search query itself needs to be dynamic.  The techniques in this post can be applied to the Search Results webpart as well.

There are several approaches to making the webpart's query dynamic:

  1. Use built-in query variable tokens
  2. Extend the CSWP with server side code
  3. Dynamically update the query with Javascript

Query Variables

The CSWP supports a set of predefined variables that can be used to build dynamic queries.  The variable tokens are replaced with actual values when the CSWP loads.  There's a plethora of variables to choose from--some of the more useful ones are Page Field, Site Property, User, URL Token, and Query String.

Say we have a page called Orders.aspx, with a CSWP that displays Orders.  We want to limit its results to orders for a specific Product, to be specified in the query string.  For instance, Orders.aspx?ProductID=3 should display only Orders with a ProductID of 3.

Assuming we have an Orders content type and a search managed property ProductID, this can be achieved by setting the CSWP's query to:

  contentType:Orders ProductID:{QueryString.ProductID}

Refer to this Technet article for the complete list of variable tokens.

Extending the CSWP

While query variables can handle many common scenarios, sometimes we need a little custom logic, or a field not available in the OOB variables.  What if we want to plug the current year into the query?  Or a value from another SP list?  Or from a user control on the page?

One solution is to extend the CSWP with server side code to intercept and update the query.  I am not going to go over the implementation details as there are already several good blog posts on it.

This article is a great tutorial on extending the CSWP to inject SharePoint Variation Labels into the query.

Dynamically Updating the CSWP with Javascript

If we can't use server side code, or need to dynamically change the results after the page loads, it is also possible to update the query using Javascript.  Note that some of the methods used in this approach are undocumented.  Use at your own risk!

Consider this scenario:  we have a CSWP that displays a list of Orders.  We want to query for orders related to a specific Product, determined by a dropdown list on the page.  When the dropdown selection is changed, the CSWP's results should dynamically update (without reloading the page).

First, add a CSWP to the page and set the default query that should run when the page loads.  In this case, I filter for Orders with Product ID 1.


Next, we will create a drop down box with Product options, and Javascript to update the CSWP on change.  I assume that jQuery is available on the page.  For prototyping purposes, we can just drop the markup in a Content Editor webpart.

To create the dropdown:

<select id="Products">
   <option value="1" selected="selected">Plushies</option>
   <option value="2">Computers</option>
   <option value="3">Unspeakable Items</option>
</select>

And now the good stuff... we attach a change handler to kick off some code when the dropdown is updated.  In the handler, we iterate through all the active data providers, looking for the ones associated with CSWPs.  In this example there would only be one.

Then, update the data provider's query to filter on the selected Product ID.  Finally, we call issueQuery() to execute.  The data provider's associated CSWP should automatically pick up the new results and update its display.

<script type="text/javascript">
  $(document).ready(function () {
    // Attach change event handler to Products dropdown
    $('#Products').change(function () {

      // Get selected product ID
      var prd = $(this).val();

      // Loop through Query Groups
      var groups = Srch.ScriptApplicationManager.get_current().queryGroups;

      $.each(groups, function () {
        // Look for query groups associated with a CSWP
        if (this.displays != null && this.displays.length > 0) {
          if (this.displays[0] instanceof Srch.ContentBySearch) {
            // Update and execute query
            var newQuery = 'contentType:Orders productID:' + prd;

            this.dataProvider.set_queryTemplate(newQuery);
            this.dataProvider.issueQuery();
          }
        }
      });
    });
  });
<script type="text/javascript">

 

Addendum: Targeting a Specific CSWP


I've been asked how to change the query of a specific CSWP, if there are more than one on the page?  In the example above where we search through the query groups, it's impossible to tell which is which.

We can leverage the $getClientControl function for this.  This approach requires using a custom display template.  $getClientControl takes as input any HTML element within a CSWP, and returns the containing CSWP object.

Now, we still have no way to grab an HTML element from inside that specific CSWP.  But if we're using a custom display template, we can tag an element with a custom ID or css class.

For example, give a DIV in the display template a special ID:

<div id="MyCSWP">Some content</div>

Now we can grab the CSWP object with $getClientControl( $("#MyCSWP")[0] ):

var ctrl = $getClientControl( $("#MyCSWP")[0] );

ctrl.get_dataProvider().set_queryTemplate(newQuery);
ctrl.get_dataProvider().issueQuery();

Note that $getClientControl expects an HTML element, not a jQuery object. So, passing $("#MyCSWP") will not work.  It has to be $("#MyCSWP")[0].  Alternatively, use document.getElementById.


13 comments:

Seems that this is not working in a standard SP wiki page. When adding the last script into the page, the SP page is getting rendered wrong....

I think it would be better to use the KQL properly and instructing it to filter by query param AND ignore a query param when its not present
For Ex:
{?ArticleCategory={QueryString.ArticleCategory} AND } ArticleCategory<>"CEO Message"

Reference :
https://msdn.microsoft.com/en-us/library/office/ee558911.aspx

How do you ensure the webpart is fully rendered before calling the $getClientControl ?

@Fred, are you calling $getClientControl within or outside of a the display template?

@Heapster, if you were to cal from inside the display template would the $getClientControl work? I do know that display templates are loaded early on and they don't have the jQuery available during that stage. How would you cope with that ?

Inside the display template, you could directly use ctx.ClientControl. $getClientControl probably wouldn't work if the control itself hasn't been rendered yet (unless you're in the PostRender handler). That said, $getClientControl does not require jQuery. It's a SharePoint function, and the argument is a HTML element. So instead of $getClientControl( $("#MyCSWP")[0] ) you could also do $getClientControl( document.getElementById("MyCSWP") ). Hope that helps!

Nice Post, I tried the same but I'm updating the query on post back(dropdown change server side) but sometimes the issuequery method works sometimes not any idea on this?

Very Useful information Thank you!!
With over 100,000 hrs of development in deep technology domains, TWB_ is the undisputed leader in creating product documentation for customers worldwide. That is how TWB_

started in 2006. TWB_’s first customer was a then $40Billion American behemoth migrating it’s JD Edwards ERP. The entire product documentation & process documentation was

delivered by TWB_ and the project executed over 3 locations in India and 1 in the US. On time.
Shift_ Product Documentation Shift_ Support Knowledge Base | Product Content Writing

Nice finding and post Thanks :)

Thank you so much for sharing this. I must say very impressive!

Hi, where should I put the Some content in the display template? I'm always getting a null value when retrieving the value of MyCSWP

Hi, where should I put the < div id=MyCSWP > Some content
< div > ? I'm always getting a null value when retrieving the value of MyCSWP. Regards,

Post a Comment