/**
 * Javascript invocation filesource.
 *
 * @description	This is where all the magic happens. Bleeding eyes!
 * @project		LondonTypographica
 * @author		$Id: common.js 3852 2011-12-08 10:56:02Z Nick $
 * @version		$Rev: 3852 $
 */

// ------------------------------------------------------------------------

/*
 * Script debugging
 */
window.debug = new Boolean(1);
window.undefined;


/**
 * Primary Application Container
 *
 * @namespace	App
 * @since		1.0.0
 */
window.App = {
	/**
	 * Catches exceptions and logs them instead of killing the script
	 *
	 * @function	{abstract App}	Exception
	 * @returns		{bool}
	 */
	Exception: function()
	{
		log(arguments);
		return true;
	},

	/**
	 * Instantiates an instance or returns an existing instance
	 *
	 * @function	{abstract App.Core} load
	 * @return		{object}
	 */
	Load: function(name, args)
	{
		if(typeof name != 'string') {
			throw "Cannot instantiate object. {name} empty";
			return;
		};

		var args = args || null,
			allowed = ['Map','Page','Toolbar','Overlay','Sidebar'];

		// The contructor should as invocable
		if( inArray(name,allowed,true) ) {
			// Needs to be a Function, of course!
			if(window[name].constructor === Function) {
				if(this[name] !== undefined) return this[name];// Return existing instance

				this[name] = new window[name](args);
				return this[name];// Return new instance
			};

			log('Failed to invoke {'+name+'}: Object does not exist');
			return null;
		};

		log('Failed to invoke {'+name+'}: Either reserved or not an invocation');
		return null;
	},

	/**
	 * Preliminary initialization container
	 *
	 * @function	{abstract App} Start
	 * @returns		{void}
	 */
	Start: function()
	{
		var pathArray = window.location.pathname.split('/');
		if(pathArray.length > 1 && typeof pathArray[1] == "string") {
			switch(pathArray[1]){
				default:
					if(document.getElementById('themap')) {
						// Invoke the Map Application
						App.Load('Map');
					};
			}
		};

		// Bind the global window.onresize listener
		var resizer = function(){App.Container.onResize.call(App.Container);};
		$(window).bind('resize.container', resizer);
		// And execute it initially
		resizer();
						
		// homogenise column heights
		if($('.eq').length) {
			var items = $('.eq'), tallest = 0;


			items.each(function(){
				var $el = $(this),
					b = parseInt($el.css('border-top-width'))+parseInt($el.css('border-bottom-width')),
					p = parseInt($el.css('padding-top'))+parseInt($el.css('padding-bottom')),
					h = parseInt($(this).height()-p-b);

				if(h > tallest) tallest = h;
			});

			items.height(tallest);
		}

		$("html").removeClass("no-js").addClass("js");
	},

	/**
	 * Container object referencing methods related to it's manipulation
	 *
	 * @function	{abstract App} Container
	 * @returns		{void}
	 */
	Container:
	{
		/**
		 * Resizes the container to isolate the App
		 */
		onResize: function()
		{
			var h = this.getHeight();
			this.setHeight((h <= 400) ? 400 : h);
			this.setWidth(window.innerWidth);
		},

		getHeight: function()
		{
			return (window.innerHeight-document.getElementsByTagName('header')[0].offsetHeight);
		},

		setHeight: function(val)
		{
			$(this.element()).height(parseInt(val));
		},

		getWidth: function()
		{
			return window.innerWidth;
		},

		setWidth: function(val)
		{
			$(this.element()).width(parseInt(val));
		},

		/**
		 * Object Constant
		 */
		element: function()
		{
			return document.getElementById('main');
		}
	}
};

