// ******************************************************************
// Locator.js
//
// Copyright (c) 2008 Catalog Data Solutions. All Rights Reserved.
//
// Class representing a locator table.
// ******************************************************************

if (typeof ajaxCaller != "object") document.write('<script type="text/javascript" src="scripts/ajaxCaller.js"></script>');

// ---------------------------------------------------------
// Data model class
// ---------------------------------------------------------

function cdsLocatorColumn(id, label, type, displayOrder, unitEnglish, unitMetric, unitStorage, numericPrecision, visible, searchable,
							rangeSearchable, searchLTE, searchGTE, filter, values, valueIds) {
	this.id = id;
    this.idx = 0;
	this.label = label;
	this.type = type;
	this.displayOrder = displayOrder;
	this.unitEnglish = unitEnglish;
	this.unitMetric = unitMetric;
	this.unitStorage = unitStorage;
    this.numericPrecision = numericPrecision;
	this.isVisible = visible;
    this.isVisibleInDetails = true;
	this.isSearchable = searchable;
	this.isRangeSearchable = rangeSearchable;
	this.searchLTE = searchLTE;
	this.searchGTE = searchGTE;
	this.filter = filter;
	this.values = values;
    this.valueIds = valueIds;
    this.parentMultiValue = null;   // used to integrate with configurators multi-value attributes
    this.functionalGroup = null;    // used for bom and such
}


// ---------------------------------------------------------
// Locator class
// ---------------------------------------------------------

function cdsLocator() {
	this.elementId = "locatorCell";
    this.notifyElementId = "locatorCell";
	this.rowsPerPage = 15;
	this.page = 0;
	this.rowCount = 0;
	this.rows = null;
	this.columns = null;
    this.columnMap = null;
	this.view = null;
	this.sort = null;
	this.defaultSort = null;
	this.assemblyDecode = null;
	this.selectedRowIdx = null;
	this.maxRows = 5000;
    this.isVisible = true;
    this.measurementSystem = null;  // current unit of measure metric or english or null if no units toggle is available
    this.debug = true;
}

// ====================================
// Public methods
// ====================================

cdsLocator.prototype.display = function() {
	if (this.view != null) {
        var ne = document.getElementById(this.notifyElementId);
        if (!this.isVisible) {
            if (ne != null) {
                ne.innerHTML = "&nbsp;";
            }
		} else if (this.rowCount == 0) {
            if (ne != null) {
                ne.innerHTML = "No results found for the current selection.";
            }
		} else if (this.rowCount < 0) {
            if (ne != null) {
                ne.innerHTML = "Too many results found for the current selection. Please select more attributes.";
            }
		} else {
			this.view.display(this);
        }
	}
}

/*
 * Return the contents of the given row and column
 */
cdsLocator.prototype.getCell = function(columnId, rowNumber) {
    var column = this.columnMap[columnId];
    if (column != null) {
        var val = this.rows[rowNumber][column.idx];
        return (val != "null") ? val : null;
    }
    return null;
}

cdsLocator.prototype.getCurrentFilter = function(searchableAttributesOnly) {
	var results = "";

	// get list of columns with filters
	if (this.columns != null) {
		for (var i = 0; i < this.columns.length; i++) {
            if (!searchableAttributesOnly || this.columns[i].isSearchable) {
                if (this.columns[i].filter != null) {
                    var filter = this.columns[i].filter;
                    var def = this.columns[i].id;
                    if (results.length > 0) results += "|" ;
                    results += def + ":" + filter;
                }
            }
		}
	}

	return (results == "") ? null : results;
}

cdsLocator.prototype.getCurrentSort = function() {
	var newSort = this.sort;
	if (newSort == null) newSort = this.defaultSort;
	return newSort;
}

cdsLocator.prototype.setColumnFilter = function(id, value, min, max) {
	var col = null;
	if (this.columns != null) {
		for (var i = 0; i < this.columns.length; i++) {
			var col = this.columns[i];
			if (col.id == id) {
				if (min != null || max != null) {
					col.filter = ((min != null) ? min : "") + ":" + ((max != null) ? max : "");
				} else {
					col.filter = ((value != null) ? value : null);
				}
				break;
			}
		}
	}
}

cdsLocator.prototype.clearAllFilters = function() {
    var col = null;
    if (this.columns != null) {
        for (var i = 0; i < this.columns.length; i++) {
            this.columns[i].filter = null;
        }
    }
}

