Day13 - 动态 新增/删除 Collection 项目(一)

延续上一篇的 ViewModel 结构
现在假设使用者提出要动态 新增/删除 Collection 项目的需求
就可能碰到的问题拆成多个 Case 来看

另外,接下来开始着重於之前强调的 Model Binding,所以文章内就不提 render 後的 html !
这篇先从大家常用的手法来做新增/删除 Collection 项目:

在 js 产生 html 来新增 以及 删除指定的 Html dom !


Case01

加入新增的功能 !

修改 Controller 内的 Action 内容如下:

  • Get Case01 时,不给定 ViewModel 的预设值

    [HttpGet]
    public IActionResult Case01()
    {
        return View();
    }
    

修改 View 内容如下:

  • 加入新增的功能 !

    • Table thead 最右边加上一栏,放上新增按钮

      <button type="button" onclick="AddItem(event)">新增</button>
      
    • Table tbody 最右边加上一栏

      <td></td>
      
    • 最下面加上 Section Script

      @section Scripts {
      <script>
      
            window.ItemsCount = @(Model?.Items?.Length ?? 0);
      
            window.ItemHtml = `
        <tr>
            <td>
                <input type="text"
                    name="Items[i].Name" />
            </td>
            <td>
                <input type="number"
                    step=1
                    min=0
                    name="Items[i].Quantity" />
            </td>
            <td>
            </td>
        </tr>
            `;
      
            window.AddItem = function () {
                const itemHtml = ItemHtml..replaceAll('[i]', `[${ItemsCount}]`);
      
                ItemsCount++;
      
                $('#Items').append(itemHtml);
            }
      </script>
      }
      

网站执行後,在该页面新增订单项目及输入资料

submit 後,可以看出资料可以如预期的传递 !


Case02

接下来,加入删除的功能 !

  • Controller

    • 从 Case01 复制 HttpGet、HttpPost Action,改名成 Case02
  • View

    • 从 Case01.cshtml 复制为 Case02.cshtml

    • 修改 Case02.cshtml

      • 把 Table tbody 最後一列改为以下语法

        <td>
          <button type="button" onclick="DeleteItem(event)">删除</button>
        </td>
        
      • 把 js script 内的 window.ItemHtml 变数中,最後一栏,改为上述的语法

      • js script 加上以下删除的 function

        window.DeleteItem = function (e) {
          const btnDom = e.target;
        
          const trDom = $(btnDom).parent().parent();
        
          trDom.remove();
        };
        

重新编译并执行网站,开启刚才新增的页面

  1. 新增三个订单项目,并输入资料
  2. 删除中间的订单项目,按下送出按钮
  3. 会发现送出前的第二笔资料不见了 !

Image

在上述第 2 步骤送出前,订单项目当下的 html 会长这样
可以发现
第一个项目是 Items[0]

第二个项目是 Items[2]

这个 index 不连续的情况,送到後端後,就会发生 Model Binding 部份失败 !

<tbody id="Items">
  <tr>
    <td>
      <input type="text" name="Items[0].Name" />
    </td>
    <td>
      <input type="number" step="1" min="0" name="Items[0].Quantity" />
    </td>
    <td>
      <button type="button" onclick="DeleteItem(event)">删除</button>
    </td>
  </tr>

  <tr>
    <td>
      <input type="text" name="Items[2].Name" />
    </td>
    <td>
      <input type="number" step="1" min="0" name="Items[2].Quantity" />
    </td>
    <td>
      <button type="button" onclick="DeleteItem(event)">删除</button>
    </td>
  </tr>
</tbody>

查看 Log ,会发现找不到 Items[1] 後

Could not bind to model of type 'Project.Models.Day13.OrderItem' as there were no values in the request for any of the properties.

就等於完成该 Property 的 Binding 了 !

Done attempting to bind property

2021-05-25 16:32:49.5225 | 25 | DEBUG | Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexObjectModelBinder | Done attempting to bind model of type 'Project.Models.Day13.OrderItem' using the name 'Items[0]'.
2021-05-25 16:32:49.5225 | 24 | DEBUG | Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexObjectModelBinder | Attempting to bind model of type 'Project.Models.Day13.OrderItem' using the name 'Items[1]' in request data ...
2021-05-25 16:32:49.5387 | 18 | DEBUG | Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexObjectModelBinder | Could not bind to model of type 'Project.Models.Day13.OrderItem' as there were no values in the request for any of the properties.
2021-05-25 16:32:49.5387 | 14 | DEBUG | Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ArrayModelBinder | Done attempting to bind property 'Project.Models.Day13.ViewModel.Items' of type 'Project.Models.Day13.OrderItem[]'.

调整方式,请看 Case03


Case03

  • Controller
    • 从 Case01 复制 HttpGet、HttpPost Action,改名成 Case02
  • View

主要就是参考 Day05.Case03 的格式来调整

  • 在每个 tbody tr 中加上

    <input type="hidden" name="Items.index" value="@(i)" />
    
  • js script 的 ItemHtml 变数也加上类似上面的语法

    <input type="hidden" name="Items.index" value="i" />
    
  • js script 的 AddItem 改为以下

    window.AddItem = function () {
        const itemHtml = ItemHtml.replaceAll('[i]', `[${ItemsCount}]`)
                                 .replaceAll('value="i"', `value="${ItemsCount}"`);
        ItemsCount++;
    
        $('#Items').append(itemHtml);
    }
    

让原本的 html 及动态新增的 html 都能加上 name 为 Items.index & value 为指定值 的项目

就可以解决 Case02 因 index 不连续而导致 Model Binding 部份失败 !


以 Case03 的 Code 来说,因为以下二点原因:

  1. 订单项目 Html 的部份,我不想维护二份 Code
    a. 第一份是:razor 语法包住 html 的部份
    b. 第二份是:js 语法的部份
  2. 不想在 js 里面组 html

接下来,我会改用另一个做法来做 !

这篇先到这里,下一篇来看 动态 新增/删除 Collection 项目 (二) !


<<:  Day5-Go变数介绍

>>:  [DAY 08] TextItem

你好 想请问css轮播问题

#photos{position:absolute;width:calc(600px*6);z-i...

成为工具人应有的工具包-20 UninstallView

UninstallView 今天来认识这个不知道是什麽的工具,解除安装概览? UninstallVi...

找LeetCode上简单的题目来撑过30天啦(DAY12)

题号:15 标题:3Sum 难度:Medium Given an integer array num...

铁人赛 Day23 -- JavaScript 初体验(一) -- Hellow World

前言 一直很想学 JavaScript 但我觉得很难的感觉一直迟迟不敢去碰它,事到如今还是来了,该面...

Day 09:递回(2)

上一回我们看到,同样的跨年倒数任务,可以用回圈或递回的方式完成。 用回圈通常可以看到某个变数(例如i...