/**
 * @package SaskTourism Google Maps Implementation
 * @version 1.2
 * @author zu.com communications <contactus@zu.com>
 * @description A self contained javascript file that will dynamically display content on http://www.sasktourism.com/
 * @copyright Copyright (c) 2007 zu.com communications
 */

SaskTourismMapsMgr = function() {

	return {

		/**
		 * The GoogleMaps object.
		 * @property _map
		 * @type GMap2
		 * @private
		 */
		_map: {},

		/**
		 * The MarkerManger which will control all markers on the map.
		 * @property _mgr
		 * @type GMarkerManager
		 * @private
		 */
		_mgr: {},

		/**
		 * The LocationManager will contain the information for that drop down.
		 * @property _locationManager
		 * @type {}
		 * @private
		 */
		_locationManager: new Array(),

		/**
		 * The absoulte minimum value for zooming.
		 * @property _minZoom
		 * @type int
		 * @private
		 * @static
		 */
		_minZoom: 4,

		/**
		 * The absoulte maximum value for zooming.
		 * @property _maxZoom
		 * @type int
		 * @private
		 * @static
		 */
		_maxZoom: 13,

		/**
		 * The initial value for zoom when the map loads.
		 * @property _defaultZoom
		 * @type int
		 * @private
		 * @static
		 */
		_defaultZoom: 5,

		/**
		 * The minimum zoom value before the results display.
		 * @property _resultsZoom
		 * @type int
		 * @private
		 * @static
		 */
		_resultsZoom: 9,

		/**
		 * The default zoom level for displaying product details.
		 * @property _detailsZoom
		 * @type int
		 * @private
		 * @static
		 */
		_detailsZoom: 5,

		/**
		 * An array of all the markers on the map.
		 * @property _gMarkers
		 * @type {index: GMarker}
		 * @private
		 */
		_gMarkers: new Array(),

		/**
		 * An array of different icons created while loading the map.
		 * @property _icons
		 * @type {index: GIcon}
		 * @private
		 */
		_icons: new Array(),

		/**
		 * An array of different markers that are currently used.
		 * @property _chMarkers
		 * @type GMarkerManager
		 * @private
		 */
		_chMarkers: {},

		/**
		 * The default Latitude coordinate for map startup.
		 * @property _defaultLat
		 * @type float
		 * @private
		 * @static
		 */
		_defaultLat: 53.14677033085084,

		/**
		 * The default Longitude coordinate for map startup.
		 * @property _defaultLng
		 * @type float
		 * @private
		 * @static
		 */
		_defaultLng: -105.556640625,

		/**
		 * All map functionality can be disabled.
		 * @property _locked
		 * @type boolean
		 * @private
		 */
		_locked: false,

		/**
		 * The primary div id name which will contains all map features.
		 * @property _mainDiv
		 * @type string
		 * @private
		 * @static
		 */
		_mainDiv: "skMap",

		/**
		 * The base url definition for all ajax calls.
		 * @property _baseUrl
		 * @type string
		 * @private
		 * @static
		 */
		_baseUrl: "/../../",

		/**
		 * The image root will be the root of all images used within this file.
		 * @property _imageRoot
		 * @type string
		 * @private
		 * @static
		 */
		_imageRoot: "/../../images/",

		/**
		 * The method name to be used for ajax calls.
		 * @property _ajaxMethod
		 * @type string
		 * @private
		 */
		_ajaxMethod: "",

		/**
		 * The type of map to be displayed.
		 * @property _mapType
		 * @type string
		 * @private
		 */
		_mapType: "",

		/**
		 * The PID (Product Id) is used to call up the xml for a specific product.
		 * @property _pid
		 * @type int
		 * @private
		 */
		_pid: "",

		/**
		 * The PTID (Product Type Id) is used to call up the xml for a specific product type.
		 * @property _ptid
		 * @type int
		 * @private
		 */
		_ptid: "",

		/**
		 * The iconType is used to define which icon set to us.
		 * @property _iconType
		 * @type string
		 * @private
		 */
		_iconType: "",

		/**
		 * The bool that determines the visibility status of the dropdown menu.
		 * @property _ddVisibility
		 * @type boolean
		 * @private
		 */
		_ddVisibility: false,

		/**
		 * The detail product id is used to call up the xml for a specific product type.
		 * @property _opid
		 * @type int
		 * @private
		 */
		_opid: "",

		/**
		 * The sProduct is the actual product name.
		 * @property _sProduct
		 * @type string
		 * @private
		 */
		_sProduct: "",

		/**
		 * The title is the name or file used for the title.
		 * @property _title
		 * @type string
		 * @private
		 */
		_title: "",

		/**
		 * The will track if the results pane is displayed or not.
		 * @property _showingResults
		 * @type boolean
		 * @private
		 */
		_showingResults: false,

		/**
		 * The will lock the update event.
		 * @property _lockEvent
		 * @type boolean
		 * @private
		 */
		_lockEvent: false,

		/**
		 * Determines if error messages should be displayed or not.
		 * @property _debug
		 * @type boolean
		 * @private
		 */
		_debug: false,

		/**
		 * Saves debug messages.
		 * @property _tracker
		 * @type string
		 * @private
		 */
		_tracker: "",

		/**
		 * Lock map functionality.
		 * @method lock
		 */
		lock: function() {
			this._locked = true;
		},

		/**
		 * Unlock map allowing complete functionality.
		 * @method unlock
		 */
		unlock: function() {
			this._locked = false;
		},

		/**
		 * Is the map currently locked?
		 * @method isLocked
		 * @return {bool} True if the map is locked, false otherwise.
		 */
		isLocked: function() {
			return this._locked;
		},

		/**
		 * Sets the debug option.
		 * @method setDebug
		 * @param {boolean} debug
		 */
		setDebug: function(debug) {
			this._debug = debug;
		},

		/**
		 * Set the value of the product name.
		 * @method setProductName
		 * @param {string} sName The new value to be assigned
		 */
		setProductName: function(sName) {
			this._sProduct = sName;
		},

		/**
		 * Return the current product name.
		 * @method getProductName
		 * @return {string} The product name.
		 */
		getProductName: function() {
			return this._sProduct;
		},

		/**
		 * Set the value of the icon type.
		 * @method setIconType
		 * @param {string} iType The new value to be assigned
		 */
		setIconType: function(iType) {
			this._iconType = iType;
		},

		/**
		 * Return the current icon type.
		 * @method getIconType
		 * @return {string} The icon type.
		 */
		getIconType: function() {
			return this._iconType;
		},

		/**
		 * Set the value of the map title.
		 * @method setTitle
		 * @param {string} title The new value to be assigned
		 */
		setTitle: function(title) {
			this._title = title;
		},

		/**
		 * Return the current map title.
		 * @method getTitle
		 * @return {string} The map title.
		 */
		getTitle: function() {
			return this._title;
		},

		/**
		 * Set the value of the map dropdown visibility.
		 * @method setDropDownVisibility
		 * @param {string} dd The new value to be assigned
		 */
		setDropDownVisibility: function(dd) {
			this._ddVisibility = dd;
		},

		/**
		 * Return the the visibility status of the dropdown menu.
		 * @method getDropDownVisibility
		 * @return {string} The map title.
		 */
		getDropDownVisibility: function() {
			return this._ddVisibility;
		},

		/**
		 * Set the value of the Detail Product Id.
		 * @method setDetailProductId
		 * @param {string} dpid The new value to be assigned
		 */
		setDetailProductId: function(ppid) {
			this._opid = ppid;
		},

		/**
		 * Get the value of the Detail Product Id.
		 * @method getDetailProductId
		 * @return value of _detailPid (ProductID)
		 */
		getDetailProductId: function() {
			return this._opid;
		},

		/**
		 * Set the value of the Product Id.
		 * @method setProductId
		 * @param {string} pid The new value to be assigned
		 */
		setProductId: function(pid) {
			this._pid = pid;
		},

		/**
		 * Get the value of the Product Id.
		 * @method getProductId
		 * @return value of _pid (ProductID)
		 */
		getProductId: function() {
			return this._pid;
		},

		/**
		 * Set the value of the Product Type Id.
		 * @method setPID
		 * @param {string} nPID The new value to be assigned
		 */
		setProductTypeId: function(ptid) {
			this._ptid = ptid;
		},

		/**
		 * Get the value of the Product Type Id.
		 * @method getPID
		 * @return value of _ptid (ProductID)
		 */
		getProductTypeId: function() {
			return this._ptid;
		},

		/**
		 * Set the value of the ajax method to be used.
		 * @method setAjaxMethod
		 * @param {string} aMethod The new value to be assigned
		 */
		setAjaxMethod: function(aMethod) {
			this._ajaxMethod = aMethod;
		},

		/**
		 * Return the current ajax method to be used.
		 * @method getAjaxMethod
		 * @return {string} The ajax method.
		 */
		getAjaxMethod: function() {
			return this._ajaxMethod;
		},

		/**
		 * Set the value of the ajax method for this page.
		 * @method setMapType
		 * @param {string} mapType The new value to be assigned
		 */
		setMapType: function(mapType) {
			this._mapType = mapType;
		},

		/**
		 * Return the the defined ajax method.
		 * @method getMapType
		 * @return {string} The ajax method.
		 */
		getMapType: function() {
			return this._mapType;
		},

		/**
		 * This function checks a variable to see if its set or not.
		 * @method isset.
		 * @parm {string} variable The method to be used.
		 * @return {boolean} Tt will either be true or false!
		 */
		isset: function(variable) {
			if ( variable != "" && variable != undefined  && variable != null ) {
				return true;
			}
			else {
				return false;
			}
		},

		/**
		 * This function deals with error messages.
		 * @method sendToDebug.
		 * @parm {string} message
		 */
		sendToDebug: function(message) {
			if ( this._debug ) {
				var currentTime = new Date();
				var hours = currentTime.getHours();
				if(hours > 11){	post = "pm";} else {post = "am";}
				if(hours > 12) {hours = hours - 12;}
				var minutes = currentTime.getMinutes();
				if (minutes < 10) {minutes = "0" + minutes;}
				var seconds = currentTime.getSeconds();
				if (seconds < 10) {seconds = "0" + seconds;}

				this._tracker += "<li style=\"padding-bottom: 5px;\">" + message + " {"+hours+":"+minutes+":"+seconds+post+"}</li>";

				var objDiv = document.getElementById("skMap_Debugger");
				objDiv.innerHTML = "<ol style=\"padding: 0px 0px 0px 25px;\">" + this._tracker + "</ol>";
				objDiv.scrollTop = objDiv.scrollHeight;
			}
			else {
				return null;
			}
		},

		/**
		 * This function initializes the entire map functionallity.
		 * @method init
		 */
		init: function() {
			//load the div panes and return the div id for the map.
			var mapDiv = this.loadDisplay();

			//globalObj will be used as the global map for the eventListener functions.
			globalObj = this;

			//create and initialize the map by declaring new GMap2.
			this._map = new GMap2(document.getElementById(mapDiv));
			this._map.setCenter(new GLatLng(this._defaultLat, this._defaultLng), this._defaultZoom);

			G_NORMAL_MAP.getMaximumResolution = function() {return globalObj._maxZoom;}
			G_NORMAL_MAP.getMinimumResolution = function() {return globalObj._minZoom;}

			if ( this.getMapType() == "mini" || this.getMapType() == "detail" ) {
				this.sendToDebug("{init} Adding rounded corners because map type is <b>" + this.getMapType() + "</b>.");
				this.addRoundedCorners();
			}

			//determine if the map should be locked or not.
			if ( this.isLocked() ) {
				this.sendToDebug("{init} The map is currently <b>locked</b>.");
				this._map.disableDragging();
				this._map.disableInfoWindow();
				this._map.disableDoubleClickZoom();
				this._map.disableScrollWheelZoom();
			}
			else {
				this.sendToDebug("{init} The map is currently <b>unlocked</b>.");
				//adding this control will hiding the pan buttons and move the controls more to the left than the original position.
				this._map.addControl(new GLargeMapControl(),new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(-14,-58)));

				//Note: that "moveend" includes zooming in and out aswell as moving the map manually.
				GEvent.addListener(this._map, "moveend", this.upDateResultsPane);
				GEvent.addListener(this._map, "click", this.clickZoomCenter);
                GEvent.addListener(this._map, "zoomend", this.zoomChecker);
                
				if ( this.getMapType() == "mini" ) {
					GEvent.addListener(this._map, "zoomend", this.showHideResultsPane);

					if ( this.getDropDownVisibility() == false ) {
						this.sendToDebug("{init} Display dropdown menu.");
						this.populateDropDown();
					}
					else {
						this.sendToDebug("{init} <b>Not</b> Displaying dropdown menu.");
					}
				}
			}

			if ( this._map.getZoom() < this._resultsZoom ) {
				this.sendToDebug("{init} Disaply info window disabled (zoom not enough).");
				this._map.disableInfoWindow();
			}

			//create the GMarkerManagers to handle all the markers.
			this._mgr = new MarkerManager(this._map, {borderPadding: 0, maxZoom: this._maxZoom, trackMarkers: false});
			this._chMarkers = new MarkerManager(this._map, {borderPadding: 0, maxZoom: this._maxZoom, trackMarkers: false});

			//load up the xml and display some markers!
			this.loadXml();
		},

		/**
		 * This function will add the rounded corners at the bottom of the map.
		 * @method addRoundedCorners
		 */
		addRoundedCorners: function() {
			function bottomLeft() {}
			function bottomRight() {}

			bottomLeft.prototype = new GControl();
			bottomRight.prototype = new GControl();

			bottomLeft.prototype.initialize = function(map) {
				var bottomLeft = document.createElement("div");
				bottomLeft.style.background = " url("+globalObj._imageRoot+"bottomLeft.png) no-repeat";
				bottomLeft.style.padding = "0px";
				bottomLeft.style.margin = "0px";
				bottomLeft.style.width = "6px";
				bottomLeft.style.height = "6px";
				map.getContainer().appendChild(bottomLeft);
				return bottomLeft;
			}

			bottomRight.prototype.initialize = function(map) {
				var bottomRight = document.createElement("div");
				bottomRight.style.background = " url("+globalObj._imageRoot+"bottomRight.png) no-repeat";
				bottomRight.style.padding = "0px";
				bottomRight.style.margin = "0px";
				bottomRight.style.width = "6px";
				bottomRight.style.height = "6px";
				map.getContainer().appendChild(bottomRight);
				return bottomRight;
			}

			bottomLeft.prototype.getDefaultPosition = function() {
				return new GControlPosition(G_ANCHOR_BOTTOM_LEFT, new GSize(0,0));
			}

			bottomRight.prototype.getDefaultPosition = function() {
				return new GControlPosition(G_ANCHOR_BOTTOM_RIGHT, new GSize(0,0));
			}

			//add the corners to the map
			this._map.addControl(new bottomLeft());
			this._map.addControl(new bottomRight());
		},

		/**
		 * This function will create the required html elements and append them to the main div.
		 * @method loadDisplay
		 * @return {string} The id for the map div.
		 */
		loadDisplay: function() {
			//create the header and attached the class and text.
			var newHeader = document.createElement("div");
			newHeader.setAttribute("id", "skMap_header");
			if ( this.getMapType() == "mini" ) {
				newHeader.innerHTML = "<img src=\""+this._imageRoot+this.getTitle()+"\" />\n";
			}
			else {
				newHeader.innerHTML = "<img src=\""+this._imageRoot+"title_finduniquelysaskatchewan.gif\" />\n";
			}

			if ( this.getMapType() == "mini" && this.getDropDownVisibility() == false ) {
				newHeader.innerHTML += "<select id=\"skMap_whereDD\" onChange=\"globalObj.changeSelectedDD(this.options[this.selectedIndex].value)\"><option value=\"0\">Already know where you're going?</option><option value=\"\"></option></select></select>\n";
			}

			//create the layout and attached the class and id.
			var newLayout = document.createElement("div");
			newLayout.setAttribute("id", "skMap_loadGMap");

			//create the bottom div and attached the class.
			var newBottom = document.createElement("div");
			newBottom.setAttribute("id", "skMap_mapBottom");

			//create the spacer div and attached the class.
			var newSpace = document.createElement("div");
			newSpace.setAttribute("id", "skMap_Spacer");

			//create the reults pane which will hold corresponding map titles and events.
			var newResults = document.createElement("div");
			newResults.setAttribute("id", "skMap_results");
			newResults.innerHTML = "<ul><li>Move the hand over a dot for more information</li><li>Click on a dot to zoom in and get details</li><li>Click anywhere or use the scale in the upper left to zoom in or out</li><li>Try the drop-down list at the top if you already know the name of your destination</li></ul>";
			
			var newRArea = document.createElement("div");
			newRArea.setAttribute("id", "skMap_ResultsArea");
			newRArea.appendChild(newSpace);
			newRArea.appendChild(newResults);

			//create a hidden input element to store the xml data in.
			var newHiddenXml = document.createElement("input");
			newHiddenXml.setAttribute("id", "skMap_xml");
			newHiddenXml.setAttribute("type", "hidden");
			newHiddenXml.setAttribute("value", "");

			//append all the new divs to the main div.
			//Note: IE7 has a problem with the appendChild function, so we have to
			//explicitly declare that we want to append the child to the that div.
			document.getElementById(this._mainDiv).appendChild(newHeader);
			document.getElementById(this._mainDiv).appendChild(newLayout);
			if ( this.getMapType() == "mini" ) {
				document.getElementById(this._mainDiv).appendChild(newRArea);
			}
			document.getElementById(this._mainDiv).appendChild(newBottom);
			document.getElementById(this._mainDiv).appendChild(newHiddenXml);

			if ( this._debug ) {
				var newDebugger = document.createElement("div");
				newDebugger.setAttribute("id", "skMap_Debugger");
				newDebugger.style.height = "200px";
				newDebugger.style.color = "#FEAC24";
				newDebugger.style.background = "#666";
				newDebugger.style.fontSize = "0.7em";
				newDebugger.style.marginTop = "10px";
				newDebugger.style.marginBottom = "10px";
				newDebugger.style.border = "2px solid #999";
				newDebugger.style.overflow = "auto";
				document.getElementById(this._mainDiv).appendChild(newDebugger);
				document.getElementById(this._mainDiv).style.height = "800px";
			}

			//return the div id for the actual map.
			return "skMap_loadGMap";
		},

		/**
		 * This function will load an xml file and populate the dropdown menu.
		 * @method populateDropDown
		 */
		populateDropDown: function() {
			//launch GDownload to grab the xml doc
			GDownloadUrl(this.buildUrlRequest("dropdown"), function(data, responseCode) {
				if (responseCode == 200) {
					var hiddenInput = document.getElementById("skMap_xml");
					//save the xml string into the value of a hidden input element for future use.
					hiddenInput.value = "";
					hiddenInput.value = data;
					globalObj.parseXml("dropdown");
				}
				else if(responseCode == -1) {
					//if there was no response then...
					this.sendToDebug("{populateDropDown} Data request timed out. Please try later.");
				}
				else {
					//if something goes wrong then...
				    this.sendToDebug("{populateDropDown} Request resulted in error. Check XML file is retrievable.");
				}
			});
		},

		/**
		 * This function will call up the xml file and save it for further use.
		 * @method loadXml
		 */
		loadXml: function() {
			//launch GDownload to grab the xml doc
			GDownloadUrl(this.buildUrlRequest("markers"), function(data, responseCode) {
				if (responseCode == 200) {
					var hiddenInput = document.getElementById("skMap_xml");
					//save the xml string into the value of a hidden input element for future use.
					hiddenInput.value = "";
					hiddenInput.value = data;

					if ( globalObj.getMapType() == "detail" ) {
						globalObj.parseXml("detail");
					}
					else {
						globalObj.parseXml("markers");
					}
				}
				else if(responseCode == -1) {
					//if there was no response then...
					this.sendToDebug("{loadXml} Data request timed out. Please try later.");
				}
				else {
					//if something goes wrong then...
				    this.sendToDebug("{loadXml} Request resulted in error. Check XML file is retrievable.");
				}
			});
		},

		/**
		 * This function will compile the needed parameters for the ajax request.
		 * @method buildUrlRequest.
		 * @parm {string} type The method to be used.
		 * @return {string} The url for the ajax call.
		 */
		buildUrlRequest: function(type) {
			var url = this._baseUrl;
			if ( type == "dropdown" ) {
				url += "productlocations.asp?method=locations";
			}
			else {
				url += "productdata2.asp?method="+this.getAjaxMethod();
			}

			if ( this.isset(this.getProductId()) ) {
				url += "&pid="+this.getProductId();
			}

			if ( this.isset(this.getProductTypeId()) ) {
				url += "&ptid="+this.getProductTypeId();
			}

			if ( this.isset(this.getDetailProductId()) ) {
				url += "&opid="+this.getDetailProductId();
			}

			if ( this.isset(this.getProductName()) ) {
				url += "&product="+this.getProductName();
			}

			this.sendToDebug("{buildUrlRequest} " + url);
			return url;
		},

		/**
		 * This function will loap and set the points and add them to the GMarkerManager.
		 * @method parseXml
		 * @param {string} type The type of xml to be parsed
		 */
		parseXml: function(type) {
			var imagePath = this._imageRoot + "map__icon_"+this.getIconType()+"_";
			this.sendToDebug("{parseXml} Icon image path: '" + imagePath +"'");

			if ( this.getMapType() == "detail" ) {
				var aIcon = G_DEFAULT_ICON;
				aIcon.image = this._imageRoot + "map__star.png";
				aIcon.iconSize = new GSize(40,35);
				aIcon.shadow = "";
			}
			else {
				var aIcon = G_DEFAULT_ICON;
				aIcon.image = imagePath + "zoomedout.png";
				aIcon.iconSize = new GSize(8,8);
				aIcon.iconAnchor = new GPoint(3,13);
				aIcon.shadow = "";
			}

			if ( type == "markers" ) {
				var data = document.getElementById("skMap_xml").value;
				var xmlDoc = GXml.parse(data);
				var markers = xmlDoc.documentElement.getElementsByTagName("OrgProduct");
				
				//var batch = [];
				for (var i = 0; i < markers.length; i++) {
					var point = new GLatLng(parseFloat(markers[i].getAttribute("OPLat")),parseFloat(markers[i].getAttribute("OPLong")));
					//batch.push(this.createMarker(markers[i].getAttribute("OrgProductID"), point, markers[i].getAttribute("OPDescription"), markers[i].getAttribute("OPName"), aIcon));
					this._map.addOverlay(this.createMarker(markers[i].getAttribute("OrgProductID"), point, markers[i].getAttribute("OPDescription"), markers[i].getAttribute("OPName"), aIcon));
				}
				//this._mgr.addMarkers(batch, this._minZoom, this._maxZoom);
				//this._mgr.refresh();
				this.upDateResultsPane();
			}
			else if (type == "detail") {
				var data = document.getElementById("skMap_xml").value;
				var xmlDoc = GXml.parse(data);
				var markers = xmlDoc.documentElement.getElementsByTagName("OrgProduct");

				var batch = [];
				for (var i = 0; i < markers.length; i++) {
					var point = new GLatLng(parseFloat(markers[i].getAttribute("OPLat")),parseFloat(markers[i].getAttribute("OPLong")));
					var mkr = this.createMarker(markers[i].getAttribute("OrgProductID"), point, markers[i].getAttribute("OPDescription"), markers[i].getAttribute("OPName"), aIcon);
				}
				this._map.addOverlay(mkr);
				this._map.setCenter(point);
				this._map.setZoom(this._detailsZoom);
			}
			else if ( type == "dropdown" ) {
				var data = document.getElementById("skMap_xml").value;
				var dropDown = document.getElementById("skMap_whereDD");
				var counter = 1;
				var xmlDoc = GXml.parse(data);
				var locations = xmlDoc.documentElement.getElementsByTagName("OPLocation");
				for (var i = 0; i < locations.length; i++) {
					var details = [];
					details["latitude"] = parseFloat(locations[i].getAttribute("Lat"));
					details["longitude"] = parseFloat(locations[i].getAttribute("Long"));
					details["zoom"] = parseInt(locations[i].getAttribute("ZoomLevel"));
					dropDown.options[counter] = new Option(locations[i].getAttribute("LocationName"), locations[i].getAttribute("LocationID"));
					this._locationManager[locations[i].getAttribute("LocationID")] = details;
					counter++;
				}
			}
			/*this._map.setZoom(parseInt(this._map.getZoom() - 1));
			this._map.setZoom(parseInt(this._map.getZoom() + 1));*/
		},

		/**
		 * This function will set the location from the dropdown menu.
		 * @method changeSelectedDD
		 * @param {int} type The index of the array that contains the details.
		 */
		changeSelectedDD: function(index) {
			if ( index == "" || index == "0" ) {
				this.sendToDebug("{changeSelectedDD} Index was zero or null.");
				return null;
			}
			this.sendToDebug("{changeSelectedDD} Index "+index+" was selected.");
			var details = this._locationManager[index];
			this._map.setCenter(new GLatLng(details["latitude"], details["longitude"]));
			this._map.setZoom(this._resultsZoom);
		},

		/**
		 * This function will loap and set the points with the MarkerManager.
		 * @method createMarker
		 * @param {int} The product index number
		 * @param {GPoint} point The lat and lng for this marker
		 * @param {string} text The text for the bubble
		 * @param {string} title The text for the tooltip
		 * @param {GIcon} aIcon The the icon object to be used
		 * @param {string} imagePath the path of the icon to be used
		 * @return {GMarker} The marker that was created
		 */
		createMarker: function(index, point, text, name, aIcon) {
			var m = new GMarker(point, {draggable:false, icon:aIcon, title:name});
			this._gMarkers[index] = m;

			GEvent.addListener(m, "click", function() {
				globalObj.clickZoomCenter("marker", point);
			});

			return m;
		},

		/**
		 * This function will display the product details
		 * @method displayMarkerInfo
		 * @param {int} The product id
		 * @param {string} The product name
		 * @param {GMarker} The current marker at that point
		 * @param {string} The product description
		 * @param {char} The letter to be used
		 */
		displayMarkerInfo: function(pid, name, marker, text, letter) {
			this.sendToDebug("{displayMarkerInfo} Displaying marker '"+name+" ("+pid+")'.");
			globalObj._lockEvent = true;
			globalObj._map.panTo(marker.getPoint());
			document.getElementById("skMapDescrip_"+letter.toLowerCase()).style.display = "block";
			var sr = document.getElementById("skMap_results");
			sr.scrollTop = 0;
			sr.scrollTop = YAHOO.util.Dom.getY("skMapAnchor_"+letter.toLowerCase()) - YAHOO.util.Dom.getY("skMap_results");
		},

		/**
		 * This function will create the lettered markers.
		 * @method buildLetteredMaker
		 * @param {GMarker} The current marker at that point
		 * @param {int} The product id
		 * @param {string} The product description
		 * @param {string} The product name
		 * @param {char} The letter to be used
		 * @return {GMarker} The lettered marker that was created
		 */
		buildLetteredMaker: function(cM, pid, text, name, letter, imagePath) {
			var li = new GIcon(G_DEFAULT_ICON);
			li.image = imagePath + letter.toLowerCase() + ".png";
			li.iconSize = new GSize(21,35);
			li.iconAnchor = new GPoint(9, 35);
			li.shadow = globalObj._imageRoot + "map__icon_shadow.png";
			li.shadowSize = new GSize(42, 35);

			ct = cM.getTitle();

			var marker = new GMarker(cM.getPoint(), {draggable:false, icon:li, title:ct});

			GEvent.addListener(marker, "click", function() {
				globalObj.displayMarkerInfo(pid, name, cM, text, letter);
			});

			globalObj._gMarkers[pid] = marker;

			return marker;
		},

		/**
		 * This function be called on every zoom eventi n the mini map.
		 * @method showHideResultsPane
		 */
		showHideResultsPane: function() {
			var c = globalObj._map.getZoom();
			var r = globalObj._resultsZoom;

			if ( !globalObj._showingResults && c >= r ) {
				globalObj._showingResults = true;
				globalObj.sendToDebug("{showHideResultsPane} Display results pane.");
			}
			else if ( globalObj._showingResults && c < r ) {
				globalObj._showingResults = false;
				globalObj.sendToDebug("{showHideResultsPane} Hide results pane.");
			}
		},

		/**
		 * This function will update the results pane after the map zooms or moves.
		 * @method upDateResultsPane
		 */
		upDateResultsPane: function() {
			if ( globalObj.getMapType() == "detail" ) {
				return null;
			}

			var imagePath = globalObj._imageRoot + "map__icon_"+globalObj.getIconType()+"_";
			globalObj.sendToDebug("{upDateResultsPane} Zoom:" + globalObj._map.getZoom()+".");

			if ( globalObj._lockEvent == true ) {
				//used for displaying marker details
				globalObj._lockEvent = false;
				return null;
			}

			var resultsDiv = document.getElementById("skMap_results");

			//if the minimum zoom has not been met, then terminate this function.
			if ( globalObj._map.getZoom() < globalObj._resultsZoom ) {
				//resultsDiv.innerHTML = "";
				globalObj._map.disableInfoWindow();
				globalObj._mgr.clearMarkers();
				globalObj._mgr.refresh();
				return null;
			}
			else {
				globalObj._map.enableInfoWindow();
			}

			//clear the current lettered markers
			globalObj._chMarkers.clearMarkers();
			globalObj._chMarkers.refresh();

			resultsDiv.innerHTML = "Loading...";
			var bounds = globalObj._map.getBounds();
			var southWest = bounds.getSouthWest();
			var northEast = bounds.getNorthEast();
			var data = document.getElementById("skMap_xml").value;
			var xmlDoc = GXml.parse(data);
			var markers = xmlDoc.documentElement.getElementsByTagName("OrgProduct");
			var batch = "";
			var jj = 0;
			

			for (var i = 0; i < markers.length; i++) {
			    var OName = markers[i].getAttribute("OPName");
				var point = new GLatLng(parseFloat(markers[i].getAttribute("OPLat")),parseFloat(markers[i].getAttribute("OPLong")));
				if ( point.lng() > southWest.lng() && point.lng() < northEast.lng() && point.lat() > southWest.lat() && point.lat() < northEast.lat() ) {
					var letter = String.fromCharCode("A".charCodeAt(0) + jj);
					batch += "<table id=\"skMapAnchor_"+letter.toLowerCase()+"\" class=\"resultsTable\"><tr><td class=\"icon\"><a href=\"#\" onclick=\"GEvent.trigger(globalObj._gMarkers["+markers[i].getAttribute("OrgProductID")+"], 'click'); return false;\"><img src=\"" + globalObj._imageRoot + "map__key_" + globalObj.getIconType() + "_" + letter.toLowerCase()+".gif\" /></a></td><td class=\"link\"><a href=\"#\" onclick=\"GEvent.trigger(globalObj._gMarkers["+markers[i].getAttribute("OrgProductID")+"], 'click'); return false;\">" + markers[i].getAttribute("OPName") + "</a><div id=\"skMapDescrip_"+letter.toLowerCase()+"\" class=\"description\" style=\"display: none;\">"+markers[i].getAttribute("OPDescription")+"<br /><div class=\"details_link\"><a href=\"/travel-information/product-detail&opid="+markers[i].getAttribute("OrgProductID")+"\" onclick=\"javascript:dcsMultiTrack(\'DCS.dcssip\',\'www.sasktourism.com\',\'DCS.dcsuri\',\'SMapDetail\',\'WT.ti\',\'"+OName.replace(/'/g,"\\'")+"\',\'WT.cg_n\',\'\',\'WT.cg_s\',\'\',\'WT.ad\',\'"+OName.replace(/'/g,"\\'")+"\',\'WT.ac\',\'\',\'DCSext.SMapDetail\',\'1\',\'DCSext.SearchDetail\',\'1\');\">Complete details</a></div></div></td></tr></table>\n";
					globalObj._chMarkers.addMarker(globalObj.buildLetteredMaker(globalObj._gMarkers[markers[i].getAttribute("OrgProductID")], markers[i].getAttribute("OrgProductID"), markers[i].getAttribute("OPDescription"), markers[i].getAttribute("OPName"), letter, imagePath), globalObj._resultsZoom, globalObj._maxZoom);
					jj++;
				}
			}

			globalObj._mgr.refresh();
			globalObj._chMarkers.refresh();
			resultsDiv.innerHTML = batch;
		},

         zoomChecker: function(o, n) {
			if ( o >= globalObj._resultsZoom && globalObj.getMapType() != "detail" ) {
				document.getElementById("skMap_results").innerHTML = "";
			}
		},

		/**
		 * This function will center and zoom to the point that was clicked.
		 * @method clickZoomCenter
		 * @param {GMap2} The map that was clicked
		 * @param {GPoint} The click coords
		 */
		clickZoomCenter: function(map, point) {
			if ( globalObj.getMapType() != "detail" ) {
				if ( globalObj._map.getZoom() < globalObj._resultsZoom ) {
					globalObj._map.setCenter(point);
					globalObj._map.setZoom(globalObj._resultsZoom);
				}
			}
		}
	}
}



