/************************************************************
*
*	Greenmap.js
* 	ŻŻŻŻŻŻŻŻŻŻŻ	
* 	Author: Paolo Isoardi
* 	Date: 	August 2006
* 	email: 	paolo.isoardi@libero.it
*	Based on code by:
*		http://www.econym.demon.co.uk/googlemaps/
*		http://groups.google.com/group/Google-Maps-API
*		http://www.dynamite.co.uk/local/
*		http://www3.telus.net/DougHenderson/index_v2.html
*		http://www.google.com/uds/samples/places.html
*		http://www.geocodezip.com/
*
************************************************************/


// Global variables
//******************
	// the google map object
	var map;
	// map view extent
	var bounds;
	// zoom levels
	var zoomLevel;
	var zoomThreshold;
	// the ajax request to the server	
	var request;
	// array of organizations read from the generated xml
	var orgs = [];
	// array of mapped markers
	var gmarkers =  [];
	// array of tabbed infowindows
  var htmls = [];
	// arrays that hold the sidebar information
	var sidebar_html = "";
	var sidebarEnd_html = "";
	// personalized tooltips variable
	var tooltip;
	// shortcuts to divs
	var loadingmsg;
	var messageDiv;
	var postcodeDiv;
	var resultDiv;
	var logger;
	// geocoder global state
	var input;
	var gLocalSearch;
	var gSelectedResults = [];
	var gCurrentResults = [];
	// to measure loading time
	var tm;
	// to control if there is a fetch in progress
	var fetch = false;
	// to control if markers should update
  // if user manually pans, update. if map pans automatically, do not update
  var update = true;
	// whether user wants to see 'organisations' or 'events' information
	var OrgsOrEvents = 'organisations';

// icons setup
//*************
	// the postcode search green icon
	var searchIcon = new GIcon();
	searchIcon.image = "inc/greenmap/images/address.png";
	searchIcon.shadow = "inc/greenmap/images/address_shadow.png";
	searchIcon.iconSize = new GSize(20, 34);
	searchIcon.shadowSize = new GSize(37, 34);
	searchIcon.iconAnchor = new GPoint(6, 20);
	searchIcon.infoWindowAnchor = new GPoint(5, 1);
	// the base icon
	var baseicon = new GIcon();
	baseicon.image = "inc/greenmap/images/icons/blank.png";
	baseicon.iconSize = new GSize(22, 22);
	baseicon.iconAnchor = new GPoint(11, 11);
	baseicon.infoWindowAnchor = new GPoint(11, 2);
	// create an associative array of GIcons()
	var gicons = [];
	gicons["0"] = new GIcon(baseicon, "inc/greenmap/images/icons/0.png");	// added for events
	gicons["blank"] = new GIcon(baseicon, "inc/greenmap/images/icons/blank.png");
	gicons["77"] = new GIcon(baseicon, "inc/greenmap/images/icons/77.png");
	gicons["78"] = new GIcon(baseicon, "inc/greenmap/images/icons/78.png");
	gicons["79"] = new GIcon(baseicon, "inc/greenmap/images/icons/79.png");
	gicons["80"] = new GIcon(baseicon, "inc/greenmap/images/icons/80.png");
	gicons["81"] = new GIcon(baseicon, "inc/greenmap/images/icons/81.png");
	gicons["82"] = new GIcon(baseicon, "inc/greenmap/images/icons/82.png");
	gicons["83"] = new GIcon(baseicon, "inc/greenmap/images/icons/83.png");
	gicons["84"] = new GIcon(baseicon, "inc/greenmap/images/icons/84.png");
	gicons["87"] = new GIcon(baseicon, "inc/greenmap/images/icons/87.png");
	gicons["88"] = new GIcon(baseicon, "inc/greenmap/images/icons/88.png");
	gicons["89"] = new GIcon(baseicon, "inc/greenmap/images/icons/89.png");
	gicons["90"] = new GIcon(baseicon, "inc/greenmap/images/icons/90.png");
	gicons["91"] = new GIcon(baseicon, "inc/greenmap/images/icons/91.png");
	gicons["92"] = new GIcon(baseicon, "inc/greenmap/images/icons/92.png");
	gicons["94"] = new GIcon(baseicon, "inc/greenmap/images/icons/94.png");
	gicons["95"] = new GIcon(baseicon, "inc/greenmap/images/icons/95.png");
	gicons["96"] = new GIcon(baseicon, "inc/greenmap/images/icons/96.png");



// this function initializes and configures the map.
// loaded at page creation
//***************************************************
function onLoad() {
	if (!GBrowserIsCompatible()) {
		alert('Your browser may not be compatible with the Google Maps system.\n'+
			  'Please visit http://maps.google.com for more information.');
	} else {
				
		// create the map	
		map = new GMap2(document.getElementById("map"));
		//Ali hack to see if i can set the map center to a borough from a link in local homepages or directory listings
		if(localbLat!=0 && localbLng!=0){
			map.setCenter(new GLatLng(localbLat, localbLng), localzlevel, G_NORMAL_MAP); //centred on local borough or organisation from directory
		} else {
			map.setCenter(new GLatLng(51.514194, -0.091904), 10, G_NORMAL_MAP);	// centered on London
		}
		
		// set controls on the map: navigation & zoom, scale, etc.
		map.addControl(new GLargeMapControl());
		map.addControl(new GScaleControl());
		map.addControl(new GMapTypeControl());
				
		// set the overview map
		overview = new GOverviewMapControl(new GSize(135, 135));
		map.addControl(overview, new GControlPosition(G_ANCHOR_BOTTOM_RIGHT));
				 
		// set fancy zoom properties
		map.enableDoubleClickZoom();
		map.enableContinuousZoom();
		
		// restricting the range of Zoom Levels
		var mt = map.getMapTypes();
		for (var i=0; i<mt.length; i++) {
			if (mt[i] == G_SATELLITE_MAP) {
				mt[i].getMaximumResolution = function() {return 19;};
			} else {
				mt[i].getMaximumResolution = function() {return 17;};
			}
			mt[i].getMinimumResolution = function() {return 6;}
		}
		
		// set up marker mouseover tooltip div
		tooltip = document.createElement("div");
		map.getContainer().appendChild(tooltip);
		tooltip.style.visibility="hidden";
		
		// listen to map movements
		GEvent.addListener(map, "moveend", updateMap);
			
		//add keyboard bindings to the map
		new GKeyboardHandler(map);
				
		// Initialize the local searcher
		gLocalSearch = new GlocalSearch();
		gLocalSearch.setCenterPoint(map);
		gLocalSearch.setSearchCompleteCallback(null, OnLocalSearch);
				
		// set the loading div
		loadingmsg = document.getElementById("loadingmsg");
		// set the messages div
		messageDiv = document.getElementById("message");
		// set the postcode div
		postcodeDiv = document.getElementById('address');
		// set the search results div
		resultDiv = document.getElementById('result');
		// set the log div
		logger = document.getElementById("logDiv");
		var currentTime = new Date();
		var hours = currentTime.getHours();
		var minutes = currentTime.getMinutes();
		var seconds = currentTime.getSeconds();
		if (minutes < 10) {minutes = "0" + minutes};	
		if (seconds < 10) {seconds = "0" + seconds};	
		/*------------------*/
		logger.innerHTML = "LOGGING STARTED ON "+agent.toUpperCase()+" AT "+hours+":"+minutes+":"+seconds;
		logger.innerHTML += "<br/>##########################<br/><br/>";
		/*------------------*/
		
		// display a first message
		messageDiv.innerHTML = '&nbsp;Welcome to the <span style="font-weight: bold; color: #666600;">'+
							'London 21 Green Map</span>! Select a borough or zoom in to get more';
		
		// load the markers for the first time
		updateMap();
		
		// refresh the legend
		legendUpdate();
		checkAll(document.getElementById("myformL").checkgroup);
		
		// check for map resize
		map.checkResize();
		
		// Set the focus on startup
		input = document.getElementById("map");
		input.focus();		
	}
}



