Integrating location and map functionality in SharePoint 2010

Overview

In SharePoint 2013 was  introduced a new field type named Geolocation that enables you to annotate SharePoint lists with location information. For example, you can now make lists “location-aware” and display latitude and longitude coordinates through Bing Maps. An entry is typically seen as a pushpin on a map view.

Due to the lack of these capabilities in SharePoint 2010 it was decided to fill the gap and bring location and map functionality into SharePoint 2010 and this is one the main reason why this project was initiated.

So, the aim of this project is to bring  location and map functionality into SharePoint 2010  (and more) the same way as they are currently available in SharePoint 2013.

Geolocation field

Geolocation field overview

SharePoint 2013 introduces a new field type named Geolocation that enables you to annotate SharePoint lists with location information.  The custom Geolocation field have been created as part of this project  that is intended for the same purposes in SharePoint 2010.

In columns of type Geolocation, you can enter location information as a pair of latitude and longitude coordinates in decimal degrees.

Add Geolocation column to a list

Geolocation column could be added to a list using standard capabilities of SharePoint 2010 UI as shown in Figure 1.

Figure 1. Add Geolocation column

CreategeoLocation

After the Geolocation column has been added, it could be used as shown below

Figure 2 New or Edit Form with Geolocation column
GeolocationNewForm

Figure 3. Display form with Geolocation column for Contacts list

GeolocationDispForm2

Figure 4. Representing Geolocation column in a list view

MapView_Popup2

Map view

Map View Overview

A map view is a SharePoint view that displays a map (with data obtained from the  Maps service ), using longitude and latitude entries from the Geolocation field type. When the Geolocation field type is available on the SharePoint list, a map view can be created  from the SharePoint UI. In the list, SharePoint 2010 displays the location on a map powered by Maps service, currently Bing Maps is supported only.

Create a map view

The following steps demonstrate how to create a map view from the SharePoint 2010 UI.

  1. Open the SharePoint 2010 list with Geolocation column.
  2. Choose Create view in Ribbon menu
  3. On the Choose a view type page, choose Map View, as shown in Figure 5.
    Figure 5. Choosing a view type
    MapViewFormat
  4. Save a view. A map view is created, as shown in Figure 6.
    Figure 6. Completed map view
    MapView

Resources

SharePoint 2010 Maps project is hosted on GitHub.

How to to retrieve and display Social Ratings in Content Query web part

Overview

In this post we will continue the discussion of accessing Social Data in Content Query web part that we started in the previous post. But this time we are going to customize Content Query web part in order to retrieve and display Social Ratings.  Our goal here to retrieve and display social ratings using the same way that they are being displayed  in List View.

When Ratings is enabled in List settings, the rating field is rendered in List View as shown below

ListViewWithRatings

and the customized Content Query web part for displaying Ratings control  will look like this

CBQWithSocialRatings

Prerequisites:

Implementation

So, lets start with the implementation of XSL stylesheet for rendering the Ratings control.

Social Ratings fields in SharePoint 2010

SharePoint 2010 comes with the following fields for Social Ratings:

  • AverageRating
  • RatingCount

For rendering AverageRating field in List View the  TEMPLATE\LAYOUTS\XSL\fldtypes_ratings.xsl is used. We will utilize templates from this file, but in order to make it work properly with CQWP, some modifications should be applied.

Ratings XSL stylesheet for CQWP

Templates definitions for the client side initialization and for rendering of of Ratings manager

