Friday, October 31, 2008

MVC Framework and jQuery = Ajax heaven

I've got an admission to make: I've never used any of the Microsoft Ajax Toolkit. But recently I've been adding some mapping functionality to the project I'm working on. We wanted users to be able to pull a marker to a position on a map and have the new position updated on the server. Obviously we were going to have to use Ajax of some kind to do that. What I want to show today is how trivially easy it's proved to be to use MVC Framework on the server and jQuery on the browser to do Ajax requests and updates. JQuery is now included in the default project for the MVC Framework, so there's no excuse not to use it.

Here's a very simple scenario. I've got a a page where I want to show a list of people when I click 'Get People'. I also want to add a person to the database when I type their name into a text box and click 'Add Person'. Here's what it looks like:

AjaxDemo

The first thing we do is create the button for 'Get People' and an unordered list to put the people in:

    <input type="button" id="getPeople" value="Get People" />
    <ul id="people_list">         

Next we have a jQuery ready handler (which fires when the page loads) that sets up the event handler for the getPeople button:

$(function() {
    $("#getPeople").click(function() {
        $.getJSON("/Home/People", null, getPeople);
    });
});

When the button is clicked we fire off a json request to /Home/People. This maps to a People action in our Home controller:

[AcceptVerbs(HttpVerbs.Get)]
public ActionResult People()
{
    var db = new DataClasses1DataContext();
    return Json(db.Persons);
}

All we do here is get all our Person records (using LINQ to SQL) and return them as Json. When the response is returned to the browser the getPeople callback function is called. Remember we set this in the getJSON jQuery method above. Here's the getPeople function:

function getPeople(people) {
    $("#people_list").text("");
    $.each(people, function(i) {
        $("#people_list").append("<li>" + this.Name + "</li>");
    });
}

The callback provides the data returned from the JSON request as its argument. All we have to do is use the handy jQuery each function to iterate through our people collection and append a new li element to our list for each one. The really nice thing here is how well the MVC Framework and jQuery interact. I didn't have to do any conversions between them. Our data just automagically converts from C# objects to Javascript.

How about the update? This is just as simple. First we need some more HTML: a text box to type the name into and a button to fire the update:

    <label>Name <input type="text" id="name" /></label><br />
    <input type="button" id="addPerson" value="Add Person" />

Next another click event handler is set up to handle the addPerson click event:

$("#addPerson").click(function() {
    var name = $("#name")[0].value;
    if (name == "") {
        alert("You must provide a name");
        return;
    }
    var person = { Name: name };
    $.post("/Home/People", person, null, "json");
});

This time we use the useful 'post' function to post our newly created person JSON object to the /Home/People URL. Because this is a POST request it's handled by our overloaded People action, this time attributed with AcceptVerbs[HttpVerbs.Post]:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult People(string Name)
{
    var db = new DataClasses1DataContext();
    var person = new Person {Name = Name};
    db.Persons.InsertOnSubmit(person);
    db.SubmitChanges();
    return null;
}

All we have to do to retrieve the Name of the person is to name the argument of the action 'Name'. The MVC Framework automatically pulls apart the request, finds a value called 'Name' and supplies it to the action. If you have a complex JSON structure you can deserialize it to a C# object using the built in data binding. All that's left to do is create a new Person instance with the given name and save it to the database. Nice!

I'm a latecomer to the Ajax party, but with jQuery and the MVC Framework I'm finding a breeze to wire up some pretty nice functionality.

You can download the demo solution here:

http://static.mikehadlow.com/JQueryAjax.zip

Thursday, October 30, 2008

Dynamic Dispatch in C# 4.0

So C# 4.0 is finally with us (in CTP form anyway). I've just been watching Ander's PDC presentation, The Future of C#. The main focus is providing interop with dynamic languages and COM, so in order to do that they've added some dynamic goodness in C# itself. There's a new static type called dynamic :) Yes, it's the number one joke at PDC it seems. The dynamic keyword allows us to say to the compiler, "don't worry what I'm doing with this type, we're going to dispatch it at runtime".

Take a look at this code:

using System;
using Microsoft.CSharp.RuntimeBinder;
using System.Scripting.Actions;
using System.Linq.Expressions;
namespace ConsoleApplication1
{
    public class Program
    {
        static void Main(string[] args)
        {
            DoSomethingDynamic(7);
            DoSomethingDynamic(new Actor());
            DoSomethingDynamic(new DynamicThing());
            Console.ReadLine();
        }
        static void DoSomethingDynamic(dynamic thing)
        {
            try
            {
                thing.Act();
            }
            catch (RuntimeBinderException)
            {
                Console.WriteLine("thing does not implement Act");
            }
        }
    }
    public class Actor
    {
        public void Act()
        {
            Console.WriteLine("Actor.Act() was called");
        }
    }
    public class DynamicThing : IDynamicObject
    {
        public MetaObject GetMetaObject(System.Linq.Expressions.Expression parameter)
        {
            return new CustomMetaObject(parameter);
        }
    }
    public class CustomMetaObject : MetaObject
    {
        public CustomMetaObject(Expression parameter) : base(parameter, Restrictions.Empty){ }
        public override MetaObject Call(CallAction action, MetaObject[] args)
        {
            Console.WriteLine("A method named: '{0}' was called", action.Name);
            return this;
        }
    }
}

OK, so we've got a little console application. I've defined a method, DoSomethingDynamic, which has a parameter, 'thing', of type dynamic. We call the Act() method of thing. This is duck typing. The compiler can't check that thing has an Act method until runtime, so we're going to wrap it in a try-catch block just in case it doesn't. A side effect of duck typing is that there's no intellisense for the thing variable, we can write whatever we like against it and it will compile. Any of these would compile: thing.Foo(), thing + 56, thing.X = "hello", var y = thing[12].

Next, in the Main method, we call DoSomethingDynamic a few times, passing in different kinds of arguments. First we pass in the literal 7. Int32 doesn't have an Act method so a RuntimeBinderException is thrown. Next we pass an instance of a normal C# class, Actor. Actor has an Act method so Act is called normally as expected.The last invocation of DoSomethingDynamic shows off how you can do dynamic dispatch in C# 4.0. We define a new class called DynamicThing and have it inherit IDynamicObject. IDynamicObject has a single method you must implement: GetMetaObject. GetMetaObject returns a MetaObject and all you have to do is implement a CustomMetaObject that knows what to do with any method (or parameter, or indexer etc) invocation. Our CustomMetaObject overrides Call and simply writes the name of the method to the console. Chris Burrows from the C# compiler team has a series of three blog posts showing off these techniques here, here and here. Anders' PDC presentation, The Future of C# is here. C# is primarily a statically typed language and Anders obviously still believes that static typing is a better paradigm for large scale software. He sees the dynamic features as a way of adding capabilities to C# that have previously been the prerogative of VB and dynamic languages. He's especially contrite about the failure of C# to interoperate with COM efficiently in the past. However there are going to be lots of cases when using dynamic will be a short cut to features that are hard to implement statically. I can see the C# forums humming with complaints that intellisense doesn't work anymore, or hard to diagnose runtime errors as a result of over zealous dynamism.

The last ten minutes of the talk, when he showed us some of the post 4.0 features, was very cool. They are rewriting the C# compiler in C#. This means that the compiler API will be just another library in the framework. Applications will be able to call compiler services at runtime giving us lots of Ruby style meta-programming goodness. Tools will be able to read the C# AST, giving us incredible power for refactoring or post compilation style tweeks.

Jim Hugunin has a related presentation, Dynamic Languages in Microsoft .NET that goes deeper into the dynamic features that Anders talks about. Also well worth watching.

Wednesday, October 29, 2008

Today's WTF

I take this as a compliment :)

tomuchrecursion

Friday, October 24, 2008

Last night's talk on Dependency Injection with Windsor at Skill's Matter, London

Thanks to everyone who attended our talk last night. Thanks too to Gojko for inviting me along. Gojko already has links to his code and slides up on his blog. You can download my slides here:

http://static.mikehadlow.com/MikeHadlow_WhyDoINeedAnIoCContainer.pptx

The code is available on Google Code here:

http://code.google.com/p/sutekicode/

Or you can download it here:

http://static.mikehadlow.com/Mike.IocDemo.zip

Tuesday, October 21, 2008

Rendering a tree view using the MVC Framework

A tree view is a wonderful way to present nested data structures or trees.  Here's an example:

treeview

I've written an HTML helper that presents tree structures as nested unordered lists in HTML. First I've got an interface to represent a tree node. It's called IComposite after the GoF pattern.

public interface IComposite<T>
{
    T Parent { get; }
    ISet<T> Children { get; }
}

Now we just need to make sure that our entity implements this interface. Here's a simple example, CompositeThing:

public class CompositeThing : IComposite<CompositeThing>
{
    public CompositeThing()
    {
        Children = new HashedSet<CompositeThing>();
    }
    public string Name { get; set; }
    public CompositeThing Parent { get; set; }
    public ISet<CompositeThing> Children { get; set; }
}

Now all we need to do is get the object graph from the database. Techniques for doing that are the subject for another post, but it's pretty straight forward with both LINQ-to-SQL and NHibernate. Once we have the graph we can pass it to our view and display it like this:

<%= Html.RenderTree(ViewData.Model.CompositeThings, thing => thing.Name) %>

And finally here's the code for  the RenderTree HtmlHelper extension method:

public static class TreeRenderHtmlHelper
{
    public static string RenderTree<T>(
        this HtmlHelper htmlHelper,
        IEnumerable<T> rootLocations,
        Func<T, string> locationRenderer)
        where T : IComposite<T>
    {
        return new TreeRenderer<T>(rootLocations, locationRenderer).Render();
    }
}
public class TreeRenderer<T> where T : IComposite<T>
{
    private readonly Func<T, string> locationRenderer;
    private readonly IEnumerable<T> rootLocations;
    private HtmlTextWriter writer;
    public TreeRenderer(
        IEnumerable<T> rootLocations,
        Func<T, string> locationRenderer)
    {
        this.rootLocations = rootLocations;
        this.locationRenderer = locationRenderer;
    }
    public string Render()
    {
        writer = new HtmlTextWriter(new StringWriter());
        RenderLocations(rootLocations);
        return writer.InnerWriter.ToString();
    }
    /// <summary>
    /// Recursively walks the location tree outputting it as hierarchical UL/LI elements
    /// </summary>
    /// <param name="locations"></param>
    private void RenderLocations(IEnumerable<T> locations)
    {
        if (locations == null) return;
        if (locations.Count() == 0) return;
        InUl(() => locations.ForEach(location => InLi(() =>
        {
            writer.Write(locationRenderer(location));
            RenderLocations(location.Children);
        })));
    }
    private void InUl(Action action)
    {
        writer.WriteLine();
        writer.RenderBeginTag(HtmlTextWriterTag.Ul);
        action();
        writer.RenderEndTag();
        writer.WriteLine();
    }
    private void InLi(Action action)
    {
        writer.RenderBeginTag(HtmlTextWriterTag.Li);
        action();
        writer.RenderEndTag();
        writer.WriteLine();
    }
}

The resulting HTML looks like this:

<html>
<head>
    <title>Tree View</title>
    <link href="jquery.treeview.css" rel="stylesheet" type="text/css" />
    <script src="jquery-1.2.6.min.js" type="text/javascript"></script>
    <script src="jquery.treeview.js" type="text/javascript"></script>
    <script type="text/javascript">
        $(function() {
            $("#treeview ul").treeview();
        });
    </script>
</head>
<body style="font-family : Arial; ">
    <h1>
        Tree View
    </h1>
    <div id="treeview">
        <ul>
            <li>Root
                <ul>
                    <li>First Child
                        <ul>
                            <li>First Grandchild</li>
                            <li>Second Grandchild</li>
                        </ul>
                    </li>
                    <li>Second Child
                        <ul>
                            <li>Third Grandchild</li>
                            <li>Fourth Grandchild</li>
                        </ul>
                    </li>
                </ul>
            </li>
        </ul>
    </div>
</body>
</html>
Note that I'm using the excellent jQuery.treeview to render the collapsible tree with expansion buttons.

Monday, October 20, 2008

Mapping entities to multiple databases with NHibernate

