// ******************************************************************
// 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
// ******************************************************************

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.displaySingleProduct = (properties["locator.displaySingleProduct"].toUpperCase().indexOf("Y") == 0);
    this.addTopSpotLinks = (properties["global.addTopSpotLinks"].toUpperCase().indexOf("Y") == 0);
    this.useAJAX = (properties["locator.useAJAX"].toUpperCase().indexOf("Y") == 0);
    this.categoryId = entities.LocatorEntity.category;
    this.view = null;
    this.knownUnits = {'in':'inch', 'mm':'millimeter', 'lb':'pound', 'oz':'ounce', 'N':'newton', 'bar':'bar', 'psi':'pound per square inch'};
    this.lastJSON = null;
    this.numberFormatter = new cdslib.NumberFormatter(properties["global.defaultUnit"], this.initFromLastJSON, this);
    if (properties["global.defaultUnit"] === "english") {
        this.numberFormatter.renderMetricFirst = false;
    }
}

// ====================================
// Public methods
// ====================================

cdsLocator.prototype.init = function() {
    this.view = new cdsLocatorView(this.elementId, null);
    // if just one row, go directly to part details
    // only do this if we're using ajax, otherwise you end up with
    // broken search when you use the back key
    if (this.useAJAX) {
        if (!this.displaySingleProduct && entities.LocatorEntity.rows.length === 1) {
            this.onSelectProduct(entities.LocatorEntity.rows[0][0], entities.LocatorEntity.rows[0][2]);
        }
    }

    this.initFromJSON(entities.LocatorEntity);
}

cdsLocator.prototype.initFromLastJSON = function(context) {
    if (context.lastJSON != null) {
        context.initFromJSON(context.lastJSON);
    } else {
        context.initFromJSON(entities.LocatorEntity);
    }
}

