// $Header: /web/src/pfc/cobrand/web/pfc/js/searchController.js,v 1.5 2008-03-31 06:20:19 mallan Exp $

/*globals RUI */

var RUI         = window.RUI      || {};
RUI.Standalone  = RUI.Standalone  || {};
RUI.PFC         = RUI.PFC         || {};
RUI.PFC.Partner = RUI.PFC.Partner || {};

//desc   Methods for manipulating class structures.
RUI.Class = {
    //method [static] create
    //desc   Creates a class and makes the method '_setOptions' available,
    //       which will merge optional, named parameters with the defaults.
    //       'initialize' is called when the class is instantiated.
    //param  [opt] defaultOptions: [Object] default named parameters
    create: function (options) {
        options = options || {};
        return function () {
            this.options = options.defaultOptions || {};
            this._setOptions = function (options) {
                RUI.Object.extend(this.options, options);
            };
            this.initialize.apply(this, arguments);
        };
    }
};

//desc   Standalone version of RUI with no dependencies on external libraries.
RUI.Standalone = {
    //method [static] use
    //desc   Replaces the standard RUI classes and methods with the
    //       RUI.Standalone versions, which are not dependent on any exteranl
    //       libraries.
    use: function () {
        RUI.Standalone.Object.extend(RUI, RUI.Standalone);
        RUI.use = undefined; /* Don't copy this method over */
    }
};

//desc   Methods applicable to generic objects.
RUI.Standalone.Object = {
    //method [static] extend
    //desc   Merges one object with another.
    //param  [req] destination [Object]
    //param  [req] source [Object]
    //return [Object] merged object
    extend: function (destination, source) {
        for (var property in source) {
            destination[property] = source[property];
        }

        return destination;
    }
};

//desc   DOM methods
RUI.Standalone.DOM = {
    //method [static] getElement
    //desc   Disambiguates between the DOM object and the ID.  Either can be
    //       passed in and the DOM object is returned.
    //param  [req] element [String or HTMLElement]
    //return [HTMLElement] element
    getElement: function (element) {
        if (typeof element === 'string') {
            element = document.getElementById(element);
        }

        return element;
    },

    //method [static] addClassName
    //desc   Adds a class name to an element.
    //param  [req] element [String or HTMLElement]
    //param  [req] classNameToAdd [String]
    addClassName: function (element, classNameToAdd) {
        element = RUI.DOM.getElement(element);

        var className = element.className;

        if (className.match(classNameToAdd)) {
            return;
        }

        element.className = className + ' ' + classNameToAdd;
    },

    //method [static] removeClassName
    //desc   Removes a class name from an element.
    //param  [req] element [String or HTMLElement]
    //param  [req] classNameToRemove [String]
    removeClassName: function (element, classNameToRemove) {
        element = RUI.DOM.getElement(element);

        var pattern = new RegExp('\\s?' + classNameToRemove);

        if (element.className) {
            element.className = element.className.replace(pattern, '');
        }
    },

    //method [static] isDisplayed
    //desc   Returns true if the element is visible.
    //param  [req] element [String or HTMLElement]
    //return [Boolean] true if visible
    isDisplayed: function (element) {
        element = RUI.DOM.getElement(element);
        if (
            element.style.display === 'none'
            || element.style.visibility === 'hidden'
        ) {
            return false;
        }
        else {
            return true;
        }
    },

    //method [static] show
    //desc   Displays an element.
    //param  [req] element [String or HTMLElement]
    show: function (element) {
        element = RUI.DOM.getElement(element);

        if (element.style.display === 'none') {
            element.style.display = '';
        }

        if (element.style.visibility === 'hidden') {
            element.style.visibility = 'visible';
        }
    },

    //method [static] hide
    //desc   Hides an element.
    //param  [req] element [String or HTMLElement]
    hide: function (element) {
        element = RUI.DOM.getElement(element);

        element.style.display = 'none';
    }
};

