// wrap the window.console to prevent errors being thrown if the console isn't active or a browser doesn't have a console.
var Debug = Debug || {};

Debug.Log = function(message) {
	if (window.console && window.console.log) {
		window.console.log(message);
	}
};
Debug.Dir = function(message) {
	if (window.console && window.console.dir) {
		window.console.dir(message);
	}
};


var PHX = {
	BUILD: "3",
	VERSION: "2.1",
	init: function() {
		//creating one harness - once on dom ready
		var managers = new PHX.managers(), //constructor internally calls init
			Sys = window.Sys || {},
			requestManager = null;
		
		// set public property on PHX object to managerss
		this.harness = managers;

		// if within the context of asp.net ajax - attach to page request manager events
		// begin request - we will dispose of all registered manager which internally dispose of all the events they're managing
		// end request - we will dispose of all registered manager which internally dispose of all the events they're managing
		//	  and then we will recreate manager for harness and reattach events on new dom elements
		if(Sys.WebForms) { 
			requestManager = Sys.WebForms.PageRequestManager.getInstance();
			requestManager.add_pageLoaded(function() { managers.init(); });
			requestManager.add_initializeRequest(function() { managers.dispose(); });
			this.requestManager = requestManager;
		}
	},
	getContentWrapper: function() {
		var $contentWrapper = $('#ContentWrapper');
		
		return ($contentWrapper);
	},
	getMainNavigation: function() {
		var $navigation = $('#Nav');
		
		return ($navigation);
	},
	getPageForm: function() {
		var $pageForm = $('#PageForm');
		
		return ($pageForm);
	},
	getSidebar: function() {
		var $sidebar = $('#Sidebar');
		
		return ($sidebar);
	},
	getRequestManager: function() {
		return (this.requestManager);
	}
}; 
/* 
 * Anchor Manager
 * 
 * Navigation management 
 * This handles when user clicks away while
 * editing an entity
 */
PHX.managers = function() { 
	this.managerHash = {};
	this.init();
}; 
PHX.managers.prototype = function() {
	// cache local reference to PHX object
	var phx = PHX;
	
	// reset constructor
	phx.managers.prototype.constructor = phx.managers;
	
	//Private methods
	
	//Public prototype object
	return {
		init: function(sender, args) {
			//create new manager
			var self = this,
				vw = new phx.managers.view(),
				b = new phx.managers.button(),
				t = new phx.managers.tab(),
				//e = new phx.managers.element(),
				tt = new phx.managers.tooltip(),
				i = new phx.managers.input(),
				bm = new phx.managers.buttonmenu(),
				n = new phx.managers.navigation(),
				cc = new phx.managers.clickconfirm(),
				tv = new phx.managers.treeview(),
				ac = new phx.managers.autocomplete(),
				f = new phx.managers.frame(),
				sg = new phx.managers.sortablegrid(),
				v = new phx.managers.validation(),
				ed = new phx.managers.editor(),
				im = new phx.managers.immediatemodal(),
				mmf = new phx.managers.modalfactory(),
				mm = mmf.create(),
				mmHash = {};
			
			// call loading status to dismiss the loading indicator
			this._showLoading(false);

			//manager constructors call init internally
			this._addManagers({
				"view" : vw,
				"button" : b,
				"input" : i,
				"buttonmenu" : bm,
				"tooltip" : tt,
				"tab" : t,
				"navigation" : n,
				//"element" : e,
				"clickconfirm" : cc,
				"treeview" : tv,
				"autocomplete" : ac,
				"iframes" : f,
				"sortableGrid" : sg,
				"editor" : ed,
				"immediateModal" : im,
				"validation" : v
			});
			
			// add the deferred modal managers to an array
			$.each(mm, function(i, m) {
				mmHash["modal-" + i] = m;
			});
			
			// add the deferred modal manager array to the list of managers
			this._addManagers(mmHash);
			
			// attach the postback event handlers to be fired whenever a postback occurs
			this._attachPostBackEventHandlers();
			
		}, 
		dispose: function(args, sender) {
			// call loading status to display the loading indicator			
			this._showLoading(true);

			//iterate through manager - call dispose on each
			this._batchManagers(function(managers) {
				managers.dispose();
			});
			
			// remove all the managers from the manager hash
			this._removeManagers();
		}, 
		managers: function(key) { 
			return this.managerHash[key];
		},
		_attachPostBackEventHandlers: function() {
			var self = this,
				elems = self._getPostbackElems();
			
			elems.bind('focus.postbacks', function() {
				self._batchManagers(function(managers) {
					managers.preDispose();
				});
			});
		},
		_getPostbackElems: function() {
			var $postbackButtons,
				$postbackLinks,
				$postbackElems;
				
			// get all buttons that are not menu buttons
			$postbackButtons = $(":button:not('.menu-button')");
			
			// get all links but filter out links that are either external, jump links or a link to another window
			$postbackLinks = $("a").filter(function(index) {
					var $this = $(this),
						elHref = $this.attr("href"),
						elTarget = $this.attr("target");
						
					if(elHref && elHref.length) {
						return !(elHref.indexOf('#') >= 0 || (elTarget && elTarget.length && elTarget !== '_self'))
					}
				});
			
			//$postbackElems = $postbackButtons;
			$postbackElems = $postbackButtons.add($postbackLinks);
			
			return ($postbackElems);
		},
		_addManagers: function(map) {
			var self = this;
			$.each(map, function(key, manager){
				self.managerHash[key] = manager;
				manager.harness = self; //set reference back to harness on manager
			});
		},
		_batchManagers: function(fn) {
			var self = this;
			$.each(this.managerHash, function(key, manager) {
				fn.call(self, manager);
			});
		},
		_removeManagers: function() {
			var self = this;
			$.each(this.managerHash, function(key, manager) {
				delete self.managerHash[key];
			});
		},
		_showLoading: function(status) {
			var loadingIndicator = $("#loading")
			,	pos = {
					sTop : function() {
						return window.pageYOffset
						|| document.documentElement && document.documentElement.scrollTop
						||  document.body.scrollTop;
					},
					wHeight : function() {
						return window.innerHeight
						|| document.documentElement && document.documentElement.clientHeight
						|| document.body.clientHeight;
					}
				}
			,	elHeight = loadingIndicator.height()
			,	elTop = pos.sTop() + (pos.wHeight() / 2) - (elHeight / 2)
			,	placeHolder = $("#HarnessPlaceHolder");

			loadingIndicator.css({
				top: elTop,
				left: status == true ? (($(window).width() - loadingIndicator.outerWidth()) / 2) + "px" : "-9999px",
				position: "absolute"
			});
		}
	}; 
}();
	/* 
 * Anchor Manager
 * 
 * Navigation management 
 * This handles when user clicks away while
 * editing an entity
 */
PHX.managers.view = function() {
	this.init();
};
PHX.managers.view.prototype = function() {
	// cache local copy of PHX object
	var phx = PHX;
	
	// rest constructor
	phx.managers.view.prototype.constructor = phx.managers.view;

	//Private methods 	
	return {
		init: function() {
			// local var to store menu container
			var $dataListing = $("#HarnessPlaceHolder > .data-listing"),
				$tableGrid = $('.grid');
			
			// apply the corner and shadow classes to the main data listing
			if($dataListing.length) {
				if($dataListing.html().length) {
					$dataListing.addClass("list-view-data-listing ui-corner-all shadow-light");
				}
			}
			
			if($tableGrid.length) {
				$tableGrid.find("tbody tr:odd").addClass("stripe");
			}
		},
		preDispose: function() {
		
		},
		dispose: function() {
			//event detachment
		}
	};
}();
/* 
 * Anchor Manager
 * 
 * Navigation management 
 * This handles when user clicks away while
 * editing an entity
 */
PHX.managers.navigation = function() {
	this.init();
};
PHX.managers.navigation.prototype = function() {
	// cache local copy of PHX object
	var phx = PHX;
	
	// reset constructor
	phx.managers.navigation.prototype.constructor = phx.managers.navigation;
	
	//Private methods 	
	return {
		init: function() {
			var sidebar = phx.getSidebar(),
				sidebarMenu = sidebar.children('#SidebarMenu');

			// call function to make the menu into an accordian menu
			this.makeAccordian(sidebarMenu);
		},
		makeAccordian: function(menu) {
			var	$links = menu.children('li.has-submenu').children('a');
		
			if(!menu.hasClass('accordian')) {
				$links
				.prepend('<span class="ui-icon ui-icon-circle-plus"></span>')
				.end()
				.filter('.active-menu')
				.find('.ui-icon')
				.toggleClass('ui-icon-circle-plus ui-icon-circle-minus')
				.end()
				.parent()					
				.find('.ui-icon')
				.bind('click', function(e) {
					e.preventDefault();
					$(e.target)
						.toggleClass('ui-icon-circle-minus ui-icon-circle-plus')
						.closest('li')
						.children('ul')
						.slideToggle();
				})
			}
			
			menu.addClass('accordian');
		},
		preDispose: function() {
		
		},
		dispose: function() {
			var sidebar = phx.getSidebar();
			
			sidebar.find('.ui-icon').unbind('click');
		}
	};
}();

