Поделиться через


Настройка пользовательского интерфейса с помощью надстроек SharePoint, размещенных у поставщика

В этой статье описываются примеры, демонстрирующие рекомендации по настройке компонентов пользовательского интерфейса SharePoint, включая следующие сценарии:

  • Управление страницей (добавление и изменение вики-страницы)

  • Отображение надстроек и данных в модальных диалоговых окнах

  • Создание персонализированных элементов пользовательского интерфейса

  • Отрисовка на стороне клиента (развертывание файлов JSLink, которые настраивают отрисовку полей в списках SharePoint)

  • Управление веб-частью и частью надстройки (удаленная подготовка и запуск части скрипта надстройки в надстройке, размещенной у поставщика)

  • Агрегирование и кэширование данных (использование локального хранилища HTML5 и файлов cookie HTTP для уменьшения числа вызовов служб в SharePoint)

Примечание.

Код, приведенный в этой статье, предоставляется "как есть" без какой-либо явной или подразумеваемой гарантии, включая подразумеваемые гарантии пригодности для какой-либо цели, для продажи или гарантии отсутствия нарушения прав иных правообладателей.

Управление страницей

Пример Core.ModifyPages включает два сценария управления страницей:

  • Создайте вики-страницу.
  • Изменение макета вики-страницы.

В этом примере используется библиотека страниц сайта по умолчанию и существующие стандартные макеты. Вы также можете обновить его, чтобы использовать настраиваемую библиотеку вики-страниц и пользовательские макеты. Пользовательский интерфейс надстройки включает две кнопки, которые создают обе вики-страницы, и две ссылки для просмотра создаваемых вами вики-страниц.

Пример работы с начальной страницей

Страница запуска для примера операций со страницей


Пример кода для первого сценария определяет, создали ли вы уже вики-страницу. В противном случае он добавляет файл в библиотеку страниц сайта и возвращает его URL-адрес.

var newpage = pageLibrary.RootFolder.Files.AddTemplateFile(newWikiPageUrl, TemplateFileType.WikiPage);
ctx.Load(newpage);
ctx.ExecuteQuery();
wikiPageUrl = String.Format("sitepages/{0}", wikiPageName);

В обоих сценариях пример добавляет HTML-код, введенный через текстовое поле на начальной странице, с помощью метода AddHtmlToWikiPage во вспомогательном классе с именем LabHelper. Этот метод вставляет HTML-код из формы в поле WikiField вики-страницы.

public void AddHtmlToWikiPage(ClientContext ctx, Web web, string folder, string html, string page)
        {
            Microsoft.SharePoint.Client.Folder pagesLib = web.GetFolderByServerRelativeUrl(folder);
            ctx.Load(pagesLib.Files);
            ctx.ExecuteQuery();

            Microsoft.SharePoint.Client.File wikiPage = null;

            foreach (Microsoft.SharePoint.Client.File aspxFile in pagesLib.Files)
            {
                if (aspxFile.Name.Equals(page, StringComparison.InvariantCultureIgnoreCase))
                {
                    wikiPage = aspxFile;
                    break;
                }
            }

            if (wikiPage == null)
            {
                return;
            }

            ctx.Load(wikiPage);
            ctx.Load(wikiPage.ListItemAllFields);
            ctx.ExecuteQuery();

            string wikiField = (string)wikiPage.ListItemAllFields["WikiField"];

            Microsoft.SharePoint.Client.ListItem listItem = wikiPage.ListItemAllFields;
            listItem["WikiField"] = html;
            listItem.Update();
            ctx.ExecuteQuery();
        }

Пример кода для второго сценария создает новый экземпляр WebPartEntity . Затем он использует методы во вспомогательном классе для заполнения веб-части XML. В XML-коде отображается список ссылок с повышенными уровнями, включая http://www.bing.com и домашнюю страницу репозитория GitHub для разработчиков и шаблонов Office 365 разработчика.

WebPartEntity wp2 = new WebPartEntity();
wp2.WebPartXml = new LabHelper().WpPromotedLinks(linksID, string.Format("{0}/Lists/{1}",
                                                                Request.QueryString["SPHostUrl"], "Links"),
                                                                string.Format("{0}/{1}", Request.QueryString["SPHostUrl"],
                                                                scenario2PageUrl), "$Resources:core,linksList");
wp2.WebPartIndex = 1;
wp2.WebPartTitle = "Links";

new LabHelper().AddWebPartToWikiPage(ctx, ctx.Web, "SitePages", wp2, scenario2Page, 2, 1, false);
new LabHelper().AddHtmlToWikiPage(ctx, ctx.Web, "SitePages", htmlEntry.Text, scenario2Page, 2, 2);

this.hplPage2.NavigateUrl = string.Format("{0}/{1}", Request.QueryString["SPHostUrl"], scenario2PageUrl);

В вспомогательном коде отображаются повышенные ссылки с таблицей в веб-части XsltListViewWebPart .

Вторая вики-страница с XsltListViewWebPart и повышенной таблицей ссылок

Вторая вики-страница с веб-частью XsltListViewWeb и таблицей рекомендуемых ссылок


Объект WpPromotedLinks в экземпляре LabHelper содержит XML-код, определяющий внешний вид веб-части, которая будет внедрена в вики-страницу. Затем метод AddWebPartToWikiPage вставляет только что определенную веб-часть в новый div тег на вики-странице.

XmlDocument xd = new XmlDocument();
            xd.PreserveWhitespace = true;
            xd.LoadXml(wikiField);

            // Sometimes the wikifield content seems to be surrounded by an additional div.
            XmlElement layoutsTable = xd.SelectSingleNode("div/div/table") as XmlElement;
            if (layoutsTable == null)
            {
                layoutsTable = xd.SelectSingleNode("div/table") as XmlElement;
            }

            XmlElement layoutsZoneInner = layoutsTable.SelectSingleNode(string.Format("tbody/tr[{0}]/td[{1}]/div/div", row, col)) as XmlElement;
            // - space element
            XmlElement space = xd.CreateElement("p");
            XmlText text = xd.CreateTextNode(" ");
            space.AppendChild(text);

            // - wpBoxDiv
            XmlElement wpBoxDiv = xd.CreateElement("div");
            layoutsZoneInner.AppendChild(wpBoxDiv);

            if (addSpace)
            {
                layoutsZoneInner.AppendChild(space);
            }

            XmlAttribute attribute = xd.CreateAttribute("class");
            wpBoxDiv.Attributes.Append(attribute);
            attribute.Value = "ms-rtestate-read ms-rte-wpbox";
            attribute = xd.CreateAttribute("contentEditable");
            wpBoxDiv.Attributes.Append(attribute);
            attribute.Value = "false";
            // - div1
            XmlElement div1 = xd.CreateElement("div");
            wpBoxDiv.AppendChild(div1);
            div1.IsEmpty = false;
            attribute = xd.CreateAttribute("class");
            div1.Attributes.Append(attribute);
            attribute.Value = "ms-rtestate-read " + wpdNew.Id.ToString("D");
            attribute = xd.CreateAttribute("id");
            div1.Attributes.Append(attribute);
            attribute.Value = "div_" + wpdNew.Id.ToString("D");
            // - div2
            XmlElement div2 = xd.CreateElement("div");
            wpBoxDiv.AppendChild(div2);
            div2.IsEmpty = false;
            attribute = xd.CreateAttribute("style");
            div2.Attributes.Append(attribute);
            attribute.Value = "display:none";
            attribute = xd.CreateAttribute("id");
            div2.Attributes.Append(attribute);
            attribute.Value = "vid_" + wpdNew.Id.ToString("D");

            ListItem listItem = webPartPage.ListItemAllFields;
            listItem["WikiField"] = xd.OuterXml;
            listItem.Update();
            ctx.ExecuteQuery();

