function ListingsByMap(mapContainerId, dataUrl, formSuffix, summaryUrl, searchPanelId, filterPanelId) {
	
	var MAX_ZOOM_LEVEL = 19; //current max zoom level for gmaps(sat. view, map view is 17)
	var GRID_PAGE_SIZE = 50;
	var map;
	var manager;
	var initialized = false;
	var baseSummaryUrl;
	var gridUrl;
	var filterValues;
	var mapLoaded = false;
	var managerLoaded = false;
	var loadedListings = {};
	var loadedClusters = new Array();
	var zoomLevelsLoaded = new Array();
	var startZoom = 0;
	var purgeListingData = false;
	var startLat;
	var startLng;
	var containingBounds;
	var editMode = false;
	var userLatId;
	var userLngId;
	var userZoomId;
	var customStart = false;
	var gridPage = 1;
	var lastScroll = 0;
	var fetchingGridRows = false;
	var fetchingMapData = false
	var allRowsLoaded = false;
	var purgeOldRows = false;	
	var currentSort = {};
	var reverseSort = false;
	var moreListings = false;
	var imageUrls = {};
	var loadingFromUserCookie = false;
	
	this.map = function() {
		return map;
	}
	
	this.initIcons = function(icons) {		
		this.listingIcon = new google.maps.MarkerImage(
				icons.listing,
				new google.maps.Size(28,28),
				new google.maps.Point(0,0),
				new google.maps.Point(12,22)
			);

		this.cluster5Icon = new google.maps.MarkerImage(
				icons.cluster5,
				new google.maps.Size(38,38),
				new google.maps.Point(0,0),
				new google.maps.Point(16,30)
			);
			
		this.cluster10Icon = new google.maps.MarkerImage(
				icons.cluster10,
				new google.maps.Size(38,38),
				new google.maps.Point(0,0),
				new google.maps.Point(16,30)
			);		
			
		this.cluster25Icon = new google.maps.MarkerImage(
				icons.cluster25,
				new google.maps.Size(38,38),
				new google.maps.Point(0,0),
				new google.maps.Point(16,30)
			);
			
		this.cluster50Icon = new google.maps.MarkerImage(
				icons.cluster50,
				new google.maps.Size(38,38),
				new google.maps.Point(0,0),
				new google.maps.Point(16,30)
			);			
	}
	
	this.initStartCenterAndZoom = function(lat,lng,zoom) {
		startLat = lat;
		startLng = lng;
		startZoom = zoom;
	}
	
	this.startPointSpecified = function() {
		return (startLat && startLng && startZoom);
	}

	this.init = function() {
	    baseSummaryUrl = summaryUrl;
	    var mapOptions = {
	        mapTypeId: google.maps.MapTypeId.ROADMAP
	    };
	    if (this.startPointSpecified()) {
	        mapOptions['center'] = new google.maps.LatLng(startLat, startLng);
	        mapOptions['zoom'] = startZoom;
	    } else {
	        mapOptions['center'] = new google.maps.LatLng(50, 50);
	        mapOptions['zoom'] = 3;
	    }
	    map = new google.maps.Map($(mapContainerId), mapOptions);
	    
	    google.maps.event.addListenerOnce(map, "tilesloaded", function() {
	        mapLoaded = true;
	    });
	    
	    if ($(mapContainerId).addEventListener) {
	        $(mapContainerId).addEventListener('DOMMouseScroll', function(e) { e.preventDefault(); }, false);
	    }
	    else if ($(mapContainerId).attachEvent) {
	        $(mapContainerId).attachEvent('onmousewheel', function() { event.returnValue = false; });
	    }
	    
	    this.initZoom();
	    this.initMapCover();
	    this.clearListingsLoaded();
	    
	    var searchButton = $('searchButton' + formSuffix);
	    if ($(filterPanelId)) {
	        var priceHigh = document.forms[0]['priceHigh' + formSuffix];
	        if (priceHigh.selectedIndex == 0) {
	            priceHigh.selectedIndex = priceHigh.options.length - 1;
	        }
	        if ($(filterPanelId).style.display == 'block') {
	            searchButton.addEvent('click', this.filterListings.bind(this).bindWithEvent(searchButton));
	            this.initInfoPanel();
	        } else if ($('summaryDetails' + formSuffix)) {
	            $('summaryDetails' + formSuffix).style.height = "475px";
	            $$('div.SummaryToggle')[0].className += " First";
	        }
	    }
	    
	    var moreLink = $('moreListings' + formSuffix);
	    if (moreLink) {
	        moreLink.addEvent('click', this.moreListings.bind(this).bindWithEvent(moreLink));
	    }

	    if (editMode) {
	        this.handleMapData(window["_mapData" + formSuffix], this.startPointSpecified());
	    } else {
	        this.setSearchFilterValues();
	        if (this.loadState()) {
	            this.fetchMapData();
	        } else {
	            this.handleMapData(window["_mapData" + formSuffix], this.startPointSpecified());
	        }
	    }
	    google.maps.event.addListener(map, 'zoom_changed', this.onZoomEnd.bind(this));
	    google.maps.event.addListener(map, 'dragend', this.onMoveEnd.bind(this));
	}
	
	this.editModePreInit = function(latId, lngId, zoomId, autoId, customId){
		editMode = true;
		userLatId = latId;
		userLngId = lngId;
		userZoomId = zoomId;
		$(autoId).addEvent('click', this.toggleCustomStart.bindWithEvent(this,false));
		$(customId).addEvent('click', this.toggleCustomStart.bindWithEvent(this,true));
		var cb = $$('td.ShowSummary input')[0];
		var toggleOptions = function(disable){
			var els = $$('#configureMap .DisableNoGrid');
			for(var ii=0;ii<els.length;ii++){
				els[ii].disabled = disable;
			}
		}
		toggleOptions(!cb.checked);
		cb.addEvent('click', function(event){
			event = new Event(event);
			toggleOptions(!event.target.checked);
		}); 		
		customStart = !($(autoId).checked);
	}
	
	this.editModePostInit = function() {		
		if(!mapLoaded) {
			google.maps.event.addListener(map,'tilesloaded',function(){
				google.maps.event.addListener(map,'dragend',function(){
					$(userZoomId).value = this.getZoom();
					$(userLatId).value = this.getCenter().lat();
					$(userLngId).value = this.getCenter().lng();
				});	
			});
		} else {
			google.maps.event.addListener(map,'dragend',function(){
				$(userZoomId).value = this.getZoom();
				$(userLatId).value = this.getCenter().lat();
				$(userLngId).value = this.getCenter().lng();
			});	
		}
	}
	
	this.toggleCustomStart = function (e,custom){		
		if(custom){
			customStart = true;
			this.hideMapCover();						
		} else {
			customStart = false;
			this.showMapCover();
		}
	}
	
	this.initZoom = function() {
		var zoomOptions = {
                sButtonHTML:'<img src="/Images/ToolPan.png">',
                sButtonZoomingHTML:'<img src="/Images/ToolZoom.png">',
                oButtonStartingStyle:{},
                oButtonTop:280,
                oButtonLeft:11,
                oButtonStyle:{background:"#fff"},
                oButtonZoomingStyle:{background:"#fff"},
                bStickyZoom:false,
                bStartInZoom:true,
                nOverlayRemoveMS:2000
              }
        map.enableKeyDragZoom({
			visualEnabled: true,
			visualPosition: google.maps.ControlPosition.LEFT,
			visualSprite: "http://maps.gstatic.com/mapfiles/ftr/controls/dragzoom_btn.png",
			visualSize: new google.maps.Size(20, 20)
		});
	}
	
	this.initInfoPanel = function() {
		var options = {
						opacity: false,
						alwaysHide:false,
						fixedHeight:452,
						show:0,
						onActive: function(toggler, element){							
							if(element.offsetHeight == 0) {
								element.style.height = '1px';
							}
							element.style.display = 'block';
							toggler.className = toggler.className.replace(/ Closed/, " Open");
							if(toggler.className.indexOf("SummaryToggle") != -1) {
								this.summaryOpen = true;
							}    
						},
						onBackground: function(toggler, element){
								toggler.className = toggler.className.replace(/ Open/, " Closed");
								setTimeout(function(){element.style.display = 'none';},500);
								if(toggler.className.indexOf("SummaryToggle") != -1) {
									this.summaryOpen = false;
								} 
							}
					    }
		this.infoAccordion = new Accordion($(searchPanelId).getElementsByClassName('Title'),$(searchPanelId).getElementsByClassName('Box'),options);
		//create slides for checkbox sections, attach events
		var expandables = $$('div.Expandable');
		var statusSlide = new Fx.Slide(expandables[0],{duration:250});
		var statusRadios = $$(document.forms[0]['specifyStatus'+formSuffix]);
		statusRadios[0].addEvent('click',function(){this.slideOut();}.bind(statusSlide));
		statusRadios[1].addEvent('click',function(){this.slideIn();}.bind(statusSlide));
		if(statusRadios[0].checked){
			statusSlide.hide();
		}		
		var typeSlide = new Fx.Slide(expandables[1],{duration:250});
		var typeRadios = $$(document.forms[0]['specifyTypes'+formSuffix]);
		typeRadios[0].addEvent('click',function(){this.slideOut();}.bind(typeSlide));
		typeRadios[1].addEvent('click',function(){this.slideIn();}.bind(typeSlide));
		if(typeRadios[0].checked){
			typeSlide.hide();
		}		
		//attach validation events to price lists
		$(document.forms[0]['priceLow' + formSuffix]).addEvent('change',function(){this.validatePrice();}.bind(this));	
		$(document.forms[0]['priceHigh' + formSuffix]).addEvent('change',function(){this.validatePrice();}.bind(this));	
		//attach validation events to status,style,type checkboxes
		$$(document.forms[0]['status' + formSuffix]).each(function(item){item.addEvent('click',function(){this.validateStatus();return true;}.bind(this))}.bind(this));
		$$(document.forms[0]['type' + formSuffix]).each(function(item){item.addEvent('click',function(){this.validateType();return true;}.bind(this))}.bind(this));
	}
	
	this.initMapCover = function(){
		var coverDiv = $(document.createElement('div'));
		coverDiv.id = 'mapCover' + formSuffix;
		coverDiv.className = 'MapCover';		
		map.getDiv().appendChild(coverDiv);
		coverDiv.style.backgroundColor = '#000';
		coverDiv.style.width = coverDiv.offsetParent.offsetWidth;
		coverDiv.style.height = coverDiv.offsetParent.offsetHeight;
		coverDiv.setOpacity(.2);
	}
	
	this.initSummaryGrid = function(url, defaultSort, sortOrders, imgUrls) {
		gridUrl = url;
		imageUrls = imgUrls;
		var summaryGrid = $('summaryGrid' + formSuffix);
		function onScroll(event){
			event = new Event(event);
			var scrollBottom = (event.target.scrollTop + event.target.offsetHeight);
			var scrollingDown = (lastScroll < scrollBottom);
			var scrollThreshold = event.target.scrollHeight-2*event.target.offsetHeight;
			if(!fetchingGridRows && !allRowsLoaded && scrollingDown && (scrollBottom > scrollThreshold)) {
				var me = this;
				var qs = Object.toQueryString(currentSort);
				window.setTimeout(function(){me.getSummaryGridPage(false,qs);},500);
				var loadingRow = document.createElement('tr');
				var cell = document.createElement('td');
				cell.innerHTML = '<div class="LoadingListings"></div>';
				cell.colSpan = 99;
				loadingRow.appendChild(cell);
				summaryGrid.tBodies[0].appendChild(loadingRow);
				fetchingGridRows = true;
			}
		}
		if(summaryGrid) {		
			//ie scrolling is on div, moz is on tbody, so have to wire events to both
			$(summaryGrid.parentNode).addEvent('scroll',onScroll.bind(this));
			$(summaryGrid.tBodies[0]).addEvent('scroll',onScroll.bind(this));
			summaryGrid.startHeight = summaryGrid.parentNode.offsetHeight;	
			summaryGrid.tBodies[0].startHeight = summaryGrid.tBodies[0].offsetHeight;
		} 		
		var sortLinks = $$('#summaryGrid' + formSuffix + ' a');
		for(var ii=0;ii<sortLinks.length;ii++){
			var sortBy = sortLinks[ii].className;
			var sortOrder = sortOrders[sortBy];
			if(sortBy == defaultSort.SortBy) {
				sortLinks[ii].style.paddingRight = '14px';
				sortLinks[ii].style.backgroundImage = 'url('+ imageUrls[defaultSort.SortOrder] + ')';
				reverseSort = (defaultSort.SortOrder != sortOrder);
			}
			sortLinks[ii].className = "SortLink";
			sortLinks[ii].naturalSort = sortOrder;
			sortLinks[ii].sortBy = sortBy;
			sortLinks[ii].addEvent('click',this.sortGrid.bind(this));
		}
		this.getSummaryGridPage();
	}
	
	this.showMapCover = function(){
		var coverDiv = $('mapCover' + formSuffix);
		coverDiv.style.display = 'block';
		coverDiv.style.width = coverDiv.offsetParent.offsetWidth;
		coverDiv.style.height = coverDiv.offsetParent.offsetHeight;
		this.map().setOptions({scrollwheel: false});
	}
	
	this.hideMapCover = function(){
		$('mapCover' + formSuffix).style.display = 'none';
		this.map().setOptions({scrollwheel: true});
	}
	
	this.listingsLoadedFor = function(zoom) {
		return zoomLevelsLoaded[zoom];
	}
	
	this.setListingsLoadedFor = function(zoom,clustered,unload) {
		zoomLevelsLoaded[zoom] = {loaded:!unload,clustered:clustered};
	}
	
	this.clearListingsLoaded = function() {
		for(var ii=0;ii<=MAX_ZOOM_LEVEL;ii++){
			zoomLevelsLoaded[ii] = {loaded:false,clustered:false};
		}
	}
	
	this.callbackRequiredFor = function(zoom) {
		if(this.listingsLoadedFor(zoom) && this.listingsLoadedFor(zoom).loaded) {
			return false;
		} else {
			for(var ii=zoom;ii>=0;ii--) {
				var zoomLevel = this.listingsLoadedFor(ii);
				if(zoomLevel && zoomLevel.loaded && !zoomLevel.clustered) {
					return false;
				}
			}
		}
		return true;
	}
	
	this.onZoomEnd = function(oldZoom, newZoom) {
	    newZoom = map.getZoom();
		if(this.callbackRequiredFor(newZoom)){
			purgeListingData = true;
			this.fetchMapData();
		}
	}
	
	this.onMoveEnd = function() {
		if(this.listingsLoadedFor(this.map().getZoom())){
			this.updateCount();
		}
		if(initialized) {
			this.saveState();
		}
	}

	this.fetchMapData = function(params) {
	    var fv = this.getSearchFilterValues();
	    var qs = (fv) ? '&' + fv : '';
	    qs = (params) ? qs + '&' + params : qs;
	    qs = (moreListings) ? qs + '&ml=true' : qs;
	    if (mapLoaded || loadingFromUserCookie) {
	        // when loadingFromUserCookie == true, it's possible that the map is not actually loaded yet after 
	        // calling setCenter and setZoom in the call to loadState, so just retry after the timeout
	        if (!mapLoaded) {
	            var that = this;
	            setTimeout(function() { that.fetchMapData(); }, 100);
	            return;
	        }
	        var bounds = map.getBounds();
	        var boundsParams = new Object();
	        boundsParams.swLat = bounds.getSouthWest().lat();
	        boundsParams.swLng = bounds.getSouthWest().lng();
	        boundsParams.neLat = bounds.getNorthEast().lat();
	        boundsParams.neLng = bounds.getNorthEast().lng();
	        boundsParams.zoom = map.getZoom();
	        qs = qs + '&' + Object.toQueryString(boundsParams);
	    }
	    var url = dataUrl + qs;
	    if (this.dataRequest && this.dataRequest.running) {
	        this.dataRequest.cancel();
	    }
	    fetchingMapData = true;
	    this.dataRequest = new Ajax(url, { method: 'get', autoCancel: true, onComplete: this.handleMapData.bind(this) });
	    this.dataRequest.request();
	    var countLabel = $('mapCount' + formSuffix);
	    if (countLabel) {
	        countLabel.innerHTML = '&nbsp;Loading listings...&nbsp;';
	    }
	}

	this.handleMapData = function(data, positionAlreadySet) {
	    fetchingMapData = false;
	    var mapData = Json.evaluate(data);
	    if (mapData.bounds) {
	        var sw = new google.maps.LatLng(mapData.bounds.southWest.lat, mapData.bounds.southWest.lng);
	        var ne = new google.maps.LatLng(mapData.bounds.northEast.lat, mapData.bounds.northEast.lng);
	        if (!mapLoaded && !positionAlreadySet && !loadingFromUserCookie) {
	            map.fitBounds(new google.maps.LatLngBounds(sw, ne));
	        }
	    }
	    loadingFromUserCookie = false;
	    var zoom = (mapData.zoom) ? mapData.zoom : this.map().getZoom();
	    this.addMarkersToMap(this.createMarkers(mapData.markers), zoom);
	    this.hideMapCover();
	    if (editMode) {
	        $(userLatId).value = map.getCenter().lat();
	        $(userLngId).value = map.getCenter().lng();
	        $(userZoomId).value = map.getZoom();
	        if (!customStart) {
	            this.showMapCover();
	        }
	    }
	    initialized = true;
	    var that = this;
	    // this is only here to fix the count not working on initial map load in some cases, otherwise should not be neccessary
	    setTimeout(function() { that.onMoveEnd() }, 250);
	}
	
	this.fetchSummary = function(listingId) {
		$('summaryDetails' + formSuffix).innerHTML = '<div class="Loading"></div>';
		if(this.infoAccordion && !this.infoAccordion.summaryOpen) {
			this.infoAccordion.display(1);
			this.infoAccordion.summaryOpen = true;
		}
		if(this.summaryRequest && this.summaryRequest.running) {
			this.summaryRequest.cancel();
		}
		
		var url = baseSummaryUrl + "&lid=" + listingId;
		this.summaryRequest = new Ajax(url,{method:'get',onComplete:this.handleSummary.bind(this)});
        this.summaryRequest.request();
	}
	
	this.handleSummary = function(summary,marker) {
		$('summaryDetails' + formSuffix).innerHTML = summary;
	}
	
	this.fetchGridRows = function(params) {
			var fv = this.getSearchFilterValues();
			var qs = (fv) ? '&' + fv : '';
			qs = (params) ? qs + '&' + params : qs;
			qs = (gridPage > 1) ? qs + '&Page=' + gridPage : qs;
			qs = (moreListings) ? qs + '&ml=true' : qs;
			var url = gridUrl + qs;
			if(this.gridRequest && this.gridRequest.running) {
				this.gridRequest.cancel();
			}
			this.gridRequest = new Ajax(url,{method:'get',autoCancel:true,onComplete: this.handleGridRows.bind(this)});
			this.gridRequest.request();
	}
	
	this.handleGridRows = function(tableData) {
		var summaryPanel = $('summaryPanel' + formSuffix);				
		var summaryGrid = $('summaryGrid' + formSuffix);				
		if(fetchingGridRows) {//if fetching from scrolling, delete loading row
			summaryGrid.deleteRow(summaryGrid.rows.length-1);
		}
		fetchingGridRows = false;				
		var noListings = $('noListings' + formSuffix);
		if(purgeOldRows) {
			var oldRowCount = summaryGrid.rows.length;
			for(var ii=1;ii<oldRowCount;ii++) {
				summaryGrid.deleteRow(1);
			}
			purgeOldRows = false;
		}
		var currentRowCount = summaryGrid.rows.length;
		var div = document.createElement('DIV');
		div.innerHTML = tableData;
		var table = div.childNodes[0];		
		if(table && table.nodeName.toLowerCase() == 'table') {
			noListings.style.display = 'none';
			summaryPanel.style.display = 'block';
			summaryPanel.style.borderLeft = 'none';
			try {//ie throws an error here
				summaryGrid.tBodies[0].style.display = 'table-row-group';
			} catch (ex) {
				summaryGrid.tBodies[0].style.display = 'block';
			}
			var newRowCount = table.rows.length;
			allRowsLoaded = (newRowCount < GRID_PAGE_SIZE); 
			var rowHeight = 0;
			for(var ii=0;ii<newRowCount;ii++) {				
				summaryGrid.tBodies[0].appendChild(table.rows[0]);
				var newRow = $(summaryGrid.rows[ii+currentRowCount]);
				newRow.title = 'Click to view listing on map';
				rowHeight += Math.max(newRow.offsetHeight,0);
				newRow.addEvent('mouseover',function(ev){
					var event = new Event(ev);
					var t = event.target;
					for(var ii=0;ii<50;ii++){
						if(t.tagName.toLowerCase() == 'tr' && (t.className.indexOf("Item") != -1)) break;
						t = t.parentNode;
					}	
					t.style.backgroundColor = '#eee';
					}.bind(this));
				newRow.addEvent('mouseout',function(ev){
					var event = new Event(ev);
					var t = event.target;
					for(var ii=0;ii<50;ii++){
						if(t.tagName.toLowerCase() == 'tr' && (t.className.indexOf("Item") != -1)) break;
						t = t.parentNode;
					}	
					t.style.backgroundColor = '#fff';
					}.bind(this));
				newRow.addEvent('click',function(ev){
					var event = new Event(ev);
					var t = event.target;
					for(var ii=0;ii<50;ii++){
						if(t.tagName.toLowerCase() == 'tr') break;
						if(t.tagName.toLowerCase() == 'a') break;
						t = t.parentNode;
					}
					if(t && t.tagName.toLowerCase() == 'tr') {	
						var inputs = t.getElementsByTagName('input');
						var x = Json.evaluate(inputs[0].value);				
						this.map().setCenter(new google.maps.LatLng(x.lat,x.lng), 16);
						this.fetchSummary(x.lid);
					}
					}.bind(this));
			}	
			summaryGrid.tBodies[0].style.height = Math.min(rowHeight,summaryGrid.tBodies[0].startHeight) + 'px';	
			summaryGrid.parentNode.style.height = Math.min(summaryGrid.offsetHeight,summaryGrid.startHeight) + 'px';	
		} else if (currentRowCount <= 1) {			
			summaryPanel.style.display = 'none';
			summaryGrid.tBodies[0].style.display = 'none';
			summaryGrid.parentNode.style.height = summaryGrid.offsetHeight + 'px';
			noListings.style.display = 'block';
			
		}
		//this.map().checkResize();
		google.maps.event.trigger(this.map(), 'resize')
	}
	
	this.createMarkers = function(markerData) {
		var markers = new Array();
		for(var ii=0;ii<markerData.length;ii++) {			
			var marker = this.createMarker(markerData[ii]);
			markers.push(marker);
		}
		return markers;
	}
	
	this.createMarker = function(marker) {
		var icon = (marker.listingId) ? this.listingIcon : this.getClusterIcon(marker.count);
		var title = this.getMarkerTitle(marker);
		var gMarker = new google.maps.Marker({
			position: new google.maps.LatLng(marker.point.lat,marker.point.lng),
			title:title,
			icon:icon
		});
		if(marker.listingId) {
			gMarker.listingId = marker.listingId;
			var me = this;
			if(summaryUrl) {
				google.maps.event.addListener(gMarker,"click",function(){me.fetchSummary(this.listingId);});
			}	
		} else {
			google.maps.event.addListener(gMarker,"click",function(){map.setCenter(this.getPosition()); map.setZoom(map.getZoom()+1);});
			gMarker.count = marker.count;
		}
		return gMarker;
	}
	
	this.getMarkerTitle = function(marker) {
		var title = '';
		if(marker.listingId) {
			title = (marker.address) ? marker.address + ", " + marker.price : marker.price; 
		} else if (marker.count) {
			title = "Click to view " + marker.count + " more listings";
		} 
		return title;
	}
	
	this.getClusterIcon = function(count){
		if(count >= 50){
			return this.cluster50Icon;
		} else if (count >= 25){
			return this.cluster25Icon;
		} else if (count >= 10){
			return this.cluster10Icon;
		} else {
			return this.cluster5Icon;
		}
	}

	this.setMapCenterAndZoom = function(bounds) {
	    this.setListingsLoadedFor(startZoom); //have to call this prior to setCenter() or data loaded twice during intial load
	    map.fitBounds(bounds);
	}

	this.setMapToContainingBounds = function() {
	    //setCenter will cause the moveend to fire, which will result in the user lat, lng, and zoom being overwritten
	    //with the containingbounds settings. The editMode conditionals are to save and restore the user settings
	    //after the call to setCenter.
	    if (containingBounds) {
	        if (editMode) {
	            var oldLat = $(userLatId).value;
	            var oldLng = $(userLngId).value;
	            var oldZoom = $(userZoomId).value;
	        }
	        var lat = (containingBounds.getSouthWest().lat() + containingBounds.getNorthEast().lat()) / 2;
	        var lng = (containingBounds.getSouthWest().lng() + containingBounds.getNorthEast().lng()) / 2;
	        map.fitBounds(containingBounds);
	        var zoom = map.getZoom();
	        if (editMode) {
	            $(userLatId).value = oldLat;
	            $(userLngId).value = oldLng;
	            $(userZoomId).value = oldZoom;
	        }
	    }
	}

	this.addMarkersToMap = function(markers,currZoom){
		var self = this;
		if(!manager) manager = new MarkerManager(map,{borderPadding: 0});
		doMarkerAdd = function() {
			managerLoaded = true;
			if(purgeListingData) {
				manager.clearMarkers();
				self.clearListingsLoaded();
				loadedClusters = new Array();
				loadedListings = {};
				purgeListingData = false;
			}
			var minZoom = (self.startPointSpecified() || initialized) ? currZoom : 0;
			var clustered = false;		
			for(var ii=0;ii<markers.length;ii++) {				
				if(markers[ii].listingId){
					var maxZoom = self.maxZoomForListing(markers[ii], currZoom); //returns -1 if no effective maximum
					if (maxZoom == Number.POSITIVE_INFINITY) {
						//No action needed
					} else if(maxZoom > 0){
						manager.addMarker(markers[ii], minZoom, maxZoom);
					} else {
						manager.addMarker(markers[ii], minZoom);
					}
				} else {
					manager.addMarker(markers[ii], minZoom, currZoom);
					clustered = true;
					loadedClusters.push({zoom:currZoom,count:markers[ii].count,latlng:markers[ii].getPosition()});
				}
			}
			self.setListingsLoadedFor(currZoom,clustered);
			manager.refresh();
		};
		if (managerLoaded) {
			doMarkerAdd();
		} else
			google.maps.event.addListenerOnce(manager, 'loaded', doMarkerAdd);
	}
	
	this.maxZoomForListing = function(marker,zoom) {
		var listing = loadedListings[marker.listingId];
		var max = -1; //listing not found
		if(listing) {
			if(listing.minZoom > zoom) {
				max = listing.minZoom--;
				listing.minZoom = zoom;
			} else {
				max = Number.POSITIVE_INFINITY; //no effective maximum
			}
		} else {			
			var minzoom = (this.startPointSpecified() || initialized) ? zoom : 0;
			loadedListings[marker.listingId] = {listingId:marker.listingId,minZoom:minzoom,latlng:marker.getPosition()};
		}
		return max;				
	}
	
	this.filterListings = function(ev) {
		ev.stopPropagation();
		ev.preventDefault;
		ev.stop();
		if(this.validateSearchFilter()){
			this.total = null;
			purgeListingData = true;
			this.setSearchFilterValues();
			this.fetchMapData();
			this.getSummaryGridPage(true,Object.toQueryString(currentSort));
			this.saveState();
		}
	}
	
	this.moreListings = function(ev) {
		ev.stopPropagation();
		ev.preventDefault;
		ev.stop();
		var link = ev.target;
		moreListings = !moreListings;
		if(moreListings) {
			link.innerHTML = "Original listings";
		} else {
			link.innerHTML = "More listings";
		}
		this.total = null;
		purgeListingData = true;
		this.saveState();
		this.fetchMapData();
		this.getSummaryGridPage(true);
	}
	
	this.sortGrid = function(event) {
		var ev = new Event(event);
		var el = ev.target;
		ev.stopPropagation();
		ev.preventDefault;
		ev.stop();
		var sortLinks = $$('#summaryGrid' + formSuffix + ' a.SortLink');
		//reset arrow on all sort links
		for(var ii=0;ii<sortLinks.length;ii++){
			sortLinks[ii].style.backgroundImage = '';
			sortLinks[ii].style.paddingRight = '0';
			sortLinks[ii].parentNode.className = '';
		}
		//determine currentSort and set callback and indicator
		el.style.paddingRight = '14px';
		el.parentNode.className = "HeaderSelected";
		var sort = "SortBy=" + el.sortBy;
		if(el.sortBy == currentSort.sortBy) {
			reverseSort = !reverseSort;
			if(reverseSort) {
				sort = sort + "&reverseSort=true";			
				el.style.backgroundImage = 'url('+ imageUrls[((el.naturalSort == "Descending") ? "Ascending" : "Descending")] + ')';
			} else {
				el.style.backgroundImage = 'url('+ imageUrls[el.naturalSort] + ')';
			} 			
		} else {
			reverseSort = false;
			el.style.backgroundImage = 'url('+ imageUrls[el.naturalSort] + ')';			
		}
		currentSort.sortBy = el.sortBy;
		currentSort.reverseSort = reverseSort;
		this.getSummaryGridPage(true,Object.toQueryString(currentSort));
	}
	
	this.getSummaryGridPage = function(purgeCurrent,params) {
		if(gridUrl){
			if(purgeCurrent) {
				purgeOldRows = true;
				gridPage = 1;
			} 
			this.fetchGridRows(params);
			gridPage++;
		}		
	}
	
	this.updateCount = function() {
		var countLabel = $('mapCount' + formSuffix);
		if(countLabel && !fetchingMapData) {
			var count = 0;
			var visible = 0;
			var bounds = this.map().getBounds();
			var currZoom = this.map().getZoom();
			for(var i in loadedListings) {
				var listing = loadedListings[i];
				count += 1;
				if(listing.minZoom <= currZoom && bounds.contains(listing.latlng)){
					visible += 1;
				}
			}
			for(var ii=0;ii<loadedClusters.length;ii++) {
				var cluster = loadedClusters[ii];	
				count += cluster.count;			
				if (cluster.zoom == currZoom && bounds.contains(cluster.latlng)){
					visible += cluster.count;
				}				 
			}
			if(!this.total){
				this.total = count;	
			}		
			countLabel.innerHTML = (this.total>0) ?	'&nbsp;Showing ' + visible + ' of ' + this.total + '&nbsp;' : '&nbsp;No listings found&nbsp;';
		}
	}
	
	this.saveState = function() {	
		var state = {};
		if($(filterPanelId)){
			for(i in filterValues) {
				state[i] = filterValues[i];
			}
		}
		state.zoom = map.getZoom();
		state.lat = map.getCenter().lat();		
		state.lng = map.getCenter().lng();
		if(moreListings) {
			state.ml = 'true';
		}
		var stateCookie = new Hash.Cookie('mapState',{duration:0});
		stateCookie.empty();
		stateCookie.extend(state);
		stateCookie.save();
	}

	this.loadState = function() {
	    var state = new Hash.Cookie('mapState');
	    if (state.length > 0) {
	        loadingFromUserCookie = true;
	        var lat = state.get('lat');
	        var lng = state.get('lng');
	        var zoom = state.get('zoom');
	        map.setCenter(new google.maps.LatLng(lat, lng));
	        map.setZoom(zoom);
	        if (state.get('ml') === 'true') {
	            moreListings = true;
	            $('moreListings' + formSuffix).setText("Original listings");
	        }
	        if ($(filterPanelId)) {
	            var status = state.get('status');
	            if (status) {
	                status = status.split(',');
	                $$(document.forms[0]['specifyStatus' + formSuffix])[1].checked = true;
	                var statusCBs = document.forms[0]['status' + formSuffix];
	                for (var ii = 0; ii < statusCBs.length; ii++) {
	                    statusCBs[ii].checked = status.contains(statusCBs[ii].value);
	                }
	            } else {
	                $$(document.forms[0]['specifyStatus' + formSuffix])[0].checked = true;
	                $$(document.forms[0]['specifyStatus' + formSuffix])[0].fireEvent('click');
	            }
	            var type = state.get('type');
	            if (type && type.length > 0) {
	                type = type.split(',');
	                $$(document.forms[0]['specifyTypes' + formSuffix])[1].checked = true;
	                var typeCBs = document.forms[0]['type' + formSuffix];
	                for (var ii = 0; ii < typeCBs.length; ii++) {
	                    typeCBs[ii].checked = type.contains(typeCBs[ii].value);
	                }
	            } else {
	                $$(document.forms[0]['specifyTypes' + formSuffix])[0].checked = true;
	                $$(document.forms[0]['specifyTypes' + formSuffix])[0].fireEvent('click');
	            }
	            var priceLow = state.get('priceLow');
	            if (priceLow) {
	                var opts = $(document.forms[0]['priceLow' + formSuffix]).options;
	                for (var ii = 0; ii < opts.length; ii++) {
	                    if (opts[ii].value == priceLow) {
	                        $(document.forms[0]['priceLow' + formSuffix]).selectedIndex = ii;
	                        break;
	                    }
	                }
	            }
	            var priceHigh = state.get('priceHigh');
	            if (priceHigh) {
	                var opts = $(document.forms[0]['priceHigh' + formSuffix]).options;
	                for (var ii = 0; ii < opts.length; ii++) {
	                    if (opts[ii].value == priceHigh) {
	                        $(document.forms[0]['priceHigh' + formSuffix]).selectedIndex = ii;
	                        break;
	                    }
	                }
	            }
	            var minBeds = state.get('beds');
	            if (minBeds) {
	                var opts = $(document.forms[0]['minBeds' + formSuffix]).options;
	                for (var ii = 0; ii < opts.length; ii++) {
	                    if (opts[ii].value == minBeds) {
	                        $(document.forms[0]['minBeds' + formSuffix]).selectedIndex = ii;
	                        break;
	                    }
	                }
	            }
	            var minBaths = state.get('baths');
	            if (minBaths) {
	                var opts = $(document.forms[0]['minBaths' + formSuffix]).options;
	                for (var ii = 0; ii < opts.length; ii++) {
	                    if (opts[ii].value == minBaths) {
	                        $(document.forms[0]['minBaths' + formSuffix]).selectedIndex = ii;
	                        break;
	                    }
	                }
	            }
	        }
	        return true;
	    } else {
	        return false;
	    }
	}
	
	this.setSearchFilterValues = function() {
		var obj = {};
		var priceLow = $(document.forms[0]['priceLow' + formSuffix]).getValue();		
		var priceHigh = $(document.forms[0]['priceHigh' + formSuffix]).getValue();
		var minBeds = $(document.forms[0]['minBeds' + formSuffix]).getValue();
		var minBaths = $(document.forms[0]['minBaths' + formSuffix]).getValue();

		var selectedTypes = new Array();
		if($$(document.forms[0]['specifyTypes' + formSuffix])[1].checked){
			var type = document.forms[0]['type' + formSuffix];
			for(var ii=0;ii<type.length;ii++) {
				if(type[ii].checked) {
					selectedTypes.push(type[ii].value);
				}
			}
			if(selectedTypes.length == type.length) {
				selectedTypes = new Array();
			}
		}
		
		var selectedStatus = new Array();
		if($$(document.forms[0]['specifyStatus' + formSuffix])[1].checked){
			var status = document.forms[0]['status' + formSuffix];
			for(var ii=0;ii<status.length;ii++) {
				if(status[ii].checked) {
					selectedStatus.push(status[ii].value);
				}
			}
			if(selectedStatus.length == status.length) {
				selectedStatus = new Array();
			}
		}

		if(priceLow > 0) obj.priceLow = priceLow;
		if(priceHigh < 99999999999) obj.priceHigh = priceHigh;
		if(minBeds > 1) obj.beds = minBeds;
		if(minBaths > 1) obj.baths = minBaths;
		if(selectedStatus.length > 0) obj.status = selectedStatus.join(',');
		if(selectedTypes.length > 0) obj.type = selectedTypes.join(',');
		if(moreListings) obj.moreListings = 'true';
		filterValues = obj;
	}
	
	this.getSearchFilterValues = function() {		
		return Object.toQueryString(filterValues);	
	}
	
	this.validatePrice = function(){
		var isValid = false;
		var errMsg = $('priceError'+formSuffix);
		var priceLow = $(document.forms[0]['priceLow' + formSuffix]).getValue().toInt();		
		var priceHigh = $(document.forms[0]['priceHigh' + formSuffix]).getValue().toInt();
		if(priceHigh > priceLow){
			isValid = true;
			errMsg.style.display = 'none';
		} else {
			errMsg.style.display = 'inline';
		}   
		return isValid;
	}
	
	this.validateType = function(){
		var isValid = $$(document.forms[0]['specifyTypes' + formSuffix])[0].checked;
		var errMsg = $('typeError'+formSuffix);
		if(!isValid){		
			var type = document.forms[0]['type' + formSuffix];
			for(var ii=0;ii<type.length;ii++) {
				if(type[ii].checked) {
					isValid = true;
					errMsg.parentNode.parentNode.style.height = errMsg.parentNode.parentNode.offsetHeight - errMsg.offsetHeight + 'px';
					errMsg.style.display = 'none';
					break;
				}
			}
		}
		if(!isValid){
			var expandSlide = (errMsg.style.display == 'none');
			errMsg.style.display = 'block';
			if(expandSlide) {
				errMsg.parentNode.parentNode.style.height = errMsg.parentNode.parentNode.offsetHeight + errMsg.offsetHeight + 'px';
			}
		}
		return isValid; 
	}
	
	this.validateStatus = function(){
		var isValid = $$(document.forms[0]['specifyStatus' + formSuffix])[0].checked;
		var errMsg = $('statusError'+formSuffix);
		if(!isValid){		
			var status = document.forms[0]['status' + formSuffix];
			for(var ii=0;ii<status.length;ii++) {
				if(status[ii].checked) {
					isValid = true;					
					errMsg.parentNode.parentNode.style.height = errMsg.parentNode.parentNode.offsetHeight - errMsg.offsetHeight + 'px';
					errMsg.style.display = 'none';
					break;
				}
			}
		}
		if(!isValid){			
			var expandSlide = (errMsg.style.display == 'none');
			errMsg.style.display = 'block';	
			if(expandSlide){	
				errMsg.parentNode.parentNode.style.height = errMsg.parentNode.parentNode.offsetHeight + errMsg.offsetHeight + 'px';
			}
		}
		return isValid;  
	}
   
	this.validateSearchFilter = function(){     
		var isValid = this.validatePrice();
		var isValid = (this.validateStatus() && isValid);
		var isValid = (this.validateType() && isValid);
		if(!isValid) alert('Please complete missing or incorrect information before submitting your search');
		return isValid;			            
	}
}

