I’m a constant user of the social bookmarking service delicious. I use it to store bookmarks for all of the interesting web sites I see, and I use it to find interesting sites other people have found. It’s a very useful service and I’ve grown to rely on it. They have, unfortunately, had some problems with downtime, slow service, and other unreliability. I’m sure that this will be diminished greatly due to their purchase by Yahoo!, and I’m not really complaining, because it is a free service.

Because I rely on it so much, I decided that I needed a way to back up my bookmarks and still have access to them anywhere with an internet connection. Exporting to an HTML file or to my Firefox bookmarks didn’t really have any appeal to me. So my idea was to put together a simple PHP script to load all of my delicious links into a mySQL database and provide a simple interface to browse them. Delicious provides a REST API that has a call to export all of your posts to XML – if you load up http://del.icio.us/api/posts/all in your web browser, provide your delicious username and password, you can get the full XML. I also took this as an opportunity to play around with some technology that I haven’t used much yet – PHP5, SimpleXML, and AJAX.

Some of this is spurred on by the fact that I got a new webhosting account at Dreamhost – this supports PHP5 and has a ton of other great features. As a side note, if you’re looking for a webhosting account, you can sign up for Dreamhost’s Level 1 account (20GB space, just about unlimited bandwidth, PHP5, PHP4, Ruby on Rails, SSH, unlimited domains/subdomains, and a free 1 year domain registration) for $30 if you use the code ‘GREGPHOTO‘ at checkout and sign up for a 1 year prepaid plan. Ok, enough of the advertising – onto how I made the app and a bunch of code examples!

Exporting delicious posts

As mentioned above, I used the link http://del.icio.us/api/posts/all through Firefox to download the XML file and save it as all.xml. This has a very simple format, an example is shown below:

1
2
3
4
5
6
7
8
<?xml version="1.0" standalone="yes"?>
<posts update="2006-01-07T17:55:47Z" user="gneustaetter">
  <post href="http://www.subzane.com/projects.details.php?ID=12" description="subzane.com - Free PHP Scripts and Classes" hash="05d908f23adbb89ef3e97e5d3a6ffd9f" tag="php" time="2006-01-06T07:50:49Z"/>
  <post href="http://priyadi.net/archives/2005/09/27/wordpress-plugin-code-autoescape/" description="Priyadi�s Place � Blog Archive � WordPress Plugin: Code Autoescape" hash="502441799d404abcbe08a07186c32626" tag="php wordpress" time="2006-01-06T06:47:59Z"/>
   <post href="http://www.huddletogether.com/projects/lightbox/" description="Lightbox JS: Fullsize Image Overlays" hash="08a5a446ff39aeb04c5fdbc50d674765" tag="javascript css design" time="2005-12-30T17:58:44Z"/>
  <post href="http://www.kayak.com/h/buzz/flights" description="Kayak Buzz popular flight searches" hash="ea74f520caad38215201d839403fc61f" tag="travel maps" time="2005-12-21T17:37:50Z"/>
  <post href="http://wsfinder.jot.com/WikiHome" description="WikiHome - wsfinder - JotSpot" hash="0e85912cf399cb7469a00e8633d13a77" tag="api reference tools" time="2005-12-21T17:31:16Z"/>
</posts>

So basically it has a top level ‘posts’ element and then ‘post’ elements inside this that detail the link href, description, the tags applied to the link, the time posted, and a hash. I’ll use all of the information except for the hash.

Parsing the XML with PHP5’s SimpleXML

One of the greatest features in PHP5 is SimpleXML, which really allows incredibly simple access to XML. For tasks such as parsing the above XML, this is a great thing to use – you basically get to iterate through the XML as if it were an Array. Let’s take a look at the parseXML function in my delicious import class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function parseXML() {
	$xml = simplexml_load_file($this->xmlfile);
	foreach($xml->post as $post) {
		$tags = $post['tag'];
		$tags = explode(' ',$tags);
		foreach($tags as $tag) {
			$tag = trim($tag);
			if(!in_array($tag, $this->tags)) {
				$this->tags[$tag]['name'] = $tag;
			}
		}
		$this->posts[] = array(
			'href' => (string)$post['href'],
			'description' => (string)$post['description'],
			'tags' => $tags,
			'time' => strtotime((string)$post['time'])
		);
	}
}
  1. The function takes no arguments and starts off (on line 2) by loading the all.xml file (path stored as $this->xmlfile) and creating a simplexml object called $xml
  2. SimpleXML supports iteration, so we can loop through the XML file as if it were an array – using the foreach construct (line 3)
  3. On lines 3 and 4, we get the tags from the tag attribute of the XML, and explode them into an array by splitting them wherever there are spaces – i.e. the tag “php5 simplexml” would turn into an array with two elements, “php5” and “simpleXML”
  4. From here, we loop through the tags array I just created and push the tags into a class-level array if they aren’t already in there yet (lines 6-11)
  5. Lastly, on lines 12-18, we populate information about the post into a class-level array that stores the information about all the posts

