如何动态创建/删除元素并允许模型绑定启动?

时间:2012-05-14 07:24:26

标签: jquery asp.net-mvc-3 razor model-binding

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

Razor观点:

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

非常简单。

现在,我需要允许用户添加,修改或删除其中任何一个&#34;问题&#34;。

对于编辑,这很简单 - 没有工作要求。但是对于添加/删除,如果我是用jQuery做的话,我需要聪明一点我生成&#34; id&#34;和&#34;名称&#34;属性(例如,计算有多少个,加1等),对于删除,我会重新渲染&#34;一些元素(例如,如果删除了一个元素,我需要将&#34; id&#34;和#34; name&#34;属性更改为-1以用于所有后续元素。)

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

对我来说太毛茸茸了,所以过去,我已经用表格中的任何内容对服务器进行了AJAX调用,然后在控制器中添加/删除元素,并渲染一个局部视图。这样,所有元素都准备好与模型绑定&#34;。我仍然需要一点但是jQuery,但远不及那么多。

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

3 个答案:

答案 0 :(得分: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>();
    }
}

重要的是,具有模板属性,该属性也是与您想要的实际模型相同类型的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. 获取整个模板块,克隆它(例如使用jquery的.clone(true)),将其命名为唯一的东西(例如,使用步骤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 =&gt; “Template_0”带有“来自第2步的问题_count”,而名称=&gt; “Template [0]”with“Questions [count from step 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 =&gt;的隐藏字段counter.val(count);

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

    这样我就可以避免处理识别已删除项目的各种方法,还可以重新编号UI中的所有项目。此外,您还可以获得服务器端代码确定删除时应该实际发生的事情(例如验证或撤消操作)的优势。

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

    干杯!

答案 1 :(得分: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。

答案 2 :(得分:-4)

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

  1. 在我的控制器中,我创建了一个包含大约100个元素的模型。全部为空,但“新近”,并且有一个“隐藏”的CSS类
  2. 根据填写的现有值的数量,我更新元素,包括删除“隐藏”的css类。
  3. 然后我使用CSS来隐藏具有“隐藏”css类的那些。
  4. 当我点击“添加更多”时,我会从前3个元素中删除“隐藏”类。
  5. 当我点击“删除”时,我只需将“隐藏”类添加回来。
  6. 很简单,为什么我之前没有想到这一点。