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

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

剃刀观点:

    @Html.EditorFor(model => model.Questions)

这可能会产生:

 

很简单。

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

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

所有这一切都是因为它是一种forms,它需要模型约束。

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

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

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

 public class QuestionListModel { public IList Questions { get; set; } public IList Template { get { return new List { new QuestionModel {/* defaults for new question */} }; } } public QuestionListModel() { Questions = new List(); } } 

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

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

在剃刀中,你绑定模板就像绑定一个常规问题一样,唯一的区别是它会隐藏在

。 您还需要将问题列表的Count绑定到隐藏字段。 在我的例子中,我有一个问题的编辑器模板,它在

使用特定的选择器进行渲染,以便以后轻松访问。 所以你最终得到的东西类似于:

 
[Template]
[for each of your items]
[Question]

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

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

      var counter = $("#QuestionsListCount"); var count = parseInt(counter.val()); count++; 
  2. 取整个.clone(true) ,克隆它(例如使用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 =>“Template_0”替换为“来自步骤2的Questions__count”,并将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 => counter.val(count);的隐藏字段counter.val(count);

  5. 利润!

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

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

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

干杯!

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

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

 data-rowId='@Model.Id' 

然后当他们点击删除时,我将id添加到隐藏列表中,或者我将该行标记为已删除的css类,并在提交之前将其挖出,例如:

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

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

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

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

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

容易,为什么我之前没有想到这一点。