目录
介绍
在第3部分中,我们已经了解了如何更新包含要添加或删除元素的列表的页面。我们基本上有一个将项目添加到列表(AddPassenger)的操作方法和一个从列表(RemovePassenger)中删除项目的动作方法。
这两种操作方法都需要往返于服务器,使用新乘客的额外字段或删除一组字段重新呈现列表。
在第3b部分的文章中,我们将看到如何在不往返到服务器的情况下实现类似的行为。为此,您至少需要Sircl的2.4.0版。如果偏离以前的代码,请确保将Sircl升级到此版本或最新版本。
局限性
有一些限制需要考虑。最主要的是,除了用户在不同列表元素上的输入之外,不应有动态内容。
我们这里的原始航班登记表确实存在问题:列表显示乘客编号(1、2、3......这是由服务器计算的动态内容。如果我们消除到服务器的往返,我们也会消除呈现服务器计算内容的功能。因此,我们将用“Passenger”替换标签“Passenger 1”。
我们遇到的另一个问题与ASP.NET(MVC)模型绑定有关。与列表中对象属性的绑定要求字段名包含元素的索引(0, 1, 2,...),同样,这是服务器计算的动态内容。
模型绑定的确切行为和语法取决于您使用的服务器端技术,并且超出了Sircl的范围。但这是一个值得关注的点。
在我们的示例中,我已将乘客列表从Passenger对象列表重构为两个字符串列表(一个列表用于名字,一个列表用于姓氏)。这并不理想,但这不是本文的主题......
在介绍了这些限制之后,现在让我们专注于解决方案。
删除项
在HTML中,从列表中删除项目(在航班上的乘客中)非常容易:只需剪切代表该列表项的HTML代码即可。为了删除HTML以响应点击事件,在Sircl中,我们使用了onclick-remove event-action属性。value属性是要删除的元素的CSS选择器。
现在,鉴于所有列表条目都具有相同的HTML,如何识别要删除的元素?为每个条目提供唯一的ID将再次需要服务器端计算。
解决方案在于使用相对CSS选择器。这是Sircl的补充。它基本上允许通过元素相对于引用它们的元素的相对位置来选择元素。这是一项在Sircl中有意义的功能,Sircl是一个使用内联CSS选择器的HTML扩展。
使用相对CSS选择器,用于删除与当前乘客条目匹配的HTML代码的按钮可以引用它所属的父列表条目元素。
请考虑以下代码,该代码表示列表中的乘客条目:
<div class="mb-3">
<span class="float-end">
<button type="button" class="btn btn-sm btn-light onclick-setchanged" onclick-remove="<DIV" title="Remove this passenger">×</button>
</span>
<p><b>Passenger:</b></p>
<div class="row">
<div class="col">
<input name="Flight.PassengerFirstNames" value="" class="form-control" placeholder="First name" required>
</div>
<div class="col">
<input name="Flight.PassengerLastNames" value="" class="form-control" placeholder="Last name" required>
</div>
</div>
</div>
该onclick-remove属性的值为“<DIV”。这是一个相对的CSS选择器:“<”箭头表示从当前元素向上查看剩余表达式的最接近匹配项。换言之,这与最接近的父级DIV元素匹配。这是用户单击“x”按钮时要删除的元素。
变化检测
在上一篇文章(第3部分)中,我们添加了一个更改检测功能,允许我们的表单知道数据何时发生更改。它依赖于更改事件和服务器在模型上设置HasChang es属性。
但是,通过单击按钮删除HTML不会触发窗体上的更改事件。我们刚刚取消了服务器往返。然而,形式发生了变化:少了一名乘客。
然而,这很容易解决:为了让窗体在单击“x”按钮时意识到变化,我们可以在按钮上添加onclick-setchanged event-action类。
添加项目
在没有服务器往返的情况下将项目添加到列表中有点棘手。当要将乘客添加到列表中时,需要将HTML代码添加到表单中。当这个HTML代码不是来自服务器时,它将来自哪里?
为了解决这个问题,我们可以让服务器提前准备HTML代码,并在初始渲染时将其以形式呈现。我们可以将代码放在一个TEMPLATE元素中。这会产生三个后果:
- HTML代码在客户端可用,无需往返服务器即可获取
- TEMPLATE元素中的HTML代码不会呈现
- 即使HTML代码位于FORM元素内部并且可能包含必填字段(或其他验证限制),但位于TEMPLATE元素内部意味着其验证属性将被忽略
因此,我们可以安全地使用一个TEMPLATE元素,并且该元素是在表单内部还是外部都无关紧要。这很好,因为这意味着我们可以将模板放在要使用的位置旁边。
在下面的代码中,模板位于引用它的按钮之后。
但是现在,我们如何使用模板将乘客添加到列表中?
我们仍然有一个按钮来添加乘客。只是这一次,该按钮不是导致服务器往返的提交按钮,而仅仅是保存Sircl事件操作的按钮。在该按钮上,您需要的是一个事件操作,用于将模板的内容附加到乘客列表中。因此,我们需要引用(即使用(相对或绝对)CSS选择器)模板和列表。不幸的是,HTML属性只能有一个值,为了简单起见,Sircl遵守了这个限制。
所以我们需要大部头的诡计......
如果我们将event-action属性放在模板本身上,我们只需要引用列表,因为模板是已知的:它是当前元素。因此,在模板上,我们添加了onclick-appendto="#passengerlist"属性。这表示将模板的内容附加到id为“passengerlist”的元素的内容。
但是TEMPLATE元素是隐藏的,无法单击。但是,这并不意味着模板无法处理点击事件。所以现在我们只需要向模板发送一个点击事件:我们只需要再引用一个项目:模板。现在我们可以在按钮上使用onclick-click="#passengertemplate" event-action属性。这告诉按钮,当它被单击时,它应该在id为“passengertemplate”: 模板的元素上触发一个点击事件。
我们知道,当模板获得点击事件时,它会将其内容附加到乘客列表的内容中。
我们所做的称为链接事件操作,它通常涉及点击事件,因为大多数Sircl操作都可以从点击事件触发。
此外,在“添加”按钮上,我们还添加了该onclick-setchanged类,以便窗体知道它已更改。
最后但并非最不重要的一点是,在添加乘客时,我们可以将焦点设置在新添加线路的“名字”字段上。这可以通过“添加乘客”按钮上的onclick-focus属性来完成,给定一个CSS选择器来选择最后一个乘客的名字字段,如下所示:
onclick-focus="#passengerlist > *:last-child INPUT[name='Flight.PassengerFirstNames']"
但有一种更简单的方法:在模板的“名字”字段上添加autofocus属性。每当复制模板时,autofocus都会被触发。
乘客名单现在如下所示:
<fieldset>
<legend>Passengers</legend>
<div id="passengerlist">
@for (int i = 0; i < Model.Flight.PassengerFirstNames.Count; i++)
{
<div class="mb-3">
<span class="float-end">
<button type="button" class="btn btn-sm btn-light onclick-setchanged" onclick-remove="<DIV" title="Remove this passenger">×</button>
</span>
<p><b>Passenger:</b></p>
<div class="row">
<div class="col">
<input name="Flight.PassengerFirstNames" value="@(Model.Flight.PassengerFirstNames[i])" class="form-control" placeholder="First name" required>
</div>
<div class="col">
<input name="Flight.PassengerLastNames" value="@(Model.Flight.PassengerLastNames[i])" class="form-control" placeholder="Last name" required>
</div>
</div>
</div>
}
</div>
<div class="mb-3">
<button type="button" class="btn btn-sm btn-secondary onclick-setchanged" onclick-click="#passengertemplate">
Add passenger
</button>
</div>
<template id="passengertemplate" onclick-appendto="#passengerlist" onload-copyto="@(Model.Flight == null ? "#passengerlist" : null)">
<div class="mb-3">
<span class="float-end">
<button type="button" class="btn btn-sm btn-light onclick-setchanged" onclick-remove="<DIV" title="Remove this passenger">×</button>
</span>
<p><b>Passenger:</b></p>
<div class="row">
<div class="col">
<input name="Flight.PassengerFirstNames" value="" class="form-control" placeholder="First name" required autofocus>
</div>
<div class="col">
<input name="Flight.PassengerLastNames" value="" class="form-control" placeholder="Last name" required>
</div>
</div>
</div>
</template>
</fieldset>
客户端验证
限制服务器往返的另一种方法是尽可能实现客户端验证。在此示例中,我使用了HTML5表单验证的required属性,以确保在必填字段为空时不会发生服务器往返。
服务器端验证仍然有效,例如,将确保预订至少有一名通行证。
控制器
可以下载此版本的完整源代码。看一下HomeController,并与第3部分的版本进行比较:看看AddPassenger和RemovePassenger操作方法是如何消失的,以及代码是如何更改以应对重构的乘客列表数据的。
https://www.codeproject.com/Articles/5379741/Build-Rich-Web-Apps-with-ASP-NET-Core-and-Sircl-4