PHX.managers.buttonmenu = function() {
	this.init();
};
PHX.managers.buttonmenu.prototype = function(){
	// cache local copy of PHX object
	var phx = PHX;
	
	phx.managers.buttonmenu.prototype.constructor = phx.managers.buttonmenu;
	
	return {
		init: function() {
			// store local reference to the buttons and the corresponding menus
			var $buttons = $('.menu-button'),
				$menus = $('.menu-button-menu');
			
			// only create the button menus if button menu elements are on the page
			if($buttons.length) {
				// hide all the menus on initial page load
				$menus.hide();
				
				// bind a click event to the document so that any open menus hide when the user clicks away from the menu
				$(window.document).bind('click', function() {
					if($menus.is(':visible')) {
						$menus.stop().slideUp(100);
					}
				});
				
				// attach event handler to each button
				$buttons.click(function(e) {
					var $this = $(this), // 'this' is the button that is clicked
						position = "", // will store the position of the clicked button
						height = "", // will store the height of the button
						$menu = $this.next(); // get the next element after the button, which should be the <div> that contains the menu
					
					// prevent the default button click action and do not allow the click event to propagate up to the document
					e.preventDefault();
					e.stopPropagation();
					
					// get the position of the button
					position = $this.position();
					
					// get the height of the button
					height = $this.outerHeight();
					
					// handle the hidden menu
					if($menu.is(':hidden')) {
						// first hide all open menus
						$menus.hide();
						
						// add the necessary css to position the menu relative to the button and slide the menu down
						$menu.css({
							top: position.top + height + "px",
							left: position.left + "px",
							width: 160 + "px",
							"z-index": 99999
						}).addClass('ui-corner-all')
							.stop()
							.slideDown(100);
					} else {
						// the menu is exposed so lets hide it
						$menu.stop().slideUp(100);
					}
				});
			}
		},
		preDispose: function() {
		
		},
		dispose: function() {
			// avoid multiple events bound to the document by unbinding here
			$(window.document).unbind('click');
		}
	};
}();

/*
 * Modal Manager Factory
 * 
 * Factory for creating modal manager
 * 
 */
PHX.managers.modalfactory = function() { };
PHX.managers.modalfactory.prototype = function() {
	// cache local copy of PHX object
	var phx = PHX;
	
	// reset constructor
	phx.managers.modalfactory.prototype.constructor = phx.managers.modalfactory;
	// private functions
	// public methods
	return {
		create: function() {
			var modalManagers = []; 
			$('.detail-view')
				.find('.Phoenix-Detail-Owned')
				.each(function() {
					modalManagers.push(new PHX.managers.deferredmodal(this)); 
				});
			
			return modalManagers;
		}
	};
}();
/*
 * Modal Manager
 * 
 * Object to contain modal elements on the page
 * 
 */
PHX.managers.deferredmodal = function(owned) {
	this.owned = owned;
	this.elementHash = {};
	this.init();
};
PHX.managers.deferredmodal.prototype = function() {
	// cache local copy of PHX object
	var phx = PHX;
	
	// reset constructor
	phx.managers.deferredmodal.prototype.constructor = phx.managers.deferredmodal;
	
	//Private methods  
	function openModal(modalIndex) {  
		var contentWrapper = phx.getContentWrapper(),
			modal = this.indexedModals[modalIndex],
			$modal = $(modal),
			$modalTabs = $modal.children('.tab-control'),
			$modalTitle = $modal.children('.modal-title').hide().text();

			$modal.dialog({
				autoOpen: true,
				bgiframe: true,
				closeOnEscape: false,
				modal: true,
				resizable: false,
				stack: true,
				title: $modalTitle,
				width: 730,
				open: function() {
					// hide the top right corner close button
					// until I wire up the click event to do the
					// same thing as the modal cancel button
					$(".ui-dialog-titlebar-close").hide();

					if($modalTabs.length) {
						$modalTabs.tabs();
					}
				},
				close: function() {
					$(this).dialog('destroy');
				}
			});
		
		// append the modal to the page wrapper (update panel)
		// otherwise, postbacks from the modal will not be async and
		// the page render after the postback will be broken
		$modal.parent().appendTo(contentWrapper);
		
		// find the first textbox and focus it
		$modal.find(':input').eq(0).focus();
	};

	//private events
	function onAddClick(e) {  
		var target = e.target,
			modalIndex = 0;
		
		// we only want to open a modal if the item clicked is the add new item button
		// otherwise, we want whatever was clicked to perform its normal action
		if($(target).hasClass("add")) {
			e.preventDefault(); // prevent the default click action
			modalIndex = 0; //first indexed modal is add modal
			openModal.call(this, modalIndex); // call the open modal function to open the modal
		}
	};
	function onEditClick(e) { 		
		var target = e.target,
			modalIndex = 0;
		
		// we only want to open a modal if the item clicked is the link to edit the item
		// otherwise, we want whatever was clicked to perform its normal action
		if($(target).hasClass("action-edit")) {
			e.preventDefault();// prevent the default click action
			modalIndex = this.editLinkIndexes[target.id] + 1; // edit modals need to be incremented by 1 due to add modal in 0 spot
			openModal.call(this, modalIndex); // call the open modal function to open the modal
		}
	};
	
	return {
		dataListing: function() {
			if(!this.elementHash["dataListing"]) { this.elementHash["dataListing"] = $(this.owned).find('.data-listing'); }
			return this.elementHash["dataListing"];
		},
		add: function() { 
			if(!this.elementHash["add"]) { this.elementHash["add"] = $(this.owned).find(':button.add'); }
			return this.elementHash["add"];
		}, 
		editLinks: function() {
			if(!this.elementHash["editLinks"]) { this.elementHash["editLinks"] = this.dataListing().find('.action-edit'); }
			return this.elementHash["editLinks"];
		},
		modals: function() {
			if(!this.elementHash["modals"]) { this.elementHash["modals"] = $(this.owned).find('.detail-modal'); }
			return this.elementHash["modals"];
		},
	
		init: function() { 
			// handle edit links
			var editLinkIndexes = [],
				indexedModals = [];
				
			this.editLinks().each(function(i) {
				editLinkIndexes[this.id] = i;
			});
			
			this.editLinkIndexes = editLinkIndexes;

			// push each modal instance on to the array, then hide the modal contents
			this.modals().each(function(i, el) {
				var $modal = $(el);

				indexedModals.push(el);
				// hide the modal content so it doesn't appear on the page when the page loads
				$modal.hide();
			});

			this.indexedModals = indexedModals;  

			// wire-up events 
			this.add().attr('onclick', '').bind('click', $.proxy(onAddClick, this));
			this.dataListing().bind('click', $.proxy(onEditClick, this)); 
		},
		preDispose: function() {
			// nothign to predispose
		},
		dispose: function() {
			// unbind any events
			this.add().unbind('click', $.proxy(onAddClick, this));
			this.dataListing().unbind('click', $.proxy(onEditClick, this));
			
			// remove any owned modals that are open
			this.modals().remove();
		}
	};

}();
/* 
 * Anchor Manager
 * 
 * Navigation management 
 * This handles when user clicks away while
 * editing an entity
 */
PHX.managers.immediatemodal = function() {
	this.init();
};
PHX.managers.immediatemodal.prototype = function() {
	// cache local copy of PHX object
	var phx = PHX;
	
	// reset constructor
	phx.managers.immediatemodal.prototype.constructor = phx.managers.immediatemodal;
	
	// private methods
	function showImmediateModal(modal) {
		var contentWrapper = phx.getContentWrapper(),
			$modal = $(modal),
			modalTitle = $modal.children('.modal-title').remove().text(),
			$modalTabs = $modal.children('.tab-control');

		$modal.dialog({
				autoOpen: true,
				bgiframe: true,
				closeOnEscape: false,
				modal: true,
				resizable: false,
				title: modalTitle,
				width: 730,
				open: function() {
					// hide the top right corner close button
					// until I wire up the click event to do the
					// same thing as the modal cancel button
					$(".ui-dialog-titlebar-close").hide();
					
					if($modalTabs.length) {
						$modalTabs.tabs();
					}
				},
				close: function() {
					$(this).dialog('destroy');
				}
			});
		// append the modal to the page wrapper (update panel)
		// otherwise, postbacks from the modal will not be async and
		// the page render after the postback will be broken
		$modal.parent().appendTo(contentWrapper);
	};
	
	return {
		init: function() {
			var self = this,
				immediateModal = self.getImmediateModal();
			
			if (immediateModal.length) {
				showImmediateModal.call(self, immediateModal);
			}
		},
		getImmediateModal: function() {
			var wrapper = phx.getContentWrapper(),
				modal = wrapper.find('.immediate-modal');
			
			return (modal);
		},
		preDispose: function() {
			// nothing to predispose
		},
		dispose: function() {
			var self = this,
				immediateModal = self.getImmediateModal();
			
			if (immediateModal.length) {
				immediateModal.remove();
			}
		}
	};
}();
/* 
 * Anchor Manager
 * 
 * Navigation management 
 * This handles when user clicks away while
 * editing an entity
 */