<xsl:stylesheet xmlns:x="http://www.w3.org/2001/XMLSchema" xmlns:d="http://schemas.microsoft.com/sharepoint/dsp" version="1.0" exclude-result-prefixes="xsl msxsl ddwrt" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:SharePoint="Microsoft.SharePoint.WebControls" xmlns:ddwrt2="urn:frontpage:internal">
<xsl:param name="PopupAverageRatingLabel" />
<xsl:param name="PopupRatingCountLabel" />
<xsl:param name="PopupUserRatingLabel" />
<xsl:param name="PopupSubmitMessage" />
<xsl:param name="PopupDataNotAvailableMessage" />
<xsl:param name="PopupDataNotRatedMessage" />
<xsl:param name="ClickToRateMessage" />
<xsl:param name="CurrentAverageUnrated" />
<xsl:param name="CurrentAverageRating0_5Stars" />
<xsl:param name="CurrentAverageRating1_5Stars" />
<xsl:param name="CurrentAverageRating1_0Stars" />
<xsl:param name="CurrentAverageRating2_0Stars" />
<xsl:param name="CurrentAverageRating2_5Stars" />
<xsl:param name="CurrentAverageRating3_0Stars" />
<xsl:param name="CurrentAverageRating3_5Stars" />
<xsl:param name="CurrentAverageRating4_0Stars" />
<xsl:param name="CurrentAverageRating4_5Stars" />
<xsl:param name="CurrentAverageRating5_0Stars" />
<xsl:param name="SubmitNewRatingOpenerMessage" />
<xsl:param name="SubmitInstruction" />
<xsl:param name="Rate1StarMessage" />
<xsl:param name="Rate2StarMessage" />
<xsl:param name="Rate3StarMessage" />
<xsl:param name="Rate4StarMessage" />
<xsl:param name="Rate5StarMessage" />
<xsl:param name="IncreaseRatingInstruction" />
<xsl:param name="DecreaseRatingInstruction" />
<xsl:param name="EscapeInstruction" />
<xsl:param name="PopupWaitMessage" />
<xsl:template name="PrintAverageRating" >
<xsl:param name="thisNode" select="."/>
<xsl:variable name="url">
<xsl:call-template name="OuterTemplate.GetSafeLink">
<xsl:with-param name="UrlColumnName" select="'LinkUrl'"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="title">
<xsl:choose>
<xsl:when test="string($thisNode/@Title)!=''">
<xsl:value-of select="$thisNode/@Title"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$thisNode/@FileLeafRef.Name"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="ratingValueLocalized">
<xsl:choose>
<xsl:when test="$thisNode/@*[name()=current()/@Name]=''">0</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$thisNode/@*[name()=current()/@Name]"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="ratingsData" select="ddwrt:GetRatingsData(string($thisNode/@ID))"/>
<xsl:variable name="controlId" select="$thisNode/@ID"/>
<xsl:variable name="ratingValue">
<xsl:value-of select="translate($ratingValueLocalized, ',','.')"/>
</xsl:variable>
<xsl:if test="not($ratingsData/ItemContentTypeContainsField) or $ratingsData/ItemContentTypeContainsField/node()='true'">
<xsl:variable name="ratingClass">
<xsl:choose>
<xsl:when test="number($ratingValue) &lt;= .25">ms-rating_0</xsl:when>
<xsl:when test="number($ratingValue) &lt;= .75">ms-rating_0_5</xsl:when>
<xsl:when test="number($ratingValue) &lt;= 1.25">ms-rating_1</xsl:when>
<xsl:when test="number($ratingValue) &lt;= 1.75">ms-rating_1_5</xsl:when>
<xsl:when test="number($ratingValue) &lt;= 2.25">ms-rating_2</xsl:when>
<xsl:when test="number($ratingValue) &lt;= 2.75">ms-rating_2_5</xsl:when>
<xsl:when test="number($ratingValue) &lt;= 3.25">ms-rating_3</xsl:when>
<xsl:when test="number($ratingValue) &lt;= 3.75">ms-rating_3_5</xsl:when>
<xsl:when test="number($ratingValue) &lt;= 4.25">ms-rating_4</xsl:when>
<xsl:when test="number($ratingValue) &lt;= 4.75">ms-rating_4_5</xsl:when>
<xsl:otherwise>ms-rating_5</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="altMessage">
<xsl:choose>
<xsl:when test="$ratingClass='ms-rating_0'"><xsl:value-of select="string($CurrentAverageUnrated)"/></xsl:when>
<xsl:when test="$ratingClass='ms-rating_0_5'"><xsl:value-of select="string($CurrentAverageRating0_5Stars)"/></xsl:when>
<xsl:when test="$ratingClass='ms-rating_1'"><xsl:value-of select="string($CurrentAverageRating1_0Stars)"/></xsl:when>
<xsl:when test="$ratingClass='ms-rating_1_5'"><xsl:value-of select="string($CurrentAverageRating1_5Stars)"/></xsl:when>
<xsl:when test="$ratingClass='ms-rating_2'"><xsl:value-of select="string($CurrentAverageRating2_0Stars)"/></xsl:when>
<xsl:when test="$ratingClass='ms-rating_2_5'"><xsl:value-of select="string($CurrentAverageRating2_5Stars)"/></xsl:when>
<xsl:when test="$ratingClass='ms-rating_3'"><xsl:value-of select="string($CurrentAverageRating3_0Stars)"/></xsl:when>
<xsl:when test="$ratingClass='ms-rating_3_5'"><xsl:value-of select="string($CurrentAverageRating3_5Stars)"/></xsl:when>
<xsl:when test="$ratingClass='ms-rating_4'"><xsl:value-of select="string($CurrentAverageRating4_0Stars)"/></xsl:when>
<xsl:when test="$ratingClass='ms-rating_4_5'"><xsl:value-of select="string($CurrentAverageRating4_5Stars)"/></xsl:when>
<xsl:when test="$ratingClass='ms-rating_5'"><xsl:value-of select="string($CurrentAverageRating5_0Stars)"/></xsl:when>
</xsl:choose>
<xsl:if test="$ratingsData/FeatureActivated/node()='true' and $ratingsData/IsUserAnonymous/node()='false'">
<xsl:text> </xsl:text>
<xsl:value-of select="string($SubmitNewRatingOpenerMessage)"/>
</xsl:if>
</xsl:variable>
<span id="RatingsCtrl_{$controlId}">
<a class="ms-currentRating" href="javascript:;">
<img class="{$ratingClass}" src="{$ratingsData/RatingImageStripUrl/node()}" alt="{$altMessage}"/>
</a>
<xsl:if test="$ratingsData/FeatureActivated/node()='true' and $ratingsData/IsUserAnonymous/node()='false'">
<span class="ms-submitRating">
<img tabindex='0' alt='{string($Rate1StarMessage)} {string($SubmitInstruction)} {string($IncreaseRatingInstruction)} {string($EscapeInstruction)}' class='ms-rate1star'/>
<img tabindex='0' alt='{string($Rate2StarMessage)} {string($SubmitInstruction)} {string($IncreaseRatingInstruction)} {string($DecreaseRatingInstruction)} {string($EscapeInstruction)}' class='ms-rate2stars'/>
<img tabindex='0' alt='{string($Rate3StarMessage)} {string($SubmitInstruction)} {string($IncreaseRatingInstruction)} {string($DecreaseRatingInstruction)} {string($EscapeInstruction)}' class='ms-rate3stars'/>
<img tabindex='0' alt='{string($Rate4StarMessage)} {string($SubmitInstruction)} {string($IncreaseRatingInstruction)} {string($DecreaseRatingInstruction)} {string($EscapeInstruction)}' class='ms-rate4stars'/>
<img tabindex='0' alt='{string($Rate5StarMessage)} {string($SubmitInstruction)} {string($DecreaseRatingInstruction)} {string($EscapeInstruction)}' class='ms-rate5stars'/>
</span>
</xsl:if>
</span>
<xsl:if test="$ratingsData/FeatureActivated/node()='true' and $ratingsData/IsUserAnonymous/node()='false'">
<script type="text/javascript">
function RatingsControlLoader_<xsl:value-of select="$controlId"/>()
{
ratingsManager.CreateControl('<xsl:value-of select="ddwrt:EcmaScriptEncode($url)"/>', '<xsl:value-of select="ddwrt:EcmaScriptEncode($title)"/>', 'RatingsCtrl_<xsl:value-of select="$controlId"/>');
}
ExecuteOrDelayUntilEventNotified(RatingsControlLoader_<xsl:value-of select="$controlId"/>, 'RatingsManagerLoaded');
</script>
</xsl:if>
</xsl:if>
</xsl:template>
<xsl:template name="emit_RatingsInitialization">
<xsl:variable name="ratingsData" select="ddwrt:GetRatingsData(string(-1))"/>
<xsl:if test="$ratingsData/FeatureActivated/node()='true' and $ratingsData/IsUserAnonymous/node()='false'">
<script src="/_layouts/Ratings.js?rev={$ratingsData/CurrentBuildVersion/node()}" type="text/javascript" defer="defer"></script>
<script type="text/javascript">
var ratingsManager = null;
function RatingsManagerLoader()
{
var ratingsData = new RatingsCommonData(
'<![CDATA[<img src="/_layouts/images/loading16.gif"/> ]]><xsl:value-of select="string($PopupWaitMessage)"/>',
'<xsl:value-of select="string($PopupAverageRatingLabel)"/>',
'<xsl:value-of select="string($PopupRatingCountLabel)"/>',
'<xsl:value-of select="string($PopupUserRatingLabel)"/>',
'<xsl:value-of select="string($PopupSubmitMessage)"/>',
'<xsl:value-of select="string($PopupDataNotAvailableMessage)"/>',
'<xsl:value-of select="string($PopupDataNotRatedMessage)"/>',
'<xsl:value-of select="string($ClickToRateMessage)"/>',
'<xsl:value-of select="$ratingsData/SessionID/node()"/>',
'<xsl:value-of select="$ratingsData/WebID/node()"/>',
'<xsl:value-of select="ddwrt:EcmaScriptEncode($SiteUrl)"/>',
'<xsl:value-of select="$ratingsData/SiteID/node()"/>',
'<xsl:value-of select="$ratingsData/NewRatingIconUrl_EcmaScriptEncoded/node()"/>',
'<xsl:value-of select="$ratingsData/EmptyRatingIconUrl_EcmaScriptEncoded/node()"/>'
);
ratingsManager = new RatingsManager(ratingsData);
NotifyEventAndExecuteWaitingJobs('RatingsManagerLoaded');
}
ExecuteOrDelayUntilScriptLoaded(RatingsManagerLoader, 'ratings.js');
</script>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

view raw
RatingsStyle.xsl
hosted with ❤ by GitHub

Main XSL stylesheet

Custom ContentQueryMain.xsl is used, in which  the template for client side initialization of Ratings control is invoked

Source

Item XSL stylesheet

Custom ItemStyle.xsl is used, in which  the template for rendering Ratings control per page is invoked

Source

Ratings XSL Parameters

Parameter values are retrieved from resource file  and are passed via ParamaterBindings web part property

<ParameterBindings>
<ParameterBinding Name="PopupAverageRatingLabel" Location="Resource(sps,Ratings_PopupAverageRatingLabel)" />
<ParameterBinding Name="PopupRatingCountLabel" Location="Resource(sps,Ratings_PopupRatingCountLabel)"/>
<ParameterBinding Name="PopupUserRatingLabel" Location="Resource(sps,Ratings_PopupUserRatingLabel)"/>
<ParameterBinding Name="PopupSubmitMessage" Location="Resource(sps,Ratings_PopupSubmitMessage)"/>
<ParameterBinding Name="PopupDataNotAvailableMessage" Location="Resource(sps,Ratings_PopupDataNotAvailableMessage)"/>
<ParameterBinding Name="PopupDataNotRatedMessage" Location="Resource(sps,Ratings_PopupDataNotRatedMessage)"/>
<ParameterBinding Name="ClickToRateMessage" Location="Resource(sps,Ratings_ClickToRateMessage)"/>
<ParameterBinding Name="CurrentAverageUnrated" Location="Resource(sps,Ratings_CurrentAverageUnrated)"/>
<ParameterBinding Name="CurrentAverageRating0_5Stars" Location="Resource(sps,Ratings_CurrentAverageRating0_5Stars)"/>
<ParameterBinding Name="CurrentAverageRating1_5Stars" Location="Resource(sps,Ratings_CurrentAverageRating1_5Stars)"/>
</ParameterBindings>

Let’s summarize.

In order to configure CQWP  for displaying Social Ratings , the following steps should be accomplished:

References

CS-Script: An alternative approach to automating and scripting in SharePoint

Overview

PowerShell is the de facto standard  for SharePoint automation and scripting nowadays. But there are several competitors for the .NET platform, one of them is CS-Script.

According to the official site information:

CS-Script is a CLR (Common Language Runtime) based scripting system which uses ECMA-compliant C# as a programming language. CS-Script currently targets Microsoft implementation of CLR (.NET 2.0/3.0/3.5/4.0/4.5) with full support on Mono.

CS-Script is an open-source initiative that is distributed under the license agreement, which can be found here. However commercial support is also available.

CS-Script combines the power and richness of C# and FCL with the flexibility of a scripting system. CS-Script can be useful for system and network administrators, developers and testers. For any one who needs an automation for solving variety of programming tasks.

CS-Script could be used for solving variety of programming tasks such as:

  • analysing and adjusting system configuration
  • extending functionality of a software system with flexible scripting
  • configuring development or testing environment
  • automating software batch build
  • automating testing, and collecting test results

CS-Script In Action

So, lets take a look at different examples of using Script C# with SharePoint  and get started with the classic example of printing the greeting.

Example 1. Hello World

//css_host /version:v3.5 /platform:x64;
//css_dir C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\ISAPI;
using System;
using Microsoft.SharePoint;
namespace CSScript4SP
{
class Program
{
static void Main(string[] args)
{
using (var site = new SPSite("http://intranet.contoso.com"))
{
using (var web = site.OpenWeb())
{
Console.WriteLine("Hello SP!");
}
}
}
}
}

view raw
HelloSP.cs
hosted with ❤ by GitHub

Running it via command-line  using CS-Script engine executable (eg. csc.exe) gives us the the following results

HelloSP

Points to note:

Since it is running under SharePoint 2010, the following directives should be specified:

  • //css_host  [/version:<CLR_Version>] [/platform:<CPU>]
    – used to force the script to be compiled and executed against legacy CLR and alternative CPU architecture on Windows x64
  • //css_dir <path> – include assemblies from SharePoint directory into script and assembly probing

Example 2. Reference another C# script

In  this example is demonstrated how to reference another C# script

//css_host /version:v3.5 /platform:x64;
//css_dir C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\ISAPI;
//css_import WebPartManagerExtensions;
using System;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebPartPages;
namespace CSScript4SP
{
class Program
{
static void Main(string[] args)
{
using (var site = new SPSite("http://intranet.contoso.com"))
{
using (var web = site.OpenWeb())
{
web.ForEachWebPartOnPage<Microsoft.SharePoint.WebPartPages.WebPart>("/en/Pages/default.aspx",PrintWebPartInfo);
}
}
}
private static void PrintWebPartInfo(SPWeb web, Microsoft.SharePoint.WebPartPages.WebPart webPart, SPLimitedWebPartManager wpm)
{
Console.WriteLine("{0} {1} {2} {3}", webPart.Title, webPart.GetType().Name, webPart.ZoneIndex, webPart.ZoneID);
}
}
}

view raw
PrintWebPartInfo.cs
hosted with ❤ by GitHub

Points to note:

Example 3. Reference library

In  this example is demonstrated how to expose classes in an assembly CustomSharePointLibrary.dll

//css_host /version:v3.5 /platform:x64;
//css_dir C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\ISAPI;
//css_reference CustomSharePointLibrary.dll;
using System;
using Microsoft.SharePoint;
namespace CSScript4SP
{
class Program
{
static void Main(string[] args)
{
using (var site = new SPSite("http://intranet.contoso.com"))
{
using (var web = site.OpenWeb())
{
//access to classes from assembly CustomSharePointLibrary.dll
}
}
}
}
}

Points to note:

//css_reference <file> –  load the assembly at execution time

References

CS-Script Project Home

How to retrieve and display social comments in Content Query web part

Introduction

Using social comments in Publishing pages is pretty common scenario for SharePoint Publishing Sites. In comparison to page fields (Page Content, Title, Page Image and etc.)  that are inserted into the “slots” of the page layout and associated with the content type of the publishing page, social comments are stored separately.  Social comments are stored in separate DB and the comment entry is associated by the page Url on which comment has been given.

Let’s take a look at several approaches how to display social comments for Publishing pages in Content Query web part as shown on picture below.

Prerequisites: to enable social comments in Publishing pages the custom page layout with Note Board web part is used  for demonstration here.

CBQ_Comments

Extending Content By Query web part

In this approach the social comments are retrieved and saved in results before it is sent to the XSL transform using  ProcessDataDelegate delegate of Content Query web part. For retrieving social comments, SocialCommentManager class is utilized, which represents the entry point in  SharePoint object model (OM) that exposes methods to do work with social comments or notes.

So, in order to attach method for processing of social comments to a delegate   ProcessDataDelegate we need to subclass Content Query web part as demonstrated below:

/// <summary>
/// Adding social comments to the results of Content Query web part
/// </summary>
[ToolboxItem(false)]
public class SocialCBQ : ContentByQueryWebPart
{
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
this.ProcessDataDelegate = ProcessSocialData;
}
private DataTable ProcessSocialData(DataTable data)
{
var context = SPServiceContext.GetContext(SPContext.Current.Site);
scm = new SocialCommentManager(context);
data.Columns.Add("PageUrl", typeof(string));
data.Columns.Add("PageComments", typeof(int));
foreach (DataRow row in data.Rows)
{
var fileRefVal = new SPFieldLookupValue((string)row[SPBuiltInFieldId.FileRef.ToString("B")]);
string pageUrl = SPContext.Current.Site.MakeFullUrl("/" + fileRefVal.LookupValue);
var pageComments = scm.GetCount(new Uri(pageUrl));
row["PageUrl"] = pageUrl;
row["PageComments"] = pageComments;
}
return data;
}
private SocialCommentManager scm;
}

view raw
SocialCBQ.cs
hosted with ❤ by GitHub

And the final step is to customize template for rendering item (ItemStyle.xsl) in order to render social columns  (comment count per page in our case) in Content Query web part:

<div class="comments">
Comments: <span class="comments" pageurl="{@PageUrl}"><xsl:value-of select="@PageComments" /></span>
</div>

Utilizing SharePoint Web Services  for retrieving social comment in CQWP

In this approach we are not going to subclass Content Query web part, but implement social comments retrieving and binding on the client side.

SharePoint Web Services exposes Social Data Service that provides methods for remote clients to Create, Read, Update, and Delete (CRUD) social data.

The following JavaScript code demonstrates how to utilize SharePoint Web Services for retrieving comment count per page and to bind it to  comments item template (ItemStyle.xsl)