So in this function we took the all.xml file that had all of the delicious links, loaded them into a simpleXML object, and populated two arrays – one holding a unique list of all the tags, another holding information about all of the posts.

Populating the database

At this point, we insert all of the information into the database – none of that is too exciting, so I won’t publish that in this post, though you can look at the delicious.class.php file in the zip file that includes all of the files. It’s in the inc/classes directory. This class uses the incredibly popular ADODB database abstraction library to take care of inserting all of the data. This is split into three tables:

  • tags – holds a record for each unique tag
  • posts – holds a record for each tag, including all of it’s attributes such the href, description, space-separated list of tags (for easier searching later), and posting date
  • posttags – holds the relationships between posts and tags. A record is created for each tag associated with a post

Building the interface

Now that we have all of the information stored in the database, we can build the user interface and backend that are needed to display the bookmarks. The final version of what we’re building can be seen here. We start by creating a class to hold get all the information from the database. I’ve called this class bookmarks.class.php and it can be found in the ‘inc/classes’ directory. Again, there really isn’t much exciting code here, so I’ll just list out the relevant functions provided by the class:

  • __construct($conn) – takes an ADODB connection object and instantiates the bookmarks class
  • getAllBookmarks() – return an ADODB resultSet object (that implements iterator functions) that includes all of the posts and their details
  • getBookmarksByTag($tag) – takes a tagid a return as ADODB resultSet, as above, for the boomarks in the appropriate tag
  • searchBookmarks($criteria) – takes a search string and return an ADODB resultSet with bookmarks that match the search criteria
  • getTags() – returns an ADODB resultSet with all of the tags and their tagids

All in all, a pretty simple class, weighing in just under 50 lines. Next we need to build the user interface to display the bookmarks – I could have built a simple, standard application that rendered the bookmarks. Instead, I decided that I’d use some AJAX in order to dynamically load information when searching and when choosing a tag. We’ll get to the JavaScript once we show the two PHP pages I’m using for the application.

index.php

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
39
40
41
42
<?
require('config.php');
require('inc/classes/bookmarks.class.php');
 
$bmarks = new bookmarks($conn);
$tags = $bmarks->getTags();
?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
 
<html>
<head>
<title>Delicious Backup</title>
<link rel="stylesheet" type="text/css" href="css/style.css">
<script language="JavaScript" src="js/prototype.js"></script>
<script language="JavaScript" src="js/bookmarks.js"></script>
</head>
 
<body>
<table cellspacing="0" cellpadding="0" width="100%" id="outerTable">
	<tr>
		<td colspan="2" align="center" id="search">
			Search <input type="text" id="searchbox">
		</td>
	</tr>
	<tr>
		<td id="bookmarks">
			<? require('bookmarks.php');?>
		</td>
		<td id="tags" align="right">
			<h3>Tags</h3>
			<ul>
			<?
			foreach($tags as $tag) {
				?><li><a href="#" onClick="setTag(<?=$tag['id'];?>);"><?=$tag['tag'];?></a></li><?
			}
			?>
			</ul>
		</td>
	</tr>
</table>
</body>
</html>

bookmarks.php

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
<?
require_once('config.php');
require_once('inc/classes/bookmarks.class.php');
 
if(!isset($bmarks)) {
	$bmarks = new bookmarks($conn);
}
 
if(isset($_REQUEST['tag'])) {
	$bookmarks = $bmarks->getBookmarksByTag($_REQUEST['tag']);
} elseif($_REQUEST['criteria']) {
	$bookmarks = $bmarks->searchBookmarks($_REQUEST['criteria']);
} else {
	$bookmarks = $bmarks->getAllBookmarks();
}
?>
<h3>Bookmarks (<?=$bmarks->numRows;?> matching)</h3>
<?
foreach($bookmarks as $bookmark) {
	?>
	<div class="bookmark">
		<a href="<?=$bookmark['href'];?>"><?=$bookmark['description'];?></a> posted <?=Date('m/d/y', $bookmark['posted']);?><br>
		<?=$bookmark['tags'];?>
	</div>
	<?
}
?>