PHX.managers.tab = function() {
	this.init();
};
PHX.managers.tab.prototype = function() {
	// cache local copy of PHX object
	var phx = PHX,
		contentWrapper = phx.getContentWrapper();
	
	// reset constructor
	phx.managers.tab.prototype.constructor = phx.managers.tab;
	
	// private functions
	function getTabContainer() {
		return contentWrapper.find('.tab-control').eq(0);
	};
	function getSelectedTabHiddenField() {
		return getTabContainer().next("[id$='_SelectedTab']");
	};
	function getSelectedTabIndex () {
		return getSelectedTabHiddenField().val();
	};
	function setSelectedTabIndex(e, ui) {
		var storage = getSelectedTabHiddenField();
		storage.val(ui.index);
	};
	// public methods
	return {
		init: function() {
			var container = this.getTabContainer();

			if(container.length) {
				container.tabs({
					select: $.proxy(setSelectedTabIndex, this),
					selected: this.getSelectedTab()
				});
			}	
		},
		getTabContainer: function() {
			return contentWrapper.find('.tab-control').eq(0);
		},
		getSelectedTab: function() {
			return getSelectedTabIndex();
		},
		preDispose: function() {
		
		},
		dispose: function() {
			// dispose things here
		}
	};
}();
/* 
 * Treeview Manager
 * 
 * Treeview management 
 * This handles creating a populating treeviews
 * 
 */
PHX.managers.treeview = function() {
	this.init();
};
PHX.managers.treeview.prototype = function() {
	// store local copy of PHX object
	var phx = PHX;
		
	// reset constructor
	phx.managers.treeview.prototype.constructor = phx.managers.treeview;
	
	// public methods
	return {
		init: function() {
			var self = this,
				contentWrapper = phx.getContentWrapper(),
				$treeview = contentWrapper.find('.treeview');

			$treeview.each(function() {
				var $this = $(this),
					storedItems = $this.next(),
					allowMultiple = true;

				if($this.hasClass('single-select')) {
					allowMultiple = false;
				}
				
				$this.bind('loaded.jstree', function (e, data) {
					var currentItems = storedItems.val(),
						parentNode = null;
					
					// load the stored node selections and populate the checked items in the tree
					self.getSelectedNodes($this, currentItems);
					
					// check each checked node
					data.inst.get_checked().each(function() {
						//get the parent node of the checked node
						parentNode = data.inst._get_parent(this);
						
						// if the checked node has a parent, open the parent
						if(parentNode.length) {
							data.inst.open_node(parentNode);
						}
					});
				})
				.bind('check_node.jstree', function (e, data) { 
					// data.rslt.obj - the node
					var node = data.rslt.obj,
						nodeId = data.rslt.obj.attr("id"),
						currentItems = storedItems.val(),
						newNodes = "";
					
					if(!allowMultiple && currentItems.length) {
						// uncheck all checked nodes
						data.inst.uncheck_all();
						
						// reset the current items
						currentItems = "";
						
						// remove the unchecked class and add the checked class to the node the user clicked
						node.toggleClass('jstree-unchecked jstree-checked');
						
						// get the new list of items to store
						newNodes = self.setSelectedNodes(nodeId, currentItems);
						
						// set the hidden field value to the new list of items to store
						storedItems.val(newNodes);
					} else {
						// add the newly selected node to the collection of already selected nodes
						newNodes = self.setSelectedNodes(nodeId, currentItems);
						// store the revised collection of nodes
						storedItems.val(newNodes);
					}

				})
				.bind('uncheck_node.jstree', function (e, data) { 
					// data.rslt.obj - the node
					var nodeToRemove = data.rslt.obj.attr("id"),
						currentItems = storedItems.val(),
						remainingNodes = "";
					// remove the unchecked node from the collection of selected nodes	
					remainingNodes = self.removeNode(nodeToRemove, currentItems);
					// store the revised collection of nodes
					storedItems.val(remainingNodes);
				})
				.jstree({
					"core" : {
						"animation" : 100
					},
					"themes" : {
						"theme" : "default",
						"dots" : true,
						"icons" : false
					},
					"checkbox": {
						"override_ui" : true,
						"two_state" : true
					},
					"plugins" : [ "themes", "html_data", "ui", "checkbox" ]
				});
			});
		},
		setSelectedNodes: function(id, currentItems) {
			// when a node (checkbox) is clicked (checked), the node's id is added to the hidden field value
			var selectedItemId = id,
				selectedItemIndex = "",
				selectedNodes = "";
				
			//selected nodes must be stored without the word node_ before the ID int value
			selectedItemIndex = this._removeNodePrefix(selectedItemId);

			if(currentItems) {
				selectedNodes = currentItems + "," + selectedItemIndex;
			} else {
				selectedNodes = selectedItemIndex;
			}
			
			return selectedNodes;
		},
		removeNode: function(item, currentItems) {
			// when checkbox is unchecked, remove item's id from hidden field
			var selected = item,
				selectedNodes = currentItems,
				splitVals = [],
				itemVal = "";

			if(selectedNodes != "") {
				// convert comma-delimited values into array
				splitVals = selectedNodes.split(",");

				if($.isArray(splitVals)) {
					// remove NODE_ID_PREFIX string from the item
					itemVal = this._removeNodePrefix(selected);

					for(var i = 0, max = splitVals.length; i < max; i += 1) {
						if(splitVals[i] == itemVal) {
							splitVals.splice(i, 1);
							break;
						}
					}
					
					selectedNodes = splitVals.join(",")
				}
			}

			return selectedNodes;
		},
		getSelectedNodes: function(tree, storage) {
			var currentItems = storage,
				splitVals = [],
				currItem = "",
				prefix = this._getNodePrefix();
			
			if(currentItems.length) {
				splitVals = currentItems.split(",");
				
				if($.isArray(splitVals)) {			
					for(var i = 0, max = splitVals.length; i < max; i += 1) {
						currItem = "#" + prefix + splitVals[i];
						$(currItem).toggleClass('jstree-unchecked jstree-checked');
					}
				}
			}
		},
		_getNodePrefix: function() {
			var prefix = "node_";
			
			return (prefix);
		},
		_removeNodePrefix: function(id) {
			var prefix = this._getNodePrefix();
			
			return id.substring(prefix.length);
		},
		preDispose: function() {
		
		},
		dispose: function() {
			// nothing to dispose
		}
	};
}();
/*
 * Validation Manager
 *
 * Client-side validation of input fields
 * Depends upon jQuery validation plugin
 *
 */
PHX.managers.validation = function() {
	this.init();
};
PHX.managers.validation.prototype = function() {
	// cache local copy of PHX object
	var phx = PHX;
	
	// reset constructor
	phx.managers.validation.prototype.constructor = phx.managers.validation;
	
	// private functions
	
	// public methods
	return {
		init: function() {			
			//event attachment
			var pageForm = phx.getPageForm(),
				$saveButton = $(":button[id$='_SubmitButton']").eq(0),
				saveAction = $saveButton.attr('onclick'),
				callback = $.noop;
			
			if(pageForm.length) {
			
				pageForm.validate({
					debug: false,
					required: ":text.required:visible",
					errorPlacement: function(error, element) { 
						error.appendTo(element.parent()); 
					}
				});
					
				if($saveButton.length) {			
					
					// setup a callback function that when called will execute the value of the onclick attribute of the button
					callback = function() { var submit = eval(saveAction); submit(); };
					
					// bind the click event to the button after removing the onclick attribute value from the button
					// you need to remove the onclick attribute from the button, else the client-side validation will not
					// have a chance to execute
					$saveButton.attr('onclick','').bind('click', function(e) {
						e.preventDefault();
						// if the form is valid, execute the stored callback
						if(pageForm.valid()) {
							callback();
						} else {
							Debug.Log("The form did not pass validation");
						}
					});
				}
			}
		},
		preDispose: function() {
		
		},
		dispose: function() {
			// nothing to dispose
		}
	};	
}();
/*
 * Element Manager
 * 
 * Element enabling/disabling
 * Async-processing double-click prevention
 * 
 */
