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

Embedding and Sharing Video in SharePoint. Part Three: Aggregating Video Feeds and utilizing API

Introduction

Previous two posts were devoted to discussion of how to integrate video from YouTube and another Video Providers into SharePoint. In the first post we have discussed how to store embedded video properties and render YouTube player using Computed Field. In the second one we have discussed how to store embed code itself as it was generated by Provider.
This time we will discuss at how you can aggregate video from RSS/Atom Feeds and provide some information about using API for retrieving video content from Video Providers.

Aggregating from Video Providers RSS/Atom Feeds using YouTube

When you retrieve a video feed or list of search results, YouTube returns an Atom feed. Below is presented description of how to display videos from YouTube Feed, in our case we will display results from YouTube channel only.
For retrieving video content and displaying results  YouTube Viewer web part will be used here. In fact, it is just RSSAggregator web part, but with custom XSLT style sheet for processing YouTube feed, for details see implementation section.
So, let’s  see how to add video from YouTube feed on page:

  • Add YouTube Viewer web part which located under category Media Extensions on page
  • Specify FeedUrl for YouTube channel, for example to display video feed from Critical Path Training specify value as shown on picture
  • Result page with  YouTube web part configured is shown below

Utilizing Video Providers API

Another option to retrieve YouTube content  is to use .NET  or JavaScript client libraries directly in SharePoint.
The table below represents summary information about API for commonly used Video Providers listed in oEmbed.

Table 1. API support for Video Providers.

Provider Name API Description
YouTube Data API, Google Data client libraries for use the YouTube Data API (Java.NET, PHP, Python, Objective-C, JavaScript)
Viddler Viddler API
Qik Qik API, client for JavaScript
Vimeo Vimeo APIs

And for displaying it we need some logic to be implemented, for example by creating  custom web part. Implementation details for this functionality  are omitted here.

Implementation

For aggregating and rendering  YouTube Feeds RSSAggregator web part is used as was noted earlier with custom XSLT style sheet for processing YouTube Video Feeds.

To render YouTube feeds we provide custom  XSLT style sheet by specifying XslLink property

and the following XSLT style sheet

<xsl:stylesheet xmlns:x="http://www.w3.org/2001/XMLSchema"
version="1.0" exclude-result-prefixes="xsl ddwrt msxsl rssaggwrt"
xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime"
xmlns:rssaggwrt="http://schemas.microsoft.com/WebParts/v3/rssagg/runtime"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:rssFeed="urn:schemas-microsoft-com:sharepoint:RSSAggregatorWebPart"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:rss1="http://purl.org/rss/1.0/" xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
xmlns:atom2="http://purl.org/atom/ns#" xmlns:ddwrt2="urn:frontpage:internal"
xmlns:media="http://search.yahoo.com/mrss/"
xmlns:yt="http://gdata.youtube.com/schemas/2007">
<xsl:param name="rss_FeedLimit">3</xsl:param>
<xsl:param name="rss_ExpandFeed">false</xsl:param>
<xsl:param name="rss_LCID">1033</xsl:param>
<xsl:param name="rss_WebPartID">RSS_Viewer_WebPart</xsl:param>
<xsl:param name="rss_alignValue">left</xsl:param>
<xsl:param name="rss_IsDesignMode">True</xsl:param>
<xsl:template match="atom:feed">
<link rel="stylesheet" Type="text/css" href="/_layouts/MediaExtensions/VideoLinks.css"/>
<xsl:call-template name="ATOMYouTubeTemplate"/>
</xsl:template>
<xsl:template name="ATOMYouTubeTemplate" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<xsl:variable name="Rows" select="atom:entry"/>
<xsl:variable name="RowCount" select="count($Rows)"/>
<div class="channel-browse" >
<div class="channels-browse-gutter-padding" >
<ul class="channels-browse-content-grid context-data-container ">
<xsl:call-template name="ATOMYouTubeTemplate.body">
<xsl:with-param name="Rows" select="$Rows"/>
<xsl:with-param name="RowCount" select="count($Rows)"/>
</xsl:call-template>
</ul>
</div>
</div>
</xsl:template>
<xsl:template name="ATOMYouTubeTemplate.body" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<xsl:param name="Rows"/>
<xsl:param name="RowCount"/>
<xsl:for-each select="$Rows">
<xsl:variable name="CurPosition" select="position()" />
<xsl:variable name="RssFeedLink" select="$rss_WebPartID" />
<xsl:variable name="CurrentElement" select="concat($RssFeedLink,$CurPosition)" />
<xsl:if test="($CurPosition &lt;= $rss_FeedLimit)">
<li class="channels-content-item">
<xsl:call-template name="ATOMYouTubeTemplate.contentitem">
<xsl:with-param name="CurrentElement" select="$CurrentElement"/>
</xsl:call-template>
</li>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name="ATOMYouTubeTemplate.contentitem" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<xsl:param name="CurrentElement"/>
<span class="context-data-item">
<a onclick="javascript:window.open(this.href, 'YouTube', 'height=600,width=800,resizable');return false;" href="{ddwrt:EnsureAllowedProtocol(string(atom:link/@href))}" class="ux-thumb-wrap" >
<span class="video-thumb">
<span class="yt-thumb-clip">
<span class="yt-thumb-clip-inner">
<xsl:variable name="ThumbnailUrl" select="media:group/media:thumbnail[@width='120']/@url">
</xsl:variable>
<img src="{$ThumbnailUrl}" alt="Thumbnail" width="194" />
<span class="vertical-align"></span>
</span>
</span>
</span>
<span class="video-time">
<xsl:value-of select="media:group/yt:duration/@seconds" /> sec
</span>
</a>
<!–<a href="{ddwrt:EnsureAllowedProtocol(string(atom:link/@href))}" title="{string(atom:title)}" class="content-item-title spf-link" dir="ltr">–>
<a onclick="javascript:window.open(this.href, 'YouTube', 'height=600,width=800,resizable');return false;" href="{ddwrt:EnsureAllowedProtocol(string(atom:link/@href))}" title="{string(atom:title)}" class="content-item-title spf-link" dir="ltr">
<xsl:call-template name="GetSafeHtml">
<xsl:with-param name="Html" select="substring(atom:title, 1, 28)"/>
</xsl:call-template>…
</a>
<span class="content-item-detail">
<span class="content-item-view-count">
<xsl:value-of select="yt:statistics/@viewCount" /> views
</span>
<span class="metadata-separator">|</span>
<span class="content-item-time-created">
<xsl:value-of select="ddwrt:FormatDate(atom:published, 2057, 3)"/>
</span>
</span>
</span>
</xsl:template>
<xsl:template name="GetSafeHtml">
<xsl:param name="Html"/>
<xsl:choose>
<xsl:when test="$rss_IsDesignMode = 'True'">
<xsl:value-of select="$Html"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="rssaggwrt:MakeSafe($Html)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

view raw
YouTubeVideoFeed.xsl
hosted with ❤ by GitHub

References

Embedding and Sharing Video in SharePoint. Part Two: Posting embedded code, support for different Video Providers, Preview view

Introduction

In previous post we have discussed  approach on how to embed video into SharePoint from YouTube. Custom List Video Links were used for storing embedded video properties and Computed Field for rendering Video player.
This time we will extend our solution with the following capabilities:

  • Support for different video providers listed in  oEmbed
  • Preview view for displaying video arranged in list
  • Alternative mode for posting the embed code for video into SharePoint

Support for different video providers

In addition to embed code using iframe, another options also available now. Some providers listed in oEmbed specification use object element for embedding code.

Table 1. Support for Video Providers listed in oEmbed specification

Provider Name Embedded Mode
YouTube IFrame, Object(YouTube)
Viddler
Qik Object(Qik)
Revision3 IFrame
Hulu IFrame
Vimeo IFrame
CollegeHumor Object(CollegeHumor)
Jest Object(Jest)
CircuitLab

