Windows Live Quick Apps – Contoso Bicycle Club Part 3

bikeclubfull_thumb_1 Developer

Welcome back to the third part of our deep dive in the Windows Live Quick Apps Website featuring the Contoso Bicycle Club. In this part we’ll take apart one of the main JavaScript function calls that this web site uses, updatePage.

In order to display the main panel you have a few of options. You can click on any of the links in the Events user control, any of the links in the Rides user control or use the links from the Rides or Events dropdown menu.

All of these links make a call to a javascript function updatePage that we’ve touched on in previous parts of this deep dive. Lets take a closer look at this function to see exactly what it does :-

function updatePage(title, feed, item, cid, album) {

hideRideMenu();

hideEventsMenu();

hideDiv($(‘SlideShowPanel’));

hideDiv($(‘DirectionsPanel’));

hideDiv( $(‘MainPanel’));

hideVideo();

showDiv($(‘TextPanel’), 500);

showDiv($(‘MenuPanel’), 500);

showDiv($(‘MapPanel’), 99999);

CID = cid;

var titleDiv = $(‘Title’);

titleDiv.innerHTML = title;

ALBUM = album;

updateText(feed, item);

// Load the collection

loadCollection(cid);

if(album == “”)

disableAnchor($(‘PhotosFromRideLink’), true);

else

{

if($(‘PhotosFromRideLink’).disabled)

disableAnchor($(‘PhotosFromRideLink’), false);

}

if(cid == “”)

{

disableAnchor($(‘MapLink’), true);

disableAnchor($(‘DirectionsLink’), true);

disableAnchor($(‘ViewRouteLink’), true);

disableAnchor($(‘BikeCamLink’), true);

}

else

{

if($(‘MapLink’).disabled)

disableAnchor($(‘MapLink’), false);

if($(‘DirectionsLink’).disabled)

disableAnchor($(‘DirectionsLink’), false);

if($(‘ViewRouteLink’).disabled)

disableAnchor($(‘ViewRouteLink’), false);

if($(‘BikeCamLink’).disabled)

disableAnchor($(‘BikeCamLink’), false);

}

// Hard coded link for London Bike Cam demo

switch(title.toLowerCase())

{

case “london river thames”:

if($(‘BikeCamLink’).disabled)

disableAnchor($(‘BikeCamLink’), false);

break;

default:

disableAnchor($(‘BikeCamLink’), true);

break;

}

}

The first few function calls simply cleans up the screen a bit should it need to. The first two function calls hide the divs that contain the drop down menus for Rides and Events found at the top of the screen. The next three statements also hide regions defined in the default.aspx for for display of various media.

If you’re not used to Asp.Net Ajax, the function call may look a little strange as it uses some shorthand notation :-

hideDiv($(‘DirectionsPanel’));

The $(‘element name’) notation is just shorthand for document.getElementById(‘element name’);

The next function call hides the video panel should it be open :-

function hideVideo() {

var div = $(‘VideoWrapper’);

if (div.style.visibility == ‘visible’) {

document.getElementById(‘VideoControl’).content.findName(‘VideoWindow’).pause();

PAUSED = true;

hideDiv(div);

deleteCyclist();

}

}

As you can see, if the panel is displayed, first off it pauses the video, then hides the panel. Finally it calls a function that deletes the cyclist icon from the map. The cyclist icon is in reality just a custom pushpin. We’ll take a closer look at this later.

Back to the updatePage function. Next we put in some calls to display various panels, the main map panel, a text panel that will hold the blog description and a menu panel giving the user some options to choose from.

Next we set the Title of the main portion of the page to the Title of the blog (see the previous article in this deep dive serious for an explanation of the input parameters to the updatePage function call).

titlediv_thumb_1 Developer

The next function call that we make displays the description from our blog into the text panel that we’ve just displayed :-

function updateText(feed, item)

{

var textPanel = $(‘TextPanel’);

loadHTML( ‘Item.aspx?feed=’ + feed + ‘&item=’ + item, textPanel)

}

As you may recall, the parameter feed is the URL to the RSS feed of the blog post and item is a counter to basically give each post its own ID. In this context it tells the updateText routine which blog post we want from the RSS feed.

