Saturday, June 8, 2013

How to get URL of current site collection and other server side properties on client site in Sharepoint

What you will do if you need to get e.g. URL of current site collection in javascript? If you may inject server side code, then you may add the following code directly to the aspx, ascx or master page files:

   1: <script type="text/javascript">
   1:  
   2: var siteCollUrl = '<%= SPContext.Current.Site.Url %>';
</script>

When page will be rendered, you will get variable siteCollUrl initialized and will be able to use it in javascript code on your page. But what if you don’t have access to the server code? E.g. when you create custom display templates for search and the only tools which you can use is html, javascript and css.

One of the way is to use ClientContext object for javascript object model. With it the above code will be rewritten like this:

   1: var siteCollUrl = "";
   2: SP.SOD.executeFunc("SP.js", "SP.ClientContext", function()
   3: {
   4:     var clientContext = new SP.ClientContext.get_current();
   5:     var site = clientContext.get_site();
   6:     clientContext.load(site);
   7:     clientContext.executeQueryAsync(Function.createDelegate(this, function(){
   8:         siteCollUrl = site.get_url();
   9:     }))
  10: });

First of all we use it within SP.SOD.executeFunc() in order to ensure that script with SP.ClientContext is loaded at the moment of executing this code (line 2). In this example we get javascript representation of SPSite (line 5) and then initialize its properties by calling SP.ClientContext.load() method and then SP.ClientContext.executeQueryAsync() which performs call to the Sharepoint endpoint asynchronously (lines 6, 7).

