Как получить адрес текущей сайт коллекции и другие серверные свойства на клиентской стороне в Sharepoint

Что вы будете делать, если вам понадобится получить адрес текущей сайт коллекции в 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). Тем не менее, это достаточно удобное стандартное средство, которое может помочь вам в повседневной работе и уменьшить количество кода.

Реклама

Об авторе sadomovalex

Старший инженер, team lead, консультант. Работаю в стеке .Net. Последние несколько лет занимаюсь разработкой enterprise приложений под Sharepoint, чему и будет в основном посвящена тематика этого блога. Также активно использую и интересуюсь ASP.Net MVC, DDD, TDD, Agile. Активно участвую в жизни многих профессиональных сообществ, SPb .Net UG, SPb ALT.Net, rsdn, Finland SP UG и др.
Запись опубликована в рубрике Client object model, Sharepoint. Добавьте в закладки постоянную ссылку.

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s