Instagram Web App Tutorial Part 1

| Comments

This post will document my attempt at creating a simple, single page web app, allowing a visitor to search Instagram for photos by tag. You can see the working app here.

This app was developed on a webkit browser (Google Chrome). View the code at github.

Getting started, I visit Instagram’s API documentation to learn how I can search for photos by tag. Registration is required, so I follow the instructions under GETTING STARTED.

I find the relevant API end point here. It takes the form:

1
GET /tags/{tag-name}/media/recent

It responds with a list of recently tagged photos in JSON. The API supports JSONP - things are looking good.

Docs provide an example request:

1
https://api.instagram.com/v1/tags/snow/media/recent?client_id=CLIENT-ID

Replacing CLIENT-ID with my own, I paste the URL into a browser to see what the JSON object looks like. I could copy and paste the response into a file to use as sample data for developing against, but I can’t be bothered right now. Perhaps later.

I’m ready to start getting stuff down, so I create the necessary directories and files for my project:

1
2
3
omar:~ admin$ mkdir -p instagram/{images,javascripts,stylesheets}
omar:~ admin$ cd instagram
omar:~ admin$ touch index.html javascripts/application.js stylesheets/application.css

I add some basic markup to index.html. I’ll be using jQuery, so I load it in the document’s head:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html>
<head>

  <script src='https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js' type='text/javascript' charset='utf-8'></script>
  <script src='javascripts/application.js' type='text/javascript' charset='utf-8'></script>
  <link rel='stylesheet' href='stylesheets/application.css' type='text/css' media='screen'>

</head>
<body>

  <div id='photos-wrap'>
  </div>

</body>
</html>

I open up appliation.js and start writing code. So as not to pollute the global namespace, I’ll wrap my code in an anonymous function, exposing only a single method, search, to the global namespace (I love minimalist APIs). The method will take a string (a photo tag) as its only parameter.

1
2
3
4
5
6
7
8
var Instagram = {};

(function(){

 Instagram.search = search;
})();

Instagram.search('cats');

I load up index.html on Google Chrome so I can start debugging. With ‘Developer Tools’ enabled and the ‘console’ panel selected, I see:

1
Uncaught ReferenceError: search is not defined

So, I define it:

1
2
3
4
5
6
7
8
9
10
(function(){

  function search(tag){
    console.log(tag);
  }

  Instagram.search = search;
})();

Instagram.search('cats');

Reloading the page, I see the error has gone… I’m making progress.

Going to flesh out my function, search. I’ll use jQuery’s getJSON function for loading JSON-encoded data from Instagram using a GET HTTP request. See documentation here.

At a minimum, the method takes a request URL. I also pass it a success handler, so I can manage the JSON encoded data which is sent back after a successful request.

1
2
3
function search(tag){
  $.getJSON(url, toScreen);
}

Both url and toScreen need to be defined:

1
2
3
4
5
6
7
8
function toScreen(data){
  console.log(data);
}

function search(tag){
  var url = "https://api.instagram.com/v1/tags/" + tag + "/media/recent?callback=?&amp;client_id=XXXXXXXXX"
  $.getJSON(url, toScreen);
}

Refreshing Chrome I see the returned JSON object printed to the JavaScript console. Using the console I drill down into the returned object. I see the photos are contained within an array called data:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- Object
  - data: Array[20]
    - 0: Object
      + caption: Object
      + comments: Object
        created_time: "1334402906"
        filter: "Nashville"
        id: "169306311223447303_5913362"
      - images: Object
        + low_resolution: Object
        - standard_resolution: Object
          height: 612
          url: "http://distilleryimage7.instagram.com/f3f8d7b2862411e19dc71231380fe523_7.jpg"
          width: 612
        + thumbnail: Object
        + likes: Object
          link: "http://instagr.am/p/JZfzFqtI8H/"
          location: Object
          tags: Array[1]
          type: "image"
          user: Object

(-) collapsed object. (+) expanded object.

I update toScreen to iterate through the data object, appending each photo to the browser:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function toScreen(photos){

  // Using jQuery's generic iterator function:
  // jQuery.each http://api.jquery.com/jQuery.each/

  $.each(photos.data, function(index, photo){

    // I'll construct the image tag on the fly.
    // The images property contains objects representing images of
    // varying quality. I'll give low_resulution a try.

    photo = "<img src='"+ photo.images.low_resolution.url + "' />";

    $('div#photos-wrap').append(photo);
  });
}

Refreshing the browser, I see 20 cat photos loading… so much cuteness on one page is hard to manage.

Now’s a good time to introduce a little more markup so I can start applying some styling.

I jot down how I’d like each photo to be represented as HTML. Attribute values which will be different for each photo I wrap with curly braces:

1
2
3
4
5
<div class='photo'>
  <a href='{photo_url}' target='_blank'><img class='main' src='{photo}' width='250' height='250' /></a>
  <img class='avatar' width='40' height='40' src='{avatar_url}' />
  <span class='heart'><strong>{like_count}</strong></span>
</div>

I now insert this markup into the $.each iterator, replacing the curly braced attribute markers with the relevant datum:

1
2
3
4
5
6
7
8
9
10
11
$.each(photos.data, function(index, photo){
  photo = "<div class='photo'>" +
    "<a href='"+ photo.link +"' target='_blank'>"+
      "<img class='main' src='" + photo.images.low_resolution.url + "' width='250' height='250' />" +
    "</a>" +
    "<img class='avatar' width='40' height='40' src='"+photo.user.profile_picture+"' />" +
    "<span class='heart'><strong>"+photo.likes.count+"</strong></span>" +
  "</div>";

  $('div#photos-wrap').append(photo);
});