// this function creates and adds the marker to the map
//******************************************************
function createMarker(point, name, id, infoTab1, infoTab2, infoTab3, icontype) {
	
	// this array stores tab information for the current marker
	if(OrgsOrEvents == 'organisations') {
		tab1 = new GInfoWindowTab("Organisation", infoTab1);
		tab2 = new GInfoWindowTab("About us", infoTab2);
		tab3 = new GInfoWindowTab("Aims", infoTab3);
  }
	else if (OrgsOrEvents == 'events'){
		tab1 = new GInfoWindowTab("Event", infoTab1);
		tab2 = new GInfoWindowTab("Description", infoTab2);
		tab3 = new GInfoWindowTab("Contacts", infoTab3);
	}

  htmls[id] = [tab1, tab3, tab2];
	// create the gmarker
	var marker = new GMarker(point, gicons[icontype]);
	
	// store some marker properties
	marker.dbid = id;
	marker.tooltip = '<div class="tooltip">'+name+'</div>';
	marker.icontype = icontype;
	marker.name = name;
	marker.latitude = point.lat();
	marker.longitude = point.lng();

	// add the directions and forum links to the end of the tabs
	var dirLink = '<a href="http://maps.google.com/maps?saddr=&daddr=' + point.toUrlValue() + '" target ="_blank" title="Open in new window">Directions to/from here</a>';

	var orgsForumLink = '<a href="http://www.london21.org/page/85/show/'+id+'" title="View full details">View Full Details</a>';
	var evsForumLink = '<a href="http://lovelondon.london21.org/page/47/show/'+id+'" title="View full details">View Full Details</a>';

	var footerinfoTabs ='<div class="infoWindowTabFooter" align="center"><hr />'+
						'	<table width="95%" align="center" border="0" cellpadding="0" cellspacing="0">'+
						'	<tr>'+
						'		<td align="left">'+dirLink+'</td>'+
						'		<td align="right">'+orgsForumLink+'</td>'+ // hack
						'	</tr>'+
						'	</table>'+
						'</div></div>';
	infoTab1 += footerinfoTabs;
	infoTab2 += footerinfoTabs;
	infoTab3 += footerinfoTabs;

 
	
	// marker listeners:
	// click event
	GEvent.addListener(marker, "click", function() {
		// do not query server for new markers
		update = false;
		// be sure the tooltip disappears
		tooltip.style.visibility="hidden";
		map.checkResize();
		marker.openInfoWindowTabsHtml(htmls[id]);
	});
	// mouseover event
	GEvent.addListener(marker,"mouseover", function() {
		showTooltip(marker);
	});
	// mouseout event
	GEvent.addListener(marker,"mouseout", function() {
		tooltip.style.visibility="hidden";
		// remove highlight to the same entry in the sidebar
		entrytohighlight = document.getElementById(id).firstChild;
		entrytohighlight.style.color = "#003300";
	});
	
	return marker;
}




// map updating function, called by every map refresh.
// it retrieves the organizations from the MySQL database
// by calling a php script, via the Ajax method
//********************************************************
function updateMap() {
	// exist function if update is set to false
	if(!update) {
		update = true;
		return 0;
  }	
	if(OrgsOrEvents == 'organisations') {
		xmlGenerator = 'read.php';
	}
	else if (OrgsOrEvents == 'events') {
		xmlGenerator = 'read_events.php';
	}

	// if there is already a request to the server, stop it, and go on with the new one
	if (fetch) {
		/*------------------*/
		logger.innerHTML += "<br/><strong>updateMap aborted</strong><br/><br/>";
		/*------------------*/
		request.abort();
		fetch = false;
	}
	
	// check for map resize
	map.checkResize();
	
	// display the loading message
	loadingmsg.style.visibility="hidden";
	messageDiv.innerHTML = '&nbsp;<strong>Loading data, please wait . . .</strong>';

	// retrieve current extent & zoom level
	bounds = map.getBounds();
	var minLat = bounds.getSouthWest().lat();
	var minLng = bounds.getSouthWest().lng();
	var maxLat = bounds.getNorthEast().lat();
	var maxLng = bounds.getNorthEast().lng();
	zoomLevel = map.getZoom();
	
	// define the max number of organizations to retrieve, based on the type of browser
	// (speed up loading time when there are too many organizations to map)
	if (agent == 'msie') {
		zoomThreshold = 14; 
		var limit = 15;	// RANDOM query
	} else {
		zoomThreshold = 13; 
		var limit = 50;	// RANDOM query
	}
	
	// if the zoom level is ok, set the limit for a full query
	if (zoomLevel > zoomThreshold) {
		limit="";		// NORMAL query
	}
	
	// set the url for the request to the server
	var urlstr = 'inc/greenmap/' + xmlGenerator + '?limit=' + limit + '&Xmin=' + minLng + '&Xmax=' + maxLng + '&Ymin=' + minLat + '&Ymax=' + maxLat;
	
	// remove the sidebar content
	sidebar_html = "";
	// hack	
	document.getElementById("result").innerHTML = sidebar_html;	
	sidebarEnd_html = "";
	document.getElementById("resultEnd").innerHTML = sidebarEnd_html;	
	
	/*------------------*/
	logger.innerHTML += "zoomLevel="+zoomLevel+"; zoomThreshold="+zoomThreshold+"; limit="+limit+"<br/>";
	/*------------------*/
	tm = new Date().getTime();
	/*------------------*/
	logger.innerHTML += "START - Markers in the array: <strong>"+gmarkers.length+"</strong><br/>";
	/*------------------*/

	// remove the markers we don't need anymore
	cleanMap();
	
	// send the request to the server
	var request = GXmlHttp.create();
	request.open("GET", urlstr, true);
	request.onreadystatechange = function() {	
		if (request.readyState == 4) {
			if (request.status == 200) {
				var xmlDoc = request.responseXML;
				if (xmlDoc.documentElement) {
					
					// parse the data
					if(OrgsOrEvents == 'organisations') {
						getOrganizations(xmlDoc, urlstr, zoomLevel, zoomThreshold, limit);
					}
					else if(OrgsOrEvents == 'events') {
						getLswevents(xmlDoc, urlstr, zoomLevel, zoomThreshold, limit);
					}

					
				} else {
					messageDiv.innerHTML = "&nbsp;Database connection failure, please retry later [request.status="+request.status+"]";
					logger.innerHTML += "IE Has bombed out: urlstr="+urlstr+" zoomLevel="+zoomLevel+" zoomThreshold="+zoomThreshold+" limit="+limit+"<br />";
				}
			} else {
				messageDiv.innerHTML = "&nbsp;Database connection failure, please retry later [request.status="+request.status+"]";
			}
			// set the fetch status as false
			fetch = false;
			
			// hide the loading message
			loadingmsg.style.visibility="hidden";
		}
	}
	
	// set the fetch status as true
	fetch = true;
	
	request.send(null);
}



