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

Bringing Map functionality into SharePoint 2010: Rendering Map List View

Overview

SharePoint 2013 introduces functionality to integrate location information and maps in SharePoint lists and location-based web and mobile apps for SharePoint with the following capabilities:

  • new field type named Geolocation that enables you to annotate SharePoint lists with location information. The built-in Geolocation field can render only with Bing Maps. However, you can create a custom field by using the Geolocation field as a parent field type, for example see my blog post about using Google Maps as map service.
  • Map View that displays a map (with data obtained from the Bing Maps service), using longitude and latitude entries from the Geolocation field type

Client-side rendering for Map List View in SharePoint 2013

Regarding Map View, SharePoint 2013 utilizes client side rendering framework for List View that allows to define the rendering logic of SharePoint list views using HTML/JavaScript. There is already predefined rendering template in SharePoint 2013  for Binq Maps service that is used when Map View is created, for more details see Create a map view for the Geolocation field in SharePoint 2013.

Map List View in SharePoint 2010

But what about SharePoint 2010? Let’s discuss one approach related with customizing List View for  XSLTListViewWebPart (XLV) . So, in proposed solution  geographical locations are stored in Custom List and custom View is used to render data on Map using Google Maps service For example, list items GeoMapDefaultView

will be represented in Map View as shown below

GeoMapView

Implementation of Map List View in SharePoint 2010

As was noted earlier for storing geographical location on a map and visualizing it in Map View we will define Custom List named GeoMap List.

GeoMap Content Type

For storing geographical locations we define the following Content Type

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<!– Parent ContentType: Item (0x01) –>
<ContentType ID="0x010087e1abb3659f42f6ad024894ec514f93"
Name="GeoMap"
Group="GeoMap"
Description="GeoMap Content Type"
Inherits="TRUE"
Version="0">
<FieldRefs>
<FieldRef ID="{F3252F03-F69E-40fb-91F7-8BD364D0882B}" Name="MapAddress1"/>
<FieldRef ID="{FBCDD54F-21AA-4eab-A376-4BC531FF264F}" Name="MapAddress2"/>
<FieldRef ID="{638D5C28-6DFC-4642-AC7C-5377E478F5CC}" Name="MapCity"/>
<FieldRef ID="{1966B506-0642-4b00-8B56-5B872C725B6A}" Name="MapState"/>
<FieldRef ID="{594036C6-29C6-4c71-A271-6721C171E675}" Name="MapZip"/>
<FieldRef ID="{6A85673B-2EE1-483d-AD5D-57161DFE8237}" Name="MapCountry"/>
<FieldRef ID="{EBFA40F7-A21A-4f4c-B941-036F5A360E0C}" Name="MapLatitude"/>
<FieldRef ID="{ADDBDAC0-123C-46d5-9BA0-0D7EA29E5711}" Name="MapLongitude"/>
<FieldRef ID="{CDF90965-628A-4cf0-91E3-D6F3DBE233CC}" Name="MapAdditionalInfo"/>
</FieldRefs>
</ContentType>
<Field Type="Text" DisplayName="Address1" Required="FALSE" ID="{F3252F03-F69E-40fb-91F7-8BD364D0882B}" SourceID="http://schemas.microsoft.com/sharepoint/v3" StaticName="MapAddress1" Name="MapAddress1" Group="GeoMap" />
<Field Type="Text" DisplayName="Address2" Required="FALSE" ID="{FBCDD54F-21AA-4eab-A376-4BC531FF264F}" SourceID="http://schemas.microsoft.com/sharepoint/v3" StaticName="MapAddress2" Name="MapAddress2" Group="GeoMap" />
<Field Type="Text" DisplayName="City" Required="FALSE" ID="{638D5C28-6DFC-4642-AC7C-5377E478F5CC}" SourceID="http://schemas.microsoft.com/sharepoint/v3" StaticName="MapCity" Name="MapCity" Group="GeoMap" />
<Field Type="Text" DisplayName="State" Required="FALSE" ID="{1966B506-0642-4b00-8B56-5B872C725B6A}" SourceID="http://schemas.microsoft.com/sharepoint/v3" StaticName="MapState" Name="MapState" Group="GeoMap" />
<Field Type="Text" DisplayName="Zip" Required="FALSE" ID="{594036C6-29C6-4c71-A271-6721C171E675}" SourceID="http://schemas.microsoft.com/sharepoint/v3" StaticName="MapZip" Name="MapZip" Group="GeoMap" />
<Field Type="Text" DisplayName="Country" Required="FALSE" ID="{6A85673B-2EE1-483d-AD5D-57161DFE8237}" SourceID="http://schemas.microsoft.com/sharepoint/v3" StaticName="MapCountry" Name="MapCountry" Group="GeoMap" />
<Field Type="Number" DisplayName="Latitude" Required="TRUE" ID="{EBFA40F7-A21A-4f4c-B941-036F5A360E0C}" SourceID="http://schemas.microsoft.com/sharepoint/v3" StaticName="MapLatitude" Name="MapLatitude" Group="GeoMap" />
<Field Type="Number" DisplayName="Longitude" Required="TRUE" ID="{ADDBDAC0-123C-46d5-9BA0-0D7EA29E5711}" SourceID="http://schemas.microsoft.com/sharepoint/v3" StaticName="MapLongitude" Name="MapLongitude" Group="GeoMap" />
<Field Type="Note" DisplayName="Additional Info" Required="FALSE" EnforceUniqueValues="FALSE" NumLines="6" RichText="FALSE" ID="{CDF90965-628A-4cf0-91E3-D6F3DBE233CC}" SourceID="http://schemas.microsoft.com/sharepoint/v3" StaticName="MapAdditionalInfo" Name="MapAdditionalInfo" Group="GeoMap" />
</Elements>

