本文介绍演示自定义 SharePoint UX 组件的最佳做法的示例,包括以下方案:
页面操作(添加和修改 Wiki 网页)
显示模式对话框中的加载项和数据
创建个性化的 UI 元素
客户端呈现(部署自定义 SharePoint 列表中字段呈现的 JSLink 文件)
Web 部件和加载项部件的操作(远程设置和运行提供程序托管的加载项中的加载项脚本部件)
数据聚合和缓存(使用 HTML5 本地存储和 HTTP cookie 来减少对 SharePoint 的服务调用次数)
注意
本文中的代码按原样提供,不提供任何明示或暗示的担保,包括对特定用途适用性、适销性或不侵权的默示担保。
页面操作
Core.ModifyPages 示例包含两个页面操作方案:
- 创建 Wiki 网页。
- 修改 Wiki 页面布局。
此示例使用默认网站页面库和现有现成布局。 也可以将它更新为使用自定义 Wiki 页面库和自定义布局。 加载项 UI 包含两个用于创建两种 Wiki 页面的按钮,以及两个用于查看创建的 Wiki 页面的链接。
页面操作示例的起始页
第一种方案的代码示例会确定是否已创建 Wiki 页面。 如果没有,它会将文件添加到网站页面库并返回其 URL。
var newpage = pageLibrary.RootFolder.Files.AddTemplateFile(newWikiPageUrl, TemplateFileType.WikiPage);
ctx.Load(newpage);
ctx.ExecuteQuery();
wikiPageUrl = String.Format("sitepages/{0}", wikiPageName);
在这两种方案中,示例使用 LabHelper 帮助程序类中的 AddHtmlToWikiPage 方法,添加通过起始页上的文本框输入的 HTML。 此方法将表单中的 HTML 插入 Wiki 页面的 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 填充 Web 部件。 XML 显示提升的链接列表,包括 http://www.bing.com 和 Office 365 开发人员模式和做法 GitHub 存储库的主页。
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 Web 部件中使用表显示提升的链接。
包含 XsltListViewWebPart 部件和升级链接表的第二个 Wiki 网页
LabHelper 实例上的 WpPromotedLinks 对象包含定义将嵌入到 Wiki 页面中的 Web 部件外观的 XML。 然后,AddWebPartToWikiPage 方法将新定义的 Web 部件插入 Wiki 页面上的新 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());
}
例如,可以选择显示特定的 UI 元素(如按钮),甚至可以采用不同的页面布局,具体取决于内容是否要显示在对话框中。
起始页 UI 显示两个用于创建对话框链接的选项,并列出了主机 Web 上的所有列表。 它还显示“确定”和“取消”按钮,在对话框上下文中可用于关闭对话框和/或在加载项中提示执行其他操作。
如果选择“添加菜单项”按钮,加载项会创建用于启动对话框的 CustomActionEntity 对象。 然后,它使用名为 AddCustomAction 的 OfficeDevPnP 核心扩展方法,将新自定义操作添加到“网站设置”菜单中。
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 方法将自定义操作添加到与 SharePoint 网站相关联的 UserCustomActions 集合。
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();
如果选择“使用脚本编辑器 Web 部件添加页面”按钮,加载项会使用 AddWikiPage 和 AddWebPartToWikiPage 方法,在网站页面库中创建 Wiki 页面,并将已配置的脚本编辑器 Web 部件添加到此页面。
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 核心指定的 Wiki 页面尚不存在,它会创建相应页面。
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 方法将新定义的 Web 部件插入 Wiki 页面上的新 <div>
标记内。 然后,它使用 JSOM 和跨域库检索用于填充主机 Web 上各 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
个性化的 UI 元素
Branding.UIElementPersonalization 示例展示了如何使用嵌入式 JavaScript 以及用户配置文件和 SharePoint 列表中存储的值,在主机 Web 上个性化设置 UI 元素。 它还使用 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" &&
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 引擎将启动主函数 (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" && Boolean(Sys) && 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 && 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 示例合并到一个预配 JSLink 文件的提供程序托管 SharePoint 加载项中。 借助客户端呈现功能,可以使用标准 Web 技术(如 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 缩短了 Body 字段文本,并通过 span 标记上的 title 属性使完整的文本显示为弹出窗口。
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 && 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 && confidential.toLowerCase() == 'yes') {
// Render HTML that contains the file name and the confidential icon.
return title + "&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;'> \
&nbsp;</div></div>&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 展示了如何使用正则表达式验证用户提供的字段值。 如果用户在“电子邮件地址”字段文本框中键入的电子邮件地址无效,则会看到红色的错误消息。 用户在创建或编辑列表项时会遇到这种情况。
无效字段文本输入的错误消息
下面的代码在模板中设置用于显示错误消息的占位符,并注册在用户尝试提交表单时触发的回调函数。 第一个回调函数返回“电子邮件地址”列的值,第二个回调函数使用正则表达式验证字符串值。
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) && 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:将列表项编辑字段设置为只读
示例 7 展示了如何将列表项编辑表单字段设为只读。 只读字段不显示任何编辑控件。
自定义列表编辑窗体中的只读字段
下面的代码示例将列表项编辑表单中的“标题”、“分配到”和“优先级”字段修改为,仅显示字段值,不显示任何编辑控件。 此代码展示了如何处理不同字段类型的分析要求。
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();
}
Web 部件和加载项部件控制
Core.AppScriptPart 示例展示了如何使用加载项脚本部件,在 SharePoint 页面上嵌入提供程序托管加载项中运行的脚本。 此示例展示了如何部署加载项脚本部件,并将它从 Web 部件库添加到 SharePoint 页面,从而修改主机网站页面 UI。
加载项脚本部件类似于 Web 部件,可以从 Web 部件库添加到 SharePoint 页面中。但在此示例中,.webpart 文件嵌入的是在提供程序托管加载项中远程运行的 JavaScript 文件。 外接程序脚本部件在 SharePoint 页面上的标记内 <div>
运行,因此,与在 IFrames 中运行的外接程序部件相比,它提供了更快速的设计和体验。
起始页中包含“运行方案”按钮,用于将加载项脚本部件部署到 Web 部件库。 下面的代码示例构造包含 .webpart 文件内容的 FileCreationInformationObject 实例,再将新文件上传到 Web 部件库中。 请注意,也可以在加载项部件安装或网站集预配过程中,自动运行此代码。
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());
}
完成这一步后,可以在 Web 部件库内的新“加载项脚本部件”类别中找到“用户配置文件信息”加载项脚本部件。 将加载项脚本部件添加到页面后,远程运行的 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 托管发布网站的常见任务;例如,预配和使用页面布局、母版页和主题,或将 JavaScript 嵌入页面布局。 它还展示了如何应用筛选器,控制可对子网站使用的网站模板,以及可对主机 Web 使用的页面布局。
提供程序托管加载项使用 CSOM 预配发布网站上的常用 UI 元素,并使用 JavaScript 在可部署到发布网站的页面布局中打造更动态的体验。 它还展示了在发布网站中使用母版页和主题的区别。
重要
若要让此示例中的功能正常运行,需要在网站上激活发布功能。 有关详细信息,请参阅启用发布功能。
示例起始页显示三种用于自定义发布网站 UI 的方案:
- 部署页面布局。
- 部署母版页和主题。
- 筛选主机网站上的可用页面布局和网站模板。
方案 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:部署母版页和主题
方案 2 展示了如何在提供程序托管加载项中为主机网站部署并设置母版页和主题。 如果选择示例起始页上的“部署并使用母版页”,此示例会将自定义母版页部署并应用于主机网站。 可以转到网站主页,查看新母版页。
此示例通过将 *.master 文件上传到母版页库并为其分配母版页内容类型来添加新母版页。 以下代码采用 *.master 文件的路径, (可以在 Visual Studio 项目中部署为资源) ,并将其添加为母版页库中的母版页。
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 设置为表示网站的 Web 对象的 MasterUrl 和 CustomMasterUrl 属性值。 为此,示例使用一种方法,提取母版页样式库中新母版页的 URL,再将相应值分配给 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 && themeEntry["ThemeUrl"].ToString().Length > 0)
{
spColorURL = UrlUtility.MakeRelativeUrl((themeEntry["ThemeUrl"] as FieldUrlValue).Url);
}
string spFontURL = null;
if (themeEntry["FontSchemeUrl"] != null && themeEntry["FontSchemeUrl"].ToString().Length > 0)
{
spFontURL = UrlUtility.MakeRelativeUrl((themeEntry["FontSchemeUrl"] as FieldUrlValue).Url);
}
string backGroundImage = null;
if (themeEntry["ImageUrl"] != null && 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 展示了如何限制用户在将模板应用于新网站以及将布局应用于新页面时可以使用的选项。 如果选择示例起始页上的“对主机 Web 应用筛选器”,此示例会将自定义页面布局设置为默认布局,并将另一个页面布局设置为除默认布局以外唯一适用于用户创建的任何新页面的选项。 此示例还减少了用户在新建子网站时可用的选项数量。 下图对比了应用筛选器前后的网站模板选择框。
在应用示例筛选器之前和之后的网站模板选择
此示例将关联的 *.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 附加扩展方法。 此方法在 OfficeDevPnPCore 扩展中实现。 已设置的适当属性包随后可用于在界面中筛选选项。
在这三种方法中,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 属性包可确保在创建子网站时,也会忽略通常继承自父网站的任何模板。