// loop through the array of organizations obtained  
// from the server and populate the map
//**************************************************
function getOrganizations(xmlDoc, urlstr, zoomLevel, zoomThreshold, limit) {
	
	orgs = xmlDoc.documentElement.getElementsByTagName("organization");
	
	if (orgs.length){
		
		// set the sidebar content
		sidebar_html += '<br/><span style="color: #003300;">&nbsp;'+orgs.length+' organizations on the map.<br/>&nbsp;Sort the list by:<br/></span>'
		sidebar_html += '<table width="92%" border="0" cellpadding="2" cellspacing="2" class="sortable" id="t1">\r\n';
		sidebar_html += '<tbody>\r\n';
		sidebar_html += '<tr>\r\n';
		sidebar_html += '    <th align="center"><a href="#" class="sortheader" onclick="ts_resortTable(this, 0);return false;" title="Order the list by type"><strong>type</strong><span class="sortarrow">&nbsp;&nbsp;&nbsp;</span></a><hr/></th>\r\n';
		sidebar_html += '    <th align="center"><a href="#" class="sortheader" onclick="ts_resortTable(this, 1);return false;" title="Order the list by name"><strong>name</strong><span class="sortarrow">&nbsp;&nbsp;&nbsp;</span></a><hr/></th>\r\n';
		sidebar_html += '</tr>\r\n';
	
		// loop through each organization retrieved from XML
		var added = 0; // store the number of added markers
		for (var i = 0; i < orgs.length; i++) {

			// obtain the organization attributes
			var lat = parseFloat(orgs[i].getAttribute("lat"));
			var lng = parseFloat(orgs[i].getAttribute("lng"));
			var id = orgs[i].getAttribute("id");
			var name = orgs[i].getAttribute("name");
			var aims = orgs[i].getAttribute("aims");
			var about = orgs[i].getAttribute("about");
			var type1 = orgs[i].getAttribute("type1");
			var type2 = orgs[i].getAttribute("type2");
			var category = orgs[i].getAttribute("categories");
			var news = orgs[i].getAttribute("newsletter");
			var newsdet = orgs[i].getAttribute("newsletterdetails");
			var voldet = orgs[i].getAttribute("voldet");
			var email = orgs[i].getAttribute("email");
			var icontype = orgs[i].getAttribute("icontype");
			
			// set the  html content of the infowindow tabs
			var infoTab1  = '<div id="infoWindowTab"><div class="infoWindowTabHeader">'+
							'<span class="title">'+name+'</span><hr/></div>';
				infoTab1 +=	'<div class="infoWindowTabCentral">'+
							'<b>Main Category: </b>'+category+
							'<br/><b>Organisation Type: </b>'+type1;
			if (type1 !== type2 && type2!=0) {
				infoTab1 += ', '+type2;}
				infoTab1 +=	'<br/><b>Newsletter: </b>'+news;
			if (newsdet !== "") {
				infoTab1 += '<br/><b>Newsletter details: </b>'+newsdet;}
				infoTab1 +=	'<br/><b>Volunteering details: </b>'+voldet+
							'<br/><b>Email: </b><a href="mailto:'+email+
							'" title="Write us!">'+email+'</a>';
				infoTab1 +=	'<br/><b>More info </b><a href="http://www.london21.org/page/85/show/'+id+
							'" title="View full details">here</a></div>';
			
			var infoTab2  = '<div id="infoWindowTab"><div class="infoWindowTabHeader"><b>Main Activities</b><hr/></div>';
				infoTab2 += '<div class="infoWindowTabCentral">'+about+'</div>';
			
			var infoTab3  = '<div id="infoWindowTab"><div class="infoWindowTabHeader"><b>Aims & Objectives</b><hr/></div>';
				infoTab3 += '<div class="infoWindowTabCentral">'+aims+'</div>';
			
			// add lines to the sidebar html
			sidebar_html += '<tr>\r\n';
			sidebar_html += '   <td width="5%" align="center"><img src="'+gicons[icontype].image+'" width="14" height="14" title="'+category+'"/><br/><span style="bottom: 0px; left: 0px; font-size: 1px; visibility: hidden;">'+icontype.substring(0,4)+'</span></td>\r\n';
			sidebar_html += '   <td width="95%" id="'+id+'"><a href="javascript:myclick('+id+')" onmouseover="mymouseover('+id+
								')" onmouseout="mymouseout('+id+')" title="View details about this organisation">'+name+
								'</a><br /></td>\r\n';
			sidebar_html += '</tr>\r\n';
			
			// check if this organization is already on the map
			var should_add = true;
			for (var j=0; j<gmarkers.length; j++) {
				if (id && gmarkers[j].dbid == id) {
					// yes it is, so don't add it to the map
					should_add = false; 
					/*------------------*/
					//logger.innerHTML += "id="+id+" OLD<br/>";
					/*------------------*/
				} 
			}
			// if it's not then put it on the map
			if(should_add) {
				// create the marker
				var point = new GLatLng(lat,lng);
				marker = createMarker(point, name, id, infoTab1, infoTab2, infoTab3, icontype);
				// add the marker to the end of gmarkers
				gmarkers.push(marker);
				// add it to the map
				map.addOverlay(marker);
				/*------------------*/
				//logger.innerHTML += "id="+id+" NEW<br/>";
				/*------------------*/
				added++;
			} 
		}// end for						
		/*------------------*/
		logger.innerHTML += "Markers added: <strong>"+added+"</strong><br/>";
		/*------------------*/
		
		// add final lines to sidebar
		sidebar_html += '</tbody>\r\n';
		sidebar_html += '</table>\r\n';
		
		// put the assembled sidebar_html contents into the sidebar div
		// hack
		document.getElementById("result").innerHTML = sidebar_html;
		
		// set the xml button link
		var xmlbutton = '<a id="xmlbutton" href="./' + urlstr + '" target="_blank" title="View the XML source for this map">XML</a> ';
	
		// add lines to sidebarEnd
		sidebarEnd_html += '<table width="90%" border="0" cellpadding="2" cellspacing="5">\r\n';
		sidebarEnd_html += '<tbody>\r\n';
		sidebarEnd_html += '<tr>\r\n';
		sidebarEnd_html += '   <td align="center">'+xmlbutton+'</td>\r\n';
		sidebarEnd_html += '</tr>\r\n';
		sidebarEnd_html += '</tbody>\r\n';
		sidebarEnd_html += '</table>\r\n';
		
		// put the assembled sidebarEnd_html contents into the sidebarEnd div
		document.getElementById("resultEnd").innerHTML = sidebarEnd_html;
	
		// hide/show the markers according to the selections in the legend
		legendUpdate();
		
		// final message to the user		
		if (zoomLevel <= zoomThreshold) {
			// message for the RANDOM query
			if (orgs.length < limit) {
				messageDiv.innerHTML = '&nbsp;'+orgs.length+' organisations mapped';
				/*------------------*/
				logger.innerHTML += "Markers within the bounds: "+orgs.length+" (RANDOM query)<br/>";
				/*------------------*/
			} else {
				// retrieve the total number of organizations
				totalnumber = xmlDoc.documentElement.getElementsByTagName("totalnumber");
				if (totalnumber.length){
					var total = totalnumber[0].getAttribute("total");
				}
				messageDiv.innerHTML = '&nbsp;'+orgs.length+' organisations out of '+total+
									', <strong>RANDOMLY SELECTED</strong>. Please ZOOM IN for more details';
				/*------------------*/
				logger.innerHTML += "Markers within the bounds: "+orgs.length+" (RANDOM query)<br/>";
				/*------------------*/
			}
		} else {
			// message for the NORMAL query
			messageDiv.innerHTML = '&nbsp;'+orgs.length+' organisations mapped';
			/*------------------*/
			logger.innerHTML += "Markers within the bounds: "+orgs.length+" (NORMAL query)<br/>";
			/*------------------*/
		}
		
	} else {
		
		messageDiv.innerHTML = "&nbsp;No organisations available";
		
		// set the fetch status as false
		fetch = false;
	}
	
	/*------------------*/
	logger.innerHTML += "END - Markers in the array: <strong>"+gmarkers.length+"</strong><br/>";
	logger.innerHTML += "======================================================<br/>";
	/*------------------*/
	tm = (new Date().getTime() - tm)/1000.0;
	/*------------------*/
	logger.innerHTML += "<strong>loading time: "+tm+"</strong><br/>";
	logger.innerHTML += "======================================================<br/>";
	/*------------------*/
	tm = 0;
}


