﻿/***************************************************************
Javascript Library for Search API
    
Library Dependencies:
- jQuery Core v1.3.2
- ivory.core.js
***************************************************************/

/* 
* Form Helper, specifically for the Search Engine
*/
$.form = (function () {

    var schema = {
        template: "data-template",
        key: "data-key",
        require: "data-require",
        source: "data-source",
        resourceUrl: "data-resource-url",
        children: "data-children",
        plugin: "data-plugin",
        defaultValue: "data-def-value",
        showcount: "data-showcount"
    }

    function resetChildren(control) {
        var parent = control.parents(".form-container");

        var children = $.form.children(control);
        if ($.isEmpty(children))
            return;

        $.each(children.split(","), function (idx, data) {

            var child = parent.find("#" + $.trim(data));
            if (child.get(0).type === "select-one") {
                var defaultValue = $.form.defaultValue(child);
                if (!$.isEmpty(defaultValue))
                    child.html('<option value="">' + defaultValue.replace(/\{0\}/g, '') + '</option>');

                resetChildren(child);
            }
        });
    }

    return {

        defaultValue: function (control) {
            var val = control.attr(schema.defaultValue);
            if (!$.isEmpty(val))
                return val;

            return "";
        },

        template: function (control) {
            return control.attr(schema.template);
        },

        // REVISIT: should it returns array instead of string?
        children: function (control) {

            var container = control;

            var type = control.attr("type");
            if (type == "checkbox") {
                container = control.parents(".checkboxlist");
            }
            if (type == "radio") {
                container = control.parents(".radiobuttonlist");
            }

            return container.attr(schema.children);
        },

        parent: function (control) {
            return control.parents(".form-container");
        },

        ajax: function (control, getValuesDelegate, ajaxExecutionDelegate) {

            var parent = $.form.parent(control);

            // if nothing is selected, just reset the child control, and return
            var values = getValuesDelegate(control);
            if ($.isEmpty(values)) {
                resetChildren(control);
                return;
            }

            var url = parent.attr(schema.resourceUrl) + "&N=" + values;

            var children = $.form.children(control);

            if ($.isEmpty(children))
                return;

            $.each(children.split(","), function (idx, data) {

                resetChildren(control);

                var child = parent.find("#" + $.trim(data));
                var datasource = child.attr(schema.source);
                if (!$.isEmpty(datasource)) {
                    var serviceUrl = url + '&dim=' + datasource;

                    var plugin = child.attr(schema.plugin);
                    if (!$.isEmpty(plugin))
                        serviceUrl += '&plugin=' + plugin;

                    var template = control.attr(schema.template);
                    if (!$.isEmpty(template))
                        serviceUrl += '&' + template.replace(/\{0\}/ig, control.val());

                    ajaxExecutionDelegate(serviceUrl, control, child);
                }
            });
        },

        LoadItemCount: function (control) {
            // This only applies to SELECT
            if (control.get(0).type !== "select-one")
                return;

            if (control.attr("data-showcount")) {
                control.find("option").each(function () {
                    if (this.text.indexOf("(") <= -1) {
                        var count = $(this).attr("data-count");
                        if (count) {
                            this.text += " (" + count + ")"; 
                        }
                    }
                });
            }
        }
    };
})();