The first statement simply gets a reference to the TextPanel div itself. The next function call is slightly more involved. It places a call to the loadHTML function which is found in the ajah.js file in the /js subdirectory of the web site :-

function loadHTML( url, div) {

var xmlHttpRequest = createXMLHttpRequest();

var handler = function() {

if (xmlHttpRequest.readyState==4) {

if (xmlHttpRequest.status==200) {

div.innerHTML = xmlHttpRequest.responseText;

} else {

alert(‘Error – LoadHTML failed.’);

}

}

}

xmlHttpRequest.onreadystatechange = handler;

xmlHttpRequest.open(“GET”, url, true);

xmlHttpRequest.send(null);

}

We pass in the url of the html we wish to access and the panel that it should be displayed in.

First off here we create an xmlHttpRequest option. Due to different browsers handling this in different ways a function has been written to get the appropriate xmlHttpRequest option for the browser that the user is currently using :-

function createXMLHttpRequest() {

try { return new ActiveXObject(“Msxml2.XMLHTTP”); } catch (e) {}

try { return new ActiveXObject(“Microsoft.XMLHTTP”); } catch (e) {}

try { return new XMLHttpRequest(); } catch (e) {}

alert(“This browser does not support Ajax”);

return null;

}

Next we create a prototype javascript function that will be called whenever the xmlHttpRequest object changes it’s state.

As you no doubt know, the xmlHttpRequest object allows you to place out of band asynchronous server side calls. Because of the asynchronous nature of it, you have to give it a method that it returns (think of this like a delegate in standard .Net development). What we’re saying here is that every time the state has changed in the ajax call, return to and run the handler prototype that we’ve setup.

The readyState can have one of 5 values :-

0 (Uninitialized)

 

 

The object has been created, but not initialized (the open method has not been called).

 

 

1 (Open)

 

 

The object has been created, but the send method has not been called.

 

 

2 (Sent)

 

 

The send method has been called, but the status and headers are not yet available.

 

 

3 (Receiving)

 

 

Some data has been received.

 

 

4 (Loaded)

 

 

All the data has been received, and is available.

 

 

All the data has been received, and is available.

What we’re interested in is when readystate returns the value 4, which basically means the ajax call has completed.

Next we check that status value, this is basically the standard html status codes :-

Number

 

 

Description

 

 

100

 

 

Continue

 

 

101

 

 

Switching protocols

 

 

200

 

 

OK

 

 

201

 

 

Created

 

 

202

 

 

Accepted

 

 

203

 

 

Non-Authoritative Information

 

 

204

 

 

No Content

 

 

205

 

 

Reset Content

 

 

206

 

 

Partial Content

 

 

300

 

 

Multiple Choices

 

 

301

 

 

Moved Permanently

 

 

302

 

 

Found

 

 

303

 

 

See Other

 

 

304

 

 

Not Modified

 

 

305

 

 

Use Proxy

 

 

307

 

 

Temporary Redirect

 

 

400

 

 

Bad Request

 

 

401

 

 

Unauthorized

 

 

402

 

 

Payment Required

 

 

403

 

 

Forbidden

 

 

404

 

 

Not Found

 

 

405

 

 

Method Not Allowed

 

 

406

 

 

Not Acceptable

 

 

407

 

 

Proxy Authentication Required

 

 

408

 

 

Request Timeout

 

 

409

 

 

Conflict

 

 

410

 

 

Gone

 

 

411

 

 

Length Required

 

 

412

 

 

Precondition Failed

 

 

413

 

 

Request Entity Too Large

 

 

414

 

 

Request-URI Too Long

 

 

415

 

 

Unsupported Media Type

 

 

416

 

 

Requested Range Not Suitable

 

 

417

 

 

Expectation Failed

 

 

500

 

 

Internal Server Error

 

 

501

 

 

Not Implemented

 

 

502

 

 

Bad Gateway

 

 

503

 

 

Service Unavailable

 

 

504

 

 

Gateway Timeout

 

 

505

 

 

HTTP Version Not Supported

 

 

As you can see, we’re checking for the value 200 which means that everything is OK and no errors were encountered.

