DropdownListFor on更新更新并发布父对象

时间:2014-11-12 00:55:11

标签: c# jquery asp.net asp.net-mvc-4 razor

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; }
}

1 个答案:

答案 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循环中使用它或指定模板名称(按原样执行操作会覆盖默认行为,最终会出现重复的nameid属性)

这会将表格提交给您的控制器

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,允许用户修改数据,检查数据,并做出有意识的决定然后保存。