function sfSuperPager (id, url, initialState, options) {
	this._id = id;
	this._url = url;
	this._initialState = initialState;
	
	options = Object.extend({
		useHistory: true
	}, options || {});
	
	// are we doign ordering on this pager?
	this._doingOrdering = (initialState.colOrdering.length >= 1);
	this._orderCol = initialState.orderCol;
	this._orderAsc = initialState.orderAsc;
	
	this._usingHistory = (options.useHistory && typeof(window.dhtmlHistory) == "object");
	
	var numItems = initialState.pager.numItems;
	var itemsPerPage = initialState.pager.itemsPerPage; 
	var currentPage = initialState.pager.currentPage;
	
	var thiss = this;
	// el, numItems, itemsPerPage, currentPage, callback
	this._pagination = new sfSuperPager.pagination(
		$(this._id + 'Pagination'), 
		numItems, 
		itemsPerPage, 
		currentPage, 
		function (page, options) {
			thiss._changePage(options);
		}
	);
			
	this._filterForm = $(this._id + 'FilterForm');
 	var table = $(this._id + 'Table');
 	if (!table) {
 		alert("Could not find table: " + this._id + 'Table');
 	}
 		
 	this._tableBody = $(table.getElementsByTagName('tbody')[0]);
 	if (!this._tableBody) {
 		alert("Could not find table body in table: " + this._id + 'Table');
 	}
	this._attachToFilter();
	this._currentReqNum = 0;
	this._initOrdering();
	
	// nasty
	table.sfSuperPager = this;
	
	if (this._usingHistory) {
		// at the moment this will only work correctly if there is just one 
		// pager on the page at any given time
		
		var listener = function (newLocation, historyData) {
			if (newLocation && newLocation.length > 0) {
				thiss.loadFromHash(newLocation);
			} else {
				// load from the initial state
				thiss._handleNewData(thiss._initialState);
			}
		}
		
		dhtmlHistory.initialize();
	    dhtmlHistory.addListener(listener);
	}
	
	if (this._usingHistory && window.location.hash.length > 1) {
		var hash = window.location.hash.substring(1);
		this.loadFromHash(hash);
	}	
}

// Load from a hash 
sfSuperPager.prototype.loadFromHash = function (hash) {
	var anchorVars = hash.toQueryParams();
	var v;
	
	// form vars!
	if (this._filterForm) {
		Form.getElements(this._filterForm).each(function (formEl) {
			if (anchorVars[formEl.name]) {
				$(formEl).value = anchorVars[formEl.name];
			}
		});
	}
	
	if (v = anchorVars.orderCol) {
		this._orderCol = v;
	}
	if (v = anchorVars.orderAsc) {
		this._orderAsc = v;
	}
	if (v = anchorVars.currentPage) {
		this._pagination.changePage(v, {addToHistory: false});
	}
}
// attach to the filter form
sfSuperPager.prototype._attachToFilter = function () {
	if (!this._filterForm) {
		return;
	}
	
	var thiss = this;
	
	var observer = function (event) {		
		Event.stop(event);
		// submit via ajax!
		thiss._submitFilter();
	}
	
	Event.observe(this._filterForm, 'submit', observer);
	
	this._filterForm.sfSuperPager = this;
}



// the filter form has been submitted
sfSuperPager.prototype._submitFilter = function () {
	this._getResults();
}

// the page has been changed
sfSuperPager.prototype._changePage = function (options) {
	this._getResults(options);
}

// refresh the data from the server
sfSuperPager.prototype.refresh = function () {
	this._getResults();
}

// get new data from server
sfSuperPager.prototype._getResults = function (options) {
	options = Object.extend({
		addToHistory: true
	}, options || {});
	
	var postStr = this.getFormString();
	
	var thiss = this;
	var success = function (xmlObj) {
		thiss._handleAjaxData(xmlObj.responseText.evalJSON());
	}
	var failure = function (xmlObj) {
		alert("Failure");
	}
	var exception = function (xmlObj, ex) {
		alert("Exception " + Object.inspect(ex));
	}
	
	// make the request
	var req = new Ajax.Request(this._url, {
		method: 'get',
		parameters: postStr,
		onComplete: success,
		onFailure: failure,
		onException: exception
	});
	
	// make the table show that new data is loading
	this._startLoading();
	
	// save state of pager to history
	if (this._usingHistory && options.addToHistory) {
		var anchor = 'location' + this._currentReqNum;
		if (this._filterForm) {
			anchor += '&' + Form.serialize(this._filterForm);
		}
		anchor += '&orderCol=' + this._orderCol;
		anchor += '&orderAsc=' + this._orderAsc;
		anchor += '&currentPage=' + this._pagination.getCurrentPage();
		
		dhtmlHistory.add(anchor, {});
	}
}

