Here in Step 3 of 7 we’re going to implement some dynamic content for the site—specifically, dynamic promotional item content and map integration. In this step we will add:
• Server-side dynamic content for promotional items driven by a database
• Client-side dynamic content for Bing Maps integration using device current location
Server-side Dynamic Content for Promotional Items
The bottom area of our web pages is for promotional items. There is room for three items each with an image, title, and description. We would like all of this to be dynamic so that new offers can be easily advertised.
There are several ways we might store content (database,
primitive storage, content management system). In this case we’ll use a SQL
Server database for the content and create a Promotions table that holds the
image URL, title, and description for each promotional item. We add a database
project to the solution for this purpose. We define three records of
promotional content.
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Data.SqlClient; namespace html5_mvc_razor.Controllers { public class HomeController : Controller { // // GET: /Home/ public ActionResult Index() { LoadPromos(); return View(); } private void LoadPromos() { Dictionary<string, Promo> Promos = new Dictionary<string, Promo>(); try { using (SqlConnection conn = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["Tours"].ConnectionString)) { conn.Open(); using (SqlCommand cmd = new SqlCommand("SELECT * FROM Promotions ORDER BY Id", conn)) { using (SqlDataReader reader = cmd.ExecuteReader()) { while (reader.Read()) { Promos.Add(Convert.ToString(reader["Id"]), new Promo { Title = Convert.ToString(reader["Title"]), Text = Convert.ToString(reader["Text"]), ImageURL = Convert.ToString(reader["ImageURL"]) } ); } } } conn.Close(); } } catch (SqlException) { // TODO: log exception } ViewBag.Promos = Promos; } } public class Promo { public string Title; public string Text; public string ImageURL; } }
One way to set dynamic content in MVC is with Razor. We can
set the image URLs based on what’s in the ViewBag using the @ syntax:
<!-- begin - homepage promos --> <div class="home_promo_container"> <div class="home_promo"> <div class="home_promo_content" style="background-image:url(images/@(ViewBag.Promos["1"].ImageURL));"> <h2>...</h2> <p>...</p> <a class="button" href="#">Learn more »</a> </div> </div> <div class="home_promo"> <div class="home_promo_content" style="background-image:url(images/@(ViewBag.Promos["2"].ImageURL));"> <h2>...</h2> <p>...</p> <a class="button" href="#">Learn more »</a> </div> </div> <div class="home_promo"> <div class="home_promo_content" style="background-image:url(images/@(ViewBag.Promos["3"].ImageURL));"> <h2>...</h2> <p>...</p> <a class="button" href="#">Learn more »</a> </div> </div> <div class="clear_both"></div> </div>
Of course the images aren’t really dynamic yet—they’re still
static files that are part of our web project—but once we change that in a later step we
have a way to indicate the URL dynamically from our database.
Now let’s look at a more modern and versatile way to pass
dynamic content from an MVC web back end to an open standards web client: data
binding with Knockout.js. The Knockout library lets us apply the Model-View-ViewModel
pattern to JavaScript. To do this, we need to:
• Use Razor to generate JavaScript code to create a ViewModel (JavaScript object) with content.
• Annotate HTML DOM elements with data-bind attributes to denote where data
binding should occur.
• Initialize and call the Knockout library to apply data binding to the HTML.
• Initialize and call the Knockout library to apply data binding to the HTML.
You can see all three of these activities in the Home view
page (Views/Home/Index.cshtml):
@{ ViewBag.Title = "Responsive Tours"; } @* Optional : Include additional stylesheets *@ @section StylesTop { <link rel="stylesheet" type="text/css" href="~/../css/stylesheet.css" /> <link rel="stylesheet" type="text/css" media="only screen and (min-width:50px) and (max-width:550px)" href="~/../css/screen_small.css"> <link rel="stylesheet" type="text/css" media="only screen and (min-width:551px) and (max-width:800px)" href="~/../css/screen_medium.css"> <!--[if lt IE 9]> <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script> <link rel="stylesheet" type="text/css" href="~/../css/stylesheet_ie.css" /> <![endif]--> } @* Optional : Include additional immediately executed script references *@ @section ScriptsTop { } @* Optional : Header content *@ @section Header { <meta name="description" content="This site was created from a template originally designed and developed by Codify Design Studio. Find more free templates at http://www.adobe.com/devnet/author_bios/chris_converse.html" /> <meta http-equiv="X-UA-Compatible" content="IE=9" /> } @* Required : Render body container here *@ <div class="page"> <header><a class="logo" href="#"></a></header> <div class="page_content"> <!-- begin - homepage promo --> <div class="marquee_container"> <div class="marquee_photos"><br/><br/>loading...</div> <div class="marquee_caption"> <div class="marquee_caption_content"></div> </div> <div class="marquee_nav"></div> <div class="marquee_panel_data"></div> </div> <div class="marquee_smallscreen"><br/><br/>loading...</div> <!-- end - homepage promo --> <!-- begin - homepage promos --> <div class="home_promo_container"> <div class="home_promo"> <div class="home_promo_content" style="background-image:url(images/@(ViewBag.Promos["1"].ImageURL));"> <h2 data-bind="text: PromoTitle1"></h2> <p data-bind="text: PromoText1"/> <a class="button" href="#">Learn more »</a> </div> </div> <div class="home_promo"> <div class="home_promo_content" style="background-image:url(images/@(ViewBag.Promos["2"].ImageURL));"> <h2 data-bind="text: PromoTitle2"></h2> <p data-bind="text: PromoText2"/> <a class="button" href="#">Learn more »</a> </div> </div> <div class="home_promo"> <div class="home_promo_content" style="background-image:url(images/@(ViewBag.Promos["3"].ImageURL));"> <h2 data-bind="text: PromoTitle3"></h2> <p data-bind="text: PromoText3"/> <a class="button" href="#">Learn more »</a> </div> </div> <div class="clear_both"></div> <!-- begin - homepage promos --> </div> <nav> <a href="">Home</a> <a href="Map">Walking Map</a> <a href="#">About Us</a> </nav> </div> <footer>© 2011 • Responsive Tours (a fictitious company)</footer> </div> @* Optional : Include fooder content *@ @section Footer { } @* Optional : Include additional deferred script references, or page initialisation *@ @section ScriptsBottom { <script type="text/javascript" src="~/../scripts/farinspace/jquery.imgpreload.min.js"></script> <script type="text/javascript" src="~/../scripts/farinspace/template.js"></script> <script type="text/javascript"> var timeToChange = 6; //seconds var transitionTime = 1.5; //seconds </script> <script> $(document).ready(function () { // Knockout view model. var viewModel = { PromoTitle1: "@(ViewBag.Promos["1"].Title)", PromoTitle2: "@(ViewBag.Promos["2"].Title)", PromoTitle3: "@(ViewBag.Promos["3"].Title)", PromoText1: "@(ViewBag.Promos["1"].Text)", PromoText2: "@(ViewBag.Promos["2"].Text)", PromoText3: "@(ViewBag.Promos["3"].Text)" }; // Activates knockout.js ko.applyBindings(viewModel); }); </script> <!-- Knockout data binding --> <script type="text/javascript" src="~/../scripts/libs/knockout-1.2.1.min.js"></script> }
With all of this in place, when we run the project we now
see the promotional item content is loaded from the database on the server side and bound to the web page on the client side. Voila! -- dynamic content.
Client-side Map Integration
The
other area of dynamic content is the map view. When a user clicks the Walking
Map link, they are supposed to get an interactive map that is initially
centered on their current location. This is an integration we can handle on the
client side. We’ll do it using Bing Maps and HTML5’s geolocation feature.
With a Bing Maps key in hand, a small amount of JavaScript is all that is needed to render an interactive map, obtain the current location (if the user grants it), and center a map on that point. The code selects one of two map elements (“map1” or “map2”) based on which is visible for thr active device layout.
With a Bing Maps key in hand, a small amount of JavaScript is all that is needed to render an interactive map, obtain the current location (if the user grants it), and center a map on that point. The code selects one of two map elements (“map1” or “map2”) based on which is visible for thr active device layout.
<!-- Bing Maps --> <script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0"></script> <script type="text/javascript"> var map = null; function getMap() { var mapOptions = { credentials: 'PucpG1...BING-MAPS-KEY...ByFfkTmeGP', mapTypeId: Microsoft.Maps.MapTypeId.automatic, zoom: 17 } var mapElement = document.getElementById("map1"); if (!isVisible(mapElement)) { mapElement = document.getElementById("map2"); } map = new Microsoft.Maps.Map(mapElement, mapOptions); } function setMapZoom(zoomLevel) { map.setView({ zoom: zoomLevel }); } function getCurrentLocation() { var geoLocationProvider = new Microsoft.Maps.GeoLocationProvider(map); geoLocationProvider.getCurrentPosition({ errorCallback: errorCallback, successCallback: displayCenter }); } function displayCenter(args) { setMapZoom(17); } function errorCallback(object) { alert('Error callback invoked, error code ' + object.errorCode); } function isVisible(obj) { if (obj == document) return true if (!obj) return false if (!obj.parentNode) return false if (obj.style) { if (obj.style.display == 'none') return false if (obj.style.visibility == 'hidden') return false } //Try the computed style in a standard way if (window.getComputedStyle) { var style = window.getComputedStyle(obj, "") if (style.display == 'none') return false if (style.visibility == 'hidden') return false } //Or get the computed style using IE's silly proprietary way var style = obj.currentStyle if (style) { if (style['display'] == 'none') return false if (style['visibility'] == 'hidden') return false } return isVisible(obj.parentNode) } </script>
When we run the project, select the map view, and give
permission to use our location we are greeted with an interactive map initially
showing our current location. We can use this in a walking tour of a city to
see where we are, find points of interest, bookmark favorite spots, and find
others in our party.
The final result of all this integration is a web site that has both
client-side and server-side dynamic content: an interactive map for walking tours, and
promotional items that can be easily changed via a database.
Summary
In Step 3 we enabled server-side dynamic content for promotional items using a database, MVC Razor, and Knockout. We also enabled client-side dynamic content via Bing Maps. Our site now has the
following functionality:
• Uses HTML5 and open standards on the web
client• Embodies responsive web design and runs on desktops, tablets, and phones.
• Provides server-side dynamic content (promotional items)
• Provides client-side dynamic content (Bing Maps)
In the next step, we'll prepare the application for cloud computing on Windows Azure.
No comments:
Post a Comment