Atomic Smash homepage splash

Building an API-driven weather widget for the WordPress Dashboard

Words by Anthony HartnellFebruary 25, 2019

When you log into the WordPress dashboard you will see a few default widgets that you can move around or hide. Let’s say you wanted to add your own widget to make this view a little more interesting. What would you build and how would you go about it?

This tutorial blog post will teach you how to create your own widget, retrieve data from an external API and add some styling to improve the way it looks. As you may know, I love working with APIs because they isolate the data from the view and by doing so, give you endless possibilities on how you can display it to the end user. For example, in a previous blog post, I explained how to retrieve and render Bristol city data onto a map. This could equally have been displayed in a table or a pie chart leaving the underlying data untouched.

Choosing an API

I thought it would be fun to create a simple weather widget that tells you the forecast for your city right there in the dashboard.

I googled around for “UK Weather API” and found Open Weather Map (https://openweathermap.org) which has really good documentation (very important for APIs) and clearly understandable results.

Their sample data is easy to read and simple which is just what I need. Here is a sample forecast for London, UK: Sample API data.

List of APIs for Open Weather Map API

Create an account to access these API endpoints.

Once you’ve signed up for an account, you will be able to access any endpoint by providing your key in the URL after appid= like so:

https://samples.openweathermap.org/data/2.5/weather?q=London,uk&appid=b6907d289e10d714a6e88b30761fae22

Retrieving the data from the API

I need to use a method to retrieve the API data in real-time and display it in the widget. For this, I’m going to use Guzzle.

What is Guzzle?

Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and trivial to integrate with web services.

Simply put, it is a PHP library for making and processing HTTP requests such as the one in the code example above.

We want to be able to download the data from the API into our widget. Often I would use Javascript to make an AJAX call to an API and render the data in JS (such as for map data) but the data needs to be in PHP so it can be rendered on the widget.

Guzzle makes this really straightforward as you only need to set up a few initial parameters, leaving it to do the rest.

Structuring the files

As usual, I am going to create a simple plugin and will start by creating the folders and files. I am going to call this “Weatherpress Dashboard”.

I’ve started by creating a folder in the plugins directory called weatherpress_dashboard and inside creating 2 files, one called composer.json and the other weatherpress_dashboard.php.

I will add the comments at the top of the php file to register the plugin and add a small bit of code to register and render the custom widget:

<?php
/*
Plugin Name: WeatherPress Dashboard
Plugin URI: https://github.com/WebAssembler/weatherpress-dashboard
Description: An API-driven weather widget for the dashboard
Version: 1.0
Author: Anthony Hartnell
Author URI: https://www.atomicsmash.co.uk/
Text Domain: wep
*/

// https://codex.wordpress.org/Function_Reference/wp_add_dashboard_widget
// Register the new dashboard widget with the 'wp_dashboard_setup' action
add_action( 'wp_dashboard_setup', 'wep_add_dashboard_widgets' );

function wep_add_dashboard_widgets() {

    wp_add_dashboard_widget(
        'wep_weather_widget',
        'Weather Press Dashboard',
        'wep_render_dashboard_widget_contents'
    );

}

// Outputs the content into the widget
function wep_render_dashboard_widget_contents() {
    echo '<h4>Weather data here</h4>';
}

Now, if you activate the plugin and go back to the dashboard, you should see the custom widget:

Image of a custom wordpress widget in the dashboard

Next, I will need to install Guzzle into the plugin, but instead of downloading it and saving it in the repo, I will use composer. Composer is a package manager for PHP which you will need to install first by following the tutorial at https://getcomposer.org/

I’ve added this into my composer.json file:

{
    "name": "WebAssembler/weatherpress-dashboard",
    "description": "Weather Press Dashboard - A custom weather widget for the WordPress Dashboard",
    "keywords": ["wordpress", "plugin", "apis", "weather"],
    "homepage": "https://www.atomicsmash.co.uk/",
    "authors": [
        {
            "name": "Anthony Hartnell",
            "email": "[email protected]",
            "homepage": "https://www.atomicsmash.co.uk/"
        }
    ],
    "type": "wordpress-plugin",
    "require": {
        "guzzlehttp/guzzle": "~6.0"
    }
}

Inside the plugin folder, I can run the command composer update --prefer-dist --ignore-platform-reqs to install guzzle. A folder called vendor will be created, with all the libraries Guzzle needs:

Composer vendor folder in Atom editor

Now, let’s load it in the plugin:

// Load in requirements from vendor folder
if( file_exists( __DIR__ . '/vendor/autoload.php' ) ){
    require 'vendor/autoload.php';
}

// Load the Guzzle Library
use GuzzleHttp\Client;

I’m now going to replace the dummy text in the widget with actual data from the weather API! ?

Guzzle lets you set a base URI and this is useful because an API will usually have many different endpoints, allowing you to assign different types of requests without repeating the base URL. Here is the code that does that:

function wep_render_dashboard_widget_contents() {

    $client = new Client( array(
        'base_uri' => "http://api.openweathermap.org/data/2.5/"
    ) );
    $response = $client->request('GET', 'weather', [
        'query' => [
            'q' => 'Bristol,uk',
            'APPID' => 'YOUR_API_KEY',
        ]
    ]);

    $payload = $response->getBody()->getContents();
    $weather_data = json_decode( $payload );

    echo '<pre>';
    print_r( $weather_data );
    echo '</pre>';

}

I am sending 2 query parameters with GET request- these will vary depending on the API spec. The docs at https://openweathermap.org/current explain how to format the URL and which query parameters to pass in. Now if you look at the widget you will see the output:

Data from API in the widget

Now I need to decide on the best way to display this data. I’d simply like to show an icon for the weather right now. Looking at the documentation, it appears that the “weather” object contains what I need. In my example, it’s an array that I will just need the first element for. As far as I can tell, that array fills up throughout the day as there are three items – Haze, Fog, and Mist- all three of which I’ve seen this morning. I will just get the top element in the array:

    if( ! empty( $weather_data->weather ) ) {

        $latest = $weather_data->weather[0];
        echo '<pre>';
        print_r( $latest );
        echo '</pre>';

    }

That outputs this:

The icon code you see there is an internal icon code used by openweathermap website. The weather conditions documentation page show you how to get the icon by providing the URL in the format of http://openweathermap.org/img/w/10d.png

    if( ! empty( $weather_data->weather ) ) {

        $latest = $weather_data->weather[0];
        $icon_url = 'http://openweathermap.org/img/w/' . $latest->icon . '.png';

        echo sprintf('<h2>Conditions: %s</h2>', $latest->main);
        echo sprintf('<img src="%s" width="%dpx">', $icon_url, 100);

    }

So far so good, but it needs a bit of adjustment to make it more visually appealing. Doing a quick google for “weather” displays the following format:

 

When you do a search for “weather” on Google, they have their own weather widget, defaulting to your city. I’ll try and show this by simply changing the output of the code and adding an additional query to get the data back in Celsius.

 

// Modified function
function wep_render_dashboard_widget_contents() {

    $client = new Client( array(
        'base_uri' => 'http://api.openweathermap.org/data/2.5/'
    ) );
    $response = $client->request('GET', 'weather', [
        'query' => [
            'q' => 'Bristol,uk',
            'units' => 'metric', // For units in celsius
            'APPID' => 'YOUR_API_KEY'
        ]
    ]);

    $payload = $response->getBody()->getContents();
    $weather_data = json_decode( $payload );

    echo sprintf('<h2>%s, UK</h2>', $weather_data->name); // City name
    echo sprintf('<h3>%s %s</h3>', date('l'), date('h:00'));   // Current day and time

    // Output the weather name and icon
    if( ! empty( $weather_data->weather ) ) {

        $current = $weather_data->weather[0];
        echo sprintf('<h3>%s</h3>', $current->main);   // Current weather name

        // Format and output the icon
        $icon_url = 'http://openweathermap.org/img/w/' . $current->icon . '.png';
        echo sprintf('<img src="%s" width="%dpx">', $icon_url, 100);

    }

    // Output the temperature
    echo sprintf('<h1>%d %s C</h1>', $weather_data->main->temp, '&#186;');

}

This is how it looks now:

Now, even though the icons are provided by the API, they are quite small and look a bit blurry when scaled up to this size. I’m going to choose a weather icon set from Icomoon so I can display a large, crisp icon and also include a simple stylesheet to make it stand out on the Dashboard. I’ve found Meteocons which is a free library designed by Alessio Atzeni

To load some CSS I’ve created a folder with a stylesheet in it and modified the main PHP file:

// Enqueue styles for the custom widget
add_action( 'admin_enqueue_scripts', 'wep_enqueue_dashboard_widget_styles' );
function wep_enqueue_dashboard_widget_styles() {
    wp_enqueue_style( 'wep_admin_style', plugin_dir_url( __FILE__ ) . 'css/admin.css', false, '1.0.0' );
}

I’ve added some simple CSS to change the colour of the h2 element:

Because I’ve registered the dashboard plugin with an ID of ‘wep_weather_widget’ (first argument of wp_add_dashboard_widget function), I can target this ID in CSS to make sure I’m only affecting the styles within my custom widget.

A quick refresh shows that it has changed the heading as expected:

Now I’m going to add some basic CSS to load the Icomoon icons as a font family inside admin.css

#wep_weather_widget @font-face {
    font-family: 'MeteoconsRegular';
    src: url('../icomoon/fonts/meteocons-webfont.eot');
    src: url('../icomoon/fonts/meteocons-webfont.eot?#iefix') format('embedded-opentype'),
         url('../icomoon/fonts/meteocons-webfont.woff') format('woff'),
         url('../icomoon/fonts/meteocons-webfont.ttf') format('truetype'),
         url('../icomoon/fonts/meteocons-webfont.svg#MeteoconsRegular') format('svg');
    font-weight: normal;
    font-style: normal;
}