/*
window.Pages = function(opts)
{
	var that = this,
	defaults = {
		'id': null,
		'request': {}
	};

	this.options = $.extend(defaults, opts);
	this.ajaxSettings = $.extend({}, this.options.request);

	//var method = ['beforeSend','complete','success','error'];
	for(var key in this.request)
	{
		this.ajaxSettings[key] = (function(key){
			return function(){
				if(null !== that.request[key].apply(that,arguments))
				{
					if(that.options.request.hasOwnProperty(key) && that.options.request[key].constructor === Function)
					{
						return that.options.request[key].apply(that,arguments);
					}
				}
			};
		})(key);
	};

	//$.ajax(this.ajaxSettings);
};
Pages.prototype = {
	request: {
		beforeSend: function(){},
		complete: function(){},
		success: function(){},
		error: function(){}
	},

	getPageContainer: function()
	{
		return document.getElementById('content');
	}
};
*/



/**
 * Support page initialiser
 *
 * @function	{abstract App} Support
 * @returns		{void}
Support: function()
{
	function UXForm(formElement, fieldElementsCollection) {

		// Field class
		function Field(id, container, type, required, validationFn) {
			this.id = id;
			this.container = container;
			this.label = $("label", this.container);
			this.input = $(type, this.container);
			this.required = required;
			this.validationFn = (typeof validationFn == "function") ? validationFn : function() { return true; };
			this.valid = (this.required) ? false : true;

			// bind events
			$(this.container)
				.hover($.proxy(this.showRequired,this),$.proxy(this.hideRequired,this));
			$(this.input)
				.bind('focus',$.proxy(this.showRequired,this))
				.bind('blur',$.proxy(this.hideRequired,this))
				.bind('keyup',$.proxy(this.validate,this));

			console.log(this.container, this.label, this.input)
		}
		Field.prototype = {
			log: function(msg) {
				console.log(this.id+": ", msg);
			},
			validate: function() {
				if(this.required && this.isEmpty()) {
					this.log("Required field is missing");
					this.valid = false;
					return false;
				} else {
					var result = this.validationFn.call(this, $(this.input).val());
					if(typeof result == "boolean") {
						this.log(result ? "valid" : "invalid");
						this.valid = result;
						return result;
					} else {
						console.log("Field "+this.id+": validation function is not returning a boolean value");
					}
				}
				this.valid = true;
				return true;
			},
			isEmpty: function() {
				return ($.trim($(this.input).val())) ? false : true;
			},
			showRequired: function() {
				$(this.container).addClass("required");
			},
			hideRequired: function() {
				if(!$(this.input).is(':focus')) {
					$(this.container).removeClass("required");
				}
			}
		}

		// constants
		this.form = $(formElement);
		this.alert = $("div.alert", this.form);
		this.submit = $("input[type=submit]", this.form);

		// populate fields
		this.fields = {};
		for(var f in fieldElementsCollection) {
			console.log("Processing: "+f);
			// store temporary field data object
			var temp = fieldElementsCollection[f];
			// allow skipping of validation function
			temp.validation = (typeof temp.validation == "function") ? temp.validation : false;
			// init new Field object
			var field = new Field(f, $(temp.selector, this.form), temp.type, temp.required, temp.validation);
			// add field to fields object
			this.fields[f] = field;
		}

		// event binding
		$("input[type=submit]")
			.click($.proxy(this.process,this));
		$("input[type=text],textarea")
			.bind('blur',$.proxy(this.validate,this));

		// validate incase data exists after refresh
		this.validate();

		// check for any messages that need to be displayed
		if(typeof form_submission_response == "object") {
			this.showAlert(form_submission_response.message);
		}

	}
	UXForm.prototype = {
		log: function(msg) {
			console.log("UXForm: ", msg);
		},
		process: function(e) {
			if(this.validate()) {
				this.log("All good");
			} else {
				this.log("Sheeeeeeeeeit");
				e.preventDefault();
			}
		},
		validate: function() {
			var response = true;
			// loop over fields and run their validation function
			for(f in this.fields) {
				if(!this.fields[f].validate()) {
					response = false;
				}
			}
			this.log(response ? "valid" : "invalid");
			response ? this.showValid() : this.showInValid();
			return response;
		},
		showValid: function() {
			$(this.form).addClass("valid");
		},
		showInValid: function() {
			$(this.form).removeClass("valid");
		},
		showAlert: function(msg) {
			$(this.alert).text(msg).addClass("show");
		}
	}

	var uxf = new UXForm(
		$("form"),
		{
			email: {
				selector: ".field_email",
				type: "input[type=text]",
				required: true,
				validation: function(email) { 
					var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
					return re.test(email);
				}
			},
			message: {
				selector: ".field_message",
				type: "textarea",
				required: true
			}
		}
	);

},
 */