This way works, but looks too complicated. Is there any simpler way to get basic server side properties? Yes, there is: it is called _spPageContextInfo (thanks to Andrey Markeev, who mentioned it). If you will check html output of your site, you will find that it is declared like this (with assumption that you are located on site collection http://example.com/test):

   1: var _spPageContextInfo = {
   2:     webServerRelativeUrl: "\u002ftest",
   3:     webAbsoluteUrl: "http:\u002f\u002fexample\u002ftest",
   4:     siteAbsoluteUrl: "http:\u002f\u002fexample.com\u002ftest",
   5:     serverRequestPath: "\u002f_layouts\u002f15\u002fosssearchresults.aspx",
   6:     layoutsUrl: "_layouts\u002f15",
   7:     webTitle: "Home",
   8:     webTemplate: "53",
   9:     tenantAppVersion: "0",
  10:     webLogoUrl: "_layouts\u002f15\u002fimages\u002fsiteicon.png",
  11:     webLanguage: 1033,
  12:     currentLanguage: 1033,
  13:     currentUICultureName: "en-US",
  14:     currentCultureName: "en-US",
  15:     clientServerTimeDelta: new Date("2013-06-07T19:07:01.3494510Z") - new Date(),
  16:     siteClientTag: "40$$15.0.4420.1017",
  17:     crossDomainPhotosEnabled: false,
  18:     webUIVersion: 15,
  19:     webPermMasks: {
  20:         High: 2147483647,
  21:         Low: 4294967295
  22:     },
  23:     pagePersonalizationScope: 1,
  24:     userId: 1,
  25:     systemUserKey: "i:0\u0029.w|s-1-5-21-...",
  26:     alertsEnabled: false,
  27:     siteServerRelativeUrl: "\u002ftest",
  28:     allowSilverlightPrompt: 'True'
  29: };

As you can see it contains many useful properties, like urls, culture info and even permission mask. Having this object our server code will be rewritten like this:

   1: <script type="text/javascript">
   2: var siteCollUrl = _spPageContextInfo.siteAbsoluteUrl;
   3: </script>

This is much better comparing with the example with ClientContext. The question however is how this variable is added to the page, where it is defined and where not. If we will check Sharepoint assemblies in decompiler, we will find that it is registered in private method SharePointClientJs_Register() of Microsoft.SharePoint.WebControls.ScriptLink class (will put whole code here, so you will know how properties are defined on server side):

   1: private static void SharePointClientJs_Register(Page page)
   2: {
   3:     if (((SPContext.Current != null) && !SPPageContentManager.IsStartupScriptRegistered(page,
   4: typeof(ScriptLink), "pagecontextinfo")) && !SPRibbon.ToolbarRibbonAdapterDataRenderingOnly)
   5:     {
   6:         SPWeb web = SPContext.Current.Web;
   7:         SPSite site = SPContext.Current.Site;
   8:         string scriptLiteralToEncode = string.IsNullOrEmpty(web.SiteLogoUrl) ?
   9: (SPUtility.GetLayoutsFolder(web) + "/images/siteicon.png") : web.SiteLogoUrl;
  10:         StringBuilder sb = new StringBuilder();
  11:         if (web != null)
  12:         {
  13:             PersonalizationScope shared;
  14:             sb.Append("var _fV4UI=");
  15:             sb.Append((web.UIVersion > 3) ? "true;" : "false;");
  16:             sb.Append("var _spPageContextInfo = {webServerRelativeUrl: \"");
  17:             sb.Append(SPHttpUtility.EcmaScriptStringLiteralEncode(web.ServerRelativeUrl));
  18:             sb.Append("\", webAbsoluteUrl: \"");
  19:             sb.Append(SPHttpUtility.EcmaScriptStringLiteralEncode(web.Url));
  20:             sb.Append("\", siteAbsoluteUrl: \"");
  21:             sb.Append(SPHttpUtility.EcmaScriptStringLiteralEncode(web.Site.Url));
  22:             sb.Append("\", serverRequestPath: \"");
  23:             sb.Append(SPHttpUtility.EcmaScriptStringLiteralEncode(HttpContext.Current.Request.Path));
  24:             sb.Append("\", layoutsUrl: \"");
  25:             sb.Append(SPHttpUtility.EcmaScriptStringLiteralEncode(SPUtility.ContextLayoutsFolder));
  26:             sb.Append("\", webTitle: \"");
  27:             if (web.DoesUserHavePermissions(SPBasePermissions.EmptyMask | SPBasePermissions.Open))
  28:             {
  29:                 sb.Append(SPHttpUtility.EcmaScriptStringLiteralEncode(web.Title));
  30:             }
  31:             else
  32:             {
  33:                 sb.Append(SPHttpUtility.EcmaScriptStringLiteralEncode(""));
  34:             }
  35:             sb.Append("\", webTemplate: \"");
  36:             if (web.DoesUserHavePermissions(SPBasePermissions.EmptyMask | SPBasePermissions.Open))
  37:             {
  38:                 sb.Append(SPHttpUtility.EcmaScriptStringLiteralEncode(
  39: web.WebTemplateId.ToString(CultureInfo.InvariantCulture)));
  40:             }
  41:             else
  42:             {
  43:                 sb.Append(SPHttpUtility.EcmaScriptStringLiteralEncode(""));
  44:             }
  45:             sb.Append("\", tenantAppVersion: \"");
  46:             sb.Append(SPHttpUtility.EcmaScriptStringLiteralEncode(
  47: SPTenantAppUtils.GetTenantAppEtag(web)));
  48:             sb.Append("\", webLogoUrl: \"");
  49:             sb.Append(SPHttpUtility.EcmaScriptStringLiteralEncode(scriptLiteralToEncode));
  50:             sb.Append("\", webLanguage: ");
  51:             sb.Append(web.Language);
  52:             sb.Append(", currentLanguage: ");
  53:             sb.Append(Thread.CurrentThread.CurrentUICulture.LCID);
  54:             sb.Append(", currentUICultureName: \"");
  55:             sb.Append(Thread.CurrentThread.CurrentUICulture.Name);
  56:             sb.Append("\", currentCultureName: \"");
  57:             sb.Append(Thread.CurrentThread.CurrentCulture.Name);
  58:             sb.Append("\", clientServerTimeDelta: new Date(\"");
  59:             sb.Append(DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture));
  60:             sb.Append("\") - new Date()");
  61:             sb.Append(", siteClientTag: \"");
  62:             sb.Append(web.SiteClientTag.ToString(CultureInfo.InvariantCulture));
  63:             sb.Append("\", crossDomainPhotosEnabled:");
  64:             sb.Append(site.WebApplication.CrossDomainPhotosEnabled ? "true" : "false");
  65:             sb.Append(", webUIVersion:");
  66:             sb.Append(web.UIVersion.ToString(CultureInfo.InvariantCulture));
  67:             sb.Append(", webPermMasks:");
  68:             sb.Append(SPUtility.BasePermissionsToJson(web.EffectiveBasePermissions));
  69:             if (SPContext.Current.ListId != Guid.Empty)
  70:             {
  71:                 sb.AppendFormat(",pageListId:\"{0}\"", SPContext.Current.ListId.ToString("B"));
  72:             }
  73:             if (SPContext.IsPageDoclibItem)
  74:             {
  75:                 sb.AppendFormat(",pageItemId:{0}", SPContext.Current.PageItemId);
  76:             }
  77:             if (web.WebPartManager != null)
  78:             {
  79:                 shared = web.WebPartManager.Scope;
  80:             }
  81:             else
  82:             {
  83:                 shared = PersonalizationScope.Shared;
  84:             }
  85:             sb.AppendFormat(", pagePersonalizationScope:{0}", (int) shared);
  86:             if (web.CurrentUser != null)
  87:             {
  88:                 sb.Append(",userId:");
  89:                 sb.Append(web.CurrentUser.ID.ToString(CultureInfo.InvariantCulture));
  90:                 sb.AppendFormat(", systemUserKey:\"{0}\"",
  91: SPHttpUtility.EcmaScriptStringLiteralEncode(web.CurrentUser.SystemUserKey));
  92:             }
  93:             if (site != null)
  94:             {
  95:                 SPWebApplication webApplication = site.WebApplication;
  96:                 sb.Append(", alertsEnabled:");
  97:                 if (((webApplication != null) && webApplication.AlertsEnabled) &&
  98: webApplication.IsEmailServerSet)
  99:                 {
 100:                     sb.Append("true");
 101:                 }
 102:                 else
 103:                 {
 104:                     sb.Append("false");
 105:                 }
 106:                 sb.Append(", siteServerRelativeUrl: \"");
 107:                 sb.Append(SPHttpUtility.EcmaScriptStringLiteralEncode(site.ServerRelativeUrl));
 108:                 sb.AppendFormat("\", allowSilverlightPrompt:'{0}'", site.WebApplication.AllowSilverlightPrompt);
 109:             }
 110:             string themedCssFolderUrl = SPUtility.GetThemedCssFolderUrl();
 111:             if (!string.IsNullOrEmpty(themedCssFolderUrl))
 112:             {
 113:                 sb.Append(",\"themedCssFolderUrl\" : \"" + themedCssFolderUrl + "\"");
 114:                 StringCollection themedImages = new StringCollection();
 115:                 StringCollection strings2 = new StringCollection();
 116:                 HashSet<string> addedThemedImages = new HashSet<string>();
 117:                 HashSet<string> set2 = new HashSet<string>();
 118:                 AddImageToCollection(themedImages, "spcommon.png", addedThemedImages);
 119:                 AddImageToCollection(themedImages, "ellipsis.11x11x32.png", addedThemedImages);
 120:                 AddImageToCollection(themedImages, "O365BrandSuite.95x30x32.png", addedThemedImages);
 121:                 AddImageToCollection(themedImages, "socialcommon.png", addedThemedImages);
 122:                 AddImageToCollection(themedImages, "spnav.png", addedThemedImages);
 123:                 SPWebPartManager webPartManager = web.WebPartManager;
 124:                 WebPartCollection webParts = null;
 125:                 if (webPartManager != null)
 126:                 {
 127:                     webParts = webPartManager.WebParts;
 128:                 }
 129:                 if (webParts != null)
 130:                 {
 131:                     for (int i = 0; i < webParts.Count; i++)
 132:                     {
 133:                         WebPart part = webParts[i] as WebPart;
 134:                         if (part != null)
 135:                         {
 136:                             AddWebpartThemedImagesToCollection(themedImages,
 137: part.GetThemedImages(), addedThemedImages);
 138:                             AddWebpartThemedImagesToCollection(strings2,
 139: part.GetThemedLocalizedImages(), set2);
 140:                         }
 141:                     }
 142:                 }
 143:                 GenerateThemedImageJson(themedImages, strings2, sb);
 144:             }
 145:             sb.Append("};");
 146:         }
 147:         SPPageContentManager.RegisterClientScriptBlock(page, typeof(ScriptLink),
 148: "pagecontextinfo", sb.ToString());
 149:     }
 150: }