// get the current state of the filter form + ordering
sfSuperPager.prototype.getFormString = function () {
	var postStr = "page=" + this._pagination.getCurrentPage();
	// add int he current request num - used for throwing out old requests of they get delayed
	this._currentReqNum++;
	postStr += "&requestNum=" + this._currentReqNum;
	
	// Form if it's there
	if (this._filterForm) {
		postStr += '&' + Form.serialize(this._filterForm);
	}
	
	if (this._doingOrdering) {
		postStr += "&orderCol=" + this._orderCol;
		postStr += "&orderAsc=" + (this._orderAsc ? '1' : '0');
	}
	return postStr;
}



sfSuperPager.prototype._startLoading = function () {
	if (!this._loadingDiv) {
		this._loadingDiv = html.div('');
		if (this._filterForm) {
			this._filterForm.appendChild(this._loadingDiv);
		}
		Element.addClassName(this._loadingDiv, 'overlay_dialog');
		Element.addClassName(this._loadingDiv, 'loading');
	} 
	
	Position.clone(this._tableBody, this._loadingDiv);
	
}

sfSuperPager.prototype._stopLoading = function () {
	this._loadingDiv.style.left = "-9999px";
}

// handle new data from an ajax call
sfSuperPager.prototype._handleAjaxData = function (newData) {
	this._stopLoading();
	if (newData.requestNum < this._currentReqNum) {
		// Throw away stale data... 
		return;
	}
	this._handleNewData(newData);
}

// draw data into the page
sfSuperPager.prototype._handleNewData = function (newData) {
	var tb = this._tableBody;
	
	// empty out the current results
	var nodes = $A(tb.childNodes);
	nodes.each(function (child) {
		tb.removeChild(child);
	});
	
	// add in the new ones
	newData.rows.each(function (row) {
		// horrible?! ;)
		var tr = html.tr();
		row.each(function(cell) {
			var td = html.td('', cell[1]);
			td.innerHTML = cell[0];
			tr.appendChild(td);
		});
		tb.appendChild(tr);
	});
	
	// update the pagination
	this._pagination.setPageInfo(
		newData.pager.numItems, 
		newData.pager.itemsPerPage, 
		newData.pager.currentPage
	);
	
	// update ordering vars
	this._orderCol = newData.orderCol;
	this._orderAsc = newData.orderAsc;
	
	// update ordering classes
	this._drawOrdering();
}
// draw ordering from current state
sfSuperPager.prototype._drawOrdering = function () {
	var headerCells = this._tableBody.parentNode.getElementsByTagName('th');
	for (var i = 0; i < headerCells.length; i++) {
		Element.removeClassName(headerCells[i], 'orderAsc');
		Element.removeClassName(headerCells[i], 'orderDesc');
	}
	
	if (this._orderAsc) {
		Element.addClassName(headerCells[this._orderCol], 'orderAsc');
	} else {
		Element.addClassName(headerCells[this._orderCol], 'orderDesc');
	}
	
}
// initialise ordering - 
sfSuperPager.prototype._initOrdering = function () {
	if (!this._doingOrdering) {
		// no init to do
		return;
	}
	
	// attach click observer to th's
	var headerCells = this._tableBody.parentNode.getElementsByTagName('th');
	var thiss = this;
	for (var i = 0; i < headerCells.length; i++) {
		if (!this._initialState.colOrdering[i]) {
			// no ordering for this col!
			continue;
		}
		
		// attach class + click handler to th
		Element.addClassName(headerCells[i], 'ordered');
		Event.observe(headerCells[i], 'click', function (evt) {
			thiss._setOrder(Event.element(evt).cellIndex);
		});
	}
	
	if (this._orderAsc) {
		Element.addClassName(headerCells[this._orderCol], 'orderAsc');
	} else {
		Element.addClassName(headerCells[this._orderCol], 'orderDesc');
	}
}
sfSuperPager.prototype._setOrder = function (orderCol) {
	if (orderCol == this._orderCol) {
		// change from asc/desc
		this._orderAsc = !this._orderAsc;
	} else {
		this._orderAsc = true;
	}
	this._orderCol = orderCol;
	this._getResults();
}