PHX.managers.element = function() {
	this.elementHash = {}; 
	this.init();
};
PHX.managers.element.prototype = function() {
	// cache local copy of PHX object
	var phx = PHX;
	
	// reset constructor
	phx.managers.element.prototype.constructor = phx.managers.element;
	
	//Private methods
	function enable(filters) {
		$.each(filters, function(i, filter) { 
			filter.each(function() {
				$(this).removeAttr("phxdisabled");
			});
		});
	};
	function enableAll() {
		enable.call(this, this.elements()); 
	};
	function disable(filters) {
		$.each(filters, function(i, filter) { 
			filter.each(function() {
				$(this).attr("phxdisabled","disabled").removeAttr("onClick");
			});
		});
	};
	function disableAll() {
		disable.call(this, this.elements()); 
	};
	
	function shouldDisable(anchor) {
		var attach = true;
		//get the link url and the target
		var elHREF = anchor.attr('href'), 
			elTarget = anchor.attr('target');
				
		if(elHREF != null && elHREF != '') {        
			if(elHREF.indexOf('#') >= 0) {	            
				//this is a jump link inside the page, so skip it
				attach = false;
			} else if (elTarget != null && elTarget != '' && elTarget != '_self') {
				//this is a link to another window, so skip it
				attach = false;
			}
		}
		
		return(attach);
	};
	
	//Private events
	function onClick(e) {
		if($(e.target).attr("phxdisabled") == "disabled") {
			e.preventDefault();
		} else { 
			//disableAll.call(this);           
		}
	};
	
	//Public prototype object
	return {
		//element hashing methods to avoid repeated lookups
		anchors: function() { 
			if(!this.elementHash["anchor"]) { this.elementHash["anchor"] = $("a"); }
			return this.elementHash["anchor"];
		},
		buttons: function() { 
			if(!this.elementHash["button"]) { this.elementHash["button"] = $(':button'); }
			return this.elementHash["button"];
		},
		images: function() {
			if(!this.elementHash["image"]) { this.elementHash["image"] = $(":image"); }
			return this.elementHash["image"];
		},
		elements: function() {
			return [this.anchors(), this.buttons(), this.images()];
		},
		
		init: function() {
			//set up
			// why do I need to call enable all every time?
			// aren't I going to enable the elements I need to enable when the page posts back or when I click Cancel / OK in the modal dialog?
			var self = this;

			enableAll.call(self);
			//event attachment
			self.buttons().bind("click", $.proxy(onClick, self));
			self.images().bind("click", $.proxy(onClick, self));
			
			self.anchors().each(function() {
				var $anchor = $(self);
				if(shouldDisable.call(self, $anchor)) {
					$anchor.bind("click", $.proxy(onClick, self));	
				}
			});
		},
		preDispose: function() {
		
		},
		dispose: function() {
			var self = this;
			self.buttons().unbind("click", $.proxy(onClick, self));
			self.images().unbind("click", $.proxy(onClick, self));
			
			self.anchors().each(function() {
				var $anchor = $(this);
				if(shouldDisable.call(self, $anchor)) {
					$anchor.unbind("click", $.proxy(onClick, self));	
				}
			});
		},
		
		//expose element enabling to other manager
		enableAll: function() {
			var self = this;
			enableAll.call(self, self.elements());
		}
	};
}();
/* 
 * Anchor Manager
 * 
 * Navigation management 
 * This handles when user clicks away while
 * editing an entity
 */
PHX.managers.clickconfirm = function() {
	this.elementHash = {};
	this.init();
};
PHX.managers.clickconfirm.prototype = function() {
	// cache local copy of PHX object
	var phx = PHX;

	// reset constructor
	phx.managers.clickconfirm.prototype.constructor = phx.managers.clickconfirm;
	
	// public methods
	return {
		//element hashing methods to avoid repeated lookups
		detailView: function() {
			if(!this.elementHash["detail"]) { this.elementHash["detail"] = $(".detail-view"); }
			return this.elementHash["detail"];
		},
		saveButton: function() {
			if(!this.elementHash["save"]) { this.elementHash["save"] = $(":button.save"); }
			return this.elementHash["save"];
		},
		init: function() {
			//set up
			var self = this,
				mainNavigation = self.getMainNavigationEl(),
				sidebar = self.getSidebarEl();
				
			//event attachment
			if(self.detailView().length == 0 || self.saveButton().length == 0) return;
			mainNavigation.bind("click", $.proxy(self.showConfirmationDialog, self));
			sidebar.bind("click", $.proxy(self.showConfirmationDialog, self));
		},
		showConfirmationDialog: function(e) {
			var self = this,
				$target = $(e.target),
				href = $target.attr('href'),
				dialogWrapper = $("<div/>"),
				// get the clicked element's action href val
				c = self._getSubstring(href.substring("javascript:".length), /%20/g," "),
				msg = self.getDialogMessage(),
				$buttons = null;
		
			// only show the modal if an anchor was clicked
			if($target.is('a')) {
				// prevent the default on click so the dialog will show
				e.preventDefault();

				dialogWrapper.html(msg)
					.dialog({
						autoOpen: true,
						closeOnEscape: true,
						modal: true,
						resizable: false,
						title: "Continue without saving?",
						width: 440,
						buttons: {
							'Continue': function (e) {
								eval(c); // perform the clicked element's original action href val
								$(this).dialog('close');
							},
							'Cancel': function () {
								$(this).dialog('close');
							}
							
						}
					});
				
				$buttons = $('.ui-dialog-buttonpane button:first-child');
				$buttons.removeClass('ui-state-default').addClass('ui-priority-primary');
				
			}
		},
		getDialogMessage: function() {
			msg = "<p><span class='modal-ui-icon ui-icon ui-icon-alert'></span>" +
				  "You are about to leave this page, and <strong>your changes have not been saved</strong>.</p>" +
				  "<p><strong>To save your work before leaving</strong>, click the Cancel button and then click the Save button on the page.<strong></p>" +
				  "<p>To continue without saving</strong>, click the Continue button.</p>";
				  
			return (msg);
		},
		getMainNavigationEl: function() {
			var mainNavigation = phx.getMainNavigation();
			
			return (mainNavigation);
		},
		getSidebarEl: function() {
			var sidebar = phx.getSidebar();
			
			return (sidebar);
		},
		_getSubstring: function(s, find, replace) {
			var i, rLen = replace.length
				fLen = find.length,
				last = 0;
	 
			while ((i = s.substring(last).indexOf(find)) > -1) {
				s = s.substring(0, i) + replace + s.substring(last + i + fLen);
				last = i + rLen;
			}
	 
			return (s);
		},
		preDispose: function() {
		
		},
		dispose: function() {
			var self = this,
				mainNavigation = self.getMainNavigationEl(),
				sidebar = self.getSidebarEl();
				
			//tear down events
			if(self.detailView().length == 0 || self.saveButton().length == 0) return; 
			mainNavigation.unbind("click", $.proxy(self.showConfirmationDialog, self));
			sidebar.unbind("click", $.proxy(self.showConfirmationDialog, self));
		}
	};
}();
/* 
 * Anchor Manager
 * 
 * Navigation management 
 * This handles when user clicks away while
 * editing an entity
 */
PHX.managers.input = function() {
	this.init();
};
PHX.managers.input.prototype = function() {
	// cache local copy of PHX object
	var phx = PHX;
	
	// reset constructor
	phx.managers.input.prototype.constructor = phx.managers.input;

	// public methods
	return {
		init: function() {			
			var $textboxes = $(':text'),
				firstField = null,
				datePickers = null,
				$dv = $('.detail-view');
			
			if($textboxes.length) {
				firstField = $textboxes.eq(0);
				datePickers = $textboxes.filter('.date-picker');
				
				// make the textboxes date-picker widgets
				if(datePickers.length) {
					datePickers.each(function() {
						var $this = $(this);

						$this.datepicker({
							changeMonth: true,
							changeYear: true,
							showButtonPanel: true
						});
					});
				}
				
				//this.setFocus(firstField);
			}
			
			if($dv.length) {
				// find all labels with class required-field that are not in a hidden (not inside a hidden modal)
				// for each label, find the corresponding input field and add the 'required' class to the input field
				$dv
					.find("label.required-field:not('.detail-modal label')")
					.each(function() {
						var $this = $(this),
							input = null;
						
						if($this.siblings(":input").length) {
							input = $this.siblings(":input");
						} else {
							input = $this.siblings().find(":input");
						}
						
						if(input.length) {
							input.addClass("required");
						}
					});
			}
		},
		setFocus: function(field) {
			field.focus();
		},
		preDispose: function() {
		
		},
		dispose: function() {
			// dispose events and objects here
		}	
	};
}();
/* 
 * Anchor Manager
 * 
 * Navigation management 
 * This handles when user clicks away while
 * editing an entity
 */
PHX.managers.button = function() {
	this.init();
};
PHX.managers.button.prototype = function() {
	// cache local copy of PHX object
	var phx = PHX;
	
	// reset constructor
	phx.managers.button.prototype.constructor = phx.managers.button;
	
	//Private functions
	return {
		init: function() {
			// style buttons
			var self = this;
			$(':button').each(function() {
				self.makeStyledButton(this);
			});
		},
		makeStyledButton: function(button) {
			var $button = $(button),
				buttonClass = $button.attr("class");
			
			switch(buttonClass) {
				case "add": case "create": case "include":
					$button.button({ icons: {primary:'ui-icon-plusthick'} });
					break;
				case "advanced": case "search":
					$button.button({ icons: {primary:'ui-icon-search'} });
					break;
				case "back":
					$button.button({ icons: {primary:'ui-icon-arrowthick-1-w'} });
					break;
				case "cancel": case "close":
					$button.button({ icons: {primary:'ui-icon-closethick'} });
					break;
				case "exclude":
					$button.button({ icons: {primary:'ui-icon-minusthick'} });
					break;
				case "change":
					$button.button({ icons: {primary:'ui-icon-gear'} });
					break;
				case "clear": case "delete":
					$button.button({ icons: {primary:'ui-icon-trash'} });
					break;
				case "continue":
					$button.button({ icons: {secondary:'ui-icon-arrowthick-1-e'} }).addClass('ui-priority-primary');
					break;
				case "edit":
					$button.button({ icons: {primary:'ui-icon-pencil'} });
					break;
				case "export":
					$button.button({ icons: {primary:'ui-icon-disk'} });
					break;
				case "menu-button":
					$button.button({ icons: {secondary:'ui-icon-triangle-1-s'} });
					break;
				case "refresh":
					$button.button({ icons: {primary:'ui-icon-refresh'} });
					break;
				case "resend":
					$button.button({ icons: {primary:'ui-icon-mail-closed'} });
					break;
				case "save":
					$button.button({ icons: {primary:'ui-icon-disk'} }).addClass('ui-priority-primary');
					break;
				default:
					$button.button();
					break;
			}
		},
		preDispose: function() {
		
		},
		dispose: function() {
			// dispose anything here
		}
	};
}();

