Dynamic Weather with NOAA, PHP, jQuery, and HTML templates

One of our aims with our new alumnae/i site was to give alums who may be far away a chance to reconnect with the campus they know and love. Of course we have the usual imagery, stories, social networking, etc. but there was one feature—weather—that could evoke the moment, in real-time on campus.

In order to make that connection as immediate and palpable as possible, we couldn’t allow the weather to cache, or only update on page load. All it took was some jQuery and HTML templates added to our existing PHP script which was already pulling an XML feed from NOAA. Read on to see how we did it…

alums-weather

The Markup


<h3>Campus Weather</h3>
<div class="weather">
    <p class="weather-description">
        The <a href="http://forecast.weather.gov/MapClick.php?lat=41.7&#38;lon=-73.93">weather</a> at Vassar
    </p>
</div>

This is our default state. Without JavaScript support, we could display the weather with a PHP include, but that may only be accurate on page load. If the page is left open, instead of current weather, you’ll see what the weather was when the page loaded. And if the page is cached, you could be seeing weather from the last time you received a fresh page. To avoid this, we won’t actually show the weather in the absence of JavaScript—we’ll show a link to the current weather instead.

To handle devices with JavaScript support, we’ll need to add markup to our default state:


<h3>Campus Weather</h3>
<div class="weather">
    <!--
    <b class="weather-temperature"></b>
    <b class="weather-temperature-scale"></b>
    -->
    <p class="weather-description">
        The <a href="http://forecast.weather.gov/MapClick.php?lat=41.7&#38;lon=-73.93">weather</a> at Vassar
        <!--
        <b class="weather-condition"></b><br />
        <b class="weather-updated-label">updated: </b>
        <b class="weather-updated-data"></b>
        -->
    </p>
</div>

Two comment blocks have been added containing markup we’ll extract and populate with weather data using JavaScript—the commented blocks are innocuous so, without JS, they do no harm. My approach is a variation on this simple method of templating described by Nicholas Zakas which is similar to Handlebars but without the overhead. The goals here are having a solid base experience that is progressively enhanced and separating markup from JavaScript allowing for more maintainable code (see slides by Nicholas Zakas).

The JavaScript

The first thing we need to do is process those comment blocks which is mostly what setWeather() does:


function setWeather () {

    var domWeather = $('.weather'),
        domWeatherDescription = $('.weather-description'),
        template1 = getTemplateString (domWeather),
        template2 = getTemplateString (domWeatherDescription);

    domWeather.prepend(template1[0]);
    if (template2[0]) {
        domWeatherDescription.html(domWeatherDescription.html() +': '+ template2[0]);
    }

    loadWeather();
    // Check weather every 15 minutes
    setInterval(function() { loadWeather(); }, 900000);

}

First, we grab “weather” and “weather-description” and get what’s inside the comment blocks of each using getTemplateString()—this function searches an element for comment nodes, removes them from the DOM, and returns the nodes in an array. Then, add the markup returned from getTemplateString() to the elements they came from. The net result—comment tags removed. The markup now looks like this:


<h3>Campus Weather</h3>
<div class="weather">
    <b class="weather-temperature"></b>
    <b class="weather-temperature-scale"></b>
    <p class="weather-description">
        The <a href="http://forecast.weather.gov/MapClick.php?lat=41.7&#38;lon=-73.93">weather</a> at Vassar <b class="weather-condition"></b><br />
        <b class="weather-updated-label">updated: </b>
        <b class="weather-updated-data"></b>
    </p>
</div>

getTemplateString() for reference:


function getTemplateString(domElement) {

    var templateString = [];
    domElement.contents().each(function() {

        // if this is s comment node
        if (this.nodeType == 8) {
            templateString.push(this.nodeValue);
            $(this).remove();
        }

    });

    return templateString || '';

}

Continuing the execution of setWeather(), we call loadWeather() to retrieve the weather data, then plug it into the markup:


function loadWeather() {

    $.getScript("[your-path]/weather-string.js",
        function() { /* data, textStatus, jqxhr */
            $('.weather-temperature').html(temp + '°');
            $('.weather-condition').html(currentCondition);
            $('.weather-updated-data').html(dateTimeStamp);
    });

}

We call loadWeather() once so it fires on page load, then we call it every 15 minutes.

Why every 15 minutes?
The feed updates every hour but I would check more often than every 60 minutes. Even though the reading occurs every hour, updates may not be readable for about 20 minutes after and, depending on when a page is loaded in that cycle, you could possibly have weather that’s over two hours old.

Then, .getScript() (an AJAX shorthand) reads weather-string.js which is written by our PHP script (explained in “The PHP” section). Here’s the contents of weather-string.js for our example:


var dateTimeStamp = '7/20, 12:53 pm',
    currentTemperatureF = 63,
    currentCondition = 'fog/mist';

Once this file is read, it’s trivial to plug the JS variables in where they belong using .html(). Here’s what the finished markup looks like:


<h3>Campus Weather</h3>
<div class="weather">
    <b class="weather-temperature">63°</b>
    <b class="weather-temperature-scale">F</b>
    <p class="weather-description">
        The <a href="http://forecast.weather.gov/MapClick.php?lat=41.7&#38;lon=-73.93">weather</a> at Vassar <b class="weather-condition"></b>fog/mist<br />
        <b class="weather-updated-label">updated: </b>
        <b class="weather-updated-data">7/20, 12:53 pm</b>
    </p>
</div>

The PHP


/* Weather
-------------------------------------

NOAA possible weather condition values:

http://w1.weather.gov/xml/current_obs/weather.php

Page representing feed (has link to feed)

http://forecast.weather.gov/MapClick.php?lat=41.7&lon=-73.93

-------------------------------------*/

$xml = simplexml_load_file("http://forecast.weather.gov/MapClick.php?lat=41.7&lon=-73.93&unit=0&lg=english&FcstType=dwml");

foreach ($xml->data as $d) {

    if ($d['type'] == 'current observations') {
 
        $dateTime = date('n/j, g:i a',strtotime($d->{'time-layout'}->{'start-valid-time'}));
        
        foreach ($d->parameters->temperature as $t) {    
            if ($t['type'] == 'apparent') {
                $temperature = $t->value;
            }        
        }
		
        foreach ($d->parameters->weather->{'weather-conditions'} as $wc) {
            if ($wc['weather-summary']) {
                $weather = $wc['weather-summary'];
	    }
        }
    }
 
}
 

This script reads an XML feed from NOAA (find your local NOAA XML feed here by entering your zipcode, then clicking the XML link). There are only a few data points we care about, all under “current observations”:

start-valid-time: When current weather was measured ($dateTime)

temperature (apparent): Current temperature ($temperature)

weather-summary: Current weather condition ($weather)

We’ll run this script every hour using a cron job. Be sure to check how many minutes after the hour your feed is updated and have your cron job runs a few minutes after that.

Finally, we need to write weather-string.js:


writeFile(array(
    'filePath' => '/YOUR PATH HERE/weather-string.js',
    'fileContent' => 'var dateTimeStamp = \'' . $dateTime . '\', 
        currentTemperatureF = '. $temperature . ', 
        currentCondition = \'' . $weather . '\';',
);

function writeFile($parameters) {

    $filePath = ($parameters['filePath']) ? $parameters['filePath'] : '';
    $fileContent = ($parameters['fileContent'])? $parameters['fileContent'] : '';

    $handle = fopen($filePath,"w+");
    fwrite($handle, $fileContent);  

}

I could have just written the file but kept it as a function call in case there are other files to write. Remember, we already saw weather-string.js when it was read by loadWeather().

Conclusion

So that’s how we did it. There are a lot of moving parts (AJAX, HTML templates, jQuery, PHP, XML), but the end result is a great weather widget that’s always current, has a good fallback in case JS is not supported, and gives our alums a real connection to campus.

One comment

Leave a Reply

Your email address will not be published. Required fields are marked *