Desert Code Camp Sessions

Yesterday we had an awesome time at Desert Code Camp learning from each other and sharing ideas with each other. I got to present 3 sessions during the day, and here are the slides to each:
** NOTE: For some strange reason, my server wants to gzip zip files when it downloads them, leading to a double-compressed file. If you're downloading the zip files in IE, it'll tell you the file is corrupt. The fault is totally mine, but the file is not corrupt. If you download the file with FireFox, it will come down just fine. Since you'll need FireFox to use FireBug, you'll be just fine. **

Thinking in JavaScript: This is an awesome introduction to what makes JavaScript different from other C-Style languages such as C#, Java, C/C++, Perl, etc. There are a few differences you need to understand, and with those in mind, you can leverage your existing skills in this new world with ease.

Intro to jQuery: In this session, we look at the most common use of jQuery -- picking something, then doing something with each item selected. For example: $("p").css("background-color","#cccccc"); In that single line, we're able to search through the entire dom for all <p> tags, and set their background-color to gray. 1 line. That's awesome. We also briefly summarize some of the other stuff you get for your 26k now only 19k library script inclusion: AJAX, Animation, Traversing and modifying the dom, etc.

jQuery Deep Dive: Here we briefly reviewed the "pick something, then do something with each" techniques. Then we dive right in with AJAX: using GET and POST to retrieve content, sending parameters in the query string or as post parameters, techniques to call ASP.NET AJAX web services and page methods -- without a ScriptManager on the page, built-in jQuery animation, how use and build plugins, and how to interact with server-side controls and page life cycle. By the time we were done with all that, we were definitely ready for lunch.

Desert Code Camp is a great opportunity for coders of all skill levels and all technology backgrounds to share and learn.  I thank you for the opportunity to learn with you.  See you at the next Desert Code Camp.

"Command Prompt" in Windows Explorer Context Menu

There's a great power-toy for adding "Command Prompt" to the Windows Explorer context menu in XP here (or search for XP Power Toys). In Vista and Server 2008, you just hold shift when you right click to get it -- built in. But what if you're on Windows Server 2003 or another version? Well, the technique to add it is just a simple registry change:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Folder\shell\Command_Prompt]
@="Command Prompt"

[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Folder\shell\Command_Prompt\command]
@="cmd.exe /k pushd \"%1\""

Copy the above into a reg file (such as "Command Prompt Here.reg" -- no quotes) or similar, double-click to merge it, and you've got a Command Prompt selection in Windows Explorer's context menu. Easy as pie. If you want to "uninstall" it, open up RegEdit, navigate to that key, and delete it. Changes take effect imediately.

Note: some have reported this syntax brings up an error. If cmd.exe isn't in the PATH, this will utterly fail. In some cases I've had success with changing the command to be @="cmd.exe \"%1\"". In some cases, that utterly fails too. Your mileage may vary, batteries not included, don't use this product while sleeping on a curling iron or driving in traffic.

And to take it a step further, I modified the command name to "Command Prompt x86" and the command to "C:\Windows\SysWoW64\cmd.exe \"%1\"" and imported into a new key name, and I now have a 32-bit command prompt in the pop-up too.