/* 
 * AutoComplete Manager
 * 
 * AutoComplete management 
 * This handles when user clicks away while
 * editing an entity
 */
PHX.managers.autocomplete = function() {
	this.init();
};
PHX.managers.autocomplete.prototype = function() {
	var	AJAX_DATA_TYPE = "xml",
		ITEM_ID_PREFIX = "_",
		phx = PHX; // store local copy of PHX object
	
	// reset constructor
	phx.managers.autocomplete.prototype.constructor = phx.managers.autocomplete;
	
	//private functions
	// UTILITY FUNCTIONS
	function enableElement(elem) {
		elem.removeAttr("disabled").removeClass('ui-state-disabled');
	};
	function disableElement(elem) {
		elem.attr("disabled", "disabled").addClass('ui-state-disabled');
	};
	function getAutoCompleteElements() {
		var $ac = $('.phoenix-autocomplete');
		
		return ($ac);
	};
	function hideElement(elem) {
			elem.hide();
	};
	function showElement(elem) {
			elem.show();
	};
	function htmlDecode(strVal) {
		return strVal.replace(/\&amp;/g,'&');
	};
	function isAlreadyInSelectedItems(item, selectedItems) {
		var isAlreadySelected = false,
			arrSelectedItems = [];

		// convert the string val to an array
		arrSelectedItems = selectedItems.split(",");
		
		// see if the item is already in the array
		if($.inArray(item, arrSelectedItems) > -1) {
			isAlreadySelected = true;
		}
		
		return isAlreadySelected;
	};
	function addToSelectedItems(item, currentItems) {
		var newItems = "";
		
		if(currentItems.length) {
			newItems = currentItems + "," + item;
		} else {
			newItems = item;
		}
		
		return newItems;
	};
	function appendToSelectedItems(id, text, container) {
		var newItem = "";
			itemText = text
			itemId = id,
			buttonIdFormat = "",
			buttonId = "",
			selectedItemsList = container.children('ul');
		
		buttonId = createButtonId(itemId, container);

		newItem = '<li><button id="' + buttonId + '" title="Remove" class="remove-selecteditem">Remove</button><span class="autocomplete-selecteditem-text">' + itemText + '</span></li>';
		
		$(newItem)
			.appendTo(selectedItemsList)
			.children(':button')
			.button({
				"icons": {
					"primary": "ui-icon-trash"
				},
				text: false
			});
	};
	function deleteItemClickHandler(event) {
		var $target = $(event.target),
			deletedItemId = $target.attr('id'),
			deletedItemVal = "",
			listItemToDelete = $target.parent(), // this should be the <li> element that contains the clicked button
			selectButton = this.data("settings").btnSelect,
			suggestButton = this.data("settings").btnSuggest,
			container = this.data("settings").selectedItems.containerEl,
			selectedItemsField = this.data("settings").selectedItems.hiddenEl,
			selectedItems = "";
		
		// prevent the default button click behavior
		event.preventDefault();
		
		// if the id is not empty
		if(deletedItemId.length) {
			// get the end of the id string based on a known prefix
			deletedItemVal = parseItemId(deletedItemId, ITEM_ID_PREFIX);
			
			// if the value is not empty
			if(deletedItemVal.length) {
				// remove the item from the displayed list of selected items
				listItemToDelete.remove();
				
				// only try to remove the item if selected items field is not empty
				if(selectedItemsField.val().length) {
					// update the hidden field that stores the ids of the selected items
					selectedItems = removeSelectedItem(selectedItemsField.val(), deletedItemVal);
					selectedItemsField.val(selectedItems);
				}

				// if there are no selected items, disable the select button and enable the suggest button
				if(selectedItemsField.val().length == 0) {
					disableElement(selectButton);
					enableElement(suggestButton);
					hideElement(container);
				} else {
					showElement(container);
				}
			}
		}
	};
	function createButtonId(itemId, container) {
		var textToStrip = "Wrapper",
			containerId = container.parent().attr('id'),
			itemIdPrefix = "Remove_" + itemId,
			newId = "";
			
		// use string.replace to replace the Wrapper part of the id with Remove_[itemid]
		newId = containerId.replace(textToStrip, itemIdPrefix);
		
		return newId;
	};
	function parseItemId(itemId, delimiter) {
		var itemIdLength = 0,
			itemValStartPos = 0,
			itemValLength = 0,
			itemVal = "";
			
		// get the length of the button id value
		itemIdLength = itemId.length;
		
		// get the start position of the last _ char and add 1 to it since we don't want to include the _ char in the position
		itemValStartPos = itemId.lastIndexOf(delimiter) + 1;
		
		// get the length of the value of the element we want to remove
		if(!isNaN(itemIdLength) && !isNaN(itemValStartPos)) {
			itemValLength = itemIdLength - itemValStartPos;
		}
		
		// only continue if the value of the element to remove is a number
		if(!isNaN(itemValLength)) {
			itemVal = itemId.substr(itemValStartPos, itemValLength);
		}
		
		return itemVal;
	};
	function removeSelectedItem(selectedItems, itemToRemove) {
		var arrSelectedItems = [],
			newVal = "";
		
		if(selectedItems.length) {
			arrSelectedItems = selectedItems.split(",");
		
			for(var i = 0, max = arrSelectedItems.length; i < max; i += 1) {
				if(arrSelectedItems[i] !== itemToRemove) {
					if(newVal.length) {
						newVal = newVal + "," + arrSelectedItems[i];
					} else {
						newVal = arrSelectedItems[i];
					}
				}
			}
		}
				
		return newVal;
	};
	function selectButtonClickHandler(event) {
		var $target = $(event.target),
			textbox = this.data("settings").textbox,
			itemId = this.data("settings").selectedItem.id,
			itemText = this.data("settings").selectedItem.text,
			selectedItemsField = this.data("settings").selectedItems.hiddenEl,
			selectedItems = selectedItemsField.val(),
			container = this.data("settings").selectedItems.containerEl,
			allowMultiple = this.data("settings").allowMultiple,
			selectButton = this.data("settings").btnSelect,
			suggestButton = this.data("settings").btnSuggest;

		// prevent the default button action
		event.preventDefault();
		
		textbox.val("");
		
		// check to see if current item in autocomplete textbox is already in list of selected items
		if(itemId.length) {
			if(!isAlreadyInSelectedItems(itemId, selectedItems)) {
				// append the suggested item to the selected items list
				appendToSelectedItems(itemId, itemText, container);
				// add the item to the selected items hidden field value
				selectedItemsField.val(addToSelectedItems(itemId, selectedItems));
			}
		}
		
		if(selectedItemsField.val().length) {
			if(allowMultiple == "false") {
				disableElement(selectButton);
				disableElement(suggestButton);
			}
			
			showElement(container);
		}
		
	};
	function suggestButtonClickHandler(event) {
		var modalHtml = "";
		
		// prevent the default button click behavior
		event.preventDefault();
		
		// get the suggest modal content
		modalHtml = getSuggestModalContent.call(this);
		
		// show the modal
		showModal.call(this, modalHtml);
	};
	function suggestModalItemClickHandler(event) {
		var $target = $(event.target),
			itemId = parseItemId($target.attr('id'), ITEM_ID_PREFIX),
			itemText = $target.text(),
			type = $target.attr('type'),
			selectedItemsField = this.data("settings").selectedItems.hiddenEl,
			selectedItems = selectedItemsField.val(),
			container = this.data("settings").selectedItems.containerEl,
			modal = this.data("settings").modal,
			modalHtml = "",
			selectButton = this.data("settings").btnSelect,
			suggestButton = this.data("settings").btnSuggest,
			allowMultiple = this.data("settings").allowMultiple;
			
		// prevent the default link click behavior
		event.preventDefault();
		
		// determine what type of link was clicked
		// if the link type is letter, make an ajax call to the get the suggested items for that letter
		// else if the link type is item, add the suggested item to the displayed list of selected items
		// and the selected items hidden field
		if(type == "Letter") {
			
			if(itemText != this.data("settings").currentLetter) {
				
				// set the current letter on the autocomplete obj
				this.data("settings").currentLetter = itemText;
				
				// get the suggest modal content
				modalHtml = getSuggestModalContent.call(this);
				
				if(modalHtml.length) {
					modal.html(modalHtml);
					modal.dialog('open');
				}
			}
			
		} else if (type == "Item") {
		
			if(itemId.length) {
				
				if(!isAlreadyInSelectedItems(itemId, selectedItems)) {
					// append the suggested item to the selected items list
					appendToSelectedItems(itemId, itemText, container);
					// add the item to the selected items hidden field value
					selectedItemsField.val(addToSelectedItems(itemId, selectedItems));

				}
				
			}

			if(selectedItemsField.val().length) {
				if(allowMultiple == "false") {
					disableElement(selectButton);
					disableElement(suggestButton);
					modal.dialog('close');
				}
				
				showElement(container);
			}
		}
		
	};
	function showModal(modalHtml) {
		var $this = this,
			content = modalHtml,
			modal = $this.data("settings").modal,
			title = $this.data("settings").modalTitle,
			existingOverlay = $('.ui-widget-overlay').eq(0);

		// set the modal html content
		modal.html(content);

		// initialize the modal
		modal.dialog({
			"autoOpen": true,
			"height": 400,
			"modal": true,
			"resizable": false,
			"title": title,
			"width": 700,
			"buttons": {
				"Done": function() {
					modal.dialog('close');
				}
			},
			"close": function() {
				// show the overlay that was previously hidden when opening a modal from another modal
				if(existingOverlay.length) {
					existingOverlay.show();
				}
			},
			"open": function() {
				// if opening a modal from another modal, hide the parent modal's overlay element
				// to avoid multiple overlays leading to a darker screen
				if(existingOverlay.length) {
					existingOverlay.hide();
				}
			}
		});
	};
	function getSuggestModalContent() {
		var $this = this,
			url = "",
			suggestUrl = $this.data("settings").suggestUrl,
			modalId = $this.data("settings").modal.attr('id'),
			currentLetter = $this.data("settings").currentLetter,
			modalContent = "";
			
		if(currentLetter.length) {
			url = suggestUrl + "&letter=" + currentLetter;
		} else {
			url = suggestUrl;
		}
		// make the ajax call to get the suggest xml
		$.ajax({
			url: url,
			dataType: "xml",
			type: "GET",
			async: false,
			success:  function(xmlDoc, status) {
				modalContent = processXMLDoc(xmlDoc, modalId);
			},
			error: function( xhr, status, error) {
				alert( 'ERROR: ' + status ) ;
				alert( xhr.responseText ) ;
			}
		});
		
						
		return (modalContent);
	};
	
	function processXMLDoc(xmlDoc, modalId) {
		var $xmlData = $(xmlDoc),
			letters = $xmlData.find('StartsWithLetter'),
			items = $xmlData.find('SuggestedItem'),
			item = "",
			letterHasValues = false,
			letter = "",
			itemId = "",
			itemText = "",
			args = [],
			newHtml = "";
		
		newHtml = '<div class="autocomplete-suggest-alphalist"><ul>';
		
		for(var i = 0, max = letters.length; i < max; i += 1) {
			item = letters.eq(i);
			letterHasValues = item.children('HasItems').text();
			letter = item.children('Letter').text();
			
			if(letterHasValues == "true") {
				newHtml = newHtml + '<li><a type="Letter" id="' + modalId + '_Letter' + letter + '">' + letter + '</a></li>';
			} else {
				newHtml = newHtml + '<li>' + letter + '</li>';
			}
		}
		
		newHtml = newHtml + '</ul></div>';
		
		//write the items for this letter
		if(items.length) {
			newHtml = newHtml + '<div class="autocomplete-suggest-results"><ul>';
			for(var i = 0, max = items.length; i < max; i += 1) {
				item = items.eq(i);
				itemId = item.children('ID').text();
				itemText = item.children('Text').text();
			
				newHtml = newHtml + '<li><a type="Item" itemID="' + itemId + '" id="' + modalId + '_Item_' + itemId + '">' + itemText + '</a></li>';
			}
			newHtml = newHtml + "</ul></div>";
		} else {
			newHtml = newHtml + '<div class="autocomplete-suggest-results">There are no available items.</div>';
		}
		
		return newHtml;
	};

	//public methods
	return {
		init: function() {
			var ac = getAutoCompleteElements();
			
			// handle each autocomplete container
			ac.each(function() {
				// cache the container and it's elements into local vars
				var $ac = $(this),
					arrSettings = $ac.children(':input:hidden[id$="_ValueElement_Settings"]').val().split("|"),
					settings = {
							textbox: $ac.find(':text'),
							btnSelect: $ac.find(':button.autocomplete-select'),
							btnSuggest: $ac.find(':button.autocomplete-suggest'),
							modal: $ac.children('.autocomplete-suggest-modal'),
							modalTitle: $ac.prev('label').html(),
							selectedItems: {
								containerEl: $ac.children('.autocomplete-selecteditems'),
								hiddenEl: $ac.children(':input:hidden[id$="_ValueElement"]'),
								btnRemove: $ac.children('.autocomplete-selecteditems').find(':button')
							},
							selectedItem: {},
							autoCompleteUrl: arrSettings[1],
							suggestUrl: arrSettings[2],
							allowMultiple: arrSettings[3],
							currentLetter: "",
							previousLetter: ""
						};

				// add the settings obj to the instance of the autocomplete container
				$ac.data("settings", settings);
										
				// hide the suggest modal
				hideElement(settings.modal);
				
				if(settings.selectedItems.hiddenEl.val().length == 0) {
					settings.selectedItems.containerEl.hide();
				}
				
				// initially disable the select button
				disableElement(settings.btnSelect);

				// if an autocomplete widget already has selected items and does not allow multiple values, disable the suggest button
				if(settings.selectedItems.hiddenEl.val().length && settings.allowMultiple == "false") {
					disableElement(settings.btnSuggest);
				}
				
				// make the textbox an autocomplete widget
				settings.textbox.autocomplete({
					minLength: 3, // do not search until the user enters at least 3 characters
					source: function(request, response) {
						var arrResults = [];
						// perform the ajax call when a user types into the textbox
						$.ajax({
							url: settings.autoCompleteUrl,
							dataType: AJAX_DATA_TYPE,
							data: {
								query: request.term
							},
							success: function(data) {
								// data is the returned xml document
								response($("Result", data).map(function() {
									return {
										value: $("Text", this).text(),
										id: $("ID", this).text()
									}
								}));
							},
							error: function() {
								Debug.Log("Request term " + request.term + " not found");
							}
						});					
					},
					select: function(e, ui) {
						// enable the select button when the user selects something in the autocomplete list
						// but only if the autocomplete should allow selecting multiple items
						if(settings.allowMultiple || (!settings.allowMultiple && settings.selectedItems.hiddenEl.val().length == 0)) {
							enableElement(settings.btnSelect);
						}
						
						// store the return values in an object to pass around
						settings.selectedItem = {
							id: ui.item.id,
							text: ui.item.value
						};
					}
				}).keydown(function(event){
					// trigger the select button click if the user pressed the enter key
					// after highlighting an item in the autocomplete list and clear out the textbox
					if (event.keyCode === 13){
						settings.btnSelect.trigger('click');
						settings.textbox.val("");
					}
				});
				
				// style the select button
				settings.btnSelect.button({
					"icons": {
						"primary": "ui-icon-plusthick"
					},
					text: false
				});
				
				// style the suggest button
				settings.btnSuggest.button({
					"icons": {
						"primary": "ui-icon-search"
					},
					text: false
				});
				
				// style the selected items remove buttons
				settings.selectedItems.btnRemove.button({
					"icons": {
						"primary": "ui-icon-trash"
					},
					text: false
				});

				// wire up the select button
				settings.btnSelect.bind('click', function(event) {
					selectButtonClickHandler.call($ac, event);
				});
				
				// wire up the suggest button
				settings.btnSuggest.bind('click', function(event) {					
					suggestButtonClickHandler.call($ac, event);
				});

				// use delegate here since user can create new items to delete
				// delegate the delete item button click to the container
				settings.selectedItems.containerEl.delegate(':button', 'click', function(event) {					
					deleteItemClickHandler.call($ac, event);
				});
				
				// use delegate here since we are populating the suggested items dynamically
				// delegate the suggested modal link clicks to the container
				settings.modal.delegate('a', 'click', function(event) {
					suggestModalItemClickHandler.call($ac, event);
				});
			});
		},
		preDispose: function() {
		
		},
		dispose: function() {
			var ac = getAutoCompleteElements();
			
			ac.each(function() {
				var $this = $(this);
				$this.data("settings").textbox.autocomplete("destroy");
				$this.data("settings").selectedItems.containerEl.undelegate(':button', 'click');
				$this.data("settings").modal.undelegate('a', 'click');
			});
		}
	};
}();
/* 
 * Tooltip Manager
 * 
 * Navigation management 
 * This handles when user clicks away while
 * editing an entity
 */