Finally in our prototype we fill the div (panel) that we passed into the main function with the value returned by our ajax call.

The final three lines of the main function call just place the ajax call.

It is interesting to note here that this is a manual Ajax call and it doesn’t use the Asp.Net Ajax framework to place the call by using the createCallback and createDelegate functions built into Asp.Net Ajax.

All of the above, as mentioned, takes our blog entry and pastes the html into the text panel that we showed on the main page.

textpanel_thumb_1 Developer

Back in our updatePage method, the next call we place is to load the map collection that we previously stored (see prevous article for explanation of map collections).

// Load the collection

loadCollection(cid);

We pass across the collection id that is unique to our map collection and passed into the updatePage method.

function loadCollection(cid)

{

// Delete any pre existing shapes from the layer

COLLECTION_LAYER.DeleteAllShapes();

// Create the new layer with the Collection

var veLayerSpec = new VEShapeSourceSpecification(VEDataType.VECollection, cid, COLLECTION_LAYER);

// Import the layer

MAP.ImportShapeLayerData(veLayerSpec, onFeedLoad, true);

}

The first statement uses a virtual earth collection layer. This is defined further up in the javascript :-

// The layer used for the maps.live.com collection

var COLLECTION_LAYER = new VEShapeLayer();

You can think of a VEShapeLayer as a layer in a paint program. It allows you to draw on it without affecting the original underneath. In the case of Virtual Earth maps, it allows you to add polylines, polygons and pushpins to the underlying map.

The first thing we do here however is call one of the built in methods to erase any shapes that may have been previously created on this layer.

Next we define the layer to hold the map collection that we want. We’re telling it that we want collection, the unique id of the collection that we want to define as a layer and the actual layer itself to which this data should be added.

Finally we merge our map collection data layer with the map itself. Once the data has been retrieved and merged we make a call to the onFeedLoad function :-

function onFeedLoad(feed)

{

var count = COLLECTION_LAYER.GetShapeCount();

for(var i=0; i < count; ++i)

{

var shape = COLLECTION_LAYER.GetShapeByIndex(i);

shape.SetCustomIcon(“<img src=’images/directions_pin.png’/>”);

}

getDirections();

}

maproute_thumb_1 Developer

The layer that we’re returning here is basically a collection of pushpin objects. In the onFeedLoad function we’re iterating through all the pushpins and swapping out the default pushpin icon with our own one.

Finally we place a call to the getDirections function :-

function getDirections()

{

// Get the directional panel

var directionsPanel = $(‘DirectionsPanel’);

// define the HTML string

var directionsHtml = ”;

// Setup the distance

var totalDistance = 0.0;

// Fetch the shape cound.

var count = COLLECTION_LAYER.GetShapeCount();

var MAP_ITEM_TITLE = ‘Click to see point on map.’;

// Enumerate the shapes

for (var i = 0 ; i < count ; i++) {

// Get the shape

var shape = COLLECTION_LAYER.GetShapeByIndex(i);

// Setup distance variable

var distance;

// If the first item

if(i == 0)

distance = 0.0;

else

distance = CalculateDistance(COLLECTION_LAYER.GetShapeByIndex(i-1).Latitude,COLLECTION_LAYER.GetShapeByIndex(i-1).Longitude,shape.Latitude, shape.Longitude);

// Add to the total distance

totalDistance = totalDistance + distance;

// build the directions HTML (is persisted throughout the enumeration)

directionsHtml += ‘<div class=”direction”>’ +

‘ <div class=”distance”>’ + distance.toFixed(2) + ‘ miles</div>’ +

‘ <a title=”‘ + MAP_ITEM_TITLE + ‘” href=”JavaScript:zoom(‘ + shape.Latitude + ‘,’ + shape.Longitude + ‘)”>’ + shape.GetTitle() + ‘</a>’ +

‘</div>’;

}

// Add the totals

directionsHtml += ‘<div class=”DirectionsTotal direction heavy”>’ +

‘ <div class=”distance heavy”>’ + totalDistance.toFixed(2) + ‘ miles</div>’ +

‘ Total Distance’ +

‘</div>’;

// Add the header and add the distance to the header

directionsHtml = ‘<h2>Directions (‘ + totalDistance.toFixed(2) + ‘ miles)</h2>’ + directionsHtml;

// Output

directionsPanel.innerHTML = directionsHtml;

}