The legacy application I'm currently replacing features multiple databases for some insane reason. Luckily it's quite easy to get NHibernate to do joins across databases so long as they are on the same server. The technique is detailed by Hector Cruz in this thread on the NHibernate forum. The trick is to specify the schema you are addressing in each mapping file. Because the schema name simply becomes a table prefix, you can also use it to specify cross database joins. So long as you follow good practice and have one mapping file per entity, it means that, in theory, each entity could be persisted to a different database. I've put together a little project to show this working using Northwind. You can download the code here:

http://static.mikehadlow.com/Mike.NHibernate.Multiple.zip

I took a backup of Northwind and then restored it to a new database so that I had a Northwind and a Northwind2. I'm going to get the Product entity from the Products table on Northwind and the Supplier from the Suppliers table in Northwind2. The great thing is that you only need a single connection string pointing to one database (in my case the original Northwind).

Here's the NHibernate configuration:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler,NHibernate" />
  </configSections>
  <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
    <session-factory>
      <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
      <property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
      <property name="connection.connection_string">Data Source=.\sqlexpress;Initial Catalog=Northwind;Integrated Security=True</property>
      <property name="dialect">NHibernate.Dialect.MsSql2005Dialect</property>
      <property name="show_sql">true</property>
      <mapping assembly="Mike.NHibernate.Multiple"/>
    </session-factory>
  </hibernate-configuration>
</configuration>

Nothing special here. I've just nominated the original Northwind database to be my  initial catalogue. Next I've got two entities, Product and Supplier:

namespace Mike.NHibernate.Multiple
{
    public class Product : Entity
    {
        public virtual string ProductName { get; set; }
        public virtual Supplier Supplier { get; set; }
    }
}
namespace Mike.NHibernate.Multiple
{
    public class Supplier : Entity
    {
        public virtual string CompanyName { get; set; }
    }
}

Once again pure persistence ignorance. You don't have to do anything special with your entities. Now, here's the trick: The mapping file for the Product entity specifies the schema as Northwind.dbo:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" schema="Northwind.dbo" >
  <class name="Mike.NHibernate.Multiple.Product, Mike.NHibernate.Multiple" table="Products">
    <id name="Id" column="ProductID" type="Int32">
      <generator class="identity" />
    </id>
    <property name="ProductName" />
    <many-to-one name="Supplier" class="Mike.NHibernate.Multiple.Supplier, Mike.NHibernate.Multiple" column="SupplierID" />
    
  </class>
</hibernate-mapping>

While the Supplier mapping file specifies Northwind2.dbo:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" schema="Northwind2.dbo" >
  <class name="Mike.NHibernate.Multiple.Supplier, Mike.NHibernate.Multiple" table="Suppliers">
    <id name="Id" column="SupplierID" type="Int32">
      <generator class="identity" />
    </id>
    <property name="CompanyName" />
  </class>
</hibernate-mapping>

Now, hey-presto! When I run this little console program to retrieve a Product from NHibernate, I get an object graph back with Product entities from Northwind and Supplier entities from Northwind2.

using System;
using NHibernate.Cfg;
namespace Mike.NHibernate.Multiple
{
    class Program
    {
        static void Main()
        {
            var configuration = new Configuration();
            configuration.Configure();
            var sessionFactory = configuration.BuildSessionFactory();
            var session = sessionFactory.OpenSession();
            var product = session.Load<Product>(1);
            Console.WriteLine("Product: {0}, Supplier: {1}", product.ProductName, product.Supplier.CompanyName);
        }
    }
}

NHibernate generates this SQL:

 

NHibernate: 
SELECT 
 product0_.ProductID as ProductID0_0_, 
 product0_.ProductName as ProductN2_0_0_, 
 product0_.SupplierID as SupplierID0_0_ 
FROM Northwind.dbo.Products product0_ 
WHERE product0_.ProductID=@p0; @p0 = '1'
NHibernate: 
SELECT 
 supplier0_.SupplierID as SupplierID1_0_, 
 supplier0_.CompanyName as CompanyN2_1_0_ 
FROM Northwind2.dbo.Suppliers supplier0_ 
WHERE supplier0_.SupplierID=@p0; @p0 = '1'

