65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 JSGrid 的主从关系

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (6投票s)

2014年9月11日

CPOL

8分钟阅读

viewsIcon

58509

downloadIcon

649

使用类似 Excel 的 JSGrid 控件(快速编辑模式下的 XsltListViewWebPart)创建主从表单。

引言

主从表单非常常见。经典的例子是一个订单及其中的商品列表……

因此,从 SharePoint 的角度来看,您需要做的是将可编辑的明细列表嵌入到编辑表单中,如下所示

通常,如果明细数据表类似 Excel,那将非常方便。 Excel 对用户来说通常非常熟悉,并且可以快速有效地添加信息。

幸运的是,在 SharePoint 中,有一个名为 JSGrid 的客户端控件,它与 Excel 工作表表非常相似。

因此,在本文中,我将利用 JSGrid 来实现一个主从表单。

将 XLV 切换到快速编辑模式

在 SharePoint 2013 中,JSGrid 用于在快速编辑模式下显示列表视图。列表视图由 XsltListViewWebPart(也称为 XLV)表示。

所以想法是将明细列表 XLV 添加到您的主列表编辑表单页面,然后以某种方式将其切换到快速编辑模式。

表单页面可以是普通的列表表单(EditForm),也可以是自定义页面布局。后者通常是创建可打印表单的绝佳方法。

所以第一步是将 XLV 添加到您的表单页面。这是基础:只需在浏览器中将页面切换到编辑模式,然后在此处添加具有明细数据的列表。

现在,通常列表视图会有一个链接,允许将它们切换到快速编辑模式

所以我只是侦察了这个链接后面的脚本。这是它

_spBodyOnLoadFunctions.push(function()
{
    EnsureScriptParams('inplview', 'InitGridFromView', '{YOUR-GUID-HERE}');
});

GUID 是 XLV 视图的。您可以通过页面源代码等方式进行侦察。搜索 g_ViewIdToViewCounterMap

如果您页面上有一个 XLV,那么只有一个这样的位置。右边的 GUID 正是您需要的。

或者,您可以在 SharePoint Designer 中侦察视图 ID,或者以任何其他您喜欢的方式获取它。

因此,如果您将 EnsureParams 脚本添加到页面,那么当页面加载时,列表将以快速编辑模式显示。

如果您正在自定义普通的列表表单,请将代码放入 EditForm 页面,例如通过 Script Editor WebPart(当然,不要忘记将 js 包装在 <script> 标签中)。

注意:如果您的网站启用了 MDS,请参阅下面的“最小下载策略”部分。

如果您正在创建自定义页面布局,请将代码放入页面源代码中,并且不要忘记将脚本包装在 <PublishingWebControls:EditModePanel runat="server"> 标签中,以便它仅在页面处于编辑模式时出现。

所以我们页面上有明细 XLV,并且我们已经将其切换到编辑模式。但它显示所有项目,而不是仅与当前主列表项目相关的项目。让我们来修复它!

过滤 XLV 以仅显示相关项目

在 SharePoint Designer 中可以轻松进行过滤。

编辑表单

如果您正在自定义编辑列表表单页面:在 SPD 中找到您的列表并打开列表表单页面编辑器,然后执行以下操作

  1. 找到 XLV(搜索 XsltListViewWebPart 标签)。在 XLV 内部,找到 ParameterBindings 标签并在其中添加以下代码
    <ParameterBinding Name="itemId" Location="QueryString(ID)" />
  2. 修改 Query 标签(在 XLV 内的 View 标签内)。将“YourLookupFieldInternalName”替换为您主查找字段的内部名称,该字段从明细指向主。
    <Where><Eq><FieldRef Name="YourLookupFieldInternalName" LookupId="TRUE" /><Value Type="Integer">{itemId}</Value></Eq></Where>

保存页面并查看。完成!XLV 现在已过滤。

结果

自定义页面布局

如果您正在创建自定义页面布局,请在 SPD 的 masterpage 文件夹中找到您的页面布局。打开页面布局编辑器并执行以下操作

  1. 找到 XLV(搜索 XsltListViewWebPart 标签)。在 XLV 之上,添加以下代码:
    <div style="display:none;">
       <SharePointWebControls:NumberField ID="ItemID" ControlMode="Display" FieldName="1d22ea11-1e32-424e-89ab-9fedbadb6ce1" runat="server"/>
    </div>
  2. 在 XLV 内部,找到 ParameterBindings 标签并在其中添加以下代码
    <ParameterBinding Name="itemId" Location="Control(ItemID,ItemFieldValue)" />
  3. 最后,修改 Query 标签(在 XLV 内的 View 标签内)。将“YourLookupFieldInternalName”替换为您主查找字段的内部名称,该字段从明细指向主。
    <Where><Eq><FieldRef Name="YourLookupFieldInternalName" LookupId="TRUE" /><Value Type="Integer">{itemId}</Value></Eq></Where>

在我的例子中,结果看起来是这样的

