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("");
}