﻿
if (typeof PLY != 'object') PLY = {};
if (!PLY.UI) PLY.UI = {};
if (!PLY.Utilities) PLY.Utilities = {};
if (!PLY.Utilities.UpdatePanelRequestManager) PLY.Utilities.UpdatePanelRequestManager = {};
if (!PLY.Utilities.UpdatePanelRequestManager.UpdatePanel) PLY.Utilities.UpdatePanelRequestManager.UpdatePanel = {};

/**
* 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;
				}
			});
		});
	}

});


/**
* 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');
};


/**
* GetQuerystringValue 
* 
* Utility method to grab values from the querystring.
* 
* @param String Required Querystring key
**/
PLY.Utilities.GetQuerystringValue = function(key) {

	var value = "";

	if (typeof window.location == 'object') {
		var queryStringItems = window.location.search.substr(1).split('&');
		for (var i = 0; i < queryStringItems.length; i++) {
			var item = queryStringItems[i].split('=');
			if (item[0].toUpperCase() == key.toUpperCase()) {
				value = item[1];
			}
		}
	}

	return value;
};

// 
PLY.Utilities.BuildQuerystringFromHash = function(settingHash) {

	var querystring = '';
	$.each(settingHash, function(key, value) {
		querystring += (querystring.length == 0 ? '?' : '&') + key + '=' + value;
	});

	return querystring;
};

/**
* SetCookie 
* 
* Utility method to set cookies.
* 
* @param String Required Cookie name
* @param String Required Cookie value
* @param Integer Required The number of days from today the cookie will expire (negative numbers are valid)
**/
PLY.Utilities.SetCookie = function(name, value, days) {

	var expirationDate = new Date();
	expirationDate.setDate(expirationDate.getDate() + days)

	document.cookie = [
			name, '=', value, ';',
			'expires=', expirationDate.toGMTString(), ';',
			'path=/'
		].join('');
};

/**
* 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);
}
    
