ASP.NET MVC と jQuery で新規登録ダイアログと編集ダイアログ

ASP.NET MVC と jQuery で削除確認ダイアログの記事で、確認を行うダイアログを作ってみましたが、ダイアログの中で入力を行うダイアログは? と気になったので、ちょっと新規登録と編集をダイアログ上で行うものを作ってみました。登録・編集を行うものは、連絡先情報(名前、メールアドレス、電話番号)にしました。

ダイアログの表示部分は、jQuery と jQuery UI の Dialog を利用して JavaScript で、フォームの部分は、ASP.NET MVC の PartialView で構築します。

(2013/07/18 追記)
部分ビューで作られた入力ダイアログでサーバー側検証エラーとなった場合に、エラー表示を伴う入力ダイアログを再表示させる方法を掲載しました(リポジトリ パターン及びサービス層も導入しています)。

動かしたときの画面はこんな感じです。

一覧画面
新規追加ダイアログ
編集ダイアログ
追加・編集後の一覧画面

新しいプロジェクトの作成で、ASP.NET MVC を選択します。ビュー・エンジンは Razor を選択しています。

テストなので、モデルはこんな感じで(TestModel.cs)。データを DB で保持していないので、シングルトンにしています。

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;

namespace TestJqueryUi003.Models
{
    public class DataModel
    {
        public int Id { get; set; }

        [Required]
        [Display(Name = "名前")]
        public string Name { get; set; }

        [Required]
        [RegularExpression(@"^[\x01-\x7F]+@(([-a-z0-9]+\.)*[a-z]+|\[\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\])",
            ErrorMessage="正しいメールアドレスを入力してください。")]
        [DataType(DataType.EmailAddress)]
        [Display(Name = "メールアドレス")]
        public string Email { get; set; }

        [Required]
        [DataType(DataType.PhoneNumber)]
        [Display(Name = "電話番号")]
        public string Phone { get; set; }
    }

    static public class TestModel
    {
        static List<DataModel> _dataModel;

        static TestModel()
        {
            _dataModel = new List<DataModel>()
            {
                new DataModel { Id = 1, Name = "user1", Email = "user1@example.com", Phone = "0123-12-0123" },
                new DataModel { Id = 2, Name = "user2", Email = "user2@example.com", Phone = "0123-12-1234" },
                new DataModel { Id = 3, Name = "user3", Email = "user3@example.com", Phone = "0123-56-7890" },
                new DataModel { Id = 4, Name = "user4", Email = "user4@example.com", Phone = "0123-45-6789" }
            };
        }

        static public IList<DataModel> GetAllContacts()
        {
            return _dataModel;
        }

        static public DataModel GetContact(string name)
        {
            return _dataModel.Where(c => c.Name == name).FirstOrDefault();
        }

        static public void CreateContat(DataModel dataModel)
        {
            dataModel.Id = _dataModel.Max(n => n.Id) + 1;
            _dataModel.Add(dataModel);
        }

        static public Boolean EditContact(DataModel dataModel)
        {
            if (_dataModel.Where(c => c.Id == dataModel.Id).Count() != 1)
                return false;

            var data =  _dataModel.Where(c => c.Name == dataModel.Name).FirstOrDefault();
            data.Name = dataModel.Name;
            data.Email = dataModel.Email;
            data.Phone = dataModel.Phone;

            return true;
        }
    }
}

コントローラー

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using TestJqueryUi003.Models;

namespace TestJqueryUi003.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            ViewBag.Message = "Welcome to ASP.NET MVC!";

            return View();
        }

        public ActionResult ListContacts()
        {
            var list = TestModel.GetAllContacts();
            return View(list);
        }

        [ChildActionOnly]
        public ActionResult CreateContact()
        {
            return PartialView();
        }

        [HttpPost]
        public ActionResult CreateContact(DataModel model)
        {
            TestModel.CreateContat(model);
            return RedirectToAction("ListContacts");
        }

        [ChildActionOnly]
        public ActionResult EditContact()
        {
            return PartialView();
        }

        [HttpPost]
        public ActionResult EditContact(DataModel model)
        {
            if (!TestModel.EditContact(model))
                TempData["editErr"] = "更新ができませんでした。";
            return RedirectToAction("ListContacts");
        }

        public ActionResult About()
        {
            return View();
        }
    }
}

新規登録と修正のフォームは部分ビューで構築するため、CreateContact と EditContact の GET メッセージで呼び出されるアクションに ChildActionOnly属性を付加し、return のところで PartialView() を呼び出しています。

次はビューですが、JavaScript を埋め込みたいので、その準備として、\Views\Shared\_Layout.cshtml に細工をします。確認ダイアログの記事では jQuery.UI を使う準備もしましたが、今回はここでは行いません。

<!DOCTYPE html>
<html>
<head>
    <title>@ViewBag.Title</title>
    <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Scripts/jquery-1.5.1.js")" type="text/javascript"></script>
    @RenderSection("styles", required:false)
    @RenderSection("scripts", required:false)
