<?xml version="1.0" encoding="UTF-8"?>
<Module>
<ModulePrefs
		title="Write fast, portable code for Mapplets and Maps API"
		author="Michael Geary"
		author_email="mapplet.feedback@mg.to"
		description="This API function lets you write faster, simpler async code for your Google Mapplet - and run the same code with the Maps API."
		thumbnail="http://mg.to/mapplet/async/thumbnail.png"
		screenshot="http://mg.to/mapplet/async/screenshot.png"
>
	<Require feature="sharedmap"/>
</ModulePrefs>
<Content type="html"><![CDATA[

<style type="text/css">
	form, form * { margin: 0; padding: 0; }
	td { font-size: 10pt; }
	.time { padding-left: 16px; }
	.code { font: 13px Arial; margin-left: 8px; color: #006600; }
</style>

<div style="font-size: 10pt;">
	<div>
		<form id="apiform" name="apiform">
			<table>
				<tr>
					<td>
						<label>
							<input type="radio" id ="apifast" name="api" value="fast" onclick="showInfo()" checked="checked" />
							GAsync API
						</label>
					</td>
					<td class="time" id="fastTime">
					</td>
				</tr>
				<tr>
					<td>
						<label>
							<input type="radio" id ="apislow" name="api" onclick="showInfo()" value="slow" />
							Old Async API
						</label>
					</td>
					<td class="time" id="slowTime">
					</td>
				</tr>
			</table>
		</form>
	</div>
	<p>
		Click each option to reload the info window, and
		compare the elapsed time between the two APIs.
	</p>
	<div>
		<a href="http://mg.to/2007/06/22/write-the-same-code-for-google-mapplets-and-maps-api" target="_blank">Read All About It</a>
	</div>
	<p>
		Mapplet code that calls the async Maps API functions can be slow
		and complex because of the individual callbacks for each async call.
		My new API makes it easy to combine several async calls into a single
		function call and callback.
		This can be many times faster, and your code is simpler too.
	</p>
	<p>
		For example, this code using the standard API:
	</p>
<pre class="code">map.getSizeAsync( function( size ) {
  map.getZoomAsync( function( zoom ) {
    map.getBoundsAsync( function( bounds ) {
      map.getCenterAsync( function( center ) {
        // act on size, zoom, bounds, and center
      });
    });
  });
});</pre>
	<p>
		is reduced to:
	</p>
<pre class="code">GAsync( map,
  'getSize', 'getZoom', 'getBounds', 'getCenter',
  function(size, zoom, bounds, center ) {
    // act on size, zoom, bounds, and center
  });
</pre>
	<p>
		The API is a single function implemented on top of the existing
		mapplet API, so you can easily copy and paste it into your own
		mapplet code.
	</p>
	<p>
		As a bonus, the same code will run unchanged in both a mapplet
		and a Maps API application!
	</p>
	<p>
		Read the
		<a href="http://mg.to/2007/06/22/write-the-same-code-for-google-mapplets-and-maps-api" target="_blank">complete details</a>.
	</p>
	<p>
		<a href="http://mg.to/mapplet/async/async.xml" target="_blank">View Mapplet Source</a>
	</p>
</div>

<script type="text/javascript">

/*
	// GAsync compact version - copy and paste this code into your mapplet
	
	// GAsync v2 by Michael Geary
	// Commented version and description at:
	// http://mg.to/2007/06/22/write-the-same-code-for-google-mapplets-and-maps-api
	// Free beer and free speech license. Enjoy!
	
	function GAsync( obj ) {
		
		function callback() {
			args[nArgs].apply( null, results );
		}
		
		function queue( iResult, name, next ) {
			
			function ready( value ) {
				results[iResult] = value;
				if( ! --nCalls )
					callback();
			}
			
			var a = [];
			if( next.join )
				a = a.concat(next), ++iArg;
			if( mapplet ) {
				a.push( ready );
				obj[ name+'Async' ].apply( obj, a );
			}
			else {
				results[iResult] = obj[name].apply( obj, a );
			}
		}
		
		var mapplet = ! window.GBrowserIsCompatible;
		var args = arguments, nArgs = args.length - 1;
		var results = [], nCalls = 0;
		
		for( var iArg = 1;  iArg < nArgs;  ++iArg ) {
			var name = args[iArg];
			if( typeof name == 'object' )
				obj = name;
			else
				queue( nCalls++, name, args[iArg+1] );
		}
		
		if( ! mapplet )
			callback();
	}
		
	// End of GAsync compact version
*/
	
	// GAsync commented version
	
	// GAsync v2 by Michael Geary
	// http://mg.to/2007/06/22/write-the-same-code-for-google-mapplets-and-maps-api
	// Free beer and free speech license. Enjoy!
	//
	// Call one or more xyzAsync() functions from the Google
	// Mapplet API, with a single callback that receives the
	// values from all of the called functions. The first argument
	// to GAsync is the object to be used. The next arguments
	// are each of the function names, without the 'Async'
	// suffix. The last argument is the callback function.
	// To call xyzAsync() functions for more than one object,
	// list another object in the argument list and the function
	// names after that will use that object.
	// To call an xyzAsync() function that takes arguments of
	// its own (other than the callback argument), place an
	// array of those arguments after the function name in
	// GAsync's argument list.
	
	// Example calls, given existing map and marker objects
	/*
		// Get the size, bounds, and center of the map
		GAsync( map, 'getSize', 'getBounds', 'getCenter',
			function( size, bounds, center ) {
				// ...
			});
		
		// Get the zoom level, size, bounds, and center for
		// the map, as well as the lat/long of the top left
		// corner of the map. Also get the point and icon
		// for marker.
		GAsync(
			map, 'getZoom', 'getSize', 'getBounds', 'getCenter',
				'fromContainerPixelToLatLng', [ new GPoint(0,0) ],
			marker, 'getPoint', 'getIcon',
			function( zoom, size, bounds, center, topleft, point, icon ) {
				// ...
			});
		
		// Equivalent code using nested xyzAsync calls:
		map.getSizeAsync( function( size ) {
			map.getBoundsAsync( function( bounds ) {
				map.getCenterAsync( function( center ) {
					// ...
				});
			});
		});
		
		map.getZoomAsync( function( zoom ) {
			map.getSizeAsync( function( size ) {
				map.getBoundsAsync( function( bounds ) {
					map.getCenterAsync( function( center ) {
						map.fromContainerPixelToLatLngAsync( new GPoint(0,0), function( topleft ) {
							marker.getPointAsync( function( point ) {
								marker.getIconAsync( function( icon ) {
									// ...
								});
							});
						});
					});
				});
			});
		});
	*/
	
	function GAsync( obj ) {
		
		// Call the callback function provided in the GAsync() call
		function callback() {
			args[nArgs].apply( null, results );
		}
		
		// Queue a single xyzAsync() function call.
		// 'iResult' is the index into the final results array.
		// 'name' is the name of the Maps API function without
		// the Async suffix.
		// 'next' is the next argument to GAsync following name;
		// If next is an array, it contains the arguments to be
		// passed to xyzAsync().
		function queue( iResult, name, next ) {
			
			// Callback for the xyzAsync() function that was called
			// by this invocation of the queue() function.
			// value is the return value from the Maps API.
			function ready( value ) {
				
				// Save the result in the final results array
				results[iResult] = value;
				
				// If every async function has completed, call the
				// GAsync callback (in the last argument to GAsync)
				// with the final results array as its arguments.
				if( ! --nCalls )
					callback();
			}
			
			// Arguments array for the xyzAsync() call
			var a = [];
			
			// If 'next' is an array, it contains arguments to
			// be passed to xyzAsync()
			if( next.join )  // Arrays have .join, strings do not
				a = a.concat(next), ++iArg;  // append and skip
			
			// Call xyzAsync() in a mapplet or xyz() in the Maps API
			if( mapplet ) {
				// The callback for xyzAsync is its last argument
				a.push( ready );
				
				// Call xyzAsync() with arguments in 'a'
				obj[ name+'Async' ].apply( obj, a );
			}
			else {
				// Maps API, call xyz() and save its return value
				results[iResult] = obj[name].apply( obj, a );
			}
		}
		
		// Is this is a mapplet or the Maps API?
		var mapplet = ! window.GBrowserIsCompatible;
		
		// 'args' is a reference to GAsync's arguments array
		// that can be used in the nested functions.
		// 'nArgs' is is the number of arguments, not counting
		// the callback function at the end. (Thus, args[nArgs]
		// is a reference to the callback function.)
		var args = arguments, nArgs = args.length - 1;
		
		// 'results' is the final results array for the callback. It
		// will be populated from the individual ready() callbacks.
		// 'nCalls' is the total number of xyzAsync() calls. It is
		// incremented as those calls are queued, and then
		// decremented to discover when all the calls are done.
		var results = [], nCalls = 0;
		
		// Loop through GAsync()'s arguments, starting at
		// the first function name (after 'obj'), and ending
		// before the callback function.
		for( var iArg = 1;  iArg < nArgs;  ++iArg ) {
			
			// Get the name of the function to be called
			var name = args[iArg];
			
			// If the argument is an object, not a name,
			// switch to that object for subsequent calls.
			// If it is a name, count and queue the function.
			if( typeof name == 'object' )
				obj = name;  // change object
			else
				queue( nCalls++, name, args[iArg+1] );
		}
		
		// If using the Maps API, call the callback now
		if( ! mapplet )
			callback();
	}
	// End of GAsync
	
	var map = new GMap2();
	var marker = new GMarker( new GLatLng(0,0), { draggable:true } );
	GEvent.addListener( marker, 'click', function() {
		showInfo();
	});
	
	GAsync( map, 'isLoaded', 'getCenter',
		function( loaded, center ) {
			marker.setPoint( center );
			map.addOverlay( marker );
			showInfo();
		}
	);
	
	var timeStart;
	
	// Gather information and open the info using either the fast or slow async API
	function showInfo() {
		map.closeInfoWindow();
		timeStart = (new Date).getTime();
		document.getElementById('apifast').checked ? fastInfo() : slowInfo();
	}
	
	// Gather information with nested async calls
	function slowInfo() {
		map.getZoomAsync( function( zoom ) {
			map.getSizeAsync( function( size ) {
				map.getBoundsAsync( function( bounds ) {
					map.getCenterAsync( function( center ) {
						map.fromContainerPixelToLatLngAsync( new GPoint(0,0), function( topleft ) {
							marker.getPointAsync( function( point ) {
								marker.getIconAsync( function( icon ) {
									info( zoom, size, bounds, center, topleft, point, icon, true );
								});
							});
						});
					});
				});
			});
		});
	}
	
	// Gather information with GAsync
	function fastInfo() {
		GAsync(
			map, 'getZoom', 'getSize', 'getBounds', 'getCenter',
				'fromContainerPixelToLatLng', [ new GPoint(0,0) ],
			marker, 'getPoint', 'getIcon',
			info );
	}
	
	// Display the info window
	function info( zoom, size, bounds, center, topleft, point, icon, slow ) {
		var rows = [];
		
		function add( label, value, type ) {
			
			rows.push( [
				'<tr>',
					'<td style="padding-right: 8px; font-weight: bold;">',
						label,
					'</td>',
					'<td>',
						format[ type || 'String' ]( value ),
					'</td>',
				'</tr>'
			].join('') );
		}
		
		var time = format.Number( ( (new Date).getTime() - timeStart ) / 1000 ) + ' seconds';
		document.getElementById( slow ? 'slowTime' : 'fastTime' ).innerHTML = time;
		
		add( 'Elapsed time', time );
		add( '&nbsp;', '&nbsp;' );
		add( 'Zoom level', zoom, 'Number' );
		add( 'Map size (px)', size, 'GSize' );
		add( 'Map bounds', bounds, 'GLatLngBounds' );
		add( 'Map center', center, 'GLatLng' );
		add( 'Map top left', topleft, 'GLatLng' );
		add( 'Marker point', point, 'GLatLng' );
		add( 'Marker icon', icon, 'GIcon' );
		
		var html = [
			'<div>',
				'<div style="font-size: 125%; margin-bottom: 8px;">',
					slow ? 'Slow Async API' : 'Fast Async API',
				'</div>',
				'<table>',
					rows.join(''),
				'</table>',
			'</div>'
		].join('');
		
		marker.openInfoWindowHtml( html, { disableGoogleLinks:true } );
	}
	
	// A handy formatter for the info window
	var format = {
		GIcon: function( value ) {
			return '<img src="' + value.image + '" style="width:' + value.iconSize.width + 'px; height:' + value.iconSize.height + 'px;" border="0" />';
		},
		GLatLng: function( value ) {
			return format.Number(value.lat()) + ', ' + format.Number(value.lng());
		},
		GLatLngBounds: function( value ) {
			return format.GLatLng( value.getSouthWest() ) +
				' to ' + format.GLatLng( value.getNorthEast() );
		},
		GPoint: function( value ) {
			return value.x + ', ' + value.y;
		},
		GSize: function( value ) {
			return value.width + ', ' + value.height;
		},
		Number: function( value ) {
			return ( '' + value ).replace( /(\.\d\d\d)\d*/, '$1' );
		},
		String: function( value ) {
			return value;
		}
	};
	
</script>

]]></Content>
</Module>