// loop through the array of LSW Events obtained  
// from the server and populate the map
//**************************************************
function getLswevents(xmlDoc, urlstr, zoomLevel, zoomThreshold, limit) {
	
	evs = xmlDoc.documentElement.getElementsByTagName("lswevent");
	
	if (evs.length){
		
		// set the sidebar content
		sidebar_html += '<br/><span style="color: #003300;">&nbsp;'+evs.length+' events on the map.<br/>&nbsp;Sort the list by:<br/></span>'
		sidebar_html += '<table width="92%" border="0" cellpadding="2" cellspacing="2" class="sortable" id="t1">\r\n';
		sidebar_html += '<tbody>\r\n';
		sidebar_html += '<tr>\r\n';
		sidebar_html += '    <th align="center"><a href="#" class="sortheader" onclick="ts_resortTable(this, 0);return false;" title="Order the list by type"><strong>type</strong><span class="sortarrow">&nbsp;&nbsp;&nbsp;</span></a><hr/></th>\r\n';
		sidebar_html += '    <th align="center"><a href="#" class="sortheader" onclick="ts_resortTable(this, 1);return false;" title="Order the list by name"><strong>name</strong><span class="sortarrow">&nbsp;&nbsp;&nbsp;</span></a><hr/></th>\r\n';
		sidebar_html += '</tr>\r\n';
	
		// loop through each organization retrieved from XML
		var added = 0; // store the number of added markers
		for (var i = 0; i < evs.length; i++) {

			// obtain the organization attributes
			var lat = parseFloat(evs[i].getAttribute("lat"));
			var lng = parseFloat(evs[i].getAttribute("lng"));
			var id = evs[i].getAttribute("id");
			var name = evs[i].getAttribute("name");
			var about = evs[i].getAttribute("about");
			var type = evs[i].getAttribute("type");
			var theme = evs[i].getAttribute("theme");
			var category = evs[i].getAttribute("categories");
			var startdate = evs[i].getAttribute("startdate");
			var enddate = evs[i].getAttribute("enddate");
			var time = evs[i].getAttribute("time");
			var fee = evs[i].getAttribute("fee");
			var venue = evs[i].getAttribute("venue");			
			var email = evs[i].getAttribute("email");
			var icontype = evs[i].getAttribute("icontype");
			
			// set the  html content of the infowindow tabs
			var infoTab1  = '<div id="infoWindowTab"><div class="infoWindowTabHeader">'+
							'<span class="title">'+name+'</span><hr/></div>';
				infoTab1 +=	'<div class="infoWindowTabCentral">';
				infoTab1 +=	'<span class="bigdate">'+startdate;
				if (enddate !== startdate) {
				infoTab1 += ' to '+enddate;}
				infoTab1 +=	'</span><br/><b>Fee: '+decodeURI(fee)+'</b><br /><b>Time: </b>'+time;
				infoTab1 += '<br/><b>Venue: </b>'+venue;
				if(theme!==""){
				infoTab1 +=	'<br/><b>Theme: </b>'+theme;}
				infoTab1 += '<br /><b>Type: </b>'+type;
				if(category==""){
				category="All categories";}
				infoTab1 +=	'<br/><b>Category: </b>'+category;
				infoTab1 += '</div>';
			var infoTab2  = '<div id="infoWindowTab"><div class="infoWindowTabHeader"><b>Description</b><hr/></div>';
				infoTab2 += '<div class="infoWindowTabCentral">'+about+'</div>';
			
			var infoTab3  = '<div id="infoWindowTab"><div class="infoWindowTabHeader"><b>Contacts</b><hr/></div>';
				infoTab3 += '<div class="infoWindowTabCentral"><br/><b>Email: </b><a href="mailto:'+email+
							'" title="Write us!">'+email+'</a></div>';
			
			// add lines to the sidebar html
			sidebar_html += '<tr>\r\n';
			sidebar_html += '   <td width="5%" align="center"><img src="'+gicons[icontype].image+'" width="14" height="14" title="'+category+'"/><br/><span style="bottom: 0px; left: 0px; font-size: 1px; visibility: hidden;">'+icontype.substring(0,4)+'</span></td>\r\n';
			sidebar_html += '   <td width="95%" id="'+id+'"><a href="javascript:myclick('+id+')" onmouseover="mymouseover('+id+
								')" onmouseout="mymouseout('+id+')" title="View details about this event">'+name+
								'</a><br /></td>\r\n';
			sidebar_html += '</tr>\r\n';
			
			// check if this organization is already on the map
			var should_add = true;
			for (var j=0; j<gmarkers.length; j++) {
				if (id && gmarkers[j].dbid == id) {
					// yes it is, so don't add it to the map
					should_add = false; 
					/*------------------*/
					//logger.innerHTML += "id="+id+" OLD<br/>";
					/*------------------*/
				} 
			}
			// if it's not then put it on the map
			if(should_add) {
				// create the marker
				var point = new GLatLng(lat,lng);
				marker = createMarker(point, name, id, infoTab1, infoTab2, infoTab3, icontype);
				// add the marker to the end of gmarkers
				gmarkers.push(marker);
				// add it to the map
				map.addOverlay(marker);
				/*------------------*/
				//logger.innerHTML += "id="+id+" NEW<br/>";
				/*------------------*/
				added++;
			} 
		}// end for						
		/*------------------*/
		logger.innerHTML += "Markers added: <strong>"+added+"</strong><br/>";
		/*------------------*/
		
		// add final lines to sidebar
		sidebar_html += '</tbody>\r\n';
		sidebar_html += '</table>\r\n';
		
		// put the assembled sidebar_html contents into the sidebar div
		document.getElementById("result").innerHTML = sidebar_html;
		
		// set the xml button link
		var xmlbutton = '<a id="xmlbutton" href="./' + urlstr + '" target="_blank" title="View the XML source for this map">XML</a> ';
	
		// add lines to sidebarEnd
		sidebarEnd_html += '<table width="90%" border="0" cellpadding="2" cellspacing="5">\r\n';
		sidebarEnd_html += '<tbody>\r\n';
		sidebarEnd_html += '<tr>\r\n';
		sidebarEnd_html += '   <td align="center">'+xmlbutton+'</td>\r\n';
		sidebarEnd_html += '</tr>\r\n';
		sidebarEnd_html += '</tbody>\r\n';
		sidebarEnd_html += '</table>\r\n';
		
		// put the assembled sidebarEnd_html contents into the sidebarEnd div
		document.getElementById("resultEnd").innerHTML = sidebarEnd_html;
	
		// hide/show the markers according to the selections in the legend
		legendUpdate();
		
		// final message to the user		
		if (zoomLevel <= zoomThreshold) {
			// message for the RANDOM query
			if (evs.length < limit) {
				messageDiv.innerHTML = '&nbsp;'+evs.length+' upcoming events mapped';
				/*------------------*/
				logger.innerHTML += "Markers within the bounds: "+evs.length+" (RANDOM query)<br/>";
				/*------------------*/
			} else {
				// retrieve the total number of organizations
				totalnumber = xmlDoc.documentElement.getElementsByTagName("totalnumber");
				if (totalnumber.length){
					var total = totalnumber[0].getAttribute("total");
				}
				messageDiv.innerHTML = '&nbsp;'+evs.length+' upcoming events out of '+total+
									', <strong>RANDOMLY SELECTED</strong>. Please ZOOM IN for more details';
				/*------------------*/
				logger.innerHTML += "Markers within the bounds: "+evs.length+" (RANDOM query)<br/>";
				/*------------------*/
			}
		} else {
			// message for the NORMAL query
			messageDiv.innerHTML = '&nbsp;'+evs.length+' upcoming events mapped';
			/*------------------*/
			logger.innerHTML += "Markers within the bounds: "+evs.length+" (NORMAL query)<br/>";
			/*------------------*/
		}
		
	} else {
		
		messageDiv.innerHTML = "&nbsp;No upcoming events available";
		
		// set the fetch status as false
		fetch = false;
	}
	
	/*------------------*/
	logger.innerHTML += "END - Markers in the array: <strong>"+gmarkers.length+"</strong><br/>";
	logger.innerHTML += "======================================================<br/>";
	/*------------------*/
	tm = (new Date().getTime() - tm)/1000.0;
	/*------------------*/
	logger.innerHTML += "<strong>loading time: "+tm+"</strong><br/>";
	logger.innerHTML += "======================================================<br/>";
	/*------------------*/
	tm = 0;
}

