背景
我正在构建一个相当简单的库存和订单管理系统。在所有此类系统中,您最终会看到一个订单(在我的例子中是 PurchaseOrder
)有一个订单行项目列表(在我的应用程序中称为 LineItem
)。
后端代码
现在,我的 ViewModel 直接映射到我的实体,所以我的 PurchaseOrderVm
看起来像这样:
public class PurchaseOrderVm
{
public PurchaseOrderVm()
{
LineItems = new List<LineItemVm>();
}
public int Id { get; set; }
public DateTime Date { get; set; }
public PurchaseOrderStatus Status { get; set; }
[Display(Name = "Shipping Cost")]
public decimal ShippingCost { get; set; }
[Display(Name = "Import VAT")]
public decimal Vat { get; set; }
[Display(Name = "Import Duty")]
public decimal ImportDuty { get; set; }
[Display(Name = "Supplier Id")]
public string SupplierId { get; set; }
[Display(Name = "Supplier")]
public string SupplierName { get; set; }
public IEnumerable<LineItemVm> LineItems { get; set; }
public List<SelectListItem> Suppliers { get; set; }
public string ButtonText => Id != 0 ? "Update Purchase Order" : "Add Purchase Order";
}
我的 LineItemVm
看起来像这样:
public class LineItemVm
{
public int Id { get; set; }
public int Quantity { get; set; }
public int OrderId { get; set; }
[Display(Name = "Product")]
public int ProductId { get; set; }
}
前端/用户体验
我知道我想要构建的体验类型,但我不确定自 MVC3 以来这方面有多少进展。我想构建这个(突出显示重要性的位):
尝试过
我只能通过这样做才能使它对单个 LineItem
起作用:
<input class="form-control " id="ProductId" name="LineItems[0].ProductId" type="number" value="">
但显然这与动态相反。允许用户根据需要添加和删除项目并让表单和模型 Binder 仍然有效的最干净的方法是什么?
最佳答案
您基本上可以使用客户端 javascript 代码构建新的订单项行。您需要确保输入元素(产品和数量)具有正确的名称属性值,以便模型绑定(bind)能够正常工作。
要使模型绑定(bind)起作用,您的输入名称应类似于 LineItems[0].ProductId
、LineItems[0].Quantity
(对于第 1 行)、 LineItems[1].ProductId
、LineItems[1].Quantity
(对于第 2 行)等
由于每一行都需要一个产品下拉列表,我们将在页面中保留一个选择元素,每当我们需要添加一个新的订单项行时,我们将克隆该元素并将其用于该行。我们将使用 css 隐藏此选择,因为这更像是供我们根据需要克隆的查找数据。
@model PurchaseOrderVm
@using (Html.BeginForm("Add","Product"))
{
@Html.LabelFor(f=>f.SupplierName)
@Html.TextBoxFor(s=>s.SupplierName)
<label>Order Products</label>
<table id="items">
<thead>
<tr>
<th>Product</th><th>Quantity</th>
</tr>
</thead>
<tbody></tbody>
</table>
<button id="addProduct">Add Product</button>
<input type="submit"/>
}
<SELECT Id="Products">
<option value="1">Product 1</option>
<option value="2">Product 2</option>
<option value="3">Product 3</option>
</SELECT>
我对产品选择元素的内容进行了硬编码。您可以使用 Html.DropDownListFor
/Html.DropDownList
辅助方法将其替换为来自您的服务器的数据
现在,监听 addProduct 按钮上的 click 事件,创建一个新行,添加 select 元素和 quantity input 元素并将其追加到表中。单击删除链接时,只需删除最近的行并重新组织我们创建的其他输入的名称值。
$(function () {
$('#addProduct').click(function (e) {
e.preventDefault();
var rowIndex = $("#items>tbody>tr").length;
var newRow = $("<tr/>");
var p = $("#Products").clone()
.attr("name", "LineItems[" + rowIndex + "].ProductId")
.data("n","LineItems[r].ProductId").show();
$("<td/>").append(p).appendTo(newRow);
var q = $("<input />")
.attr("name", "LineItems[" + rowIndex + "].Quantity")
.data("n","LineItems[r].Quantity");
var r = $("<a/>").attr("class", "remove").text("Remove");
$("<td/>").append(q).append(r).appendTo(newRow);
$("#items>tbody").append(newRow);
});
$("#items").on("click",".remove",function(e) {
e.preventDefault();
$(this).closest("tr").remove();
//Re organize the input names
$("#items>tbody>tr").each(function (rowIndex, row) {
$(row).find("input").each(function(a, b) {
var newName = $(b).data("n").replace("r", rowIndex);
$(b).attr("name", newName);
});
});
});
});
现在当提交表单时,您的 View 模型的 LineItems 属性将具有正确的数据。
[HttpPost]
public ActionResult Add(PurchaseOrderVm model)
{
// check model.LineItems
// to do : Return something
}
关于javascript - 能够动态地将子列表项添加到 ViewModel,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40575124/