Sdílet prostřednictvím


Použití jazyka AJAX k implementaci scénářů mapování

od Microsoftu

Stáhnout PDF

Toto je krok 11 bezplatného kurzu aplikace NerdDinner , který vás provede sestavením malé, ale kompletní webové aplikace pomocí ASP.NET MVC 1.

Krok 11 ukazuje, jak integrovat podporu mapování AJAX do naší aplikace NerdDinner a umožnit tak uživatelům, kteří vytvářejí, upravují nebo prohlížejí večeře, grafické zobrazení umístění večeře.

Pokud používáte ASP.NET MVC 3, doporučujeme postupovat podle kurzů Začínáme S MVC 3 nebo MVC Music Store.

NerdDinner Krok 11: Integrace mapy AJAX

Díky integraci podpory mapování AJAX teď naše aplikace bude vizuálně zajímavější. To umožní uživatelům, kteří vytvářejí, upravují nebo prohlížejí večeře, zobrazit umístění večeře graficky.

Vytvoření částečného zobrazení mapy

Funkci mapování budeme používat na několika místech v rámci naší aplikace. Aby byl náš kód suchý, zapouzdřeme společné funkce mapování do jedné částečné šablony, kterou můžeme znovu použít pro více akcí a zobrazení kontroleru. Toto částečné zobrazení pojmenujeme map.ascx a vytvoříme ho v adresáři \Views\Dinners.

Soubor map.ascx můžeme vytvořit částečně tak, že klikneme pravým tlačítkem na adresář \Views\Dinners a zvolíme příkaz nabídky Add-View>. Toto zobrazení pojmenujeme "Map.ascx", zkontrolujeme ho jako částečné zobrazení a označíme ho jako třídu modelu "Dinner" se silnými typy:

Snímek obrazovky s dialogovým oknem Přidat zobrazení Nerd Dinner dot Models dot Dinner je zapsán v poli Zobrazit datová třída.

Když klikneme na tlačítko Přidat, vytvoří se naše částečná šablona. Potom soubor Map.ascx aktualizujeme tak, aby měl následující obsah:

<script src="http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.2" type="text/javascript"></script>
<script src="/Scripts/Map.js" type="text/javascript"></script>

<div id="theMap">
</div>

<script type="text/javascript">
   
    $(document).ready(function() {
        var latitude = <%=Model.Latitude%>;
        var longitude = <%=Model.Longitude%>;
                
        if ((latitude == 0) || (longitude == 0))
            LoadMap();
        else
            LoadMap(latitude, longitude, mapLoaded);
    });
      
   function mapLoaded() {
        var title = "<%=Html.Encode(Model.Title) %>";
        var address = "<%=Html.Encode(Model.Address) %>";
    
        LoadPin(center, title, address);
        map.SetZoomLevel(14);
    } 
      
</script>

První <odkaz na skript> odkazuje na knihovnu mapování Microsoft Virtual Earth 6.2. Druhý <odkaz na skript> odkazuje na soubor map.js, který zanedlouho vytvoříme a který zapouzdří naši běžnou logiku mapování JavaScriptu. Element <div id="theMap"> je kontejner HTML, který Virtual Earth použije k hostování mapy.

Pak máme blok vloženého <skriptu> , který obsahuje dvě funkce JavaScriptu specifické pro toto zobrazení. První funkce používá jQuery ke připojení funkce, která se spustí, když je stránka připravená ke spuštění skriptu na straně klienta. Volá pomocnou funkci LoadMap(), kterou definujeme v souboru skriptu Map.js pro načtení ovládacího prvku virtuální mapy země. Druhá funkce je obslužná rutina události zpětného volání, která do mapy přidá špendlík, který identifikuje umístění.

Všimněte si, jak používáme blok %= %> na straně <serveru v bloku skriptu na straně klienta k vložení zeměpisné šířky a délky večeře, kterou chceme namapovat do JavaScriptu. Jedná se o užitečnou techniku výstupu dynamických hodnot, které mohou být použity skriptem na straně klienta (bez nutnosti samostatného volání AJAX zpět na server k načtení hodnot – což je rychlejší). Bloky <%= %> se spustí, když se zobrazení vykresluje na serveru, takže výstup HTML bude jenom s vloženými javascriptovými hodnotami (například var latitude = 47.64312;).

Vytvoření knihovny nástrojů Map.js