// remove all markers outside the map bounds
//*******************************************
function cleanMap() {
	
	//retrieve map bounds
	var minLat = bounds.getSouthWest().lat();
	var minLng = bounds.getSouthWest().lng();
	var maxLat = bounds.getNorthEast().lat();
	var maxLng = bounds.getNorthEast().lng();
	
	// widen the extent of half in the 4 directions
	// to keep in the array some markers (speed up panning)
	var span = map.getBounds().toSpan()
	var spanX = span.x;
	var spanY = span.y;
	minLat = minLat-(spanY/2);
	minLng = minLng-(spanX/2);
	maxLat = maxLat+(spanY/2);
	maxLng = maxLng+(spanX/2);
	
	// if we are performing a random query, we must remove ALL the previous random markers
	if (zoomLevel <= zoomThreshold) {
		
		var removed = 0;
		// clear all the markers
		for (var j=0; j<gmarkers.length; j++) {
			map.removeOverlay(gmarkers[j]);
			removed++;
		}
		gmarkers = [];
		htmls = [];
		/*------------------*/
		logger.innerHTML += "Markers removed: <strong>"+removed+"</strong><br/>";
		/*------------------*/
		
	// but if the query is normal then we must remove only the markers outside the view bounds
	} else {
		if (!gmarkers.length == 0) {
			
			
			var removed = 0;
			// loop through the markers array (4 times! trick to get rid of all the markers falling outside)
			for (var k=0; k<4; k++) {
				for (var j=0; j<gmarkers.length; j++) {
					// check if the marker is outside the bounds
					if (gmarkers[j].longitude < minLng || gmarkers[j].longitude > maxLng || gmarkers[j].latitude < minLat || gmarkers[j].latitude > maxLat) {
						/*------------------*/
						//logger.innerHTML += "id="+gmarkers[j].dbid+" REMOVED<br/>";
						/*------------------*/
						//delete htmls[gmarkers[j].dbid];
						map.removeOverlay(gmarkers[j]);
						gmarkers.splice(j,1);
						removed++;
					}
				}
			}
			/*------------------*/
			logger.innerHTML += "Markers removed: <strong>"+removed+"</strong><br/>";
			/*------------------*/
		}	
	}

}


// pick up the clicks in the  sidebar entries,
// center the map and open the corresponding infowindow
//******************************************************
function myclick(id) {
	
	// be sure the tooltip disappears
	tooltip.style.visibility="hidden";
	
	for (var j=0; j<gmarkers.length; j++) {
		if (id && gmarkers[j].dbid == id) {
			var marker = gmarkers[j];
		}
	}
	
	var tabsHtml = htmls[marker.dbid];
	var lat = marker.latitude;
	var lng = marker.longitude;
	
	// center and zoom on the selected organization
	map.setCenter(new GLatLng(lat, lng), 16);
	
	// open the infowindow (delay is for msie...)
	setTimeout(function() {
		map.checkResize();
		marker.openInfoWindowTabsHtml(tabsHtml);
	}, 1200);
}



// pick up the clicks in the search result div,
// recenter the map and open the infowindow
//**********************************************
function myclickS(lat,lng,id) {
	
	// center and zoom on the marker
	map.setCenter(new GLatLng(lat, lng), 16);
	
	// open the infowindow
    // we need to delay the infowindow opening, otherwise gmarkers.length=0
	setTimeout(function() {
		for (var i=0; i<gmarkers.length; i++) {
			// if the database id is the same, we got the right marker
			if (id && gmarkers[i].dbid == id) {
				map.checkResize();
				gmarkers[i].openInfoWindowTabsHtml(htmls[gmarkers[i].dbid]);
			}
		}
	}, 1200);
}



// invoked when the mouse goes over an entry in the sidebar tab
// It launches the tooltip on the icon 
//**************************************************************
function mymouseover(id) {
	// show the tooltip
	for (var i=0; i<gmarkers.length; i++) {
		if (id && gmarkers[i].dbid == id) {
			showTooltip(gmarkers[i]);
		}
	}
}



// invoked when the mouse leaves an entry in the sidebar tab
// It hides the tooltip 
//***********************************************************
function mymouseout(sidID) {
	// hide the tooltip
	tooltip.style.visibility="hidden";
	
	// remove highlight to the same entry in the sidebar
	entrytohighlight = document.getElementById(sidID).firstChild;
	entrytohighlight.style.color = "#003300";
}


