// ******************************************************************
// cdsLocator
//
// Copyright (c) 2007 Catalog Data Solutions. All Rights Reserved.
//
// Displays a query ui in the form of a table with dropdown search
// choices
//
// cdsLocatorView: Class representing a locator table display.  This
// class should be usable without the cdsLocator wrapper class in
// order to allow locators with other data sources
//
// Column data model uses the following predefined columns:
//   columns[0]: product number
//   columns[1]: displayed product number
//   columns[2]: product flag
//
// Known issues:
// element.setAttribute("readonly") doesn't work in ie7
// element.setAttribute("class") doesn't work in ie7
// both of the above issues temporarily fixed using element.var = val
// ******************************************************************

// load ajaxCaller.js to enable http remote calls and cdsUtilities.js for static file urls
if (typeof ajaxCaller != "object") document.write('<script type="text/javascript" src="scripts/ajaxCaller.js"></script>');
if (typeof fixUrl != "function") document.write('<script type="text/javascript" src="scripts/cdsUtilities.js"></script>');

function cdsLocator() {
	this.elementId = properties["locator.locatorElement"];
	this.rowsPerPage = properties["locator.rowsPerPage"];
	this.outputUrlMap = properties["locator.outputUrlMap"];
	this.altOutputUrlMap = properties["locator.altOutputUrlMap"];
	this.permanentFilter = null;		// filter to be appended on each service call
	this.defaultSort = null;			// sort to be appended to each service call if no other sort present
	this.forceVisibleColumns = null;	// array of column ids to be forced visible (primarily used with service manual)
	this.categoryId = entities.LocatorEntity.category;
	this.view = null;
}

// ====================================
// Public methods
// ====================================

cdsLocator.prototype.init = function() {
	this.view = new cdsLocatorView(this.elementId, null);
	this.initFromJSON(entities.LocatorEntity);
}

cdsLocator.prototype.initFromJSON = function(json) {
	this.currentCategory = json.category;
	this.sort = json.sort;
	this.currentPage = json.page;
	this.filter = json.filter;
	this.maxRows = json.totalResults;
	this.imageUrl = fixUrl(json.imageUrl);
	this.columns = json.columns;
	// turn forced visible columns on
	if (this.forceVisibleColumns != null) {
		for (var i = 0; i < this.columns.length; i++) {
			for (var j = 0; j < this.forceVisibleColumns.length; j++) {
				if (this.columns[i].id == this.forceVisibleColumns[j]) {
					this.columns[i].isVisible = true;
					break;
				}
			}
		}
	}
	this.rows = json.rows;
	this.view.columns = this.columns;
	this.view.rowsPerPage = this.rowsPerPage;
	this.view.rows = this.rows;
	this.view.maxRows = this.maxRows;
	this.view.startRow = this.currentPage * this.rowsPerPage;
	this.view.image = this.imageUrl;
	this.initFilter();
	this.display();
	this.waitingForServer = false;
	document.body.style.cursor = "";
}

cdsLocator.prototype.display = function() {
	this.view.display(this);
}

// setup filters in column objects
cdsLocator.prototype.initFilter = function() {
	if (this.filter != null) {
		var allfs = this.filter.split("|");
		for (var i = 0; i < allfs.length; i++) {
			var a = allfs[i].split(":");
			if (a.length == 2) {
				var def = unescape(a[0]);
				var val = unescape(a[1]);
				if (val.length > 0) {
					for (var j = 0; j < this.columns.length; j++) {
						if (def == this.columns[j].id) {
							this.columns[j].currentQuery = [val];
						}
					}
				}
			} else if (a.length == 3) {
				var def = unescape(a[0]);
				var min = unescape(a[1]);
				var max = unescape(a[2]);
				if (min.length > 0 || max.length > 0) {
					for (var j = 0; j < this.columns.length; j++) {
						if (def == this.columns[j].id) {
							this.columns[j].currentQuery = [null,((min.length > 0 && min != "x") ? min : null),((max.length > 0 && max != "x") ? max : null)];
						}
					}
				}
			}
		}
	}
}

