SharePoint:扩展DVWP - 赠送部分:当“找不到匹配项”时修复Insert表单操作
Richard问了一个很棒的问题:“你是否在设置insert链接的时候遇到过无匹配项( no matching items )的模版?”
是的, 我在使用DVWP时遇到过相同的问题。当列表为空时调用特定的模版,这绝对是个bug。
解决的方法是在SPD的设计视图中,勾选“显示示例数据(Show sample data)” ,来告诉 SPD 模拟有数据的情况。这样至少可以允许我们处理该链接了。在实际情况下页面进行渲染时,还需要进行一点稍复杂的处理。看来我需要为这个系列写一个增篇了。 ;)
I’ll do that.
现在我们就开始。
拨开外壳
打开 edit 和insert 模版非常容易,只需要勾选一些选框就行。然而, SharePoint Designer (SPD) 使底层的XSL发生了重大变化:添加了变量和参数,修改一些已有变量的值,添加新的模版和条件格式,等等。
变量
dvt_1_automode 是一个变量,它告诉 DVWP 是否要绘制 edit 和/或delete 链接。 0 = 否; 1 = 是。打开 edit 和/或 delete 会将其设置为1。
如果edit template 被打开,一个新的参数会创建出来:dvt_1_form_editkey。如果 insert 模版被打开,会创建 dvt_1_form_insertmode 。 这两个参数会在点击相应的edit或insert 链接时通过 ParameterBindings 传递给该页面。
<xsl:variable name="dvt_1_automode">0</xsl:variable>
…变成…
<xsl:param name="dvt_1_form_editkey" />
<xsl:param name="dvt_1_form_insertmode" />
<xsl:variable name="dvt_1_automode">1</xsl:variable>
在主模版dvt_1里,, 当 dvt_RowCount = 0,或者 RowLimit = 0时,IsEmpty 变量都被修改为 true 。
<xsl:variable name="IsEmpty" select="$dvt_RowCount = 0" />
…变成…
<xsl:variable name="IsEmpty" select="$dvt_RowCount = 0 or $RowLimit = 0" />
设计
首先要通过SPD进行的一个设计上的修改是:通过清空th标记的nowrap属性来允许列头折行。
<th class="ms-vh" nowrap="nowrap">Location</th>
<th class="ms-vh" nowrap="nowrap">Group</th>
…变成…
<th class="ms-vh" nowrap="">Location</th>
<th class="ms-vh" nowrap="">Group</th>
接下来的一个设计上的变更是设置一个条件,通过检验该条件来确定哪个模板会被调用。如果我们不是在插入模式中,它会调用dvt_1.body,传递给它一些行用于显示。然后是dvt_1.rowinsert,传递dvt_1_form_insertmode,决定是渲染插入数据的表单还是只是输出一个插入链接。
然后它会传递ShowNavigation 和ShowFormActions 参数给dvt_1.commandfooter 模版,从而添加 X – Y 分页链接到页脚部分。
<xsl:with-param name="ShowNavigation">
<xsl:if test="not($dvt_1_form_insertmode = '1')">1</xsl:if>
</xsl:with-param>
<xsl:with-param name="ShowFormActions">
<xsl:if test="($dvt_1_automode = '0' and $dvt_1_form_insertmode)">1</xsl:if>
</xsl:with-param>
因此,这两个参数也被添加到了dvt_1.commandfooter模板,并且添加了相关的一些元素,用来在需要时显示他们。
新的模版
在默认的DVWP中, dvt_1.body 模版会遍历所有返回回来的行,为每一行都调用一次dvt_1.rowview 模版。
在更新后的版本中,它仍然会遍历所有的行,但是会检查每一行的dvt_1_form_editkey 是否与该行的ID相匹配。如果是,dvt_1.rowedit 模版会被调用,并传递该行的位置。对与剩下的所有行,会调用 dvt_1.rowview 。
<xsl:call-template name="dvt_1.rowview" />
…变为…
<xsl:choose>
<xsl:when test="$dvt_1_form_editkey = ddwrt:EscapeDelims(string(@ID))">
<xsl:call-template name="dvt_1.rowedit">
<xsl:with-param name="Pos" select="concat('_', position())" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="dvt_1.rowview" />
</xsl:otherwise>
</xsl:choose>
当然,edit 模版(dvt_1.rowedit) 也要被添加到页面中。(有关使用edit模版的更多信息,请参考扩展DVWP的第26部分:修改edit模版)因此insert 模版大致如下所示:
…becomes…
<xsl:template name="dvt_1.rowinsert">
<xsl:param name="IsInsertMode" />
<xsl:variable name="Pos">_new</xsl:variable>
<tr>
<xsl:choose>
<xsl:when test="$IsInsertMode = '1'">
<xsl:if test="$dvt_1_automode = '1'" ddwrt:cf_ignore="1">
<td class="ms-vb" width="1%" nowrap="nowrap">
<xsl:call-template name="dvt_1.automode">
<xsl:with-param name="KeyField">ID</xsl:with-param>
<xsl:with-param name="KeyValue" select="ddwrt:EscapeDelims(string(@ID))" />
<xsl:with-param name="Mode">insert</xsl:with-param>
</xsl:call-template>
</td>
</xsl:if>
<td class="ms-vb">
<SharePoint:FormField runat="server" id="ff1{$Pos}" ControlMode="New" FieldName="YourField" {code} />
<SharePoint:FieldDescription runat="server" id="ff1description{$Pos}" FieldName="YourField" ControlMode="Edit" />
</td>
<td class="ms-vb">
//The next column
</td>
.
.
.
</xsl:when>
<xsl:otherwise>
<td class="ms-vb" colspan="99">
<a href="javascript: {ddwrt:GenFireServerEvent('__cancel;dvt_1_form_insertmode={1}')}">insert</a>
</td>
</xsl:otherwise>
</xsl:choose>
</tr>
</xsl:template>
注意其中的第6行, IsInsertMode 变量决定了数据控件是否渲染,或者只是输出insert 行。
进一步的修改
在 SPD里,当我们首次把DVWP添加到页面上时,会有一个链接可以让我们设置没有匹配项时所显示的文字。
Now, how do we display the insert link?
Option 1. Since the empty template is being called, we could simply add the insert link to that template as well. In an STP discussion with Richard, Matt Bramer suggested adding the link below the dvt_ViewEmptyText.
当然我们也可以直接打开数据视图属性对话框,之前我们也曾用它来修改直接编辑选项。其中有一项可以设置当没有找到匹配的数据时所显示的文字,这里直接输入一些类似“该视图中没有项目”的文字即可。
但,这也正是会出现问题的地方。
当SPD 添加上面的文本后,它会跳过输出insert链接的 insert 模版。
重返核心
因此,让我们分析一下究竟是什么原因导致的这个问题,并试图对其进行修复。
首先,SPD并没有创建其他的变量,当dvt_RowCount = 0时 dvt_IsEmpty 被设为true。接下来在dvt_1模版的主table外包装了一层xsl:choose块。
<table border="0" width="100%" cellpadding="2" cellspacing="0">
.
.
.
…变为…
<xsl:variable name="dvt_IsEmpty" select="$dvt_RowCount = 0" />
<xsl:choose>
<xsl:when test="$dvt_IsEmpty">
<xsl:call-template name="dvt_1.empty" />
</xsl:when>
<xsl:otherwise>
<table border="0" width="100%" cellpadding="2" cellspacing="0">
.
.
.
换句话说,如果列表为空,主table就不会渲染;直接进入dvt_1.empty 模版,它如下所示:
<xsl:template name="dvt_1.empty">
<xsl:variable name="dvt_ViewEmptyText">There are no items to show in this view.</xsl:variable>
<table border="0" width="100%">
<tr>
<td class="ms-vb">
<xsl:value-of select="$dvt_ViewEmptyText" />
</td>
</tr>
</table>
</xsl:template>
其中 insert 模版和所有其他的东西一样都被跳过了。这的确不是我们所期望的。
修复它
这就是 SharePoint。里面有不止一种解决问题的方式。
有一点是肯定的,那就是我们必须在列表为空的情况下进入insert模式,使得SharePoint渲染出insert表单来。当点击insert链接时, dvt_1_form_insertmode 被设为1,我们可以以此进行判断。
<xsl:when test="$dvt_IsEmpty">
…改为…
<xsl:when test="$dvt_IsEmpty and not($dvt_1_form_insertmode = '1')">
现在,我们该如何显示insert 链接呢?
选择 1. 因为 empty 模版被调用,我们可以简单的把insert 链接同时添加到该模版中。在与Richard的 STP discussion 中, Matt Bramer 建议将该链接添加到dvt_ViewEmptyText下面。
<xsl:template name="dvt_1.empty">
<xsl:variable name="dvt_ViewEmptyText">There are no items to show in this view.</xsl:variable>
<table border="0" width="100%">
<tr>
<td class="ms-vb">
<xsl:value-of select="$dvt_ViewEmptyText" />
</td>
</tr>
<tr>
<td class="ms-vb">
<a href="javascript: {ddwrt:GenFireServerEvent('__cancel;dvt_1_form_insertmode={1}')}">insert</a>
</td>
</tr>
</table>
</xsl:template>
选择 2. 与其放在单独一行里,倒不如直接跟在dvt_ViewEmptyText后面。
<xsl:template name="dvt_1.empty">
<xsl:variable name="dvt_ViewEmptyText">There are no items to show in this view.</xsl:variable>
<table border="0" width="100%">
<tr>
<td class="ms-vb"><xsl:value-of select="$dvt_ViewEmptyText" /> Be the first to <a href="javascript: {ddwrt:GenFireServerEvent('__cancel;dvt_1_form_insertmode={1}')}">insert a list item</a>.
</td>
</tr>
</table>
</xsl:template>
选项 3. 另一种使得insert 模版被调用的方法是将其从xsl:choose 块中移出来,使其必然运行。
<xsl:choose>
<xsl:when test="$dvt_IsEmpty and not($dvt_1_form_insertmode = '1')">
<xsl:call-template name="dvt_1.empty" />
</xsl:when>
<xsl:otherwise><table border="0" width="100%" cellpadding="2" cellspacing="0">
<tr valign="top">
<xsl:if test="$dvt_1_automode = '1'" ddwrt:cf_ignore="1">
<th class="ms-vh" width="1%" nowrap=""></th>
</xsl:if>
<th class="ms-vh" nowrap="">Your Field</th>
.
.
</tr>
<xsl:if test="not($dvt_1_form_insertmode = '1')"><xsl:call-template name="dvt_1.body">
<xsl:with-param name="Rows" select="$Rows"/>
<xsl:with-param name="FirstRow" select="1" />
<xsl:with-param name="LastRow" select="$LastRow - $FirstRow + 1" />
</xsl:call-template></xsl:if>
<xsl:call-template name="dvt_1.rowinsert">
<xsl:with-param name="IsInsertMode">
<xsl:if test="$dvt_1_form_insertmode = '1'">1</xsl:if>
</xsl:with-param>
</xsl:call-template>
</table>
<xsl:call-template name="dvt_1.commandfooter">
.
.
</xsl:call-template></xsl:otherwise>
</xsl:choose>
…变成…
<xsl:choose>
<xsl:when test="$dvt_IsEmpty and not($dvt_1_form_insertmode = '1')">
<xsl:call-template name="dvt_1.empty" />
</xsl:when>
<xsl:otherwise><table border="0" width="100%" cellpadding="2" cellspacing="0">
<tr valign="top">
<xsl:if test="$dvt_1_automode = '1'" ddwrt:cf_ignore="1">
<th class="ms-vh" width="1%" nowrap=""></th>
</xsl:if>
<th class="ms-vh" nowrap="">Your Field</th>
.
.
</tr>
<xsl:if test="not($dvt_1_form_insertmode = '1')"><xsl:call-template name="dvt_1.body">
<xsl:with-param name="Rows" select="$Rows"/>
<xsl:with-param name="FirstRow" select="1" />
<xsl:with-param name="LastRow" select="$LastRow - $FirstRow + 1" />
</xsl:call-template></xsl:if>
</table>
<xsl:call-template name="dvt_1.commandfooter">
.
.
</xsl:call-template></xsl:otherwise>
</xsl:choose>
<table border="0" width="100%" cellpadding="2" cellspacing="0">
<xsl:call-template name="dvt_1.rowinsert">
<xsl:with-param name="IsInsertMode">
<xsl:if test="$dvt_1_form_insertmode = '1'">1</xsl:if>
</xsl:with-param>
</xsl:call-template>
</table>
注意其中第一段代码中的第 19-23行被移到了xsl:otherwise 块的外面(对应到第二段代码中的第26-30 行)。但它们还是被包裹到table标记中,因为这些标记是包含在xsl:otherwise 中的, dvt_1.rowinsert 模版中并没有这些标记。
选项 4. 就个人而言,我喜欢显示列标题,即使该列表是空的。因此,与上面几个选项类似,我们也可以把标题行从xsl:choose 中分离出来,这样他们就会出现在每一个场景中。 (我喜欢第2种方案,所以下面的代码假设你是在empty模版中处理insert 链接的输出。)
<xsl:choose>
<xsl:when test="$dvt_IsEmpty and not($dvt_1_form_insertmode = '1')">
<xsl:call-template name="dvt_1.empty" />
</xsl:when>
<xsl:otherwise><table border="0" width="100%" cellpadding="2" cellspacing="0">
<tr valign="top">
<xsl:if test="$dvt_1_automode = '1'" ddwrt:cf_ignore="1">
<th class="ms-vh" width="1%" nowrap=""></th>
</xsl:if>
<th class="ms-vh" nowrap="">Your Field</th>
.
.
</tr>
<xsl:if test="not($dvt_1_form_insertmode = '1')"><xsl:call-template name="dvt_1.body">
<xsl:with-param name="Rows" select="$Rows"/>
<xsl:with-param name="FirstRow" select="1" />
<xsl:with-param name="LastRow" select="$LastRow - $FirstRow + 1" />
</xsl:call-template></xsl:if>
<xsl:call-template name="dvt_1.rowinsert">
<xsl:with-param name="IsInsertMode">
<xsl:if test="$dvt_1_form_insertmode = '1'">1</xsl:if>
</xsl:with-param>
</xsl:call-template>
</table>
<xsl:call-template name="dvt_1.commandfooter">
.
.
</xsl:call-template></xsl:otherwise>
</xsl:choose>
…改为…
<table border="0" width="100%" cellpadding="2" cellspacing="0">
<tr valign="top">
<xsl:if test="$dvt_1_automode = '1'" ddwrt:cf_ignore="1">
<th class="ms-vh" width="1%" nowrap=""></th>
</xsl:if>
<th class="ms-vh" nowrap="">Your Field</th>
.
.
</tr>
<xsl:choose>
<xsl:when test="$dvt_IsEmpty and not($dvt_1_form_insertmode = '1')">
<xsl:call-template name="dvt_1.empty" />
</xsl:when>
<xsl:otherwise>
<xsl:if test="not($dvt_1_form_insertmode = '1')"><xsl:call-template name="dvt_1.body">
<xsl:with-param name="Rows" select="$Rows"/>
<xsl:with-param name="FirstRow" select="1" />
<xsl:with-param name="LastRow" select="$LastRow - $FirstRow + 1" />
</xsl:call-template></xsl:if>
<xsl:call-template name="dvt_1.rowinsert">
<xsl:with-param name="IsInsertMode">
<xsl:if test="$dvt_1_form_insertmode = '1'">1</xsl:if>
</xsl:with-param>
</xsl:call-template>
<xsl:call-template name="dvt_1.commandfooter">
.
.
</xsl:call-template></xsl:otherwise>
</xsl:choose>
</table>
注意,table标记并没有包含所有xsl:choose中的内容。所以我们需要快速的串一遍整个模版的调用过程,以保证其运转过程和我们预想的一致。
- dvt_1.empty 并没有在 table 里,但现在却包进来了。所以要移除其table 标记,以使得其中的tr落入主table中。
<xsl:template name="dvt_1.empty">
<xsl:variable name="dvt_ViewEmptyText">There are no items to show in this view.</xsl:variable>
<table border="0" width="100%">
<tr>
<td class="ms-vb"><xsl:value-of select="$dvt_ViewEmptyText" /> Be the first to <a href="javascript: {ddwrt:GenFireServerEvent('__cancel;dvt_1_form_insertmode={1}')}">insert a list item</a>. </td>
</tr>
</table>
</xsl:template>
<xsl:template name="dvt_1.empty">
<xsl:variable name="dvt_ViewEmptyText">There are no items to show in this view.</xsl:variable>
<tr>
<td class="ms-vb" colspan="99"><xsl:value-of select="$dvt_ViewEmptyText" /> Be the first to <a href="javascript: {ddwrt:GenFireServerEvent('__cancel;dvt_1_form_insertmode={1}')}">insert a list item</a>. </td>
</tr>
</xsl:template>
<xsl:template name="dvt_1.commandfooter">
<xsl:param name="FirstRow" />
<xsl:param name="LastRow" />
.
.
<table cellspacing="0" cellpadding="4" border="0" width="100%">
<tr>
<xsl:if test="$ShowFormActions = '1'">
<xsl:call-template name="dvt_1.formactions" />
</xsl:if>
.
. </tr>
</table>
</xsl:template>
…改为…
<xsl:template name="dvt_1.commandfooter">
<xsl:param name="FirstRow" />
<xsl:param name="LastRow" />
.
.
<tr>
<xsl:if test="$ShowFormActions = '1'">
<xsl:call-template name="dvt_1.formactions" />
</xsl:if>
.
. </tr>
</xsl:template>
- 选项 4: 包含列头(也是用的选项2的方法输出的insert链接)
- 至此,我们通过少许更改,修正了SharePoint默认的功能,使得在空列表的情况下可以显示插入链接。
参考资料
SharePoint:Extending the DVWP - Bonus:Fixing the Insert Form Action When "No Mathcing Items"