// center and zoom on the selected London borough
//************************************************
function changeCategory() {
form = document.myformC.category
	OrgsOrEvents = form.options[form.selectedIndex].value;
	updateMap();	
	document.getElementById("map").focus();
}


// center and zoom on the selected London borough
//************************************************
function gotoEntry() {
	form = document.myformB.borough
	if (!form.options[0].selected) {
		latlng = form.options[form.selectedIndex].value;
		latlng = latlng.split(',');
		lat = latlng[0];
		lng = latlng[1];
		map.setCenter(new GLatLng(lat, lng), 13);
		form.options[0].selected = "selected";
	}
	document.getElementById("map").focus();
}



// display the tooltips around the marker
//****************************************
function showTooltip(marker) {
	
	// highlight the same entry in the sidebar
	sidID = marker.dbid
	//sidID-toString();
	entrytohighlight=document.getElementById(sidID).firstChild;
	entrytohighlight.style.color="blue";
		
	//move the tooltip label around the marker
	tooltip.innerHTML = marker.tooltip;
	var pointSW = map.fromLatLngToDivPixel(map.getBounds().getSouthWest());
	var pointNE = map.fromLatLngToDivPixel(map.getBounds().getNorthEast());
	var offset  = map.fromLatLngToDivPixel(marker.getPoint());
	//var anchor  = marker.getIcon().iconAnchor;
	var width   = marker.getIcon().iconSize.width;
	var height  = marker.getIcon().iconSize.height;
	var x =   offset.x - pointSW.x;
	var y = - offset.y + pointSW.y;
	var x2 = parseInt((  pointNE.x - pointSW.x) / 2);
	var y2 = parseInt((- pointNE.y + pointSW.y) / 2);
	tooltip.style.position = "absolute";
	tooltip.style.top    = "";
	tooltip.style.bottom = "";
	tooltip.style.left   = "";
	tooltip.style.right  = "";
	// TR and BR quadrants
	if (x > x2) {
		x = - offset.x + pointNE.x + width;
		var fromright = (x - width/2);
		tooltip.style.right = fromright.toString() + "px";
		// TR
		if (y > y2) {
			y = offset.y - pointNE.y;
			var fromtop = (y + height/2);
			tooltip.style.top = fromtop.toString() + "px";
		// BR
		} else {
			y = - offset.y + pointSW.y + height;
			var frombottom = (y - height/2);
			tooltip.style.bottom = frombottom.toString() + "px";
		}
	// TL and BL quadrants
	} else {
		x = offset.x - pointSW.x + width;
		var fromleft = (x - width/2);
		tooltip.style.left = fromleft.toString() + "px";
		// TL
		if (y > y2) {
			y = offset.y - pointNE.y;
			var fromtop = (y + height/2);
			tooltip.style.top = fromtop.toString() + "px";
		// BL
		} else {
			y = - offset.y + pointSW.y + height;
			var frombottom = (y - height/2);
			tooltip.style.bottom = frombottom.toString() + "px";
		}
	}
	tooltip.style.visibility="visible"; 
}



// this function hides/shows the markers
//***************************************
function legendUpdate() {

	var legend = document.getElementById("myformL")
	var icon;
	
	// loop through the legend elements
	for (var z=0; z<legend.checkgroup.length; z++){
		
		icon = legend.checkgroup[z].value;
		
		// if the checkbox is Unchecked
		if (legend.checkgroup[z].checked == false) {		
			
			// change the icon opacity to .25
			if (agent == 'msie') {
				document.getElementById("img_"+icon).style.filter='alpha(opacity=25)';
			} else {
				document.getElementById("img_"+icon).style.opacity='.25';
			}
			
			// loop through all the mapped markers
			for (var k=0;k<gmarkers.length;k++) {
				// if this marker is of the right type
				if (gmarkers[k].icontype == icon)  {
					// hide the marker
					gmarkers[k].hide();
				}
			}
			
		// if the checkbox is Checked
		} else {
			
			// change the icon opacity to 1
			if (agent == 'msie') {
				document.getElementById("img_"+icon).style.filter='alpha(opacity=100)';
			} else {
				document.getElementById("img_"+icon).style.opacity='1';
			}
			
			// loop through all the mapped markers
			for (var h=0;h<gmarkers.length;h++) {
				// if this marker is of the right type
				if (gmarkers[h].icontype == icon)  {
					// show the marker
					gmarkers[h].show();
				}
			}
		} //end if
	} // end for
}



// show all or hide all the checkboxes in the legend
//***************************************************
var checked = false;
function checkAll(field) {
	
	// if checkboxes are not checked then check them
	if (!checked) {
	
		// loop through the array of checkboxes & check them
		for (i = 0; i < field.length; i++) { 
			field[i].checked = true;
		}
		checked = true;
		
		// change the button label
		return "Hide all";
		
	} else {
	
		// loop through the array of checkboxes & uncheck them
		for (i = 0; i < field.length; i++) { 
			field[i].checked = false;
		}
		checked = false;
		
		// change the button label
		return "Show all";
	}
}



// check/uncheck the checkbox when the user clicks on the icon
//*************************************************************
function checkThis(thisckbx) {
	var legend = document.getElementById("myformL")
	var ckbx = legend.checkgroup[thisckbx];
	
	if (ckbx.checked == true) {
		ckbx.checked = false;
	} else {
		ckbx.checked = true;
	}
	
	legendUpdate();
}



// search organizations in the database
//**************************************
function preSearch(type) {
	if(OrgsOrEvents == 'organisations') {
		urlFile = 'search.php';
	}
	else if(OrgsOrEvents == 'events') {
		urlFile = 'searchevents.php';
	}
	var querystr = document.getElementById('s').value;
	querystr = trim(querystr);

	// replace "*" with "%"
	querystr = querystr.replace(/\*/g, '%'); 

    //If the form data is valid, query the DB and return the results
	if (querystr == "") {
		resultDiv.innerHTML = '<span style="color: #CC0000;"><br/>&nbsp;Please enter a name</span>';
	} else if (querystr.length < 2) {
		resultDiv.innerHTML = '';
		//	messageDiv.innerHTML = '&nbsp;Search error: string too short';
	} else {
		resultDiv.innerHTML = '<span style="color: #CC0000;"><br/>&nbsp;Searching for <strong>'+querystr+'</strong>,<br/>&nbsp;please wait . . .</span>';
		
		var url = 'inc/greenmap/' + urlFile + '?s='+querystr;
		var request = GXmlHttp.create();
		request.open('GET', url, true);
        //Check that the PHP script has finished sending us the result
		request.onreadystatechange = function() {
			if(request.readyState == 4 && request.status == 200) {
				if (!request.responseText.length == 0) {
					
					resultDiv.innerHTML = request.responseText;
					messageDiv.innerHTML = '&nbsp;Search result for <strong>'+querystr+'</strong>: ' + document.getElementById('t2').summary + ' ' + OrgsOrEvents+' found';
				} else {
					resultDiv.innerHTML = '<span style="color: #CC0000;"><br/>&nbsp;Search result for <strong>'+querystr+'</strong>:<br/>&nbsp;not found</span>';
					//messageDiv.innerHTML = '&nbsp;Search result for <strong>'+querystr+'</strong>: not found';
				}
			}
			//messageDiv.innerHTML = "&nbsp;Database connection failure, please retry later";
		};
		request.send(null);  
	}
}



