Manage User Custom Actions in Office 365


Custom Actions offer a flexible way to extend capabilities of the SharePoint. The possibilities span the range of including custom JavaScript on every page to extending the Ribbon. In SharePoint 2013/SharePoint Online you can leverage the CSOM/REST  to manage custom actions. Below are demonstrated two simple examples of using custom actions in real world scenarios and I hope you you’ll find them useful.

Example 1. Enable jQuery

Let’s get started with an example that demonstrate how to add jQuery library to Office 365/SharePoint Online site. Unfortunately  it is not supported to reference external resources, for example from Microsoft Ajax Content Delivery Network (CDN) that hosts popular third party JavaScript libraries including jQuery. The prerequisite for referencing JavaScript files is that they could only be accesible when located within the site collection. So, the first step would be to save a jQuery library into Style Library: /Style Library/Scripts/jQuery/jquery-2.1.1.js.

The following Activate-JQuery.ps1 script  demonstrates how to enable jQuery library  in Office 365/SharePoint Online site

. ".\UserCustomActions.ps1"
Enable jQuery Library
Enable jQuery Library in Office 365/SharePoint Online site
.\Activate-JQuery.ps1 -SiteUrl "; -UserName "" -Password "password"
Function Activate-JQuery([string]$SiteUrl,[string]$UserName,[string]$Password)
$context = New-Object Microsoft.SharePoint.Client.ClientContext($SiteUrl)
$context.Credentials = Get-SPOCredentials UserName $UserName Password $Password
$sequenceNo = 1482
$jQueryUrl = "~SiteCollection/Style Library/Scripts/jQuery/jquery-2.1.1.js"
Add-ScriptLinkAction Context $Context ScriptSrc $jQueryUrl Sequence $sequenceNo
ActivateJQuery SiteUrl $SiteUrl UserName $UserName Password $Password

view raw
hosted with ❤ by GitHub

Dependencies: UserCustomActions.ps1

Example 2. Enable Google Analytics

The following Activate-GoogleAnalytics.ps1 script demonstrates how to activate tracking code in Office 365/SharePoint Online site

. ".\UserCustomActions.ps1"
Activate Google Analytics
Activate Google Analytics in Office 365/SharePoint Online site
.\Activate-GoogleAnalytics.ps1 -SiteUrl "; -UserName "" -Password "password"
Function Activate-GoogleAnalytics([string]$SiteUrl,[string]$UserName,[string]$Password)
$context = New-Object Microsoft.SharePoint.Client.ClientContext($SiteUrl)
$context.Credentials = Get-SPOCredentials UserName $UserName Password $Password
$sequenceNo = 1480
#Insert your Tracking code here or specify valid Tracking ID
$TrackingCode = "(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
ga('create', 'UA-XXXXXXXX-X', 'auto');
ga('send', 'pageview');"
Add-ScriptLinkAction Context $Context ScriptBlock $TrackingCode Sequence $sequenceNo
ActivateGoogleAnalytics SiteUrl $SiteUrl UserName $UserName Password $Password

Dependencies: UserCustomActions.ps1

Follow these instructions to use Google Analytics to collect data from Office 365/SharePoint Online sites.

To set up the web tracking code:

  1. Find the tracking code snippet for your property.
    Sign in to your Google Analytics account, and click Admin in the top menu bar. From the Account and Propertycolumns, select the property you’re working with. Click Tracking Info / Tracking Code.
  2. Find your tracking code snippet. It’s in a box with several lines of JavaScript in it. Everything in this box is your tracking code snippet. It starts with <script> and ends with </script>.
    The tracking code contains a unique ID that corresponds to each Google Analytics property. Don’t mix up tracking code snippets from different properties, and don’t reuse the same tracking code snippet on multiple domains. Click to expand this image and see where the tracking code snippet is in the interface.
  3. Open , paste the tracking code into $TrackingCode variable and run the script to register tracking code in Office 365/SharePoint Online site
  4. Check your set up.
    Make sure that the tracking snippet installed on your website matches the code shown in the view, and see more ways you can verify your set up.

Embedding Video to a SharePoint Library


We have already discussed how to embed video from YouTube and another Video Providers into SharePoint, in particular it was demonstrated  how to:

In both cases some kind of customization should be applied for List/Library in order to render YouTube player in List Forms and Views. In first case custom fields are used for storing embed code properties and a computed field for rendering player. In the second case, custom fields are used for storing embed code itself and YouTube player is rendered via customized List View and Field.

This time we will discuss another approach that is intended for sharing video in out-of-the box List and Libraries, like Blogs and Discussion Boards.

How to embed Video in SharePoint Blog and Discussion Board

Before we will dive into technical details about implementation let me show some usage scenarios.

In order to embed a video into SharePoint:

  • On YouTube site click the Share button located under the video.
  • Click the Embed button.
  • Copy VideoId value from  src attribute value provided in the expanded box .
  • Add reply  for a Discussion and select Insert Tab – Share VideoYouTube Video Button to insert YouTube video on page
  • Specify YouTube Video parameters and click OK button to insert YouTube player on page
  • Saved Message item (for Discussion List)
  • Blogs View


The solution consist of:

  • SharePoint Ribbon and Page Component for Sharing Video
  • RTE with the capability to insert video player
  • Render video player on List Forms and Views

SharePoint Ribbon and Page Component for Sharing Video

1. The existing group Ribbon.EditingTools.CPInsert.Media is extended with the buttons for video sharing.

2. Video Sharing Page component is developed for interaction with the Server Ribbon:

//Page Component for Sharing Video
MediaExtensions.Ribbon.RTE.VideoPageComponent = function () {
MediaExtensions.Ribbon.RTE.VideoPageComponent.prototype = {
mediaCommands: ['VideoGroup', 'ShareVideo', 'ShareVideoMenuOpen', 'ShareVideoMenuClose', 'EmbedVideo', 'InsertVideoWeb'],
registerWithPageManager: function () {
}, getFocusedCommands: function () {
return this.mediaCommands;
}, getGlobalCommands: function () {
return this.mediaCommands;
}, canHandleCommand: function (commandId) {
return true;
}, handleCommand: function (commandId, properties, sequence) {
if (commandId === 'ShareVideo') {
return true;
if (commandId === 'InsertVideoWeb') {
return true;
return true;
}, isFocusable: function () {
return true
}, receiveFocus: function () {
return true;
}, yieldFocus: function () {
return true;
}, invokeVideoCommand: function (commandId) {
var range = RTE.Cursor.get_range();
if (!range.get_isEditable()) {
var dlg = new MediaExtensions.Ribbon.RTE.ShareVideoDialog();;
MediaExtensions.Ribbon.RTE.VideoPageComponent.registerClass('MediaExtensions.Ribbon.RTE.VideoPageComponent', CUI.Page.PageComponent);
//Register Page Component for Sharing Video
function ShareVideoInit() {
var currentPageManager = SP.Ribbon.PageManager.get_instance();
currentPageManager.addPageComponent(new MediaExtensions.Ribbon.RTE.VideoPageComponent());

view raw
hosted with ❤ by GitHub

RTE with the capability to insert video player

By default  YouTube video player is rendered as IFrame element. Since in SharePoint 2010 it is not allowed to store iframe content into Publishing HTML field the alternative solution is proposed here:

1. User provides all  the properties for video in RTE dialog

2. Video player tag is inserted into SharePoint Rich Text Editor from RTE dialog:

<!– Video player tag is inserted into SharePoint Rich Text Editor from RTE Dialog –>
<!– When the List Form or View page is rendered, this tag is replaced with <iframe> (and video player) via YouTube IFrame API –>
<div id="ytplayer">
<span style="display: none;">Z6-iUNfJsJw;320;215</span> <!– player info in the following format VideoId;Width:Height –>

view raw
hosted with ❤ by GitHub

3. When the List Form or View page is rendered, YouTube player is created and video is loaded.

Render video player on List Forms and Views

The IFrame player API is intended for embedding a YouTube video player on a website and control the player using JavaScript. The IFrame API posts content to an <iframe> tag on a page.

The code demonstrates how to create an embedded player that will load a video

var ytplayersinfolist = [];
//This function creates an <iframe> (and YouTube player)
//after the API code downloads
function onYouTubeIframeAPIReady() {
if (typeof window.playerInfoList === 'undefined')
for (var i = 0; i < window.playerInfoList.length; i++) {
// This function loads the IFrame Player API code asynchronously
function registerYTIFrameApi() {
var tag = document.createElement('script');
tag.src = ";;
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
function initYTPlayer(playerInfo) {
return new YT.Player(, {
height: playerInfo.height,
width: playerInfo.width,
videoId: playerInfo.videoId

view raw
hosted with ❤ by GitHub


Bringing Map functionality into SharePoint 2010: Rendering Map List View


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


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="">
<!– Parent ContentType: Item (0x01) –>
<ContentType ID="0x010087e1abb3659f42f6ad024894ec514f93"
Description="GeoMap Content Type"
<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"/>
<Field Type="Text" DisplayName="Address1" Required="FALSE" ID="{F3252F03-F69E-40fb-91F7-8BD364D0882B}" SourceID="" StaticName="MapAddress1" Name="MapAddress1" Group="GeoMap" />
<Field Type="Text" DisplayName="Address2" Required="FALSE" ID="{FBCDD54F-21AA-4eab-A376-4BC531FF264F}" SourceID="" StaticName="MapAddress2" Name="MapAddress2" Group="GeoMap" />
<Field Type="Text" DisplayName="City" Required="FALSE" ID="{638D5C28-6DFC-4642-AC7C-5377E478F5CC}" SourceID="" StaticName="MapCity" Name="MapCity" Group="GeoMap" />
<Field Type="Text" DisplayName="State" Required="FALSE" ID="{1966B506-0642-4b00-8B56-5B872C725B6A}" SourceID="" StaticName="MapState" Name="MapState" Group="GeoMap" />
<Field Type="Text" DisplayName="Zip" Required="FALSE" ID="{594036C6-29C6-4c71-A271-6721C171E675}" SourceID="" StaticName="MapZip" Name="MapZip" Group="GeoMap" />
<Field Type="Text" DisplayName="Country" Required="FALSE" ID="{6A85673B-2EE1-483d-AD5D-57161DFE8237}" SourceID="" StaticName="MapCountry" Name="MapCountry" Group="GeoMap" />
<Field Type="Number" DisplayName="Latitude" Required="TRUE" ID="{EBFA40F7-A21A-4f4c-B941-036F5A360E0C}" SourceID="" StaticName="MapLatitude" Name="MapLatitude" Group="GeoMap" />
<Field Type="Number" DisplayName="Longitude" Required="TRUE" ID="{ADDBDAC0-123C-46d5-9BA0-0D7EA29E5711}" SourceID="" 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="" StaticName="MapAdditionalInfo" Name="MapAdditionalInfo" Group="GeoMap" />

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=""
version="1.0" exclude-result-prefixes="xsl msxsl ddwrt x d asp __designer SharePoint ddwrt2"
<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>
<div id="map_canvas" style="width:100%; height:480px"></div>
<xsl:apply-templates mode="footer" select="." />
<xsl:template name="GeoMapViewOverride" mode="RootTemplate" match="View[List/@TemplateType=10488]" ddwrt:dvt_mode="root">
<script src=""></script>
<script src=";sensor=false"></script>
<script src="/_layouts/spgeomap.js"></script>
<script type="text/javascript">
ExecuteOrDelayUntilScriptLoaded(initSPGeoMap, "sp.js");
<xsl:call-template name="View_Default_RootTemplate"/>

view raw
hosted with ❤ by GitHub

GeoMap rendering control

var _listId;
_listId = listId;
if (entries==null)
alert('No Map data was found');
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.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);, marker);
})(marker, i));
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();
var mapItems = listMap.getItems(query);
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});
function onMapFailure() {
function initSPGeoMap() {
var mapCtl = new SP.GeoMapControl();

view raw
hosted with ❤ by GitHub


  • 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

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") {
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;;
function InitControl() {
gMapRedirectControl = document.getElementById(_inputId_gMapRedirectControl);
if (gMapRedirectControl != null)
AddEvtHandler(gMapRedirectControl, "onclick", RedirectToGMaps);
GetGoogleStaticMapUrl: function(fldvalue, width, height) {
var googleStaticMapUrl = ';;
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 = ';;
googleMapUrl += '?z=11';
googleMapUrl += '&t=m';
googleMapUrl += '&q=loc:' + fldvalue.latitude + '+' + fldvalue.longitude;
return googleMapUrl;
RedirectToGMaps: function(googleMapStaticUrl) {
var url = googleMapStaticUrl.replace("&nord", "");;
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)) {
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);
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 '';
_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');
function RedirectToGMaps() {
var googleMapStaticUrl = document.getElementById(_inputId_gMapField).childNodes[0].src;;
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!');
var addressControl = document.getElementById(_inputId_address);
if (addressControl == null)
var address = addressControl.value;
geocoder = new google.maps.Geocoder();
geocoder.geocode({ 'address': address }, function (results, status) {
if (status == google.maps.GeocoderStatus.OK) {
var marker = new google.maps.Marker({
map: map,
position: results[0].geometry.location
var point = new Object();
point.latitude = results[0];
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 =
// Update Map control.
var googleStaticMapUrl = GMapsControlTemplate.GetGoogleStaticMapUrl(point, 400, 400);
= 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("\" fld=\"");
ret.push("\" ><img title=\"");
ret.push("\"border=0 src=\"" + "/_layouts/15/images/callout-target.png");
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("' ></div></div>");
calloutContent.push("' ></div>");
calloutContent.push('<img src="' + googleMapStaticUrl + '" alt="Google Maps" />');
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);
GMapsControl_PreRender: function(inCtx) {
GMapsControl_PostRender: function(inCtx) {
if (ctx != null && ctx.BaseViewID != null && inCtx != null && inCtx.BaseViewID != null) {
if (inCtx.BaseViewID == ctx.BaseViewID) {
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
ExecuteOrDelayUntilScriptLoaded(_registerGMapsControlTemplate, 'clienttemplates.js');
window.initialize = function () { }

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


  • 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.


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.


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;

view raw
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=''&gt; \
<soapenv:Body> \
<GetListItems xmlns=''&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> \
url: L_Menu_BaseUrl + "/_vti_bin/lists.asmx",
type: "POST",
dataType: "xml",
data: soapEnv,
complete: onOrgChartDataLoaded,
contentType: "text/xml; charset=\"utf-8\""

view raw
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;
$.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 });


Result page with Org Structure rendered as Chart in List View