// ====================================
// 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.initFromJSON = function(json) {
	this.rowCount = json.count;
	this.columns = json.columns;
    this.columnMap = new Object();
    for (var i = 0; i < this.columns.length; i++) {
        this.columnMap[this.columns[i].id] = this.columns[i];
        this.columns[i].idx = i;
    }
	this.rows = json.rows;
	if (json.page != null) this.page = json.page;
	if (json.selectedRowIdx != null) this.selectedRowIdx = json.selectedRowIdx;

	this.view = null;
	this.view = new cdsLocatorView(this.elementId, this.columns, this.rows);
	this.view.rowsPerPage = this.rowsPerPage;
	this.view.maxRows = this.rowCount;
	this.view.startRow = this.page * this.rowsPerPage;
	this.display();
	this.waitingForServer = false;
    setConfiguratorEvents(true);
	document.body.style.cursor = "";

	if (this.selectedRowIdx != null) {
		var e = document.getElementById("locatorRow_" + locator.selectedRowIdx);
		if (e != null) e.onclick();
		this.selectedRowIdx = null;
	}
}

cdsLocator.prototype.getServerResultsCallback = function(text, headers, callingContext) {
	eval("var o = " + text + ";");
	locator.initFromJSON(o);
}

cdsLocator.prototype.getServerResults = function(callingElement) {
	var s = "locate?domain=" + SERIES +
        "&locale=" + LOCALE +
		"&page=" + this.page +
		"&count=" + this.rowsPerPage +
        "&maxcount=" + this.maxRows +
		((this.assemblyDecode != null) ? ("&assemblyDecode=" + escape(this.assemblyDecode)) : "") +
		((this.getCurrentFilter() != null) ? ("&filter=" + this.getCurrentFilter()) : "") +
		((this.getCurrentSort() != null) ? ("&sort=" + this.getCurrentSort()) : "");
    if (this.debug) {
        s += "&debug=true";
    }
	this.waitingForServer = true;
    setConfiguratorEvents(false);
	document.body.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.getServerResults(element);
}

cdsLocator.prototype.onClickReset = function(columnNo, element) {
	if (this.waitingForServer) return;
	this.page = 0;
	var col = this.columns[columnNo];
	col.filter = null;
	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.sort = null;
	if (this.columns != null) {
		for (var i = 0; i < this.columns.length; i++) {
			this.columns.filter = null;
		}
	}
	this.getServerResults(element);
}

cdsLocator.prototype.getDisplayValue = function(value, column, forRow, pn, availability) {
    var v = value;

    // convert units
    if (this.measurementSystem != null && !isNaN(value) && column.unitStorage != null) {
        var conversion = null;
        var precision = column.numericPrecision;
        if (precision == null) {
            precision = 0;
        }

        if (this.measurementSystem == "metric" && column.unitStorage != column.unitMetric) {
            conversion = column.unitStorage + "-" + column.unitMetric;
        } else if (this.measurementSystem == "english" && column.unitStorage != column.unitEnglish) {
            conversion = column.unitStorage + "-" + column.unitEnglish;
        }

        switch (conversion) {
        case "Kv-Cv":
            v = value * 1.16;
            break;
        case "Cv-Kv":
            v = value / 1.16;
            break;
        case "bar-psi":
            v = value * 14.5;
            precision--;
            break;
        case "psi-bar":
            v = value / 14.5;
            precision++;
            break;
        case "mm-in":
            v = value / 25.4;
            precision++;
            break;
        case "in-mm":
            v = value * 25.4;
            precision--;
            break;
        case "&deg;C-&deg;F":
            v = (9 / 5) * value + 32;
            break;
        case "&deg;F-&deg;C":
            v = (5 / 9) * (value - 32);
            break;
        }
        if (column.type != "fraction" && precision >= 0) {
            if (typeof v.toFixed != "function") {
                v = parseFloat(v);
            }
            v = v.toFixed(precision);
        }
    }

    // convert fractions
    if (column.type == "fraction") {
        v = this.toFraction(v);
    }

    // fix nulls
    if (v == null || v == "null") {
        v = "";
    }

    if (column.id === "aAvailability" && v === "Today" && forRow) {
        v = "<img src=\"images/asco_buttons/ascotodaylogo.jpg\" />"
    }

    if (column.id === "aAvailability" && v === "5 Day" && forRow) {
        v = "<img src=\"images/asco_buttons/quickship.jpg\" />"
    }

    if (column.id === "aCatalogLink" && v && v.length && catalogLinkPath) {
        v = '<a href="' + catalogLinkPath + (catalogLinkPath.match(/.*\/$/) ? "" : "/") + v +
                '" target="catalog_tab"><img src="images/pdficon_small.gif" /></a>';
    }

    // deal with buy link
    if (column.id === "aBuyLink" && pn && rfqLink) {
        if (availability) {
            v = "<a href='" +
                rfqLink.replace(/ASSEMBLYGOESHERE/g, encodeURIComponent(pn)) +
                "'><img src='images/asco_buttons/BuyNow.gif'></a>";
        } else {
            v = "<a href='" +
                rfqLink.replace(/ASSEMBLYGOESHERE/g, encodeURIComponent(pn)) +
                "'><img src='images/asco_buttons/GetQuoteSmall.gif'></a>";
        }
    }

    return v;
}