//desc   Event methods
//note   Ripped straight from Prototype 1.5 (c) 2005 Sam Stephenson
//       <sam@conio.net>. Prototype is freely distributable under the terms of
//       an MIT-style license. or details, see the Prototype web site:
//       http://prototype.conio.net/
RUI.Standalone.Event = {
    //method observe
    //desc   Attaches an event handler to an element.
    //param  [req] element [String or HTMLElement]
    //param  [req] name [String] event name without the leading 'on'
    //param  [req] observer [Function] event handler
    //param  [opt] useCapture [Boolean] true uses bubbling instead
    observe: function (element, name, observer, useCapture) {
        element = RUI.DOM.getElement(element);
        useCapture = useCapture || false;

        if (
            name === 'keypress'
            && (
                navigator.appVersion.match(/Konqueror|Safari|KHTML/)
                || element.attachEvent
            )
        ) {
            name = 'keydown';
        }

        RUI.Event._observeAndCache(
            element,
            name,
            observer,
            useCapture
        );
    },

    //method stopObserving
    //desc   Removes an event handler from an element.
    //param  [req] element [String or HTMLElement]
    //param  [req] name [String] event name without the leading 'on'
    //param  [req] observer [Function] event handler
    //param  [opt] useCapture [Boolean] true uses bubbling instead
    stopObserving: function (element, name, observer, useCapture) {
        element = RUI.DOM.getElement(element);
        useCapture = useCapture || false;

        if (
            name === 'keypress'
            && (
                navigator.appVersion.match(/Konqueror|Safari|KHTML/)
                || element.detachEvent
            )
        ) {
            name = 'keydown';
        }

        if (element.removeEventListener) {
            element.removeEventListener(name, observer, useCapture);
        }
        else if (element.detachEvent) {
            try {
                element.detachEvent('on' + name, observer);
            }
            catch (e) {}
        }
    },

    //method [static] unloadCache
    //desc   Removes all event handlers from the cache.
    unloadCache: function () {
        if (!RUI.Event.observers) {
            return;
        }

        for (var i = 0; i < RUI.Event.observers.length; i++) {
            RUI.Event.stopObserving.apply(this, RUI.Event.observers[i]);
            RUI.Event.observers[i][0] = null;
        }

        RUI.Event.observers = false;
    },

    //method [static] stop
    //desc   Prevents event propagation.
    //param  [req] event [Event]
    stop: function (event) {
        if (event.preventDefault) {
            event.preventDefault();
            event.stopPropagation();
        }
        else {
            event.returnValue = false;
            event.cancelBubble = true;
        }
    },

    //method [static protected] _observeAndCache
    //desc   Attaches an event handler and adds to the cache.
    //param  [req] element [String or HTMLElement]
    //param  [req] name [String] event name without the leading 'on'
    //param  [req] observer [Function] event handler
    //param  [opt] useCapture [Boolean] true uses bubbling instead
    _observeAndCache: function (element, name, observer, useCapture) {
        if (!this.observers) {
            this.observers = [];
        }

        if (element.addEventListener) {
            this.observers.push([element, name, observer, useCapture]);
            element.addEventListener(name, observer, useCapture);
        }
        else if (element.attachEvent) {
            this.observers.push([element, name, observer, useCapture]);
            element.attachEvent('on' + name, observer);
        }
    }
};

//desc   Function methods
RUI.Standalone.Function = {
    //method [static] bind
    //desc   Curry function for re-scoping functions.
    //param  [req] func [Function]
    //param  [req] scope [object]
    //param  [opt] bindAsEventListener [Boolean]
    bind: function (func, scope, bindAsEventListener) {
        var args = [];
        scope = scope || window;

        for (var i = 2; i < arguments.length; i++) {
            if (bindAsEventListener && i === arguments.length - 1) {
                continue;
            }

            args.push(arguments[i]);
        }

        return function (event) {
            if (bindAsEventListener) {
                args = [event].concat(args);
            }

            func.apply(scope, args);
        };
    }
};

//class  RUI.Standalone.ClearingField
//desc   A form field containing a default value that clears itself when
//       focused.
//note   This is a cut-down version of the reference implementation of
//       RUI.ClearingField with no dependency on Prototype and no cusotmisation
//       options.
RUI.Standalone.ClearingField = RUI.Class.create();