Alternative mode for posting embedded code

Additionally to existing mode for embedding video code, the approach described below allows to paste the embed code as it was generated by Provider. Let’s take a look at the common usage scenario:

  • Generate and copy the embed code on the Provider website
  • Select Embed Code from New Item for Video Links List

  • In New Form for Video Links List paste the embed code and fill in comments if needed as shown below

For displaying video player the same (see previous post for details) Computed Field EmbeddedVideoOnForm field is used

Preview view for displaying video

In addition to default view (video player items with details), Preview view is intended to display items arranged by columns as shown below

Implementation

To paste the embed code we define Embed Code Content Type

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<!– Parent ContentType: Link (0x0105) –>
<ContentType ID="0x010503"
Name="Embed Code"
Group="$Resources:List_Content_Types"
Description="Embed Code Content Type"
Inherits="FALSE"
Version="0">
<FieldRefs>
<FieldRef ID="{c29e077d-f466-4d8e-8bbe-72b66c5f205c}" Name="URL" Required="FALSE" Hidden="TRUE"/>
<FieldRef ID="{9F79BBE2-19A3-4341-96F6-BFDC024DEAB3}" Name="EmbedCode" Required="TRUE" />
</FieldRefs>
</ContentType>
</Elements>

where field EmbedCode for storing the embed code  is used

<Field ID="{9F79BBE2-19A3-4341-96F6-BFDC024DEAB3}"
Name="EmbedCode"
SourceID="http://schemas.microsoft.com/sharepoint/v3"
StaticName="EmbedCode"
Group="Media Columns"
Type="Note"
DisplayName="Embed Code"
Hidden="FALSE"
Sortable="FALSE">
</Field>

view raw
EmbedCode Field.xml
hosted with ❤ by GitHub

As was noted earlier, for displaying video player the same (see previous post for details) Computed Field EmbeddedVideoOnForm field is used, below is presented complete XSLT style sheet for  rendering of a field on a 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"
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:template name ="RenderEmbeddedPlayer" match ="FieldRef[@Name='EmbeddedVideoOnForm']" mode="Computed_body" >
<xsl:param name="thisNode" select="."/>
<xsl:variable name="width">
<xsl:call-template name="ensureVideoPlayerSize">
<xsl:with-param name="videoSize" select="$thisNode/@VideoWidth"/>
<xsl:with-param name="defaultSize" select="560"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="height">
<xsl:call-template name="ensureVideoPlayerSize">
<xsl:with-param name="videoSize" select="$thisNode/@VideoHeight"/>
<xsl:with-param name="defaultSize" select="315"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="frameborder">
<xsl:value-of select="$thisNode/@FrameBorder"/>
</xsl:variable>
<xsl:variable name="src">
<xsl:value-of select="$thisNode/@URL"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="$thisNode/@EmbeddingMode ='IFrame'">
<iframe width="{$width}" height="{$height}" src="{$src}" frameborder="{$frameborder}" allowfullscreen=""></iframe>
</xsl:when>
<xsl:when test="$thisNode/@EmbeddingMode ='Object(YouTube)'">
<object width="{$width}" height="{$height}">
<param name="movie" value="{$src}"></param>
<param name="allowFullScreen" value="true"></param>
<param name="allowscriptaccess" value="always"></param>
<embed src="{$src}" type="application/x-shockwave-flash" width="{$width}" height="{$height}" allowscriptaccess="always" allowfullscreen="true"></embed>
</object>
</xsl:when>
<xsl:when test="$thisNode/@EmbeddingMode ='Object(Qik)'">
<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,115,0" width="{$width}" height="{$height}" align="middle">
<param name="allowScriptAccess" value="sameDomain" />
<param name="allowFullScreen" value="true" />
<param name="movie" value="{$src}" />
<param name="quality" value="high" />
<param name="bgcolor" value="#000000" />
<param name="FlashVars" value="streamID=9d0242b2912a444e84a31c2ca3249268&amp;autoplay=false" />
<embed src="{$src}" quality="high" bgcolor="#000000" width="{$width}" height="{$height}" name="qikPlayer" align="middle" allowScriptAccess="sameDomain" allowFullScreen="true" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" FlashVars="streamID=9d0242b2912a444e84a31c2ca3249268&amp;autoplay=false"></embed>
</object>
</xsl:when>
<xsl:when test="$thisNode/@EmbeddingMode ='Object(CollegeHumor)'">
<object type="application/x-shockwave-flash" data="{$src}" width="{$width}" height="{$height}">
<param name="allowfullscreen" value="true"/>
<param name="wmode" value="transparent"/>
<param name="allowScriptAccess" value="always"/>
<param name="movie" quality="best" value="{$src}"/>
<embed src="{$src}" type="application/x-shockwave-flash" wmode="transparent" width="{$width}" height="{$height}" allowScriptAccess="always"></embed>
</object>
</xsl:when>
<xsl:when test="$thisNode/@EmbeddingMode ='Object(Jest)'">
<object type="application/x-shockwave-flash" data="{$src}" width="{$width}" height="{$height}">
<param name="allowfullscreen" value="true"/>
<param name="wmode" value="transparent"/>
<param name="allowScriptAccess" value="always"/>
<param name="movie" quality="best" value="{$src}"/>
<embed src="{$src}" type="application/x-shockwave-flash" wmode="transparent" width="{$width}" height="{$height}" allowScriptAccess="always"></embed>
</object>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="renderEmbeddedCodeAsHtml">
<xsl:with-param name="embedId" select="$thisNode/@ID" />
<xsl:with-param name="embeddedCode" select="$thisNode/@EmbedCode" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match ="FieldRef[@Name='EmbedCode']" ddwrt:dvt_mode="body" mode="Note_body">
<xsl:param name="thisNode" select="."/>
<div class="videolink-embedcode">
<xsl:value-of select="$thisNode/@EmbedCode" disable-output-escaping="yes" />
</div>
</xsl:template>
<xsl:template name="ensureVideoPlayerSize">
<xsl:param name="videoSize"/>
<xsl:param name="defaultSize"/>
<xsl:choose>
<xsl:when test="$videoSize &gt; 0">
<xsl:value-of select="$videoSize"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$defaultSize"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="renderEmbeddedCodeAsHtml">
<xsl:param name="embedId"/>
<xsl:param name="embeddedCode"/>
<xsl:variable name="embeddedCodeUrl">
<xsl:value-of select="substring-before(substring-after($embeddedCode,'&gt;'),'&lt;/a&gt;')"/>
</xsl:variable>
<xsl:variable name="embeddedCodeFixed">
<xsl:value-of select="substring-before($embeddedCode,'&lt;a')"/>
<xsl:value-of select="$embeddedCodeUrl" />
<xsl:value-of select="substring-after($embeddedCode,'&lt;/a&gt;')"/>
</xsl:variable>
<xsl:variable name="embeddedCodeHtml">
<xsl:value-of select="$embeddedCodeFixed" disable-output-escaping="yes" />
</xsl:variable>
<div id="embeddedPlayerContainer{$embedId}">
</div>
<!–<xsl:value-of select="$embeddedCodeHtml" />–>
<script type="text/javascript">
var playerHost = 'embeddedPlayerContainer<xsl:value-of select="$embedId" />';
var player = '<xsl:value-of select="$embeddedCodeFixed" />';
<![CDATA[
player = player.replace(/&quot;/g, "'").replace(/&lt;/g, "<").replace(/&gt;/g, ">");
document.getElementById(playerHost).innerHTML = player;
]]>
</script>
</xsl:template>
</xsl:stylesheet>

References

Embedding and Sharing Video in SharePoint

Introduction

The solution described here allows to embed video hosted on YouTube into SharePoint. The same approach may be applied for embedding video from another video hosting sites, for example from Vimeo.