/**
 * Assitive Toolbar
 *
 * @constructor	Toolbar
 * @since		1.0.0
 */
window.Toolbar = function(opts){
	var that = this;

	$.extend(this, {
		'element':		document.getElementById('toolbar'),
		'closeBtn':		'.buttons > .close',
		'isVisible':	new Boolean(),
		'height':		null,
		'ontoggle':		null
	}, opts||{});

	this.element.style.visibility = 'hidden';
	this.height = this.element.offsetHeight-1;
	this.element.style.bottom = -this.height+'px';
	this.isVisible = (parseInt(this.element.style.bottom) >= 0);

	$(this.element).bind('click', function(e){
		e.preventDefault();
		that.toggle();
	});
};
Toolbar.prototype = {
	/**
	 * Toggle visible state of the toolbar
	 *
	 * @function	{abstract Toolbar}	toggle
	 * @returns		{void}
	 */
	toggle: function()
	{
		var that = this,
			conf = {
				'css'		: {'bottom':0},
				'speed'		: 350,
				'visible'	: true
		};

		if(this.toolbar.isVisible) {
			conf.css.bottom = -this.toolbar.height;
			conf.speed = 250;
			conf.visible = false;
		};

		$(this.toolbar.element).animate(conf.css, conf.speed, function(){
			that.toolbar.isVisible = conf.visible;
			if(that.ontoggle) that.ontoggle.call(that);
		});
	}
};


/**
 * Google Maps Initialization
 *
 * @constructor	{private}	App.Map
 * @since		1.0.0
 */