I then just need to add the following snippet to admin enqueue scripts to include the icomoon fonts and a custom Google Font called “Rubik”. Here is the finished code block:

// Enqueue styles for the custom widget
add_action( 'admin_enqueue_scripts', 'wep_enqueue_dashboard_widget_styles' );
function wep_enqueue_dashboard_widget_styles() {
    wp_enqueue_style( 'wep_admin_style', plugin_dir_url( __FILE__ ) . 'css/admin.css', false, '1.0.0' );
    wp_enqueue_style( 'wep_icons', plugin_dir_url( __FILE__ ) . 'icomoon/style.css', false, '1.0.0' );
    wp_enqueue_style( 'wep_font', 'https://fonts.googleapis.com/css?family=Rubik:300,400', false, '1.0.0' );
}

Using the Icomoon web app, I was able to give each Meteocon a class before downloading it. This allowed me to easily translate the API icon classes such as ’03d’ to more readable class names such as ‘scattered-clouds’.  I’ve created a helper function in PHP to fetch the correct icon:

// Swap the api icon codes for the fonts loaded from Meteocons
function wep_get_class_from_icon( $icon_id ) {
    if( ! $icon_id ) return;
    $class = '';
    switch( $icon_id ) {
        case '01d':
            $class = 'sunny'; // full sun
            break;
        case '01n':
            $class = 'moon'; // full sun
            break;
        case '02d':
            $class = 'few-clouds'; // partly sunny
            break;
        case '02n':
            $class = 'few-clouds-night';
            break;
        case '03d':
            $class = 'scattered-clouds'; // cloudy
            break;
        case '04d':
            $class = 'broken-clouds';
            break;
        case '09d':
        case '09n':
            $class = 'shower-rain';
            break;
        case '10d':
            $class = 'rain-sun';
            break;
        case '10n':
            $class = 'rain-night';
            break;
        case '11d':
        case '11n':
            $class = 'thunderstorm';
            break;
        case '13d':
        case '13n':
            $class = 'snow';
            break;
        case '50d':
        case '50n':
            $class = 'haze'; // haze/mist
            break;
    }

    return $class;
}