/* 
* Helper class to deal with postcode control
*/
var $postcode = (function () {
    var schema = {
        template: "data-template",
        currentvalue: "data-currentvalue",
        redirectUrl: "data-redirect-url",
        removeNValues: "data-remove-n"
    }

    function replaceNValues(NValues, removeNValues) {
        if (removeNValues != undefined && removeNValues.length > 0) {
            var ns = NValues.split(/[\s+]/),
                        nslen = ns.length,
                        removens = removeNValues.split(","),
                        removenslen = removens.length,
                        replaceN = [],
                        i = nslen;

            while ((--i) >= 0) {
                var hasFound = false,
                            j = removenslen;

                while ((--j) >= 0)
                    if (removens[j] == ns[i]) {
                        hasFound = true;
                        break;
                    }

                if (!hasFound)
                    replaceN.push(ns[i]);
            }
            return replaceN.join('+');
        }
        return NValues;
    }

    return {
        Init: function ($container) {
            var postcode = $container.find(".txtPostcode"),
                distance = $container.find("#cboDistance"),
                templateUrl = $container.attr(schema.redirectUrl),
                removeNValues = $container.attr(schema.removeNValues),
                currentValue = eval($container.attr(schema.currentvalue));

            if (currentValue != null && currentValue.length == 2) {
                postcode.val(currentValue[0]);
                distance.val(currentValue[1]);
            }

            function doPostCodeSearch() {
                var enteredPostcode = postcode.val(),
                    enteredDistance = distance.val();

                if (!$.isEmpty(enteredPostcode)) {

                    var template = postcode.attr(schema.template);
                    var query = template.replace(/\{0\}/g, enteredPostcode).split("=");

                    if (templateUrl != undefined && templateUrl.length > 0) {
                        $.url.query(query[0], query[1], templateUrl);
                        templateUrl = $.url.joinBaseWithQueryString(templateUrl);
                    }
                    else {
                        $.url.query(query[0], query[1]);
                    }

                    template = distance.attr(schema.template);
                    query = template.replace(/\{0\}/g, enteredDistance).split("=");
                    $.url.query(query[0], query[1]);

                    $.url.query("N", replaceNValues($.url.query("N"), removeNValues));

                    if (templateUrl != undefined && templateUrl.length > 0) {
                        $.url.redirect(templateUrl);
                    }
                    else {
                        $.url.redirect();
                    }
                }
                else {
                    alert("Please enter valid postcode!");
                }
            }

            $container.find(":input[type=text]").keypress(function (ev) {
                if (ev.which == '13') {
                    ev.preventDefault();
                    doPostCodeSearch();
                }
            });

            // IE6 won't execute window.location change when href is set with 'javascript:;', so we have to remove the attribute
            $.getButton($container).removeAttr("href").click(doPostCodeSearch);
        }
    }
})();
/* 
* Helper class to deal with keyword control
*/
var $keyword = (function () {
    var schema = {
        template: "data-template",
        watermark: "data-watermark-text",
        currentvalue: "data-currentvalue",
        sourceUrl: "data-source-url",
        delay: "data-delay",
        minLength: "data-min-length",
        redirectUrl: "data-redirect-url"
    }

    function initWatermark(textbox) {
        var watermark = textbox.attr(schema.watermark);
        if ($.isEmpty(watermark))
            return;

        textbox.focus(function () {
            if ($(this).val() === watermark) {
                $(this).val("").removeClass("watermark");
            }
            else {
                this.select();
            }
        });

        textbox.blur(function () {
            var propVal = $.trim($(this).val());

            if (!propVal.length) {
                $(this).val(watermark).addClass("watermark");
            }
        });
    }

    function initAutoComplete(textbox) {
        if (!textbox.hasClass("autocomplete"))
            return;

        var delay = textbox.attr(schema.delay);
        var source = textbox.attr(schema.sourceUrl);
        var minLength = textbox.attr(schema.minLength);
        textbox.autocomplete({
            ajaxUrl: source,
            minLength: minLength
        });
    }

    function getVal(textbox) {
        var keyword = textbox.val(), watermark = textbox.attr(schema.watermark);

        if ($.isEmpty(keyword))
            return "";

        if (escape(keyword) === escape(watermark))
            return "";

        return escape(keyword);
    }

    function setResetUrl(resetButton, template) {
        var re = new RegExp("[\\?\\&]?" + template.replace("{0}", "[^&]+"), "i"),
            href = window.location.toString().replace(re, "");

        resetButton.attr("href", href);
    }

    return {
        Init: function ($container) {
            var textbox = $container.find(":text"),
                resetButton = $container.find(".reset"),
                goButton = $.getButton($container),
                doKeywordSearch = function () {
                    var keyword = getVal(textbox), template, query;
                    if (keyword.length) {
                        var templateUrl = textbox.attr(schema.redirectUrl)
                        template = textbox.attr(schema.template);
                        query = template.replace(/\{0\}/g, keyword).split("=");
                        if (templateUrl != undefined && templateUrl.length > 0) {
                            $.url.query(query[0], query[1], templateUrl);
                            $.url.redirect(templateUrl);
                        }
                        else {
                            $.url.query(query[0], query[1]);
                            $.url.redirect();
                        }

                    }
                    else {
                        alert("Please specify a keyword or keywords to search for.");
                    }
                };

            if (!$.isEmpty(textbox)) {
                initWatermark(textbox);
                initAutoComplete(textbox);
            }

            if (resetButton.length) {
                setResetUrl(resetButton, textbox.attr(schema.template));
            }

            textbox.keypress(function (ev) {
                if (ev.which === 13) {
                    // It is navigation if we have go button, otherwise it is search module
                    if (goButton.length > 0) {
                        ev.preventDefault();
                        doKeywordSearch();
                    }
                    else {
                        $.form.parent($container).find('[data-def-value=Search]').click();
                    }
                }
            });

            // IE6 won't execute window.location change when href is set with 'javascript:;', so we have to remove the attribute
            goButton.removeAttr("href").click(doKeywordSearch);
        },

        GetValue: function (control) {
            return getVal(control);
        }
    }
})();

