Adding Weather to Your Site with jQuery and YQL

If you're working on a travel, news or other locally-oriented site, adding a display of the current weather conditions in the local area is the perfect touch. In the past, this was a relatively difficult task, involving server-side integration with weather APIs and more. Today, however, jQuery and YQL (a free web service offered by Yahoo) can be used to easily add a customized weather display to your site. In this article I'll guide you through the process from start to finish.

Adding Weather to Your Site with jQuery and YQL


I'll show you two possible looks for the finished widget. The first uses Yahoo Weather's icons, and is purely academic (unless you receive permission to use the icons from Yahoo):

Weather 1

The second look uses the public icons specified in the Yahoo Weather data feeds:

Weather 2

In both cases, hovering over the icon displays the text for the condition - e.g. Fair or Sunny or Scattered Showers.

Tutorial Details

  • Technologies: HTML, CSS, JavaScript, jQuery, YQL
  • Difficulty: Intermediate
  • Estimated Completion Time: 30-45 minutes

Introduction - The Power of YQL

In years gone by, using webservice data in a client-side web application required a proxy on a server (due to the AJAX cross-domain loading restrictions imposed by browsers), and often translation of data into JSON format so it would work easily with JavaScript. Today, however, there's a much simpler option thanks to Yahoo's free YQL (Yahoo! Query Language) service. What exactly is YQL? The rather broad definition given on the YQL site is:

The Yahoo! Query Language is an expressive SQL-like language that lets you query, filter, and join data across Web services. With YQL, apps run faster with fewer lines of code and a smaller network footprint.

Let's put legs on that: YQL acts like a giant free proxy with powerful built-in data amalgamation tools. It can be used to easily integrate data from across the entire web into your client-side JavaScript applications. In other words, it's a way to make really cool stuff while saving tons of time and work. If you aren't familiar with YQL, you definitely should explore the YQL site. I won't be going into much technical detail about using YQL in this tutorial; instead, I'll just show what's needed for the specific tasks we have.

Using YQL To Get Weather Conditions Data

Now let's look at the case at hand - getting weather data. One of the best sources of free weather data is Yahoo Weather's RSS feeds. However, this data can't be loaded via standard AJAX because it would require cross-domain loading. In the old days, we would have had to start setting up a proxy on our server. Today, though, we can use some YQL magic. Because YQL has the weather data built-in, we don't even have to build any URL-based queries. We can use a simple query like this to retrieve the current weather and forecast for Manhattan:

SELECT * FROM weather.forecast WHERE location='10001'

To see this in action, go here and use the Test button. Note that you can choose the output format - XML or JSON.

In this tutorial, though, we don't need the forecast. Here's the query we can use to get just the current weather:

SELECT item.condition FROM weather.forecast WHERE location='10001'

To see this in action, go here and use the Test button. Here's an example of the output we receive:

mycallback({
 "query": {
  "count": 1,
  "created": "2011-06-23T19:54:07Z",
  "lang": "en-US",
  "results": {
   "channel": {
    "item": {
     "condition": {
      "code": "26",
      "date": "Thu, 23 Jun 2011 3:09 pm EDT",
      "temp": "70",
      "text": "Cloudy"
     }
    }
   }
  }
 }
});

In the query.results.channel.item.condition object, we find several items. First, code specifies the icon associated with the current conditions. The date property indicates the last update time. Finally, temp and text describe the current temperature and conditions respectively.

If you want the temperature in metric units, you can use this query:

SELECT item.condition FROM weather.forecast WHERE location='10001' AND u='c'

All of this sample code is great, but you probably want data for a location other than Manhattan. If you want to retrieve a US-based location, all you need to do is replace 10001 in the location parameter with the ZIP code of the desired location. If you want an international location, it's a little more tricky.

For these locations, you'll need to determine a special location code that uses the format CCRR0000, where the CC represents the country code, the RR represents the region code (just XX for most countries), and the 0000 represents the station ID. For example, the location code for Paris, France is FRXX0076. The easiest way to find an international location code is by going to Weather.com, searching for the location, going to its local weather page and noting the URL. Suppose you searched for Madrid, Spain. The URL in your browser for the local weather page looks like this: http://www.weather.com/weather/today/SPXX0050.

Note the code at the end - SPXX0050. This is the location code we need. So, the condition query for Madrid's weather in metric units would be:

SELECT item.condition FROM weather.forecast WHERE location='SPXX0050' AND u='c'

Now that you understand where the data comes from and the query needed to get it, we're ready to start writing code.

Getting Started with Code

You'll need an HTML file to follow along with the tutorial. You can place it anywhere on your computer, and no testing server is needed; all code will be client-side. To simplify the tutorial, we'll use inline CSS and JavaScript, but you're welcome to use external .js or .css files if desired.

Tip: Want to experiment without cluttering up your computer with lots of files? For this type of tutorial, it can be helpful to use jsBin or jsFiddle to follow along. These free online tools let you experiment with HTML, JavaScript and CSS without having to create any files on your computer. Best of all, you can save, version and share your experiments.

If you're starting with a blank HTML file, here's the code you should have to start:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
 
  <title>Weather Widget</title>
  <style type="text/css">
  /* CSS will go here */
  
  </style>
</head>
<body>

  <!-- Markup will go here -->

  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
  <script type="text/javascript">
  // JavaScript will go here
 
  </script>
</body>
</html>

Note the designated places for markup, CSS and JavaScript. Additionally, note that we have included jQuery 1.6.1 from Google's CDN.

Adding the HTML Markup

To create the widget as shown above, we're going to have a dedicated wrapper div with several span elements within - one for the location text, one for the icon, and one for the temperature. Add the following markup to the body of your page:

<div id="wxWrap">
    <span id="wxIntro">
        Currently in Manhattan, NY: 
    </span>
    <span id="wxIcon2"></span>
    <span id="wxTemp"></span>
</div>

Obviously, the Currently in Manhattan, NY text is optional, and can be customized as desired. Note the icon holder span:

<span id="wxIcon2"></span> 

As mentioned before, there are two possible looks for the widget based on which icon set you use. In order to make it easy to experiment with both looks, we'll write the code so that you can switch between them by simply changing the id on the icon span from wxIcon2 to wxIcon. Using wxIcon2 will cause the freely-usable icons to be used; using wxIcon will cause the Yahoo Weather icons to be used (just for experimenting). Here's what the line should look like if you're experimenting:

<span id="wxIcon"></span> 

Note that we're not creating a place to display the condition text (e.g. Sunny or Thunderstorm). This is because the icon will be configured to display that text in a tooltip on hover. However, if you want to display that text separately so it's always visible, you could create a separate span and configure the JavaScript later on to fill it.

Adding the CSS Styling

Now we're ready to add some styling. There are several points worthy of note:

  • The #wxWrap div will use progressive-enhancement CSS3 techniques to display rounded corners and a background gradient in browsers that support these styles. Older browsers will render square corners and a solid gray background.
  • To simplify vertical positioning, we use the inline-block display mode for each of the inner spans.
  • For the experimental icon holder #wxIcon, we are using the technique called CSS sprites. This means that all of the various weather icons are combined into one larger image. When displaying one of these icons, we use CSS background properties to display only the relevant small portion of that larger image. This technique means that only one image has to be loaded (versus many tiny individual files), thereby speeding up the page.

Add the following CSS to the style tag in the head of the page:

#wxWrap {
    width: 350px;
    background: #EEE; /* Old browsers */
    background: -moz-linear-gradient(top, rgba(240,240,240,1) 0%, rgba(224,224,224,1) 100%); /* FF3.6+ */
    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(240,240,240,1)), color-stop(100%,rgba(224,224,224,1))); /* Chrome,Safari4+ */
    background: -webkit-linear-gradient(top, rgba(240,240,240,1) 0%,rgba(224,224,224,1) 100%); /* Chrome10+,Safari5.1+ */
    background: -o-linear-gradient(top, rgba(240,240,240,1) 0%,rgba(224,224,224,1) 100%); /* Opera11.10+ */
    background: -ms-linear-gradient(top, rgba(240,240,240,1) 0%,rgba(224,224,224,1) 100%); /* IE10+ */
    filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f0f0f0', endColorstr='#e0e0e0',GradientType=0 ); /* IE6-9 */
    background: linear-gradient(top, rgba(240,240,240,1) 0%,rgba(224,224,224,1) 100%); /* W3C */
    padding: 2px 13px 2px 11px;
    -webkit-border-radius: 4px;
    -moz-border-radius: 4px;
    border-radius: 4px;
}
#wxIntro {
    display: inline-block;
    font: 14px/20px Arial,Verdana,sans-serif;
    color: #666;
    vertical-align: top;
    padding-top: 9px;
}
#wxIcon {
    display: inline-block;
    width: 61px;
    height: 34px;
    margin: 2px 0 -1px 1px;
    overflow: hidden;
    background: url('http://l.yimg.com/a/lib/ywc/img/wicons.png') no-repeat 61px 0;
}
#wxIcon2 {
    display: inline-block;
    width: 34px;
    height: 34px;
    margin: 1px 6px 0 8px;
    overflow: hidden;
}
#wxTemp {
    display: inline-block;
    font: 20px/28px Arial,Verdana,sans-serif;
    color: #333;
    vertical-align: top;
    padding-top: 5px;
    margin-left: 0;
}