cdsLocator.prototype.getCurrentFilter = function() {
	var results = (this.permanentFilter == null) ? "" : this.permanentFilter;

	// get list of columns with filters
	for (var i = 0; i < this.columns.length; i++) {
		if (this.columns[i].currentQuery != null) {
			var filter = this.columns[i].currentQuery;
			var def = this.columns[i].id;
			if (results.length > 0) results += "|" ;
			results += def + ":";
			if (filter[0] != null) {		// equality filter
				results += escape(filter[0]);
			} else {
				if (filter[1] != null) {	// min filter
					results += escape(filter[1]);
				} else {
					results += "x";
				}
				results += ":";
				if (filter[2] != null) {	// max filter
					results += escape(filter[2]);
				} else {
					results += "x";
				}
			}
		}
	}
	
	return (results == "") ? null : results;
}

cdsLocator.prototype.getCurrentSort = function() {
	var newSort = this.sort;
	if (newSort == null) newSort = this.defaultSort;
	return newSort;
}

// ====================================
// Event callback functions
// ====================================

cdsLocator.prototype.onSelectProduct = function(productNumber, flag) {
	if (flag == "alternateUrl") {
		location.href = this.altOutputUrlMap.replace(/%DOMAIN%/g, escape(domain)).replace(/%ID%/g, escape(productNumber)).replace(/%CATEGORY%/g, escape(this.categoryId));
	} else {
		location.href = this.outputUrlMap.replace(/%DOMAIN%/g, escape(domain)).replace(/%ID%/g, escape(productNumber)).replace(/%CATEGORY%/g, escape(this.categoryId));
	}
}

cdsLocator.prototype.getServerResultsCallback = function(text, headers, callingContext) {
	eval("var o = " + text + ";");
	widgets.cdsLocator.initFromJSON(o);
}

// cmd?d=domain&e=Locator&category=category&page=0&filter=filter&sort=sort
cdsLocator.prototype.getServerResults = function(callingElement) {
	var s = "service?domain=" + domain +
		"&entity=Locator&category=" + this.currentCategory +
		"&page=" + this.page + 
		((this.filter != null) ? ("&filter=" + escape(this.filter)) : "") +
		((this.getCurrentSort() != null) ? ("&sort=" + this.getCurrentSort()) : "");
	this.waitingForServer = true;
	document.body.style.cursor = "wait";
	callingElement.style.cursor = "wait";
	ajaxCaller.getPlainText(s, this.getServerResultsCallback);
}

cdsLocator.prototype.onChangeResultsPage = function(pageNo, element) {
	if (this.waitingForServer) return;
	this.page = pageNo;
	this.getServerResults(element);
}

cdsLocator.prototype.onClickSort = function(columnNo, element) {
	if (this.waitingForServer) return;
	this.page = 0;
	var sortColumn = this.columns[columnNo].id;
	var sortForward = true;
	if (this.sort != null) {
		var currentSort = this.sort;
		var currentSortFwd = true;
		if (currentSort.indexOf("-") == 0) {
			currentSort = this.sort.substring(1);
			currentSortFwd = false;
		}
		if (currentSort == sortColumn) {
			sortForward = !currentSortFwd;
		}
	}
	if (!sortForward) {
		sortColumn = "-" + sortColumn;
	}
	this.sort = sortColumn;
	this.getServerResults(element);
}

cdsLocator.prototype.onChangeFilter = function(element) {
	if (this.waitingForServer) return;
	this.page = 0;
	this.filter = this.getCurrentFilter();
	this.getServerResults(element);
}

cdsLocator.prototype.onClickReset = function(columnNo, element) {
	if (this.waitingForServer) return;
	this.page = 0;
	var col = this.columns[columnNo];
	col.currentQuery = null;
	this.filter = this.getCurrentFilter();
	if (this.sort != null) {
		var testSort = (this.sort.indexOf("-") == 0) ? ("-" + col.id) : col.id;
		if (this.sort == col.id) this.sort = null;
	}
	this.getServerResults(element);
}

cdsLocator.prototype.onClickResetAll = function(element) {
	if (this.waitingForServer) return;
	this.page = 0;
	this.filter = this.permanentFilter;
	this.sort = null;
	this.getServerResults(element);
}

// ---------------------------------------------------------
// Data model class
// ---------------------------------------------------------