Refreshing the browser I see this works… but my toScreen function now looks forsaken. All that string concatenation inside the jQuery.each callback makes me angry/thirsty/feverish.

The HTML markup deserves to have it’s own object, where it can safely exist, far from the dangerous machinery operating inside toScreen.

So…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// Instantiate empty objects.
var Instagram = {};
Instagram.Template = {};

// HTML markup goes here, please!
Instagram.Template.Views = {

  "photo": "<div class='photo'>" +
            "<a href='{url}' target='_blank'><img class='main' src='{photo_url}' width='250' height='250' /></a>" +
            "<img class='avatar_url' width='40' height='40' src='{avatar}' />" +
            "<span class='heart'><strong>{like_count}</strong></span>" +
          "</div>"
};

(function(){

  function toScreen(photos){
    $.each(photos.data, function(index, photo){

      // Undefined function toTemplate, takes
      // the photo object and returns markup
      // ready for display.

      photo = toTemplate(photo);
      $('div#photos-wrap').append(photo);
    });
  }

  function search(tag){
    var url = "https://api.instagram.com/v1/tags/" + tag + "/media/recent?callback=?&client_id=a307c0d0dada4b77b974766d71b72e0e";
    $.getJSON(url, toScreen);
  }


  Instagram.search = search;
})();

Instagram.search('cats');

Refreshing the browser I get Uncaught ReferenceError: toTemplate is not defined. I’ll fix that…

1
2
3
4
5
6
7
8
9
10
function toTemplate(photo){
  photo = {
    like_count: photo.likes.count,
    avatar_url: photo.user.profile_picture,
    photo_url: photo.images.low_resolution.url,
    url: photo.link
  };

  return Instagram.Template.generate('photo', Instgram.Teamplate.Views['photo']);
}

Instagram.Template.generate is undefined. I plan to create a function which finds strings matching property names in a passed object, replacing them with the corresponding values.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// A simple (it does the job) function for template parsing.
Instagram.Template.generate = function(template, photo){

  // Define variable for regular expression.
  var re;

  // Fetch template.
  template = Instagram.Template.Views[template];

  // Loop through the passed photo object.
  for(var attribute in photo){

    // Generate a regular expression.
    re = new RegExp("{" + attribute + "}","g");

    // Apply the regular expression instance with 'replace'.
    template = template.replace(re, photo[attribute]);
  }

  return template;
};

I refresh the browser; things are coming together. Time to introduce the search form:

1
2
3
4
5
6
7
8
9
10
11
12
<body>
  <div id='photos-wrap'>
    <form id='search'>
      <button type='submit' id='search-button' tabindex='2'>
        <span class='button-content'>Search</span>
      </button>
      <div class='search-wrap'>
        <input class='search-tag' type='text' tabindex='1' />
      </div>
    </form>
  </div>
</body>

I’ll bind an event handler to the form button, so I can invoke my search method whenever the form is submitted:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$(function(){

  // Bind an event handler to the `click` event on the form's button
  $('form#search button').click(function(){
    // Extract the value of the search input text field.
    var tag = $('input.search-tag').val();

    // Invoke `search`, passing `tag`.
    Instagram.search(tag);
  });

  // Start with a search on cats. Humanity loves cat pictures, right?
  Instagram.search('cats');
});

The end of my first iteration is drawing near. I still need to slap on some makeup, which I’ll do shortly. First, I want to revisit some code I’m not too happy with:

1
2
3
4
5
6
7
8
9
function toScreen(photos){
  ....
  // Appending new photos to the bottom of 'body' - new stuff isn't visible
  // unless you scroll down the page. I want to 'prepend'!

  - $('div#photos-wrap').append(photo);
  + $('div#photos-wrap').prepend(photo);
  ....
}

Introducing an object for holding configuration data, and a new function for concatenating the request URL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
+ Instagram.Config = {
+  clientID: "XXXXXXXXXXXXX";,
+  apiHost: "https://api.instagram.com";
+ };

  (function(){
  ...

+   function generateUrl(tag){
+     var config = Instagram.config;
+     return config.apiHost + "/v1/tags/" + tag + "/media/recent?callback=?&amp;client_id=" + config.clientID;
+   }


    function search(tag){
-     var url = "https://api.instagram.com/v1/tags/" + tag + "/media/recent?callback=?&amp;client_id=a307c0d0dada4b77b974766d71b72e0e";
-     $.getJSON(url, toScreen);
+     $.getJSON(generateUrl(tag), toScreen);
    }

  ...
  })();

Calling $.prepend for every photo is wasteful. Why not concatenate each photo string, prepending the final result.

1
2
3
4
5
6
7
8
9
  function toScreen(photos){
+   var html = "";
    $.each(photos.data, function(index, photo){
-     photo = toTemplate(photo);
-     $("div#photos-wrap").prepend(photo);
+     html += toTemplate(photo);
    });
+   $("div#photos-wrap").prepend(html);
  }

Ok. I’ve slapped on some makeup - see stylesheets/application.css, and I think we’re good for a first go.

In another iteration it might be worth implementing the following features:

  • Error handling (gracefully deal with invalid tags, API server down…).
  • Bookmarking searches, for future reference.
  • Marking favourite photos for quick retrieval at a later date.
  • Add pagination.
  • Displaying more photo data (username, comments).

Comments