
(function($) {
	$.fn.raTable = function(settings) {
		var cfg = $.extend({
			debug          : false,			// if debug is true log messages to the Firebug / Chrome console
			sortAscending  : 'asc',			// ascending sort header class
			sortDescending : 'desc',		// descending sort header class
			headerSelected : 'selected',	// selected header class
			pagingSelected : 'selected',	// selected paging link class
			altRow         : 'alt',			// alternate row class
			overlayClass   : 'overlay',		// AJAX blocking table class
			overlayOpacity : 0.7,			// AJAX blocking table opacity
			sortable       : true,			// should sorting be enabled
			sortStart      : function(event, th) {},	// called before sorting, return false to prevent
			sortEnd        : function() {},				// called after sorting
			pagingStart    : function() {}, 			// called before paging, return false to prevent
			pagingEnd      : function() {}, 			// called after paging

			// filters for cell and row updates
			// receives JSON data and allows formatting
			rowFilter: function(event, index, row) { return row; },
			cellFilter: function(event, object, property, value) { return value; }
		}, settings);

		return this.filter("table").each(function() {
			var table = $(this);
			var get   = {};
			var url   = table.attr('datasrc') || location.href;

			// if the URL has a querystring, remove it and add it to get
			if(url.indexOf('?') != -1) {
				url = url.split('?');

				var params = url[1].split('&');

				for(var i = 0; i < params.length; i++) {
					var kv = params[i].split('=');

					get[kv[0]] = kv[1];
				}

				url = url[0];
			}

			// set up row and cell filters and make sure they can get to the Ajax URL
			table.data('url', url).bind("rowFilter", cfg.rowFilter).bind("cellFilter", cfg.cellFilter);

			get['limit'] = table.find('tbody tr').length; // number of rows displayed

			// add click to all headers with an id to handle sorting
			if(cfg.sortable) {
				table.bind("sortStart", cfg.sortStart).bind("sortEnd", cfg.sortEnd) // add custom sort handlers
					.bind("sortStart", addContentOverlay).bind("sortEnd", removeContentOverlay) // add AJAX UI blocking overlay
					.find("th[id]")
					.click(function() {
					var self = $(this);
					var evt  = $.Event("sortStart");

					self.trigger(evt, [this]);

					if(typeof(evt.result) != "undefined" && !evt.result)
						return;

					// set the new sort field and direction
					get['sort'] = self.attr("id").replace(table.attr('id'), '') + "-" + (self.hasClass(cfg.sortAscending) ? "desc" : "asc");

					// remove all selection class from other th's
					table.find("th").not(self).removeClass(cfg.headerSelected).removeClass(cfg.sortAscending).removeClass(cfg.sortDescending);

					self.addClass(cfg.headerSelected);

					// toggle the sortAscending and sortDescending selection classes
					if(self.hasClass(cfg.sortAscending))
						self.removeClass(cfg.sortAscending).addClass(cfg.sortDescending);
					else
						self.addClass(cfg.sortAscending).removeClass(cfg.sortDescending);

					// make the ajax call and load the results into the table body
					$.getJSON(getUrl(), function(data) {
						ajax(data);
						self.trigger("sortEnd");
					});
				});
			}

			table.bind("pagingStart", cfg.pagingStart).bind("pagingEnd", cfg.pagingEnd) // add custom paging handlers
				.bind("pagingStart", addContentOverlay).bind("pagingEnd", removeContentOverlay) // add AJAX UI blocking overlay
				.find("tfoot tr.pagination a")
				.click(function() {
				var self = $(this);
				var evt  = $.Event("pagingStart");

				self.trigger(evt);

				if(typeof(evt.result) != "undefined" && !evt.result)
					return false;

				self.parent().find("a").not(self).removeClass(cfg.pagingSelected);
				self.addClass(cfg.pagingSelected);

				get['page'] = /\d+/.exec(self.attr('href'));

				$.getJSON(getUrl(), function(data) {
					ajax(data);
					self.trigger("pagingEnd");
				});

				return false;
			});

			// the AJAX success handler
			function ajax(data) {
				// get columns
				var cols = table.find("th[id]");
				var body = $("<tbody/>");
				var id   = table.attr("id");

				for(var i = 0; i < data.length; i++) {
					var tr  = $("<tr/>");
					var evt = $.Event("rowFilter");

					table.trigger(evt, [i, data[i]]);

					data[i] = evt.result;

					if(data[i].cssClass)
						tr.addClass(data[i].cssClass);

					if(i % 2 != 0)
						tr.addClass(cfg.altRow);

					cols.each(function() {
						var prop = $(this).attr("id").replace(id, '');
						var td   = $("<td/>");
						var evt  = $.Event("cellFilter");

						if(data[i][prop])
							data[i][prop] = data[i][prop].replace('<', '&lt;').replace('>', '&gt;');

						table.trigger(evt, [data[i], prop, data[i][prop]]);

						data[i][prop] = evt.result;

						td.html(data[i][prop]);

						tr.append(td);
					});

					body.append(tr);
				}

				table.find("tbody").replaceWith(body);
			}

			// display an overlay over the table contents during the AJAX load
			function addContentOverlay(args) {
				if(!args)
					args = {};

				if(typeof(args.result) != "undefined" && !args.result)
					return;

				var tbody  = table.find("tbody");
				var coords = tbody.position();
				var div    = $("<div/>").attr("id", "table_overlay").addClass(cfg.overlayClass).css({
					"top"    : coords.top,
					"left"   : coords.left,
					"width"  : tbody.outerWidth(),
					"height" : tbody.outerHeight(),
					"opacity": cfg.overlayOpacity
				});

				table.parent().append(div);
			}

			function removeContentOverlay() {
				$("#table_overlay").remove();
			}

			// get the URL to use for ajax calls
			function getUrl() {
				var querystring = [];

				for(var i in get)
					querystring[querystring.length] = i + '=' + get[i];

				querystring = querystring.length > 0 ? '?' + querystring.join('&') : '';

				var tmpUrl = url + '.json' + querystring;

				if(cfg.debug && console && console.log)
					console.log('Requesting: ' + tmpUrl);

				return tmpUrl;
			}
		});
	};
})(jQuery);

