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