To ‘this’ or not to ‘this’ with the Client Object model
Recently I developed a CustomAction on a list with some client script in the CommandAction. Nothing fancy or new.
I put a .js file in the _layouts folder and referenced the file by a CustomAction with Location ‘ScriptLink’ and ScriptSrc. Put some code in the file to get some data I needed and called executeQueryAsync with two delegates like this:
clientContext.executeQueryAsync(Function.createDelegate(this, this.OnSucceeded), Function.createDelegate(this, this.OnFailed));
Deployed as a Farm solution on the environment and everything went well so far.
After this, minds changed and it had to become a sandboxed solution. Ok, let’s do it.
1. Set the Sandboxed Solution property to True on the project
2. Removed the CustomAction with Location=’ScriptLink’
3. Put the code from the js file from the _layouts folder in the CommandUIHandler’s attribute CommandAction
4. Deployed the sandboxed solution
I always use FireFox when programming and all of a sudden I received the following error:
b is undefined
![]()
and things stopped working. While in the Farm solution the CustomAction worked as expected and the OnSucceeded and OnFailed were executed.
I put in some alert statements to see where the code broke and it seemed the OnSucceeded and the OnFailed methods weren’t executed at all.
By removing the ‘this’ keyword in the createDelegate everything went well again:
clientContext.executeQueryAsync(Function.createDelegate(this, OnSucceeded), Function.createDelegate(this, OnFailed));
This behaviour seems a bit strange to me. When putting the code in a separate js file and deploying it to the _layouts folder the ‘this’ keyword can be used, but putting the exact same code in the CommandAction of CommandUIHandler the OnSucceeded and OnFailed methods can’t be found anymore using the ‘this’ keyword.
By the way, in IE you will receive the following error:
‘b’ is null or not an object

Client side social dashboard with SharePoint 2010 and SPServices
The social dashboard functionality
|
Functionality
|
Employee
|
Manager
|
Board member
|
|
Tags of current user
|
V
|
V
|
V
|
|
Most used tags
|
V
|
V
|
V
|
|
Drill down to url
|
V
|
V
|
V
|
|
Top active users
|
V
|
V
|
V
|
|
Drill down to tag
|
X
|
V
|
V
|
|
Activity of employees in own department
|
X
|
V
|
V
|
|
Activity of employees in a selectable department
|
X
|
X
|
V
|
<script src="/jQueryLibrary/jquery-1.4.4.min.js" type="text/javascript"></script> <script src=" /jQueryLibrary/jquery.SPServices-0.5.8.min.js" type="text/javascript"></script> <script src=" /jQueryLibrary/jquery.tmpl.js" type="text/javascript"></script>
Tags of current user
var currentUserAccount = $().SPServices.SPGetCurrentUser({
fieldName: "Name"
});
$().SPServices({
operation: "GetTagsOfUser",
userAccountName: $().SPServices.SPGetCurrentUser(),
completefunc: function (xData, Status) {
$(xData.responseXML).find("SocialTagDetail").each(function () {
tagName = $("Term>Name", $(this)).text();
tagsofuser.push({ Tag: tagName, Count: 1 });
});
tagsofuser = uniqueTags(tagsofuser);
SortByCount(tagsofuser);
$("#currentUserTagsText").show();
$("#currentUserTags").html("");
$("#tagsofuserTemplate").tmpl(tagsofuser)
.appendTo("#currentUserTags");
}
});
<?xml version="1.0"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <soap:Body> <GetTagsOfUserResponse xmlns="http://microsoft.com/webservices/SharePointPortalServer/SocialDataService"> <GetTagsOfUserResult> <SocialTagDetail> <Url>http://sp2010/Lists/Tasks/AllItems.aspx</Url> <Owner>sp2010\mark</Owner> <LastModifiedTime>2010-12-19T13:28:09.437</LastModifiedTime> <Title>Tasks - All Tasks</Title> <Term> <Id>974a854f-31b4-431f-91cb-a6289f58c978</Id> <Name>I like it</Name> </Term> <IsPrivate>false</IsPrivate> </SocialTagDetail> Rest of the SocialTagDetails are intentionally left out� </GetTagsOfUserResult> </GetTagsOfUserResponse> </soap:Body> </soap:Envelope>
<div><script id="tagsofuserTemplate" type="text/x-jquery-tmpl">
<div> </div>
<div><div style="float:left;width:40%">
<div> </div>
<div> ${Tag}
<div> </div>
<div></div>
<div> </div>
<div><div style="float:left;width:20%">
<div> </div>
<div> ${Count}
<div> </div>
<div></div><br />
<div> </div>
<div></script>
<div>
Most used tags
$().SPServices({
operation: "GetAllTagTerms",
debug: false,
completefunc: function (xData, Status) {
$(xData.responseXML).find("SocialTermDetail").each(function () {
termName = $("Term>Name", $(this)).text();
termGuid = $("Term>Id", $(this)).text();
counter = $("Count", $(this)).text();
terms.push({ Term: termName, Count: counter, TermGuid: termGuid });
});
SortByCount(terms);
terms.length = 5;
$("#showterms").html("");
$("#termTemplate").tmpl(terms)
.appendTo("#showterms");
}
});
<div><div style="float:left;width:40%">
<div> </div>
<div> <a href="#" id=${TermGuid}>
<div> </div>
<div> ${Term}
<div> </div>
<div> </a>
<div> </div>
<div></div>
<div> </div>
<div><div style="float:left;width:20%">
<div> </div>
<div> ${Count}
<div> </div>
<div></div>
<div>
$().SPServices({
operation: "GetAllTagUrls",
termID: termId,
debug: true,
completefunc: function (xData, Status) {
$(xData.responseXML).find("SocialUrlDetail").each(function () {
url = $("Url", $(this)).text();
countUrl = $("Count", $(this)).text();
urlsofterm.push({ Url: url, Count: countUrl });
});
SortByCount(urlsofterm);
$("#urlsoftermContainer").show();
$("#showurlsofterm").html("");
$("#urlTemplate").tmpl(urlsofterm)
.appendTo("#showurlsofterm");
}
});

