//FUNCTIONS FOR SORTING, SELECTING, ETC. TABLE ROWS
//
//Felipe M. L. Gasper

//TO ENABLE...
//
//STRIPING: GIVE A TABLE OR TABLE BODY 'striped' IN ITS HTML class ATTRIBUTE.
//	TABLE BODIES WITH 'notStriped' IN THEIR class WON'T BE STRIPED.
//ROW SELECTING: GIVE A TABLE OR TABLE BODY 'rowsSelectable' IN THEIR class.
//	TABLE BODIES WITH 'rowsNotSelectable' WILL NOT INHERIT SELECTABILITY.
//	SPECIFY INDIVIDUAL ROW SELECTABILITY WITH 'selectable'/'notSelectable'.
//	PRIORITY GIVEN TO ROWS, THEN BODIES, THEN TABLES
//SORTING: GIVE A TABLE 'sortable' IN ITS HTML CLASS FOR COLUMN-AT-A-TIME
//	SORTING. PUTTING 'multiSortable' WITH IT ALLOWS MULTI-COLUMN SORTS.
//(SORTING REQUIRES <thead> AND <tbody> TAGS)
//BUTTONS FOR DEFAULT SORT, SELECT ALL, AND UNSELECT ALL: ADD 'showButtons'
//	TO TABLE'S CLASS

//CSS STUFF:
//STRIPING: APPLIES 'even'/'odd' TO ROW class ATTRIBUTES AND 'striped'
//	TO TABLE BODIES AND TABLES THAT ARE STRIPED
//ROW SELECTING: APPLIES 'selected' AND 'dragOver' TO ROW class ATTRIBUTES
//SORTING: APPLIES 'sortedAsc'/'sortedDesc' TO CELLS IN TABLE HEADS
//	AND BODIES AND 'sortable' TO THE APPROPRIATE TABLE HEAD CELLS. NOTE THAT
//	ALL HEADERS AND CELLS IN THEAD AND SORTABLE TBODY ELEMENTS WILL GET
//	'unsorted' ADDED TO THEIR CLASS, REGARDLESS OF WHETHER THEY'RE 'sortable'.
//	THIS ALLOWS CSS TO DISTINGUISH BETWEEN SORTABLE UNSORTED
//	AND NON-SORTABLE UNSORTED.
//	ALSO APPLIES 'sorting' TO THE TABLE DURING SORTS.
//BUTTONS: HAVE 'defaultSortButton', 'selectAllButton', & 'unselectAllButton'
//	IN THEIR RESPECTIVE CLASSES. BUTTONS ARE ADDED IMMEDIATELY BEFORE
//	THE TABLE IN THE DOCUMENT TREE. USE CSS TO STYLE THEIR APPEARANCE.

//JAVASCRIPT STUFF:
//STRIPING: SETS 'striped' TO BOOLEAN true FOR STRIPED TABLE BODIES AND TABLES
//	USE superTable.stripeTbody TO RE-STRIPE A TABLE BODY
//ROW SELECTING: SETS 'selected' AND 'selectable' PROPERTIES FOR ROWS
//SORTING: SETS 'sortable', 'multiSortable', 'sorts', AND 'sortTbody' ON
//	TABLES AND 'sortable' ON HEADER CELLS. 'sorts' IS AN ARRAY OF Sort
//	OBJECTS, EACH OF WHICH HAS 'index', 'order', AND 'asc' PROPERTIES.

//TO SPECIFY SORT CRITERIA FOR A COLUMN:
//IF tableObj REPRESENTS THE TABLE ON WHICH YOU'RE ADDING THE SORT:
//	DEFINE tableObj.sorterFuncs ARRAY, WITH INDICES CORRESPONDING TO FUNCTIONS
//		THAT GENERATE SORT VALUES (parseInt, parseFloat, ETC.)
//	YOU MAY DEFINE A tableObj.defaultSorterFunc FUNCTION.
//	ADDITIONALLY, SET A COLUMN'S sorterFuncs ENTRY TO false TO MAKE IT
//		NON-SORTABLE.
//	FOR EXAMPLE, [parseInt, String, false] WOULD BE USED FOR A TABLE
//		WHOSE COLUMNS ARE INTEGERS, STRINGS, AND NON-SORTABLE
//THESE VALUES SHOULD BE SET BEFORE THIS SCRIPT EXECUTES.

//EITHER THE LAST TABLE BODY WITH 'sortable' IN ITS class ATTRIBUTE OR THE
//FIRST TABLE BODY (IF NONE IS SPECIFIED AS 'sortable') WILL BE USED AS THE
//TABLE BODY ON WHICH TO SORT. BY THIS METHOD YOU CAN SEPARATE A 'TOTALS' ROW
//(OR SIMILAR THINGIE) NOT TO BE SORTED.

//YOU MAY SPECIFY tableObj.onrowselect/tableObj.onrowunselect FUNCTIONS.
//THESE FUNCTIONS RECEIVE THE ROW SELECTED/UNSELECTED AS THE 1ST PARAMETER.

//YOU CAN CALL superTable.sortDefault(table), superTable.selectAll(table),
//AND superTable.unselectAll(table)

//YOU CAN HAVE OTHER BUTTONS APPEAR WITH THEM BY ADDING THEM TO THE
//buttons ARRAY IN THE TABLE OBJECT. NOTE THAT push, pop, shift, AND unshift
//ARE OVERRIDDEN FOR THIS ARRAY SUCH THAT BUTTONS AUTOMATICALLY APPEAR IN THE
//DOCUMENT.

//ROW SELECTING ALLOWS CLICK-DRAG SELECTING OF MULTIPLE (ADJACENT) ROWS
//AND "SKIPS OVER" NON-SELECTABLE ROWS.

//CLICKS THAT GO TO LINKS, FORM ELEMENTS, ETC. IN TABLES WON'T SORT OR SELECT.
//IT'S STILL A GOOD IDEA TO stopPropagation, BUT GENERALLY YOU'RE FINE.

//BROWSER BUGS:
//SAFARI: -allRows, allCells, realCellIndex USED TO SIDESTEP BUGS IN SAFARI'S
//           DOM IMPLEMENTATION.
//        -SAFARI THINKS TEXT NODES CAN RECEIVE CLICKS. MAYBE THEY CAN, BUT
//           THIS LIBRARY IGNORES SUCH FOOLISHNESS.
//IE: -SINCE :hover ISN'T SUPPORTED, THERE'S NO (PALATABLE) WAY OF GETTING
//       FLOAT HIGHLIGHTS. DEAN EDWARDS'S IE7 LIBRARY MIGHT HELP HERE.

//----------------------------------------------------------------------
//BEGIN CODE

//WANT TO GET THIS OUT OF THE CODE EVENTUALLY....
function makeArray(inList) {
	return Array.prototype.map.call(inList, function(item) { return item; });
}

//FINDS OUT IF THE BROWSER SCREWS UP TABLE ELEMENT COUNTS (SAFARI)
function domCheck() {
	var testTable = document.createElement('table');
	var testThead = document.createElement('thead');
	var testTfoot = document.createElement('tfoot');
	var testTbody = document.createElement('tbody');

	testTable.appendChild(testThead);
	testTable.appendChild(testTfoot);
	testTable.appendChild(testTbody);

	for (var r=0; r < 3; r++) {
		var newRow = document.createElement('tr');
		for (var c=0; c < 3; c++) {
			newRow.appendChild(document.createElement('th'));
			newRow.appendChild(document.createElement('td'));
		}
		testTbody.appendChild(newRow);
	}

	window.rowsOk = (testTbody.rows.length == testTable.rows.length)
		&& (testTbody.rows.length == 3)
		&& (testTbody.rows[0].parentNode === testTbody)
	;

	window.cellsOk = (testTbody.rows[0].cells.length == 6);
	window.cellIndexOk = testTbody.rows[0].cells[3]
	   && (testTbody.rows[0].cells[3].cellIndex == 3);
}

//RETURNS AN ARRAY OF ALL ROWS (tr) IN AN OBJECT
//NECESSARY FOR BROWSERS THAT SCREW UP TABLE ROWS
function allRows(object) {
	var rowsCollection = object.getElementsByTagName('tr');

	return makeArray(rowsCollection);
}

//RETURNS AN ARRAY OF ALL CELLS (td, th) IN AN OBJECT
//NECESSARY FOR BROWSERS THAT SCREW UP CELL PROPERTIES
function allCells(object) {
	var tds = object.getElementsByTagName('td');
	var ths = object.getElementsByTagName('th');

	return makeArray(tds).concat(makeArray(ths));
}

//APPLY STRIPING
//USING sectionRowIndex IS REALLY, REALLY SLOW HERE....AT LEAST ON MOZ
var superTable = {};
superTable.stripeTbody = function(theTbody) {
	var theRows = window.rowsOk ? theTbody.rows : allRows(theTbody);
	for (var r=0; r < theRows.length; r++) {
		var curRow = theRows[r];
		classRemove(curRow,'odd','even');
		classAdd(curRow, (r % 2) ? 'odd' : 'even');
	}
}


//NOT AS SIMPLE AS YOU'D THINK IF OUR <thead> INCLUDES CELLS WHERE
//rowSpan AND colSpan ARE > 1
//
superTable.findSortIndex = function(hdrCell,cacheyn) {
	var thead = bubbleUpFind(hdrCell,'thead');
	if (cacheyn) {
		var indexCache = [];
	}
	var theadRows = window.rowsOk ? thead.rows : allRows(thead);

	var sortIndex = false;

	//WHICH COLUMNS FOR EACH HDRROW ARE TAKEN BY A PRECEDING ROW'S ELEMENTS.
	//ACCOMODATES FOR rowSpan > 1
	var nonColumns = [[]];
	nonColumns.add = function(cellObject,startCol) {
		var overbiteHeight = cellObject.rowSpan - 1;
		var overbiteWidth = cellObject.colSpan - 1;

		//FOR EACH "OVERBITE" ROW....
		for (var rr=curRow+1; rr <= curRow+overbiteHeight; rr++) {
			//FOR EACH COLUMN OF "OVERBITE"...
			for (var hh=startCol; hh <= startCol+overbiteWidth; hh++) {
				if (isUndefined(this[rr]))
					this[rr] = [];
				this[rr].push(hh);
			}
		}
	}

	//GO THROUGH AND COMPUTE SORT INDICES UNTIL WE'VE FOUND THE RIGHT <th>
	for (var r=0; (r < theadRows.length) && (sortIndex === false); r++) {
		var curRow = theadRows[r];
		var rowCells = cellsOk ? curRow.cells : allCells(curRow);

		//THIS IS THE SORT INDEX, SPECIAL FOR OUR CODE
		var curColIndex = 0;

		//c IS THE SAME AS DOM'S colIndex
		for (var c=0; (c < rowCells.length) && (sortIndex === false); c++) {
			var curCell = rowCells[c];

			//INCREMENT curColIndex IF ITS CURRENT VALUE IS A NONCOLUMN
			while (nonColumns[r].indexOf(curColIndex) != -1)
				curColIndex++;

			sortIndex = (curCell === hdrCell) && curColIndex;
			if (cacheyn) {
				if (indexCache[curColIndex]) {
					indexCache[curColIndex].push(curCell);
				} else {
					indexCache[curColIndex] = [curCell];
				}
			}

			//SAFARI STUPIDISM
			if (curCell.colSpan == 0) curColIndex++;
			else curColIndex += curCell.colSpan;

			if (curCell.rowSpan > 1) nonColumns.add(curCell,curColIndex);
		}
	}

	return (cacheyn ? indexCache : sortIndex);
}
superTable.sortIndexCache = function(tableObj) {
//	var lastTheadCell = allCells(allRows(tableObj.tHead).last()).last();
	var lastTheadRow = tableObj.tHead.rows[tableObj.tHead.rows.length - 1];
	var lastTheadCell = lastTheadRow.cells[lastTheadRow.cells.length - 1];

	return superTable.findSortIndex(lastTheadCell, true);
}

superTable.sortTbody = function(tableObj) {
	var sortTbody = tableObj.tBodies[0];
	for (var b=1; b < tableObj.tBodies.length; b++) {
		var curTbody = tableObj.tBodies[b];
		if (classHas(curTbody,'sortable')) {
			sortTbody = curTbody;
		}
	}

	return sortTbody;
}

superTable.sortDefault = function(tableObj) {
	var oldSorts = tableObj.sorts;
	var originalRows = oldSorts && oldSorts.originalRows;

	if (originalRows) {
		this.reinsertRows(originalRows);

		tableObj.sorts = this.newSortList(tableObj);
		oldSorts.map(function(s) { s.updateCss() });
	}
}

superTable.showsButtons = function(elem) {
	return classHas(elem, 'showButtons');
}
superTable.isStriped = function(elem) {
	return classHas(elem,'striped')
	   || ((elem.tagName.toLowerCase() == 'tbody')
	      && classHas(elem.parentNode,'striped')
	   );
}
superTable.isSortable = function(elem) {
	return classHas(elem,'sortable')
	   || ((elem.tagName.toLowerCase() == 'tbody')
	      && classHas(elem.parentNode,'sortable')
	   );
}
superTable.isSelectable = function(row) {
	return (row.parentNode.tagName.toLowerCase() != 'thead') && (

	//PURE AND SIMPLE
	classHas(row,'selectable') ||

	//INHERITED
	!classHas(row,'notSelectable') && (
	   //IN TABLE, NOT CANCELED IN TBODY
	   (classHas(row.parentNode.parentNode,'rowsSelectable') &&
	      !classHas(row.parentNode,'rowsNotSelectable')) ||
	   //IN TBODY
	   classHas(row.parentNode,'rowsSelectable')
	)

     );
}

