jQuery(function($){
	var theCriteria = 'table.sortable';
	var theSortUpIndicator = $('<span class="sortindicator">&#9650;</span>');
	var theSortDnIndicator = $('<span class="sortindicator">&#9660;</span>');
 	var theInitializer = function(_,inTable){
		inTable = $(inTable);
		var theTBody = inTable.find( 'tbody.sort-body' )[0] || inTable.find( 'tbody' )[0];
		var theSortableTypes = 'text number date'.split(' ');
		inTable.find( 'thead th' ).each(function(inColumnIndex,inTH){
			inTH = $(inTH);

			var theSortType = $.grep( theSortableTypes, function(inType){ return inTH.is('.sort-'+inType) } )[0];
			if (!theSortType) return;

			inTH.originalText = inTH.text();
			var theSortDescendingByDefaultFlag = inTH.is('.sort-descending');
			inTH.addClass( 'sortable' );
			inTH.css( { cursor:'pointer'} );
			inTH.click(function(){
				var theSortDescendingFlag = inTH.is('.sorted') ? inTH.sortNextDownFlag : theSortDescendingByDefaultFlag;

				// Separate rows not to sort
				var theRowsToSort=[],theRowsCount=0;
				var theLastSortable;
				var theRowNodes = theTBody.getElementsByTagName( 'tr' );
				for (var i=0,len=theRowNodes.length;i<len;i++){
					var theRow = theRowNodes[ i ];
					if (theLastSortable && $(theRow).is('.sort-withprevious')){
						if (!theLastSortable.childRows) theLastSortable.childRows = [];
						theLastSortable.childRows[ theLastSortable.childRows.length ] = theRow;
					} else {
						theRowsToSort[ theRowsCount++ ] = theRow;
						theLastSortable = theRow;
					}
				}

				// Sort the rows using the data from the column, based on the class of the column.
				// Only the first sort on a column will look up the values; subsequent cache the data
				theRowsToSort.sort(function(inRowA,inRowB){
					var theRows   = [ inRowA, inRowB ];
					var theValues = [ null,   null   ];
					for ( var i=0; i<=1; ++i ){
						var theRow = theRows[i];
						if (!theRow.sortValueCache) theRow.sortValueCache = [];
						theValues[i] = theRow.sortValueCache[ inColumnIndex ];
						if (theValues[i] == null){
							var theTD = $(theRow).find( '> *:eq('+inColumnIndex+')' );
							theValues[i] = theTD.attr('sortvalue') || theTD.text();
							switch(theSortType){
								case 'number': theValues[i] = theValues[i] * 1;           break;
								case 'text'  : theValues[i] = theValues[i].toLowerCase(); break;
								case 'date'  : theValues[i] = new Date( theValues[i] );   break;
							}
							theRow.sortValueCache[ inColumnIndex ] = theValues[i];
						}
					}
					var theResult = theValues[0] < theValues[1] ? -1 : theValues[0] > theValues[1] ? 1 : 0;
					if ( theSortDescendingFlag ) theResult *= -1;
					return theResult;
				});

				// Shove the values into a new tbody.
				// (New TBody is 2x faster on FF than just shuffling in the same.)
				var theOldTBody = theTBody;
				theTBody = theTBody.cloneNode(false);
				theOldTBody.parentNode.replaceChild( theTBody, theOldTBody );
				for (var i=0;i<theRowsCount;++i){
					var theRow=theRowsToSort[i];
					theTBody.appendChild(theRow);
					if (theRow.childRows){
						for (var j=0,len=theRow.childRows.length;j<len;++j ){
							theTBody.appendChild(theRow.childRows[j]);
						}
					}
				}

				// Clear out sort information from the previously-sorted column
				var theLastSortedHead = inTable.sortedHead;
				if (theLastSortedHead){
					theLastSortedHead.text(theLastSortedHead.originalText);
					theLastSortedHead.removeClass('sorted');
					delete theLastSortedHead.sortNextDownFlag;
				}

				// Mark this column as sorted.
				inTH.append( theSortDescendingFlag ? theSortDnIndicator : theSortUpIndicator ); 
				inTH.addClass('sorted');
				inTable.sortedHead = inTH;
				inTH.sortNextDownFlag = !theSortDescendingFlag;
			});
			if (inTH.is('.sort-default')) inTH.click();
		});
	};
	$(theCriteria).each( theInitializer );
	$(document).bind('DOMNodeInserted',function(inEvent){
		$(inEvent.target).find(theCriteria).andSelf().filter(theCriteria).each(theInitializer);
	});
});