RUI.Standalone.ClearingField.prototype = {
    //method initialize
    //desc   Constructor.
    //param  [req] input [String] or [HTMLElement] input field ID or object
    //       default value is populated.
    initialize: function (input) {
        this.input = RUI.DOM.getElement(input);
        this.defaultValue = this.input.value;

        RUI.Event.observe(
            this.input,
            'focus',
            RUI.Function.bind(this.clear, this)
        );

        // Put the default value back in if the field is empty onblur
        RUI.Event.observe(this.input, 'blur', RUI.Function.bind(function (e) {
            if (!this.input.value) {
                this.input.value = this.defaultValue;
            }
        }, this));
    },

    //method clear
    //desc   Clears the field.
    clear: function () {
        // Clear the if it contains the default value
        if (this.input.value === this.defaultValue) {
            this.input.value = '';
        }
    }
};

//class  RUI.Standalone.ContentSwitch
//desc   A class to show/hide content
//eg     <a href="..." id="theTrigger">The Trigger</a>
//       <div id="theContent">The content to control</div>
//       <script type="text/javascript">
//         new RUI.ContentSwitch('theTrigger', 'theContent');
//       </script>
//note   This is a cut-down version of the reference implementation of
//       RUI.ContentSwitch with no dependency on Prototype and no cusotmisation
//       options.
RUI.Standalone.ContentSwitch = RUI.Class.create({ defaultOptions: {
    disableOnShow  : 'false',
    displayStyle   : 'block',
    triggerClass   : 'trigger',
    disabledClass  : 'disabled',
    contractedClass: 'contracted',
    expandedClass  : 'expanded'
}});

RUI.Standalone.ContentSwitch.prototype = {
    //method initialize
    //desc   Constructor.
    //param  [req] trigger [String] or [HTMLElement] trigger anchor ID
    //       or object
    //param  [req] content [String] or [HTMLElement] Content ID or object.
    //       This is not required if the content ID is embedded in the trigger
    //       href as an anchor.
    //param  [opt] disableOnShow: [Boolean] true if the trigger should
    //       be disabled once the content has been shown.
    //param  [opt] displayStyle: [String] CSS display property that the content
    //       should be when displayed.
    //param  [opt] callback: [function] callback to be triggered on show/hide
    initialize: function (trigger, content, options) {
        this.trigger = RUI.DOM.getElement(trigger);
        this.content = RUI.DOM.getElement(content);
        this._setOptions(options);

        this.handler = RUI.Function.bind(function (e) {
            this.toggle(e);
            RUI.Event.stop(e);
        }, this, true);

        this.disabledHandler = RUI.Function.bind(function (e) {
            RUI.Event.stop(e);
        }, this, true);

        RUI.Event.observe(this.trigger, 'click', this.handler);
    },

    //method enable
    //desc   Re-enables a previously disabled trigger.
    enable: function () {
        RUI.Event.stopObserving(this.trigger, 'click', this.disabledHandler);
        RUI.DOM.removeClassName(this.trigger, this.options.disabledClass);
        RUI.Event.observe(this.trigger, 'click', this.handler);
    },

    //method disable
    //desc   Disables the trigger.
    disable: function () {
        RUI.DOM.addClassName(this.trigger, this.options.disabledClass);
        RUI.Event.stopObserving(this.trigger, 'click', this.handler);
        RUI.Event.observe(this.trigger, 'click', this.disabledHandler);
    },

    //method show
    //desc   Shows the content.
    //param  [opt] event [Event]
    show: function (e) {
        RUI.DOM.removeClassName(this.trigger, this.options.expandedClass);
        if (this.options.disableOnShow) {
            this.disable();
        }
        else {
            RUI.DOM.addClassName(this.trigger, this.options.contractedClass);
        }
        this.content.style.display = this.options.displayStyle;
        this.invokeCallback();
    },

    //method hide
    //desc   Hides the content.
    //param  [opt] event [Event]
    hide: function (e) {
        this.hideWithoutCallback(e);
        this.invokeCallback();
    },

    //method hideWithoutCallback
    //desc   Hides the content and explicitly does not call the callback.
    //param  [opt] event [Event]
    hideWithoutCallback: function (e) {
        // Don't hide disabled triggers
        if (this.trigger.className.match(this.options.disabledClass)) {
            return;
        }
        RUI.DOM.removeClassName(this.trigger, this.options.contractedClass);
        RUI.DOM.addClassName(this.trigger, this.options.expandedClass);
        RUI.DOM.hide(this.content);
    },

    //method toggle
    //desc   Toggles the display of the content.
    //param  [opt] event [Event]
    toggle: function (e) {
        if (RUI.DOM.isDisplayed(this.content)) {
            this.hide();
        }
        else {
            this.show();
        }
    },

    //method getContent
    //desc   Returns the content element.
    //return [HTMLElement] content
    getContent: function () {
        return this.content;
    },

    //method getTrigger
    //desc   Returns the trigger element.
    //return [HTMLElement] trigger
    getTrigger: function () {
        return this.trigger;
    },

    //method invokeCallback
    //desc   Calls the callback function.
    invokeCallback: function () {
        if (this.options.callback) {
            this.options.callback.call(this);
        }
    }
};