//A CONSTRUCTOR FOR A Sort OBJECT (USED TO SORT TABLES)
superTable.Sort = function(index) {
	this.index = index;
	this.asc = true;

	return this;
}

//REVERSE THE SORT ORDER - WE DO THIS FOR INDIVIDUAL SORTS TO MAKE
//MULTIPLE-COLUMN SORTING SENSIBLE
superTable.Sort.prototype.reverse = function() {
	this.asc = !this.asc;

	if (this.list.length == 1) {
		//THIS IS FASTER THAN DOING THE ENTIRE SORT AGAIN, BUT IT ONLY WORKS
		//IF THERE'S ONLY ONE SORT COLUMN
		var sortTbody = superTable.sortTbody(this.list.table);
		var bodyRows = rowsOk ? makeArray(sortTbody.rows) :allRows(sortTbody);
		bodyRows.reverse();
		superTable.reinsertRows(bodyRows);
	} else {
		this.list.sync();
	}

	this.updateCss();
}

//UPDATE THE CSS TO REFLECT A CHANGE IN SORT STATUS (ASC/DESC/UNSORTED)
superTable.Sort.prototype.updateCss = function() {
	//A SORT IS ACTIVE IFF IT IS PRESENT IN THE TABLE'S sorts ARRAY
	var active = this.list.table.sorts.indexOf(this) != -1;

	//'unsorted' WILL ALWAYS BE REMOVED
	var sortClasses = ['sortedDesc','sortedAsc','unsorted'];

	var newClass = active ? sortClasses[Number(this.asc)] : false;
	var otherClasses = sortClasses.removeByValue(newClass);

	var classFixer = function(elem) {
		if (newClass) {
			classAdd(elem, newClass);
		}
		classRemove.apply(this, [elem].concat(otherClasses));
	}

	//CELLS FIRST
	var sortTbody = superTable.sortTbody(this.list.table);
	var tbodyRows = rowsOk ? sortTbody.rows : allRows(sortTbody);
	for (var r=0; r < tbodyRows.length; r++) {
		var curRow = tbodyRows[r];
		var rowCells = window.cellsOk ? curRow.cells : allCells(curRow);
		var cellToFix = rowCells[this.index];

		classFixer(cellToFix);
	}

	//NOW HEADERS
	var indexCache = superTable.sortIndexCache(this.list.table);
	for (var h=0; h < indexCache[this.index].length; h++) {
		var hdr = indexCache[this.index][h];
		classFixer(hdr);
	}

	if (superTable.isStriped(sortTbody)) {
		superTable.stripeTbody(sortTbody);
	}
}

superTable.reinsertRows = function(theRows) {
	var sortTbody = theRows[0].parentNode;

	//SUPPOSEDLY FASTER IN SOME BROWSERS.
	superTable.removeRows(theRows);

	Array.prototype.forEach.call(theRows, function(row) {
		sortTbody.appendChild(row);
	});
}
superTable.removeRows = function(theRows) {
	var sortTbody = theRows[0].parentNode;
	for (var r=theRows.length-1; r >= 0; r--) {
		sortTbody.removeChild(theRows[r]);
	}
}

//SINCE IE CAN'T PROPERLY HAVE SUBCLASSES INHERIT FROM Array...
superTable.newSortList = function(tableObj) {
	var sortList = [];
	sortList.table = tableObj;
	sortList.originalRows = tableObj.sorts
	   ? tableObj.sorts.originalRows
	   : (rowsOk
	      ? makeArray(superTable.sortTbody(tableObj).rows)
	      : allRows(superTable.sortTbody(tableObj))
	   )
	;
	var _push = sortList.push;
	sortList.push = function(newSort) {
		newSort.order = this.length;
		newSort.list = this;
		return _push.call(this,newSort);
	}

	for (var a=1; a < arguments.length; a++) {
		sortList.push(arguments[a]);
	}

	sortList.sync = function() {
		var sorterFuncs = this.table.sorterFuncs;
		var rowSortFuncs = this.map( function(s) {
			var transformer =
			   sorterFuncs[s.index] || function(v) { return v; };

			var rowLevelTransformer = function(row) {
				return transformer(XBinnerText(
					(window.cellsOk ? row.cells : allCells(row))[s.index]
				));
			};
			rowLevelTransformer.reverse = !s.asc;

			return rowLevelTransformer;
		});

		var sortTbody = superTable.sortTbody(this.table);

		//sortBy NEEDS A MUTABLE length PROPERTY, SO WE NEED A JS ARRAY
		var theRows = rowsOk ? makeArray(sortTbody.rows) : allRows(sortTbody);
		theRows.sortBy.apply(theRows,rowSortFuncs);

		superTable.reinsertRows(theRows);
	}

	return sortList;
}