Pojďme teď vytvořit Map.js soubor, který můžeme použít k zapouzdření funkcí JavaScriptu pro naši mapu (a implementovat výše uvedené metody LoadMap a LoadPin). Můžeme to udělat tak, že klikneme pravým tlačítkem na adresář \Scripts v rámci našeho projektu a pak zvolíme příkaz nabídky Přidat> novou položku, vybereme položku jazyka JScript a pojmenujeme ji "Map.js".

Níže je kód JavaScriptu, který přidáme do souboru Map.js, který bude pracovat s Virtual Earth, aby zobrazil naši mapu a přidal do ní připnuté polohy pro naše večeře:

var map = null;
var points = [];
var shapes = [];
var center = null;

function LoadMap(latitude, longitude, onMapLoaded) {
    map = new VEMap('theMap');
    options = new VEMapOptions();
    options.EnableBirdseye = false;

    // Makes the control bar less obtrusize.
    map.SetDashboardSize(VEDashboardSize.Small);
    
    if (onMapLoaded != null)
        map.onLoadMap = onMapLoaded;

    if (latitude != null && longitude != null) {
        center = new VELatLong(latitude, longitude);
    }

    map.LoadMap(center, null, null, null, null, null, null, options);
}

function LoadPin(LL, name, description) {
    var shape = new VEShape(VEShapeType.Pushpin, LL);

    //Make a nice Pushpin shape with a title and description
    shape.SetTitle("<span class=\"pinTitle\"> " + escape(name) + "</span>");
    if (description !== undefined) {
        shape.SetDescription("<p class=\"pinDetails\">" + 
        escape(description) + "</p>");
    }
    map.AddShape(shape);
    points.push(LL);
    shapes.push(shape);
}

function FindAddressOnMap(where) {
    var numberOfResults = 20;
    var setBestMapView = true;
    var showResults = true;

    map.Find("", where, null, null, null,
           numberOfResults, showResults, true, true,
           setBestMapView, callbackForLocation);
}

function callbackForLocation(layer, resultsArray, places,
            hasMore, VEErrorMessage) {
            
    clearMap();

    if (places == null) 
        return;

    //Make a pushpin for each place we find
    $.each(places, function(i, item) {
        description = "";
        if (item.Description !== undefined) {
            description = item.Description;
        }
        var LL = new VELatLong(item.LatLong.Latitude,
                        item.LatLong.Longitude);
                        
        LoadPin(LL, item.Name, description);
    });

    //Make sure all pushpins are visible
    if (points.length > 1) {
        map.SetMapView(points);
    }

    //If we've found exactly one place, that's our address.
    if (points.length === 1) {
        $("#Latitude").val(points[0].Latitude);
        $("#Longitude").val(points[0].Longitude);
    }
}

function clearMap() {
    map.Clear();
    points = [];
    shapes = [];
}

Integrace mapy s vytvářením a úpravami formulářů

Teď integrujeme podporu map s našimi stávajícími scénáři vytváření a úprav. Dobrou zprávou je, že je to docela snadné a nevyžaduje, abychom změnili kód kontroleru. Vzhledem k tomu, že naše zobrazení Vytvořit a upravit sdílí společné částečné zobrazení "DinnerForm" pro implementaci uživatelského rozhraní formuláře večeře, můžeme mapu přidat na jednom místě a použít ji náš scénář Vytvořit a Upravit.

Stačí otevřít částečné zobrazení \Views\Dinners\DinnerForm.ascx a aktualizovat ho tak, aby zahrnovalo naši novou mapu částečně. Níže je uvedeno, jak bude aktualizovaný formulář DinnerForm vypadat po přidání mapy (poznámka: Prvky formuláře HTML jsou z fragmentu kódu níže vynechány pro stručnost):

<%= Html.ValidationSummary() %>
 
<% using (Html.BeginForm()) { %>
 
    <fieldset>

        <div id="dinnerDiv">
            <p>
               [HTML Form Elements Removed for Brevity]
            </p>                 
            <p>
               <input type="submit" value="Save"/>
            </p>
        </div>
        
        <div id="mapDiv">    
            <%Html.RenderPartial("Map", Model.Dinner); %>
        </div> 
            
    </fieldset>

    <script type="text/javascript">

        $(document).ready(function() {
            $("#Address").blur(function(evt) {
                $("#Latitude").val("");
                $("#Longitude").val("");

                var address = jQuery.trim($("#Address").val());
                if (address.length < 1)
                    return;

                FindAddressOnMap(address);
            });
        });
    
    </script>

<% } %>