function cdsLocatorColumn(id, label, type, unit, visible, searchable, rangeSearch, selectLTE, selectGTE, valueList) {
	this.id = id;														// unique identifier of column
	this.label = label;													// text to display
	this.type = type;													// valid types: string, decimal, fraction, attachment
	this.unit = unit;													// the unit of measure for this column (null for string or non applicable numeric types)
	this.isVisible = (visible != null) ? visible : true;				// whether to display this column
	this.isSearchable = (searchable != null) ? searchable : true;		// whether to show controls for searching/sorting (mostly false for part number)
	this.hasRangeSearch = (rangeSearch != null) ? rangeSearch : false;	// add range to select
	this.isSelectLTE = (selectLTE != null) ? selectLTE : false;			// when value selected, filter by all <= selection
	this.isSelectGTE = (selectGTE != null) ? selectGTE : false;			// when value selected, filter by all >= selection
	
	this.currentQuery = null;		// contains the current query filter on this column
									// format of currentQuery is null or an array of [equal value, min value, max value]
	this.valueList = valueList;		// contains the current list of values for this column (for filtering select box)
}

// ---------------------------------------------------------
// cdsLocatorView class
// ---------------------------------------------------------

function cdsLocatorView(elementId, columns, rows) {
	this.elementId = elementId;		// the parent element id we create our table under
	this.columns = columns;			// array of cdsLocatorColumn objects
	this.rows = rows;				// array of arrays containing row data
	this.startRow = 0;				// first row to display out of the total rows in current query, first row is 0
	this.rowsPerPage = 0;			// the number of rows displayed in each page
	this.maxRows = 0;				// total number of rows in current query
	this.image = null;				// the url of an image to display at the top of the locator
	this.toggleUnits = false;		// whether to allow toggling of units
	this.units = "metric";			// current unit of measure (only used if toggleUnits is true) - metric or english
	this.selectedRow = null;		// currently selected row
	this.productNumberLabel = properties["locator.productNumberLabel"];
}

// draw the locator
cdsLocatorView.prototype.display = function(controller) {
	this.selectedRow = null;
	var parentElement = document.getElementById(this.elementId);
	while (parentElement.hasChildNodes()) { parentElement.removeChild(parentElement.lastChild); }
	parentElement.appendChild(this.renderControls(controller));
	parentElement.appendChild(this.renderLocator(controller));
}

// ====================================
// Private methods
// ====================================

// render the table containing image, result page selections, buttons
cdsLocatorView.prototype.renderControls = function(controller) {
	var table = document.createElement("table");
	table.className = "cdsLocatorControlTable";

	// image
	if (this.image != null && this.image.length > 0) {
		var tbody = document.createElement("tbody");
		var tr = document.createElement("tr");
		var td = document.createElement("td");
		td.setAttribute("colSpan", ((this.toggleUnits) ? 6 : 5));
		var img = document.createElement("img");
		img.setAttribute("src", this.image);
		td.appendChild(img);
		tr.appendChild(td);
		tbody.appendChild(tr);
		table.appendChild(tbody);
	}

	// if we don't have any rows, don't display the actual locator
	if (this.rows != null && this.rows.length > 0) {
		// controls
		var tfoot = document.createElement("tfoot");
		var tr = document.createElement("tr");

		// page links text
		/*
		var td = document.createElement("td");
		td.className = "cdsLocatorControlPageSelectLabelCell";
		td.appendChild(document.createTextNode("Result page"));
		tr.appendChild(td);
		*/

		// page links select
		var totalPages = this.maxRows / this.rowsPerPage;
		var currentPage = Math.ceil((this.startRow + 1) / this.rowsPerPage) - 1;
		td = document.createElement("td");
		td.className = "cdsLocatorControlPageSelectCell";
		tr.appendChild(td);
		if (totalPages > 1) {
			if (currentPage > 0) {
				var span = document.createElement("span");
				td.appendChild(span);
				span.locator = controller;
				span.currentPage = currentPage - 1;
				span.onclick = function(e) { this.locator.onChangeResultsPage(this.currentPage, this); }
				span.appendChild(document.createTextNode("View Previous Page"));
			}
			if (currentPage < (totalPages - 1)) {
				var span = document.createElement("span");
				td.appendChild(span);
				span.locator = controller;
				span.currentPage = currentPage + 1;
				span.onclick = function(e) { this.locator.onChangeResultsPage(this.currentPage, this); }
				span.appendChild(document.createTextNode("View Next Page"));
			}
		}

		// page number
		td = document.createElement("td");
		td.className = "cdsLocatorControlRecordCountCell";
		td.appendChild(document.createTextNode("Showing records " + (this.startRow + 1) + " through " + (this.startRow + this.rows.length) + " of " + this.maxRows));
		tr.appendChild(td);

		// buttons
		td = document.createElement("td");
		td.className = "cdsLocatorControlGoCell";
		var btn = document.createElement("input");
		btn.setAttribute("type", "button");
		btn.setAttribute("value", "Go");

		// set onclick event handler
		btn.locator = controller;
		btn.onclick = function(e) {
			if (this.locator.view.selectedRow == null) {
				alert("Please select a row to view details about.");
			} else {
				this.locator.onSelectProduct(this.locator.view.selectedRow.productNumber, this.locator.view.selectedRow.productFlag);
			}
		}

		td.appendChild(btn);
		tr.appendChild(td);

		td = document.createElement("td");
		td.className = "cdsLocatorControlResetCell";
		btn = document.createElement("input");
		btn.setAttribute("type", "button");
		btn.setAttribute("value", "Reset All");

		// set onclick event handler
		btn.locator = controller;
		btn.onclick = function(e) { this.locator.onClickResetAll(this); }

		td.appendChild(btn);
		tr.appendChild(td);

		// units selector
		if (this.toggleUnits) {
			td = document.createElement("td");
			td.className = "cdsLocatorControlUnitCell";
			var sel = document.createElement("select");
			var opt = document.createElement("option");
			opt.setAttribute("value", "metric");
			if (this.units == "metric") opt.setAttribute("selected", "selected");
			opt.appendChild(document.createTextNode("Display Metric Units"));
			sel.appendChild(opt);
			opt = document.createElement("option");
			opt.setAttribute("value", "english");
			if (this.units == "english") opt.setAttribute("selected", "selected");
			opt.appendChild(document.createTextNode("Display English Units"));
			sel.appendChild(opt);
			td.appendChild(sel);
			tr.appendChild(td);
		}
		tfoot.appendChild(tr);
		table.appendChild(tfoot);
	}
	
	return table;
}