var $range = (function () {
    var schema = {
        template: "data-template",
        content: "data-content",
        tolerance: "data-tolerance",
        redirecturl: "data-redirect-url"
    }

    return {
        Init: function ($container) {
            var minInput, maxInput,
                toleranceCheckbox = $container.find(":checkbox:first"),
                name = $container.attr("id").match(/range(\w+)Row/)[1],
                isToleranceStateChanged = false;

            $container.find("select").each(function () {
                var that = $(this);
                if ($(this).hasClass("Min")) {
                    minInput = that;
                } else {
                    maxInput = that;
                }
            });

            // IE6 won't execute window.location change when href is set with 'javascript:;', so we have to remove the attribute
            $.getButton($container).removeAttr("href").click(function () {
                var enteredMinVal = minInput.val(),
                    enteredMaxVal = maxInput.val(),
                    template = minInput.attr(schema.template),
                    tolerance = 0;

                if (toleranceCheckbox.length && toleranceCheckbox[0].checked) {
                    tolerance = parseFloat(toleranceCheckbox.attr("value"), 10);
                }

                $range.Redirect($container, enteredMinVal, enteredMaxVal, template, tolerance);
            });

            var tooltip = $container.find(".tooltip");
            if (tooltip.length) {
                $.loadScript("/lib/jquery/plugins/qtip.js", function () {
                    var elementsFixed = false,
                        beforeShowHandler = function () {
                            var wrapper = this.elements.wrapper;
                            if (!(elementsFixed || wrapper.children(".arrow").length)) {
                                wrapper.prepend("<span class='arrow'>&nbsp;</span>");
                                wrapper.find(".qtip-contentWrapper")[0].style.border = "";
                                wrapper.find(".qtip-contentWrapper")[0].style.background = "";
                                wrapper.find(".qtip-content")[0].style.color = "";
                                elementsFixed = true;
                            }
                        };
                    tooltip.qtip({
                        content: tooltip.attr(schema.content),
                        position: { corner: { tooltip: "leftMiddle", target: "rightMiddle"} },
                        api: { "beforeShow": beforeShowHandler },
                        style: { classes: { "content": "range-tooltip-content"} }
                    });
                    tooltip.removeAttr("data-tooltip");
                });
            }

            if (toleranceCheckbox.length) {
                if (!$.cookie("userSearch")) {
                    $.subCookie("userSearch", name + "_Tolerance", String(toleranceCheckbox[0].checked));
                }

                $.each($.url.query('Range').split('|'), function (i, val) {
                    if (val.indexOf('Price:') == 0) {
                        if (val.indexOf('~') > 0)
                            toleranceCheckbox[0].checked = 'checked';
                        else
                            toleranceCheckbox[0].checked = '';
                        return false;
                    }
                });

                toleranceCheckbox.bind(($.browser.msie ? "click" : "change"), function (ev) {
                    var chkBox = this;
                    $.subCookie("userSearch", name + "_Tolerance", String(chkBox.checked));
                    if (!isToleranceStateChanged) {
                        isToleranceStateChanged = true;
                    }
                });
            }
        },

        Redirect: function (container, enteredMinVal, enteredMaxVal, queryTemplate, tolerance) {
            var removeExisting = (enteredMinVal === "Min" && enteredMaxVal === "Max"), newRange = "", foundMatch = false, temp, query, queryValue, currentRange;

            if (parseInt(enteredMinVal) > parseInt(enteredMaxVal)) {
                temp = enteredMinVal;
                enteredMinVal = enteredMaxVal;
                enteredMaxVal = temp;
            }

            // construct query based on user selection            
            query = queryTemplate.replace(/\{0\}/g, enteredMinVal).split("=");
            queryValue = query[1] + "," + enteredMaxVal + (tolerance > 0 ? ("~" + tolerance) : "");
            var templateUrl = container.attr(schema.redirecturl);

            if (templateUrl != undefined && templateUrl.length > 0)
                currentRange = $.url.query(query[0], null, templateUrl);
            else
                currentRange = $.url.query(query[0]);

            $.each(currentRange.split("|"), function (idx, data) {
                if (queryValue.indexOf(data.split(":")[0]) >= 0) {
                    data = (!removeExisting) ? queryValue : "";
                    foundMatch = true;
                }

                if (newRange.length > 0)
                    newRange += "|";

                newRange += data;
            });

            if (!foundMatch) {
                if (newRange.length > 0)
                    newRange += "|";

                newRange += queryValue;
            }


            if (templateUrl != undefined && templateUrl.length > 0) {
                $.url.query(query[0], newRange, templateUrl);
                $.url.redirect(templateUrl);
            }
            else {
                $.url.query(query[0], newRange);
                $.url.redirect();
            }



        }


    }
})();


/* 
* Helper class to deal with range control (free text box)
*/
var $freerange = (function () {
    var schema = {
        minvalue: "data-minvalue",
        maxvalue: "data-maxvalue",
        template: "data-template",
        currentvalue: "data-currentvalue"
    },

    defaultValue = {
        min: "Min",
        max: "Max"
    }

    function initInputTextBox($ctrl, defValue, minValue, maxValue, contextValue) {

        if (!$.isEmpty(contextValue) && contextValue != minValue && contextValue != maxValue && contextValue != defValue)
            $ctrl.val(contextValue);
        else
            $ctrl.val(defValue);

        $ctrl.focus(function () {
            if ($(this).val() == defValue)
                $(this).val("");
        });

        $ctrl.blur(function () {
            if ($(this).val().trim() == "") {
                $(this).val(defValue);
                return;
            }

            if (!validateInput($(this).val(), defValue, minValue, maxValue)) {
                this.focus(); // BUG: sometimes it doesn't focus properly
                return;
            }
        });
    }

    function inputIsValid(enteredValue, defValue, minValue, maxValue) {
        return (enteredValue == defValue
            || (parseInt(enteredValue) >= parseInt(minValue) && parseInt(enteredValue) <= parseInt(maxValue)))
    }

    function validateInput(enteredValue, defValue, minValue, maxValue) {
        if (!inputIsValid(enteredValue, defValue, minValue, maxValue)) {
            alert("Valid inputs are between " + minValue + " and " + maxValue);
            return false;
        }

        return true;
    }

    return {
        Init: function ($freerangeContainer) {
            var minValue = $freerangeContainer.attr(schema.minvalue);
            var maxValue = $freerangeContainer.attr(schema.maxvalue);
            var currentValue = eval($freerangeContainer.attr(schema.currentvalue));

            var minInput = $freerangeContainer.find(".Min");
            initInputTextBox(minInput, defaultValue.min, minValue, maxValue, currentValue[0]);

            var maxInput = $freerangeContainer.find(".Max");
            initInputTextBox(maxInput, defaultValue.max, minValue, maxValue, currentValue[1]);

            // IE6 won't execute window.location change when href is set with 'javascript:;', so we have to remove the attribute
            $.getButton($freerangeContainer).removeAttr("href").click(function () {
                var enteredMinVal = minInput.val();
                if (!validateInput(enteredMinVal, defaultValue.min, minValue, maxValue))
                    return;

                var enteredMaxVal = maxInput.val();
                if (!validateInput(enteredMaxVal, defaultValue.max, minValue, maxValue))
                    return;

                $range.Redirect(enteredMinVal, enteredMaxVal, minInput.attr(schema.template));
            });
        }
    }
})();