function CountCommentsOnUrl(url,result)
{
var soapEnv =
"<soap:Envelope xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance&#39; xmlns:xsd='http://www.w3.org/2001/XMLSchema&#39; xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'&gt; \
<soap:Body> \
<CountCommentsOnUrl xmlns='http://microsoft.com/webservices/SharePointPortalServer/SocialDataService'&gt; \
<url>" + url + "</url> \
</CountCommentsOnUrl> \
</soap:Body> \
</soap:Envelope>";
$.ajax({
pageurl: url,
result: result,
url: "/_vti_bin/SocialDataService.asmx?op=CountCommentsOnUrl",
type: "POST",
dataType: "xml",
data: soapEnv,
contentType: "text/xml; charset=\"utf-8\"",
success: function(data, status, xhr){
if(this.result !== undefined) {
var commentCount = $('CountCommentsOnUrlResponse', data).find('CountCommentsOnUrlResult').text();
this.result(commentCount);
}
}
});
}
$(function() {
$('span.comments').each(function() {
var pageUrl = $(this).attr('pageurl');
var comment = $(this);
CountCommentsOnUrl(pageUrl,function(commentCount){
comment.text(commentCount);
});
});
});

Code excerpt for comment item from ItemStyle.xsl:

<div class="comments">
Comments: <span class="comments" pageurl="{@LinkUrl}"></span>
</div>

References

Beyond the Slideshow web part capabilities in SharePoint 2010

Overview

In one of the previous post we have started the discussion of customizing   Slideshow web part, in particular it was demonstrated  how to provide additional filtering for picture items. This time we’re going to go further and consider different scenarios of using SharePoint SlideShow capabilities. We deliberately do not consider the use of  third party libraries for Slideshow or creating custom web parts, but try to build new experience using existing tools only.

1. Customize the display for Slideshow control: display additional picture properties

Suppose in addition to standard properties, custom properties should be displayed  from Picture Library in Slideshow web part.

PictureLibCustomFields

The following  fields are retrieved  from Picture Library for Slideshow:

So, our goal is to display slideshow using custom layout as shown below

CarsSlideshow

Solution:

Solution consist of the following parts:

  • load custom picture properties, see loadCarPicturesAdditionalInfo method for details
  • Slideshow library (imglib.js) methods overriding for slideshow object initialization, picture  changing and displaying
function SlideshowObjectInitializer() {
ChangePic = (function(ChangePicOrig) {
return function() {
var ssObj = arguments[0]; //SlideShow object
if(typeof ssObj.additionalInfo != "undefined")
ChangePicOrig.apply(this, arguments);
};
})(ChangePic);
ShowPic = (function(ShowPicOrig) {
return function() {
ShowPicOrig.apply(this, arguments); //call original ShowPic
var ssObj = arguments[0]; //SlideShow object
ShowAdditionalPicInfo(ssObj);
};
})(ShowPic);
function ShowAdditionalPicInfo(ssObj)
{
var curPicIdx=ssObj.index; //current picture index
if(ssObj.additionalInfo.length == 0)
return;
var picEntry = ssObj.additionalInfo[curPicIdx];
var ssobj_ext_cell = '<td>';
ssobj_ext_cell += '<div style="font-size:22px;"><span style="font-weight:bold">Car Model:</span>' + picEntry.CarModel + '</div>';
ssobj_ext_cell += '<div style="font-size:22px;"><span style="font-weight:bold">Car Description:</span>' + picEntry.CarDesc + '</div>';
ssobj_ext_cell += '</td>';
var ssobj_row = jQuery(ssObj.cell).closest('tr');
if(ssobj_row.find('td').length > 1) {
ssobj_row.find('td:nth-child(2)').replaceWith(ssobj_ext_cell);
}
else
ssobj_row.append(ssobj_ext_cell);
}
SlideshowObject = (function(SlideshowObjectOrig) {
return function() {
SlideshowObjectOrig.apply(this, arguments);
var ssobj = this;
ExecuteOrDelayUntilScriptLoaded(function(){
loadCarPicturesAdditionalInfo(function(picEntries){
ssobj.additionalInfo = picEntries;
ChangePic(ssobj);
});
},'SP.js');
};
})(SlideshowObject);
}
function loadCarPicturesAdditionalInfo(cbPicsResults) {
var context = new SP.ClientContext.get_current();
var web = context.get_web();
var list = web.get_lists().getByTitle("Pictures");
var viewXml = '<View></View>';
var query = new SP.CamlQuery();
query.set_viewXml(viewXml);
var items = list.getItems(query);
context.load(items,"Include(CarModel,CarDesc)");
context.add_requestSucceeded(onLoaded);
context.add_requestFailed(onFailure);
context.executeQueryAsync();
function onLoaded() {
var picEntries = [];
var itemsCount = items.get_count();
for (i = 0; i < itemsCount; i++) {
var item = items.itemAt(i);
var picEntry = item.get_fieldValues();
picEntries.push(picEntry);
}
cbPicsResults(picEntries);
}
function onFailure() {
cbPicsResults(null);
}
}

view raw
SSObj_customprops.js
hosted with ❤ by GitHub

Usage

Save JavaScript code to the file and embed it into the page with Slideshow web part  as demonstrated below

<script src="/_layouts/SE/imglibutils.js" type="text/javascript"></script>
<script type="text/javascript">
ExecuteOrDelayUntilScriptLoaded(SlideshowObjectInitializer, 'imglib.js');
</script>

2. Customize the display for Slideshow control: display original pictures

In Slideshow web part, picture is initialized with field value that contains the Url of  Web image (EncodedAbsWebImgUrl  field). Unfortunately it is not supported to configure in web part what image type (original, web or thumbnail) should be displayed in Slideshow The solution that demonstrated below allows to display original images instead of web images in Slideshow web part.

Solution:

In order to specify original images, method overriding for picture initialization is used:

function SlideshowObjectInitializer() {
ShowPic = (function(ShowPicOrig) {
return function() {
var ssObj = arguments[0]; //SlideShow object
var curPicIdx=ssObj.index; //current picture index
ShowPicOrig.apply(this, arguments); //call original ShowPic
//apply some changes to display original picture in SlideShow control
ssObj.image.src = ssObj.linkArray[curPicIdx]; //display original image instead of web image
//change picture & container size to auto instead of fixed (by default web image size is used)
ssObj.image.setAttribute('height','100%');
ssObj.image.setAttribute('width','100%');
var cell = ssObj.cell;
cell.style.width = 'auto';
cell.style.height = 'auto';
cell.style.display = '';
var pcell = ssObj.cell.parentNode;
pcell.style.width = 'auto';
pcell.style.height = 'auto';
};
})(ShowPic);
}
ExecuteOrDelayUntilScriptLoaded(SlideshowObjectInitializer, 'imglib.js');

Usage

One of the most simple way to apply these changes is to embed specified JavaScript code via Content Editor web part (CEWP), for more details please follow this article.

  • Save JavaScript code to the file, for example in SlideshowObjectInitializer.txt and upload it to SiteAssets Library
  • Add CEWP on page where Slideshow web part is located and in the Content Editor tool pane, under Content Link property, type /SiteAssets/SlideshowObjectInitializer.txt

3. Slideshow List View

Slideshow List View is only available for Pictures Library. It is pretty common scenario when another types of repositories are used for storing images, for example Assets Library. Let’s look at how to create a Slideshow  List View for images stored in Assets Library.

Solution:

Slideshow List View