Отображение надстроек и данных в модальных диалоговых окнах

В примере Core.Dialog показаны два метода внедрения модальных ссылок диалогового окна. Эти ссылки отображают страницу надстройки, размещенной у поставщика, на хост-сайте SharePoint. Надстройка использует клиентскую объектную модель (CSOM) для создания пользовательского действия и JavaScript для запуска и отображения сведений в диалоговом окне. Поскольку некоторые из этих сведений поступают с хост-сайта, он также использует объектную модель JavaScript (JSOM) для получения информации с хост-сайта. Так как надстройка работает в домене, отличном от домена узла SharePoint, она также использует междоменной библиотеке SharePoint для выполнения вызовов к хост-сайту.

Примечание.

Дополнительные сведения об использовании междоменной библиотеки в этом сценарии см. в статье Доступ к данным SharePoint из надстроек с помощью междоменной библиотеки.

Начальная страница — это страница, которая отображается в диалоговом окне. Чтобы обработать любые различия в отображении с учетом контекста отображения (диалогового окна и полностраничного), надстройка определяет, отображается ли она в диалоговом окне. Для этого используется параметр строки запроса, который передается вместе со ссылками, которые запускают диалоговые окна.

private string SetIsDlg(string isDlgValue)
        {
            var urlParams = HttpUtility.ParseQueryString(Request.QueryString.ToString());
            urlParams.Set("IsDlg", isDlgValue);
            return string.Format("{0}://{1}:{2}{3}?{4}", Request.Url.Scheme, Request.Url.Host, Request.Url.Port, Request.Url.AbsolutePath, urlParams.ToString());
        }

Например, можно выбрать отображение определенных элементов пользовательского интерфейса (например, кнопок) или даже различных макетов страниц в зависимости от того, отображается ли содержимое в диалоговом окне.

В пользовательском интерфейсе начальной страницы представлены два варианта создания ссылок на диалоговое окно, а также список всех списков на хост-сайте. Здесь также представлены кнопки "ОК" и "Отмена ", которые можно использовать в контексте диалогового окна для закрытия диалогового окна и (или) запроса дополнительных действий в надстройке.

При нажатии кнопки Добавить элемент меню надстройка создает объект CustomActionEntity , который запускает диалоговое окно. Затем он использует метод расширения OfficeDevPnP Core с именем AddCustomAction , чтобы добавить новое настраиваемое действие в меню Параметры сайта .

StringBuilder modelDialogScript = new StringBuilder(10);
modelDialogScript.Append("javascript:var dlg=SP.UI.ModalDialog.showModalDialog({url: '");
modelDialogScript.Append(String.Format("{0}", SetIsDlg("1")));
modelDialogScript.Append("', dialogReturnValueCallback:function(res, val) {} });");

// Create a custom action.
CustomActionEntity customAction = new CustomActionEntity()
{
  Title = "Office AMS Dialog sample",
  Description = "Shows how to start an add-in inside a dialog box.",
  Location = "Microsoft.SharePoint.StandardMenu",
  Group = "SiteActions",
  Sequence = 10000,
  Url = modelDialogScript.ToString(),
};

// Add the custom action to the site.
cc.Web.AddCustomAction(customAction);

Метод AddCustomAction добавляет пользовательское действие в коллекцию UserCustomActions , связанную с сайтом SharePoint.

var newAction = existingActions.Add();
            newAction.Description = customAction.Description;
            newAction.Location = customAction.Location;
            if (customAction.Location == JavaScriptExtensions.SCRIPT_LOCATION)
            {
                newAction.ScriptBlock = customAction.ScriptBlock;
            }
            else
            {
                newAction.Sequence = customAction.Sequence;
                newAction.Url = customAction.Url;
                newAction.Group = customAction.Group;
                newAction.Title = customAction.Title;
                newAction.ImageUrl = customAction.ImageUrl;
                if (customAction.Rights != null)
                {
                    newAction.Rights = customAction.Rights;
                }
            }
            newAction.Update();
            web.Context.Load(web, w => w.UserCustomActions);
            web.Context.ExecuteQuery();

При нажатии кнопки Добавить страницу с помощью веб-части редактора скриптов надстройка использует методы AddWikiPage и AddWebPartToWikiPage для создания вики-страницы в библиотеке страниц сайта и добавления на нее настроенной веб-части редактора скриптов.

string scenario1Page = String.Format("scenario1-{0}.aspx", DateTime.Now.Ticks);
string scenario1PageUrl = cc.Web.AddWikiPage("Site Pages", scenario1Page);
cc.Web.AddLayoutToWikiPage("SitePages", WikiPageLayout.OneColumn, scenario1Page);
WebPartEntity scriptEditorWp = new WebPartEntity();
scriptEditorWp.WebPartXml = ScriptEditorWebPart();
scriptEditorWp.WebPartIndex = 1;
scriptEditorWp.WebPartTitle = "Script editor test";
cc.Web.AddWebPartToWikiPage("SitePages", scriptEditorWp, scenario1Page, 1, 1, false);

Метод AddWikiPage загружает библиотеку страниц сайта. Если вики-страница, указанная надстройкой OfficeDevPnP Core , еще не существует, она создает страницу.

public static string AddWikiPage(this Web web, string wikiPageLibraryName, string wikiPageName)
        {
            string wikiPageUrl = "";


            var pageLibrary = web.Lists.GetByTitle(wikiPageLibraryName);

            web.Context.Load(pageLibrary.RootFolder, f => f.ServerRelativeUrl);
            web.Context.ExecuteQuery();

            var pageLibraryUrl = pageLibrary.RootFolder.ServerRelativeUrl;
            var newWikiPageUrl = pageLibraryUrl + "/" + wikiPageName;

            var currentPageFile = web.GetFileByServerRelativeUrl(newWikiPageUrl);

            web.Context.Load(currentPageFile, f => f.Exists);
            web.Context.ExecuteQuery();

            if (!currentPageFile.Exists)
            {
                var newpage = pageLibrary.RootFolder.Files.AddTemplateFile(newWikiPageUrl, TemplateFileType.WikiPage);

                web.Context.Load(newpage);
                web.Context.ExecuteQuery();

                wikiPageUrl = UrlUtility.Combine("sitepages", wikiPageName);
            }

            return wikiPageUrl;
        }

Метод AddWebPartToWikiPage вставляет только что определенную веб-часть в новый <div> тег на вики-странице. Затем он использует JSOM и междоменной библиотеки для получения сведений, заполняющих список списков SharePoint на хост-сайте.

function printAllListNamesFromHostWeb() {
        var context;
        var factory;
        var appContextSite;
        var collList;

        context = new SP.ClientContext(appWebUrl);
        factory = new SP.ProxyWebRequestExecutorFactory(appWebUrl);
        context.set_webRequestExecutorFactory(factory);
        appContextSite = new SP.AppContextSite(context, spHostUrl);

        this.web = appContextSite.get_web();
        collList = this.web.get_lists();
        context.load(collList);

        context.executeQueryAsync(
            Function.createDelegate(this, successHandler),
            Function.createDelegate(this, errorHandler)
        );

https://github.com/SharePoint/PnP/tree/dev/Samples/Core.AppScriptPart

Персонализированные элементы пользовательского интерфейса

В примере Branding.UIElementPersonalization показано, как использовать внедренные коды JavaScript и значения, хранящиеся в профилях пользователей и списках SharePoint, для персонализации элементов пользовательского интерфейса на хост-сайте. Он также использует локальное хранилище HTML5 для минимизации вызовов к хост-сайту.

На начальной странице примера предлагается добавить одну из трех строк (XX, YY или ZZ) в раздел Обо мне на странице профиля пользователя.

Надстройка уже развернула три образа и список SharePoint, содержащий заголовки и URL-адреса для каждого изображения, а также дополнительное поле, которое связывает каждое изображение с одной из трех строк. После нажатия кнопки Внедрить настройку надстройка внедряет файл personalize.js в коллекцию пользовательских действий.

public void AddPersonalizeJsLink(ClientContext ctx, Web web)
        {
            string scenarioUrl = String.Format("{0}://{1}:{2}/Scripts", this.Request.Url.Scheme,
                                                this.Request.Url.DnsSafeHost, this.Request.Url.Port);
            string revision = Guid.NewGuid().ToString().Replace("-", "");
            string personalizeJsLink = string.Format("{0}/{1}?rev={2}", scenarioUrl, "personalize.js", revision);

            StringBuilder scripts = new StringBuilder(@"
                var headID = document.getElementsByTagName('head')[0];
                var");

            scripts.AppendFormat(@"
                newScript = document.createElement('script');
                newScript.type = 'text/javascript';
                newScript.src = '{0}';
                headID.appendChild(newScript);", personalizeJsLink);
            string scriptBlock = scripts.ToString();

            var existingActions = web.UserCustomActions;
            ctx.Load(existingActions);
            ctx.ExecuteQuery();

            var actions = existingActions.ToArray();
            foreach (var action in actions)
            {
                if (action.Description == "personalize" &amp;&amp;
                    action.Location == "ScriptLink")
                {
                    action.DeleteObject();
                    ctx.ExecuteQuery();
                }
            }

            var newAction = existingActions.Add();
            newAction.Description = "personalize";
            newAction.Location = "ScriptLink";

            newAction.ScriptBlock = scriptBlock;
            newAction.Update();
            ctx.Load(web, s => s.UserCustomActions);
            ctx.ExecuteQuery();
        }

Так как сайты групп SharePoint по умолчанию используют стратегию минимального скачивания (MDS), код в файле personalize.js сначала пытается зарегистрировать себя в MDS. Таким образом, при загрузке страницы, содержащей JavaScript, подсистема MDS запускает функцию main (RemoteManager_Inject). Если MDS отключена, эта функция запускается сразу.

// Register script for MDS, if possible.
RegisterModuleInit("personalize.js", RemoteManager_Inject); //MDS registration
RemoteManager_Inject(); //non-MDS run

if (typeof (Sys) != "undefined" &amp;&amp; Boolean(Sys) &amp;&amp; Boolean(Sys.Application)) {
    Sys.Application.notifyScriptLoaded();h
}

if (typeof (NotifyScriptLoadedAndExecuteWaitingJobs) == "function") {
    NotifyScriptLoadedAndExecuteWaitingJobs("scenario1.js");
}
// The RemoteManager_Inject function is the entry point for loading the other scripts that perform the customizations. When a given script depends on another script, be sure to load the dependent script after the one on which it depends. This sample loads the JQuery library before the personalizeIt function that uses JQuery.
function RemoteManager_Inject() {

    var jQuery = "https://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.0.2.min.js";

    // Load jQuery.
    loadScript(jQuery, function () {

        personalizeIt();

    });
}

Функция personalizeIt() проверяет локальное хранилище HTML5 перед поиском сведений профиля пользователя. Если он переходит к сведениям профиля пользователя, он сохраняет полученные сведения в локальном хранилище HTML5.

function personalizeIt() {
    clientContext = SP.ClientContext.get_current();

    var fileref = document.createElement('script');
    fileref.setAttribute("type", "text/javascript");
    fileref.setAttribute("src", "/_layouts/15/SP.UserProfiles.js");
    document.getElementsByTagName("head")[0].appendChild(fileref);

    SP.SOD.executeOrDelayUntilScriptLoaded(function () {

        // Get localstorage values if they exist.
        buCode = localStorage.getItem("bucode");
        buCodeTimeStamp = localStorage.getItem("buCodeTimeStamp");

        // Determine whether the page already has embedded personalized image.
        var pageTitle = $('#pageTitle')[0].innerHTML;
        if (pageTitle.indexOf("img") > -1) {
            personalized = true;
        }
        else {
            personalized = false;
        }

        // If nothing is in localstorage, get profile data, which will also populate localstorage.
        if (buCode == "" || buCode == null) {
            getProfileData(clientContext);
            personalized = false;
        }
        else {
            // Check for expiration.
            if (isKeyExpired("buCodeTimeStamp")) {
                getProfileData(clientContext);

                if (buCode != "" || buCode != null) {
                    // Set timestamp for expiration.
                    currentTime = Math.floor((new Date().getTime()) / 1000);
                    localStorage.setItem("buCodeTimeStamp", currentTime);

                    // Set personalized to false so that the code can check for a new image in case buCode was updated.
                    personalized = false;
                }
            }
        }

        // Load image or make sure it is current based on the value in AboutMe.
        if (!personalized) {
            loadPersonalizedImage(buCode);
        }


    }, 'SP.UserProfiles.js');
}

Файл personalize.js также содержит код, который проверяет, истек ли срок действия ключа локального хранилища. Он не встроен в локальное хранилище HTML5.

// Check to see if the key has expired
function isKeyExpired(TimeStampKey) {

    // Retrieve the example setting for expiration in seconds.
    var expiryStamp = localStorage.getItem(TimeStampKey);

    if (expiryStamp != null &amp;&amp; cacheTimeout != null) {

        // Retrieve the timestamp and compare against specified cache timeout settings to see if it is expired.
        var currentTime = Math.floor((new Date().getTime()) / 1000);

        if (currentTime - parseInt(expiryStamp) > parseInt(cacheTimeout)) {
            return true; //Expired
        }
        else {
            return false;
        }
    }
    else {
        //default
        return true;
    }
}

Обработка на стороне клиента

В примере Branding.ClientSideRendering показано, как использовать надстройку, размещенную у поставщика, для удаленной подготовки артефактов SharePoint и файлов JSLink, использующих отрисовку на стороне клиента для настройки внешнего вида и поведения полей списка SharePoint. Файлы JSLink и отрисовка на стороне клиента позволяют управлять тем, как отображаются элементы управления на странице SharePoint (представления списка, поля списка, формы добавления и редактирования). Этот элемент управления может уменьшить или устранить необходимость в настраиваемых типах полей. Отрисовка на стороне клиента позволяет удаленно управлять внешним видом поля списка.

Пример объединяет примеры JSLink из примеров кода отрисовки на стороне клиента (JSLink) в одну надстройку, размещенную у поставщика, для SharePoint, которая подготавливает файлы JSLink. Отрисовка на стороне клиента позволяет использовать стандартные веб-технологии, такие как HTML и JavaScript, для определения логики отрисовки пользовательских и предопределенных типов полей.

При запуске примера на начальной странице будет предложено подготовить все примеры.

Начальная страница примера отрисовки на стороне клиента

Страница запуска примера отображения на стороне клиента

При выборе пункта Подготовка примеров надстройка развертывает образ, а также все списки SharePoint, представления списков, элементы списка, формы и файлы JavaScript, которые используются в каждом примере. Надстройка создает папку с именем JSLink-Samples в библиотеке стилей, а затем отправляет в нее файлы JavaScript. Метод UploadFileToFolder выполняет отправку и возврат каждого файла JavaScript.

public static void UploadFileToFolder(Web web, string filePath, Folder folder)
        {
            using (FileStream fs = new FileStream(filePath, FileMode.Open))
            {
                FileCreationInformation flciNewFile = new FileCreationInformation();

                flciNewFile.ContentStream = fs;
                flciNewFile.Url = System.IO.Path.GetFileName(filePath);
                flciNewFile.Overwrite = true;

                Microsoft.SharePoint.Client.File uploadFile = folder.Files.Add(flciNewFile);
                uploadFile.CheckIn("CSR sample js file", CheckinType.MajorCheckIn);

                folder.Context.Load(uploadFile);
                folder.Context.ExecuteQuery();
            }
        }

Пример 1. Применение форматирования к столбцу списка

В примере 1 показано, как применить форматирование к столбцу списка на основе значения поля. Значения полей приоритета 1 (высокий), 2 (обычный) и 3 (низкий) отображаются красным, оранжевым и желтым цветами.

Отображение поля пользовательского списка

Отображение поля пользовательского списка

Следующий Код JavaScript переопределяет отображение поля по умолчанию и создает шаблон отображения для поля списка приоритетов . Метод в анонимной функции, которая загружает контекстную информацию о поле, отображение которого требуется переопределить, используется во всех примерах.

(function () {

    // Create object that has the context information about the field that you want to render differently.
    var priorityFiledContext = {};
    priorityFiledContext.Templates = {};
    priorityFiledContext.Templates.Fields = {
        // Apply the new rendering for Priority field in List View.
        "Priority": { "View": priorityFiledTemplate }
    };

    SPClientTemplates.TemplateManager.RegisterTemplateOverrides(priorityFiledContext);

})();

// This function provides the rendering logic for list view.
function priorityFiledTemplate(ctx) {

    var priority = ctx.CurrentItem[ctx.CurrentFieldSchema.Name];

    // Return HTML element with appropriate color based on the Priority column's value.
    switch (priority) {
        case "(1) High":
            return "<span style='color :#f00'>" + priority + "</span>";
            break;
        case "(2) Normal":
            return "<span style='color :#ff6a00'>" + priority + "</span>";
            break;
        case "(3) Low":
            return "<span style='color :#cab023'>" + priority + "</span>";
    }
}

Пример 2. Сокращение длинного текста

В примере 2 показано, как усечь длинный текст, хранящийся в поле Текст списка объявлений . Полный текст отображается в виде всплывающего окна, которое появляется при наведении указателя мыши на элемент списка, как показано на следующем рисунке.

Отображение сокращенного поля списка с отображением всплывающего окна

Сокращенное представление поля списка со всплывающей подсказкой

Следующий код JavaScript сокращает текст поля Текст и приводит к отображению полного текста в виде всплывающего окна с помощью атрибута title в теге span .

function bodyFiledTemplate(ctx) {

    var bodyValue = ctx.CurrentItem[ctx.CurrentFieldSchema.Name];

    // This regex expression is used to delete HTML tags from the Body field.
    var regex = /(<([^>]+)>)/ig;

    bodyValue = bodyValue.replace(regex, "");

    var newBodyValue = bodyValue;

    if (bodyValue &amp;&amp; bodyValue.length >= 100)
    {
        newBodyValue = bodyValue.substring(0, 100) + " ...";
    }

    return "<span title='" + bodyValue + "'>" + newBodyValue + "</span>";

}

Пример 3. Отображение изображения с именем документа

В примере 3 показано, как отобразить изображение рядом с именем документа в библиотеке документов. Красный значок отображается каждый раз, когда для параметра Конфиденциальное поле задано значение Да.

Изображение рядом с именем документа

Изображение рядом с именем документа

Следующий Код JavaScript проверяет значение поля Конфиденциально, а затем настраивает отображение поля Имя на основе значения другого поля. В примере используется образ, отправляемый при выборе параметра Подготовить примеры.

function linkFilenameFiledTemplate(ctx) {

    var confidential = ctx.CurrentItem["Confidential"];
    var title = ctx.CurrentItem["FileLeafRef"];

    // This Regex expression is used to delete the extension (.docx, .pdf ...) from the file name.
    title = title.replace(/\.[^/.]+$/, "")

    // Check confidential field value.
    if (confidential &amp;&amp; confidential.toLowerCase() == 'yes') {
        // Render HTML that contains the file name and the confidential icon.
        return title + "&amp;nbsp;<img src= '" + _spPageContextInfo.siteServerRelativeUrl + "/Style%20Library/JSLink-Samples/imgs/Confidential.png' alt='Confidential Document' title='Confidential Document'/>";
    }
    else
    {
        return title;
    }
}

Пример 4. Отображение линейчатой диаграммы

В примере 4 показано, как отобразить линейчатую диаграмму в поле %Завершено списка задач. Внешний вид линейчатой диаграммы зависит от значения поля %Завершено , как показано на следующем рисунке. Обратите внимание, что линейчатая диаграмма также отображается в формах для создания и редактирования элементов списка задач.

Гистограмма в поле "% завершения"

Гистограмма в поле

Следующий код создает линейчатую диаграмму и связывает ее с формами представления и отображения (percentCompleteViewFiledTemplate), а затем с новыми формами и формами редактирования (percentCompleteEditFiledTemplate).

// This function provides the rendering logic for View and Display forms.
function percentCompleteViewFiledTemplate(ctx) {

    var percentComplete = ctx.CurrentItem[ctx.CurrentFieldSchema.Name];
    return "<div style='background-color: #e5e5e5; width: 100px;  display:inline-block;'> \
            <div style='width: " + percentComplete.replace(/\s+/g, '') + "; background-color: #0094ff;'> \
            &amp;nbsp;</div></div>&amp;nbsp;" + percentComplete;

}

// This function provides the rendering logic for New and Edit forms.
function percentCompleteEditFiledTemplate(ctx) {

    var formCtx = SPClientTemplates.Utility.GetFormContextForCurrentField(ctx);

    // Register a callback just before submit.
    formCtx.registerGetValueCallback(formCtx.fieldName, function () {
        return document.getElementById('inpPercentComplete').value;
    });

    return "<input type='range' id='inpPercentComplete' name='inpPercentComplete' min='0' max='100' \
            oninput='outPercentComplete.value=inpPercentComplete.value' value='" + formCtx.fieldValue + "' /> \
            <output name='outPercentComplete' for='inpPercentComplete' >" + formCtx.fieldValue + "</output>%";
}

Пример 5. Изменение шаблона отрисовки

В примере 5 показано, как изменить шаблон отрисовки для представления списка. В этом представлении отображаются заголовки элементов списка, которые разворачиваются как аккордеоны при их выборе. В развернутом представлении отображаются дополнительные поля элементов списка.

Свернутое и развернутое представления элемента списка

Свернутое и развернутое представления элемента списка

Следующий код настраивает шаблон и регистрирует его в шаблоне списка. Он настраивает общий макет, а затем использует обработчик событий OnPostRender для регистрации функции JavaScript, которая выполняется при отрисовке списка. Эта функция связывает событие с CSS и обработкой событий, реализующей функциональность аккордеона.

(function () {

    // jQuery library is required in this sample.
    // Fallback to loading jQuery from a CDN path if the local is unavailable.
    (window.jQuery || document.write('<script src="//ajax.aspnetcdn.com/ajax/jquery/jquery-1.10.0.min.js"><\/script>'));

    // Create object that has the context information about the field that you want to render differently.
    var accordionContext = {};
    accordionContext.Templates = {};

    // Be careful when adding the header for the template, because it will break the default list view render.
    accordionContext.Templates.Header = "<div class='accordion'>";
    accordionContext.Templates.Footer = "</div>";

    // Add OnPostRender event handler to add accordion click events and style.
    accordionContext.OnPostRender = accordionOnPostRender;

    // This line of code tells the TemplateManager that you want to change all the HTML for item row rendering.
    accordionContext.Templates.Item = accordionTemplate;

    SPClientTemplates.TemplateManager.RegisterTemplateOverrides(accordionContext);

})();

// This function provides the rendering logic.
function accordionTemplate(ctx) {
    var title = ctx.CurrentItem["Title"];
    var description = ctx.CurrentItem["Description"];

    // Return whole item html.
    return "<h2>" + title + "</h2><p>" + description + "</p><br/>";
}

function accordionOnPostRender() {

    // Register event to collapse and expand when selecting accordion header.
    $('.accordion h2').click(function () {
        $(this).next().slideToggle();
    }).next().hide();

    $('.accordion h2').css('cursor', 'pointer');
}

Пример 6. Проверка значений полей

В примере 6 показано, как использовать регулярные выражения для проверки значений полей, предоставленных пользователем. Когда пользователь вводит недопустимый адрес электронной почты в текстовое поле Email, отображается сообщение об ошибке красного цвета. Это происходит, когда пользователь создает или изменяет элемент списка.

Сообщение о недопустимом текстовом вводе

Сообщение о недопустимом текстовом вводе

Следующий код настраивает шаблон с заполнителем для отображения сообщения об ошибке и регистрирует функции обратного вызова, которые активируются каждый раз, когда пользователь пытается отправить формы. Первый обратный вызов возвращает значение столбца Email, а второй обратный вызов использует регулярные выражения для проверки строкового значения.

function emailFiledTemplate(ctx) {
  var formCtx = SPClientTemplates.Utility.GetFormContextForCurrentField(ctx);

  // Register a callback just before submit.
  formCtx.registerGetValueCallback(formCtx.fieldName, function () {
      return document.getElementById('inpEmail').value;
  });

  // Create container for various validations.
  var validators = new SPClientForms.ClientValidation.ValidatorSet();
  validators.RegisterValidator(new emailValidator());

  // Validation failure handler.
  formCtx.registerValidationErrorCallback(formCtx.fieldName, emailOnError);

  formCtx.registerClientValidator(formCtx.fieldName, validators);

  return "<span dir='none'><input type='text' value='" + formCtx.fieldValue + "'  maxlength='255' id='inpEmail' class='ms-long'> \
          <br><span id='spnError' class='ms-formvalidation ms-csrformvalidation'></span></span>";
}

// Custom validation object to validate email format.
emailValidator = function () {
  emailValidator.prototype.Validate = function (value) {
    var isError = false;
    var errorMessage = "";

    //Email format Regex expression
    var emailRejex = /\S+@\S+\.\S+/;

    if (!emailRejex.test(value) &amp;&amp; value.trim()) {
      isError = true;
      errorMessage = "Invalid email address";
    }

    // Send error message to error callback function (emailOnError).
    return new SPClientForms.ClientValidation.ValidationResult(isError, errorMessage);
  };
};

// Add error message to spnError element under the input field element.
function emailOnError(error) {
  document.getElementById("spnError").innerHTML = "<span role='alert'>" + error.errorMessage + "</span>";
}

Пример 7. Изменение полей элемента списка только для чтения

В примере семь показано, как сделать поля формы редактирования элементов списка только для чтения. Поля, доступные только для чтения, отображаются без элементов управления редактирования.

Поле только для чтения в настраиваемой форме редактирования списка

Поля, доступные только для чтения, в форме редактирования пользовательского списка

В следующем примере кода поля Title, AssignedTo и Priority в форме редактирования элемента списка изменяются таким образом, что значения полей отображаются только без элементов управления редактирования. В коде показано, как обрабатывать требования к синтаксическому анализу для разных типов полей.

function readonlyFieldTemplate(ctx) {
  // Reuse SharePoint JavaScript libraries.
  switch (ctx.CurrentFieldSchema.FieldType) {
    case "Text":
    case "Number":
    case "Integer":
    case "Currency":
    case "Choice":
    case "Computed":
      return SPField_FormDisplay_Default(ctx);

    case "MultiChoice":
      prepareMultiChoiceFieldValue(ctx);
      return SPField_FormDisplay_Default(ctx);

    case "Boolean":
      return SPField_FormDisplay_DefaultNoEncode(ctx);

    case "Note":
      prepareNoteFieldValue(ctx);
      return SPFieldNote_Display(ctx);

    case "File":
      return SPFieldFile_Display(ctx);

    case "Lookup":
    case "LookupMulti":
      return SPFieldLookup_Display(ctx);

    case "URL":
      return RenderFieldValueDefault(ctx);

    case "User":
      prepareUserFieldValue(ctx);
      return SPFieldUser_Display(ctx);

    case "UserMulti":
      prepareUserFieldValue(ctx);
      return SPFieldUserMulti_Display(ctx);

    case "DateTime":
      return SPFieldDateTime_Display(ctx);

    case "Attachments":
      return SPFieldAttachments_Default(ctx);

    case "TaxonomyFieldType":
      //Re-use JavaScript from the sp.ui.taxonomy.js SharePoint JavaScript library.
      return SP.UI.Taxonomy.TaxonomyFieldTemplate.renderDisplayControl(ctx);
  }
}

// User control needs specific formatted value to render content correctly.
function prepareUserFieldValue(ctx) {
  var item = ctx['CurrentItem'];
  var userField = item[ctx.CurrentFieldSchema.Name];
  var fieldValue = "";

  for (var i = 0; i < userField.length; i++) {
    fieldValue += userField[i].EntityData.SPUserID + SPClientTemplates.Utility.UserLookupDelimitString + userField[i].DisplayText;

    if ((i + 1) != userField.length) {
      fieldValue += SPClientTemplates.Utility.UserLookupDelimitString
    }
  }

  ctx["CurrentFieldValue"] = fieldValue;
}

// Choice control needs specific formatted value to render content correctly.
function prepareMultiChoiceFieldValue(ctx) {
  if (ctx["CurrentFieldValue"]) {
    var fieldValue = ctx["CurrentFieldValue"];

    var find = ';#';
    var regExpObj = new RegExp(find, 'g');

    fieldValue = fieldValue.replace(regExpObj, '; ');
    fieldValue = fieldValue.replace(/^; /g, '');
    fieldValue = fieldValue.replace(/; $/g, '');

    ctx["CurrentFieldValue"] = fieldValue;
  }
}

// Note control needs specific formatted value to render content correctly.
function prepareNoteFieldValue(ctx) {
  if (ctx["CurrentFieldValue"]) {
    var fieldValue = ctx["CurrentFieldValue"];
    fieldValue = "<div>" + fieldValue.replace(/\n/g, '<br />'); + "</div>";

    ctx["CurrentFieldValue"] = fieldValue;
  }
}

Пример 8. Скрытие полей

В примере 8 показано, как скрыть поля в новых и измененных формах элементов списка. Пример скрывает поле Предшественники , когда пользователь создает или изменяет элемент списка задач.

Этот пример развертывается в качестве формы редактирования и создания для списка с именем списка CSR-Hide-Controls. Сведения о том, как просмотреть форму после развертывания примера, см. в разделе Branding.ClientSideRendering.

Следующий код находит поле Предшественники в HTML-коде формы и скрывает его. Поле остается в HTML, но пользователь не может видеть его в браузере.

(function () {
  // jQuery library is required in this sample.
  // Fallback to loading jQuery from a CDN path if the local is unavailable.
  (window.jQuery || document.write('<script src="//ajax.aspnetcdn.com/ajax/jquery/jquery-1.10.0.min.js"><\/script>'));

  // Create object that has the context information about the field that we want to render differently.
  var hiddenFiledContext = {};
  hiddenFiledContext.Templates = {};
  hiddenFiledContext.Templates.OnPostRender = hiddenFiledOnPreRender;
  hiddenFiledContext.Templates.Fields = {
    // Apply the new rendering for Predecessors field in New and Edit forms.
    "Predecessors": {
      "NewForm": hiddenFiledTemplate,
      "EditForm": hiddenFiledTemplate
    }
  };

  SPClientTemplates.TemplateManager.RegisterTemplateOverrides(hiddenFiledContext);
})();


// This function provides the rendering logic.
function hiddenFiledTemplate() {
  return "<span class='csrHiddenField'></span>";
}

// This function provides the rendering logic.
function hiddenFiledOnPreRender(ctx) {
  jQuery(".csrHiddenField").closest("tr").hide();
}

Управление веб-частью и частью надстройки

В примере Core.AppScriptPart показано, как использовать части скриптов надстройки для внедрения скриптов, выполняемых в надстройке, размещенной у поставщика, на странице SharePoint. В этом примере показано, как изменить пользовательский интерфейс страницы на хост-сайте, развернув часть скрипта надстройки и добавив ее на страницу SharePoint из коллекции веб-частей.

Часть скрипта надстройки похожа на веб-часть, в которой ее можно добавить на страницу SharePoint из коллекции веб-частей, но в этом случае webpart-файл внедряет файл JavaScript, который выполняется удаленно, в надстройку, размещенную у поставщика. Часть скрипта надстройки выполняется внутри тега <div> на странице SharePoint и, следовательно, обеспечивает более гибкий дизайн и удобство работы, чем при использовании частей надстройки, которые выполняются в IFrames.

Начальная страница содержит кнопку Выполнить сценарий , которая развертывает часть скрипта надстройки в коллекции веб-частей. В следующем примере кода создается экземпляр FileCreationInformationObject , содержащий содержимое webpart-файла, а затем передает новый файл в коллекцию веб-частей. Обратите внимание, что этот код также можно запустить автоматически при установке части надстройки или в процессе подготовки семейства веб-сайтов.

var spContext = SharePointContextProvider.Current.GetSharePointContext(Context);
using (var clientContext = spContext.CreateUserClientContextForSPHost())
{
  var folder = clientContext.Web.Lists.GetByTitle("Web Part Gallery").RootFolder;
  clientContext.Load(folder);
  clientContext.ExecuteQuery();

  // Upload the OneDrive for Business Usage Guidelines.docx.
  using (var stream = System.IO.File.OpenRead(Server.MapPath("~/userprofileinformation.webpart")))
  {
    FileCreationInformation fileInfo = new FileCreationInformation();
    fileInfo.ContentStream = stream;
    fileInfo.Overwrite = true;
    fileInfo.Url = "userprofileinformation.webpart";
    File file = folder.Files.Add(fileInfo);
    clientContext.ExecuteQuery();
  }

  // Update the group for uploaded web part.
  var list = clientContext.Web.Lists.GetByTitle("Web Part Gallery");
  CamlQuery camlQuery = CamlQuery.CreateAllItemsQuery(100);
  Microsoft.SharePoint.Client.ListItemCollection items = list.GetItems(camlQuery);
  clientContext.Load(items);
  clientContext.ExecuteQuery();
  foreach (var item in items)
  {
    // Random group name to differentiate it from the rest.
    if (item["FileLeafRef"].ToString().ToLowerInvariant() == "userprofileinformation.webpart")
    {
      item["Group"] = "add-in Script Part";
      item.Update();
      clientContext.ExecuteQuery();
    }
  }

  lblStatus.Text = string.Format("add-in script part has been added to Web Part Gallery. You can find 'User Profile Information' script part under 'Add-in Script Part' group in the <a href='{0}'>host web</a>.", spContext.SPHostUrl.ToString());
}

После завершения этого шага можно найти часть скрипта сведений о профиле пользователя в новой категории Часть скрипта надстройки в коллекции веб-частей . После добавления части скрипта надстройки на страницу удаленно запущенный JavaScript управляет отображением сведений на странице.

При просмотре части скрипта надстройки в режиме редактирования вы увидите, что она внедряет файл JavaScript, который выполняется удаленно. Скрипт userprofileinformation.js использует JSON для получения сведений о профиле пользователя с хост-сайта.

function sharePointReady() {
  clientContext = SP.ClientContext.get_current();

  var fileref = document.createElement('script');
  fileref.setAttribute("type", "text/javascript");
  fileref.setAttribute("src", "/_layouts/15/SP.UserProfiles.js");
  document.getElementsByTagName("head")[0].appendChild(fileref);

  SP.SOD.executeOrDelayUntilScriptLoaded(function () {

    //Get Instance of People Manager Class.
    var peopleManager = new SP.UserProfiles.PeopleManager(clientContext);

    //Get properties of the current user.
    userProfileProperties = peopleManager.getMyProperties();
    clientContext.load(userProfileProperties);
    clientContext.executeQueryAsync(Function.createDelegate(this, function (sender, args) {
      var firstname = userProfileProperties.get_userProfileProperties()['FirstName'];
      var name = userProfileProperties.get_userProfileProperties()['PreferredName'];
      var title = userProfileProperties.get_userProfileProperties()['Title'];
      var aboutMe = userProfileProperties.get_userProfileProperties()['AboutMe'];
      var picture = userProfileProperties.get_userProfileProperties()['PictureURL'];

      var html = "<div><h2>Welcome " + firstname + "</h2></div><div><div style='float: left; margin-left:10px'><img style='float:left;margin-right:10px' src='" + picture + "' /><b>Name</b>: " + name + "<br /><b>Title</b>: " + title + "<br />" + aboutMe + "</div></div>";

      document.getElementById('UserProfileAboutMe').innerHTML = html;
    }), Function.createDelegate(this, function (sender, args) {
      console.log('The following error has occurred while loading user profile property: ' + args.get_message());
    }));
  }, 'SP.UserProfiles.js');
}

Функции публикации подготовки

В примере Provisioning.PublishingFeatures показано, как выполнять распространенные задачи с сайтами публикации, размещенными на Office 365, например подготовка и использование макетов страниц, master страниц и тем или внедрение JavaScript в макеты страниц. Здесь также показано, как применять фильтры, которые определяют, какие шаблоны сайтов доступны для дочерних сайтов и какие макеты страниц доступны на хост-сайте.

Надстройка, размещенная у поставщика, использует CSOM для подготовки часто используемых элементов пользовательского интерфейса на сайтах публикации, а javaScript — для создания более динамических интерфейсов макетов страниц, которые можно развернуть на сайтах публикации. Здесь также показаны различия между использованием master страниц и тем на сайтах публикации.

Важно!

Чтобы функциональные возможности этого примера работали, необходимо активировать функции публикации на сайте. Дополнительные сведения см. в разделе Включение функций публикации.

На начальной странице примера представлены три сценария настройки пользовательского интерфейса сайтов публикации:

  • Развертывание макетов страниц.
  • Развертывание master страниц и тем.
  • Фильтрация доступных макетов страниц и шаблонов сайтов на хост-сайте.

Сценарий 1. Развертывание страниц

Сценарий 1 показывает, как развернуть пользовательский макет страницы. Кнопка Развернуть макеты страниц, показанная на следующем рисунке, создает новый макет страницы и страницу, которая использует этот макет.

Кнопка для развертывания макетов страниц

Кнопка для развертывания макетов страниц

Новую страницу можно просмотреть, перейдя на только что созданную демонстрационную страницу на хост-сайте, которая содержит внедренный Код JavaScript и отображает сведения о профиле пользователя.

В примере отображаются сведения об этом пользователе на странице. Он также добавляет страницу, хотя в этом случае использует объект PublishingPageInformation для создания новой страницы.

Пример добавляет новый макет страницы, отправляя файл в коллекцию главных страниц и назначая ему тип контента макета страницы. Следующий код берет путь к файлу *.aspx (который можно развернуть в качестве ресурса в проекте Visual Studio) и добавляет его в качестве макета страницы в коллекцию главных страниц.

// Get the path to the file that you are about to deploy.
List masterPageGallery = web.GetCatalog((int)ListTemplateType.MasterPageCatalog);
Folder rootFolder = masterPageGallery.RootFolder;
web.Context.Load(masterPageGallery);
web.Context.Load(rootFolder);
web.Context.ExecuteQuery();

var fileBytes = System.IO.File.ReadAllBytes(sourceFilePath);

// Use CSOM to upload the file.
FileCreationInformation newFile = new FileCreationInformation();
newFile.Content = fileBytes;
newFile.Url = UrlUtility.Combine(rootFolder.ServerRelativeUrl, fileName);
newFile.Overwrite = true;

Microsoft.SharePoint.Client.File uploadFile = rootFolder.Files.Add(newFile);
web.Context.Load(uploadFile);
web.Context.ExecuteQuery();

// Check out the file if needed.
if (masterPageGallery.ForceCheckout || masterPageGallery.EnableVersioning)
{
  if (uploadFile.CheckOutType == CheckOutType.None)
  {
    uploadFile.CheckOut();
  }
}

// Get content type for ID to assign associated content type information.
ContentType associatedCt = web.GetContentTypeById(associatedContentTypeID);

var listItem = uploadFile.ListItemAllFields;
listItem["Title"] = title;
listItem["MasterPageDescription"] = description;
// Set the item as page layout.
listItem["ContentTypeId"] = Constants.PAGE_LAYOUT_CONTENT_TYPE;
// Set the associated content type ID property
listItem["PublishingAssociatedContentType"] = string.Format(";#{0};#{1};#", associatedCt.Name, associatedCt.Id);
listItem["UIVersion"] = Convert.ToString(15);
listItem.Update();

// Check in the page layout if needed.
if (masterPageGallery.ForceCheckout || masterPageGallery.EnableVersioning)
{
  uploadFile.CheckIn(string.Empty, CheckinType.MajorCheckIn);
  listItem.File.Publish(string.Empty);
}
web.Context.ExecuteQuery();

Вы можете убедиться, что новая страница использует новый макет страницы, перейдя в библиотеку Страницы хост-сайта.

Сценарий 2. Развертывание страниц и тем master

Сценарий 2 показывает, как развернуть и настроить master страниц и тем для хост-сайта из надстройки, размещенной у поставщика. Если вы выберете Развернуть master и используете его на начальной странице примера, пример развертывает и применяет настраиваемую страницу master к хост-сайту. Новую страницу master можно просмотреть, перейдя на домашнюю страницу сайта.

Пример добавляет новую страницу master путем отправки файла *.master в коллекцию эталонных страниц и назначения ему типа контента страницы master. Следующий код берет путь к файлу *.master (который можно развернуть в качестве ресурса в проекте Visual Studio) и добавляет его в качестве master страницы в коллекции эталонных страниц.

string fileName = Path.GetFileName(sourceFilePath);
// Get the path to the file that you are about to deploy.
List masterPageGallery = web.GetCatalog((int)ListTemplateType.MasterPageCatalog);
Folder rootFolder = masterPageGallery.RootFolder;
web.Context.Load(masterPageGallery);
web.Context.Load(rootFolder);
web.Context.ExecuteQuery();

// Get the file name from the provided path.
var fileBytes = System.IO.File.ReadAllBytes(sourceFilePath);

// Use CSOM to upload the file.
FileCreationInformation newFile = new FileCreationInformation();
newFile.Content = fileBytes;
newFile.Url = UrlUtility.Combine(rootFolder.ServerRelativeUrl, fileName);
newFile.Overwrite = true;

Microsoft.SharePoint.Client.File uploadFile = rootFolder.Files.Add(newFile);
web.Context.Load(uploadFile);
web.Context.ExecuteQuery();


var listItem = uploadFile.ListItemAllFields;
if (masterPageGallery.ForceCheckout || masterPageGallery.EnableVersioning)
{
  if (uploadFile.CheckOutType == CheckOutType.None)
  {
    uploadFile.CheckOut();
  }
}

listItem["Title"] = title;
listItem["MasterPageDescription"] = description;
// Set content type as master page.
listItem["ContentTypeId"] = Constants.MASTERPAGE_CONTENT_TYPE;
listItem["UIVersion"] = uiVersion;
listItem.Update();
if (masterPageGallery.ForceCheckout || masterPageGallery.EnableVersioning)
{
  uploadFile.CheckIn(string.Empty, CheckinType.MajorCheckIn);
  listItem.File.Publish(string.Empty);
}
web.Context.Load(listItem);
web.Context.ExecuteQuery();

Далее необходимо задать URL-адрес новой страницы master в качестве значения свойств MasterUrl и CustomMasterUrlвеб-объекта, представляющего сайт. Пример обрабатывает это с помощью одного метода, который получает URL-адрес новой master страницы в коллекции эталонных страниц, а затем присваивает это значение свойствам Web.MasterUrl и Web.CustomMasterUrl.

// Assign master page to the host web.
clientContext.Web.SetMasterPagesForSiteByName("contoso.master", "contoso.master");

При выборе параметра Развернуть тему и ее использовании пример развертывает и применяет настраиваемую тему к хост-сайту. Пример задает цветовую палитру, фоновое изображение и схему шрифтов темы, добавив новую тему с этими значениями (которые можно развернуть в качестве ресурсов в проекте Visual Studio) в коллекцию тем. В следующем коде создается новая тема.

List themesOverviewList = web.GetCatalog((int)ListTemplateType.DesignCatalog);
web.Context.Load(themesOverviewList);
web.Context.ExecuteQuery();
ListItemCreationInformation itemInfo = new ListItemCreationInformation();
Microsoft.SharePoint.Client.ListItem item = themesOverviewList.AddItem(itemInfo);
item["Name"] = themeName;
item["Title"] = themeName;
if (!string.IsNullOrEmpty(colorFileName))
{
  item["ThemeUrl"] = UrlUtility.Combine(rootWeb.ServerRelativeUrl, string.Format(Constants.THEMES_DIRECTORY, Path.GetFileName(colorFileName)));
}
if (!string.IsNullOrEmpty(fontFileName))
{
  item["FontSchemeUrl"] = UrlUtility.Combine(rootWeb.ServerRelativeUrl, string.Format(Constants.THEMES_DIRECTORY, Path.GetFileName(fontFileName)));
}
if (!string.IsNullOrEmpty(backgroundName))
{
  item["ImageUrl"] = UrlUtility.Combine(rootWeb.ServerRelativeUrl, string.Format(Constants.THEMES_DIRECTORY, Path.GetFileName(backgroundName)));
}
item["DisplayOrder"] = 11;
item.Update();
web.Context.ExecuteQuery();

Следующим шагом является установка этой новой темы в качестве темы для сайта. Следующий код делает это, извлекая тему из коллекции тем, а затем применяя ее значения к хост-сайту.

CamlQuery query = new CamlQuery();
// Find the theme by themeName.
string camlString = string.Format(CAML_QUERY_FIND_BY_FILENAME, themeName);
query.ViewXml = camlString;
var found = themeList.GetItems(query);
rootWeb.Context.Load(found);
LoggingUtility.Internal.TraceVerbose("Getting theme: {0}", themeName);
rootWeb.Context.ExecuteQuery();
if (found.Count > 0)
{
  ListItem themeEntry = found[0];

  // set the properties for applying the custom theme that was just uploaded.
  string spColorURL = null;
  if (themeEntry["ThemeUrl"] != null &amp;&amp; themeEntry["ThemeUrl"].ToString().Length > 0)
  {
    spColorURL = UrlUtility.MakeRelativeUrl((themeEntry["ThemeUrl"] as FieldUrlValue).Url);
  }
  string spFontURL = null;
  if (themeEntry["FontSchemeUrl"] != null &amp;&amp; themeEntry["FontSchemeUrl"].ToString().Length > 0)
  {
    spFontURL = UrlUtility.MakeRelativeUrl((themeEntry["FontSchemeUrl"] as FieldUrlValue).Url);
  }
  string backGroundImage = null;
  if (themeEntry["ImageUrl"] != null &amp;&amp; themeEntry["ImageUrl"].ToString().Length > 0)
  {
    backGroundImage = UrlUtility.MakeRelativeUrl((themeEntry["ImageUrl"] as FieldUrlValue).Url);
  }

  // Set theme for demonstration.
  // TODO: Why is shareGenerated false? If deploying to root and inheriting, maybe use shareGenerated = true.
  web.ApplyTheme(spColorURL,
                      spFontURL,
                      backGroundImage,
                      false);
  web.Context.ExecuteQuery();

Сценарий 3. Фильтрация доступных макетов страниц и шаблонов сайтов

Сценарий 3 показывает, как ограничить параметры, доступные пользователям при применении шаблонов к новым сайтам, а макеты — к новым страницам. При выборе параметра Применить фильтры для размещения веб-сайта на начальной странице примера пример задает настраиваемый макет страницы в качестве значения по умолчанию и один дополнительный макет страницы в качестве единственного другого варианта для любых новых страниц, создаваемых пользователем. Этот пример также сокращает количество доступных параметров для пользователей при создании новых дочерних сайтов. На следующем рисунке показано, как выглядит поле выбора шаблона сайта до и после применения фильтров.

Выбор шаблона сайта до и после применения примеров фильтров

Выбор шаблона сайта до и после применения примеров фильтров

Пример задает как стандартные, так и доступные макеты страниц, передавая связанные файлы *.aspx в методы расширения, как показано в коде.

List<string> pageLayouts = new List<string>();
pageLayouts.Add("ContosoLinksBelow.aspx");
pageLayouts.Add("ContosoLinksRight.aspx");
clientContext.Web.SetAvailablePageLayouts(clientContext.Web, pageLayouts);

// Set default page layout for the site.
clientContext.Web.SetDefaultPageLayoutForSite(clientContext.Web, "ContosoLinksBelow.aspx");

В этом примере задаются доступные шаблоны сайтов, выполняя аналогичные действия. В этом случае экземпляры WebTemplateEntity , определяющие каждый шаблон сайта, передаются в метод расширения SetAvailableWebTemplates.

List<WebTemplateEntity> templates = new List<WebTemplateEntity>();
templates.Add(new WebTemplateEntity() { LanguageCode = "1035", TemplateName = "STS#0" });
templates.Add(new WebTemplateEntity() { LanguageCode = "", TemplateName = "STS#0" });
templates.Add(new WebTemplateEntity() { LanguageCode = "", TemplateName = "BLOG#0" });
clientContext.Web.SetAvailableWebTemplates(templates);

Все три этих метода расширения — SetAvailablePageLayouts, SetDefaultPageLayoutForSite и SetAvailableWebTemplates — работают одинаково. Они создают XML-документы, содержащие пары "ключ-значение", определяющие доступные и стандартные макеты, а также доступные шаблоны. Затем они передают эти документы в дополнительный метод расширения с именем SetPropertyBagValue. Этот метод реализован в расширении OfficeDevPnP Core. После настройки соответствующих пакетов свойств эти пакеты свойств затем используются для фильтрации параметров в интерфейсе.

Из трех методов SetAvailableWebTemplates отображает полный шаблон.

public static void SetAvailableWebTemplates(this Web web, List<WebTemplateEntity> availableTemplates)
{
  string propertyValue = string.Empty;

  LanguageTemplateHash languages = new LanguageTemplateHash();
  foreach (var item in availableTemplates)
  {
    AddTemplateToCollection(languages, item);
  }

  if (availableTemplates.Count > 0)
  {
    XmlDocument xd = new XmlDocument();
    XmlNode xmlNode = xd.CreateElement("webtemplates");
    xd.AppendChild(xmlNode);
    foreach (var language in languages)
    {
      XmlNode xmlLcidNode = xmlNode.AppendChild(xd.CreateElement("lcid"));
      XmlAttribute xmlAttribute = xd.CreateAttribute("id");
      xmlAttribute.Value = language.Key;
      xmlLcidNode.Attributes.SetNamedItem(xmlAttribute);

      foreach (string item in language.Value)
      {
        XmlNode xmlWTNode = xmlLcidNode.AppendChild(xd.CreateElement("webtemplate"));
        XmlAttribute xmlAttributeName = xd.CreateAttribute("name");
        xmlAttributeName.Value = item;
        xmlWTNode.Attributes.SetNamedItem(xmlAttributeName);
      }
    }
    propertyValue = xmlNode.OuterXml;
  }
  // Save the XML entry to property bag.
  web.SetPropertyBagValue(AvailableWebTemplates, propertyValue);
  // Set that templates are not inherited.
  web.SetPropertyBagValue(InheritWebTemplates, "False");
}

Контейнер свойств InheritWebTemplates гарантирует, что все шаблоны, которые обычно наследуются от родительского сайта, также игнорируются при создании дочерних сайтов.

См. также