新项目问题

好的,您已经过滤了可编辑的网格,听起来很棒!……但是如何添加新项目,我们是否真的必须每次都强制用户手动选择订单?当然不是。

为了解决这个问题,显然我需要做这样的事情

  1. 确定何时将新记录添加到 JSGrid
  2. 在“订单”列中填写正确的值
  3. 隐藏该列本身,以便用户无法编辑它

JSGrid 是一个很大程度上未被记录的控件,拥有巨大的客户端 API,并且处理起来很困难。当试图弄清楚如何使用 JSGrid 做某事时,我通常会深入到 SharePoint 的 JS 内部:)

关于 JSGrid 的好消息是它有大量的事件,您可以附加处理程序到这些事件。我发现 SP.JsGrid.EventType.OnEntryRecordPropertyChanged 事件在录入记录行(用于添加新记录的最后一行)发生更改时触发。

为了订阅事件,使用 jsGrid.AttachEvent 方法。

jsGrid 对象本身可以从 JSGrid 容器元素的属性中获取。此容器元素的 ID 可以通过 g_SPGridInitInfo 全局变量确定。

var viewId = "{GUID-OF-YOUR-VIEW}";
var jsGridContainer = $get("spgridcontainer_" + g_SPGridInitInfo[viewId].jsInitObj.qualifier)
var jsGrid = jsGridContainer.jsgrid;
jsGrid.AttachEvent(SP.JsGrid.EventType.OnEntryRecordPropertyChanged, function() {
  debugger;
});

我在浏览器的 JS 控制台中运行了此代码,然后尝试更改录入记录行。当我点击“BMW W5”值时,调试器弹出

很好,现在我们可以探索堆栈跟踪、参数,并思考我们还能做什么。例如,这是传递给此事件处理程序的参数的屏幕截图

如您所见,参数描述了更改:字段名称、旧值和新值、字段类型以及一些其他信息。JSGrid 并非完全文档化,但其 API 非常好!

在堆栈跟踪的上面,我找到了 UpdateProperties 方法。事实证明,此方法可用于更新给定行中的任何属性。所以过了一段时间,我用以下代码更新了主查找列(lock 用于防止递归)

var viewId = '{YOUR-VIEW-GUID-HERE}';
var orderFieldValue = 7; // ID of the master item
var orderFieldInternalName = "Order0"; // Internal name of the lookup field
var jsGridContainer = $get("spgridcontainer_" + g_SPGridInitInfo[viewId].jsInitObj.qualifier)
var jsGrid = jsGridContainer.jsgrid;
var lock = 0;
jsGrid.AttachEvent(SP.JsGrid.EventType.OnEntryRecordPropertyChanged, function(args) {
    if (lock == 0) {
        lock = 1;
        var update = SP.JsGrid.CreateUnvalidatedPropertyUpdate(args.recordKey,orderFieldInternalName,orderFieldValue,false);
        jsGrid.UpdateProperties([update], SP.JsGrid.UserAction.UserEdit);
        lock = 0;
    }
});

我直接从控制台测试了代码,它奏效了!

最后一步是隐藏查找列。这非常简单

jsGrid.HideColumn(orderFieldInternalName);

完善

好了,现在它工作了,但肯定需要一些清理

  1. 不需要“添加列”按钮。
  2. JSGrid 列可以被过滤和排序,这个功能不知何故覆盖了我的订单过滤器。
  3. 应隐藏“停止编辑此列表”链接。

为了隐藏“添加列”,我不得不修改我用来将表单切换到编辑视图的脚本。

_spBodyOnLoadFunctions.push(function()
{
    var viewId = "{YOUR-VIEW-GUID-HERE}";
    g_SPGridInitInfo[viewId].jsInitObj.canUserAddColumn = false;
    g_SPGridInitInfo[viewId].jsInitObj.showAddColumn = false;
    EnsureScriptParams('inplview', 'InitGridFromView', viewId);
});

为了防止列排序和过滤,我用以下代码

var columns = jsGrid.GetColumns();
for (var i in columns)
{
    columns[i].isSortable = false;
    columns[i].isAutoFilterable = false;
}
jsGrid.UpdateColumns(new SP.JsGrid.ColumnInfoCollection(columns));

为了隐藏“停止编辑此列表”行,我不得不将其包装在一个 div 中,并为该 div 设置 style.display='none'

var hero = $get('Hero-' + ctx.wpq);
var wrapper = document.createElement("div");
wrapper.style.display = 'none';
hero.parentNode.insertBefore(wrapper, hero);
wrapper.appendChild(hero);

最后,需要进行一些包装以确保自定义的应用时机正确。我最终使用了 CSR OnPostRender 事件。

最终代码

所以,这里是 Script Editor Web Part 的最终完整 JS 代码,用于进行我在文章中描述的所有更改(XLV 过滤不包含,因为它是在声明性中完成的,请参阅“过滤 XLV 以仅显示相关项目”一章)