Část DinnerForm výše přebírá objekt typu "DinnerFormViewModel" jako typ modelu (protože k naplnění rozevíracího seznamu zemí potřebuje objekt Dinner i SelectList). Část mapy potřebuje jako typ modelu objekt typu "Dinner", takže když mapu vykreslíme jako částečnou, předáváme do něj jenom dílčí vlastnost DinnerFormViewModel:

<% Html.RenderPartial("Map", Model.Dinner); %>

Funkce JavaScriptu, kterou jsme přidali do částečné části, používá jQuery k připojení události rozostření k textovému poli HTML "Address" (Adresa). Pravděpodobně jste už slyšeli o událostech fokusu, které se aktivují, když uživatel klikne na textové pole nebo se do textového pole zasadí. Opakem je událost rozostření, která se aktivuje, když uživatel opustí textové pole. Výše uvedená obslužná rutina události v takovém případě vymaže hodnoty textového pole zeměpisná šířka a délka a potom vykreslí nové umístění adresy na naší mapě. Obslužná rutina události zpětného volání, kterou jsme definovali v souboru map.js, pak aktualizuje textová pole zeměpisné délky a šířky v našem formuláři pomocí hodnot vrácených virtuální zemí na základě zadané adresy.

A teď, když aplikaci znovu spustíme a klikneme na kartu Host Dinner (Host Dinner), zobrazí se výchozí mapa spolu s našimi standardními prvky formuláře Večeře:

Snímek obrazovky se stránkou Host Dinner (Večeře hostitele) a zobrazenou výchozí mapou

Když napíšeme adresu a pak se tabulátorem přesuneme, mapa se dynamicky aktualizuje tak, aby zobrazovala polohu, a naše obslužná rutina události naplní textová pole zeměpisné šířky a délky hodnotami umístění:

Snímek obrazovky se stránkou Nerd Dinners (Nerd Dinners) se zobrazenou mapou

Pokud uložíme novou večeři a znovu ji otevřeme pro úpravy, zjistíme, že se při načtení stránky zobrazí umístění mapy:

Snímek obrazovky se stránkou Upravit na webu Nerd Dinners

Při každé změně pole adresy se aktualizuje mapa a souřadnice zeměpisné šířky a délky.

Teď, když se na mapě zobrazuje místo večeře, můžeme také změnit pole formuláře Zeměpisná šířka a Zeměpisná délka z viditelných textových polí na skryté prvky (protože mapa je automaticky aktualizuje při každém zadání adresy). Abychom to mohli udělat, přepneme z použití pomocné rutiny HTML Html.TextBox() na metodu pomocníka Html.Hidden():

<p>
    <%= Html.Hidden("Latitude", Model.Dinner.Latitude)%>
    <%= Html.Hidden("Longitude", Model.Dinner.Longitude)%>
</p>

A teď jsou naše formuláře uživatelsky přívětivější a vyhněte se zobrazení nezpracované zeměpisné šířky a délky (a přitom je stále ukládáme při každé večeři v databázi):

Snímek obrazovky s mapou na stránce Nerd Dinners

Integrace mapy se zobrazením podrobností

Teď, když máme mapu integrovanou se scénáři Vytvořit a Upravit, můžeme ji integrovat také se scénářem Podrobností. Stačí volat <% Html.RenderPartial("map"); %> v zobrazení Podrobnosti.

Níže je vidět, jak vypadá zdrojový kód kompletního zobrazení podrobností (s integrací map):

<asp:Content ID="Title" ContentPlaceHolderID="TitleContent"runat="server">
    <%= Html.Encode(Model.Title) %>
</asp:Content>

<asp:Content ID="details" ContentPlaceHolderID="MainContent" runat="server">

    <div id="dinnerDiv">

        <h2><%=Html.Encode(Model.Title) %></h2>
        <p>
            <strong>When:</strong> 
            <%=Model.EventDate.ToShortDateString() %> 

            <strong>@</strong>
            <%=Model.EventDate.ToShortTimeString() %>
        </p>
        <p>
            <strong>Where:</strong> 
            <%=Html.Encode(Model.Address) %>,
            <%=Html.Encode(Model.Country) %>
        </p>
         <p>
            <strong>Description:</strong> 
            <%=Html.Encode(Model.Description) %>
        </p>       
        <p>
            <strong>Organizer:</strong> 
            <%=Html.Encode(Model.HostedBy) %>
            (<%=Html.Encode(Model.ContactPhone) %>)
        </p>
    
        <%Html.RenderPartial("RSVPStatus"); %>
        <%Html.RenderPartial("EditAndDeleteLinks"); %>
 
    </div>
    
    <div id="mapDiv">
        <%Html.RenderPartial("map"); %>    
    </div>   
         