/* 
* Helper class to deal with range control (slider)
*/
var $slider = (function () {
    var schema = {
        increment: "data-increment",
        minvalue: "data-minvalue",
        maxvalue: "data-maxvalue",
        currentvalue: "data-currentvalue",
        callback: "data-callback"
    }

    function format(type, value) {
        if (type == "currency") {
            return "$" + value;
        }
        return value;
    }

    function onChange(event, ui, callback, sliderContainer) {
        var minControl = sliderContainer.find(".Min");
        var maxControl = sliderContainer.find(".Max");

        minControl.val(ui.values[0]);
        maxControl.val(ui.values[1]);

        if (callback)
            callback(event, ui);
        else {
            var sliderInfoControl = sliderContainer.find('.sliderInfo');
            if (sliderInfoControl)
                sliderInfoControl.text(ui.values[0] + ' - ' + ui.values[1]);
        }
    }

    return {
        GenerateInfo: function (sender, ui, callback, format_type) {
            var parent = sender.parents(".sliderize");
            var minValue = (parent.attr(schema.minvalue) == ui.values[0]) ? "Min" : format(format_type, ui.values[0]);
            var maxValue = (parent.attr(schema.maxvalue) == ui.values[1]) ? "Max" : format(format_type, ui.values[1]);
            var html = callback(minValue, maxValue);
            parent.find(".sliderInfo").html(html);
        },

        Init: function (control) {
            var $host = control;
            var increment = eval($host.attr(schema.increment));
            var minValue = eval($host.attr(schema.minvalue));
            var maxValue = eval($host.attr(schema.maxvalue));
            var selectedValue = eval($host.attr(schema.currentvalue));
            var onSlideCallback = eval($host.attr(schema.callback));

            var sliderControl = $host.find(".sliderControl");
            var minControl = $host.find(".Min");
            var maxControl = $host.find(".Max");

            sliderControl.slider({
                range: true,
                min: minValue,
                max: maxValue,
                step: increment,
                values: selectedValue, // input is array eg: [0, 10000],
                slide: function (event, ui) {
                    onChange(event, ui, onSlideCallback, $host)
                },
                change: function (event, ui) {
                    onChange(event, ui, onSlideCallback, $host)
                }
            });

            minControl.change(function () {
                var sliderControl = $(this).siblings(".sliderControl");
                sliderControl.slider("values", 0, $(this).val());

                var sliderInfoControl = $(this).siblings('.sliderInfo');
                sliderInfoControl.text($(this).val() + ' - ' + sliderControl.slider("values", 1));
            });

            maxControl.change(function () {
                var sliderControl = $(this).siblings(".sliderControl");
                sliderControl.slider("values", 1, $(this).val());

                var sliderInfoControl = $(this).siblings('.sliderInfo');
                sliderInfoControl.text(sliderControl.slider("values", 0) + ' - ' + $(this).val());
            });

            sliderControl.slider("values", 1, maxValue);
        }
    };
})();


/* 
* Helper class to deal with select control
*/
var $select = (function () {

    function callback(sender, target, result) {

        $.bind.select(sender, target, result);

        $.form.LoadItemCount(target);

        // reload the selected value
        $history.load_children_cbo(sender);

    }

    return {

        Init: function (control) {

            $.form.LoadItemCount(control);

            var postCallbackEvent = control.attr("data-post-event");

            control.change(function () {
                $.form.ajax(

                    $(this),

                    function (sender) {

                        var parentValues = "";

                        // Check if there are parents
                        var parent = $.form.parent(sender).find("*[data-children=" + sender.get(0).id + "]");
                        while (parent != undefined && parent.length > 0) {
                            var values = "";
                        if (!$.isEmpty(parent)) {
                                values = $multiList.GetValues(parent);
                                if ($.isEmpty(values))
                                    values = parent.val();

                                //return sender.val() + "+" + parentValues;
                            }
                            parentValues += " " + values;
                            var parent = $.form.parent(parent).find("*[data-children=" + parent.get(0).id + "]");
                        }

                        return sender.val() + "+" + parentValues;
                    },

                    function (url, sender, target) {
                        var customDataBinder = target.attr("data-databinder");

                        $.ajax({
                            url: url,
                            beforeSend: function (xhr) {
                                target.html("<option>LOADING</option>");
                                target.attr("disabled", true);
                            },

                            success: function (result) {

                                if (customDataBinder) {
                                    var cCallback = eval(customDataBinder);
                                    cCallback(sender, target, result);
                                }
                                else
                                    callback(sender, target, result);

                                if (postCallbackEvent) {
                                    var pCallback = eval(postCallbackEvent);
                                    pCallback(sender, target, result);
                                }

                                target.removeAttr("disabled");
                            }
                        });
                    });
            });
        },

        AjaxCallback: function (sender, target, result) {
            callback(sender, target, result);
        }
    };
})();


