部分ビューでの検証エラーの表示と jQuery UI のダイアログ表示(2)

ASP.NET MVC 4 の部分ビューを利用した jQuery UI のダイアログ表示について、前回の続きを書いていきます。残っているのは、コントローラーとビュー関連になります。

まずはコントローラーから。Controllers フォルダに HomeController を作ります。

using System.Web.Mvc;

using TestJqueryUi005.Helpers;
using TestJqueryUi005.Models;
using TestJqueryUi005.Services;

namespace TestJqueryUi005.Controllers
{
    public class HomeController : Controller
    {
        private ITestService _service;

        public HomeController() : this(new TestService()) {}
        public HomeController(ITestService service)
        {
            _service = service;
        }

        //
        // GET: /Home/

        public ActionResult Index()
        {
            return View(new DataContainer { Contacts = _service.GetListContacts() });
        }

        //
        // GET: /Home/Details/ 5
        // Details の表示は Ajax(jQuery の Ajax API の load) で行う

        public ActionResult Details(int id)
        {
            var target = _service.GetContact(id);
            if (target == null)
            {
                return HttpNotFound();
            }

            return PartialView("_Details", target);
        }

        //
        // GET: /Home/Create/

        public ActionResult Create()
        {
            // ドロップダウンリスト用のデータ
            ViewData["TargetContact.RelationshipId"] = new SelectList(_service.GetListRelations(), "Id", "Name");

            return PartialView("_Create");
        }

        //
        // POST: /Home/Create/

        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create(DataContainer container)
        {
            if (ModelState.IsValid)
            {
                try
                {
                    _service.CreateContact(container.TargetContact);
                    TempData["StatusMessage"] = "連絡先を追加しました。";
                    return RedirectToAction("Index");
                }
                catch (ValidateException e)
                {
                    ModelState.AddModelError(PropertyHelper.GetPropertyName(() => container.TargetContact) +
                        "." + e.TargetName, e.Message);
                }
            }

            // ドロップダウンリスト用のデータ
            ViewData["TargetContact.RelationshipId"] = new SelectList(_service.GetListRelations(), "Id", "Name");

            container.Action = "Create";
            container.Contacts = _service.GetListContacts();
            return View("Index", container);
        }

        //
        // GET: /Home/Edit/5
        // Edit の表示は Ajax(jQuery の Ajax API の load) で行う

        public ActionResult Edit(int id)
        {
            var target = _service.GetContact(id);
            if (target == null)
            {
                return HttpNotFound();
            }

            var container = new DataContainer
            {
                TargetContact = target,
            };

            // ドロップダウンリスト用のデータ
            ViewData["TargetContact.RelationshipId"] = new SelectList(_service.GetListRelations(), "Id", "Name",
                container.TargetContact.RelationshipId);

            return PartialView("_Edit", container);
        }

        //
        // POST: /Home/Edit/5

        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Edit(DataContainer container)
        {
            if (ModelState.IsValid)
            {
                try
                {
                    _service.EditContact(container.TargetContact);
                    TempData["StatusMessage"] = "連絡先を変更しました。";
                    return RedirectToAction("Index");
                }
                catch (ValidateException e)
                {
                    ModelState.AddModelError(PropertyHelper.GetPropertyName(() => container.TargetContact) +
                        "." + e.TargetName, e.Message);
                }
            }

            container.Action = "Edit";
            container.Contacts = _service.GetListContacts();

            // ドロップダウンリスト用のデータ
            ViewData["TargetContact.RelationshipId"] = new SelectList(_service.GetListRelations(), "Id", "Name",
                container.TargetContact.RelationshipId);

            return View("Index", container);
        }

        // 
        // GET: /Home/Delete/5

        public ActionResult Delete(int id)
        {
            var target = _service.GetContact(id);
            if (target == null)
            {
                return HttpNotFound();
            }

            var container = new DataContainer
            {
                TargetContact = target,
            };

            return PartialView("_delete", container);
        }

        //
        // POST: /Home/Delete/5