// deals with pagination of results
sfSuperPager.pagination = function (el, numItems, itemsPerPage, currentPage, callback) {
	this._el = el;
	this._callback = callback;
	this._initialDisplay();
	this.setPageInfo(numItems, itemsPerPage, currentPage);
}

// called when the number of items etc. has changed
sfSuperPager.pagination.prototype.setPageInfo = function (numItems, itemsPerPage, currentPage) {
	this._numItems = numItems;
	this._itemsPerPage = itemsPerPage;
	this._currentPage = currentPage;
	if (itemsPerPage > 0) {
		this._numPages = Math.ceil(numItems / itemsPerPage);
	} else {
		this._numPages = 1;
	}
	
	
	this._updateDisplay();
}

// prepares the pagination element
sfSuperPager.pagination.prototype._initialDisplay = function () {
	// empty it
	this.emptyEl(this._el);
	
	// add in two divs - one for the numbers and one for the total
	this._navEl = html.div('', {'class': 'pagerNav'});
	this._totalEl = html.div('', {'class': 'pagerTotal'});
	
	this._el.appendChild(this._totalEl);
	this._el.appendChild(this._navEl);
}

// update the display elements of the pagination
sfSuperPager.pagination.prototype._updateDisplay = function () {
	// empty out the current page numbers
	this.emptyEl(this._navEl);
	var thiss = this;
	// set the total pages:
	this._totalEl.innerHTML = this._numItems + " item";
	if (this._numItems !=- 1) {
		this._totalEl.innerHTML += "s";
	}
	this._totalEl.innerHTML += " found";
	// add in the new pages:
	if (this._numPages <= 1) {
		return
	}
	var observer = function (event) {
		var page = Event.element(event).innerHTML;
		thiss.changePage(page);
		Event.stop(event);
	}
	
	this._navEl.appendChild(html.span(' Page:  '));
	
	var minPage = Math.max(1, this._currentPage - 5);
	var maxPage = Math.min(this._numPages, minPage + 9);
	
	if (minPage > 1) {
		var pageEl = this._makePageLink(1, observer);
		this._navEl.appendChild(pageEl);
		this._navEl.appendChild(html.span(' ... '));
	}
	
	for (var page = minPage; page <= maxPage; page++) {
		var pageEl = this._makePageLink(page, observer);
		this._navEl.appendChild(pageEl);
	}
	
	if (maxPage < this._numPages) {
		this._navEl.appendChild(html.span(' ... '));
		var pageEl = this._makePageLink(this._numPages, observer);
		this._navEl.appendChild(pageEl);
	}
}

// make a link for a page number
sfSuperPager.pagination.prototype._makePageLink = function (page, observer) {
	if (this._currentPage != page) {
		var opts = {href: '#'};
	} else {
		var opts = {href: '#', 'class': 'sel'}
	}
	
	var pageEl = html.a(" " + page, opts);
	Event.observe(pageEl, "click", observer);
	return pageEl;
}

// get the current page that is selected in the pagination
sfSuperPager.pagination.prototype.getCurrentPage = function () {
	return this._currentPage;
}

// change the page to another one - calls back with the new page.
sfSuperPager.pagination.prototype.changePage = function (page, options) {
	this._currentPage = page;
	this._callback(page, options);
}

// change the page to another one - calls back with the new page.
sfSuperPager.pagination.prototype.nextPage = function () {
	if (this._currentPage < this._numPages) {
		this.changePage(this._currentPage + 1);
	}
}

sfSuperPager.pagination.prototype.prevPage = function () {
	if (this._currentPage > 1) {
		this.changePage(this._currentPage - 1);
	}
}

// empty an element.  There is probably one of these in javascript already!
sfSuperPager.pagination.prototype.emptyEl = function (el) {
	var nodes = $A(el.childNodes);
	nodes.each(function (child) {
		el.removeChild(child);
	});
}