(Now hopefully I won't forget it again.)

jQuery demo at SEVDNUG

It was a blast to present deeper concepts of jQuery at the SEVDNUG this evening. We enjoyed discussing ajax in jQuery, calling web services and page methods, building and using plugins, animation, and general best practices for JavaScript. The content from the demo can be found here. Note that because of a server misconfiguration, the file will come down double-zipped. Firefox seems to download the file just fine, while IE seems to choke every time.

For quick reference as well, here's the contents of the Resources page:

Tools

Learning and Reference Material

jQuery Plugins

FAQ

IoC: What is it and why do I care?

It finally sank into my melon why Dependency Injection, Inversion of Control, and IoC Containers exist. The 'a-ha' moment came, and I wanted to share it. Here's my "explain it to your mom" definition of IoC Containers, and the back-story to make it make sense. First, we start with bugs ...

Bugs: These are when a program malfunctions. They aren't good. An abundance of bugs means the program is broken, and users are grumpy.

Trying to solve bugs more proactively leads us to ...

Unit Testing: Automated testing of software leads to catching and solving bugs faster. Unit testing specifically involves testing a small piece of the program in isolation -- away from all other parts of the program. More generally, the term "testing" is used to mean testing the application, its parts, its functions, interaction between parts, access to external resources (a database, a web service, etc). Testing code frequently and automatically leads to better code because bugs are exposed by tests, not by users.

Unit Testing highlights inconsistent object initialization. When I create a new transaction, I need to set the date, set the customer, set the product, and verify all these exist. That seems to fundamentally go against the nature of Object Oriented development: Polymorphism, Inheritance, and Encapsulation. The other two aren't important to this discussion, but Encapsulation is.

Encapsulation: Encapsulation is hiding the grunt of the task behind a simpler skin. "Yes, I know to wash the car, you need to get it wet, rub soap on it, scrub, rinse, dry, wax, etc. I just want to call Car.Wash() and you figure out the details." This is encapsulation.

There are many ways to encapsulate awkward code, but for this discussion we'll discuss encapsulating object creation. Encapsulation of object initialization is solved via ...

Factory Pattern: In the Factory Pattern, object initialization is done in a central place for each type of object. When you want a Car object, you call the CarFactory, pass it parameters, and it returns a fully initialized car. Initialization is done centrally in the Factory method, so object initialization is always uniform and complete.

During testing, you have to test lots of permutations of sequential behaviors that may depend on external resources that are slow. For example, consider a test to see if a recent transaction query only grabs transactions within 7 days. To test this, you need some transactions older than 7 days ago and some newer than 7 days ago. If your TransactionFactory is coded to create all transactions as "right now", you can't exactly wait around 7 days for your unit test to finish. In a less obscure example, say you are trying to verify a calculation, but the database query used to populate the calculation is slow. What you need is the ability to alter these dependencies during a test. This leads to ...

Dependency Injection: Dependency Injection is the process of telling an object what its dependencies are. If you're creating transactions on a variety of dates, and the Factory marks every new transaction as "today", you may want to specify what "today" means to the Factory during a test. In the real program, "today" will always mean "right now". But during the test, "today" needs to match the scenario we're testing to make sure the program functions correctly.

Dependency Injection is also really handy if the unit you're testing depends on other things which are slow -- for example a database or a web service. If these things take seconds or minutes to return the requested resource, a single test takes at least as long. If we have many such tests -- or groups or suites of these tests -- the tests will be said to "take too long to run", so they won't be run often. A test that isn't run doesn't find bugs, thus doesn't serve its purpose. This dilemma leads us to ...

Mocking: Creating a mock (fake) object that behaves in a predictable way is a great way to "fake-out" an external resource with a faster "work-alike" counterpart. If the GeoLocation service takes too long to return, and we've proven (through another test) that it always returns the same answer when we ask for a specific location, we may not want to flex that resource to calculate the distance between a customer and a delivery location. Instead, we can create a mock web service that always answers correctly instantaneously. This mock object stands in for the real, expensive resource during a test. The mock object is injected into place via Dependency Injection.

When you've got mock objects in the mix, the Factory's object initialization parameters can get pretty intense. Not only do I need to specify the make & model of the car I want built, I also need to specify which database you get it from (the real one or the mock object), maybe what "today" means for initializing the waranty, and maybe I also need to specify other external resources such as the GeoLocation service strategy for how to find the car. This initialization puts a heavy burden on the other parts of the program that use this code. Mocking via Dependency Injection and hiding details of execution through Encapsulation seem to be fundamentally at odds, which leads us to ...

IoC Container: Inversion of Control (IoC) -- is a methodology of injecting dependencies through a centralized Factory for all objects in the application. The IoC container typically has a method like Resolve() or Create() or Get(). You pass it details of what you want, it makes an object in its centralized factory, and hands it back. The benefit to being centralized is not only does it know how to make your object, but it knows how to make many objects. If your object requires external dependencies, it can find and create those, and inject them in as it creates your object.  You need not worry about these details.

The IoC Container is an uber object Factory that centrally creates all objects, and recursively resolves Dependency Injection requirements of those objects while encapsulating this complexity from consumers of these objects.

If you're in the middle of a unit test, and you need to adjust what "today" means, you configure the IoC Container, and all objects that need it get it. But the code that wants the object doesn't need to know any of this. It doesn't need to know if it's in the middle of a test or not. It doesn't even need to know if the database is real or the data its working on is real.  It just says to the IoC container, "Give me an object that is ..." and poof, there it is.

IoC Container methodology is awesome, as it empowers:
  • consistent initialization through a central factory
  • dependency injection to allow mock objects to stand in for computationally expensive objects during tests
  • consuming classes need not resolve dependencies to use objects
IoC is awesome. It is hardly the magic bullet though. The nature of the IoC Container is it must know how to resolve all the initialization of all the objects in the application. To do this effectively, it must be configured to know all the dependencies it needs to inject, to know which object to return when asked for an interface, how to instantiate and initialize everything, and all this in a generic, maintainable way. This configuration is key. You're specifying, "When you're asked for a 'car' that is 'red', go create it like this, initialize its dependencies like this, etc." Beefy IoC container libraries will allow you to return the Singleton instance of an object created previously, a new object, a cached object (if it exists), etc.

Traditionally, IoC configuration comes in two ways. I haven't found any compelling reason to choose one over the other as both are not that great: create the map in code or in a configuration file.

Code is great as you get compile time support for it. If you change the name of a class, the IoC configuration breaks until you also change it. (Or the refactor tool does it for you.) But on the other hand, it takes a recompile to change your mind.

A configuration file is easily changed, but it's fragile. Generally, you have to specify the full object namespace and type name, maybe even the library name and/or path it came from. And all this without intellisense, compile-time checking, or even spell checking.

So, that's what IoC is. It isn't the magic bullet, but it can sure make unit testing much more effective. To sum it up, my 'a-ha' "to my mom" definition of IoC is:

The IoC Container is an uber object Factory that centrally creates all objects, and recursively resolves Dependency Injection requirements of those objects while encapsulating this complexity from consumers of these objects.

(no, my mom didn't get it either.)

Rob

iPhone and Google Maps

I've gotten quite a bit more feedback than I anticipated about my prior post about Google Maps and the iPhone. Who would've thought that not being able to mash up live GPS data, maps, and one or more external files of geographic data in a generic, consumer-driven way would be such a stir?

Since then, I've made some progress on this rant.
  • LocWidget: this handy iPhone app allows you to send Latitude, Longitude, and Altitude measurements as query parameters to a website of your choosing. It also takes it a step further, sending the phone's unique id. (Great for those dispatch apps we are barred from creating.) The great part: it gets the data to a website in a way that any site developer can consume. The bad part: you gotta keep launching the app to get anything done.
  • Alocola: this is another handy iPhone app that registers the url prefix "alcola://" in iPhone's Safari. The great part: now the site can request the location, the user is prompted to agree, and the GPS data is passed to the url as query string parameters. In theory, this url could be triggered by a JavaScript timer. The bad part: the user has to click ok every time, and it closed and reopened safari as it sped through the app. I'm not positive, but I'd think it would open a new browser window on each run. In short order, if called periodically from a web app, you'd max out the 6 safari tabs. And not to mention you're reloading the page on each round trip through GPS land. The author has made the source available under GPL2 license, so in theory one could cache the "yes, do this" the way other apps do, register a more natural prefix (like gps://), and other nicities.
These are both great steps forward, but don't fundamentally solve the problem at hand. What I want is:
  1. Per domain permanent acceptance to use location services like I can per app now
  2. Live GPS data and external KML can mix in a central, standard place, without changing applications
I see this could get solved easily in one of two ways. Either
  • Provide a field in the Google Maps app for "url of a KML file" (or maybe access to Google's "My Maps")
Or
  • Provide a JavaScript function in Safari to get location data
I realize the GPS Browser functionality is to come with HTML 5. Will HTML 5 become spec before or after 802.11n gets ratified?
Yeah, I know, dream on. Flash isn't coming to the iPhone in this millennia. Neither is copy & paste. Or even a system-wide "switch to landscape" keyboard for those of us with hands bigger than my kindergartner. Next I'll dream of having Mono or Silverlight on the iPhone. Or wilder still, it prompts me to allow location services on each request, but there's no firewall to block a game from uploading my high score, date & time, and possibly my phone number or phone guid to it's service on every game-over. ("I'm really thrilled I just got a high score, but I don't need to publish to all the world my phone data so you can bombard me with even more targeted ads while I play, thank you very much.")

But if you're watching, Steve, and if it isn't too much to ask, can you give me the ability to do real time GPS / KML mashups? Either in the Google Maps iPhone app or in Safari is fine. And I'll forget I even mentioned those silly terms like "Flash" or ".NET" or "copy & paste".

Rob

"Oh duh": Google Maps and iPhone

Google Maps is awesome.  It is phenomenally cool.  It is the quintessential definition to many of what is Web 2.0: a very dynamic, very interactive, very usable site that presents what I want in a way I want it right now, lets me use it intuitively, and publish it to anywhere else easily.  Some may argue Google Maps is the reason cell phones now have GPS's in them: for turn-by-turn directions based on map data downloaded to the phone on demand.

Google Maps is also the quintessential example of a mashup: take two unrelated services, push them together, and you've got something incredibly new and cool.  Take presidential election results and geolocate voter preferences.  Take restaurant reviews and show me how close I am to them.  Take driving directions to the next level by overlaying speed trap markers.  It is awesome.  And the Google Maps API makes it drop-dead simple to do this in JavaScript, static images (with really long urls), or Flash.  I'm a happy camper.

In the other corner is the iPhone.  It is an awesome piece of engineering and elegance.  It is the new standard for what a phone should do.  It's touch screen interface is the standard by which all PDAs are judged going forward.  It's simple user navigation is awesome.  It's "always within reach" browser is awsesome.  (Sometimes when I'm upstairs and want to quickly look something up, I won't bother going all the way downstairs to the computer.  I'll pop out the iPhone and get on the net.)  Stop by the App Store or Cydia and download just about any kind of app from "keep my kids busy while I just quickly finish up ..." to "are there any wireless networks open here?" to "lemmie quickly RDP / VNC into that machine over there and ..."  And all that "in your pocket", instantly available anywhere, at the push of a button is awesome.

The iPhone takes Google Maps to the next level too: you can search for a restaurant name or genre, pan around the map, and see things close to you.  You can pick a contact from your address book, and get directions from "where I am right now" (thanks GPS) to their street address.  As you're driving, you can watch the little blue blinking ball dance accross the map in unison to your driving.  It's incredibly marvelous engineering feats behind the scenes, and incredibly simple to use.  Cudos to both Google and Apple.  Awesome.

Here's the rub: iPhone Google Maps mashups.  They don't exist.  They can't.  Google Maps app for the iPhone is crafted by Apple.  It is not JavaScript, not Flash, not static images with cryptic urls.  It's an app.  Ok, granted, there is an iPhone app to show me speed traps.  An iPhone app to geolocate stuff "around me" -- restuarants, theatres, etc.  I can download 1000 iPhone apps to map various things.  They're cool.  They're also seperate applications.  If I want to both see route information and see the speed traps on 1 map, I'm screwed ... unless the app developer implimented everything and it's dog.

The speed trap app is a great example.  I think they call it trapster.  I can see my current location, I can see speed traps identified by others using this service.  I can mark ones I find when I'm sitting by the side of the road with blinky lights in my rear view mirror.  It's like driving in a video game.  It's awesome.  But the developer didn't impliment screen jestures for navigating the map.  I can't pinch to zoom out or in, I can't swipe the screen sideways to move.  I have to use the little joystick controls on the bottom control bar.  And I definately can't put in my destination address and see the route of how to get there.  Or where the closest ATM is that supports my bank's card.  Or if any of my address book contacts live nearby.

Around Me is another awesome app that shows me restaurants, theatres, etc, around me.  (Not that I have time to use any of them, but that's another mater.)  But what if I want directions to there?  Sorry, that's in the other maps app.

"Ok," so I say to myself.  "I'm a 1/2 descent web user.  I'll just use the real Google Maps website for my mashups."  That's all fine and good except ... now I'm in app #3: Safari.  That has no Flash plugin, by the way.  And (in theory) any url that contains map information is automatically grabbed by the phone and routed to the Google Maps app.  And it doesn't have my GPS info, so I have to keep track of my current location by manually panning around as I drive.

I came to the awful conclusion at the end of this mental journey that the iPhone's Google Maps app is incredibly cool.  But it fundamentally defeats the purpose: today's internet is about mashups.  Combining data in interesting ways.  And I can't do that with any map data, a target destination, and my current location from my iPhone with Google Maps.  "Oops."

jQuery - LINQ to JavaScript

I had the privilege of presenting jQuery at Desert Code Camp today.  jQuery is the do more, write less JavaScript library.  Because it runs client-side, it can be used easily with any server framework.  There's nice support for intellisense in Visual Studio 2008 as well (with a bug-fix update).

jQuery basically breaks down into two operations: picking things, and doing something with those things.  Picking things is done via a CSS / XPath strign describing the DOM nodes you want to do.  This returns an array of DOM elements (wrapped nicely in a jQuery object) that get passed into each of jQuery's 'do it' functions.  It becomes trivial to change css on all items, change the innerHTML, append DOM elements, even do AJAX and visual effects.  What's best: this whole process usually takes about 20 characters -- very small, very clean, very nice.

The slides from the content can be found here.  We generated all the code we saw on the fly inside FireBug.  Enjoy!

Rob

Conditional Compile based on .net framework version

Ok, I've been after this one for quite a while. I want to include stuff like Moth.Linq and ExtensionAttribute in .net 2.0 compilations, but exclude them from .net 3.5 compilations. In effect, I want to cross compile to .net 2.0 and .net 3.5. Not only do I want to do this from within Visual Studio, I also want the Continuous Integration server to do it too. Tall order? Yes.

A bit of background:

- Moth.Linq is a mechanism for producing LINQ functionality in assemblies compiled in Visual Studio 2008 targeted to the .net 2.0 framework. You add this piece of code, add a 'using Moth.Linq;' at the top, and you can use LINQ. Very cool. (Disclaimer: this isn't LINQ to SQL or LINQ to Entities, just LINQ -- e.g. lamda expressions in SQL style syntax.)

- Daniel Moth has another awesome trick for using Extension Methods in .net 2.0 deployed assemblies. Since Extension Methods are a compiler trick, not a function of the language, at compile time, the method itself is put back into a static class, and the calls to that method are magically transformed into calls to that static class's method passing in the parameter that the extension method was attached to. None of this needs the magic System.Core.dll file that adds all the magic C# 3.0 sauce. Well, to make this compiler trick work requires the ExtensionAttribute inside the System.Runtime.CompilerServices namespace -- Inside System.Core.dll. What does it do? I have no idea. But create this attribute, and extension methods just work. Very cool.

- I use NAnt triggered by Cruise Control .NET. NAntContrib has a fabulous task called msbuild that you can pass a solution file to, and it'll build everything in the solution. Jeffrey Palermo has a great trick for enabling NAnt to build .net 3.5 projects. Combine these, and the standard NAnt build file that's been working forever now works with Visual Studio 2008 projects as well. Very cool.

- Some developers on the team have VS 2005, some have VS 2008. Some deployment servers run .net 2.0, some .net 3.5. So, the code just has to work in both scenarios. Those of us in VS 2005 hate it when those of us in VS 2008 use the cool C# 3.0 features. Those of us in VS 2008 hate holding back.

So, the task at hand is clear: create a way that NAnt and Visual Studio can conditionally include things like Moth.Linq and Extension Methods. The clear solution: pre-processor flags. I wrap the Extension Method code like so:
#if NET_20
namespace System.Runtime.CompilerServices {
public class ExtensionAttribute : Attribute { }
}
#endif
Now if the preprocessor flag is defined, it is compiled in. If not, it's not.

Well, that's all fine and good, but how to get the target .net framework to trigger defining the preprocessor flag? Sadly, I haven't found a good solution to that, but I did find this:

The idea is from Karsten: define preprocessor flags on the msbuild command line like so:
msbuild /p:DefineConstants=NET_20 /t:Rebuild
Ok, I'm 1/2 way there. NAnt's syntax for defining the command line args is like so:
<arg value="/p:DefineConstants=NET_20" />
I put that inside the <msbuild> task, insure I've defined the framework version NAnt is targeting like so:
<property name="nant.settings.currentframework" value="net-2.0" />
and away we go. This task now defines a perfect .net 2.0 build.

Want a .net 3.5 build? Set the property nant.settings.currentframework to "net-3.5" and don't set the arg in the msbuild task. I chose to put both "NET_20" and "net-2.0" into properties that I could set to "" and "net-3.5" based on a task passed into NAnt, and we're good. We now have Continuous Integration building .net 2.0 compatible code. Excelent.

How do we get the VS 2005 guys to do the same? Well, that seems a task for another day. My vote was to give them all VS 2008. We'll see how that goes over. :D My backup plan is defining project configurations for "Debug 2.0" and "Release 2.0" that define these. That's likely incredibly fragile though -- add a project and you tank that convention. Next in line is make them run NAnt to build, but getting out of Visual Studio to build seems very unintuitive. Make it a menu option in the external tools menu? That seems ripe for confusion for the new employees stepping onto the scene.

Well, in any event, 1/2 the problem is now solved. Wicked cool.

Rob

Thinking in JavaScript - A-ha!

I had the great honor of presenting 'Thinking in JavaScript' at the Java Users' Group last night. I love leading a discussion of learning, and this was a great example of it. I was even asked during conversation afterwards if I was a member of 'the club' [the Java Users' Group -- JUG]. Well, do I have to know Java to be part of the JUG? I guess not. What a fun and welcoming crowd.

Our premise was thus: you know object-oriented development. You know C-style syntax from either C, C++, Java, C#, or any of the derivatives. You're approaching JavaScript, and need to get into the mindset of how JavaScript works.

The majority of JavaScript is very much identical to all C-style languages: curly braces, semi-colons, objects, inheritance, debuggers, etc. But JavaScript is also different in a few ways we need to wrap our heads around. During the discussion, we identified 5 points that make JavaScript unique:
  • A JavaScript object is like a dictionary
    • Roughly like Dictionary<string, object>

  • A JavaScript method is an object
    • You can assign and retrieve them

  • All function parameters are optional
    • No function overloading, but no need

  • Constructors declare instance level variables
    • They aren't private

  • All JavaScript objects reference a prototype
    • The prototype defines common implementation

Each one of these points requires more explanation than just that -- thus the 2 hours we spent discussing it. But once you've wrapped your head around these few ideas, you can effectively code in JavaScript, because you'll know how JavaScript thinks. You'll be Thinking in JavaScript.

The slides for this evening's discussion can be found here. The source code for the evening was mostly pulled either from the slides themselves and copied into FireBug, or we made it up on the spot. We also discussed jQuery and referenced tutorials available from 15 days of jQuery.

I asked briefly in the midst of the demo if anyone would like to see more jQuery stuff.  Just about every hand went up, so I've signed up to present a jQuery session at Desert Code Camp in 2 weeks.  That promises to be a ton of fun too.

If you have any questions, thoughts, suggestions, gripes, or kudos about 'Thinking in JavaScript' or you'd like some assistance in JavaScript development efforts, don't hesitate to drop me a line.  The more we know about how to code effectively, the less spaghetti code there will be for us to clean up later...  :)

Rob

CSS A complete Journey at Java Users Group

I was asked on short-notice to fill in for the Java Users Group, and to present CSS. I have so much fun speaking, teaching, and learning with fellow developers that it was a no-brainer to say yes. However, what was so unique to me is I'm not a Java developer. I'm a C# / ASP.NET developer. It really emphasized to me one of the really cool truths of internet development: there are 1000 server-side technologies and web servers you can use to host your content, but at the end of the day, there are just 3 types of content that get rendered through the web to the client's browser: HTML, JavaScript, and CSS. (Granted, browser plugins allow for other things, but not natively.)

All your base are belong to Gates So, after the full disclosure disclaimer, we from opposite sides of the technical globe were able to discuss Web 2.0 very effectively.

I've presented "CSS - A complete journey" before, and it just keeps getting better and better. CSS has some quirks, but it's by no means hard. Once you understand how to reference CSS from the page, how to attach styles to elements, and a bit about order of precedence, everything else is just futzing with browser incompatibilities.

Here are the slides and sample code we used during our discussion on CSS. Included is the page from CSS Zen Garden that we adjusted using the Firefox Web Developer Toolbar plugin. Included as well is the Box Model discussion from Brain Jar's site. I wish I had created each of these brilliant works, but alas, credit is due to their authors.  Included as well is the positioning mini-site I built to show the effect of float, position, and overflow.  And as with HTML and JavaScript, this is a client technology.  You can view source on any page you can see, look through their CSS, and discover how their solutions work.  Thus every page on the internet can be a learning tool.

I look forward to next month's Java Users Group, where they've invited me back to discuss "Thinking in JavaScript".  We'll look at how we can leverage our knowledge of C-style syntax, OO principals, and html DOM to become instantly productive in JavaScript.  I can't wait.