superTable.addTableSort = function(tableObj,newSortIndex) {
	var newSort = new this.Sort(newSortIndex);
	var multiSortable = classHas(tableObj,'multiSortable');
	var sortTbody = this.sortTbody(tableObj);

	var formerSort = tableObj.sorts && tableObj.sorts[0];

	if (multiSortable && formerSort) {
		tableObj.sorts.push(newSort);
	} else {
		tableObj.sorts = this.newSortList(tableObj,newSort);
	}

	tableObj.sorts.sync();

	newSort.updateCss();

	if (formerSort) formerSort.updateCss();
}

superTable.rowsSelectable = function(elem) {
	return classHas(elem,'rowsSelectable')
	   || classHas(elem.parentNode,'rowsSelectable');
}
superTable.rowNotSelectable = function(row) {
	return classHas(row,'notSelectable');
}
//ROW SELECT (TOGGLE) FUNCTION FOR A SINGLE ROW
superTable.rowSelect = function(theRow,force) {
	var makeSelected = force || (isUndefined(force) && !theRow.selected);

	if (makeSelected) {
		classAdd(theRow,'selected');
		if (superTable.onrowselect) {
			superTable.onrowselect(theRow);
		}
	} else {
		classRemove(theRow,'selected');
		if (superTable.onrowunselect) {
			superTable.onrowunselect(theRow);
		}
	}
	theRow.selected = makeSelected;
}
superTable.selectAll = function(obj, force) {
	var selectYN = (!isUndefined(force) ? force : true);
	var rows = obj.getElementsByTagName('tr');
	for (var r=0; r < rows.length; r++) {
		var curRow = rows[r];
		if (this.isSelectable(curRow)) this.rowSelect(curRow,selectYN);
	}
}
//THIS IS A WRAPPER TO selectAll TO PREVENT CODE DUPLICATION
superTable.unselectAll = function(obj) {
	this.selectAll(obj,false);
}

superTable.nextRow = function(row,previous) {
	var prevNext = previous ? 'previousSibling' : 'nextSibling';
	//EITHER THE NEXT/PREVIOUS SIBLING OR THE PARENT'S NEXT/PREVIOUS SIBLING'S
	//FIRST/LAST CHILD
	var theSibling = row[prevNext]
	   || (row.parentNode[prevNext]
	      && row.parentNode[prevNext][previous ? 'lastChild' : 'firstChild']);

	//(A CHILD OF A TBODY HAS TO BE A ROW IF IT'S OF nodeType 1)
	var rowSibling = theSibling && ((theSibling.nodeType == 1)
	   ? theSibling
	   : superTable.nextRow(theSibling,previous)
	);

	return rowSibling;
}
superTable.previousRow = function(row) {
	return superTable.nextRow(row, true);
}

//TAKEN FROM http://www.quirksmode.org/js/findpos.html
superTable.findPosY = function(obj) {
	var curtop = 0;
	if (obj.offsetParent) {
		while (obj.offsetParent) {
			curtop += obj.offsetTop
			obj = obj.offsetParent;
		}
	//MIGHT AS WELL SUPPORT NETSCAPE 4, THOUGH AGAINST MY BETTER JUDGEMENT
	} else if (obj.y) {
		curtop += obj.y;
	}

	return curtop;
}


//----------------------------------------------------------------------
// DragRows OBJECT - TRACKS ROW SELECTING WHILE THE MOUSE IS DRAGGED

//THIS MECHANISM USES offsetTop IN BROWSERS THAT SUPPORT IT (VIRTUALLY ALL),
//THOUGH IT CAN USE PURE W3C DOM IF NEEDS BE (WHICH IS SLOWER)
superTable.DragRows = function(newRow) {
	var usePureDom = isUndefined(document.body.offsetTop);

	this.rows = [];
	this.places = [];
	this.findRowPlace = usePureDom
	   ? function(row) { return row.rowIndex; }
	   : superTable.findPosY
	;

	if (newRow) {
		this.push(newRow);
		this.curPlace = this.places[0];
	}

	this.startCount = this.rows.length;

	return this;
}

// IF newRowPlace ISN'T SPECIFIED, THEN WE RUN THE SAME FUNCTION THAT WOULD
// HAVE TO BE RUN ANYWAY.
superTable.DragRows.prototype.push = function(newRow, newRowPlace) {
	if (Array.prototype.indexOf.call(this.rows, newRow) == -1) {
		classAdd(newRow, 'dragOver');
		this.rows.push(newRow);
		this.places.push(newRowPlace || this.findRowPlace(newRow));
	}

	return this.rows.length;
}
superTable.DragRows.prototype.pop = function() {
	var retVal;
	if (this.rows.length > 1) {
		retVal = this.rows.pop();
		classRemove(retVal,'dragOver');
		this.places.pop();
	}

	return retVal;
}
superTable.DragRows.prototype.update = function(newRow) {
	var newRowPlace = this.findRowPlace(newRow);

	if (this.rows.length > 0) {
		//DELETE ROWS THAT AREN'T BETWEEN THE mousedown ROW AND newRow
		while (!this.places.last().between(this.places[0],newRowPlace)) {
			this.pop();
		}

		//IF WE ALREADY CONTAIN THE "NEW" ROW, THEN WE'RE DONE
		if (Array.prototype.indexOf.call(this.rows, newRow) == -1) {
			//NOW ADD ROWS TO THE ARRAY UNTIL WE MEET THE mouseoverED ONE
			var goBackwards =
			   (parseInt(newRowPlace) < parseInt(this.places[0]));
			var stepper =superTable[goBackwards ? 'previousRow' : 'nextRow'];

			var curRow = stepper(this.rows.last());
			while (curRow && (curRow !== newRow)) {
				this.push(curRow);
				curRow = stepper(curRow);
			}

			//FINALLY, ADD THE NEW ROW
			this.push(newRow, newRowPlace);
		}
	} else {
		this.push(newRow, newRowPlace);
	}
}
superTable.DragRows.prototype.undrag = function(toSelect) {
	this.rows.forEach(toSelect
	   ? function(row) {
		if (superTable.isSelectable(row)) {
			superTable.rowSelect(row);
		}

		classRemove(row,'dragOver');
	   }
	   : function(row) { classRemove(row,'dragOver'); }
	);
}


superTable.newButton = function(tableObj, buttonText, func, htmlClass) {
	//THIS WILL BE OF TYPE 'submit'. IT HAS TO BE A <button>
	//BECAUSE OTHERWISE SAFARI REQUIRES THE BUTTON TO BE IN A FORM.
	//UNFORTUNATELY, W3C DOM DOESN'T LET US CHANGE THE BUTTON'S type
	//(AND NEITHER DOES OPERA).
	var theButton = document.createElement('button');

	buttonText = buttonText || '';

	if (htmlClass) {
		classAdd(theButton, htmlClass);
	}
	theButton.appendChild(document.createTextNode(buttonText));
	theButton.table = tableObj;
	if (func) XBaddEventListener(theButton,'click',function(e) {
		func(e);
		e.preventDefault();
	});

	return theButton;
}

superTable.dragMouseoverHandler = function(e) {
//ADD "IF MOUSE BUTTON NOT PRESSED, undrag AND REMOVE THE mouseover LISTENER"
//....IF THAT'S EVER REALLY POSSIBLE IN A CROSS-BROWSER MANNER

	var dragRow = bubbleUpFind(e.target,'tr');
	if (dragRow) {
		if (superTable.dragRows) {
			superTable.dragRows.update(dragRow);
		} else {
			if (superTable.dragRows) {
				superTable.dragRows.undrag();
			}
			superTable.dragRows = new superTable.DragRows(dragRow);
		}
	}
};

superTable.listeners = {};
superTable.listeners.mousedown = function(e) {
	var theTable = bubbleUpFind(e.target,'table');
	//SCROLLBAR IS FOR MOZILLA....VERY STRANGE
	var clickElemsRegex = /^(a|input|button|select|option|scrollbar)$/i;
	if (theTable && !clickElemsRegex.test(e.target.tagName)) {
		var theRow = bubbleUpFind(e.target,'tr');
		//MAKE SURE THE CLICK ISN'T GOING FOR A SORT BEFORE ASSIGNING
		//THE LISTENER FOR ROW SELECTING
		if (!bubbleUpFind(e.target,'thead')
		   && superTable.isSelectable(theRow)) {
			superTable.dragTable = theTable;
			XBaddEventListener(superTable.dragTable,'mouseover',
			   superTable.dragMouseoverHandler);
			if (theRow) {
				if (superTable.dragRows) {
					superTable.dragRows.undrag();
				}
				superTable.dragRows = new superTable.DragRows(theRow);
			}
		}
	}
}
superTable.listeners.mouseup = function(e) {
	if (superTable.dragRows) {
	//ROW SELECTING
		XBremoveEventListener(
		   superTable.dragTable,'mouseover',superTable.dragMouseoverHandler
		);

		var theRow = bubbleUpFind(e.target,'tr');
		if (theRow
		   && (bubbleUpFind(theRow,'table') === superTable.dragTable)) {
			superTable.dragRows.update(theRow);
		}

		//lastSelectedIndex IS TO MAKE SHIFT-CLICK WORK CORRECTLY
		if (e.shiftKey) {

			//CAN'T USE dragRows.places HERE
			var indices = [
			   superTable.dragRows.rows.last().rowIndex,
			   superTable.dragTable.lastSelectedIndex
			].sortBy(Number)

			for (var r=Number(indices[0]); r < Number(indices[1]); r++) {
				if (superTable.isSelectable(superTable.dragTable.rows[r])) {
					superTable.rowSelect(
					   superTable.dragTable.rows[r],true
					);
				}
			}
		}
		superTable.dragTable.lastSelectedIndex =
		   superTable.dragRows.rows.last().rowIndex;

		superTable.dragRows.undrag(true);

		delete superTable.dragTable;
		delete superTable.dragRows;
	} else if (bubbleUpFind(e.target,'thead')) {
		//SORTING
		var theTable = bubbleUpFind(e.target,'table');
		if (superTable.isSortable(theTable)) {
			var clickCell = bubbleUpFind(e.target,'th','td');
			var sortIndex = superTable.findSortIndex(clickCell);

			//A SORT FUNCTION OF false INDICATES NOT SORTABLE
			if (theTable.sorterFuncs[sortIndex] !== false) {
				//THERE CAN ONLY BE ONE OF THESE
				var existingMatchingSorts = theTable.sorts
				   && theTable.sorts.filter(
					function(s) { return (s.index == sortIndex) }
				);

				if (existingMatchingSorts && existingMatchingSorts[0]) {
					existingMatchingSorts[0].reverse();
				} else {
					superTable.addTableSort(theTable,sortIndex);
				}
			}
		}
	}
}
for (var htype in superTable.listeners) {
	XBaddEventListener(document, htype, superTable.listeners[htype]);
}
domCheck();

//APPLY PROPERTIES THAT CAN'T BE DEALT WITH DYNAMICALLY, LIKE STRIPING
//AND ADDING CSS PROPERTIES
//
//ARGUMENTS ARE TABLES TO "FIX"
//
superTable.fixTables = function() {
	//DETECT IF WE GOT CALLED AS AN EVENT LISTENER
	var listener = (arguments.length == 1) && !arguments.nodeType;

	var tables = ((arguments.length > 0) && !listener)
	   ? arguments
	   : document.getElementsByTagName('table');

//	for (var t=0; t < tables.length; t++) {
//		var curTable = tables[t];
	Array.prototype.forEach.call(tables, function(curTable) {

		if (superTable.showsButtons(curTable)) {
			curTable.buttons = curTable.buttons || [];

			var refreshButtons = function() {
				var buttonList = (arguments.length > 0)
				   ? arguments
				   : curTable.buttons
				;

				Array.prototype.forEach.call(buttonList, function(button) {
					curTable.parentNode.insertBefore(button, curTable);
				});
			}

			curTable.buttons.push = function() {
				Array.prototype.forEach.call(arguments, function(arg) {
					Array.prototype.push.call(curTable.buttons, arg);
					refreshButtons(arg);
				});
			};

			refreshButtons();
		}

		//ADD 'sortable' CSS CLASS TO APPLICABLE thead ELEMENTS
		if (superTable.isSortable(curTable)) {
			var indexCache = superTable.sortIndexCache(curTable);
			curTable.sorterFuncs = curTable.sorterFuncs || [];
			for (var i=0; i < indexCache.length; i++) {
				var columnSortable = isUndefined(curTable.sorterFuncs[i])
				   || (curTable.sorterFuncs[i] !== false);
				if (indexCache[i] && columnSortable) {
					for (var h=0; h < indexCache[i].length; h++) {
						var curHeader = indexCache[i][h];
						classAdd(curHeader,'sortable');
					}
				}
			}

			if (superTable.showsButtons(curTable)) {
				curTable.buttons.push(superTable.newButton(
				   curTable,'Default Sort', function(e) {
					superTable.sortDefault(e.currentTarget.table);
				}));
			}
		}

		if (classHas(curTable,'rowsSelectable')
		   && superTable.showsButtons(curTable)) {
			curTable.buttons.push(superTable.newButton(
			   curTable,'Select All', function(e) {
				superTable.selectAll(curTable);
			}));
			curTable.buttons.push(superTable.newButton(
			   curTable,'Unselect All', function(e) {
				superTable.unselectAll(curTable);
			}));
		}

		//DO STRIPING
		for (var b=0; b < curTable.tBodies.length; b++) {
			var curTbody = curTable.tBodies[b];
			if (superTable.isStriped(curTbody)) {
				superTable.stripeTbody(curTbody);
			}
		}
	});
}

//UNFORTUNATELY, THIS INVOLVES SOME BROWSER DETECTION,
//BUT ALL WE CARE ABOUT IS IDENTIFYING IE, REALLY, SO IT'S NOT *THAT* BAD.
//SOME CODE TAKEN FROM CHRIS NOTT'S "Browser Detect" v2.1.6
var ua = navigator.userAgent.toLowerCase();
superTable.usingIE = (ua.indexOf('msie') != -1)
   && (ua.indexOf('opera') == -1)
   && (ua.indexOf('webtv') == -1)
;
delete ua;

//ACCOMODATES FOR IE'S LACK OF :hover
//AS WELL AS LEGACY EVENT HANDLING (WHERE NEEDED)
//
//THIS SECTION IS DEPRECATED!! DEAN EDWARDS'S IE7 LIBRARY FIXES LOTS OF THIS
//
superTable.compensate = function() {
	var cssListeners = {
		mouseover: function(e, removeYN) {
			var addRemove = removeYN ? classRemove : classAdd;
			var finished = false;

			//LEGACY DOM CAN'T HAVE e.target; IN THIS CASE, WE JUST GO
			//THROUGH THE ELEMENT AT HAND
			var curElem = e.target || e.currentTarget;

			while (!finished) {
				switch (curElem.tagName.toLowerCase()) {
				//HOVER OVER SORT-ENABLED TABLE HEADERS
				case 'th':
					var tableSection = curElem.parentNode.parentNode;
				   	if (tableSection.tagName.toLowerCase() == 'thead') {
						var theTable = tableSection.parentNode;
						var colIndex = superTable.findSortIndex(curElem);
						if ((colIndex !== false)
						   && ((theTable.sorterFuncs[colIndex] !== false)
						      || isUndefined(
						         theTable.sorterFuncs[colIndex]
						      )
						   )
						) {
							addRemove(curElem,'IEhover');
						}
					}
					break;
				//HOVER OVER SELECTABLE ROWS
				case 'tr':
					if (superTable.isSelectable(curElem)) {
						addRemove(curElem,'IEhover');
					}
					break;
				}

				finished = (curElem === e.currentTarget)
				   || !curElem.parentNode.tagName;
				if (!finished) {
					curElem = curElem.parentNode;
				}
			}
		},
		mouseout: function(e) {
			cssListeners.mouseover(e, true);
		}
	};

	var doCompensation = function (elem) {
		for (var evType in cssListeners) {
			XBaddEventListener(elem, evType, cssListeners[evType]);
		}
	};

	//IF WE CAN APPLY THIS TO THE WHOLE DOCUMENT, THAT'S BETTER BECAUSE IT
	//SAVES TIME. OTHERWISE (IE5/MAC), WE CHUG THROUGH EACH ELEMENT
	var ieDom = !!document.attachEvent;
	if (ieDom) {
		doCompensation(document);
	} else {
		//DO HEADER CELLS
//		makeArray(document.getElementsByTagName('thead')).map(function(th) {
		Array.prototype.map.call(
		   document.getElementsByTagName('thead'),
		   function(th) {
			allCells(th).map(doCompensation);
		});

//		makeArray(document.getElementsByTagName('tr')).map(doCompensation);
		Array.prototype.map.call(
		   document.getElementsByTagName('tr'), doCompensation
		);
	}
}

//----------------------------------------------------------------------
// INITIALIZE

XBaddEventListener(window,'load', superTable.fixTables);
if (superTable.usingIE) {
	XBaddEventListener(window,'load',superTable.compensate);
}
