Creating expandable master-details table (JQuery DataTables and ASP.NET MVC integration - Part IV)
Introduction
Creating fully functional tables with pagination, sorting, filtering features is an easy task if you use JQuery DataTables plugin. This plugin enables you to add all of these functionalities to the plain HTML table using a single line of code shown in the following example:
("#myTable").dataTables();
This line of code finds a HTML table with an id “myTable” and adds pagination, sorting, and filtering by keyword functionalities. A plain HTML table is converted into the fully functional table shown on the figure below:
In this article will be shown how the standard DataTable can be extended to become expandable master-details table. Goal of the article is to present how can be shown list of companies with expand button that will show list of employees for the selected company as it is shown in the figure below:
In this exampe each record in the table (companies in this example) has expand button and when this button is clicked, details are opened. Details can be either some child records (employees that works for this company as in this example), additional details about the company (e.g. logo, description, and other information that are not shown in table row) or combination of these two.
Background
Expandable tables are common requirement in the situations where you have related entities. If you have companies that have employees, teachers that have students, sales order with sales order items probably you will have some requirement to implement list of master records (companies, teachers, sales orders) with ability to show related records for each master row when the master row is expanded. Other situation where you might need expandable tables is when just minimal set of columns is shown in table and there is a requirement that when user clicks on any row more details should be shown in the expanded row. These requirement can be easily implemented using the JQuery 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 table on the client-side.
When expandable table is implemented, the following scenario is used:
- One additional column is added to the table, containing the expand/collapse buttons that enables user to expand selected table row,
- When user click on the expand button, AJAX call is sent to the server and HTML for details will be returned,
- HTML that is returned is injected as a sub-row using DataTables fnOpen function and expand button is converted to collapse button,
- When user click on the collapse button sub-row is closed using the DataTables fnClose function and collapse button is converted back to the expand button.
Using the code
In this example, it is shown how expandable master-details table can be implemented in ASP.NET MVC using JQuery DataTables plugin. Three parts of the project need to be implemented:
- Model where are defined structures of data that will be shown,
- Controller – class that reacts on the requests sent by the DataTables plugin,
- View – rendering engine and client side logic that need to be implemented.
In the following sections are described these parts.
Model
The model is represented as a two classes related with one-to-many relationship. In this example are used companies as a master information and employees as details. 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; }
}
Relationship between employees and their companies is established via CompanyID property in the employee class.
Controller
Controller class responds to the user actions and return responses. From the client-side will be sent request for list of employees by company id that is sent when the expand button is presses. This request will contain id of the company for whom the list of emplyees shoudl be shown in the expanded row. Action method in the controller that return employees by company id is 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 id of the company and finds all employees with that company id. There is a one utility partial view that format 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>
In this view is generated a HTML table, using a list of employees that are filtered by the controller. This HTML code is sent back to the client-side as a AJAX response. In the following figure is shown AJAX response of the Home/CompanyEmployees call traced in the FireBug:
View
View pages are used to show tables and contain client-side JavaScripts. There are three view pages used in this example:
- Static view page where table of companies is static HTML table,
- Server side view where table of companies is generated on the server-side,
- Ajax view page where table of companies is loaded via AJAX call.
However, all these view uses the same client-side logic for showing details (list of employees) – ajax call is sent to the controller action /Home/CompanyEmployees described above and HTML response returned by the controller is injected in the table.
All three views uses the same layout page where are included 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>
On the layout page are included all necessary CSS/JavaScript files, defined common structure of the page and left two sections that will be defined on the particular pages. These sections are:
- onReady section used to include custom JavaScript that will be executed on document ready event,
- Body section where HTML code for each page will be placed.
In the following examples are described three different cases of usage of expandable table.
Static view
In the static view page, primary company table is generated as a plain HTML table. 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 image with rel attribute equal to the id of the company that is shown. When user clicks on this image, AJAX call will be sent to the server-side controller and HTML that is returned will be shown as an expanded row. JavaScript code that performs this action is placed in the head section of the view and it 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, click handler is added to the each image in the first column. When a user clicks on the image, this code checks whether the image is in closed state or not. If image is in the closed state AJAX call is sent to the server, returned HTML is injected using a fnOpen function, and image is changed, otherwise, details row is closed using a fnClose function. oTable variable is a refernece to the DataTable object initialized in the foowing code.
You will need to add DataTables initialization code that will initialize company table. This is required in order to use fnOpen and fnClose functions. Example of DataTable initialization code is shown below. Only specific settings is usage of JQueryUI for styles and making first column (containing 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, company table is generated on the server-side dynamically. 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 - view dynamically generates the same structure as in the previous example. Therefore, on ready section is same as in the previous view.
Note that this view uses list of companies for generating table, therefore action method should pass list of all companies in the application. Example of the action method that is used in this example to pass 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 AJAX call. This is performance improvement of the previous case that should be used if you expect that there will be large number of items in the table, and that loading of of them at once could cause perfrmance issue. The body of the view page just represents a 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>
On ready section that contains 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');
});
}
});
Difference is that ‘live’ function is used instead of the direct click handler because event should be added to the elements that are dynamically added in each reload. Without live function, you will need to apply click handler each time table is reloaded from the server side.
DataTables initialization call is also changed - you will need to set bServerSide
parameter to true indicating that companies will be loaded from AJAX source, and server side page that will provide table data. 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 is used fnRender
function to generate image and place ID of the company in the rel attribute of the expand/collapse image. Setting server side processing in ASP.NET MVC is out of 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 a first article in this group of articles).
Conclusion
In this article is explained how you can create expandable master details table using JQuery DataTables plugin and ASP.NET MVC. This is a fourth article in the group of article I wrote about the integration of JQuery DataTables plugin into the ASP.NET MVC application. Few other articles that might interest you are:
- Integration JQuery DataTables plugin into the ASP.NET MVC application where is described how you can implement server-side processing in ASP.NET in order to implement high performances server side table using JQuery DataTables plugin,
- Creating editable DataTables in ASP.NET MVC where is described how you can implement add, edit and delete functionalities in data table,
- Creating parent-child relationships between the tables in ASP.NET MVC – and article similar to this one where is explained how you could connect two tables in the parent child manner.
Articles in this group might enable you to create functional and effective tables in ASP.NET MVC using the JQuery DataTables plugin.
Post Comment
wHO8b0 There is perceptibly a bundle to identify about this. I feel you made certain nice points in features also.
dQJTvp This is one awesome article.Really thank you!
OovDbx I used to be able to find good advice from your blog posts.
yJEmXr This put up truly made my day. You can not believe just how
6Wna8Z It as not that I want to replicate your web-site, but I really like the style and design. Could you let me know which theme are you using? Or was it especially designed?
OoJJDk Really informative post.Really looking forward to read more. Fantastic.
srabgB Major thankies for the article.Really looking forward to read more. Cool.
Wow, amazing blog layout! How long have you been blogging for? you make blogging look easy. The overall look of your web site is excellent, let alone the content!. Thanks For Your article about sex.
you have got an amazing weblog right here! would you wish to make some invite posts on my weblog?
This blog was how do you say it? Relevant!! Finally I have found something that helped me. Cheers!
Major thankies for the post.Thanks Again. Keep writing.
Keep up the great work , I read few blog posts on this internet site and I conceive that your weblog is rattling interesting and contains lots of good info.
Woh I like your blog posts, saved to bookmarks !.
Im grateful for the blog post.Really looking forward to read more. Keep writing.
Wow, fantastic blog layout! How long have you been blogging for? you make blogging look easy. The overall look of your site is wonderful, as well as the content!. Thanks For Your article about sex.
Thanks for the article post.Really thank you!
your placement in google and could damage your quality score if advertising
Thanks for sharing, this is a fantastic article. Really Cool.
Looking forward to reading more. Great article post.Really looking forward to read more.
You ave made some good points there. I looked on the net for additional information about the issue and found most people will go along with your views on this website.
Thanks for sharing, this is a fantastic blog post.Much thanks again. Fantastic.
the time to study or go to the material or internet sites we ave linked to below the
I'аve read a few just right stuff here. Definitely price bookmarking for revisiting. I wonder how much effort you place to make such a great informative website.
I'аve learn some good stuff here. Definitely worth bookmarking for revisiting. I wonder how so much attempt you place to create this sort of fantastic informative website.
wants to find out about this topic. You realize a whole lot its almost tough to argue with you (not that I really will need toHaHa).
Im grateful for the blog post.Really looking forward to read more. Will read on
Personally, if all webmasters and bloggers made good content as you did, the web will be much more useful than ever before.
I think other site proprietors should take this site as an model, very clean and excellent user friendly style and design, let alone the content. You are an expert in this topic!
Looking forward to reading more. Great article.Really thank you! Want more.
Im no pro, but I feel you just made an excellent point. You definitely know what youre talking about, and I can seriously get behind that. Thanks for being so upfront and so sincere.
Usually I do not read post on blogs, however I wish to say that this write-up very forced me to try and do so! Your writing style has been amazed me. Thank you, very great post.|
Very good information. Lucky me I recently found your site by chance (stumbleupon). I ave book marked it for later!
Very interesting details you have remarked, thanks for posting. Women have been trained to speak softly and carry a lipstick. Those days are over. by Bella Abzug.
Really appreciate you sharing this blog.
Im obliged for the blog article.Really looking forward to read more. Fantastic.
Usually I don at read article on blogs, however I wish to say that this write-up very compelled me to take a look at and do it! Your writing taste has been amazed me. Thank you, quite nice post.
There as definately a lot to find out about this issue. I like all of the points you made.
you ave gotten an excellent weblog here! would you wish to make some invite posts on my weblog?
Very good blog article.Really looking forward to read more. Really Great.
zmmWEP Very clean web site , appreciate it for this post.
bjDdct Way cool! Some very valid points! I appreciate you writing this article plus the rest of the site is really good.
we like to honor numerous other web web-sites on the web, even if they aren
Simply a smiling visitor here to share the love (:, btw outstanding design.
let yаА аБТu get free shi?ping fаА аБТom certain
Pretty nice post. I just stumbled upon your blog and wanted to say that I ave truly enjoyed browsing your blog posts. After all I will be subscribing to your feed and I hope you write again very soon!
welcome to wholesale mac makeup from us.
This is one awesome article post.Really thank you! Keep writing.
Those concerned with privacy will be relieved to know you can prevent the public from seeing your personal listening habits if you so choose.
Very informative post.Really looking forward to read more. Really Great.
Regards for helping out, fantastic info.