Analyzing of the usage of this method shows, that it is added on the page with adding of standard scripts like init,js or sp.core.js via ScriptLink control to the page:

   1: internal static void RegisterForControl(Control ctrl, Page page, string name,
   2: bool localizable, bool defer, bool loadAfterUI, string language, bool injectNoDefer,
   3: bool controlRegistration, [Optional, DefaultParameterValue(false)] bool loadInlineLast,
   4: [Optional, DefaultParameterValue(false)] bool ignoreFileNotFound)
   5: {
   6:     ...
   7:     RegisterStringsJsIfNecessary(name, page);
   8:     if ((string.Compare(name, "core.js", StringComparison.OrdinalIgnoreCase) == 0) || (string.Compare(name, "bform.js", StringComparison.OrdinalIgnoreCase) == 0))
   9:     {
  10:         ...
  11:     }
  12:     if (string.Compare(name, "SP.Core.js", StringComparison.OrdinalIgnoreCase) == 0)
  13:     {
  14:         ...
  15:         SharePointClientJs_Register(page);
  16:     }
  17:     else if (string.Compare(name, "init.js", StringComparison.OrdinalIgnoreCase) == 0)
  18:     {
  19:         InitJs_Register(page);
  20:     }
  21:     ...
  22: }

Also in InitJs_Register():

   1: private static void InitJs_Register(Page page)
   2: {
   3:     ...
   4:     SharePointClientJs_Register(page);
   5: }

These 2 scripts are basic Sharepoint client scripts, they are defined on most Sharepoint pages (e.g. they will be defined on publishing pages or on application layouts pages). I.e. it is quite safe to use _spPageContextInfo object there. The only problem which I found at the moment is that it is not be available in apps. Anyway it is quite useful OTB tool which may help in your work and reduce the amount of code.

2 comments:

  1. _spPageContextInfo is a very useful object, thank you! By the way, in SP 2010 it has fewer userful properties in comparison with SP 2013. For instaince, webAbsoluteUrl is missing.

    ReplyDelete
  2. Hi Denis,
    yes in Sharepoint 2010 it has less properties.

    ReplyDelete