PHX.managers.tooltip = function() {
	this.init();
};
PHX.managers.tooltip.prototype = function() {
	// cache local copy of PHX object
	var phx = PHX;
	
	// reset constructor
	phx.managers.tooltip.prototype.constructor = phx.managers.tooltip;
	// private variables
	
	return {
		init: function() {
			var $tt = this.getTooltipElements();
				
			if($tt.length) {
				$tt.tooltip({
					track : true,
					delay : 0,
					showBody : " - ",
					fade : 250,
					extraClass : "ui-corner-all shadow-dark",
					fixPNG: true
				}).addClass('ui-corner-all ui-icon ui-icon-help');
			}
		},
		getTooltipElements: function() {
			var $tooltips = $(".tooltip");
		
			return ($tooltips);
		},
		preDispose: function() {
		
		},
		dispose: function() {
			// dispose anything here
		}
	};
}();
/* 
 * iFrame Manager
 * 
 * Navigation management 
 * This handles when user clicks away while
 * editing an entity
 */
PHX.managers.frame = function() {
	this.init();
};
PHX.managers.frame.prototype = function() {
	// cache local reference to PHX object
	var phx = PHX;
	
	// reset constructor
	phx.managers.frame.prototype.constructor = phx.managers.frame;
	
	//Private functions
	function setIframeSource() {
		var src = this.data('settings').src,
			frame = this.data('settings').frame;
		
		frame.attr("src", src.val());	
	};
	return {
		init: function() {
			// style buttons
			var $this = $(this),
				contentWrapper = phx.getContentWrapper(),
				$frame = contentWrapper.find('.Phoenix-FileUpload');
			
			$frame.each(function() {
				var $this = $(this),
					settings = {
						src: $this.children('.frame-source'),
						frame: $this.children('iframe')
					};
					
				$this.data("settings", settings);
				
				setIframeSource.call($this);
				
			});
		},
		preDispose: function() {
		
		},
		dispose: function() {
			// dispose anything here
		}
	};
}();