The main idea here to store embedded video properties and not the embedded code itself.
For storing embedded video properties we will use Custom  List, see implementation section for description. Page for Video Links list (default view with embedded player)  is shown below

Usage

In order to embed a video into SharePoint:

  • On YouTube site click the Share button located under the video.
  • Click the Embed button.
  • Copy at least src attribute value provided in the expanded box (see table 1 for supported attributes).

  • Create new Video Link item  and paste attribute values for embedded code into Video Links item. Save it.

Table 1. Mapping between embedded code iframe attributes and Video Link item

Attribute name Video Links field name
src URL
width Video Width
height Video Height
allowfullscreen Allow Fullscreen
frameborder Frame Border

Video Links implementation

As was mentioned earlier only properties for embedded video code are stored and not the embedded code itself. For storing embedded video code properties is used Custom List that extends OOB Links List (TemplateType = 103)

For embedded video properties we create Video Link Content Type that inherits from Link CT (0x0105)

where we define the following fields

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<Field Type="Number"
DisplayName="Frame Border"
Required="FALSE"
EnforceUniqueValues="FALSE"
Group="Media Columns"
ID="{594e551f-180d-47ec-90e5-9195225c5932}"
StaticName="FrameBorder"
Name="FrameBorder"
Percentage="FALSE"
Hidden="FALSE"
SourceID="http://schemas.microsoft.com/sharepoint/v3">
<Default>0</Default>
</Field>
<Field Type="Number"
DisplayName="Video Height"
Required="FALSE"
EnforceUniqueValues="FALSE"
Group="Media Columns"
ID="{a1e94df1-eb6e-4fd2-aef7-bf0cc175c760}"
StaticName="VideoHeight"
Name="VideoHeight"
Percentage="FALSE"
Hidden="FALSE"
SourceID="http://schemas.microsoft.com/sharepoint/v3"/>
<Field Type="Number"
DisplayName="Video Width"
Required="FALSE"
EnforceUniqueValues="FALSE"
Group="Media Columns"
ID="{e0406eee-7432-47d8-9080-8c1c4db23170}"
StaticName="VideoWidth"
Name="VideoWidth"
Percentage="FALSE"
Hidden="FALSE"
SourceID="http://schemas.microsoft.com/sharepoint/v3"/>
<Field Type="Boolean"
DisplayName="Allow FullScreen"
EnforceUniqueValues="FALSE"
Group="Media Columns"
ID="{b6ba6c8f-81d6-478a-a303-3b18687ec934}"
StaticName="AllowFullScreen"
Name="AllowFullScreen"
Required="FALSE"
Hidden="FALSE"
SourceID="http://schemas.microsoft.com/sharepoint/v3">
<Default>0</Default>
</Field>
<Field Type="Choice"
DisplayName="Embedding Mode"
Required="FALSE"
EnforceUniqueValues="FALSE"
Format="Dropdown"
FillInChoice="FALSE"
Group="Media Columns"
ID="{5836ef4c-c440-4cb8-a471-0ee918bfc710}"
StaticName="EmbeddingMode"
Name="EmbeddingMode"
Hidden="FALSE"
SourceID="http://schemas.microsoft.com/sharepoint/v3">
<Default>IFrame</Default>
<CHOICES>
<CHOICE>IFrame</CHOICE>
<CHOICE>Object</CHOICE>
</CHOICES>
</Field>
<Field
ID="{C1D8C50A-2146-41f6-80CC-02C7691392A3}"
Type="Computed"
Name="EmbeddedVideoOnForm"
StaticName="EmbeddedVideoOnForm"
DisplaceOnUpgrade="TRUE"
ShowInNewForm="FALSE"
ShowInDisplayForm="FALSE"
ShowInEditForm="FALSE"
ShowInFileDlg="FALSE"
DisplayName="Embedded Video"
SourceID="http://schemas.microsoft.com/sharepoint/v3/fields"
Sealed="TRUE"
Sortable="FALSE"
Filterable="FALSE">
<FieldRefs>
<FieldRef Name="URL" />
<FieldRef Name="FileLeafRef" />
<FieldRef Name="FileRef" />
<FieldRef Name="FSObjType" />
<FieldRef Name="EmbeddingMode" />
<FieldRef Name="VideoWidth" />
<FieldRef Name="VideoHeight" />
<FieldRef Name="FrameBorder" />
</FieldRefs>
<DisplayPattern>
<IfEqual>
<Expr1>
<Column Name="EmbeddingMode" />
</Expr1>
<Expr2>IFrame</Expr2>
<Then>
<HTML><![CDATA[<iframe width=']]></HTML>
<Field Name="VideoWidth"/>
<HTML><![CDATA[' height=']]></HTML>
<Field Name="VideoHeight"/>
<HTML><![CDATA[' src=']]></HTML>
<Field Name="URL"/>
<HTML><![CDATA[' frameborder=']]></HTML>
<Field Name="FrameBorder"/>
<HTML><![CDATA[' allowfullscreen']]></HTML>
<HTML>
<![CDATA['></iframe>]]>
</HTML>
</Then>
<Else>
<HTML>
<![CDATA[Not supported yet]]>
</HTML>
</Else>
</IfEqual>
</DisplayPattern>
</Field>
</Elements>

Pay attention, for rendering YouTube player Computed Field EmbeddedVideoOnForm is intended.
For rendering of a field on a List View the following XSLT style sheet is used

And finally, we create List Definition Video Links from Content Type Video Link. List schema and Template files for Video Links including whole project  may be found on GitHub.

References

  • Video Links project on GitHub
  • How to embed Vimeo player see here
  • How to embed YouTube player  see here
  • How to customize the rendering of a field in List View see here

Rendering Content Query Web Part results in Table Layout

It is known that OOB Content Query Web Part (CQWP)  renders results using Lists for arranging items as shown below

In ASP.NET WebForms for Web Control DataList there is a possibility to specify layout rendering mode using property RepeatLayout, what if the similar functionality would be available in CQWP?

So, our  goal to extend CQWP, i.e. in addition to List Layout rendering mode, lets implement functionality for rendering Content Query Web Part results in Plain Old Table Layout.
In this approach, we would like to achieve the following options:

  • Possibility to easily arrange results in columns
  • Specify items direction (horizontal or vertical)

Solution Structure

Content Query Web Part Class

using System;
using System.ComponentModel;
using System.Security.Permissions;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint.Publishing.WebControls;
using Microsoft.SharePoint.Security;
using Microsoft.SharePoint.WebPartPages;
namespace CQWPWithTableLayout.WebControls
{
[ToolboxItem(false)]
public class CBQTableLayout : ContentByQueryWebPart
{
#region Control Lifecycle
protected override void CreateChildControls()
{
try
{
base.CreateChildControls();
}
catch (Exception ex)
{
throw;
}
}
protected override void ModifyXsltArgumentList(ArgumentClassWrapper argList)
{
argList.AddParameter("RepeatDirection", string.Empty, RepeatDirection);
argList.AddParameter("RepeatColumns", string.Empty, RepeatColumns);
base.ModifyXsltArgumentList(argList);
}
/// <summary>
/// Return the tool panes that configure this <see cref="T:Microsoft.SharePoint.Publishing.WebControls.ContentByQueryWebPart"/> object..
/// </summary>
[SharePointPermission(SecurityAction.Demand, ObjectModel = true)]
public override ToolPart[] GetToolParts()
{
return new ToolPart[3]
{
new ContentByQueryToolPart(),
new WebPartToolPart(),
new CBQTableLayoutToolPart()
};
}
#endregion
#region Properties
[Category("TableLayout")]
[Personalizable(PersonalizationScope.Shared), DefaultValue(3), WebBrowsable(true)]
public int RepeatColumns { get; set; }
[Category("TableLayout")]
[Personalizable(PersonalizationScope.Shared), DefaultValue(RepeatDirection.Horizontal), WebBrowsable(true)]
public RepeatDirection RepeatDirection { get; set; }
#endregion
}
}

Tool Part Class Implementation with the ability to specify Table Layout properties

using System;
using System.Globalization;
using System.Web.UI.WebControls;
namespace CQWPWithTableLayout.WebControls
{
public class CBQTableLayoutToolPart : Microsoft.SharePoint.WebPartPages.ToolPart
{
public CBQTableLayoutToolPart()
{
Init += InitToolPart;
}
private void InitToolPart(object sender, EventArgs e)
{
Title = "Table Layout Settings";
_targetWebPart = ParentToolPane.SelectedWebPart as CBQTableLayout;
if (_targetWebPart == null)
throw new Exception("Wrong web part type error");
}
protected override void CreateChildControls()
{
CreateTableLayoutSection();
PopulateTableLayoutSection();
base.CreateChildControls();
}
public override void ApplyChanges()
{
this.ApplySettingsSectionChanges();
base.ApplyChanges();
}
private void CreateTableLayoutSection()
{
_columnsBox = new TextBox {MaxLength = 2};
_directionBox = new DropDownList();
var mainTable = new Table { CellPadding = 2, CellSpacing = 2 };
mainTable.Style["border-collapse"] = "collapse";
//Repeat Columns
AddRepeatColumnsProperty(mainTable);
//Repeat Direction
AddRepeatDirectionProperty(mainTable);
Controls.Add(mainTable);
}
private void AddRepeatColumnsProperty(Table section)
{
var rowHeader = new TableRow();
var cellHeader = new TableCell { Text = "Repeat Columns" };
rowHeader.Cells.Add(cellHeader);
section.Rows.Add(rowHeader);
var rowItem = new TableRow();
var cellItem = new TableCell();
cellItem.Controls.Add(_columnsBox);
rowItem.Cells.Add(cellItem);
section.Rows.Add(rowItem);
}
private void AddRepeatDirectionProperty(Table section)
{
var rowHeader = new TableRow();
var cellHeader = new TableCell { Text = "Repeat Direction" };
rowHeader.Cells.Add(cellHeader);
section.Rows.Add(rowHeader);
var rowItem = new TableRow();
var cellItem = new TableCell();
cellItem.Controls.Add(_directionBox);
rowItem.Cells.Add(cellItem);
section.Rows.Add(rowItem);
}
private void PopulateTableLayoutSection()
{
_columnsBox.Text = _targetWebPart.RepeatColumns.ToString(CultureInfo.InvariantCulture);
var directions = Enum.GetValues(typeof(RepeatDirection));
_directionBox.DataSource = directions;
_directionBox.DataBind();
_directionBox.Text = _targetWebPart.RepeatDirection.ToString();
}
protected void ApplySettingsSectionChanges()
{
_targetWebPart.RepeatColumns = int.Parse(_columnsBox.Text);
_targetWebPart.RepeatDirection = (RepeatDirection)Enum.Parse(typeof(RepeatDirection), _directionBox.Text);
}
private TextBox _columnsBox;
private DropDownList _directionBox;
private CBQTableLayout _targetWebPart;
}
}

Web Part manifest file

<?xml version="1.0" encoding="utf-8"?>
<webParts>
<webPart xmlns="http://schemas.microsoft.com/WebPart/v3">
<metaData>
<type name="CQWPWithTableLayout.WebControls.CBQTableLayout, CQWPWithTableLayout, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f96554baea0809a2" />
<importErrorMessage>Cannot import this Web Part.</importErrorMessage>
</metaData>
<data>
<properties>
<property name="Title" type="string">Content Query (Plain Old Table Layout)</property>
<property name="Description" type="string">Displays a dynamic view of content from your site.</property>
<property name="ChromeType">TitleOnly</property>
<property name="ChromeState">Normal</property>
<property name="ItemLimit" type="int">15</property>
<property name="SortBy" type="string">{8c06beca-0777-48f7-91c7-6da68bc07b69}</property>
<property name="SortByDirection" type="Microsoft.SharePoint.Publishing.WebControls.ContentByQueryWebPart+SortDirection,Microsoft.SharePoint.Publishing,Version=14.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c">Desc</property>
<property name="GroupStyle" type="string">DefaultHeader</property>
<property name="ItemStyle" type="string">Default</property>
<property name="ServerTemplate" type="string"></property>
<property name="MainXslLink" type="string" >/Style Library/XSL Style Sheets/ContentQueryMainTableLayout.xsl</property>
<property name="RepeatColumns" type="int">0</property>
<property name="RepeatDirection" type="System.Web.UI.WebControls.RepeatDirection, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">Vertical</property>
</properties>
</data>
</webPart>
</webParts>

Pay attention that we provide custom XSLT for processing of the CQWP, for more details see How to: Customize XSL for the SharePoint Content By Query Web Part.

Main XSLT style sheet for rendering results in Table Layout

<xsl:stylesheet
version="1.0"
exclude-result-prefixes="x xsl cmswrt cbq"
xmlns:x="http://www.w3.org/2001/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:cmswrt="http://schemas.microsoft.com/WebPart/v3/Publishing/runtime"
xmlns:cbq="urn:schemas-microsoft-com:ContentByQueryWebPart">
<xsl:output method="xml" indent="no" media-type="text/html" omit-xml-declaration="yes"/>
<xsl:param name="cbq_isgrouping" />
<xsl:param name="cbq_columnwidth" />
<xsl:param name="Group" />
<xsl:param name="GroupType" />
<xsl:param name="cbq_iseditmode" />
<xsl:param name="cbq_viewemptytext" />
<xsl:param name="cbq_errortext" />
<xsl:param name="SiteId" />
<xsl:param name="WebUrl" />
<xsl:param name="PageId" />
<xsl:param name="WebPartId" />
<xsl:param name="FeedPageUrl" />
<xsl:param name="FeedEnabled" />
<xsl:param name="SiteUrl" />
<xsl:param name="BlankTitle" />
<xsl:param name="BlankGroup" />
<xsl:param name="UseCopyUtil" />
<xsl:param name="DataColumnTypes" />
<xsl:param name="ClientId" />
<xsl:param name="Source" />
<xsl:param name="RootSiteRef" />
<xsl:param name="CBQPageUrl" />
<xsl:param name="CBQPageUrlQueryStringForFilters" />
<xsl:param name="RepeatDirection" />
<xsl:param name="RepeatColumns" />
<xsl:variable name="BeginTable" select="string('&lt;table class=&quot;dfwp-list&quot;&gt;')" />
<xsl:variable name="EndTable" select="string('&lt;/table&gt;')" />
<xsl:variable name="BeginTableRow" select="string('&lt;tr&gt;')" />
<xsl:variable name="EndTableRow" select="string('&lt;/tr&gt;')" />
<xsl:variable name="BeginTableCell" select="string('&lt;td class=&quot;dfwp-item&quot;&gt;')" />
<xsl:variable name="EndTableCell" select="string('&lt;/td&gt;')" />
<xsl:template match="/">
<xsl:call-template name="OuterTemplate" />
</xsl:template>
<xsl:template name="OuterTemplate">
<xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row" />
<xsl:variable name="RowCount" select="count($Rows)" />
<xsl:variable name="IsEmpty" select="$RowCount = 0" />
<div id="{concat('cbqwp', $ClientId)}" class="cbq-layout-main">
<xsl:if test="$cbq_iseditmode = 'True' and string-length($cbq_errortext) != 0">
<div class="wp-content description">
<xsl:value-of disable-output-escaping="yes" select="$cbq_errortext" />
</div>
</xsl:if>
<xsl:choose>
<xsl:when test="$IsEmpty">
<xsl:call-template name="OuterTemplate.Empty" >
<xsl:with-param name="EditMode" select="$cbq_iseditmode" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="OuterTemplate.Body">
<xsl:with-param name="Rows" select="$Rows" />
<xsl:with-param name="FirstRow" select="1" />
<xsl:with-param name="LastRow" select="$RowCount" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</div>
<xsl:if test="$FeedEnabled = 'True' and $PageId != ''">
<div class="cqfeed">
<xsl:variable name="FeedUrl1" select="concat($SiteUrl,$FeedPageUrl,'xsl=1&amp;web=',$WebUrl,'&amp;page=',$PageId,'&amp;wp=',$WebPartId,'&amp;pageurl=',$CBQPageUrl,$CBQPageUrlQueryStringForFilters)" />
<a href="{cmswrt:RegisterFeedUrl( $FeedUrl1, 'application/rss+xml')}">
<img src="\_layouts\images\rss.gif" border="0" alt="{cmswrt:GetPublishingResource('CbqRssAlt')}"/>
</a>
</div>
</xsl:if>
</xsl:template>
<xsl:template name="OuterTemplate.Empty">
<xsl:param name="EditMode" />
<xsl:if test="$EditMode = 'True' and string-length($cbq_errortext) = 0">
<div class="wp-content description">
<xsl:value-of disable-output-escaping="yes" select="$cbq_viewemptytext" />
</div>
</xsl:if>
</xsl:template>
<xsl:template name="OuterTemplate.Body">
<xsl:param name="Rows" />
<xsl:param name="FirstRow" />
<xsl:param name="LastRow" />
<xsl:value-of disable-output-escaping="yes" select="$BeginTable" />
<xsl:choose>
<xsl:when test="$RepeatDirection = 1">
<xsl:call-template name="OuterTemplate.CallVerticalLayoutTemplate">
<xsl:with-param name="Rows" select="$Rows" />
<xsl:with-param name="FirstRow" select="$FirstRow" />
<xsl:with-param name="LastRow" select="$LastRow" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:for-each select="$Rows">
<xsl:variable name="CurPosition" select="position()" />
<xsl:call-template name="OuterTemplate.CallHorizontalLayoutTemplate">
<xsl:with-param name="CurPosition" select="$CurPosition" />
<xsl:with-param name="LastRow" select="$LastRow" />
</xsl:call-template>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
<xsl:value-of disable-output-escaping="yes" select="$EndTable" />
</xsl:template>
<xsl:template name="OuterTemplate.CallVerticalContainerTemplate">
<xsl:param name="RowPosition" />
<xsl:param name="RowsCount" />
<xsl:param name="ColsCount" />
<xsl:if test="$RowPosition &lt;= $RowsCount">
<xsl:value-of disable-output-escaping="yes" select="$BeginTableRow" />
<xsl:variable name="CurPosition" select="$RowPosition" />
<xsl:variable name="AllRows" select="/dsQueryResponse/Rows/Row" />
<xsl:for-each select="$AllRows">
<xsl:variable name="ColPosition" select="position()" />
<xsl:choose>
<xsl:when test="$ColPosition != 1 and $ColPosition &lt;= $ColsCount">
<xsl:variable name="CurPositionAbs" select="$CurPosition + $RowsCount * ($ColPosition – 1)" />
<xsl:if test="$CurPositionAbs &lt;= (count($AllRows))">
<xsl:call-template name="OuterTemplate.CallItemTemplate">
<xsl:with-param name="CurPosition" select="$CurPositionAbs" />
</xsl:call-template>
</xsl:if>
</xsl:when>
<xsl:otherwise>
<xsl:if test="$ColPosition &lt;= $ColsCount">
<xsl:call-template name="OuterTemplate.CallItemTemplate">
<xsl:with-param name="CurPosition" select="$RowPosition" />
</xsl:call-template>
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
<xsl:value-of disable-output-escaping="yes" select="$EndTableRow" />
</xsl:if>
<xsl:if test="$RowPosition &lt;= $RowsCount">
<xsl:call-template name="OuterTemplate.CallVerticalContainerTemplate">
<xsl:with-param name="RowPosition">
<xsl:value-of select="$RowPosition + 1"/>
</xsl:with-param>
<xsl:with-param name="RowsCount">
<xsl:value-of select="$RowsCount"/>
</xsl:with-param>
<xsl:with-param name="ColsCount">
<xsl:value-of select="$ColsCount"/>
</xsl:with-param>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="OuterTemplate.CallHeaderTemplate">
<xsl:apply-templates select="." mode="header">
</xsl:apply-templates>
</xsl:template>
<xsl:template name="OuterTemplate.CallVerticalLayoutTemplate">
<xsl:param name="Rows" />
<xsl:param name="FirstRow" />
<xsl:param name="LastRow" />
<!–Calc Cols & Rows–>
<xsl:choose>
<xsl:when test="$RepeatColumns = 0 or $RepeatColumns = 1">
<xsl:variable name="ColsCount" select="1" />
<xsl:variable name="RowsCount" select="$LastRow" />
<xsl:call-template name="OuterTemplate.CallVerticalContainerTemplate">
<xsl:with-param name="RowPosition" select="1" />
<xsl:with-param name="RowsCount" select="$RowsCount" />
<xsl:with-param name="ColsCount" select="$ColsCount" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="ColsCount" select="$RepeatColumns" />
<xsl:variable name="RowsCount" select="floor(($LastRow + $RepeatColumns – 1) div $RepeatColumns)" />
<xsl:call-template name="OuterTemplate.CallVerticalContainerTemplate">
<xsl:with-param name="RowPosition" select="1" />
<xsl:with-param name="RowsCount" select="$RowsCount" />
<xsl:with-param name="ColsCount" select="$ColsCount" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="OuterTemplate.CallHorizontalLayoutTemplate">
<xsl:param name="CurPosition" />
<xsl:param name="LastRow" />
<xsl:choose>
<xsl:when test="$RepeatColumns = 0">
<xsl:if test="$CurPosition = 1">
<xsl:value-of disable-output-escaping="yes" select="$BeginTableRow" />
</xsl:if>
</xsl:when>
<xsl:otherwise>
<xsl:if test="$CurPosition mod $RepeatColumns = 1">
<xsl:value-of disable-output-escaping="yes" select="$BeginTableRow" />
</xsl:if>
</xsl:otherwise>
</xsl:choose>
<xsl:call-template name="OuterTemplate.CallItemTemplate">
<xsl:with-param name="CurPosition" select="$CurPosition" />
</xsl:call-template>
<xsl:choose>
<xsl:when test="$RepeatColumns = 0">
<xsl:if test="$CurPosition = $LastRow">
<xsl:value-of disable-output-escaping="yes" select="$EndTableRow" />
</xsl:if>
</xsl:when>
<xsl:otherwise>
<xsl:if test="$CurPosition mod $RepeatColumns = 0">
<xsl:value-of disable-output-escaping="yes" select="$EndTableRow" />
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="OuterTemplate.CallItemTemplateDummy">
<xsl:param name="CurPosition" />
<xsl:value-of disable-output-escaping="yes" select="$BeginTableCell" />
<xsl:value-of disable-output-escaping="yes" select="$CurPosition" />
<xsl:value-of disable-output-escaping="yes" select="$EndTableCell" />
</xsl:template>
<xsl:template name="OuterTemplate.CallItemTemplate">
<xsl:param name="CurPosition" />
<xsl:value-of disable-output-escaping="yes" select="$BeginTableCell" />
<xsl:choose>
<xsl:when test="@Style='NewsRollUpItem'">
<xsl:apply-templates select="." mode="itemstyle">
<xsl:with-param name="EditMode" select="$cbq_iseditmode" />
</xsl:apply-templates>
</xsl:when>
<xsl:when test="@Style='NewsBigItem'">
<xsl:apply-templates select="." mode="itemstyle">
<xsl:with-param name="CurPos" select="$CurPosition" />
</xsl:apply-templates>
</xsl:when>
<xsl:when test="@Style='NewsCategoryItem'">
<xsl:apply-templates select="." mode="itemstyle">
<xsl:with-param name="CurPos" select="$CurPosition" />
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="/dsQueryResponse/Rows/Row[position() = $CurPosition]" mode="itemstyle">
</xsl:apply-templates>
<!–<xsl:value-of disable-output-escaping="yes" select="$CurPosition" />–>
</xsl:otherwise>
</xsl:choose>
<xsl:value-of disable-output-escaping="yes" select="$EndTableCell" />
</xsl:template>
<xsl:template name="OuterTemplate.CallFooterTemplate">
</xsl:template>
<xsl:template name="OuterTemplate.GetSafeLink">
<xsl:param name="UrlColumnName"/>
<xsl:if test="$UseCopyUtil = 'True'">
<xsl:value-of select="concat($RootSiteRef,'/_layouts/CopyUtil.aspx?Use=id&amp;Action=dispform&amp;ItemId=',@ID,'&amp;ListId=',@ListId,'&amp;WebId=',@WebId,'&amp;SiteId=',$SiteId,'&amp;Source=',$Source)"/>
</xsl:if>
<xsl:if test="$UseCopyUtil != 'True'">
<xsl:call-template name="OuterTemplate.GetSafeStaticUrl">
<xsl:with-param name="UrlColumnName" select="$UrlColumnName"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="OuterTemplate.GetTitle">
<xsl:param name="Title"/>
<xsl:param name="UrlColumnName"/>
<xsl:param name="UseFileName" select="0"/>
<xsl:choose>
<xsl:when test="string-length($Title) != 0 and $UseFileName = 0">
<xsl:value-of select="$Title" />
</xsl:when>
<xsl:when test="$UseCopyUtil = 'True' and $UseFileName = 0">
<xsl:value-of select="$BlankTitle" />
</xsl:when>
<xsl:otherwise>
<xsl:variable name="FileNameWithExtension">
<xsl:call-template name="OuterTemplate.GetPageNameFromUrl">
<xsl:with-param name="UrlColumnName" select="$UrlColumnName" />
</xsl:call-template>
</xsl:variable>
<xsl:choose>
<xsl:when test="$UseFileName = 1">
<xsl:call-template name="OuterTemplate.GetFileNameWithoutExtension">
<xsl:with-param name="input" select="$FileNameWithExtension" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$FileNameWithExtension" />
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="OuterTemplate.FormatColumnIntoUrl">
<xsl:param name="UrlColumnName"/>
<xsl:variable name="Value" select="@*[name()=$UrlColumnName]"/>
<xsl:if test="contains($DataColumnTypes,concat(';',$UrlColumnName,',URL;'))">
<xsl:call-template name="OuterTemplate.FormatValueIntoUrl">
<xsl:with-param name="Value" select="$Value"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="not(contains($DataColumnTypes,concat(';',$UrlColumnName,',URL;')))">
<xsl:value-of select="$Value"/>
</xsl:if>
</xsl:template>
<xsl:template name="OuterTemplate.FormatValueIntoUrl">
<xsl:param name="Value"/>
<xsl:if test="not(contains($Value,', '))">
<xsl:value-of select="$Value"/>
</xsl:if>
<xsl:if test="contains($Value,', ')">
<xsl:call-template name="OuterTemplate.Replace">
<xsl:with-param name="Value" select="substring-before($Value,', ')"/>
<xsl:with-param name="Search" select="',,'"/>
<xsl:with-param name="Replace" select="','"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="OuterTemplate.Replace">
<xsl:param name="Value"/>
<xsl:param name="Search"/>
<xsl:param name="Replace"/>
<xsl:if test="contains($Value,$Search)">
<xsl:value-of select="concat(substring-before($Value,$Search),$Replace)"/>
<xsl:call-template name="OuterTemplate.Replace">
<xsl:with-param name="Value" select="substring-after($Value,$Search)"/>
<xsl:with-param name="Search" select="$Search"/>
<xsl:with-param name="Replace" select="$Replace"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="not(contains($Value,$Search))">
<xsl:value-of select="$Value"/>
</xsl:if>
</xsl:template>
<xsl:template name="OuterTemplate.GetSafeStaticUrl">
<xsl:param name="UrlColumnName"/>
<xsl:variable name="Url">
<xsl:call-template name="OuterTemplate.FormatColumnIntoUrl">
<xsl:with-param name="UrlColumnName" select="$UrlColumnName"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="cmswrt:EnsureIsAllowedProtocol($Url)"/>
</xsl:template>
<xsl:template name="OuterTemplate.GetColumnDataForUnescapedOutput">
<xsl:param name="Name"/>
<xsl:param name="MustBeOfType"/>
<xsl:if test="contains($DataColumnTypes,concat(';',$Name,',',$MustBeOfType,';'))">
<xsl:value-of select="@*[name()=$Name]"/>
</xsl:if>
</xsl:template>
<xsl:template name="OuterTemplate.GetPageNameFromUrl">
<xsl:param name="UrlColumnName"/>
<xsl:variable name="Url">
<xsl:call-template name="OuterTemplate.FormatColumnIntoUrl">
<xsl:with-param name="UrlColumnName" select="$UrlColumnName"/>
</xsl:call-template>
</xsl:variable>
<xsl:call-template name="OuterTemplate.GetPageNameFromUrlRecursive">
<xsl:with-param name="Url" select="$Url"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="OuterTemplate.GetPageNameFromUrlRecursive">
<xsl:param name="Url"/>
<xsl:choose>
<xsl:when test="contains($Url,'/') and substring($Url,string-length($Url)) != '/'">
<xsl:call-template name="OuterTemplate.GetPageNameFromUrlRecursive">
<xsl:with-param name="Url" select="substring-after($Url,'/')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$Url"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="OuterTemplate.GetGroupName">
<xsl:param name="GroupName"/>
<xsl:param name="GroupType"/>
<xsl:choose>
<xsl:when test="string-length(normalize-space($GroupName)) = 0">
<xsl:value-of select="$BlankGroup"/>
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="$GroupType='URL'">
<xsl:variable name="Url">
<xsl:call-template name="OuterTemplate.FormatValueIntoUrl">
<xsl:with-param name="Value" select="$GroupName"/>
</xsl:call-template>
</xsl:variable>
<xsl:call-template name="OuterTemplate.GetPageNameFromUrlRecursive">
<xsl:with-param name="Url" select="$Url"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$GroupName" />
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="OuterTemplate.CallPresenceStatusIconTemplate">
<xsl:if test="string-length(@SipAddress) != 0">
<span class="presence-status-icon">
<img src="/_layouts/images/imnhdr.gif" onload="IMNRC('{@SipAddress}')" ShowOfflinePawn="1" alt="" id="{concat('MWP_pawn_',$ClientId,'_',@ID,'type=sip')}"/>
</span>
</xsl:if>
</xsl:template>
<xsl:template name="OuterTemplate.GetFileNameWithoutExtension">
<xsl:param name="input"/>
<xsl:variable name="extension">
<xsl:value-of select="substring-after($input, '.')"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="contains($extension, '.')">
<xsl:variable name="afterextension">
<xsl:call-template name="OuterTemplate.GetFileNameWithoutExtension">
<xsl:with-param name="input" select="$extension"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="concat(substring-before($input, '.'), $afterextension)"/>
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="contains($input, '.')">
<xsl:value-of select="substring-before($input, '.')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$input"/>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

Results

Below is shown Web Part configured  to display results in 4 columns and horizontal mode layout

CBQTableLayoutHorizontal2

Creating SharePoint 2013 Geolocation field using Google Maps

SharePoint 2013 Preview introduces a new field type named Geolocation that enables you to annotate SharePoint lists with location information. In columns of type Geolocation, you can enter location information as a pair of latitude and longitude coordinates in decimal degrees, or retrieve the coordinates of the user’s current location from the browser if it implements the W3C Geolocation API. For more information about the Geolocation field, see Integrating location and map functionality in SharePoint 2013.

By default location is rendered using Bing Maps but it could be customized, for example by creating custom Geolocation field that renders Nokia Maps as described here.

What about another Map providers, let’s take a look on how to implement geolocation field that renders Google Maps.

First of all we need to create custom Geolocation field, for details  see How to: Create a custom Geolocation field that renders using Nokia Maps.
Here we will only  implement Geolocation field template for Google Maps, all the remaining steps the same as described in How to: Create a custom Geolocation field that renders using Nokia Maps. This is client side rendering template file that  JSLink method of the field class points to, for more information see  How to: Customize a field type using client-side rendering

Below is presented  GMapsGeolocatioFieldTemplate.js rendering template for Google Maps.

function $_global_googlemapscontrol() {
(function() {
if (typeof GMapsControlTemplate == "object") {
return;
}
window.GMapsControlTemplate = (function() {
return {
GMapsControl_Display: function(rCtx) {
if (rCtx == null || rCtx.CurrentFieldValue == null || rCtx.CurrentFieldValue == '')
return '';
var _myData = SPClientTemplates.Utility.GetFormContextForCurrentField(rCtx);
if (_myData == null || _myData.fieldSchema == null)
return '';
_myData.registerInitCallback(_myData.fieldName, InitControl);
var gMapRedirectControl;
var _inputId_gMapRedirectControl = _myData.fieldName + '_' +
_myData.fieldSchema.Id + '_$gMapField';
var fldvalue = GMapsControlTemplate.ParseGeolocationValue(rCtx.CurrentFieldValue);
var googleStaticMapUrl = GMapsControlTemplate.GetGoogleStaticMapUrl(fldvalue, 400, 300);
var result = '<div>';
result += GMapsControlTemplate.GetRenderableFieldValue(fldvalue);
result += '<BR />';
result += '<a id="' + STSHtmlEncode(_inputId_gMapRedirectControl) + '" href="javascript:">';
result += '<img src="' + googleStaticMapUrl + '" alt="Google Maps" />'
result += '</a>';
result += '</div>';
return result;
function RedirectToGMaps() {
var googleMapStaticUrl = document.getElementById(_inputId_gMapRedirectControl).childNodes[0].src;
window.open(googleMapStaticUrl);
}
function InitControl() {
gMapRedirectControl = document.getElementById(_inputId_gMapRedirectControl);
if (gMapRedirectControl != null)
AddEvtHandler(gMapRedirectControl, "onclick", RedirectToGMaps);
}
},
GetGoogleStaticMapUrl: function(fldvalue, width, height) {
var googleStaticMapUrl = 'http://maps.googleapis.com/maps/api/staticmap&#39;;
googleStaticMapUrl += '?center=' + fldvalue.latitude + ',' + fldvalue.longitude;
googleStaticMapUrl += '&zoom=11';
googleStaticMapUrl += '&size=' + width + 'x' + height;
googleStaticMapUrl += '&markers=color:blue%7Clabel:S%7C' + fldvalue.latitude + ',' + fldvalue.longitude;
googleStaticMapUrl += '&sensor=false';
return googleStaticMapUrl;
},
GetGoogleMapUrl: function (fldvalue) {
var googleMapUrl = 'https://maps.google.com/maps&#39;;
googleMapUrl += '?z=11';
googleMapUrl += '&t=m';
googleMapUrl += '&q=loc:' + fldvalue.latitude + '+' + fldvalue.longitude;
return googleMapUrl;
},
RedirectToGMaps: function(googleMapStaticUrl) {
var url = googleMapStaticUrl.replace("&nord", "");
window.open(url);
},
ParseGeolocationValue: function(fieldValue) {
var spatialtype = "POINT";
var space = ' ';
var openingBracket = '(';
var closingBracket = ')';
var point = new Object();
point.longitude = null;
point.latitude = null;
point.altitude = null;
point.measure = null;
if (fieldValue == null || fieldValue == '')
return null;
var valueIndex = 0;
var valueLength = fieldValue.length;
var subStr;
var argIndex = 0;
var index = fieldValue.indexOf(openingBracket, valueIndex);
if (index <= valueIndex) {
return null;
}
var headEnd = index;
if (fieldValue.charCodeAt(index 1) == space.charCodeAt(0)) {
headEnd;
}
subStr = fieldValue.substr(valueIndex, headEnd valueIndex);
if (spatialtype.toLowerCase() != subStr.toLowerCase()) {
return null;
}
valueIndex = index + 1;
while (valueIndex < valueLength) {
index = fieldValue.indexOf(space, valueIndex);
if (index <= valueIndex) {
index = fieldValue.indexOf(closingBracket, valueIndex);
}
if (index <= valueIndex) {
return null;
}
subStr = fieldValue.substr(valueIndex, index valueIndex);
if (argIndex == 0) {
point.longitude = parseFloat(subStr);
}
else if (argIndex == 1) {
point.latitude = parseFloat(subStr);
}
else if (argIndex == 2) {
point.altitude = parseFloat(subStr);
}
else if (argIndex == 3) {
point.measure = parseFloat(subStr);
}
argIndex++;
valueIndex = index + 1;
}
if (argIndex < 2) {
return null;
}
return point;
},
BuildGeolocationValue: function(latitude, longitude) {
var geolocationValue = 'Point (' + longitude + ' ' + latitude + ')';
return geolocationValue;
},
GetRenderableFieldValue: function(fieldValue) {
var fldValue = 'Longitude: ' + fieldValue.longitude + ', Latitude: ' + fieldValue.latitude;
return fldValue;
},
GMapsControl_Edit: function(rCtx) {
if (rCtx == null)
return '';
var _myData = SPClientTemplates.Utility.GetFormContextForCurrentField(rCtx);
if (_myData == null || _myData.fieldSchema == null)
return '';
downloadJS('http://maps.google.com/maps/api/js?sensor=false&callback=initialize&#39;);
_myData.registerInitCallback(_myData.fieldName, InitControl);
var fldvalue = GMapsControlTemplate.ParseGeolocationValue(rCtx.CurrentFieldValue);
var controlIdHeader = _myData.fieldName + '_' + _myData.fieldSchema.Id + '_$';
var mapDiv = '';
var searchControl;
var gMapField;
var _inputId_gMapField = controlIdHeader + 'gMapField';
var _inputId_locationDisplayControl = controlIdHeader + 'locationDisplayControl';
var _inputId_nativeGeolocationValue = controlIdHeader + 'nativeGeolocationValue';
var googleStaticMapUrl = '';
mapDiv += '<div>';
mapDiv += '<H3><label id="' + _inputId_locationDisplayControl + '">';
if (fldvalue != null) {
mapDiv += GMapsControlTemplate.GetRenderableFieldValue(fldvalue);
}
mapDiv += '</H3></label>';
mapDiv += '<label id="' + _inputId_nativeGeolocationValue
+ '" style="visibility: hidden;">';
if (fldvalue != null) {
mapDiv += 'Point(' + fldvalue.longitude + ' ' + fldvalue.latitude +')';
}
mapDiv += '</label>';
mapDiv += '<a id="' + STSHtmlEncode(_inputId_gMapField) + '" href="javascript:">';
mapDiv += '<img alt="Loading…" src="';
if (fldvalue != null) {
googleStaticMapUrl = GMapsControlTemplate.GetGoogleStaticMapUrl(fldvalue, 400, 300);
mapDiv += googleStaticMapUrl;
}
mapDiv += '" />';
mapDiv += '</a>';
mapDiv += '</div>';
var _inputId_address = controlIdHeader + "address";
var _inputId_searchButton = controlIdHeader + "searchButton";
var result = '<div id="mainDiv">';
result += '<div id="inputControls">';
result += '<div id="searchControls">';
result += '<table>';
result += '<tr>';
result += '<td width=100%>';
result += '<input type="text" id="' + _inputId_address + '" value="" style="width: 100%;"/>';
result += '</td>';
result += '<td style="width: 100%; text-align: right;">';
result += '<input type="button" id="' + _inputId_searchButton
+ '" value="Search" style="width: 70%;" />';
result += '</td>';
result += '</tr>';
result += '</table>';
result += '</div>';
result += '<div id="mapControls" width=50px />';
result += mapDiv;
result += '</div>';
result += '</div>';
function downloadJS(jsFile) {
var thescript = document.createElement('script');
thescript.setAttribute('type','text/javascript');
//thescript.setAttribute('charset','UTF-8');
thescript.setAttribute('src',jsFile);
document.getElementsByTagName('head')[0].appendChild(thescript);
}
function RedirectToGMaps() {
var googleMapStaticUrl = document.getElementById(_inputId_gMapField).childNodes[0].src;
window.open(googleMapStaticUrl);
}
function InitControl() {
gMapField = document.getElementById(_inputId_gMapField);
if (gMapField != null) {
AddEvtHandler(gMapField, "onclick", RedirectToGMaps);
var fldValue = document.getElementById(_inputId_nativeGeolocationValue).textContent;
if(typeof fldValue == "undefined" || fldValue == '')
{
gMapField.setAttribute('style', 'visibility: hidden;');
}
}
searchControl = document.getElementById(_inputId_searchButton);
if (searchControl != null)
AddEvtHandler(searchControl, "onclick", GeocodeAddress);
}
function GeocodeAddress() {
if (typeof google == "undefined" || typeof google.maps.Geocoder == "undefined")
{
alert('Google Maps not loaded completelly!');
return;
}
var addressControl = document.getElementById(_inputId_address);
if (addressControl == null)
return;
var address = addressControl.value;
geocoder = new google.maps.Geocoder();
geocoder.geocode({ 'address': address }, function (results, status) {
if (status == google.maps.GeocoderStatus.OK) {
/*map.setCenter(results[0].geometry.location);
var marker = new google.maps.Marker({
map: map,
position: results[0].geometry.location
});*/
var point = new Object();
point.latitude = results[0].geometry.location.lat();
point.longitude = results[0].geometry.location.lng();
UpdateGeolocationValue(point.latitude, point.longitude);
} else {
alert("Geocode was not successful for the following reason: " + status);
}
});
}
function UpdateGeolocationValue(latitude, longitude) {
// Update native value.
document.getElementById(_inputId_nativeGeolocationValue).textContent =
GMapsControlTemplate.BuildGeolocationValue(latitude, longitude);
// Update display value.
var point = new Object();
point.latitude = latitude;
point.longitude = longitude;
document.getElementById(_inputId_locationDisplayControl).textContent =
GMapsControlTemplate.GetRenderableFieldValue(point);
// Update Map control.
var googleStaticMapUrl = GMapsControlTemplate.GetGoogleStaticMapUrl(point, 400, 400);
document.getElementById(_inputId_gMapField).childNodes[0].src
= googleStaticMapUrl;
gMapField.setAttribute('style', 'visibility: none;');
}
_myData.registerGetValueCallback(_myData.fieldName, function() {
var newValue = document.getElementById(_inputId_nativeGeolocationValue).textContent;
if(newValue == '')
return '';
var newFldValue = GMapsControlTemplate.ParseGeolocationValue(newValue);
return "Point(" + String(newFldValue.longitude) + " " + String(newFldValue.latitude) + ")";
});
return result;
},
GMapsControl_View: function(inCtx, field, listItem, listSchema) {
if (field.XSLRender == '1') {
return listItem[field.Name].toString();
}
else {
var fldvalue = GMapsControlTemplate.ParseGeolocationValue(listItem[field.Name]);
var ret = [];
if (fldvalue != null) {
ret.push("<a class=\"js-locationfield-callout\" href=\"javascript:void(0)\" liid=\"");
ret.push(GenerateIID(inCtx));
ret.push("\" fld=\"");
ret.push(field.Name);
ret.push("\" ><img title=\"");
ret.push(STSHtmlEncode(Strings.STS.L_Clippy_Tooltip));
ret.push("\"border=0 src=\"" + "/_layouts/15/images/callout-target.png");
ret.push("\"/></a>");
}
return ret.join('');
}
},
SetupMappyHoverHandlers: function(inCtx) {
EnsureScriptFunc("callout.js", "Callout", function() {
EnsureScriptFunc("core.js", "GetListItemByIID", function() {
EnsureScriptFunc("mquery.js", "m$", function() {
((m$('.js-locationfield-callout')).not(".js-locationfield-calloutInitialized")).forEach(function(e) {
var listItemID = e.getAttribute("liid");
var fieldName = e.getAttribute("fld");
var calloutTitle = '';
var calloutContent = [];
var listItem = GetListItemByIID(listItemID);
var values = GMapsControlTemplate.ParseGeolocationValue(listItem[fieldName]);
var width=300;
var googleMapStaticUrl = GMapsControlTemplate.GetGoogleStaticMapUrl
(values, width, 300);
calloutContent.push("<div><div class='ms-positionRelative' id='loc_mapcontainer_");
calloutContent.push(listItemID);
calloutContent.push("_");
calloutContent.push(fieldName);
//calloutContent.push("' ></div></div>");
calloutContent.push("' ></div>");
calloutContent.push("<div>");
calloutContent.push('<img src="' + googleMapStaticUrl + '" alt="Google Maps" />');
calloutContent.push("</div>");
calloutContent.push("</div>");
var callout = CalloutManager.createNew({
launchPoint: e,
openOptions: {
closeCalloutOnBlur: true,
event: "click",
showCloseButton: true
},
ID: listItemID + "_" + fieldName,
title: calloutTitle,
content: calloutContent.join(''),
contentWidth: width+40
});
callout.addAction(new CalloutAction({
text: "Browse on Google Maps",
onClickCallback: function () {
var googleMapUrl = GMapsControlTemplate.GetGoogleMapUrl(values);
GMapsControlTemplate.RedirectToGMaps(googleMapUrl);
}
}));
(m$(e)).addClass("js-locationfield-calloutInitialized");
});
});
});
});
},
GMapsControl_PreRender: function(inCtx) {
},
GMapsControl_PostRender: function(inCtx) {
if (ctx != null && ctx.BaseViewID != null && inCtx != null && inCtx.BaseViewID != null) {
if (inCtx.BaseViewID == ctx.BaseViewID) {
GMapsControlTemplate.SetupMappyHoverHandlers(inCtx);
}
}
}
};
})();
function _registerGMapsControlTemplate() {
var googleMapsControlContext = {};
googleMapsControlContext.Templates = {};
googleMapsControlContext.OnPreRender = GMapsControlTemplate.GMapsControl_PreRender;
googleMapsControlContext.OnPostRender = GMapsControlTemplate.GMapsControl_PostRender;
googleMapsControlContext.Templates.Fields = {
// Provide custom field name while registering .
'GMapFieldGeolocation': {
'View': GMapsControlTemplate.GMapsControl_View,
'DisplayForm': GMapsControlTemplate.GMapsControl_Display,
'EditForm': GMapsControlTemplate.GMapsControl_Edit,
'NewForm': GMapsControlTemplate.GMapsControl_Edit
}
};
SPClientTemplates.TemplateManager.RegisterTemplateOverrides(googleMapsControlContext);
}
ExecuteOrDelayUntilScriptLoaded(_registerGMapsControlTemplate, 'clienttemplates.js');
})();
}
window.initialize = function () { }
$_global_googlemapscontrol();

SharePoint 2013 project with implementation of custom geolocation field that renders Google Maps may be found here.

Results

  • Creating Google Maps Geolocation column
  • List view with Google Maps Geolocation column
  • Create or edit form with Google Maps Geolocation column

For searching locations on Edit form is used Google Geocoding API.

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