//class  RUI.Standalone.TabbedPane
//desc   A tabbed content pane
//note   This is a cut-down version of the reference implementation of
//       RUI.TabbedPane with no dependency on Prototype and fewer
//       cusotmisation options.
RUI.Standalone.TabbedPane = RUI.Class.create({ defaultOptions: {
    activeClass: 'active'
}});

RUI.Standalone.TabbedPane.prototype = {
    //method initialize
    //desc   Constructor.
    //param  [req] navigation [String or HTMLElement] navigation ID or
    //       object.  All anchors within this element are treated as
    //       triggers for the tabs and there anchor names (anything after
    //       the last '#' are used as the id for the content.
    //param  [opt] activeClass: [String] class name for active tab
    //note   Each tab anchor should be treated as if it were the argument
    //       to an instance of RUI.ContentSwitch.
    initialize: function (navigation, options) {
        this._setOptions(options);

        navigation = RUI.DOM.getElement(navigation);

        var tabAnchors = RUI.DOM.getElement(
            navigation
        ).getElementsByTagName('a');

        var contentSwitches = [];
        var that = this;

        var tabAnchor, split, anchorName, content, contentSwitch;

        for (var i = 0; i < tabAnchors.length; i++) {
            tabAnchor = tabAnchors[i];
            split = tabAnchor.href.split('#');
            anchorName = split[split.length - 1]; // The '#' anchor name
            content = RUI.DOM.getElement(anchorName);
            contentSwitches.push(new RUI.ContentSwitch(tabAnchor, content, {
                disableOnShow: true,
                // Disabled RUI.ContentSwitch is equivalent to active tab.
                // i.e. you can't click on the current tab.
                disabledClass: this.options.activeClass,
                expandedClass: '',
                callback     : function () {
                    // Close all other tabs.  Also enable these tabs so they
                    // can be clicked on again.
                    for (var j = 0; j < contentSwitches.length; j++) {
                        contentSwitch = contentSwitches[j];
                        if (contentSwitch !== this) {
                            contentSwitch.enable();
                            // Hide without callback to prevent recursion
                            contentSwitch.hideWithoutCallback();
                        }
                        else {
                            that.activeTab = this;

                            if (that.options.callback) {
                                that.options.callback.call(that);
                            }
                        }
                    }
                }
            }));
        }
    },

    //method getActiveTab
    //desc   Returns the anchor for the active tab.
    //return [HTMLElement] active tab anchor
    getActiveTab: function () {
        return this.activeTab;
    }
};

//method RUI.PFC.Partner.QuickSearchFormController
//desc   Set up controller for embedded search form interface.
//return [undefined]
RUI.PFC.Partner.QuickSearchFormController = function () {
    var ID = {
        LOCATION_INPUT_BUY : 'pfcQuickSearchTextBuy',
        LOCATION_INPUT_RENT: 'pfcQuickSearchTextRent',
        NAVIGATION         : 'pfcBannerLinks'
    };

    // Clearing input fields
    (new RUI.ClearingField(ID.LOCATION_INPUT_BUY));
    (new RUI.ClearingField(ID.LOCATION_INPUT_RENT));

    // Tabbed search panels
    (new RUI.TabbedPane(ID.NAVIGATION, {
        activeClass: 'pfcActive',
        callback   : function () {
            RUI.DOM.getElement(
                ID.NAVIGATION
            ).className = this.getActiveTab().getTrigger().id;
        }
    }));

    // Form validation for buy/rent
};

// Use RUI.Standalone in place of the RUI namespace to break the dependency on
// Prototype.
RUI.Standalone.use();

/* Prevent memory leaks in IE */
if (navigator.appVersion.match(/\bMSIE\b/)) {
    RUI.Event.observe(
        window,
        'unload',
        RUI.Event.unloadCache,
        false
    );
}