</asp:Content>

A když teď uživatel přejde na adresu URL /Dinners/Details/[id], zobrazí se mu podrobnosti o večeři, umístění večeře na mapě (doplněné o připínáčku, který při najetí myší zobrazí název večeře a její adresu) a bude mít odkaz AJAX na RSVP:

Snímek obrazovky webové stránky Nerd Dinners Zobrazí se mapa.

Implementace hledání umístění v databázi a úložišti

Na závěr implementace AJAX přidáme na domovskou stránku aplikace Mapu, která uživatelům umožní graficky vyhledávat večeře v jejich blízkosti.

Snímek obrazovky s domovskou stránkou Nerd Dinners Zobrazí se mapa.

Začneme implementací podpory v rámci naší databázové vrstvy a vrstvy úložiště dat, abychom mohli efektivně vyhledávat funkce Dinners na základě umístění. Mohli bychom použít nové geoprostorové funkce SQL 2008 k implementaci, nebo alternativně můžeme použít přístup k funkci SQL, který Gary Dryden probral v tomto článku: http://www.codeproject.com/KB/cs/distancebetweenlocations.aspx.

Abychom mohli tuto techniku implementovat, otevřeme v sadě Visual Studio Průzkumník serveru, vybereme databázi NerdDinner, klikneme pravým tlačítkem na dílčí uzel functions pod ní a zvolíme vytvoření nové skalární funkce:

Snímek obrazovky s Průzkumníkem serveru v sadě Visual Studio Je vybraná databáze Nerd Dinner a dílčí uzel functions. Skalární funkce s hodnotami je zvýrazněná.

Potom vložíme následující funkci DistanceBetween:

CREATE FUNCTION [dbo].[DistanceBetween](@Lat1 as real,
                @Long1 as real, @Lat2 as real, @Long2 as real)
RETURNS real
AS
BEGIN

DECLARE @dLat1InRad as float(53);
SET @dLat1InRad = @Lat1 * (PI()/180.0);
DECLARE @dLong1InRad as float(53);
SET @dLong1InRad = @Long1 * (PI()/180.0);
DECLARE @dLat2InRad as float(53);
SET @dLat2InRad = @Lat2 * (PI()/180.0);
DECLARE @dLong2InRad as float(53);
SET @dLong2InRad = @Long2 * (PI()/180.0);

DECLARE @dLongitude as float(53);
SET @dLongitude = @dLong2InRad - @dLong1InRad;
DECLARE @dLatitude as float(53);
SET @dLatitude = @dLat2InRad - @dLat1InRad;
/* Intermediate result a. */
DECLARE @a as float(53);
SET @a = SQUARE (SIN (@dLatitude / 2.0)) + COS (@dLat1InRad)
                 * COS (@dLat2InRad)
                 * SQUARE(SIN (@dLongitude / 2.0));
/* Intermediate result c (great circle distance in Radians). */
DECLARE @c as real;
SET @c = 2.0 * ATN2 (SQRT (@a), SQRT (1.0 - @a));
DECLARE @kEarthRadius as real;
/* SET kEarthRadius = 3956.0 miles */
SET @kEarthRadius = 6376.5;        /* kms */

DECLARE @dDistance as real;
SET @dDistance = @kEarthRadius * @c;
return (@dDistance);
END

Potom vytvoříme novou funkci s hodnotou tabulky v SQL Server, kterou budeme nazývat "NearestDinners":

Snímek obrazovky se serverem S Q L Table-Valued funkce je zvýrazněná.

Tato funkce tabulky "NearestDinners" používá pomocnou funkci DistanceBetween k vrácení všech večeří do 100 mil od zadané zeměpisné šířky a délky:

CREATE FUNCTION [dbo].[NearestDinners]
      (
      @lat real,
      @long real
      )
RETURNS  TABLE
AS
      RETURN
      SELECT Dinners.DinnerID
      FROM   Dinners 
      WHERE  dbo.DistanceBetween(@lat, @long, Latitude, Longitude) <100

Tuto funkci zavoláme tak, že poklikáním na soubor NerdDinner.dbml v adresáři \Models nejprve otevřeme návrháře LINQ to SQL:

Snímek obrazovky se souborem Nerd Dinner dot d b m l v adresáři Models

Potom přetáhneme funkce NearestDinners a DistanceBetween do návrháře LINQ to SQL, což způsobí jejich přidání jako metody do naší třídy LINQ to SQL NerdDinnerDataContext:

Snímek obrazovky s funkcemi Nejbližší večeře a Vzdálenost mezi

Pak můžeme zveřejnit metodu dotazu "FindByLocation" pro naši třídu DinnerRepository, která používá funkci NearestDinner k vrácení nadcházejících dinner, které jsou vzdálené do 100 mil od zadaného umístění:

public IQueryable<Dinner> FindByLocation(float latitude, float longitude) {

   var dinners = from dinner in FindUpcomingDinners()
                 join i in db.NearestDinners(latitude, longitude)
                 on dinner.DinnerID equals i.DinnerID
                 select dinner;

   return dinners;
}

Implementace metody akcí vyhledávání AJAX založené na formátu JSON

Teď implementujeme metodu akce kontroleru, která využívá novou metodu úložiště FindByLocation() k vrácení seznamu dat Večeře, která se dají použít k naplnění mapy. Tato metoda akce vrátí data Dinner zpět ve formátu JSON (JavaScript Object Notation), aby s nimi bylo možné snadno manipulovat pomocí JavaScriptu na klientovi.

Abychom to mohli implementovat, vytvoříme novou třídu SearchController tak, že klikneme pravým tlačítkem na adresář \Controllers a zvolíme příkaz nabídky Add-Controller>. Pak implementujeme metodu akce SearchByLocation v rámci nové třídy SearchController, jak je znázorněno níže:

public class JsonDinner {
    public int      DinnerID    { get; set; }
    public string   Title       { get; set; }
    public double   Latitude    { get; set; }
    public double   Longitude   { get; set; }
    public string   Description { get; set; }
    public int      RSVPCount   { get; set; }
}

public class SearchController : Controller {

    DinnerRepository dinnerRepository = new DinnerRepository();

    //
    // AJAX: /Search/SearchByLocation

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult SearchByLocation(float longitude, float latitude) {

        var dinners = dinnerRepository.FindByLocation(latitude,longitude);

        var jsonDinners = from dinner in dinners
                          select new JsonDinner {
                              DinnerID = dinner.DinnerID,
                              Latitude = dinner.Latitude,
                              Longitude = dinner.Longitude,
                              Title = dinner.Title,
                              Description = dinner.Description,
                              RSVPCount = dinner.RSVPs.Count
                          };

        return Json(jsonDinners.ToList());
    }
}

