HOWTO: Searching Flickr

Flickr is well known both as a full-featured image storage system, and one of the web applications that got everyone talking about Web 2.0 and Ajax.

In the following section we'll make use of the powerful Flickr API to search for a list of photos that have a particular tag on them. We'll also demonstrate how to show and hide parts of the application based on its state, as well as how to animate the display when these states change.

(If you want to get the completed source code, it's available here.)

Tutorial reviewed 2006-12-06, by MB.

Screenshot of Flickr search form showing flowers as search results.

Getting Started

The first thing we need to do is create an empty XHTML document into which we can place the mark-up for the Flickr seach form. If you created the template at the beginning of the Introduction to XForms then go ahead and use that. Otherwise, create a new document in your editor and copy the template from here. Change the title to "Flickr Search Form", and save the file as flickr.html.

The finished form also requires at least version 1.4.2.1016 of formsPlayer, so if necessary, change the object tag, as follows:

   <head>
      <object width="0" height="0" id="formsPlayer"
         classid="CLSID:4D0ABA11-C5F0-4478-991A-375C4B648F58"
         codebase="http://skimstone.x-port.net/files/releases/formsPlayer-1.4.2.1016.cab#Version=1,4,2,1016"
      >
         <b>formsPlayer has not been installed.</b>
      </object>

We'll also create a separate CSS file to hold our style rules so add the following line to the head:

    <title>Flickr Search Form</title>
    <link rel="stylesheet" href="flickr.css" type="text/css" />
  </head>

And then create an empty file, called flickr.css. We're ready to go.

Creating the Form

The application is going to be made up of two parts. The first is a small form that will allow a user to enter a search term and then press a button to perform the search.

The second part will contain a repeating structure that will automatically take each photo element returned from the search, and uses it to create a reference to the actual image on the Flickr servers.

To create the search form, add the following to the body element:

  <body>
    <xf:group id="main-body">
      <xf:input ref="tags">
        <xf:label>Tags:</xf:label>
      </xf:input>
      <xf:submit>
        <xf:label>Find</xf:label>
      </xf:submit>
    </xf:group>
  </body>

This gives us a simple input control with a button next to it. We've placed the two controls inside a group mainly to make styling easier. Add the following style rules to your flickr.css file:

body
{
  font-family           : "Trebuchet MS", Verdana, Helvetica, Sans-Serif;
  font-size             : 11px;
}

#main-body
{
  margin                : 0;
  background-color      : #eee;
  border                : 5px solid #333;
  padding               : 15px;
  display               : block;
}

Save both of your files and load flickr.html in the browser, and you should have something like this:

Screenshot of the Flickr search form

Using the Flickr API

Flickr provides a number of different services for interacting with its data. If you look at the list of methods that the Flickr API supports, you'll see they are divided into groups; one group of API methods deals with photos, another with people, and so on.

Flickr can accept requests to these methods in a number of different formats, all of which we can use with XForms. The easiest to understand and test is REST, and to use it to make a call to any one of the methods listed we need to go through the following URI:

http://www.flickr.com/services/rest/

The parameters that you must pass are the method you want to call, and a special application key. The key must be obtained from Flickr, and although we have one for this tutorial, if you plan to develop the sample form further you should get your own key.

The particular method we will use for this form is flickr.photos.search, and you can see from the documentation that this method takes a number of parameters. Of the optional parameters, the only ones that we'll use are tags and per_page.

The tags parameter contains the tag or tags that the user of our form wants to search for, whilst per_page indicates how many items 'per page' we would like to be returned; since there may be tens of thousands of items that match a particular tag, Flickr will give the list to us one page at a time, and this parameter indicates how many items we would like on each of those pages.

Combining these two method-specific parameters with our general ones, and using the 'REST URL', we have a resulting call to the method that looks like this (the extra line breaks are to make it easier to read):

http://www.flickr.com/services/rest/?
  method=flickr.photos.search&
  api_key=68149024a667e0be3c63708f002ffe1e&
  tags=dogs&
  per_page=12

The results of such a call might be something like this (try it):

<rsp stat="ok">
  <photos page="1" pages="983" perpage="24" total="98265">
    <photo id="94048829" owner="54228211@N00" secret="9b2e52936b"
     server="37" title="These paws are made for walking - 5997"
     ispublic="1" isfriend="0" isfamily="0"
    />
    <photo id="94044468" owner="23819962@N00" secret="1641bc41c5"
     server="41" title="listen up dogs"
     ispublic="1" isfriend="0" isfamily="0"
    />
    .
    .
    .
  </photos>
</rsp>

All data from Flickr is structured in much the same way as this; the containing element is always rsp, and there is always a stat attribute that indicates whether the request was successful or not.

What is inside the rsp element will change from method to method, but if the result is a set of photos, then they will always be in a list like this. Note that there is actually no URL for the image itself in the set of results, since it is our job to create it by combining together the information passed to us in the photo element. This is because Flickr stores multiple formats of the same image, and we'll see how to combine the pieces later.

Setting Up the Search

In our del.icio.us form we saw that all of the parameters we needed had a corresponding form control. We could do the same here, but it would mean creating a form control for things like the method and api_key which are not pieces of information that we want our users to be able to update. To create a nice easy to use form for searching Flickr, we only really need one form control, and that is for the tags value; all the other fields can be given fixed values.

XForms Data Islands

To achieve this we need to create an XForms data island that will hold all of our items. This may seem a little different to how we worked with the del.icio.us example, but in fact it isn't--all that happened with the del.icio.us form was that formsPlayer created a 'default' data island for us since we didn't specify one explicitly. And it used the form controls that we had provided to 'work out' how the 'automatic' data island should be structured. (This is called 'lazy authoring', for obvious reaons.)

Let's create a data island that contains the items we need for our search on Flickr (instances are placed inside an XForms model):

    <link rel="stylesheet" href="flickr.css" type="text/css" />
    <xf:model>
      <xf:instance>
        <instanceData xmlns="">
          <method>flickr.photos.search</method>
          <api_key>68149024a667e0be3c63708f002ffe1e</api_key>
          <tags />
          <per_page>12</per_page>
        </instanceData>
      </xf:instance>
    </xf:model>
  </head>

Hopefully this all looks familiar to you, since all we have created is one XML item for each of the parameters that Flickr needs, and given each a value should it need one. Note that we've also created an empty entry for tags, even though we have a form control that refers to it. This is because the automatic creation of 'default' items only happens if we don't provide an instance; but once you do create one, then you will have to include within it any items that you need for your controls. However, the input control that we created earlier will correctly reference the tags item in this instance, just as before it was referencing the one that was created automatically.

Specifying a Separator for Submission

Now that we have set up a data island ready to send to Flickr, we can go on to prepare the request that will be used to indicate where to send everything. You saw earlier how to specify a request to del.icio.us, and we need to do much the same here:

      </xf:instance>
      <xf:submission id="sub-flickr"
       method="get" action="http://www.flickr.com/services/rest/"
       separator="&amp;"
      />
    </xf:model>

The only difference you will see is that we had to explicitly say that the separator value is "&amp;", since with XForms the default is ";". (We didn't bother to do this for del.icio.us, since their servers understand both values.)

Save the form, refresh, type a value into the input control, and press the "Find" button, and you should get something like this:

Screenshot of XML returned from Flickr.

We mentioned in an earlier section that replacing the form with the data returned from the server was the default behaviour for XForms, making it exactly the same as HTML forms. In the next section we'll show how to make use of the XML without replacing the entire form, but before we do, go 'back' in the browser to your form, and try pressing "Find" again without entering a tag; note that this time you should get an error message from Flickr:

Screenshot of XML returned from Flickr when there is an error.

Capturing the XML Returned by Flickr

The whole point of using the Flickr API is so that we can make use of the XML returned. Unlike HTML forms where the page will be replaced by whatever comes back from the server, XForms allows us to retrieve data from a server, store it locally and then manipulate it.

In order to manage the parameters that were to be sent to Flickr we created an instance in the model. We'll do the same now to manage the data that is returned from Flickr:

      </xf:instance>
      <xf:instance id="inst-rs">
        <dummy xmlns="" />
      </xf:instance>
      <xf:submission id="sub-flickr"

We've given the instance a dummy value since it will be replaced anyway by whatever comes back from Flickr.

Now we can modify the request so that instead of replacing the entire form--the default behaviour--the returned data is placed into our new instance. This is easily done by adding the following two attributes to the submission:

      <xf:submission id="sub-flickr"
       method="get" action="http://www.flickr.com/services/rest/"
       separator="&amp;"
       replace="instance" instance="inst-rs"
      />

(Note that the default value for replace is all, which is the 'HTML form'-style behaviour that we have been seeing, where the entire form is replaced with whatever the server returns.)

If you refresh your form and try searching again it appears that nothing has happened! This is because the data from Flickr is now being stored in the instance inst-rs, and the form is no longer being replaced. The next step is to do something with the data.

The Output Control

The output control has much the same structure as the input control, which means we can add labels and hints for example. But as you'd probably guess, it's not an interactive control--it's just used to get some data out of the instance so that the user can see it.

We'll use an output control as a simple way to show any error text that comes back from Flickr. Add the following just after the submit button:

      </xf:submit>
      <xf:output ref="instance('inst-rs')/err/@msg">
        <xf:label>Error:</xf:label>
      </xf:output>
    </xf:group>

Note that we have to use the instance function since there is more than one instance our form--one for specifying the parameters to send, and one to hold the response from Flickr--and formsPlayer will always use the first one unless told to do otherwise.

You'll no doubt be wondering what is going to happen here if there is no error string returned from Flickr. The answer is that in XForms if you add a form control and connect it to an item that does not exist, then by default it is regarded as non-relevant--and as we saw earlier, relevance is used to show and hide controls, so our control will simply not appear unless there is an error message.

(Note that if you don't create an instance then items will never 'not exist' since formsPlayer will create a set of default items for you. If you are not clear on why this is, you might want to revisit the earlier discussion about 'lazy authoring'.)

In our example, our output form control will automatically be given the CSS pseudo-class :disabled if there is no error (i.e., the item err/@msg does not exist), and it will have the class :enabled if there is an error. This is a very simple and convenient way to have the conditional display of items, and it means that we can specify CSS style rules that take advantage of these automatic class changes. In this case we don't need anything, since the default style rule for :disabled is display: none;.

If you reload your form and press "Find" with nothing in the Tags: control, you should get the following:

Screenshot of a search on the Flickr form that results in an error.

Just to spell out what is happening, Flickr is giving us back exactly the same data that you saw before, but now instead of wiping out our form, formsPlayer is storing the returned XML in a data island. An output control is 'wired up' to show a specific item in the data island if it exists, and if it doesn't, the control is hidden.

The Repeat Control

A repeating structure usually involves defining a list of items to 'repeat over' (or iterate) and a template to be applied to each item in the list.

In many systems the template part would need to be defined with a separate file, and although there will be certain situations where this is a good approach, there are also plenty of situations where it's just that bit too much work for the simple job in hand.

The XForms repeat element allows us to specify both the list and template in one. The items that make up the list are expressed using an XPath expression, and the template is defined by placing mark-up inside repeat. (The mark-up can be anything, including further repeat elements.)

Specifying the Items for Repeat

For the Flickr form we want a repeating structure that has as many entries as there are photo items in the returned XML. The XPath to create a list of photo items is:

instance('inst-rs')/photos/photo

We can use this expression to set up a repeating structure that operates on each returned photo, by adding the following to the form just after the output that shows any error results:

      </xf:output>
      <xf:repeat nodeset="instance('inst-rs')/photos/photo">
        [...]
      </xf:repeat>
    </xf:group>

The Repeat Template

The template is created from any tags that are inside the repeat element itself, and can include any HTML and XForms tags, including the repeat element. A moment ago we set the template to a simple ellipsis:

      <xf:repeat nodeset="instance('inst-rs')/photos/photo">
        [...]
      </xf:repeat>

This will give us enough to test with, so save and refresh the form, and try searching for something--you should see as many ellipses as there are search results:

Screenshot of Flickr search form showing search results...but only the image titles

Now that we know that both the search and our repeat are working fine, let's fill out the repeat template.

We'll first use a simple output control to show the title of each of the photos returned from Flickr, before we then move on to show how to display the actual image.

XPath Evaluation in Repeat Templates

The XML item that holds this data is the title attribute on each photo element. The XPath expression needed to refer to, say, the title of the third photo would be:

instance('inst-rs')/photos/photo[3]/@title

However, when we place controls into a repeat template, their XPath expressions are evaluated one at a time, in the context of each of the items in the list. So to show the title of each of the photos in the list, we need only do this:

      <xf:repeat nodeset="instance('inst-rs')/photos/photo">
        [<xf:output ref="@title" />]
      </xf:repeat>

Refresh the form, and try searching for something--you should see a list of image titles:

Screenshot of Flickr search form showing search results...but only the image titles

Displaying the images

Flickr doesn't give us a URL for each photo since it stores many different formats of posted images. Instead, when we search, Flickr provides us with all the bits we need to create the URL of the image we want, using a set of predefined rules. This allows us to choose from a number of image formats, depending on the context.

To display each image, we therefore need to calculate the URLs and then make use of the URL to create an image in the form. We'll now show how to do that.

The Output control and calculated expressions

The first thing we need to do is create the full URL for each image. The URL consists of the Flickr domain name, a number for a server to retrieve the image from, an ID, a 'secret number', and a size:

http://static.flickr.com/{server}/{id}_{secret}_{size}.jpg

The following mark-up creates a URL like this for each photo in the search results:

      <xf:repeat nodeset="instance('inst-rs')/photos/photo">
        <xf:output
         value="concat('
            'http://static.flickr.com/',
            @server, '/',
            @id, '_',
            @secret, '_s.jpg'
         )"
        />
      </xf:repeat>

The main new feature to note is that we're using the attribute value on our output control, rather than the ref attribute. The distinction is that ref refers to an item in an instance, whilst value can be any calculated expression.

In the value attribute we have an XPath expression that is making use of the concat function. All this function does is to join together all of its arguments to produce a string:

concat(
  'http://static.flickr.com/',
  @server,
  '/',
  @id,
  '_',
  @secret,
  '_s.jpg'
)

This is an XPath function that combines each of its parameters together to create a string. If any of the parameters are themselves XPath expressions then they are evaluated first, so in this case the values for the attributes server, id and secret are obtained from each photo and placed into the result.

The Output control and images

Now that the output control has access to the calculated URL, we can make use of it to load the desired image. To do this, all we need to do is add the mediatype attribute which indicates that the data in the output is not just to be used as plain text for display, but is a reference to some other kind of object. The values of @mediatype will usually be MIME types, so to indicate that we want an image we simply use image/jpeg or image/*.

      <xf:repeat nodeset="instance('inst-rs')/photos/photo">
        <xf:output
         value="concat('
            'http://static.flickr.com/',
            @server, '/',
            @id, '_',
            @secret, '_s.jpg'
         )"
         mediatype="image/*"
         class="image"
        />
      </xf:repeat>

You'll see that we also added a @class whilst we're here; this is so that we can make the images a little neater by adding the following style rule to the end of flickr.css:

xf\:output.image
{
  border                : 1px solid black;
  width                 : 75px;
  height                : 75px;
  margin                : 5px;
}

Note the use of the class "image" that we set on the output; this is so that other uses of output don't get displayed as 75x75 squares.

Now, save, refresh and search, and you should have results that look something like this:

Screenshot of Flickr search form showing flowers as search results.

Switch and Case

Our form is working fine so far, but we can improve on the user experience by having different parts of the form active at different stages.

For example, once a search is underway it would be nice to hide any previous images or error messages, and instead show some kind of indication that the search is 'in progress'. We can do this by making use of switch and case.

The switch element provides a container for a set of cases, only one of which will be active at any one time. We'll see exactly how to select a particular case in a moment, but first add the following mark-up after the submit button:

    </xf:submit>
    <xf:switch>
      <xf:case id="case-start">Start</xf:case>
      <xf:case id="case-busy">Busy</xf:case>
      <xf:case id="case-done">Done</xf:case>
    </xf:switch>
    <xf:output ref="instance('inst-rs')/err/@msg">

If you reload your form you will see the word "Start" just below the input control, with "Busy" and "Error" hidden because only one case can be active at a time.

The Toggle Action

The next step is to change the active case as the user interacts with the application. The 'action' to do this is called toggle and it takes a single value, the id of the case to make active:

<xf:toggle case="case-busy" />

You might be tempted to think that the 'busy' case should be made active when the user clicks on the button, and this is indeed what many non-MVC systems would do. But XForms goes to great lengths to decouple the data and the user interface, and it is much better to change the active case to 'busy' when the submission begins, as indicated by the xforms-submit event.

By doing this we tie our 'busy' state to the request to Flickr, rather than some action performed by the user, and it's good to get into this habit; more advanced tutorials will show how to send data to a server as a result of other activities in the form, not just mouse clicks.

Just as we're going to use the xforms-submit event to select the 'busy' state, we'll use the xforms-submit-done event (dispatched when submission has completed successfully) to select the 'done' state. Modify the submission element to include our two action handlers:

      <xf:submission id="sub-flickr"
       method="get" action="http://www.flickr.com/services/rest/"
       separator="&amp;"
       replace="instance" instance="inst-rs"
      >
        <xf:toggle case="case-busy" ev:event="xforms-submit" />
        <xf:toggle case="case-done" ev:event="xforms-submit-done" />
      </xf:submission>

Save your changes and refresh your form, and you should see "Start" below the input control. Then each time you search you should see "Busy" as the search begins, and "Done" when it completes.

Showing an 'In Progress' Animation

Our next step is to show an image to the user to indicate that the search is in progress. Of course you can put any mark-up you like in the "Busy" case, but we're going to show an animated image to give the user a sense of something happening. An image is attached to this page, and you will need to copy it to the same directory as your form. Then replace the text in the case with an img tag:

        <xf:case id="case-busy">
          <img src="spinner.gif" alt="Busy" />
        </xf:case>

Since we've already configured this case to be toggled when a submission begins, the user will see the animated image the moment they press the "Find" button.

Using Group to Set Evaluation Context

Next we'll move the error message and repeat into the "Done" case. However, as we do we'll make a slight modification; you probably noticed that the XPath expressions in output and repeat both begin with a call to the instance function:

<xf:output ref="instance('inst-rs')/err/@msg">
  <xf:label>Error:</xf:label>
</xf:output>
<xf:repeat nodeset="instance('inst-rs')/photos/photo">
  ...
</xf:repeat>

You may have wondered if it was possible to factor this out, and set a common context for both expressions, and it is.

Although groups can be used simply to establish a common area for a set of controls, another use for them is to set an evaluation context for a whole set of XPath expressions. This makes them not only easier to type but also to maintain, since if the data source changes it is easier to move controls around if their expressions are shorter.

For example, if you had an address item that had further items nested underneath, for street, city, country, and so on, you could set the context to be the address item, and then the expressions for each of the contained controls would be shorter.

To illustrate this, as we move the controls into the "Done" case we'll add a group to set context:

        <xf:case id="case-done">
          <xf:group ref="instance('inst-rs')">
            <xf:output ref="err/@msg">
              <xf:label>Error:</xf:label>
            </xf:output>
            <xf:repeat nodeset="photos/photo">
              <xf:output
               value="concat('
                &lt;img src=&#34;http://static.flickr.com/',
                @server, '/',
                @id, '_',
                @secret, '_s.jpg&#34; /&gt;'
               )"
               class="image"
              />
            </xf:repeat>
          </xf:group>
        </xf:case>

Setting the context won't have any effect on the way the form works, but moving everything into the "Done" case has; if you reload your saved form and click on "Find", any previous search results will be hidden, replaced by the 'in progress' animation, and once the search is complete, the "Done" state will be toggled in (showing either the images or an error message).

Animating the Display

The next step is to have some kind of animation when the images are ready to be viewed, such as the whole panel sliding into view.

We can achieve this easily by adding a CSS rule that tells formsPlayer to slide down a case when it receives the xforms-select event.

the following to the end of flickr.css:

xf\:case
{
  -event-xforms-select   : fx-Effect-SlideDown(duration:1);
}

Note that there is a bug in Internet Explorer which gives us a slight flicker before the animation begins. We can work around this by overriding the default rule that hides deselected cases, and replacing it with a rule that performs a slide up on receiving the event xforms-deselect. The resulting CSS is:

xf\:case
{
  -event-xforms-deselect : fx-Effect-SlideUp(duration:0);
  -event-xforms-select   : fx-Effect-SlideDown(duration:1);
}

Now, whenever an XForms case element receives the xforms-select event, a slide-down animation will be initiated, set to take 1 second. Similarly, whenever the xforms-deselect event occurs, a slide-up animation will be initiated.

Save the form, refresh and try searching; once the results are available they should slide into view, and each time you try a new search the previous results should be immediately hidden, before the new ones slide in again.

The Range Control

The range control is the XForms equivalent of a 'slider'. The XForms version allows us to indicate start, end and step values. We'll use a slider to allow the user to control the size of the images that are displayed.

The first step is to create some instance data to hold the current size of an image. In our applications we often have an instance called control that we use to hold assorted values like this, so add the following to the model:

      </xf:submission>
      <xf:instance id="inst-control">
        <instanceData>
          <size>75</size>
        </instanceData>
      </xf:instance>
    </xf:model>

This gives us an element called size with an initial value of 75. We use this to start us off, since it is the size of the smaller images on Flickr that we have been using so far.

Next we add our range control. As with the other controls you have seen, we must have a label, but we can also optionally have help, hints, and so on. The range control supports start and end attributes to indicate the 'range' of possible values. We'll use 1 to 200, so as to allow our images to go from 1px up to 200px:

      </xf:submit>
      <xf:range
       ref="instance('inst-control')/size"
       start="1" end="200"
       style="width: 200px;">
        <xf:label>Size:</xf:label>
      </xf:range>
      <xf:switch>

The range control can only be used on data that is typed to be an integer. We saw how to set the data type of a node in the del.icio.us form when we set an element to be a date, and we will use the same technique here. Add a bind statement to the model, below our control instance:

      </xf:instance>
      <xf:bind nodeset="instance('inst-control')/size" type="xs:integer" />
    </xf:model>

Obtaining intermediate values from the range control

If you play with the form so far, you will have noticed that the images only resize after you have finished dragging the slider. [This part of the tutorial is being changed. In a previous version you could resize the images with the range control, but the technique to achieve this was non-standard. This is being changed.] This is fine for many situations, but doesn't feel quite right for tasks relating to the images, since more often than not you will drag the slider until the images look right, rather than paying attention to the value.

We can easily make our application resize the images as you drag by using the incremental attribute. This tells formsPlayer to periodically update the data value in size, rather than waiting until the user has finished dragging. The same technique can be used in any other form controls; you might use it in an input control to provide 'suggestions' to the user as they type, for example.

The attribute is set as follows:

      <xf:range
       ref="instance('inst-control')/scale"
       incremental="true"
       start="1" end="200"
       style="width: 200px;">
        <xf:label>Size:</xf:label>
      </xf:range>

Now try dragging the control.

The if() function

We saw ealier with the use of concat() how expressions can contain calls to functions. Another very useful function is if() which works much like it does in spreadsheets; if the first value is true then the second value is returned, otherwise the third value is returned.

We'll make use of this in the part of our application that works out the URL for the photo. Recall that we created the URL of the photo by joining various parts of the data that Flickr provided us with. One of these parts is a single letter to indicate what size of image we would like, with the choices being s, m and l. Let's avoid some of the distortion we see when we make the images too large by using the medium-sized image if the user takes the size over 100px:

              <xf:output
               value="concat(
                 'http://static.flickr.com/',
                 @server, '/',
                 @id, '_',
                 @secret,
                 '_',
                 if(
                   instance('inst-control')/size &lt; 100,
                   's',
                   'm'
                 ),
                 '.jpg',
                '/&gt;'
               )"
               class="image"
              />