window.initMap = function()
{
	App.Load('Map').initialize();
};
window.Map = function()
{
	// Load the map async  //  
	$.getScript("http://maps.googleapis.com/maps/api/js?sensor=false&callback=initMap");
	// Toolbar properties
	//this.toolbar = App.Load('Toolbar');
};
Map.prototype = {
	/**
	 * Initialize the map
	 *
	 * This method is triggered by Google Maps callback after the
	 * script has initiated.
	 *
	 * @function {abstract App.Map} initialize
	 * @return {void}
	 */
	initialize: function()
	{
		log('Initializing Google Maps API callback');

		// Map properties
		this.map = {
			'ready':		new Boolean(),
			'element':		document.getElementById('themap'),
			'mapTypeId': {
				zoomedIn: 'TypographicaIn',
				zoomedOut: 'TypographicaOut'
			},
			'canvas':		null,
			'lat':			51.5001524,
			'lng':			-0.1262362,
			'markerCollection':	new Object(),
			'marker': {
				'icon':		'/images/marker.png',
				'cursor':	'pointer',
				'animation':google.maps.Animation.DROP,
			},
			'options': {
				'mapTypeId':		google.maps.MapTypeId.ROADMAP,
				'backgroundColor':	'rgb(51,51,51)',
				'zoom': 			13,
				'minZoom':			11,
				'maxZoom':			18,
				'scrollwheel': 		false,
				'disableDefaultUI':	true,
				'zoomControl': 		true,
				'panControl':		true,
				'zoomControlOptions': {
					'position': google.maps.ControlPosition.TOP_RIGHT,
					'style': google.maps.ZoomControlStyle.LARGE
				},
				'panControlOptions': {
					'position': google.maps.ControlPosition.TOP_RIGHT
				}
			},
			'colors': {
				'white':	"rgb(255,255,255)",
				'pale': 	"rgb(226,228,229)",
				'light': 	"rgb(179,179,179)",
				'medium': 	"rgb(129,129,129)",
				'dark': 	"rgb(115,115,115)",
				'vdark': 	"rgb(115,115,115)",
				'black':	"rgb(51,51,51)"
			},
			'styles': {
				'zoomedIn': [
					{featureType:'all',elementType:'all',stylers:[{hue:'rgb(129,129,129)'},{lightness:0},{saturation:-100},]},
					{featureType:'road.local',elementType:'geometry',stylers:[{lightness:-30}]},
					{featureType:'road.arterial',elementType:'geometry',stylers:[{lightness:-80}]},
					{featureType:'road.highway',elementType:'geometry',stylers:[{lightness:-80}]},
					//{featureType: "road.highway",elementType: "labels",stylers: [{ visibility: "off" }]}, // show hidhway labels
					{featureType:'transit.station',elementType:'all',stylers:[{lightness:-20}]},
					{featureType:'poi.school',elementType:'all',stylers:[{visibility:'off'}]},
					{featureType:'poi.medical',elementType:'all',stylers:[{visibility:'off'}]},
					{featureType:'poi.place_of_worship',elementType:'all',stylers:[{visibility:'off'}]},
					{featureType:'landscape.natural',elementType:'all',stylers:[{visibility:'off'}]},
					{featureType:'water',elementType:'all',stylers:[{lightness:20}]},
					{featureType:'poi.sports_complex',elementType:'all',stylers:[{visibility:'off'}]},
					{featureType:'transit.station.rail',elementType:'all',stylers:[{hue:'#c79900'}]},
					{featureType:'transit.line',elementType:'all',stylers:[{hue:'#ff0000'}]},
					//{featureType:'administrative',elementType:'all',stylers:[{visibility:'off'}]}, // show administrative labels
					{featureType:'transit.station.airport',elementType:'all',stylers:[{visibility:'off'}]},
					{featureType:'poi.park',elementType:'all',stylers:[{lightness:30}]},
					{featureType:'landscape',elementType:'all',stylers:[{lightness:-50}]},
					{featureType:'landscape.natural',elementType:'all',stylers:[{visibility:'off'}]},
					{featureType:'landscape.man_made',elementType:'all',stylers:[{visibility:'off'}]}
				],
				'zoomedOut': [
					{featureType:'all',elementType:'all',stylers:[{hue:'rgb(129,129,129)'},{lightness:0},{saturation:-100},]},
					{featureType:'road.local',elementType:'geometry',stylers:[{lightness:-30}]},
					{featureType:'road.arterial',elementType:'geometry',stylers:[{lightness:-80}]},
					{featureType:'road.highway',elementType:'geometry',stylers:[{lightness:-80}]},
					{featureType: "road.highway",elementType: "labels",stylers: [{ visibility: "off" }]}, // hide highway labels due to rendering bug in GMaps v3
					{featureType:'transit.station',elementType:'all',stylers:[{lightness:-20}]},
					{featureType:'poi.school',elementType:'all',stylers:[{visibility:'off'}]},
					{featureType:'poi.medical',elementType:'all',stylers:[{visibility:'off'}]},
					{featureType:'poi.place_of_worship',elementType:'all',stylers:[{visibility:'off'}]},
					{featureType:'landscape.natural',elementType:'all',stylers:[{visibility:'off'}]},
					{featureType:'water',elementType:'all',stylers:[{lightness:20}]},
					{featureType:'poi.sports_complex',elementType:'all',stylers:[{visibility:'off'}]},
					{featureType:'transit.station.rail',elementType:'all',stylers:[{hue:'#c79900'}]},
					{featureType:'transit.line',elementType:'all',stylers:[{hue:'#ff0000'}]},
					//{featureType:'administrative',elementType:'all',stylers:[{visibility:'off'}]}, // show administrative labels
					{featureType:'transit.station.airport',elementType:'all',stylers:[{visibility:'off'}]},
					{featureType:'poi.park',elementType:'all',stylers:[{lightness:30}]},
					{featureType:'landscape',elementType:'all',stylers:[{lightness:-50}]},
					{featureType:'landscape.natural',elementType:'all',stylers:[{visibility:'off'}]},
					{featureType:'landscape.man_made',elementType:'all',stylers:[{visibility:'off'}]}
				]
			}
		};

		// Sidebar settings
		this.sidebar = {
			'container':	document.getElementById('sidebar'),
			'closeBtn':		document.getElementById('sidebar-close')
		};

		// Collect any existing marker attributes
		if(MARKERS !== undefined && MARKERS.constructor === Object) {
			this.appendCollection(MARKERS);
		};

		// Instantiate the map
		this.map.canvas = new google.maps.Map(this.map.element,this.map.options);

		// Center app to default location
		this.map.canvas.setCenter(this.getLatLng(this.map.lat,this.map.lng));

		// map style options
		var styledMapOptionsOut = {map: this.map.canvas, name: 'ZoomedOut'};
		var styledMapOptionsIn = {map: this.map.canvas, name: 'zoomedIn'};

		// zoomed out map
		var sMapType = new google.maps.StyledMapType(this.map.styles.zoomedOut,styledMapOptionsOut); 
		this.map.canvas.mapTypes.set(this.map.mapTypeId.zoomedOut, sMapType); 

		// zoomed in map
		var sMapType2 = new google.maps.StyledMapType(this.map.styles.zoomedIn,styledMapOptionsIn); 
		this.map.canvas.mapTypes.set(this.map.mapTypeId.zoomedIn, sMapType2);

		// set map to zoomedIn style by default
		this.map.canvas.setMapTypeId(this.map.mapTypeId.zoomedIn);

		// Map onload callback
		google.maps.event.addListener(this.map.canvas, 'tilesloaded', function(){
			if(!this.ready) {
				setTimeout(function(){
					$(App.Container.element()).removeClass('loader');
					this.displayMarkers();
					this.ready = true;
				}.bind(this), 100);
			}
		}.bind(this));

		// Map zoom_changed handler switches map style when zoom level is less than 12
		google.maps.event.addListener(this.map.canvas, 'zoom_changed', function()
		{
			var zoomLevel = this.map.canvas.getZoom();
			//DEBUG alert(zoomLevel+', '+map.getMapTypeId());
			var sMapType;
			// === IF Zoom Level <= 8 use mapStyleZoomedIn 
			if(zoomLevel <=11)
				this.map.canvas.setMapTypeId(this.map.mapTypeId.zoomedOut);
			// === If Zoom Level > 8 use mapStyleZoomedOut 
			else
				this.map.canvas.setMapTypeId(this.map.mapTypeId.zoomedIn); 
		}.bind(this));
	},

	/**
	 * Returns the google.maps.LatLng class
	 *
	 * @function	{abstract App.Map}	getLatLng
	 * @params		{array}
	 * @returns		{object}			google.maps.LatLng class
	 */
	getLatLng: function()
	{
		return new google.maps.LatLng(arguments[0],arguments[1]);
	},

	appendCollection: function(collection)
	{
		this.map.markerCollection = new Object();
		for(var i in collection) this.map.markerCollection[i] = {'data':collection[i]};
	},

	/**
	 * This event is fired when the map tiles are finalised
	 *
	 * @function	{abstract App.Map}	displayMarkers
	 * @returns		{void}
	 */
	displayMarkers: function()
	{
		for(var i in this.map.markerCollection) {
			this.map.markerCollection[i].marker = new google.maps.Marker(
				$.extend({}, this.map.marker, {
					'position': this.getLatLng(this.map.markerCollection[i].data.lat,this.map.markerCollection[i].data.lng),
					'map': this.map.canvas,
					'asset_id': i
				})
			);

			google.maps.event.addListener(this.map.markerCollection[i].marker, 'click', function(){
				new Map.prototype.Sidebar(this);
			});
		};
	},

	/**
	 * Clear the markers on the map
	 *
	 * @function	{abstract App.Map}	clearMarkers
	 * @returns		{void}
	 */
	clearMarkers: function()
	{
		if(!this.map.markerCollection.length) return;

		for(var i in this.map.markerCollection) {
			this.map.markerCollection[i].setMap(null);
		};

		this.markerCollection = new Array();
	},

	panTo: function(LatLng)
	{
		this.map.canvas.panTo(LatLng);
	},

	panBy: function(x,y)
	{
		this.map.canvas.panBy(x,y)
	}
};