// string trim function
// http://www.breakingpar.com
//****************************
function trim(inputString) {
   // Removes leading and trailing spaces from the passed string. Also removes
   // consecutive spaces and replaces it with one space. If something besides
   // a string is passed in (null, custom object, etc.) then return the input.
   if (typeof inputString != "string") { return inputString; }
   var retValue = inputString;
   var ch = retValue.substring(0, 1);
   while (ch == " ") { // Check for spaces at the beginning of the string
      retValue = retValue.substring(1, retValue.length);
      ch = retValue.substring(0, 1);
   }
   ch = retValue.substring(retValue.length-1, retValue.length);
   while (ch == " ") { // Check for spaces at the end of the string
      retValue = retValue.substring(0, retValue.length-1);
      ch = retValue.substring(retValue.length-1, retValue.length);
   }
   while (retValue.indexOf("  ") != -1) { // Note that there are two spaces in the string - look for multiple spaces within the string
      retValue = retValue.substring(0, retValue.indexOf("  ")) + retValue.substring(retValue.indexOf("  ")+1, retValue.length); // Again, there are two spaces in each of the strings
   }
   return retValue; // Return the trimmed string back to the user
} // Ends the "trim" function



// wheel zoom function
//(http://www.techgreen.freespaces.com/map/map.html)
//***************************************************
function wheelZoom(a) {
  if (a.detail) // Firefox
  {
    if (a.detail < 0)
    { map.zoomIn(); }
    else if (a.detail > 0)
    { map.zoomOut(); }
  }
  else if (a.wheelDelta) // IE
  {
    if (a.wheelDelta > 0)
    { map.zoomIn(); }
    else if (a.wheelDelta < 0)
    { map.zoomOut(); }
  }
}



// hide/show the log panel
//*************************
function showHidediv(DivId, showhide){
	if (showhide) {
		document.getElementById(DivId).style.visibility="visible";
	} else {
		document.getElementById(DivId).style.visibility="hidden";
	}
}
		


/***************************************************************
*
*	geocoder.js
*	modified from http://www.google.com/uds/samples/places.html
*
***************************************************************/
	
	
	var myMarker;
	
	// Called when Local Search results are returned, we clear the old
	// results and load the new ones.
	//*****************************************************************
	function OnLocalSearch() {
		if (!gLocalSearch.results) return;
		
		// Clear the map
		for (var i = 0; i < gCurrentResults.length; i++) {
			if (!gCurrentResults[i].selected()) {
				map.removeOverlay(gCurrentResults[i].marker());
			}
		}
		gCurrentResults = [];
		
		for (var i = 0; i < gLocalSearch.results.length; i++) {
			gCurrentResults.push(new LocalResult(gLocalSearch.results[i]));
		}
		
		// move the map to the first result
		var first = gLocalSearch.results[0];
		if (!first){
			//messageDiv.innerHTML = "&nbsp;Address not found";
			postcodeDiv.innerHTML = '<span style="color: #CC0000;"><br/>&nbsp;Address not found</span>';
		} else {
			// add a link into the result div
			myAddress  = '<table width="90%" border="0" cellpadding="2" cellspacing="5">';
			myAddress += '<tbody>';
			myAddress += '<tr>';
			myAddress += '    <td><img src="inc/greenmap/images/address_lg.png" width="15" height="25"/></td>';
			myAddress += '    <td align="left">Postcode: <a href="javascript:myclickP('+parseFloat(first.lat)+', '+parseFloat(first.lng)+')" title="Visit this address">'+document.getElementById("q").value.toUpperCase()+'</a></td>';							
			myAddress += '</tr>';
			myAddress += '</tbody>';
			myAddress += '</table>';
			postcodeDiv.innerHTML = myAddress;
			
			map.setCenter(new GLatLng(parseFloat(first.lat), parseFloat(first.lng)), 16);
		}
	}
	
	
	
	// Cancel the form submission, executing an AJAX Search API search.
	//******************************************************************
	function CaptureForm(form) {
		if (!form["q"].value) {
			postcodeDiv.innerHTML = '<span style="color: #CC0000;"><br/>&nbsp;Please enter a postcode</span>';
			return false;
		} else {
			postcodeDiv.innerHTML = '<span style="color: #CC0000;"><br/>&nbsp;Searching for <strong>'+form["q"].value.toUpperCase()+'</strong>,<br/>&nbsp;please wait . . .</span>';
			gLocalSearch.execute(form["q"].value);
			return false;
		}
	}
	
	
	
	// A class representing a single Local Search result 
	// returned by the Google AJAX Search API.
	//***************************************************
	function LocalResult(result) {
		this.result_ = result;
		map.addOverlay(this.marker(searchIcon));
	}
	
	
	
	// Returns the GMap marker for this result, creating it with the given
	// icon if it has not already been created.
	//*********************************************************************
	LocalResult.prototype.marker = function(opt_icon) {
		if (this.marker_) return this.marker_;
		var marker = new GMarker(new GLatLng(parseFloat(this.result_.lat), parseFloat(this.result_.lng)), {icon:opt_icon, title:document.getElementById("q").value});
		
		GEvent.bind(marker, "click", this, function() {
													
			marker.postcode = "<b>Postcode:</b> "+document.getElementById("q").value.toUpperCase()+
								'<br/><br/><div class="infoWindowPostcode" align="center"><hr><a href="http://maps.google.com/maps?saddr=&daddr='+parseFloat(this.result_.lat)+
								',' +parseFloat(this.result_.lng)+ '" target ="_blank" onmouseover="javascript:infoDirOver()" onmouseout="javascript:infoDirOut()">Directions to/from here</a></div>';
			
			marker.id = "Postcode"
			marker.openInfoWindowHtml(marker.postcode);
		
		});
		this.marker_ = marker;
		myMarker = marker;
		return marker;
	}
	
	
	
	// Returns true if this result is currently "saved"
	//**************************************************
	LocalResult.prototype.selected = function() {
		return this.selected_;
	}
	
	
	function myclickP(lat, lng) {
	
		map.setCenter(new GLatLng(lat, lng), 16);
		myMarker.mypostcode = "<b>Postcode:</b> "+document.getElementById("q").value.toUpperCase()+
								'<br/><br/><div class="infoWindowPostcode" align="center"><hr><a href="http://maps.google.com/maps?saddr=&daddr='+lat+
								',' +lng+ '" target ="_blank" onmouseover="javascript:infoDirOver()" onmouseout="javascript:infoDirOut()">Directions to/from here</a></div>';
	
		myMarker.openInfoWindowHtml(myMarker.mypostcode);
	
	}
// end geocoder