I can then use it in the widget output as the following:

$icon_class = wep_get_class_from_icon( $current->icon );
echo '<div class="icon ' . $icon_class . '"></div>';

I have gone ahead and styled the widget and icon with CSS. This is the finished admin.css file:

#wep_weather_widget @font-face {
    font-family: 'MeteoconsRegular';
    src: url('../icomoon/fonts/meteocons-webfont.eot');
    src: url('../icomoon/fonts/meteocons-webfont.eot?#iefix') format('embedded-opentype'),
         url('../icomoon/fonts/meteocons-webfont.woff') format('woff'),
         url('../icomoon/fonts/meteocons-webfont.ttf') format('truetype'),
         url('../icomoon/fonts/meteocons-webfont.svg#MeteoconsRegular') format('svg');
    font-weight: normal;
    font-style: normal;
}

#wep_weather_widget .inside {
    font-family: 'Rubik', sans-serif;
    font-size: 1.3rem;
}

#wep_weather_widget .hndle {
    background: #9c70d2;
    color: white;
    font-weight: 300;
    font-size: 1rem;
}

#wep_weather_widget .toggle-indicator {
    color: white;
}

#wep_weather_widget .accordion-section-title:after,
#wep_weather_widget .handlediv,
#wep_weather_widget .item-edit {
    color: white;
}