First off we get a reference to the directions panel (div) defined in the default.aspx page then we define a couple of variables, one that will hold the actual directions and the next to hold the total distance between each pushpin in our layer.

Next we make a function call to get the total number of pushpins that we have added to the map layer and we round out the setting up of this function call by defining a title that we can display.

The next thing we do here is to iterate through all of pushpins we have defined in our map layer one by one.

Wihtin the loop, we get a reference to the actual pushpin itself :-

var shape = COLLECTION_LAYER.GetShapeByIndex(i);

then we call a function call that calculates the distance between this pushpin and the previous one :-

function CalculateDistance(startLatitude, startLongitude, endLatitude, endLongitude)

{

var R = 3959; // miles

// Get the Latitude differential

var dLat = (endLatitude-startLatitude).toRad();

// Get the Longitude differential

var dLon = (endLongitude-startLongitude).toRad();

///TODO – add comments.

var a = Math.sin(dLat/2) * Math.sin(dLat/2) +

Math.cos(startLatitude.toRad()) * Math.cos(endLatitude.toRad()) *

Math.sin(dLon/2) * Math.sin(dLon/2);

var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));

var d = R * c;

return d;

}

Because Virtual Earth deals with latitudes and longitudes and we deal with miles, all this function does is convert the difference between the two points into miles. It’s worth taking note of this routine as it can be very handy in your applications.

We take this value returned and add it to our total miles variable.

Our next series of statements basically just builds a link for the current pushpin that we’re iterating against. If you click on a pushpin the map will zoom in to that point allowing you to see more detail.

Once we iterate through each individual pushpin, we add another link to show the whole map layer. Basically this is a “zoom out” if you’ve zoomed in by clicking on an individual pushpin.

maproutezoomed_thumb_1 Developer

Finally we load all of this into our Directions panel.

We are now back in our main updatePage function :-

if(album == “”)

disableAnchor($(‘PhotosFromRideLink’), true);

else

{

if($(‘PhotosFromRideLink’).disabled)

disableAnchor($(‘PhotosFromRideLink’), false);

}

if(cid == “”)

{

disableAnchor($(‘MapLink’), true);

disableAnchor($(‘DirectionsLink’), true);

disableAnchor($(‘ViewRouteLink’), true);

disableAnchor($(‘BikeCamLink’), true);

}

else

{

if($(‘MapLink’).disabled)

disableAnchor($(‘MapLink’), false);

if($(‘DirectionsLink’).disabled)

disableAnchor($(‘DirectionsLink’), false);

if($(‘ViewRouteLink’).disabled)

disableAnchor($(‘ViewRouteLink’), false);

if($(‘BikeCamLink’).disabled)

disableAnchor($(‘BikeCamLink’), false);

}

// Hard coded link for London Bike Cam demo

switch(title.toLowerCase())

{

case “london river thames”:

if($(‘BikeCamLink’).disabled)

disableAnchor($(‘BikeCamLink’), false);

break;

default:

disableAnchor($(‘BikeCamLink’), true);

break;

}

Here we are dealing with the MenuPanel and we’re simply going over which of the 6 options that are displayed in the panel.

menupanel_thumb_1 Developer

Firstly we’re checking to see if there’s a photo album associated with the blog post. If there is then activate the “Photos from Ride” menu button, otherwise disable it.

Next we check whether there is an actual map collection associated with the blog post. The reason for this is that some items listed in the Events user control (which links to the Events RSS feed) may not have been defined yet and therefore will not have a map associated with them. If a map does indeed exist then enable all the buttons that pertain to the map (View Route, Map, Directions and Bike Cam) otherwise disable these buttons.

One of the rides (London River Thames) has a video associated it. The next series of statements checks to see if we are displaying the details for this blog post, if so then we enable the Bike CAM button, otherwise disable it.

And that’s all there is to it. The updatePage javascript function is basically the main routine for this application which sets up and displays the main portion of the default.aspx but as you’ve seen, it’s fairly straight forward.