Что вы будете делать, если вам понадобится получить адрес текущей сайт коллекции в javascript? Как вы понимаете, location.href для этого использовать нельзя, т.к. имея только адрес текущей страницы, мы не сможем сказать, какая его часть относится к сайту, какая к сайт коллекции (для этого в Sharepoint определены managed paths). Если есть возможность внедрить серверный код, то эта задача может быть решена добавлением следующего кода напрямую в aspx, ascx или master файл:
<script type="text/javascript"> var siteCollUrl = '<%= SPContext.Current.Site.Url %>'; </script>
После рендеринга страницы вы получите инициализированную переменную siteCollUrl, в которой будет установлен адрес сайт коллекции. Далее вы сможете использовать ее в javascript коде на странице. Но что если у вас нет доступа к серверному коду? Например, если вы создаете кастомный шаблон отображения (display template) для поиска и единственные доступные инструменты, которые вы можете использовать, это html, css и javascript.
Один из способов – это использовать клиентскую объектную модель, в частности объект ClientContext. В этом случае показанный выше код будет переписан следующим образом:
var siteCollUrl = ""; SP.SOD.executeFunc("SP.js", "SP.ClientContext", function() { var clientContext = new SP.ClientContext.get_current(); var site = clientContext.get_site(); clientContext.load(site); clientContext.executeQueryAsync(Function.createDelegate(this, function(){ siteCollUrl = site.get_url(); })) });
Прежде всего весь код обернут в SP.SOD.executeFunc() для того, чтобы убедиться, что скрипт с SP.ClientContext загружен и готов к использованию на момент выполнения нашего кода (строка). В этом примере мы получаем объект javascript-аналог серверного класса SPSite, который представляет сайт коллекцию (строка), и затем инициализируем его свойства с помощью вызовов методов SP.ClientContext.load() и SP.ClientContext.executeQueryAsync(). Это в свою очередь приводит к асинхронному вызову Sharepoint end-point-а на серверной стороне (строки).
Этот метод работает, но выглядит сложно. Есть ли более простой способ получить серверные свойства на клиентской стороне? Такие способы есть, и один из них объект _spPageContextInfo. Если вы проверите html код вашей страницы на Sharepoint сайте, вы обнаружите эту переменную, инициализированную следующим образом (предполагаем, что вы находитесь на сайт коллекции с адресом http://example.com/test):
var _spPageContextInfo = { webServerRelativeUrl: "\u002ftest", webAbsoluteUrl: "http:\u002f\u002fexample\u002ftest", siteAbsoluteUrl: "http:\u002f\u002fexample.com\u002ftest", serverRequestPath: "\u002f_layouts\u002f15\u002fosssearchresults.aspx", layoutsUrl: "_layouts\u002f15", webTitle: "Home", webTemplate: "53", tenantAppVersion: "0", webLogoUrl: "_layouts\u002f15\u002fimages\u002fsiteicon.png", webLanguage: 1033, currentLanguage: 1033, currentUICultureName: "en-US", currentCultureName: "en-US", clientServerTimeDelta: new Date("2013-06-07T19:07:01.3494510Z") - new Date(), siteClientTag: "40$$15.0.4420.1017", crossDomainPhotosEnabled: false, webUIVersion: 15, webPermMasks: { High: 2147483647, Low: 4294967295 }, pagePersonalizationScope: 1, userId: 1, systemUserKey: "i:0\u0029.w|s-1-5-21-...", alertsEnabled: false, siteServerRelativeUrl: "\u002ftest", allowSilverlightPrompt: 'True' };
Как видно из примера, _spPageContextInfo содержит много полезных свойств таких, как адреса сайта и сайт коллекции, информация о культуре и даже маски разрешений (permission mask). С объектом _spPageContextInfo наш код будет переписан следующим образом:
<script type="text/javascript"> var siteCollUrl = _spPageContextInfo.siteAbsoluteUrl; </script>
Последний пример выглядит гораздо более привлекательнее, чем с ClientContext. Однако остается вопрос, как именно _spPageContextInfo добавляется на страницу, и где он доступен, а где – нет. Для ответа на этот вопрос необходимо изучить код сборок Sharepoint-а в рефлекторе. Объект _spPageContextInfo регистрируется в приватном методе SharePointClientJs_Register() класса ScriptLink (я решил показать код всего метода здесь, чтобы было понятно, как именно устанавливаются javascript свойства с помощью серверного кода):
private static void SharePointClientJs_Register(Page page) { if (((SPContext.Current != null) && !SPPageContentManager.IsStartupScriptRegistered(page, typeof(ScriptLink), "pagecontextinfo")) && !SPRibbon.ToolbarRibbonAdapterDataRenderingOnly) { SPWeb web = SPContext.Current.Web; SPSite site = SPContext.Current.Site; string scriptLiteralToEncode = string.IsNullOrEmpty(web.SiteLogoUrl) ? (SPUtility.GetLayoutsFolder(web) + "/images/siteicon.png") : web.SiteLogoUrl; StringBuilder sb = new StringBuilder(); if (web != null) { PersonalizationScope shared; sb.Append("var _fV4UI="); sb.Append((web.UIVersion > 3) ? "true;" : "false;"); sb.Append("var _spPageContextInfo = {webServerRelativeUrl: \""); sb.Append(SPHttpUtility.EcmaScriptStringLiteralEncode(web.ServerRelativeUrl)); sb.Append("\", webAbsoluteUrl: \""); sb.Append(SPHttpUtility.EcmaScriptStringLiteralEncode(web.Url)); sb.Append("\", siteAbsoluteUrl: \""); sb.Append(SPHttpUtility.EcmaScriptStringLiteralEncode(web.Site.Url)); sb.Append("\", serverRequestPath: \""); sb.Append(SPHttpUtility.EcmaScriptStringLiteralEncode(HttpContext.Current.Request.Path)); sb.Append("\", layoutsUrl: \""); sb.Append(SPHttpUtility.EcmaScriptStringLiteralEncode(SPUtility.ContextLayoutsFolder)); sb.Append("\", webTitle: \""); if (web.DoesUserHavePermissions(SPBasePermissions.EmptyMask | SPBasePermissions.Open)) { sb.Append(SPHttpUtility.EcmaScriptStringLiteralEncode(web.Title)); } else { sb.Append(SPHttpUtility.EcmaScriptStringLiteralEncode("")); } sb.Append("\", webTemplate: \""); if (web.DoesUserHavePermissions(SPBasePermissions.EmptyMask | SPBasePermissions.Open)) { sb.Append(SPHttpUtility.EcmaScriptStringLiteralEncode( web.WebTemplateId.ToString(CultureInfo.InvariantCulture))); } else { sb.Append(SPHttpUtility.EcmaScriptStringLiteralEncode("")); } sb.Append("\", tenantAppVersion: \""); sb.Append(SPHttpUtility.EcmaScriptStringLiteralEncode( SPTenantAppUtils.GetTenantAppEtag(web))); sb.Append("\", webLogoUrl: \""); sb.Append(SPHttpUtility.EcmaScriptStringLiteralEncode(scriptLiteralToEncode)); sb.Append("\", webLanguage: "); sb.Append(web.Language); sb.Append(", currentLanguage: "); sb.Append(Thread.CurrentThread.CurrentUICulture.LCID); sb.Append(", currentUICultureName: \""); sb.Append(Thread.CurrentThread.CurrentUICulture.Name); sb.Append("\", currentCultureName: \""); sb.Append(Thread.CurrentThread.CurrentCulture.Name); sb.Append("\", clientServerTimeDelta: new Date(\""); sb.Append(DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture)); sb.Append("\") - new Date()"); sb.Append(", siteClientTag: \""); sb.Append(web.SiteClientTag.ToString(CultureInfo.InvariantCulture)); sb.Append("\", crossDomainPhotosEnabled:"); sb.Append(site.WebApplication.CrossDomainPhotosEnabled ? "true" : "false"); sb.Append(", webUIVersion:"); sb.Append(web.UIVersion.ToString(CultureInfo.InvariantCulture)); sb.Append(", webPermMasks:"); sb.Append(SPUtility.BasePermissionsToJson(web.EffectiveBasePermissions)); if (SPContext.Current.ListId != Guid.Empty) { sb.AppendFormat(",pageListId:\"{0}\"", SPContext.Current.ListId.ToString("B")); } if (SPContext.IsPageDoclibItem) { sb.AppendFormat(",pageItemId:{0}", SPContext.Current.PageItemId); } if (web.WebPartManager != null) { shared = web.WebPartManager.Scope; } else { shared = PersonalizationScope.Shared; } sb.AppendFormat(", pagePersonalizationScope:{0}", (int) shared); if (web.CurrentUser != null) { sb.Append(",userId:"); sb.Append(web.CurrentUser.ID.ToString(CultureInfo.InvariantCulture)); sb.AppendFormat(", systemUserKey:\"{0}\"", SPHttpUtility.EcmaScriptStringLiteralEncode(web.CurrentUser.SystemUserKey)); } if (site != null) { SPWebApplication webApplication = site.WebApplication; sb.Append(", alertsEnabled:"); if (((webApplication != null) && webApplication.AlertsEnabled) && webApplication.IsEmailServerSet) { sb.Append("true"); } else { sb.Append("false"); } sb.Append(", siteServerRelativeUrl: \""); sb.Append(SPHttpUtility.EcmaScriptStringLiteralEncode(site.ServerRelativeUrl)); sb.AppendFormat("\", allowSilverlightPrompt:'{0}'", site.WebApplication.AllowSilverlightPrompt); } string themedCssFolderUrl = SPUtility.GetThemedCssFolderUrl(); if (!string.IsNullOrEmpty(themedCssFolderUrl)) { sb.Append(",\"themedCssFolderUrl\" : \"" + themedCssFolderUrl + "\""); StringCollection themedImages = new StringCollection(); StringCollection strings2 = new StringCollection(); HashSet<string> addedThemedImages = new HashSet<string>(); HashSet<string> set2 = new HashSet<string>(); AddImageToCollection(themedImages, "spcommon.png", addedThemedImages); AddImageToCollection(themedImages, "ellipsis.11x11x32.png", addedThemedImages); AddImageToCollection(themedImages, "O365BrandSuite.95x30x32.png", addedThemedImages); AddImageToCollection(themedImages, "socialcommon.png", addedThemedImages); AddImageToCollection(themedImages, "spnav.png", addedThemedImages); SPWebPartManager webPartManager = web.WebPartManager; WebPartCollection webParts = null; if (webPartManager != null) { webParts = webPartManager.WebParts; } if (webParts != null) { for (int i = 0; i < webParts.Count; i++) { WebPart part = webParts[i] as WebPart; if (part != null) { AddWebpartThemedImagesToCollection(themedImages, part.GetThemedImages(), addedThemedImages); AddWebpartThemedImagesToCollection(strings2, part.GetThemedLocalizedImages(), set2); } } } GenerateThemedImageJson(themedImages, strings2, sb); } sb.Append("};"); } SPPageContentManager.RegisterClientScriptBlock(page, typeof(ScriptLink), "pagecontextinfo", sb.ToString()); } }
Анализ использования данного метода показывает, что он вызывается при регистрации на стандартных скриптов init.js и sp.core.js, которые добавляются на страницу с помощью контрола ScriptLink:
internal static void RegisterForControl(Control ctrl, Page page, string name, bool localizable, bool defer, bool loadAfterUI, string language, bool injectNoDefer, bool controlRegistration, [Optional, DefaultParameterValue(false)] bool loadInlineLast, [Optional, DefaultParameterValue(false)] bool ignoreFileNotFound) { ... RegisterStringsJsIfNecessary(name, page); if ((string.Compare(name, "core.js", StringComparison.OrdinalIgnoreCase) == 0) || (string.Compare(name, "bform.js", StringComparison.OrdinalIgnoreCase) == 0)) { ... } if (string.Compare(name, "SP.Core.js", StringComparison.OrdinalIgnoreCase) == 0) { ... SharePointClientJs_Register(page); } else if (string.Compare(name, "init.js", StringComparison.OrdinalIgnoreCase) == 0) { InitJs_Register(page); } ... }
Также в методе InitJs_Register():
private static void InitJs_Register(Page page) { ... SharePointClientJs_Register(page); }
Эти 2 скрипта являются одними из основных клиентских скриптов Sharepoint-а и добавляются практически на все страницы на Sharepoint сайтах (например, по умолчанию присутствуют на publishing и на application layout страницах). Т.е. будет достаточно безопасно использовать объект _spPageContextInfo в вашем коде, определенном для этих страниц. Одна проблема, которую я нашел на текущий момент, связана с тем, что он не определен в Sharepoint приложениях (apps). Тем не менее, это достаточно удобное стандартное средство, которое может помочь вам в повседневной работе и уменьшить количество кода.