cdsLocator.prototype.toFraction = function(decimal) {
    var wp = 0;
    var sign = 1;
    if(decimal<0) {
        sign = -1;
        decimal *= -1;
    }
    if(decimal>=1.0) {
        var tmp = decimal%1;
        wp = decimal - tmp;
        decimal = tmp;
    }

    var fp = decimal;
    var d1 = 0;
    var denominator = 1;
    var dd = decimal*denominator;
    var numerator = dd - dd%1;
    var delta = numerator/denominator - decimal;
    while((delta>0 && delta>1.0E-12)||(delta<0 && delta<-1.0E-12)) {
        fp = 1.0/fp;
        var tmp = fp%1;
        var ip = fp - tmp;
        fp = tmp;
        tmp = denominator;
        denominator = ip*denominator + d1;
        d1 = tmp;
        dd = decimal*denominator;
        numerator = dd - dd%1;
        delta = numerator/denominator - decimal;
    }

    var ret = '';
    if(wp>0.5) {
        if(numerator<0.5) {
            ret = (sign*wp)+'';
        }
        else {
            ret = (sign*wp)+' '+numerator+'/'+denominator;
        }
    }
    else {
        if(numerator<0.5) {
            ret = '0';
        }
        else {
            ret = (sign*numerator)+'/'+denominator;
        }
    }

    return ret;
}

// ---------------------------------------------------------
// 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.selectedRow = null;		// currently selected row
	this.pnPrefix = null;			// prefix added to all part numbers
	this.pnSuffix = null;			// suffix added to all part numbers
}

cdsLocatorView.prototype.getColumn = function(id) {
	for (var i = 0; i < this.columns.length; i++) {
		if (this.columns[i].id == id) return this.columns[i];
	}
	return null;
}