// render the table containing the locator itself
cdsLocatorView.prototype.renderLocator = function(controller) {
	var table = document.createElement("table");
	table.className = "cdsLocatorTable";
	table.appendChild(this.renderLocatorHeader(controller));

	var tbody = document.createElement("tbody");
	for (var i = 0; i < this.rows.length; i++) {
		var row = this.rows[i];
		var productNumber = row[0];
		var displayProductNumber = row[1];
		var productFlag = row[2];
		var tr = document.createElement("tr");
		tr.className = ((i % 2 == 0) ? "cdsLocatorRowEven" : "cdsLocatorRowOdd");
		for (var j = 0; j < this.columns.length; j++) {
			if (this.columns[j].isVisible && this.columns[j].valueList.length > 0) {
				var td = document.createElement("td");
				td.setAttribute("colSpan", 2);
				var val = (row[j] != null) ? row[j] : "";
				
				// fix fractions
				if (val != "" && this.columns[j].type == "fraction") {
					val = toFraction(val);
				}
				
				if (j == 0) {
					td.className = "cdsLocatorPartNumberCell";
					var a = document.createElement("a");
					a.locator = controller;
					a.setAttribute("href", "javascript:widgets.cdsLocator.onSelectProduct(\"" + productNumber + "\"" + 
								((productFlag != null) ? (",\"" + productFlag + "\"") : "") + ")");
					a.appendChild(document.createTextNode(((displayProductNumber != null) ? displayProductNumber : val)));
					td.appendChild(a);
				} else {
					// if attachment type, make a link instead
					if (val != "" && this.columns[j].type == "attachment") {
						var a = document.createElement("a");
						a.setAttribute("href", fixUrl(val));
						a.appendChild(document.createTextNode(this.columns[j].label));
						td.appendChild(a);
					} else {
						td.appendChild(document.createTextNode(val));
					}
				}
				tr.appendChild(td);
			}
		}
		
		// set event handler for clicking on row
		tr.locatorView = this;
		tr.onclick = function(e) {
			if (this.locatorView.selectedRow == this) {
				this.className = this.classNameSave;
				this.locatorView.selectedRow = null;
			} else {
				if (this.locatorView.selectedRow != null) {
					this.locatorView.selectedRow.className = this.locatorView.selectedRow.classNameSave;
				}
				this.classNameSave = this.className;
				this.className = "cdsLocatorRowSelected";
				this.locatorView.selectedRow = this;
			}
		}

		// set double click handler
		tr.productNumber = productNumber;
		tr.productFlag = productFlag;
		tr.locator = controller;
		tr.ondblclick = function(e) { this.locator.onSelectProduct(this.productNumber, this.productFlag); }

		tbody.appendChild(tr);
	}
	table.appendChild(tbody);

	return table;
}