/* 
* Helper class to deal with checkbox control
*/
var $multiList = (function () {

    var schema = {
        resourceUrl: "data-resource-url",
        datasource: "data-source",
        redirectUrl: "data-redirect-url"
    }

    function callback(sender, target, result) {

        var type = target.attr("type");
        if (type == "select-one") {
            $select.AjaxCallback(sender, target, result);
            return;
        }

        var data = JSON.parse(result);

        // replace using foreach
        var options = "";
        for (var i = 0; i < data.length; i++) {
            options += "<input type='checkbox' checked='checked' value='" + data[i].Value + "' />" + data[i].Text;
        }

        target.html(options);
    }

    return {

        Init: function (control) {

            // register click event on every checkbox
            control.find(":checkbox,:radio").click(function () {
                $.form.ajax(

                    $(this),

                    function (sender) {
                        return $multiList.GetValues(sender);
                    },

                    function (url, sender, target) {
                        $.ajax({
                            url: url,
                            success: function (result) {
                                callback(sender, target, result);
                            }
                        });
                    });
            });

            // register click event on update button to resfresh the result
            // IE6 won't execute window.location change when href is set with 'javascript:;', so we have to remove the attribute
            $.getButton(control.parents(".multilist-panel")).removeAttr("href").click(function () {

                var multiListControl = $(this).parent(".multilist-panel");
                var currentMultiListControl = $(this).parent(".multilist-panel").find("ul");
                var checkedValues = $multiList.GetValues(currentMultiListControl);

                if ($.isEmpty(checkedValues)) {
                    alert("Please select at least one option to continue!");
                    return;
                }
                var templateUrl = multiListControl.attr(schema.redirectUrl);
                var currentValues;
                var currentISearchValues;

                if (templateUrl != undefined && templateUrl.length > 0) {
                    currentValues = unescape($.url.query("N", null, templateUrl).replace(/\+/g, " "));
                    currentISearchValues = unescape($.url.query("include", null, templateUrl).replace(/\+/g, " "));
                }
                else {
                    currentValues = unescape($.url.query("N", null).replace(/\+/g, " "));
                    currentISearchValues = unescape($.url.query("include", null).replace(/\+/g, " "));
                }

                var unchecked = $multiList.GetUncheckedValues(control);
                $.each(unchecked.split(" "), function (idx, data) {
                    currentValues = currentValues.replace(data, "");
                });

                $.each(checkedValues.split(" "), function (idx, data) {
                    currentValues = currentValues.replace(data, "");
                });

                if (currentValues.length > 0) {
                    currentValues += " ";
                }

                currentValues += checkedValues;

                var newSearchValues = "";
                if (currentISearchValues) {
                    var key = currentMultiListControl.attr("data-source");
                    var values = checkedValues.replace(/\s+/g, ","); 

                    //Re-construct the includes fields
                    $.each(currentISearchValues.split(";"), function (idx, searchParams) {
                        var searchParam = searchParams.split(":");
                        if (searchParam[0].toLowerCase() == key.toLowerCase()) {
                            newSearchValues += searchParam[0].toLowerCase() + ":" + values.toLowerCase() + ";";
                        }
                        else if(searchParams.length) {
                            newSearchValues += searchParams + ";";
                        }
                    });
                }

                // do trimming since the input can be messy after the processing above
                var newValues = "";
                $.each(currentValues.split(" "), function (idx, data) {
                    if (data.trim().length > 0)
                        newValues += data.trim() + " ";
                });

                var iSearchValues = currentISearchValues.replace(/\s+/g, ",");

                if (templateUrl != undefined && templateUrl.length > 0) {
                    $.url.query("N", newValues.trim(), templateUrl);
                    $.url.redirect(templateUrl);
                }
                else {
                    $.url.query("N", newValues.trim());
                    if (newSearchValues.length > 0) {
                        $.url.query("include", newSearchValues.trim());
                    }
                    $.url.redirect();
                }
            });
        },

        GetValues: function ($sender) {

            var $parent = $sender;

            // The sender can be the parent itself
            if (!$sender.hasClass("checkboxlist") && !$sender.hasClass("radiobuttonlist")) {
                if ($sender.parents(".checkboxlist").length > 0)
                    $parent = $sender.parents(".checkboxlist");
                else if ($sender.parents(".radiobuttonlist").length > 0)
                    $parent = $sender.parents(".radiobuttonlist");
            }

            var str = "";
            $parent.find("input:checked").each(function () {
                var value = unescape($(this).val()).replace(/\+/g, " ");
                str += " " + value;
            });

            var items = $.trim(str).split(" ");

            var values = "";

            for (var i = 0; i < items.length; i++) {
                var item = items[i];
                if (values.indexOf(item + " ") <= -1)
                    values += $.trim(item) + " ";
            }

            return $.trim(values);
        },

        GetUncheckedValues: function ($sender) {

            var $parent = $sender;

            // The sender can be the parent itself
            if (!$sender.hasClass("checkboxlist") && !$sender.hasClass("radiobuttonlist")) {
                if ($sender.parents(".checkboxlist").length > 0)
                    $parent = $sender.parents(".checkboxlist");
                else if ($sender.parents(".radiobuttonlist").length > 0)
                    $parent = $sender.parents(".radiobuttonlist");
            }

            var str = "";
            $parent.find("input:not(:checked)").each(function () {
                var value = unescape($(this).val()).replace(/\+/g, " ");
                str += " " + value;
            });

            var items = $.trim(str).split(" ");

            var values = "";

            for (var i = 0; i < items.length; i++) {
                var item = items[i];
                if (values.indexOf(item + " ") <= -1)
                    values += item + " ";
            }

            return $.trim(values);
        }
    };
})();