// draw the locator
cdsLocatorView.prototype.display = function(controller) {
	this.selectedRow = null;
	controller.selectedRow = null;
	var parentElement = document.getElementById(this.elementId);
	while (parentElement.hasChildNodes()) { parentElement.removeChild(parentElement.lastChild); }

	// allow calling program to alter visibility
	if (typeof isColumnVisible == "function") {
		for (var i = 0; i < this.columns.length; i++) {
			this.columns[i].isVisible = isColumnVisible(this.columns[i]);
		}
	}

	parentElement.appendChild(this.renderLocator(controller));
	parentElement.appendChild(this.renderControls(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) {
		var tbody = document.createElement("tbody");
		var tr = document.createElement("tr");
		var td = document.createElement("td");
		td.setAttribute("colSpan", ((controller.measurementSystem != null) ? 6 : 5));
		var img = document.createElement("img");
		img.setAttribute("src", this.image);
		td.appendChild(img);
		tr.appendChild(td);
		tbody.appendChild(tr);
		table.appendChild(tbody);
	}

	// controls
	var tfoot = document.createElement("tfoot");
	var tr = document.createElement("tr");

	// page number
	var td = document.createElement("td");
	td.className = "cdsLocatorControlRecordCountCell";
    var showingLabel = dynamicLocaleLabels["locatorShowingRecordsLabel"];
    showingLabel = showingLabel.replace("<%start%>", this.startRow + 1).replace("<%end%>", this.startRow + this.rows.length).replace("<%total%>", this.maxRows);
	td.innerHTML = showingLabel;
	tr.appendChild(td);

	// page links text
	var totalPages = Math.ceil(this.maxRows / this.rowsPerPage);
	var currentPage = Math.ceil((this.startRow + 1) / this.rowsPerPage) - 1;
	td = document.createElement("td");
	td.className = "cdsLocatorControlPageSelectLabelCell";
	tr.appendChild(td);
	var innerStr = "";
	if (totalPages > 1) {
		if (currentPage > 0) {
            innerStr += "<a href='javascript:locator.onChangeResultsPage(" + (currentPage - 1) + ")'>" + dynamicLocaleLabels["locatorViewPreviousLabel"] + "</a>";
		}
		if (currentPage < (totalPages - 1)) {
			if (innerStr.length > 0) innerStr += "&nbsp;&nbsp;&nbsp;&nbsp;";
            innerStr += "<a href='javascript:locator.onChangeResultsPage(" + (currentPage + 1) + ")'>" + dynamicLocaleLabels["locatorViewNextLabel"] + "</a>";
		}
	}
	td.innerHTML = innerStr;

	td = document.createElement("td");
	td.className = "cdsLocatorControlPageSelectUnitCell";
	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));

    // find columns for product number and label
    var productNumberColumn = 0;
    var labelColumn = 1;
    var availColumn = 2;
    for (var i = 0; i < this.columns.length; i++) {
        if (this.columns[i].id === "productId") {
            productNumberColumn = i;
        } else if (this.columns[i].id === "label") {
            labelColumn = i;
        } else if (this.columns[i].id === "aAvailability") {
            availColumn = i;
        }
    }

	var tbody = document.createElement("tbody");
	for (var i = 0; i < this.rows.length; i++) {
		var row = this.rows[i];
		var productNumber = row[productNumberColumn];
		var label = row[labelColumn];
        var availability = (row[availColumn] == "Today" || row[availColumn] == "Preferred Range");
        var idx = i;
		var tr = document.createElement("tr");
		tr.className = ((i % 2 == 0) ? "cdsLocatorRowEven" : "cdsLocatorRowOdd");
		tr.locatorView = this;
		tr.productNumber = productNumber;
        tr.availability = availability;
        tr.label = label;
        tr.idx = idx;
		tr.id = "locatorRow_" + i;

		// rfq link
        /*
		var td = document.createElement("td");
		td.setAttribute("colSpan", 2);
		var a = document.createElement("a");
		a.setAttribute("href", RFQ_URL.replace(/ASSEMBLYGOESHERE/g, escape(fullProductNumber)));
		var img = document.createElement("img");
		a.appendChild(img);
		img.setAttribute("src", "images/asco_buttons/GetQuoteSmall.gif");
		td.appendChild(a);
		tr.appendChild(td);
        */

		for (var j = 0; j < this.columns.length; j++) {
			if (this.columns[j].isVisible) {
				var td = document.createElement("td");
				td.setAttribute("colSpan", 2);
				var val = (row[j] != null) ? row[j] : "";
                val = controller.getDisplayValue(val, this.columns[j], true, productNumber, availability);

				// use images if availability = today
                /*
				if (this.columns[j].id == "availability") {
					if (val == "Today") {
						val = "<img src='images/asco_buttons/ascotodaylogo.jpg' />";
						img.setAttribute("src", "images/asco_buttons/BuyNow.gif");
					}
				}
                */

				if (this.columns[j].id === "productId" || this.columns[j].id === "label") {
					td.className = "cdsLocatorPartNumberCell";
					var a = document.createElement("a");
                    a.setAttribute("href", "javascript:onLocatorSelectProduct(\"" + productNumber + "\", \"" + label + "\", " + availability + ")");
					a.appendChild(document.createTextNode(label));
					td.appendChild(a);
				} else {
					td.innerHTML = val;
				}
				tr.appendChild(td);
			}
		}

		// set event handler for clicking on row
		tr.onclick = function(e) {
			if (this.locatorView.selectedRow == this) {
				this.className = this.classNameSave;
				this.locatorView.selectedRow = null;
				controller.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;
				controller.selectedRow = this.idx;
			}
			onLocatorSelectRow(this.idx);
		}

		// set double click handler
		tr.productNumber = productNumber;
		tr.ondblclick = function(e) { onLocatorSelectProduct(this.productNumber); }

		tbody.appendChild(tr);
	}
	table.appendChild(tbody);

	return table;
}

// render the column header row