GeoMap List

GeoMap List is based on Generic List with GeoMap Content Type and with custom View

XSLT stylesheet for Map 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[@BaseViewID='80']" mode="full" ddwrt:ghost="always">
<tr class="ms-viewheadertr"></tr>
<tr>
<td>
<div id="map_canvas" style="width:100%; height:480px"></div>
</td>
</tr>
<xsl:apply-templates mode="footer" select="." />
</xsl:template>
<xsl:template name="GeoMapViewOverride" mode="RootTemplate" match="View[List/@TemplateType=10488]" ddwrt:dvt_mode="root">
<script src="http://ajax.aspnetcdn.com/ajax/jquery/jquery-1.8.0.js"></script>
<script src="https://maps.googleapis.com/maps/api/js?v=3.exp&amp;sensor=false"></script>
<script src="/_layouts/spgeomap.js"></script>
<script type="text/javascript">
ExecuteOrDelayUntilScriptLoaded(initSPGeoMap, "sp.js");
</script>
<xsl:call-template name="View_Default_RootTemplate"/>
</xsl:template>
</xsl:stylesheet>

view raw
GeoMap.xsl
hosted with ❤ by GitHub

GeoMap rendering control

SP.GeoMapControl=function()
{
var _listId;
this.Init=function(listId)
{
_listId = listId;
loadMapData(function(entries){
if (entries==null)
{
alert('No Map data was found');
}
else
{
initMapControl(entries);
}
});
}
function initMapControl(mapEntries, params) {
var mapOptions = {
center: new google.maps.LatLng(34.397, 150.644),
zoom: 8,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
var map = new google.maps.Map(document.getElementById("map_canvas"),mapOptions);
var infowindow = new google.maps.InfoWindow();
var bounds = new google.maps.LatLngBounds;
var i,marker;
for (i = 0; i < mapEntries.length; i++) {
var mapEntry = mapEntries[i];
var pos = new google.maps.LatLng(mapEntry.lat,mapEntry.lng);
marker = new google.maps.Marker({
map: map,
position: pos
});
google.maps.event.addListener(marker, 'click', (function(marker, i) {
return function() {
infowindow.setContent(mapEntries[i].content);
infowindow.open(map, marker);
}
})(marker, i));
bounds.extend(pos);
}
map.fitBounds(bounds);
}
function loadMapData(fnCallback) {
var context = new SP.ClientContext.get_current();
var web = context.get_web();
var listMap = web.get_lists().getById(_listId);
var viewXml = '<View><RowLimit>1200</RowLimit></View>';
var query = new SP.CamlQuery();
query.set_viewXml(viewXml);
var mapItems = listMap.getItems(query);
context.load(mapItems);
context.add_requestSucceeded(onMapLoaded);
context.add_requestFailed(onMapFailure);
context.executeQueryAsync();
function onMapLoaded() {
var mapsEntries=[];
var count=mapItems.get_count();
for (i=0 ; i < count; i++)
{
var mapItem=mapItems.itemAt(i);
var dataValues=mapItem.get_fieldValues();
var mapContent = '';
if(dataValues['MapAdditionalInfo'] != null)
mapContent = dataValues['MapAdditionalInfo'];
mapsEntries.push({ mapId: dataValues['ID'], title: dataValues['Title'], lat: dataValues['MapLatitude'], lng: dataValues['MapLongitude'],content: mapContent});
}
fnCallback(mapsEntries);
}
function onMapFailure() {
fnCallback(null);
}
}
};
function initSPGeoMap() {
var mapCtl = new SP.GeoMapControl();
mapCtl.Init(ctx.listName);
}

view raw
SPGeoMap.js
hosted with ❤ by GitHub

References

  • Google Maps JavaScript API v3
  • How to: Customize the Rendering of a Field on a List View in SharePoint 2010 on MSDN
  • How to: Customize a list view in apps for SharePoint using client-side rendering on MSDN

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

Overview

The creation of non standard presentations for Lists/Libraries  is a fairly common  scenario and using the XSLT List View Web Part (XLV) possibilities that can be achieved pretty easily.
Let’s take a look how to render list of question and answers using Accordion Menu in SharePoint. Actually the idea for this post appeared after posting  of corresponding question on StackOverflow.
So, let’s discuss how it could be accomplished using XLV. For Accordion we will utilize jQuery UI library.

Accordion

Implementation

FAQ Custom List

First of all, let us define where  the questions and answers (FAQ) we’ll be stored. For this we will use Custom List with Content Type.

<ContentType ID="0x0100fb1027dc96a44bf280f6cb823a8da5ae"
Name="FAQ"
Group="SE"
Description="FAQ Content Type"
Inherits="TRUE"
Version="0">
<FieldRefs>
<FieldRef Name="LinkTitle" ID="{82642ec8-ef9b-478f-acf9-31f7d45fbc31}" DisplayName="Question" Sealed="TRUE"/>
<FieldRef Name="LinkTitleNoMenu" ID="{bc91a437-52e7-49e1-8c4e-4698904b2b6d}" DisplayName="Question" Sealed="TRUE"/>
<FieldRef Name="Title" ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}" DisplayName="Question" Sealed="TRUE"/>
<FieldRef ID="{b0747420-54bc-41b2-a1b3-8432f2dbdc70}" Name="Answer"/>
</FieldRefs>
</ContentType>