/***********************************************
*	sorttable.js
* 	http://kryogenix.org/code/browser/sorttable
*
***********************************************/
	
	addEvent(window, "load", sortables_init);
	
	var SORT_COLUMN_INDEX;
	
	function sortables_init() {
		// Find all tables with class sortable and make them sortable
		if (!document.getElementsByTagName) return;
		tbls = document.getElementsByTagName("table");
		for (ti=0;ti<tbls.length;ti++) {
			thisTbl = tbls[ti];
			if (((' '+thisTbl.className+' ').indexOf("sortable") != -1) && (thisTbl.id)) {
				//initTable(thisTbl.id);
				ts_makeSortable(thisTbl);
			}
		}
	}
	
	function ts_makeSortable(table) {
		if (table.rows && table.rows.length > 0) {
			var firstRow = table.rows[0];
		}
		if (!firstRow) return;
		
		// We have a first row: assume it's the header, and make its contents clickable links
		for (var i=0;i<firstRow.cells.length;i++) {
			var cell = firstRow.cells[i];
			var txt = ts_getInnerText(cell);
			cell.innerHTML = '<a href="#" class="sortheader" '+ 
			'onclick="ts_resortTable(this, '+i+');return false;">' +
			txt+'<span class="sortarrow">&nbsp;&nbsp;&nbsp;</span></a>';
		}
	}
	
	function ts_getInnerText(el) {
		if (typeof el == "string") return el;
		if (typeof el == "undefined") { return el };
		if (el.innerText) return el.innerText;	//Not needed but it is faster
		var str = "";
		
		var cs = el.childNodes;
		var l = cs.length;
		for (var i = 0; i < l; i++) {
			switch (cs[i].nodeType) {
				case 1: //ELEMENT_NODE
					str += ts_getInnerText(cs[i]);
					break;
				case 3:	//TEXT_NODE
					str += cs[i].nodeValue;
					break;
			}
		}
		return str;
	}
	
	function ts_resortTable(lnk,clid) {
		// get the span
		var span;
		for (var ci=0;ci<lnk.childNodes.length;ci++) {
			if (lnk.childNodes[ci].tagName && lnk.childNodes[ci].tagName.toLowerCase() == 'span') span = lnk.childNodes[ci];
		}
		var spantext = ts_getInnerText(span);
		var td = lnk.parentNode;
		var column = clid || td.cellIndex;
		var table = getParent(td,'TABLE');
		
		// Work out a type for the column
		if (table.rows.length <= 1) return;
		var itm = ts_getInnerText(table.rows[1].cells[column]);
		sortfn = ts_sort_caseinsensitive;
		if (itm.match(/^\d\d[\/-]\d\d[\/-]\d\d\d\d$/)) sortfn = ts_sort_date;
		if (itm.match(/^\d\d[\/-]\d\d[\/-]\d\d$/)) sortfn = ts_sort_date;
		if (itm.match(/^[£$]/)) sortfn = ts_sort_currency;
		if (itm.match(/^[\d\.]+$/)) sortfn = ts_sort_numeric;
		SORT_COLUMN_INDEX = column;
		var firstRow = new Array();
		var newRows = new Array();
		for (i=0;i<table.rows[0].length;i++) { firstRow[i] = table.rows[0][i]; }
		for (j=1;j<table.rows.length;j++) { newRows[j-1] = table.rows[j]; }
	
		newRows.sort(sortfn);
	
		if (span.getAttribute("sortdir") == 'down') {
			ARROW = '&nbsp;&nbsp;&uarr;';
			newRows.reverse();
			span.setAttribute('sortdir','up');
		} else {
			ARROW = '&nbsp;&nbsp;&darr;';
			span.setAttribute('sortdir','down');
		}
		
		// We appendChild rows that already exist to the tbody, so it moves them rather than creating new ones
		// don't do sortbottom rows
		for (i=0;i<newRows.length;i++) { if (!newRows[i].className || (newRows[i].className && (newRows[i].className.indexOf('sortbottom') == -1))) table.tBodies[0].appendChild(newRows[i]);}
		// do sortbottom rows only
		for (i=0;i<newRows.length;i++) { if (newRows[i].className && (newRows[i].className.indexOf('sortbottom') != -1)) table.tBodies[0].appendChild(newRows[i]);}
		
		// Delete any other arrows there may be showing
		var allspans = document.getElementsByTagName("span");
		for (var ci=0;ci<allspans.length;ci++) {
			if (allspans[ci].className == 'sortarrow') {
				if (getParent(allspans[ci],"table") == getParent(lnk,"table")) { // in the same table as us?
					allspans[ci].innerHTML = '&nbsp;&nbsp;&nbsp;';
				}
			}
		}
			
		span.innerHTML = ARROW;
	}
	
	function getParent(el, pTagName) {
		if (el == null) return null;
		else if (el.nodeType == 1 && el.tagName.toLowerCase() == pTagName.toLowerCase())	// Gecko bug, supposed to be uppercase
			return el;
		else
			return getParent(el.parentNode, pTagName);
	}
	function ts_sort_date(a,b) {
		// y2k notes: two digit years less than 50 are treated as 20XX, greater than 50 are treated as 19XX
		aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]);
		bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]);
		if (aa.length == 10) {
			dt1 = aa.substr(6,4)+aa.substr(3,2)+aa.substr(0,2);
		} else {
			yr = aa.substr(6,2);
			if (parseInt(yr) < 50) { yr = '20'+yr; } else { yr = '19'+yr; }
			dt1 = yr+aa.substr(3,2)+aa.substr(0,2);
		}
		if (bb.length == 10) {
			dt2 = bb.substr(6,4)+bb.substr(3,2)+bb.substr(0,2);
		} else {
			yr = bb.substr(6,2);
			if (parseInt(yr) < 50) { yr = '20'+yr; } else { yr = '19'+yr; }
			dt2 = yr+bb.substr(3,2)+bb.substr(0,2);
		}
		if (dt1==dt2) return 0;
		if (dt1<dt2) return -1;
		return 1;
	}
	
	function ts_sort_currency(a,b) { 
		aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]).replace(/[^0-9.]/g,'');
		bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]).replace(/[^0-9.]/g,'');
		return parseFloat(aa) - parseFloat(bb);
	}
	
	function ts_sort_numeric(a,b) { 
		aa = parseFloat(ts_getInnerText(a.cells[SORT_COLUMN_INDEX]));
		if (isNaN(aa)) aa = 0;
		bb = parseFloat(ts_getInnerText(b.cells[SORT_COLUMN_INDEX])); 
		if (isNaN(bb)) bb = 0;
		return aa-bb;
	}
	
	function ts_sort_caseinsensitive(a,b) {
		aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]).toLowerCase();
		bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]).toLowerCase();
		if (aa==bb) return 0;
		if (aa<bb) return -1;
		return 1;
	}
	
	function ts_sort_default(a,b) {
		aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]);
		bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]);
		if (aa==bb) return 0;
		if (aa<bb) return -1;
		return 1;
	}
	
	
	function addEvent(elm, evType, fn, useCapture)
	// addEvent and removeEvent
	// cross-browser event handling for IE5+,  NS6 and Mozilla
	// By Scott Andrew
	{
	  if (elm.addEventListener){
		elm.addEventListener(evType, fn, useCapture);
		return true;
	  } else if (elm.attachEvent){
		var r = elm.attachEvent("on"+evType, fn);
		return r;
	  } else {
		alert("Handler could not be removed");
	  }
	} 
// end sorttable