/**
 * Sidebar controller
 *
 * TODO: 
 *
 * @namespace	App
 * @since		1.0.0
 */
Map.prototype.Sidebar = function(marker, options)
{
	$.extend(this, {'asset':App.Map.map.markerCollection[marker.asset_id].data}, App.Map.sidebar);

	this.marker = marker;

	this.options = $.extend({
		'id': 'asset-'+this.marker.asset_id,
		'elementClass': 'infobox',
		'autoOpen': true
	}, options || {});

	log('Initializing Map.Sidebar ['+this.options.id+']');

	// EEK! Someone removed the siderbar?!
	if( !this.container ) return;

	// Resize the sidebar on window resize
	$(window).bind('resize.sidebar', this.resizer.bind(this));
	this.resizer();

	// Sidebar close button event listener
	if(!$.data(this.closeBtn, 'hasClickListener')) {
		log('Bind Sidebar close event listener');
		$(this.closeBtn)
			.data('hasClickListener', true)
			.click('click.sidebar', function(e){
				e.preventDefault();
				this.close();
			}.bind(this));
	};

	// Does the requested asset data already exist?
	this.element = document.getElementById(this.options.id);
	// Append the new data if it doesn't
	this.element = (!this.element) ? this.create() : this.element;

	// Auto-open? defaults to true
	if(this.options.autoOpen){
		this.open();
	}
};
Map.prototype.Sidebar.prototype = {
	/**
	 * Resizes the sidebar
	 *
	 * @function	{abstract App.Map.Sidebar}	resizer
	 * @returns		{void}
	 */
	resizer: function()
	{
		$(this.container).height(App.Container.getHeight());
	},

	/**
	 * Returns boolean on sidebar visibility
	 *
	 * @function	{abstract App.Map.Sidebar}	isVisible
	 * @returns		{void}
	 */
	isVisible: function()
	{
		return $(this.container).is(':visible');
	},

	/**
	 * Create the asset info box and appends to the sidebar
	 *
	 * @function	{abstract App.Map.Sidebar}	create
	 * @returns		{HTMLElement}
	 */
	create: function()
	{
		var parentNode = document.createElement('div'), childNode = [];

		var createElement = function(el,attr,inner){
				var isInner = !!inner;

				var node = document.createElement(el);

				if(attr && attr.constructor === Object) {
					for(var property in attr) {
						node.setAttribute(property, attr[property]);
					}
				};

				if(isInner)	node.innerHTML = inner;

				return node;
		};

		parentNode.setAttribute('class',this.options.elementClass);
		parentNode.setAttribute('id',this.options.id);
		parentNode.style.display = 'none';// hide for fadeIn

		if(this.asset.hasOwnProperty('title')) {
			var node = createElement('h2', null, this.asset.title);
			childNode.push(node);
		};

		if(this.asset.hasOwnProperty('display_name')) {
			var node = createElement('h3', null, "Submitted by "+this.asset.display_name);
			childNode.push(node);
		};

		if(this.asset.hasOwnProperty('tags') && this.asset.tags.length) {
			var node = createElement('h4', {'class':'tags'}, "Tags: "+this.asset.tags.join(', '));
			childNode.push(node);
		};

		if(this.asset.hasOwnProperty('image')) {
			var node = createElement('div', {'class':'image'});

			var w = (this.asset.hasOwnProperty('width')) ? this.asset.width : false,
				h = (this.asset.hasOwnProperty('height')) ? this.asset.height : false

			if(h) node.style.height = h+'px';

			childNode.push(node);

			this.preloadImage(node, w, h);
		};

		if(this.asset.hasOwnProperty('desc')) {
			var node = createElement('div', {'class':'desc'}, this.asset.desc);
			childNode.push(node);
		};

		// Append child elements to container
		for(var i in childNode) parentNode.appendChild(childNode[i]);
		// Append container to sidebar
		this.container.appendChild(parentNode);
		// Reference this element
		return document.getElementById(this.options.id);
	},

	/**
	 * Preload the asset and append to a parentNode
	 *
	 * @function	{abstract App.Map.Sidebar}	preloadImage
	 * @params		{HTMLElement}				parentNode
	 * @returns		{void}
	 */
	preloadImage: function(parentNode, w, h)
	{
		var $el = $(parentNode).addClass('loader');

		var node = document.createElement('img');
		node.setAttribute('alt',this.asset.title);
		
		if(w) node.setAttribute('width',w);
		if(h) node.setAttribute('height',h);

		node.onload = function(){
			$el.removeClass('loader');
			parentNode.appendChild(node);
		};
		node.src = this.asset.image;
	},

	/**
	 * Opens the sidebar and displays the asset data
	 *
	 * @function	{abstract App.Map.Sidebar}	open
	 * @returns		{void}
	 */
	open: function()
	{
		var $el = $(this.element);
		// Hide any existing mothermarkers
		$('.'+this.options.elementClass+':visible', this.container).hide();

		if(!this.isVisible()) {
			$(this.container).show().animate({'left':0}, 400, function(){
				$el.fadeIn(250, function(){
					this.panToMarker();
				}.bind(this));
			}.bind(this));
		}
		else {
			$el.fadeIn(250, function(){
				this.panToMarker();
			}.bind(this));
		}
	},

	/**
	 * Closes the sidebar
	 *
	 * @function	{abstract App.Map.Sidebar}	close
	 * @returns		{void}
	 */
	close: function()
	{
		$('.'+this.options.elementClass+':visible', this.container).hide();

		if(this.isVisible()) {
			$(this.container).animate({'left':-this.container.offsetWidth}, 400, function(){
				$(this).hide();
			});
		}
	},

	panToMarker: function()
	{
		var left = Math.round(App.Container.getWidth()/2),
			view = Math.round(App.Container.getWidth()-577),
			offset = left-view;

		if(offset < 0) {
			offset = Math.abs(offset);
			center = 0-((view/2)-offset);
		}
		else {
			// Need to work out the positive offset
		}

		// Pan to the marker's center point
		App.Map.panTo(this.marker.getPosition());
		// Adjust position to viewport
		App.Map.panBy(center,0);
	}
};


/**
 * inArray - Find value in array
 *
 * Extends the Array prototype to walk through an array
 * and find an existing value. This is quicker than jQuery's
 * native jQuery.inArray method, which is a little more arduous.
 *
 * @access	{public}
 * @param	{string}	needle
 * @param	{object}	haystack
 * @param	{boolean}	strict
 * @return	{boolean}
 */
window.inArray = function(needle, haystack, strict)
{
	var key = '', strict = !! strict;
	for(key in haystack) {
		if(strict) {
			if(haystack[key] === needle) return true;
		}
		else {
			if(haystack[key] == needle) return true;
		}
	};
	return false;
};

/**
 * Binding scope for objective methods
 *
 * Extends the Function prototype to allow binding a scope
 * to a function, increasing the ease of objective scope
 * accessibility.
 *
 * @access	{public}
 * @param	{object}	scope
 * @return	{function}
 */
if(!Function.prototype.bind) {
	Function.prototype.bind = function(scope)
	{
		var TheFunction = this;
		return function() {
			return TheFunction.apply(scope,arguments);
		}
	};
};



/*
 * Error handling
 */
//window.onerror = App.Exception;

/*
 * Initiate scripts
 */
window.onload = App.Start;