view raw
FAQContentType.xml
hosted with ❤ by GitHub

Custom View for arranging items using jQuery UI Accordion

After creating Custom List we add new View for displaying Accordion for FAQ items

<View BaseViewID="10" Type="HTML" WebPartZoneID="Main" DisplayName="Accordion" DefaultView="FALSE" SetupPath="pages\viewpage.aspx" ImageUrl="/_layouts/images/generic.png" Url="Accordion.aspx">
<Toolbar Type="Standard" />
<XslLink Default="TRUE">FAQ.xsl</XslLink>
<RowLimit Paged="TRUE">30</RowLimit>
<ViewFields>
<FieldRef Name="Title" ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}" DisplayName="Question" Sealed="TRUE"/>
<FieldRef Name="Answer" ID="{b0747420-54bc-41b2-a1b3-8432f2dbdc70}"></FieldRef>
</ViewFields>
<Query>
<OrderBy>
<FieldRef Name="ID"></FieldRef>
</OrderBy>
</Query>
<ParameterBindings/>
</View>

view raw
FAQAccordionView.xml
hosted with ❤ by GitHub

XSLT style sheet for rendering Accordion View

XSLT style sheet for FAQ List is intended for the following purposes:

  • loading jQuery Core and UI Libraries
  • rendering list items using layout as specified for Accordion menu
  • initializing and rendering Accordion for List items
