The following few examples explain how I created Easy Flickr showing how easy it is to re-skin and improve the accessibility of a web application if only the developers were nice enough to provide you with an API.
Download the demo examples for this how-to
The plan
The first step to building an alternative interface to a system is to analyze the problems and barriers the interface has. In the case of Flickr, what I heard from several people with varying disabilities were two things: the interface is cluttered, and there are some "weird JavaScript things going on".
With this in mind my battle plan was to:
- build an interface that only has what is really needed
- be scripting independent to begin with and
- add scripting to make the experience easier - if at all
The bare bones of a search interface
I started with a simple search interface:
<form action="index.php" method="get" accept-charset="utf-8">
<p>
<label for="s">Show me pictures of</label>
<input type="text" name="s" value="" id="s">
<input type="submit" value="Search">
</p>
</form>
Once this was done, it was time to start with the PHP part. The first thing I wanted to make sure is that I can get the search terms and make sure that I filter out any nasty things evil people my add in. I also store the current document's file name in a $base variable to write out as the form action (and later for navigation links).
I test if there was a search already performed and if there is one, I can show the rest of the document. I store the cleaned up data string in the variable $tag.
<?php
$base = 'index.php';
$tag = null;
if(isset($_GET['s'])){
$tag = strip_tags($_GET['s']);
preg_match_all("/([a-z|A-Z|0-9|=|:+])+/",$tag,$cleartag);
$tag = implode($cleartag[0],'+');
}
?>
<form action="<?php echo $base;?>" method="get" accept-charset="utf-8">
<p>
<label for="s">Show me pictures of</label>
<input type="text" name="s" value="<?php echo str_replace('+',' ',$tag)?>" id="s">
<input type="submit" value="Search">
</p>
</form>
<?php if($tag){ ?>
[…]
<?php }?>
Retrieving the data from flickr
As to where to get the information from, I started perusing the Flickr API documentation. As I didn't want implementers of Easy Flickr to have to get a developer ID (or clobber mine), I was especially interested in the API calls that don't need any authentication. What I found was the feeds API:
http://api.flickr.com/services/feeds/photos_public.gne?tags=tag
This one has several different output formats - in the past I've used JSON a lot to build JavaScript-driven mashups - but for this exercise the php_serial format was right on the money:
http://api.flickr.com/services/feeds/photos_public.gne?tags=tag&format=php_serial';
Using this, you can get a lot of information about the latest 20 photos matching tag in a format native to PHP. The documentation tells you to use get_file_contents() to get the data, but I am on a server that disallows loading data from another server. Therefore I used cURL to get the data. In order to get a feed of the latest 20 photos for a certain tag all I needed to do was:
$url = 'http://api.flickr.com/services/feeds/photos_public.gne?' .
'tags='.$tag.'&format=php_serial';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$feed = curl_exec($ch);
curl_close($ch);
$data = unserialize($feed);
if(isset($data['items'])){
$items = $data['items'];
}
The $data variable will then contain an object with all the feed information (this would be too much to display here, if you want to see what a feed looks like check this demo showing the information for the term 'donkey')
The $items array is a list of 20 photos, where each item has a lot of information about the photo. this is an example output:
Array
(
[title] => El Burro
[url] => http://www.flickr.com/photos/photobddy/2581085487/
[description] => <p><a href="http://www.flickr.com/people/photobddy/">photobddy</a> posted a photo:</p>
<p><a href="http://www.flickr.com/photos/photobddy/2581085487/" title="El Burro"><img src="http://farm4.static.flickr.com/3111/2581085487_b28942f4ea_m.jpg" width="236" height="240" alt="El Burro" /></a></p>
[description_raw] =>
[m_url] => http://farm4.static.flickr.com/3111/2581085487_b28942f4ea_m.jpg
[t_url] => http://farm4.static.flickr.com/3111/2581085487_b28942f4ea_s.jpg
[l_url] => http://farm4.static.flickr.com/3111/2581085487_b28942f4ea.jpg
[photo_xml] =>
[date] => 1213563076
[date_taken] => 2008-06-14T16:52:01-08:00
[date_taken_nice] => 14th June, 2008
[guid] => /photo/2581085487
[author_name] => photobddy
[author_url] => http://www.flickr.com/people/photobddy/
[author_nsid] => 68599119@N00
[photo_url] => http://farm4.static.flickr.com/3111/2581085487_da44110653_o.jpg
[thumb_url] => http://farm4.static.flickr.com/3111/2581085487_b28942f4ea_s.jpg
[height] => 1040
[width] => 1024
[l_width] => 492
[tags] => bw peru holga donkey pole
[tagsa] => Array
(
[0] => bw
[1] => peru
[2] => holga
[3] => donkey
[4] => pole
)
[photo_mime] => image/jpeg
[license] => deed.en_GB
[tags_list] => Array
(
[0] => bw
[1] => peru
[2] => holga
[3] => donkey
[4] => pole
)
)
Displaying the search results
In order to display the search results I nest the code in a condition that checks if $items is defined and is an array. If it isn't, then I show an error message.
<?php if(isset($items) && is_array($items) && sizeof($items) > 0){ ?>
[…]
<?php } else { ?>
<p id="fail">Sorry, I couldn't find any photos for this search.</p>
<?php } ?>
That done, all I need to show the image is to get the correct array item and display some appropriate HTML with data taken from the item data:
<?php
$c = 0;
$now = $items[$c];
?>
<h1 id="by">
<a href="<?php echo $now['url']?>"><?php echo $now['title']?></a>
taken and copyright by
<a href="<?php echo $now['author_url']?>"><?php echo $now['author_name']?></a>
</h1>
<div id="mainimg"><img src="<?php echo $now['l_url']?>" alt="<?php echo $now['title']?>"></div>
That is it for the base functionality. Check out the demo easyflickr-showphoto.php to see it in action.
Providing navigation the classic way
In order to show the next and previous photos, all I had to do was to make the $c variable a URL parameter. Then I'd check if it is more than zero and show the previous thumbnail with an appropriate link and check if it less than the amount of available items and show the next item respectively.
<?php
$c = isset($_GET['c']) ? $_GET['c'] : 0;
$now = $items[$c];
?>
<h1 id="by">
<a href="<?php echo $now['url']?>"><?php echo $now['title']?></a>
taken and copyright by
<a href="<?php echo $now['author_url']?>"><?php echo $now['author_name']?></a>
</h1>
<div id="mainimg"><img src="<?php echo $now['l_url']?>" alt="<?php echo $now['title']?>"></div>
<div id="previous">
<?php if($c > 0){ ?>
<?php
$url = $base .'?s=' . $tag .'&c=' . ($c-1);
$cur = $items[$c-1];
?>
<a href="<?php echo $url?>">Previous Photo:
<img src="<?php echo $cur['t_url']?>" alt="Previous: <?php echo $cur['title']?>">
</a>
<?php } ?>
</div>
<div id="next">
<?php if($c < sizeof($items)-1){ ?>
<?php
$url = $base .'?s=' . $tag .'&c=' . ($c+1);
$cur = $items[$c+1];
?>
<a href="<?php echo $url?>">Next Photo:
<img src="<?php echo $cur['t_url']?>" alt="Next: <?php echo $cur['title']?>">
</a>
<?php } ?>
</div>
This would be a good way of navigating a gallery. Check out the demo easyflickr-navigation-fail.php to see it in action and failing.
Alas this does not work the way it should. The reason is that we are not navigating through a static array of data but get a live feed from flickr. New pictures are constantly uploaded, which means the counter of the array is not enough to navigate!
Fixing the navigation for dynamic feed data
In order to fix the display of the photos I needed to find a unique identifier, as the array counter was not enough. What I've taken was the user nsid and the photo's guid and created a unique parameter that way that I send as $c on the previous and next navigation items.
I predefine $c as zero and then loop over the whole array. I compare the current entry data with the unique parameter and re-assign $c as the array counter if there is a match.
<?php
function getUnique($now){
return $now['author_nsid'] . '-' . str_replace('/photo/','',$now['guid']);
}
?>
<?php
$c = 0;
if(isset($_GET['c'])){
$parts = explode('-',$_GET['c']);
foreach ($items as $k=>$i){
if($i['author_nsid'] === $parts[0] &&
strpos($i['guid'],$parts[1])){
$c = $k;
}
}
}
$now = $items[$c];
?>
<h1 id="by">
<a href="<?php echo $now['url']?>"><?php echo $now['title']?></a>
taken and copyright by
<a href="<?php echo $now['author_url']?>"><?php echo $now['author_name']?></a>
</h1>
<div id="mainimg"><img src="<?php echo $now['l_url']?>" alt="<?php echo $now['title']?>"></div>
<div id="previous">
<?php if($c > 0){ ?>
<?php
$url = $base .'?s=' . $tag .'&c=' . getUnique($items[$c-1]);
$cur = $items[$c-1];
?>
<a href="<?php echo $url?>">Previous Photo:
<img src="<?php echo $cur['t_url']?>" alt="Previous: <?php echo $cur['title']?>">
</a>
<?php } ?>
</div>
<div id="next">
<?php if($c < sizeof($items)-1){ ?>
<?php
$url = $base .'?s=' . $tag .'&c=' . getUnique($items[$c+1]);
$cur = $items[$c+1];
?>
<a href="<?php echo $url?>">Next Photo:
<img src="<?php echo $cur['t_url']?>" alt="Next: <?php echo $cur['title']?>">
</a>
<?php } ?>
</div>
This fixes the problem of new photos coming in Check out the demo easyflickr-navigation.php to see it in action.
That's already a working interface, but it is not really efficient to call the Flickr API on every page view. As I didn't want to cache locally, I opted for using JavaScript to avoid unnecessary page loads.
Adding JavaScript to make the interface more responsive
You could get the data from Flickr in JSON format, but I didn't want to add extra overhead by calling it with JavaScript. Instead I am using PHP to assemble a JavaScript array based on the data already in the page.
<script type="text/javascript" src="http://yui.yahooapis.com/2.5.2/build/yahoo-dom-event/yahoo-dom-event.js"></script>
<script type="text/javascript" charset="utf-8" src="easyflickr-yui.js"></script>
<?php
$script = array();
if(isset($data)){
foreach($data['items'] as $item){
$script[] = '{' .
'"t":"'. $item['title'] .'",'.
'"u":"'. $item['url'] .'",'.
'"th":"'. $item['t_url'] .'",'.
'"l":"'. $item['l_url'] .'",'.
'"an":"'. $item['author_name'] .'",'.
'"au":"'. $item['author_url'] .'"'.
'}';
}
}
echo '<script type="text/javascript">' .
'YAHOO.example.easyFlickr.data({
count:'.$c.',
items:['.implode($script,',').']
})' .
'</script>';
?>
</script>
This provides the script (easyflickr-yui.js) creating the right behaviour with the data it needs in an easy format. The script itself is pretty easy. I start with some variables (the counter and the feed) and retrieve the HTML elements I'll change later on:
YAHOO.example.easyFlickr = function(){
var c = 0;
var feed = null;
var by = YAHOO.util.Dom.get('by');
var img = YAHOO.util.Dom.get('mainimg');
var search = YAHOO.util.Dom.get('s');
var prev = YAHOO.util.Dom.get('previous');
var next = YAHOO.util.Dom.get('next');
The data() method retrieves the object I assembled with PHP and overrides the counter and feed variables with the right values. I then apply events to the DIVs surrounding the next and previous links that decrement or increment the current counter and call the update() method. I use event delegation to make sure that any of the HTML in the DIV can be activated.
function data(obj){
c = obj.count;
feed = obj.items;
YAHOO.util.Event.on(prev,'click',function(e){
c--;
update();
YAHOO.util.Event.preventDefault(e);
});
YAHOO.util.Event.on(next,'click',function(e){
c++;
update();
YAHOO.util.Event.preventDefault(e);
});
};
The update() method first and foremost checks that the HTML elements that will be altered are available.
function update(){
if(prev && next && img && search){
I alter the main photo by re-setting the src attribute to the large photo url from the feed item data and the alt attribute t become the title information.
var photo = img.getElementsByTagName('img')[0];
if(photo){
photo.setAttribute('src',feed[c].l);
photo.setAttribute('alt',feed[c].t);
}
The title of the photo and the user name and url can be reached and changed by altering the links in the heading with the ID by.
var info = by.getElementsByTagName('a');
if(info.length === 2){
info[0].setAttribute('href',feed[c].u);
info[0].innerHTML = feed[c].t;
info[1].setAttribute('href',feed[c].au);
info[1].innerHTML = feed[c].an;
}
Navigating the array is now easier. As I am not re-loading the feed on ever action I just need to compare the array counter, get the correct data and write out either a new link and thumbnail or purge the HTML of the previous and next DIVs.
if(c >= 1){
var item = feed[c-1];
var out = '<a href="' + item.u + '">Previous Photo: ' +
'<img src=' + item.th + ' alt="Previous: ' + item.t + '">' +
'</a>';
prev.innerHTML = out;
} else {
prev.innerHTML = '';
}
if(c < feed.length-1){
var item = feed[c+1];
var out = '<a href="' + item.u + '">Next Photo: ' +
'<img src=' + item.th + ' alt="Next: ' + item.t + '">' +
'</a>';
next.innerHTML = out;
} else {
next.innerHTML = '';
}
I re-set the search input field to force screen readers to update the virtual buffer and I am done. All that is left is to return the data() method as a public method so that the callback created with PHP works.
search.value = search.value;
}
}
return {data:data};
}();
The final outcome means you don't need to reload the page but instead just click through the photos. Check out the demo easyflickr-navigation-js.php to see it in action.
That's all folks
That's about it. Not much sophistication going on but I hope I could show how easy it is to take Flickr and cut it down to the bare minimum.
You can then start adding more HTML to allow for styling. Check out the demo easyflickr-styled.php to see it in action using the YUI grids.
Feel free to download the code examples and leave your comments on the blog Building Easy Flickr - step by step.
