﻿/**
 * PLYutils
 * 
 * Contains PLY authored JS utility methods to power client-side features.
 *
 * @author: PLY Interactive
 * @dependency: jquery.js
 **/
    
/**
 * jQuery Extensions
 **/
	jQuery.fn.extend({

    /**
     * jQuery.isVisible()
     *
     * jQuery Extension.  Checks for whether the given element is visible.  The reason for this extension is that it also
     * checks parent elements -- so if a parent element is hidden, the element correctly identifies itself as hidden as well.
     *
     * @param Bool Optional If false, parent visibility is ignored.
     *
     * @returns Bool True if visible, false if not visible.
     **/
		isVisible : function(includeParents) {
			// Visibility is assumed true unless proven false
			var _isVisible = true;
			
			if (typeof includeParents != "boolean") {
				includeParents = false;
			}
			if (this.css('display') == "none" || this.css('visibility') == "hidden") {
				_isVisible = false;
			} else if (includeParents) {
				var _parentCollection = this.parents();
				var i = 0;
				while(i < _parentCollection.length && _isVisible == true) {
					var _$parentElement = $(_parentCollection[i]);
					if(_$parentElement.css('display') == "none" || _$parentElement.css('visibility') == "hidden") {
						_isVisible = false;
					}
					i++;
				}
			}
			
			return _isVisible;
		},
    /**
     * jQuery.findElementsInOrder()
     *
     * jQuery Extension.  Finds all form elements in a container
     *
     * @param String Required element selector.
     * @param String Optional number of elements to return.
     **/
        findElementsInOrder : function(elementSelector, ignoreHiddenElements, elementCount) {
			var _matchCount = 0;
	        var _formElementCollection = $(this).find("*").filter(function(index) {
				var _isMatch = ($(this).is(elementSelector) && (ignoreHiddenElements || $(this).isVisible(true)));
				if (typeof elementCount == "number" && _matchCount >= elementCount)
					_isMatch = false;
				if (_isMatch)
					_matchCount++;

	            return _isMatch;
	        });

	        //
	        return _formElementCollection;
		},
    /**
     * jQuery.focusFirstElement()
     *
     * jQuery Extension.  Focuses the first field in a container
     *
     * @param {Bool}	Optional If false, text in input fields isn't selected after the field receives focus.  Defaults to True.
     * @param {String}	Optional The CSS Selector to be used to prevent an element from being selected by default.  If the element
	 *							 satisfies the specified selector, then focus will not be placed upon that element.  If not specified,
	 *							 this defaults to the CSS class '.noAutoFocus'.
     **/
        focusFirstElement : function(selectText, strExcludeSelector) {
			// Parameter Defaults
			if (typeof selectText != 'boolean') {
				selectText = true;
			}
			if (typeof strExcludeSelector != 'string') {
				strExcludeSelector = '.noAutoFocus';
			}
			// Find the element we're looking for
	        var i = 0;
	        var _formElementCollection = $(this).findElementsInOrder("input:not(:button, :disabled, :hidden), textarea, select", false);
	        var _$formElement = $(_formElementCollection[i]);
			// Get the first visible element from the found collection.  Unfortunately the :hidden pseudo-selector does not properly handle elements 
			// which are invisible due to a parent's visibility, making it necessary to reiterate the test visibility using our custom jQuery extention.
	        while (i < _formElementCollection.length && !_$formElement.isVisible(true)) {
	            _$formElement = $(_formElementCollection[i]);
	            i++;
	        }
			// Before doing anything, verify that the element isn't something we're specifically supposed to not focus on.
			var _isExcludedFromAutoFocus = _$formElement.is(strExcludeSelector);
			if (!_isExcludedFromAutoFocus) { 	     
				// Set focus on first element 
				// When using flash with the window mode as transparent, it steals focus in IE6. Accordingly we have to handle IE6 specially.
				var _isIE6 = ( $.browser.msie && parseInt($.browser.version, 10) < 7 );
				// The bug only occurs when the flash is embedded / dynamically placed by sIFR. Accordingly, we only need to do the fix if the sIFR 
				// replacment hasn't occured yet.
				var _sIFRReplacementHasOccured = ( $('.sIFR-replaced').length > 0 );  
				if (!_isIE6 || (_isIE6 && _sIFRReplacementHasOccured)) {
					_$formElement.focus();
				} else {
					// To prevent the SWF from stealing focus, we set a single blur handler to return focus to the element that we're trying to focus on.
					_$formElement.one('blur', function() {
						_$formElement.focus();
					});
					_$formElement.focus();
				}
				if (selectText) {
					_$formElement.select();
				}
			}
		},
    /**
     * jQuery.submitOnEnter()
     *
     * jQuery Extension.  Finds all form elements in a container and submits the form when the enter key is pressed
     *
     * @param object settings
     *     elementSelector --Default is to apply submit on enter to all form elements within the matched element.
     **/
        submitOnEnter : function(newSettings) {
            var _$match = $(this);
            var _defaults = {
                'elementSelector'   : 'input, textarea, select',
                'targetElement'     : $(document).find("form"),
                'action'            : 'submit'
            };

            var _settings = $.extend({}, _defaults, newSettings);

         // Disable default Safari enter key press behavior
			//$(document).keypress(function (event) {
			//	if(event.keyCode == 13) {
			//		return false;
			//	}
			//});
		 // Set up keyup event for matched elements
            _$match.find(_settings.elementSelector).each(function(index) {
                $(this).keyup(function(event) {
                    if(event.which == 13) {
                        _settings.targetElement.trigger(_settings.action);
                    }
                });
            });
		},
    /**
     * jQuery.tabButtonOverride()
     *
     * jQuery Extension.  Overrides the tab button on Matched element(s) to tab to a specific field
     *
     * @param jQuery Required container of which to focus the first found form element
     **/
        tabButtonOverride : function($targetContainer) {    
            var _$match = $(this);
            
            if (typeof $targetContainer == "string")
                $targetContainer = $($targetContainer);
                
            _$match.each(function(index) {
                var _targetElement = $targetContainer.findElementsInOrder("input:not(:button, :disabled, :hidden), textarea, select")[0];
                $(this).keydown(function(event) {
                    if(event.keyCode == 9) {
                        _targetElement.focus();
                        _targetElement.select();
                        return false;
                    }
                });
            });
		}

	});
	
   /**
	* PLY Namespacing
	**/
    if(typeof PLY != 'object') PLY = {};
    if(!PLY.Utilities) PLY.Utilities = {};
    if(!PLY.Utilities.UpdatePanelRequestManager) PLY.Utilities.UpdatePanelRequestManager = {};
    if(!PLY.Utilities.UpdatePanelRequestManager.UpdatePanel) PLY.Utilities.UpdatePanelRequestManager.UpdatePanel = {};
 
   /**
	* Toggle open/close an expandable div
	*
	* @param {jQuery Object}  	Required $title		
	* @param {jQuery Object} 	Required $content	
	**/
	PLY.Utilities.ToggleDisplay = function($title, $content) {
		$title.toggleClass('ed');
		$content.animate({ height: 'toggle' }, 200, 'swing');
	};
	
   /**
	* Launches a Browser Window / Popup for the specified URL and with the specified
	* settings.
	* 
	* @param {String} 		 	Required url 		The URL to open in the popup window.
	* @param {Hash / Object} 	Optional settings	The settings for the resulting popup, review the defaults hash for acceptable options and the preset defaults.
	**/
	PLY.Utilities.BrowserWindow = function(url, settings) {
		var _this = this;

		this.url = url;
		// Defaults not yet built out. Preserves the ability to do so, though.
		this.defaults = {
			'scrollbars': '1',
			'toolbar': '0',
			'menubar': '0',
			'status': '0',
			'resizable': '1',
			'location': '0',
			'personalbar': '0',
			'titlebar': '0'
		}
		this.settings = $.extend(this.defaults, settings);

		/**
		* MSIE browsers add a scrollbar at all times that is 19px wide, accordingly
		* modify the width setting to be an additional 19 pixels.
		**/
		if ($.browser.msie && typeof this.settings.width != undefined) {
			this.settings.width = parseInt(this.settings.width, 10) + 19 + 'px';
		}

		this.windowObjectInstance;

		this.launch = function() {
			var _strSettings = this._getSettingsString();
			this.windowObjectInstance = window.open(this.url, '_new', _strSettings);
		}

		this.close = function() {
			if (typeof this.windowObjectInstance.close == 'function') {
				this.windowObjectInstance.close();
			}
		}

		this._getSettingsString = function() {
			var _strSettings = '';
			$.each(this.settings, function(key, value) {
				if (_strSettings != '') {
					_strSettings += ',';
				}
				_strSettings += key + "=" + value;
			});
			return _strSettings;
		}
	}
	
   /**
    * PLY Update Panel Request Manager
	*
	* Used for managing client-side callback functions
	* associated with the use of .NET update panels.
	**/

   /**
    * Returns the current instance of the UpdatePanelRequestManager
    * or instantiates a new instance of the class.
    *
    * @returns {Object} The current instances of the UpdatePanelRequestManager.
    **/
    PLY.Utilities.UpdatePanelRequestManager.getInstance = function() {
        if(!PLY.Utilities.UpdatePanelRequestManager._Instance)
            PLY.Utilities.UpdatePanelRequestManager._Instance = new PLY.Utilities.UpdatePanelRequestManager.Class();
        return PLY.Utilities.UpdatePanelRequestManager._Instance;
    }

   /**
    * UpdatePanelRequestManager Class definition.
    *
    * This should be used as a singleton.  If there are multiple
    * instances it is both harmful for performance and it prevents
    * the class from working correctly.
    **/
    PLY.Utilities.UpdatePanelRequestManager.Class = function() {
        var _this = this;
        this._updatePanels = [];
        this._pageRequestManager = Sys.WebForms.PageRequestManager.getInstance();
        this.arrRequestData = [];

        /**
         * Setup callbacks to be executed when a Asynchronous post occurs which
         * is handled by the .NET PageRequestManager and determined to be relevant.
         *
         * Relevance is determined using the post back trigger elements -- if the trigger
         * for the Asynchornous post matches that specified when calling this function, then
         * the specified callback is executed.
         *
         * @param {Object} settings The settings object / hash.  Acceptable options:
         *      - onBeginRequest {Function} Callback function to execute when a relevant request begins.
         *      - onEndRequest {Function} Callback function to execute when a relevant request ends (after content has been updated and redrawn).
         *      - onError {Function} Callback function to execute in the event of a related error.
         *      - asyncTriggerElementCssSelectors {Array} Collection of trigger css selectors that are used to determine if the associated callbacks are "relevant"
         *                                                and should be executed.
         *
         * @returns {void}
         **/
        this.setupAsyncEvents = function(settings) {
            // Defaults
            var _defaults = {
                'onBeginRequest'                   : '',
                'onEndRequest'                     : '',
                'onError'                          : '',
                'asyncTriggerElementCssSelectors'  : []
            }
            var _settings = $.extend({}, _defaults, settings);
            // Ensure the trigger collection is an array
            if(typeof _settings.asyncTriggerElementCssSelectors  == "string")
                _settings.asyncTriggerElementCssSelectors   = [ _settings.asyncTriggerElementCssSelectors ];
            // Add the "panel" which we setup here to the collection.
            var updatePanel = _this._addUpdatePanel(
                new PLY.Utilities.UpdatePanelRequestManager.UpdatePanel(
					_settings.onBeginRequest,
					_settings.onEndRequest,
					_settings.onError,
					_settings.asyncTriggerElementCssSelectors
				)
            );
            return updatePanel;
        }

        /**
         * Private function.  Adds the specified update panel to the
         * current panel collection
         *
         * @param updatePanel {Object} The panel to be added to the collection.
         *
         * @returns {Boolean} True or false depending upon whether the panel was successfully added to the collection.
         **/
        this._addUpdatePanel = function(updatePanel) {
            if(typeof updatePanel == 'object') {
                _this._updatePanels.push(updatePanel);
                return updatePanel;
            }
            return null;
        }

        /**
         * Private function.   Iterates through the current collection of panels and checks for relevance using
         * the specified trigger element ID.   If relevance is determined, execute the provided callback.
         *
         * @param triggerElementID {String} The ID of the trigger element used to determine relevance.
         * @param callback {Function} The callback function to execute if relevance is determined.
         *
         * @returns {void}
         **/
        this._getRelatedUpdatePanelsAndExecuteCallback = function(triggerElementID, callback) {
            if(typeof triggerElementID != 'string') triggerElementID = '';
            var _curUpdatePanel;
            for(var i = 0; i < _this._updatePanels.length; i++) {
                _curUpdatePanel = _this._updatePanels[i];
                if(_curUpdatePanel.isRelatedTriggerElement(triggerElementID) && (typeof callback == 'function')) {
                    callback(_curUpdatePanel);
                }
            }
        }

        /**
         * Private function.  Sets up two event handlers attached to all PageRequestManager
         * asynchronous posts.   With each Asynchronous post, checks the current panel collection for
         * relevance and executes callbacks as is appropriate.
         *
         * @returns {void}
         **/
        this._setupRequestHandlers = function() {
            var _triggerElementID;
            with(_this._pageRequestManager) {
                add_beginRequest(function(sender, args) {
                    if(args) _triggerElementID = $(args.get_postBackElement()).attr('id');
                    if(typeof _triggerElementID == 'string') {
                        _this._getRelatedUpdatePanelsAndExecuteCallback(
                            _triggerElementID,
                            function(updatePanel) {
                                if(typeof updatePanel.onBeginRequest == 'function')
                                    updatePanel.onBeginRequest(sender, args);
                            }
                        );
                    }
                });
                add_endRequest(function(sender, args) {
                    if(typeof _triggerElementID == 'string') {
                        if(args.get_error() != null) {
                            _this._getRelatedUpdatePanelsAndExecuteCallback(
                                _triggerElementID,
                                function(updatePanel) {
                                    if(typeof updatePanel.onError == "function")
                                        updatePanel.onError(sender, args);
                                }
                            );
                        } else {
                            _this._getRelatedUpdatePanelsAndExecuteCallback(
                                _triggerElementID,
                                function(updatePanel) {
                                    if(typeof updatePanel.onEndRequest == 'function')
                                        updatePanel.onEndRequest(sender, args);
                                }
                            );
                        }
                    }
                });
            }
        }
        // Implicit setup of the event handlers at instantiation.
        this._setupRequestHandlers();
    }

   /**
    * UpdatePanel Class Definition.   Provides a common container used by the UpdatePanelRequestManager to store
    * callbacks, triggers, and other related information specific to a given update panel instance.
    *
    * @param onBeginRequest {Function} The callback function to execute when a relevant request begins.
    * @param onEndRequest {Function} The callback function to execute when a relevant request ends.
    * @param onError {Function} The callback function to execute when a relevant asynchronous error occurs.
    * @param asyncTriggerElementCssSelectors {Array} Collection of trigger element css selectors related to the Update Panel.
    *
    * @returns {Object} New UpdatePanel instance.
    **/
    PLY.Utilities.UpdatePanelRequestManager.UpdatePanel = function(onBeginRequest, onEndRequest, onError, asyncTriggerElementCssSelectors) {
        var _this = this;
        this._asyncTriggerElementIDs = [];
        this._asyncTriggerElementCssSelectors = (typeof asyncTriggerElementCssSelectors == 'object' ? asyncTriggerElementCssSelectors : []);

        /**
        * Initialize the UpdatePanel class instance by collecting appropriate
        * callback functins.
        **/
        this._init = function(onBeginRequest, onEndRequest, onError) {
            _this.onBeginRequest = this._getFunctionOrEmptyString(onBeginRequest);
            _this.onEndRequest = this._getFunctionOrEmptyString(onEndRequest);
            _this.onError = this._getFunctionOrEmptyString(onError);
            $.each(this._asyncTriggerElementCssSelectors, function(index, cssSelector) {
                var _$triggerElement = $(cssSelector);
                _$triggerElement.each(function(index, element) {
                    _this._asyncTriggerElementIDs.push($(element).attr('id'));
                });
            });
        }

        /**
        * Checks the passed in parameter for whether its a function.
        * Returns either that function, or an empty string.
        *
        * @param param {Mixed} The parameter to verify as a function.
        *
        * @returns {Mixed} Either the parameter if it is a function, or an empty string.
        **/
        this._getFunctionOrEmptyString = function(param) {
            return (typeof param == 'function' ? param : '');
        }

        /**
        * Returns whether the specified element ID is the ID of
        * one of the triggers associated with this update panel.
        *
        * @param elementID {String} The ID of the element to check.
        *
        * @returns {Boolean} True if the element ID is that of one of the panel's related triggers, false if not.
        **/
        this.isRelatedTriggerElement = function(elementID) {
            if (typeof elementID != "string") return false;
            return ($.inArray(elementID, _this._asyncTriggerElementIDs) >= 0);
        }

        /**
        * Adds a Trigger Element's ID to the collection.
        *
        * @param elementID {String} The ID of the element to add to the collection of Trigger Elements.
        *
        * @returns {Boolean} True or False depending upon whether the elementID was added to the Trigger Element Collection.
        **/
        this.addTriggerElement = function(elementID) {
            if (typeof elementID != 'string' || elementID == '') return false;
            _this._asyncTriggerElementIDs.push(elementID);
            return true;
        }

        // Implicit init upon instantiation
        this._init(onBeginRequest, onEndRequest, onError);
	}
    