読者です 読者をやめる 読者になる 読者になる

1.21 jigowatts

Great Scott!

ASP.NET MVCはじめました~子画面の検索結果を親画面へ受け渡す

概要

子画面で検索を行い、その結果を親画面へ戻すという処理を実装してみます。

たとえば、トランザクションデータを検索する際の項目にマスタデータのIDが必要な場合、子画面でマスタデータを名称検索し、マスタデータのIDを親画面へ戻し、この値を使って検索するというようなケースを想定しています。

f:id:sh_yoshida:20141209012615p:plain

環境

Visual Studio 2010
ASP.NET MVC2
SQLServer2008 R2
jQuery 1.11.1
jQuery UI 1.11.1

今回の要件は

  1. 親画面より検索用子画面を呼び出す
  2. 「名前」項目で検索した結果を一覧に表示する
  3. 一覧のチェックボックスで選択したデータのIDを親画面へ受け渡す

として、サンプルコードを書いてみました。

実装

jQueryの用意

MicrosoftのCDNからjQuery/jQuery UIとCSSを読み込みます。
■Mvc2App/Views/Shared/Site.Master

<head runat="server">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
    <link href="../../Content/Site.css" rel="stylesheet" type="text/css" />
    <script type="text/javascript" src="<%: Url.Content("~/Scripts/MicrosoftAjax.debug.js") %>"></script>
    <script type="text/javascript" src="<%: Url.Content("~/Scripts/MicrosoftMvcAjax.debug.js") %>"></script>
    <link href="http://ajax.aspnetcdn.com/ajax/jquery.ui/1.11.1/themes/smoothness/jquery-ui.css" rel="Stylesheet" />
    <script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.11.1.min.js"></script>
    <script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jquery.ui/1.11.1/jquery-ui.min.js"></script>

    <asp:ContentPlaceHolder ID="ScriptContent" runat="server" />
</head>
...
コントローラーの用意

■Mvc2App/Controllers/GroupController.cs

namespace Mvc2App.Controllers
{
    public class GroupController : Controller
    {
        private readonly IGroupService _service;
        public GroupController()
            : this(new GroupService())
        {
        }
        public GroupController(IGroupService service)
        {
            _service = service;
        }

        ...
        //親画面での検索処理は省略
        public ActionResult Search() 
        {
            var model = new GroupSearchViewModel();
            return View(model);
        }
        //子画面(ダイアログ)の部分ビュー生成
        [ChildActionOnly]
        public ActionResult PersonIDSearch()
        {
            return PartialView();
        }
        //子画面検索の制御
        [HttpPost]
        public ActionResult SearchPeople(string personName)
        {
            var model = _service.PeopleSearch(personName);
            return PartialView("_peopleGrid", model);
        }

    }
}
サービスインタフェースとサービスクラスの用意*1

■Mvc2App/Services/Abstractions/IGroupService.cs

namespace Mvc2App.Services.Abstractions
{
    public interface IGroupService
    {
        PeopleSearchViewModel SearchPeople(string personName);
    }
}

