SharePoint 2013 客户端渲染:列表表单 + KnockoutJs





5.00/5 (9投票s)
使用 CSR 和 KnockoutJs 自定义 SharePoint 2013 列表窗体的三个示例。
引言
在本文中,我将演示使用 KnockoutJs 和 SharePoint 客户端渲染来自定义列表窗体的三个实际示例。
- 只读字段,可切换到编辑模式
- 内联为查找字段添加值
- 联动字段
背景
有些人认为 SharePoint 和其他企业平台的用户体验不重要。我强烈不同意。
糟糕的用户体验会使日常工作变得枯燥且效率低下。当人们面对糟糕的界面时,更容易出错。更糟糕的是,如果用户界面过于复杂且难以使用,人们往往会完全避免使用它。
但是我们**可以**改变它,我相信 CSR 和 KnockoutJs 是实现这一目标的绝佳工具组合。
客户端渲染 (CSR) 是 SharePoint 2013 中用于显示列表视图、列表窗体和搜索结果的默认 JavaScript 渲染引擎。
如果您想了解更多关于客户端渲染的信息,请参考以下文章:
KnockoutJs 是一个 MVVM 框架,在 JavaScript 和 HTML 领域实现了双向数据绑定概念。您可以在其官方网站上了解更多关于 KnockoutJs 的信息。
本文无意解释 KnockoutJs 或 CSR 的工作原理,而是旨在展示如何有效地将两者结合使用,它们的优势是什么,以及需要注意的地方。因此,如果您对 KnockoutJs 或 CSR 完全不熟悉,请先访问上面的链接并掌握基础知识,否则本文可能会令人困惑。
示例 1:只读字段,可切换到编辑模式
假设有一个很少更改的字段。例如,在下面的列表窗体中,很明显“Title”字段不应经常修改。
为了强调重命名城市是个坏主意,我将默认使该字段只读,并在其旁边放置一个“编辑”按钮,以便当用户单击“编辑”时,会显示一个带有警告消息的确认对话框,如下所示:
在实现方面,这意味着遵循以下 4 个步骤:
- 覆盖字段模板
- 为字段创建**只读模式**,该模式默认显示,并包含处于显示模式的字段控件 + “编辑按钮”。
- 为该字段创建**编辑模式**,该模式包含处于编辑模式的字段控件。
- 实现“编辑”**按钮操作**,以便在单击时显示确认对话框,如果单击“确定”,则隐藏只读模式并显示编辑模式。
让我们快速回顾一下每个步骤。
步骤 1:覆盖编辑窗体上的默认字段模板
第一步很简单,可以使用以下代码片段实现:
SPClientTemplates.TemplateManager.RegisterTemplateOverrides({
Templates: {
Fields: {
"Title": {
EditForm: function(ctx) {
return 'some html code here';
},
},
},
},
});
注意:覆盖时应使用字段的内部名称(在本例中为“Title”)。
结果截图
步骤 2:为字段创建只读模式
最简单的情况下,我们可以直接使用 `ctx.CurrentFieldValue` - 但这仅对简单的文本字段有效。更好的方法是重用字段的默认显示模板,这将适用于任何字段类型。
不幸的是,似乎没有完全支持的方法可以在 CSR 中重用默认字段模板。我自己通常会从 TemplateManager 对象的 `_defaultTemplates` 属性中获取这些模板,但也有一些其他方法可以做到这一点 - 请使用最适合您的方法。
因此,在使用 `_defaultTemplates` 的情况下,要获取默认模板,我使用这段代码:
SPClientTemplates._defaultTemplates.Fields.default.all.all[<Field type>][<Control mode>]
在此,*<Field type>* 是字段的类型,例如“Text”、“Note”等;*<Control mode>* 可以是“EditForm”、“NewForm”和“DisplayForm”之一。
默认模板是一个函数,它显然只接受 `ctx` 作为参数。了解了所有这些之后,我现在可以轻松地为字段创建只读模式:
SPClientTemplates.TemplateManager.RegisterTemplateOverrides({
Templates: {
Fields: {
"Title": {
EditForm: function(ctx) {
var fieldType = ctx.CurrentFieldSchema.FieldType;
var defaultTemplates = SPClientTemplates._defaultTemplates.Fields.default.all.all;
return defaultTemplates[fieldType]["DisplayForm"](ctx) + '<button>Edit</button>';
},
},
},
},
});
结果截图
步骤 3:创建编辑模式
了解了如何重用模板后,创建编辑模式是一个简单的练习:
var fieldType = ctx.CurrentFieldSchema.FieldType;
var defaultTemplates = SPClientTemplates._defaultTemplates.Fields.default.all.all;
return defaultTemplates[fieldType]["DisplayForm"](ctx) + '<button>Edit</button>'
+ defaultTemplates[fieldType]["EditForm"](ctx);
结果截图
步骤 4:实现“编辑”按钮操作
现在,当然可以使用 jQuery 或原生 JavaScript 来实现模式切换,但是 KnockoutJs (KO) 和其他现代双向绑定 JavaScript 框架提供了更直观、更简单的方式来创建动态界面!...
为了将 KnockoutJs 用于此任务,我需要做三件简单的事情:
- 将 KnockoutJs 部署到页面
- 稍微调整我们的 HTML 并添加 `data-bind` 属性
- 创建一个表示页面模型的 JavaScript 对象并调用 `ko.applyBindings`
可以通过任何您想要的方式部署 KnockoutJs - 通过 ScriptLink 自定义操作、母版页、JSLink 等。
在 KnockoutJs 准备就绪并可使用后,让我们更改 HTML 并添加 `data-bind` 属性。这是我得到的结果:
var fieldType = ctx.CurrentFieldSchema.FieldType;
var defaultTemplates = SPClientTemplates._defaultTemplates.Fields.default.all.all;
return '<div data-bind="visible: !editMode()">'
+ defaultTemplates[fieldType]["DisplayForm"](ctx)
+ '<button data-bind="click: switchToEditMode">Edit</button>'
+ '</div>'
+ '<div data-bind="visible: editMode()">'
+ defaultTemplates[fieldType]["EditForm"](ctx)
+ '</div>';
所以您可以看到,我将只读模式和编辑模式包装在单独的 div 中,它们根据特定的 `editMode` 字段可见或隐藏。这个字段是可观察的,因此它实际上是一个函数,这就是为什么我调用它来获取其值。
此外,“编辑”按钮具有 `click` 绑定,因此无论何时单击它,都会调用特定的 `switchToEditMode` 方法。
现在让我们创建页面模型对象并在此处添加 `editMode` 和 `switchToEditMode`:
var model = {
editMode: ko.observable(false),
switchToEditMode: function() {
if (confirm('Are you sure want to rename a city!?'))
model.editMode(true);
}
};
这个页面模型显然应该使用 `ko.applyBindings` 绑定到 HTML。`applyBindings` 方法作用于 DOM,因此在调用 `applyBindings` 之前,必须先渲染由我们的模板生成的 HTML。
因此,放置 `ko.applyBindings` 的明显位置是 CSR 的 PostRender 处理程序。但重要的是要理解,在列表窗体中,渲染过程会为每个字段控件发生,这意味着 PostRender 将被调用多次。因此,我通常会在那里添加一个额外的条件,检查表单中的最后一个字段,以便 `ko.applyBindings` 只调用一次。
最终代码
所以这是最终代码:
SPClientTemplates.TemplateManager.RegisterTemplateOverrides({
Templates: {
Fields: {
"Title": {
EditForm: function(ctx) {
var fieldType = ctx.CurrentFieldSchema.FieldType;
var defaultTemplates = SPClientTemplates._defaultTemplates.Fields.default.all.all;
return '<div data-bind="visible: !editMode()">'
+ defaultTemplates[fieldType]["DisplayForm"](ctx)
+ '<button data-bind="click: switchToEditMode">Edit</button>'
+ '</div>'
+ '<div data-bind="visible: editMode()">'
+ defaultTemplates[fieldType]["EditForm"](ctx)
+ '</div>';
},
},
},
},
OnPostRender: {
if (ctx.ListSchema.Field[0].Name == "Liked") // this is the last field on the form
{
var model = {
editMode: ko.observable(false),
switchToEditMode: function() {
if (confirm('Are you sure want to rename a city!?'))
model.editMode(true);
}
};
ko.applyBindings(model);
}
}
});
别忘了,在正确地将脚本包含到页面并使其与最小下载策略协同工作方面,总会有一些样板代码。
我通常使用这个骨架代码来实现此目的,它已被证明非常可靠:
SP.SOD.executeFunc("clienttemplates.js", "SPClientTemplates", function() {
function init() {
SPClientTemplates.TemplateManager.RegisterTemplateOverrides({
// overrides go here
});
}
RegisterModuleInit(SPClientTemplates.Utility.ReplaceUrlTokens("~siteCollection/Style Library/file.js"), init);
init();
});
注意:不要忘记更改文件名和路径。
示例 2:内联为查找字段添加值
有时,用户需要频繁地向某个查找项添加值。在这种情况下,内联界面可用于添加查找项,可以节省大量时间和挫败感。所以,假设我输入了一个新城市,但查找中没有相应的国家/地区:
我不想打开新窗口并导航到“国家/地区”列表等。相反,我希望“添加”按钮就在此窗体中:
每当单击此按钮时,我都希望显示一个简单的界面,例如一个文本框,允许我输入国家/地区的名称 + 确定和取消按钮。
好的。让我们来实现这一点。
UI
在 UI 方面,一切都与前一个示例非常相似,只是将 `editMode` 替换为 `addMode`。
SPClientTemplates.TemplateManager.RegisterTemplateOverrides({
Templates: {
Fields: {
"Country": {
NewForm: function(ctx) {
var fieldType = ctx.CurrentFieldSchema.FieldType;
var defaultTemplates = SPClientTemplates._defaultTemplates.Fields.default.all.all;
return '<div data-bind="visible: !addMode()">'
+ '<table><tr><td>'
+ defaultTemplates[fieldType]["NewForm"](ctx)
+ '</td><td>'
+ '<button data-bind="click: switchToAddMode">Add</button>'
+ '</td></tr></table>'
+ '</div>'
+ '<div data-bind="visible: addMode()">'
+ '<input type="text" />'
+ '<button>OK</button>'
+ '<button>Cancel</button>'
+ '</div>';
},
}
},
},
OnPostRender: function(ctx) {
if (ctx.ListSchema.Field[0].Name == "Liked")
{
var model = {
addMode: ko.observable(false),
switchToAddMode: function() {
model.addMode(true);
}
};
ko.applyBindings(model);
}
}
});
正如您所见,这与前一个示例的代码有 90% 相同。
我添加了 `
` 似乎是在同一行上将“添加”按钮与下拉列表保持在一起的最合法方式。
现在我们有了基本 UI,让我们让“确定”和“取消”按钮生效。 向查找项添加条目单击“确定”按钮时,应发生 3 件主要事情:
向列表添加条目很简单,例如可以使用一小段 JSOM 来完成: var context = SP.ClientContext.get_current();
var list = context.get_web().get_lists().getByTitle("Country");
var item = list.addItem(new SP.ListItemCreationInformation());
item.set_item("Title", model.countryName());
item.update();
context.executeQueryAsync(
function() {
alert("item added");
},
function() {
alert("error");
});
这里 `model.countryName` 应该是绑定到文本框值的 KnockoutJs 可观察对象,因此它包含用户在字段中输入的内容。 现在,在添加条目后,我们还应该将新国家/地区添加到下拉列表中,但不幸的是,没有现成的 API 或受支持的方法可以做到这一点。默认模板就像黑盒子一样,因此我们拥有的选项要么是重新实现整个字段(这是正确的方法,但涉及相当多的代码),要么通过 DOM 来 hack 下拉列表。 今天,为了保持示例简单,我将采用第二种方法,但请记住,任何 DOM hack 本质上都是糟糕的做法,在下次更新后(O365 更新非常频繁)它可能突然停止工作,仅此而已... 通过 DOM,向下拉列表添加元素是一项非常简单的任务。首先,让我们看一下下拉列表元素的源代码: 值显然等于条目 ID。现在创建适当的代码很容易: var context = SP.ClientContext.get_current();
var list = context.get_web().get_lists().getByTitle("Countries");
var item = list.addItem(new SP.ListItemCreationInformation());
item.set_item("Title", model.countryName());
item.update();
context.load(item,"ID");
context.executeQueryAsync(
function() {
// add new country to dropdown
var option = document.createElement("option");
option.value = item.get_id();
option.innerHTML = model.countryName();
var select = document.querySelector("#countryTD select");
select.appendChild(option);
// turn off the edit mode and clear input
model.editMode(false);
model.countryName('');
},
function() {
alert("error");
});
注意几点:
|