<xsl:stylesheet xmlns:x="http://www.w3.org/2001/XMLSchema"
xmlns:d="http://schemas.microsoft.com/sharepoint/dsp"
version="1.0" exclude-result-prefixes="xsl msxsl ddwrt x d asp __designer SharePoint ddwrt2"
xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime"
xmlns:asp="http://schemas.microsoft.com/ASPNET/20"
xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:SharePoint="Microsoft.SharePoint.WebControls"
xmlns:ddwrt2="urn:frontpage:internal">
<xsl:import href="/_layouts/xsl/main.xsl"/>
<xsl:output method="html" indent="no"/>
<xsl:template match="View" mode="full" ddwrt:ghost="always">
<tr class="ms-viewheadertr"></tr>
<tr>
<td>
<!– Slideshow control –>
<table border="0" style="table-layout: fixed">
<tbody>
<tr>
<td >
<div id="slidecontrol_cell" style="display: table-cell; vertical-align: middle; text-align: center;">
<span style="vertical-align: middle; height: 100%; display: inline-block"></span>
<a id="slidecontrol_link" target="_blank">
<img id="slidecontrol_curr" style="opacity: 0.9900000000000007; border: 0px; vertical-align: middle;" alt=""/>
</a>
</div>
</td>
</tr>
<tr>
<td style="height: 75px">
<div style="height: 75px; opacity: 0.9900000000000007;" id="slidecontrol_text">
<span id="slidecontrol_title" class="ms-slideshow-title"></span>
<br/>
</div>
</td>
</tr>
<tr>
<td style="text-align: center">
<a id="btn_prev" >
<img border="0" style="position: relative; cursor: hand" onmouseover="HiliteButton()" onmouseout="DemoteButton()" src="/_layouts/images/plprev1.gif" alt="Previous Image"/>
</a>
<a id="btn_pp" >
<img border="0" style="position: relative; cursor: hand" onmouseover="HiliteButton()" onmouseout="DemoteButton()" src="/_layouts/images/plpause1.gif" id="slidecontrol_playpause" alt="Pause"/>
</a>
<a id="btn_next">
<img border="0" style="position: relative; cursor: hand" onmouseover="HiliteButton()" onmouseout="DemoteButton()" src="/_layouts/images/plnext1.gif" alt="Next Image"/>
</a>
</td>
</tr>
</tbody>
</table>
<img id="slidecontrol_next" width="0" height="0" style="visibility: hidden" />
<img id="slidecontrol_prev" width="0" height="0" style="visibility: hidden" />
<img id="slidecontrol_pp" width="0" height="0" style="visibility: hidden" />
</td>
</tr>
<script type="text/javascript">
var slidecontrol_slideshowObject;
ExecuteOrDelayUntilScriptLoaded(function(){
slidecontrol_slideshowObject = createSlideControl('slidecontrol_');
},'SP.js');
</script>
<xsl:apply-templates mode="footer" select="." />
</xsl:template>
<xsl:template name="SlideshowViewOverride" mode="RootTemplate" match="View" ddwrt:dvt_mode="root">
<script type='text/javascript' src="/_layouts/1033/imglib.js"></script>
<script type='text/javascript' src="/_layouts/yaspp/slideshow.js"></script>
<!–script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script–>
<script type="text/javascript">
</script>
<xsl:call-template name="View_Default_RootTemplate"/>
</xsl:template>
</xsl:stylesheet>

Slideshow control initialization and retrieving picture entries from Assets Library

//initialize SlideshowObject control
function createSlideControl(slidecontrolId)
{
loadPicturesInfo(function(picEntries) {
var slidecontrol_slideshowObject = new SlideshowObject(slidecontrolId, picEntries.src, picEntries.fullImageSrc, picEntries.title, picEntries.description, picEntries.height, picEntries.width, 15, 1.0);
ChangePic(slidecontrol_slideshowObject);
InitSlideControlNavButtons(slidecontrol_slideshowObject);
});
}
//
function InitSlideControlNavButtons(slideshowObject) {
var btn_prev = document.getElementById("btn_prev");
AddEvtHandler(btn_prev, "onclick", function() { NextPrevious(slideshowObject, false);});
var btn_pp = document.getElementById("btn_pp");
AddEvtHandler(btn_pp, "onclick", function() { PlayPause(slideshowObject);});
var btn_next = document.getElementById("btn_next");
AddEvtHandler(btn_next, "onclick", function() { NextPrevious(slideshowObject, true);});
}
//retrieve picture entries for SlideObject control
function loadPicturesInfo(cbPicsResults) {
var context = new SP.ClientContext.get_current();
var web = context.get_web();
var list = web.get_lists().getByTitle(ctx.ListTitle);
var viewXml = '<View></View>';
var query = new SP.CamlQuery();
query.set_viewXml(viewXml);
var items = list.getItems(query);
context.load(items,"Include(Title,EncodedAbsWebImgUrl,EncodedAbsUrl,ImageWidth,ImageHeight)");
context.add_requestSucceeded(onLoaded);
context.add_requestFailed(onFailure);
context.executeQueryAsync();
function onLoaded() {
var widthArray = [];
var heightArray = [];
var pictureArray = [];
var linkArray = [];
var titleArray = [];
var descriptionArray = [];
var itemsCount = items.get_count();
for (i = 0; i < itemsCount; i++) {
var item = items.itemAt(i);
var picEntry = item.get_fieldValues();
titleArray.push(picEntry.Title);
pictureArray.push(picEntry.EncodedAbsWebImgUrl);
linkArray.push(picEntry.EncodedAbsUrl);
descriptionArray.push('');
widthArray.push(picEntry.ImageWidth);
heightArray.push(picEntry.ImageHeight);
}
picEntries = {title:titleArray,description:descriptionArray,fullImageSrc: linkArray,src:pictureArray, width:widthArray,height:heightArray};
cbPicsResults(picEntries);
}
function onFailure() {
cbPicsResults(null);
}
}

view raw
slideshow.js
hosted with ❤ by GitHub

4. Aggregate picture items and render as Slideshow

The last example demonstrates how to display slideshow for pictures aggregated  from site collection.  For aggregating pictures from site collection, Content Query web part is utilized.

Solution

OuterTemplate.SlideShowBody template is intended for rendering Slideshow control in CQWP