The large list of background styles in the #wxWrap declaration (used to create the background gradient) were generated with the help of a CSS3 gradient generator. Also, note in the #wxIcon declaration that we make the background sprite image hidden at first (by positioning it off the edge); it will be positioned appropriately, revealing the relevant icon, once data has been loaded.

Most of the other styles are simply cosmetic, and can be customized if desired. If you're using the experimental icons, you can use almost any background color because the icons are in PNG format and have a transparent background*. If you're using the standard icons, though, you'll want to keep a light-colored background because they are in GIF format and have a white matte.

* We don't worry about IE6's lack of compatibility for the transparent PNGs, since those icons are only for experimenting and even Microsoft has recommended abandoning IE6. If IE6 compatibility is critical for your needs, you can add a workaround.

Making Things Work with JavaScript + jQuery

Now we need to start adding JavaScript to make the widget functional. For simplicity, I'll show the entire chunk of needed JavaScript first, then break it down bit by bit.

Add the following chunk of JavaScript code where denoted within the script tag at the bottom of the page:

$(function(){

    // Specify the ZIP/location code and units (f or c)
    var loc = '10001'; // or e.g. SPXX0050
    var u = 'f';

    var query = "SELECT item.condition FROM weather.forecast WHERE location='" + loc + "' AND u='" + u + "'";
    var cacheBuster = Math.floor((new Date().getTime()) / 1200 / 1000);
    var url = 'http://query.yahooapis.com/v1/public/yql?q=' + encodeURIComponent(query) + '&format=json&_nocache=' + cacheBuster;

    window['wxCallback'] = function(data) {
        var info = data.query.results.channel.item.condition;
        $('#wxIcon').css({
            backgroundPosition: '-' + (61 * info.code) + 'px 0'
        }).attr({
            title: info.text
        });
        $('#wxIcon2').append('<img src="http://l.yimg.com/a/i/us/we/52/' + info.code + '.gif" width="34" height="34" title="' + info.text + '" />');
        $('#wxTemp').html(info.temp + '&deg;' + (u.toUpperCase()));
    };

    $.ajax({
        url: url,
        dataType: 'jsonp',
        cache: true,
        jsonpCallback: 'wxCallback'
    });
    
});

If you preview your page now, you should see the working widget in all of its sunny (or rainy) glory. If you update the markup by replacing

<span id="wxIcon2"></span> 

with the alternative

<span id="wxIcon"></span> 

as described above, you should also be able to see the experimental icons. Now, I'll go into detail on what all of the code is actually doing.

JavaScript Breakdown - Overview

In summary, here's what the above chunk does:

  • Everything is wrapped in the jQuery document.ready handler, so it executes as soon as the page is ready for manipulation.
  • First, the location and units are specified. These are used to then build a YQL query.
  • We take the query text and create a request URL for executing it on the YQL server.
  • We define a callback function to handle the data once it loads. This function updates the icons, condition text and temperature text.
  • Finally, we actually load the data via the jQuery.ajax function. We use the special jsonp (JSON+callback) format, which is required to work around cross-domain restrictions.

JavaScript Breakdown - Details

First up, the location and units are specified: Specify the ZIP/location code and units (f or c) var loc = '10001'; or e.g. SPXX0050 var u = 'f';

Naturally, you'll want to update the loc variable with whatever location you want to use. As mentioned earlier, this should be a ZIP code within the US, or an international location code for other countries. The u variable specifies units - either Farenheit (via the default f) or Celcius (via c).

Now we have everything needed to build the YQL query:

var query = "SELECT item.condition FROM weather.forecast WHERE location='" + loc + "' AND u='" + u + "'";

The next step is to create the YQL request URL to execute that query. Here's the basic format of a public YQL request URL:

http://query.yahooapis.com/v1/public/yql?q=URL_ENCODED_QUERY_HERE&format=json

Note the place for the URL-encoded query. Additionally, note that we have format=json appended on the end. This tells the YQL server to return the response in JSON format, allowing for easy use with JavaScript. YQL can also return results in XML format for situations where that format is more useful.

Handling Caching

There's still one more important factor that must be considered in building our YQL request - caching. Normally when making AJAX requests via jQuery, a special URL parameter is automatically appended to the request, passing either a random value or the current time. This ensures that the browser always makes a "fresh" request to the server. In our case, though, this isn't what we want. We only want a "fresh" request to be made every 20 minutes or so, since most stations only report data once an hour. We want the browser to request fresh information, but not unnecessarily-fresh information.