PHX.managers.imageManager = function(applicationPath, folderDataUrl) {
	this.init(applicationPath, folderDataUrl);
};
PHX.managers.imageManager.prototype = function() {
	// cache local copy of PHX object
	var phx = PHX;
	
	// reset constructor
	phx.managers.imageManager.prototype.constructor = phx.managers.imageManager;

	// public methods
	return {
		init: function(applicationPath, folderDataUrl) {
			this.setApplicationPath(applicationPath);
			this.setFolderDataUrl(folderDataUrl);
			this.setFoldersAreLoaded(false);
	    
			this.showBrowserView();
		},
		// properties
		setApplicationPath : function(applicationPath) {
			this._ApplicationPath = applicationPath;
		},
		getApplicationPath : function() {
			return (this._ApplicationPath);
		},
		setFolderDataUrl : function(folderDataUrl) {
			this._FolderDataUrl = folderDataUrl;
		},
		getFolderDataUrl : function() {
			return (this._FolderDataUrl);
		},
		getFolderContentsPanel : function() {
			return ($('#FolderContentsPanel'));
		},
		setFoldersAreLoaded : function(loaded) {
			this._FoldersAreLoaded = loaded;
		},
		getFoldersAreLoaded : function() {
			return (this._FoldersAreLoaded);
		},
		showBrowserView: function() {
			var self = this,
				$folderTree = $('#FolderTreePanel'),
				url = this.getFolderDataUrl() + "?images=true";
				
			//load the folders if they haven't been loaded yet
			if (this.getFoldersAreLoaded() == false) {
				this.setFoldersAreLoaded(true);

				$folderTree.bind("loaded.jstree", function (event, data) {
					// binding to the load is required to trigger the internal loaded event
				})
				.jstree({
					"core" : {
						"animation" : 100
					},
					"themes" : {
						"theme" : "default",
						"dots" : true,
						"icons" : true
					},
					"xml_data" : {
						"ajax" : {
							"url" : url,
							"success" : function() {
								self.getFolders_Success();
							},
							"error" : function(textStatus) {
								self.getFolders_Failure(textStatus);
							}
						},
						"xsl" : "nest"
					},
					"plugins" : [ "themes", "xml_data", "ui"]
				})
				.bind("select_node.jstree", function (node, data) {
					// get the id of the node that was clicked
					var nodeId = data.rslt.obj.attr("id");
					
					// callback function that handles node click
					self.folderClick(nodeId)
				})
			}
		},
		buildFolderContentsView : function(images) {	
			var builder = "<ul id='ImageListing'>";		
			
			for (var i = 0, max = images.length; i < max; i += 1) {
				builder = builder + "<li>";
				builder = builder + "<a id='" + this.getItemImageLinkId(images[i].id) + "'><img class='ui-corner-all shadow-light' src='" + this.getApplicationPath() + images[i].relUrl + "' width='100px' height='100px' alt='' /></a>";
				builder = builder + "<div class='image-details'><a id='" + this.getItemTitleLinkId(images[i].id) + "'>" + images[i].label + "</a><p><em>Size: " + images[i].size + "kB, Width: " + images[i].width + "px, Height:" + images[i].height + "px</em></p></div>";
				builder = builder + "</li>";
			}

			builder = builder + "</ul>";

			return builder;
		},
		setFolderContentsPanelHtml : function(updatedHtml) {
			var folderContentsPanel = this.getFolderContentsPanel();
			
			folderContentsPanel.html(updatedHtml);
		},
		//event handlers
		folderClick : function (nodeId) {
			var self = this,
				url = self.getFolderDataUrl() + "?images=true&folder=" + nodeId;
				
			self.setFolderContentsPanelHtml("<p>Loading...</p>");

			$.ajax({
				url: url,
				dataType: "xml",
				type: "GET",
				async: false,
				success : function(data, status, obj) {
					self.getImages_Success(data, obj);
				},
				error : function(obj, status, errorMsg) {
					self.getImages_Failure(errorMsg);
				}
			});

			return false;
		},
		imageClick : function(e) {
			var imageFullPath = e.target.src,
				funcNum = this.getUrlParam('CKEditorFuncNum');
						
			window.opener.CKEDITOR.tools.callFunction(funcNum, imageFullPath);
			window.close();
		},
		
		//async methods
		getFolders_Success : function() {
			this.setFolderContentsPanelHtml("<p>Please select a folder at left.</p>");
		},
		getFolders_Failure : function(errorMsg) {
			alert("Unable to retrieve folder list: " + errorMsg);
		},
		getImages_Success : function(data, obj) {
			var self = this,
				$xmlData = $(data),
				rootNode = $xmlData.children('root'),
				childNodes = rootNode.children('Item'),
				images = this.convertImageXmlNodes(childNodes), // get image Item nodes
				folderContentsPanel = this.getFolderContentsPanel();

			if (images.length) {
				folderContentsPanel.html(this.buildFolderContentsView(images));

				//now that the html has been set, attach the events
				for (var i = 0, max = images.length; i < max; i += 1) {
					$("#" + this.getItemImageLinkId(images[i].id)).bind('click', $.proxy(this.imageClick, self));
					$("#" + this.getItemTitleLinkId(images[i].id)).bind('click', $.proxy(this.imageClick, self));
				}
			} else {
				folderContentsPanel.html("<p>This folder is empty.</p>");
			}
		},
		getImages_Failure : function(errorMsg) {
			alert("Unable to retrieve folder images: " + errorMsg);
		},
		
		//utility methods
		getUrlParam : function(paramName) {
			var reParam = new RegExp('(?:[\?&]|&amp;)' + paramName + '=([^&]+)', 'i'),
				match = window.location.search.match(reParam);

			return (match && match.length > 1) ? match[1] : '';
		},
		convertFolderXmlNode : function(folderXmlNode) {
			var folderAttributes = folderXmlNode.attributes,
				folderId = folderAttributes.getNamedItem("id").value,
				folderName = folderAttributes.getNamedItem("name").value,
				folderObject = { id: folderId, label: folderName };

			return folderObject;
		},
		convertImageXmlNodes : function(imageXmlNodeList) {
			var imageArray = [];

			if (imageXmlNodeList.length > 0) {
				for (var i = 0, max = imageXmlNodeList.length ; i < max; i += 1) {
					imageArray[i] = this.convertImageXmlNode(imageXmlNodeList[i]);
				}
			}

			return imageArray;
		},
		convertImageXmlNode : function(imageXmlNode) {
			var	$imageNode = $(imageXmlNode),
				imageId = $imageNode.attr("id"),
				imageName = $imageNode.attr("name"),
				imageSize = $imageNode.attr("sizeKB"),
				imageWidth = $imageNode.attr("width"),
				imageHeight = $imageNode.attr("height"),
				imageUrl = $imageNode.attr("relPath"),
				imageObject = {
					id: imageId,
					label: imageName,
					size: imageSize,
					width: imageWidth,
					height: imageHeight,
					relUrl: imageUrl
				};
			
			return imageObject;
		},
		getItemImageLinkId : function(imageId) {
			return ("ImageLink_" + imageId);
		},
		getItemTitleLinkId : function(imageId) {
			return ("ImageTitle_" + imageId);
		},
		preDispose: function() {
		
		},
		dispose: function() {
			// dispose anything here
		}
	};
}();

/**
 * Sortable List Manager
 * 
 * This handles drag and drop sorting
 *
 **/