// render the column header row
cdsLocatorView.prototype.renderLocatorHeader = function(controller) {
	var thead = document.createElement("thead");
	
	// if we don't have any rows, don't display the actual locator
	if (this.rows != null && this.rows.length > 0) {
		// labels
		var tr = document.createElement("tr");
		tr.className = "cdsLocatorHeaderLabelRow";
		for (var i = 0; i < this.columns.length; i++) {
			var column = this.columns[i];
			if (column.isVisible && column.valueList.length > 0) {
				var td = document.createElement("td");
				td.setAttribute("colSpan", 2);
				if (!column.isSearchable) td.setAttribute("rowSpan", 3);	// don't display select/sort
				var label = column.label;
				if (i == 0 && this.productNumberLabel != null) label = this.productNumberLabel;
				if (column.unit != null) {
					label += " (" + column.unit + ")";
				}
				td.appendChild(document.createTextNode(label));
				tr.appendChild(td);
			}
		}
		thead.appendChild(tr);

		// selection boxes
		tr = document.createElement("tr");
		tr.className = "cdsLocatorHeaderSelectRow";
		for (var i = 0; i < this.columns.length; i++) {
			var column = this.columns[i];
			if (column.isVisible && column.isSearchable && column.valueList.length > 0) {
				td = document.createElement("td");
				td.setAttribute("colSpan", 2);
				var list = column.valueList;

				// if we have a single available value do not show dropdown box
				var query = (column.currentQuery == null) ? null : ((column.currentQuery[0] == null) ? null : column.currentQuery[0]);
				if (query == null && list.length == 1) query = list[0];
				if (query != null) {
					var inp = document.createElement("input");
					inp.setAttribute("type", "text");
					inp.readOnly = "readonly";		// setting this using setAttribute doesn't work in ie7
					inp.setAttribute("size", (query.length + 1));

					// fix for fractions
					var vf = query;
					if (vf !== null && vf !== "") {
						if (column.type === "fraction") {
							vf = toFraction(vf);
						}
					}
					
					inp.setAttribute("value", vf);
					td.appendChild(inp);

				// if we have a range entery, display the min and max
				} else if (column.currentQuery != null) {
					var inp = document.createElement("input");
					inp.setAttribute("type", "text");
					inp.className = "cdsLocatorHeaderSelectRowRangeField";
					inp.readOnly = "readonly";
					inp.setAttribute("size", 4);

					// fix for fractions
					var vf = column.currentQuery[1];
					if (vf !== null && vf !== "") {
						if (column.type === "fraction") {
							vf = toFraction(vf);
						}
					} else {
						vf = "";
					}

					inp.setAttribute("value", vf);
					td.appendChild(inp);
					inp = document.createElement("input");
					inp.setAttribute("type", "text");
					inp.className = "cdsLocatorHeaderSelectRowRangeField";
					inp.readOnly = "readonly";
					inp.setAttribute("size", 4);

					// fix for fractions
					var vf = column.currentQuery[2];
					if (vf !== null && vf !== "") {
						if (column.type === "fraction") {
							vf = toFraction(vf);
						}
					} else {
						vf = "";
					}

					inp.setAttribute("value", vf);
					td.appendChild(inp);

				// otherwise display the dropdown list
				} else {
					var sel = document.createElement("select");
					var opt = document.createElement("option");
					opt.setAttribute("value", "all");
					opt.appendChild(document.createTextNode("Select"));
					opt.setAttribute("selected", "selected");
					sel.appendChild(opt);

					// if a range column show range option
					if (column.hasRangeSearch) {
						var opt = document.createElement("option");
						opt.setAttribute("value", "range");
						opt.appendChild(document.createTextNode("Range"));
						sel.appendChild(opt);
					}

					for (var j = 0; j < list.length; j++) {
						opt = document.createElement("option");
						opt.setAttribute("value", list[j]);
						
						// fix for fractions
						var vf = list[j];
						if (vf !== null && vf !== "") {
							if (column.type === "fraction") {
								vf = toFraction(vf);
							}
						}
						
						opt.appendChild(document.createTextNode(vf));
						sel.appendChild(opt);
					}

					// set onchange event handler				
					sel.locator = controller;
					sel.columnIndex = i;
					sel.onchange = function(e) {
						var value = this.options[this.selectedIndex].value;
						if (value == "range") {
							var parent = this.parentNode;
							parent.removeChild(this);
							var inp1 = document.createElement("input");
							inp1.className = "cdsLocatorHeaderSelectRowRangeField";
							inp1.setAttribute("type", "text");
							inp1.setAttribute("size", 4);
							inp1.setAttribute("value", "Min");
							inp1.onfocus = function(e) { this.select(); }
							parent.appendChild(inp1);
							var inp2 = document.createElement("input");
							inp1.className = "cdsLocatorHeaderSelectRowRangeField";
							inp2.setAttribute("type", "text");
							inp2.setAttribute("size", 4);
							inp2.setAttribute("value", "Max");
							inp2.onfocus = function(e) { this.select(); }
							parent.appendChild(inp2);
							var btn = document.createElement("input");
							btn.setAttribute("type", "button");
							btn.setAttribute("value", "Go");
							parent.appendChild(btn);

							// set onclick event handler
							btn.minInput = inp1;
							btn.maxInput = inp2;
							btn.locator = controller;
							btn.columnIndex = this.columnIndex;
							btn.minPossibleValue = this.options[2].value;
							btn.maxPossibleValue = this.options[this.options.length - 1].value;
							btn.onclick = function(e) {
								var min = btn.minInput.value;
								if (min == null || min.length == 0 || min == "Min") min = null;
								var max = btn.maxInput.value;
								if (max == null || max.length == 0 || max == "Max") max = null;

								// check for invalid values
								if (min == null && max == null) {
									alert("Please enter either a min or a max value.");
									this.minInput.value = "Min";
									this.maxInput.value = "Max";
									this.minInput.focus();
									return;
								}
								if (min != null && +min != min) {
									alert("Please enter a numeric min value (or none).");
									this.minInput.value = "Min";
									this.minInput.focus();
									return;
								}
								if (max != null && +max != max) {
									alert("Please enter a numeric max value (or none).");
									this.maxInput.value = "Max";
									this.maxInput.focus();
									return;
								}
								if (min != null && +min > this.maxPossibleValue) {
									alert("Please enter a min value of no more than " + this.maxPossibleValue + ".");
									this.minInput.value = "Min";
									this.minInput.focus();
									return;
								}
								if (max != null && +max < this.minPossibleValue) {
									alert("Please enter a max value of at least " + this.minPossibleValue + ".");
									this.maxInput.value = "Max";
									this.maxInput.focus();
									return;
								}
								if (max != null && min != null && +max < +min) {
									alert("Please enter a min value which is less than the max value.");
									this.minInput.value = "Min";
									this.maxInput.value = "Max";
									this.minInput.focus();
									return;
								}
								this.locator.columns[this.columnIndex].currentQuery = [null, min, max];
								this.locator.onChangeFilter(this);
							}
						} else {
							this.locator.columns[this.columnIndex].currentQuery = (value == "all") ? null : [value, null, null];
							this.locator.onChangeFilter(this);
						}
					}
					td.appendChild(sel);
				}
				tr.appendChild(td);
			}
		}
		thead.appendChild(tr);

		// reset and sort
		tr = document.createElement("tr");
		tr.className = "cdsLocatorHeaderSortRow";
		for (var i = 0; i < this.columns.length; i++) {
			var column = this.columns[i];
			if (column.isVisible && column.isSearchable && column.valueList.length > 0) {
				td = document.createElement("td");
				td.className = (column.currentQuery != null) ? "cdsLocatorHeaderResetCell" : "cdsLocatorHeaderResetCellDisabled";
				td.appendChild(document.createTextNode("Reset"));
				if (column.currentQuery != null) {
					td.locator = controller;
					td.columnIndex = i;
					td.onclick = function(e) { this.locator.onClickReset(this.columnIndex, this); }
				}

				tr.appendChild(td);
				td = document.createElement("td");
				td.className = "cdsLocatorHeaderSortCell";
				td.appendChild(document.createTextNode("Sort"));
				td.locator = controller;
				td.sortIndex = i;
				td.onclick = function(e) { this.locator.onClickSort(this.sortIndex, this); }
				tr.appendChild(td);
			}
		}
		thead.appendChild(tr);
	}

	return thead;
}