        [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public ActionResult DeleteConfirmed(int id)
        {
            try
            {
                _service.DeleteContact(id);
                TempData["StatusMessage"] = "連絡先を削除しました。";
                return RedirectToAction("Index");
            }
            catch
            {
                return View("Error");
            }
        }

        protected override void Dispose(bool disposing)
        {
            _service.Dispose();
            base.Dispose(disposing);
        }
    }
}

GET 要求を処理する Details, Create, Edit, Delete の各アクションは PartialView を返すようにしています。また、 Index で表示する一覧と部分ビューで表示する特定の連絡先(Contact クラス)の二種類の情報を渡すために DataContainer クラス(前回 Models フォルダに作成しています)を利用しています。
なお、データ入力の際にドロップダウンリストを利用するので、必要な所で Viewdata にSelectList のインスタンスをセットしています(Edit では選択値もセットしています)。

ビューに行く前に表示するための環境をセットします。
Content/Site.css

~省略~
/* Message
-----------------------------------------------------------*/
.message-success {
    color: #7ac0da;
    font-size: 1.3em;
    font-weight: bold;
    margin: 20px 0 10px 0;
}

label.error {
    color: #f00;
}

このスタイル設定を適当なところ(末尾でも)にセットします。

次にアニメーション GIF ファイルです。jQuery の Ajax API Load でサーバーにデータを取りに行っている間、メッセージ「Now Loading…」とともに表示するくるくる回るアニメーション GIF を表示するようにしていますが、このファイルをセットする必要があります(まぁ、無ければ画像が表示されないだけですが 😀 )。Content フォルダに images フォルダを作成し、GIF ファイルを置いておきます。
アニメーション GIF の入手ですが、わたしは「Chimply generates your images」さんのところから入手しました。

次にビューです。

/Views/Home/_Create.cshtml

@model TestJqueryUi005.Models.DataContainer

@using (Html.BeginForm()) {
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>Contact</legend>

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

        <div class="editor-label">
            @Html.LabelFor(model => model.TargetContact.RelationshipId)
        </div>
        <div class="editor-field">
            @Html.DropDownListFor(model => model.TargetContact.RelationshipId, null)
            @Html.ValidationMessageFor(model => model.TargetContact.RelationshipId)
        </div>

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

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

/Views/Home/_Delete.cshtml

@model TestJqueryUi005.Models.DataContainer

<h3>Are you sure you want to delete this?</h3>
<fieldset>
    <legend>Contact</legend>

    <div class="display-label">
         @Html.DisplayNameFor(model => model.TargetContact.Name)
    </div>
    <div class="display-field">
        @Html.DisplayFor(model => model.TargetContact.Name)
    </div>

    <div class="display-label">
         @Html.DisplayNameFor(model => model.TargetContact.Relationship.Name)
    </div>
    <div class="display-field">
        @Html.DisplayFor(model => model.TargetContact.Relationship.Name)
    </div>

    <div class="display-label">
         @Html.DisplayNameFor(model => model.TargetContact.Email)
    </div>
    <div class="display-field">
        @Html.DisplayFor(model => model.TargetContact.Email)
    </div>

    <div class="display-label">
         @Html.DisplayNameFor(model => model.TargetContact.Phone)
    </div>
    <div class="display-field">
        @Html.DisplayFor(model => model.TargetContact.Phone)
    </div>
</fieldset>
@using (Html.BeginForm()) {
    @Html.AntiForgeryToken()
    <p>
        @Html.HiddenFor(model => model.TargetContact.Id)
    </p>
}

/Views/Home/_Details.cshtml

@model TestJqueryUi005.Models.Contact

<fieldset>
    <legend>Contact</legend>

    @Html.HiddenFor(model => model.Id)

    <div class="display-label">
         @Html.DisplayNameFor(model => model.Name)
    </div>
    <div class="display-field">
        @Html.DisplayFor(model => model.Name)
    </div>

    <div class="display-label">
         @Html.DisplayNameFor(model => model.Relationship.Name)
    </div>
    <div class="display-field">
        @Html.DisplayFor(model => model.Relationship.Name)
    </div>

    <div class="display-label">
         @Html.DisplayNameFor(model => model.Email)
    </div>
    <div class="display-field">
        @Html.DisplayFor(model => model.Email)
    </div>

    <div class="display-label">
         @Html.DisplayNameFor(model => model.Phone)
    </div>
    <div class="display-field">
        @Html.DisplayFor(model => model.Phone)
    </div>
</fieldset>

/Views/Home/_Edit.cshtml

@model TestJqueryUi005.Models.DataContainer

@using (Html.BeginForm()) {
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>Contact</legend>

        @Html.HiddenFor(model => model.TargetContact.Id)

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

        <div class="editor-label">
            @Html.LabelFor(model => model.TargetContact.RelationshipId)
        </div>
        <div class="editor-field">
            @Html.DropDownListFor(model => model.TargetContact.RelationshipId, null)
            @Html.ValidationMessageFor(model => model.TargetContact.RelationshipId)
        </div>

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

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

/Views/Home/Index.cshtml

@model TestJqueryUi005.Models.DataContainer

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

<p class="message-success">@TempData["StatusMessage"]</p>

<h2>@ViewBag.Title</h2>

<p>
    <input type="button" id="createButton" value="連絡先の作成" />
</p>
<table>
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.TargetContact.Name)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.TargetContact.Relationship.Name)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.TargetContact.Email)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.TargetContact.Phone)
        </th>
        <th></th>
    </tr>

@foreach (var item in Model.Contacts)
{
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.Name)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Relationship.Name)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Email)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Phone)
        </td>
        <td>
            <a href="#" onclick="showEditGetdata('@item.Id')">Edit</a> |
            <a href="#" onclick="showDetailsGetData('@item.Id')">Details</a> |
            <a href="#" onclick="showDeleteGetData('@item.Id')">Delete</a>
        </td>
    </tr>
}

</table>
<div id="detailsDialog" style="display:none;"></div>
<div id="createDialog" style="display:none;">
<!-- Model.Action がセットされるのは検証エラーのときのみ -->
    @if (Model.Action == "Create")
    {
        @Html.Partial("_Create")
    }