#wep_weather_widget #icon_wrapper {
    display: inline-flex;
    width: 100%;
}
#wep_weather_widget .meta h2 {
    font-size: 2.4rem;
    padding-top: 40px;
    font-weight: 300;
}

#wep_weather_widget .meta h3 {
    font-size: 2rem;
    padding-top: 5px;
    font-weight: 300;
}


#wep_weather_widget .inside div.icon {
    margin-right: 20px;
}

#wep_weather_widget .inside div.icon:before {
    font-family: 'Icomoon';
    font-size: 180px;
    line-height: 220px;
    font-weight: 200;
}

/* Weather Icons */
#wep_weather_widget .inside div.icon.sunny:before {
    content: '\e928';
}

#wep_weather_widget .inside div.icon.moon:before {
    content: '\e921';
}

#wep_weather_widget .inside div.icon.few-clouds:before {
    content: '\e922';
}

#wep_weather_widget .inside div.icon.few-clouds-night:before {
    content: '\e921';
}

#wep_weather_widget .inside div.icon.scattered-clouds:before {
    content: '\e91c';
}

#wep_weather_widget .inside div.icon.broken-clouds:before {
    content: '\e911';
}

#wep_weather_widget .inside div.icon.shower-rain:before {
    content: '\e919';
}

#wep_weather_widget .inside div.icon.rain-sun:before {
    content: '\e918';
}

#wep_weather_widget .inside div.icon.rain-night:before {
    content: '\e908';
}

#wep_weather_widget .inside div.icon.thunderstorm:before {
    content: '\e91a';
}
#wep_weather_widget .inside div.icon.snow:before {
    content: '\e913';
}

#wep_weather_widget .inside div.icon.haze:before {
    content: '\e925';
}

And here is how the Widget looks now!

Image of styled api driven dashboard widget

Also, the final plugin folder looks like this:

In Summary

This tutorial shows how easy it can be to get Guzzle to work with WordPress, either in a theme or inside a plugin and there are numerous applications for it. You could extend this plugin by providing a simple admin interface to further customize the output of the widget, e.g. a text field that lets you set the base city.

The theme files used in this tutorial are available for free on my GitHub page:

https://github.com/WebAssembler/weatherpress-dashboard

Profile picture of Anthony Hartnell

Anthony HartnellDeveloper

Anthony works in the development team and enjoys creating plugins and integrating maps, libraries and other APIs into Wordpress.

Go back to top

Keep up to date with Atomic news