Simple Instagram WordPress Widget, pt 2

This is the second half of a two-part article about building a simple WordPress Instagram Widget. You may want to read Part 1 if you haven’t done so yet.

Recap

In Part 1, we covered how to extend the WordPress WP_Widget class to create a new widget in your theme’s functions.php. We dove into how to capture pieces of data in the Widget settings on the back end, and make sure they get stored & updated properly.

In this article, we’ll take a look at how to take that data that was output on the front end & make a call to the Instagram API to retrieve our photos.

The front end display of the widget

To review, here was the function we wrote to display the widget:

/* Front end display of widget */
public function widget( $args, $instance ) { /* 1 */
    $title = apply_filters( 'instagram_widget', $instance['title'] ); /* 2 */
    $subtitle = apply_filters( 'instagram_widget', $instance['subtitle'] );
    $key = apply_filters( 'instagram_widget', $instance['key'] );
    $user = apply_filters( 'instagram_widget', $instance['user'] );
    $count = apply_filters( 'instagram_widget', $instance['count'] );

    echo $args['before_widget']; /* 3 */
    if ( ! empty( $title ) ) {
        echo $args['before_title'] . $title . $args['after_title'];
    }
    if ( ! empty( $subtitle ) ) {
        echo $args['before_content'] . $subtitle . $args['after_content'];
    }
    if ( ! empty( $key ) && ! empty( $user ) && ! empty( $count )  ) { /* 4 */
        
        echo $args['before_content'];
        
        /* ajax-y Goodness Goes Here */

        echo $args['after_content'];
    }       
    echo $args['after_widget'];
}

What we need to do now is make a call to the Instagram servers, and insert the data we get back between echo $args['before_content'] and echo $args['after_content'].

AJAX to the rescue

While we could probably retrieve the Instagram photos in PHP using something like cURL, I opted for JavaScript instead. The reason for this is simply that I’m more comfortable working in JavaScript, and using jQuery.ajax() is a breeze. And since these images are of secondary importance, I’d rather render the page as quickly as possible and then load them in asynchronously.

The basic structure of our AJAX call will look something like this:

var $widgetcontent = jQuery('.widget_instagram_widget .widgetcontent:last-child');

jQuery.ajax({
    url: 'https://instagram.com/something',
    dataType: 'jsonp'
})
    .done(function(json) {
        // do stuff
    })
    .fail(function(jqXHR) {
        console.error(jqXHR);
        $widgetcontent.append('<p>' + 'Sorry, but we are unable to retrieve images from Instagram at this time.' + '</p>');
    });

If you’ve used jQuery.ajax() before, this should look pretty familiar: We make an AJAX call to the Instagram URL, using JSONP since it’s a cross-domain request. Then we setup our .done() and .fail() handlers to manage the data we get back.

If the request fails, we simply log the errors to the console and display a message to users.

If it succeeds, we’ll need to loop through the JSON returned from Instagram, and retrieve our image, link, & caption.

The Instagram API

This is where the Instagram documentation comes in handy. The first step is to register our application to get a client ID; then we need to figure out our user ID. Fortunately, someone has put together a nifty tool that will look up a user ID based on their Instagram username.

With both IDs in hand, we’re ready to start testing the API. Instagram provides an API console that will let you test various requests and preview the response from the server. I usually opt for a Chrome extension like postman rest client or advanced rest client, because they will store a history of my requests for future reference.

A quick review of the Instagram endpoints documentation tells me that the endpoint we’re looking for is “get user’s recent media“, which is formatted something like this:

https://api.instagram.com/v1/users/{user-id}/media/recent/?client_id=YOUR-CLIENT_ID

If we include the “count” parameter mentioned in the docs to specify the number of recent photos to retrieve, we get a URL structure like this:

https://api.instagram.com/v1/users/{user-id}/media/recent/?client_id=YOUR-CLIENT_ID&count=IMAGE_COUNT

After making a GET request to this endpoint with my user & client ID, I get a 200 OK response with a bunch of JSON. There’s a ton of information in the response, including likes & comments for each post. But all I’m looking for in this case is the image URL, link to the post, and caption. Based on the structure of the JSON, it looks like I can get the info for each image like this:

data.images.low_resolution.url
data.link
data.caption.text

Displaying our Instagram image

So, following the logic above, here’s how we can build out our callback function:

    .done(function(json) {
        for (var i = 0; i < json.data.length; i++) {
            var image = json.data[i].images.low_resolution.url;
            var link = json.data[i].link;
            var caption = (json.data[i].caption !== null) ? json.data[i].caption.text : 'Instagram Image';
            $widgetcontent.append(
                '<a href=\"' + link + '\" title=\"' + caption + '\" class=\"instagram\">' +
                '<img src=\"' + image + '\" />' + 
                '</a>'
            );
        }
    })

The response from Instagram is automatically passed as an argument to the function, so we just loop through that data, assign variables to each of the pieces of data, and then append some HTML to the page. (We cached $widgetcontent earlier, using the selector .widget_instagram_widget .widgetcontent:last-child).

Important to note here is that we're checking whether data.caption exists before using its text as our link's title tag, and assigning a generic "Instagram Image" title if it doesn't exist. This is critical because otherwise the JS will fail as soon as it encounters an image without a caption.

Displaying the Widget

To display the widget, we'll just wrap everything in a function. Here's the full JavaScript (all of which gets plopped in our theme's JS file):

jQuery(document).ready(function($) {
    function getInstagram(id, user, num) {
        var url = 'https://api.instagram.com/v1/users/' + user + '/media/recent?client_id=' + id + '&count=' + num;
        var $widgetcontent = jQuery('.widget_instagram_widget .widgetcontent:last-child');

        jQuery.ajax({
            url: url,
            dataType: 'jsonp'
        })
            .done(function(json) {
                for (var i = 0; i < json.data.length; i++) {
                    var image = json.data[i].images.low_resolution.url;
                    var link = json.data[i].link;
                    var caption = (json.data[i].caption !== null) ? json.data[i].caption.text : 'Instagram Image';
                    $widgetcontent.append(
                        '<a href=\"' + link + '\" title=\"' + caption + '\" class=\"instagram\">' +
                        '<img src=\"' + image + '\" />' + 
                        '</a>'
                    );
                }        
            })
            .fail(function(jqXHR) {
                console.error(jqXHR);
                $widgetcontent.append('<p>' + 'Sorry, but we are unable to retrieve images from Instagram at this time.' + '</p>');
            });
    }

    // Retrieve back-end data from hidden input fields
    var $key = jQuery('#insta-key');
    var $user = jQuery('#insta-user');
    var $count = jQuery('#insta-count');

    // Fire away!
    getInstagram($key.val(), $user.val(), $count.val());
});

Wrapping Up

And with that, we have a working Instagram WordPress widget! There's a lot more that could be done to improve and optimize this implementation, but for now I'm going to leave it as-is. (Update: I've posted the full source to Github if you want to check it out).

Further Reading

Anything I'm missing? If you've seen otherful helpful articles on this topic, I'd love to link them up here! Just hit me up on Twitter: @lukeelmers