Man ..而且我认为我的项目很简单......
这是我的问题:
我正在为使用MVC 4的项目经理制作一个清单,我为每个任务项都有一个编辑模板。在每个任务项中,我有一个DropDownListFor,用户可以使用它来选择任务状态('完成','进行'等)
当用户更改任务状态时,脚本会进入并添加完成日期(如果更改为已完成状态)。我还希望它使用我的HttpPost方法“UpdateTaskState”更新并保存数据库中的任务更改。
作为一个附带问题,这是实现我的目标的正确和正确的方法吗?我很乐意拥有它,以便每次更改时都不需要刷新任务视图。
我的任务编辑器模板:
@model Models.task
@Html.HiddenFor(model => model.task_id, new { @id = "taskID" })
@Html.HiddenFor(model => model.task_name)
@Html.HiddenFor(model => model.task_desc)
@Html.HiddenFor(model => model.user_completed,new {@id = "UserCompleted" })
@Html.HiddenFor(model => model.completion_date, new { @id = "CompletionDate" })
<table style="width:80%">
<tr style="width:60%">
<th colspan="3">@Html.DisplayFor(model => model.task_name)</th>
<th align="left">
@Html.DropDownListFor(model => model.task_state_id,
new SelectList((System.Collections.IEnumerable)ViewData["TaskStates"], "task_state_id", "state"),
new { @Id = "ddlState" })
</th>
<td>
@Html.EditorFor(model => model.notes, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.notes, "", new { @class = "text-danger" })
</td>
</tr>
<tr>
<td colspan="3">@Html.DisplayFor(model => model.task_desc)</td>
<td>@Html.Label("Completed by ")@Html.DisplayFor(model => model.user_completed)@Html.Label(", ")@Html.DisplayFor(model => model.completion_date)</td>
</tr>
</table>
我对脚本的主要观点:
@model NSCEngineering.Models.NRIAndCategoriesViewModel
@{ ViewBag.Title = "Details";}
<h2>Details</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
<div class="form-horizontal">
<hr />
@Html.HiddenFor(item => Model.TaskStates)
@Html.HiddenFor(item => Model.id, new{ @id ="NriID" })
<div class="NRISummary">
@Html.LabelFor(item => Model.Summary )
@Html.DisplayFor(item => Model.Summary, "_nri")
</div>
<hr />
<div class="tasks">
@Html.LabelFor(item => Model.Tasks )
@for (int i = 0; i < Model.Tasks.Count(); i++ )
{
@Html.EditorFor(item => Model.Tasks[i], "_task", new{@id = "taskItem"})
}
</div>
</div>
}
<p>
@Html.ActionLink("Back to List", "Index")
</p>
@section Scripts{
<script type="text/javascript">
$(this.document).ready(function () {
$('#ddlState').change(function () //wire up on change event of the 'country' dropdownlist
{
var selection = $('#ddlState').val(); //get the selection made in the dropdownlist
if (selection == '4') {
$('#CompletionDate').val('@DateTime.Now.Date');
}
var completion = $('#CompletionDate').val();
alert(completion);
alert($('#taskID').val());
var url = '@Url.Action("UpdateTaskState", "nris")';
$.ajax({
url: url,
type: 'POST',
data: $('#taskItem').serializeArray(),
contentType: "application/json; charset=utf-8",
success: function (e) {
$("#message").html("Success");
},
error: function (xhr, status, error) {
// Show the error
$('#message').html(xhr.responseText);
}
})
})
});
}
UPDATE1
@model NSCEngineering.Models.NRIAndCategoriesViewModel
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
<p>
@Html.ActionLink("Back to List", "Index")
</p>
</div>
</div>
@Html.LabelFor(item => Model.nriSummary)
@Html.DisplayFor(item => Model.nriSummary, "_nri") //just displays project summary details
<div class="form-group">
@for (int i = 0; i < Model.Categories.Count; i++)
{
<div style="border:solid ; border-width:1px">
<table class="category" style="width:100%">
<thead style="font-size:large ; background-color:black">
@Html.DisplayFor(c => c.Categories[i].category_name)
</thead>
<tbody>
<tr>
@renderTasksControl(Model.Categories[i].tasks, Model.StateList)
</tr>
@for (int k = 0; k < Model.Categories[i].Subcategories.Count; k++)
{
<tr>
<td>
<table>
<thead style="font-size:larger">
@Html.DisplayFor(c => c.Categories[i].Subcategories[k].category_name)
@renderCategoryPercentage(Model.Categories[i].Subcategories[k].tasks)
</thead>
<tbody>
@renderTasksControl(Model.Categories[i].Subcategories[k].tasks, Model.StateList)
</tbody>
</table>
</td>
</tr>
}
</tbody>
</table>
</div>
}
</div>
}
@helper renderTasksControl(IList<NSCEngineering.Models.task> TaskList, SelectList states) {
for (int i = 0; i < TaskList.Count; i++) {
<div class="task">
@Html.DisplayFor(model => TaskList[i].task_name)
@Html.DropDownListFor(model => TaskList[i].task_state_id, states, new { @class = "ddlState" })
@Html.HiddenFor(model => TaskList[i].task_id)
@Html.HiddenFor(model => TaskList[i].nri_id)
@Html.DisplayFor(model => TaskList[i].completion_date, new { @class = "date" })
@Html.HiddenFor(model => TaskList[i].category_id)
@*@Html.EditorFor(model => Task.notes)
@Html.ValidationMessageFor(model => Task.notes, "", new { @class = "text-danger" })*@
@Html.DisplayFor(model => TaskList[i].task_desc)
@Html.DisplayFor(model => TaskList[i].user_completed)
</div>
}
}
@helper renderCategoryPercentage(IList<NSCEngineering.Models.task> taskList) {
int sum = 0;
int total = 0;
var percentage = "";
foreach (NSCEngineering.Models.task task in taskList)
{
if (task.task_state_id != -1) {
sum += task.task_state_id;
}
total += 3;
}
if (total != 0){
var ratio = ((double)sum / total);
percentage = string.Format("{0:0.0%}", ratio);
}
else {
percentage = "Invalid Value";
}
<text> @sum + @total </text>
<br />
@percentage
};
@section Scripts{
<script type="text/javascript">
//this is me trying to get the completiondate to programatically update
$(this.document).ready(function () {
$('.ddlState').change(function () {
if ($('.ddlState').val() == 3) {
date = '@DateTime.Now.Date';
var task = $(this).closest('.task');
var completionDate = task.children('.date');
task.children($('.date')).text(date);
alert(date);
alert(task.children($('.date')).text());
}
else {
$('.completion_date').val(null);
}
// location.reload(true);
})
});
</script>
}
public partial class category
{
public category()
{
tasks = new List<task>();
Subcategories = new List<category>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public byte category_id { get; set; }
[Required]
public string category_name { get; set; }
public byte? parent_category_id { get; set; }
[ForeignKey("parent_category_id")]
public category ParentCategory { get; set; }
[InverseProperty("ParentCategory")]
public virtual IList<category> Subcategories { get; set; }
public virtual IList<task> tasks { get; set; }
}
答案 0 :(得分:1)
首先创建一个视图模型,表示您想要显示/编辑的内容,避免通过线路发送和接收不必要的数据
查看模型
public class TaskViewModel
{
public int ID { get; set; }
public string Name { get; set; }
public int State { get; set; }
public string Description { get; set; }
public string Notes { get; set; }
}
public class NRIAndCategoriesViewModel
{
public List<TaskViewModel> Tasks { get; set; }
public SelectList StateList { get; set; }
// other properties of the model to display in the main view
}
在您的GET方法中,初始化NRIAndCategoriesViewModel
的实例,将任务的属性映射到TaskViewModel
的集合并分配StateList
(创建SelectList
一次而是通过ViewData
传递集合并为每个任务构建SelectList
。
查看
@model NSCEngineering.Models.NRIAndCategoriesViewModel
@using (Html.BeginForm())
{
...
for (int i = 0; i < Model.Tasks.Count; i++)
{
<div class="task">
@Html.HiddenFor(m => m.Tasks[i].ID, new { @class = "id" })
@Html.DisplayFor(m => m.Tasks[i].Name)
@Html.DropDownListFor(m => m.Tasks[i].State, Model.StateList, new { @class = "state" })
@Html.TextAreaFor(m => m.Tasks[i].Notes, new { @class = "notes" })
@Html.DisplayFor(m => m.Tasks[i].Description)
</div>
}
<input type="submit" value="Save" />
}
请注意,您可以EditorTemplate
使用TaskViewModel
,但正确使用
@Html.EditorFor(m => m.Tasks)
不要在for循环中使用它或指定模板名称(按原样执行操作会覆盖默认行为,最终会出现重复的name
和id
属性)
这会将表格提交给您的控制器
public ActionResult UpdateTaskState(NRIAndCategoriesViewModel viewModel)
所以你可以(1)检查验证错误并在必要时返回视图,(2)从数据库中获取数据模型,(3)循环TaskViewModel
中的每个viewModel.Tasks
并更新数据模型中的相应属性,包括当前日期和用户ID,(4)保存数据模型,最后(5)重定向到Index
视图。
您似乎过分担心这会导致数据库调用过多。如果你使用EF,那么只保存已修改的任务(不是全部,所以最终结果是相同的 - 实际上,如果用户选择了错误的State
则更糟糕,然后更正它因为正在进行额外的通话)。由于发送和发布大量无用的数据,因为它的影响远远大于数据库调用,因此它有点讽刺。但是,如果您确实希望一次仅回发一个任务(并因此失去诸如客户端不显眼的验证之类的好处),那么您可以使用以下脚本
var url = '@Url.Action("UpdateTaskState", "nris")';
$('.State').change(function() {
var state = $(this).val();
var task = $(this).closest('.task');
var id = task.children('.id').val();
var notes = task.children('.notes').val();
$.post(url, {ID: id, State: state, Notes: notes }, function(response) {
// do something with the response
});
});
然后将控制器更改为
public ActionResult UpdateTaskState(TaskViewModel viewModel)
请注意,如果您在表格行中渲染控件,则需要修改选择器以适合您的表格布局
但从用户角度考虑这一点。用户不希望仅通过从下拉列表中选择项目来保存数据。如果用户从下拉列表中选择一个项目,然后修改Notes
,则永远不会保存注释,用户也不会更聪明。它糟糕的用户界面设计,非常令人困惑!坚持标准submit
,允许用户修改数据,检查数据,并做出有意识的决定然后保存。