びぼーろくっ!

誰かに見せるわけでもないけど、備忘録として。。

.NET MVC RazorでTODOList(動的なフォーム)を作成する方法があった

何気に痒い所に手が届かない.NET MVC RazorですがTODOListのような動的フォームを作成する方法がありました
本来ならScriptメインで記述すればいいのですが、Validationや共通のヘルパーなどを使用したかったのでRazorでやりたかったのです。

使ったライブラリ
www.nuget.org


んで実現のために作ったサンプルコードはこちら

Controller

// SampleController.cs
public class SampleController
{
  /// <summary>
  /// 初期レンダリング時にkickされる
  /// </summary>
  /// <returns></returns>
  [HttpGet]
  public ActionResult Index()
  {
    // サンプルの為、DBロジックをハードコーディングしてます。
    var parent = new Parent()
    {
      Age = 4,
      Name = "ねこのきなこ",
      Skills = new List<Skill>()
      {
        new Skill() {Name = "お皿洗い", Experience = 2 },
        new Skill() {Name = "あまえる", Experience = 4 },
      }
    };
    return View(parent);
  }

  /// <summary>
  /// submit時にkick
  /// </summary>
  /// <param name="model"></param>
  /// <returns></returns>
  [HttpPost]
  public ActionResult Create(Parent model)
  {
    // 登録処理
    return View("Index");
  }

  /// <summary>
  /// スキル追加
  /// </summary>
  /// <returns></returns>
  [HttpGet]
  public ActionResult AddChild()
  {
    return PartialView("Skills", new Skill());
  }
}

Model側

// Parent.cs
public class Parent
{
  [Display(Name = "おなまえ")]
  public string Name { get; set; }

  [Display(Name = "おとし")]
  public int Age { get; set; }

  [Display(Name = "できることリスト")]
  public List<Skill> Skills { get; set; }
}
// Skill.cs
public class Skill
{
  [Display(Name = "できること")]
  public string Name { get; set; }

  [Display(Name = "つづけたねんすう")]
  public int Experience { get; set; }
}

View側
Index.cshmlt

@model HogeHogeNameSpace.Parent
@section Scripts {
    <script src="~/js/sample.js"></script>
}

@using (Html.BeginForm("Create", "Sample"))
{
    <div class="mdl-grid">
        @Html.CustomTextEditorFor(m => m.Name, 50)
    </div>
    <div class="mdl-grid">
        @Html.CustomNumericEditorFor(m => m.Age, 2)
        <span>さい</span>
    </div>
    <table id="children">
        <tbody>
            @foreach (var skill in Model.Skills)
            {
                @Html.Partial("_Skills", skill)
            }
        </tbody>
    </table>
    @Html.CustomTextButton("addChild", "Skill追加", "mode_edit")
    @Html.CustomTextButton("Save", "Save", "mode_edit", null, "submit");
}

_Skills.cshtml

@model HogeHogeNameSpace.Skill
@using HtmlHelpers.BeginCollectionItem;

<!---使用するとHtmlのName属性の衝突を避けることが出来て、CollectionのItemに応じたIdが割り当てされる-->
@using (Html.BeginCollectionItem("Skills"))
{
    <tr>
        <td>
            @Html.CustomTextEditorFor(m => m.Name, 50, false)
            @Html.CustomNumericEditorFor(m => m.Experience, 2, false)
            <span>ねん</span>
        </td>
        <td>
            @Html.CustomButtonIcon("removeItem", "close", "close", new Dictionary<string, string>() { { "data-action", "removeItem" } })
        </td>
    </tr>
}

sample.js

$("#addChild").on("click", function () {
    $.ajax({
        // urlの置換
        url: '/Sample/AddChild'
        , success: function (partialView) {
            $('#children> tbody:last-child').append(partialView);
        }
    });
});


完成した画面はこちら。Frameworkをカスタムヘルパーで参照しています。これがしたかったのです。
画面初期時
f:id:kinachan0725:20190116183305p:plain

スキル追加ボタン押下時
f:id:kinachan0725:20190116183312p:plain

実装のコツとしては各リストをパーシャル化してBeginCollectionItemを使用する。
第一引数に対応するCollectionNameを入力
この場合、Parentから呼び出されているので、"Skills"ですね。

次にコントローラーにスキルを追加する為のルーティングを用意します。
AddChildで空のSkillを返してあげてAjaxでRequest、Responseを処理します。

結構簡単な事なのにRazorでやろうとしたら大変だったなぁ。
でもValidationとか割とモデル内で書けるから便利だったりするので、この方法で動的リストを作ろうと思いましたっ。

参考にしたリンク
stackoverflow.com