Click here to Skip to main content
Click here to Skip to main content

Creating an expandable master-details table (jQuery DataTables and ASP.NET MVC integration - Part IV)

, 2 May 2011 CPOL
Rate this:
Please Sign up or sign in to vote.
This article shows how an expandable master-details table can be implemented in ASP.NET MVC using the jQuery DataTables plug-in.

Introduction

Creating fully functional tables with pagination, sorting, filtering features is an easy task if you use the jQuery DataTables plug-in. This plug-in enables you to add all of these functionalities to a plain HTML table using a single line of code as shown in the following example:

("#myTable").dataTables();

This line of code finds an HTML table with the ID "myTable" and adds pagination, sorting, and filtering by keyword functionalities. A plain HTML table is converted into a fully functional table shown in the figure below:

datatables.png

In this article, we will see how the standard DataTable can be extended to become an expandable master-details table. The goal of the article is to present how to show a list of companies with an expand button that will show the list of employees for the selected company, as shown in the figure below:

expandableDataTables-table.png

In this example, each record in the table (companies in this example) has an expand button and when this button is clicked, details are opened. The details can be either child records (employees that work for this company, as in this example), additional details about the company (e.g., logo, description, and other information that are not shown in the table row), or combination of these two.

Background

Expandable tables are a common requirement in situations where you have related entities. If you have companies that have employees, teachers that have students, sales orders with sales order items, probably you will have some requirement to implement list of a master records (companies, teachers, sales orders) with the ability to show the related records for each master row when the master row is expanded. Other situations where you might need expandable tables are when just a minimal set of columns is shown in a table and there is a requirement that when the user clicks on a row, more details should be shown in the expanded row. These requirement can be easily implemented using the juery DataTables plugin - this article describes how it should be configured, what client-side logic should be added, and what should be implemented on the server-side to support expandable tables on the client-side.

When an expandable table is implemented, the following scenario is used:

  1. An additional column is added to the table, containing the expand/collapse buttons that enable the user to expand the selected table row
  2. When the user clicks on the expand button, an AJAX call is sent to the server and the HTML for the details will be returned
  3. HTML that is returned is injected as a sub-row using the DataTables fnOpen function and the expand button is converted to a collapse button
  4. When the user clicks on the collapse button, a sub-row is closed using the DataTables fnClose function and the collapse button is converted back to the expand button

This article explains how you can implement this scenario with the jQuery DataTables plug-in.

Using the code

This example shows how an expandable master-details table can be implemented in ASP.NET MVC using the jQuery DataTables plug-in. Three parts of the project need to be implemented:

  1. Model that defines the structures of the data that will be shown
  2. Controller class that reacts on the requests sent by the DataTables plug-in
  3. View which is the rendering engine and has the client side logic that needs to be implemented

The following sections describe these parts.

Model

The model is represented as two classes related with a one-to-many relationship. This example uses companies as master information and employees as details. The source code of the model classes is shown in the listing below:

public class Company 
{
    public int ID { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
} 

public class Employee 
{ 
    public int EmployeeID { get; set; }
    public string Name { get; set; }
    public string Position { get; set; }
    public string Phone { get; set; }
    public string Email { get; set; }
    public int CompanyID { get; set; }
}

The relationship between employees and their companies is established via the CompanyID property in the Employee class.

Controller

The Controller class responds to the user actions and returns responses. The client-side will sent a request for the list of employees by company ID that is sent when the expand button is pressed. This request will contain the ID of the company for which the list of employees should be shown in the expanded row. The Action method in the controller returns employees by company ID as shown in the listing below:

public class HomeController : Controller
{
    public ActionResult CompanyEmployees(int? CompanyID)
    {
       var employees = DataRepository.GetEmployees();
       var companyEmployees = (from e in employees
                               where (CompanyID == null || e.CompanyID == CompanyID)
                               select e).ToList();
       return View(companyEmployees);
    }
}

This action method takes the ID of the company and finds all the employees with that company ID. There is a a utility partial view that formats the returned list of companies - this view is shown below:

@model IEnumerable<JQueryDataTables.Models.Employee>

<table cellpadding="4" cellspacing="0" 
           border="0" style="padding-left:50px;">
    <tr>
        <th>Employee</th>
        <th>Position</th>
        <th>Phone</th>
        <th>Email</th>
    </tr>
@foreach (var item in Model) {
    <tr>
        <td>@item.Name</td>
        <td>@item.Position</td>
        <td>@item.Phone</td>
        <td>@item.Email</td>
    </tr>
}
</table>

This view generates an HTML table using a list of employees that are filtered by the controller. This HTML code is sent back to the client-side as an AJAX response. The following figure shows the AJAX response of the Home/CompanyEmployees call traced in Firebug:

expandableDataTables-details.png

View

View pages are used to show tables and contain client-side JavaScript. There are three view pages used in this example:

  1. Static view page where the table of companies is a static HTML table
  2. Server side view where a table of companies is generated on the server-side
  3. AJAX view page where a table of companies is loaded via an AJAX call

However, all these views use the same client-side logic for showing details (list of employees) - an AJAX call is sent to the controller action /Home/CompanyEmployees described above and an HTML response is returned by the controller which is injected in the table.

All three views use the same layout page which includes common elements required by all of them. This layout page is shown in the following listing:

<html>
    <head>
        <title>Implementation of Master-Details tables using a JQuery DataTables plugin</title>
        <link href="@Url.Content("~/Content/dataTables/demo_page.css")"
                                 rel="stylesheet" type="text/css" />
        <link href="@Url.Content("~/Content/dataTables/demo_table.css")" 
                                 rel="stylesheet" type="text/css" />
        <link href="@Url.Content("~/Content/dataTables/demo_table_jui.css")" 
                                 rel="stylesheet" type="text/css" />
        <link href="@Url.Content("~/Content/themes/base/jquery-ui.css")" 
                                 rel="stylesheet" type="text/css" media="all" />
        <link href="@Url.Content("~/Content/themes/smoothness/jquery-ui-1.7.2.custom.css")"
                                 rel="stylesheet" type="text/css" media="all" />
        <script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript">
        </script>
        <script src="@Url.Content("~/Scripts/jquery.dataTables.min.js")" 
                                  type="text/javascript"></script>
        <script language="javascript" type="text/javascript">
        $(document).ready(function () {
            @RenderSection("onReady", required: false)
            });
        </script>
    </head>
    <body id="dt_example">
        <div id="container">
            <a href="/Home/Index">Static table</a> | 
            <a href="/Home/ServerSide">Server-side generated table</a> | 
            <a href="/Home/Ajax">Ajax-loaded table</a>
            @RenderBody()
        </div>
    </body>
</html> 

The layout page includes all the necessary CSS/JavaScript files, the defined common structure of the page, and the left two sections that will be defined on the particular pages. These sections are:

  1. onReady section used to include custom JavaScript that will be executed on the document ready event,
  2. Body section where the HTML code for each page will be placed.

In the following examples are described three different cases of usage of the expandable table.

Static view

In the static view page, a primary company table is generated as a plain HTML table. The HTML source of the static table is shown in the following listing:

<div id="demo">
    <table id="companies" class="display">
        <thead>
            <tr>
                <th></th>
                <th>Company name</th>
                <th>Address</th>
                <th>Town</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td><img src="/Content/images/details_open.png" 
                    rel="0" alt="expand/collapse"></td>
                <td>Emkay Entertainments</td>
                <td>Nobel House, Regent Centre</td>
                <td>Lothian</td>
            </tr>
            <tr>
                <td><img src="/Content/images/details_open.png" 
                   rel="0" alt="expand/collapse"></td>
                <td>The Empire</td>
                <td>Milton Keynes Leisure Plaza</td>
                <td>Buckinghamshire</td>
            </tr>
            ...
        </tbody>
    </table>
</div>

This code is placed in the body section of the view. In the first column is placed an image with a rel attribute equal to the ID of the company that is shown. When the user clicks on this image, an AJAX call will be sent to the server-side controller and the HTML that is returned will be shown as an expanded row. The JavaScript code that performs this action is placed in the head section of the view, and is shown below:

var oTable;
$('#companies tbody td img').click(function () {
    var nTr = this.parentNode.parentNode;
    if (this.src.match('details_close')) {
        /* This row is already open - close it */
        this.src = "/Content/images/details_open.png";
        oTable.fnClose(nTr);
    }
    else {
        /* Open this row */
        this.src = "/Content/images/details_close.png";
        var companyid = $(this).attr("rel");
        $.get("CompanyEmployees?CompanyID=" + companyid, function (employees) {
            oTable.fnOpen(nTr, employees, 'details');
        });
    }
});

In the JavaScript shown above, a click handler is added to each image in the first column. When the user clicks on the image, this code checks whether the image is in closed state or not. If the image is in closed state, an AJAX call is sent to the server; the returned HTML is injected using the fnOpen function and the image is changed; otherwise, the details row is closed using the fnClose function. The oTable variable is a reference to the DataTable object initialized in the following code.

You will need to add the DataTables initialization code that will initialize the company table. This is required in order to use the fnOpen and fnClose functions. An example of the DataTable initialization code is shown below. The only specific setting is the usage of jQueryUI for styles and making the first column (containing the image) non-sortable and non-searchable.

/* Initialize table and make first column non-sortable*/
oTable = $('#companies').dataTable({  "bJQueryUI": true,
                                        "aoColumns": [
                                        {   "bSortable": false,
                                            "bSearchable": false },
                                        null,
                                        null,
                                        null
                                ]
    });

Server-side generated view

In the second example in this article, the company table is generated on the server-side dynamically. The body of the view page is shown in the following listing:

<div id="demo">
    <table id="companies" class="display">
        <thead>
            <tr>
                <th></th>
                <th>Company Name</th>
                <th>Address</th>
                <th>Town</th>
            </tr>
        </thead>
    @foreach (var item in Model) {
        <tr>
            <td><img src="/Content/images/details_open.png" 
                alt="expand/collapse" rel="@item.ID"/></td>
            <td>@item.Name</td>
            <td>@item.Address</td>
            <td>@item.Town</td>
        </tr>
    }
    </table>
</div>

As you can see, there are no big differences - the view dynamically generates the same structure as in the previous example. Therefore, the on ready section is the same as in the previous view.

Note that this view uses a list of companies for generating a table, therefore the action method should pass the list of all companies in the application. An example of the action method that is used in this example to pass the model to the view is shown in the listing below:

public class HomeController : Controller
{
    public ActionResult ServerSide()
    {
        return View(DataRepository.GetCompanies());
    }
}

AJAX view page

The last example in this article is a view page where companies are dynamically loaded via an AJAX call. This is a performance improvement of the previous case that should be used if you expect that there will be a large number of items in the table, and that loading all of them at once could cause performance issues. The body of the view page just represents the structure of the table, as shown in the listing below:

<div id="demo">
    <table id="companies" class="display">
        <thead>
            <tr>
                <th></th>
                <th>Company name</th>
                <th>Address</th>
                <th>Town</th>
            </tr>
        </thead>
        <tbody>
        </tbody>
    </table>
</div>

The onready section that contains the initialization code is slightly modified as shown in the following listing:

var oTable;
$('#companies tbody td img').live('click', function () {
    var nTr = this.parentNode.parentNode;
    if (this.src.match('details_close')) {
        /* This row is already open - close it */
        this.src = "/Content/images/details_open.png";
        oTable.fnClose(nTr);
    }
    else {
        /* Open this row */
        this.src = "/Content/images/details_close.png";
        var companyid = $(this).attr("rel");
        $.get("CompanyEmployees?CompanyID=" + companyid, function (employees) {
            oTable.fnOpen(nTr, employees, 'details');
        });
    }
});

The difference is that the live function is used instead of the direct click handler because the event should be added to the elements that are dynamically added in each reload. Without the live function, you will need to apply a click handler each time the table is reloaded from the server side.

The DataTables initialization call is also changed - you will need to set the bServerSide parameter to true indicating that the companies will be loaded from the AJAX source and the server side page will provide the table data. The initialization call is shown in the following listing:

/* Initialize table and make first column non-sortable*/
oTable = $('#companies').dataTable({
                            "bProcessing": true,
                            "bServerSide": true,
                            "sAjaxSource": 'AjaxHandler',
                            "bJQueryUI": true,
                            "aoColumns": [
                      { "bSortable": false,
                                    "bSearchable": false,
                        "fnRender": function (oObj) {
                              return '/Content/images/details_open.png" ' + 
                                     'alt="expand/collapse" rel="' + 
                                     oObj.aData[0] + '"/>';
                                      } 
                       },
                        null,
                        null,
                        null
                                ]
});

Note that in the first column, the fnRender function is used to generate an image and place the ID of the company in the rel attribute of the expand/collapse image. Setting server side processing in ASP.NET MVC is out of the scope of this article so I recommend you to take a look at the Integrating DataTables into the ASP.NET MVC application article (this is the first article in this group of articles).

Conclusion

This article explained how you can create an expandable master details table using the jQuery DataTables plug-in and ASP.NET MVC. This is the fourth article in the group of articles I wrote about the integration of the jQuery DataTables plug-in into an ASP.NET MVC application. A few other articles that might interest you are:

  1. Integrating the jQuery DataTables plug-in into an ASP.NET MVC application which describes how you can implement server-side processing in ASP.NET in order to implement high performance server side tables using the jQuery DataTables plug-in
  2. Creating editable DataTables in ASP.NET MVC which describes how you can implement add, edit, and delete functionalities in a data table
  3. Creating parent-child relationships between tables in ASP.NET MVC - an article similar to this one which explains how you can connect two tables in a parent child manner

Articles in this group might enable you to create functional and effective tables in ASP.NET MVC using the jQuery DataTables plug-in.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Jovan Popovic
Architect Gowi
Serbia Serbia
Started as a young scientist - winning the highest national awards in mathematics, physics, electrotechnics, and electronics.
Graduated from Faculty of Electrical Engineering, Department of Computer Techniques and Informatics, University of Belgrade, Serbia, as a first in the class, as a Master of Software Sciences.
Currently working in Gowi as a Software engineer, architect, and project manager since 2004 - mostly using Microsoft technologies (ASP.NET, C#). Member of JQuery community - created few popular plugins (four popular JQuery DataTables add-ins and loadJSON template engine).
Interests: Software engineering process(estimation and standardization), mobile and business intelligence platforms.

Comments and Discussions

 
QuestionOther language in interface PinmemberCélio19-Sep-14 8:47 
GeneralMy vote of 5 PinmemberMember 1038910813-Sep-14 13:32 
Questiondoesn't work :( PinmemberRobert Everett16-Mar-14 13:35 
GeneralExcellent Article. PinmemberIvan B Dcosta17-Nov-13 19:53 
Bugthe "rel" attribute PinmemberCarlo Lo Presti15-Nov-13 9:08 
QuestionIEnumerable vs IQueryable PinmemberBenCoding1-Nov-13 7:14 
QuestionDataTable column Filtering Pingroupsonusntsh26-Jun-13 2:27 
GeneralPartialView PinmemberSándor Hatvani15-Jan-13 3:04 
QuestionExpand/Collapse & Editable at multiple levels datatable sample PinmemberMember 947500226-Oct-12 0:15 
QuestionDataTables warning: JSON data from server could not be parsed. This is caused by a JSON formatting Pinmembermazzy_b20-Sep-12 10:18 
GeneralMy vote of 5 PinmemberChristian Amado28-Aug-12 13:45 
GeneralMy vote of 5 PinmemberReza Ahmadi31-Jul-12 1:47 
QuestionMy vote of 5 and a Question PinmemberLatin Warrior13-Jun-12 6:13 
GeneralMy vote of 4 Pinmemberandy(-RKO)15-May-12 23:58 
QuestionWorks only with 4 columns Pinmemberhmtech9-May-12 9:37 
AnswerRe: Works only with 4 columns Pinmemberhmtech9-May-12 9:56 
Questiondont show details PinmemberGonzalo Soriano4-May-12 7:47 
QuestionHtml helpers PinmemberSridhar Sathya2-May-12 7:22 
GeneralGreat post Pinmemberstevefortner9-Feb-12 10:55 
GeneralMy vote of 5 PinmemberFranzBe21-Jan-12 2:26 
QuestionEditable detail table PinmemberDavid Sellen11-Aug-11 5:03 
AnswerRe: Editable detail table Pinmemberlordoffriends3-Dec-12 4:44 
GeneralMy vote of 5 PinmemberDavid Sellen11-Aug-11 5:01 
Questiongood post PinmemberShining Legend19-Jun-11 3:32 
AnswerRe: good post PinmemberJovan Popovic20-Jun-11 3:05 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141223.1 | Last Updated 2 May 2011
Article Copyright 2011 by Jovan Popovic
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid