EnableSorting([tableConfig]) – Add sorting capability to HTML Table

Standard

I was having fun learning JavaScript and jQuery for a while now, at the same time I was trying to develop some utility functions that I might use or need in a future project that I’m planning to develop.

One of the things that I thought to be useful is adding a sorting capability to HTML table. Although there are a lot of open source plugins or widgets that I can simply use but my intention is to build it from scratch myself to get a depth experience in the language and related JavaScript technologies. I like to share my experience developing the function and the related source code in this post.

The function accept two parameters:

  • table selector: either the table id or class (follow the same jQuery selector standards).
  • sortingConfig: optional parameter that can be passed to define which column is sortable and also define a default sorting after the table is first rendered.

the function returns an object that includes below public members:

  • sort: a function that can be used to dynamically sort the table from the code, passing the column selector and a sort order (asc | desc).
  • toString: override the object prototype toString function, when called it return the selector used (useful for debugging).
  • selector: the selector passed to the EnableSorting function object.

below is the JavaScript source code for the function. I encapsulated the code inside a function object and extended  the prototype object to share the public members. I also added the function to jQuery functions set in order to be  used directly from jQuery by using the jQuery.fn.extend() feature.

"use strict";
/**
 * @author Rjawad-admin
 */
//EnableSorting constructor function, accept tableId and optional tableConfig
function EnableSorting(tableSelector, sortingConfig) {

	this.selector = tableSelector;

	var table = $(tableSelector);

	var tableHeaderRow = $("thead", table).children("tr");
	var tableHeaderColumns = tableHeaderRow.children("th");

	//check if sortingConfig.columnsConfig exists
	if (sortingConfig !== undefined) {
		// check if the SortingConfig is exists
		if (sortingConfig.columnsConfig !== undefined && sortingConfig.columnsConfig.length > 0)
			tableHeaderColumns = tableHeaderColumns.filter(function(index) {
				return sortingConfig.columnsConfig.some(checkIfSortable, this);
			});
		//check if default sortsing is specified
		if (sortingConfig.defaultSort !== undefined)
			this.sort(sortingConfig.defaultSort.columnSelector, sortingConfig.defaultSort.sortOrder);
	}

	//check if the column is listed in the sortable columns
	function checkIfSortable(element, index, array) {
		return (element.id != undefined && element.id.toLowerCase() === $(this, tableHeaderRow).attr('id').toLowerCase()) && element.sortable;
	}

	//Add the sort click event to the sortable columns
	tableHeaderColumns.each(function(index, columnElement) {

		var eventData = {
			sortOrder : "asc",
			sortColumnIndex : $(columnElement, tableHeaderRow).index() // the column index of the original columns set
		};

		$(columnElement).on("click", null, eventData, function(event) {
			applySortToColumn(event);
		});
	});

	//Private: applySortToColumn
	function applySortToColumn(event) {

		var tableBody = $("tbody", table);
		var tableData = tableBody.find("tr").toArray();

		//copy to a temp map by extracting the column values from the table to sort it.
		var map = tableData.map(function(e, i) {

			var trColumns = $(e, this).children("td");
			var elementValue = trColumns.eq(event.data.sortColumnIndex).text().toLowerCase();

			return {
				index : i,
				value : elementValue
			};
		}, tableData);

		if (event.data.sortOrder.toLowerCase() === "asc") {
			//ascending sort
			//sorting the map containing the reduced values
			map.sort(function(a, b) {
				// if Numberic then apply (a-b),
				if ($.isNumeric(a.value) && $.isNumeric(b.value))
					return a.value - b.value;
				else
					//if string then (a > b)
					return a.value > b.value ? 1 : a.value < b.value ? -1 : 0;
			});
			event.data.sortOrder = "desc";
		} else {
			//descending  sort
			// sorting the map containing the reduced values
			map.sort(function(a, b) {
				// if Numberic then apply (b-a)
				if ($.isNumeric(a.value) && $.isNumeric(b.value))
					return b.value - a.value;
				else
					//if string then apply (a < b)
					return a.value < b.value ? 1 : a.value > b.value ? -1 : 0;
			});
			event.data.sortOrder = "asc";
		};

		// map the sorted map to the original array
		var sortedData = map.map(function(e) {
			return tableData[e.index];
		});

		// empty the table body tag (all rows/cells) and populate it based on the sorted TR array.
		tableBody.empty();
		tableBody.append(sortedData);
	};
}

//Sort: public method to override the current sorting and specify a new sort
EnableSorting.prototype.sort = function(columnSelector, sortOrder) {

	var tableHeaderRow = $("thead", this.selector).children("tr");
	var sortColumnIndex = $(columnSelector, tableHeaderRow).index();

	var tableBody = $("tbody", this.selector);
	var tableData = tableBody.find("tr").toArray();

	//copy to a temp map by extracting the column values from the table to sort it.
	var map = tableData.map(function(e, i) {

		var trColumns = $(e, this).children("td");
		var elementValue = trColumns.eq(sortColumnIndex).text().toLowerCase();

		return {
			index : i,
			value : elementValue
		};
	}, tableData);

	if (sortOrder.toLowerCase() === "asc") {
		//ascending sort
		//sorting the map containing the reduced values
		map.sort(function(a, b) {
			// if Numberic then apply (a-b),
			if ($.isNumeric(a.value) && $.isNumeric(b.value))
				return a.value - b.value;
			else
				//if string then (a > b)
				return a.value > b.value ? 1 : a.value < b.value ? -1 : 0;
		});
	} else {
		//descending  sort
		// sorting the map containing the reduced values
		map.sort(function(a, b) {
			// if Numberic then apply (b-a)
			if ($.isNumeric(a.value) && $.isNumeric(b.value))
				return b.value - a.value;
			else
				//if string then apply (a < b)
				return a.value < b.value ? 1 : a.value > b.value ? -1 : 0;
		});
	};

	// map the sorted map to the original array
	var sortedData = map.map(function(e) {
		return tableData[e.index];
	});

	// empty the table body tag (all rows/cells) and populate it based on the sorted TR array.
	tableBody.empty();
	tableBody.append(sortedData);
};

//override the toString function to display the table Id associated with the LiveTable object
EnableSorting.prototype.toString = function() {
	return "<EnableSorting: " + this.selector + ">";
};

//extend to JQuery function prototype
jQuery.fn.extend({
	enableSorting : function(sortingConfig) {
		return new EnableSorting(this.selector, sortingConfig);
	}
});

The function (EnableSorting) can be used either by creating a new object of EnableSorting function or by calling it directly from a jQuery object.

below is a sample table to show how the enableSorting method can be used. The implementation assume a well formatted HTML5 table (for the sake of simplicity).

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>EnableSorting</title>
		<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
		<script src="EnableSorting.js"></script>
		<script src="AppLogic.js"></script>
	</head>
	<body>
		<table id="classes">
			<thead>
				<tr>
					<th id="ClassName">Class Name</th>
					<th id="StartDate">Start Date</th>
					<th id="MaxNoOfAttendies">Max No of Attendies</th>
					<th id="Status">Status</th>
				</tr>
			</thead>
			<tbody>
				<tr>
					<td>Math</td><td>10/05/2013</td><td>15</td><td>Open</td>
				</tr>
				<tr>
					<td>Chemistry</td><td>10/15/2013</td><td>20</td><td>Open</td>
				</tr>
				<tr>
					<td>Physics</td><td>09/05/2013</td><td>20</td><td>Closed</td>
				</tr>
				<tr>
					<td>English</td><td>10/20/2013</td><td>30</td><td>Cancelled</td>
				</tr>
				<tr>
					<td>History</td><td>10/05/2013</td><td>25</td><td>Registration Closed</td>
				</tr>
				<tr>
					<td>Computer Skills</td><td>09/20/2013</td><td>10</td><td>Open</td>
				</tr>
			</tbody>
		</table>
	</body>
</html>

Below is a sample code showing the two ways of using the enableSorting function on above table.

$(document).ready(function() {

/*Using the EnableSorting Function:
create a new object of
EnableSorting and pass the id of the
HTML table (# must be used for ID,
following jQuery selector standards)*/
var tableClasses= new EnableSorting("#classes");

/*Using the enableSorting function from
a jQuery object (as extended function)
after selecting the table*/
var tableClasses = $("#classes").enableSorting();

});

The function accepts optional parameter (sortingConfig) object to define the sortable columns and also specify a default sorting after the table is first rendered. Below example shows how the sortingConfig is used to add sorting to specific columns and disable it on other columns.

$(document).ready(function() {

	var sortingConfig = {
		columnsConfig : [{
			id : "ClassName",
			sortable : false
		}, {
			id : "StartDate",
			sortable : true
		}, {
			id : "MaxNoOfAttendies",
			sortable : false
		}, {
			id : "Status",
			sortable : true
		}],
		defaultSort : {
			columnSelector : "#Status",
			sortOrder : "asc"
		}
	};

	var classes = $("#classes").enableSorting(sortingConfig);
});

The function returns an object that has “sort” method that can be invoked to sort the table based on the passed sorting column and sort order (asc | desc). below example shows how the sort function can be used

var classes = $("#classes").enableSorting(sortingConfig);
classes.sort("#StartDate", "asc");

EnableSorting is tested with Chrome, Firefox and latest version of IE and it seems work fine. I noticed it did not work on browsers that does not support the JavaScript array.some method that I used in the function.

I’m still working to optimize the function but so far I had so much fun developing it and learn new stuff, nothing more beneficial than experience and practice, reading gives me knowledge while practicing solidify my knowledge and push my knowledge limits.

Feel free to use the code and modify it.

I’m sure there might be better ways to add sorting to a table, appreciate any feedback or criticism to enhance it or fix any encountered bugs.

Happy coding!

Advertisements