<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[@BaseViewID='10']" mode="full" ddwrt:ghost="always">
<tr class="ms-viewheadertr"></tr>
<tr>
<td>
<div id="accordionFAQ">
<xsl:apply-templates select="." mode="RenderView" />
</div>
</td>
</tr>
<xsl:apply-templates mode="footer" select="." />
</xsl:template>
<xsl:template mode="Item" match="Row[../../@BaseViewID='10']" ddwrt:ghost="always">
<xsl:param name="Fields" select="."/>
<xsl:param name="Collapse" select="."/>
<xsl:param name="Position" select="1"/>
<xsl:param name="Last" select="1"/>
<xsl:variable name="thisNode" select="."/>
<h3>
<xsl:value-of select="$thisNode/@Title" />
</h3>
<div>
<xsl:value-of select="$thisNode/@Answer" disable-output-escaping="yes" />
</div>
</xsl:template>
<xsl:template name="FAQViewOverride" mode="RootTemplate" match="View[List/@TemplateType=11999]" ddwrt:dvt_mode="root">
<link rel="stylesheet" href="http://ajax.aspnetcdn.com/ajax/jquery.ui/1.8.10/themes/redmond/jquery-ui.css" />
<script src="http://ajax.aspnetcdn.com/ajax/jquery/jquery-1.8.0.js"></script>
<script src="http://ajax.aspnetcdn.com/ajax/jquery.ui/1.8.22/jquery-ui.js"></script>
<script>
$(function() {
$( "#accordionFAQ" ).accordion();
});
</script>
<xsl:call-template name="View_Default_RootTemplate"/>
</xsl:template>
</xsl:stylesheet>

view raw
FAQ.xsl
hosted with ❤ by GitHub

Using jQuery UI Accordion

jQuery Core and UI libraries are injected during XSLT style sheet processing. After JavaScript files which hosted on Microsoft CDN are loaded, jQuery UI Accordion is initialized for items.

<xsl:template name="FAQViewOverride" mode="RootTemplate" match="View[List/@TemplateType=11999]" ddwrt:dvt_mode="root">
<link rel="stylesheet" href="http://ajax.aspnetcdn.com/ajax/jquery.ui/1.8.10/themes/redmond/jquery-ui.css" />
<script src="http://ajax.aspnetcdn.com/ajax/jquery/jquery-1.8.0.js"></script>
<script src="http://ajax.aspnetcdn.com/ajax/jquery.ui/1.8.22/jquery-ui.js"></script>
<script>
$(function() {
$( "#accordionFAQ" ).accordion();
});
</script>
<xsl:call-template name="View_Default_RootTemplate"/>
</xsl:template>

view raw
FAQViewOverride.xsl
hosted with ❤ by GitHub

Results

FAQ List Accordion View rendered in XLV is shown below on picture

FAQ Accordion View

References

Visualizing organizational structure in SharePoint with Google Chart Tools

Sometimes in SharePoint we need to visualize hierarchical  data as organizational chart. Prerequisites: not to use  Flash or Silverlight technologies here.

So, in our scenario for storage of organization structure will be used SharePoint List based on Custom List , for rendering engine  Google Chart Tools.

Implementation

First, let’s take a look at list’s schema for storing organizational structure