/* 
* Helper class to deal with Sorting Control
*/
var $sortSelection = (function () {

    function getCurrentSort() {
        return $.url.query("sort");
    }

    function attach_OnChange(control) {
        control.change(function () {

            var current = getCurrentSort();
            if (current.toLowerCase() === this.value.toLowerCase())
                return;

            // reset paging
            $.url.query("page", "1");

            // update the sort rule
            $.url.query("sort", this.value);
            $.url.redirect();
        });
    }

    return {
        Init: function (control) {

            var current = getCurrentSort();
            control.find("option[value='" + current + "']").attr("selected", true)

            attach_OnChange(control);
        }
    }

})();


/* 
* Helper class to deal with Search Engine
*/
var SearchControl = (function () {

    var schema = {
        QueryTemplate: "data-template",
        Key: "data-key",
        RequireValidation: "data-require",
        AjaxServiceUrl: "data-resource-url",
        DimMapping: "data-dim-map"
    }

    var searchContainerAttr = {
        redirectUrl: "data-redirect-url",
        redirectOverrideFunc: "data-override-redirect"
    }

    // Extract query template from control attributes.
    // If query template exists, it will use the template to generate query
    // Otherwise it will retrieve the key attribute of the control
    function ExtractQuery(control) {
        var value = escape(control.val());
        var queryTemplate = control.attr(schema.QueryTemplate);

        if ($.isEmpty(queryTemplate))
            return control.attr(schema.Key) + "=" + value;

        var sliderContainer = control.parents(".sliderize");
        if (sliderContainer.length == 1) {
            if (control.hasClass("Max") && sliderContainer.attr("MaxValue") == value)
                return "";
        }

        return queryTemplate.replace(/\{0\}/g, value);
    }

    function getKeywordQueryData(control) {
        var template = $.form.template(control);
        if ($.isEmpty(template)) {
            return "";
        }

        var value = $keyword.GetValue(control);
        if ($.isEmpty(value)) {
            return "";
        }

        return template.replace(/\{0\}/g, escape(value));
    }

    function SaveHistoryToHashTag(containerName) {
        var searchContainer = "#" + containerName,
            $form = $(searchContainer);

        function saveHistory(event, src) {
            if (src !== 'history') {
                window.location.hash = "/" + JSON.stringify($history.BuildHistoryObj(containerName));
            }
        }

        $form.find(":input").each(function () {
            switch (this.type) {
                case "checkbox":
                case "radio":
                case "select-one":
                    $(this).change(saveHistory);
                    break;
                case "text":
                    $(this).blur(saveHistory);
                    break;
            }
        });
    }

    // Start of Public Methods
    return {
        Init: function (containerName) {
            SaveHistoryToHashTag(containerName);
        },

        Validate: function ($form) {
            var errorMessage = "";
            $form.find(" :input[" + schema.RequireValidation + "^='true']").each(function () {
                var selectedValue = $(this).val();
                if ($.isEmpty(selectedValue))
                    errorMessage += "Please select " + $(this).attr("class") + "\n";
            });
            return errorMessage;
        },

        Submit: function (sender, containerName) {

            var searchContainer = "#" + containerName;
            var $form = $(searchContainer);

            var errorMessage = SearchControl.Validate($form);
            if (errorMessage.length > 0) {
                alert(errorMessage);
                return;
            }

            var selectedValue = "";
            var query = new Array();
            var matchString = "";

            $form.find(":input").each(function () {

                switch (this.type) {

                    case "checkbox":
                    case "radio":
                        {
                            if ((this.type == "checkbox" && $(this).is(":checked") != true) ||
                            (this.type == "radio" && $(this).is(":checked") != true))
                                break;

                            var value = $(this).val();

                            if ($.isEmpty(value))
                                break;

                            selectedValue += value + " ";

                            break;
                        }

                    case "select-one":
                        {
                            var self = $(this),
                                value = self.val(),
                                isLocation = self.attr("data-islocation");

                            if (isLocation && $(searchContainer).find(":input[data-source=Postcode]").val().length > 0)
                                break;

                            if ($.isEmpty(value))
                                break;

                            var defaultValue = $.form.defaultValue(self);

                            // If has query template, don't treat it as navigation
                            // instead use query array to push
                            var queryTemplate = self.attr(schema.QueryTemplate);
                            if (!$.isEmpty(queryTemplate)) {

                                var index = self.get(0).selectedIndex;
                                if ($(self.find("option")[index]).text() == defaultValue)
                                    value = defaultValue;

                                var val = queryTemplate.replace(/\{0\}/g, value);
                                query.push(val);
                                break;
                            }

                            // If the control has default value, and current index = 0, skip it
                            if (defaultValue.length > 0 && self.get(0).selectedIndex == 0)
                                break;

                            selectedValue += value + " ";

                            break;
                        }

                    case "text":
                        {
                            //Add N value and matches base on autocomplete search result
                            var dimMapping = $(this).attr(schema.DimMapping);
                            if (!$.isEmpty(dimMapping)) {
                                $.each(dimMapping.split("&"), function (idx) {
                                    var dimMap = this.split("=");
                                    if (dimMap.length == 2) {
                                        if (dimMap[0].toLowerCase() == "n")
                                            selectedValue += dimMap[1] + " ";
                                        else
                                            matchString += dimMap[0] + ":" + dimMap[1] + "|";
                                    }
                                });
                                break;
                            }

                            var queryData = getKeywordQueryData($(this));
                            if (!$.isEmpty(queryData))
                                query.push(queryData);

                            break;
                        }

                    case "hidden":
                        {
                            var value = $(this).val();

                            if ($.isEmpty(value))
                                break;

                            var val = ExtractQuery($(this));
                            if (!$.isEmpty(val))
                                query.push(val);

                            break;
                        }
                }
            });

            var querystring = "";
            var rangeParam = {};
            for (var i = 0; i < query.length; i++) {
                var item = query[i];

                if (item.indexOf("Range=") > -1) {
                    var rangeItem = item.split("=");
                    if (rangeItem.length > 0) {
                        var rangeProperty = rangeItem[1].split(":");

                        var key = "Nf_" + rangeProperty[0];
                        var value = rangeProperty[1];
                        if ($.isEmpty(rangeParam[key])) {
                            rangeParam[key] = value;
                        } else {
                            var minValue = parseInt(rangeParam[key]);
                            var maxValue = parseInt(value);
                            if (minValue > maxValue) {
                                var tolerance = value.split("~")[1];
                                var temp = minValue;
                                minValue = maxValue;
                                maxValue = temp + (tolerance ? ("~" + tolerance) : "");
                                rangeParam[key] = minValue + "," + maxValue;
                            } else {
                                rangeParam[key] += "," + value;
                            }

                            searchCookie = $.cookie("userSearch");
                            var toleranceRegExp = new RegExp(rangeProperty[0] + "_Tolerance=false");
                            if (searchCookie && toleranceRegExp.test(searchCookie)) {
                                rangeParam[key] = rangeParam[key].split("~")[0];
                            }
                        }
                    }
                }
                else {
                    if (querystring.length > 0)
                        querystring += "&";
                    querystring += item;
                }
            }

            if (querystring.length > 0)
                querystring = "&" + querystring;

            var rangeString = "";
            for (var i in rangeParam) {
                if (i.toString().indexOf("Nf_") < 0)
                    continue;

                var key = i.toString().replace("Nf_", "");
                var value = rangeParam[i];

                if (value == "0")
                    continue;

                if (rangeString.length > 0)
                    rangeString += "|";

                rangeString += key + ":" + value;
            }

            if (rangeString.length > 0) {
                rangeString = "&Range=" + rangeString;
            }

            // Retrieve Base N, vertical and silo from Resource URL             
            var resourceUrl = unescape($form.attr(schema.AjaxServiceUrl)).replace(/\?/g, "&");
            var hiddenNav = "";
            var verticalSiloInfo = "";
            $.each(resourceUrl.split("&"), function (idx) {
                if (this.toLowerCase().indexOf("base=") == 0) {
                    hiddenNav = this.toLowerCase().replace("base=", "");
                }
                else if (this.toLowerCase().indexOf("vertical=") == 0 || this.toLowerCase().indexOf("silo=") == 0) {
                    if (verticalSiloInfo.length > 0)
                        verticalSiloInfo += "&";
                    verticalSiloInfo += this;
                }
                else if (this.indexOf("match=") == 0) {
                    matchString += this.replace("match=", "")
                }
            });

            if (verticalSiloInfo.length > 0)
                verticalSiloInfo = "&" + verticalSiloInfo;

            var n = escape($.trim(selectedValue));

            if (!$.isEmpty(hiddenNav))
                n = hiddenNav + " " + n;

            queryString = "N=" + $.trim(n) + querystring + rangeString;

            if (!$.isEmpty(matchString))
                queryString += "&match=" + matchString.trim('|');

            var historyObj = $history.BuildHistoryObj(containerName);
            window.location.hash = "/" + JSON.stringify(historyObj);

            var redirectUrl = $(searchContainer).attr(searchContainerAttr.redirectUrl);

            var resultUrl = redirectUrl;
            if (redirectUrl.indexOf("?") > -1) {
                resultUrl += "&";
            }
            else {
                resultUrl += "?";
            }

            resultUrl += queryString + verticalSiloInfo + "&eapi=2";

            var senderJQuery = $(sender);

            var redirectOverride = senderJQuery.attr(searchContainerAttr.redirectOverrideFunc);

            //override the redirect behaviour if required
            if (redirectOverride) {
                eval(redirectOverride + '(\'' + resultUrl + '\')');
            } else {
                // to workaround ie6
                setTimeout(function () {
                    window.location = resultUrl;
                }, 0);
            }
        }
    };
})();


