【问题标题】:How to dynamically create/remove elements and allow model binding to kick in?如何动态创建/删除元素并允许模型绑定启动?
【发布时间】:2012-05-14 07:24:25
【问题描述】:

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

剃刀视图:

<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,但还远远不够。

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

【问题讨论】:

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


    【解决方案1】:

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

    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&lt;T&gt;。这样,MVC 将为您完成自动编号。因此,使用此模型,您将获得带有“Questions_0__Title”编号的集合,并且您还会获得一个带有“Template_0__Title”命名的模板行。

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

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

    <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 => "Template_0" 替换为 "Questions__count from step 2",并且 names => “模板[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 中的所有项目重新编号。此外,您还可以让服务器端代码确定删除时实际应该发生的情况(例如验证或撤消操作)。

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

    干杯!

    【讨论】:

    • 不错的一个。尽管如此,它还是相当复杂的,因为您必须处理 id/name 属性、跟踪计数器等。不知道为什么没有人创建一个 jQuery/MVC 插件来为我们做所有这些事情。
    • 是的,确实如此。关于计数器,您实际上并不需要它们,因为您可以通过使用“问题”选择器计算所有子项来确定它。但我同意,这仍然让你不得不担心 ID 和名称。不过,有一种方法可以解决这个问题,即为其创建一个自定义 ModelBinder。在这种情况下,您可以生成您喜欢的任何 html(例如没有子索引)并编写活页夹以将其提取回模型。
    • 是的,我可能会使用自定义模型活页夹。如果我必须做额外的工作,我宁愿在服务器上做而不是在客户端上做
    • @PabloRomeo 你能告诉我如何把这个例子和我更新的代码联系起来吗? stackoverflow.com/questions/11379958/…
    • 有用的答案。我创建了一个显示它工作的小提琴。 dotnetfiddle.net/8lc92N
    【解决方案2】:

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

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

    data-rowId='@Model.Id'
    

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

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

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

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

    【讨论】:

    • 谢谢,但我认为这行不通。要启动 MVC 模型绑定,表单字段名称需要与模型属性匹配。因此,“将字符串拆分回数组”不是模型绑定,也不是默认的模型绑定器(您需要一个自定义绑定器)。
    • 取决于您的要求。使用 deletedIds 的想法是绑定到视图模型中的单独字段。当他们单击删除时,您会删除客户端上的行并更新 deletedIds 字段。对于添加,您创建一个 Id 值为 0 的新行。活页夹不关心什么是数组,只要它看起来像一个数组即可。然后,您可以通过查看它们的 id 在服务器上确定哪些行是新的。另外,我怀疑 MVC 可能不会关心数组中有哪些数字,只要它们符合命名约定。新行可以使用任何 id。
    【解决方案3】:

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

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

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

    【讨论】:

      猜你喜欢
      • 2013-08-18
      • 2017-04-26
      • 1970-01-01
      • 2013-04-19
      • 1970-01-01
      • 2020-09-29
      相关资源
      最近更新 更多