When you're building your own proxy, you have to worry about a complex setup to enable caching of requests. In our case, you'd have to write a script that only retrieves the weather from some service once every e.g. 20 minutes, and otherwise returns the most recently cached data. Thanks to YQL, though, we don't have to worry about all of that. One of YQL's best features is that it will automatically cache requests that have identical URLs. Moreover, requests that take advantage of cached data don't have any request limits, meaning that properly-implemented caching usage can essentially remove all usage limits, period.

In order to take advantage of these YQL features, we want to create a URL that only changes periodically. For this example, I've chosen 20 minutes. We take the current time (via new Date().getTime()), then divide it by 1000 (to move from milliseconds to seconds), then divide it by 1200 (to move from seconds to 20-minute increments). Finally, we round it. The end value, thus, only changes once every 20 minutes. We take that value and then append it on the end of the request URL via a made-up _nocache parameter. Later on, we'll disable jQuery's automatic cache-busting as well, by passing cache:true to the jQuery.ajax call. This ensures that a unique request will only be made once every 20 minutes.

var cacheBuster = Math.floor((new Date().getTime()) / 1200 / 1000);
var url = 'http://query.yahooapis.com/v1/public/yql?q=' + encodeURIComponent(query) + '&format=json&_nocache=' + cacheBuster;

Defining the Load Handler

Next up, we create the callback that will be executed once the weather data has loaded. For most AJAX requests, we would just define an anonymous function inline with the AJAX request. However, the problem is that jQuery would actually do the following with such a configuration:

  • jQuery would create a randomly-named global function, e.g. jQuery_callback_12345789
  • This newly-created global function would call the real handler we passed
  • Our request URL would be automatically updated so that it specified the new global function as the callback, via ...&callback=jQuery_callback_123456789

Usually this is helpful. But in our case, it would mean that every request has a unique URL, because the callback parameter would always be random; and that would disable the automatic YQL caching. So, to avoid this, we have to manually create a global function then pass its name via the jsonpCallback parameter in the jQuery.ajax call.

Here's the load handler code:

window['wxCallback'] = function(data) {
    var info = data.query.results.channel.item.condition;
    $('#wxIcon').css({
        backgroundPosition: '-' + (61 * info.code) + 'px 0'
    }).attr({
        title: info.text
    });
    $('#wxIcon2').append('<img src="http://l.yimg.com/a/i/us/we/52/' + info.code + '.gif" width="34" height="34" title="' + info.text + '" />');
    $('#wxTemp').html(info.temp + '&deg;' + (u.toUpperCase()));
};

First, we reference the actual conditions object via data.query.results.channel.item.condition. This value is determined from the example JSON response included at the start of the tutorial. Then, we update the "experimental" icon if it exists. We update the background position using the numeric info.code value, so that only the relevant icon shows out of the larger sprite image. Additionally, we use info.text to set the title property on the span so that hovering over the icon will show e.g. Sunny or Cloudy.

Likewise, the "standard" icon holder is updated as well, by appending the specified icon image. The title attribute is set on the photo, so hovering will display the condition text in a tooltip.

Finally, we use the info.temp property to update the temperature display. I also appended a degree sign and the unit (°F or °C), but that's optional.

Making the Actual AJAX Request

Now that we have the request URL and the handler ready, we can make the actual AJAX request:

$.ajax({
    url: url,
    dataType: 'jsonp',
    cache: true,
    jsonpCallback: 'wxCallback'
});

Because we're loading JSON, we'd usually use $.getJSON instead of $.ajax. However, as mentioned above, we're going to override some of the default jQuery AJAX behaviors, so we need to use the lower-level $.ajax method. All four parameters are important:

  • url obviously specifies the URL we want to load from. We use the special YQL request URL that was determined earlier.
  • dataType is set to jsonp, so jQuery knows we're loading in the JSON+callback format.
  • cache is enabled, so that jQuery won't add its own cache-busting code. As mentioned above, we're relying on the special cache buster value we appended to the URL manually.
  • jsonpCallback is used to specify wxCallback, the name of the global load handler function we just created. Again, this ensures that jQuery doesn't try to help by creating a random function name that breaks caching.

And that's it! You have a fully-functional widget that displays the current weather conditions for any location in the world, all while leveraging the advantages of YQL's built-in caching.

Summary

In this tutorial, you've learned how jQuery and YQL can be used together to build powerful client-side web applications that consume all types of data. In this tutorial we specifically explored weather data, but there are numerous other possibilities - explore the data tables listed in the YQL console for more inspiration. Additionally, you've learned the needed tricks to take advantage of YQL's powerful built-in caching features.

Thanks to your new knowledge, your website visitors will be able to enjoy the latest weather data on your site. Best of all, it only took a few lines of code and is highly flexible. Possible modifications and enhancements include displaying the condition text (e.g. Sunny or Cloudy), or displaying the forecast in addition to the current conditions. Have fun with your new skills!