3

我过去做过这个,我可能不得不再做一次,但在我做之前,我想把它扔在那里,看看人们如何处理它。

剃刀视图:

<ul>
   @Html.EditorFor(model => model.Questions)
</ul>

可以产生:

<ul>
   <li><input type="text" id="Questions_0__Title" name="Questions[0].Title" value="hi"/></li>
   <li><input type="text" id="Questions_1__Title" name="Questions[1].Title" value="hi2"/></li>
<ul>

非常简单。

现在,我需要允许用户添加、编辑或删除任何这些“问题”。

对于编辑来说,这很容易——不需要工作。但是对于添加/删除,如果我用 jQuery 来做,我需要对生成“id”和“name”属性的方式很聪明(例如,计算有多少,加 1,等等),对于删除我会“重新渲染”一些元素(例如,如果一个被删除,我需要将所有后续元素的“id”和“name”属性更改为-1)。

所有这一切都是因为它是一种形式,并且需要模型绑定。

这对我来说太麻烦了,所以在过去,我使用表单上的任何内容对服务器进行了 AJAX 调用,然后在控制器中添加/删除元素,并呈现部分视图。这样所有元素都“准备好被模型绑定”。我仍然需要一点,但 jQuery,但远不及那么多。

有没有人找到更好的方法?

4

3 回答 3

3

我也不得不处理完全相同的情况。我想出的最简单的解决方案是使用包装模型,类似于:

public class QuestionListModel
{
    public IList<QuestionModel> Questions { get; set; }

    public IList<QuestionModel> Template
    {
        get
        {
            return new List<QuestionModel>
                       {
                           new QuestionModel {/* defaults for new question */}
                       };
        }
    }

    public QuestionListModel()
    {
        Questions = new List<QuestionModel>();
    }
}

重要的部分是拥有Template属性,它也是IEnumerable<T>与您想要的实际模型相同的类型。这样,MVC 将为您完成自动编号。所以使用这个模型,你得到的是你的集合,带有“Questions_0__Title”编号,你还得到一个带有“Template_0__Title”命名的模板行。

我将我的模板行从 UI 中隐藏起来,并在添加新行时使用它。

在 razor 中,您可以像绑定常规问题一样绑定模板,唯一的区别是它会在隐藏的<div>. 您还需要将Count您的问题列表绑定到隐藏字段。就我而言,我有一个问题的编辑器模板,它<div>使用特定的选择器将其呈现在内部,以便以后轻松访问。所以你最终得到的是类似于:

<div class="templateContainer">
  <div class="question">
    [Template]
  </div>
</div>
<div class="items">
  [for each of your items]
  <div class="question">
    [Question]
  </div>
</div>

添加行时,诀窍是使用 javascript:

  1. 从隐藏字段中获取计数,将其递增。

        var counter = $("#QuestionsListCount");
        var count = parseInt(counter.val());
        count++;
    
  2. 获取整个模板块,克隆它(.clone(true)例如使用 jquery),将其命名为唯一的东西(例如使用步骤 1 中的计数器值),并将其附加到您的问题所在的部分。

        var template = $("#templateContainer");
        var newItem = template.clone(true);
        var newId = "item_" + count;
        var newQuestion = newItem.children().first();
        newQuestion.attr("id", newId);
        newQuestion.appendTo('#items');
    
  3. 对于每个项目,例如新附加块中的输入(您可以使用分配给它的新 id 找到它),您将 ids => "Template_0" 替换为 "Questions__count from step 2",并且 names => "Template[ 0]”和“问题[从步骤 2 开始计数]”。

        $("#" + newId + " :input").each(function (index, input) {
            input.id = input.id.replace("Template_0", "Questions_" + (count - 1));
            input.name = input.name.replace("Template[0]", "Questions[" + (count - 1) + "]");
        });
    
  4. 更新计数器的隐藏字段=>counter.val(count);

  5. ...
  6. 利润!

现在,关于删除,我这样做的方式是,我的 ViewModel 实际上有一个IsDeleted标志,我也将它绑定到问题编辑器模板中的隐藏字段。这样,删除就像隐藏特定的问题一样简单(问题选择器很方便),然后将该IsDeleted字段设置为 true。当您通过默认模型绑定器取回整个列表时,您需要丢弃所有已删除的列表(或发出实际删除,具体取决于您的后端数据模型)。

通过这种方式,我避免了处理识别已删除项目的单独方法,也避免了重新编号 UI 中的所有项目。此外,您还可以让服务器端代码确定删除时实际应该发生的情况(例如验证或撤消操作)。

这是一篇很长的文章,但实现并不是那么困难(或长),并且可以很容易地以通用方式重用。

干杯!

于 2012-05-14T12:56:37.113 回答
0

我假设这些东西有一个主键 Id 字段?

不确定这是最好的方法,但我有一个隐藏的输入字段,例如,deletedIds。在渲染输入元素时,我添加了一个属性,例如:

data-rowId='@Model.Id'

然后当他们单击删除时,我将 id 添加到隐藏列表中,或者我使用 css 类将该行标记为已删除,并在提交之前最后将它们铲起,例如:

var deletedIds = [];
$('myselector').each(function(){ deletedIds.push($(this).attr('data-rowId'));});
$('deletedIds').val(deletedIds.join(','));

然后将字符串拆分回服务器上的数组。

如果您在位置上处理它们并且您确定服务器上的序列不能在请求之间更改,那么我将使用相同的方法,但使用数组的索引而不是 id。

于 2012-05-14T09:34:04.127 回答
-5

所以我想到了一个更好的解决方案,根本不需要太多的 JS。

  1. 在我的控制器中,我创建了一个包含大约 100 个元素的模型。全部为空,但“新”了起来,并带有“隐藏”的 css 类
  2. 根据填充了多少现有值,我更新了元素,包括删除“隐藏”的 css 类。
  3. 然后我使用 CSS 来隐藏带有“隐藏”css 类的那些。
  4. 当我单击“添加更多”时,我从前 3 个元素中删除了“隐藏”类。
  5. 当我单击“删除”时,我只需重新添加“隐藏”类。

简单,我以前怎么没想到。

于 2012-05-25T06:50:21.113 回答