</div>
<div id="editDialog" style="display:none;">
    @if (Model.Action == "Edit")
    {
        @Html.Partial("_Edit")
    }
</div>
<div id="deleteDialog" style="display:none;"></div>
<div style="display:none;">
    <p id="action">@Model.Action</p>
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/jqueryui")
    @Scripts.Render("~/bundles/jqueryval")

<script type="text/javascript">
    <!--
    // ローディング中に表示する内容
    function loadingMessage(target) {
        $(target).html("<div><img src='/Content/images/image_475773.gif' width='64' hright='64' alt='Now Loading...' /><p>Now Loading...</p></div>");
    }

    // ダイアログに表示するボタン(OK ボタンのみ)
    function okButton() {
        var hash = {};
        hash["OK"] = function () {
            $(this).dialog("close");
            window.location = "/";
        };
        return hash;
    }

    // ダイアログに表示するボタン(Submit ボタンとキャンセルボタン)
    function submitCancelBtn(name, mode) {
        var hash = {};
        hash[name] = function () {
            if ($(this).children("form").valid()) {
                $(this).children("form").submit();
                $(this).dialog("close");
            }
        };
        hash["キャンセル"] = function () {
            $(this).dialog("close");
            if (mode == "valErr") { // サーバー側での検証エラーを表示しているときにキャンセルされた場合 URL 表示が変わっているので Index ページに移動
                window.location = "/";
            }
        };
        return hash;
    }

    // ダイアログに表示するボタン(コマンド実行ボタンとダイアログのクローズボタン)
    function commandCloseBtn(commandName, command, closeName, mode) {
        var hash = {};
        hash[commandName] = command;
        hash[closeName] = function () {
            $(this).dialog("close");
        };
        return hash;
    }

    // データが取得できなかったときに表示するダイアログ
    function notFound(target, title) {
        $(target).html("<p style='color:red;'>指定されたデータの読み込みができませんでした。一覧を再読み込みします。</p>");
        showDialog(target, title, okButton());
    }

    // ダイアログを表示する
    function showDialog(target, title, button) {
        $(target).dialog({
            autoOpen: true,
            title: title,
            width: 550,
            height: "auto",
            modal: true,
            buttons: button,
            open: function (event, ui) {
                $(".ui-dialog-titlebar-close").hide();
            }
        });
        if ($(this).children("form")[0]) { // form 要素があるか?
            $(this).children("form").validate();
        }
    }

    // jQuery Ajax API の Load でサーバーから部分ビューを取得する
    function ajaxLoad(target, showCommand, url, title) {
        loadingMessage(target);
        showCommand;
        $(target).load(url, function (response, status) {
            if (status == "success") {
                $(this).children("form").validate();
            }
            else {
                $(this).dialog("close");
                notFound(this, title);
            }
        });
    }

    // 作成ボタンクリック時の処理
    $(function() {
        $("#createButton").click(function () {
            ajaxLoad($("#createDialog"), showCreate(), "/Home/Create/", "新規作成");
        });
    });

    // 新規作成ダイアログの表示
    function showCreate(mode) {
        showDialog($("#createDialog"), "新規作成", submitCancelBtn("作成", mode));
    }

    // _Details 部分ビューの取得
    function showDetailsGetData(id) {
        ajaxLoad($("#detailsDialog"), showDetails(id), "/Home/Details/" + id, "詳細");
    }

    // 詳細ダイアログの表示
    function showDetails(id) {
        showDialog($("#detailsDialog"), "詳細",
            commandCloseBtn("修正", function () {
                $(this).dialog("close");
                showEditGetdata(id);
            },
            "OK", null)
        );
    }

    // _Edit 部分ビューの取得
    function showEditGetdata(id) {
        ajaxLoad($("#editDialog"), showEdit(), "/Home/Edit/" + id, "修正");
    }

    // 修正ダイアログの表示
    function showEdit(mode) {
        showDialog($("#editDialog"), "データ修正", submitCancelBtn("修正", mode));
    }

    // _Delete 部分ビューの取得
    function showDeleteGetData(id) {
        ajaxLoad($("#deleteDialog"), showDeleteConfirm(), "/Home/Delete/" + id, "削除");
    }

    // 削除の確認ダイアログの表示
    function showDeleteConfirm() {
        showDialog($("#deleteDialog"), "削除の確認", submitCancelBtn("削除", null));
    }

    // サーバー側から検証エラーで戻されたときのダイアログ表示の振り分け
    $(function () {
        $(document).ready(function () {
            if ($("#action").text() == "Create") {
                showCreate("valErr");
            }
            else if ($("#action").text() == "Edit") {
                showEdit("valErr");
            }
        });
    });

    //-->
</script>
}

ダイアログの生成、クローズ、ボタンクリック時の処理などは Index.cshtml にページ埋め込みの JavaScript として書いています(これだけ長いと本当は外部に出したほうがいいんですけど :mrgreen: )。JavaScript が何をしているのかはコメントを入れておいたので分かるかと思います。

以上で部分ビューでダイアログ表示するプログラムが動きます。


コメントを残す

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