As you can see the Product was retrieved from Northwind and the Supplier from Northwind2. It's similar to when you do cross database joins in a stored procedure. The stored procedure has to live in a particular database, but because each table gets prefixed with it's database name the DBMS simply looks up the table in the referenced database.

Note that this trick is simply to deal with a legacy situation that I can't do much about. You really don't want to architect a system like this from scratch.

Thursday, October 16, 2008

MVC Framework beta released

The MVC Framework beta was released yesterday. You can download it here:

http://www.microsoft.com/downloads/details.aspx?familyid=a24d1e00-cd35-4f66-baa0-2362bdde0766&displaylang=en&tm

A couple of things to note:

  1. It puts the MVC assemblies in the GAC when you run the installer. This means that they won't be copied to your bin directory by default. Beware of this if you are expecting to deploy your application to a non-MVC installed server.
  2. It doesn't include the 'futures' assembly Microsoft.Web.Mvc. You have to download this separately from the Codeplex project site.
  3. There are a number of other small (some breaking changes) so be sure to checkout the release notes.

Thanks to Steve Mason for this. It's been a very quiet release.

Wednesday, October 15, 2008

Using ServiceLocator in an MVC Framework application

A couple of weeks ago I blogged about the common service locator that had just been announced by the Microsoft Patterns and Practices team. I've just updated our current application to use it. Here is what you need to do:

First, download the the binaries from codeplex. You can also get the code and build it yourself. You then need to add the Microsoft.Practices.ServiceLocation assembly to your project.

ServiceLocation

If, like me, you're using Castle Windsor you should download the code for the Castle Windsor Adaptor. This includes a file called WindsorServiceLocator.cs that you can add to your project. If you are using another IoC container, check the main page of the common service locator codeplex project for the relevant adaptor.

In your application startup code (Global.asax.cs Application_Start() for examle), register the WindsorServiceLocator with the ServiceLocator:

ServiceLocator.SetLocatorProvider(() => new WindsorServiceLocator(container));

You can now grab components from any place in your application using the ServiceLocator static Current property:

var contextService = ServiceLocator.Current.GetInstance<IContextService>();

And there's no direct dependency on Windsor so you can swap in StructureMap, AutoFac, Unity or whatever when the mood takes you.

Now there's a big big caveat with this. You should really only use the static service locator when dependency injection (DI) is not an option. In an MVC Framework application you will typically provide a ControllerFactory that gets controller instances from the IoC container. Every further dependency in the object graph that handles the request should be provided by DI. Even when you need to defer service location, the abstract factory pattern is a better choice than ServiceLocator.

The next thing that needs to happen is for ServiceLocator to be fully leveraged by the MVC Framework code. At the moment you have to use the MvcContrib WindsorControllerFactory, or write your own controller factory. It would be very nice if simply calling SetLocationProvider did the same thing.

Thursday, October 09, 2008

Using Google Maps with the MVC Framework

For the last week or so I've been having a lot fun at work adding some Google map functionality to our application. It's an internal application for my clients, so I can't show you any of the code, but I've put together a little demo to demonstrate some of the techniques. The core message here is that it's easy. Taking a some geographic information from your model and putting some markers on a google map with a bit of interactive functionality is really only a matter of a few lines of code. You can download a zip file of the solution here:

http://static.mikehadlow.com/Mike.GoogleMaps.zip

Here's the application. It's pretty simple, it just displays a map with a collection of locations. For fun I've made it show some of the places I've been lucky enough to live in during my fun-packed life :) Each location has a latitude, longitude, name and an image. You can click on the marker for a location and its name is displayed in a speech bubble (known as an 'info window' by Google) and it's image is displayed to the right of the map.

googleMapDemo

I build the map object graph in memory with a simple 'repository' (this is just a demo, your repository would normally talk to a database).