<xsl:template name="OuterTemplate.SlideShowBody">
<xsl:param name="Rows" />
<xsl:param name="FirstRow" />
<xsl:param name="LastRow" />
<script type="text/javascript">
function loadScript(url, callback)
{
// adding the script tag to the head as suggested before
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = url;
// then bind the event to the callback function
// there are several events for cross browser compatibility
script.onreadystatechange = callback;
script.onload = callback;
// fire the loading
head.appendChild(script);
}
function InitSlideControlNavButtons(slideshowObject) {
var btn_prev = document.getElementById("btn_prev");
AddEvtHandler(btn_prev, "onclick", function() { NextPrevious(slideshowObject, false);});
var btn_pp = document.getElementById("btn_pp");
AddEvtHandler(btn_pp, "onclick", function() { PlayPause(slideshowObject);});
var btn_next = document.getElementById("btn_next");
AddEvtHandler(btn_next, "onclick", function() { NextPrevious(slideshowObject, true);});
}
<xsl:text>var widthArray = [</xsl:text>
<xsl:for-each select="$Rows">
<xsl:value-of select="@ImageWidth" />
<xsl:if test="position() != last()">, </xsl:if>
</xsl:for-each>
<xsl:text>];</xsl:text>
<xsl:text>var linkArray = [</xsl:text>
<xsl:for-each select="$Rows">
<xsl:value-of select="concat('&quot;',@LinkUrl,'&quot;')" />
<xsl:if test="position() != last()">, </xsl:if>
</xsl:for-each>
<xsl:text>];</xsl:text>
<xsl:text>var heightArray = [</xsl:text>
<xsl:for-each select="$Rows">
<xsl:value-of select="@ImageHeight" />
<xsl:if test="position() != last()">, </xsl:if>
</xsl:for-each>
<xsl:text>];</xsl:text>
<xsl:text>var pictureArray = [</xsl:text>
<xsl:for-each select="$Rows">
<xsl:value-of select="concat('&quot;',@LinkUrl,'&quot;')" />
<xsl:if test="position() != last()">, </xsl:if>
</xsl:for-each>
<xsl:text>];</xsl:text>
<xsl:text>var titleArray = [</xsl:text>
<xsl:for-each select="$Rows">
<xsl:value-of select="concat('&quot;',@Title,'&quot;')" />
<xsl:if test="position() != last()">, </xsl:if>
</xsl:for-each>
<xsl:text>];</xsl:text>
<xsl:text>var descriptionArray = [</xsl:text>
<xsl:for-each select="$Rows">
<xsl:value-of select="concat('&quot;',@Description,'&quot;')" />
<xsl:if test="position() != last()">, </xsl:if>
</xsl:for-each>
<xsl:text>];</xsl:text>
loadScript("/_layouts/1033/imglib.js", function()
{
var slidecontrol = new SlideshowObject('slidecontrol_', pictureArray, linkArray, titleArray, descriptionArray, heightArray, widthArray, 15, 1.0);
ChangePic(slidecontrol);
InitSlideControlNavButtons(slidecontrol);
});
</script>
<xsl:call-template name="SlideShowControl" >
</xsl:call-template>
</xsl:template>
<xsl:template name="SlideShowControl">
<table border="0" style="table-layout: fixed">
<tbody>
<tr>
<td >
<div id="slidecontrol_cell" style="display: table-cell; vertical-align: middle; text-align: center;">
<span style="vertical-align: middle; height: 100%; display: inline-block"></span>
<a id="slidecontrol_link" target="_blank">
<img id="slidecontrol_curr" style="opacity: 0.9900000000000007; border: 0px; vertical-align: middle;" alt=""/>
</a>
</div>
</td>
</tr>
<tr>
<td style="height: 75px">
<div style="height: 75px; opacity: 0.9900000000000007;" id="slidecontrol_text">
<span id="slidecontrol_title" class="ms-slideshow-title"></span>
<br/>
</div>
</td>
</tr>
<tr>
<td style="text-align: center">
<a id="btn_prev" >
<img border="0" style="position: relative; cursor: hand" onmouseover="HiliteButton()" onmouseout="DemoteButton()" src="/_layouts/images/plprev1.gif" alt="Previous Image"/>
</a>
<a id="btn_pp" >
<img border="0" style="position: relative; cursor: hand" onmouseover="HiliteButton()" onmouseout="DemoteButton()" src="/_layouts/images/plpause1.gif" id="slidecontrol_playpause" alt="Pause"/>
</a>
<a id="btn_next">
<img border="0" style="position: relative; cursor: hand" onmouseover="HiliteButton()" onmouseout="DemoteButton()" src="/_layouts/images/plnext1.gif" alt="Next Image"/>
</a>
</td>
</tr>
</tbody>
</table>
<img id="slidecontrol_next" width="0" height="0" style="visibility: hidden" />
<img id="slidecontrol_prev" width="0" height="0" style="visibility: hidden" />
<img id="slidecontrol_pp" width="0" height="0" style="visibility: hidden" />
</xsl:template>

Usage

  • Configure source properties (List Type: Picture Library, Content Type:  Picture)PicturesCQWP
  • Specify additional fields for retrieving via  CommonViewFields property. For more details how to display custom fields in a CQWP, follow this article
<property name="CommonViewFields" type="string">Description,Text;ImageWidth,Integer;ImageHeight,Integer;Title,Text;</property>

view raw
CQWP_Pictures.xml
hosted with ❤ by GitHub

  • Replace OuterTemplate.Body template with OuterTemplate.SlideShowBody in ContentQueryMain.xsl. For more details how to customize XSL for the Content Query Web Part follow this article

Conclusion

The main idea of this post  was to demonstrate how to combine different components available in SharePoint 2010 in order to build new functionality without creating new one from scratch (i.e.: new web parts) or using third party components.

Customize the rendering of a List View in Sharepoint 2013: Displaying List Items in Accordion

Overview

We have already discussed how to create Accordion List View in SharePoint 2010 in this post.
SharePoint 2013 introduces client side rendering framework for List View that allows to define the rendering logic of SharePoint list views using HTML/JavaScript. So, let’s discuss how to create Accordion List View in SharePoint 2013 based on client-side rendering.

For demonstration purposes we will create FAQ List based on Custom List. Default view for our list  will look like this

FAQDefaultView

And our goal here to customize it in such a way list items will be displayed  in accordion as shown below

AccordionListView

Implementation

In order to simplify our solution we restrict ourselves to the creation of client side rendering template only and applying it to the existing List View. If you are interested how to build custom solution for Accordion List  please follow my post about creating Accordion List View for SharePoint 2010.

Create Custom List for FAQ

FAQ List is based on Custom List. For storing questions we will utilize Title field, for Answers we will create new field with Note type

Create  client-side rendered view for Accordion

Client-side rendered view of an FAQ list is presented below