<script type="text/javascript">
_spBodyOnLoadFunctions.push(function()
{
    var viewId = '{YOUR-VIEW-GUID-HERE}';
    var orderFieldValue = GetUrlKeyValue("ID"); // ID of the master item
    var orderFieldInternalName = "Order0"; // Internal name of the lookup field

    // Switch to edit view and hide "Add column" button
    SP.SOD.executeFunc('inplview', 'InitGridFromView', function() {
        g_SPGridInitInfo[viewId].jsInitObj.canUserAddColumn = false;
        g_SPGridInitInfo[viewId].jsInitObj.showAddColumn = false;
        InitGridFromView(viewId);
    });


    SP.SOD.executeFunc('clienttemplates.js', 'SPClientTemplates.TemplateManager.RegisterTemplateOverrides', function() {
    
        var done = false;
        SPClientTemplates.TemplateManager.RegisterTemplateOverrides({
        
            OnPostRender: function(ctx) {
            
                if (ctx.view != viewId || ctx.enteringGridMode || !ctx.inGridMode || done)
                  return;
                
                // Hide quick links (aka "hero") row
                var hero = $get('Hero-' + ctx.wpq);
                var wrapper = document.createElement("div");
                wrapper.style.display = 'none';
                hero.parentNode.insertBefore(wrapper, hero);
                wrapper.appendChild(hero);
                
                // Fetch JSGrid object
                var jsGridContainer = $get("spgridcontainer_" + g_SPGridInitInfo[viewId].jsInitObj.qualifier)
                var jsGrid = jsGridContainer.jsgrid;
                
                // Automatically update master lookup column
                var lock = 0;
                jsGrid.AttachEvent(SP.JsGrid.EventType.OnEntryRecordPropertyChanged, function(args) {
                    if (lock == 0) {
                        lock = 1;
                        var update = SP.JsGrid.CreateUnvalidatedPropertyUpdate(args.recordKey,orderFieldInternalName,orderFieldValue,false);
                        jsGrid.UpdateProperties([update], SP.JsGrid.UserAction.UserEdit);
                        lock = 0;
                    }
                });
                
                // Make columns non-sortable and non-filterable
                var columns = jsGrid.GetColumns();
                for (var i in columns)
                {
                    columns[i].isSortable = false;
                    columns[i].isAutoFilterable = false;
                }
                jsGrid.UpdateColumns(new SP.JsGrid.ColumnInfoCollection(columns));
        
                // Hide master lookup column
                jsGrid.HideColumn(orderFieldInternalName);
                
                done = true;
            }
            
        });
        
    });
    
});
</script>

注意:如果您正在创建自定义页面布局,请将以下行替换为

    var orderFieldValue = GetUrlKeyValue("ID"); // ID of the master item

请使用此行

    var orderFieldValue = <SharePointWebControls:NumberField ControlMode="Display" FieldName="1d22ea11-1e32-424e-89ab-9fedbadb6ce1" runat="server"/>; // ID of the current page

最小下载策略

启用最小下载策略后,Script Editor Web Part 的效果不太好。因此,对于普通的列表表单,我们需要稍微更改代码,并将其放入一个单独的文件中,然后以某种方式将此文件附加到页面,例如通过 JSLink。

所以我创建了一个 js 文件 /Style Library/JSGrid-masterDetails.js 并进行了修改

SP.SOD.executeFunc('clienttemplates.js', 'SPClientTemplates.Utility.ReplaceUrlTokens', function() {

    function init()
    {

       // all the code here, starting from _spBodyOnLoadFunctions.push(... and so on.

    }

    RegisterModuleInit(SPClientTemplates.Utility.ReplaceUrlTokens("~site/Style Library/JSGrid-masterDetails.js"), init);
    init();

});

然后,要将其附加到您的列表表单,请编辑列表表单页面,然后编辑 XLV WebPart

在 Miscellaneous 部分,找到 JSLink 属性,并将您的 js 文件链接放在此处

链接应如下所示:~site/Style Library/JSGrid-masterDetails.js

源代码

存档包含以下文件

  • JSGrid-masterDetails.js - 通过 JSLink 附加到列表编辑表单的代码
  • XLV_ListEditForm.aspx - 列表表单上 XLV 的示例标记
  • CustomPageLayout.aspx - 自定义页面布局的示例标记。除了文章中解释的内容之外,它还通过 CSR 使用自定义显示模板,以便列表在显示模式下对打印机友好

请不要忘记 aspx 文件中的标记取决于实际列表,因此这些文件在您的系统上将无法正常工作,主要提供参考。

结论

JSGrid 具有强大而灵活的 API,可以为用户提供非常方便的类似 Excel 的界面。您可能已经注意到,我与 JSGrid 本身并没有遇到太多问题。包装很困难,但 JSGrid 本身很棒!

在主从表单的情况下,JSGrid 提供了绝佳的机会,可以使用类似 Excel 的界面以及自定义页面布局和普通的列表表单。

© . All rights reserved.