PHX.managers.sortablegrid = function() {
	this.init();
};
PHX.managers.sortablegrid.prototype = function() {
	// cache local reference to PHX object
	var phx = PHX;
	
	// reset constructor
	phx.managers.sortablegrid.prototype.constructor = phx.managers.sortablegrid;
	
	// Private functions
	
	// Public Methods
	return {
		init: function() {
			// style buttons
			var $this = $(this),
				contentWrapper = phx.getContentWrapper(),
				$grid = contentWrapper.find(".sortable-grid");
			
			if($grid.length) {
				$grid.each(function() {
					var $this = $(this)
						, $hiddenElContainer = $this.siblings(".sortable-storage")
						, settings = {
							swapValueEl: $hiddenElContainer.children(":input.sortable-value"),
							swapActionId: $hiddenElContainer.children(".sortable-action")
						};
						
					$this.data("settings", settings);
					
					//bind a click event to the hidden link - we will trigger a click on the link later
					$this.data("settings").swapActionId.bind('click', function() {
						Debug.Log("CLICK EVENT BOUND");
					});
					
					$this.sortable({
						containment: "parent", // contain dragging to within the parent of the draggable rows - tbody
						cursor: "crosshair",
						handle: ".drag-handle", // restrict sort start click to a specific element, not the entire row
						items: "tbody > tr", //specify the items within the main container that can be sorted
						opacity: 0.8,
						placeholder: "ui-state-highlight", // the class to add to the placeholder item that is dynamically created when dragging an item
						forcePlaceholderSize: true,
						tolerance: "pointer", // when dragging to end of list, last item not replaced unless tolerance is set to pointer
						update: function(event, ui) {
							// only way to get all items is to call toArray of sortable object
							// server-side method expects colon-delimited string of ids so join array parts with a colon
							var reorderedItems = $this.sortable("toArray").join(":"),
								// get the id of the hidden link and replace the _ char with $ because of the .net id format requirement
								postbackTargetId = $this.data("settings").swapActionId.attr("id").replace(/_/g, "$");
							
							if(reorderedItems != null && reorderedItems != "") {
								// store string in hidden input field for postback
								$this.data("settings").swapValueEl.val(reorderedItems);

								// perform the postback to save the updated item order
								WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(postbackTargetId, "", true, "", "", false, true));
							}
						}
					});
					
					
				});
			}
		},
		preDispose: function() {
		
		},
		dispose: function() {
			// dispose anything here
		}
	};
}();

/**
 * Editor Manager
 * 
 * Rich Text Editor management
 * Handles the rich text editors on a page
 * 
 **/
PHX.managers.editor = function() {
	var appPath = $("#EditorAppPath").val(),
		fileRepoPath = $("EditorFileRepoPath").val();
		
	this.init(appPath, fileRepoPath);
};
PHX.managers.editor.prototype = function() {
	// cache local reference to PHX object
	var phx = PHX;
	
	// reset constructor
	phx.managers.editor.prototype.constructor = phx.managers.editor;
	   
    var CONTAINER_CLASS = "Phoenix-Detail-Field-Text-Container", 
        CONTAINER_OVER_CLASS = "Phoenix-Detail-Field-Text-Container-Over", 
        CONTAINER_ACTIVE_CLASS = "Phoenix-Detail-Field-Text-Container-Active", 
        DISPLAY_CLASS = "Phoenix-Detail-Field-TextDisplay",
        TEXTAREA_CLASS = "Phoenix-Detail-Field-TextArea",
        CKEDITOR_CONFIG_LOCATION = '/ckeditor/config.js';
    
    var editorInfoHash = [];
    var activeEditorInfo = { container: null, display: null, textArea: null};
    var editorModelEl, editorEl;
    var editor = null;
    var applicationPath, folderDataUrl;
    //Text Display Methods
    var hideEditorDisplay = function(display) {
		var $display = $(display);
        //Hide Text Display Area
		$display.hide();
    };
    var showEditorDisplay = function(display) {
		var $display = $(display);
        //Show Text Display Area
		$display.show();
    };
    
    //Editor Methods
    var createEditor = function(editorContainer, html) { 
		if ( editor )
			return;
			 
		// Create a new editor inside the <div id="editor">
		editor = GetCKEditor(editorContainer.id);
		
		// set the data and focus on the editor
		editor.setData(html, function() {
			this.focus();
		});
    };
    var destroyEditor = function() {     
		if ( !editor )
			return;
 
		// Destroy the editor.
		editor.destroy();
		editor = null;
    };
    
    //Active Editor Methods
    var resetActiveEditor = function() { 
        var $activeEditor;
		
		//Destroy singleton editor
        destroyEditor();
		
		$activeEditor = $(activeEditorInfo.container);
        
		if($activeEditor.hasClass(CONTAINER_ACTIVE_CLASS)) {
			$activeEditor.removeClass(CONTAINER_ACTIVE_CLASS).addClass(CONTAINER_CLASS);
		}
        
        showEditorDisplay(activeEditorInfo.display);
    };
    var destroyActiveEditor = function() { 
        //Reset active editor (if exists) before destroying
        resetActiveEditor();
            
        //Destroy active editor info
        activeEditorInfo.container = null;
        activeEditorInfo.display = null;
        activeEditorInfo.textArea = null;
    };
    var setActiveEditor = function(editorContainer) { 
        resetActiveEditor();
        
        //Get editor info based on active editor container
        //Insert Phoenix Editor before Text Display within Text Container
        var editorInfo = editorInfoHash[editorContainer.id], 
            editorDisplay = editorInfo.display, 
            editorTextArea = editorInfo.textArea;
        
        //Set active editor info with current container and its children
        activeEditorInfo.container = editorContainer;
        activeEditorInfo.display = editorDisplay;
        activeEditorInfo.textArea = editorTextArea;
		
        hideEditorDisplay(editorDisplay);
        
        //Create editor in active editor container
        createEditor(editorContainer, editorTextArea.value);
    };
    var saveActiveEditor = function() {
		var html = "";
		
        if(activeEditorInfo.container !== null) {    
            if(editor && editor !== null) {
                // Retrieve the editor contents. In an Ajax application, this data would be
				// sent to the server or used in any other way.
				html = editor.getData();
				
				activeEditorInfo.display.innerHTML = html;
                activeEditorInfo.textArea.value = html;
            }
        }
    };
    
    return { 
        init: function(appPath, dataUrl) { 
            editor = null;
            folderDataUrl = dataUrl;
            applicationPath = appPath;
            activeEditorInfo.container = null;
            activeEditorInfo.display = null;
            activeEditorInfo.textArea = null;
			
			// element lookup by class in jQuery requires that the class name is prefixed with the . char
			var CONTAINER_CLASS_LOOKUP = "." + CONTAINER_CLASS,
				DISPLAY_CLASS_LOOKUP = "." + DISPLAY_CLASS,
				TEXTAREA_CLASS_LOOKUP = "." + TEXTAREA_CLASS,
				$editorContainers = $(CONTAINER_CLASS_LOOKUP); //Grab all text container for purposes of setting up editor info hash and general event attachment

			if($editorContainers.length) {
				var self = this;
				
				$editorContainers.each( function() {
					var $this = $(this),
						editorDisplay = $this.find(DISPLAY_CLASS_LOOKUP).eq(0).get(0),
						editorTextArea = $this.find(TEXTAREA_CLASS_LOOKUP).eq(0).get(0),
						editorId = this.id;
					
					editorDisplay.innerHTML = editorTextArea.value;
					
					editorInfoHash[editorId] = 
					{
						display: editorDisplay, 
						textArea: editorTextArea 
					};
					
					$this.bind('mouseover.editorEvents', function() {
						var $this = $(this);
						
						$this.removeClass(CONTAINER_CLASS).addClass(CONTAINER_OVER_CLASS);
						
						$this.attr("title", "Click here to edit");
					});
					
					$this.bind('mouseout.editorEvents', function() {
						var $this = $(this);
						
						$this.removeClass(CONTAINER_OVER_CLASS).addClass(CONTAINER_CLASS);
						
						$this.attr("title", "");
					});
					
					$this.bind('click.editorEvents', function() {
						var $this = $(this),
							currentContainer = $this.get(0);
						
						$this.removeClass(CONTAINER_OVER_CLASS).addClass(CONTAINER_ACTIVE_CLASS);
						
						if(activeEditorInfo.container != null) {
							if(activeEditorInfo.container.id != currentContainer.id) { 
								saveActiveEditor(); 
								setActiveEditor(currentContainer);
							}
						} else {
							setActiveEditor(currentContainer);
						}
					});
				});
			}

        }, 
        hideActiveEditor: function() { 
            if(activeEditorInfo.container != null) {
                saveActiveEditor();
                resetActiveEditor();
            }
        },
        showActiveEditor: function() { 
            if(activeEditorInfo.container != null) {
                setActiveEditor(activeEditorInfo.container);
            }
        },
        saveActiveEditor: function() {
            if(activeEditorInfo.container != null) {
                saveActiveEditor();
                destroyActiveEditor();
            }
        },
		preDispose: function() {
			if(activeEditorInfo.container != null) {
                saveActiveEditor();
            }
		},
		dispose: function() {
			var CONTAINER_CLASS_LOOKUP = "." + CONTAINER_CLASS,
				$editorContainers = $(CONTAINER_CLASS_LOOKUP);
			
			// destroy the active editor
			destroyActiveEditor();
			
			// unbind any events
			if($editorContainers.length) {
				$editorContainers.each( function() {
					var $this = $(this);

					$this.unbind(".editorEvents");
				});
			}
		}
    };
}();

/**
* wrap the call the PHX.init in an anonymous function
* and ensure the $ shortcut used internally is a reference to the jQuery object
**/
jQuery(function($) { PHX.init(); }());
