This website is no longer actively supported
Written by John DeVight on 14 Mar 2013 21:31
Download Source Code
Overview
I recently wrote an article about Exporting the Kendo UI Grid Data to Excel in an ASP.NET Application and while that is great for people who use Kendo UI, I thought it would be nice to demonstrate how to export data from an HTML table to Excel. While writing that article, I was populating the table with 500 rows and decided it would be nice if I could make the table a fixed height and be able to scroll the content. Thus, I started writing a jQuery plugin for a scrollable table.

Getting Started
When I started writing the jQuery plugin, I looked around for a good article on writing a jQuery plugin. I found that How to Create Your Own jQuery Plugin by Elijah Manor did a great job of explaining how to write a jQuery plugin. I would recommend reading this article if you are interested in writing your own jQuery plugins.
While the attached source code contains a Visual Studio 2012 solution file and a Web.config file, you don't need Visual Studio to run the attached example. The example will run just fine if you open the index.html file in a browser. I used FireFox 19.0 when writing the jQuery plugin and also tested it using Internet Explorer 9.0.
Understanding the HTML Structure for the Scrollable Table
The scollable table isn't just a table. It is actually comprised of div's and tables. The is a 'root' div that contains 2 div's. The first div has a table that is for the scrollable table column headings. The second div has a table that is for the scrollable table data. Here is the HTML structure:
<div class='scrollable-table'> <div class='table-heading'> <table> <colgroup></colgroup> <thead> <tr></tr> </thead> </table> </div> <div class='table-body'> <table> <colgroup></colgroup> <tbody></tbody> </table> </div> </div>
The table in the 'table-heading' and the table in the 'table-body' both contain a <colgroup> element. For each column in the table, a <col style="width:{number}px;"> element is added to the <colgroup> where {number} represents the width of the column.
The table in 'table-heading' contains a <thead> element. For each column in the table, a <td>{title}</td> element is added to the <tr> element with the column title where {title} represents the title of the column.
The table in 'table-body' contains a <tbody> element. For each JSON object in the array of data, a table row element is added to the <tbody> element with table cells containing the data from the JSON object.
Creating the jQuery Plugin
Overview
When I use the Scrollable Table plugin, I want to use a jQuery selector to get the 'root' element and then call the "scrollableTable" function and pass in all the options needed by the jQuery plugin. Assuning the 'root' HTML element is defined as:
<div id="people"></div>
I would call the jQuery plugin like this:
$("#people").scrollableTable({ /* list of options defined here */ });
Defining the structure of the jQuery Plugin
The jQuery plugin is defined using the jQuery.prototype function. A shortcut that is commonly used for this is $.fn. The safest way to declare a jQuery plugin is in an annonymous function. An anonymous function executes immediately and provides a way of defining an alias for jQuery. While it is common to use the dollar sign as the alias for jQuery, the dollar sign could conflict with other JavaScript libraries. Here is an jQuery plugin wrapped in an anonymous function:
// The dollar sign ($) is an alias for jQuery that was passed into // the anonymous function making dollar sign safe to use. (function ($) { $.fn.scrollableTable = function (options) { }); })(jQuery); // Pass jQuery as a parameter into the anonymous function.
As described in the How to Create Your Own jQuery Plugin article, there are 2 things I want to do before I start implementing the jQuery plugin:
- Create a module for the plugin that will contain all the public methods for the plugin.
- Allow for Chaining by returning the jQuery object.
Here is the code:
// The dollar sign ($) is an alias for jQuery that was passed into // the anonymous function making dollar sign safe to use. (function ($) { // Module containing all the public functions for the plugin. ScrollableTable = function (element, options) { }; // The Scrollable Table jQuery plugin. $.fn.scrollableTable = function (options) { return this.each(function () { // Instantiate the module and pass in a reference to the element and the options passed in. (new ScrollableTable($(this), options)); }); }; })(jQuery); // Pass jQuery as a parameter into the anonymous function.
Options for the Scrollable Table Plugin
Before getting into creating the ScrollableTable module, it would help to know what options are being passed in to the jQuery plugin. Here are the options:
- Height for the Scrollable Table.
- Array of JSON objects containing the data to be displayed.
- Array of columns. Each column is defined as a JSON object with the following attributes:
- title: the column title.
- width: the column width.
- field: the attribute in the JSON object containing the value to be displayed in a table cell.
- isDate: flag indicating that the value is a date.
Here is an example of the options passed into the Scrollable Table plugin:
{ height: 400, columns: [ { title: "First Name", field: "FirstName", width: 100 }, { title: "Last Name", field: "LastName", width: 100 }, { title: "Birth Date", field: "BirthDate", isDate: true, width: 100 } ], data: [ { FirstName: "Nancy", LastName: "Kirkland", BirthDate: new Date("Sep 19 1937") }, { FirstName: "Robert", LastName: "Suyama", BirthDate: new Date("Mar 27 1966") }, { FirstName: "Janet", LastName: "Buchanan", BirthDate: new Date("Aug 30 1963") } ] }
Creating the ScrollableTable Module
The ScrollableTable module does the following:
- Store the options as an attribute of the module.
- Store the instance of the module with the element using the jQuery.data function.
- Define the init function that will initialize the scrollable table.
- Call the init function.
The options and element are stored as an attribute of the module, with the following code:
this.options = options; this.element = element;
The instance of the module is stored with the element using the jQuery.data function with the following code:
element.data("scrollableTable", this);
The init function creates all the elements for the scrollable table. The init function starts with adding the 'scrollable-table' class to the 'root' element. Next the elements for the 'table-heading' and 'table-body' are created. jQuery selectors are used to get the elements that need to be populated. Here is the code:
this.init = function (element, options) { var that = this; element.addClass("scrollable-table"); // Create the elements for the table-heading. var $heading = $("<div class='table-heading'><table><colgroup></colgroup><thead><tr></tr></thead></table></div>"); var $headingTableColGroup = $heading.find("colgroup"); var $headingTableRow = $heading.find("tr"); // Create the elements for the table-body. var $body = $("<div class='table-body' style='height:" + that.options.height + "px;'><table><colgroup></colgroup><tbody></tbody></table></div>"); var $bodyTableColGroup = $body.find("colgroup"); var $bodyTableTbody = $body.find("tbody"); };
Next the init function loops through the array of columns and:
- Creates a <col> element for each column and adds it to the table in the 'table-header'.
- Creates a <td> element for each column and adds it to the table row in the 'table-header'.
- Creates a <col> element for each column and adds it to the table in the 'table-body'.
The 'table-header' is then appended to the 'scrollable-table' element.
Here is the code:
// In the table-heading and table-body, add a col to the colgroup for each column. // In the table-heading, add a table cell with the title of each column. $.each(that.options.columns, function (idx, column) { $headingTableColGroup.append("<col style='width:" + column.width + "px;'/>"); $bodyTableColGroup.append("<col style='min-width:" + column.width + "px; width: " + ((idx == that.options.columns.length - 1) ? "100%" : column.width + "px") + ";'/>"); $headingTableRow.append("<td>" + column.title + "</td>"); }); // Add the table-heading to the scrollable-table. element.append($heading[0].outerHTML);
Next the init function calls the _displayData function. The _displayData function takes 2 parameters:
- The <tbody> element to add the rows.
- The data to display in the scrollable table.
Finally, the init function appends the 'table-body' to the 'scrollable-table' element.
Here is the code in the init function:
that._displayData($bodyTableTbody, that.options.data); // Add the table-body to the scrollable-table. element.append($body[0].outerHTML);
The _displayData function loops through the array of data items and:
- Creates a <tr> element.
- loops through the columns array and for each column, and using the column.field attribute, gets the value from the data item to create a <td> element that is appended to the <tr> element.
- Adds the <tr> element to the <tbody> element in the 'table-body'.
And here is the code for the _displayData function:
this._displayData = function ($tbody, data) { /// <summary> /// Display the data in the table body. /// </summary> var that = this; // In the table-body, add a row for each data item. $.each(data, function (idx, item) { $tableRow = $("<tr></tr>"); // For each column in the data item, add a table cell with the value. $.each(that.options.columns, function (col, column) { $tableRow.append("<td>" + (column.isDate ? item[column.field].toDateString() : item[column.field]) + "</td>"); }); // Add the row to the table-body. $tbody.append($tableRow[0].outerHTML); }); };
After the init function is defined, the init function is called.
Implementing Filtering
In addition to initializing the scrollable table, I thought it would be nice to add a filter capability. I added 2 additional functions to the ScrollableTable module; applyFilter and removeFilter.
The applyFilter takes a parameter of criteria. The criteria JSON Object contains 2 attributes:
- field: the name of the field to apply the filter.
- exp: the regular expression object used to determine whether the item is included in the filter.
The applyFilter function loops throught the data, applies the regular expression to each item and adds the items that are included in the filter to an array. The <tbody> element is emptied of all rows and the _displayData function is called to add the filtered items to the <tbody> element. Last, the array of filtered items are stored as an attribute of the module. Here is the code:
this.applyFilter = function (criteria) { var that = this; setTimeout(function () { var filtered = []; $.each(that.options.data, function (idx, item) { if (criteria.exp.test(item[criteria.field]) == true) { filtered.push(item); } }); $tbody = that.element.find(".table-body tbody"); $tbody.empty(); that._displayData($tbody, filtered); that.filtered = filtered; }); };
The removeFilter function empties the <tbody> element of all rows and then calls the _displayData function to add all the data items to the <tbody> element. Here is the code:
this.removeFilter = function () { var that = this; setTimeout(function () { $tbody = that.element.find(".table-body tbody"); $tbody.empty(); that._displayData($tbody, that.options.data); that.filtered = null; }); };
The Completed ScrollableTable Module
Here is the code for the ScrollableTable module:
// Module containing all the public functions for the plugin. ScrollableTable = function (element, options) { this.options = options; this.element = element; element.data("scrollableTable", this); this.init = function (element, options) { /// <summary> /// Initialize the plugin. /// </summary> /// <param name="element" type="jQuery Object">The 'root' element to create a scrollable table.</param> /// <param name="options" type="JSON Object"> /// The options for the scrollable table. Options are: /// 1. height (Integer): height of the table. /// 2. columns (Array): array of columns. Each column has the following attributes: /// - title (String): the column title. /// - width (Integer): the column width. /// - field (String): the attribute in the JSON object containing the value to be displayed in a table cell. /// - isDate (Boolean): flag indicating that the value is a date. /// 3. data (Array): array of JSON objects containing the data to be displayed. /// </param> var that = this; // The "root" div is the scollable-table. element.addClass("scrollable-table"); // layout of the scrollable table is: // <div class='scrollable-table'> // <div class='table-heading'> // <table> // <colgroup></colgroup> // <thead> // <tr></tr> // </thead> // </table> // </div> // <div class='table-body'> // <table> // <colgroup></colgroup> // <tbody></tbody> // </table> // </div> // </div> // Create the elements for the table-heading. var $heading = $("<div class='table-heading'><table><colgroup></colgroup><thead><tr></tr></thead></table></div>"); var $headingTableColGroup = $heading.find("colgroup"); var $headingTableRow = $heading.find("tr"); // Create the elements for the table-body. var $body = $("<div class='table-body' style='height:" + that.options.height + "px;'><table><colgroup></colgroup><tbody></tbody></table></div>"); var $bodyTableColGroup = $body.find("colgroup"); var $bodyTableTbody = $body.find("tbody"); // In the table-heading and table-body, add a col to the colgroup for each column. // In the table-heading, add a table cell with the title of each column. $.each(that.options.columns, function (idx, column) { $headingTableColGroup.append("<col style='width:" + column.width + "px;'/>"); $bodyTableColGroup.append("<col style='min-width:" + column.width + "px; width: " + ((idx == that.options.columns.length - 1) ? "100%" : column.width + "px") + ";'/>"); $headingTableRow.append("<td>" + column.title + "</td>"); }); // Add the table-heading to the scrollable-table. element.append($heading[0].outerHTML); that._displayData($bodyTableTbody, that.options.data); // Add the table-body to the scrollable-table. element.append($body[0].outerHTML); }; this.applyFilter = function (criteria) { /// <summary> /// Filter the data in the table. /// </summary> /// <param name="criteria" type="JSON object"> /// The JSON object has 2 attributes: /// 1. field: the field to filter on. /// 2. exp: the regular expression to apply to the field. /// </param> var that = this; setTimeout(function () { var filtered = []; $.each(that.options.data, function (idx, item) { if (criteria.exp.test(item[criteria.field]) == true) { filtered.push(item); } }); $tbody = that.element.find(".table-body tbody"); $tbody.empty(); that._displayData($tbody, filtered); that.filtered = filtered; }); }; this.removeFilter = function () { /// <summary> /// Remove the filter. /// </summary> var that = this; setTimeout(function () { $tbody = that.element.find(".table-body tbody"); $tbody.empty(); that._displayData($tbody, that.options.data); that.filtered = null; }); }; this._displayData = function ($tbody, data) { /// <summary> /// Display the data in the table body. /// </summary> var that = this; // In the table-body, add a row for each data item. $.each(data, function (idx, item) { $tableRow = $("<tr></tr>"); // For each column in the data item, add a table cell with the value. $.each(that.options.columns, function (col, column) { $tableRow.append("<td>" + (column.isDate ? item[column.field].toDateString() : item[column.field]) + "</td>"); }); // Add the row to the table-body. $tbody.append($tableRow[0].outerHTML); }); }; // Initialize the scrollable table. this.init(element, options); };
Implementing the Plugin
The HTML
The HTML contains a div with a button to apply a filter where FirstName begins with "M" and a button to remove the filter. There is also a div for the scrollable table where i have also specified a style attribute setting the width to 800px. Here is the HTML:
<div id="tableActions"> <button id="applyFilter">Filter where FirstName begins with "M"</button> <button id="removeFilter">Remove filter on FirstName</button> </div> <div id="people" style="width:800px;"></div>
The JavaScript Code
I created an Index module with an init function. The first thing that the Index.init function does is generate some data to use. To do this, I used the createRandomData function defined in people.js that can be found on the Kendo UI demos website. Here is the url: http://demos.kendoui.com/content/shared/js/people.js. I then use a jQuery selector to get the div that will display the scrollable table and call the scrollableTable plugin passing the options containing the height, columns and data. Next I add click event handlers to the button that applies the filter and the button that removes the filter. Since the ScrollableTable module is stored with the element using the jQuery.data function, to get the ScrollableTable module, I just need to use the jQuery.data function like this:
$("#people").data("scrollableTable")
Here is the complete code for the Index module:
Index = function ($) { var init = function () { var people = createRandomData(500); $("#people").scrollableTable({ height: 400, columns: [ { title: "First Name", field: "FirstName", width: 100 }, { title: "Last Name", field: "LastName", width: 100 }, { title: "City", field: "City", width: 100 }, { title: "Title", field: "Title", width: 200 }, { title: "Birth Date", field: "BirthDate", isDate: true, width: 200 }, { title: "Age", field: "Age", width: 100 } ], data: people }); $("#applyFilter").on("click", function () { $("#people").data("scrollableTable").applyFilter({ field: "FirstName", exp: new RegExp("^M") }); }); $("#removeFilter").on("click", function () { $("#people").data("scrollableTable").removeFilter(); }); }; return { init: init }; }(jQuery); $(function () { Index.init(); });
Defining the Styles for the Scrollable Table
The styles defined leverage the CSS3 standards. Each style defined below has a comment that should explain the purpose of the style.
/* Create a border around the scrollable table */ .scrollable-table { border: 1px solid black; } /* Remove border spacing within the tables (CSS3 equivalent of the table cellspacing attribute) */ .scrollable-table table { border-spacing: 0; } /* Define cell padding of 3px for all table cells (CSS3 equivalent of the table cellpadding attribute) */ .scrollable-table table td { padding: 3px; } /* Create a border between each column */ .scrollable-table table td:not(:first-of-type) { border-left: 1px solid black; } /* The table-heading and table-body will take up the full width within the scrollable-table */ .scrollable-table .table-heading, .table-body { width: 100%; } /* The table-heading will have a background color of light gray */ .scrollable-table .table-heading { background-color: lightgray; } /* The column headings will be bold */ .scrollable-table .table-heading thead td { font-weight: bold; } /* The table-body will have a vertical scroll bar and a border along the top. */ .scrollable-table .table-body { overflow-y: scroll; overflow-x: hidden; border-top: 1px solid black; } /* Even rows in the table-body will be light blue. */ .scrollable-table .table-body tr:nth-child(even) { background-color: lightblue; }
References
- How to Create Your Own jQuery Plugin
- Extending the Scrollable Table jQuery Plugin to Export to Excel
- Exporting the Kendo UI Grid Data to Excel in an ASP.NET Application