</head>
<body>
    <div class="page">
        <div id="header">
            <div id="title">
                <h1>My MVC Application</h1>
            </div>
            <div id="logindisplay">
                @Html.Partial("_LogOnPartial")
            </div>
            <div id="menucontainer">
                <ul id="menu">
                    <li>@Html.ActionLink("Home", "Index", "Home")</li>
                    <li>@Html.ActionLink("About", "About", "Home")</li>
                </ul>
            </div>
        </div>
        <div id="main">
            @RenderBody()
        </div>
        <div id="footer">
        </div>
    </div>
</body>
</html>

ビューは Index.cshtml から

@{
    ViewBag.Title = "Home Page";
}

<h2>@ViewBag.Message</h2>
<p>
    @Html.ActionLink("連絡先一覧を表示", "ListContacts")
</p>

ここでは、連絡先一覧へのリンクを追加しているだけです。

次に連絡先一覧のビュー(ListContacts.cshtml)

@model IEnumerable<TestJqueryUi003.Models.DataModel>

@{
    ViewBag.Title = "連絡先一欄";
}

@section scripts {
<script type="text/javascript">
<!--
    function showCreateDialog() {
        $("#createDialog").dialog({
            title: "新規作成",
            width: 550,
            height: 400,
            modal: true,
            buttons: {
                "作成": function () {
                    $("#createDialog form").submit();
                    if ($("#createDialog form").valid()) {
                        $(this).dialog("close");
                    }
                },
                "キャンセル": function () {
                    $(this).dialog("close");
                }
            },
            close: function () {
                $(this).dialog("destroy");
            }
        });
    }

    function showEditDialog(id, name, email, phone) {
        setForm(id, name, email, phone);
        $("#editDialog").dialog({
            title: "連絡先編集",
            width: 550,
            height: 400,
            modal: true,
            buttons: {
                "更新": function () {
                    $("#editDialog form").submit();
                    if ($("#editDialog form").valid()) {
                        $(this).dialog("close");
                    }
                },
                "キャンセル": function () {
                    $(this).dialog("close");
                }
            },
            close: function () {
                $(this).dialog("destroy");
            }
        });
    }

    function setForm(id, name, email, phone) {
        $("#editDialog form")
            .find("input[id=Id]").val(id)
            .end()
            .find("input[id=Name]").val(name)
            .end()
            .find("input[id=Email]").val(email)
            .end()
            .find("input[id=Phone]").val(phone);
    }
//-->
</script>
}

<h2>@ViewBag.Title</h2>

<div style="color: Red">@Html.Encode(TempData["editErr"])</div>

<p>
    <a href="#" onclick="showCreateDialog()">連絡先の作成</a>
</p>
<table>
    <tr>
        <th>
            名前
        </th>
        <th>
            メールアドレス
        </th>
        <th>
            電話番号
        </th>
        <th></th>
    </tr>

@foreach (var item in Model) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.Name)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Email)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Phone)
        </td>
        <td>
            <a href="#" onclick="showEditDialog('@item.Id', '@item.Name', '@item.Email', '@item.Phone')">編集</a> |
            @Html.ActionLink("Delete", "Delete", new { /* id=item.PrimaryKey */ })
        </td>
    </tr>
}

</table>

<div id="createDialog" style="display:none;">
@Html.Action("CreateContact")
</div>
<div id="editDialog" style="display:none;">
@Html.Action("EditContact")
</div>

最下部の

<div id=”createDialog” style=”display:none;”>
@Html.Action(“CreateContact”)
</div>
<div id=”editDialog” style=”display:none;”>
@Html.Action(“EditContact”)
</div>

のところで、部分ビューをはめ込みます(それぞれ、id を “createDialog”, “editDialog” と指定しています)。

新規登録ダイアログは、この createDialog でマークアップされたところをダイアログで表示します。フォームが submit されたときに、データ検証が実行されるため、検証エラーが発生することを考慮して、showCreateDialog メソッド中の「作成」ボタンがクリックされたときの処理に、valid() の評価が入っています(検証をパスしなければダイアログを閉じません)。

編集ダイアログは、前述の editDialog でマークアップされたところをダイアログで表示します。編集のリンクをクリックしたときに showEditDialog(id, name, email, phone) メソッドが呼び出され、そのなかで各入力項目に値をセットする setForm(id, name, email, phone) メソッドを呼び出しています。セットする先は、部分ビューとしてはめ込まれてくる(<div id=”editDialog” style=”display:none;”> としてあるので、画面上に描画されませんが)編集ビューの<input ~ value=”” />になります。

次は、新規登録ビュー(CreateContact.cshtml)

@model TestJqueryUi003.Models.DataModel

<link href="@Url.Content("~/Content/themes/base/jquery.ui.all.css")" rel="Stylesheet" type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-ui-1.8.11.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>

@using (Html.BeginForm("CreateContact")) {
    @Html.ValidationSummary(true)
    <fieldset>
        <legend>DataModel</legend>

        <div class="editor-label">
            @Html.LabelFor(model => model.Name)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Name)
            @Html.ValidationMessageFor(model => model.Name)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Email)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Email)
            @Html.ValidationMessageFor(model => model.Email)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Phone)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Phone)
            @Html.ValidationMessageFor(model => model.Phone)
        </div>
    </fieldset>
}

ここでは次の jQuery UI への参照を追加しています。

<link href=”@Url.Content(“~/Content/themes/base/jquery.ui.all.css”)” rel=”Stylesheet” type=”text/css” />
<script src=”@Url.Content(“~/Scripts/jquery-ui-1.8.11.js”)” type=”text/javascript”></script>

最後に編集ビュー(EditContact.cshtml)

@model TestJqueryUi003.Models.DataModel

<script src="@Url.Content("~/Scripts/jquery-ui-1.8.11.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>

@using (Html.BeginForm("EditContact")) {
    @Html.ValidationSummary(true)
    <fieldset>
        <legend>DataModel</legend>

        @Html.HiddenFor(model => model.Id)
        <div class="editor-label">
            @Html.LabelFor(model => model.Name)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Name)
            @Html.ValidationMessageFor(model => model.Name)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Email)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Email)
            @Html.ValidationMessageFor(model => model.Email)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Phone)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Phone)
            @Html.ValidationMessageFor(model => model.Phone)
        </div>
    </fieldset>
}

新規登録ビューと同様に jQuery UI への参照の追加を行い、キー項目 Id を @Html.HiddenFor(model => model.Id) としています(実際に編集前のデータをセットするのは、一覧ビューのところで書いたとおり、編集リンクのクリックで起動する JavaScript になります)。

注意事項としては、新規登録と編集の入力画面の生成を部分ビューで行っていることから、サーバー側での検証でエラーを検出しても、入力画面の再表示をさせることができないことが挙げられると思います。クライアント側でのデータ検証(例: TestModel.cs での RegularExpression 属性の活用)と、もし、サーバー側で検証エラー等が発生した場合には一覧画面に戻したときにエラーとなったことの通知(TempData プロパティなどを活用したごく簡単な通知の例を EditContact アクションで書いています)が必要だと思います。

(2011/04/26 追記)
入力ダイアログで入力された値のサーバーでの検証エラーの表示」の記事で、エラー通知とともに入力データをセットしてダイアログを再表示させるコードを書いてみました。

(2013/07/11 追記)
部分ビューで入力画面を作成したときにサーバー側検証エラーとなった場合に、入力画面を再表示させる方法の例示を書きました。

(2013/07/15 追記)
上記の部分ビューを jQuery UI を用いてダイアログ化したものを掲載しました。

 

とりあえず、こんな感じで動いています。なお、あくまで感触を掴むために書いたものなので、エラー・ハンドリングを省いています。もし参考にされる方がいらしたら、ちゃんとエラー処理をしてください 😉

何か気がついたこととかあったら、指摘していただけると嬉しいです 🙂


ASP.NET MVC と jQuery で新規登録ダイアログと編集ダイアログ」への5件のフィードバック

  1. こんにちは。
    サンプルを参考にさせていただき、動作するようになりました。
    ただ、ListContactsにDeleteのリンクがありますが、DeleteのViewが無いため
    削除ができないようです。
    DeleteのViewも掲載していただけないでしょうか。
    よろしくお願いします。

    1. こんにちは。
      アイテム削除についてですが、この投稿自体が「ASP.NET MVC と jQuery で削除確認ダイアログ」の続きとして新規登録と編集をダイアログ上で行うものを作ってみるというものなので、新規登録と編集の関わる機能のみの実装になっています。
      削除機能についてですが、アプローチとしては2種類考えられますが、いづれの方法を取るにしろ、ビューだけでの問題ではなく、コントローラーに削除のためのアクションを実装する必要がありますね。

      1つ目は、ASP.NET MVC の一般的な方法でサーバー側に要求を送り、削除確認のビューを表示して確認が行われたら削除するという方法です(スキャフォールディングで生成されるパターンです)。もし、もし良く分からないようであれば、http://www.makcraft.com/blog/meditation/2013/06/02/when-you-apply-the-repository-pattern-cleanup-operations/ を参考にどうぞ。

      2つ目は、jQuery で削除確認ダイアログを表示させる方法で、これは冒頭の「ASP.NET MVC と jQuery で削除確認ダイアログ」で行っているものになります。

  2. 了解しました。
    編集の機能を参考にして削除機能を「jQuery で削除確認ダイアログを表示させる方法」で作成してみたところ上手く動作しました。
    「ASP.NET MVC と jQuery で削除確認ダイアログ」の方も見てみます。
    わかりやすいサンプルをありがとうございました。

mino へ返信する コメントをキャンセル

メールアドレスが公開されることはありません。 が付いている欄は必須項目です