Sunday, September 12, 2010

jQuery dialog extender for Asp.Net MVC Views

“This post and the demo solution is still valid for MVC 2.o.

However, a new version of this post Using MVC 3.0 and Razor with minor improvements is available here.

This idea came to me during a discussion at work. The requirement was to create put an Add/Edit form in a dialog box. I’ve always been against the idea of rendering the markup inside the dialog div before hand, as the user should NOT pay the cost of a rendering something that might not be needed.

The idea went further to being able to provide the link to edit dialog any where in the application. This triggered the thought of the extender pattern that we use to ajaxify server controls in the web forms. So here’s the implementation, create a view then create a dialog extender for the view. The first example will be a regular view (without a form), the second (in the next blog) will be an edit form.

Extending a simple view (without a form in it):

As always, the application starts with a model:

    public class Product {

        public Guid Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
   }

Here’s a controller (with dummy data) for it:

    public class ProductsController : Controller {

        private List<Product> _data = new List<Product> {

                        new Product() { 
                                Id = new Guid("4C341BA3-E971-43C0-8BB1-07F51320E10B"), 
                                Name = "My New Product", 
                                Description = "This is a cool way to extend the jQuery UI dialog!" },

                        new Product() { 
                                Id = new Guid("D12C7542-99D0-4610-AB47-7A38F45CD026"), 
                                Name = "My New B", 
                                Description = "BBBeeeeeeeeeeeeeeeeeeeeeeeeeee!" },

                        new Product() { 
                                Id = new Guid("66B1F0EA-1230-4D62-9BE1-A5158BE10784"), 
                                Name = "My New C", 
                                Description = "CCCeeeeeeeeeeeeeeeeeeeeeeeeeee!" },

                        new Product() { 
                                Id = new Guid("959FB827-BBEE-47D8-9B4D-14BF3092A1F1"), 
                                Name = "My New D", 
                                Description = "DDDeeeeeeeeeeeeeeeeeeeeeeeeeee!" },

                        new Product() { 
                                Id = new Guid("C093608F-E252-4758-9924-62B2955C0AC5"), 
                                Name = "My New E", 
                                Desctiption = "EEEeeeeeeeeeeeeeeeeeeeeeeeeeee!" }
        };

        public ActionResult Details(Guid id) {

            var model = _data.SingleOrDefault(p => p.Id == id);
            return View(model);
        }

        public ActionResult Index() {

            return View();
        }
    }

And here’s the details view (Details.ascx):

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MVCDialogExtender.Models.Product>" %>
<fieldset>
    <legend>Fields</legend>
        
    <div class="display-label">Name</div>
    <div class="display-field"><%: Model.Name %></div>
        
    <div class="display-label">Description</div>
    <div class="display-field"><%: Model.Description %></div>
        
</fieldset>

The details view can be a view or a partial view. Here’s the extender to the view (ProductDetailsDialog.ascx). I will add this view in the Shared folder so that it can be accessed from any view across the application. Also, since this is going in the Shared folder, I will follow the naming convention - “<Controller name><Action Name>Dialog” .

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

<div id="ProductDetailsDialogDiv" title="Product details" style="display:none;"></div>

<script type="text/javascript">

    $(document).ready(function () {
        $('#ProductDetailsDialogDiv').dialog({
            autoOpen: false,
            modal: true,
            close: function (event, ui) { $("#ProductDetailsDialogDiv").html(""); }
        });
    });
        $.ajax({
            type: "GET",
            url: encodeURI('<%= Url.Action("Details", "Products") %>' + "?id=" + id),
            cache: false,
            dataType: 'html',
            error: function (XMLHttpRequest, textStatus, errorThrown) {
                $("#ProductDetailsDialogDiv").html(errorThrown); 
            },
            success: function (data, textStatus, XMLHttpRequest) {
                $("#ProductDetailsDialogDiv").html(data); 
            },
            complete: function (XMLHttpRequest, textStatus) {
                $("#ProductDetailsCloseButton").click(function () { $('#ProductDetailsDialogDiv').dialog("close"); });
                $('#ProductDetailsDialogDiv').dialog("open"); 
            }
        });
    }

</script>

As soon in the view above, the dialog container DIV tag is emply an will be populated only when the user demands it. The javascript method responsible for populating and opening the dialog box will be called from outside this view so it will be handy to have a consistent naming convention, in my example I’ve chosen - “open<View Name>”. Again, since multiple such dialog may be present on the page I will follow the naming convention of “<View Name>Div” to avoid duplicate Id issue.

The detail extender inherits from a view model called DialogExtender which is an empty class right now but later if something needs to be added to the dialogs at a global level, the DialogExtender will be the way to go.

Finally here’s how we place a link to see the dialog extender:

    <p>
        <!--The link that will show the dialog-->
        <a href="javascript:openProductDetailsDialog('4C341BA3-E971-43C0-8BB1-07F51320E10B');">Product details</a>
        <!--The Dialog extender will sit here quietly-->
        <% Html.RenderPartial("ProductDetailsDialog", new DialogExtender()); %>
    </p>

OR in a list view

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<MVCDialogExtender.Models.Product>>" %>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

    <h2>List</h2>

    <table>
        <tr>
            <th></th>
            <th>
                Name
            </th>
        </tr>

    <% foreach (var item in Model) { %>
        <tr>
            <td>
                <a href="javascript:openProductDetailsDialog('<%: item.Id %>');">Details</a>
            </td>
            <td>
                <%: item.Name %>
            </td>
        </tr>    
    <% } %>

    </table>

    <% Html.RenderPartial("ProductDetailsDialog", new DialogExtender()); %>

</asp:Content>

The results:

Link

List

 

The source code of this demo can be downloaded here.

With the intention to keep the blogs short I will be extending MVC forms in the next blog.

 

Thoughts, opinions, ideas and comments welcomed.

3 comments:

  1. Thanks, I like this example, I was begining to understand PartialView with Dialog ajax options, this example is the best tahat I've see.
    Please, I would like that you send the function Add a new Product..

    Thank you very much from Bolivia Software.

    ReplyDelete
  2. My mail is jhonnyninascrum@gmail.com or jhonnynina740@gmail.com or jhonnynina740@hotmail.com thanks

    ReplyDelete
  3. Thanks Jhonny, please refer next post - "jQuery dialog extender for Asp.Net MVC Forms".
    The link (also mentioned at the end of this post) is - http://www.mayanksrivastava.com/2010/09/jquery-dialog-extender-for-aspnet-mvc_16.html

    Cheers!
    ~Mayank

    ReplyDelete