MVC – Making a popup editor element in asp.net mvc

This document demonstrates a way to present the viewer with a list of records, and use a popup window to edit the data. This is acomplished in MVC by using a viewmodel that contains the record list – and at the same time provides the data fields of an optional selected record. The popup by itsself is rendered with jQuery and the WEBFW css framework. (all included in the downloadable solution at the end).

Our final result will look like this:

The key to making this all work is creating the view with both the list, and an optional part that will function as the popup. In the code block below you will see detection based on the presence of a Model.ActiveCustomerId. As long as this field is blank, we will only show the list with the selection buttons. When a selection is made, the controller will fill the customerId field and an extra record with the customer data, and the popup will be rendered. WebFW will provide us with the styling and a draggebale popup. Of couse you can also use more advanced framworks such as bootstrap for this task.

The viewmodel

The viewmodel layouts our data structure. The ActiveCustomerId field will be used to fill with a customerId string if a record is selected for editing. The ActiveCustomer record contains the entire custoimer data structure in that event. You will see later on in the controller how this data is added. Finaly, the IENumerable contains the full list from which we will make our selection.

using System.Collections.Generic;
using MVC_Sample.Models;

namespace MVC_Sample.ViewModels
{
    public class CustomerEditViewModel
    {
        public string ActiveCustomerId { get; set; }
        public Customers ActiveCustomer { get; set; }
        public IEnumerable<Customers> CustomerList { get; set; }
    }
}

The view

@model MVC_Sample.ViewModels.CustomerEditViewModel
@{
    ViewBag.Title = "Home";
}
<h2>@ViewBag.Title</h2>
<table class="mGrid">
    @foreach (var m in Model.CustomerList) {
        <tr>
            <td>
                <input type="button" value="Select" class="buttonBase" onclick="location.href = '@Url.Action("Index", "Home", new {
                                                                                                     custId = m.CustomerID
                                                                                                 })'"/>
            </td>
            <td>
                @m.CompanyName
            </td>
            <td>
                @m.ContactName
            </td>
        </tr>
    }
</table>
@if (!String.IsNullOrEmpty(Model.ActiveCustomerId)) {
    <div class="blockPopup">
        @using (Html.BeginForm()) {
            <!-- we need this passed back to the action to find the record to edit -->
            @Html.HiddenFor(m=>m.ActiveCustomerId)
            <div class="blockFunction">Selection made</div>
            <div class="blockContent">
                @Model.ActiveCustomerId
            </div>
            <table>
                <tr>
                    <td>
                        @Html.LabelForModel(Model.ActiveCustomer.CompanyName)
                    </td>
                    <td>
                        @Html.TextBoxFor(x => Model.ActiveCustomer.CompanyName)
                    </td>
                </tr>
                <tr>
                    <td>
                        @Html.LabelForModel(Model.ActiveCustomer.ContactName)
                    </td>
                    <td>
                        @Html.TextBoxFor(x => Model.ActiveCustomer.ContactName)
                    </td>
                </tr>
            </table>
            <input type="submit" value="Save" class="buttonBase"/>
        }
    </div>
}

The controller

The controller class below is used to make our little magic work. The default Index(..) action will be used to present the list of options to the user. Once a selection is made the exact same Index(..) method is called, but this time with a customer id as parameter. The action will fill a new viewmodel, this time with both the list and the record to edit, and will present the view with the popup to the user.

Finaly, when the editor is saving the data using the [httppost] version of our index action, the database is updated and we are redirected back to the empty default action, re-presenting the list and closing the popup window.

using System.Linq;
using System.Web.Mvc;
using MVC_Sample.ViewModels;

namespace MVC_Sample.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult About() {
            return View();
        }

        /// <summary>
        ///     The viewmodel contains the list of customers, and in the event of a selection
        ///     also the id and data for the customer we want to edit.
        /// </summary>
        /// <param name="custId"></param>
        /// <returns></returns>
        public ActionResult Index(string custId) {
            var dc = Helpers.GetContext();
            var model = new CustomerEditViewModel {
                ActiveCustomerId = custId,
                CustomerList = dc.Customers.Select(x => x)
            };
            if (!string.IsNullOrWhiteSpace(custId)) {
                model.ActiveCustomer = dc.Customers.Where(x => x.CustomerID == custId)
                    .Select(x => x)
                    .Single();
            }
            return View(model);
        }

        /// <summary>
        ///     Save the record and return to the normal index view
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        [HttpPost]
        public ActionResult Index(CustomerEditViewModel model) {
            var dc = Helpers.GetContext();
            var entry = dc.Customers.Where(x => x.CustomerID == model.ActiveCustomerId)
                .Select(x => x)
                .Single();
            entry.CompanyName = model.ActiveCustomer.CompanyName;
            entry.ContactName = model.ActiveCustomer.ContactName;
            dc.SubmitChanges();
            //
            // Redirect to the default indiex view (close popup)
            return RedirectToAction("Index", new {
                custId = string.Empty
            });
        }
    }
}