cdsLocatorView.prototype.renderLocatorHeader = function(controller) {
	var thead = document.createElement("thead");

	// 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) {
            var td = document.createElement("td");
            td.setAttribute("colSpan", 2);
            var label = column.label;
            if (controller.measurementSystem != null) {
                if (controller.measurementSystem == "metric") {
                    if (column.unitMetric != null) {
                        label += " (" + column.unitMetric + ")";
                    }
                } else {
                    if (column.unitEnglish != null) {
                        label += ' <span style="white-space: nowrap;">(' + column.unitEnglish + ')</span>';
                    }
                }
            }
            td.innerHTML = 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) {
			td = document.createElement("td");
			td.setAttribute("colSpan", 2);
			var list = column.values;

			if (column.isSearchable) {
                if (column.filter != null) {
                    // single filter, display it
                    var pos = column.filter.indexOf(":");
                    if (pos < 0) {
                        var inp = document.createElement("input");
                        inp.setAttribute("type", "text");
                        inp.readOnly = "readonly";		// setting this using setAttribute doesn't work in ie7
                        inp.setAttribute("size", (column.filter.length + 1));

                        var vf = column.filter;
                        if (column.values != null && column.values[0] != null) {
                            vf = column.values[0];
                        }
                        vf = controller.getDisplayValue(vf, column);
                        inp.setAttribute("value", vf);
                        td.appendChild(inp);

                    // if we have a range entery, display the min and max
                    } else {
                        var min = column.filter.substring(0, pos);
                        var max = column.filter.substring(pos + 1);
                        var inp = document.createElement("input");
                        inp.setAttribute("type", "text");
                        inp.className = "cdsLocatorHeaderSelectRowRangeField";
                        inp.readOnly = "readonly";
                        inp.setAttribute("size", 4);

                        minf = controller.getDisplayValue(min, column);
                        maxf = controller.getDisplayValue(max, column);

                        inp.setAttribute("value", minf);
                        td.appendChild(inp);
                        inp = document.createElement("input");
                        inp.setAttribute("type", "text");
                        inp.className = "cdsLocatorHeaderSelectRowRangeField";
                        inp.readOnly = "readonly";
                        inp.setAttribute("size", 4);
                        inp.setAttribute("value", maxf);
                        td.appendChild(inp);
                    }

                // otherwise display the dropdown list
                } else if (list != null) {
                    var sel = document.createElement("select");
                    var opt = document.createElement("option");
                    opt.setAttribute("value", "all");
                    opt.innerHTML = dynamicLocaleLabels["locatorSelectLabel"];
                    opt.setAttribute("selected", "selected");
                    sel.appendChild(opt);

                    // if a range column show range option
                    if (column.isRangeSearchable) {
                        var opt = document.createElement("option");
                        opt.setAttribute("value", "range");
                        opt.appendChild(document.createTextNode(dynamicLocaleLabels["locatorRangeLabel"]));
                        sel.appendChild(opt);
                    }

                    for (var j = 0; j < list.length; j++) {
                        opt = document.createElement("option");
                        var opval = list[j];
                        if (column.valueIds != null && column.valueIds[j] != null) {
                            opval = column.valueIds[j];
                        }
                        opt.setAttribute("value", opval);

                        // fix fractions
                        var vf = list[j];
                        if (vf != null) {
                            vf = controller.getDisplayValue(vf, column);
                        }

                        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].filter = ((min != null) ? min : "") + ":" + ((max != null) ? max : "");
                                this.locator.onChangeFilter();
                            }
                        } else {
                            this.locator.columns[this.columnIndex].filter = (value == "all") ? null : value;
                            this.locator.onChangeFilter();
                        }
                    }
                    td.appendChild(sel);
                } else {
                }
            }
			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) {
			td = document.createElement("td");
			td.className = (column.filter != null) ? "cdsLocatorHeaderResetCell" : "cdsLocatorHeaderResetCellDisabled";
            if (column.isSearchable) {
                td.innerHTML = dynamicLocaleLabels["locatorResetLabel"];
                if (column.filter != null) {
                    td.locator = controller;
                    td.columnIndex = i;
                    td.onclick = function(e) { this.locator.onClickReset(this.columnIndex); }
                }
            }

			tr.appendChild(td);
			td = document.createElement("td");
			td.className = "cdsLocatorHeaderSortCell";
            if (column.isSearchable) {
                td.innerHTML = dynamicLocaleLabels["locatorSortLabel"];
                td.locator = controller;
                td.sortIndex = i;
                td.onclick = function(e) { this.locator.onClickSort(this.sortIndex); }
            }
			tr.appendChild(td);
		}
	}
	thead.appendChild(tr);

	return thead;
}