/**
 * This will be the only function explicity called
 * @method loadSkMapObj
 */
function loadSkMapObj() {
	stMaps = new SaskTourismMapsMgr();
	stMaps.setProductId(this.preSkMap_pid);
	stMaps.setProductTypeId(this.preSkMap_ptid);
	stMaps.setIconType(this.preSkMap_iconType);
	stMaps.setDetailProductId(this.preSkMap_opid);
	stMaps.setProductName(this.preSkMap_sProduct);
	stMaps.setTitle(this.preSkMap_title);
	stMaps.setMapType(this.preSkMap_mapType);
	stMaps.setAjaxMethod(this.preSkMap_ajaxMethod);

	if ( this.preSkMap_hideDropDown == true || this.preSkMap_hideDropDown == false ) {
		stMaps.setDropDownVisibility();
	}

	if ( this.preSkMap_debug == true || this.preSkMap_debug == false ) {
		stMaps.setDebug(this.preSkMap_debug);
	}

	if ( this.preSkMap_lock ) {
		stMaps.lock();
	}

	//start everything!
	stMaps.init();

	//this will attach a listener for when the page is left or closed, elimiating (for the most part) javascript leaks.
	if (window.attachEvent) {
		window.attachEvent("onunload", GUnload);
	}
	else {
		window.addEventListener("unload", GUnload, false);
	}
}



/**
 * Creates a new MarkerManager that will show/hide markers on a map.
 *
 * @constructor
 * @param {Map} map The map to manage.
 * @param {Object} opt_opts A container for optional arguments:
 * 		{Number} maxZoom The maximum zoom level for which to create tiles.
 * 		{Number} borderPadding The width in pixels beyond the map border, where markers should be display.
 * 		{Boolean} trackMarkers Whether or not this manager should track marker movements.
 */
function MarkerManager(map, opt_opts) {
  var me = this;
  me.map_ = map;
  me.mapZoom_ = map.getZoom();
  me.projection_ = map.getCurrentMapType().getProjection();

  opt_opts = opt_opts || {};
  me.tileSize_ = MarkerManager.DEFAULT_TILE_SIZE_;

  var maxZoom = MarkerManager.DEFAULT_MAX_ZOOM_;
  if(opt_opts.maxZoom != undefined) {
    maxZoom = opt_opts.maxZoom;
  }
  me.maxZoom_ = maxZoom;

  me.trackMarkers_ = opt_opts.trackMarkers;

  var padding;
  if (typeof opt_opts.borderPadding == "number") {
    padding = opt_opts.borderPadding;
  } else {
    padding = MarkerManager.DEFAULT_BORDER_PADDING_;
  }
  // The padding in pixels beyond the viewport, where we will pre-load markers.
  me.swPadding_ = new GSize(-padding, padding);
  me.nePadding_ = new GSize(padding, -padding);
  me.borderPadding_ = padding;

  me.gridWidth_ = [];

  me.grid_ = [];
  me.grid_[maxZoom] = [];
  me.numMarkers_ = [];
  me.numMarkers_[maxZoom] = 0;

  GEvent.bind(map, "moveend", me, me.onMapMoveEnd_);

  // NOTE: These two closures provide easy access to the map.
  // They are used as callbacks, not as methods.
  me.removeOverlay_ = function(marker) {
    if (!marker.isInfoWindowOpened) {
      map.removeOverlay(marker);
      me.shownMarkers_--;
    }
  };
  me.addOverlay_ = function(marker) {
    if (!marker.isInfoWindowOpened) {
      marker.isInfoWindowOpened = false;
      map.addOverlay(marker);
      me.shownMarkers_++;
    }
  };

  me.resetManager_();
  me.shownMarkers_ = 0;

  me.shownBounds_ = me.getMapGridBounds_();
};

// Static constants:
MarkerManager.DEFAULT_TILE_SIZE_ = 1024;
MarkerManager.DEFAULT_MAX_ZOOM_ = 17;
MarkerManager.DEFAULT_BORDER_PADDING_ = 100;
MarkerManager.MERCATOR_ZOOM_LEVEL_ZERO_RANGE = 256;


/**
 * Initializes MarkerManager arrays for all zoom levels
 * Called by constructor and by clearAllMarkers
 */
MarkerManager.prototype.resetManager_ = function() {
  var me = this;
  var mapWidth = MarkerManager.MERCATOR_ZOOM_LEVEL_ZERO_RANGE;
  for (var zoom = 0; zoom <= me.maxZoom_; ++zoom) {
    me.grid_[zoom] = [];
    me.numMarkers_[zoom] = 0;
    me.gridWidth_[zoom] = Math.ceil(mapWidth/me.tileSize_);
    mapWidth <<= 1;
  }
};

/**
 * Removes all currently displayed markers
 * and calls resetManager to clear arrays
 */
MarkerManager.prototype.clearMarkers = function() {
  var me = this;
  me.processAll_(me.shownBounds_, me.removeOverlay_);
  me.resetManager_();
};


/**
 * Gets the tile coordinate for a given latlng point.
 *
 * @param {LatLng} latlng The geographical point.
 * @param {Number} zoom The zoom level.
 * @param {GSize} padding The padding used to shift the pixel coordinate.
 *               Used for expanding a bounds to include an extra padding
 *               of pixels surrounding the bounds.
 * @return {GPoint} The point in tile coordinates.
 *
 */
MarkerManager.prototype.getTilePoint_ = function(latlng, zoom, padding) {
  var pixelPoint = this.projection_.fromLatLngToPixel(latlng, zoom);
  return new GPoint(
      Math.floor((pixelPoint.x + padding.width) / this.tileSize_),
      Math.floor((pixelPoint.y + padding.height) / this.tileSize_));
};


/**
 * Finds the appropriate place to add the marker to the grid.
 * Optimized for speed; does not actually add the marker to the map.
 * Designed for batch-processing thousands of markers.
 *
 * @param {Marker} marker The marker to add.
 * @param {Number} minZoom The minimum zoom for displaying the marker.
 * @param {Number} maxZoom The maximum zoom for displaying the marker.
 */
MarkerManager.prototype.addMarkerBatch_ = function(marker, minZoom, maxZoom) {
  var mPoint = marker.getPoint();
  // Tracking markers is expensive, so we do this only if the
  // user explicitly requested it when creating marker manager.
  if (this.trackMarkers_) {
    GEvent.bind(marker, "changed", this, this.onMarkerMoved_);
  }

  GEvent.addListener(marker, "infowindowopen", function() {
    marker.isInfoWindowOpened = true;
  });

  GEvent.addListener(marker, "infowindowclose", function() {
    marker.isInfoWindowOpened = false;
  });

  var gridPoint = this.getTilePoint_(mPoint, maxZoom, GSize.ZERO);

  for (var zoom = maxZoom; zoom >= minZoom; zoom--) {
    var cell = this.getGridCellCreate_(gridPoint.x, gridPoint.y, zoom);
    cell.push(marker);

    gridPoint.x = gridPoint.x >> 1;
    gridPoint.y = gridPoint.y >> 1;
  }
};


/**
 * Returns whether or not the given point is visible in the shown bounds. This
 * is a helper method that takes care of the corner case, when shownBounds have
 * negative minX value.
 *
 * @param {Point} point a point on a grid.
 * @return {Boolean} Whether or not the given point is visible in the currently
 * shown bounds.
 */
MarkerManager.prototype.isGridPointVisible_ = function(point) {
  var me = this;
  var vertical = me.shownBounds_.minY <= point.y &&
      point.y <= me.shownBounds_.maxY;
  var minX = me.shownBounds_.minX;
  var horizontal = minX <= point.x && point.x <= me.shownBounds_.maxX;
  if (!horizontal && minX < 0) {
    // Shifts the negative part of the rectangle. As point.x is always less
    // than grid width, only test shifted minX .. 0 part of the shown bounds.
    var width = me.gridWidth_[me.shownBounds_.z];
    horizontal = minX + width <= point.x && point.x <= width - 1;
  }
  return vertical && horizontal;
}


/**
 * Reacts to a notification from a marker that it has moved to a new location.
 * It scans the grid all all zoom levels and moves the marker from the old grid
 * location to a new grid location.
 *
 * @param {Marker} marker The marker that moved.
 * @param {LatLng} oldPoint The old position of the marker.
 * @param {LatLng} newPoint The new position of the marker.
 */
MarkerManager.prototype.onMarkerMoved_ = function(marker, oldPoint, newPoint) {
  // NOTE: We do not know the minimum or maximum zoom the marker was
  // added at, so we start at the absolute maximum. Whenever we successfully
  // remove a marker at a given zoom, we add it at the new grid coordinates.
  var me = this;
  var zoom = me.maxZoom_;
  var changed = false;
  var oldGrid = me.getTilePoint_(oldPoint, zoom, GSize.ZERO);
  var newGrid = me.getTilePoint_(newPoint, zoom, GSize.ZERO);
  while (zoom >= 0 && (oldGrid.x != newGrid.x || oldGrid.y != newGrid.y)) {
    var cell = me.getGridCellNoCreate_(oldGrid.x, oldGrid.y, zoom);
    if (cell) {
      if (me.removeFromArray(cell, marker)) {
        me.getGridCellCreate_(newGrid.x, newGrid.y, zoom).push(marker);
      }
    }
    // For the current zoom we also need to update the map. Markers that no
    // longer are visible are removed from the map. Markers that moved into
    // the shown bounds are added to the map. This also lets us keep the count
    // of visible markers up to date.
    if (zoom == me.mapZoom_) {
      if (me.isGridPointVisible_(oldGrid)) {
        if (!me.isGridPointVisible_(newGrid)) {
          me.removeOverlay_(marker);
          changed = true;
        }
      } else {
        if (me.isGridPointVisible_(newGrid)) {
          me.addOverlay_(marker);
          changed = true;
        }
      }
    }
    oldGrid.x = oldGrid.x >> 1;
    oldGrid.y = oldGrid.y >> 1;
    newGrid.x = newGrid.x >> 1;
    newGrid.y = newGrid.y >> 1;
    --zoom;
  }
  if (changed) {
    me.notifyListeners_();
  }
};


/**
 * Searches at every zoom level to find grid cell
 * that marker would be in, removes from that array if found.
 * Also removes marker with removeOverlay if visible.
 * @param {GMarker} marker The marker to delete.
 */
MarkerManager.prototype.removeMarker = function(marker) {
  var me = this;
  var zoom = me.maxZoom_;
  var changed = false;
  var point = marker.getPoint();
  var grid = me.getTilePoint_(point, zoom, GSize.ZERO);
  while (zoom >= 0) {
    var cell = me.getGridCellNoCreate_(grid.x, grid.y, zoom);

    if (cell) {
      me.removeFromArray(cell, marker);
    }
    // For the current zoom we also need to update the map. Markers that no
    // longer are visible are removed from the map. This also lets us keep the count
    // of visible markers up to date.
    if (zoom == me.mapZoom_) {
      if (me.isGridPointVisible_(grid)) {
          me.removeOverlay_(marker);
          changed = true;
      }
    }
    grid.x = grid.x >> 1;
    grid.y = grid.y >> 1;
    --zoom;
  }
  if (changed) {
    me.notifyListeners_();
  }
};


/**
 * Add many markers at once.
 * Does not actually update the map, just the internal grid.
 *
 * @param {Array of Marker} markers The markers to add.
 * @param {Number} minZoom The minimum zoom level to display the markers.
 * @param {Number} opt_maxZoom The maximum zoom level to display the markers.
 */
MarkerManager.prototype.addMarkers = function(markers, minZoom, opt_maxZoom) {
  var maxZoom = this.getOptMaxZoom_(opt_maxZoom);
  for (var i = markers.length - 1; i >= 0; i--) {
    this.addMarkerBatch_(markers[i], minZoom, maxZoom);
  }

  this.numMarkers_[minZoom] += markers.length;
  this.refresh();
};


/**
 * Returns the value of the optional maximum zoom. This method is defined so
 * that we have just one place where optional maximum zoom is calculated.
 *
 * @param {Number} opt_maxZoom The optinal maximum zoom.
 * @return The maximum zoom.
 */
MarkerManager.prototype.getOptMaxZoom_ = function(opt_maxZoom) {
  return opt_maxZoom != undefined ? opt_maxZoom : this.maxZoom_;
}


/**
 * Calculates the total number of markers potentially visible at a given
 * zoom level.
 *
 * @param {Number} zoom The zoom level to check.
 */
MarkerManager.prototype.getMarkerCount = function(zoom) {
  var total = 0;
  for (var z = 0; z <= zoom; z++) {
    total += this.numMarkers_[z];
  }
  return total;
};


/**
 * Add a single marker to the map.
 *
 * @param {Marker} marker The marker to add.
 * @param {Number} minZoom The minimum zoom level to display the marker.
 * @param {Number} opt_maxZoom The maximum zoom level to display the marker.
 */
MarkerManager.prototype.addMarker = function(marker, minZoom, opt_maxZoom) {
  var me = this;
  var maxZoom = this.getOptMaxZoom_(opt_maxZoom);
  me.addMarkerBatch_(marker, minZoom, maxZoom);
  var gridPoint = me.getTilePoint_(marker.getPoint(), me.mapZoom_, GSize.ZERO);
  if(me.isGridPointVisible_(gridPoint) &&
     minZoom <= me.shownBounds_.z &&
     me.shownBounds_.z <= maxZoom ) {
    me.addOverlay_(marker);
    me.notifyListeners_();
  }
  this.numMarkers_[minZoom]++;
};

/**
 * Returns true if this bounds (inclusively) contains the given point.
 * @param {Point} point  The point to test.
 * @return {Boolean} This Bounds contains the given Point.
 */
GBounds.prototype.containsPoint = function(point) {
  var outer = this;
  return (outer.minX <= point.x &&
          outer.maxX >= point.x &&
          outer.minY <= point.y &&
          outer.maxY >= point.y);
}

/**
 * Get a cell in the grid, creating it first if necessary.
 *
 * Optimization candidate
 *
 * @param {Number} x The x coordinate of the cell.
 * @param {Number} y The y coordinate of the cell.
 * @param {Number} z The z coordinate of the cell.
 * @return {Array} The cell in the array.
 */
MarkerManager.prototype.getGridCellCreate_ = function(x, y, z) {
  var grid = this.grid_[z];
  if (x < 0) {
    x += this.gridWidth_[z];
  }
  var gridCol = grid[x];
  if (!gridCol) {
    gridCol = grid[x] = [];
    return gridCol[y] = [];
  }
  var gridCell = gridCol[y];
  if (!gridCell) {
    return gridCol[y] = [];
  }
  return gridCell;
};


/**
 * Get a cell in the grid, returning undefined if it does not exist.
 *
 * NOTE: Optimized for speed -- otherwise could combine with getGridCellCreate_.
 *
 * @param {Number} x The x coordinate of the cell.
 * @param {Number} y The y coordinate of the cell.
 * @param {Number} z The z coordinate of the cell.
 * @return {Array} The cell in the array.
 */
MarkerManager.prototype.getGridCellNoCreate_ = function(x, y, z) {
  var grid = this.grid_[z];
  if (x < 0) {
    x += this.gridWidth_[z];
  }
  var gridCol = grid[x];
  return gridCol ? gridCol[y] : undefined;
};


/**
 * Turns at geographical bounds into a grid-space bounds.
 *
 * @param {LatLngBounds} bounds The geographical bounds.
 * @param {Number} zoom The zoom level of the bounds.
 * @param {GSize} swPadding The padding in pixels to extend beyond the
 * given bounds.
 * @param {GSize} nePadding The padding in pixels to extend beyond the
 * given bounds.
 * @return {GBounds} The bounds in grid space.
 */
MarkerManager.prototype.getGridBounds_ = function(bounds, zoom, swPadding,
                                                  nePadding) {
  zoom = Math.min(zoom, this.maxZoom_);

  var bl = bounds.getSouthWest();
  var tr = bounds.getNorthEast();
  var sw = this.getTilePoint_(bl, zoom, swPadding);
  var ne = this.getTilePoint_(tr, zoom, nePadding);
  var gw = this.gridWidth_[zoom];

  // Crossing the prime meridian requires correction of bounds.
  if (tr.lng() < bl.lng() || ne.x < sw.x) {
    sw.x -= gw;
  }
  if (ne.x - sw.x  + 1 >= gw) {
    // Computed grid bounds are larger than the world; truncate.
    sw.x = 0;
    ne.x = gw - 1;
  }
  var gridBounds = new GBounds([sw, ne]);
  gridBounds.z = zoom;
  return gridBounds;
};


/**
 * Gets the grid-space bounds for the current map viewport.
 *
 * @return {Bounds} The bounds in grid space.
 */
MarkerManager.prototype.getMapGridBounds_ = function() {
  var me = this;
  return me.getGridBounds_(me.map_.getBounds(), me.mapZoom_,
                           me.swPadding_, me.nePadding_);
};


/**
 * Event listener for map:movend.
 * NOTE: Use a timeout so that the user is not blocked
 * from moving the map.
 *
 */
MarkerManager.prototype.onMapMoveEnd_ = function() {
  var me = this;
  me.objectSetTimeout_(this, this.updateMarkers_, 0);
};


/**
 * Call a function or evaluate an expression after a specified number of
 * milliseconds.
 *
 * Equivalent to the standard window.setTimeout function, but the given
 * function executes as a method of this instance. So the function passed to
 * objectSetTimeout can contain references to this.
 *    objectSetTimeout(this, function() { alert(this.x) }, 1000);
 *
 * @param {Object} object  The target object.
 * @param {Function} command  The command to run.
 * @param {Number} milliseconds  The delay.
 * @return {Boolean}  Success.
 */
MarkerManager.prototype.objectSetTimeout_ = function(object, command, milliseconds) {
  return window.setTimeout(function() {
    command.call(object);
  }, milliseconds);
};


/**
 * Refresh forces the marker-manager into a good state.
 * <ol>
 *   <li>If never before initialized, shows all the markers.</li>
 *   <li>If previously initialized, removes and re-adds all markers.</li>
 * </ol>
 */
MarkerManager.prototype.refresh = function() {
  var me = this;
  if (me.shownMarkers_ > 0) {
    me.processAll_(me.shownBounds_, me.removeOverlay_);
  }
  me.processAll_(me.shownBounds_, me.addOverlay_);
  me.notifyListeners_();
};


/**
 * After the viewport may have changed, add or remove markers as needed.
 */
MarkerManager.prototype.updateMarkers_ = function() {
  var me = this;
  me.mapZoom_ = this.map_.getZoom();
  var newBounds = me.getMapGridBounds_();

  // If the move does not include new grid sections,
  // we have no work to do:
  if (newBounds.equals(me.shownBounds_) && newBounds.z == me.shownBounds_.z) {
    return;
  }

  if (newBounds.z != me.shownBounds_.z) {
    me.processAll_(me.shownBounds_, me.removeOverlay_);
    me.processAll_(newBounds, me.addOverlay_);
  } else {
    // Remove markers:
    me.rectangleDiff_(me.shownBounds_, newBounds, me.removeCellMarkers_);

    // Add markers:
    me.rectangleDiff_(newBounds, me.shownBounds_, me.addCellMarkers_);
  }
  me.shownBounds_ = newBounds;

  me.notifyListeners_();
};


/**
 * Notify listeners when the state of what is displayed changes.
 */
MarkerManager.prototype.notifyListeners_ = function() {
  GEvent.trigger(this, "changed", this.shownBounds_, this.shownMarkers_);
};


/**
 * Process all markers in the bounds provided, using a callback.
 *
 * @param {Bounds} bounds The bounds in grid space.
 * @param {Function} callback The function to call for each marker.
 */
MarkerManager.prototype.processAll_ = function(bounds, callback) {
  for (var x = bounds.minX; x <= bounds.maxX; x++) {
    for (var y = bounds.minY; y <= bounds.maxY; y++) {
      this.processCellMarkers_(x, y,  bounds.z, callback);
    }
  }
};


/**
 * Process all markers in the grid cell, using a callback.
 *
 * @param {Number} x The x coordinate of the cell.
 * @param {Number} y The y coordinate of the cell.
 * @param {Number} z The z coordinate of the cell.
 * @param {Function} callback The function to call for each marker.
 */
MarkerManager.prototype.processCellMarkers_ = function(x, y, z, callback) {
  var cell = this.getGridCellNoCreate_(x, y, z);
  if (cell) {
    for (var i = cell.length - 1; i >= 0; i--) {
      callback(cell[i]);
    }
  }
};


/**
 * Remove all markers in a grid cell.
 *
 * @param {Number} x The x coordinate of the cell.
 * @param {Number} y The y coordinate of the cell.
 * @param {Number} z The z coordinate of the cell.
 */
MarkerManager.prototype.removeCellMarkers_ = function(x, y, z) {
  this.processCellMarkers_(x, y, z, this.removeOverlay_);
};


/**
 * Add all markers in a grid cell.
 *
 * @param {Number} x The x coordinate of the cell.
 * @param {Number} y The y coordinate of the cell.
 * @param {Number} z The z coordinate of the cell.
 */
MarkerManager.prototype.addCellMarkers_ = function(x, y, z) {
  this.processCellMarkers_(x, y, z, this.addOverlay_);
};


/**
 * Use the rectangleDiffCoords function to process all grid cells
 * that are in bounds1 but not bounds2, using a callback, and using
 * the current MarkerManager object as the instance.
 *
 * Pass the z parameter to the callback in addition to x and y.
 *
 * @param {Bounds} bounds1 The bounds of all points we may process.
 * @param {Bounds} bounds2 The bounds of points to exclude.
 * @param {Function} callback The callback function to call
 *                   for each grid coordinate (x, y, z).
 */
MarkerManager.prototype.rectangleDiff_ = function(bounds1, bounds2, callback) {
  var me = this;
  me.rectangleDiffCoords(bounds1, bounds2, function(x, y) {
    callback.apply(me, [x, y, bounds1.z]);
  });
};


/**
 * Calls the function for all points in bounds1, not in bounds2
 *
 * @param {Bounds} bounds1 The bounds of all points we may process.
 * @param {Bounds} bounds2 The bounds of points to exclude.
 * @param {Function} callback The callback function to call
 *                   for each grid coordinate.
 */
MarkerManager.prototype.rectangleDiffCoords = function(bounds1, bounds2, callback) {
  var minX1 = bounds1.minX;
  var minY1 = bounds1.minY;
  var maxX1 = bounds1.maxX;
  var maxY1 = bounds1.maxY;
  var minX2 = bounds2.minX;
  var minY2 = bounds2.minY;
  var maxX2 = bounds2.maxX;
  var maxY2 = bounds2.maxY;

  for (var x = minX1; x <= maxX1; x++) {  // All x in R1
    // All above:
    for (var y = minY1; y <= maxY1 && y < minY2; y++) {  // y in R1 above R2
      callback(x, y);
    }
    // All below:
    for (var y = Math.max(maxY2 + 1, minY1);  // y in R1 below R2
         y <= maxY1; y++) {
      callback(x, y);
    }
  }

  for (var y = Math.max(minY1, minY2);
       y <= Math.min(maxY1, maxY2); y++) {  // All y in R2 and in R1
    // Strictly left:
    for (var x = Math.min(maxX1 + 1, minX2) - 1;
         x >= minX1; x--) {  // x in R1 left of R2
      callback(x, y);
    }
    // Strictly right:
    for (var x = Math.max(minX1, maxX2 + 1);  // x in R1 right of R2
         x <= maxX1; x++) {
      callback(x, y);
    }
  }
};


/**
 * Removes value from array. O(N).
 *
 * @param {Array} array  The array to modify.
 * @param {any} value  The value to remove.
 * @param {Boolean} opt_notype  Flag to disable type checking in equality.
 * @return {Number}  The number of instances of value that were removed.
 */
MarkerManager.prototype.removeFromArray = function(array, value, opt_notype) {
  var shift = 0;
  for (var i = 0; i < array.length; ++i) {
    if (array[i] === value || (opt_notype && array[i] == value)) {
      array.splice(i--, 1);
      shift++;
    }
  }
  return shift;
};


/**
 * Copyright (c) 2007, Yahoo! Inc. All rights reserved.
 * Code licensed under the BSD License:
 * version: 2.4.1
 */