var $history = (function () {

    // Id-ify the control id into jQuery ID Selector
    function $id(control_id) {
        return $(id(control_id));
    }

    // Id-ify the control id into jQuery ID 
    function id(control_id) {
        return "#" + control_id;
    }

    function deserialize() {
        var fragment = location.hash.substring(2);
        if (fragment.length > 0) {
            fragment = window.decodeURIComponent ? decodeURIComponent(fragment) : unescape(fragment);
            return (/^\s*\{/).test(fragment) ? JSON.parse(fragment) : null;
        }
        return null;
    }

    function load_cbo($cbo, value) {
        //for handling ie 6 val timeout problem
        try {
            $cbo.val(value);
        }
        catch (ex) {
            setTimeout(
                function () {
                    $cbo.val(value)
                }, 1);
        }
        //

        if ($cbo.get(0) == undefined) return;

        // if there is no value updated, just return
        if ($cbo.get(0).selectedIndex === 0)
            return;

        var children = $.form.children($cbo);
        if (!$.isEmpty(children))
            $cbo.trigger('change', ['history']);
    }

    function load_range($tab, range, values) {
        var value = values.split(",");
        var min = value[0];
        var max = value[1];

        if (min.length > 0 || max.length > 0) {
            var $range = $tab.find(id("range" + range + "Container .sliderControl"));
            $range.slider("values", 0, min);
            $range.slider("values", 1, max);
        }
    }

    function load_multi($multi, value) {
        $.each($multi.find('li input'), function () {
            $(this).removeAttr('checked');
        });
        $.each(value.split(","), function (idx, data) {
            var selectedMulti = $multi.find('input[value="' + data + '"]');
            selectedMulti[0].checked = true;
            selectedMulti.click();
            selectedMulti[0].checked = true;
        });
    }

    function load_text($tab, range, value) {
        $tab.find('input[type=text][data-source=' + range + ']').val(value);
    }

    return {

        load_children_cbo: function ($cbo) {

            var history = deserialize();
            if (history == null)
                return;

            var $tab = $id(history.tab);

            var children = $.form.children($cbo);
            if (children == null)
                return;

            $.each(children.split(","), function (idx, data) {
                var $child = $tab.find(id(data));

                var child_selected_value = history.cbo[$child.get(0).id];

                if (child_selected_value != null)
                    load_cbo($child, child_selected_value);
            });
        },

        Load_children_multi: function ($cbo) {

            var history = deserialize();
            if (history == null)
                return;

            var $tab = $id(history.tab);

            $.each(history.multi, function (key, val) {
                var $multi = $tab.find(id(key));
                load_multi($multi, val)
            });
        },

        Load: function () {

            var history = deserialize();
            if (history == null)
                return;

            this.LoadHistory(JSON.stringify(history))
        },

        LoadHistory: function (history) {

            //Add history back to url so children load can access history

            window.location.hash = '/' + history;
            history = JSON.parse(history);

            var $tab = $id(history.tab);


            $.each(history.cbo, function (key, val) {
                var $cbo = $tab.find(id(key));
                load_cbo($cbo, val);
            });

            $.each(history.range, function (key, val) {
                load_range($tab, key, val)
            });

            $.each(history.multi, function (key, val) {
                var $multi = $tab.find(id(key));
                load_multi($multi, val)
            });

            $.each(history.text, function (key, val) {
                load_text($tab, key, val);
            });

        },

        BuildHistoryObj: function (containerName) {
            var historyObj = { cbo: {}, range: {}, multi: {}, text: {} };

            var searchContainer = "#" + containerName;
            var $form = $(searchContainer);
            var query = new Array();

            historyObj.tab = containerName;

            $form.find(":input").each(function () {

                switch (this.type) {

                    case "checkbox":
                    case "radio":
                        {
                            if ((this.type == "checkbox" && this.checked != true) ||
                            (this.type == "radio" && this.checked != true))
                                break;

                            var value = $(this).val();

                            if ($.isEmpty(value))
                                break;

                            //get first parent with id
                            var itemId = $(this).parents('div').attr('id');

                            if (!$.isEmpty(historyObj.multi[itemId]))
                                historyObj.multi[itemId] = historyObj.multi[itemId] + "," + value;
                            else
                                historyObj.multi[itemId] = value;

                            break;
                        }

                    case "select-one":
                        {
                            var value = $(this).val();
                            if ($.isEmpty(value))
                                break;

                            var defaultValue = $.form.defaultValue($(this));

                            // If has query template, don't treat it as navigation
                            // instead use query array to push
                            var queryTemplate = $(this).attr("data-template");
                            if (!$.isEmpty(queryTemplate)) {

                                var index = $(this).get(0).selectedIndex;
                                if ($($(this).find("option")[index]).text() == defaultValue)
                                    value = defaultValue;
                            }

                            // If the control has default value, and current index = 0, skip it
                            if (defaultValue.length > 0 && $(this).get(0).selectedIndex == 0)
                                break;

                            var itemId = $(this).attr('id');
                            historyObj.cbo[itemId] = value;

                            break;
                        }
                    case "text":
                        {
                            var value = $(this).val();
                            if ($.isEmpty(value))
                                break;

                            historyObj.text[$(this).attr('data-source')] = value;
                            break;
                        }
                }
            });

            var rangeParam = new Array();
            for (var i = 0; i < query.length; i++) {
                var item = query[i];

                if (item.indexOf("Range=") > -1) {
                    var rangeItem = item.split("=");
                    if (rangeItem.length > 0) {
                        var rangeProperty = rangeItem[1].split(":");

                        var key = "Nf_" + rangeProperty[0];
                        var value = rangeProperty[1];
                        if ($.isEmpty(rangeParam[key]))
                            rangeParam[key] = value;
                        else
                            rangeParam[key] += "," + value;
                    }
                }
            }

            for (var i in rangeParam) {
                if (i.toString().indexOf("Nf_") < 0)
                    continue;

                var key = i.toString().replace("Nf_", "");
                var value = rangeParam[i];

                if (value == "0")
                    continue;

                historyObj.range[key] = value;
            }

            return historyObj;
        }

    };
})();

$.refinement = (function () {

    return {

        init: function () {

            var ajaxUrl = $(".refinement").attr("data-resource-url") + "&dim=";

            $(".expand-link").click(function () {
                var link = $(this),
                    target = link.parents(".body"),
                    url = ajaxUrl + link.attr("data-source") + "&refurl=" + escape(link.attr("data-refurl"));

                target.children("ul").append("<li class='loading'>Loading...</li>");

                $.get(url, function (data) {
                    $.bind.list(null, target, data);
                });

            });

            $("ul.active .selected").live("click", function (ev) {
                var url = $(this).find("input").attr("data-url");
                window.location = url;
            });
        }
    };

})();