■Mvc2App/Services/GroupService.cs
SearchPeopleメソッドのPeopleRepositoryクラスについてはこちらの記事を見ていただければコードがあります。が!PeopleのスペルをPepleと誤記しているため罠となっています(;;´¬`;)*2

namespace Mvc2App.Services
{
    public class GroupService : IGroupService
    {
        private readonly IGroupRepository _repository;
        public GroupService()
            : this(new GroupRepository())
        {
        }
        public GroupService(IGroupRepository repository) 
        {
            _repository = repository;
        }

        public PeopleSearchViewModel SearchPeople(string personName)
        {
            var model = new PeopleSearchViewModel();

            var repos = new PeopleRepository();
            var condition = new PeopleSearchConditionModel() { Name = personName };
            model.data = repos.Search(condition);
            return model;
        }
    
    }
}
ビューモデルの用意

■Mvc2App/ViewModels/Group/GroupSearchViewModel.cs

namespace Mvc2App.ViewModels.Group
{
    public class GroupSearchViewModel : SearchResultViewModel<UserGroup>
    {
        public UserGroupSearchConditionModel conditions { get; set; }
    }

    public class UserGroupSearchConditionModel 
    {
        public string ID { get; set; }
        public string GroupName { get; set; }
        public string PersonID { get; set; }
        public string Note { get; set; }
        public string UpdatedBy { get; set; }
        public string UpdateDate { get; set; }
    }
}
ビュー(JavaScript)の用意

■Mvc2App/Views/Group/Search.aspx

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
 Inherits="System.Web.Mvc.ViewPage<Mvc2App.ViewModels.Group.GroupSearchViewModel>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
	Search
</asp:Content>
<asp:Content ID="ScriptContent" ContentPlaceHolderID="ScriptContent" runat="server">
    <script type="text/javascript" src="<%: Url.Content("~/Scripts/Libs/GroupSearch.js") %>"></script>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

    <h2>Search</h2>
    <% using (Html.BeginForm()) { %>
    <fieldset>

        <legend>Search Fields</legend>

            <div class="field-box">
                <div class="field">
                    <%: Html.LabelFor(model => model.conditions.ID) %>
                    <%: Html.EditorFor(model => model.conditions.ID)%>
                </div>
                <div class="field">
                    <%: Html.LabelFor(model => model.conditions.GroupName)%>
                    <%: Html.EditorFor(model => model.conditions.GroupName)%>            
                </div>
                <div class="field">
                    <%: Html.LabelFor(model => model.conditions.PersonID)%>
                    <%: Html.EditorFor(model => model.conditions.PersonID)%>            
                    <a href="#" id="personIDSearch">ID検索</a>
                </div>
                <div class="field">
                    <%: Html.LabelFor(model => model.conditions.Note)%>
                    <%: Html.EditorFor(model => model.conditions.Note)%>
                </div>
                <div class="field">
                    <%: Html.LabelFor(model => model.conditions.UpdatedBy)%>
                    <%: Html.EditorFor(model => model.conditions.UpdatedBy)%>        
                </div>
                <div class="field">
                    <%: Html.LabelFor(model => model.conditions.UpdateDate)%>
                    <%: Html.EditorFor(model => model.conditions.UpdateDate)%>            
                </div>           
            </div>

            <div class="field-box">
                <p>
                    <input type="submit" value="Search" />
                </p>
            </div>
    </fieldset>
    <%} %>

   <div id="personIDSearchDialog" style="display:none;">
        <%: Html.Action("PersonIDSearch") %>
    </div>

</asp:Content>

■Mvc2App/Scripts/Libs/GroupSearch.js

$(function () {
    $('#personIDSearch').click(function () {
        showDialog();
    });
});

//dialog
function showDialog() {
    $('#personIDSearchDialog').dialog({
        title: "個人ID検索",
        width: 550,
        height: 400,
        modal: true,
        buttons: {
            "OK": function (event) {
                var result = "";
                var record = $('#tPeople tr');

                $.each(record, function () {
                    if ($('td:nth-child(1) #personSelector:checked', this).val()) {
                        var personIDValue = $('td:nth-child(2)', this).text();
                        result = result + personIDValue + ",";
                    }
                });

                $('#conditions_PersonID').val(result);
                $(this).dialog("close");
            },
            "キャンセル": function () { $(this).dialog("close"); }
        },
        close: function () { $(this).dialog("destroy"); }
    });
}

ID検索リンクのクリックイベントでjQuery UIによるダイアログを生成し、中身はActionヘルパーで呼び出す部分ビューを表示します。
子画面検索のリクエストはAjax.BeginFormヘルパーを使用しさらに結果表示用の部分ビューを返します。

部分ビューの用意

■Mvc2App/Views/Group/PersonIDSearch.ascx

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<dynamic>" %>

    <% using (Ajax.BeginForm("SearchPeople", new AjaxOptions() { UpdateTargetId = "peopleGrid" })) 
       { %>
    <div>
        <label>名前</label>
        <input type="text" name="personName" />
        <input type="submit" value="Search" />
    </div>
    <%} %>

    <div id="peopleGrid">
    </div>

■Mvc2App/Views/Group/_peopleGrid.ascx

<%@ Control Language="C#"
 Inherits="System.Web.Mvc.ViewUserControl<Mvc2App.ViewModels.People.PeopleSearchViewModel>" %>

 <table id="tPeople">
<% if (Model.data != null) { %>
    <tr>
        <th>選択</th>
        <th>ID</th>
        <th>名前</th>
        <th>住所</th>
        <th>電話番号</th>
    </tr>
    <% foreach (var item in Model.data)
       {%>
    <tr>
        <td><%: Html.CheckBox("personSelector") %></td>
        <td><%: item.ID %></td>
        <td><%: item.Name %></td>
        <td><%: item.Address %></td>
        <td><%: item.PhoneNumber %></td>
    </tr>           
    <%} %>       

<%} %>
</table>

今回一番悩んだのが、チェックボックスで選択したデータを親画面へ戻す制御。
ダイアログで「OK」ボタンがクリックされた処理としてIDで指定したtableタグを元に子要素をぐるぐる回して判定してますが、もっとスマートな方法があるかも??

var result = "";
var record = $('#tPeople tr');

$.each(record, function () {
    if ($('td:nth-child(1) #personSelector:checked', this).val()) {
        var personIDValue = $('td:nth-child(2)', this).text();
        result = result + personIDValue + ",";
    }
});

$('#conditions_PersonID').val(result);

実行結果

親画面から、ID検索リンクをクリック。


f:id:sh_yoshida:20141209012619p:plain

検索用のダイアログが表示されるので、適当な名前で検索し、データを選択後「OK」ボタンをクリック。


f:id:sh_yoshida:20141209012615p:plain

選択したデータのIDが親画面へ表示されます。


f:id:sh_yoshida:20141209012623p:plain

*1:2015/5/15 追記

*2:ソースはリファクタリング済みですがブログは戒めのためそのままに。