(function () {
loadCss('http://ajax.aspnetcdn.com/ajax/jquery.ui/1.8.10/themes/redmond/jquery-ui.css&#39;);
function OnAccordionViewPostRender(renderCtx) {
jQuery(function() {
jQuery( "#accordionFAQ" ).accordion();
});
}
function loadCss(url){
var link = document.createElement('link');
link.href = url;
link.rel = 'stylesheet';
document.getElementsByTagName('head')[0].appendChild(link);
}
function OnAccordionViewPreRender(renderCtx) {
}
function RenderAccordionViewBodyTemplate(renderCtx) {
var listData = renderCtx.ListData;
if (renderCtx.Templates.Body == '') {
return RenderViewTemplate(renderCtx);
}
var accordionHtml ='';
accordionHtml = '<div id="accordionFAQ">';
for (var idx in listData.Row) {
var listItem = listData.Row[idx];
accordionHtml += '<h3>';
accordionHtml += listItem.Title;
accordionHtml += '</h3>';
accordionHtml += '<div>';
accordionHtml += listItem.Answer;
accordionHtml += '</div>';
}
accordionHtml += '</div>';
return accordionHtml;
}
function _registerAccordionViewTemplate() {
var accordionViewContext = {};
//accordionViewContext.BaseViewID = 'Accordion';
accordionViewContext.Templates = {};
accordionViewContext.Templates.View = RenderAccordionViewBodyTemplate;
accordionViewContext.OnPreRender = OnAccordionViewPreRender;
accordionViewContext.OnPostRender = OnAccordionViewPostRender;
SPClientTemplates.TemplateManager.RegisterTemplateOverrides(accordionViewContext);
}
ExecuteOrDelayUntilScriptLoaded(_registerAccordionViewTemplate, 'clienttemplates.js');
})();

Customize a List View

The last step is to apply client-side rendering template to existing view. Let’s first add FAQ List instance on on page, after that  we only need to specify JSLink property value for List View

Please note that actually  three JavaScript files have been specified(the first two are jQuery libraries) for  JSLink property. All of them are stored in my case in hive, but of course they could be stored not only in file system.

Different ways of extending People Editor in the client side (SharePoint 2010)

Overview

Previously we have already discussed the possible ways for customization of  People Editor,in particular it was demonstrated  how to add  initialization and validation capabilities on the client side. This time we are going to concentrate on some aspects  of extensibility for  People Editor control that are available on the client side.

The entry point for extending of the People Editor control on the client side  is AfterCallbackClientScript property. It allows to specify the callback JavaScript function to be executed after People Entity value is resolved and  control is initialized.

//Callback function for People Editor AfterCallbackClientScript property
//ctx – People Editor Client Id
function AfterCallbackClientScript(ctx) {}

The different approach is to override Entity Editor callback function itself, for example the code below demonstrates how to execute method before control is initialized

//Override EntityEditorCallback method
var EntityEditorCallback = (function () {
var EntityEditorCallbackOrig = EntityEditorCallback;
return function() {
var result = arguments[0];
var ctx = arguments[1];
if(typeof PreCallbackClientScript != 'undefined')
PreCallbackClientScript(result, ctx);
EntityEditorCallbackOrig(result, ctx);
};
})();

The following example demonstrates the usage of the second approach.

Customize People Editor in Alerts subscription page to enable sending of  alerts assigned to SharePoint Group

Sending alerts assigned to SharePoint Group is not supported by default in SharePoint 2010. The main idea of this solution is to enable the selection of SharePoint Groups in people editor on subscription pages and the sending alerts to individual users from these groups

How it works

In Alert subscription page (New or Edit forms) type or select in People picker dialog box the SharePoint Group name

AlertsStep1

After clicking “Check names” button or OK button in People picker the SharePoint group will be  expanded  to individual users in the text box as shown below

AlertsStep2

Implementation

So, the solution consist of the following tasks:

  • activate SharePoint Groups  for selection in people editor control
  • expand a group entity to individual user entities in the text box

Enable SharePoint Group for People Editor via Delegate Control

As was noted earlier SharePoint Groups are not allowed for selection set  on people editor control in Alerts subscription pages

In order to activate  the selection of SharePoint Group in Alerts subscription pages the following control is intended (code behind):

public class SendAlertsManager : UserControl
{
#region Control Lifecycle
protected override void OnLoad(EventArgs e)
{
if (IsAlertNewPage || IsAlertEditPage)
{
EnableSendAlertsToSPGroup();
Visible = true;
}
else
{
Visible = false;
}
}
private void EnableSendAlertsToSPGroup()
{
var pe = FindControl<PeopleEditor>(Page.Controls);
if(pe!= null)
{
if(!pe.SelectionSet.Contains("SPGroup"))
{
pe.SelectionSet += ",SPGroup";
//pe.AfterCallbackClientScript = "expandGroupCallback";
}
}
}
protected override void OnPreRender(EventArgs e)
{
EnableSendAlertsToSPGroup();
base.OnPreRender(e);
}
#endregion
#region Control Utilities
/// <summary>
/// Find control recursivelly
/// (http://weblogs.asp.net/eporter/archive/2007/02/24/asp-net-findcontrol-recursive-with-generics.aspx)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="controls"></param>
/// <returns></returns>
public static T FindControl<T>(System.Web.UI.ControlCollection controls) where T : class
{
T found = default(T);
if (controls != null && controls.Count > 0)
{
for (int i = 0; i < controls.Count; i++)
{
if (found != null) break;
if (controls[i] is T)
{
found = controls[i] as T;
break;
}
found = FindControl<T>(controls[i].Controls);
}
}
return found;
}
#endregion
#region Properties
private bool IsAlertNewPage
{
get
{
return
(Context.Request.Url.PathAndQuery.IndexOf("_layouts/subnew.aspx",
StringComparison.InvariantCultureIgnoreCase) > 0);
}
}
private bool IsAlertEditPage
{
get
{
return
(Context.Request.Url.PathAndQuery.IndexOf("_layouts/subedit.aspx",
StringComparison.InvariantCultureIgnoreCase) > 0);
}
}
#endregion
}

view raw
SendAlertsManager.cs
hosted with ❤ by GitHub

Control with  reference to  JavaScript  library –  expand a group entity to individual user entities for picker editor control

<%@ Assembly Name="$SharePoint.Project.AssemblyFullName$" %>
<%@ Assembly Name="Microsoft.Web.CommandUI, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="asp" Namespace="System.Web.UI" Assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Register Tagprefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Control Language="C#" AutoEventWireup="true" Inherits="SharePoint.ControlExtender.WebControls.SendAlertsManager" %>
<script type="text/javascript" src="/_layouts/YASPP/entityeditorutils.js"></script>

Manifest file for registration of SendAlertsManager control via Delegate Control

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<Control
Id="AdditionalPageHead"
Sequence="80"
ControlSrc="~/_controltemplates/SendAlertsManager.ascx">
</Control>
</Elements>

Expand SharePoint Group entity to individual Users entities (entityeditorutils.js)

Expand SharePoint Groups to individual User entities for People Editor

//Expand Group entity to to individual Users entities
function expandGroupEntity(groupEntity, fnCallback) {
var context = new SP.ClientContext.get_current();
var groups = context.get_web().get_siteGroups();
var groupId = getSPGroupIdForEntity(groupEntity);
var group = groups.getById(groupId);
this.users = group.get_users();
context.load(users);
context.add_requestSucceeded(onLoaded);
context.add_requestFailed(onFailure);
context.executeQueryAsync();
function onLoaded() {
var userEntities = '<Entities>';
var count = users.get_count();
for (i = 0; i < count; i++) {
var userItem = users.itemAt(i);
userEntities += UserEntryToXml(userItem);
}
userEntities += '</Entities>';
var results = GetEntities(userEntities);
fnCallback(groupEntity,results);
}
function onFailure(sender, args) {
fnCallback(groupEntity,null);
}
}
//Create Entity xml fron SPUser
function UserEntryToXml(userItem)
{
return '<Entity Key="' + userItem.get_loginName() + '" DisplayText="' + userItem.get_title() + '" IsResolved="True" Description="' + userItem.get_loginName() + '"><MultipleMatches /></Entity>';
}
function prepareUserEntities(result,ctx,fnCallback)
{
var entities=GetEntities(result);
if (entities==null)
return;
for(var x=0;x<entities.childNodes.length;x++)
{
var entity=entities.childNodes[x];
if(isSPGroupEntity(entity)) {
expandGroupEntity(entity,fnCallback);
}
else
fnCallback(null,null);
}
}
function getSPGroupIdForEntity(entity)
{
var groupId = 1;
var keys = EntityEditor_SelectNodes(entity,"Key");
for (var i=0;i<keys.length;i++) {
if(keys[i].firstChild.nodeValue == "SPGroupID") {
groupId = parseInt(keys[0].nextSibling.firstChild.nodeValue);
break;
}
}
return groupId;
}
function isSPGroupEntity(entity)
{
var isSPGroupEntity = false;
var keys = EntityEditor_SelectNodes(entity,"Key");
for (var i=0;i<keys.length;i++) {
if(keys[i].firstChild.nodeValue == "SPGroupID") {
isSPGroupEntity = true;
break;
}
}
return isSPGroupEntity;
}
var EntityEditorCallback = (function () {
var EntityEditorCallbackOrig = EntityEditorCallback;
return function() {
var result = arguments[0];
var ctx = arguments[1];
prepareUserEntities(result,ctx,function (groupEntity,userEntities) {
if (groupEntity != null) {
result = result.replace(EntitytoHtml(groupEntity),UserEntitiestoHtml(userEntities));
}
EntityEditorCallbackOrig(result, ctx);
});
};
})();
//Serialize user entities to Html
function UserEntitiestoHtml(entities)
{
var entityHtml = '';
for(i=0;i<entities.childNodes.length;i++)
entityHtml += EntitytoHtml(entities.childNodes[i]);
return entityHtml;
}
//Serialize entity to Html
function EntitytoHtml(entity)
{
var entityHtml = EntityEditor_XmlToString(entity);
entityHtml = entityHtml.replace(/<MultipleMatches\/>/g, '<MultipleMatches />');
return entityHtml;
}
//Entity Editor Helper methods
//select nodes from xml entity
function EntityEditor_SelectNodes(xmlNode, tagName)
{ULSGjk:;
//if(document.implementation && document.implementation.createDocument)
//{
var elems=xmlNode.getElementsByTagName(tagName);
if(elems.length > 0)
return elems;
return null;
//}
//else
//{
// return xmlNode.selectNodes(tagName);
//}
}
//Serialize Xml to String
function EntityEditor_XmlToString(xmlData) {
var xmlString;
//IE
if (window.ActiveXObject){
xmlString = xmlData.xml;
}
// code for Mozilla, Firefox, Opera, etc.
else{
xmlString = (new XMLSerializer()).serializeToString(xmlData);
}
return xmlString;
}

view raw
entityeditorutils.js
hosted with ❤ by GitHub

Extending Picture Library Slideshow web part in SharePoint 2010

Overview

According to MSDN Picture Library Slideshow web part is used to display the pictures in a picture library slideshow.

Below are provided some details about  how Picture Library Slideshow web part works:

Step 1. Query Picture Library

Picture items are retrieved via SPQuery from Picture Library and results are saved in internal array of ImageInfo entries

public struct ImageInfo
{
public int width;
public int height;
public string src;
public string fullImageSrc;
public string title;
public string descrption;
}

view raw
ImageInfo.cs
hosted with ❤ by GitHub

Step 2. Initialize and render Slideshow control

JavaScript  Slideshow Library ( imglib.js)  is included and slideshow control is rendered.

Information about Picture entries is passed to the client side and Slideshow control is initialized

//1.Initializtion of Picture entries for SlideshowObject
var pictureArray = [];
var linkArray = [];
var titleArray = [];
var descriptionArray = [];
var heightArray = [];
var widthArray = [];
//2.Creating of slideshow object
//transitionTime – the interval time, in seconds, between the display of subsequent pictures (Speed property of PictureLibrarySlideshowWebPart)
//mode – the display order (sequential or random) of the pictures (Mode property of PictureLibrarySlideshowWebPart)
var slideshowObject = new SlideshowObject(slideshowObjectId, pictureArray, linkArray, titleArray, descriptionArray, heightArray, widthArray, transitionTime, mode);
ChangePic(slideshowObject);

Extending Picture Library Slideshow web part

Picture Library Slideshow web part class is marked as sealed, so it could not be extended by inheriting from it . But there is another way how it could be extended.

As was noted earlier SlideshowObject  is instantiated using object constructor. Using the technique with overriding   SlideshowObject constructor it is possible to provide additional logic as shown below:

SlideshowObject = (function(SlideshowObjectOrig) {
return function() {
//Some code could be placed here
//Call original SlideshowObject constructor
return SlideshowObjectOrig.apply(this, arguments);
};
})(SlideshowObject);

For example, the following example demonstrates how to exclude pictures with no titles from displaying them in Slideshow :

//Overridden SlideshowObject constructor with method for excluding pictures
function SlideshowObjectInitializer() {
SlideshowObject = (function(SlideshowObjectOrig) {
return function() {
//Exclude pictures
if(typeof excludePic != 'undefined') {
for(i=0,k=0;i<arguments[1].length;i++) {
var imageInfo = {src: arguments[1][i], fullImageSrc: arguments[2][i], title: arguments[3][i],description: arguments[4][i], height: arguments[5][i], width: arguments[6][i]};
if(excludePic(imageInfo)) {
for(j=1;j<7;j++) {
arguments[j].splice(i, 1);
}
i;
}
}
}
//Call original SlideshowObject constructor
return SlideshowObjectOrig.apply(this, arguments);
};
})(SlideshowObject);
}
//Exclide pictures with no titles
function excludePic(imageInfo) {
return (imageInfo.title.length == 0);
}
ExecuteOrDelayUntilScriptLoaded(SlideshowObjectInitializer, 'imglib.js');

view raw
imglibutils.js
hosted with ❤ by GitHub

Customizing Search Results (SharePoint 2010): People Search Results Custom Sorting using XSLT

Overview

By default People Search Results web part sorting capabilities are limited by the following options:

  • Relevance (default)
  • Social Distance
  • Name

Below is presented XSLT based approach how to provide additional sorting options for People Search Results web part.

But before we proceed let me clarify the main limitation of this solution:

  • the specified method allows to sort  search results returned for page only (to control results returned for page ResultsPerPage property is used)
  • applying sorting take place during XSLT transformation (in our case it  is applied to relevant results)

Solution description

Provide custom sorting options values

ParameterBindings property is used for storing custom sort options values and passing them to XSLT:

<ParameterBindings>
<ParameterBinding Name="CustomNameSortLabels" DefaultValue="Job Title,Department,Last Name" />
<ParameterBinding Name="CustomNameSortValues" DefaultValue="jobtitle,department,lastname" />
<ParameterBinding Name="V1FromUrl" Location="QueryString(v1)" />
<ParameterBinding Name="QueryFromUrl" Location="QueryString(k)" />
<ParameterBinding Name="HsFromUrl" Location="QueryString(hs1)" />
<ParameterBinding Name="RmFromUrl" Location="QueryString(rm1)" />
</ParameterBindings>

,where

CustomNameSortLabels and CustomNameSortValues   are used for storing  custom sort option labels and values respectively.

Customize Sort Options  in People Search Results

In order to display custom sort options the following XSLT template is used:

<xsl:template name="GetCustomSortResultUrl">
<xsl:param name="NameSortValue"/>
<xsl:value-of select="concat(concat(concat('peopleresults.aspx?k=',$QueryFromUrl),'&amp;v1='),$NameSortValue)"/>
</xsl:template>
<xsl:template name="CustomSortOptions">
<xsl:param name="NameSortValues" select="$CustomNameSortValues"/>
<xsl:param name="NameSortLabels" select="$CustomNameSortLabels"/>
<xsl:param name="separator" select="','"/>
<xsl:choose>
<xsl:when test="not(contains($NameSortValues, $separator))">
<xsl:if test="string-length($NameSortValues) &gt; 0">
<xsl:variable name="NameSortUrl">
<xsl:call-template name="GetCustomSortResultUrl">
<xsl:with-param name="NameSortValue" select="$NameSortValues"/>
</xsl:call-template>
</xsl:variable>
<xsl:element name="option">
<xsl:attribute name="value">
<xsl:value-of select="ddwrt:EnsureAllowedProtocol(string($NameSortUrl))"/>
</xsl:attribute>
<xsl:if test="$NameSortValues = $V1FromUrl and string-length($HsFromUrl) = 0">
<xsl:attribute name="selected">selected</xsl:attribute>
</xsl:if>
<xsl:value-of select="$NameSortLabels"/>
</xsl:element>
</xsl:if>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="NameSortUrl">
<xsl:call-template name="GetCustomSortResultUrl">
<xsl:with-param name="NameSortValue" select="substring-before($NameSortValues, $separator)"/>
</xsl:call-template>
</xsl:variable>
<xsl:element name="option">
<xsl:attribute name="value">
<xsl:value-of select="ddwrt:EnsureAllowedProtocol(string($NameSortUrl))"/>
</xsl:attribute>
<xsl:if test="substring-before($NameSortValues, $separator) = $V1FromUrl">
<xsl:attribute name="selected">selected</xsl:attribute>
</xsl:if>
<xsl:value-of select="substring-before($NameSortLabels, $separator)"/>
</xsl:element>
<xsl:call-template name="CustomSortOptions">
<xsl:with-param name="NameSortValues" select="substring-after($NameSortValues, $separator)"/>
<xsl:with-param name="NameSortLabels" select="substring-after($NameSortLabels, $separator)"/>
<xsl:with-param name="separator" select="','"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>

Complete XSLT file for People Search Results  could be found here

Below is shown sorting options control after applying specified XSLT and providing Parameter Binding property value from above example

SortOptions

Sorting for search results is defined using XSLT sorting element:

<xsl:for-each select="All_Results/Result">
<xsl:sort data-type="text" order="ascending" select="*[name() = $V1FromUrl]"/>
<xsl:call-template name="SingleResult"/>
</xsl:for-each>

Complete XSLT file for People Search Results with custom Sort Options could be found here

Results

People Search Result page with results sorted by Last Name is shown below

PeopleSearchResultsCustomSort

References