The index.php requires a config file that creates a database connection object, requires the bookmarks.class.php file we just created, instantiates the bookmarks class, and then gets a list of the tags (lines 2-6). From there, it’s mostly simple HTML work, requiring the bookmarks.php file (line 27), and finally looping through and displaying the tags – each with a link, when clicked, calls a javascript function ‘setTag’ that we’ll get to later.

The bookmarks.php file requires the same files as the index.php page, with the slight difference of using require_once instead of require. This page is going to be called within index.php on the first load, but then all other actions are going to call this page directly, not in the context on index.php. For the same reason, lines 5-7 conditionally instantiate a bookmarks object if it doesn’t not yet exist. From here on, the rest of the page is very simple – it determines whether it should be showing all of the bookmarks, bookmarks for a particular tag, or the search results. Each case uses one of the functions we defined in the bookmarks class. Lastly, it loops through the resultSet returned by the function and prints out each bookmark.

Bring on the AJAX

So we’ve built the basic structure of our pages and the backend, but I’ve put off talking about the JavaScript that’s needed to run this. There’s been a revolution in the world of web design – and JavaScript is coming out as a big winner. There are a number of incredibly useful JavaScript libraries that have been lowering the bar for creating compelling, dynamic websites. The one that’s probably used the most is the Prototype library. This library makes simple AJAX work really simple. Let’s walk through JavaScript, first with a refresher on the index.php page:

index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
 
<html>
<head>
<title>Delicious Backup</title>
<link rel="stylesheet" type="text/css" href="css/style.css">
<script language="JavaScript" src="js/prototype.js"></script>
<script language="JavaScript" src="js/bookmarks.js"></script>
</head>
 
<body>
<table cellspacing="0" cellpadding="0" width="100%" id="outerTable">
	<tr>
		<td colspan="2" align="center" id="search">
			Search <input type="text" id="searchbox">
		</td>
	</tr>
	<tr>
		<td id="bookmarks">
			<? require('bookmarks.php');?>
		</td>

You can see on line 7 and 8 we’re linking to two external JavaScript files – the first is the prototype library, and the second is the logic used for this application. Now for the contents of bookmarks.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Event.observe(window, 'load', init, false);
 
function init() {
	Field.focus('searchbox');
	Event.observe('searchbox', 'keyup', doSearch, false);
}
 
function setTag(tagid) {
	var url = 'bookmarks.php';
	var pars = 'tag='+tagid;
	var target = 'bookmarks';
	var myAjax = new Ajax.Updater(target, url, {method: 'get', parameters: pars});
}
 
function doSearch() {
	var url = 'bookmarks.php';
	var pars = 'criteria='+$F('searchbox');
	var target = 'bookmarks';
	var myAjax = new Ajax.Updater(target, url, {method: 'get', parameters: pars});
}

Line 1 starts off by using Prototype’s Event object to add an observer that will call the ‘init’ function ‘onLoad’ of the window. The init function (lines 1-3), sets the field focus on the text box ‘searchbox’, that is defined on index.php. It also sets an observer on this text box that will call the ‘doSearch’ function whenever a character is entered (or cleared) from the search box element.

Skipping down to the doSearch function (lines 15-20), this just sets a couple variables – the page we want to request (bokmarks.php), the parameters that we want to send (this uses the prototype $F function to get the current value from the searchbox element), and the target element we want to update – in this case, a table cell with an ID of ‘bookmarks’. Lastly, we use the Ajax.Updater provided by the Prototype library to send off a request to the page, get the resulting HTML, and replace the contents of the bookmarks table cell with the new contents – not too hard, huh? The setTag function is almost identical except it takes a tagid as an input and sends that off to the bookmarks page.

Now when we open up the final results, we have a dynamic application that reloads boomarks when you click on a tag, and performs a live search on the bookmarks. I’d didn’t spend any time to spruce up the pages, but that’s for another day. I hope you enjoyed this walkthrough, and learned something you didn’t know yet. You can download all of the project files below:

Download Delicious Backup Project Files (135 KB)