Name Type Description
ParentOrgElement Lookup Used for parent/child relationship
OrgDescription Note Display text for chart box
OrgAdditionalProperties Note Visual behavior for chart box

From Google Charts library we’ll use Organizational Chart package only.

1. Google Charts Tools loading and package initialization

function visualizeOrgChart(orgChartProperties) {
google.load('visualization', '1', { packages: ['orgchart'] });
google.OrgChartData = orgChartProperties;
google.setOnLoadCallback(loadOrgChart);
}

view raw
visualizeOrgChart.js
hosted with ❤ by GitHub

2. Fetch data using SharePoint Web Service from list that contains organizational structure

function loadOrgChart() {
var soapEnv =
"<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/'&gt; \
<soapenv:Body> \
<GetListItems xmlns='http://schemas.microsoft.com/sharepoint/soap/'&gt; \
<listName>" + google.OrgChartData.ListName + "</listName> \
<viewFields> \
<ViewFields> \
<FieldRef Name='" + google.OrgChartData.TitleFieldName + "' /> \
<FieldRef Name='" + google.OrgChartData.TooltipFieldName + "' /> \
<FieldRef Name='" + google.OrgChartData.ParentFieldName + "' /> \
<FieldRef Name='" + google.OrgChartData.StyleFieldName + "' /> \
</ViewFields> \
</viewFields> \
</GetListItems> \
</soapenv:Body> \
</soapenv:Envelope>";
$.ajax({
url: L_Menu_BaseUrl + "/_vti_bin/lists.asmx",
type: "POST",
dataType: "xml",
data: soapEnv,
complete: onOrgChartDataLoaded,
contentType: "text/xml; charset=\"utf-8\""
});
}

view raw
loadOrgChart.js
hosted with ❤ by GitHub

3. Bind data source and draw chart

function onOrgChartDataLoaded(orgChartData, status) {
var data = new google.visualization.DataTable();
data.addColumn('string', 'Name');
data.addColumn('string', 'Parent');
data.addColumn('string', 'ToolTip');
var chartFieldNames = { Title: generateOwsFieldName(google.OrgChartData.TitleFieldName),
Tooltip: generateOwsFieldName(google.OrgChartData.TooltipFieldName),
Parent: generateOwsFieldName(google.OrgChartData.ParentFieldName),
Properties: generateOwsFieldName(google.OrgChartData.StyleFieldName)
};
var orgChartRowProperties = {};
var orgChartDataRows = [];
var orgChartResult = ($.browser.msie ? orgChartData.responseXML : orgChartData.responseText);
$(orgChartResult).find("z\\:row").each(function (rowIndex) {
var orgChartRow = [];
//if ($(this).attr(chartFieldNames.Layout) == undefined)
orgChartRow[0] = $(this).attr(chartFieldNames.Title);
//else {
// orgChartRow[0] = { v: $(this).attr(chartFieldNames.Title), f: $(this).attr(chartFieldNames.Layout) };
//}
orgChartRow[1] = ($(this).attr(chartFieldNames.Parent) != undefined ? $(this).attr(chartFieldNames.Parent).split(';#')[1] : '');
orgChartRow[2] = $(this).attr(chartFieldNames.Tooltip);
if ($(this).attr(chartFieldNames.Properties) != undefined) {
orgChartRowProperties[rowIndex] = parseChartRowProperties($(this).attr(chartFieldNames.Properties));
}
orgChartDataRows[orgChartDataRows.length] = orgChartRow;
});
data.addRows(orgChartDataRows);
$.each(orgChartRowProperties, function (rowIndex, rowProperty) {
data.setRowProperties(parseInt(rowIndex), rowProperty);
});
var chart = new google.visualization.OrgChart(document.getElementById('orgChartCanvas'));
chart.draw(data, { allowHtml: true, allowCollapse: true });
}

Usage

Result page with Org Structure rendered as Chart in List View

 

Validate SharePoint People Editor in SharePoint 2010

Overview