cdsLocator.prototype.initFromJSON = function(json) {
    this.lastJSON = 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 = new Array(json.columns.length);
    for (var i = 0; i < json.columns.length; i++) {
        var col = json.columns[i];
        this.columns[i] = new cdsLocatorColumn(col[0], col[1], col[2], col[3], col[4], col[5], col[6], col[7], col[8], col[9], col[10], col[11], col[12]);
    }
    // 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;
                }
            }
        }
    }
    // determine if we should have unit toggle
    this.view.toggleUnits = false;
    for (var i = 0; i < this.columns.length; i++) {
        if (this.columns[i].isVisible && this.columns[i].unitMetric && this.columns[i].unitEnglish) {
            this.view.toggleUnits = true;
            break;
        }
    }
    this.rows = json.rows;

    // get precision for all columns
    this.precisionMap = {};
    for (var i = 0; i < this.columns.length; i++) {
        if (this.columns[i].type === 'decimal' || this.columns[i].type === 'fraction') {
            var precision = 0;
            var v = null;
            for (var j = 0; j < this.rows.length; j++) {
                if (this.rows[j][i]) {
                    v = this.rows[j][i];
                    break;
                }
            }
            if (v) {
                var pos = v.indexOf('.');
                if (pos != -1) {
                    precision = v.toString().length - pos - 1;
                }
                this.precisionMap[this.columns[i].id] = precision;
            }
        }
    }

    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();

    // do any customer specific functionality
    if (typeof locatorPreDisplayCallback === "function") {
        locatorPreDisplayCallback(this);
    }
    this.display();
    // do any customer specific functionality
    if (typeof locatorPostDisplayCallback === "function") {
        locatorPostDisplayCallback(this);
    }

    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 = decodeURIComponent(a[0]);
                var val = decodeURIComponent(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 = decodeURIComponent(a[0]);
                var min = decodeURIComponent(a[1]);
                var max = decodeURIComponent(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 += encodeURIComponent(filter[0]);
            } else {
                if (filter[1] != null) {    // min filter
                    results += encodeURIComponent(filter[1]);
                } else {
                    results += "x";
                }
                results += ":";
                if (filter[2] != null) {    // max filter
                    results += encodeURIComponent(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;
}

cdsLocator.prototype.getDisplayValue = function(value, column) {
    var v;

    if (column.type === "decimal" || column.type === "fraction") {
        v = this.numberFormatter.formatForDisplay(value, this.precisionMap[column.id], column.unit, column.unitMetric,
                column.unitEnglish, (column.type === "fraction"));
    } else {
        v = this.numberFormatter.formatForDisplay(value, null, null, null, null, false);
    }

    return v;
}

cdsLocator.prototype.getBaseValue = function(value, column) {
    var v;

    if (column.type === "decimal" || column.type === "fraction") {
        v = this.numberFormatter.unformatFromDisplay(value, column.unit, column.unitMetric, column.unitEnglish);
    } else {
        v = this.numberFormatter.unformatFromDisplay(value, null, null, null);
    }
    return v;
}

// ====================================
// Event callback functions
// ====================================

cdsLocator.prototype.onSelectProduct = function(productNumber, flag) {
    if (flag == "alternateUrl") {
        location.href = this.altOutputUrlMap.replace(/%DOMAIN%/g, encodeURIComponent(domain)).replace(/%ID%/g, encodeURIComponent(productNumber)).replace(/%CATEGORY%/g, encodeURIComponent(this.categoryId));
    } else {
        location.href = this.outputUrlMap.replace(/%DOMAIN%/g, encodeURIComponent(domain)).replace(/%ID%/g, encodeURIComponent(productNumber)).replace(/%CATEGORY%/g, encodeURIComponent(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) {
    if (this.useAJAX) {
        var s = "service?domain=" + domain +
            "&entity=Locator&category=" + this.currentCategory +
            "&page=" + this.page +
            ((this.filter != null) ? ("&filter=" + encodeURIComponent(this.filter)) : "") +
            ((this.getCurrentSort() != null) ? ("&sort=" + this.getCurrentSort()) : "");
        this.waitingForServer = true;
        document.body.style.cursor = "wait";
        if (callingElement) {
            callingElement.style.cursor = "wait";
        }
        ajaxCaller.getPlainText(s, this.getServerResultsCallback);
    } else {
        var s = "service?domain=" + domain +
            "&command=locate&category=" + this.currentCategory +
            "&page=" + this.page +
            ((this.filter != null) ? ("&filter=" + encodeURIComponent(this.filter)) : "") +
            ((this.getCurrentSort() != null) ? ("&sort=" + this.getCurrentSort()) : "");
        location.href = s;
    }
}

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, tooltip, type, unit, unitMetric, unitEnglish, visible, searchable, rangeSearch, selectLTE, selectGTE, valueList) {
    this.id = id;                                                        // unique identifier of column
    this.label = label;                                                    // text to display
    this.tooltip = tooltip;                                             // html to display for mouse over tooltip
    this.type = type;                                                    // valid types: string, decimal, fraction, attachment
    this.unit = unit;                                                    // the unit of measure for this column when retrieved from database
    this.unitMetric = unitMetric;                                       // the unit of measure for this column when metric units are selected
    this.unitEnglish = unitEnglish;                                     // the unit of measure for this column when english units are selected
    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.selectedRow = null;        // currently selected row
    this.productNumberLabel = properties["locator.productNumberLabel"];
    this.thumbnailColumn = properties["global.thumbnailColumn"];
}

// 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));
    for (var i = 0; i < this.columns.length; i++) {
        if (this.columns[i].tooltip != null) {
            $('#attribute_tooltip_' + this.columns[i].id).cluetip({'local': true,'arrows': true,'showTitle': false,'dropShadow': false});
        }
    }
}

// ====================================
// Private methods
// ====================================

// render the table containing image, result page selections, buttons
cdsLocatorView.prototype.renderControls = function(controller) {
    var table = document.createElement("table");
    table.className = "cdsLocatorControlTable";
    table.setAttribute("id", "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);

        // units selector
        if (this.toggleUnits) {
            td = document.createElement("td");
            td.className = "cdsLocatorControlUnitCell";
            tr.appendChild(td);
            controller.numberFormatter.renderMeasurementSystemToggle(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);

        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.setAttribute("id", "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] : "";

                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));
                        if (this.addTopSpotLinks && typeof pageTracker != "undefined") {
                            a.onclick = function() {
                                pageTracker._link(this.href);
                                return false;
                            };
                        }
                        td.appendChild(a);

                    // if thumbnail column, show images
                    } else if (this.columns[j].id === this.thumbnailColumn && val && val !== "") {
                        var img = document.createElement("img");
                        img.setAttribute("src", fixUrl(val));
                        td.appendChild(img);

                    } else {
                        td.innerHTML = controller.getDisplayValue(val, this.columns[j]);
                    }
                }
                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 && 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.tooltip != null) {
                    label = "<a id='attribute_tooltip_" + column.id + "' rel='#attribute_tooltip_" + column.id + "_content'>" + label + "</a>";
                }
                var du = controller.numberFormatter.getDisplayUnit(column.unit, column.unitMetric, column.unitEnglish);
                if (du) {
                    label += " (" + du + ")";
                }
                if (column.tooltip != null) {
                    label += "<div id='attribute_tooltip_" + column.id + "_content' style='display: none;'>" + column.tooltip + "</div>";
                }
                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 && column.isSearchable && column.valueList && column.valueList.length > 0) {
                td = document.createElement("td");
                td.setAttribute("colSpan", 2);
                var list = column.valueList;

                // if we have a single available value and no range query 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].label;
                if (query != null && !(column.currentQuery && (column.currentQuery[1] || column.currentQuery[2]))) {
                    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));
                    inp.setAttribute("value", controller.getDisplayValue(query, column));
                    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);
                    inp.setAttribute("value", controller.getDisplayValue(column.currentQuery[1], column));
                    td.appendChild(inp);

                    inp = document.createElement("input");
                    inp.setAttribute("type", "text");
                    inp.className = "cdsLocatorHeaderSelectRowRangeField";
                    inp.readOnly = "readonly";
                    inp.setAttribute("size", 4);
                    inp.setAttribute("value", controller.getDisplayValue(column.currentQuery[2], column));
                    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].id.substring(1));
                        opt.appendChild(document.createTextNode(controller.getDisplayValue(list[j].label, column)));
                        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;

                                // fix units, fractions
                                min = controller.getBaseValue(min, this.locator.columns[this.columnIndex]);
                                max = controller.getBaseValue(max, this.locator.columns[this.columnIndex]);

                                // 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 " + controller.getDisplayValue(this.maxPossibleValue, column) + ".");
                                    this.minInput.value = "Min";
                                    this.minInput.focus();
                                    return;
                                }
                                if (max != null && +max < this.minPossibleValue) {
                                    alert("Please enter a max value of at least " + controller.getDisplayValue(this.minPossibleValue, column) + ".");
                                    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 && 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;
}