if(typeof YAHOO=="undefined"||!YAHOO){var YAHOO={};}YAHOO.namespace=function(){var A=arguments,E=null,C,B,D;for(C=0;C<A.length;C=C+1){D=A[C].split(".");E=YAHOO;for(B=(D[0]=="YAHOO")?1:0;B<D.length;B=B+1){E[D[B]]=E[D[B]]||{};E=E[D[B]];}}return E;};YAHOO.log=function(D,A,C){var B=YAHOO.widget.Logger;if(B&&B.log){return B.log(D,A,C);}else{return false;}};YAHOO.register=function(A,E,D){var I=YAHOO.env.modules;if(!I[A]){I[A]={versions:[],builds:[]};}var B=I[A],H=D.version,G=D.build,F=YAHOO.env.listeners;B.name=A;B.version=H;B.build=G;B.versions.push(H);B.builds.push(G);B.mainClass=E;for(var C=0;C<F.length;C=C+1){F[C](B);}if(E){E.VERSION=H;E.BUILD=G;}else{YAHOO.log("mainClass is undefined for module "+A,"warn");}};YAHOO.env=YAHOO.env||{modules:[],listeners:[]};YAHOO.env.getVersion=function(A){return YAHOO.env.modules[A]||null;};YAHOO.env.ua=function(){var C={ie:0,opera:0,gecko:0,webkit:0,mobile:null};var B=navigator.userAgent,A;if((/KHTML/).test(B)){C.webkit=1;}A=B.match(/AppleWebKit\/([^\s]*)/);if(A&&A[1]){C.webkit=parseFloat(A[1]);if(/ Mobile\//.test(B)){C.mobile="Apple";}else{A=B.match(/NokiaN[^\/]*/);if(A){C.mobile=A[0];}}}if(!C.webkit){A=B.match(/Opera[\s\/]([^\s]*)/);if(A&&A[1]){C.opera=parseFloat(A[1]);A=B.match(/Opera Mini[^;]*/);if(A){C.mobile=A[0];}}else{A=B.match(/MSIE\s([^;]*)/);if(A&&A[1]){C.ie=parseFloat(A[1]);}else{A=B.match(/Gecko\/([^\s]*)/);if(A){C.gecko=1;A=B.match(/rv:([^\s\)]*)/);if(A&&A[1]){C.gecko=parseFloat(A[1]);}}}}}return C;}();(function(){YAHOO.namespace("util","widget","example");if("undefined"!==typeof YAHOO_config){var B=YAHOO_config.listener,A=YAHOO.env.listeners,D=true,C;if(B){for(C=0;C<A.length;C=C+1){if(A[C]==B){D=false;break;}}if(D){A.push(B);}}}})();YAHOO.lang=YAHOO.lang||{isArray:function(B){if(B){var A=YAHOO.lang;return A.isNumber(B.length)&&A.isFunction(B.splice);}return false;},isBoolean:function(A){return typeof A==="boolean";},isFunction:function(A){return typeof A==="function";},isNull:function(A){return A===null;},isNumber:function(A){return typeof A==="number"&&isFinite(A);},isObject:function(A){return(A&&(typeof A==="object"||YAHOO.lang.isFunction(A)))||false;},isString:function(A){return typeof A==="string";},isUndefined:function(A){return typeof A==="undefined";},hasOwnProperty:function(A,B){if(Object.prototype.hasOwnProperty){return A.hasOwnProperty(B);}return !YAHOO.lang.isUndefined(A[B])&&A.constructor.prototype[B]!==A[B];},_IEEnumFix:function(C,B){if(YAHOO.env.ua.ie){var E=["toString","valueOf"],A;for(A=0;A<E.length;A=A+1){var F=E[A],D=B[F];if(YAHOO.lang.isFunction(D)&&D!=Object.prototype[F]){C[F]=D;}}}},extend:function(D,E,C){if(!E||!D){throw new Error("YAHOO.lang.extend failed, please check that all dependencies are included.");}var B=function(){};B.prototype=E.prototype;D.prototype=new B();D.prototype.constructor=D;D.superclass=E.prototype;if(E.prototype.constructor==Object.prototype.constructor){E.prototype.constructor=E;}if(C){for(var A in C){D.prototype[A]=C[A];}YAHOO.lang._IEEnumFix(D.prototype,C);}},augmentObject:function(E,D){if(!D||!E){throw new Error("Absorb failed, verify dependencies.");}var A=arguments,C,F,B=A[2];if(B&&B!==true){for(C=2;C<A.length;C=C+1){E[A[C]]=D[A[C]];}}else{for(F in D){if(B||!E[F]){E[F]=D[F];}}YAHOO.lang._IEEnumFix(E,D);}},augmentProto:function(D,C){if(!C||!D){throw new Error("Augment failed, verify dependencies.");}var A=[D.prototype,C.prototype];for(var B=2;B<arguments.length;B=B+1){A.push(arguments[B]);}YAHOO.lang.augmentObject.apply(this,A);},dump:function(A,G){var C=YAHOO.lang,D,F,I=[],J="{...}",B="f(){...}",H=", ",E=" => ";if(!C.isObject(A)){return A+"";}else{if(A instanceof Date||("nodeType" in A&&"tagName" in A)){return A;}else{if(C.isFunction(A)){return B;}}}G=(C.isNumber(G))?G:3;if(C.isArray(A)){I.push("[");for(D=0,F=A.length;D<F;D=D+1){if(C.isObject(A[D])){I.push((G>0)?C.dump(A[D],G-1):J);}else{I.push(A[D]);}I.push(H);}if(I.length>1){I.pop();}I.push("]");}else{I.push("{");for(D in A){if(C.hasOwnProperty(A,D)){I.push(D+E);if(C.isObject(A[D])){I.push((G>0)?C.dump(A[D],G-1):J);}else{I.push(A[D]);}I.push(H);}}if(I.length>1){I.pop();}I.push("}");}return I.join("");},substitute:function(Q,B,J){var G,F,E,M,N,P,D=YAHOO.lang,L=[],C,H="dump",K=" ",A="{",O="}";for(;;){G=Q.lastIndexOf(A);if(G<0){break;}F=Q.indexOf(O,G);if(G+1>=F){break;}C=Q.substring(G+1,F);M=C;P=null;E=M.indexOf(K);if(E>-1){P=M.substring(E+1);M=M.substring(0,E);}N=B[M];if(J){N=J(M,N,P);}if(D.isObject(N)){if(D.isArray(N)){N=D.dump(N,parseInt(P,10));}else{P=P||"";var I=P.indexOf(H);if(I>-1){P=P.substring(4);}if(N.toString===Object.prototype.toString||I>-1){N=D.dump(N,parseInt(P,10));}else{N=N.toString();}}}else{if(!D.isString(N)&&!D.isNumber(N)){N="~-"+L.length+"-~";L[L.length]=C;}}Q=Q.substring(0,G)+N+Q.substring(F+1);}for(G=L.length-1;G>=0;G=G-1){Q=Q.replace(new RegExp("~-"+G+"-~"),"{"+L[G]+"}","g");}return Q;},trim:function(A){try{return A.replace(/^\s+|\s+$/g,"");}catch(B){return A;}},merge:function(){var D={},B=arguments;for(var C=0,A=B.length;C<A;C=C+1){YAHOO.lang.augmentObject(D,B[C],true);}return D;},later:function(H,B,I,D,E){H=H||0;B=B||{};var C=I,G=D,F,A;if(YAHOO.lang.isString(I)){C=B[I];}if(!C){throw new TypeError("method undefined");}if(!YAHOO.lang.isArray(G)){G=[D];}F=function(){C.apply(B,G);};A=(E)?setInterval(F,H):setTimeout(F,H);return{interval:E,cancel:function(){if(this.interval){clearInterval(A);}else{clearTimeout(A);}}};},isValue:function(B){var A=YAHOO.lang;return(A.isObject(B)||A.isString(B)||A.isNumber(B)||A.isBoolean(B));}};YAHOO.util.Lang=YAHOO.lang;YAHOO.lang.augment=YAHOO.lang.augmentProto;YAHOO.augment=YAHOO.lang.augmentProto;YAHOO.extend=YAHOO.lang.extend;YAHOO.register("yahoo",YAHOO,{version:"2.4.1",build:"742"});(function(){var B=YAHOO.util,L,J,H=0,K={},F={},N=window.document;var C=YAHOO.env.ua.opera,M=YAHOO.env.ua.webkit,A=YAHOO.env.ua.gecko,G=YAHOO.env.ua.ie;var E={HYPHEN:/(-[a-z])/i,ROOT_TAG:/^body|html$/i};var O=function(Q){if(!E.HYPHEN.test(Q)){return Q;}if(K[Q]){return K[Q];}var R=Q;while(E.HYPHEN.exec(R)){R=R.replace(RegExp.$1,RegExp.$1.substr(1).toUpperCase());}K[Q]=R;return R;};var P=function(R){var Q=F[R];if(!Q){Q=new RegExp("(?:^|\\s+)"+R+"(?:\\s+|$)");F[R]=Q;}return Q;};if(N.defaultView&&N.defaultView.getComputedStyle){L=function(Q,T){var S=null;if(T=="float"){T="cssFloat";}var R=N.defaultView.getComputedStyle(Q,"");if(R){S=R[O(T)];}return Q.style[T]||S;};}else{if(N.documentElement.currentStyle&&G){L=function(Q,S){switch(O(S)){case"opacity":var U=100;try{U=Q.filters["DXImageTransform.Microsoft.Alpha"].opacity;}catch(T){try{U=Q.filters("alpha").opacity;}catch(T){}}return U/100;case"float":S="styleFloat";default:var R=Q.currentStyle?Q.currentStyle[S]:null;return(Q.style[S]||R);}};}else{L=function(Q,R){return Q.style[R];};}}if(G){J=function(Q,R,S){switch(R){case"opacity":if(YAHOO.lang.isString(Q.style.filter)){Q.style.filter="alpha(opacity="+S*100+")";if(!Q.currentStyle||!Q.currentStyle.hasLayout){Q.style.zoom=1;}}break;case"float":R="styleFloat";default:Q.style[R]=S;}};}else{J=function(Q,R,S){if(R=="float"){R="cssFloat";}Q.style[R]=S;};}var D=function(Q,R){return Q&&Q.nodeType==1&&(!R||R(Q));};YAHOO.util.Dom={get:function(S){if(S&&(S.tagName||S.item)){return S;}if(YAHOO.lang.isString(S)||!S){return N.getElementById(S);}if(S.length!==undefined){var T=[];for(var R=0,Q=S.length;R<Q;++R){T[T.length]=B.Dom.get(S[R]);}return T;}return S;},getStyle:function(Q,S){S=O(S);var R=function(T){return L(T,S);};return B.Dom.batch(Q,R,B.Dom,true);},setStyle:function(Q,S,T){S=O(S);var R=function(U){J(U,S,T);};B.Dom.batch(Q,R,B.Dom,true);},getXY:function(Q){var R=function(S){if((S.parentNode===null||S.offsetParent===null||this.getStyle(S,"display")=="none")&&S!=S.ownerDocument.body){return false;}return I(S);};return B.Dom.batch(Q,R,B.Dom,true);},getX:function(Q){var R=function(S){return B.Dom.getXY(S)[0];};return B.Dom.batch(Q,R,B.Dom,true);},getY:function(Q){var R=function(S){return B.Dom.getXY(S)[1];};return B.Dom.batch(Q,R,B.Dom,true);},setXY:function(Q,T,S){var R=function(W){var V=this.getStyle(W,"position");if(V=="static"){this.setStyle(W,"position","relative");V="relative";}var Y=this.getXY(W);if(Y===false){return false;}var X=[parseInt(this.getStyle(W,"left"),10),parseInt(this.getStyle(W,"top"),10)];if(isNaN(X[0])){X[0]=(V=="relative")?0:W.offsetLeft;}if(isNaN(X[1])){X[1]=(V=="relative")?0:W.offsetTop;}if(T[0]!==null){W.style.left=T[0]-Y[0]+X[0]+"px";}if(T[1]!==null){W.style.top=T[1]-Y[1]+X[1]+"px";}if(!S){var U=this.getXY(W);if((T[0]!==null&&U[0]!=T[0])||(T[1]!==null&&U[1]!=T[1])){this.setXY(W,T,true);}}};B.Dom.batch(Q,R,B.Dom,true);},setX:function(R,Q){B.Dom.setXY(R,[Q,null]);},setY:function(Q,R){B.Dom.setXY(Q,[null,R]);},getRegion:function(Q){var R=function(S){if((S.parentNode===null||S.offsetParent===null||this.getStyle(S,"display")=="none")&&S!=N.body){return false;}var T=B.Region.getRegion(S);return T;};return B.Dom.batch(Q,R,B.Dom,true);},getClientWidth:function(){return B.Dom.getViewportWidth();},getClientHeight:function(){return B.Dom.getViewportHeight();},getElementsByClassName:function(U,Y,V,W){Y=Y||"*";V=(V)?B.Dom.get(V):null||N;if(!V){return[];}var R=[],Q=V.getElementsByTagName(Y),X=P(U);for(var S=0,T=Q.length;S<T;++S){if(X.test(Q[S].className)){R[R.length]=Q[S];if(W){W.call(Q[S],Q[S]);}}}return R;},hasClass:function(S,R){var Q=P(R);var T=function(U){return Q.test(U.className);};return B.Dom.batch(S,T,B.Dom,true);},addClass:function(R,Q){var S=function(T){if(this.hasClass(T,Q)){return false;}T.className=YAHOO.lang.trim([T.className,Q].join(" "));return true;};return B.Dom.batch(R,S,B.Dom,true);},removeClass:function(S,R){var Q=P(R);var T=function(U){if(!this.hasClass(U,R)){return false;}var V=U.className;U.className=V.replace(Q," ");if(this.hasClass(U,R)){this.removeClass(U,R);}U.className=YAHOO.lang.trim(U.className);return true;};return B.Dom.batch(S,T,B.Dom,true);},replaceClass:function(T,R,Q){if(!Q||R===Q){return false;}var S=P(R);var U=function(V){if(!this.hasClass(V,R)){this.addClass(V,Q);return true;}V.className=V.className.replace(S," "+Q+" ");if(this.hasClass(V,R)){this.replaceClass(V,R,Q);}V.className=YAHOO.lang.trim(V.className);return true;};return B.Dom.batch(T,U,B.Dom,true);},generateId:function(Q,S){S=S||"yui-gen";var R=function(T){if(T&&T.id){return T.id;}var U=S+H++;if(T){T.id=U;}return U;};return B.Dom.batch(Q,R,B.Dom,true)||R.apply(B.Dom,arguments);},isAncestor:function(Q,R){Q=B.Dom.get(Q);R=B.Dom.get(R);if(!Q||!R){return false;}if(Q.contains&&R.nodeType&&!M){return Q.contains(R);}else{if(Q.compareDocumentPosition&&R.nodeType){return !!(Q.compareDocumentPosition(R)&16);}else{if(R.nodeType){return !!this.getAncestorBy(R,function(S){return S==Q;});}}}return false;},inDocument:function(Q){return this.isAncestor(N.documentElement,Q);},getElementsBy:function(X,R,S,U){R=R||"*";S=(S)?B.Dom.get(S):null||N;if(!S){return[];}var T=[],W=S.getElementsByTagName(R);for(var V=0,Q=W.length;V<Q;++V){if(X(W[V])){T[T.length]=W[V];if(U){U(W[V]);}}}return T;},batch:function(U,X,W,S){U=(U&&(U.tagName||U.item))?U:B.Dom.get(U);if(!U||!X){return false;}var T=(S)?W:window;if(U.tagName||U.length===undefined){return X.call(T,U,W);}var V=[];for(var R=0,Q=U.length;R<Q;++R){V[V.length]=X.call(T,U[R],W);}return V;},getDocumentHeight:function(){var R=(N.compatMode!="CSS1Compat")?N.body.scrollHeight:N.documentElement.scrollHeight;var Q=Math.max(R,B.Dom.getViewportHeight());return Q;},getDocumentWidth:function(){var R=(N.compatMode!="CSS1Compat")?N.body.scrollWidth:N.documentElement.scrollWidth;var Q=Math.max(R,B.Dom.getViewportWidth());return Q;},getViewportHeight:function(){var Q=self.innerHeight;var R=N.compatMode;if((R||G)&&!C){Q=(R=="CSS1Compat")?N.documentElement.clientHeight:N.body.clientHeight;
}return Q;},getViewportWidth:function(){var Q=self.innerWidth;var R=N.compatMode;if(R||G){Q=(R=="CSS1Compat")?N.documentElement.clientWidth:N.body.clientWidth;}return Q;},getAncestorBy:function(Q,R){while(Q=Q.parentNode){if(D(Q,R)){return Q;}}return null;},getAncestorByClassName:function(R,Q){R=B.Dom.get(R);if(!R){return null;}var S=function(T){return B.Dom.hasClass(T,Q);};return B.Dom.getAncestorBy(R,S);},getAncestorByTagName:function(R,Q){R=B.Dom.get(R);if(!R){return null;}var S=function(T){return T.tagName&&T.tagName.toUpperCase()==Q.toUpperCase();};return B.Dom.getAncestorBy(R,S);},getPreviousSiblingBy:function(Q,R){while(Q){Q=Q.previousSibling;if(D(Q,R)){return Q;}}return null;},getPreviousSibling:function(Q){Q=B.Dom.get(Q);if(!Q){return null;}return B.Dom.getPreviousSiblingBy(Q);},getNextSiblingBy:function(Q,R){while(Q){Q=Q.nextSibling;if(D(Q,R)){return Q;}}return null;},getNextSibling:function(Q){Q=B.Dom.get(Q);if(!Q){return null;}return B.Dom.getNextSiblingBy(Q);},getFirstChildBy:function(Q,S){var R=(D(Q.firstChild,S))?Q.firstChild:null;return R||B.Dom.getNextSiblingBy(Q.firstChild,S);},getFirstChild:function(Q,R){Q=B.Dom.get(Q);if(!Q){return null;}return B.Dom.getFirstChildBy(Q);},getLastChildBy:function(Q,S){if(!Q){return null;}var R=(D(Q.lastChild,S))?Q.lastChild:null;return R||B.Dom.getPreviousSiblingBy(Q.lastChild,S);},getLastChild:function(Q){Q=B.Dom.get(Q);return B.Dom.getLastChildBy(Q);},getChildrenBy:function(R,T){var S=B.Dom.getFirstChildBy(R,T);var Q=S?[S]:[];B.Dom.getNextSiblingBy(S,function(U){if(!T||T(U)){Q[Q.length]=U;}return false;});return Q;},getChildren:function(Q){Q=B.Dom.get(Q);if(!Q){}return B.Dom.getChildrenBy(Q);},getDocumentScrollLeft:function(Q){Q=Q||N;return Math.max(Q.documentElement.scrollLeft,Q.body.scrollLeft);},getDocumentScrollTop:function(Q){Q=Q||N;return Math.max(Q.documentElement.scrollTop,Q.body.scrollTop);},insertBefore:function(R,Q){R=B.Dom.get(R);Q=B.Dom.get(Q);if(!R||!Q||!Q.parentNode){return null;}return Q.parentNode.insertBefore(R,Q);},insertAfter:function(R,Q){R=B.Dom.get(R);Q=B.Dom.get(Q);if(!R||!Q||!Q.parentNode){return null;}if(Q.nextSibling){return Q.parentNode.insertBefore(R,Q.nextSibling);}else{return Q.parentNode.appendChild(R);}},getClientRegion:function(){var S=B.Dom.getDocumentScrollTop(),R=B.Dom.getDocumentScrollLeft(),T=B.Dom.getViewportWidth()+R,Q=B.Dom.getViewportHeight()+S;return new B.Region(S,T,Q,R);}};var I=function(){if(N.documentElement.getBoundingClientRect){return function(R){var S=R.getBoundingClientRect();var Q=R.ownerDocument;return[S.left+B.Dom.getDocumentScrollLeft(Q),S.top+B.Dom.getDocumentScrollTop(Q)];};}else{return function(S){var T=[S.offsetLeft,S.offsetTop];var R=S.offsetParent;var Q=(M&&B.Dom.getStyle(S,"position")=="absolute"&&S.offsetParent==S.ownerDocument.body);if(R!=S){while(R){T[0]+=R.offsetLeft;T[1]+=R.offsetTop;if(!Q&&M&&B.Dom.getStyle(R,"position")=="absolute"){Q=true;}R=R.offsetParent;}}if(Q){T[0]-=S.ownerDocument.body.offsetLeft;T[1]-=S.ownerDocument.body.offsetTop;}R=S.parentNode;while(R.tagName&&!E.ROOT_TAG.test(R.tagName)){if(B.Dom.getStyle(R,"display").search(/^inline|table-row.*$/i)){T[0]-=R.scrollLeft;T[1]-=R.scrollTop;}R=R.parentNode;}return T;};}}();})();YAHOO.util.Region=function(C,D,A,B){this.top=C;this[1]=C;this.right=D;this.bottom=A;this.left=B;this[0]=B;};YAHOO.util.Region.prototype.contains=function(A){return(A.left>=this.left&&A.right<=this.right&&A.top>=this.top&&A.bottom<=this.bottom);};YAHOO.util.Region.prototype.getArea=function(){return((this.bottom-this.top)*(this.right-this.left));};YAHOO.util.Region.prototype.intersect=function(E){var C=Math.max(this.top,E.top);var D=Math.min(this.right,E.right);var A=Math.min(this.bottom,E.bottom);var B=Math.max(this.left,E.left);if(A>=C&&D>=B){return new YAHOO.util.Region(C,D,A,B);}else{return null;}};YAHOO.util.Region.prototype.union=function(E){var C=Math.min(this.top,E.top);var D=Math.max(this.right,E.right);var A=Math.max(this.bottom,E.bottom);var B=Math.min(this.left,E.left);return new YAHOO.util.Region(C,D,A,B);};YAHOO.util.Region.prototype.toString=function(){return("Region {top: "+this.top+", right: "+this.right+", bottom: "+this.bottom+", left: "+this.left+"}");};YAHOO.util.Region.getRegion=function(D){var F=YAHOO.util.Dom.getXY(D);var C=F[1];var E=F[0]+D.offsetWidth;var A=F[1]+D.offsetHeight;var B=F[0];return new YAHOO.util.Region(C,E,A,B);};YAHOO.util.Point=function(A,B){if(YAHOO.lang.isArray(A)){B=A[1];A=A[0];}this.x=this.right=this.left=this[0]=A;this.y=this.top=this.bottom=this[1]=B;};YAHOO.util.Point.prototype=new YAHOO.util.Region();YAHOO.register("dom",YAHOO.util.Dom,{version:"2.4.1",build:"742"});YAHOO.util.CustomEvent=function(D,B,C,A){this.type=D;this.scope=B||window;this.silent=C;this.signature=A||YAHOO.util.CustomEvent.LIST;this.subscribers=[];if(!this.silent){}var E="_YUICEOnSubscribe";if(D!==E){this.subscribeEvent=new YAHOO.util.CustomEvent(E,this,true);}this.lastError=null;};YAHOO.util.CustomEvent.LIST=0;YAHOO.util.CustomEvent.FLAT=1;YAHOO.util.CustomEvent.prototype={subscribe:function(B,C,A){if(!B){throw new Error("Invalid callback for subscriber to '"+this.type+"'");}if(this.subscribeEvent){this.subscribeEvent.fire(B,C,A);}this.subscribers.push(new YAHOO.util.Subscriber(B,C,A));},unsubscribe:function(D,F){if(!D){return this.unsubscribeAll();}var E=false;for(var B=0,A=this.subscribers.length;B<A;++B){var C=this.subscribers[B];if(C&&C.contains(D,F)){this._delete(B);E=true;}}return E;},fire:function(){var D=this.subscribers.length;if(!D&&this.silent){return true;}var H=[],F=true,C,I=false;for(C=0;C<arguments.length;++C){H.push(arguments[C]);}if(!this.silent){}for(C=0;C<D;++C){var L=this.subscribers[C];if(!L){I=true;}else{if(!this.silent){}var K=L.getScope(this.scope);if(this.signature==YAHOO.util.CustomEvent.FLAT){var A=null;if(H.length>0){A=H[0];}try{F=L.fn.call(K,A,L.obj);}catch(E){this.lastError=E;}}else{try{F=L.fn.call(K,this.type,H,L.obj);}catch(G){this.lastError=G;}}if(false===F){if(!this.silent){}return false;}}}if(I){var J=[],B=this.subscribers;for(C=0,D=B.length;C<D;C=C+1){J.push(B[C]);}this.subscribers=J;}return true;},unsubscribeAll:function(){for(var B=0,A=this.subscribers.length;B<A;++B){this._delete(A-1-B);}this.subscribers=[];return B;},_delete:function(A){var B=this.subscribers[A];if(B){delete B.fn;delete B.obj;}this.subscribers[A]=null;},toString:function(){return"CustomEvent: '"+this.type+"', scope: "+this.scope;}};YAHOO.util.Subscriber=function(B,C,A){this.fn=B;this.obj=YAHOO.lang.isUndefined(C)?null:C;this.override=A;};YAHOO.util.Subscriber.prototype.getScope=function(A){if(this.override){if(this.override===true){return this.obj;}else{return this.override;}}return A;};YAHOO.util.Subscriber.prototype.contains=function(A,B){if(B){return(this.fn==A&&this.obj==B);}else{return(this.fn==A);}};YAHOO.util.Subscriber.prototype.toString=function(){return"Subscriber { obj: "+this.obj+", override: "+(this.override||"no")+" }";};if(!YAHOO.util.Event){YAHOO.util.Event=function(){var H=false;var I=[];var J=[];var G=[];var E=[];var C=0;var F=[];var B=[];var A=0;var D={63232:38,63233:40,63234:37,63235:39,63276:33,63277:34,25:9};return{POLL_RETRYS:4000,POLL_INTERVAL:10,EL:0,TYPE:1,FN:2,WFN:3,UNLOAD_OBJ:3,ADJ_SCOPE:4,OBJ:5,OVERRIDE:6,lastError:null,isSafari:YAHOO.env.ua.webkit,webkit:YAHOO.env.ua.webkit,isIE:YAHOO.env.ua.ie,_interval:null,_dri:null,DOMReady:false,startInterval:function(){if(!this._interval){var K=this;var L=function(){K._tryPreloadAttach();};this._interval=setInterval(L,this.POLL_INTERVAL);}},onAvailable:function(P,M,Q,O,N){var K=(YAHOO.lang.isString(P))?[P]:P;for(var L=0;L<K.length;L=L+1){F.push({id:K[L],fn:M,obj:Q,override:O,checkReady:N});}C=this.POLL_RETRYS;this.startInterval();},onContentReady:function(M,K,N,L){this.onAvailable(M,K,N,L,true);},onDOMReady:function(K,M,L){if(this.DOMReady){setTimeout(function(){var N=window;if(L){if(L===true){N=M;}else{N=L;}}K.call(N,"DOMReady",[],M);},0);}else{this.DOMReadyEvent.subscribe(K,M,L);}},addListener:function(M,K,V,Q,L){if(!V||!V.call){return false;}if(this._isValidCollection(M)){var W=true;for(var R=0,T=M.length;R<T;++R){W=this.on(M[R],K,V,Q,L)&&W;}return W;}else{if(YAHOO.lang.isString(M)){var P=this.getEl(M);if(P){M=P;}else{this.onAvailable(M,function(){YAHOO.util.Event.on(M,K,V,Q,L);});return true;}}}if(!M){return false;}if("unload"==K&&Q!==this){J[J.length]=[M,K,V,Q,L];return true;}var Y=M;if(L){if(L===true){Y=Q;}else{Y=L;}}var N=function(Z){return V.call(Y,YAHOO.util.Event.getEvent(Z,M),Q);};var X=[M,K,V,N,Y,Q,L];var S=I.length;I[S]=X;if(this.useLegacyEvent(M,K)){var O=this.getLegacyIndex(M,K);if(O==-1||M!=G[O][0]){O=G.length;B[M.id+K]=O;G[O]=[M,K,M["on"+K]];E[O]=[];M["on"+K]=function(Z){YAHOO.util.Event.fireLegacyEvent(YAHOO.util.Event.getEvent(Z),O);};}E[O].push(X);}else{try{this._simpleAdd(M,K,N,false);}catch(U){this.lastError=U;this.removeListener(M,K,V);return false;}}return true;},fireLegacyEvent:function(O,M){var Q=true,K,S,R,T,P;S=E[M];for(var L=0,N=S.length;L<N;++L){R=S[L];if(R&&R[this.WFN]){T=R[this.ADJ_SCOPE];P=R[this.WFN].call(T,O);Q=(Q&&P);}}K=G[M];if(K&&K[2]){K[2](O);}return Q;},getLegacyIndex:function(L,M){var K=this.generateId(L)+M;if(typeof B[K]=="undefined"){return -1;}else{return B[K];}},useLegacyEvent:function(L,M){if(this.webkit&&("click"==M||"dblclick"==M)){var K=parseInt(this.webkit,10);if(!isNaN(K)&&K<418){return true;}}return false;},removeListener:function(L,K,T){var O,R,V;if(typeof L=="string"){L=this.getEl(L);}else{if(this._isValidCollection(L)){var U=true;for(O=0,R=L.length;O<R;++O){U=(this.removeListener(L[O],K,T)&&U);}return U;}}if(!T||!T.call){return this.purgeElement(L,false,K);}if("unload"==K){for(O=0,R=J.length;O<R;O++){V=J[O];if(V&&V[0]==L&&V[1]==K&&V[2]==T){J[O]=null;return true;}}return false;}var P=null;var Q=arguments[3];if("undefined"===typeof Q){Q=this._getCacheIndex(L,K,T);}if(Q>=0){P=I[Q];}if(!L||!P){return false;}if(this.useLegacyEvent(L,K)){var N=this.getLegacyIndex(L,K);var M=E[N];if(M){for(O=0,R=M.length;O<R;++O){V=M[O];if(V&&V[this.EL]==L&&V[this.TYPE]==K&&V[this.FN]==T){M[O]=null;break;}}}}else{try{this._simpleRemove(L,K,P[this.WFN],false);}catch(S){this.lastError=S;return false;}}delete I[Q][this.WFN];delete I[Q][this.FN];I[Q]=null;return true;},getTarget:function(M,L){var K=M.target||M.srcElement;return this.resolveTextNode(K);},resolveTextNode:function(K){if(K&&3==K.nodeType){return K.parentNode;}else{return K;}},getPageX:function(L){var K=L.pageX;if(!K&&0!==K){K=L.clientX||0;if(this.isIE){K+=this._getScrollLeft();}}return K;},getPageY:function(K){var L=K.pageY;if(!L&&0!==L){L=K.clientY||0;if(this.isIE){L+=this._getScrollTop();}}return L;},getXY:function(K){return[this.getPageX(K),this.getPageY(K)];
},getRelatedTarget:function(L){var K=L.relatedTarget;if(!K){if(L.type=="mouseout"){K=L.toElement;}else{if(L.type=="mouseover"){K=L.fromElement;}}}return this.resolveTextNode(K);},getTime:function(M){if(!M.time){var L=new Date().getTime();try{M.time=L;}catch(K){this.lastError=K;return L;}}return M.time;},stopEvent:function(K){this.stopPropagation(K);this.preventDefault(K);},stopPropagation:function(K){if(K.stopPropagation){K.stopPropagation();}else{K.cancelBubble=true;}},preventDefault:function(K){if(K.preventDefault){K.preventDefault();}else{K.returnValue=false;}},getEvent:function(M,K){var L=M||window.event;if(!L){var N=this.getEvent.caller;while(N){L=N.arguments[0];if(L&&Event==L.constructor){break;}N=N.caller;}}return L;},getCharCode:function(L){var K=L.keyCode||L.charCode||0;if(YAHOO.env.ua.webkit&&(K in D)){K=D[K];}return K;},_getCacheIndex:function(O,P,N){for(var M=0,L=I.length;M<L;++M){var K=I[M];if(K&&K[this.FN]==N&&K[this.EL]==O&&K[this.TYPE]==P){return M;}}return -1;},generateId:function(K){var L=K.id;if(!L){L="yuievtautoid-"+A;++A;K.id=L;}return L;},_isValidCollection:function(L){try{return(L&&typeof L!=="string"&&L.length&&!L.tagName&&!L.alert&&typeof L[0]!=="undefined");}catch(K){return false;}},elCache:{},getEl:function(K){return(typeof K==="string")?document.getElementById(K):K;},clearCache:function(){},DOMReadyEvent:new YAHOO.util.CustomEvent("DOMReady",this),_load:function(L){if(!H){H=true;var K=YAHOO.util.Event;K._ready();K._tryPreloadAttach();}},_ready:function(L){var K=YAHOO.util.Event;if(!K.DOMReady){K.DOMReady=true;K.DOMReadyEvent.fire();K._simpleRemove(document,"DOMContentLoaded",K._ready);}},_tryPreloadAttach:function(){if(this.locked){return false;}if(this.isIE){if(!this.DOMReady){this.startInterval();return false;}}this.locked=true;var P=!H;if(!P){P=(C>0);}var O=[];var Q=function(S,T){var R=S;if(T.override){if(T.override===true){R=T.obj;}else{R=T.override;}}T.fn.call(R,T.obj);};var L,K,N,M;for(L=0,K=F.length;L<K;++L){N=F[L];if(N&&!N.checkReady){M=this.getEl(N.id);if(M){Q(M,N);F[L]=null;}else{O.push(N);}}}for(L=0,K=F.length;L<K;++L){N=F[L];if(N&&N.checkReady){M=this.getEl(N.id);if(M){if(H||M.nextSibling){Q(M,N);F[L]=null;}}else{O.push(N);}}}C=(O.length===0)?0:C-1;if(P){this.startInterval();}else{clearInterval(this._interval);this._interval=null;}this.locked=false;return true;},purgeElement:function(O,P,R){var M=(YAHOO.lang.isString(O))?this.getEl(O):O;var Q=this.getListeners(M,R),N,K;if(Q){for(N=0,K=Q.length;N<K;++N){var L=Q[N];this.removeListener(M,L.type,L.fn,L.index);}}if(P&&M&&M.childNodes){for(N=0,K=M.childNodes.length;N<K;++N){this.purgeElement(M.childNodes[N],P,R);}}},getListeners:function(M,K){var P=[],L;if(!K){L=[I,J];}else{if(K==="unload"){L=[J];}else{L=[I];}}var R=(YAHOO.lang.isString(M))?this.getEl(M):M;for(var O=0;O<L.length;O=O+1){var T=L[O];if(T&&T.length>0){for(var Q=0,S=T.length;Q<S;++Q){var N=T[Q];if(N&&N[this.EL]===R&&(!K||K===N[this.TYPE])){P.push({type:N[this.TYPE],fn:N[this.FN],obj:N[this.OBJ],adjust:N[this.OVERRIDE],scope:N[this.ADJ_SCOPE],index:Q});}}}}return(P.length)?P:null;},_unload:function(R){var Q=YAHOO.util.Event,O,N,L,K,M;for(O=0,K=J.length;O<K;++O){L=J[O];if(L){var P=window;if(L[Q.ADJ_SCOPE]){if(L[Q.ADJ_SCOPE]===true){P=L[Q.UNLOAD_OBJ];}else{P=L[Q.ADJ_SCOPE];}}L[Q.FN].call(P,Q.getEvent(R,L[Q.EL]),L[Q.UNLOAD_OBJ]);J[O]=null;L=null;P=null;}}J=null;if(YAHOO.env.ua.ie&&I&&I.length>0){N=I.length;while(N){M=N-1;L=I[M];if(L){Q.removeListener(L[Q.EL],L[Q.TYPE],L[Q.FN],M);}N--;}L=null;}G=null;Q._simpleRemove(window,"unload",Q._unload);},_getScrollLeft:function(){return this._getScroll()[1];},_getScrollTop:function(){return this._getScroll()[0];},_getScroll:function(){var K=document.documentElement,L=document.body;if(K&&(K.scrollTop||K.scrollLeft)){return[K.scrollTop,K.scrollLeft];}else{if(L){return[L.scrollTop,L.scrollLeft];}else{return[0,0];}}},regCE:function(){},_simpleAdd:function(){if(window.addEventListener){return function(M,N,L,K){M.addEventListener(N,L,(K));};}else{if(window.attachEvent){return function(M,N,L,K){M.attachEvent("on"+N,L);};}else{return function(){};}}}(),_simpleRemove:function(){if(window.removeEventListener){return function(M,N,L,K){M.removeEventListener(N,L,(K));};}else{if(window.detachEvent){return function(L,M,K){L.detachEvent("on"+M,K);};}else{return function(){};}}}()};}();(function(){var A=YAHOO.util.Event;A.on=A.addListener;if(A.isIE){YAHOO.util.Event.onDOMReady(YAHOO.util.Event._tryPreloadAttach,YAHOO.util.Event,true);A._dri=setInterval(function(){var C=document.createElement("p");try{C.doScroll("left");clearInterval(A._dri);A._dri=null;A._ready();C=null;}catch(B){C=null;}},A.POLL_INTERVAL);}else{if(A.webkit){A._dri=setInterval(function(){var B=document.readyState;if("loaded"==B||"complete"==B){clearInterval(A._dri);A._dri=null;A._ready();}},A.POLL_INTERVAL);}else{A._simpleAdd(document,"DOMContentLoaded",A._ready);}}A._simpleAdd(window,"load",A._load);A._simpleAdd(window,"unload",A._unload);A._tryPreloadAttach();})();}YAHOO.util.EventProvider=function(){};YAHOO.util.EventProvider.prototype={__yui_events:null,__yui_subscribers:null,subscribe:function(A,C,F,E){this.__yui_events=this.__yui_events||{};var D=this.__yui_events[A];if(D){D.subscribe(C,F,E);}else{this.__yui_subscribers=this.__yui_subscribers||{};var B=this.__yui_subscribers;if(!B[A]){B[A]=[];}B[A].push({fn:C,obj:F,override:E});}},unsubscribe:function(C,E,G){this.__yui_events=this.__yui_events||{};var A=this.__yui_events;if(C){var F=A[C];if(F){return F.unsubscribe(E,G);}}else{var B=true;for(var D in A){if(YAHOO.lang.hasOwnProperty(A,D)){B=B&&A[D].unsubscribe(E,G);}}return B;}return false;},unsubscribeAll:function(A){return this.unsubscribe(A);},createEvent:function(G,D){this.__yui_events=this.__yui_events||{};var A=D||{};var I=this.__yui_events;if(I[G]){}else{var H=A.scope||this;var E=(A.silent);var B=new YAHOO.util.CustomEvent(G,H,E,YAHOO.util.CustomEvent.FLAT);I[G]=B;if(A.onSubscribeCallback){B.subscribeEvent.subscribe(A.onSubscribeCallback);}this.__yui_subscribers=this.__yui_subscribers||{};
var F=this.__yui_subscribers[G];if(F){for(var C=0;C<F.length;++C){B.subscribe(F[C].fn,F[C].obj,F[C].override);}}}return I[G];},fireEvent:function(E,D,A,C){this.__yui_events=this.__yui_events||{};var G=this.__yui_events[E];if(!G){return null;}var B=[];for(var F=1;F<arguments.length;++F){B.push(arguments[F]);}return G.fire.apply(G,B);},hasEvent:function(A){if(this.__yui_events){if(this.__yui_events[A]){return true;}}return false;}};YAHOO.util.KeyListener=function(A,F,B,C){if(!A){}else{if(!F){}else{if(!B){}}}if(!C){C=YAHOO.util.KeyListener.KEYDOWN;}var D=new YAHOO.util.CustomEvent("keyPressed");this.enabledEvent=new YAHOO.util.CustomEvent("enabled");this.disabledEvent=new YAHOO.util.CustomEvent("disabled");if(typeof A=="string"){A=document.getElementById(A);}if(typeof B=="function"){D.subscribe(B);}else{D.subscribe(B.fn,B.scope,B.correctScope);}function E(J,I){if(!F.shift){F.shift=false;}if(!F.alt){F.alt=false;}if(!F.ctrl){F.ctrl=false;}if(J.shiftKey==F.shift&&J.altKey==F.alt&&J.ctrlKey==F.ctrl){var G;if(F.keys instanceof Array){for(var H=0;H<F.keys.length;H++){G=F.keys[H];if(G==J.charCode){D.fire(J.charCode,J);break;}else{if(G==J.keyCode){D.fire(J.keyCode,J);break;}}}}else{G=F.keys;if(G==J.charCode){D.fire(J.charCode,J);}else{if(G==J.keyCode){D.fire(J.keyCode,J);}}}}}this.enable=function(){if(!this.enabled){YAHOO.util.Event.addListener(A,C,E);this.enabledEvent.fire(F);}this.enabled=true;};this.disable=function(){if(this.enabled){YAHOO.util.Event.removeListener(A,C,E);this.disabledEvent.fire(F);}this.enabled=false;};this.toString=function(){return"KeyListener ["+F.keys+"] "+A.tagName+(A.id?"["+A.id+"]":"");};};YAHOO.util.KeyListener.KEYDOWN="keydown";YAHOO.util.KeyListener.KEYUP="keyup";YAHOO.util.KeyListener.KEY={ALT:18,BACK_SPACE:8,CAPS_LOCK:20,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,META:224,NUM_LOCK:144,PAGE_DOWN:34,PAGE_UP:33,PAUSE:19,PRINTSCREEN:44,RIGHT:39,SCROLL_LOCK:145,SHIFT:16,SPACE:32,TAB:9,UP:38};YAHOO.register("event",YAHOO.util.Event,{version:"2.4.1",build:"742"});YAHOO.register("yahoo-dom-event", YAHOO, {version: "2.4.1", build: "742"});