Metoda akce SearchByLocation v SearchController interně volá metodu FindByLocation na DinnerRepository k získání seznamu blízkých večeří. Místo toho, aby se objekty Dinner vracely přímo klientovi, ale vrací objekty JsonDinner. Třída JsonDinner zveřejňuje podmnožinu vlastností Dinner (například: z bezpečnostních důvodů nezveřejňuje jména lidí, kteří mají rsvp'd na večeři). Zahrnuje také vlastnost RSVPCount, která na večeři neexistuje a která se dynamicky počítá počítáním počtu objektů RSVP přidružených ke konkrétní večeři.

Potom použijeme pomocnou metodu Json() v základní třídě Controller, abychom vrátili sekvenci večeří ve formátu drátu založeném na formátu JSON. JSON je standardní textový formát pro reprezentaci jednoduchých datových struktur. Níže je příklad toho, jak vypadá seznam dvou objektů JsonDinner ve formátu JSON, když se vrátí z naší metody action:

[{"DinnerID":53,"Title":"Dinner with the Family","Latitude":47.64312,"Longitude":-122.130609,"Description":"Fun dinner","RSVPCount":2}, 
{"DinnerID":54,"Title":"Another Dinner","Latitude":47.632546,"Longitude":-122.21201,"Description":"Dinner with Friends","RSVPCount":3}]

Volání metody AJAX založené na formátu JSON pomocí jQuery

Nyní jsme připraveni aktualizovat domovskou stránku aplikace NerdDinner tak, aby používala metodu akce SearchController SearchByLocation. K tomu otevřeme šablonu zobrazení /Views/Home/Index.aspx a aktualizujeme ji tak, aby měla textové pole, tlačítko hledání, mapu a <element div> s názvem dinnerList:

<h2>Find a Dinner</h2>

<div id="mapDivLeft">

    <div id="searchBox">
        Enter your location: <%=Html.TextBox("Location") %>
        <input id="search" type="submit" value="Search"/>
    </div>

    <div id="theMap">
    </div>

</div>

<div id="mapDivRight">
    <div id="dinnerList"></div>
</div>

Na stránku pak můžeme přidat dvě funkce JavaScriptu:

<script type="text/javascript">

    $(document).ready(function() {
        LoadMap();
    });

    $("#search").click(function(evt) {
        var where = jQuery.trim($("#Location").val());
        if (where.length < 1) 
            return;

        FindDinnersGivenLocation(where);
    });

</script>

První javascriptová funkce načte mapu při prvním načtení stránky. Druhá funkce JavaScriptu propoštá obslužnou rutinu události kliknutí v JavaScriptu na tlačítko hledání. Při stisknutí tlačítka se zavolá javascriptová funkce FindDinnersGivenLocation(), kterou přidáme do souboru Map.js:

function FindDinnersGivenLocation(where) {
    map.Find("", where, null, null, null, null, null, false,
       null, null, callbackUpdateMapDinners);
}

Tato funkce FindDinnersGivenLocation() volá mapu. Na virtuálním ovládacím prvku Země najděte() a zacentrujte ho na zadané místo. Když se virtuální služba mapy země vrátí, mapa. Metoda Find() vyvolá metodu zpětného volání callbackUpdateMapDinners, které jsme jí předali jako poslední argument.

Metoda callbackUpdateMapDinners() je místo, kde se provádí skutečná práce. Používá pomocnou metodu jQuery $.post() k provedení volání AJAX do metody akce SearchByLocation() naší služby SearchController – předává jí zeměpisnou šířku a délku nově vycentrované mapy. Definuje vloženou funkci, která bude volána po dokončení pomocné metody $.post() a výsledky večeře ve formátu JSON vrácené z metody akce SearchByLocation() se předají pomocí proměnné s názvem "dinners". Potom provede foreach pro každou vrácenou večeři a použije zeměpisnou šířku a délku večeře a další vlastnosti k přidání nového špendlíku na mapu. Přidá také položku večeře do html seznamu večeří napravo od mapy. Potom propojí událost přechodu myší pro připínáky i seznam HTML, aby se zobrazily podrobnosti o večeři, když na ně uživatel najede myší:

function callbackUpdateMapDinners(layer, resultsArray, places, hasMore, VEErrorMessage) {

    $("#dinnerList").empty();
    clearMap();
    var center = map.GetCenter();

    $.post("/Search/SearchByLocation", { latitude: center.Latitude, 
                                         longitude: center.Longitude },     
    function(dinners) {
        $.each(dinners, function(i, dinner) {

            var LL = new VELatLong(dinner.Latitude, 
                                   dinner.Longitude, 0, null);

            var RsvpMessage = "";

            if (dinner.RSVPCount == 1)
                RsvpMessage = "" + dinner.RSVPCount + "RSVP";
            else
                RsvpMessage = "" + dinner.RSVPCount + "RSVPs";

            // Add Pin to Map
            LoadPin(LL, '<a href="/Dinners/Details/' + dinner.DinnerID + '">'
                        + dinner.Title + '</a>',
                        "<p>" + dinner.Description + "</p>" + RsvpMessage);

            //Add a dinner to the <ul> dinnerList on the right
            $('#dinnerList').append($('<li/>')
                            .attr("class", "dinnerItem")
                            .append($('<a/>').attr("href",
                                      "/Dinners/Details/" + dinner.DinnerID)
                            .html(dinner.Title))
                            .append(" ("+RsvpMessage+")"));
        });

        // Adjust zoom to display all the pins we just added.
        map.SetMapView(points);

        // Display the event's pin-bubble on hover.
        $(".dinnerItem").each(function(i, dinner) {
            $(dinner).hover(
                function() { map.ShowInfoBox(shapes[i]); },
                function() { map.HideInfoBox(shapes[i]); }
            );
        });
    }, "json");

A když teď aplikaci spustíme a navštívíme domovskou stránku, zobrazí se nám mapa. Když zadáme název města, mapa zobrazí nadcházející večeře v jeho blízkosti:

Snímek obrazovky domovské stránky Nerd Dinner se zobrazenou mapou

Když najedete myší na večeři, zobrazí se o ní podrobnosti.

Kliknutím na název Večeře v bublině nebo na pravé straně v seznamu HTML nás přejdete na večeři , kterou pak můžeme volitelně použít pro:

Snímek obrazovky se stránkou podrobností o večeři nerdů s mapou zobrazující navigaci na večeři

Další krok

Teď jsme implementovali všechny funkce aplikace NerdDinner. Pojďme se teď podívat, jak můžeme povolit jeho automatizované testování jednotek.