using Mike.GoogleMaps.Models;
namespace Mike.GoogleMaps.Repositories
{
    public class MapRepository
    {
        public Map GetById(int id)
        {
            return new Map
                   {
                       Name = "Places Mike has lived",
                       Zoom = 1,
                       LatLng = new LatLng { Latitude = 0.0, Longitude = 0.0, },
                       Locations =
                           {
                               new Location
                               {
                                   Name = "St. Julians, Sevenoaks, UK",
                                   LatLng = new LatLng { Latitude = 51.25136, Longitude = 0.21992 },
                                   Image = "st_julians.jpg"
                               },
                               new Location
                               {
                                   Name = "Kadasali, Gujerat, India",
                                   LatLng = new LatLng { Latitude = 21.235142, Longitude = 71.4462 },
                                   Image = "india.jpg"
                               },
                               // ...
                           }
                   };
        }
    }
}

Next we have a controller action that returns the object graph serialized as JSON:

public ActionResult Map()
{
    var mapRepository = new MapRepository();
    var map = mapRepository.GetById(1);
    return Json(map);
}

On the home controller's index view we have some simple HTML that has div placeholders for the content. One for the map name, another for the map itself and two more for the dynamic location name and image. Please forgive the inline CSS :(

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /><title>
 Mike's Google Maps Demo
</title><link href="Content/Site.css" rel="stylesheet" type="text/css" />
    <script src="../../Content/jquery-1.2.6.min.js" type="text/javascript"></script>
    <script src="http://www.google.com/jsapi?key="<key>" type="text/javascript"></script>
    <script src="../../Scripts/LocationsMap.js" type="text/javascript" ></script>
</head>
<body>
    <div class="page">
...
  <h2 id="mapName"></h2>
  <div id="map" style="width : 700px; height : 400px; margin : 0px; padding : 
   0px; float : left; margin-right:20px;"></div>
     
  <p id="info"></p>
  <img id="image" src="" />
  <div style="clear:both;"></div>
...
    </div>
</body>
</html>

Note that this is the HTML as rendered and is a combination of the master view and the home controller's index view. Also note the script references for the Google maps API, jQuery and the LocationMap.js script which controls the page.

jQuery makes writing Javascript a dream. I am a Javascript novice, but I found it blissfully easy to write this code. Here's the javascript which does all the work:

google.load("maps", "2");
// make a json request to get the map data from the Map action
$(function() {
    if (google.maps.BrowserIsCompatible()) {
        $.getJSON("/Home/Map", initialise);
    }
});
function initialise(mapData) {
    $("#mapName").text(mapData.Name);
    // create the map
    var map = new google.maps.Map2($("#map")[0]);
    map.addControl(new google.maps.SmallMapControl());
    map.addControl(new google.maps.MapTypeControl());
    map.setMapType(G_SATELLITE_MAP);
    var latlng = new google.maps.LatLng(mapData.LatLng.Latitude, mapData.LatLng.Longitude);
    var zoom = mapData.Zoom;
    map.setCenter(latlng, zoom);
    // set the marker for each location
    $.each(mapData.Locations, function(i, location) {
        setupLocationMarker(map, location);
    });
}
function setupLocationMarker(map, location) {
    // create a marker
    var latlng = new google.maps.LatLng(location.LatLng.Latitude, location.LatLng.Longitude);
    var marker = new google.maps.Marker(latlng);
    map.addOverlay(marker);
    // add a marker click event
    google.maps.Event.addListener(marker, "click", function(latlng) {
        
        // show the name and image on the page
        $("#info").text(location.Name);
        $("#image")[0].src = "../../Content/" + location.Image;
        
        // open the info window with the location name
        map.openInfoWindow(latlng, $("<p></p>").text(location.Name)[0]);
    });    
    
}

When the page loads we make an ajax request 'getJSON' to the HomeController's Map action listed above. When the call completes, it fires the callback function 'initialise'. This creates the map and binds it to the map div. We set the centre of the map to the map object's LatLng and the zoom level to the map's Zoom value.

Next we iterate (using jQuery's $.each()) through the locations and call setupLocationMarker for each one. This creates a new Marker object for each location and adds it to the map. It also adds a click event handler to each marker to set the name and image url, and popup the info window.

Simple and easy. I've been really impressed by the power of jQuery. It's very good news that Microsoft have adopted it. With the Firebug plugin for Firefox doing javascript development is a real pleasure.  As for the Google maps API, it is nicely conceived and has excellent documentation.

So what's stopping you? Get mapping!