[Note: This demo uses the Microsoft Bing maps control, but you are welcome to use the Google Maps control if you prefer. The relevant JavaScript for Geolocation should be identical for both.]
-
Go to The Bing Maps Portal to obtain a key for the BingMaps control. See this article for more information.
-
Open "4- JavaScript API/labs/geolocation/begin/map.html" and run the page. You should see nothing but an empty box with a border at this point. Inspect both the map.html file and css/style.css to inspect the existing styles and elements on the page.
-
Open js/geolocation.js and enter the following, replacing the text "your_key_here" with the key you obtained in step 1:
var _creds = "your_key_here"; var mapDiv = document.getElementById("map"); var _map = new Microsoft.Maps.Map(mapDiv, { credentials: _creds }); -
Now, create a locate function:
function locate() { navigator.geolocation.getCurrentPosition(function (position) { var coordinates = position.coords; setDetails(position); }, errorHandler); } -
Add an empty function named
errorHandler(we'll flesh this out later):function errorHandler(e) {} -
Add the
setDetailsfunction, which will display the users lat and long information on the screen:function setDetails(position) { $('#lat').text('Latitude: ' + position.coords.latitude); $('#long').text('Longitude: ' + position.coords.longitude); $('#accuracy').text('Accuracy: ' + position.coords.accuracy + ' Meters'); $('#geoDetails').fadeIn('slow'); } -
Now, and add a call to locate just below the method:
locate();Refresh the page and be sure to give the browser permission to use your location.
-
The API call returns a coords object with the users lat, long and other information. By itself, this information isn't very interesting, so let's use this information to place a pushpin on a map. Start by adding the following function:
function placeLocationOnMap(latitude, longitude) { var location = new Microsoft.Maps.Location(latitude, longitude); _map.setView({ zoom: 12, center: location }); // Add a pushpin to the map representing the current location var pin = new Microsoft.Maps.Pushpin(location); _map.entities.push(pin); } -
Now, add the following just above the
setDetails(position);line from step #4:placeLocationOnMap(coordinates.latitude, coordinates.longitude); -
Refresh the page and click "Accept" (if prompted) to give your browser permission to use your location. You should now see the map control centered on a location (reasonably) near you.
-
One of the great features of Geolocation is that the browser will typically adapt its mechanism for determining your position based on the most-reliable information. On mobile devices, this means that the browser takes GPS location data into account. If you have a mobile device emulator (iPhone via XCode or the Windows Phone Emulator in Visual Studio), open this page in the built-in browser. Try modifying the GPS-reported location in the emulator (supported with both iOS and WP7 emulators) and refresh the page to see the mapped location change.
-
Because geolocation is opt-in, not all of your users will with to use it. What's more, users with an older browser won't have the feature available, so you'll want to consider degrading the experience in a way that still makes this feature useful. As such, we'll add some code that will allow the user to manually enter their address, and then use that information to place a pushpin on the map. First, let's add some logic that handles any error that might occur, such as the user declining to share their location automatically. Replace the empty
errorHandlerfunction with the following:function errorHandler(e) { if (e.code === 1) { // PERMISSION_DENIED displayError('Please enter your location in the form below'); } else if (e.code === 2) { //POSITION_UNAVAILABLE displayError('Cannot find your location. Make sure your network connection is active and refresh the page to try again.'); } else if (e.code === 3) { //TIMEOUT displayError('Cannot find your location. Refresh the page to try again, or provide you address in the form below.'); } showFallback(); } -
Add the
displayError()function:function displayError(msg) { $('#error').text(msg); } -
Now, let's flesh out the
showFallback()function:function showFallback() { $('#details').show(); $('#geoDetails').hide(); $('#findMe').click(function () { $('#error').text(''); var address = $('#address').val(); $.getJSON('http://dev.virtualearth.net/REST/v1/Locations?query=' + address + '&key=' + _creds + '&jsonp=?', function (result) { if (result.resourceSets[0].estimatedTotal > 0) { var loc = result.resourceSets[0].resources[0].point.coordinates; placeLocationOnMap(loc[0], loc[1]); } else { displayError("sorry that address cannot be found"); } }); }); } -
Refresh the page, and click "Decline" when you are asked to share your information (you may need to close the window and re-open it) or modify privacy setting in your browser to clear any exceptions for this site. Enter a location (like 'Taj Mahal' or 'Mount Rushmore') by hand and click enter.
[Extra Credit] Check out the Modernizr Shims and Polyfils list for a geolocation shim, and add it to the page.
-
Open "4- JavaScript API/labs/canvas/begin/sales.html" and add a canvas element to the page:
<canvas id="chart" width="600" height="450"></canvas> -
Open style.css and take note of the css style for the canvas element. Open sales.html in the browser.
-
Now, open
js/chart.jsand add some initial code to define some common variables, get the canvas element and select the 2d drawing content:var _canvas, _ctx; window.onload = function() { var baseY, baseX, chartWidth, salesData; baseY = 375, baseX = 110, chartWidth = 475; _canvas = document.getElementById('chart'); _ctx = _canvas.getContext("2d"); };The
getContextfunction is a special function that the Canvas API uses to retrieve an object we can use to manipulate a canvas. Currently "2d" is the only value accepted as the parameter to this function, though "3d" contexts are in the works among various vendors (WebGL, for instance). -
Now lets add a
drawAxesfunction and draw the y-axis on the canvas:function drawAxes(baseX, baseY, chartWidth) { var leftY, rightX; leftY = 5; rightX = baseX + chartWidth; //Draw Y Axis _ctx.moveTo(baseX, leftY); _ctx.lineTo(baseX, baseY); //Draw Arrow for Y Axis _ctx.moveTo(baseX, leftY); _ctx.lineTo(baseX + 5, leftY + 5); _ctx.moveTo(baseX, leftY); _ctx.lineTo(baseX - 5, leftY + 5); //Define Style and stroke lines _ctx.strokeStyle = "#000"; _ctx.stroke(); } -
Insert a call to
drawAxesjust after the call to `_canvas.getContext('2d'):drawAxes(baseX, baseY, chartWidth); -
Refresh the page. You should see a straight black arrow on the left of the canvas.
-
To draw the x-axis, insert the following into
drawAxesbefore the_ctx.strokeStyleline://Draw X Axis _ctx.moveTo(baseX, baseY); _ctx.lineTo(rightX, baseY); //Draw Arrow for X Axis _ctx.moveTo(rightX, baseY); _ctx.lineTo(rightX - 5, baseY + 5); _ctx.moveTo(rightX, baseY); _ctx.lineTo(rightX - 5, baseY - 5); -
Now lets add the bars to our graph. We'll start by defining a salesData object to hold our categories and sales figures. Insert the following just before the line
_canvas = document.getElementById('chart'):salesData = [{ category: "Basketballs", sales: 150 }, { category: "Baseballs", sales: 125 }, { category: "Footballs", sales: 300 }];This is a simple Array of sales categories and figures. Typically the kind of information we'd obtain from a database or service, but you can use your imagination, here.
-
To draw the bars on the chart, start by creating a
drawBarsfunction:function drawBars(salesData, baseX, baseY) { var i, length, colors, xPos, barWidth, category, sales; length = salesData.length; barWidth = 80; xPos = baseX + 30; for (i = 0; i < length; i++) { category = salesData[i].category; sales = salesData[i].sales; _ctx.fillRect(xPos, baseY - sales-1, barWidth, sales); xPos += 125; } }The relevant piece of this function is
_ctx.fillRect, which creates a rectangle on the canvas at a given x (xPos) and y (baseY - sales-1) position, and with a specified width (barWidth) and height (sales). -
Now add the following just after the call to
drawAxes:drawBars(salesData, baseX, baseY); -
Refresh the page to see your beautiful canvas chart!
-
Now that we have a basic chart, let's label our axes and the individual bars. Start by adding a function called
labelAxes:function labelAxes() { var height, heightOffset, widthOffset; height = _ctx.canvas.height; heightOffset = height/2; widthOffset = _ctx.canvas.width/2; _ctx.font = "bold 18px sans-serif"; _ctx.fillText("Units Sold", 10, heightOffset); _ctx.fillText("(in 100s)", 17, heightOffset + 17); _ctx.fillText("Product", widthOffset, height - 20); }Adding text to a canvas is as simple as calling the
fillTextfunction with the text and the x and y coordinates on which to place the text. Thefontproperty is also available, and accepts anything you can use for a CSSfontstyle. -
Add a call to
labelAxes()just after the call todrawBars:labelAxes(); -
Refresh the page to see your labels.
-
Now let's label the bars. Start by creating a
labelBarfunction:function labelBar(category, xPos, baseY) { _ctx.fillStyle = "#000"; _ctx.font = "14px sans-serif"; _ctx.fillText(category, xPos - (category.length/2 - 10), baseY + 20); }Similar to step #2, we call
fillText, this time using the category name from our data object for the label. -
To call
labelBar, add the following just before the last line of theforloop in thedrawBarsfunction:labelBar(category, xPos, baseY); -
Refresh the page.
-
The last steps of this module will explore some different options for adding color and images to a canvas. We'll start by replacing the black bars on our graph with some peppy colors. Start by adding the following colors array to the
drawBarsfunction, just before theforloop:colors = ["#e34c26", "#0092bf", "rgba(240, 101, 41, 0.90)"]; -
To use these elements, we can add the following inside of the
forloop, just before the call to_ctx.fillRect:_ctx.fillStyle = colors[i % length]; -
Refresh the page to see the new colors.
-
It's also possible to use both linear and radial gradients in shapes on a canvas, so let's replace these basic colors with a fancy gradient. Start by adding
createGradientfunction:function createGradient(x, y, width, color) { var gradient; gradient = _ctx.createLinearGradient(x, y, x+width, y); gradient.addColorStop(0, color); gradient.addColorStop(1, "#efe3e3"); return gradient; }This simple function accepts some information about where the shape will be rendered on the screen and a primary color and, using that information, calls the
createLinearGradientfunction to obtain a gradient object. We then add two color stops (the first with our primary color and the second with a shade of grey) before returning the gradient object. -
Now that we have a gradient function we can use it by adding a call to
fillStylein thedrawBarsfunction, just before the call to_ctx.fillRect:_ctx.fillStyle = createGradient(xPos, baseY - sales-1, barWidth, colors[i % length]); -
Refresh the page to see the gradient in action.
-
For the final enhancement to our bar chart, we'll replace the rectangular bars with images. Start by commenting out or removing the call to
drawBarsat the top of thechart.jsfile. -
Now let's add a
drawImagesfunction:function drawImages(salesData, baseX, baseY) { var i, length, xPos, barWidth; length = salesData.length; barWidth = 80; xPos = baseX + 30; for (i = 0; i < length; i++) { var sales, category, img; category = salesData[i].category; sales = salesData[i].sales; img = new Image(); } }Similar to our
drawBarsfunction, this function will loop through the collection of categories. in this case, however, we're defining a new DOM Image element to be added to our Canvas. -
Set the source of each
imgelement by adding the following just before the end of theforloop:img.src = "../assets/" + category + ".jpg";This line will load each image into an in-memory element.
-
Once each image is loaded, we can use it on our canvas. To make sure we don't attempt to access the image until after it's been fully loaded, we'll add the following to the image
onloadevent, just before our call toimg.src:img.onload = (function(height, base, currentImage, currentCategory) { return function() { var yPos; yPos = base - height - 1; _ctx.drawImage(currentImage, 30, 30, barWidth, height, xPos, yPos, barWidth, height); labelBar(currentCategory, xPos, baseY); xPos += 125; } })(sales, baseY, img, category);There's some JavaScript closure trickery here that we need to ensure that the onload event obtains the correct values for sales, baseY, img and category when it fires. Beyond that, the relevant piece of information is the call to
drawImage, which takes a DOM Image element and performs a manipulation or translation of the image before rendering it to the screen.drawImagehas several overloads, with the one above being the most complex as it both crops and resizes the image to match the dimensions of our bars before drawing them to the screen. See the resources below for more information about thedrawImagefunction. -
Now let's add a call to
drawImagesjust before the call tolabelAxes:drawImages(salesData, baseX, baseY); -
Refresh the page and revel in your canvas expertise!
[Extra Credit] Use the Explorer Canvas and Canvas Text libraries to polyfill canvas support for older browsers. See this MSDN Magazine Article for more information.
-
Open "4- JavaScript API/labs/web_storage/begin/order.html" in a browser. This page should look similar to the page from the forms module in Lab #1.
-
Open js/storage.js and add the following function, which should fire when the user clicks the "Save for Later" button:
$('#saveForLater').click(function () {}); -
Accessing local storage is as simple as calling
window.localStorage. Items can be access likewindow.localStorage["name"]or as an expando,window.localStorage.name. Add the following inside the function from step #2 to save the order information to the localStorage object:window.localStorage.name = $('#orderName').val(); window.localStorage.email = $('#orderEmail').val(); window.localStorage.webSite = $('#orderWebsite').val(); window.localStorage.phone = $('#orderTelephone').val(); window.localStorage.delivery = $('#deliveryDate').val(); window.localStorage.address = $('#orderShipping').val(); window.localStorage.quantity = $('#orderQty').val(); $('#saveForLater').val('Saved!'); -
Refresh the page and enter some data in the fields and click "Save for later"
-
Open up the developer tools in your browser and inspect the local storage. Some browsers have a visual inspector, but all browsers allow you to open the console tab and type
window.localStorageto see the contents of the object. Do so and make sure that your data has been saved. -
Refresh the page. The fields are blank, so lets use the information in local storage to re-populate these when the user returns to the page. Place the following outside of the
$('#saveForLater')function in step #2:$('#orderName').val(window.localStorage.name); $('#orderEmail').val(window.localStorage.email); $('#orderWebsite').val(window.localStorage.webSite); $('#orderTelephone').val(window.localStorage.phone); $('#deliveryDate').val(window.localStorage.delivery); $('#orderShipping').val(window.localStorage.address); $('#orderQty').val(window.localStorage.quantity); -
Now refresh the page. You saved data should now be in the form. To verify that localStorage is persistent, close the browser, re-open and return to this page.
-
Where Local Storage is persistent, Session Storage only lasts for the user's current session (for instance, in a single tab). Let's see how this works by adding a Session variable that displays to the user the time the order was initiated. Add the following to the end of js/storage.js:
if (!window.sessionStorage.orderStamp) { window.sessionStorage.orderStamp = formatDate(new Date()); } $('#time').text("Order initiated on: " + window.sessionStorage.orderStamp);Note that
sessionStorageis accessed similarly to local storage, and its properties can also be accessed in expando fashion. -
Refresh the page, and take note of the timestamp on the form. Refresh the page several times, and notice that the timestamp does not change.
-
Now, open another tab and navigate to the same page. Notice that the timestamp is different than the original tab, but also stays the same when the page is refreshed several times.