Validation of SharePoint PeopleEditor WebControl on the client-side is often required. On server side validation is supported, see AllowEmpty and  ValidatorEnabled properties for details, but the lack of client side validation makes it inconvenient for scenarios where client side validation is mandatory. So let’s discuss some techniques how it could be achieved.

Server-Side Validation

In order to enable/disable validation for Entity Editor based controls (like People Editor) the following properties should be specified in combination:

For example, the  code below demonstrates how to disable empty values:

<wssawc:PeopleEditor
AllowEmpty="false"
ValidatorEnabled="true"
id="userPicker"
runat="server"
SelectionSet="User,SecGroup"
/>

Client-Side Validation

Let’s start with the simple method that allows to check if PeopleEditor control is not empty.

The code below demonstrates  how to validate if SharePoint PeopleEditor control value is not empty

function validateIfPeopleEditorNotEmpty(peId) {
var $pe = $("#" + peId);
var $peValHolder = $pe.find("input[id$='hiddenSpanData']");
return ($peValHolder.val().length > 0);
}

It can be used in the following scenarios:

Since jQuery is commonly used  in SharePoint front-end development, it validation  capabilities could be used  for  PeopleEditor,  for example  jQuery Validation plugin. The code below demonstrates how validation rule that allow us to verify if PeopleEditor value is not empty, may look like.

//People Editor Validator with jQuery Validation plugin
function validatePeopleEditor(peId,messageText)
{
var $pe = $("#" + peId);
var $peValHolder = $pe.find("input[id$='hiddenSpanData']");
$peValHolder.rules("add", {
required: true,
messages: {
required: messageText
}
});
}

References

Initializing SharePoint People Editor using JavaScript in SharePoint 2010

Sometimes SharePoint PeopleEditor WebControl need to be initialized on the client side.

Initialize People Editor control

//Init People Picker
//peoplePickerId – SharePoint People Editor ClientID
//pickerEntityXml – PickerEntity in xml (see method ToXmlData for class PickerEntity)
function initPeoplePickerBox(peoplePickerId, pickerEntityXml) {
var $pe = $("#" + peoplePickerId);
var entityProps = {LoginName: pickerEntityXml.attr("Key"),
UserName: pickerEntityXml.attr("DisplayText"),
Description: pickerEntityXml.attr("Description")};
var entityExtendedProps = pickerEntityXml.find("ArrayOfDictionaryEntry");
var $peData = $pe.find("input[id$='hiddenSpanData']");
var $peDisplayArea = $pe.find("div[id$='_upLevelDiv']");
var displayData = "<span id='span{LoginName}' iscontenttype='true' tabindex='-1' class='ms-entity-resolved' contenteditable='false' title='{LoginName}'>" +
" <div style='display:none;' id='divEntityData' key='{LoginName}' displaytext='{UserName}' isresolved='True' description='{LoginName}'>" +
" <div data='{PickerEntityData}'></div>" +
" </div>" +
" <span id='content' tabindex='-1' contenteditable='false' onmousedown='onMouseDownRw(event);' oncontextmenu='onContextMenuSpnRw(event,ctx);'>{UserName}</span>" +
"</span>";
var pickerEntityData = convertFromXML(entityExtendedProps[0]);
displayData = displayData.replace(/{LoginName}/g, entityProps.LoginName);
displayData = displayData.replace(/{UserName}/g, entityProps.UserName);
displayData = displayData.replace('{PickerEntityData}', pickerEntityData);
$peDisplayArea.append(displayData);
$peData.val(displayData);
}

Clear People Editor control

//Clear People Picker
//peoplePickerId – SharePoint People Editor ClientID
function clearPeoplePickerBox(peoplePickerId) {
var $pe = $("#" + peoplePickerId);
var $peData = $pe.find("input[id$='hiddenSpanData']");
var $peDisplayArea = $pe.find("div[id$='_upLevelDiv']");
$peDisplayArea.children().remove();
$peData.val("");
}