Top active users
$().SPServices({
operation: "GetUserCollectionFromSite",
completefunc: function (xData, Status) {
$(xData.responseXML).find("User").each(function () {
userName = $(this).attr("LoginName");
userNames.push({ UserName: userName });
});
GetTopTagsOfUsers(userNames);
}
});
function CountTagsOfUser(useraccountname, f) {
var tagCount = "";
var tagsofuser = [];
$().SPServices({
operation: "CountTagsOfUser",
userAccountName: useraccountname,
debug: true,
completefunc: function (xData, Status) {
$(xData.responseXML).find("CountTagsOfUserResult").each(function () {
tagCount = $(this).text();
tagsofuser.push({ Count: tagCount, UserAccountName: useraccountname });
});
SortByCount(tagsofuser);
if (typeof f == "function") f(tagsofuser);
return tagsofuser;
}
});
}
function GetTopTagsOfUsers(usernames) {
var result = [];
$.each(usernames, function (key, value) {
CountTagsOfUser(value["UserName"], function (items) {
$.each(items, function (i, n) {
result.push({ Count: n["Count"], UserAccountName: n["UserAccountName"] });
});
if (usernames.length == result.length) {
SortByCount(result);
result.length = 5;
$("#showcounttagsofuser").html("");
if (isCEO || isManager) {
$("#counttagsofuserTemplate").tmpl(result)
.appendTo("#showcounttagsofuser");
} else {
$("#counttagsofuserTemplateForUser").tmpl(result)
.appendTo("#showcounttagsofuser");
}
}
});
});
}
<script id="counttagsofuserTemplateForUser" type="text/x-jquery-tmpl">
<div style="float:left;width:40%">
${UserAccountName}
</div>
<div style="float:left;width:20%">
${Count}
</div>
</script>
<script id="counttagsofuserTemplate" type="text/x-jquery-tmpl">
<div style="float:left;width:40%">
<a href="#" id=${UserAccountName}>
${UserAccountName}
</a>
</div>
<div style="float:left;width:20%">
${Count}
</div>
</script>
What’s the difference between a ‘regular’ employee, a manager and a board member?
|
UserName
|
Department
|
Manager
|
Role
|
|
Alex
|
ICT
|
Andrew
|
Employee
|
|
Andrew
|
ICT
|
|
Manager
|
|
Anita
|
Marketing
|
Mark
|
Employee
|
|
Chris
|
HR
|
Dave
|
Employee
|
|
Dave
|
HR
|
|
Manager
|
|
Jeff
|
Marketing
|
Mark
|
Employee
|
|
Mark
|
Marketing
|
|
Manager
|
|
Paul
|
CEO
|
|
Board member
|
var CEOdepartment = "CEO";
$().SPServices({
operation: "GetUserProfileByName",
AccountName: currentUserAccount,
completefunc: function (xData, Status) {
$(xData.responseXML).find("GetUserProfileByNameResult>PropertyData").each(function () {
//check for CEO department
if ($("Name", $(this)).text().toUpperCase() == "Department".toUpperCase()) {
departmentOfCurrentUser = $("Values>ValueData>Value", $(this)).text();
if (departmentOfCurrentUser.toUpperCase() == CEOdepartment.toUpperCase()) {
isCEO = true;
GetDepartmentsAndUsers();
}
}
else if ($("Name", $(this)).text().toUpperCase() == "Manager".toUpperCase()) {
managerName = $("Values>ValueData>Value", $(this)).text();
if (managerName == "") {
if (!isCEO) {
isManager = true;
GetDepartmentsAndUsers();
}
}
}
});
}
});
<?xml version="1.0"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <soap:Body> <GetUserProfileByNameResponse xmlns="http://microsoft.com/webservices/SharePointPortalServer/UserProfileService"> <GetUserProfileByNameResult> <PropertyData> <IsPrivacyChanged>false</IsPrivacyChanged> <IsValueChanged>false</IsValueChanged> <Name>AccountName</Name> <Privacy>Public</Privacy> <Values> <ValueData> <Value xsi:type="xsd:string">sp2010\paul</Value> </ValueData> </Values> </PropertyData> <PropertyData> <IsPrivacyChanged>false</IsPrivacyChanged> <IsValueChanged>false</IsValueChanged> <Name>Department</Name> <Privacy>Public</Privacy> <Values> <ValueData> <Value xsi:type="xsd:string">CEO</Value> </ValueData> </Values> </PropertyData> </GetUserProfileByNameResult> </GetUserProfileByNameResponse> </soap:Body> </soap:Envelope>
Activity of employees in own or a selectable department
var queryTextSQL = "<QueryPacket xmlns='urn:Microsoft.Search.Query' Revision='1000'>"
queryTextSQL += "<Query>"
queryTextSQL += "<Context>"
queryTextSQL += "<QueryText language='en-US' type='MSSQLFT'>"
if (isCEO) {
queryTextSQL += "SELECT Title, Rank, Size, Description, Write, Path, AccountName, Department FROM scope() WHERE ( (\"SCOPE\" = 'People') ) ORDER BY \"Rank\" DESC"
} else {
queryTextSQL += "SELECT Title, Rank, Size, Description, Write, Path, AccountName, Department FROM scope() WHERE ( (\"SCOPE\" = 'People') ) AND (CONTAINS (Department,'" + departmentOfCurrentUser + "')) ORDER BY \"Rank\" DESC"
}
queryTextSQL += "</QueryText>"
queryTextSQL += "</Context>"
queryTextSQL += "</Query>"
queryTextSQL += "</QueryPacket>";
$().ready(function () {
var resultText = "";
$().SPServices({
operation: "QueryEx",
queryXml: queryTextSQL,
completefunc: function (xData, Status) {
$(xData.responseXML).find("RelevantResults").each(function (i) {
accountName = $("ACCOUNTNAME", $(this)).text();
departments[i] = $("DEPARTMENT", $(this)).text();
users.push({ AccountName: accountName, Department: departments[i] });
});
if (isCEO) {
$('#departmentContainer').show();
fillDepartmentsDropdown('#departmentSelection', $.unique(departments), "Select department");
}
if (isCEO || isManager) {
$('#userContainer').show();
fillUsersDropdown('#userSelection', users, "Select user");
}
}
});
});
function fillUsersDropdown(dropdownName, arrayToUse, text) {
$(dropdownName).html("");
$.each(arrayToUse,
function (key, value) {
$(dropdownName).append('<option value="' + value["AccountName"] + '">' + value["AccountName"] + '</option>');
});
$(dropdownName).prepend("<option value='0' selected='true'>" + text + "</option>");
$(dropdownName).find("option:first")[0].selected = true;
}
<div style="float: left; width: 100%; display: none;" id="departmentContainer"> <div style="float: left; width: 15%"> Select department</div> <select id="departmentSelection" style="float: left; width: 15%"> </select> </div> <br /> <div id="userContainer" style="float: left; width: 100%; display: none;"> <div style="float: left; width: 15%"> Select user</div> <select id="userSelection" style="float: left; width: 15%"> </select> <div style="clear: both; float: left; width: 25%"> <a href="#" onclick="GetTagsOfSelection(userSelection.value, departmentSelection.value);return false"> Show tags of selection</a></div> <h4 style="clear: both; float: left; display: none;" id="showtagsofuserText"> Tags of selection:</h4> <div id="showtagsofuser" style="clear: both;"> </div> </div>
$('select').change(function (e) {
if (this.id == "departmentSelection") {
$('#userSelection option').each(function (i, option) {
$(option).remove();
});
j = 0;
childArray.length = 0;
if (this[this.selectedIndex].value != 0) {
departmentSelected = this[this.selectedIndex].text;
//filter the users array on the selected department
filteredUsers = $.grep(users, function (filter) {
return filter["Department"] == departmentSelected;
});
fillUsersDropdown('#userSelection', filteredUsers, "Select user");
}
else {
fillUsersDropdown('#userSelection', users, "Select user");
}
}
});
function GetTagsOfSelection(userSelection, departmentSelection) {
var result = [];
if (userSelection != 0) {
GetTagsOfUser(userSelection, true, true);
} else if (departmentSelection != 0) {
$.each(filteredUsers, function (key, value) {
GetTagsOfUser(value["AccountName"], false, true, false, function (items) {
$.each(items, function (i, n) {
result.push({ Tag: n["Tag"], Count: 1 });
});
result = uniqueTags(result);
SortByCount(result);
$("#showtagsofuserText").show();
$("#showtagsofuser").html("");
$("#tagsofuserTemplate").tmpl(result)
.appendTo("#showtagsofuser");
//}
});
});
}
}
The result
Summary
Bind (SP)Gridview field to generic list of strings
Probably this is my lack of knowledge in the (sp)gridview area, but I want to share it anyway.
I realized I always bound gridviews to custom objects only. Recently I wanted to bound a generic list of strings (List<string>) to a field in a gridview and didn’t know how to accomplish this. After trying a few options and some searching I found the answer:
An exclamation mark: !
Example:
BoundField field = new BoundField(); field.DataField = "!";
Never thought of this one myself.
Simple Provider Consumer Visual Webparts
Because of a question on http://sharepoint.stackexchange.com/questions/14325/created-connectable-webparts-but-the-connections-menu-item-is-not-showing I decided to create a provider consumer webpart in his most simple form.
Create an empty SharePoint project in Visual studio.
Add two Visual WebParts:
- ProviderVisualWebpart
- ConsumerVisualWebpart
In this most simple form of a provider consumer webpart only one string is passed from the provider to the consumer and it displayed in a Label control on the consumer webpart.
Code in ProviderVisualWebpart.cs:
public class ProviderVisualWebPart : System.Web.UI.WebControls.WebParts.WebPart, ITransformableFilterValues
{
private const string _ascxPath = @"~/_CONTROLTEMPLATES/ConsumerProviderVWP/ProviderVisualWebPart/ProviderVisualWebPartUserControl.ascx";
protected override void CreateChildControls()
{
Control control = Page.LoadControl(_ascxPath);
Controls.Add(control);
}
public string ParameterName
{
get
{
return "Letter";
}
}
public ReadOnlyCollection<string> ParameterValues
{
get
{
List<string> values = this.GetValues();
return new ReadOnlyCollection<string>(values);
}
}
private List<string> GetValues()
{
List<string> valueList = new List<string>();
valueList.Add("A");
return valueList;
}
[ConnectionProvider("Letter Filter", "ITransformableFilterValues")]
public ITransformableFilterValues SetConnectionInterface()
{
return this;
}
public bool AllowEmptyValue
{
get { return true; }
}
public bool AllowMultipleValues
{
get { return false; }
}
public bool AllowAllValue
{
get { return true; }
}
}
Code in ConsumerVisualWebpart.cs:
public class ConsumerVisualWebPart : System.Web.UI.WebControls.WebParts.WebPart
{
private const string _ascxPath = @"~/_CONTROLTEMPLATES/ConsumerProviderVWP/ConsumerVisualWebPart/ConsumerVisualWebPartUserControl.ascx";
public ConsumerVisualWebPart()
{
_filterProviders = new List<IFilterValues>();
}
protected override void CreateChildControls()
{
ConsumerVisualWebPartUserControl control = (ConsumerVisualWebPartUserControl)Page.LoadControl(_ascxPath);
control.ValueSendByProviderProperty = "From provider: ";
foreach (IFilterValues filter in FilterProviders)
{
if (filter.ParameterValues != null)
{
foreach (string item in filter.ParameterValues)
{
control.ValueSendByProviderProperty += item;
}
}
}
Controls.Add(control);
}
protected override void Render(HtmlTextWriter writer)
{
base.Render(writer);
foreach (IFilterValues filter in FilterProviders)
{
writer.WriteLine(string.Format("Parameter: {0} <br>", filter.ParameterName));
}
}
/// <summary>
/// Hold incoming filtervalues
/// </summary>
private List<IFilterValues> _filterProviders;
private List<IFilterValues> FilterProviders
{
get { return _filterProviders; }
}
[ConnectionConsumer("filter", "UniqueIDForConsumer", AllowsMultipleConnections = true)]
public void SetFilter(IFilterValues filterValues)
{
if (filterValues != null)
{
List<ConsumerParameter> parameters = new List<ConsumerParameter>();
parameters.Add(new ConsumerParameter("Letter", ConsumerParameterCapabilities.SupportsSingleValue | ConsumerParameterCapabilities.SupportsAllValue | ConsumerParameterCapabilities.SupportsEmptyValue));
filterValues.SetConsumerParameters(new System.Collections.ObjectModel.ReadOnlyCollection<ConsumerParameter>(parameters));
this.FilterProviders.Add(filterValues);
}
}
}
Code in ConsumerVisualWebpartUserControl.ascx.cs:
public partial class ConsumerVisualWebPartUserControl : UserControl
{
public string ValueSendByProviderProperty { get; set; }
protected void Page_Load(object sender, EventArgs e)
{
ValueSendByProvider.Text = ValueSendByProviderProperty;
}
}
Control added to VisualWebpart.ascx:
<asp:Label ID="ValueSendByProvider" runat="server" Text="Label"></asp:Label>
That’s it!
Put both controls on a page and connect them to each other.
No connection set:
Search Refiners (part 4) – User selection based
In this series:
- Search Refiners part 1 – Expanding the OOTB search Refinement Panel
- Search Refiners part 2 – Use of CustomFilters
- Search Refiners part 3 – Chart based
- Search Refiners part 4 – User selection based (this post)
After building a chart based search refiner, now it’s time to get some interaction in the selection of search terms. In this post I will show you how to use jQuery in combination with the RefinementManager.
With interaction I mean the user can drag a search term to a place on the screen to refine the results. Unfortunately the OOTB Refinement Panel and the results webpart ‘talk’ to each other by the parameters in the url. Therefor a postback must occur to actually refine the results with the selected term. But a little animation just looks great.
For the drag and drop functionality I used the standard jQuery library and a custom jQuery UI selection. With this functionality in hand I created my own drag and drop implementation for this specific solution.
Script part
Because I already have a little experience with programming against the RefinementManager class in the chart based example I first focused on the drag drop functionality.
Two divs are placed on the screen:
1. to list all the terms the user can choose from to select
2. the already selected terms by the user
The items in both divs has to have the ability to drag items from and to drop items to. The user has to have the possibility to drag items from the ‘terms to select’ part to the ’selected terms’ part and vice versa.
Both the divs contain an unordered list with list items.
So a single div with items in it looks in plain HTML like:
<div> <ul> <li>item01</li> <li>item02</li> </ul> </div>
A single item has to be draggable to move it from the ‘terms to select’ to the ’selected terms’ part(and vice versa). So the li element has to be draggable.
On the other hand when a li element a dragged, the list to which the single item has to be added has to be droppable, the ul element.
To accomplish this just a few lines of jQuery are neccesary, the rest is alredy build in the jQuery libraries.
var $currentSelectedItems = $("#selectedTerms"),
$baseItemList = $("#termsToSelect");</pre>
//make the li's in the selectedTerms div draggable
$("li", $currentSelectedItems).draggable({
appendTo: $baseItemList,
helper: "clone"
});
//make the ul in the selectedTerms div droppable
$("ul", $currentSelectedItems).droppable({
accept: "#termsToSelect li",
hoverClass: "ui-state-hover",
drop: function (event, ui) {
moveTerm(ui.draggable, $currentSelectedItems);
}
});
The draggable functionality is nothing special. The element passed to appendTo is the container during dragging. Second the clone helper is used here, which means a clone of the actual item is dragged around. Another option here is original, which drags the original item around.
The droppable functionality uses different options. First I’m telling the element what items to accept: an li element within ‘termsToSelect’. Second a nice hover class and finally a function is specified what to do when the actual drop occurs: move the term to from ‘terms to select’ to ’selected terms’ and navigate to the url which the item received from the RefinementPanel (getting there soon).
//move term
function moveTerm($item, $listToAdjust) {
$item.fadeOut(function () {
var $list = $("ul", $listToAdjust);
$item.appendTo($list).fadeIn();
window.location.href = $item.find('a').attr("href");
});
}
The script above is the script which act on the ’selected terms’ part ($currentSelectedItems or $(“#selectedTerms”)). The same functionality have to be added to the ‘terms to select’ part. Because it’s almost the same this code it’s left out here for readability.
That’s is with all the script, let’s move the code behind all this.
The webpart
The code behind this (just a regular webpart) works with the RefinmentManager class of the page as in the chart based example, the previous post in this series. The extra functionality here is that the selected term is needed from the RefinmentManager’s filter. If a term is selected or not is just another node in the refinement xml, just as Value and Url as shown in the following code listing:
string filterValue = filter.SelectSingleNode("Value").InnerText;
string filterUrl = filter.SelectSingleNode("Url").InnerText;
if (filter.SelectSingleNode("Selection").InnerText.Equals("Selected"))
{
dateSelectedList.Add(new ListItem(filterValue, filterUrl));
}
else
{
dateToSelectList.Add(new ListItem(filterValue, filterUrl));
}
dateSelectedList and dateToSelectList are both generic lists of ListItems and in the RenderContents method the items in the lists are added to the appropriate containers.
After putting this all together it looks like the folowing image when no selection are made by the user:

When dragging starts from ‘terms to select’ the location where to drag to is highlighted:
Because ‘helper’ was set to ‘clone’ earlier the item that’s dragged, ‘Past Week’, is still in the ‘terms to select’ container and also moving around the screen.
When dropping the item for a moment the screen looks like (because of the ‘appendto’ option):
And then a postback occurs and it looks like:
And the terms displayed behave just like the OOTB refiners.
Summary
The OOTB refinement functionality is great, but it can be made much more attractive with just a little script as shown in this post. What I’ve shown you here is just a little example with one of the possibilities to make it more attractive. There are a lot of jQuery libraries with excellent functionality which you can use to do all kinds of stuff and don’t forget the design. This example looks nothing like a nice production ready refiner, but go wild and make it look awesome.
DIWUG SharePoint eMagazine 4
A couple of months ago Marianne van Wanrooij and I had a chat at one of the SharePoint conferences in The Netherlands. Before I realised I promised her to write an article for the awesome DIWUG SharePoint eMagazine.
Just kidding, I am pleased I could write an article!
Since I like the jQuery library for SharePoint WebServices called SPServices of Marc Anderson, the choice of a subject for the article wasn’t that hard.
Please download the magazine here and enjoy!
http://www.diwug.nl/Pages/downloads.aspx
Thanks to Marianne and Mirjam for publishing and thanks to Marc D Anderson for reviewing my article!
SDN Magazine
Recently I got the opportunity to write an article for the SDN magazine. SDN is an abbreveation of Software Developement Network and is a Dutch network. Alexander Meijers asked me if I had time and pleasure of writing an article for the SDN magazine, so I did. Here is a link to the contents of the magazine:
http://www.sdn.nl/SDN/Magazine/tabid/66/articleType/ArticleView/articleId/3003/SDN-Magazine-108.aspx
The articles will be soon available at the SDN site: http://www.sdn.nl.
Alexander, do you know when the articles will be available?
Search Refiners (part 3) – Chart based
In this series:
- Search Refiners part 1 – Expanding the OOTB search Refinement Panel
- Search Refiners part 2 – Use of CustomFilters
- Search Refiners part 3 – Chart based (this post)
- Search Refiners part 4 – User selection based
In the previous two posts about search refiners no code was written to adjust the OOTB Search Refinement Panel, just some XML modifications.
In this post a chart based search refiner will be built and some code is needed to accomplish this. The refinement will be built on the Modified Date.
Create the refiner
To get the refinement categories and their values, the RefinementManager class will be used.
The RefinementManager will get an instance of the refinement manager on the current page. This means the OOTB Search Refinement Panel has to be present on the current page. Not only it has to be present, it also has to have the filtercategory on which the refinement is going to perform on, in this case the Modified Date.
Get the instance of the refinement manager:
RefinementManager refinementManager = RefinementManager.GetInstance(this.Page);
To get the refinement values:
XmlDocument refinementXmlDoc = refinementManager.GetRefinementXml();
The GetRefinementXML method gets the filtered xml document of the refinement manager.
For the example is this post only the Modified Date is of interest, so an XPath expression is used to get to the right filter category and values:
XmlNodeList filters = refXmlDocument.DocumentElement.SelectNodes("/FilterPanel/FilterCategory[@ManagedProperty='" + refiner + "']/Filters/Filter[Count>0]");
The refiner variable is declared as:
private const string refiner = "Write";
By looping through the XMLNodes in the XMLNodeList the neccesary values are available, just as in the XML which is rendered by the OOTB Search Refinement Panel. The elements of the childnodes (filter categories) are shown:
Actually, this was the most interesting part. What’s left is displaying the values to the user, in this case: putting it in a chart; and binding it all together.
Putting it all together
In CreateChildControls the chart is setup:
private Microsoft.Office.Server.WebControls.Chart chart;
protected override void CreateChildControls()
{
ChartArea area = new ChartArea("Pie Chart");
chart = new Microsoft.Office.Server.WebControls.Chart();
chart.Width = 160;
chart.Height = 160;
chart.ChartAreas.Add(area);
Controls.Add(chart);
}
And in the OnPreRender the refiment manager, the values of the filter category Modified Date and the chart are put together.
protected override void OnPreRender(EventArgs e)
{
RefinementManager refManager = RefinementManager.GetInstance(this.Page);
XmlDocument refXmlDocument = refManager.GetRefinementXml();
if (refXmlDocument != null)
{
Series chartSeries = new Series();
chartSeries.ChartType = SeriesChartType.Pie;
XmlNodeList filters = refXmlDocument.DocumentElement.SelectNodes("/FilterPanel/FilterCategory[@ManagedProperty='" + refiner + "']/Filters/Filter[Count>0]");
if (filters.Count != 0)
{
foreach (XmlNode filter in filters)
{
string xValue = filter.SelectSingleNode("Value").InnerText;
string yValue = filter.SelectSingleNode("Count").InnerText;
int i = chartSeries.Points.AddXY(xValue, yValue);
// display text in chart
chartSeries.Points[i].Label = xValue;
//use the url to make the pie parts clickable
chartSeries.Points[i].Url = filter.SelectSingleNode("Url").InnerText;
}
//add 'Any Modified Date' option
XmlNodeList xmlFilterNodesAllItems = refXmlDocument.DocumentElement.SelectNodes("/FilterPanel/FilterCategory[@ManagedProperty='" + refiner + "']/Filters/Filter[Count='']");
chart.Titles.Clear();
System.Web.UI.DataVisualization.Charting.Title allItemsTitle = new System.Web.UI.DataVisualization.Charting.Title();
allItemsTitle.Text = xmlFilterNodesAllItems[0].SelectSingleNode("Value").InnerText;
allItemsTitle.Url = xmlFilterNodesAllItems[0].SelectSingleNode("Url").InnerText;
allItemsTitle.ToolTip = "Refine by: " + allItemsTitle.Text;
allItemsTitle.ForeColor = Color.FromArgb(0, 114, 188);
chart.Titles.Add(allItemsTitle);
}
if (chartSeries.Points.Count > 0)
chart.Series.Add(chartSeries);
}
}
In the XPath expressions used to get the filter values for Modified Date and ‘Any Modified Date’ are slightly different. Look at the Count differences.
By removing the Count in the first XPath expression the chart will display ‘Any Modified Date’ and the chart kind of gets messed up:

and ‘Any Modified Date’ is not clickable, because it get’s no colored part in the pie chart. The reason for this is that the Count value for Any Modified Date is empty.
This is the reason to use Count>0 for the values displayed in the pie chart and Count=” to get Any Modified Date.
The pie chart now looks like this:
And after selecting ‘Past Month’:
Summary
To transform a textual search refiner to a chart based search refiner is shown in this post.
As long as the OOTB Search Refinement Panel is present on the page and the filter category used by the code is present in the Filter Category Definition (property of Search Refinement Panel web part), the code in this post can be used to visualize a search refiner.
If it’s not present, you can extend the Refinement Panel webpart. This is not covered in this post, but maybe I’ll show you this in a future post.
Search Refiners (part 2) – Use of CustomFilters
In this series:
- Search Refiners part 1 – Expanding the OOTB search Refinement Panel
- Search Refiners part 2 – Use of CustomFilters (this post)
- Search Refiners part 3 - Chart based
- Search Refiners part 4 – User selection based
The OOTB Search Refinement Panel can be expanded with CustomFilters. An example of the use of a default custom filter used in the OOTB Filter Definition is the Modified Date refiner.
This custom filter is of type RangeMapping. This means a range of values are mapped to a custom value.
The XML below shows the standard custom filters section of the Modified Date refiner.
<CustomFilters MappingType="RangeMapping" DataType="Date" ValueReference="Relative" ShowAllInMore="False"> <CustomFilter CustomValue="Past 24 Hours"> <OriginalValue>-1..</OriginalValue> </CustomFilter> <CustomFilter CustomValue="Past Week"> <OriginalValue>-7..</OriginalValue> </CustomFilter> <CustomFilter CustomValue="Past Month"> <OriginalValue>-30..</OriginalValue> </CustomFilter> <CustomFilter CustomValue="Past Six Months"> <OriginalValue>-183..</OriginalValue> </CustomFilter> <CustomFilter CustomValue="Past Year"> <OriginalValue>-365..</OriginalValue> </CustomFilter> <CustomFilter CustomValue="Earlier"> <OriginalValue>..-365</OriginalValue> </CustomFilter> </CustomFilters>
In this case the DataType is set to Date and the ValueReference to Relative. The DataType represents the type returning from the managed property and the ValueReference compares the data on a relative basis.
Besides the RangeMapping the mapping type ValueMapping can be used. This means a value can be used to map to a customvalue.
This type will be used in the following example.
Intro
To simply add a new custom filter let’s proceed with the example in the first post of this series.
In this example a number of documents were uploaded to the document library. In this library CarLease documents, Employee contracts and other types are stored. The documents are not ´typed´. It´s just the name of the document which suggests a type. Suppose the Employee contracts are the base contracts and other contracts are supplementary.
This distinction is what the search refiner has to show:
- Employee Contract
- Additional Contract
and not all the possible types of documents.
Create the refiner
To set the type of a document add a column named Document Type with some document type options like `Car Lease`, ´Health Care´ and ´Employee Contract´. Set the type of each document.

This is a simple way to ´type´ a document. A better real life approach is to create different content types and use these as the base of the documents to distinguish them. Just for now the column with the types is used.
This ´type´ is going to be used in the search refinement panel. Before it can be used a managed property has to be created and a full crawl of the content has to be performed. If you don´t recall how to do this, please check the first post in this serie.
The next step is to add a new refinement category in the refinement panel with the custom filter. The easiest way to define the XML is to use Visual Studio to get some indentation and coloring which will help you format the XML properly.
Copy the existing XML from the Filter Definition in the refinement panel and copy it in Visual Studio. In the first post of this serie a new category has been added, so now the focus is on the custom filter.
The parent element of a single custom filter the the CustomFilters element.
<CustomFilters MappingType="ValueMapping" DataType="String" ValueReference="Absolute" ShowAllInMore="False"> ... </CustomFilters>
Since the Document Type is just a piece of text the DataType is set to String. In the Modified Date example the Date type is used. Besides these two types the type Numeric can be used when appropriate.
The ValueReference is set to Absolute for comparison on an absolute basis. Other option is ShowAllInMore. When a user selects the ’show more’ option in the refiner more options for the refiner will be shown. In the Category definition an attribute NumberOfFiltersToDisplay is used which represents the number of filters to show at first glance. When more filters are present the ’show more’ link is displayed. Once selected the other filters are shown. With the ShowAllInMore option set to true all these filters are shown when selecting ’show more’, even if there are no results with this filter.
Now a custom filter can be defined:
<CustomFilter CustomValue="Employee Contract"> <OriginalValue>Employee Contract</OriginalValue> </CustomFilter>
The original value is the value of the property returned by the search, the custom value is a value you can choose youself to display in the search refinement panel.
The OriginalValue element is not limited to one. You can define multiple element to map to one custom value:
<CustomFilter CustomValue="Additional Contract"> <OriginalValue>Health Care</OriginalValue> <OriginalValue>Care Lease</OriginalValue> </CustomFilter>
Edit the search refinement panel and select the Filter Categoy Defintion.
After crawling and preparing the XML for the search refinment panel the property can be used in the search.
The full XML for the new Category with custom filters looks like this:
<Category Title="Document Type" Description="Type of document" Type="Microsoft.Office.Server.Search.WebControls.ManagedPropertyFilterGenerator" MetadataThreshold="3" NumberOfFiltersToDisplay="3" MaxNumberOfFilters="20" ShowMoreLink="True" MappedProperty="DocumentType" MoreLinkText="show more" LessLinkText="show fewer"> <CustomFilters MappingType="ValueMapping" DataType="String" ValueReference="Absolute" ShowAllInMore="False"> <CustomFilter CustomValue="Additional Contract"> <OriginalValue>Health Care</OriginalValue> <OriginalValue>Care Lease</OriginalValue> </CustomFilter> <CustomFilter CustomValue="Employee Contract"> <OriginalValue>Employee Contract</OriginalValue> </CustomFilter> </CustomFilters> </Category>
Test the refiner
Perform a search on one of the words in the title of one of the documents in the Document Library. Notice the Document Type refiner.

Notice the refiner displays only two filters, ‘Employee Contract’ and ‘Additional Contract’. Just as defined in the custom filters part of the XML.
Summary
Custom filters can be very useful to guide users in performing a search and refine the results. The example used here is just for demonstration purposes, but I guess you can think of something useful in your organisation. Maybe refiners on file size or different content types.
Search Refiners (part 1) – Expanding the OOTB search Refinement Panel
In this series:
- Search Refiners part 1 – Expanding the OOTB search Refinement Panel (this post)
- Search Refiners part 2 – Use of CustomFilters
- Search Refiners part 3 – Chart based
- Search Refiners part 4 – User selection based
Intro
A large multinational company has several contracts. These contract are stored in SharePoint. Based on the
multinational character of the company the contracts are stored in different languages.
Titles of the documents are all in English, so there is no way to determine from the title in what language the
document is written. Therefor a new column is introduced where the language can be stored.
This column in a lookup column to another list where all the languages are defined.
When people are searching for contracts there is no quick way to see in what language the document is written. To
prevent users to open multiple documents to see the language of the content a search refiner will help them out.
The existing OOTB refinement panel can be expanded just with XML.
Before doing so some preparations has to be done.
Create lists
First a custom list has to be created which will store the possible language values.
A document library has to be added to a site which will contain the documents for the contracts. An additional column
has to be added to let the user choose the language in which the document is written. This will be a lookup column to
the previous created list of languages.
A set of documents are uploaded with their languages set.

Crawl content
Start Central Administration, select Application Management and Manage Service Applications. Select Search Service
Application to display the Search Administration page.
Select Content Sources and start an incremental crawl.
Create managed property
When this is finished create a Managed Property called Language and add a mapping to the crawled property ows_Language.

Start a Full Crawl.
Check if any results are present in the crawled property.
Modify Filter Category Definition
Create an Enterprise Search Center and edit the Refinement Panel. In the webpart properties in the section Refinement
select the button next to the Filter Category Definition textbox to show all the text in it.
The XML is not difficult to read and understand when copying and pasting the XML into an XML file in Visual Studio..
Even without checking on the full Refinement Panel XML schema it’s pretty clear what to do by copying an existing
Category element and make some changes.
Full explanation of this schema can be found on MSDN here http://msdn.microsoft.com/en-us/library/ee819920.aspx �
Add to the filter category definition in the webpart properties:
<Category Title="Language" Description="Language of document" Type="Microsoft.Office.Server.Search.WebControls.ManagedPropertyFilterGenerator" MetadataThreshold="3" NumberOfFiltersToDisplay="3" MaxNumberOfFilters="20" ShowMoreLink="True" MappedProperty="Language" MoreLinkText="show more" LessLinkText="show fewer" />
Don’t forget to uncheck ‘Use default configuration’.
Unchecking this option is quite important. When this checkbox is selected any changes to the filter category definition
configuration are not persistent. A better experience would be disabling the Filter Category Definition when this
checkbox is selected…
Apply and save the changes.
Test the refiner
Start a search on one of the words in the title of one of the documents uploaded in the created Document Library.
Notice the Language refiner on the left.
The values displayed of the Language refiner is default sorted by count. This means the value which is used most
appears on top.
Refiner is not showing
When the refiner is not showing in the Search Center there are a few things to check.
- Make sure to fully crawl the content after creating the managed property and confirm the crawled property contains
values. - Make sure there is enough data which uses in this case the language. In the above XML the value of the
MetadataThreshold attribute is set to 3. This means the number of results that must contain a value to display the
filter generator under the filter category is set to 3. - Uncheck Use Default Configuration in the webpart properties of the Refinement Panel, section Refinement.
- In the webpart properties of the Refinement Panel in the secion Refinement a value is displayed for Number of
Categories to Display. If the number of categories exceeds this number and the new category is defined last in the XML,
it won’t show up.
Summary
SharePoint provides OOTB an easy way to create your own search refiners.
A crawled property, content and some XML to define the filter category are sufficient.















